// scenes.jsx — six explainer animations for Attesta's protocol.
// Each scene is a self-contained React component rendered inside a <Stage>.
// They share a small visual vocabulary (envelope, timeline, notary, hash).

const INK   = '#0b1d3a';
const PAPER = '#ffffff';
const SOFT  = '#E9F1FF';
const MUTE  = '#6c7a93';
const LINE  = '#d8dfeb';
const BLUE  = '#4D61FC';
const DEEP  = '#00396B';
const RED   = '#d24a4a';
const SEAL  = '#4D61FC';
const MONO  = "'JetBrains Mono', ui-monospace, SFMono-Regular, monospace";
const SANS  = "'Lato', Helvetica, Arial, sans-serif";

// Deterministic pseudo-hash for visual filler
function hex(seed, len = 6) {
  let s = '';
  let x = seed * 9301 + 49297;
  for (let i = 0; i < len; i++) {
    x = (x * 9301 + 49297) % 233280;
    s += '0123456789abcdef'[Math.floor((x / 233280) * 16)];
  }
  return s;
}

// ───────────────────────────── shared bits ──────────────────────────────────

// Caption is intentionally a no-op when the scenes are embedded in the
// website — the page's own step copy already serves this purpose.
function Caption() { return null; }

function Envelope({ x, y, w = 200, h = 130, seal = false, tampered = false, label, opacity = 1, scale = 1, rotation = 0 }) {
  return (
    <div style={{
      position: 'absolute', left: x, top: y,
      width: w, height: h,
      opacity,
      transform: `translate(-50%, -50%) scale(${scale}) rotate(${rotation}deg)`,
      willChange: 'transform, opacity',
    }}>
      <div style={{
        position: 'absolute', inset: 0,
        background: PAPER,
        border: `1.5px solid ${tampered ? RED : INK}`,
        borderRadius: 2,
        boxShadow: '0 2px 0 rgba(21,20,15,0.08)',
      }}>
        {/* text lines */}
        <div style={{ position: 'absolute', left: 14, top: 18, right: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
          <div style={{ height: 4, background: tampered ? RED : LINE, width: '70%', opacity: tampered ? 0.7 : 1 }} />
          <div style={{ height: 4, background: LINE, width: '92%' }} />
          <div style={{ height: 4, background: LINE, width: '85%' }} />
          <div style={{ height: 4, background: tampered ? RED : LINE, width: '60%' }} />
        </div>
        {/* seal — digital signature mark */}
        {seal && (
          <div style={{
            position: 'absolute', right: 10, bottom: 10,
            width: 44, height: 44,
            border: `1.5px solid ${SEAL}`,
            color: SEAL,
            borderRadius: '50%',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontFamily: MONO, fontSize: 9, fontWeight: 700,
            letterSpacing: '0.05em',
            transform: 'rotate(-8deg)',
            background: 'rgba(233,241,255,0.85)',
          }}>SIGN</div>
        )}
        {tampered && (
          <div style={{
            position: 'absolute', left: '50%', top: '50%',
            transform: 'translate(-50%, -50%) rotate(-6deg)',
            color: RED, fontFamily: MONO, fontSize: 11, fontWeight: 700,
            letterSpacing: '0.1em',
            border: `1px solid ${RED}`, padding: '2px 6px',
            background: 'rgba(246,244,239,0.9)',
          }}>ALTERED</div>
        )}
      </div>
      {label && (
        <div style={{
          position: 'absolute', left: 0, right: 0, top: h + 8,
          textAlign: 'center', fontFamily: MONO, fontSize: 11, color: MUTE,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>{label}</div>
      )}
    </div>
  );
}

function Tick({ size = 26, color = BLUE, progress = 1 }) {
  // Progress 0..1 reveals the check stroke.
  const L = 32;
  const dash = L * progress;
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none">
      <circle cx="16" cy="16" r="14" stroke={color} strokeWidth="1.5" opacity="0.9" />
      <path d="M9 16.5l4.5 4.5L23 11" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"
        strokeDasharray={`${dash} ${L}`} />
    </svg>
  );
}

function Cross({ size = 26, color = RED, progress = 1 }) {
  const L = 18;
  const dash = L * progress;
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none">
      <circle cx="16" cy="16" r="14" stroke={color} strokeWidth="1.5" />
      <path d="M11 11l10 10M21 11l-10 10" stroke={color} strokeWidth="2.2" strokeLinecap="round"
        strokeDasharray={`${dash} ${L}`} />
    </svg>
  );
}

// ═══════════════════════════ SCENE 1 — SIGN AND SUBMIT ═════════════════════
// Three real senders submit signed records. One bad actor tries to slip in a
// tampered record. The door verifies every signature on arrival; admitted
// records land in the right-hand ledger so you can see what's been accepted
// "so far" as the scene plays out.

function PersonIcon({ size = 30, color = INK }) {
  return (
    <svg width={size} height={size} viewBox="0 0 30 30" fill="none">
      <circle cx="15" cy="11" r="4.5" stroke={color} strokeWidth="1.5" />
      <path d="M5 25.5c0-4 4.5-6.5 10-6.5s10 2.5 10 6.5" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
    </svg>
  );
}
function HospitalIcon({ size = 30, color = INK }) {
  return (
    <svg width={size} height={size} viewBox="0 0 30 30" fill="none">
      <rect x="5" y="9" width="20" height="17" stroke={color} strokeWidth="1.5" />
      <path d="M15 13v8M11 17h8" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
      <path d="M10 9V5h10v4" stroke={color} strokeWidth="1.5" />
    </svg>
  );
}
function ShieldIcon({ size = 30, color = INK }) {
  return (
    <svg width={size} height={size} viewBox="0 0 30 30" fill="none">
      <path d="M15 4l9 3v8c0 6-4 9.5-9 11-5-1.5-9-5-9-11V7l9-3z" stroke={color} strokeWidth="1.5" strokeLinejoin="round" />
      <path d="M11 15l2.7 2.7L19 12.5" stroke={color} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}
function MaskIcon({ size = 30, color = RED }) {
  return (
    <svg width={size} height={size} viewBox="0 0 30 30" fill="none">
      <path d="M4 11c4-2 8-3 11-3s7 1 11 3c0 5-2 11-11 13C6 22 4 16 4 11z" stroke={color} strokeWidth="1.5" strokeLinejoin="round" />
      <circle cx="11" cy="13" r="1.2" fill={color} />
      <circle cx="19" cy="13" r="1.2" fill={color} />
    </svg>
  );
}

function Scene1() {
  const t = useTime();

  // ─── Layout ─────────────────────────────────────────────────────────
  const SENDER_X   = 170;
  const DOOR_X     = 620;
  const VERIFY_X   = 760;   // verification panel sits just past the door
  const LEDGER_X   = 1000;
  const LEDGER_W   = 240;

  // Four senders along the left, evenly spaced
  const ROW = { sarah: 170, hospital: 305, board: 440, attacker: 575 };

  // ─── Submissions ────────────────────────────────────────────────────
  // Sequence: Sarah ✓ → Hospital ✓ → Attacker ✗ → Board ✓
  // Each beat ≈ 3.0s.  Loop ≈ 13s.
  const EVENTS = [
    { id: 'e1', sender: 'sarah',    label: 'Dr. Sarah Chen',         outcome: 'admit',
      recordType: 'Board certification',  recId: '#4012',
      bodyHash: hex(11, 12), sigHash: hex(11, 12), senderKey: hex(31, 6),
      startAt: 0.2 },
    { id: 'e2', sender: 'hospital', label: 'Mercy General Hospital', outcome: 'admit',
      recordType: 'Privileging update',   recId: '#4013',
      bodyHash: hex(22, 12), sigHash: hex(22, 12), senderKey: hex(82, 6),
      startAt: 3.3 },
    { id: 'e3', sender: 'attacker', label: 'unknown sender',         outcome: 'reject',
      recordType: 'Forged credential',    recId: null,
      bodyHash: hex(99, 12), sigHash: hex(91, 12), senderKey: hex(13, 6),
      startAt: 6.4, fakeName: 'Dr. Sarah Chen' },
    { id: 'e4', sender: 'board',    label: 'State Licensing Board',  outcome: 'admit',
      recordType: 'License verification', recId: '#4014',
      bodyHash: hex(44, 12), sigHash: hex(44, 12), senderKey: hex(55, 6),
      startAt: 9.5 },
  ];

  // Per-event offsets (relative to startAt)
  const D = {
    sign:    0.0,   // ✎ signing… label flies onto envelope
    depart:  0.8,   // envelope leaves sender
    arrive:  1.8,   // envelope hits door
    check:   2.0,   // chips light up
    decide:  2.4,   // ADMIT or REJECT stamp
    land:    2.8,   // admitted → slides to ledger | rejected → bounces
    done:    3.2,   // off-screen, ready for next
  };

  // What's currently happening?
  const active = EVENTS.findLast
    ? EVENTS.findLast(e => t >= e.startAt && t < e.startAt + D.done + 0.4)
    : [...EVENTS].reverse().find(e => t >= e.startAt && t < e.startAt + D.done + 0.4);

  // Build ledger contents so far (what's been ADMITTED before now)
  const admitted = EVENTS.filter(e =>
    e.outcome === 'admit' && t >= e.startAt + D.land
  );

  // ─── Envelope position for an event ─────────────────────────────────
  const envOf = (ev) => {
    const lt = t - ev.startAt;
    let x, y, opacity = 1, rotation = 0;

    if (lt < D.depart) {
      x = SENDER_X + 60; y = ROW[ev.sender];
      opacity = Math.min(1, lt / 0.25);
    } else if (lt < D.arrive) {
      const p = Easing.easeInOutCubic((lt - D.depart) / (D.arrive - D.depart));
      x = (SENDER_X + 60) + ((DOOR_X - 60) - (SENDER_X + 60)) * p;
      y = ROW[ev.sender] + (390 - ROW[ev.sender]) * p;
    } else if (lt < D.land) {
      x = DOOR_X - 60; y = 390;
    } else if (lt < D.done) {
      const p = (lt - D.land) / (D.done - D.land);
      if (ev.outcome === 'admit') {
        // Slide into ledger
        const tx = LEDGER_X + LEDGER_W / 2;
        const slotIdx = admitted.indexOf(ev);
        const ty = 178 + Math.max(0, slotIdx) * 78 + 32;
        x = (DOOR_X - 60) + (tx - (DOOR_X - 60)) * Easing.easeInOutCubic(p);
        y = 390 + (ty - 390) * Easing.easeInOutCubic(p);
        opacity = 1 - p * 0.4;
      } else {
        // Bounce back to sender, fade
        x = (DOOR_X - 60) - 240 * Easing.easeInQuad(p);
        y = 390 + 80 * Math.sin(p * Math.PI);
        rotation = -10 * p;
        opacity = 1 - p;
      }
    } else {
      opacity = 0; x = -200; y = 0;
    }
    return { x, y, opacity, rotation, lt };
  };

  // ─── SENDERS ────────────────────────────────────────────────────────
  const SENDERS = [
    { id: 'sarah',    name: 'Dr. Sarah Chen',          role: 'physician',         Icon: PersonIcon,   key: hex(31, 6) },
    { id: 'hospital', name: 'Mercy General Hospital',  role: 'employer',          Icon: HospitalIcon, key: hex(82, 6) },
    { id: 'board',    name: 'State Licensing Board',   role: 'authority',         Icon: ShieldIcon,   key: hex(55, 6) },
    { id: 'attacker', name: 'Unknown sender',          role: 'unverified',        Icon: MaskIcon,     key: hex(13, 6), bad: true },
  ];

  return (
    <div style={{ position: 'absolute', inset: 0 }}>

      {/* ── HEADER STRIP ── */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: 24,
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        fontFamily: MONO, fontSize: 10, letterSpacing: '0.18em', fontWeight: 700,
      }}>
        <span style={{ color: BLUE }}>SENDERS</span>
        <span style={{ color: BLUE }}>DOOR · VERIFY EVERY ARRIVAL</span>
        <span style={{ color: BLUE }}>ADMITTED RECORDS</span>
      </div>

      {/* ── SENDERS COLUMN ── */}
      {SENDERS.map(s => {
        const isActive = active && active.sender === s.id;
        return (
          <React.Fragment key={s.id}>
            {/* icon disc */}
            <div style={{
              position: 'absolute', left: SENDER_X, top: ROW[s.id],
              transform: 'translate(-50%, -50%)',
              width: 60, height: 60,
              border: `1.5px solid ${s.bad ? RED : INK}`,
              borderStyle: s.bad ? 'dashed' : 'solid',
              borderRadius: '50%', background: PAPER,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              opacity: isActive ? 1 : 0.85,
              transition: 'opacity 200ms',
            }}>
              <s.Icon size={28} color={s.bad ? RED : INK} />
            </div>
            {/* name + key */}
            <div style={{
              position: 'absolute', left: SENDER_X + 42, top: ROW[s.id] - 22,
              fontFamily: SANS, fontSize: 14, fontWeight: 700,
              color: s.bad ? RED : INK,
            }}>{s.name}</div>
            <div style={{
              position: 'absolute', left: SENDER_X + 42, top: ROW[s.id] - 4,
              fontFamily: SANS, fontSize: 11, color: MUTE,
            }}>{s.role}</div>
            <div style={{
              position: 'absolute', left: SENDER_X + 42, top: ROW[s.id] + 12,
              fontFamily: MONO, fontSize: 10, color: s.bad ? RED : MUTE,
              letterSpacing: '0.06em',
            }}>key 0x{s.key}{s.bad ? ' · unknown' : ''}</div>
          </React.Fragment>
        );
      })}

      {/* dashed corridor from each sender to door */}
      <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
        {SENDERS.map(s => (
          <line key={s.id}
            x1={SENDER_X + 32} y1={ROW[s.id]}
            x2={DOOR_X - 22} y2={390}
            stroke={LINE} strokeWidth="1" strokeDasharray="3 5" opacity="0.5" />
        ))}
      </svg>

      {/* ── DOOR ── */}
      <div style={{
        position: 'absolute', left: DOOR_X, top: 140, width: 3, height: 510,
        transform: 'translate(-50%, 0)',
        background: BLUE,
      }} />
      <div style={{
        position: 'absolute', left: DOOR_X, top: 110,
        transform: 'translate(-50%, -100%)',
        fontFamily: MONO, fontSize: 11, color: BLUE,
        letterSpacing: '0.20em', fontWeight: 700,
      }}>DOOR</div>
      <div style={{
        position: 'absolute', left: DOOR_X, top: 660,
        transform: 'translate(-50%, 0)',
        fontFamily: MONO, fontSize: 10, color: MUTE,
        letterSpacing: '0.10em',
      }}>signature · identity · authorization</div>

      {/* ── ENVELOPES (one per active event) ── */}
      {EVENTS.map(ev => {
        const lt = t - ev.startAt;
        if (lt < 0 || lt > D.done + 0.4) return null;
        const { x, y, opacity, rotation } = envOf(ev);
        const failed = ev.outcome === 'reject' && lt >= D.decide;
        const showSig = lt >= 0.5;
        return (
          <div key={ev.id} style={{
            position: 'absolute', left: x, top: y,
            transform: `translate(-50%, -50%) rotate(${rotation}deg)`,
            width: 140, height: 90,
            background: PAPER,
            border: `1.5px solid ${failed ? RED : INK}`,
            opacity, padding: '10px 12px',
            boxShadow: lt < D.arrive ? '0 2px 0 rgba(11,29,58,0.06)' : 'none',
            zIndex: 4,
            transition: 'border-color 160ms',
          }}>
            <div style={{ fontFamily: MONO, fontSize: 9, color: MUTE,
              letterSpacing: '0.10em', marginBottom: 4 }}>
              {ev.recordType.toUpperCase()}
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
              <div style={{ height: 3, background: LINE, width: '90%' }} />
              <div style={{ height: 3, background: failed ? RED : LINE, width: '70%', opacity: failed ? 0.7 : 1 }} />
              <div style={{ height: 3, background: LINE, width: '82%' }} />
            </div>
            {showSig && (
              <div style={{
                position: 'absolute', right: 8, bottom: 6,
                fontFamily: MONO, fontSize: 8, fontWeight: 700,
                color: failed ? RED : BLUE,
                letterSpacing: '0.04em',
              }}>SIG 0x{ev.sigHash.slice(0, 6)}</div>
            )}
          </div>
        );
      })}

      {/* ── Signing label flies in from sender → envelope ── */}
      {EVENTS.map(ev => {
        const lt = t - ev.startAt;
        if (lt < D.sign || lt > D.depart) return null;
        const p = (lt - D.sign) / (D.depart - D.sign);
        return (
          <div key={ev.id + '-sign'} style={{
            position: 'absolute',
            left: SENDER_X + 20 + 30 * p,
            top: ROW[ev.sender] - 30 + 25 * p,
            transform: 'translate(-50%, -50%)',
            fontFamily: MONO, fontSize: 11, fontWeight: 700,
            color: ev.outcome === 'reject' ? RED : BLUE,
            letterSpacing: '0.10em',
            opacity: 1 - p,
          }}>✎ signing…</div>
        );
      })}

      {/* ── VERIFICATION CHIPS (inline, just past the door) ── */}
      {active && (() => {
        const lt = t - active.startAt;
        if (lt < D.check - 0.1 || lt > D.land) return null;
        const opac = clamp((lt - (D.check - 0.1)) / 0.25, 0, 1) *
                     clamp((D.land + 0.1 - lt) / 0.2, 0, 1);
        const sigOk = active.outcome === 'admit';
        const idOk  = active.outcome === 'admit';
        const claimedName = active.fakeName || active.label;

        return (
          <div style={{
            position: 'absolute', left: VERIFY_X, top: 305,
            width: 220, opacity: opac,
            fontFamily: MONO, fontSize: 11,
            zIndex: 5,
          }}>
            <div style={{
              fontSize: 9, color: BLUE, letterSpacing: '0.18em',
              fontWeight: 700, marginBottom: 8,
            }}>CHECK</div>

            <VerifyChip
              label="signature"
              ok={sigOk}
              hint={sigOk
                ? `matches body 0x${active.bodyHash.slice(0,6)}…`
                : 'broken — body altered after signing'} />
            <VerifyChip
              label="identity"
              ok={idOk}
              hint={idOk
                ? `key 0x${active.senderKey} → ${active.label}`
                : `claims "${claimedName}" — key 0x${active.senderKey} not on file`} />
            <VerifyChip
              label="authorization"
              ok={idOk}
              hint={idOk
                ? 'permitted sender'
                : 'no authorization'} />

            {lt >= D.decide && (
              <div style={{
                marginTop: 10, padding: '6px 10px',
                background: sigOk ? BLUE : RED,
                color: '#fff', textAlign: 'center',
                fontWeight: 700, letterSpacing: '0.14em',
                fontSize: 11,
              }}>
                {sigOk ? '✓ ADMITTED' : '✕ REJECTED'}
              </div>
            )}
          </div>
        );
      })()}

      {/* ── LEDGER (aggregate picture so far) ── */}
      <div style={{
        position: 'absolute', left: LEDGER_X, top: 140, width: LEDGER_W,
      }}>
        <div style={{
          padding: '10px 14px',
          background: SOFT, border: `1.5px solid ${BLUE}`,
          fontFamily: MONO, fontSize: 11,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          marginBottom: 8,
        }}>
          <span style={{ color: DEEP, fontWeight: 700, letterSpacing: '0.14em' }}>LEDGER</span>
          <span style={{ color: BLUE, fontWeight: 700, letterSpacing: '0.08em' }}>
            {admitted.length} / 3
          </span>
        </div>

        {/* 3 slots — fill as admitted */}
        {[0, 1, 2].map(i => {
          const ev = admitted[i];
          const empty = !ev;
          return (
            <div key={i} style={{
              position: 'relative',
              padding: '10px 14px', marginBottom: 8,
              background: PAPER,
              border: empty ? `1px dashed ${LINE}` : `1.5px solid ${INK}`,
              minHeight: 56,
              opacity: empty ? 0.6 : 1,
              transition: 'opacity 200ms',
            }}>
              {empty ? (
                <div style={{
                  fontFamily: MONO, fontSize: 10, color: MUTE,
                  letterSpacing: '0.12em',
                }}>awaiting…</div>
              ) : (
                <>
                  <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                    <span style={{ fontFamily: MONO, fontSize: 9, color: MUTE,
                      letterSpacing: '0.10em' }}>{ev.recordType.toUpperCase()}</span>
                    <span style={{ fontFamily: MONO, fontSize: 9, color: BLUE,
                      fontWeight: 700, letterSpacing: '0.08em' }}>{ev.recId}</span>
                  </div>
                  <div style={{ fontFamily: SANS, fontSize: 12, fontWeight: 700,
                    color: INK, marginTop: 4 }}>{ev.label}</div>
                  <div style={{ fontFamily: MONO, fontSize: 9, color: MUTE,
                    marginTop: 2, letterSpacing: '0.04em' }}>
                    sig 0x{ev.sigHash.slice(0, 8)}
                  </div>
                </>
              )}
            </div>
          );
        })}

        {/* Rejected counter */}
        {t > 9.0 && (
          <div style={{
            marginTop: 4, padding: '6px 12px',
            border: `1px dashed ${RED}`,
            fontFamily: MONO, fontSize: 10, color: RED,
            letterSpacing: '0.10em',
            display: 'flex', justifyContent: 'space-between',
          }}>
            <span>REJECTED</span>
            <span style={{ fontWeight: 700 }}>1</span>
          </div>
        )}
      </div>

      <Caption>
        Three trusted senders — physician, hospital, licensing board — each sign and submit records. An impostor's forged record is turned away at the door.{' '}
        <span style={{ color: MUTE }}>The ledger on the right is the running picture of what's been admitted so far.</span>
      </Caption>
    </div>
  );
}

// Small verify chip used by Scene 1's inline checks
function VerifyChip({ label, ok, hint }) {
  const color = ok ? BLUE : RED;
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      marginBottom: 8,
    }}>
      <div style={{ marginTop: 2 }}>
        {ok ? <Tick size={16} color={BLUE} /> : <Cross size={16} color={RED} />}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: MONO, fontSize: 10, color, fontWeight: 700,
          letterSpacing: '0.10em' }}>{label.toUpperCase()}</div>
        <div style={{ fontFamily: MONO, fontSize: 9, color: MUTE,
          marginTop: 1, lineHeight: 1.4, letterSpacing: '0.02em' }}>{hint}</div>
      </div>
    </div>
  );
}


// ═════════════════════════ SCENE 2 — RECORD & RECEIPT ══════════════════════

function Scene2() {
  const t = useTime();

  // ─── Layout ─────────────────────────────────────────────────────────
  const RAIL_Y    = 360;
  const SLOT_W    = 96;
  const DROP_FROM = 120;   // envelope falls onto the rail from this y

  // Existing entries already on the rail when the scene starts
  const existing = [
    { id: 4012, x: 220, sender: '—', hash: hex(40, 8) },
    { id: 4013, x: 320, sender: '—', hash: hex(41, 8) },
    { id: 4014, x: 420, sender: '—', hash: hex(42, 8) },
  ];

  // New submissions — three records get added to the timeline in sequence.
  // Actors were established in scene 1; here each just shows up as an entry
  // being added to the log.
  const NEW = [
    { id: 4015, slotX: 540, name: 'Dr. Sarah Chen',          recordType: 'Board certification',  startAt: 0.5, hash: hex(15, 10), time: '14:21:03Z' },
    { id: 4016, slotX: 640, name: 'Mercy General Hospital',  recordType: 'Privileging update',   startAt: 3.0, hash: hex(16, 10), time: '14:21:18Z' },
    { id: 4017, slotX: 740, name: 'State Licensing Board',   recordType: 'License verification', startAt: 5.5, hash: hex(17, 10), time: '14:21:34Z' },
  ];

  // Per-submission relative beats
  const D = {
    rise:    0.0,   // envelope rises from sender
    arrive:  0.9,   // arrives at its slot
    stamp:   1.1,   // STAMPED into rail (flash)
    receipt: 1.3,   // receipt panel appears on right
    settle:  1.8,   // settled, hold
  };

  // (no sender mapping needed here — actors are established in scene 1)

  // How many new entries have settled — drives the LEDGER STATE counter
  const settled = NEW.filter(n => t >= n.startAt + D.settle);
  const settledCount = settled.length;

  // ─── Envelope position for a new submission ─────────────────────────
  const envOf = (n) => {
    const lt = t - n.startAt;
    if (lt < D.rise) return null;
    if (lt < D.arrive) {
      const p = Easing.easeInCubic(lt / D.arrive);
      return { x: n.slotX, y: DROP_FROM + (RAIL_Y - DROP_FROM) * p, opacity: clamp(lt / 0.18, 0, 1), scale: 1, stage: 'flying' };
    }
    if (lt < D.stamp) {
      const p = (lt - D.arrive) / (D.stamp - D.arrive);
      return { x: n.slotX, y: RAIL_Y, opacity: 1, scale: 1 + 0.06 * (1 - p), stage: 'stamping' };
    }
    return { x: n.slotX, y: RAIL_Y, opacity: 1, scale: 1, stage: 'stamped' };
  };

  return (
    <div style={{ position: 'absolute', inset: 0 }}>

      {/* ── LEDGER STATE (top, the aggregate picture) ── */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: 30,
        padding: '12px 22px',
        background: SOFT, border: `1.5px solid ${BLUE}`,
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        fontFamily: MONO, fontSize: 12,
      }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
          <span style={{ fontWeight: 700, color: DEEP, letterSpacing: '0.16em' }}>PERMANENT TIMELINE</span>
          <span style={{ color: MUTE, letterSpacing: '0.06em' }}>append-only · one-way</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 20 }}>
          <span style={{ color: MUTE, letterSpacing: '0.08em' }}>
            length{' '}
            <span style={{ color: DEEP, fontWeight: 700 }}>{4014 + settledCount}</span>
          </span>
          <span style={{ color: MUTE, letterSpacing: '0.08em' }}>
            just stamped{' '}
            <span style={{ color: BLUE, fontWeight: 700 }}>{settledCount}</span>
          </span>
        </div>
      </div>

      {/* ── Timeline rail ── */}
      <div style={{
        position: 'absolute', left: 120, right: 380, top: RAIL_Y, height: 2,
        background: INK, opacity: 0.85,
      }} />
      {/* tick marks + position labels */}
      {[...existing.map(e => ({ x: e.x, label: `#${e.id}` })),
        ...NEW.map(n => ({ x: n.slotX, label: `#${n.id}` }))].map((m, i) => (
        <React.Fragment key={i}>
          <div style={{
            position: 'absolute', left: m.x, top: RAIL_Y - 8, width: 1, height: 18,
            background: INK, opacity: 0.55,
          }} />
          <div style={{
            position: 'absolute', left: m.x, top: RAIL_Y + 60,
            transform: 'translate(-50%, 0)',
            fontFamily: MONO, fontSize: 10, color: MUTE, letterSpacing: '0.06em',
          }}>{m.label}</div>
        </React.Fragment>
      ))}

      <div style={{
        position: 'absolute', left: 120, top: RAIL_Y - 28,
        fontFamily: MONO, fontSize: 10, color: MUTE, letterSpacing: '0.16em',
      }}>← EARLIER</div>
      <div style={{
        position: 'absolute', left: 820, top: RAIL_Y - 28,
        fontFamily: MONO, fontSize: 10, color: BLUE, letterSpacing: '0.16em', fontWeight: 700,
      }}>NOW →</div>

      {/* ── Existing envelopes (already stamped) ── */}
      {existing.map(e => (
        <React.Fragment key={e.id}>
          <Envelope x={e.x} y={RAIL_Y} w={SLOT_W - 8} h={64} seal />
        </React.Fragment>
      ))}

      {/* ── NEW envelopes (flying + stamping) ── */}
      {NEW.map(n => {
        const pos = envOf(n);
        if (!pos) return null;
        return (
          <React.Fragment key={n.id}>
            <div style={{
              position: 'absolute',
              left: pos.x, top: pos.y,
              transform: `translate(-50%, -50%) scale(${pos.scale})`,
              width: SLOT_W - 8, height: 64,
              background: PAPER,
              border: `1.5px solid ${pos.stage === 'flying' ? INK : BLUE}`,
              opacity: pos.opacity,
              padding: '8px 10px',
              zIndex: 4,
              boxShadow: pos.stage === 'stamping'
                ? `0 0 0 6px rgba(77,97,252,0.18)`
                : 'none',
              transition: 'box-shadow 200ms',
            }}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                <div style={{ height: 3, background: LINE, width: '88%' }} />
                <div style={{ height: 3, background: LINE, width: '70%' }} />
                <div style={{ height: 3, background: LINE, width: '82%' }} />
              </div>
              {pos.stage !== 'flying' && (
                <div style={{
                  position: 'absolute', right: 6, bottom: 4,
                  fontFamily: MONO, fontSize: 8, color: BLUE, fontWeight: 700,
                  letterSpacing: '0.04em',
                }}>{n.id}</div>
              )}
            </div>

            {/* "STAMPED" flash */}
            {pos.stage === 'stamped' && (t - n.startAt) < D.stamp + 0.5 && (
              <div style={{
                position: 'absolute', left: n.slotX, top: RAIL_Y - 60,
                transform: 'translate(-50%, 0)',
                fontFamily: MONO, fontSize: 10, color: BLUE,
                letterSpacing: '0.18em', fontWeight: 700,
                opacity: 1 - ((t - n.startAt) - D.stamp) / 0.5,
                border: `1px solid ${BLUE}`, padding: '3px 8px',
                background: PAPER,
              }}>STAMPED</div>
            )}
          </React.Fragment>
        );
      })}

      {/* (no sender icons — actors are established in scene 1) */}

      {/* ── Receipt cards (right side, pile up as each is issued) ── */}
      <div style={{
        position: 'absolute', right: 60, top: 110,
        fontFamily: MONO, fontSize: 10, color: BLUE,
        letterSpacing: '0.18em', fontWeight: 700,
      }}>RECEIPTS · ISSUED TO SENDERS</div>

      {NEW.map((n, i) => {
        const lt = t - n.startAt;
        if (lt < D.receipt) return null;
        const p = clamp((lt - D.receipt) / 0.4, 0, 1);
        return (
          <div key={'r-' + n.id} style={{
            position: 'absolute',
            right: 60 - (1 - p) * 20,
            top: 140 + i * 130,
            width: 290,
            opacity: p,
            transform: `scale(${0.92 + 0.08 * p})`,
            transformOrigin: 'right top',
            padding: '12px 14px',
            background: PAPER, border: `1.5px solid ${INK}`,
            fontFamily: MONO, fontSize: 11, color: INK,
            lineHeight: 1.6,
            boxShadow: '0 4px 12px rgba(11,29,58,0.06)',
          }}>
            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              marginBottom: 8,
            }}>
              <span style={{ fontWeight: 700, letterSpacing: '0.14em' }}>RECEIPT</span>
              <span style={{ color: BLUE, fontWeight: 700 }}>#{n.id}</span>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '60px 1fr', rowGap: 3, fontSize: 10 }}>
              <span style={{ color: MUTE }}>issued to</span>
              <span style={{ color: INK }}>{n.name}</span>
              <span style={{ color: MUTE }}>record</span>
              <span style={{ color: INK }}>{n.recordType}</span>
              <span style={{ color: MUTE }}>hash</span>
              <span style={{ color: INK }}>0x{n.hash}</span>
              <span style={{ color: MUTE }}>time</span>
              <span style={{ color: INK }}>{n.time}</span>
            </div>
            <div style={{
              marginTop: 8, paddingTop: 6,
              borderTop: `1px dashed ${LINE}`,
              fontSize: 9, color: MUTE, letterSpacing: '0.04em', fontStyle: 'italic',
            }}>cryptographic promise of position #{n.id}</div>
          </div>
        );
      })}

      <Caption>
        Each admitted record is stamped onto a permanent, append-only timeline. Three senders, three stamps, three receipts.{' '}
        <span style={{ color: MUTE }}>Your data stays sealed — only the position and the hash become part of the public record.</span>
      </Caption>
    </div>
  );
}

// ═════════════════════════ SCENE 3 — NOTARIZE ══════════════════════════════

function Scene3() {
  const t = useTime();

  // ── 4 independent witnesses + 1 auditor, each a separate org on its own network ──
  const witnesses = [
    { id: 'ia',  x: 92,  label: 'Internet Archive',    role: 'WITNESS', desc: 'long-term public archive',  sig: hex(91, 6), signAt: 1.4 },
    { id: 'mit', x: 286, label: 'MIT CSAIL',           role: 'WITNESS', desc: 'university security lab',   sig: hex(92, 6), signAt: 1.8 },
    { id: 'moz', x: 480, label: 'Mozilla Foundation',  role: 'WITNESS', desc: 'open-source steward',       sig: hex(93, 6), signAt: 2.2 },
    { id: 'eff', x: 674, label: 'EFF',                 role: 'WITNESS', desc: 'civil liberties',           sig: hex(94, 6), signAt: 2.6 },
  ];
  const auditor =
    { id: 'lf',  x: 920, label: 'Linux Foundation',    role: 'AUDITOR', desc: 'independent cross-check',   sig: hex(95, 6), signAt: 3.2 };
  const allParties = [...witnesses, auditor];
  const CARD_W = 178;
  const CARD_Y = 410;

  const signedW = witnesses.filter(w => t >= w.signAt).length;
  const auditorSigned = t >= auditor.signAt;
  const THRESH = 3;
  const locked = signedW >= THRESH && auditorSigned;
  const lockedAt = Math.max(witnesses[THRESH - 1].signAt, auditor.signAt);
  const lockT = locked ? clamp((t - lockedAt) / 0.5, 0, 1) : 0;

  const STATE_CX = 580;
  const STATE_BOTTOM = 260;

  return (
    <div style={{ position: 'absolute', inset: 0 }}>
      {/* ── LEDGER STATE ── */}
      <div style={{
        position: 'absolute', left: 220, right: 220, top: 120,
        padding: '18px 24px',
        border: `2px solid ${INK}`,
        background: PAPER,
        fontFamily: MONO, fontSize: 13, color: INK,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span style={{ fontWeight: 700, letterSpacing: '0.18em' }}>LEDGER STATE · UPDATE PROPOSED</span>
          <span style={{
            fontSize: 11,
            color: locked ? '#fff' : MUTE,
            background: locked ? BLUE : 'transparent',
            border: `1.5px solid ${locked ? BLUE : LINE}`,
            padding: '4px 10px',
            letterSpacing: '0.14em',
            transition: 'all 220ms',
          }}>{locked ? '✓ LOCKED' : 'PENDING'}</span>
        </div>
        <div style={{ marginTop: 10, color: MUTE, fontSize: 12, letterSpacing: '0.04em' }}>
          height <span style={{ color: INK }}>4015</span>
          &nbsp;·&nbsp; root <span style={{ color: INK }}>0x{hex(99, 14)}</span>
        </div>
        <div style={{ marginTop: 6, color: MUTE, fontSize: 11 }}>
          gathering seals:&nbsp;
          <span style={{ color: signedW >= THRESH ? BLUE : INK, fontWeight: 700 }}>{signedW}</span>
          &nbsp;/ {THRESH} witnesses &nbsp;·&nbsp;
          <span style={{ color: auditorSigned ? DEEP : MUTE, fontWeight: 700 }}>
            {auditorSigned ? '✓' : '○'}
          </span> auditor
        </div>
      </div>

      {/* ── Connecting lines (fan-out + witness mesh + auditor) ── */}
      <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
        {allParties.map((p, i) => {
          const isAud = p.role === 'AUDITOR';
          const reached = t >= 0.4 + i * 0.08;
          return (
            <line key={p.id}
              x1={STATE_CX} y1={STATE_BOTTOM}
              x2={p.x + CARD_W / 2} y2={CARD_Y - 4}
              stroke={isAud ? DEEP : BLUE}
              strokeWidth="1"
              strokeDasharray={isAud ? '4 4' : 'none'}
              opacity={reached ? 0.45 : 0}
            />
          );
        })}
        {/* Witness ↔ witness mesh (showing they network with each other) */}
        {witnesses.slice(0, -1).map((w, i) => {
          const next = witnesses[i + 1];
          return (
            <line key={'mesh-' + i}
              x1={w.x + CARD_W} y1={CARD_Y + 92}
              x2={next.x} y2={CARD_Y + 92}
              stroke={LINE} strokeWidth="1" opacity={0.7}
            />
          );
        })}
        {/* EFF ↔ Auditor (cross-tier link) */}
        <line
          x1={witnesses[3].x + CARD_W} y1={CARD_Y + 92}
          x2={auditor.x} y2={CARD_Y + 92}
          stroke={DEEP} strokeWidth="1" strokeDasharray="3 3" opacity={0.7}
        />
      </svg>

      {/* ── Signature-return packets flying back to state ── */}
      {allParties.map(p => {
        const rt = (t - p.signAt) / 0.55;
        if (rt < 0 || rt > 1) return null;
        const startX = p.x + CARD_W / 2;
        const startY = CARD_Y - 4;
        const endX = STATE_CX;
        const endY = STATE_BOTTOM;
        const ease = Easing.easeInOutCubic(rt);
        const x = startX + (endX - startX) * ease;
        const y = startY + (endY - startY) * ease;
        const isAud = p.role === 'AUDITOR';
        return (
          <div key={'ret-' + p.id} style={{
            position: 'absolute', left: x, top: y,
            transform: 'translate(-50%, -50%)',
            width: 30, height: 16,
            background: isAud ? DEEP : BLUE,
            color: '#fff',
            border: `1px solid ${PAPER}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontFamily: MONO, fontSize: 7, fontWeight: 700,
            letterSpacing: '0.06em',
            opacity: 1 - rt * 0.3,
          }}>SIG</div>
        );
      })}

      {/* ── Section labels ── */}
      <div style={{
        position: 'absolute', left: 92, top: CARD_Y - 32,
        fontFamily: MONO, fontSize: 11, color: BLUE,
        letterSpacing: '0.18em', fontWeight: 700,
      }}>4 INDEPENDENT WITNESSES &nbsp;<span style={{ color: MUTE, fontSize: 10, letterSpacing: '0.08em', fontWeight: 400 }}>each on its own network</span></div>
      <div style={{
        position: 'absolute', left: 920, top: CARD_Y - 32,
        fontFamily: MONO, fontSize: 11, color: DEEP,
        letterSpacing: '0.18em', fontWeight: 700,
      }}>+ AUDITOR</div>

      {/* ── Party cards ── */}
      {allParties.map(p => {
        const signed = t >= p.signAt;
        const signProgress = clamp((t - p.signAt) / 0.4, 0, 1);
        const isAud = p.role === 'AUDITOR';
        const accent = isAud ? DEEP : BLUE;
        return (
          <div key={p.id} style={{
            position: 'absolute', left: p.x, top: CARD_Y,
            width: CARD_W, padding: '12px 14px',
            border: `1.5px ${isAud ? 'dashed' : 'solid'} ${signed ? accent : INK}`,
            background: PAPER,
            fontFamily: MONO, fontSize: 11, color: INK,
            transition: 'border-color 220ms',
          }}>
            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              fontSize: 9, letterSpacing: '0.16em', color: accent, fontWeight: 700,
              marginBottom: 8,
            }}>
              <span>{p.role}</span>
              {signed && <Tick size={16} color={accent} progress={signProgress} />}
            </div>
            <div style={{ fontFamily: SANS, fontSize: 14, fontWeight: 700, color: INK, lineHeight: 1.2 }}>
              {p.label}
            </div>
            <div style={{ fontSize: 10, color: MUTE, letterSpacing: '0.02em', marginTop: 3 }}>
              {p.desc}
            </div>
            <div style={{ fontSize: 10, color: signed ? accent : MUTE, marginTop: 10 }}>
              {signed ? `sig 0x${p.sig}` : 'awaiting…'}
            </div>
          </div>
        );
      })}

      {/* ── Lock graphic ── */}
      {locked && (
        <div style={{
          position: 'absolute', left: 1110, top: 130,
          width: 50, height: 60, opacity: lockT,
          transform: `scale(${0.85 + 0.15 * lockT})`,
        }}>
          <svg viewBox="0 0 56 70" width="50" height="60" fill="none">
            <rect x="6" y="28" width="44" height="36" stroke={BLUE} strokeWidth="2" />
            <path d="M14 28v-8a14 14 0 0 1 28 0v8" stroke={BLUE} strokeWidth="2" strokeLinecap="round" />
            <circle cx="28" cy="46" r="3" fill={BLUE} />
          </svg>
        </div>
      )}

      <Caption>
        The update is relayed to independent witnesses on separate networks, with an auditor cross-checking.{' '}
        <span style={{ color: MUTE }}>Only when enough seals are gathered does the timeline lock — no single party, not even Attesta, can alter it.</span>
      </Caption>
    </div>
  );
}

// ═════════════════════════ SCENE 4 — PUBLISH ═══════════════════════════════

function Scene4() {
  const t = useTime();

  // ─── Record stream (the internal log being written) ────────────────
  // Eight admitted records of six different types. They stream in over time.
  const RECORDS = [
    { id: 4015, type: 'board certification',  sender: 'Dr. Sarah Chen',          short: 'BOARD',     hash: hex(15, 8), at: 0.3 },
    { id: 4016, type: 'privileging update',   sender: 'Mercy General Hospital',  short: 'PRIV',      hash: hex(16, 8), at: 0.8 },
    { id: 4017, type: 'license verification', sender: 'State Licensing Board',   short: 'LICENSE',   hash: hex(17, 8), at: 1.3 },
    { id: 4018, type: 'CME completion',       sender: 'Cleveland Clinic CME',    short: 'CME',       hash: hex(18, 8), at: 1.8 },
    { id: 4019, type: 'malpractice closure',  sender: 'MedPro Indemnity',        short: 'CLAIM',     hash: hex(19, 8), at: 5.2 },
    { id: 4020, type: 'DEA registration',     sender: 'DEA Field Office 12',     short: 'DEA',       hash: hex(20, 8), at: 5.7 },
    { id: 4021, type: 'peer review',          sender: 'Mass General Brigham',    short: 'PEER',      hash: hex(21, 8), at: 6.2 },
    { id: 4022, type: 'NPI verification',     sender: 'NPPES Registry',          short: 'NPI',       hash: hex(22, 8), at: 6.7 },
  ];

  // Per-record cosmetic delay so its "fade-in" feels typewriter-ish
  const APPEAR = 0.25;

  // ─── Checkpoints (two are published during the loop) ──────────────
  // Tessera signs a checkpoint over the new entries, then writes it to
  // each public read-only object store.
  const SNAPS = [
    {
      id: 'C-148', root: hex(148, 14), records: [4015, 4016, 4017, 4018],
      bundleAt: 2.5, publishAt: 3.0,
    },
    {
      id: 'C-149', root: hex(149, 14), records: [4019, 4020, 4021, 4022],
      bundleAt: 7.4, publishAt: 7.9,
    },
  ];

  // Public object-store mirrors — Attesta currently publishes to plain
  // object storage (S3 / R2 / GCS). Anyone can fetch over HTTPS.
  const NETWORKS = [
    { id: 's3', name: 'AWS S3',               desc: 'us-east-1 · primary',     addr: 's3://attesta-log/checkpoint/', delay: 0.20 },
    { id: 'r2', name: 'Cloudflare R2',        desc: 'global edge · mirror',    addr: 'r2://attesta-log/checkpoint/', delay: 0.45 },
    { id: 'gcs',name: 'Google Cloud Storage', desc: 'us-central1 · mirror',    addr: 'gs://attesta-log/checkpoint/', delay: 0.70 },
  ];

  // ─── Layout constants ───────────────────────────────────────────────
  const LOG_X = 60, LOG_W = 540;
  const LOG_Y = 100, LOG_H = 320;
  const NET_X = 720, NET_W = 500;
  const NET_Y = 100;
  const FEED_Y = 460, FEED_H = 152;

  // ─── Aggregate stats (header counters) ──────────────────────────────
  const recordsInLog = RECORDS.filter(r => t >= r.at + APPEAR).length;
  const snapsPublished = SNAPS.filter(s => t >= s.publishAt).length;
  // Downloads ticker (visible curiosity)
  const downloads = Math.max(0, Math.floor((t - 1.5) * 53));

  // Last 6 records visible in the log panel
  const visibleRecords = RECORDS
    .filter(r => t >= r.at)
    .slice(-7); // keep panel from overflowing

  // ─── Per-snapshot: position of the snapshot block + per-network state ──
  function snapState(s) {
    if (t < s.bundleAt) return { stage: 'idle' };
    if (t < s.publishAt) {
      // bundling at center: bracket grows around bundled records
      const p = clamp((t - s.bundleAt) / (s.publishAt - s.bundleAt), 0, 1);
      return { stage: 'bundling', bundleP: p };
    }
    return { stage: 'publishing', age: t - s.publishAt };
  }

  // For a network arrival, compute progress 0..1 with the snapshot's delay
  function arrival(s, n) {
    const st = snapState(s);
    if (st.stage !== 'publishing') return 0;
    return clamp((st.age - n.delay) / 0.55, 0, 1);
  }

  const fmtTs = (eventTime) => {
    const base = 14 * 3600 + 23 * 60 + 12;
    const totalCs = Math.floor((base + eventTime) * 100);
    const cs = ((totalCs % 100) + 100) % 100;
    const s  = ((Math.floor(totalCs / 100) % 60) + 60) % 60;
    const m  = ((Math.floor(totalCs / 6000) % 60) + 60) % 60;
    const h  = Math.floor(totalCs / 360000);
    return `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}.${String(cs).padStart(2,'0')}`;
  };

  // Build the public notices feed (one entry per snapshot×network arrival)
  const FEED = [];
  for (const s of SNAPS) {
    for (const n of NETWORKS) {
      const eventTime = s.publishAt + n.delay;
      FEED.push({
        id: `${s.id}-${n.id}`,
        time: eventTime,
        network: n,
        snap: s,
      });
    }
  }
  const FEED_DURATION = 12.0;
  const visibleFeed = FEED
    .filter(e => t >= e.time && t < e.time + FEED_DURATION)
    .sort((a, b) => b.time - a.time);

  // Pulse animation from internal log → networks during publish
  const activePublishSnap = SNAPS.find(s => {
    const st = snapState(s);
    return st.stage === 'publishing' && st.age < 1.2;
  });

  // Network short labels for icons (bucket marks)
  const netIcon = (id) => {
    if (id === 's3')  return 'S3';
    if (id === 'r2')  return 'R2';
    if (id === 'gcs') return 'GCS';
    return '●';
  };

  return (
    <div style={{ position: 'absolute', inset: 0 }}>

      {/* ── TOP HEADER ── aggregate counters */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: 30,
        padding: '12px 22px',
        background: SOFT, border: `1.5px solid ${BLUE}`,
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        fontFamily: MONO, fontSize: 12,
      }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
          <span style={{ fontWeight: 700, color: DEEP, letterSpacing: '0.18em' }}>PUBLISH · OPEN READ</span>
          <span style={{ color: MUTE, letterSpacing: '0.06em' }}>no accounts · no fees · plain HTTPS</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 24, fontVariantNumeric: 'tabular-nums' }}>
          <span style={{ color: MUTE }}>entries&nbsp;<span style={{ color: DEEP, fontWeight: 700 }}>{recordsInLog}</span></span>
          <span style={{ color: MUTE }}>checkpoints&nbsp;<span style={{ color: DEEP, fontWeight: 700 }}>{snapsPublished}</span></span>
          <span style={{ color: MUTE }}>fetches&nbsp;<span style={{ color: BLUE, fontWeight: 700 }}>{downloads.toLocaleString()}</span></span>
        </div>
      </div>

      {/* ── INTERNAL LOG PANEL (left) ── */}
      <div style={{
        position: 'absolute', left: LOG_X, top: LOG_Y, width: LOG_W, height: LOG_H,
        background: PAPER, border: `1.5px solid ${INK}`,
        padding: '14px 16px',
        overflow: 'hidden',
      }}>
        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          fontFamily: MONO, fontSize: 10, color: BLUE,
          letterSpacing: '0.18em', fontWeight: 700, marginBottom: 10,
        }}>
          <span>TESSERA LOG · APPEND-ONLY</span>
          <span style={{ color: MUTE, letterSpacing: '0.10em', fontWeight: 400 }}>hashes only · no payloads</span>
        </div>

        <div style={{ fontFamily: MONO, fontSize: 11 }}>
          {visibleRecords.map((r) => {
            const age = t - r.at;
            const opac = clamp(age / APPEAR, 0, 1);
            const slide = (1 - opac) * 8;

            // Is this record part of a snapshot currently bundling/publishing?
            const inBundlingSnap = SNAPS.find(s => {
              const st = snapState(s);
              return s.records.includes(r.id) && (st.stage === 'bundling' || (st.stage === 'publishing' && st.age < 0.8));
            });
            const highlighted = !!inBundlingSnap;
            const bundlingP = inBundlingSnap ? snapState(inBundlingSnap).bundleP || 1 : 0;

            return (
              <div key={r.id} style={{
                opacity: opac,
                transform: `translateX(${slide}px)`,
                display: 'flex', gap: 10, alignItems: 'center',
                padding: '4px 6px',
                marginBottom: 2,
                background: highlighted ? `rgba(77,97,252,${0.06 + bundlingP * 0.06})` : 'transparent',
                borderLeft: highlighted ? `2px solid ${BLUE}` : `2px solid transparent`,
                transition: 'background 200ms, border-color 200ms',
              }}>
                <span style={{ color: BLUE, fontWeight: 700, fontVariantNumeric: 'tabular-nums', width: 50 }}>#{r.id}</span>
                <span style={{
                  fontSize: 8, color: DEEP, background: '#E9F1FF',
                  padding: '2px 6px', letterSpacing: '0.08em', fontWeight: 700,
                  border: `1px solid ${LINE}`, minWidth: 64, textAlign: 'center',
                }}>{r.short}</span>
                <span style={{ flex: 1, color: INK, fontFamily: SANS, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                  {r.sender}
                </span>
                <span style={{ color: MUTE, fontSize: 9 }}>0x{r.hash}</span>
              </div>
            );
          })}
        </div>

        {/* Bundle bracket annotation, shown during bundling */}
        {SNAPS.map(s => {
          const st = snapState(s);
          if (st.stage !== 'bundling' && !(st.stage === 'publishing' && st.age < 0.5)) return null;
          const p = st.stage === 'bundling' ? st.bundleP : 1;
          return (
            <div key={s.id + '-anno'} style={{
              position: 'absolute', right: 12, bottom: 12,
              fontFamily: MONO, fontSize: 10, color: BLUE, fontWeight: 700,
              letterSpacing: '0.12em',
              opacity: p,
              border: `1px solid ${BLUE}`, padding: '4px 8px',
              background: PAPER,
            }}>CHECKPOINT → {s.id}</div>
          );
        })}
      </div>

      {/* ── PUBLIC OBJECT-STORE MIRRORS (right) ── */}
      <div style={{
        position: 'absolute', left: NET_X, top: LOG_Y - 24,
        fontFamily: MONO, fontSize: 10, color: BLUE,
        letterSpacing: '0.18em', fontWeight: 700,
      }}>PUBLIC OBJECT STORES · READ-ONLY</div>

      {NETWORKS.map((n, i) => {
        const cardY = NET_Y + i * 102;
        // Most recent snapshot to arrive at this network
        let latestArrived = null;
        for (const s of SNAPS) {
          if (arrival(s, n) >= 1) latestArrived = s;
        }
        // Is something landing right now?
        const landingSnap = SNAPS.find(s => {
          const a = arrival(s, n);
          return a > 0 && a < 1;
        });
        const landingP = landingSnap ? arrival(landingSnap, n) : 0;

        return (
          <React.Fragment key={n.id}>
            {/* Card */}
            <div style={{
              position: 'absolute', left: NET_X, top: cardY, width: NET_W, height: 88,
              border: `1.5px solid ${latestArrived ? BLUE : LINE}`,
              background: PAPER,
              padding: '10px 14px',
              fontFamily: MONO, fontSize: 11, color: INK,
              transition: 'border-color 240ms',
              boxShadow: landingSnap && landingP > 0.6 ? `0 0 0 6px rgba(77,97,252,0.10)` : 'none',
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
                <div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                    <span style={{
                      display: 'inline-block', minWidth: 30, padding: '2px 6px',
                      fontSize: 9, fontWeight: 700, letterSpacing: '0.08em',
                      background: BLUE, color: '#fff', textAlign: 'center',
                    }}>{netIcon(n.id)}</span>
                    <span style={{ fontFamily: SANS, fontSize: 15, fontWeight: 700, color: INK }}>{n.name}</span>
                  </div>
                  <div style={{ fontSize: 10, color: MUTE, marginTop: 2, marginLeft: 42 }}>{n.desc}</div>
                </div>
                <span style={{
                  fontSize: 9, letterSpacing: '0.14em', fontWeight: 700,
                  color: latestArrived ? BLUE : MUTE,
                  border: `1px solid ${latestArrived ? BLUE : LINE}`,
                  padding: '3px 8px',
                  background: PAPER,
                }}>
                  {landingSnap && landingP < 1 ? 'INCOMING…' : latestArrived ? 'PUBLISHED' : 'WAITING'}
                </span>
              </div>
              <div style={{
                marginTop: 8, paddingTop: 8,
                borderTop: `1px dashed ${LINE}`,
                display: 'flex', justifyContent: 'space-between',
                fontSize: 10,
              }}>
                <span style={{ color: MUTE }}>latest object</span>
                <span style={{ color: latestArrived ? INK : MUTE, fontVariantNumeric: 'tabular-nums', fontSize: 9 }}>
                  {latestArrived ? `${n.addr}${latestArrived.id}` : '—'}
                </span>
              </div>
            </div>

            {/* Beam from internal log → this network */}
            {landingSnap && (() => {
              const fromX = LOG_X + LOG_W;
              const fromY = LOG_Y + LOG_H - 30;
              const toX = NET_X;
              const toY = cardY + 44;
              const x = fromX + (toX - fromX) * landingP;
              const y = fromY + (toY - fromY) * landingP;
              return (
                <React.Fragment>
                  <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
                    <line x1={fromX} y1={fromY} x2={x} y2={y}
                      stroke={BLUE} strokeWidth="1" strokeDasharray="2 4" opacity="0.55" />
                  </svg>
                  <div style={{
                    position: 'absolute', left: x, top: y,
                    transform: 'translate(-50%, -50%)',
                    padding: '4px 8px',
                    border: `1.5px solid ${BLUE}`, background: PAPER,
                    fontFamily: MONO, fontSize: 9, color: BLUE, fontWeight: 700,
                    letterSpacing: '0.10em',
                    boxShadow: '0 2px 6px rgba(77,97,252,0.18)',
                  }}>{landingSnap.id}</div>
                </React.Fragment>
              );
            })()}
          </React.Fragment>
        );
      })}

      {/* ── PUBLIC NOTICES FEED (bottom) ── */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: FEED_Y,
        height: FEED_H,
        border: `1px solid ${LINE}`,
        background: PAPER,
        padding: '12px 16px',
        overflow: 'hidden',
      }}>
        <div style={{
          display: 'flex', justifyContent: 'space-between',
          fontFamily: MONO, fontSize: 10, color: MUTE,
          letterSpacing: '0.14em', fontWeight: 700, marginBottom: 8,
        }}>
          <span>PUBLISH LOG · STREAMING</span>
          <span>{visibleFeed.length === 0 ? 'awaiting first checkpoint…' : `${visibleFeed.length} live`}</span>
        </div>

        <div style={{ fontFamily: MONO, fontSize: 11 }}>
          {visibleFeed.slice(0, 6).map((e) => {
            const age = t - e.time;
            const fadeIn = age < 0.25 ? age / 0.25 : 1;
            const slide = age < 0.25 ? (1 - age / 0.25) * -6 : 0;
            return (
              <div key={e.id} style={{
                opacity: fadeIn,
                marginBottom: 4,
                display: 'flex', gap: 14, alignItems: 'center',
                color: INK,
                transform: `translateY(${slide}px)`,
              }}>
                <span style={{ color: MUTE, width: 110, fontVariantNumeric: 'tabular-nums' }}>[{fmtTs(e.time)}]</span>
                <span style={{ width: 150, display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{
                    display: 'inline-block', minWidth: 26, padding: '1px 5px',
                    fontSize: 8, fontWeight: 700, letterSpacing: '0.08em',
                    background: BLUE, color: '#fff', textAlign: 'center',
                  }}>{netIcon(e.network.id)}</span>
                  <span>PUT {e.network.name}</span>
                </span>
                <span style={{ flex: 1 }}>
                  checkpoint <span style={{ color: BLUE, fontWeight: 700 }}>{e.snap.id}</span> · root 0x{e.snap.root.slice(0, 14)} · {e.snap.records.length} entries
                </span>
                <span style={{ color: BLUE, fontWeight: 700 }}>200 OK</span>
              </div>
            );
          })}
        </div>
      </div>

      {/* ── Backend + privacy strip (single-line, no extra prose) ── */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: FEED_Y + FEED_H + 12,
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        fontFamily: MONO, fontSize: 10, letterSpacing: '0.10em',
      }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 9, height: 9, border: `1.5px solid ${DEEP}`, background: PAPER, display: 'inline-block' }} />
            <span style={{ color: DEEP, fontWeight: 700 }}>PAYLOADS PRIVATE</span>
          </span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 9, height: 9, background: BLUE, display: 'inline-block' }} />
            <span style={{ color: BLUE, fontWeight: 700 }}>EXISTENCE PUBLIC</span>
          </span>
        </span>
        <span style={{ color: MUTE, letterSpacing: '0.14em' }}>
          backend · <span style={{ color: DEEP, fontWeight: 700 }}>Tessera</span>  ·  C2SP tlog-tiles
        </span>
      </div>
    </div>
  );
}

// ═════════════════════════ SCENE 5 — MONITORS ═══════════════════════════════

function Scene5() {
  const t = useTime();

  // ── 3 named monitors with phase-shifted cycles so they're never in sync ──
  const monitors = [
    { id: 'cf',  name: 'Cloudflare Research', desc: 'infrastructure operator', phase: 0.0 },
    { id: 'cl',  name: 'Citizen Lab',         desc: 'independent researcher',  phase: 1.0 },
    { id: 'eth', name: 'ETH Zürich SecLab',   desc: 'university audit team',   phase: 2.0 },
  ];

  const CYCLE = 3.0;
  const fraudStart = 5.2;
  const fraudEnd   = 6.4;
  const fraudActive = t > fraudStart && t < fraudEnd;

  // Each monitor cycles fetch → recompute → compare on its own offset.
  const cyclePhase = (m) => ((t + m.phase) % CYCLE) / CYCLE;
  const cycleProgress = (p) => {
    if (p < 0.30) return { fetch: p / 0.30, recompute: 0, compare: 0, status: 'fetching' };
    if (p < 0.65) return { fetch: 1, recompute: (p - 0.30) / 0.35, compare: 0, status: 'recomputing' };
    return                { fetch: 1, recompute: 1, compare: (p - 0.65) / 0.35, status: 'agreeing'  };
  };

  // Always-ticking checks counter
  const checksTotal = 142893 + Math.floor(t * 9);

  // Pre-baked log entries; one per 0.55s, plus a fraud entry near fraudStart.
  const logEntries = React.useMemo(() => {
    const arr = [];
    for (let i = 0; i < 30; i++) {
      const eventTime = -4 + i * 0.55;
      const mi = i % monitors.length;
      arr.push({
        id: i, time: eventTime,
        monitor: monitors[mi].name,
        root: hex(70 + i, 8),
        isFraud: false,
      });
    }
    arr.push({
      id: 999, time: fraudStart + 0.05,
      monitor: 'ALL MONITORS',
      root: '— — — —',
      isFraud: true,
    });
    return arr.sort((a, b) => a.time - b.time);
  }, []);

  const FEED_DURATION = 4.8;
  const visibleEntries = logEntries.filter(e => t >= e.time && t < e.time + FEED_DURATION);
  const reverseVisible = [...visibleEntries].sort((a, b) => b.time - a.time);

  const fmtTs = (eventTime) => {
    const base = 14 * 3600 + 23 * 60 + 12;
    const totalCs = Math.floor((base + eventTime) * 100);
    const cs = ((totalCs % 100) + 100) % 100;
    const s  = ((Math.floor(totalCs / 100) % 60) + 60) % 60;
    const m  = ((Math.floor(totalCs / 6000) % 60) + 60) % 60;
    const h  = Math.floor(totalCs / 360000);
    return `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}.${String(cs).padStart(2,'0')}`;
  };

  const GREEN = 'oklch(58% 0.16 145)';

  return (
    <div style={{ position: 'absolute', inset: 0 }}>
      {/* Header — LIVE/ANOMALY indicator + counter */}
      <div style={{ position: 'absolute', left: '50%', top: 36, transform: 'translate(-50%, 0)', textAlign: 'center' }}>
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
          fontFamily: MONO, fontSize: 11, color: fraudActive ? RED : INK,
          letterSpacing: '0.18em', fontWeight: 700,
        }}>
          <div style={{
            width: 9, height: 9, borderRadius: '50%',
            background: fraudActive ? RED : GREEN,
            opacity: 0.45 + Math.abs(Math.sin(t * 4)) * 0.55,
            boxShadow: `0 0 8px ${fraudActive ? RED : GREEN}`,
          }} />
          {fraudActive
            ? '⚠ ANOMALY · CROSS-MONITOR DISAGREEMENT'
            : 'LIVE · CONTINUOUS MONITORING · 24 / 7'}
        </div>
        <div style={{
          fontFamily: MONO, fontSize: 11, color: MUTE, marginTop: 6,
          fontVariantNumeric: 'tabular-nums',
        }}>
          checks performed: <span style={{ fontWeight: 700, color: INK }}>{checksTotal.toLocaleString()}</span>
          &nbsp;·&nbsp; ongoing since 2024-08
        </div>
      </div>

      {/* Three monitor cards, each continuously cycling */}
      {monitors.map((m, i) => {
        const p = cyclePhase(m);
        const { fetch, recompute, compare, status } = cycleProgress(p);
        const X = 60 + i * 395;
        const rootIdx = Math.floor((t + m.phase) / CYCLE);
        const currentRoot = hex(100 + rootIdx + i * 7, 10);
        return (
          <div key={m.id} style={{
            position: 'absolute', left: X, top: 120,
            width: 370, padding: '16px 18px',
            border: `1.5px solid ${fraudActive ? RED : INK}`,
            background: PAPER,
            fontFamily: MONO, fontSize: 11, color: INK,
            transition: 'border-color 200ms',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
              <div>
                <div style={{ fontSize: 9, color: BLUE, letterSpacing: '0.16em', fontWeight: 700 }}>MONITOR</div>
                <div style={{ fontFamily: SANS, fontSize: 14, fontWeight: 700, color: INK, marginTop: 2 }}>{m.name}</div>
                <div style={{ fontSize: 10, color: MUTE, marginTop: 2 }}>{m.desc}</div>
              </div>
              <div style={{
                width: 9, height: 9, borderRadius: '50%',
                background: fraudActive ? RED : GREEN,
                opacity: 0.45 + Math.abs(Math.sin(t * 5 + i * 1.3)) * 0.55,
                marginTop: 4,
                boxShadow: `0 0 6px ${fraudActive ? RED : GREEN}`,
              }} />
            </div>

            {[
              { label: 'fetch snapshot', progress: fetch },
              { label: 'recompute math', progress: recompute },
              { label: 'compare result', progress: compare },
            ].map((step, j) => (
              <div key={j} style={{ marginBottom: 7 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 9, color: MUTE, letterSpacing: '0.06em', marginBottom: 3 }}>
                  <span>{step.label}</span>
                  <span style={{ fontVariantNumeric: 'tabular-nums' }}>{Math.floor(step.progress * 100)}%</span>
                </div>
                <div style={{ height: 2, background: LINE, position: 'relative' }}>
                  <div style={{
                    position: 'absolute', left: 0, top: 0, height: '100%',
                    width: `${step.progress * 100}%`,
                    background: fraudActive ? RED : INK,
                  }} />
                </div>
              </div>
            ))}

            <div style={{ marginTop: 10, display: 'flex', justifyContent: 'space-between', fontSize: 10 }}>
              <span style={{ color: MUTE }}>last root</span>
              <span style={{ color: fraudActive ? RED : INK, fontVariantNumeric: 'tabular-nums' }}>0x{currentRoot}</span>
            </div>
            <div style={{ marginTop: 4, display: 'flex', justifyContent: 'space-between', fontSize: 10 }}>
              <span style={{ color: MUTE }}>cycle #{14823 + Math.floor((t + m.phase) / CYCLE * 4)}</span>
              <span style={{ color: fraudActive ? RED : GREEN, fontWeight: 700, letterSpacing: '0.06em' }}>
                {fraudActive ? '✕ MISMATCH' : '✓ AGREE'}
              </span>
            </div>
          </div>
        );
      })}

      {/* Live verification log feed */}
      <div style={{
        position: 'absolute', left: 60, right: 60, top: 442,
        border: `1px solid ${LINE}`,
        background: PAPER,
        padding: '12px 16px',
        height: 158,
        overflow: 'hidden',
      }}>
        <div style={{
          display: 'flex', justifyContent: 'space-between',
          fontFamily: MONO, fontSize: 10, color: MUTE,
          letterSpacing: '0.14em', fontWeight: 700, marginBottom: 8,
        }}>
          <span>VERIFICATION LOG · LIVE</span>
          <span>streaming · {checksTotal.toLocaleString()} total checks</span>
        </div>
        <div style={{ fontFamily: MONO, fontSize: 11 }}>
          {reverseVisible.slice(0, 6).map((e) => {
            const age = t - e.time;
            const fadeIn = age < 0.25 ? age / 0.25 : 1;
            const fadeOut = age > FEED_DURATION - 0.6 ? Math.max(0, (FEED_DURATION - age) / 0.6) : 1;
            const slideIn = age < 0.25 ? (1 - age / 0.25) * -6 : 0;
            const opacity = Math.min(fadeIn, fadeOut);
            return (
              <div key={e.id} style={{
                opacity,
                marginBottom: 4,
                display: 'flex', gap: 14,
                color: e.isFraud ? RED : INK,
                fontWeight: e.isFraud ? 700 : 400,
                transform: `translateY(${slideIn}px)`,
              }}>
                <span style={{ color: MUTE, width: 110, fontVariantNumeric: 'tabular-nums' }}>[{fmtTs(e.time)}]</span>
                <span style={{ width: 200 }}>{e.monitor}</span>
                <span style={{ flex: 1 }}>
                  {e.isFraud
                    ? 'CROSS-MONITOR MISMATCH — fraud proof emitted'
                    : <>verified root <span style={{ color: e.isFraud ? RED : MUTE }}>0x{e.root}</span></>}
                </span>
                <span style={{ color: e.isFraud ? RED : GREEN, fontWeight: 700 }}>
                  {e.isFraud ? '⚠' : '✓'}
                </span>
              </div>
            );
          })}
        </div>
      </div>

      <Caption>
        Independent monitors run round-the-clock — fetching, recomputing, comparing.{' '}
        <span style={{ color: MUTE }}>The moment two snapshots disagree, the contradiction is caught and published as undeniable proof.</span>
      </Caption>
    </div>
  );
}

// ═════════════════════════ SCENE 6 — VERIFY FOREVER ═════════════════════════

function Scene6() {
  const t = useTime();

  // Year ticker
  const startYear = 2026;
  const endYear = 2148;
  const yearT = clamp((t - 1.5) / 5.5, 0, 1);
  const year = Math.floor(startYear + yearT * (endYear - startYear));

  // Status flags that flick to OFFLINE over time
  const stages = [
    { at: 2.5, label: 'attesta.com',         off: 'OFFLINE' },
    { at: 3.8, label: 'attesta servers',     off: 'DECOMMISSIONED' },
    { at: 5.0, label: 'attesta the company', off: 'DISSOLVED' },
  ];

  // Verification computation
  const verifyStart = 0.8;
  const verifyT = clamp((t - verifyStart) / 1.0, 0, 1);
  const verified = t >= verifyStart + 1.0;

  return (
    <div style={{ position: 'absolute', inset: 0 }}>
      {/* Left: proof file */}
      <div style={{
        position: 'absolute', left: 260, top: 360,
        transform: 'translate(-50%, -50%)',
        width: 200, padding: '14px 16px',
        border: `1.5px solid ${INK}`,
        background: PAPER,
        fontFamily: MONO, fontSize: 12, color: INK,
        lineHeight: 1.7,
      }}>
        <div style={{ fontWeight: 700, letterSpacing: '0.12em', marginBottom: 8 }}>PROOF.TXT</div>
        <div style={{ fontSize: 11, color: MUTE }}>your receipt</div>
        <div style={{ marginTop: 6 }}>pos #4015</div>
        <div>0x{hex(15, 10)}</div>
      </div>

      {/* Plus */}
      <div style={{
        position: 'absolute', left: 420, top: 360,
        transform: 'translate(-50%, -50%)',
        fontFamily: MONO, fontSize: 26, color: MUTE,
      }}>+</div>

      {/* Middle: public snapshot */}
      <div style={{
        position: 'absolute', left: 580, top: 360,
        transform: 'translate(-50%, -50%)',
        width: 200, padding: '14px 16px',
        border: `1.5px solid ${INK}`,
        background: PAPER,
        fontFamily: MONO, fontSize: 12, color: INK,
        lineHeight: 1.7,
      }}>
        <div style={{ fontWeight: 700, letterSpacing: '0.12em', marginBottom: 8 }}>SNAPSHOT</div>
        <div style={{ fontSize: 11, color: MUTE }}>public network</div>
        <div style={{ marginTop: 6 }}>root 0x{hex(7, 10)}</div>
      </div>

      {/* Arrow */}
      <div style={{
        position: 'absolute', left: 760, top: 360,
        transform: 'translate(0, -50%)',
        width: 80, display: 'flex', alignItems: 'center',
      }}>
        <div style={{ flex: 1, height: 1, background: INK }} />
        <div style={{ width: 0, height: 0, borderLeft: `8px solid ${INK}`, borderTop: '5px solid transparent', borderBottom: '5px solid transparent' }} />
      </div>

      {/* Verification result */}
      <div style={{
        position: 'absolute', left: 990, top: 360,
        transform: 'translate(-50%, -50%)',
        width: 200, height: 200,
        border: `1.5px solid ${verified ? BLUE : INK}`,
        background: PAPER,
        display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center',
        fontFamily: MONO, fontSize: 13, color: INK,
        letterSpacing: '0.08em',
        transition: 'border-color 200ms',
      }}>
        {!verified && (
          <>
            <div style={{ fontSize: 11, color: MUTE, marginBottom: 12, letterSpacing: '0.12em' }}>VERIFYING</div>
            <div style={{ width: 120, height: 2, background: LINE, position: 'relative' }}>
              <div style={{ position: 'absolute', left: 0, top: 0, height: '100%', width: `${verifyT * 100}%`, background: INK }} />
            </div>
          </>
        )}
        {verified && (
          <>
            <Tick size={48} />
            <div style={{ marginTop: 14, color: BLUE, fontWeight: 700 }}>GENUINE</div>
          </>
        )}
      </div>

      {/* Year + dead-attesta status, drifting in */}
      <div style={{
        position: 'absolute', left: 0, right: 0, top: 130,
        textAlign: 'center',
        fontFamily: MONO, fontSize: 14, color: INK,
        letterSpacing: '0.16em',
      }}>
        YEAR <span style={{ fontWeight: 700 }}>{year}</span>
      </div>

      <div style={{
        position: 'absolute', left: 0, right: 0, top: 180,
        textAlign: 'center',
        fontFamily: MONO, fontSize: 11, color: MUTE,
        letterSpacing: '0.08em',
        display: 'flex', justifyContent: 'center', gap: 24,
      }}>
        {stages.map(s => {
          const active = t >= s.at;
          return (
            <span key={s.label} style={{
              opacity: active ? 1 : 0.3,
              color: active ? RED : MUTE,
              borderBottom: active ? `1px solid ${RED}` : '1px solid transparent',
              paddingBottom: 2,
            }}>
              {s.label}: {active ? s.off : '—'}
            </span>
          );
        })}
      </div>

      <Caption>
        You walk away with a final proof file.{' '}
        <span style={{ color: MUTE }}>Receipt + public snapshot = verifiable forever — offline, decades later, even if Attesta is long gone.</span>
      </Caption>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────

Object.assign(window, {
  Scene1, Scene2, Scene3, Scene4, Scene5, Scene6,
});
