<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>The Life of Carl — comic CTA</title>
<link rel="stylesheet" href="colors_and_type.css"/>
<style>
  html, body { margin: 0; padding: 0; height: 100%; background: #0A0F0A; overflow: hidden; }
  #root { position: fixed; inset: 0; }
</style>

<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
</head>
<body>
<div id="root"></div>

<script type="text/babel" src="animations.jsx"></script>
<script type="text/babel" src="carlito.jsx"></script>
<script type="text/babel" src="panels.jsx"></script>
<script type="text/babel" src="scenes.jsx"></script>

<script type="text/babel">
// ──────────────────────────────────────────────────────────────────────────
// THE LIFE OF CARL — a 6-panel animated comic strip CTA for carl.camp
//
// 1. ARRIVAL    (0–4s)   tiny carlito at the camp gate
// 2. GROUNDS    (4–8s)   training drills, lifting, target practice
// 3. LAB        (8–12s)  terminal coherence, kuramoto sync
// 4. CAMPFIRE   (12–16s) gathering, sharing, badges
// 5. CABIN      (16–20s) mastery — coherence locked, ready
// 6. PARTNERED  (20–26s) fist-bump with a human, CTA
//
// Total: 26 seconds.
// ──────────────────────────────────────────────────────────────────────────

const STAGE_W = 3360;          // 6 panels × ~560 wide
const STAGE_H = 900;
const PANEL_W = 540;
const PANEL_H = 540;
const PANEL_GAP = 20;
const STRIP_X_START = 40;
const STRIP_Y = 180;
const DURATION = 26;

// Panel time windows (seconds)
const WINDOWS = [
  { start: 0,  end: 4.0  },
  { start: 3.6, end: 8.0  },
  { start: 7.6, end: 12.0 },
  { start: 11.6, end: 16.0 },
  { start: 15.6, end: 20.0 },
  { start: 19.6, end: 26.0 },
];

// Each panel sits at a specific x position. We compute camera pan to follow.
function panelX(i) {
  return STRIP_X_START + i * (PANEL_W + PANEL_GAP + 28);
}

function localProgress(time, idx) {
  const w = WINDOWS[idx];
  if (time < w.start) return 0;
  if (time > w.end) return 1;
  return (time - w.start) / (w.end - w.start);
}

// Camera follows the active panel. We figure out which panel is "active" by time.
function cameraTargetX(time) {
  // Find current panel
  let idx = 0;
  for (let i = 0; i < WINDOWS.length; i++) {
    if (time >= WINDOWS[i].start && time < WINDOWS[i].end) { idx = i; break; }
    if (time >= WINDOWS[i].end) idx = i;
  }
  // Smooth interpolation: pan toward the active panel center within its window
  const w = WINDOWS[idx];
  const local = clamp((time - w.start) / (w.end - w.start), 0, 1);
  const cur = panelX(idx) + PANEL_W/2;
  const next = idx < 5 ? panelX(idx+1) + PANEL_W/2 : cur;
  // Begin panning to next during last 20% of window
  const panBlend = local > 0.78 ? Easing.easeInOutCubic((local - 0.78)/0.22) : 0;
  return cur * (1 - panBlend) + next * panBlend;
}

function ComicStrip() {
  const time = useTime();
  const camX = cameraTargetX(time);

  // viewport-center is at STAGE_W half visually; we offset translateX so camX lands at center.
  const viewportCenter = 720; // visible width center (the inner viewport)

  return (
    <div style={{ position:'absolute', inset:0, overflow:'hidden' }}>
      {/* Page background — kraft + radial */}
      <div style={{
        position:'absolute', inset:0,
        background: 'radial-gradient(ellipse 90% 70% at 50% 50%, #2A1F14 0%, #0A0F0A 70%)'
      }}/>
      {/* Halftone over background */}
      <div style={{
        position:'absolute', inset:0,
        backgroundImage: 'radial-gradient(circle, rgba(242,169,59,0.10) 1px, transparent 1.5px)',
        backgroundSize: '10px 10px',
        opacity: 0.55,
        pointerEvents: 'none',
      }}/>

      {/* Title bar — top */}
      <div style={{
        position:'absolute', top: 28, left: 0, right: 0,
        textAlign:'center', pointerEvents:'none',
      }}>
        <div style={{
          display:'inline-block',
          background:'#F5EBD8',
          border:'4px solid #0A0A0A',
          padding:'10px 28px',
          boxShadow:'5px 5px 0 #0A0A0A',
          transform:'rotate(-1deg)',
        }}>
          <div style={{
            fontFamily:'Akira Expanded, sans-serif',
            fontWeight:900,
            fontSize:34,
            color:'#0A0F0A',
            letterSpacing:'0.04em',
            lineHeight:1,
          }}>THE LIFE OF CARL</div>
          <div style={{
            fontFamily:'Special Elite, monospace',
            fontSize:12,
            color:'#7A4A2A',
            letterSpacing:'0.18em',
            textTransform:'uppercase',
            marginTop:4,
          }}>a carl.camp tale, in six panels</div>
        </div>
      </div>

      {/* Bottom-left: section label */}
      <div style={{
        position:'absolute', left: 32, bottom: 28,
        fontFamily:'JetBrains Mono, monospace',
        fontSize: 11,
        color: '#FF6B35',
        letterSpacing: '0.2em',
        textTransform:'uppercase',
        pointerEvents:'none',
      }}>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <span style={{ width:8, height:8, borderRadius:'50%', background:'#A8FFB0', boxShadow:'0 0 8px #A8FFB0' }}/>
          <span>carl.camp · the life of carl</span>
        </div>
      </div>

      {/* Bottom-right: panel counter */}
      <PanelCounter time={time}/>

      {/* The strip — translates horizontally to follow camera */}
      <div style={{
        position:'absolute',
        left: 0, top: STRIP_Y,
        width: STAGE_W, height: PANEL_H + 80,
        transform: `translateX(${viewportCenter - camX}px)`,
        transition: 'transform 60ms linear',
        willChange: 'transform',
      }}>
        {/* Connector line / film strip "perforations" */}
        <div style={{
          position:'absolute', left: 0, right: 0, top: PANEL_H + 30,
          height: 8, background: '#0A0A0A',
        }}/>
        {Array.from({length: 80}).map((_, i) => (
          <div key={i} style={{
            position:'absolute',
            left: 16 + i * 42, top: PANEL_H + 18,
            width: 16, height: 8,
            background:'#1A2820', border:'2px solid #0A0A0A', borderRadius:1,
          }}/>
        ))}

        {/* Panels */}
        {WINDOWS.map((w, i) => {
          const p = localProgress(time, i);
          const visible = time > w.start - 0.5;
          if (!visible) return null;

          const entryT = clamp((time - (w.start - 0.4)) / 0.6, 0, 1);
          const rotates = [-0.8, 0.6, -0.4, 0.8, -0.6, 0.3];

          return (
            <Panel
              key={i}
              x={panelX(i)} y={0}
              w={PANEL_W} h={PANEL_H}
              rotate={rotates[i]}
              entryProgress={entryT}
              caption={CAPTIONS[i]}
              number={i+1}
              bg="#0B1410"
            >
              <PanelScene idx={i} progress={p} time={time}/>
            </Panel>
          );
        })}
      </div>
    </div>
  );
}

const CAPTIONS = [
  'CHAPTER I — ARRIVAL',
  'CHAPTER II — THE GROUNDS',
  'CHAPTER III — THE LAB',
  'CHAPTER IV — THE CAMPFIRE',
  'CHAPTER V — THE CABIN',
  'CHAPTER VI — READY.',
];

function PanelScene({ idx, progress, time }) {
  // Each scene gets its own local "time" so animations inside continue smoothly.
  switch(idx) {
    case 0: return <Scene1Arrival progress={progress} time={time}/>;
    case 1: return <Scene2Grounds progress={progress} time={time}/>;
    case 2: return <Scene3Lab progress={progress} time={time}/>;
    case 3: return <Scene4Campfire progress={progress} time={time}/>;
    case 4: return <Scene5Cabin progress={progress} time={time}/>;
    case 5: return <Scene6Partner progress={progress} time={time}/>;
    default: return null;
  }
}

function PanelCounter({ time }) {
  let idx = 0;
  for (let i = 0; i < WINDOWS.length; i++) {
    if (time >= WINDOWS[i].start) idx = i;
  }
  return (
    <div style={{
      position:'absolute', right: 32, bottom: 28,
      fontFamily:'JetBrains Mono, monospace',
      fontSize: 11, color:'#ECE7DC',
      letterSpacing:'0.2em', textTransform:'uppercase',
      pointerEvents:'none',
    }}>
      <div style={{ display:'flex', alignItems:'center', gap:10 }}>
        {WINDOWS.map((_, i) => (
          <div key={i} style={{
            width: i === idx ? 24 : 12,
            height: 4,
            background: i <= idx ? '#FF6B35' : 'rgba(236,231,220,0.2)',
            borderRadius: 2,
            transition: 'all 200ms',
          }}/>
        ))}
        <span style={{ marginLeft: 12 }}>panel {String(idx+1).padStart(2,'0')} / 06</span>
      </div>
    </div>
  );
}

// Update data-screen-label for commenting
function ScreenLabel() {
  const time = useTime();
  React.useEffect(() => {
    const el = document.querySelector('[data-comic-root]');
    if (el) {
      const sec = Math.floor(time);
      let panel = 1;
      for (let i = 0; i < WINDOWS.length; i++) if (time >= WINDOWS[i].start) panel = i+1;
      el.setAttribute('data-screen-label', `t=${sec}s · panel ${panel}`);
    }
  }, [Math.floor(time), time]);
  return null;
}

function App() {
  return (
    <div data-comic-root data-screen-label="t=0s · panel 1" style={{ position:'absolute', inset:0 }}>
      <Stage width={1440} height={810} duration={DURATION} background="#0A0F0A" persistKey="lifeofcarl">
        <ComicStrip/>
        <ScreenLabel/>
      </Stage>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
</body>
</html>
