// components.jsx — Erudity landing components.
// Exposes: BrainMark, Header, Hero, Roulette, FlyingCards, Download, Footer

const { useState, useEffect, useRef, useMemo } = React;

// Capture campaign UTMs from the inbound URL once at script load so every
// store-badge href on the page carries them through to /download/*, where
// the server turns them into Apple `pt`/`ct` and Play `referrer` params.
// See ERU-66 and infrastructure/cloud_run_services/website/ATTRIBUTION.md.
const ATTRIBUTION_QUERY = (() => {
  const inbound = new URLSearchParams(window.location.search);
  const outbound = new URLSearchParams();
  for (const key of ['utm_source', 'utm_medium', 'utm_campaign']) {
    const value = inbound.get(key);
    if (value) outbound.set(key, value);
  }
  const serialized = outbound.toString();
  return serialized ? `?${serialized}` : '';
})();

// ─────────────────────────────────────────────────────────
// Brand mark — purple brain, used in header & footer
// ─────────────────────────────────────────────────────────
function BrainMark({ size = 22, color = "currentColor" }) {
  // Real Erudity brand mark — lib/resources/erudity_logo.svg
  return (
    <svg width={size} height={size} viewBox="0 0 602 576" fill="none" style={{ color, display: 'block' }}>
      <path d="M223.62 271.738C227.55 274.264 229.468 277.77 230.807 282.175C231.814 287.811 231.773 292.597 230.682 298.238L230.261 300.466C226.133 321.137 216.592 340.887 199.807 354.175C202.84 357.461 206.206 360.021 209.784 362.667C230.024 377.75 240.69 402.166 245.807 426.175C246.307 428.175 245.807 426.175 246.307 428.175C247.462 445.78 247.962 445.78 244.194 451.664C239.837 456.413 234.624 459.006 228.174 459.363C223.02 458.955 217.963 457.645 214.472 453.645C210.458 447.947 209.61 442.162 208.745 435.363C206.849 421.056 202.669 408.094 192.807 397.175L188.807 393.175C188.807 393.175 188.919 393.287 186.553 391.355C179.87 387.3 186.553 391.355 179.87 387.3C169.088 379.963 155.84 379.036 143.182 379.3C136.163 379.35 130.87 378.106 124.932 374.363C120.988 370.303 119.145 365.8 118.807 360.175C119.732 353.699 121.781 349.453 126.807 345.175C132.111 342.173 136.98 341.979 142.973 341.741L143.057 341.738C158.117 341.132 170.38 335.501 180.776 324.519C190.104 313.756 194.163 302.217 195.644 288.205C196.415 281.635 198.271 277.516 203.245 272.925C209.881 268.393 216.344 268.217 223.62 271.738Z" fill="currentColor"/>
      <path fillRule="evenodd" clipRule="evenodd" d="M355.058 0.0189403C379.467 -0.660678 426.112 17.1752 426.112 17.1752L432.612 20.6752C444.952 27.9159 456.824 38.2548 466.112 49.1752L468.112 51.1752C469.788 53.2665 471.337 55.353 472.862 57.5502L474.218 59.4801C483.039 72.1509 483.039 72.1509 485.37 78.7182C487.513 82.9705 489.337 83.9365 493.55 86.0502C494.82 86.7222 496.087 87.4008 497.351 88.0854L499.271 89.1249C517.292 99.4039 531.891 116.066 543.112 133.175L544.73 135.558C559.578 158.593 566.612 191.175 565.112 218.175C569.825 223.518 573.451 229.767 577.109 235.847C578.342 237.896 579.6 239.931 580.898 241.941C602.304 276.075 606.443 320.277 597.724 358.886C596.472 364.113 594.875 369.125 592.847 374.109L590.112 379.175L589.308 381.785C586.587 389.502 582.782 396.63 576.987 402.425C571.87 408.454 571.347 415.733 570.043 423.286C566.259 444.56 556.558 462.09 544.254 479.54C543.185 481.071 541.648 482.622 540.612 484.175L539.612 485.175L538.612 485.675C528.083 504.343 501.429 518.06 482.046 524.652C480.112 525.175 480.112 525.175 478.112 525.175L477.331 526.847C475.933 529.518 474.325 531.944 472.612 534.425L470.769 537.128C470.222 537.804 469.176 538.479 468.612 539.175L466.612 541.175C464.52 544.648 461.524 546.435 458.362 548.925L456.34 550.527C449.947 555.482 443.308 559.483 436.112 563.175L433.355 564.597C406.483 577.592 374.053 579.473 345.837 569.817C331.311 564.584 317.436 557.572 306.444 546.484C304.061 544.124 303.974 543.17 300.807 542.175C299.807 542.175 298.705 543.281 298.495 543.851C288.299 560.988 260.772 569.265 242.964 574.015C240.336 574.569 237.786 574.918 235.112 575.175L233.191 575.37C204.773 578.083 177.366 570.984 153.808 554.898C151.365 552.996 150.112 552.175 150.112 552.175C150.112 552.175 146.441 549.846 145.112 548.175C143.564 546.903 141.978 545.674 140.362 544.488C134.175 539.669 129.753 533.848 125.202 527.519C122.91 524.948 121.256 524.038 118.05 522.863L115.452 521.874C113.04 521.032 111.612 520.175 111.612 520.175L110.112 519.675L107.8 518.55C93.6229 513.264 81.9999 503.434 71.1124 493.175L69.2882 491.511C64.8071 487.27 61.1522 482.473 57.4874 477.53C55.2332 474.369 53.1124 472.175 53.1124 472.175C53.1124 472.175 50.6771 468.951 50.0138 467.315C49.0308 464.981 47.935 462.728 46.8077 460.46C40.4331 447.558 34.8785 435.343 33.2191 420.96C32.1105 411.643 29.9127 406.491 23.8038 399.457C9.15467 382.367 3.7128 357.837 1.11239 336.175L0.807214 333.676C-2.70298 302.275 5.20533 264.264 25.1124 239.175L27.5069 235.999C28.3753 234.85 29.2438 233.7 30.1124 232.55L31.3338 230.918C33.5179 228.054 35.6677 225.689 38.4093 223.347C40.8811 220.922 41.5161 219.377 41.628 215.917C41.5653 214.294 41.4752 212.671 41.3624 211.05C41.3053 209.255 41.256 207.459 41.214 205.663L41.1407 202.896C40.7958 169.752 54.8793 138.248 77.1124 114.175L78.5094 112.645C90.766 99.3656 104.703 89.6435 121.112 82.1752L123.112 81.1752L124.112 80.1752C124.665 79.6229 125.112 78.1752 125.112 78.1752L125.827 76.1557C130.501 65.3169 137.841 55.4965 146.112 47.1752L148.737 44.4877L151.112 42.1752L152.511 40.6752C158.119 34.7659 164.608 30.0389 171.112 25.1752L173.062 23.6127C188.722 12.6354 208.546 6.64241 227.112 3.17519L230.015 2.58925C252.779 -0.453674 275.778 3.65702 294.843 16.6596C296.881 18.0209 298.896 19.136 301.112 20.1752L302.773 18.6713C317.547 6.01084 335.908 1.03483 355.058 0.0189403ZM318.807 65.1752C318.814 58.4439 319.379 54.4308 323.807 49.1752C349.187 25.7238 394.817 42.0891 419.571 58.5146L421.745 59.9877C424.195 61.6431 428.807 65.1752 428.807 65.1752L431.807 68.1752C435.732 71.9349 439.22 76.1373 442.557 80.4213C454.108 96.8154 457.069 117.881 453.932 137.409C451.301 152.004 445.655 164.828 433.167 173.628L430.807 175.175C423.588 180.317 416.011 182.299 407.495 184.3L407.089 184.389C403.296 185.217 399.524 186.041 395.807 187.175C391.027 189.433 388.606 192.347 386.495 197.097C384.692 202.545 385.556 208.017 387.807 213.175C391.522 217.05 396.096 220.037 401.432 221.058C423.01 222.123 445.5 211.001 461.62 197.3C480.03 179.981 488.78 158.724 490.581 133.917L490.807 130.175L492.085 131.198C492.536 131.525 492.982 131.85 493.307 132.175C502.807 139.175 513.807 153.675 518.307 166.175L519.807 169.175C526.561 186.224 528.075 203.942 526.807 222.175C526.186 225.102 525.514 226.511 526.733 229.273L527.006 229.659C528.791 232.187 532.807 237.175 532.807 237.175L534.807 239.175C558.851 264.698 564.6 299.696 563.807 333.175C563.807 333.175 563.331 334.001 562.807 334.175C562.437 334.299 561.807 334.175 561.807 334.175C551.992 325.063 541.553 316.58 528.995 311.644C501.402 301.228 473.442 301.225 446.37 313.425C441.922 315.507 438.741 316.601 433.807 316.175L431.807 315.175L429.948 314.296C418.578 308.342 411.752 298.655 407.557 286.8C406.323 283.029 405.134 279.654 402.874 276.343C399.018 272.298 394.807 270.461 389.307 269.675C383.451 270.512 379.321 272.423 375.432 276.925C372.881 282.029 371.455 286.562 372.057 292.3C375.462 309.893 385.914 327.911 400.495 338.425C403.942 340.785 406.934 343.134 409.807 346.175C406.595 349.953 403.651 353.844 400.995 358.035C391.909 374.015 384.289 395.93 388.557 414.425C391.057 419.924 394.472 423.357 399.807 426.175C406.383 427.831 411.912 427.179 417.995 424.05C422.576 419.311 424.217 413.587 424.772 407.158C425.781 391.91 429.279 379.781 437.803 366.874C448.9 351.928 464.164 343.621 482.432 340.8C498.357 339.047 513.07 342.241 526.017 351.855L527.745 353.175C530.768 355.484 533.505 357.824 535.995 360.722L537.807 363.175C542.067 368.883 544.95 374.284 546.807 381.175L545.163 382.531L542.995 384.363L540.85 386.156C536.801 390.159 535.506 393.263 535.342 398.998C535.004 420.364 527.375 440.067 514.492 456.991L512.995 458.925C511.323 461.088 509.63 463.131 507.807 465.175L506.807 466.175L505.807 467.175C491.529 480.79 475.083 488.527 455.964 492.546C454.89 492.76 453.807 493.675 453.807 493.675L452.807 495.175C449.608 502.313 444.724 512.218 437.807 516.175L435.807 518.175C428.196 524.696 419.955 529.678 410.499 532.992C390.519 539.398 367.624 540.105 348.456 530.605L345.807 529.175C340.78 526.47 336.314 523.536 332.069 519.703C320.177 507.458 318.781 495.641 318.807 479.175V65.1752ZM173.979 70.9057L175.807 69.1752C195.393 48.5587 224.492 35.3194 253.237 40.0377L255.37 40.4252C260.576 41.3708 265.503 42.2768 270.104 45.0229L271.221 45.6076C271.224 45.6091 271.227 45.6105 271.23 45.6119C271.632 45.8006 272.03 45.9879 272.307 46.1752C274.307 47.1752 276.807 48.6752 278.046 50.2221C283.57 61.0166 283.807 61.1752 283.807 73.6752V487.675C283.806 491.996 282.665 495.935 281.807 500.175C277.644 512.008 268.416 520.852 257.932 527.425C238.843 538.126 219.122 540.63 197.981 535.24C185.455 531.578 161.307 512.675 161.307 512.675L158.807 510.175L156.807 507.175C155.768 505.458 154.728 503.742 153.676 502.033L152.495 500.113L151.348 498.181C148.011 492.822 145.498 491.837 139.6 490.386L135.995 489.55C122.648 486.142 102.658 478.551 95.3073 466.175L93.8072 464.175C76.6518 446.591 67.8749 423.016 67.0462 398.707C66.9885 396.862 66.8995 395.019 66.8072 393.175C66.2767 389.709 66.0875 389.408 63.2447 387.05C53.3234 379.218 46.7797 367.292 42.6954 355.525C33.7394 327.811 34.4278 297.19 47.7716 270.949L48.8072 268.988C51.2485 264.323 53.9502 260.731 57.8072 257.175C62.6496 263.774 67.2908 270.319 73.6939 275.503L75.8072 277.175C88.6104 288.212 103.617 297.803 120.237 301.898C125.981 302.518 130.16 300.393 134.682 297.05C138.067 292.471 139.807 287.832 139.807 282.113C137.418 275.094 134.328 270.842 127.659 267.507C114.44 261.332 103.363 255.23 93.1197 244.55C81.4626 231.75 77.1021 215.884 77.651 198.824L77.8072 196.175C78.1556 187.959 80.2203 179.956 82.8072 172.175C88.8795 156.591 99.8073 137.175 114.807 128.675L117.307 126.675L118.807 125.175C124.242 120.54 131.021 117.11 137.721 114.734C142.659 112.99 148.804 111.782 151.245 106.738C152.439 103.243 153.829 100.04 155.561 96.767L156.62 94.8002C161.402 85.8429 166.6 77.9612 173.979 70.9057Z" fill="currentColor"/>
      <path d="M226.807 129.175C227.995 130.8 227.995 130.8 228.807 133.175L230.495 136.8C233.862 146.176 229.778 155.302 225.919 163.926L225.807 164.175C214.64 187.487 194.959 208.108 170.432 217.175C150.09 223.71 150.09 223.71 140.557 219.675C135.413 216.749 132.99 212.553 131.073 207.078C129.79 200.984 131.818 196.053 134.753 190.718C138.314 186.31 142.414 185.937 147.807 185.175C165.097 181.612 177.77 173.873 187.932 159.265C191.997 152.567 195.03 145.329 198.108 138.136C200.767 132.264 202.82 129.725 208.807 127.175C214.492 125.28 221.439 126.688 226.807 129.175Z" fill="currentColor"/>
    </svg>
  );
}

// ─────────────────────────────────────────────────────────
// Header
// ─────────────────────────────────────────────────────────
function Header({ theme, onToggleTheme, onJoin }) {
  return (
    <header className="site-header">
      <a href="#top" className="brand" aria-label="Erudity home">
        <span className="brand-mark" style={{ color: 'white' }}><BrainMark size={20} /></span>
        <span className="brand-word">ERUDITY</span>
      </a>
      <div className="header-right">
        <button
          className="theme-toggle"
          onClick={onToggleTheme}
          aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
          title={theme === 'light' ? 'Dark mode' : 'Light mode'}
        >
          {theme === 'light' ? (
            // moon
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
            </svg>
          ) : (
            // sun
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="4" />
              <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
            </svg>
          )}
        </button>
        <button className="header-cta" onClick={onJoin}>Download</button>
      </div>
    </header>
  );
}

// ─────────────────────────────────────────────────────────
// Hero
// ─────────────────────────────────────────────────────────
function Hero() {
  return (
    <section className="hero" id="top">
      <div className="hero-eyebrow">
        <span className="dot" />
        <span>Available on iOS &amp; Android</span>
      </div>
      <h1>
        Learn anything.
        <span className="accent">Actually anything.</span>
      </h1>
      <p className="hero-sub">
        Discover fascinating facts about any subject you can think of.
        <br />
        From "Ancient Rome" to "History of competitive eating" — the world is your syllabus.
      </p>
      <div className="hero-badges">
        <a href={`/download/appstore${ATTRIBUTION_QUERY}`} target="_blank" rel="noopener noreferrer" aria-label="Download on the App Store">
          <img src="/app_store_badge.svg" alt="" />
        </a>
        <a href={`/download/googleplay${ATTRIBUTION_QUERY}`} target="_blank" rel="noopener noreferrer" aria-label="Get it on Google Play">
          <img src="/google_play_badge.svg" alt="" />
        </a>
      </div>
      <div className="hero-scroll">
        <span>Scroll</span>
        <span className="arrow" />
      </div>
    </section>
  );
}

// ─────────────────────────────────────────────────────────
// Roulette — three styles
// ─────────────────────────────────────────────────────────
function SlotRoulette({ subjects, secondsPerItem = 0.8 }) {
  // Continuous vertical scroll — no flicker, no pause. Always readable.
  // Strip is duplicated; we scroll one full copy then loop seamlessly.
  const ITEM_H = 84;
  const totalH = subjects.length * ITEM_H;
  const duration = subjects.length * secondsPerItem;
  const animName = `slotScroll_${subjects.length}_${ITEM_H}`;
  const keyframes = `
    @keyframes ${animName} {
      from { transform: translate3d(0, 0, 0); }
      to   { transform: translate3d(0, -${totalH}px, 0); }
    }
  `;
  const stripStyle = { animation: `${animName} ${duration}s linear infinite` };

  return (
    <div className="roulette-shell">
      <style>{keyframes}</style>
      <div className="roulette-band" />
      <div className="roulette-reel">
        <div className="roulette-strip" style={stripStyle}>
          {[...subjects, ...subjects].map((s, i) => (
            <div key={i} className="roulette-item">{s}</div>
          ))}
        </div>
        <div className="roulette-band-clip" aria-hidden="true">
          <div className="roulette-strip" style={stripStyle}>
            {[...subjects, ...subjects].map((s, i) => (
              <div key={i} className="roulette-item roulette-item-accent">{s}</div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

function TickerRoulette({ subjects, speed = 3600 }) {
  // Horizontal ticker — center one highlighted
  const [centerIdx, setCenterIdx] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setCenterIdx((p) => (p + 1) % subjects.length), speed);
    return () => clearInterval(id);
  }, [subjects.length, speed]);

  const visible = useMemo(() => {
    return [-2, -1, 0, 1, 2].map((offset) => ({
      offset,
      subject: subjects[(centerIdx + offset + subjects.length * 5) % subjects.length],
    }));
  }, [centerIdx, subjects]);

  return (
    <div className="roulette-shell" style={{ height: 168 }}>
      <div className="roulette-band" style={{ left: '50%', transform: 'translate(-50%, -50%)', width: 360, right: 'auto' }} />
      <div className="roulette-reel" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {visible.map(({ offset, subject }) => (
          <div
            key={offset}
            className={`roulette-item ${offset === 0 ? 'center' : 'dim'}`}
            style={{
              position: 'absolute',
              left: '50%',
              top: '50%',
              width: 360,
              transform: `translate(calc(-50% + ${offset * 220}px), -50%)`,
              transition: 'transform .55s cubic-bezier(.2,.8,.2,1.05), opacity .4s',
              opacity: offset === 0 ? 1 : Math.max(0, 0.5 - Math.abs(offset) * 0.18),
              fontSize: offset === 0 ? 'clamp(22px, 3.2vw, 40px)' : 'clamp(14px, 1.6vw, 22px)',
            }}
          >
            {subject}
          </div>
        ))}
      </div>
    </div>
  );
}

function FadeRoulette({ subjects, speed = 3600 }) {
  // Calm fade-cycling
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setIdx((p) => (p + 1) % subjects.length), speed);
    return () => clearInterval(id);
  }, [subjects.length, speed]);

  return (
    <div className="roulette-shell">
      <div className="roulette-band" style={{ left: '50%', transform: 'translate(-50%, -50%)', width: 'min(720px, 88%)', right: 'auto' }} />
      <div className="roulette-reel">
        {subjects.map((s, i) => (
          <div
            key={i}
            className="roulette-item center"
            style={{
              top: '50%',
              transform: 'translateY(-50%)',
              opacity: i === idx ? 1 : 0,
              transition: 'opacity .8s ease',
            }}
          >
            {s}
          </div>
        ))}
      </div>
    </div>
  );
}

function Roulette({ subjects, variant, onJoin }) {
  const Inner = variant === 'ticker' ? TickerRoulette
              : variant === 'fade' ? FadeRoulette
              : SlotRoulette;
  return (
    <section className="roulette-section">
      <div className="section-eyebrow">Think of a subject — any subject</div>
      <h2 className="roulette-question">What would you like to learn?</h2>
      <Inner subjects={subjects} />
      <div className="roulette-cta">
        <button className="btn btn-primary" onClick={onJoin}>
          Download
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
        </button>
        <a href="#cards" className="btn btn-ghost">See examples</a>
      </div>
    </section>
  );
}

// ─────────────────────────────────────────────────────────
// Flying Cards on scroll — imperative rAF for 60fps
// ─────────────────────────────────────────────────────────

const clamp = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
const easeOut = (t) => 1 - Math.pow(1 - t, 3);
const lerp = (a, b, t) => a + (b - a) * t;

// Per-card exit direction so cards fly off in varied upward diagonals instead
// of all straight up. xFrac is fraction of viewport width; rZ is matching tilt.
const EXIT_DIRS = [
  { x: -0.55, rZ: -14 },
  { x:  0.65, rZ:  16 },
  { x: -0.7,  rZ: -18 },
  { x:  0.4,  rZ:  10 },
  { x: -0.3,  rZ:  -8 },
  { x:  0.6,  rZ:  20 },
  { x: -0.5,  rZ: -12 },
];

// Stack-of-cards. Scroll determines which card is *active* (a discrete index);
// the actual fly-off animation is owned by CSS transitions on `.is-gone` so it
// always plays smoothly to completion regardless of where the user stops
// scrolling. Cards are stacked at the same centered position — only the active
// one is visible (`.is-active`), all others sit at opacity 0 underneath. The
// final card is the marketing hook and never enters the gone state.
function FlyingCards({ questions }) {
  const trackRef = useRef(null);
  const introRef = useRef(null);
  const cardRefs = useRef([]);
  const total = questions.length;
  const activeRef = useRef(0);  // last applied active index — gates className updates

  useEffect(() => {
    const track = trackRef.current;
    if (!track) return;
    let raf = null;

    const applyActive = (next) => {
      for (let i = 0; i < total; i++) {
        const el = cardRefs.current[i];
        if (!el) continue;
        // Three states encoded as classes:
        //   i <  next : .is-gone   (already flown off — CSS transition runs to off-screen)
        //   i == next : .is-active (visible at center)
        //   i >  next : (no class) (sitting underneath, opacity 0 in CSS)
        el.classList.toggle('is-gone',   i < next);
        el.classList.toggle('is-active', i === next);
      }
    };

    const update = () => {
      raf = null;
      const rect = track.getBoundingClientRect();
      const totalH = rect.height - window.innerHeight;
      const progress = totalH > 0 ? clamp(-rect.top / totalH, 0, 1) : 0;

      // Pinned intro headline fades over the last sliver of scroll, just before
      // the marketing card takes the stage on its own.
      if (introRef.current) {
        introRef.current.style.opacity = clamp((0.95 - progress) / 0.1, 0, 1);
      }

      // Discrete active index — each card owns 1/total of the scroll length.
      // Crossing a threshold flips the class; CSS handles the rest.
      const next = Math.min(total - 1, Math.floor(progress * total));
      if (next !== activeRef.current) {
        activeRef.current = next;
        applyActive(next);
      }
    };

    // Card sizing in JS, applied as inline style. The CSS-based approach
    // (lvh / var(--cards-vh) inside min()) wasn't reliably re-evaluating on
    // window resize for elements inside a sticky ancestor — inline styles
    // always win and recompute every resize event.
    const computeCardSize = () => {
      const vw = window.innerWidth;
      if (vw <= 720) {
        // Mobile: width-only sizing. Mobile browsers fire `resize` whenever
        // their address bar shows/hides on scroll, so anything depending on
        // window.innerHeight would resize the card mid-scroll. Phones are
        // always full-screen anyway, so vw alone is sufficient.
        return {
          w: Math.max(180, Math.min(320, vw * 0.68)),
          h: Math.max(280, Math.min(560, vw * 1.20)),
        };
      }
      // Desktop: bound by vh too so windowed browsers don't push the card
      // into the headline. Aspect 30/54 ≈ 0.56.
      const vh = window.innerHeight;
      return {
        w: Math.max(180, Math.min(380, vw * 0.26, vh * 0.30)),
        h: Math.max(320, Math.min(680, vw * 0.46, vh * 0.54)),
      };
    };
    const applyCardSizes = () => {
      const sz = computeCardSize();
      for (let i = 0; i < total; i++) {
        const el = cardRefs.current[i];
        if (!el) continue;
        el.style.width = `${sz.w}px`;
        el.style.height = `${sz.h}px`;
      }
    };

    const onScroll = () => {
      if (raf == null) raf = requestAnimationFrame(update);
    };
    const onResize = () => {
      applyCardSizes();
      if (raf == null) raf = requestAnimationFrame(update);
    };
    applyCardSizes();
    // Apply correct active classes on mount (in case page loads scrolled).
    activeRef.current = -1;
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onResize);
    update();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onResize);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [questions, total]);

  return (
    <section className="cards-section" id="cards">
      <div className="cards-track" ref={trackRef}>
        <div className="cards-stage">
          <div ref={introRef} className="cards-intro-pinned">
            <div className="section-eyebrow" style={{ marginBottom: 14 }}>Endless rabbit holes</div>
            <h2 style={{
              fontSize: 'clamp(24px, 4.6vw, 64px)',
              fontWeight: 900,
              letterSpacing: '-0.025em',
              lineHeight: 1.05,
              fontFamily: 'var(--font-sans)',
            }}>
              From personal finance <span style={{ color: 'var(--accent)' }}>to modern rocketry.</span>
            </h2>
          </div>

          <div className="cards-deck">
            {questions.map((q, i) => {
              const dir = EXIT_DIRS[i % EXIT_DIRS.length];
              return (
                <div
                  key={i}
                  ref={(el) => { cardRefs.current[i] = el; }}
                  // Card 0 starts active so the page renders with it visible
                  // before JS runs. JS will reconcile if scrolled deeper.
                  className={i === 0 ? 'fly-card is-active' : 'fly-card'}
                  style={{
                    // Higher z-index on earlier cards so the active one paints
                    // over the upcoming cards (which sit underneath at opacity 0).
                    zIndex: total - i,
                    // Per-card diagonal exit direction (consumed by CSS .is-gone).
                    '--exit-x': `${dir.x * 100}vw`,
                    '--exit-rz': `${dir.rZ}deg`,
                  }}
                >
                  <div className="fly-card-topic">{q.eyebrow}</div>
                  <div className="fly-card-body">
                    <div className="fly-card-label">QUESTION</div>
                    <div className="fly-card-q">{q.q}</div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────────────────────────────────────────────────────
// Download
// ─────────────────────────────────────────────────────────
const Download = React.forwardRef(function Download({ }, ref) {
  return (
    <section className="download" ref={ref} id="join">
      <div className="section-eyebrow">Ready when you are.</div>
      <h2>
        Erudity is out on <span className="accent">iOS &amp; Android.</span>
      </h2>
      <p className="download-sub">
        Pick a subject. Get a course in seconds.
      </p>
      <div className="download-badges">
        <a href={`/download/appstore${ATTRIBUTION_QUERY}`} target="_blank" rel="noopener noreferrer" aria-label="Download on the App Store">
          <img src="/app_store_badge.svg" alt="" />
        </a>
        <a href={`/download/googleplay${ATTRIBUTION_QUERY}`} target="_blank" rel="noopener noreferrer" aria-label="Get it on Google Play">
          <img src="/google_play_badge.svg" alt="" />
        </a>
      </div>
    </section>
  );
});

// ─────────────────────────────────────────────────────────
// Footer
// ─────────────────────────────────────────────────────────
function Footer() {
  return (
    <footer className="site-footer">
      <div className="footer-row">
        <div className="footer-brand">
          <a href="#top" className="brand">
            <span className="brand-mark" style={{ color: 'white' }}><BrainMark size={20} /></span>
            <span className="brand-word">ERUDITY</span>
          </a>
          <p>Erudity — the world is your syllabus.</p>
        </div>
        <div className="footer-links">
          <div className="footer-col">
            <h4>Legal</h4>
            <a href="/support/privacy">Privacy Policy</a>
            <a href="/support/terms">Terms of Service</a>
          </div>
          <div className="footer-col">
            <h4>Support</h4>
            <a href="/contact">Contact us</a>
          </div>
        </div>
      </div>
      <div className="footer-bottom">
        <span>{new Date().getFullYear()} · ERUDITY LTD</span>
      </div>
    </footer>
  );
}

// Expose
Object.assign(window, { BrainMark, Header, Hero, Roulette, FlyingCards, Download, Footer });
