// Hero — hosting + privacy lead. Headline + descriptor + CTAs + stat strip
// on the left; a 3D ASCII render of the TensorHost isometric box mark on the
// right — Lambert-shaded, z-buffered, auto-rotating, and tumbling on scroll.

// One-time keyframe injection for the descriptor light-up. Each word flickers
// on like a cold filament/tube light; 'Private' then holds a slow amber glow.
(function injectHeroAnim() {
  if (typeof document === 'undefined' || document.getElementById('th-hero-anim')) return;
  const s = document.createElement('style');
  s.id = 'th-hero-anim';
  s.textContent = `
    @keyframes th-bulb {
      0%   { opacity: 0;    transform: translateY(3px); }
      8%   { opacity: 0.7;  transform: translateY(0); }
      13%  { opacity: 0.12; }
      24%  { opacity: 0.88; }
      31%  { opacity: 0.3; }
      44%  { opacity: 1; }
      100% { opacity: 1;    transform: translateY(0); }
    }
    @keyframes th-glow {
      0%, 100% { text-shadow: 0 0 14px var(--accent-soft); }
      50%      { text-shadow: 0 0 28px var(--accent-soft), 0 0 11px var(--accent-soft); }
    }
    .th-bulb {
      animation: th-bulb 0.9s cubic-bezier(0.2,0,0,1) both;
      will-change: opacity, transform;
    }
    .th-bulb-glow {
      animation: th-bulb 0.9s cubic-bezier(0.2,0,0,1) both,
                 th-glow 3.6s ease-in-out infinite both;
      will-change: opacity, transform, text-shadow;
    }
    @media (prefers-reduced-motion: reduce) {
      .th-bulb, .th-bulb-glow { animation: none !important; opacity: 1 !important; transform: none !important; }
    }
  `;
  document.head.appendChild(s);
})();

// 'Owned. Encrypted. Private.' — staggered filament-flicker entrance, held
// until the boot overlay dissolves so it plays on reveal, not behind it.
function HeroDescriptor() {
  const [armed, setArmed] = React.useState(() => !!window.__thBootDone);
  const [settled, setSettled] = React.useState(false);
  React.useEffect(() => {
    if (armed) return;
    const on = () => setArmed(true);
    window.addEventListener('th-boot-done', on);
    return () => window.removeEventListener('th-boot-done', on);
  }, [armed]);
  // Safety net: once the staggered entrance has had time to finish, pin the
  // words visible. Guarantees the end-state even if an environment suspends
  // CSS animations (so they never reach their 100% keyframe).
  React.useEffect(() => {
    if (!armed) return;
    const t = setTimeout(() => setSettled(true), 2400);
    return () => clearTimeout(t);
  }, [armed]);

  const words = [
    { t: 'Owned.', d: 0.2 },
    { t: 'Encrypted.', d: 0.62 },
    { t: 'Private.', d: 1.04, accent: true },
  ];
  return (
    <div style={{ fontSize: 'clamp(24px,2.6vw,34px)', fontWeight: 400, letterSpacing: '-0.02em', color: 'var(--fg)', margin: '0 0 22px', display: 'flex', flexWrap: 'wrap', gap: '0 0.32em' }}>
      {words.map((w) => (
        <span
          key={w.t}
          className={armed ? (w.accent ? 'th-bulb-glow' : 'th-bulb') : undefined}
          style={{
            color: w.accent ? 'var(--accent)' : 'var(--fg)',
            fontWeight: w.accent ? 700 : 400,
            textShadow: w.accent ? '0 0 14px var(--accent-soft)' : undefined,
            opacity: armed ? (settled ? 1 : undefined) : 0,
            animationDelay: w.accent ? (w.d + 's, ' + (w.d + 0.8) + 's') : (w.d + 's'),
          }}
        >
          {w.t}
        </span>
      ))}
    </div>
  );
}

function AsciiCube({ live = true }) {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const W = 72, H = 44;            // ASCII grid (higher resolution)
    const grad = ":-~=+*ox#%&@"; // dark → bright (faintest dots dropped for legibility)
    const GN = grad.length - 1;
    const K2 = 30;                   // viewer distance (large = near-orthographic, less perspective)
    const ASPECT = 0.5;             // char height/width compensation
    const RING = 0.52;              // hollow-frame threshold (|u| or |v| beyond → keep)
    const STEP = 0.04;              // face sampling resolution
    const FPS = 30, FRAME_MS = 1000 / FPS;

    // Six cube faces: [originAxis fn(u,v)->[x,y,z], normal]
    const faces = [
      [(u, v) => [1, u, v], [1, 0, 0]],
      [(u, v) => [-1, u, v], [-1, 0, 0]],
      [(u, v) => [u, 1, v], [0, 1, 0]],
      [(u, v) => [u, -1, v], [0, -1, 0]],
      [(u, v) => [u, v, 1], [0, 0, 1]],
      [(u, v) => [u, v, -1], [0, 0, -1]],
    ];

    // Light direction (from upper-front), normalized.
    const lx = 0, ly = 0.6, lz = -0.8;

    const cb = new Array(W * H);    // char index per cell (-1 = empty, -2 = black hole)
    const lb = new Float32Array(W * H); // luminance per cell
    const eb = new Int8Array(W * H);    // 1 = cell sits on a cube/aperture edge
    const zb = new Float32Array(W * H);

    let raf;
    // Persistent orientation + angular velocity (radians / frame) for momentum.
    let angA = -0.77, angB = 0.55, velA = 0, velB = 0;
    let spinDir = 1; // auto-rotate direction; follows the last fling, never self-reverses
    let scrollY = window.scrollY || window.pageYOffset || 0;
    let lastScroll = scrollY;
    const onScroll = () => { scrollY = window.scrollY || window.pageYOffset || 0; };
    window.addEventListener('scroll', onScroll, { passive: true });

    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    // Mouse-drag orbit. Updates orientation directly and records velocity so
    // the cube keeps coasting after release.
    let dragging = false, lastX = 0, lastY = 0;
    const el = wrapRef.current;
    const onDown = (e) => {
      dragging = true; lastX = e.clientX; lastY = e.clientY;
      velA = 0; velB = 0;
      if (el) el.style.cursor = 'grabbing';
      if (el && el.setPointerCapture) try { el.setPointerCapture(e.pointerId); } catch (err) {}
    };
    const onMove = (e) => {
      if (!dragging) return;
      velB = -(e.clientX - lastX) * 0.009;
      velA = -(e.clientY - lastY) * 0.009;
      angB += velB; angA += velA;
      lastX = e.clientX; lastY = e.clientY;
      e.preventDefault();
    };
    const onUp = () => { dragging = false; if (el) el.style.cursor = 'grab'; };
    if (el) {
      el.addEventListener('pointerdown', onDown);
      el.addEventListener('pointermove', onMove);
      el.addEventListener('pointerup', onUp);
      el.addEventListener('pointercancel', onUp);
    }

    // Single canvas — drawing ~1k glyphs/frame is far cheaper than mutating
    // thousands of DOM spans. Brand color is re-read whenever the theme flips.
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const root = document.documentElement;
    let fg = '#e8e8e8', accent = '#e2a23b', themeKey = null;
    const refreshColor = () => {
      const k = root.getAttribute('data-theme') || '';
      if (k === themeKey) return;
      themeKey = k;
      const cs = getComputedStyle(root);
      fg = (cs.getPropertyValue('--fg') || '#e8e8e8').trim() || '#e8e8e8';
      accent = (cs.getPropertyValue('--accent') || '#e2a23b').trim() || '#e2a23b';
    };
    refreshColor();

    // Fit canvas to the container width so the cube fills the column.
    let chh = 8, cellW = 6, dpr = Math.min(2, window.devicePixelRatio || 1);
    const fit = () => {
      const w = wrapRef.current ? wrapRef.current.clientWidth : 460;
      cellW = Math.max(3, Math.min(11, w / W));
      chh = cellW * 1.5;
      const cssW = W * cellW, cssH = H * chh;
      dpr = Math.min(2, window.devicePixelRatio || 1);
      canvas.style.width = cssW + 'px';
      canvas.style.height = cssH + 'px';
      canvas.width = Math.round(cssW * dpr);
      canvas.height = Math.round(cssH * dpr);
    };
    fit();
    const ro = ('ResizeObserver' in window) ? new ResizeObserver(fit) : null;
    if (ro && wrapRef.current) ro.observe(wrapRef.current);

    let lastT = 0;
    function frame(t) {
      raf = requestAnimationFrame(frame);
      if (t - lastT < FRAME_MS) return;     // throttle to ~30fps
      lastT = t;

      if (!dragging) {
        // Scroll nudges spin as an impulse, then momentum coasts with friction.
        const ds = scrollY - lastScroll; lastScroll = scrollY;
        velB += ds * 0.0009;
        angA += velA; angB += velB;
        velA *= 0.95; velB *= 0.95;
        // Remember the direction of any meaningful spin so auto-rotation can
        // continue that way instead of snapping back to a fixed direction.
        if (Math.abs(velB) > 0.012) spinDir = velB < 0 ? -1 : 1;
        // Once a fling has mostly died, take over with eased auto-rotation:
        // dwell at the four isometric poses (B = pi/4 + n*pi/2, where all three
        // faces show like the logo) and speed up through the flat face-on
        // angles. Tilt eases back to the canonical three-quarter view.
        if (live && !reduce && Math.abs(velA) + Math.abs(velB) < 0.012) {
          const HALF = Math.PI / 2, POSE = Math.PI / 4;
          const phase = (((angB - POSE) % HALF) + HALF) % HALF;
          const ph = phase / HALF;
          // Squared sine → a moderate dwell at each isometric pose with a
          // smooth (not abrupt) pass through the flat angles between them.
          const speed = 0.08 + 1.2 * Math.pow(Math.sin(Math.PI * ph), 2);
          angB += spinDir * 0.028 * speed;
          angA += (-0.62 - angA) * 0.035;
        }
      }
      const A = angA, B = angB;
      const sinA = Math.sin(A), cosA = Math.cos(A);
      const sinB = Math.sin(B), cosB = Math.cos(B);

      cb.fill(-1);
      zb.fill(0);
      eb.fill(0);

      const K1 = W * K2 * 0.17;

      for (let f = 0; f < faces.length; f++) {
        const map = faces[f][0], nrm = faces[f][1];
        for (let u = -1; u <= 1.0001; u += STEP) {
          for (let v = -1; v <= 1.0001; v += STEP) {
            const p = map(u, v);
            // point + normal share the same rotation (Y then X)
            const px = p[0], py = p[1] * 1.3, pz = p[2]; // stretch vertically → taller box
            // rotate Y
            let x1 = px * cosB + pz * sinB;
            let z1 = -px * sinB + pz * cosB;
            let y1 = py;
            // rotate X
            let y2 = y1 * cosA - z1 * sinA;
            let z2 = y1 * sinA + z1 * cosA;
            let x2 = x1;
            const zc = z2 + K2;
            const ooz = 1 / zc;
            const sx = (W / 2 + K1 * ooz * x2) | 0;
            const sy = (H / 2 - K1 * ASPECT * ooz * y2) | 0;
            if (sx < 0 || sx >= W || sy < 0 || sy >= H) continue;
            const idx = sx + sy * W;
            if (ooz <= zb[idx]) continue;
            zb[idx] = ooz; // claim this cell (occludes anything behind it)

            // Center region of each face is a hole — rendered as an opaque
            // black patch. It still writes the z-buffer above, so the back
            // faces behind it stay hidden (not see-through).
            if (Math.abs(u) < RING && Math.abs(v) < RING) { cb[idx] = -2; continue; }

            // rotate normal the same way
            const nx0 = nrm[0], ny0 = nrm[1], nz0 = nrm[2];
            let nx1 = nx0 * cosB + nz0 * sinB;
            let nz1 = -nx0 * sinB + nz0 * cosB;
            let ny1 = ny0;
            let ny2 = ny1 * cosA - nz1 * sinA;
            let nz2 = ny1 * sinA + nz1 * cosA;
            let nx2 = nx1;
            let L = nx2 * lx + ny2 * ly + nz2 * lz;
            if (L < 0) L = 0;
            const lum = 0.34 + 0.66 * L;
            const ci = Math.max(0, Math.min(GN, (lum * GN) | 0));

            cb[idx] = ci;
            lb[idx] = lum;
            // Edge = near the face's outer border (cube silhouette) or near
            // the aperture rim (RING boundary) — these trace the logo linework.
            const m = Math.abs(u) > Math.abs(v) ? Math.abs(u) : Math.abs(v);
            eb[idx] = (m > 0.9 || m < RING + 0.1) ? 1 : 0;
          }
        }
      }

      // Paint to canvas: glyph from the ramp, font-size + alpha from luminance
      // to give depth — bright/lit cells grow and brighten, dark cells recede.
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, W * cellW, H * chh);
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      refreshColor();
      ctx.fillStyle = fg;
      const half = cellW / 2, halfH = chh / 2;
      // Pass 1: opaque black hole patches (drawn under the glyphs).
      ctx.globalAlpha = 1;
      ctx.fillStyle = '#000';
      for (let i = 0; i < W * H; i++) {
        if (cb[i] !== -2) continue;
        const x = (i % W) * cellW, y = ((i / W) | 0) * chh;
        // overdraw slightly so adjacent patch cells tile without seams
        ctx.fillRect(x - 0.5, y - 0.5, cellW + 1, chh + 1);
      }
      // Pass 2: frame glyphs, sized by luminance for depth — all neutral.
      ctx.fillStyle = fg;
      for (let i = 0; i < W * H; i++) {
        const ci = cb[i];
        if (ci < 0) continue;
        const lum = lb[i];
        const x = (i % W) * cellW + half;
        const y = ((i / W) | 0) * chh + halfH;
        const fpx = chh * (0.66 + 0.7 * lum);
        ctx.font = '700 ' + fpx.toFixed(1) + "px 'Space Mono', ui-monospace, monospace";
        ctx.globalAlpha = 0.66 + 0.34 * lum;
        ctx.fillText(grad[ci], x, y);
      }
      ctx.globalAlpha = 1;
    }
    // Kick off: paint one frame synchronously so the cube is never blank even
    // if rAF is throttled (background tab), then let rAF drive the animation.
    frame(performance.now());

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('scroll', onScroll);
      if (el) {
        el.removeEventListener('pointerdown', onDown);
        el.removeEventListener('pointermove', onMove);
        el.removeEventListener('pointerup', onUp);
        el.removeEventListener('pointercancel', onUp);
      }
      if (ro && wrapRef.current) ro.unobserve(wrapRef.current);
    };
  }, [live]);

  return (
    <div ref={wrapRef} style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 360, cursor: 'grab', touchAction: 'none' }}>
      <canvas ref={canvasRef} aria-hidden style={{ userSelect: 'none', display: 'block' }} />
    </div>
  );
}

function Hero({ live = true }) {
  const stats = [
    ['Zero', 'keys we hold', 'storage & mail encrypted'],
    ['Toronto', 'data residency', 'Canada · PIPEDA'],
    ['100%', 'owned hardware', 'in-house ops'],
    ['Tier 3', 'datacenter', 'Toronto, Canada'],
  ];
  const go = (id) => (e) => { e.preventDefault(); const el = document.getElementById(id); if (el) window.scrollTo({ top: el.offsetTop - 56, behavior: 'smooth' }); };

  return (
    <header id="top" data-screen-label="Hero" style={{ borderBottom: '1px solid var(--rule)', position: 'relative', background: 'var(--bg)' }}>
      <div aria-hidden style={{
        position: 'absolute', inset: 0, pointerEvents: 'none', opacity: 0.6,
        backgroundImage: 'linear-gradient(var(--grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--grid-line) 1px, transparent 1px)',
        backgroundSize: '64px 64px', maskImage: 'linear-gradient(to bottom, #000, transparent 80%)',
        WebkitMaskImage: 'linear-gradient(to bottom, #000, transparent 80%)',
      }} />
      <div style={{ maxWidth: 1280, margin: '0 auto', padding: '40px 32px 0', position: 'relative' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1.15fr 1fr', gap: 64, alignItems: 'center' }}>
          <div>
            <h1 style={{ fontSize: 'clamp(40px, 5vw, 66px)', fontWeight: 400, lineHeight: 1.04, letterSpacing: '-0.03em', margin: '0 0 10px', color: 'var(--fg)' }}>
              <span className="th-wordmark" style={{ display: 'block', fontFamily: 'var(--th-font-mono, "Space Mono"), monospace', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', fontSize: 'clamp(40px, 5vw, 66px)', lineHeight: 1, margin: '0 0 18px' }}>TensorHost</span>Hardware we own.<br />Data you keep.
            </h1>
            <HeroDescriptor />
            <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--fg-2)', maxWidth: '54ch', margin: '0 0 30px' }}>
              Web, email, storage, and GPU compute, on hardware we own and operate in an ISO/IEC 27001:2022-certified facility in Toronto. Storage and mail are encrypted with keys we never hold, so even we can't read them.
            </p>
            <div style={{ display: 'flex', gap: 12 }}>
              <Button kind="primary" href="Register.html" accentArrow>Get started</Button>
              <Button kind="secondary" href="Contact.html">Talk to us</Button>
            </div>
          </div>
          <AsciiCube live={live} />
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', borderTop: '1px solid var(--rule)', marginTop: 72 }}>
          {stats.map(([big, label, sub], i) => (
            <div key={label} style={{ padding: '26px 28px 30px', borderLeft: i ? '1px solid var(--rule-soft)' : 'none' }}>
              <div style={{ fontSize: 34, fontWeight: 400, letterSpacing: '-0.03em', fontVariantNumeric: 'tabular-nums', lineHeight: 1, color: 'var(--fg)' }}>{big}</div>
              <div style={{ fontSize: 14, color: 'var(--fg)', marginTop: 12 }}>{label}</div>
              <div style={{ fontSize: 12, color: 'var(--fg-4)', marginTop: 3 }}>{sub}</div>
            </div>
          ))}
        </div>
      </div>
    </header>
  );
}

window.Hero = Hero;
