// timeline-scenes.jsx — "§ 02 · 72 hours to hired" animation
// 15s loop showing an abstract moss-green arc sweeping through 72 hours,
// with six caption reveals (12h, 24h, 36h, 48h, 60h, 72h).

const T_PALETTE = {
  cream:    '#f5f0e8',
  creamDk:  '#ebe4d6',
  ink:      '#0f1713',
  ink70:    'rgba(15,23,19,0.70)',
  ink55:    'rgba(15,23,19,0.55)',
  ink40:    'rgba(15,23,19,0.40)',
  ink25:    'rgba(15,23,19,0.25)',
  ink12:    'rgba(15,23,19,0.12)',
  ink08:    'rgba(15,23,19,0.08)',
  moss700:  '#32553f',
  moss600:  '#3f6b54',
  moss500:  '#4d7e65',
  moss400:  '#6fa37a',
  moss300:  '#93b89a',
  moss200:  '#b8cdbb',
  moss100:  '#d7e1d4',
  amber:    '#cd8a4b',
  amberSoft:'#e2a568',
  amberGlow:'rgba(205,138,75,0.35)',
};
const T_FONTS = {
  display: '"Space Grotesk", system-ui, sans-serif',
  serif:   '"Instrument Serif", Georgia, serif',
  mono:    '"JetBrains Mono", ui-monospace, monospace',
};

// Easing helper (matches Easing.easeOutCubic feel)
const tEase = (t) => 1 - Math.pow(1 - t, 3);
const tEaseIO = (t) => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2,3)/2;

// ─── Film grain ─────────────────────────────────────────────────────────
function TFilmGrain({ opacity = 0.08 }) {
  return (
    <div style={{
      position: 'absolute', inset: 0, pointerEvents: 'none',
      opacity, mixBlendMode: 'multiply',
      backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.14  0 0 0 0 0.12  0 0 0 0 0.08  0 0 0 1 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>")`,
      backgroundSize: '220px 220px',
    }} />
  );
}

// ─── Corner chrome ──────────────────────────────────────────────────────
function TCornerChrome() {
  const time = useTime();
  const pulse = 0.5 + 0.5 * Math.sin(time * Math.PI * 1.1);

  return (
    <>
      {/* Top-left logo */}
      <img
        src="assets/digital-heroes-logo.png"
        alt=""
        style={{
          position: 'absolute', left: 56, top: 44,
          height: 52, width: 'auto',
          opacity: 0.42,
          pointerEvents: 'none',
        }}
      />

      {/* Top-right edition */}
      <div style={{
        position: 'absolute', right: 56, top: 58,
        fontFamily: T_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.16em',
        color: T_PALETTE.ink40,
        textTransform: 'uppercase',
      }}>
        vol. 01 — engagement
      </div>

      {/* Bottom-right section caption + pulse */}
      <div style={{
        position: 'absolute', right: 56, bottom: 56,
        display: 'flex', alignItems: 'center', gap: 12,
        fontFamily: T_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.14em',
        color: T_PALETTE.ink70,
        textTransform: 'uppercase',
        opacity: 0.75,
      }}>
        <span>§ 02 · 72-hour hire</span>
        <div style={{
          width: 8, height: 8, borderRadius: 4,
          background: T_PALETTE.moss500,
          opacity: 0.45 + 0.55 * pulse,
          boxShadow: `0 0 ${6 + 10*pulse}px ${T_PALETTE.moss400}`,
        }}/>
      </div>

      {/* Bottom-left folio */}
      <div style={{
        position: 'absolute', left: 56, bottom: 56,
        fontFamily: T_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.14em',
        color: T_PALETTE.ink40,
      }}>
        fig.&nbsp;&nbsp;02·i — engagement arc
      </div>
    </>
  );
}

// ─── Arc + timeline line ────────────────────────────────────────────────
// The arc is a single horizontal moss-green line that progressively
// "fills" from left (0h) to right (72h) as time advances.
// Timeline domain: sweep begins at t=1.5s, ends at t=13s (11.5s total for
// 72 hours). At t=1.5 → 5, covers 0h→24h. t=5→9, 24→48. t=9→13, 48→72.

const SWEEP_START = 1.5;
const SWEEP_END   = 13.0;
const HOLD_END    = 14.0;

function sweepHour(t) {
  // Returns hour value (0–72) for a given time t.
  if (t < SWEEP_START) return 0;
  if (t > SWEEP_END) return 72;
  const k = (t - SWEEP_START) / (SWEEP_END - SWEEP_START);
  return 72 * k;
}

// Reverse phase: 14–15s fades back to t=0 state.
function reverseAlpha(t) {
  if (t < HOLD_END) return 0;
  return (t - HOLD_END) / (15 - HOLD_END); // 0→1 across last second
}

// Layout constants
const TL_LEFT   = 240;
const TL_RIGHT  = 1680;
const TL_Y      = 540;    // vertical centerline
const TL_WIDTH  = TL_RIGHT - TL_LEFT;

function hourToX(hour) {
  return TL_LEFT + (hour / 72) * TL_WIDTH;
}

// ─── Tick marks ─────────────────────────────────────────────────────────
function TimelineAxis() {
  const time = useTime();
  const rev = reverseAlpha(time);
  const axisOpacity = 1 - rev;

  // Dashed unfilled portion (whole line)
  // Filled portion (left of current sweep)
  const curHour = sweepHour(time) * (1 - rev);
  const fillX = hourToX(curHour);

  // Intro: at t<1.5s, fade line in
  const introOpacity = Math.min(1, Math.max(0, time / 1.2));
  const opacity = axisOpacity * introOpacity;

  return (
    <>
      {/* Base line (dashed, unfilled) */}
      <div style={{
        position: 'absolute',
        left: TL_LEFT, top: TL_Y,
        width: TL_WIDTH, height: 1,
        borderTop: `1px dashed ${T_PALETTE.ink25}`,
        opacity,
      }}/>

      {/* Filled moss line (the arc sweep) */}
      <div style={{
        position: 'absolute',
        left: TL_LEFT, top: TL_Y - 1,
        width: fillX - TL_LEFT, height: 3,
        background: `linear-gradient(to right, ${T_PALETTE.moss500}, ${T_PALETTE.moss400})`,
        opacity,
        boxShadow: `0 0 14px ${T_PALETTE.moss300}`,
        transition: 'none',
      }}/>

      {/* Hour tick marks */}
      {[0, 24, 48, 72].map((h) => {
        const x = hourToX(h);
        const reached = sweepHour(time) >= h;
        const isAmber = h === 72 && time > 11.5 && time < HOLD_END;
        return (
          <div key={h} style={{
            position: 'absolute',
            left: x - 1, top: TL_Y - 12,
            width: 2, height: 24,
            background: reached
              ? (isAmber ? T_PALETTE.amber : T_PALETTE.moss600)
              : T_PALETTE.ink25,
            opacity,
            transition: 'none',
          }}/>
        );
      })}

      {/* Hour labels */}
      {[0, 24, 48, 72].map((h) => {
        const x = hourToX(h);
        const reached = sweepHour(time) >= h;
        const isAmber = h === 72 && time > 11.5 && time < HOLD_END;
        return (
          <div key={h} style={{
            position: 'absolute',
            left: x, top: TL_Y + 22,
            transform: 'translateX(-50%)',
            fontFamily: T_FONTS.mono,
            fontSize: 18,
            letterSpacing: '0.08em',
            color: reached
              ? (isAmber ? T_PALETTE.amber : T_PALETTE.moss700)
              : T_PALETTE.ink40,
            fontWeight: reached ? 600 : 400,
            opacity,
            transition: 'none',
          }}>
            {h}h
          </div>
        );
      })}
    </>
  );
}

// ─── Sweep cursor (the dot that leads the sweep) ────────────────────────
function SweepCursor() {
  const time = useTime();
  const rev = reverseAlpha(time);

  // Only visible during sweep
  if (time < SWEEP_START - 0.3) return null;
  if (time > HOLD_END + 0.2) return null;

  const hour = sweepHour(time) * (1 - rev);
  const x = hourToX(hour);

  // Amber bloom at 72h
  const amberBloom = hour >= 71.5 && time < HOLD_END
    ? Math.min(1, (hour - 71.5) / 0.5) * (1 - rev)
    : 0;

  const pulse = 0.5 + 0.5 * Math.sin(time * Math.PI * 3);
  const introOpacity = Math.min(1, Math.max(0, (time - (SWEEP_START - 0.3)) / 0.5));

  return (
    <>
      {/* Outer halo */}
      <div style={{
        position: 'absolute',
        left: x - 18, top: TL_Y - 18,
        width: 36, height: 36, borderRadius: '50%',
        background: amberBloom > 0
          ? `radial-gradient(circle, ${T_PALETTE.amberGlow}, transparent 70%)`
          : `radial-gradient(circle, rgba(111,163,122,0.35), transparent 70%)`,
        opacity: (0.5 + 0.5 * pulse) * introOpacity * (1 - rev),
        transition: 'none',
      }}/>
      {/* Core dot */}
      <div style={{
        position: 'absolute',
        left: x - 6, top: TL_Y - 6,
        width: 12, height: 12, borderRadius: '50%',
        background: amberBloom > 0.3 ? T_PALETTE.amber : T_PALETTE.moss600,
        boxShadow: amberBloom > 0.3
          ? `0 0 20px ${T_PALETTE.amber}, 0 0 40px ${T_PALETTE.amberGlow}`
          : `0 0 12px ${T_PALETTE.moss400}`,
        opacity: introOpacity * (1 - rev),
        transition: 'none',
      }}/>
    </>
  );
}

// ─── Caption reveal component ───────────────────────────────────────────
// Each caption appears when the sweep passes its hour mark.
// revealTime is the timeline-time when this caption should fade in (0.3s).
// Captions alternate above/below the timeline for rhythm.
function CaptionReveal({ hour, revealTime, above, title, body, serifWord, isAmber }) {
  const time = useTime();
  const rev = reverseAlpha(time);
  const local = time - revealTime;
  const alpha = local < 0 ? 0 : Math.min(1, local / 0.3);
  const opacity = alpha * (1 - rev);
  if (opacity < 0.01) return null;

  const rise = 8 * (1 - alpha); // translate-up on reveal

  const x = hourToX(hour);
  const y = above ? TL_Y - 68 : TL_Y + 68;

  // Caption width + anchor
  const width = 280;
  // Last caption (72h) anchors right-aligned so it doesn't overflow
  let leftOffset;
  if (hour === 72) leftOffset = -width + 40;
  else if (hour === 0) leftOffset = -20;
  else leftOffset = -width / 2;

  return (
    <div style={{
      position: 'absolute',
      left: x + leftOffset,
      top: y,
      width,
      transform: above
        ? `translateY(${-rise}px)`
        : `translateY(${rise}px)`,
      transformOrigin: above ? 'bottom left' : 'top left',
      opacity,
      // If above, anchor to bottom so text grows upward visually
      ...(above ? { marginTop: -80 } : {}),
      transition: 'none',
    }}>
      {/* Hour tag */}
      <div style={{
        fontFamily: T_FONTS.mono,
        fontSize: 11,
        letterSpacing: '0.18em',
        color: isAmber ? T_PALETTE.amber : T_PALETTE.moss600,
        textTransform: 'uppercase',
        fontWeight: 600,
        marginBottom: 6,
      }}>
        hour {hour}
      </div>
      {/* Title */}
      <div style={{
        fontFamily: T_FONTS.display,
        fontWeight: 600,
        fontSize: 22,
        lineHeight: 1.2,
        letterSpacing: '-0.015em',
        color: T_PALETTE.ink,
        marginBottom: 4,
      }}>
        {serifWord ? (
          <>
            {title.split(serifWord).map((part, i, arr) => (
              <React.Fragment key={i}>
                {part}
                {i < arr.length - 1 && (
                  <span style={{
                    fontFamily: T_FONTS.serif,
                    fontStyle: 'italic',
                    fontWeight: 400,
                    color: isAmber ? T_PALETTE.amber : T_PALETTE.moss600,
                    letterSpacing: '-0.005em',
                  }}>
                    {serifWord}
                  </span>
                )}
              </React.Fragment>
            ))}
          </>
        ) : title}
      </div>
      {/* Body */}
      <div style={{
        fontFamily: T_FONTS.display,
        fontWeight: 400,
        fontSize: 15,
        lineHeight: 1.4,
        color: T_PALETTE.ink55,
      }}>
        {body}
      </div>
      {/* Connector line from text to timeline */}
      <div style={{
        position: 'absolute',
        left: hour === 72 ? width - 40 : (hour === 0 ? 20 : width / 2),
        top: above ? 'auto' : -12,
        bottom: above ? -12 : 'auto',
        width: 1, height: 12,
        background: isAmber ? T_PALETTE.amber : T_PALETTE.moss400,
        opacity: 0.55,
      }}/>
    </div>
  );
}

// ─── Intro header text (§ 01 · you brief us) ────────────────────────────
function IntroHeader() {
  const time = useTime();
  const rev = reverseAlpha(time);
  // Visible 0–1.5 at full, then fades out as sweep starts, back in on reverse
  let opacity;
  if (time < 1.0) opacity = Math.min(1, time / 0.6);
  else if (time < 1.5) opacity = 1 - (time - 1.0) / 0.5;
  else opacity = 0;
  opacity = Math.max(opacity, rev);

  if (opacity < 0.01) return null;

  return (
    <div style={{
      position: 'absolute',
      left: 0, right: 0, top: TL_Y - 140,
      textAlign: 'center',
      opacity,
      transition: 'none',
    }}>
      <div style={{
        fontFamily: T_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.18em',
        color: T_PALETTE.moss600,
        textTransform: 'uppercase',
        marginBottom: 14,
      }}>
        t — zero
      </div>
      <div style={{
        fontFamily: T_FONTS.display,
        fontWeight: 500,
        fontSize: 36,
        letterSpacing: '-0.02em',
        color: T_PALETTE.ink,
      }}>
        § 01 ·{' '}
        <span style={{
          fontFamily: T_FONTS.serif,
          fontStyle: 'italic',
          fontWeight: 400,
          color: T_PALETTE.moss600,
        }}>
          you brief us
        </span>
      </div>
    </div>
  );
}

// ─── Section title (persistent over sweep) ──────────────────────────────
function SectionTitle() {
  const time = useTime();
  const rev = reverseAlpha(time);
  // Appears after intro fades (t~1.5), stays through 13, fades on reverse
  let opacity;
  if (time < 1.5) opacity = 0;
  else if (time < 2.2) opacity = (time - 1.5) / 0.7;
  else opacity = 1;
  opacity *= (1 - rev);

  if (opacity < 0.01) return null;

  return (
    <div style={{
      position: 'absolute',
      left: 0, right: 0, top: 180,
      textAlign: 'center',
      opacity,
      transition: 'none',
    }}>
      <div style={{
        fontFamily: T_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.2em',
        color: T_PALETTE.moss600,
        textTransform: 'uppercase',
        marginBottom: 18,
      }}>
        § 02 · engagement
      </div>
      <div style={{
        fontFamily: T_FONTS.display,
        fontWeight: 600,
        fontSize: 78,
        lineHeight: 1,
        letterSpacing: '-0.035em',
        color: T_PALETTE.ink,
      }}>
        72 hours to{' '}
        <span style={{
          fontFamily: T_FONTS.serif,
          fontStyle: 'italic',
          fontWeight: 400,
          color: T_PALETTE.moss600,
          letterSpacing: '-0.02em',
        }}>
          hired
        </span>
        .
      </div>
    </div>
  );
}

// ─── Amber bloom at hour 72 ─────────────────────────────────────────────
function AmberBloom() {
  const time = useTime();
  const rev = reverseAlpha(time);
  // Visible from ~11.5 (when sweep nears 72h) through hold
  let a;
  if (time < 11.5) a = 0;
  else if (time < 12.2) a = (time - 11.5) / 0.7;
  else if (time < HOLD_END) a = 1;
  else a = 0;
  const opacity = a * (1 - rev);

  if (opacity < 0.01) return null;

  const x = hourToX(72);
  const breathe = 0.85 + 0.15 * Math.sin(time * Math.PI * 1.5);

  return (
    <div style={{
      position: 'absolute',
      left: x - 180, top: TL_Y - 180,
      width: 360, height: 360, borderRadius: '50%',
      background: `radial-gradient(circle, ${T_PALETTE.amberGlow}, transparent 65%)`,
      opacity: opacity * breathe * 0.8,
      pointerEvents: 'none',
      transition: 'none',
    }}/>
  );
}

// ─── Main scene ─────────────────────────────────────────────────────────
function TimelineScene() {
  return (
    <>
      {/* Background tint variance */}
      <div style={{
        position: 'absolute', inset: 0,
        background: `radial-gradient(ellipse at 50% 40%, ${T_PALETTE.cream} 0%, ${T_PALETTE.creamDk} 100%)`,
      }}/>

      <SectionTitle />
      <IntroHeader />
      <AmberBloom />
      <TimelineAxis />
      <SweepCursor />

      {/* Six captions. Alternating above/below for rhythm. */}
      {/* Reveals at the moment the sweep passes each hour mark. */}
      {/* Hours: 12, 24, 36, 48, 60, 72 */}
      {/* time when sweep reaches hour h: SWEEP_START + (h/72)*(SWEEP_END-SWEEP_START) */}
      <CaptionReveal
        hour={12}
        revealTime={SWEEP_START + (12/72) * (SWEEP_END - SWEEP_START)}
        above={true}
        title="strategy + scope, written"
        serifWord="written"
        body="$0 — no commitment yet"
      />
      <CaptionReveal
        hour={24}
        revealTime={SWEEP_START + (24/72) * (SWEEP_END - SWEEP_START)}
        above={false}
        title="20-min call + SOW delivered"
        serifWord="delivered"
        body="lands in your inbox before you hang up"
      />
      <CaptionReveal
        hour={36}
        revealTime={SWEEP_START + (36/72) * (SWEEP_END - SWEEP_START)}
        above={true}
        title="team matched"
        serifWord="matched"
        body="names, portfolios, rates — no decks"
      />
      <CaptionReveal
        hour={48}
        revealTime={SWEEP_START + (48/72) * (SWEEP_END - SWEEP_START)}
        above={false}
        title="contract + kickoff"
        serifWord="kickoff"
        body="skip discovery theatre. start shipping."
      />
      <CaptionReveal
        hour={60}
        revealTime={SWEEP_START + (60/72) * (SWEEP_END - SWEEP_START)}
        above={true}
        title="environment provisioned"
        serifWord="provisioned"
        body="Slack, GitHub, staging — access granted"
      />
      <CaptionReveal
        hour={72}
        revealTime={SWEEP_START + (72/72) * (SWEEP_END - SWEEP_START) - 0.15}
        above={false}
        title="first commit shipped"
        serifWord="shipped"
        body="you own the code. always."
        isAmber={true}
      />

      <TFilmGrain opacity={0.07} />
      <TCornerChrome />
    </>
  );
}

// Expose to global so HTML can mount it
Object.assign(window, {
  TimelineScene,
  T_PALETTE,
  T_FONTS,
});
