/* 3D Eiffel Tower — filled silhouette, extruded in Z for a real "mass" feel.
   Continuous rotation within a comfortable arc + scroll-linked tilt. */

const { useEffect, useRef, useState } = React;

/* Filled Eiffel silhouette with the famous arch cut out.
   viewBox 400x1000, anatomically-correct proportions. */
function EiffelFill({ color = "#0E1235", opacity = 1, sheen = false }) {
  return (
    <svg
      viewBox="0 0 400 1000"
      fill={color}
      style={{ opacity, display: "block" }}
      aria-hidden="true"
    >
      <defs>
        {sheen && (
          <linearGradient id="sheen" x1="0" x2="1" y1="0" y2="0">
            <stop offset="0" stopColor="rgba(255,255,255,0)" />
            <stop offset="0.45" stopColor="rgba(255,255,255,0.18)" />
            <stop offset="0.55" stopColor="rgba(255,255,255,0.05)" />
            <stop offset="1" stopColor="rgba(255,255,255,0)" />
          </linearGradient>
        )}
      </defs>
      {/* main silhouette — outer perimeter + inner arch cutout (evenodd) */}
      <path
        fillRule="evenodd"
        d="
          M 10 1000
          Q 40 945 70 890
          Q 88 862 95 840
          L 132 645
          L 175 150
          L 197 82
          L 200 10
          L 203 82
          L 225 150
          L 268 645
          L 305 840
          Q 312 862 330 890
          Q 360 945 390 1000
          Z
          M 132 1000
          L 130 884
          Q 148 845 200 832
          Q 252 845 270 884
          L 268 1000
          Z
        "
      />
      {/* platform decks — visually anchor the silhouette */}
      <rect x="80" y="822" width="240" height="18" rx="2" />
      <rect x="128" y="638" width="144" height="11" rx="2" />
      <rect x="170" y="146" width="60" height="9" rx="2" />
      {/* observation cabin under top platform */}
      <rect x="183" y="155" width="34" height="22" rx="3" />
      {sheen && (
        <path
          fillRule="evenodd"
          fill="url(#sheen)"
          d="
            M 10 1000
            Q 40 945 70 890
            Q 88 862 95 840
            L 132 645
            L 175 150
            L 197 82
            L 200 10
            L 203 82
            L 225 150
            L 268 645
            L 305 840
            Q 312 862 330 890
            Q 360 945 390 1000
            Z
            M 132 1000
            L 130 884
            Q 148 845 200 832
            Q 252 845 270 884
            L 268 1000
            Z
          "
        />
      )}
    </svg>
  );
}

/* Lattice texture overlay — light cross-hatching only on the front layer */
function EiffelLattice({ color = "rgba(255,255,255,0.18)" }) {
  return (
    <svg viewBox="0 0 400 1000" fill="none" stroke={color} strokeWidth="1" style={{ display: "block" }} aria-hidden="true">
      {/* horizontal ribs on lower section */}
      {[820, 760, 700, 640, 555, 470, 385, 305, 220].map((y, i) => {
        // taper: at y=820 width is ~242, at y=170 width is ~50
        const t = (820 - y) / (820 - 170);
        const halfW = 121 - 87 * t;
        return <line key={i} x1={200 - halfW} y1={y} x2={200 + halfW} y2={y} opacity={0.55} />;
      })}
      {/* arch curve highlight */}
      <path d="M 132 884 Q 200 822 268 884" stroke={color} strokeWidth="1.4" opacity="0.7" />
      {/* antenna ticks */}
      <line x1="200" y1="30" x2="200" y2="78" strokeWidth="1.2" opacity="0.7" />
      <line x1="196" y1="60" x2="204" y2="60" strokeWidth="1" opacity="0.6" />
      <line x1="197" y1="45" x2="203" y2="45" strokeWidth="1" opacity="0.6" />
    </svg>
  );
}

function EiffelExtruded() {
  const LAYERS = 12;
  return (
    <>
      {Array.from({ length: LAYERS }).map((_, i) => {
        const t = i / (LAYERS - 1); // 0..1
        const z = (i - (LAYERS - 1) / 2) * 4.8;
        const dist = Math.abs(t - 0.5) * 2; // 0 center → 1 outer
        const isCenter = i === Math.floor(LAYERS / 2);
        const isFront = i === LAYERS - 1;
        let color;
        if (dist < 0.1) color = "#0E1235";
        else if (dist < 0.45) color = `rgba(58, 41, 168, ${0.95 - dist * 0.7})`;
        else color = `rgba(110, 91, 255, ${Math.max(0.08, 0.55 * (1 - dist))})`;
        return (
          <div
            className="eiffel-layer"
            key={i}
            style={{ transform: `translateZ(${z}px)` }}
          >
            <EiffelFill color={color} sheen={isFront} />
            {isFront && (
              <div className="eiffel-overlay">
                <EiffelLattice color="rgba(255,255,255,0.18)" />
              </div>
            )}
          </div>
        );
      })}
    </>
  );
}

/* HeroEiffel3D — high-fidelity procedural Eiffel Tower.
   Uses the real hyperbolic flare, visible iron lattice (instanced),
   three platforms with arches, top cabin and antenna. Continuous slow
   rotation, paused when off-screen. Capped DPR for perf. */
function HeroEiffel3D() {
  const canvasRef = useRef(null);
  useEffect(() => {
    const TH = window.THREE;
    if (!TH || !canvasRef.current) return;
    const canvas = canvasRef.current;

    const renderer = new TH.WebGLRenderer({ canvas, alpha: true, antialias: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.75));
    renderer.outputColorSpace = TH.SRGBColorSpace;
    renderer.toneMapping = TH.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.05;

    const scene = new TH.Scene();
    const camera = new TH.PerspectiveCamera(26, 1, 0.1, 500);

    // The scene is rendered ONCE at mount (and on resize / GLB load).
    // No rAF loop, no rotation — keeps the GPU completely free during scroll.
    function renderOnce() { renderer.render(scene, camera); }
    function resize() {
      const w = canvas.clientWidth || canvas.parentElement.clientWidth;
      const h = canvas.clientHeight || canvas.parentElement.clientHeight;
      renderer.setSize(w, h, false);
      camera.aspect = w / Math.max(1, h);
      camera.updateProjectionMatrix();
      renderOnce();
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    // --- Cinematic lighting -----------------------------------------
    scene.add(new TH.HemisphereLight(0xfff4dd, 0x36305a, 0.55));
    const key = new TH.DirectionalLight(0xfff1d2, 1.35);
    key.position.set(24, 40, 18); scene.add(key);
    const fill = new TH.DirectionalLight(0x9a8bff, 0.85);
    fill.position.set(-26, 14, -8); scene.add(fill);
    const rim = new TH.DirectionalLight(0xffffff, 0.55);
    rim.position.set(0, 6, -34); scene.add(rim);

    // --- Real Eiffel silhouette ----------------------------------
    // Tower height 330m. Normalize to H units; key levels match real ratios.
    const H = 33;
    const L1 = 0.173;  // first platform (57m)
    const L2 = 0.349;  // second platform (115m)
    const L3 = 0.836;  // top platform (276m)

    // Half-width (distance from central axis to a leg corner) along height.
    // Hand-tuned to match the famous hyperbolic flare.
    const SAMPLES = [
      [0.000, 6.20],
      [0.060, 4.30],
      [0.120, 3.40],
      [L1,    2.80],
      [0.240, 2.20],
      [L2,    1.70],
      [0.500, 1.10],
      [0.680, 0.65],
      [L3,    0.32],
      [1.000, 0.10],
    ];
    function halfWidth(t) {
      for (let i = 0; i < SAMPLES.length - 1; i++) {
        const [a, wa] = SAMPLES[i];
        const [b, wb] = SAMPLES[i + 1];
        if (t <= b) {
          const k = (t - a) / (b - a);
          const s = k * k * (3 - 2 * k); // smoothstep
          return wa + (wb - wa) * s;
        }
      }
      return SAMPLES[SAMPLES.length - 1][1];
    }

    // --- Materials -----------------------------------------------
    const ironMat = new TH.MeshStandardMaterial({
      color: 0x4a3e34, roughness: 0.55, metalness: 0.78,
    });
    const ironDark = new TH.MeshStandardMaterial({
      color: 0x2a221c, roughness: 0.62, metalness: 0.7,
    });
    const deckMat = new TH.MeshStandardMaterial({
      color: 0x3b3127, roughness: 0.7, metalness: 0.6,
    });

    const tower = new TH.Group();

    // --- 4 splaying legs (tube geometry along the curve) --------
    const LEG_SEGS = 32;
    const cornerSigns = [[1, 1], [1, -1], [-1, 1], [-1, -1]];
    for (const [sx, sz] of cornerSigns) {
      const pts = [];
      for (let i = 0; i <= LEG_SEGS; i++) {
        const t = (i / LEG_SEGS) * L3;
        const w = halfWidth(t);
        pts.push(new TH.Vector3(sx * w, t * H, sz * w));
      }
      const curve = new TH.CatmullRomCurve3(pts);
      const tube = new TH.TubeGeometry(curve, LEG_SEGS, 0.22, 8, false);
      tower.add(new TH.Mesh(tube, ironMat));
    }

    // --- Central upper column (above top platform) --------------
    {
      const pts = [];
      const segs = 12;
      for (let i = 0; i <= segs; i++) {
        const t = L3 + (i / segs) * (1 - L3);
        const w = halfWidth(t);
        pts.push(new TH.Vector3(0, t * H, 0));
        void w;
      }
      const curve = new TH.CatmullRomCurve3(pts);
      const tube = new TH.TubeGeometry(curve, segs, 0.30, 8, false);
      tower.add(new TH.Mesh(tube, ironMat));
    }

    // --- Iron lattice — InstancedMesh of thin bars between legs --
    // For each of 4 vertical sides and each level slice, draw 2 diagonals + 1 horizontal.
    const BRACE_LEVELS = 22;
    const sidePairs = [
      [[ 1,  1], [ 1, -1]],
      [[ 1, -1], [-1, -1]],
      [[-1, -1], [-1,  1]],
      [[-1,  1], [ 1,  1]],
    ];
    const barGeo = new TH.CylinderGeometry(0.06, 0.06, 1, 5);
    const maxInstances = BRACE_LEVELS * sidePairs.length * 3 + 64;
    const bars = new TH.InstancedMesh(barGeo, ironDark, maxInstances);
    const dummy = new TH.Object3D();
    const upY = new TH.Vector3(0, 1, 0);
    let bi = 0;
    function placeBar(p1, p2) {
      const mid = p1.clone().add(p2).multiplyScalar(0.5);
      const dir = p2.clone().sub(p1);
      const len = dir.length();
      if (len < 0.01) return;
      dir.normalize();
      dummy.position.copy(mid);
      dummy.scale.set(1, len, 1);
      dummy.quaternion.setFromUnitVectors(upY, dir);
      dummy.updateMatrix();
      bars.setMatrixAt(bi++, dummy.matrix);
    }
    // Skip level windows where platforms sit (avoid bars clipping decks)
    const SKIP = [L1, L2];
    for (let lvl = 0; lvl < BRACE_LEVELS; lvl++) {
      const t0 = (lvl / BRACE_LEVELS) * L3;
      const t1 = ((lvl + 1) / BRACE_LEVELS) * L3;
      const mid = (t0 + t1) / 2;
      if (SKIP.some((s) => Math.abs(mid - s) < 0.012)) continue;
      for (const [a, b] of sidePairs) {
        const wa0 = halfWidth(t0);
        const wa1 = halfWidth(t1);
        const A0 = new TH.Vector3(a[0] * wa0, t0 * H, a[1] * wa0);
        const A1 = new TH.Vector3(a[0] * wa1, t1 * H, a[1] * wa1);
        const B0 = new TH.Vector3(b[0] * wa0, t0 * H, b[1] * wa0);
        const B1 = new TH.Vector3(b[0] * wa1, t1 * H, b[1] * wa1);
        placeBar(A0, B1);
        placeBar(B0, A1);
        // horizontal rib at the bottom of the slice
        placeBar(A0, B0);
      }
    }
    // upper-section internal bracing (above top platform)
    for (let lvl = 0; lvl < 6; lvl++) {
      const t0 = L3 + (lvl / 6) * (1 - L3) * 0.85;
      const t1 = L3 + ((lvl + 1) / 6) * (1 - L3) * 0.85;
      const w0 = halfWidth(t0) * 0.55;
      const w1 = halfWidth(t1) * 0.55;
      for (const [sx, sz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
        placeBar(
          new TH.Vector3(sx * w0, t0 * H, sz * w0),
          new TH.Vector3(-sx * w1, t1 * H, -sz * w1)
        );
      }
    }
    bars.count = bi;
    bars.instanceMatrix.needsUpdate = true;
    tower.add(bars);

    // --- 4 grand arches at the base -----------------------------
    {
      const archMat = ironMat;
      for (let i = 0; i < 4; i++) {
        const angle = i * Math.PI / 2;
        const r = 5.1;
        const arch = new TH.TorusGeometry(r, 0.32, 8, 22, Math.PI);
        const ma = new TH.Mesh(arch, archMat);
        ma.position.set(0, 0.4, 0);
        ma.rotation.y = angle;
        tower.add(ma);
      }
    }

    // --- Platforms ----------------------------------------------
    function platform(yT, halfW, thickness, oversize) {
      const w = halfW * 2 + (oversize || 0.6);
      const g = new TH.BoxGeometry(w, thickness, w);
      const m = new TH.Mesh(g, deckMat);
      m.position.y = yT * H;
      return m;
    }
    tower.add(platform(L1, halfWidth(L1), 0.55, 1.0));
    tower.add(platform(L2, halfWidth(L2), 0.45, 0.7));
    tower.add(platform(L3, halfWidth(L3), 0.35, 0.4));

    // Edge rims on top of platforms (lighter)
    function rim_(yT, halfW, oversize) {
      const w = halfW * 2 + (oversize || 0.7);
      const g = new TH.BoxGeometry(w, 0.16, w);
      const m = new TH.Mesh(g, ironMat);
      m.position.y = yT * H + 0.35;
      return m;
    }
    tower.add(rim_(L1, halfWidth(L1), 1.1));
    tower.add(rim_(L2, halfWidth(L2), 0.8));

    // --- Top observation cabin + antenna ------------------------
    {
      const cabin = new TH.CylinderGeometry(0.55, 0.55, 1.0, 10);
      const mc = new TH.Mesh(cabin, deckMat);
      mc.position.y = L3 * H + 0.7;
      tower.add(mc);
      const mast = new TH.CylinderGeometry(0.18, 0.42, 2.6, 8);
      const mm = new TH.Mesh(mast, ironMat);
      mm.position.y = L3 * H + 2.1;
      tower.add(mm);
      const ant = new TH.CylinderGeometry(0.05, 0.18, 3.0, 8);
      const ma = new TH.Mesh(ant, ironMat);
      ma.position.y = L3 * H + 4.2;
      tower.add(ma);
      const tip = new TH.SphereGeometry(0.12, 10, 8);
      const mt = new TH.Mesh(tip, ironMat);
      mt.position.y = L3 * H + 5.85;
      tower.add(mt);
    }

    // Center vertically in the view
    tower.position.y = -15.5;
    scene.add(tower);

    // --- Soft ground glow under the tower -----------------------
    {
      const g = new TH.CircleGeometry(9, 32);
      const m = new TH.MeshBasicMaterial({
        color: 0x6E5BFF, transparent: true, opacity: 0.18,
      });
      const disc = new TH.Mesh(g, m);
      disc.rotation.x = -Math.PI / 2;
      disc.position.y = -15.5;
      scene.add(disc);
    }

    // --- Camera placement ---------------------------------------
    // z=90 (was 62) opens the vertical visible range to ~41 units; tower
    // height ~33 → ~4 units of margin top and bottom, no more cropping.
    camera.position.set(0, 6, 90);
    camera.lookAt(0, 1, 0);

    // --- Try to upgrade to a real GLB model ---------------------
    let rotationTarget = tower;
    if (window.GLTFLoader) {
      const loader = new window.GLTFLoader();
      const url = window.WALKTOVISIT_EIFFEL_URL || "assets/scene.gltf";
      loader.load(
        url,
        (gltf) => {
          const model = gltf.scene || gltf.scenes?.[0];
          if (!model) return;
          const TARGET_HEIGHT = 28; // smaller envelope so antenna doesn't crop
          const bbox = new TH.Box3().setFromObject(model);
          const size = new TH.Vector3();
          bbox.getSize(size);
          const scale = TARGET_HEIGHT / Math.max(0.001, size.y);
          model.scale.setScalar(scale);
          const bbox2 = new TH.Box3().setFromObject(model);
          const center2 = new TH.Vector3();
          bbox2.getCenter(center2);
          model.position.x -= center2.x;
          model.position.z -= center2.z;
          model.position.y -= bbox2.min.y;
          model.position.y += -13;
          tower.visible = false;
          scene.add(model);
          rotationTarget = model;
          renderOnce();
        },
        undefined,
        () => { /* keep procedural fallback */ }
      );
    }

    // Render the procedural scene once now — visible until GLB loads.
    renderOnce();

    // --- Rotation loop, paused during scroll & when off-screen ---
    let raf = 0;
    let running = true;
    let last = performance.now();
    function tick(now) {
      if (!running) return;
      // Pause rendering during active scroll (frees compositor budget).
      if (document.body.hasAttribute("data-scrolling")) {
        last = now;
        raf = requestAnimationFrame(tick);
        return;
      }
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      rotationTarget.rotation.y += dt * 0.22; // slow, continuous
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    raf = requestAnimationFrame(tick);

    const io = new IntersectionObserver((entries) => {
      for (const e of entries) {
        if (e.isIntersecting && !running) {
          running = true;
          last = performance.now();
          raf = requestAnimationFrame(tick);
        } else if (!e.isIntersecting && running) {
          running = false;
          cancelAnimationFrame(raf);
        }
      }
    }, { threshold: 0.01 });
    io.observe(canvas);

    return () => {
      running = false;
      cancelAnimationFrame(raf);
      io.disconnect();
      ro.disconnect();
      scene.traverse((o) => {
        if (o.geometry) o.geometry.dispose();
        if (o.material) {
          if (Array.isArray(o.material)) o.material.forEach((m) => m.dispose());
          else o.material.dispose();
        }
      });
      renderer.dispose();
    };
  }, []);

  return (
    <div className="eiffel-3d">
      <canvas ref={canvasRef} />
    </div>
  );
}

/* legacy 3D component — kept but unused */
function HeroEiffel3D_unused() {
  const canvasRef = useRef(null);
  useEffect(() => {
    const TH = window.THREE;
    if (!TH || !canvasRef.current) return;
    const canvas = canvasRef.current;

    const renderer = new TH.WebGLRenderer({ canvas, alpha: true, antialias: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.outputColorSpace = TH.SRGBColorSpace;

    const scene = new TH.Scene();
    const camera = new TH.PerspectiveCamera(30, 1, 0.1, 400);

    function resize() {
      const w = canvas.clientWidth || canvas.parentElement.clientWidth;
      const h = canvas.clientHeight || canvas.parentElement.clientHeight;
      renderer.setSize(w, h, false);
      camera.aspect = w / Math.max(1, h);
      camera.updateProjectionMatrix();
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    // Lights — hero-grade: warm key + indigo fill + cool rim
    scene.add(new TH.AmbientLight(0xffffff, 0.55));
    const key = new TH.DirectionalLight(0xfff4dd, 1.15);
    key.position.set(22, 32, 18); scene.add(key);
    const fill = new TH.DirectionalLight(0x9C8CFF, 0.7);
    fill.position.set(-20, 12, -16); scene.add(fill);
    const rim = new TH.DirectionalLight(0xffffff, 0.35);
    rim.position.set(0, 6, -28); scene.add(rim);

    // ----- Eiffel geometry -----
    const group = new TH.Group();
    const m = new TH.MeshStandardMaterial({ color: 0x4B5572, roughness: 0.45, metalness: 0.55 });

    const BASE_SPREAD = 7.5;
    const P1_SPREAD = 3.3, P1_Y = 9;
    const P2_SPREAD = 1.8, P2_Y = 16;
    const P3_SPREAD = 0.6, P3_Y = 24;
    const TIP_Y = 31.5;

    const upY = new TH.Vector3(0, 1, 0);
    function bar(baseC, topC, rBottom, rTop, sides = 4, mat = m) {
      const mid = baseC.clone().add(topC).multiplyScalar(0.5);
      const h = baseC.distanceTo(topC);
      const dir = topC.clone().sub(baseC).normalize();
      const mesh = new TH.Mesh(new TH.CylinderGeometry(rTop, rBottom, h, sides, 1), mat);
      mesh.position.copy(mid);
      mesh.quaternion.setFromUnitVectors(upY, dir);
      return mesh;
    }

    // 4 splaying legs (square cross-section, tapered)
    for (const dx of [-1, 1]) {
      for (const dz of [-1, 1]) {
        const baseC = new TH.Vector3(dx * BASE_SPREAD, 0, dz * BASE_SPREAD);
        const topC  = new TH.Vector3(dx * P1_SPREAD, P1_Y, dz * P1_SPREAD);
        const leg = bar(baseC, topC, 1.5, 0.55, 4);
        leg.rotation.y += Math.PI / 4; // diamond cross-section facing outward
        group.add(leg);
      }
    }

    // Mid lattice X-bracing between legs at mid-height
    function brace(p1, p2) {
      const b = bar(p1, p2, 0.08, 0.08, 4);
      return b;
    }
    for (const sideAxis of ["x", "z"]) {
      for (const sign of [-1, 1]) {
        // For side facing +x (sign=1, sideAxis='x'): legs at (1, -1) and (1, 1) in (x,z)
        // For side -x: (-1,-1) and (-1,1). Etc.
        const a = sideAxis === "x" ? new TH.Vector3(sign * BASE_SPREAD, 0, -BASE_SPREAD)
                                   : new TH.Vector3(-BASE_SPREAD, 0, sign * BASE_SPREAD);
        const b = sideAxis === "x" ? new TH.Vector3(sign * BASE_SPREAD, 0,  BASE_SPREAD)
                                   : new TH.Vector3( BASE_SPREAD, 0, sign * BASE_SPREAD);
        const aT = sideAxis === "x" ? new TH.Vector3(sign * P1_SPREAD, P1_Y, -P1_SPREAD)
                                    : new TH.Vector3(-P1_SPREAD, P1_Y, sign * P1_SPREAD);
        const bT = sideAxis === "x" ? new TH.Vector3(sign * P1_SPREAD, P1_Y,  P1_SPREAD)
                                    : new TH.Vector3( P1_SPREAD, P1_Y, sign * P1_SPREAD);
        // Mid-side horizontal beam (suggests platform between tiers)
        const lowMid = a.clone().add(b).multiplyScalar(0.5).lerp(aT.clone().add(bT).multiplyScalar(0.5), 0.55);
        group.add(brace(a.clone().lerp(aT, 0.55), b.clone().lerp(bT, 0.55)));
        // Two diagonals forming X
        group.add(brace(a, bT));
        group.add(brace(b, aT));
        void lowMid;
      }
    }

    // Big arch under platform 1, one per side, in the side plane
    function archMesh(rotY) {
      const radius = BASE_SPREAD * 0.86;
      const torus = new TH.TorusGeometry(radius, 0.22, 6, 16, Math.PI);
      const arch = new TH.Mesh(torus, m);
      arch.position.y = 0;
      arch.rotation.y = rotY;
      return arch;
    }
    group.add(archMesh(0));
    group.add(archMesh(Math.PI));
    group.add(archMesh(Math.PI / 2));
    group.add(archMesh(-Math.PI / 2));

    // Platform 1 (square plate)
    const p1Plate = new TH.Mesh(new TH.BoxGeometry(P1_SPREAD * 2 + 1.2, 0.7, P1_SPREAD * 2 + 1.2), m);
    p1Plate.position.y = P1_Y; group.add(p1Plate);
    // P1 rim line
    const p1Top = new TH.Mesh(new TH.BoxGeometry(P1_SPREAD * 2 + 1.6, 0.18, P1_SPREAD * 2 + 1.6), m);
    p1Top.position.y = P1_Y + 0.45; group.add(p1Top);

    // 4 columns P1 -> P2
    for (const dx of [-1, 1]) {
      for (const dz of [-1, 1]) {
        const a = new TH.Vector3(dx * P1_SPREAD, P1_Y, dz * P1_SPREAD);
        const b = new TH.Vector3(dx * P2_SPREAD, P2_Y, dz * P2_SPREAD);
        const col = bar(a, b, 0.45, 0.28, 4);
        col.rotation.y += Math.PI / 4;
        group.add(col);
      }
    }
    // Mid-mid X bracing
    for (const sideAxis of ["x", "z"]) {
      for (const sign of [-1, 1]) {
        const a = sideAxis === "x" ? new TH.Vector3(sign * P1_SPREAD, P1_Y, -P1_SPREAD)
                                   : new TH.Vector3(-P1_SPREAD, P1_Y, sign * P1_SPREAD);
        const b = sideAxis === "x" ? new TH.Vector3(sign * P1_SPREAD, P1_Y,  P1_SPREAD)
                                   : new TH.Vector3( P1_SPREAD, P1_Y, sign * P1_SPREAD);
        const aT = sideAxis === "x" ? new TH.Vector3(sign * P2_SPREAD, P2_Y, -P2_SPREAD)
                                    : new TH.Vector3(-P2_SPREAD, P2_Y, sign * P2_SPREAD);
        const bT = sideAxis === "x" ? new TH.Vector3(sign * P2_SPREAD, P2_Y,  P2_SPREAD)
                                    : new TH.Vector3( P2_SPREAD, P2_Y, sign * P2_SPREAD);
        group.add(brace(a, bT));
        group.add(brace(b, aT));
      }
    }

    // Platform 2
    const p2Plate = new TH.Mesh(new TH.BoxGeometry(P2_SPREAD * 2 + 0.8, 0.5, P2_SPREAD * 2 + 0.8), m);
    p2Plate.position.y = P2_Y; group.add(p2Plate);

    // 4 narrow columns P2 -> P3
    for (const dx of [-1, 1]) {
      for (const dz of [-1, 1]) {
        const a = new TH.Vector3(dx * P2_SPREAD, P2_Y, dz * P2_SPREAD);
        const b = new TH.Vector3(dx * P3_SPREAD, P3_Y, dz * P3_SPREAD);
        const col = bar(a, b, 0.28, 0.14, 4);
        col.rotation.y += Math.PI / 4;
        group.add(col);
      }
    }
    // Few diagonal braces in upper section
    for (const sideAxis of ["x", "z"]) {
      for (const sign of [-1, 1]) {
        const a = sideAxis === "x" ? new TH.Vector3(sign * P2_SPREAD, P2_Y, -P2_SPREAD)
                                   : new TH.Vector3(-P2_SPREAD, P2_Y, sign * P2_SPREAD);
        const b = sideAxis === "x" ? new TH.Vector3(sign * P2_SPREAD, P2_Y,  P2_SPREAD)
                                   : new TH.Vector3( P2_SPREAD, P2_Y, sign * P2_SPREAD);
        const aT = sideAxis === "x" ? new TH.Vector3(sign * P3_SPREAD, P3_Y, -P3_SPREAD)
                                    : new TH.Vector3(-P3_SPREAD, P3_Y, sign * P3_SPREAD);
        const bT = sideAxis === "x" ? new TH.Vector3(sign * P3_SPREAD, P3_Y,  P3_SPREAD)
                                    : new TH.Vector3( P3_SPREAD, P3_Y, sign * P3_SPREAD);
        group.add(brace(a, bT));
        group.add(brace(b, aT));
      }
    }

    // Platform 3 (observation deck) + small structure
    const p3Plate = new TH.Mesh(new TH.BoxGeometry(P3_SPREAD * 2 + 0.5, 0.4, P3_SPREAD * 2 + 0.5), m);
    p3Plate.position.y = P3_Y; group.add(p3Plate);
    const obsCage = new TH.Mesh(new TH.CylinderGeometry(0.55, 0.55, 1.2, 8), m);
    obsCage.position.y = P3_Y + 0.8; group.add(obsCage);

    // Mast + antenna
    const mast = new TH.Mesh(new TH.CylinderGeometry(0.18, 0.45, 3.2, 6), m);
    mast.position.y = P3_Y + 2.8; group.add(mast);
    const ant = new TH.Mesh(new TH.CylinderGeometry(0.06, 0.18, 3.0, 6), m);
    ant.position.y = TIP_Y - 1.5; group.add(ant);
    const tip = new TH.Mesh(new TH.SphereGeometry(0.18, 8, 6), m);
    tip.position.y = TIP_Y + 0.4; group.add(tip);

    // Center the model vertically in the view
    group.position.y = -15;
    scene.add(group);

    // Place camera
    camera.position.set(0, 6, 56);
    camera.lookAt(0, 0, 0);

    // Animation: sine-sweep Y rotation + scroll-driven X tilt + scale
    let raf;
    const start = performance.now();
    const PERIOD = 11000;
    const AMPLITUDE = Math.PI * 0.30; // ~54°
    function tick(now) {
      const elapsed = now - start;
      const phase = (elapsed % PERIOD) / PERIOD;
      group.rotation.y = Math.sin(phase * Math.PI * 2) * AMPLITUDE + elapsed * 0.00018;
      const sy = window.scrollY || 0;
      const tilt = -0.10 + Math.min(0.45, sy * 0.0010);
      group.rotation.x = tilt;
      const scale = Math.max(0.86, 1 - sy * 0.00045);
      group.scale.setScalar(scale);
      group.position.y = -15 + Math.min(8, sy * 0.018);
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      scene.traverse((o) => {
        if (o.geometry) o.geometry.dispose();
        if (o.material) o.material.dispose();
      });
      renderer.dispose();
    };
  }, []);

  return (
    <div className="eiffel-3d">
      <canvas ref={canvasRef} />
    </div>
  );
}

function EiffelHero() {
  const [scrolled, setScrolled] = useState(false);

  // Track scroll only for the nav state — the 3D rotation/tilt is handled inside HeroEiffel3D.
  useEffect(() => {
    const onScroll = () => setScrolled((window.scrollY || 0) > 40);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // ambient particles
  const particles = Array.from({ length: 22 }).map((_, i) => {
    const left = (i * 137) % 100;
    const top = (i * 53 + 17) % 100;
    const delay = (i * 0.31) % 4;
    const dur = 4 + ((i * 0.7) % 4);
    return (
      <span
        key={i}
        style={{
          left: `${left}%`,
          top: `${top}%`,
          animation: `floaty ${dur}s ease-in-out ${delay}s infinite`,
          width: 3 + (i % 3) * 2,
          height: 3 + (i % 3) * 2,
          opacity: 0.25 + ((i * 7) % 5) * 0.12,
        }}
      />
    );
  });

  return (
    <>
      <nav className={"nav " + (scrolled ? "scrolled" : "")}>
        <div className="logo">
          <div className="logo-mark">
            <svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 2 L4 7 V17 L12 22 L20 17 V7 Z" />
              <path d="M12 2 V22 M4 7 L20 17 M20 7 L4 17" opacity="0.6" />
            </svg>
          </div>
          walktovisit
        </div>
        <div className="nav-links">
          <a href="#tour">How it works</a>
          <a href="#pricing">Pricing</a>
          <a href="#languages">Languages</a>
          <a href="#download">Download</a>
        </div>
        <div className="nav-cta">
          <div className="lang-pill">🌐 EN</div>
          <a className="btn btn-primary" href="#download">
            Get the app
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 5l7 7-7 7" /></svg>
          </a>
        </div>
      </nav>

      <header className="hero">
        <div className="wrap hero-grid">
          <div className="hero-copy">
            <div className="eyebrow"><span className="dot" /> Self-guided audio tours · Paris, Rome, Berlin…</div>
            <h1 className="display">
              A pocket guide<br />
              for <em>every</em><br />
              city.
            </h1>
            <p className="lede">
              Walk Paris, Rome, Berlin, Amsterdam at your own pace — with a turn-by-turn 3D map,
              studio-grade narrated stops, and souvenirs you actually keep: the memories.
            </p>
            <div className="hero-ctas">
              <a className="btn btn-primary" href="#download">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M17.5 12.5c0-3 2.5-4.4 2.6-4.5-1.4-2.1-3.7-2.3-4.5-2.4-1.9-.2-3.7 1.1-4.7 1.1s-2.5-1.1-4.1-1.1c-2.1 0-4 1.2-5.1 3.1-2.2 3.8-.6 9.3 1.5 12.4 1 1.5 2.3 3.1 3.9 3.1 1.6-.1 2.2-1 4.1-1s2.4 1 4.1 1c1.7 0 2.8-1.5 3.8-3 1.2-1.7 1.7-3.4 1.7-3.5-.1 0-3.3-1.3-3.3-5.2zM14.4 4.5c.9-1 1.4-2.5 1.3-3.9-1.2.1-2.7.8-3.6 1.8-.8.9-1.5 2.3-1.3 3.7 1.3.1 2.7-.7 3.6-1.6z" /></svg>
                Download for iOS
              </a>
              <a className="btn btn-ghost" href="#tour">See a Paris tour →</a>
            </div>

            <div className="hero-meta">
              <div className="m"><b>4.99€</b><span>per city circuit</span></div>
              <div className="m"><b>8</b><span>languages</span></div>
              <div className="m"><b>120+</b><span>stops mapped</span></div>
            </div>
          </div>

          <div className="eiffel-stage">
            <div className="eiffel-particles">{particles}</div>
            <div className="eiffel-floor" />
            <HeroEiffel3D />

            <div className="eiffel-card tl">
              <div className="blob">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="10" r="3" /><path d="M12 22s7-7.5 7-13a7 7 0 0 0-14 0c0 5.5 7 13 7 13z" /></svg>
              </div>
              <div className="meta">Tour Eiffel<small>Stop 4 of 12 · Paris</small></div>
            </div>
            <div className="eiffel-card br">
              <div className="blob">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 18V8a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10" /><circle cx="7" cy="18" r="3" /><circle cx="17" cy="18" r="3" /><path d="M17 9h2l2 4v3h-2" /></svg>
              </div>
              <div className="meta">Audio guide ready<small>1:17 · Français · Streamed</small></div>
            </div>
          </div>
        </div>
      </header>
    </>
  );
}

window.EiffelHero = EiffelHero;
