// arcsec-hero.jsx — the full-page arcsec hero.
// The honest "magnification" model: a true angle (faint) collapses toward the
// sightline as it shrinks; a bold, magnified copy stays visible while a ×N
// counter discloses exactly how much we zoomed to keep it on screen.
const { useState, useRef, useEffect } = React;
const { useRaf, clamp, lerp, easeInOut, formatAngle, fmtInt, UnitChain, StarField } = window;

const ACCENT = '#f2b350';
const TXT = 'rgba(255,255,255,0.94)';
const DIM = 'rgba(255,255,255,0.46)';
const FAINT = 'rgba(255,255,255,0.30)';
const MONO = "'IBM Plex Mono', ui-monospace, monospace";

// ── scene math ──────────────────────────────────────────────────────
// p in [0,1]: 0 = wide open (32°, unmagnified), 1 = a true 1.000″.
const TH_OPEN = 16;            // widest true half-angle (deg) → 32° full
const TH_MIN = 0.5 / 3600;     // 0.5″ half-angle → 1.000″ full
const AP_FLOOR = 0.5;          // bold/magnified half-angle never drops below 1° full
function scene(p) {
  const trueHalf = Math.exp(lerp(Math.log(TH_OPEN), Math.log(TH_MIN), clamp(p, 0, 1)));
  const apHalf = Math.max(trueHalf, AP_FLOOR);
  const mag = apHalf / trueHalf;        // exact: bold is this many times the true
  return { trueHalf, apHalf, mag, trueDeg: trueHalf * 2 };
}

// ── the instrument ──────────────────────────────────────────────────
const VB = { W: 640, H: 540, vx: 70, vy: 270, L: 556, arcR: 92 };
function Instrument({ apHalf, trueHalf, mag, ringPhase }) {
  const { W, H, vx, vy, L, arcR } = VB;
  const ah = (apHalf * Math.PI) / 180, th = (trueHalf * Math.PI) / 180;
  const P = (ang, r, sign) => [vx + r * Math.cos(ang), vy - sign * r * Math.sin(ang)];
  const bt = P(ah, L, 1), bb = P(ah, L, -1);     // bold (magnified) tips
  const tt = P(th, L, 1), tb = P(th, L, -1);     // true tips
  const at = P(ah, arcR, 1), ab = P(ah, arcR, -1);
  const arc = `M ${at[0].toFixed(2)} ${at[1].toFixed(2)} A ${arcR} ${arcR} 0 0 1 ${ab[0].toFixed(2)} ${ab[1].toFixed(2)}`;
  const zoom = mag > 1.25;
  const ringOp = clamp(Math.log10(mag) / 3.56, 0, 1) * 0.5;

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%" preserveAspectRatio="xMidYMid meet"
      style={{ display: 'block', overflow: 'visible' }}>
      <defs>
        <linearGradient id="wedge" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stopColor={ACCENT} stopOpacity="0.22" />
          <stop offset="100%" stopColor={ACCENT} stopOpacity="0.02" />
        </linearGradient>
      </defs>

      {/* zoom rings — drift outward from the vertex while magnifying */}
      {zoom && [0, 1, 2, 3, 4].map((i) => {
        const f = ((i + ringPhase) % 5) / 5;
        return <circle key={i} cx={vx} cy={vy} r={20 + f * (L * 0.96)} fill="none"
          stroke={ACCENT} strokeWidth="1" strokeOpacity={ringOp * (1 - f) * 0.9} />;
      })}

      {/* sightline */}
      <line x1={vx} y1={vy} x2={vx + L} y2={vy} stroke={FAINT} strokeWidth="1" strokeDasharray="2 7" />

      {/* the magnified (bold) wedge */}
      <path d={`M ${vx} ${vy} L ${bt[0].toFixed(2)} ${bt[1].toFixed(2)} L ${bb[0].toFixed(2)} ${bb[1].toFixed(2)} Z`} fill="url(#wedge)" />

      {/* true angle — faint, collapses onto the sightline */}
      <line x1={vx} y1={vy} x2={tt[0]} y2={tt[1]} stroke="rgba(255,255,255,0.34)" strokeWidth="1.25" />
      <line x1={vx} y1={vy} x2={tb[0]} y2={tb[1]} stroke="rgba(255,255,255,0.34)" strokeWidth="1.25" />

      {/* bold magnified rays */}
      <line x1={vx} y1={vy} x2={bt[0]} y2={bt[1]} stroke={ACCENT} strokeWidth="2.4" strokeLinecap="round" />
      <line x1={vx} y1={vy} x2={bb[0]} y2={bb[1]} stroke={ACCENT} strokeWidth="2.4" strokeLinecap="round" />
      <path d={arc} fill="none" stroke={ACCENT} strokeWidth="1.6" strokeOpacity="0.85" />

      {/* vertex */}
      <circle cx={vx} cy={vy} r="4.5" fill={ACCENT} />
      <circle cx={vx} cy={vy} r="9" fill="none" stroke={ACCENT} strokeWidth="1" strokeOpacity="0.4" />
    </svg>
  );
}

// real-world equivalence — gives the true angle a tangible scale.
// hand mnemonics for large angles; a 1-euro coin (Ø23.25mm) at a computed
// distance for the small ones (where the sense of scale actually bites).
function scaleAnchor(deg) {
  if (deg >= 18) return 'about a hand span at arm\u2019s length';
  if (deg >= 6) return 'about a clenched fist at arm\u2019s length';
  if (deg >= 1.6) return 'about two fingers at arm\u2019s length';
  if (deg >= 0.6) return 'about a fingernail at arm\u2019s length';
  const d = 0.02325 / Math.tan((deg * Math.PI) / 180); // metres
  const dist = d >= 1000 ? (d / 1000).toFixed(1).replace(/\.0$/, '') + ' km'
    : d >= 1 ? Math.round(d) + ' m'
      : Math.round(d * 100) + ' cm';
  return 'a \u20ac1 coin seen from ' + dist;
}
const fmtShown = (deg) => (deg >= 10 ? deg.toFixed(0) : deg.toFixed(1)) + '\u00b0';

// ── readouts ────────────────────────────────────────────────────────
function Def() {
  return (
    <p style={{ margin: '26px 0 0', fontSize: 15.5, lineHeight: 1.7, color: DIM, maxWidth: 400 }}>
      <span style={{ color: TXT }}>arcsecond</span>, n. — one&nbsp;3,600<sup style={{ fontSize: '0.7em' }}>th</sup> of a degree.
    </p>
  );
}
function Magnification({ mag, shownDeg }) {
  const on = mag > 1.25;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
      <div style={{ fontSize: 30, fontWeight: 500, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums', color: on ? ACCENT : 'rgba(255,255,255,0.4)' }}>
        {'\u00d7'}{fmtInt(mag)}
      </div>
      <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: FAINT }}>
        {on ? `the real angle, shown at ${fmtShown(shownDeg)}` : 'actual size · no magnification'}
      </div>
    </div>
  );
}

// ── the page ────────────────────────────────────────────────────────
const CYCLE = [4000, 1700, 3000, 1300]; // open→1″ · hold · back · hold
function autoP(t) {
  const total = CYCLE.reduce((a, b) => a + b, 0);
  let u = t % total;
  if (u < CYCLE[0]) return easeInOut(u / CYCLE[0]); u -= CYCLE[0];
  if (u < CYCLE[1]) return 1; u -= CYCLE[1];
  if (u < CYCLE[2]) return 1 - easeInOut(u / CYCLE[2]);
  return 0;
}

function ArcsecHero() {
  const [, force] = useState(0);
  const pRef = useRef(0);
  const ringPhase = useRef(0);

  useRaf((el, abs) => {
    pRef.current = lerp(pRef.current, autoP(abs), 0.12);
    ringPhase.current = (ringPhase.current + 0.0055) % 1;
    force((n) => (n + 1) & 0xffff);
  });

  const { apHalf, trueHalf, mag, trueDeg } = scene(pRef.current);
  const f = formatAngle(trueDeg);
  const shownDeg = apHalf * 2;
  const anchor = scaleAnchor(trueDeg);
  const starScale = 1 + clamp(Math.log10(mag) / 3.56, 0, 1) * 0.9;

  return (
    <div className="hero">
      <StarField seed={3} scale={starScale} origin="11% 50%" />
      <div className="hero-text">
        <h1 className="wordmark">arcsec<span style={{ color: ACCENT, fontWeight: 400 }}>{'\u2033'}</span></h1>
        <Def />
        <div className="hero-readout">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 5, color: 'rgba(255,255,255,0.96)' }}>
              <span style={{ fontSize: 78, fontWeight: 500, letterSpacing: '-0.04em', fontVariantNumeric: 'tabular-nums', lineHeight: 0.9 }}>{f.num}</span>
              <span style={{ fontSize: 46, fontWeight: 400, color: ACCENT }}>{f.unit}</span>
            </div>
            <UnitChain activeKey={f.key} accent={ACCENT} />
            <div className="hero-anchor" style={{ fontSize: 13, color: DIM, letterSpacing: '0.01em' }}>
              {'\u2248 '}{anchor}
            </div>
          </div>
          <Magnification mag={mag} shownDeg={shownDeg} />
        </div>
      </div>
      <div className="hero-diagram">
        <Instrument apHalf={apHalf} trueHalf={trueHalf} mag={mag} ringPhase={ringPhase.current} />
      </div>
    </div>
  );
}

Object.assign(window, { ArcsecHero });
