/* Hero.jsx — DARK cosmic hero with 3 orbiting planets (수학·사고력·금융).
   Visual engine inherited verbatim from sriki-design-system/marketing/Hero.jsx
   (Three.js procedural planets, halos, orbits, dust). Only the foreground
   copy is rewritten bilingual KR-priority. Rationale: spec §2.1.
   The planet metaphor IS the SRIKI brand identity — do not redesign. */

const Hero = ({ onPrimary, onSecondary }) => {
  const t = window.useT();
  const { lang } = React.useContext(window.LangContext);
  const canvasRef     = React.useRef(null);
  const labelLayerRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    /* Three.js is now loaded as an ES module (see index.html import map),
       so window.THREE is populated asynchronously. Wait for the ready event
       if the post-processing addons aren't attached yet. */
    let cleanupFn = null;
    let disposed  = false;

    const trySetup = () => {
      if (disposed || cleanupFn) return;
      if (!window.THREE || !window.THREE.EffectComposer) return;
      cleanupFn = setup();
    };

    const setup = () => {
    const THREE = window.THREE;

    const renderer = new THREE.WebGLRenderer({
      canvas, antialias: true, alpha: true,
      powerPreference: 'high-performance',
    });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setClearColor(0x000000, 0);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.10;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 200);
    const CAM_BASE = new THREE.Vector3(0, 6.4, 7.1);
    camera.position.copy(CAM_BASE);
    camera.lookAt(0, 0, 0);

    const key  = new THREE.DirectionalLight(0xfff0d0, 1.85); key.position.set(5, 6, 4); scene.add(key);
    const fill = new THREE.DirectionalLight(0xa78bfa, 0.55); fill.position.set(-4, 2, -2); scene.add(fill);
    scene.add(new THREE.AmbientLight(0x1a153a, 0.55));

    const ORBITAL_PERIOD = 95;
    const PLANETS = [
      { id: 'math',     label: '수학',   sub: 'Math',     size: 1.30,
        baseHex: '#A78BFA', deepHex: '#5B21B6', rimHex: '#DDD6FE', haloColor: '#A78BFA',
        pattern: 'wireframe',
        orbitR: 1.85, orbitTilt:  -8, orbitPeriod: ORBITAL_PERIOD, orbitPhase: 0.0,
        moons: [
          { r: 0.55, period: 22, phase: 0.0,  tilt: 28, size: 0.10, color: '#DDD6FE' },
          { r: 0.78, period: 38, phase: 2.1,  tilt: -14, size: 0.07, color: '#A78BFA' },
        ] },
      { id: 'thinking', label: '사고력', sub: 'Thinking', size: 0.85,
        baseHex: '#3B82F6', deepHex: '#1E3A8A', rimHex: '#60A5FA', haloColor: '#3B82F6',
        pattern: 'clouds',
        orbitR: 2.85, orbitTilt:  18, orbitPeriod: ORBITAL_PERIOD, orbitPhase: Math.PI * (2/3),
        moons: [{ r: 0.42, period: 18, phase: 1.0, tilt: 22, size: 0.08, color: '#93C5FD' }] },
      { id: 'finance',  label: '금융',   sub: 'Finance',  size: 1.05,
        baseHex: '#FCD34D', deepHex: '#92400E', rimHex: '#FEF3C7', haloColor: '#FCD34D',
        pattern: 'bands',
        orbitR: 3.65, orbitTilt: -22, orbitPeriod: ORBITAL_PERIOD, orbitPhase: Math.PI * (4/3),
        moons: [
          { r: 0.50, period: 20, phase: 0.6, tilt: 16,  size: 0.09, color: '#FEF3C7' },
          { r: 0.72, period: 32, phase: 3.2, tilt: -28, size: 0.06, color: '#FCD34D' },
        ] },
    ];

    function planetPos(p, t, out = new THREE.Vector3()) {
      const θ = p.orbitPhase + (t / p.orbitPeriod) * Math.PI * 2;
      const tilt = p.orbitTilt * Math.PI / 180;
      const rx = Math.cos(θ) * p.orbitR;
      const rz = Math.sin(θ) * p.orbitR;
      out.set(rx, rz * Math.sin(tilt), rz * Math.cos(tilt));
      return out;
    }

    function makeNoise(seed) {
      let s = seed | 0;
      const rand = () => { s = (s * 9301 + 49297) | 0; s = s % 233280; return (s < 0 ? s + 233280 : s) / 233280; };
      const perm = new Uint8Array(512), p0 = new Uint8Array(256);
      for (let i = 0; i < 256; i++) p0[i] = i;
      for (let i = 255; i > 0; i--) { const j = (rand() * (i + 1)) | 0; [p0[i], p0[j]] = [p0[j], p0[i]]; }
      for (let i = 0; i < 512; i++) perm[i] = p0[i & 255];
      const fade = t => t*t*t*(t*(t*6-15)+10);
      const lerp = (a,b,t) => a + t*(b-a);
      const grad = (h, x, y) => { switch (h & 3) { case 0: return x+y; case 1: return -x+y; case 2: return x-y; default: return -x-y; } };
      const n2 = (x, y) => {
        const X = Math.floor(x) & 255, Y = Math.floor(y) & 255;
        x -= Math.floor(x); y -= Math.floor(y);
        const u = fade(x), v = fade(y);
        const A = perm[X] + Y, B = perm[X + 1] + Y;
        return lerp(lerp(grad(perm[A], x, y), grad(perm[B], x-1, y), u),
                    lerp(grad(perm[A+1], x, y-1), grad(perm[B+1], x-1, y-1), u), v);
      };
      return (x, y, octaves = 5, lac = 2.0, gain = 0.5) => {
        let sum = 0, amp = 1, freq = 1, norm = 0;
        for (let i = 0; i < octaves; i++) { sum += amp * n2(x * freq, y * freq); norm += amp; amp *= gain; freq *= lac; }
        return sum / norm;
      };
    }
    const hexToRgb = hex => { const h = hex.replace('#',''); return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)]; };
    const mixRgb = (a, b, t) => [Math.round(a[0]+(b[0]-a[0])*t), Math.round(a[1]+(b[1]-a[1])*t), Math.round(a[2]+(b[2]-a[2])*t)];

    function texWireframe(base, rim, deep) {
      /* Math planet surface — same procedural violet body that backed
         the wireframe globe (fbm-noise tinted gradient + subtle rim-hued
         nebulae), but with the grid lines and constellation node dots
         removed. Result: a smooth lavender sphere with soft cloud-like
         brightness variation, no holographic data-sphere overlay. */
      const W = 2048, H = 1024;
      const c = document.createElement('canvas'); c.width = W; c.height = H;
      const ctx = c.getContext('2d');
      const baseRgb = hexToRgb(base), deepRgb = hexToRgb(deep);
      const darkBase = mixRgb(deepRgb, [10, 5, 25], 0.35);
      const fbm = makeNoise(1337);
      const img = ctx.createImageData(W, H); const d = img.data;
      for (let y = 0; y < H; y++) {
        const v = y / H; const polar = 1.0 - Math.abs(v - 0.5) * 1.8;
        for (let x = 0; x < W; x++) {
          const u = x / W; const a = u * Math.PI * 2;
          const n = (fbm(Math.cos(a) * 2.4, v * 2.0 + Math.sin(a) * 0.6, 5) + 1) * 0.5;
          const surface = mixRgb(darkBase, baseRgb, Math.max(0, Math.min(1, polar * 0.70 + n * 0.30 - 0.12)));
          const i = (y * W + x) * 4; d[i] = surface[0]; d[i+1] = surface[1]; d[i+2] = surface[2]; d[i+3] = 255;
        }
      }
      ctx.putImageData(img, 0, 0);

      /* Subtle rim-hued highlight nebulae — soft brightness variation
         that reads as cloud-like surface detail without the grid. */
      ctx.globalCompositeOperation = 'lighter';
      for (let i = 0; i < 22; i++) {
        const x = Math.random()*W, y = Math.random()*H, r = 80+Math.random()*200;
        const rg = ctx.createRadialGradient(x, y, 0, x, y, r);
        rg.addColorStop(0, rim+'33'); rg.addColorStop(1, rim+'00');
        ctx.fillStyle = rg; ctx.beginPath(); ctx.ellipse(x, y, r, r*0.55, 0, 0, Math.PI*2); ctx.fill();
      }
      ctx.globalCompositeOperation = 'source-over';

      const tex = new THREE.CanvasTexture(c); tex.colorSpace = THREE.SRGBColorSpace; tex.anisotropy = 16; tex.wrapS = THREE.RepeatWrapping;
      return tex;
    }

    function texClouds(base, deep, rim) {
      const W = 2048, H = 1024;
      const c = document.createElement('canvas'); c.width = W; c.height = H;
      const ctx = c.getContext('2d');
      const baseRgb = hexToRgb(base), deepRgb = hexToRgb(deep), rimRgb = hexToRgb(rim);
      const polarRgb = [200, 220, 245];
      const fbm = makeNoise(424242);
      const img = ctx.createImageData(W, H); const d = img.data;
      for (let y = 0; y < H; y++) {
        const v = y / H; const lat = (v - 0.5) * Math.PI;
        const polar = Math.pow(Math.abs(Math.sin(lat)), 2.5);
        for (let x = 0; x < W; x++) {
          const u = x / W; const a = u * Math.PI * 2;
          const n = (fbm(Math.cos(a) * 1.6, v * 1.4 + Math.sin(a) * 0.4, 5) + 1) * 0.5;
          let ocean = mixRgb(deepRgb, baseRgb, n * 0.85 + 0.05);
          ocean = mixRgb(ocean, rimRgb, Math.max(0, n - 0.6) * 0.8);
          ocean = mixRgb(ocean, polarRgb, polar * 0.85);
          const i = (y * W + x) * 4; d[i] = ocean[0]; d[i+1] = ocean[1]; d[i+2] = ocean[2]; d[i+3] = 255;
        }
      }
      ctx.putImageData(img, 0, 0);
      const cFbm = makeNoise(99);
      const cloudImg = ctx.createImageData(W, H); const cd = cloudImg.data;
      for (let y = 0; y < H; y++) {
        const v = y / H; const latBoost = Math.pow(1.0 - Math.abs(v - 0.5) * 2.0, 0.5);
        for (let x = 0; x < W; x++) {
          const u = x / W; const a = u * Math.PI * 2;
          const n1 = (cFbm(Math.cos(a) * 4.0, v * 3.2 + Math.sin(a) * 1.0, 6) + 1) * 0.5;
          const n2 = (cFbm(Math.cos(a) * 8.0 + 5, v * 6.0 + Math.sin(a) * 2.0, 4) + 1) * 0.5;
          const cloud = Math.pow(Math.max(0, n1 * 0.7 + n2 * 0.3 - 0.42), 1.4) * 2.2 * latBoost;
          const a8 = Math.min(255, cloud * 255);
          const i = (y * W + x) * 4; cd[i] = 255; cd[i+1] = 255; cd[i+2] = 255; cd[i+3] = a8;
        }
      }
      const tmp = document.createElement('canvas'); tmp.width = W; tmp.height = H;
      tmp.getContext('2d').putImageData(cloudImg, 0, 0);
      ctx.globalAlpha = 0.85; ctx.drawImage(tmp, 0, 0); ctx.globalAlpha = 1.0;
      const tex = new THREE.CanvasTexture(c); tex.colorSpace = THREE.SRGBColorSpace; tex.anisotropy = 16; tex.wrapS = THREE.RepeatWrapping;
      return tex;
    }

    function texBands(base, deep, rim) {
      const W = 2048, H = 1024;
      const c = document.createElement('canvas'); c.width = W; c.height = H;
      const ctx = c.getContext('2d');
      const palette = [hexToRgb('#FEF3C7'),hexToRgb('#FCD34D'),hexToRgb('#FBBF24'),hexToRgb('#FDE68A'),hexToRgb('#F59E0B'),hexToRgb('#B45309'),hexToRgb('#FCD34D'),hexToRgb('#FEF3C7')];
      const fbm = makeNoise(7777);
      const img = ctx.createImageData(W, H); const d = img.data;
      for (let y = 0; y < H; y++) {
        const v = y / H; const offset = fbm(2.0, v * 12.0, 4) * 0.04;
        const latIdx = (v + offset) * palette.length;
        const i0 = Math.floor(latIdx) % palette.length;
        const i1 = (i0 + 1) % palette.length;
        const tBand = latIdx - Math.floor(latIdx);
        for (let x = 0; x < W; x++) {
          const u = x / W; const a = u * Math.PI * 2;
          const streak = (fbm(Math.cos(a) * 14.0, v * 3.5 + Math.sin(a) * 2.0, 4) + 1) * 0.5;
          let color = mixRgb(palette[i0], palette[i1], tBand);
          color = mixRgb(color, [255, 248, 220], (streak - 0.5) * 0.45);
          const i = (y * W + x) * 4; d[i] = color[0]; d[i+1] = color[1]; d[i+2] = color[2]; d[i+3] = 255;
        }
      }
      ctx.putImageData(img, 0, 0);
      ctx.globalCompositeOperation = 'multiply';
      const sx = W * 0.62, sy = H * 0.62, sr = 140;
      const sg = ctx.createRadialGradient(sx, sy, 0, sx, sy, sr);
      sg.addColorStop(0, '#8a4a10'); sg.addColorStop(0.7, '#c87b2a99'); sg.addColorStop(1, '#FCD34D00');
      ctx.fillStyle = sg; ctx.beginPath(); ctx.ellipse(sx, sy, sr * 1.6, sr * 0.7, 0, 0, Math.PI*2); ctx.fill();
      ctx.globalCompositeOperation = 'source-over';
      const tex = new THREE.CanvasTexture(c); tex.colorSpace = THREE.SRGBColorSpace; tex.anisotropy = 16; tex.wrapS = THREE.RepeatWrapping;
      return tex;
    }

    function texHalo(hex) {
      const c = document.createElement('canvas'); c.width = c.height = 256;
      const ctx = c.getContext('2d');
      const g = ctx.createRadialGradient(128,128,8,128,128,128);
      g.addColorStop(0, hex+'bb'); g.addColorStop(0.35, hex+'55');
      g.addColorStop(0.7, hex+'18'); g.addColorStop(1, hex+'00');
      ctx.fillStyle = g; ctx.fillRect(0,0,256,256);
      const tex = new THREE.CanvasTexture(c); tex.colorSpace = THREE.SRGBColorSpace; return tex;
    }

    const planetGeom = new THREE.SphereGeometry(1.0, 96, 96);
    const moonGeom   = new THREE.SphereGeometry(1.0, 24, 24);

    /* NASA-derived planet textures (public domain / CC BY 4.0).
       Earth: Three.js r160 example assets.
       Jupiter, Neptune: Solar System Scope (Wikimedia Commons mirror). */
    const texLoader = new THREE.TextureLoader();
    const loadSRGB = (url) => {
      const t = texLoader.load(url);
      t.colorSpace = THREE.SRGBColorSpace;
      t.anisotropy = 16;
      return t;
    };
    const loadLinear = (url) => {
      const t = texLoader.load(url);
      t.colorSpace = THREE.NoColorSpace;
      t.anisotropy = 16;
      return t;
    };

    /* Hue-shifted texture loader.
       Loads an image, draws it into a canvas, walks every pixel through
       an RGB↔HSL hue rotation, and exposes the result as a CanvasTexture.
       Used to turn Neptune (deep blue gas giant) into a "purple planet"
       for the math sphere — keeping all the real atmospheric detail
       (bands, polar darkening, subtle storms) while shifting the hue.
       Returns the Texture synchronously; pixels arrive after onload. */
    /* Luminance-gradient tint loader.
       Reads each pixel's perceived luminance (Rec. 709) and maps it onto
       a 3-stop color gradient: dark → mid → light. The source image's
       saturation/hue is discarded entirely, so the result is guaranteed
       to be in the gradient's color family no matter how desaturated
       the source is. Used for the math planet — we want a real-looking
       gas giant *and* a guaranteed vivid violet, which hue-shifting a
       cream-colored Saturn can never deliver. */
    const loadLumTintedSRGB = (url, darkHex, midHex, lightHex, contrast = 1.0, lumBoost = 1.0) => {
      const placeholder = document.createElement('canvas');
      placeholder.width = 2; placeholder.height = 2;
      const tex = new THREE.CanvasTexture(placeholder);
      tex.colorSpace = THREE.SRGBColorSpace;
      tex.anisotropy = 16;
      tex.wrapS = THREE.RepeatWrapping;

      const [dr, dg, db] = hexToRgb(darkHex);
      const [mr, mg, mb] = hexToRgb(midHex);
      const [lr, lg, lb] = hexToRgb(lightHex);

      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => {
        const W = img.width, H = img.height;
        placeholder.width = W; placeholder.height = H;
        const ctx = placeholder.getContext('2d');
        ctx.drawImage(img, 0, 0);
        const data = ctx.getImageData(0, 0, W, H);
        const d = data.data;
        for (let i = 0; i < d.length; i += 4) {
          /* Rec. 709 luminance — closer to perceptual brightness than
             a flat average. */
          let lum = (d[i] * 0.2126 + d[i+1] * 0.7152 + d[i+2] * 0.0722) / 255;
          lum = 0.5 + (lum - 0.5) * contrast;
          lum *= lumBoost;
          lum = Math.min(1, Math.max(0, lum));
          /* 3-stop gradient: dark→mid in lower half, mid→light in upper half. */
          let r, g, b;
          if (lum < 0.5) {
            const t = lum * 2;
            r = dr + (mr - dr) * t;
            g = dg + (mg - dg) * t;
            b = db + (mb - db) * t;
          } else {
            const t = (lum - 0.5) * 2;
            r = mr + (lr - mr) * t;
            g = mg + (lg - mg) * t;
            b = mb + (lb - mb) * t;
          }
          d[i]   = r | 0;
          d[i+1] = g | 0;
          d[i+2] = b | 0;
        }
        ctx.putImageData(data, 0, 0);
        tex.needsUpdate = true;
      };
      img.src = url;
      return tex;
    };

    const loadHueShiftedSRGB = (url, deltaHueDeg, satBoost = 1.0, lumBoost = 1.0, contrast = 1.0) => {
      const placeholder = document.createElement('canvas');
      placeholder.width = 2; placeholder.height = 2;
      const tex = new THREE.CanvasTexture(placeholder);
      tex.colorSpace = THREE.SRGBColorSpace;
      tex.anisotropy = 16;
      tex.wrapS = THREE.RepeatWrapping;

      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => {
        const W = img.width, H = img.height;
        placeholder.width = W; placeholder.height = H;
        const ctx = placeholder.getContext('2d');
        ctx.drawImage(img, 0, 0);
        const data = ctx.getImageData(0, 0, W, H);
        const d = data.data;
        const dh = deltaHueDeg / 360;
        for (let i = 0; i < d.length; i += 4) {
          let r = d[i] / 255, g = d[i+1] / 255, b = d[i+2] / 255;
          const mx = Math.max(r, g, b), mn = Math.min(r, g, b);
          let l = (mx + mn) * 0.5;
          let h, s;
          if (mx === mn) { h = 0; s = 0; }
          else {
            const dd = mx - mn;
            s = l > 0.5 ? dd / (2 - mx - mn) : dd / (mx + mn);
            switch (mx) {
              case r: h = (g - b) / dd + (g < b ? 6 : 0); break;
              case g: h = (b - r) / dd + 2; break;
              default: h = (r - g) / dd + 4;
            }
            h /= 6;
          }
          h = (h + dh) % 1; if (h < 0) h += 1;
          s = Math.min(1, s * satBoost);
          /* Contrast around the texture's natural midpoint — pulls bands
             and storm features out of an otherwise flat gas-giant base. */
          l = 0.5 + (l - 0.5) * contrast;
          l = Math.min(1, Math.max(0, l * lumBoost));
          if (s === 0) {
            r = g = b = l;
          } else {
            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            const p = 2 * l - q;
            const hue2rgb = (t) => {
              if (t < 0) t += 1; if (t > 1) t -= 1;
              if (t < 1/6) return p + (q - p) * 6 * t;
              if (t < 1/2) return q;
              if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
              return p;
            };
            r = hue2rgb(h + 1/3);
            g = hue2rgb(h);
            b = hue2rgb(h - 1/3);
          }
          d[i]   = (r * 255) | 0;
          d[i+1] = (g * 255) | 0;
          d[i+2] = (b * 255) | 0;
        }
        ctx.putImageData(data, 0, 0);
        tex.needsUpdate = true;
      };
      img.src = url;
      return tex;
    };

    const planets = PLANETS.map(p => {
      let mat;
      if (p.pattern === 'clouds') {
        /* Real Earth — diffuse, normal (terrain), and specular (ocean) maps.
           specular map → metalnessMap so oceans pick up sun glint while
           continents stay matte. Subtle base emissive in planet's rim hue
           keeps it consistent with the other two planets' palette. */
        const colorMap  = loadSRGB('assets/textures/earth_atmos_2048.jpg');
        const normalMap = loadLinear('assets/textures/earth_normal_2048.jpg');
        const specMap   = loadLinear('assets/textures/earth_specular_2048.jpg');
        mat = new THREE.MeshStandardMaterial({
          map:           colorMap,
          normalMap,
          normalScale:   new THREE.Vector2(1.2, 1.2),
          metalnessMap:  specMap,
          metalness:     1.0,
          roughness:     0.62,
          emissive:      new THREE.Color(p.rimHex),
          emissiveIntensity: 0.08,
        });
      } else if (p.pattern === 'bands') {
        /* Real Jupiter — Solar System Scope 2k texture (CC BY 4.0,
           NASA Cassini imagery). Gas giant bands and the Great Red Spot
           come for free; no extra normal/spec maps needed. */
        const colorMap = loadSRGB('assets/textures/jupiter_2k.jpg');
        mat = new THREE.MeshStandardMaterial({
          map: colorMap,
          roughness: 0.85, metalness: 0.0,
          emissive: new THREE.Color(p.rimHex), emissiveIntensity: 0.10,
        });
      } else {
        /* Math planet — abstract violet wireframe globe (grid + node dots
           at every intersection, constellation-style). Reads as an
           abstract data-sphere rather than a photographic celestial body,
           which suits "수학". texWireframe is the procedural texture
           defined above; it inherits the planet's deepHex/baseHex/rimHex
           palette so the violet identity is guaranteed. */
        const colorMap = texWireframe(p.baseHex, p.rimHex, p.deepHex);
        mat = new THREE.MeshStandardMaterial({
          map: colorMap,
          roughness: 0.55, metalness: 0.10,
          emissive: new THREE.Color(p.haloColor), emissiveIntensity: 0.18,
        });
      }
      const group = new THREE.Group(); scene.add(group);
      const mesh = new THREE.Mesh(planetGeom, mat); mesh.scale.setScalar(p.size); group.add(mesh);

      /* Planet rim/halo effects removed — per design feedback, focus on the
         planet itself with no surrounding glow. Fresnel atmosphere shell
         and additive halo sprite both removed. Tooltip hover still drives
         a subtle scale-up animation in animate(). */
      const moons = (p.moons || []).map(mDef => {
        const mMat = new THREE.MeshStandardMaterial({
          color: new THREE.Color(mDef.color),
          roughness: 0.5, metalness: 0.1,
          emissive: new THREE.Color(mDef.color),
          emissiveIntensity: 0.35,
        });
        const mMesh = new THREE.Mesh(moonGeom, mMat);
        mMesh.scale.setScalar(mDef.size); group.add(mMesh);
        return { def: mDef, mesh: mMesh };
      });
      return { def: p, group, mesh, moons,
               spinPhase: Math.random()*Math.PI*2,
               spinSpeed: 0.10 + Math.random()*0.06 };
    });

    /* Orbits — finer segments, lower opacity, thin warm-gold strokes. */
    PLANETS.forEach(p => {
      const SEG = 360;
      const positions = new Float32Array((SEG+1)*3);
      const tilt = p.orbitTilt * Math.PI / 180;
      for (let i=0; i<=SEG; i++) {
        const θ=(i/SEG)*Math.PI*2;
        const rx=Math.cos(θ)*p.orbitR, rz=Math.sin(θ)*p.orbitR;
        positions[i*3]=rx; positions[i*3+1]=rz*Math.sin(tilt); positions[i*3+2]=rz*Math.cos(tilt);
      }
      const g = new THREE.BufferGeometry();
      g.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      const m = new THREE.LineBasicMaterial({ color: 0xE89E2A, transparent: true, opacity: 0.32, depthWrite: false });
      scene.add(new THREE.Line(g, m));
    });

    /* Star dust — 4× density, color variation (warm/neutral/cool tones),
       size distribution favors many small stars + a few bright ones, and
       the fragment shader uses a tighter falloff for sharper pinpoints. */
    const stars = (() => {
      const COUNT = 1500;
      const positions = new Float32Array(COUNT*3);
      const sizes  = new Float32Array(COUNT);
      const phases = new Float32Array(COUNT);
      const colors = new Float32Array(COUNT*3);
      /* Three stellar color tones — warm K-type, neutral G/F, cool A/B —
         to break the "all-white pinpricks" look that screams "fake stars". */
      const palette = [
        [1.00, 0.94, 0.86], // warm
        [1.00, 0.98, 0.96], // neutral
        [0.88, 0.94, 1.00], // cool
      ];
      for (let i=0; i<COUNT; i++) {
        const u=Math.random(), v=Math.random();
        const θ=2*Math.PI*u, φ=Math.acos(2*v-1);
        const r=22+Math.random()*18;
        positions[i*3]   = r*Math.sin(φ)*Math.cos(θ);
        positions[i*3+1] = r*Math.sin(φ)*Math.sin(θ);
        positions[i*3+2] = r*Math.cos(φ);
        /* Power distribution — most stars small, ~3% are bright accents. */
        const bright = Math.random() < 0.03;
        sizes[i]  = bright ? (2.4 + Math.random()*1.6) : (0.5 + Math.random()*1.0);
        phases[i] = Math.random()*Math.PI*2;
        const col = palette[Math.floor(Math.random()*palette.length)];
        colors[i*3]   = col[0];
        colors[i*3+1] = col[1];
        colors[i*3+2] = col[2];
      }
      const g = new THREE.BufferGeometry();
      g.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      g.setAttribute('aSize',    new THREE.BufferAttribute(sizes, 1));
      g.setAttribute('aPhase',   new THREE.BufferAttribute(phases, 1));
      g.setAttribute('aColor',   new THREE.BufferAttribute(colors, 3));
      const m = new THREE.ShaderMaterial({
        uniforms: { uTime:{value:0}, uPxR:{value:renderer.getPixelRatio()} },
        transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
        vertexShader: `
          attribute float aSize, aPhase;
          attribute vec3  aColor;
          uniform float uTime, uPxR;
          varying float vA;
          varying vec3  vC;
          void main() {
            vec4 mv = modelViewMatrix * vec4(position, 1.0);
            gl_Position = projectionMatrix * mv;
            /* Twinkle factor — small amplitude so they don't pulse. */
            vA = 0.55 + 0.25 * sin(uTime * 1.1 + aPhase);
            vC = aColor;
            gl_PointSize = aSize * uPxR * (320.0 / -mv.z);
          }
        `,
        fragmentShader: `
          varying float vA;
          varying vec3  vC;
          void main() {
            vec2 d = gl_PointCoord - 0.5;
            float r = length(d);
            if (r > 0.5) discard;
            /* Tight core + soft halo — gives pinpoint sharpness instead of
               the fat fuzzy blobs the previous shader produced. */
            float core = smoothstep(0.18, 0.0,  r);
            float halo = smoothstep(0.50, 0.10, r) * 0.45;
            float a = (core + halo) * vA;
            gl_FragColor = vec4(vC, a);
          }
        `,
      });
      const pts = new THREE.Points(g, m); scene.add(pts); return pts;
    })();

    /* Tooltips */
    const labelLayer = labelLayerRef.current;
    if (labelLayer) {
      labelLayer.innerHTML = '';
      planets.forEach(pi => {
        const el = document.createElement('div');
        el.className = 'planet-tooltip';
        el.innerHTML =
          `<span class="planet-tooltip-dot" style="background:${pi.def.haloColor};box-shadow:0 0 12px ${pi.def.haloColor}99"></span>` +
          `<span class="planet-tooltip-text">` +
            `<span class="planet-tooltip-ko">${pi.def.label}</span>` +
            `<span class="planet-tooltip-en">${pi.def.sub}</span>` +
          `</span>`;
        labelLayer.appendChild(el);
        pi.labelEl = el;
      });
    }

    const raycaster = new THREE.Raycaster();
    const ndc = new THREE.Vector2();
    const hoverProj = new THREE.Vector3();
    let hovered = null;
    function onMove(e) {
      const rect = canvas.getBoundingClientRect();
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;
      ndc.x =  (mx / rect.width)  * 2 - 1;
      ndc.y = -(my / rect.height) * 2 + 1;
      raycaster.setFromCamera(ndc, camera);
      const hits = raycaster.intersectObjects(planets.map(p=>p.mesh), false);
      let next = hits.length ? planets.find(p => p.mesh === hits[0].object) : null;
      /* Screen-space proximity fallback — when the cursor isn't directly
         over a planet sphere, pick the nearest planet whose projected
         centre is within the planet's true screen-space radius (plus
         a small margin). The radius is computed from the perspective
         projection so it matches the visible footprint exactly,
         regardless of how big the planet looks at the current camera
         distance — fixes "only the centre of a huge planet triggers". */
      if (!next) {
        let bestDist = Infinity, bestPlanet = null;
        const focal = rect.height / (2 * Math.tan(camera.fov * Math.PI / 360));
        planets.forEach(pi => {
          hoverProj.copy(pi.group.position).project(camera);
          const sx = (hoverProj.x * 0.5 + 0.5) * rect.width;
          const sy = (-hoverProj.y * 0.5 + 0.5) * rect.height;
          const dx = mx - sx, dy = my - sy;
          const dist = Math.hypot(dx, dy);
          const planetDist = camera.position.distanceTo(pi.group.position);
          const screenRadius = (pi.def.size * focal) / Math.max(planetDist, 0.001);
          const threshold = screenRadius + 28;
          if (dist < threshold && dist < bestDist) {
            bestDist = dist;
            bestPlanet = pi;
          }
        });
        next = bestPlanet;
      }
      hovered = next;
      canvas.style.cursor = hovered ? 'pointer' : 'default';
    }
    canvas.style.pointerEvents = 'auto';
    canvas.addEventListener('pointermove', onMove);
    canvas.addEventListener('pointerleave', () => { hovered = null; canvas.style.cursor='default'; });

    /* Bloom intentionally disabled — full-frame bloom was too bright and
       washed out the planet textures. Fresnel rim shader on each planet
       still provides a subtle, localised glow. */

    function resize() {
      const rect = canvas.getBoundingClientRect();
      if (rect.width === 0 || rect.height === 0) return;
      renderer.setSize(rect.width, rect.height, false);
      camera.aspect = rect.width / rect.height;
      camera.updateProjectionMatrix();
    }
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);

    let rafId; const startT = performance.now();
    const projTmp = new THREE.Vector3(), tmp = new THREE.Vector3();

    /* ─── HERO RECORDER ──────────────────────────────────────────────
       Captures the live WebGL canvas to a webm video file.
       Usage:
         · Console:   recordHero(10)  → 10-second recording
         · Console:   recordHero(10, { fps: 60, mbps: 12 })
         · URL flag:  ?dev=1          → shows a small REC button top-right
       The recorded file matches what the visitor sees on the page,
       exactly — no AI generation, no quality drift. */
    const pickMime = () => {
      const candidates = [
        'video/webm;codecs=vp9',
        'video/webm;codecs=vp8',
        'video/webm',
        'video/mp4;codecs=h264',
      ];
      for (const m of candidates) {
        if (window.MediaRecorder && MediaRecorder.isTypeSupported(m)) return m;
      }
      return 'video/webm';
    };

    let activeRecorder = null;
    window.recordHero = (durationSec = 10, opts = {}) => {
      if (activeRecorder) { console.warn('Recording already in progress'); return; }
      const fps  = opts.fps  || 60;
      const mbps = opts.mbps || 10;
      const mime = pickMime();
      const stream = canvas.captureStream(fps);
      const ext = mime.startsWith('video/mp4') ? 'mp4' : 'webm';
      try {
        const recorder = new MediaRecorder(stream, {
          mimeType: mime,
          videoBitsPerSecond: mbps * 1_000_000,
        });
        const chunks = [];
        recorder.ondataavailable = e => { if (e.data && e.data.size) chunks.push(e.data); };
        recorder.onstop = () => {
          const blob = new Blob(chunks, { type: mime });
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
          a.href = url;
          a.download = `sriki-hero-${stamp}.${ext}`;
          document.body.appendChild(a); a.click(); a.remove();
          URL.revokeObjectURL(url);
          const sizeMb = (blob.size / 1024 / 1024).toFixed(2);
          console.log(`%c✓ Recording saved`, 'color:#15be53;font-weight:bold',
            `\n  file: ${a.download}\n  size: ${sizeMb} MB\n  codec: ${mime}`);
          activeRecorder = null;
        };
        activeRecorder = recorder;
        recorder.start();
        console.log(`%c● REC`, 'color:#ea2261;font-weight:bold',
          `\n  duration: ${durationSec}s\n  fps: ${fps}\n  bitrate: ${mbps} Mbps\n  codec: ${mime}`);
        setTimeout(() => { try { recorder.stop(); } catch (e) {} }, durationSec * 1000);
      } catch (err) {
        console.error('Recording failed:', err);
        activeRecorder = null;
      }
    };

    /* Show a small REC button if ?dev=1 in URL */
    if (typeof location !== 'undefined' && /[?&]dev=1\b/.test(location.search)) {
      const btn = document.createElement('button');
      btn.textContent = '● REC 10s';
      Object.assign(btn.style, {
        position: 'fixed', top: '76px', right: '16px', zIndex: '100',
        padding: '6px 12px', borderRadius: '4px',
        background: 'rgba(234,34,97,0.92)', color: '#fff',
        border: '1px solid rgba(255,255,255,0.2)',
        fontFamily: 'var(--font-mono, monospace)', fontSize: '11px',
        letterSpacing: '0.08em', textTransform: 'uppercase',
        cursor: 'pointer',
      });
      btn.id = 'sriki-rec-btn';
      btn.addEventListener('click', () => {
        if (activeRecorder) return;
        btn.style.background = 'rgba(124,58,237,0.92)';
        btn.textContent = '● recording…';
        window.recordHero(10);
        setTimeout(() => {
          btn.style.background = 'rgba(234,34,97,0.92)';
          btn.textContent = '● REC 10s';
        }, 10500);
      });
      document.body.appendChild(btn);
    }

    /* Convenience banner — printed once when Hero mounts */
    console.log(
      '%cSRIKI Hero · recordHero(seconds)',
      'color:#7c3aed;font-weight:bold;font-size:14px',
      '\n  Type   recordHero(10)   in the console to capture a 10-second clip.',
      '\n  Add    ?dev=1           to the URL for an on-screen REC button.',
    );

    function animate() {
      const t = (performance.now() - startT) / 1000;
      stars.material.uniforms.uTime.value = t;
      planets.forEach(pi => {
        planetPos(pi.def, t, tmp);
        pi.group.position.copy(tmp);
        pi.mesh.rotation.y = pi.spinPhase + t * pi.spinSpeed;
        const tgt = (hovered === pi) ? 1.10 : 1.0;
        const cur = pi.mesh.scale.x / pi.def.size;
        const next = cur + (tgt - cur) * 0.14;
        pi.mesh.scale.setScalar(pi.def.size * next);
        pi.moons.forEach(m => {
          const mθ = m.def.phase + (t / m.def.period) * Math.PI * 2;
          const mTilt = m.def.tilt * Math.PI / 180;
          const mrx = Math.cos(mθ) * m.def.r;
          const mrz = Math.sin(mθ) * m.def.r;
          m.mesh.position.set(mrx, mrz * Math.sin(mTilt), mrz * Math.cos(mTilt));
          m.mesh.rotation.y = t * 0.5;
        });
      });
      camera.position.x = CAM_BASE.x + Math.sin(t * 0.10) * 0.08;
      camera.position.y = CAM_BASE.y + Math.cos(t * 0.13) * 0.05;
      camera.lookAt(0, 0, 0);
      renderer.render(scene, camera);

      if (labelLayer) {
        const rect = canvas.getBoundingClientRect();
        planets.forEach(pi => {
          if (!pi.labelEl) return;
          const show = (hovered === pi);
          pi.labelEl.classList.toggle('is-visible', show);
          if (show) {
            projTmp.copy(pi.group.position).project(camera);
            const sx = (projTmp.x * 0.5 + 0.5) * rect.width;
            const sy = (-projTmp.y * 0.5 + 0.5) * rect.height;
            pi.labelEl.style.transform = `translate(${sx}px, ${sy}px) translate(-50%, -50%)`;
          }
        });
      }
      rafId = requestAnimationFrame(animate);
    }
    animate();

      return () => {
        cancelAnimationFrame(rafId);
        ro.disconnect();
        renderer.dispose();
      };
    }; /* end setup() */

    trySetup();
    if (!cleanupFn) {
      window.addEventListener('three-ready', trySetup);
    }

    return () => {
      disposed = true;
      window.removeEventListener('three-ready', trySetup);
      if (cleanupFn) cleanupFn();
    };
  }, []);

  /* Bilingual headline rendering — KR primary 60px, EN sub 28px below */
  const isKo = lang === 'ko';

  return (
    <section id="top" className="hero-section" style={{
      position: 'relative', overflow: 'hidden',
      padding: '78px 24px 84px',
      minHeight: 760,
      background:
        'radial-gradient(ellipse 55% 60% at 70% 45%, rgba(74, 35, 130, 0.40), transparent 70%),' +
        'radial-gradient(ellipse 45% 50% at 18% 78%, rgba(232, 158, 42, 0.12), transparent 70%),' +
        'radial-gradient(ellipse 40% 50% at 90% 100%, rgba(124, 58, 237, 0.18), transparent 70%),' +
        'linear-gradient(135deg, #02040C 0%, #06081A 50%, #02040C 100%)',
    }}>
      <canvas ref={canvasRef} className="hero-canvas" style={{
        position: 'absolute', inset: 0,
        width: '100%', height: '100%',
        display: 'block', zIndex: 0,
        WebkitMaskImage: 'linear-gradient(to right, transparent 18%, black 42%)',
        maskImage:       'linear-gradient(to right, transparent 18%, black 42%)',
      }} />
      <div ref={labelLayerRef} style={{
        position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 2,
      }} />

      <style>{`
        .planet-tooltip {
          position: absolute; left: 0; top: 0;
          display: flex; align-items: center; gap: 8px;
          padding: 6px 12px;
          background: rgba(10, 8, 28, 0.88);
          backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
          border: 1px solid rgba(167, 139, 250, 0.28);
          border-radius: 9999px;
          box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55), 0 0 18px rgba(167, 139, 250, 0.22);
          font-family: var(--font-sans); font-feature-settings: "ss01";
          line-height: 1; white-space: nowrap;
          opacity: 0; transition: opacity 180ms cubic-bezier(0.2,0.6,0.25,1);
          will-change: transform, opacity;
        }
        .planet-tooltip.is-visible { opacity: 1; }
        .planet-tooltip-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
        .planet-tooltip-text { display: flex; align-items: baseline; gap: 6px; }
        .planet-tooltip-ko { font-size: 13px; font-weight: 600; color: #fff; }
        .planet-tooltip-en { font-size: 11px; font-weight: 500; color: rgba(255,255,255,0.65); text-transform: uppercase; letter-spacing: 0.10em; }
        .hero-eyebrow-chip {
          display: inline-flex; align-items: center; gap: 8px;
          padding: 6px 14px 6px 10px;
          background: rgba(255,255,255,0.06);
          border: 1px solid rgba(196,181,253,0.30);
          border-radius: 9999px;
          backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
        }
        .hero-eyebrow-chip > span:first-child {
          width: 7px; height: 7px; border-radius: 50%;
          background: #C4B5FD;
          box-shadow: 0 0 12px rgba(196,181,253,0.7);
        }
        .hero-trust-row {
          display: flex; flex-wrap: wrap;
          gap: 10px 18px;
          margin-top: 22px;
          max-width: 600px;
        }
        .hero-trust-item {
          display: inline-flex; align-items: center; gap: 8px;
          font-size: 14px; font-weight: 500;
          color: rgba(255,255,255,0.82);
        }
        .hero-trust-item svg { flex-shrink: 0; color: #C4B5FD; }
        /* Headline highlight — luminous purple gradient on the payoff word.
           Lavender → violet → bright purple to glow on the dark cosmic bg.
           filter: drop-shadow adds a soft purple halo (background-clip:text
           makes regular text-shadow invisible, so we use filter instead). */
        .hero-h1-highlight {
          background: linear-gradient(135deg, #E9D5FF 0%, #C4B5FD 35%, #A78BFA 75%, #C084FC 130%);
          -webkit-background-clip: text;
          background-clip: text;
          -webkit-text-fill-color: transparent;
          color: transparent;
          font-weight: 900;
          filter: drop-shadow(0 0 16px rgba(167, 139, 250, 0.45));
        }
        @media (max-width: 720px) {
          .hero-h1 { font-size: 34px !important; line-height: 1.28 !important; letter-spacing: -0.6px !important; }
          .hero-h1-sub { font-size: 15px !important; }
          /* Mobile: trust items stack one per line for readability */
          .hero-trust-row {
            flex-direction: column;
            align-items: flex-start;
            gap: 10px !important;
          }
          /* Mobile: keep the manual <br> so the headline breaks at the
             intended sentence boundary ("수학에서 출발해," / "Start with math.")
             instead of wrapping mid-clause. */
        }
        @media (max-width: 767px) {
          /* Phone: drop the fade mask so planets are visible (was: 18% fade-in, hid them all). */
          .hero-canvas {
            -webkit-mask-image: none !important;
            mask-image: none !important;
            opacity: 0.55;
          }
          /* Phone hero: tighter top padding (status bar + nav already eat ~80px) */
          .hero-section {
            padding: 84px 16px 56px !important;
            min-height: auto !important;
          }
          /* Phone: CTA buttons share row width equally so neither gets clipped */
          .hero-cta-row > button {
            flex: 1 1 0;
            min-width: 0;
            padding-left: 12px !important;
            padding-right: 12px !important;
            font-size: 15px !important;
          }
        }
      `}</style>

      <div style={{ maxWidth: 1080, margin: '0 auto', position: 'relative', zIndex: 3, pointerEvents: 'none' }}>
        <h1 className="hero-h1" lang={isKo ? 'ko' : 'en'} style={{
          fontFamily: 'var(--font-sans)', fontFeatureSettings: '"ss01"',
          fontSize: 56, fontWeight: 800, lineHeight: 1.20,
          letterSpacing: isKo ? '-1.0px' : '-1.4px',
          color: '#fff', margin: 0, maxWidth: 720,
          textShadow: '0 2px 30px rgba(0,0,0,0.6)',
        }}>
          {isKo ? (
            <>
              수학에서 출발해,<br className="hero-h1-break"/>
              {' '}아이의 <span className="hero-h1-highlight">인생</span>을 설계합니다.
            </>
          ) : (
            <>
              Start with math.<br className="hero-h1-break"/>
              {' '}Design a <span className="hero-h1-highlight">life</span>.
            </>
          )}
        </h1>

        <div className="hero-h1-sub" lang={isKo ? 'ko' : 'en'} style={{
          marginTop: 18,
          fontFamily: 'var(--font-sans)', fontFeatureSettings: '"ss01"',
          fontSize: 20, fontWeight: 500, lineHeight: 1.40,
          color: 'rgba(255,255,255,0.72)', maxWidth: 620,
          letterSpacing: isKo ? '-0.3px' : '0',
        }}>{isKo ? '수학을 넘어, 사고력과 금융교육까지 함께.' : 'Beyond math — into thinking and finance, together.'}</div>

        <div className="hero-trust-row">
          <span className="hero-trust-item">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="8" r="7"/>
              <polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/>
            </svg>
            {isKo ? '현직 증권 애널리스트 대표 직접 운영' : 'Run by a working securities analyst'}
          </span>
          <span className="hero-trust-item">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
            </svg>
            {isKo ? '6–8명 소수 정예' : '6–8 students per class'}
          </span>
          <span className="hero-trust-item">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <rect x="5" y="2" width="14" height="20" rx="2.5" ry="2.5"/>
              <line x1="12" y1="18" x2="12.01" y2="18"/>
            </svg>
            {isKo ? '자체 제작 앱 사용' : 'Our own in-house app'}
          </span>
          <span className="hero-trust-item">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <rect x="2" y="7" width="20" height="15" rx="2" ry="2"/>
              <polyline points="17 2 12 7 7 2"/>
            </svg>
            {isKo ? '매일경제·한국경제·YTN 등 다수 방송 및 유튜브 출연' : 'Featured on Maekyung, Hankyung, YTN and YouTube'}
          </span>
        </div>

        <div className="hero-cta-row" style={{ display: 'flex', gap: 12, marginTop: 36, flexWrap: 'nowrap', pointerEvents: 'auto' }}>
          <Button variant="primary"   size="lg" onClick={onPrimary}>{t('hero.cta.primary')}</Button>
          <Button variant="secondary" size="lg" onClick={onSecondary}>{t('hero.cta.secondary')} →</Button>
        </div>
      </div>
    </section>
  );
};

window.Hero = Hero;
