// Home — Z-Stack of four labeled planes in real WebGL (Three.js).
// Mouse drives camera parallax. Click dollies the camera forward through the picked plane.

const { useEffect, useRef, useState } = React;

function Home({ onSelect, onNarrate }) {
  const mountRef = useRef(null);
  const stateRef = useRef(null);
  // Keep callbacks in refs so changing identities don't re-run the THREE effect
  // and tear down the scene mid-dive.
  const onSelectRef = useRef(onSelect);
  const onNarrateRef = useRef(onNarrate);
  useEffect(() => { onSelectRef.current = onSelect; }, [onSelect]);
  useEffect(() => { onNarrateRef.current = onNarrate; }, [onNarrate]);
  const [hovered, setHovered] = useState(null); // {id, title, blurb, n}
  const [focused, setFocused] = useState(0);    // which plane is brought forward
  const [diving, setDiving] = useState(null);

  useEffect(() => {
    const mount = mountRef.current;
    if (!mount) return;

    // -------- scene / camera / renderer --------
    const scene = new THREE.Scene();
    scene.background = null;

    const aspect = mount.clientWidth / mount.clientHeight;
    const camera = new THREE.PerspectiveCamera(40, aspect, 0.1, 60);
    camera.position.set(0, 0, 4.0);
    // Look at the stack center — intro lives in the left half, stack in the right half
    const lookTarget = new THREE.Vector3(2.0, -0.05, -1);
    camera.lookAt(lookTarget);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(mount.clientWidth, mount.clientHeight);
    renderer.setClearColor(0x000000, 0);
    mount.appendChild(renderer.domElement);
    renderer.domElement.style.display = "block";
    renderer.domElement.style.cursor = "default";

    // -------- atmospheric dust (depth cue) --------
    // GLSL-driven curl-noise field, 20k particles — cheap on GPU.
    const dustGeo = new THREE.BufferGeometry();
    const dustCount = 20000;
    const positions = new Float32Array(dustCount * 3);
    const sizes = new Float32Array(dustCount);
    const seeds = new Float32Array(dustCount);
    for (let i = 0; i < dustCount; i++) {
      positions[i * 3 + 0] = (Math.random() - 0.5) * 18;
      positions[i * 3 + 1] = (Math.random() - 0.5) * 11;
      positions[i * 3 + 2] = -Math.random() * 16 + 2.5;
      sizes[i] = Math.random() * 0.020 + 0.003;
      seeds[i] = Math.random();
    }
    dustGeo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
    dustGeo.setAttribute("aSize", new THREE.BufferAttribute(sizes, 1));
    dustGeo.setAttribute("aSeed", new THREE.BufferAttribute(seeds, 1));
    const dustMat = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uMouse: { value: new THREE.Vector2(0, 0) },
      },
      transparent: true,
      depthWrite: false,
      blending: THREE.AdditiveBlending,
      vertexShader: `
        attribute float aSize;
        attribute float aSeed;
        uniform float uTime;
        uniform vec2 uMouse;
        varying float vDepth;
        varying float vSeed;

        // curl-noise approximation — cheap and stable
        vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
        vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
        vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
        vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
        float snoise(vec3 v){
          const vec2 C = vec2(1.0/6.0, 1.0/3.0);
          const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
          vec3 i  = floor(v + dot(v, C.yyy));
          vec3 x0 = v - i + dot(i, C.xxx);
          vec3 g = step(x0.yzx, x0.xyz);
          vec3 l = 1.0 - g;
          vec3 i1 = min(g.xyz, l.zxy);
          vec3 i2 = max(g.xyz, l.zxy);
          vec3 x1 = x0 - i1 + C.xxx;
          vec3 x2 = x0 - i2 + C.yyy;
          vec3 x3 = x0 - D.yyy;
          i = mod289(i);
          vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0))
            + i.y + vec4(0.0, i1.y, i2.y, 1.0))
            + i.x + vec4(0.0, i1.x, i2.x, 1.0));
          float n_ = 0.142857142857;
          vec3 ns = n_ * D.wyz - D.xzx;
          vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
          vec4 x_ = floor(j * ns.z);
          vec4 y_ = floor(j - 7.0 * x_);
          vec4 x = x_ *ns.x + ns.yyyy;
          vec4 y = y_ *ns.x + ns.yyyy;
          vec4 h = 1.0 - abs(x) - abs(y);
          vec4 b0 = vec4(x.xy, y.xy);
          vec4 b1 = vec4(x.zw, y.zw);
          vec4 s0 = floor(b0)*2.0 + 1.0;
          vec4 s1 = floor(b1)*2.0 + 1.0;
          vec4 sh = -step(h, vec4(0.0));
          vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
          vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
          vec3 p0 = vec3(a0.xy, h.x);
          vec3 p1 = vec3(a0.zw, h.y);
          vec3 p2 = vec3(a1.xy, h.z);
          vec3 p3 = vec3(a1.zw, h.w);
          vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
          p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
          vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
          m = m * m;
          return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
        }

        void main() {
          vec3 p = position;
          float t = uTime * 0.08;
          // curl-ish flow: sample noise at three offsets, take derivatives
          float e = 0.4;
          float n1 = snoise(vec3(p.x*0.18, p.y*0.18, t));
          float n2 = snoise(vec3(p.y*0.18, p.z*0.18, t + 31.0));
          float n3 = snoise(vec3(p.z*0.18, p.x*0.18, t + 71.0));
          p.x += n1 * 0.35;
          p.y += n2 * 0.35;
          p.z += n3 * 0.18;

          // gentle attraction toward mouse (in world-X/Y space)
          vec2 m = uMouse * vec2(3.0, 1.8);
          vec2 toM = m - p.xy;
          float d = length(toM);
          p.xy += normalize(toM + 0.0001) * (1.0 / (1.0 + d*d*0.4)) * 0.18;

          vec4 mv = modelViewMatrix * vec4(p, 1.0);
          vDepth = -mv.z;
          vSeed = aSeed;
          gl_PointSize = aSize * 320.0 / vDepth;
          gl_Position = projectionMatrix * mv;
        }`,
      fragmentShader: `
        varying float vDepth;
        varying float vSeed;
        void main() {
          float d = length(gl_PointCoord - 0.5);
          if (d > 0.5) discard;
          float a = smoothstep(0.5, 0.0, d);
          float depthFade = clamp(1.0 - vDepth * 0.07, 0.05, 1.0);
          // warm-to-cool tint by depth, slight per-particle hue jitter
          vec3 warm = vec3(0.95, 0.82, 0.58);
          vec3 cool = vec3(0.55, 0.7, 0.95);
          vec3 col = mix(warm, cool, clamp(vDepth*0.1, 0.0, 1.0));
          col = mix(col, col.bgr, vSeed * 0.15);
          gl_FragColor = vec4(col, a * depthFade * 0.55);
        }`,
    });
    const dust = new THREE.Points(dustGeo, dustMat);
    scene.add(dust);

    // -------- volumetric raymarched backdrop (full-screen quad behind dust) --
    // Cheap 6-tap noise volume — reads as warm fog with directional light.
    const bgGeo = new THREE.PlaneGeometry(2, 2);
    const bgMat = new THREE.ShaderMaterial({
      uniforms: { uTime: { value: 0 }, uMouse: { value: new THREE.Vector2(0, 0) }, uAspect: { value: 1 } },
      depthWrite: false,
      depthTest: false,
      vertexShader: `
        varying vec2 vUv;
        void main() { vUv = uv; gl_Position = vec4(position.xy, 1.0, 1.0); }`,
      fragmentShader: `
        precision highp float;
        varying vec2 vUv;
        uniform float uTime;
        uniform vec2 uMouse;
        uniform float uAspect;

        // value-noise helpers
        float hash(vec3 p){ p = fract(p*0.3183 + 0.1); p *= 17.0; return fract(p.x*p.y*p.z*(p.x+p.y+p.z)); }
        float vnoise(vec3 p){
          vec3 i = floor(p); vec3 f = fract(p); f = f*f*(3.0-2.0*f);
          return mix(mix(mix(hash(i+vec3(0,0,0)), hash(i+vec3(1,0,0)), f.x),
                         mix(hash(i+vec3(0,1,0)), hash(i+vec3(1,1,0)), f.x), f.y),
                     mix(mix(hash(i+vec3(0,0,1)), hash(i+vec3(1,0,1)), f.x),
                         mix(hash(i+vec3(0,1,1)), hash(i+vec3(1,1,1)), f.x), f.y), f.z);
        }
        float fbm(vec3 p){
          float a = 0.5, s = 0.0;
          for (int i=0; i<5; i++) { s += a * vnoise(p); p *= 2.02; a *= 0.5; }
          return s;
        }
        void main(){
          vec2 uv = (vUv - 0.5) * vec2(uAspect, 1.0);
          vec3 p = vec3(uv * 1.8, uTime * 0.03);
          p.xy += uMouse * 0.18;
          // raymarch a thin slab — sample density at 3 depths, blend
          float d = 0.0;
          for (int i=0; i<3; i++) {
            float fi = float(i);
            d += fbm(p + vec3(0.0, 0.0, fi * 0.4));
          }
          d /= 3.0;
          // light from upper-left, attenuated by density
          vec2 lightDir = normalize(vec2(-0.4, 0.7));
          float lit = pow(max(0.0, dot(normalize(vec2(uv.x, uv.y)) + lightDir * 0.001, lightDir)), 1.6);
          // warm dark base, amber highlight
          vec3 base = mix(vec3(0.062, 0.052, 0.040), vec3(0.10, 0.083, 0.060), d);
          vec3 highlight = vec3(0.45, 0.30, 0.10) * pow(d, 3.0) * 0.4;
          vec3 col = base + highlight * lit;
          // gentle vignette
          float vig = smoothstep(1.2, 0.4, length(uv));
          col *= mix(0.55, 1.0, vig);
          gl_FragColor = vec4(col, 1.0);
        }`,
    });
    const bg = new THREE.Mesh(bgGeo, bgMat);
    bg.renderOrder = -100;
    scene.add(bg);

    // -------- four labeled planes --------
    // Stack-slot positions. Slot 0 is the front of the deck, slot 3 the back.
    // Each plane animates between slots as the user cycles.
    // Anchored to the RIGHT half of the screen.
    const SLOT_POSITIONS = [
      { x: 1.85, y:  0.05, z:  0.20, rotY: -0.14, rotX: 0.02 }, // 0 FRONT
      { x: 2.15, y: -0.20, z: -0.55, rotY: -0.17, rotX: 0.03 }, // 1
      { x: 2.45, y: -0.45, z: -1.30, rotY: -0.20, rotX: 0.04 }, // 2
      { x: 2.75, y: -0.70, z: -2.05, rotY: -0.23, rotX: 0.05 }, // 3 BACK
    ];
    const planes = WORLDS.map((w, i) => makePlane(w, i, SLOT_POSITIONS));
    planes.forEach((p) => scene.add(p.group));

    // Clock is needed early because setStack stamps the start time of each transition.
    const clock = new THREE.Clock();

    // stackOrder[rank] = plane index. Plane at rank 0 is the front of the deck.
    let stackOrder = [0, 1, 2, 3];
    // active per-plane transitions (Bezier arcs)
    const transitions = {};

    function setStack(newOrder, leadIdx) {
      const t0 = clock.elapsedTime;
      newOrder.forEach((planeIdx, newRank) => {
        const oldRank = stackOrder.indexOf(planeIdx);
        if (oldRank === newRank) return;
        const fromSlot = SLOT_POSITIONS[oldRank];
        const toSlot   = SLOT_POSITIONS[newRank];
        const isLead = planeIdx === leadIdx;
        let control = null;
        if (isLead) {
          // Real card motion: arc OUT of the deck (right + forward + up),
          // then around to the new slot. Big detour proportional to travel.
          const dist = Math.abs(newRank - oldRank);
          const dir  = newRank > oldRank ? 1 : -1; // going to back / to front
          const detour = 0.45 + dist * 0.25;
          control = {
            x: (fromSlot.x + toSlot.x) / 2 + detour * 1.5,
            y: (fromSlot.y + toSlot.y) / 2 + 0.55,
            z: (fromSlot.z + toSlot.z) / 2 + 1.20,
            // rotate the card mid-flight — tilts toward direction of travel
            rotZ: dir * 0.22,
          };
        }
        transitions[planeIdx] = {
          fromSlot, toSlot, control,
          startTime: t0,
          dur: 0.7,
          isLead,
        };
      });
      stackOrder = newOrder;
    }

    let focusIdx = stackOrder[0];

    // -------- raycaster + pointer --------
    const ray = new THREE.Raycaster();
    const pointer = new THREE.Vector2(0, 0);   // -1..1
    const pointerSmooth = new THREE.Vector2(0, 0);
    let pickable = planes.map((p) => p.hit);

    const onMove = (e) => {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
    };
    mount.addEventListener("mousemove", onMove);

    // scroll wheel cycles focused plane — rotate the stack
    const onWheel = (e) => {
      if (stateRef.current?.dive) return;
      e.preventDefault();
      const dir = Math.sign(e.deltaY);
      let newOrder, lead;
      if (dir > 0) {
        // forward: front card goes to back
        newOrder = [...stackOrder.slice(1), stackOrder[0]];
        lead = stackOrder[0];
      } else if (dir < 0) {
        // backward: back card comes to front
        newOrder = [stackOrder[stackOrder.length - 1], ...stackOrder.slice(0, -1)];
        lead = stackOrder[stackOrder.length - 1];
      } else { return; }
      setStack(newOrder, lead);
      focusIdx = newOrder[0];
      setFocused(focusIdx);
    };
    mount.addEventListener("wheel", onWheel, { passive: false });

    function cycleTo(planeIdx) {
      // bring planeIdx to the front by rotating stackOrder so its first entry is planeIdx
      const at = stackOrder.indexOf(planeIdx);
      if (at <= 0) return;
      const newOrder = [...stackOrder.slice(at), ...stackOrder.slice(0, at)];
      setStack(newOrder, planeIdx);
      focusIdx = newOrder[0];
      setFocused(focusIdx);
    }

    // keyboard nav
    const onKey = (e) => {
      if (stateRef.current?.dive) return;
      if (document.activeElement?.tagName === "INPUT") return;
      if (e.key === "ArrowRight" || e.key === "ArrowDown" || e.key === "Tab") {
        e.preventDefault();
        const newOrder = [...stackOrder.slice(1), stackOrder[0]];
        const lead = stackOrder[0];
        setStack(newOrder, lead);
        focusIdx = newOrder[0]; setFocused(focusIdx);
      } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
        e.preventDefault();
        const newOrder = [stackOrder[stackOrder.length - 1], ...stackOrder.slice(0, -1)];
        const lead = stackOrder[stackOrder.length - 1];
        setStack(newOrder, lead);
        focusIdx = newOrder[0]; setFocused(focusIdx);
      } else if (e.key === "Enter") {
        beginDive(planes[focusIdx].id);
      } else if (e.key >= "1" && e.key <= "4") {
        cycleTo(parseInt(e.key, 10) - 1);
      }
    };
    window.addEventListener("keydown", onKey);

    const onNavigate = (e) => {
      if (stateRef.current?.dive) return;
      const id = e.detail?.id;
      if (!id) return;
      const planeIdx = WORLDS.findIndex((w) => w.id === id);
      if (planeIdx === -1) return;
      if (planeIdx !== stackOrder[0]) {
        cycleTo(planeIdx);
        setTimeout(() => beginDive(id), 320);
      } else {
        beginDive(id);
      }
    };
    window.addEventListener("home:navigate", onNavigate);

    let hoveredId = null;
    const onClick = (e) => {
      if (stateRef.current?.dive) return;
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
      ray.setFromCamera(pointer, camera);
      const hits = ray.intersectObjects(pickable, false);
      if (hits.length) {
        const id = hits[0].object.userData.worldId;
        const planeIdx = WORLDS.findIndex(w => w.id === id);
        // if click target is NOT the front card, cycle it to front first; otherwise dive
        if (planeIdx !== stackOrder[0]) {
          cycleTo(planeIdx);
        } else {
          beginDive(id);
        }
      }
    };
    mount.addEventListener("click", onClick);

    function beginDive(id) {
      const w = WORLDS.find((x) => x.id === id);
      if (!w) return;
      setDiving(w);
      const pickedIdx = WORLDS.findIndex(x => x.id === id);

      // Per-world picked-plane warp — thematic continuation into the destination.
      // Rotations are LARGE so phase-1 warp reads clearly. dz is the forward push
      // applied across phase 2 (when the camera dollies through).
      const PICKED_WARP = {
        // Projects — schematic plate tilts forward like a drafting sheet being laid down
        projects:    { dx:  0.0, dy: 0.3, dz: 1.6, rotX: -1.10, rotY:  0.00, rotZ: 0,    scale: 1.6 },
        // Professional — plate flips backward, becoming the head of a vertical spine
        professional:{ dx:  0.0, dy: 0.0, dz: 1.6, rotX:  1.05, rotY:  0.00, rotZ: 0,    scale: 1.3 },
        // Creative — plate rotates ~90° around Y so it reads as an LP seen edge-on
        creative:    { dx: -0.3, dy: 0.0, dz: 1.7, rotX:  0.00, rotY: -1.55, rotZ: 0,    scale: 1.5 },
        // Personal — plate spins toward camera with a Z-roll (constellation flare)
        personal:    { dx:  0.0, dy: 0.0, dz: 1.8, rotX:  0.00, rotY:  0.00, rotZ: 0.55, scale: 2.0 },
      }[id] || { dx: 0, dy: 0, dz: 1.6, rotX: 0, rotY: 0, rotZ: 0, scale: 1.5 };

      // Dispersal vectors for the non-picked planes — each peels away in its own direction.
      const dispersals = {};
      planes.forEach((p, i) => {
        if (i === pickedIdx) return;
        const rank = stackOrder.indexOf(i);
        // Cardinal direction per remaining rank so they spread distinctly
        // rank 0 (if not picked): up-left and forward
        // rank 1: right and slightly forward
        // rank 2: back into depth
        // rank 3: down and away
        const CARDINALS = [
          { dx: -5, dy:  2.5, dz:  1.0, rot:  0.6 },
          { dx:  5, dy:  1.5, dz:  0.5, rot: -0.5 },
          { dx: -1, dy: -1.5, dz: -4.5, rot:  0.2 },
          { dx:  1, dy: -4,   dz:  0.5, rot: -0.7 },
        ];
        const dir = CARDINALS[rank] || CARDINALS[3];
        dispersals[i] = {
          startPos: p.group.position.clone(),
          startRot: { x: p.group.rotation.x, y: p.group.rotation.y, z: p.group.rotation.z },
          dir,
        };
      });

      // Picked plane warp record
      const pickedStart = {
        pos: planes[pickedIdx].group.position.clone(),
        rot: { x: planes[pickedIdx].group.rotation.x, y: planes[pickedIdx].group.rotation.y, z: planes[pickedIdx].group.rotation.z },
      };

      stateRef.current.dive = {
        id,
        pickedIdx,
        pickedStart,
        pickedWarp: PICKED_WARP,
        dispersals,
        startZ: camera.position.z,
        targetZ: w.z - 1.2,
        t: 0,
        dur: 1.4,
        narrationFired: {},
      };

      // narration lines per world
      const NARRATION = {
        projects:     ["$ ssh projects.deva", "establishing channel …", "loading schematics · sheet 1/4"],
        professional: ["$ ssh professional.deva", "opening timeline.spine …", "11 nodes · chronological"],
        creative:     ["$ ssh creative.deva", "unsleeving DEVA-001 …", "33⅓ RPM · side A queued"],
        personal:     ["$ ssh personal.deva", "mapping constellation …", "core node · 6 branches"],
      }[id] || [];
      stateRef.current.dive.narration = NARRATION;
    }

    // -------- resize --------
    const onResize = () => {
      const w = mount.clientWidth, h = mount.clientHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    window.addEventListener("resize", onResize);

    // -------- render loop --------
    let raf = 0;
    stateRef.current = { dive: null };

    function tick() {
      const dt = clock.getDelta();
      const t = clock.elapsedTime;
      dustMat.uniforms.uTime.value = t;
      dustMat.uniforms.uMouse.value.set(pointerSmooth.x, pointerSmooth.y);
      bgMat.uniforms.uTime.value = t;
      bgMat.uniforms.uMouse.value.set(pointerSmooth.x, pointerSmooth.y);
      bgMat.uniforms.uAspect.value = mount.clientWidth / mount.clientHeight;

      // damp pointer
      pointerSmooth.x += (pointer.x - pointerSmooth.x) * 0.06;
      pointerSmooth.y += (pointer.y - pointerSmooth.y) * 0.06;

      // camera parallax (1.5× mouse influence)
      const dive = stateRef.current.dive;
      if (!dive) {
        camera.position.x += (pointerSmooth.x * 0.825 - camera.position.x) * 0.06;
        camera.position.y += (pointerSmooth.y * 0.48 - camera.position.y) * 0.06;
        camera.position.z += (4.0 - camera.position.z) * 0.05;
      } else {
        dive.t += dt / dive.dur;
        const k = Math.min(1, dive.t);

        // Two-phase dive:
        //   Phase 1 (k = 0..0.6): picked plate WARPS into its world pose. Camera barely moves.
        //                         Other plates disperse outward.
        //   Phase 2 (k = 0.6..1): camera DOLLIES forward through the posed plate.
        //                         Picked plate fades; we land in the world.
        const phase1 = Math.min(1, k / 0.6);                        // 0..1 across phase 1
        const phase2 = Math.max(0, (k - 0.6) / 0.4);                // 0..1 across phase 2

        // ease-in-out cubic helper
        const easeInOut = (x) => x < 0.5 ? 4*x*x*x : 1 - Math.pow(-2*x+2, 3)/2;
        const e1 = easeInOut(phase1);
        const e2 = easeInOut(phase2);

        // Camera mostly waits during phase 1 (small forward creep), then dollies through in phase 2.
        const camCreep = dive.startZ + (dive.startZ - 1.0 - dive.startZ) * 0.18 * e1;
        camera.position.z = camCreep + (dive.targetZ - camCreep) * e2;

        // ---- picked plane: full warp completes in phase 1; in phase 2 just lifts slightly toward camera ----
        const picked = planes[dive.pickedIdx];
        const w = dive.pickedWarp;
        const s = dive.pickedStart;
        // Phase 1 applies the FULL rotation + most of the scale.
        // Phase 2 adds a small forward translation (dz) so it feels like we go through it.
        const warpE = e1;
        const dzE   = e2; // dz only in phase 2 — camera moves toward it
        picked.group.position.set(
          s.pos.x + w.dx * warpE,
          s.pos.y + w.dy * warpE,
          s.pos.z + w.dz * dzE * 0.55,  // partial forward lift; camera does the rest
        );
        picked.group.rotation.x = s.rot.x + w.rotX * warpE;
        picked.group.rotation.y = s.rot.y + w.rotY * warpE;
        picked.group.rotation.z = s.rot.z + w.rotZ * warpE;
        const sc = 1 + (w.scale - 1) * warpE;
        picked.group.scale.set(sc, sc, sc);

        // ---- non-picked planes: peel away in phase 1, almost gone by phase 2 ----
        const eDisp = 1 - Math.pow(1 - phase1, 2.4);
        planes.forEach((p, i) => {
          if (i === dive.pickedIdx) return;
          const d = dive.dispersals[i];
          if (!d) return;
          p.group.position.set(
            d.startPos.x + d.dir.dx * eDisp,
            d.startPos.y + d.dir.dy * eDisp,
            d.startPos.z + d.dir.dz * eDisp,
          );
          p.group.rotation.z = d.startRot.z + d.dir.rot * eDisp;
        });

        // ---- terminal narration fires at thresholds ----
        const NARR_TIMES = [0.0, 0.30, 0.70];
        NARR_TIMES.forEach((thr, idx) => {
          if (k >= thr && !dive.narrationFired[idx] && dive.narration?.[idx]) {
            dive.narrationFired[idx] = true;
            onNarrateRef.current?.(dive.narration[idx]);
          }
        });

        // ---- fade: non-picked finish disappearing by mid phase 1; picked fades only in late phase 2 ----
        const otherFade  = Math.max(0, 1 - phase1 * 1.4);
        const pickedFade = Math.max(0, 1 - Math.max(0, phase2 - 0.6) * 2.5);
        planes.forEach((p, i) => p.setOpacity(i === dive.pickedIdx ? pickedFade : otherFade));
        dustMat.opacity = Math.max(0.2, 1 - phase2 * 0.85);

        if (dive.t >= 1) {
          stateRef.current.dive = null;
          onSelectRef.current?.(dive.id);
          setDiving(null);
        }
      }
      // keep aim point slightly right so the planes group sits in the right 2/3
      const aim = lookTarget.clone();
      aim.x += pointerSmooth.x * 0.225;
      aim.y += pointerSmooth.y * 0.12;
      camera.lookAt(aim);

      // hover pick
      ray.setFromCamera(pointerSmooth, camera);
      const hits = ray.intersectObjects(pickable, false);
      const id = hits.length ? hits[0].object.userData.worldId : null;
      if (id !== hoveredId) {
        hoveredId = id;
        renderer.domElement.style.cursor = id ? "pointer" : "default";
        if (id) {
          const w = WORLDS.find((x) => x.id === id);
          setHovered(w);
        } else {
          setHovered(null);
        }
      }

      // animate planes — each plane is either mid-transition (Bezier arc) or settled
      // in its current slot. Bob is layered on top either way.
      // (Skip while diving — the dive owns plane positions then.)
      if (!stateRef.current.dive) {
        planes.forEach((p, i) => {
        const rank = stackOrder.indexOf(i);
        const slot = SLOT_POSITIONS[rank];
        const wob = Math.sin(t * 0.4 + i * 1.2) * 0.018;
        const trans = transitions[i];

        if (trans) {
          // progress 0..1
          const tk = Math.min(1, (t - trans.startTime) / trans.dur);
          // ease-in-out cubic for the timing curve
          const e = tk < 0.5 ? 4 * tk * tk * tk : 1 - Math.pow(-2 * tk + 2, 3) / 2;
          let px, py, pz;
          if (trans.control) {
            // quadratic Bezier: A -> C -> B  (A = from, C = control, B = to)
            const u = 1 - e;
            px = u * u * trans.fromSlot.x + 2 * u * e * trans.control.x + e * e * trans.toSlot.x;
            py = u * u * trans.fromSlot.y + 2 * u * e * trans.control.y + e * e * trans.toSlot.y;
            pz = u * u * trans.fromSlot.z + 2 * u * e * trans.control.z + e * e * trans.toSlot.z;
          } else {
            px = trans.fromSlot.x + (trans.toSlot.x - trans.fromSlot.x) * e;
            py = trans.fromSlot.y + (trans.toSlot.y - trans.fromSlot.y) * e;
            pz = trans.fromSlot.z + (trans.toSlot.z - trans.fromSlot.z) * e;
          }
          p.group.position.set(px, py + wob, pz);

          // rotation interpolates between slot rotations, with a mid-flight roll for the lead card
          const rotY = trans.fromSlot.rotY + (trans.toSlot.rotY - trans.fromSlot.rotY) * e;
          const rotX = trans.fromSlot.rotX + (trans.toSlot.rotX - trans.fromSlot.rotX) * e;
          // a sine bump for an extra roll mid-arc on the moving card
          const roll = trans.control ? Math.sin(tk * Math.PI) * (trans.control.rotZ || 0) : 0;
          p.group.rotation.y = rotY;
          p.group.rotation.x = rotX;
          p.group.rotation.z = roll;

          if (tk >= 1) {
            // snap to final and clear
            p.group.position.set(trans.toSlot.x, trans.toSlot.y + wob, trans.toSlot.z);
            p.group.rotation.z = 0;
            delete transitions[i];
          }
        } else {
          // settled — hold the slot position with tiny damp-in (handles initial frames)
          p.group.position.x += (slot.x - p.group.position.x) * 0.18;
          p.group.position.y += ((slot.y + wob) - p.group.position.y) * 0.18;
          p.group.position.z += (slot.z - p.group.position.z) * 0.18;
          p.group.rotation.y += (slot.rotY - p.group.rotation.y) * 0.15;
          p.group.rotation.x += (slot.rotX - p.group.rotation.x) * 0.15;
          p.group.rotation.z += (0 - p.group.rotation.z) * 0.15;
        }

        const isFront = rank === 0;
        const targetK = id === p.id ? 1 : (isFront ? 0.55 : 0);
        p.hoverK += (targetK - p.hoverK) * 0.1;
        p.setHover(p.hoverK, isFront, rank);
      });
      } // end if (!dive)

      // Push the mouse position (in world units, on the z=0 plane) into each plate
      // so the rim-light shader can highlight a soft spot where the cursor hovers.
      const mouseWorld = new THREE.Vector3(pointerSmooth.x, pointerSmooth.y, 0.5).unproject(camera);
      planes.forEach((p) => p.setMouseWorld(mouseWorld));

      // Assign renderOrder by current world Z (back to front) so a card mid-shuffle
      // layers based on where it ACTUALLY is in 3D.
      // mid-shuffle layers based on where it ACTUALLY is in 3D — the moving card
      // doesn't pop to top until its arc has physically passed in front.
      const sortedByZ = planes
        .map((p) => ({ p, z: p.getWorldZ() }))
        .sort((a, b) => a.z - b.z); // back to front
      sortedByZ.forEach((entry, drawIdx) => {
        entry.p.setRenderOrder(drawIdx * 2 + 1);
      });

      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    tick();

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
      window.removeEventListener("keydown", onKey);
      window.removeEventListener("home:navigate", onNavigate);
      mount.removeEventListener("mousemove", onMove);
      mount.removeEventListener("click", onClick);
      mount.removeEventListener("wheel", onWheel);
      renderer.dispose();
      planes.forEach((p) => p.dispose());
      dustGeo.dispose();
      dustMat.dispose();
      bgGeo.dispose();
      bgMat.dispose();
      mount.removeChild(renderer.domElement);
    };
  }, []);

  return (
    <div className="world-enter" style={{ position: "absolute", inset: 0 }}>
      <div ref={mountRef} style={{ position: "absolute", inset: 0 }} />

      {/* anchor copy overlay — LEFT half, vertically centered. The Z-stack lives in the right half. */}
      <div style={homeStyles.intro}>
        <div className="label" style={{ marginBottom: 14 }}>// portfolio.exe — z-stack home</div>
        <h1 style={homeStyles.h1}>
          <span style={{ color: "var(--muted)", fontStyle: "normal" }}>I'm </span>
          {ABOUT.name}.
        </h1>
        <p style={homeStyles.tagline}>
          Finance + engineering. I build research and trading systems for myself,
          and I write equity research the rigorous way.
        </p>
        <p style={homeStyles.taglineSm}>
          Currently shipping <em>Leviathan</em> — an autonomous equity research platform.
          Looking for the next seat where research rigor and engineering throughput compound.
        </p>

        {/* plane indicator — since they're stacked, this shows which one is forward */}
        <div style={homeStyles.planePicker}>
          <div className="mono" style={{ color: "var(--muted)", fontSize: 10, letterSpacing: ".22em", marginBottom: 8 }}>
            — STACK · {String(focused+1).padStart(2,"0")} OF 04 FORWARD
          </div>
          {WORLDS.map((w, i) => (
            <div
              key={w.id}
              role="button"
              tabIndex={0}
              onClick={() => window.dispatchEvent(new CustomEvent("home:navigate", { detail: { id: w.id } }))}
              onKeyDown={(e) => {
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  window.dispatchEvent(new CustomEvent("home:navigate", { detail: { id: w.id } }));
                }
              }}
              onMouseEnter={() => setFocused(i)}
              aria-label={`Enter ${w.title}`}
              style={{
                ...homeStyles.planeDot,
                cursor: "pointer",
                userSelect: "none",
                borderColor: focused === i ? "var(--amber)" : "var(--line-2)",
                background: focused === i ? "oklch(80% 0.14 70 / 0.10)" : "transparent",
                transition: "border-color 140ms ease, background 140ms ease",
              }}
            >
              <span className="mono" style={{
                fontSize: 10, letterSpacing: ".22em",
                color: focused === i ? "var(--amber)" : "var(--muted)",
              }}>{w.n}</span>
              <span className="mono" style={{
                fontSize: 10, letterSpacing: ".22em",
                color: focused === i ? "var(--fg)" : "var(--fg-2)",
              }}>{w.title.toUpperCase()}</span>
              <span className="mono" style={{ fontSize: 10, color: "var(--dim)", letterSpacing: ".15em" }}>
                {focused === i ? "◀ FRONT" : "ENTER →"}
              </span>
            </div>
          ))}
        </div>

        <div style={homeStyles.hintRow}>
          <span className="mono" style={{ color: "var(--muted)", fontSize: 11, letterSpacing: ".15em", display: "inline-flex", alignItems: "center", flexWrap: "wrap", gap: 4 }}>
            <span style={kbdStyle}>SCROLL</span>
            <span style={kbdStyle}>← →</span>
            <span>cycle stack</span>
            <span style={{ margin: "0 6px", color: "var(--line-2)" }}>•</span>
            <span style={kbdStyle}>↵</span>
            <span>enter</span>
            <span style={{ margin: "0 6px", color: "var(--line-2)" }}>•</span>
            <span style={kbdStyle}>/</span>
            <span>terminal</span>
          </span>
        </div>
      </div>

      {/* hover readout */}
      <div style={{
        ...homeStyles.readout,
        opacity: hovered && !diving ? 1 : 0,
        transform: `translateY(${hovered ? 0 : 8}px)`,
      }}>
        {hovered && (
          <>
            <div className="mono" style={{ color: "var(--amber)", fontSize: 11, letterSpacing: ".22em" }}>
              {hovered.n} / {hovered.title.toUpperCase()} · {hovered.sub.toUpperCase()}
            </div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 17, lineHeight: 1.4, color: "var(--fg-2)", margin: "8px 0 6px", fontStyle: "italic" }}>
              {hovered.blurb}
            </div>
            <div className="mono" style={{ color: "var(--phosphor)", fontSize: 10, letterSpacing: ".22em" }}>
              CLICK TO ENTER ↵
            </div>
          </>
        )}
      </div>

      {/* corner technicals */}
      <div style={homeStyles.cornerTL}>
        <span className="mono" style={{ color: "var(--muted)", fontSize: 10, letterSpacing: ".22em" }}>
          ARIHANT.DEVA / PORTFOLIO.SYS
        </span>
        <span className="mono" style={{ color: "var(--dim)", fontSize: 10, letterSpacing: ".22em" }}>
          BUILD 2026.05 · LAT 40.72N LON 74.04W
        </span>
      </div>
      <div style={homeStyles.cornerTR}>
        <span className="mono" style={{ color: "var(--muted)", fontSize: 10, letterSpacing: ".22em" }}>
          PLANES 4 · DEPTH ENABLED · WEBGL
        </span>
      </div>

      {diving && (
        <div style={{ position: "absolute", inset: 0, pointerEvents: "none", display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div className="mono" style={{ color: "var(--amber)", fontSize: 12, letterSpacing: ".3em" }}>
            ENTERING <span style={{ color: "var(--fg)" }}>{diving.title.toUpperCase()}</span>
            <span className="blink"> _</span>
          </div>
        </div>
      )}
    </div>
  );
}

// --- plane factory ---------------------------------------------------------
function makePlane(w, i, slotPositions) {
  const group = new THREE.Group();
  // initial position = slot that matches starting rank (rank == i at boot)
  const start = (slotPositions && slotPositions[i]) || { x: 0, y: 0, z: 0 };
  group.position.set(start.x, start.y, start.z);

  // The plate texture (Canvas → CanvasTexture)
  const tex = drawPlaneTexture(w);
  // Custom shader material adds mouse-driven rim lighting on top of the texture.
  // The mouse world position is uploaded each frame; a soft gaussian highlight
  // sweeps across the plate as the cursor moves, with a stronger edge rim on hover.
  const mat = new THREE.ShaderMaterial({
    uniforms: {
      uMap: { value: tex },
      uMouseWorld: { value: new THREE.Vector3(0, 0, 0) },
      uHover: { value: 0 },
      uOpacity: { value: 1 },
    },
    transparent: true,
    depthWrite: false,
    side: THREE.DoubleSide,
    vertexShader: `
      varying vec2 vUv;
      varying vec3 vWorld;
      void main() {
        vUv = uv;
        vec4 wp = modelMatrix * vec4(position, 1.0);
        vWorld = wp.xyz;
        gl_Position = projectionMatrix * viewMatrix * wp;
      }`,
    fragmentShader: `
      precision highp float;
      varying vec2 vUv;
      varying vec3 vWorld;
      uniform sampler2D uMap;
      uniform vec3 uMouseWorld;
      uniform float uHover;
      uniform float uOpacity;
      void main() {
        vec4 base = texture2D(uMap, vUv);
        vec2 d = vWorld.xy - uMouseWorld.xy;
        float dist = length(d);
        float light = exp(-dist * dist * 5.0) * (0.30 + 0.50 * uHover);
        float edge = pow(1.0 - 2.0 * min(min(vUv.x, 1.0-vUv.x), min(vUv.y, 1.0-vUv.y)), 4.0);
        vec3 warm = vec3(1.0, 0.88, 0.60);
        vec3 col = base.rgb + warm * light * 0.7 + warm * edge * uHover * 0.25;
        gl_FragColor = vec4(col, base.a * uOpacity);
      }`,
  });
  const aspect = 16 / 10;
  const width = 2.2;
  const geo = new THREE.PlaneGeometry(width, width / aspect, 24, 16);
  const mesh = new THREE.Mesh(geo, mat);
  mesh.userData.worldId = w.id;
  group.add(mesh);

  // glow halo behind plane — sized tight to plane so it doesn't bleed offscreen
  const haloTex = drawHaloTexture(w.hue);
  const haloMat = new THREE.MeshBasicMaterial({
    map: haloTex,
    transparent: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
  const halo = new THREE.Mesh(new THREE.PlaneGeometry(width * 1.25, (width / aspect) * 1.35), haloMat);
  halo.position.z = -0.02;
  halo.material.opacity = 0.22;
  group.add(halo);

  // initial tilt — matches the per-rank target so we don't snap on first frame
  group.rotation.y = -0.12 - i * 0.025;
  group.rotation.x =  0.02 + i * 0.008;

  let hoverK = 0;

  return {
    id: w.id,
    group,
    hit: mesh,
    get hoverK() { return hoverK; },
    set hoverK(v) { hoverK = v; },
    setHover(k, isFront, rank) {
      mesh.position.z = k * 0.15;
      mesh.position.y = k * 0.04;
      mat.uniforms.uHover.value = k;
      halo.material.opacity = (isFront ? 0.34 : 0.18) + k * 0.28;
      const rankOpacity = rank === 0 ? 1.0 : rank === 1 ? 0.94 : rank === 2 ? 0.82 : 0.66;
      mat.uniforms.uOpacity.value = Math.min(1, rankOpacity + k * 0.1);
    },
    setRenderOrder(n) {
      // Halos always draw ABOVE all plates (additive blending makes them feel like
      // real light glows that shine on top of whatever's in front). Plates sort among
      // themselves by world Z; halos sort among themselves but always above plates.
      mesh.renderOrder = n;
      halo.renderOrder = 100 + n;
    },
    getWorldZ() {
      // approximate world Z used for back-to-front sorting each frame
      return group.position.z + mesh.position.z;
    },
    setOpacity(o) { mat.uniforms.uOpacity.value = o; halo.material.opacity = 0.22 * o; },
    setMouseWorld(p) { mat.uniforms.uMouseWorld.value.copy(p); },
    dispose() {
      geo.dispose(); mat.dispose(); haloMat.dispose();
      tex.dispose(); haloTex.dispose();
      halo.geometry.dispose();
    },
  };
}

function drawPlaneTexture(w) {
  const W = 1280, H = 800;
  const c = document.createElement("canvas");
  c.width = W; c.height = H;
  const g = c.getContext("2d");

  // background — translucent dark with subtle tint
  g.fillStyle = `oklch(16% 0.014 60 / 0.86)`;
  g.fillRect(0, 0, W, H);

  // inner border
  g.strokeStyle = `oklch(70% 0.10 ${w.hue} / 0.55)`;
  g.lineWidth = 2;
  g.strokeRect(18, 18, W - 36, H - 36);

  // double inner line
  g.strokeStyle = `oklch(70% 0.10 ${w.hue} / 0.22)`;
  g.lineWidth = 1;
  g.strokeRect(34, 34, W - 68, H - 68);

  // technical grid in background
  g.strokeStyle = `oklch(80% 0.02 ${w.hue} / 0.06)`;
  g.lineWidth = 1;
  for (let x = 40; x < W - 40; x += 32) {
    g.beginPath(); g.moveTo(x, 40); g.lineTo(x, H - 40); g.stroke();
  }
  for (let y = 40; y < H - 40; y += 32) {
    g.beginPath(); g.moveTo(40, y); g.lineTo(W - 40, y); g.stroke();
  }

  // corner brackets
  drawBracket(g, 48, 48, 28, 28, `oklch(85% 0.12 ${w.hue})`);
  drawBracket(g, W - 48, 48, -28, 28, `oklch(85% 0.12 ${w.hue})`);
  drawBracket(g, 48, H - 48, 28, -28, `oklch(85% 0.12 ${w.hue})`);
  drawBracket(g, W - 48, H - 48, -28, -28, `oklch(85% 0.12 ${w.hue})`);

  // big plane number
  g.fillStyle = `oklch(85% 0.12 ${w.hue})`;
  g.font = "600 220px 'Instrument Serif', Georgia, serif";
  g.textAlign = "left";
  g.textBaseline = "top";
  g.fillText(w.n, 82, 84);

  // title
  g.fillStyle = "rgba(238, 232, 220, 0.96)";
  g.font = "italic 110px 'Instrument Serif', Georgia, serif";
  g.fillText(w.title, 82, 320);

  // subtitle
  g.fillStyle = "rgba(220, 200, 165, 0.7)";
  g.font = "500 24px 'JetBrains Mono', monospace";
  g.fillText(w.sub.toUpperCase(), 88, 470);

  // separator
  g.strokeStyle = `oklch(70% 0.10 ${w.hue} / 0.5)`;
  g.lineWidth = 1;
  g.beginPath(); g.moveTo(82, 510); g.lineTo(W - 82, 510); g.stroke();

  // small data row
  g.fillStyle = "rgba(180, 165, 140, 0.6)";
  g.font = "500 18px 'JetBrains Mono', monospace";
  g.fillText(`PLANE.${w.n}  ·  DEPTH ${w.z.toFixed(2)}u  ·  CLICK TO ENTER`, 82, 528);

  // bottom-right tag
  g.textAlign = "right";
  g.font = "500 18px 'JetBrains Mono', monospace";
  g.fillStyle = `oklch(80% 0.12 ${w.hue} / 0.85)`;
  g.fillText(`◢ ${w.id.toUpperCase()}`, W - 88, H - 96);

  // bottom-left dot pattern
  g.fillStyle = "rgba(220, 200, 165, 0.35)";
  for (let i = 0; i < 6; i++) {
    g.beginPath(); g.arc(96 + i * 12, H - 92, 2, 0, Math.PI * 2); g.fill();
  }

  const tex = new THREE.CanvasTexture(c);
  tex.anisotropy = 8;
  tex.minFilter = THREE.LinearFilter;
  tex.magFilter = THREE.LinearFilter;
  return tex;
}

function drawBracket(g, x, y, dx, dy, color) {
  g.strokeStyle = color;
  g.lineWidth = 2;
  g.beginPath();
  g.moveTo(x + dx, y); g.lineTo(x, y); g.lineTo(x, y + dy);
  g.stroke();
}

function drawHaloTexture(hue) {
  const W = 512, H = 320;
  const c = document.createElement("canvas");
  c.width = W; c.height = H;
  const g = c.getContext("2d");
  const grad = g.createRadialGradient(W/2, H/2, 0, W/2, H/2, W/2);
  grad.addColorStop(0, `oklch(80% 0.16 ${hue} / 0.55)`);
  grad.addColorStop(0.4, `oklch(70% 0.12 ${hue} / 0.18)`);
  grad.addColorStop(1, `oklch(40% 0.05 ${hue} / 0)`);
  g.fillStyle = grad;
  g.fillRect(0, 0, W, H);
  const tex = new THREE.CanvasTexture(c);
  return tex;
}

function makeGrid() {
  // No-op stub kept for ABI; unused after volumetric backdrop replaced it.
  return null;
}

// overlay copy
const kbdStyle = {
  display: "inline-block",
  border: "1px solid var(--line)",
  padding: "2px 6px",
  margin: "0 2px",
  fontSize: 10,
  color: "var(--fg-2)",
  letterSpacing: ".15em",
};

const homeStyles = {
  intro: {
    position: "absolute",
    top: "50%",
    left: "5vw",
    transform: "translateY(-50%)",
    zIndex: 5,
    maxWidth: "min(440px, 38vw)",
    pointerEvents: "none",
  },
  h1: {
    fontFamily: "var(--serif)",
    fontSize: "clamp(28px, 3.2vw, 48px)",
    margin: 0,
    fontWeight: 400,
    letterSpacing: "-0.01em",
    lineHeight: 1.05,
    color: "var(--fg)",
    fontStyle: "italic",
  },
  tagline: {
    marginTop: 16,
    fontFamily: "var(--serif)",
    fontSize: "clamp(14px, 1.05vw, 16px)",
    color: "var(--fg-2)",
    lineHeight: 1.55,
    textWrap: "pretty",
  },
  taglineSm: {
    marginTop: 10,
    fontFamily: "var(--serif)",
    fontSize: "clamp(13px, 0.95vw, 15px)",
    color: "var(--muted)",
    lineHeight: 1.55,
    fontStyle: "italic",
    textWrap: "pretty",
  },
  planePicker: {
    pointerEvents: "auto",
    marginTop: 22,
    display: "flex",
    flexDirection: "column",
    gap: 4,
    maxWidth: 340,
  },
  planeDot: {
    display: "grid",
    gridTemplateColumns: "32px 1fr auto",
    alignItems: "center",
    padding: "6px 12px",
    border: "1px solid var(--line-2)",
    transition: "all 200ms ease",
    cursor: "default",
    gap: 12,
  },
  hintRow: {
    marginTop: 18,
  },
  readout: {
    position: "absolute",
    right: "4vw",
    bottom: 110,
    width: "min(420px, 36vw)",
    padding: "14px 20px",
    background: "oklch(11% 0.012 60 / 0.78)",
    border: "1px solid var(--line)",
    backdropFilter: "blur(8px)",
    WebkitBackdropFilter: "blur(8px)",
    transition: "opacity 240ms ease, transform 240ms ease",
    zIndex: 6,
    pointerEvents: "none",
  },
  cornerTL: {
    position: "absolute", top: 18, left: 24, display: "flex", flexDirection: "column", gap: 4, zIndex: 5,
  },
  cornerTR: {
    position: "absolute", top: 18, right: 24, zIndex: 5,
  },
};

window.Home = Home;
