Quaternions
diff --git a/active_projects/quaternions.py b/active_projects/quaternions.py
index 191ee42758..484e444792 100644
--- a/active_projects/quaternions.py
+++ b/active_projects/quaternions.py
@@ -4778,7 +4778,8 @@ class HypersphereStereographicProjection(SpecialThreeDScene):
             [-1, 1, 0, 0],
             [0, 0, 1, 1],
             [0, 1, -1, 1],
-        ]
+        ],
+        "unit_labels_scale_factor": 1,
     }
 
     def construct(self):
@@ -4827,6 +4828,7 @@ def update_label(label):
             return label
 
         label.add_updater(update_label)
+        self.pink_dot_label = label
 
         def get_pq_point():
             point = self.project_quaternion(q_tracker.get_value())
@@ -4841,9 +4843,12 @@ def get_pq_point():
         def get_pq_line():
             point = get_pq_point()
             norm = get_norm(point)
+            origin = self.axes.coords_to_point(0, 0, 0)
             if norm > dot_radius:
+                point -= origin
                 point *= (norm - dot_radius) / norm
-            result = Line(ORIGIN, point)
+                point += origin
+            result = Line(origin, point)
             result.set_stroke(width=1)
             return result
 
@@ -5114,6 +5119,7 @@ def get_unit_labels(self):
         labels = VGroup()
         for tex, coords, vect in tex_coords_vects:
             label = TexMobject(tex)
+            label.scale(self.unit_labels_scale_factor)
             label.rotate(90 * DEGREES, RIGHT)
             label.next_to(c2p(*coords), vect, SMALL_BUFF)
             labels.add(label)
@@ -5333,10 +5339,12 @@ def setup_multiplier_tracker(self):
     def add_unit_labels(self):
         labels = self.unit_labels = self.get_unit_labels()
         one_label = TexMobject("1")
+        one_label.scale(self.unit_labels_scale_factor)
         one_label.set_shade_in_3d(True)
         one_label.rotate(90 * DEGREES, RIGHT)
         one_label.next_to(ORIGIN, IN + RIGHT, SMALL_BUFF)
-        self.add(labels, one_label)
+        labels.add(one_label)
+        self.add(labels)
 
     def show_multiplication_by_i_on_circle_1i(self):
         m_tracker = self.multiplier_tracker
@@ -6420,27 +6428,95 @@ class QuaternionEndscreen(PatreonEndScreen):
     }
 
 
-class Thumbnail(RuleOfQuaternionMultiplication):
+class ThumbnailP1(RuleOfQuaternionMultiplication):
     CONFIG = {
         "three_d_axes_config": {
             "num_axis_pieces": 20,
-        }
+        },
+        "unit_labels_scale_factor": 1.5,
+        "quaternion": [1, 0, 0, 0],
     }
 
     def construct(self):
         self.setup_all_trackers()
-        quat = normalize([-0.5, 0.5, -0.5, 0.5])
-        self.multiplier_tracker.set_value(quat)
-        self.q_tracker.set_value(quat)
-        sphere = self.get_projected_sphere(0, solid=False)
-        # self.specially_color_sphere(sphere)
-        # sphere.set_fill(opacity=0.5)
-        sphere.set_fill_by_checkerboard(BLUE_E, BLUE, opacity=0.8)
-        for face in sphere:
+        self.remove(self.pink_dot_label)
+        q_tracker = self.q_tracker
+        m_tracker = self.multiplier_tracker
+
+        # quat = normalize([-0.5, 0.5, -0.5, 0.5])
+        quat = normalize(self.quaternion)
+        m_tracker.set_value(quat)
+        q_tracker.set_value(quat)
+        proj_sphere = self.get_projected_sphere(0, solid=False)
+        # self.specially_color_sphere(proj_sphere)
+        proj_sphere.set_color_by_gradient(
+            BLUE, YELLOW
+        )
+        proj_sphere.set_stroke(WHITE)
+        proj_sphere.set_fill(opacity=0.4)
+        for i, face in enumerate(proj_sphere):
+            alpha = i / len(proj_sphere)
+            opacity = 0.7 * (1 - there_and_back(alpha))
+            face.set_fill(opacity=opacity)
+
+        # unit_sphere = self.get_projected_sphere(0, quaternion=[1, 0, 0, 0], solid=False)
+        # self.specially_color_sphere(unit_sphere)
+        # unit_sphere.set_stroke(width=0)
+        # proj_sphere.set_fill_by_checkerboard(BLUE_E, BLUE, opacity=0.8)
+        for face in proj_sphere:
             face.points = face.points[::-1]
+            max_r = np.max(np.apply_along_axis(get_norm, 1, face.points))
+            if max_r > 30:
+                face.fade(1)
+
+        for label in self.unit_labels:
+            label.set_shade_in_3d(False)
+            label.set_background_stroke(color=BLACK, width=2)
+
+        self.add(proj_sphere)
+        # self.add(unit_sphere)
+
+        for mobject in self.mobjects:
+            try:
+                mobject.shift(IN)
+            except ValueError:
+                pass
 
         self.set_camera_orientation(
             phi=70 * DEGREES,
             theta=-110 * DEGREES,
         )
-        self.add(sphere)
+
+
+class ThumbnailP2(ThumbnailP1):
+    CONFIG = {
+        "quaternion": [0, 1, 0, 0],
+    }
+
+
+class ThumbnailOverlay(Scene):
+    def construct(self):
+        title = TextMobject("Quaternions")
+        title.set_width(8)
+        title.to_edge(UP)
+        v_line = Line(DOWN, UP)
+        v_line.set_height(FRAME_HEIGHT)
+
+        title.set_background_stroke(color=BLACK, width=1)
+
+        rect = BackgroundRectangle(title[4:6])
+        rect.set_fill(opacity=1)
+        rect.stretch(0.9, 0)
+        rect.stretch(1.1, 1)
+        title.add_to_back(BackgroundRectangle(title[0]))
+        title.add_to_back(rect)
+
+        arrow = Arrow(LEFT, RIGHT)
+        arrow.scale(1.5)
+        arrow.tip.scale(2)
+        arrow.set_stroke(width=10)
+        arrow.set_color(YELLOW)
+
+        self.add(v_line)
+        self.add(arrow)
+        self.add(title)
diff --git a/scene/three_d_scene.py b/scene/three_d_scene.py
index 133f6d8969..430de9fa10 100644
--- a/scene/three_d_scene.py
+++ b/scene/three_d_scene.py
@@ -74,9 +74,11 @@ def get_moving_mobjects(self, *animations):
         return moving_mobjects
 
     def add_fixed_orientation_mobjects(self, *mobjects, **kwargs):
+        self.add(*mobjects)
         self.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs)
 
     def add_fixed_in_frame_mobjects(self, *mobjects):
+        self.add(*mobjects)
         self.camera.add_fixed_in_frame_mobjects(*mobjects)
 
     def remove_fixed_orientation_mobjects(self, *mobjects):
diff --git a/utils/space_ops.py b/utils/space_ops.py
index 07d8ea494b..821e86acc3 100644
--- a/utils/space_ops.py
+++ b/utils/space_ops.py
@@ -7,13 +7,46 @@
 from functools import reduce
 from utils.iterables import adjacent_pairs
 
-# Matrix operations
-
 
 def get_norm(vect):
     return sum([x**2 for x in vect])**0.5
 
 
+# Quaternions
+# TODO, implement quaternion type
+
+
+def quaternion_mult(q1, q2):
+    w1, x1, y1, z1 = q1
+    w2, x2, y2, z2 = q2
+    return np.array([
+        w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
+        w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
+        w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2,
+        w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2,
+    ])
+
+
+def quaternion_from_angle_axis(angle, axis):
+    return np.append(
+        np.cos(angle / 2),
+        np.sin(angle / 2) * normalize(axis)
+    )
+
+
+def quaternion_conjugate(quaternion):
+    result = np.array(quaternion)
+    result[1:] *= -1
+    return result
+
+
+def rotate_vector(vector, angle, axis=OUT):
+    quat = quaternion_from_angle_axis(angle, axis)
+    quat_inv = quaternion_conjugate(quat)
+    product = reduce(quaternion_mult, [quat, vector, quat_inv])
+    return product[1:]
+
+
 def thick_diagonal(dim, thickness=2):
     row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
     col_indices = np.transpose(row_indices)
@@ -64,10 +97,6 @@ def z_to_vector(vector):
     return np.dot(rotation_about_z(theta), phi_down)
 
 
-def rotate_vector(vector, angle, axis=OUT):
-    return np.dot(rotation_matrix(angle, axis), vector)
-
-
 def angle_between(v1, v2):
     return np.arccos(np.dot(
         v1 / get_norm(v1),
