#!/usr/bin/env python3
"""
SURYA - Epic Manim Animation
Das's debut album visualized in stunning 3D mathematics

NOTE: For LaTeX equations, install MacTeX: brew install --cask mactex
      This version uses Text fallbacks for systems without LaTeX.
"""

from manim import *
import numpy as np
import random

# Try to use LaTeX, fall back to Text
try:
    test = MathTex(r"\phi")
    USE_LATEX = True
except:
    USE_LATEX = False

# ============================================================================
# COLOR PALETTE (matching the website)
# ============================================================================
COLORS = {
    "intro": "#1a0a2e",      # Deep purple
    "skin": "#f472b6",       # Pink
    "u_saved_me": "#22d3ee", # Cyan
    "nothing": "#666666",    # Grey
    "sweet_relief": "#a78bfa", # Purple
    "natures_call": "#5eead4", # Teal
    "dreamcatcher": "#c4b5fd", # Soft purple
    "idk": "#f472b6",        # Dark pink
    "with_u": "#fbbf24",     # GOLD - THE TURN!
    "poor_you": "#fb923c",   # Orange
    "wait_4_u": "#818cf8",   # Indigo
    "run_to_u": "#22d3ee",   # Cyan
    "medications": "#ef4444", # Red
    "hollow": "#fbbf24",     # Gold
}

DURATION = 25  # seconds per track

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

def create_title(text, color, corner=UL):
    """Create corner-positioned title that won't cover graphics."""
    title = Text(text, font_size=28, color=color)
    title.to_corner(corner, buff=0.4)
    return title

def create_subtitle(text, color):
    """Create bottom-edge subtitle for lyrics/quotes."""
    sub = Text(text, font_size=18, color=color, opacity=0.7)
    sub.to_edge(DOWN, buff=0.2)
    return sub

def create_equation(latex_str, text_str, font_size=24, color=WHITE):
    """Create equation - uses LaTeX if available, Text fallback otherwise."""
    try:
        return MathTex(latex_str, font_size=font_size, color=color)
    except:
        return Text(text_str, font_size=font_size-4, color=color)

def smooth_text_animation(scene, text, hold_time=2):
    """Fade in text, hold, fade out - smooth and subtle."""
    scene.play(FadeIn(text, shift=UP*0.1), run_time=0.5)
    scene.wait(hold_time)
    scene.play(FadeOut(text, shift=UP*0.1), run_time=0.5)


# ============================================================================
# TRACK 1: SKIN (INTRO) - 3D Morphing Parametric Surface
# ============================================================================
class Track01Skin(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = COLORS["intro"]
        
        # Title in corner
        title = create_title("01 · SKIN (INTRO)", COLORS["skin"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"S(u,v) = (\sin u \cos v, \sin u \sin v, \cos u + \sin 3v)",
            "S(u,v) = parametric surface",
            font_size=20, color=COLORS["skin"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=1)
        self.wait(1)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create morphing skin-like surface
        def skin_surface(u, v, t=0):
            x = np.sin(u) * np.cos(v) * (1 + 0.2*np.sin(3*u + t))
            y = np.sin(u) * np.sin(v) * (1 + 0.2*np.cos(3*v + t))
            z = np.cos(u) + 0.3*np.sin(3*v + 2*u + t)
            return np.array([x, y, z])
        
        surface = Surface(
            lambda u, v: skin_surface(u, v, 0),
            u_range=[0, PI],
            v_range=[0, TAU],
            resolution=(40, 40),
            fill_opacity=0.8,
        )
        surface.set_color_by_gradient(COLORS["skin"], COLORS["intro"], COLORS["skin"])
        
        self.begin_ambient_camera_rotation(rate=0.15)
        self.play(Create(surface), run_time=3)
        
        # Morph the surface over time
        for t in np.linspace(0, 4*PI, 40):
            new_surface = Surface(
                lambda u, v, t=t: skin_surface(u, v, t),
                u_range=[0, PI],
                v_range=[0, TAU],
                resolution=(40, 40),
                fill_opacity=0.8,
            )
            new_surface.set_color_by_gradient(COLORS["skin"], COLORS["intro"], COLORS["skin"])
            self.play(Transform(surface, new_surface), run_time=0.5)
        
        self.play(FadeOut(title), FadeOut(surface), run_time=1)


# ============================================================================
# TRACK 2: U SAVED ME - Particles Coalescing into Sacred Geometry
# ============================================================================
class Track02USavedMe(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-30*DEGREES)
        self.camera.background_color = "#0a1628"
        
        title = create_title("02 · U SAVED ME", COLORS["u_saved_me"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\text{Icosahedron: } \phi = \frac{1+\sqrt{5}}{2}",
            "φ = (1 + √5) / 2 ≈ 1.618",
            font_size=20, color=COLORS["u_saved_me"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create scattered particles
        particles = VGroup()
        target_positions = []
        
        # Generate sacred geometry target points (icosahedron vertices + midpoints)
        phi = (1 + np.sqrt(5)) / 2  # Golden ratio
        icosa_verts = [
            [0, 1, phi], [0, -1, phi], [0, 1, -phi], [0, -1, -phi],
            [1, phi, 0], [-1, phi, 0], [1, -phi, 0], [-1, -phi, 0],
            [phi, 0, 1], [-phi, 0, 1], [phi, 0, -1], [-phi, 0, -1]
        ]
        
        for v in icosa_verts:
            norm = np.linalg.norm(v)
            target_positions.append(np.array(v) / norm * 2.5)
        
        # Add midpoints for denser geometry
        for i, v1 in enumerate(icosa_verts):
            for v2 in icosa_verts[i+1:]:
                mid = (np.array(v1) + np.array(v2)) / 2
                if np.linalg.norm(mid) > 0.5:
                    norm = np.linalg.norm(mid)
                    target_positions.append(mid / norm * 2.5)
        
        # Create particles at random positions
        for i, target in enumerate(target_positions[:60]):
            start_pos = np.array([
                random.uniform(-6, 6),
                random.uniform(-6, 6),
                random.uniform(-6, 6)
            ])
            particle = Dot3D(point=start_pos, radius=0.08, color=COLORS["u_saved_me"])
            particles.add(particle)
        
        self.play(FadeIn(particles), run_time=1)
        self.begin_ambient_camera_rotation(rate=0.12)
        
        # Coalesce particles to sacred geometry positions
        animations = []
        for i, (particle, target) in enumerate(zip(particles, target_positions[:60])):
            animations.append(particle.animate.move_to(target))
        
        self.play(*animations, run_time=4, rate_func=smooth)
        
        # Draw connecting lines (edges of sacred geometry)
        lines = VGroup()
        for i, p1 in enumerate(particles):
            for p2 in particles[i+1:]:
                dist = np.linalg.norm(p1.get_center() - p2.get_center())
                if dist < 2.2:
                    line = Line3D(p1.get_center(), p2.get_center(), 
                                 color=COLORS["u_saved_me"], stroke_width=1)
                    line.set_opacity(0.5)
                    lines.add(line)
        
        self.play(Create(lines), run_time=3)
        self.wait(10)
        
        # Pulse effect
        self.play(
            lines.animate.set_opacity(1),
            particles.animate.set_color(WHITE),
            run_time=1
        )
        self.play(
            lines.animate.set_opacity(0.5),
            particles.animate.set_color(COLORS["u_saved_me"]),
            run_time=1
        )
        
        self.play(FadeOut(title), FadeOut(particles), FadeOut(lines), run_time=1)


# ============================================================================
# TRACK 3: NOTHING - 3D Voronoi-like Explosion into Void
# ============================================================================
class Track03Nothing(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = "#0a0a0a"
        
        title = create_title("03 · NOTHING", COLORS["nothing"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\emptyset = \{x : x \neq x\}",
            "∅ = { } — the empty set",
            font_size=22, color=COLORS["nothing"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create a solid sphere that will shatter
        sphere = Sphere(radius=2, resolution=(30, 30))
        sphere.set_color(COLORS["nothing"])
        sphere.set_opacity(0.8)
        
        self.play(Create(sphere), run_time=2)
        self.wait(1)
        
        # Create fragments
        fragments = VGroup()
        for _ in range(60):
            center = np.array([
                random.uniform(-1.8, 1.8),
                random.uniform(-1.8, 1.8),
                random.uniform(-1.8, 1.8)
            ])
            if np.linalg.norm(center) < 2:
                size = random.uniform(0.15, 0.4)
                # Create simple 3D dots as fragments
                fragment = Dot3D(center, radius=size, color=COLORS["nothing"])
                fragment.set_opacity(random.uniform(0.5, 0.9))
                fragments.add(fragment)
        
        # Explosion effect
        self.play(FadeOut(sphere), FadeIn(fragments), run_time=0.5)
        
        self.begin_ambient_camera_rotation(rate=0.1)
        
        # Fragments drift outward into void
        drift_animations = []
        for fragment in fragments:
            direction = fragment.get_center()
            if np.linalg.norm(direction) > 0.1:
                direction = direction / np.linalg.norm(direction)
            else:
                direction = np.array([random.uniform(-1,1), random.uniform(-1,1), random.uniform(-1,1)])
            target = fragment.get_center() + direction * random.uniform(4, 8)
            drift_animations.append(fragment.animate.move_to(target).set_opacity(0.1))
        
        self.play(*drift_animations, run_time=15, rate_func=linear)
        
        self.play(FadeOut(title), FadeOut(fragments), run_time=1)


# ============================================================================
# TRACK 4: SWEET RELIEF - Ghost Spirals in 3D
# ============================================================================
class Track04SweetRelief(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-60*DEGREES)
        self.camera.background_color = "#1a0a2e"
        
        title = create_title("04 · SWEET RELIEF", COLORS["sweet_relief"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\gamma(t) = (r\cos t, r\sin t, ct)",
            "helix: (r·cos(t), r·sin(t), c·t)",
            font_size=22, color=COLORS["sweet_relief"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create multiple ghost spirals
        spirals = VGroup()
        
        for i in range(5):
            phase = i * TAU / 5
            radius = 1.5 + i * 0.3
            opacity = 0.8 - i * 0.12
            
            spiral = ParametricFunction(
                lambda t, r=radius, p=phase: np.array([
                    r * np.cos(t + p),
                    r * np.sin(t + p),
                    t * 0.4
                ]),
                t_range=[-4*PI, 4*PI],
                color=COLORS["sweet_relief"],
                stroke_width=3 - i*0.4,
                stroke_opacity=opacity
            )
            spirals.add(spiral)
        
        self.begin_ambient_camera_rotation(rate=0.2)
        
        for spiral in spirals:
            self.play(Create(spiral), run_time=1.5)
        
        # Add ethereal particles floating around spirals
        particles = VGroup()
        for _ in range(100):
            t = random.uniform(-4*PI, 4*PI)
            r = random.uniform(0.5, 3)
            theta = random.uniform(0, TAU)
            pos = np.array([r*np.cos(theta), r*np.sin(theta), t*0.4])
            particle = Dot3D(pos, radius=0.03, color=COLORS["sweet_relief"])
            particle.set_opacity(random.uniform(0.3, 0.7))
            particles.add(particle)
        
        self.play(FadeIn(particles), run_time=2)
        
        # Gentle floating animation
        self.wait(12)
        
        self.play(FadeOut(title), FadeOut(spirals), FadeOut(particles), run_time=1)


# ============================================================================
# TRACK 5: TIPTOE - 3D Lissajous Curves
# ============================================================================
class Track05Tiptoe(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = "#0f172a"
        
        title = create_title("05 · TIPTOE", "#60a5fa")
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"(x,y,z) = (A\sin at, B\sin bt, C\sin ct)",
            "Lissajous: (A·sin(at), B·sin(bt), C·sin(ct))",
            font_size=20, color="#60a5fa"
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create 3D Lissajous curves with different frequency ratios
        curves = VGroup()
        
        ratios = [(3, 2, 5), (5, 4, 3), (7, 6, 5), (4, 3, 7)]
        colors = ["#60a5fa", "#818cf8", "#a78bfa", "#c4b5fd"]
        
        for (a, b, c), color in zip(ratios, colors):
            curve = ParametricFunction(
                lambda t, a=a, b=b, c=c: np.array([
                    2.5 * np.sin(a * t + PI/4),
                    2.5 * np.sin(b * t),
                    2.5 * np.sin(c * t)
                ]),
                t_range=[0, TAU],
                color=color,
                stroke_width=2.5
            )
            curves.add(curve)
        
        self.begin_ambient_camera_rotation(rate=0.15)
        
        for curve in curves:
            self.play(Create(curve), run_time=3)
        
        # Gentle pulsing
        self.play(curves.animate.set_stroke(width=4), run_time=1)
        self.play(curves.animate.set_stroke(width=2.5), run_time=1)
        
        self.wait(10)
        
        self.play(FadeOut(title), FadeOut(curves), run_time=1)


# ============================================================================
# TRACK 6: NATURE'S CALL - 3D Fractal Tree
# ============================================================================
class Track06NaturesCall(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.8)
        self.camera.background_color = "#052e16"
        
        title = create_title("06 · NATURE'S CALL", COLORS["natures_call"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"F \to F[+F]F[-F]F",
            "L-System: F → F[+F]F[-F]F",
            font_size=20, color=COLORS["natures_call"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Build 3D fractal tree
        branches = VGroup()
        leaves = VGroup()
        
        def add_branch(start, direction, length, depth, branches_group, leaves_group):
            if depth == 0 or length < 0.1:
                # Add leaf
                leaf = Dot3D(start, radius=0.08, color="#22c55e")
                leaf.set_opacity(0.8)
                leaves_group.add(leaf)
                return
            
            end = start + direction * length
            branch = Line3D(start, end, color=COLORS["natures_call"], stroke_width=max(1, depth*0.8))
            branches_group.add(branch)
            
            # Create child branches
            for angle in [PI/5, -PI/5, PI/6, -PI/6]:
                if random.random() > 0.4:
                    # Rotate direction
                    rot_matrix = np.array([
                        [np.cos(angle), -np.sin(angle), 0],
                        [np.sin(angle), np.cos(angle), 0],
                        [0, 0, 1]
                    ])
                    new_dir = rot_matrix @ direction
                    new_dir[2] += 0.3  # Tendency to grow up
                    new_dir = new_dir / np.linalg.norm(new_dir)
                    add_branch(end, new_dir, length * 0.7, depth - 1, branches_group, leaves_group)
        
        # Start tree from bottom
        start = np.array([0, 0, -3])
        direction = np.array([0, 0, 1])
        add_branch(start, direction, 1.5, 5, branches, leaves)
        
        self.begin_ambient_camera_rotation(rate=0.1)
        
        # Grow branches progressively
        batch_size = max(1, len(branches)//10)
        for i in range(0, len(branches), batch_size):
            batch = branches[i:i+batch_size]
            self.play(Create(batch), run_time=0.8)
        
        # Bloom leaves
        self.play(FadeIn(leaves, scale=0.5), run_time=3)
        
        self.wait(8)
        
        self.play(FadeOut(title), FadeOut(branches), FadeOut(leaves), run_time=1)


# ============================================================================
# TRACK 7: DREAMCATCHER (INTERLUDE) - Sacred Geometry Web
# ============================================================================
class Track07Dreamcatcher(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=80*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = "#1e1b4b"
        
        title = create_title("07 · DREAMCATCHER", COLORS["dreamcatcher"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\phi = \frac{1 + \sqrt{5}}{2}",
            "φ = (1 + √5) / 2 ≈ 1.618",
            font_size=24, color=COLORS["dreamcatcher"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create dreamcatcher structure - concentric rings with web
        rings = VGroup()
        web_lines = VGroup()
        
        # Outer rings
        for i, r in enumerate([3, 2.5, 2, 1.5, 1]):
            ring = Circle(radius=r, color=COLORS["dreamcatcher"], stroke_width=2-i*0.3)
            ring.rotate(PI/2, axis=RIGHT)  # Make it face camera
            rings.add(ring)
        
        # Web pattern (radial lines)
        for angle in np.linspace(0, TAU, 12, endpoint=False):
            line = Line3D(
                np.array([0, 0, 0]),
                np.array([3*np.cos(angle), 0, 3*np.sin(angle)]),
                color=COLORS["dreamcatcher"],
                stroke_width=1
            )
            web_lines.add(line)
        
        # Spiral web
        spiral = ParametricFunction(
            lambda t: np.array([
                (0.3 + t*0.4) * np.cos(t*3),
                0,
                (0.3 + t*0.4) * np.sin(t*3)
            ]),
            t_range=[0, 7],
            color=COLORS["dreamcatcher"],
            stroke_width=1.5
        )
        
        self.begin_ambient_camera_rotation(rate=0.25)
        
        self.play(Create(rings), run_time=3)
        self.play(Create(web_lines), run_time=2)
        self.play(Create(spiral), run_time=3)
        
        # Add glowing center
        center_glow = Dot3D(ORIGIN, radius=0.15, color=WHITE)
        self.play(FadeIn(center_glow, scale=0.5), run_time=1)
        
        # Pulse
        self.play(center_glow.animate.scale(1.5).set_color(COLORS["dreamcatcher"]), run_time=1)
        self.play(center_glow.animate.scale(1/1.5).set_color(WHITE), run_time=1)
        
        self.wait(10)
        
        self.play(FadeOut(title), FadeOut(rings), FadeOut(web_lines), 
                 FadeOut(spiral), FadeOut(center_glow), run_time=1)


# ============================================================================
# TRACK 8: IDK - 3D Lorenz Attractor
# ============================================================================
class Track08IDK(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.6)
        self.camera.background_color = "#1f0a2e"
        
        title = create_title("08 · IDK", COLORS["idk"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\dot{x} = \sigma(y-x), \dot{y} = x(\rho-z)-y",
            "Lorenz: dx/dt = σ(y-x), dy/dt = x(ρ-z)-y",
            font_size=16, color=COLORS["idk"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(2)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Generate Lorenz attractor points
        sigma, rho, beta = 10, 28, 8/3
        dt = 0.005
        
        def lorenz_step(x, y, z):
            dx = sigma * (y - x) * dt
            dy = (x * (rho - z) - y) * dt
            dz = (x * y - beta * z) * dt
            return x + dx, y + dy, z + dz
        
        # Generate trajectory
        x, y, z = 0.1, 0, 0
        points = []
        for _ in range(8000):
            x, y, z = lorenz_step(x, y, z)
            points.append([x * 0.12, y * 0.12, (z - 25) * 0.12])
        
        # Create curve from points
        attractor = VMobject()
        attractor.set_points_smoothly([np.array(p) for p in points[::4]])
        attractor.set_stroke(color=COLORS["idk"], width=1.5, opacity=0.9)
        
        self.begin_ambient_camera_rotation(rate=0.15)
        
        self.play(Create(attractor), run_time=12, rate_func=linear)
        
        # Add trailing particle
        particle = Dot3D(np.array(points[-1]), radius=0.1, color=WHITE)
        self.play(FadeIn(particle), run_time=0.5)
        
        # Animate particle along last part of trajectory
        for i in range(-100, -1, 5):
            self.play(particle.animate.move_to(np.array(points[i])), run_time=0.1)
        
        self.wait(5)
        
        self.play(FadeOut(title), FadeOut(attractor), FadeOut(particle), run_time=1)


# ============================================================================
# TRACK 9: WITH U - THE TURN! Two Souls Orbiting + Stars
# ============================================================================
class Track09WithU(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=70*DEGREES, theta=-60*DEGREES)
        self.camera.background_color = "#0c0a09"
        
        title = create_title("09 · WITH U", COLORS["with_u"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"F = G\frac{m_1 m_2}{r^2}",
            "F = G · m₁m₂ / r²",
            font_size=26, color=COLORS["with_u"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create two souls (glowing spheres)
        soul1 = Sphere(radius=0.3, resolution=(20, 20))
        soul1.set_color(COLORS["with_u"])
        soul1.set_opacity(0.9)
        
        soul2 = Sphere(radius=0.25, resolution=(20, 20))
        soul2.set_color("#fcd34d")  # Slightly different gold
        soul2.set_opacity(0.9)
        
        # Position at opposite sides
        soul1.move_to([2, 0, 0])
        soul2.move_to([-2, 0, 0])
        
        self.play(FadeIn(soul1, scale=0.5), FadeIn(soul2, scale=0.5), run_time=2)
        
        # Orbital trails
        trail1 = VMobject(stroke_color=COLORS["with_u"], stroke_width=2, stroke_opacity=0.6)
        trail2 = VMobject(stroke_color="#fcd34d", stroke_width=2, stroke_opacity=0.6)
        
        self.add(trail1, trail2)
        
        self.begin_ambient_camera_rotation(rate=0.1)
        
        # Animate orbital dance
        orbit_time = 10
        steps = 150
        for i in range(steps):
            t = i / steps * orbit_time
            angle = t * 1.5
            
            # Elliptical orbits with slight vertical motion
            r1 = 2 + 0.3*np.sin(angle*2)
            r2 = 2 + 0.3*np.sin(angle*2 + PI)
            
            pos1 = np.array([r1*np.cos(angle), r1*np.sin(angle), 0.5*np.sin(angle*3)])
            pos2 = np.array([r2*np.cos(angle + PI), r2*np.sin(angle + PI), 0.5*np.sin(angle*3 + PI)])
            
            self.play(
                soul1.animate.move_to(pos1),
                soul2.animate.move_to(pos2),
                run_time=orbit_time/steps,
                rate_func=linear
            )
            
            # Update trails
            if len(trail1.points) > 0:
                trail1.add_line_to(pos1)
                trail2.add_line_to(pos2)
            else:
                trail1.start_new_path(pos1)
                trail2.start_new_path(pos2)
        
        # EPIC MOMENT - Stars appear!
        stars = VGroup()
        for _ in range(200):
            star_pos = np.array([
                random.uniform(-8, 8),
                random.uniform(-8, 8),
                random.uniform(-5, 5)
            ])
            star = Dot3D(star_pos, radius=0.03, color=WHITE)
            star.set_opacity(random.uniform(0.3, 1.0))
            stars.add(star)
        
        self.play(FadeIn(stars, lag_ratio=0.02), run_time=3)
        
        # Souls come together
        self.play(
            soul1.animate.move_to([0.3, 0, 0]),
            soul2.animate.move_to([-0.3, 0, 0]),
            run_time=2
        )
        
        # Glow brighter together
        self.play(
            soul1.animate.scale(1.3).set_color(WHITE),
            soul2.animate.scale(1.3).set_color(WHITE),
            stars.animate.set_opacity(0.5),
            run_time=1.5
        )
        
        self.wait(2)
        
        self.play(FadeOut(title), FadeOut(soul1), FadeOut(soul2), 
                 FadeOut(trail1), FadeOut(trail2), FadeOut(stars), run_time=1.5)


# ============================================================================
# TRACK 10: POOR YOU POOR ME - Drifting Objects in Orange Glow
# ============================================================================
class Track10PoorYou(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-50*DEGREES)
        self.camera.background_color = "#1c1917"
        
        title = create_title("10 · POOR YOU POOR ME", COLORS["poor_you"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\vec{v}(t) = \vec{v}_0 e^{-\lambda t}",
            "v(t) = v₀ · e^(-λt) → 0",
            font_size=22, color=COLORS["poor_you"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create drifting geometric objects
        objects = VGroup()
        
        # Various shapes drifting apart
        for i in range(8):
            angle = i * TAU / 8
            pos = np.array([1.5*np.cos(angle), 1.5*np.sin(angle), random.uniform(-1, 1)])
            
            if i % 4 == 0:
                shape = Sphere(radius=0.3, resolution=(10, 10))
            elif i % 4 == 1:
                shape = Dot3D(pos, radius=0.25)
            elif i % 4 == 2:
                shape = Sphere(radius=0.35, resolution=(8, 8))
            else:
                shape = Dot3D(pos, radius=0.2)
            
            shape.set_color(COLORS["poor_you"])
            shape.set_opacity(0.8)
            shape.move_to(pos)
            objects.add(shape)
        
        # Add some smaller fragments
        for _ in range(25):
            frag = Dot3D(
                point=np.array([random.uniform(-3, 3), random.uniform(-3, 3), random.uniform(-2, 2)]),
                radius=random.uniform(0.05, 0.15),
                color=COLORS["poor_you"]
            )
            frag.set_opacity(random.uniform(0.4, 0.8))
            objects.add(frag)
        
        self.play(FadeIn(objects), run_time=2)
        
        self.begin_ambient_camera_rotation(rate=0.08)
        
        # Bittersweet drifting apart
        drift_animations = []
        for obj in objects:
            direction = obj.get_center()
            if np.linalg.norm(direction) > 0.1:
                direction = direction / np.linalg.norm(direction)
            else:
                direction = np.array([random.uniform(-1,1), random.uniform(-1,1), random.uniform(-1,1)])
            
            target = obj.get_center() + direction * random.uniform(1, 3)
            drift_animations.append(
                obj.animate.move_to(target).set_opacity(obj.get_fill_opacity() * 0.5)
            )
        
        self.play(*drift_animations, run_time=15, rate_func=smooth)
        
        self.wait(3)
        
        self.play(FadeOut(title), FadeOut(objects), run_time=1)


# ============================================================================
# TRACK 11: WAIT 4 U - Concentric Pulsing Spheres
# ============================================================================
class Track11Wait4U(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = "#0f172a"
        
        title = create_title("11 · WAIT 4 U", COLORS["wait_4_u"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"r(t) = r_0 + A\sin(\omega t)",
            "r(t) = r₀ + A·sin(ωt)",
            font_size=22, color=COLORS["wait_4_u"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create concentric spheres
        spheres = VGroup()
        base_radii = [0.5, 1, 1.5, 2, 2.5, 3]
        
        for i, r in enumerate(base_radii):
            sphere = Sphere(radius=r, resolution=(20, 20))
            sphere.set_color(COLORS["wait_4_u"])
            sphere.set_opacity(0.2 - i*0.025)
            sphere.set_stroke(color=COLORS["wait_4_u"], width=1, opacity=0.5)
            spheres.add(sphere)
        
        self.play(Create(spheres), run_time=3)
        
        self.begin_ambient_camera_rotation(rate=0.12)
        
        # Pulsing animation - waves emanating outward
        for cycle in range(8):
            pulse_animations = []
            for i, (sphere, base_r) in enumerate(zip(spheres, base_radii)):
                phase = i * 0.2
                new_r = base_r * (1 + 0.15 * np.sin(cycle * PI/2 + phase))
                new_sphere = Sphere(radius=new_r, resolution=(20, 20))
                new_sphere.set_color(COLORS["wait_4_u"])
                new_sphere.set_opacity(0.2 - i*0.025)
                pulse_animations.append(Transform(sphere, new_sphere))
            
            self.play(*pulse_animations, run_time=1.5)
        
        self.wait(5)
        
        self.play(FadeOut(title), FadeOut(spheres), run_time=1)


# ============================================================================
# TRACK 12: RUN TO U - Speed Lines + 3D Collision
# ============================================================================
class Track12RunToU(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
        self.camera.background_color = "#082f49"
        
        title = create_title("12 · RUN TO U", COLORS["run_to_u"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"p_1 + p_2 = p_{final}",
            "p₁ + p₂ = p_final (momentum)",
            font_size=22, color=COLORS["run_to_u"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Create two objects rushing toward each other
        obj1 = Sphere(radius=0.4, resolution=(15, 15))
        obj1.set_color(COLORS["run_to_u"])
        obj1.move_to([-5, 0, 0])
        
        obj2 = Sphere(radius=0.35, resolution=(15, 15))
        obj2.set_color("#67e8f9")
        obj2.move_to([5, 0, 0])
        
        # Speed lines
        speed_lines = VGroup()
        for _ in range(30):
            y = random.uniform(-3, 3)
            z = random.uniform(-2, 2)
            line = Line3D([-6, y, z], [6, y, z], color=COLORS["run_to_u"], stroke_width=1)
            line.set_opacity(random.uniform(0.2, 0.6))
            speed_lines.add(line)
        
        self.play(FadeIn(speed_lines), run_time=1)
        self.play(FadeIn(obj1), FadeIn(obj2), run_time=1)
        
        self.begin_ambient_camera_rotation(rate=0.15)
        
        # Rush toward each other with increasing speed
        self.play(
            obj1.animate.move_to([-2, 0, 0]),
            obj2.animate.move_to([2, 0, 0]),
            run_time=3,
            rate_func=rate_functions.ease_in_quad
        )
        
        self.play(
            obj1.animate.move_to([0, 0, 0]),
            obj2.animate.move_to([0, 0, 0]),
            run_time=1.5,
            rate_func=rate_functions.ease_in_expo
        )
        
        # Collision burst!
        burst_particles = []
        for _ in range(50):
            direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)])
            direction = direction / np.linalg.norm(direction)
            particle = Dot3D(ORIGIN, radius=0.1, color=WHITE)
            burst_particles.append((particle, direction))
        
        burst_group = VGroup(*[p for p, d in burst_particles])
        
        self.play(
            FadeOut(obj1), FadeOut(obj2),
            FadeIn(burst_group, scale=0.5),
            run_time=0.3
        )
        
        # Expand burst
        burst_anims = []
        for particle, direction in burst_particles:
            target = direction * random.uniform(2, 5)
            burst_anims.append(particle.animate.move_to(target).set_opacity(0))
        
        self.play(*burst_anims, run_time=3)
        
        # New combined entity emerges
        combined = Sphere(radius=0.6, resolution=(25, 25))
        combined.set_color_by_gradient(COLORS["run_to_u"], "#67e8f9")
        
        self.play(FadeIn(combined, scale=0.3), run_time=2)
        
        # Gentle rotation
        self.wait(8)
        
        self.play(FadeOut(title), FadeOut(combined), FadeOut(speed_lines), FadeOut(burst_group), run_time=1)


# ============================================================================
# TRACK 13: MEDICATIONS - Chaos Attractor with Red Pulsing
# ============================================================================
class Track13Medications(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.7)
        self.camera.background_color = "#1c0a0a"
        
        title = create_title("13 · MEDICATIONS", COLORS["medications"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"\frac{dx}{dt} = a(y-x)",
            "Chen attractor: dx/dt = a(y-x)",
            font_size=18, color=COLORS["medications"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Generate Chen attractor (another chaotic system)
        a, b, c = 35, 3, 28
        dt = 0.002
        
        def chen_step(x, y, z):
            dx = a * (y - x) * dt
            dy = ((c - a) * x - x * z + c * y) * dt
            dz = (x * y - b * z) * dt
            return x + dx, y + dy, z + dz
        
        x, y, z = 0.1, 0.1, 0.1
        points = []
        for _ in range(10000):
            x, y, z = chen_step(x, y, z)
            points.append([x * 0.08, y * 0.08, z * 0.08])
        
        attractor = VMobject()
        attractor.set_points_smoothly([np.array(p) for p in points[::5]])
        attractor.set_stroke(color=COLORS["medications"], width=1.2, opacity=0.8)
        
        self.begin_ambient_camera_rotation(rate=0.18)
        
        self.play(Create(attractor), run_time=8, rate_func=linear)
        
        # Red pulsing effect
        for _ in range(6):
            self.play(
                attractor.animate.set_stroke(color="#ff6b6b", opacity=1),
                run_time=0.5
            )
            self.play(
                attractor.animate.set_stroke(color=COLORS["medications"], opacity=0.8),
                run_time=0.5
            )
        
        self.wait(5)
        
        self.play(FadeOut(title), FadeOut(attractor), run_time=1)


# ============================================================================
# TRACK 14: HOLLOW - Golden Spiral to Moon, Epic Finale
# ============================================================================
class Track14Hollow(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES, zoom=0.7)
        self.camera.background_color = "#1a0a2e"
        
        title = create_title("14 · HOLLOW", COLORS["hollow"])
        self.add_fixed_in_frame_mobjects(title)
        self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
        
        # Equation
        equation = create_equation(
            r"r = a \cdot \phi^{\theta/90°}",
            "Golden spiral: r = a · φ^(θ/90°)",
            font_size=22, color=COLORS["hollow"]
        )
        equation.to_corner(DR, buff=0.5)
        self.add_fixed_in_frame_mobjects(equation)
        self.play(FadeIn(equation), run_time=0.8)
        self.wait(1.5)
        self.play(FadeOut(equation), run_time=0.5)
        
        # Golden spiral
        phi = (1 + np.sqrt(5)) / 2
        
        spiral = ParametricFunction(
            lambda t: np.array([
                0.1 * phi**(t / (PI/2)) * np.cos(t),
                0.1 * phi**(t / (PI/2)) * np.sin(t),
                t * 0.05
            ]),
            t_range=[0, 6*PI],
            color=COLORS["hollow"],
            stroke_width=3
        )
        
        self.begin_ambient_camera_rotation(rate=0.1)
        
        self.play(Create(spiral), run_time=5)
        
        # Moon appears in distance
        moon = Sphere(radius=1.2, resolution=(30, 30))
        moon.set_color("#fef3c7")
        moon.set_opacity(0.9)
        moon.move_to([6, 4, 3])
        
        self.play(FadeIn(moon, scale=0.5), run_time=2)
        
        # Stars appear
        stars = VGroup()
        for _ in range(150):
            star_pos = np.array([
                random.uniform(-10, 10),
                random.uniform(-10, 10),
                random.uniform(-5, 8)
            ])
            star = Dot3D(star_pos, radius=0.03, color=WHITE)
            star.set_opacity(random.uniform(0.2, 0.8))
            stars.add(star)
        
        self.play(FadeIn(stars, lag_ratio=0.01), run_time=3)
        
        # Epic camera pullback
        self.stop_ambient_camera_rotation()
        
        self.move_camera(
            phi=60 * DEGREES,
            theta=-60 * DEGREES,
            frame_center=ORIGIN,
            zoom=0.5,
            run_time=8,
            rate_func=smooth
        )
        
        # Final glow
        self.play(
            moon.animate.set_color(COLORS["hollow"]),
            spiral.animate.set_stroke(color=WHITE, width=4),
            run_time=2
        )
        
        # Fade to completion
        self.wait(2)
        
        all_objects = VGroup(spiral, moon, stars)
        self.play(FadeOut(title), FadeOut(all_objects), run_time=2)


# ============================================================================
# COMBINED MEGA SCENE - All Tracks in Sequence
# ============================================================================
class SuryaEpic(ThreeDScene):
    """
    Master scene combining all tracks.
    For full render, use individual track classes and combine with ffmpeg.
    This serves as a preview/test scene.
    """
    def construct(self):
        self.camera.background_color = "#1a0a2e"
        
        # Intro title
        album_title = Text("S U R Y A", font_size=72, color=COLORS["hollow"])
        artist = Text("Das", font_size=36, color=WHITE)
        artist.next_to(album_title, DOWN, buff=0.5)
        
        self.add_fixed_in_frame_mobjects(album_title, artist)
        self.play(FadeIn(album_title, shift=UP*0.5), run_time=2)
        self.play(FadeIn(artist), run_time=1)
        self.wait(2)
        self.play(FadeOut(album_title), FadeOut(artist), run_time=1)
        
        # Note: For full animation, render each track separately and combine
        # This is just a preview intro
        
        preview = Text(
            "Render individual tracks with:\nmanim -pqh surya_epic.py Track01Skin",
            font_size=20, color=WHITE
        )
        self.add_fixed_in_frame_mobjects(preview)
        self.play(FadeIn(preview), run_time=1)
        self.wait(3)
