{"project":{"id":"xskKhk9","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Daybreak — Launch Film","thumbnail":"UklGRooOAABXRUJQVlA4IH4OAAAwqACdASogA1gCPlEokUajoiGhILM4SHAKCWlu8p8eNxg1d58d+SvDNmQfVD+h6R3ip9LzzHftV6TPqT9AD+5/4z1OPUd9AD9dPTH9kb9t/TA1Yjyh/Xe0z/GeE/gz8Y+23qe40+q/Ud+OfZj8P+YnsJ/gPAH1K+oF+O/yD/Sfaf6UO1qmM9Qj1c+b/6X+5/uD5mP9L6CfWz/Vfkz8If6X/ofFA8T7yX2Bf5J/Ov9l/Xvyn+lb+U/5X+W/MX2j/mH9+/7X+d+AT+Sf0z/j/3z2ufYt+4Hskftx//wWM8MZadOnTp06dOnTp06dOnTp06dOnTp06dOnTp06dOnTp06dOnTp06dOnTfR7WoFAoFAoFAoFAoFAoFAoFAoFAoFAoFAoFAoFAHdjYWWGaJQqP24XjprROIy0TiMtE4jLROIy0FJAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg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","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"DIRECTION.md","content":"# Daybreak — Launch Film (45–60s, code-built)\n\n**Product (invented):** Daybreak — the calendar that books your focus before anyone books you. — daybreak.app\n\n## Brand canon\nDaybreak believes the morning is the most valuable real estate in a knowledge worker's week, and that the modern calendar — a first-come-first-served free-for-all — quietly sells it off thirty minutes at a time. Daybreak speaks in calm, declarative sentences: warm but unsentimental, confident without hype, never exclamation marks, never jargon. Its palette is pre-dawn ink (#0B0E17) cut by dawn light (#FFB36B → #FF5C39); its villain is the meeting-shredded week, never the person living it. Every line attacks the old paradigm (\"the calendar works for everyone but you\") and offers one promise: focus gets booked first, and everything else routes around it. Tagline: **Own the morning.**\n\n## Story — 3 acts\n1. **PAIN (0–16s)** — A week grid assembles, then 17 meetings slam in until the week is shrapnel. \"Your calendar works for everyone but you.\"\n2. **TURN (16–30s)** — THE DROP. Dawn light. \"Focus gets booked first.\" One monolithic 9:00–11:30 block lands before anything else exists.\n3. **PROOF (30–48s)** — One journey: Monday 8:54, focus reserved; a conflicting invite arrives and Daybreak counter-proposes 1:30 PM; focus timer runs, PR merges. Then the Act-1 grid RETURNS, transformed: five calm dawn mornings. \"Five mornings. Yours again.\"\n4. **CLOSE (48–60s)** — Quiet line → logo stamp → \"Own the morning.\" → daybreak.app CTA.\n\nRhyme: the identical grid geometry (GRID in constants.js) carries Act 1's chaos and Act 3's calm.\n\n## Cue sheet (1920×1080 @ 30fps, 1800 frames)\n| TC | Frame | Event |\n|------|-------|-------|\n| 00:00.0 | 0 | Black. Tick pulse + low drone begin |\n| 00:00.3 | 8 | Week grid draws in left→right |\n| 00:02.5 | 75 | Meetings slam in (17 blocks, accelerating, camera shake per hit) |\n| 00:10.1 | 304 | Double-booking conflict lands (red) |\n| 00:11.5 | 345 | H1: \"Your calendar works for everyone but you.\" |\n| 00:14.5 | 435 | Grid compresses + dims · riser · 0.3s vacuum |\n| **00:16.0** | **480** | **★ DROP — hard cut: dawn field, \"Focus gets booked first.\"** |\n| 00:18.0 | 540 | Daybreak wordmark in |\n| 00:20.3 | 608 | Monolith focus block lands (9:00–11:30) + shake |\n| 00:24.0 | 720 | \"Meetings route around it.\" |\n| 00:27.0 | 810 | \"Automatically. Every week.\" |\n| 00:30.0 | 900 | Act 3: product UI, Monday 8:54, focus reserved |\n| 00:34.0 | 1020 | Conflicting invite toast |\n| 00:36.0 | 1080 | Daybreak counter-proposes 1:30 PM |\n| 00:38.0 | 1140 | Focus HUD: live timer, 14 notifications held |\n| 00:40.0 | 1200 | \"PR #214 merged\" card |\n| **00:44.0** | **1320** | **★ CRASH (the only one) — week grid returns, transformed** |\n| 00:46.0 | 1380 | H2: \"Five mornings. Yours again.\" |\n| 00:49.0 | 1470 | Quiet line: \"The morning is the most valuable real estate in your week.\" |\n| **00:54.0** | **1620** | **★ FINAL HIT — logo stamp** |\n| 00:56.0 | 1680 | Tagline: \"Own the morning.\" |\n| 00:57.2 | 1716 | CTA: daybreak.app — start free |\n| 01:00.0 | 1800 | End |\n\n## Music anchors (hard, 3 of 3)\n- **DROP @ 16.000s** — pre-drop vacuum 15.7–16.0s, full pattern at 16.0 (verified ≥20 dB RMS step)\n- **CRASH @ 44.000s** — the only crash; everything strips back to pads after\n- **FINAL HIT @ 54.000s** — boom + chord stab on logo stamp; silence by ~59s\n\nScore: Web Audio synth (120 BPM so all anchors land on beats), event-list scheduler, verified via OfflineAudioContext RMS probe (`window.__qaScore()`).\n"},{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Daybreak — Launch Film</title>\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n  <link href=\"https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,650&family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" href=\"/css/styles.css\">\n</head>\n<body>\n  <main class=\"theater\">\n    <div class=\"stage-wrap\" id=\"stageWrap\">\n      <div id=\"stage\" class=\"stage\">\n        <div class=\"stage__shake\" id=\"shake\">\n          <section id=\"act1\" class=\"scene\" aria-hidden=\"true\"></section>\n          <section id=\"act2\" class=\"scene\" aria-hidden=\"true\"></section>\n          <section id=\"act3\" class=\"scene\" aria-hidden=\"true\"></section>\n          <section id=\"outro\" class=\"scene\" aria-hidden=\"true\"></section>\n        </div>\n        <div id=\"flash\" class=\"flash\"></div>\n      </div>\n    </div>\n    <div class=\"controls\">\n      <button id=\"playBtn\" class=\"btn\">Play</button>\n      <button id=\"restartBtn\" class=\"btn btn--ghost\">Restart</button>\n      <div class=\"scrub-wrap\">\n        <div class=\"cue-markers\" id=\"cueMarkers\"></div>\n        <input type=\"range\" id=\"scrub\" min=\"0\" max=\"1799\" value=\"0\" step=\"1\" aria-label=\"Timeline scrubber\">\n      </div>\n      <div class=\"readout\" id=\"readout\">00:00.0 · f0</div>\n    </div>\n  </main>\n  <script type=\"module\" src=\"/js/main.js\"></script>\n</body>\n</html>\n"},{"name":"README.md","content":"# Blnq Project\n"}],"folders":[{"folder":"css","files":[{"name":"styles.css","content":"/* Daybreak — Launch Film */\n:root {\n  color-scheme: dark;\n  --bg: #0B0E17;\n  --ink: #F4EFE6;\n  --muted: #9AA2B5;\n  --faint: #8B94AA;\n  --dawn1: #FFB36B;\n  --dawn2: #FF5C39;\n  --slate: #353C52;\n  --slate-edge: #4A5270;\n  --alert: #E4593F;\n  --line: rgba(244, 239, 230, 0.09);\n  --surface: #111626;\n  --font-display: \"Fraunces\", Georgia, serif;\n  --font-ui: \"Inter\", system-ui, sans-serif;\n}\n\n* { box-sizing: border-box; margin: 0; }\nhtml, body { height: 100%; }\nbody { background: #05070D; color: var(--ink); font-family: var(--font-ui); overflow: hidden; }\n\n/* ---------- theater chrome ---------- */\n.theater { display: flex; flex-direction: column; height: 100vh; }\n.stage-wrap { position: relative; flex: 1; overflow: hidden; }\n.stage {\n  position: absolute; left: 50%; top: 50%;\n  width: 1920px; height: 1080px;\n  background: var(--bg);\n  transform: translate(-50%, -50%) scale(var(--fit, 0.5));\n  overflow: hidden;\n}\n.stage__shake { position: absolute; inset: 0; }\n.scene { position: absolute; inset: 0; display: none; }\n.scene.on { display: block; }\n.flash { position: absolute; inset: 0; background: #FFEEDC; opacity: 0; pointer-events: none; z-index: 50; }\n\n.controls {\n  display: flex; gap: 14px; align-items: center;\n  padding: 12px 18px;\n  background: #0A0D15; border-top: 1px solid var(--line);\n}\n.btn {\n  font: 600 14px var(--font-ui); color: #0B0E17;\n  background: var(--dawn1); border: 0; border-radius: 8px;\n  padding: 9px 20px; cursor: pointer; min-width: 92px;\n}\n.btn--ghost { background: transparent; color: var(--ink); border: 1px solid var(--slate-edge); }\n.btn:hover { filter: brightness(1.12); }\n.scrub-wrap { position: relative; flex: 1; height: 30px; display: flex; align-items: flex-end; }\n#scrub { width: 100%; accent-color: var(--dawn2); cursor: pointer; }\n.cue-markers { position: absolute; left: 0; right: 0; top: 0; height: 12px; pointer-events: none; }\n.cue-mark { position: absolute; top: 0; width: 2px; height: 9px; background: var(--dawn1); }\n.cue-mark span {\n  position: absolute; top: -2px; left: 5px;\n  font: 600 9px var(--font-ui); letter-spacing: 0.1em; color: var(--muted); white-space: nowrap;\n}\n.readout { font: 500 13px ui-monospace, SFMono-Regular, monospace; color: var(--muted); min-width: 130px; text-align: right; }\n\n/* ---------- shared scene pieces ---------- */\n.headline {\n  position: absolute; left: 50%; transform: translateX(-50%);\n  text-align: center;\n  font: 500 92px/1.08 var(--font-display);\n  letter-spacing: -0.01em; color: var(--ink);\n  opacity: 0; width: 1560px;\n}\n.scrim { position: absolute; inset: 0; background: rgba(6, 8, 14, 0.6); opacity: 0; }\n.glow {\n  position: absolute; inset: 0; opacity: 0;\n  background: radial-gradient(1100px 640px at 50% 112%, rgba(255, 140, 80, 0.38), transparent 66%);\n}\n.logo-mark { position: relative; width: 34px; height: 17px; border-radius: 17px 17px 0 0; background: linear-gradient(180deg, var(--dawn1), var(--dawn2)); }\n.logo-mark::after { content: \"\"; position: absolute; left: -22%; right: -22%; top: calc(100% + 3px); height: 3px; border-radius: 2px; background: var(--ink); }\n\n/* calendar (acts 1 & 3 — same geometry, the rhyme) */\n.cal { position: absolute; transform-origin: 50% 50%; }\n.cal__dayhead { position: absolute; top: -46px; font: 600 16px var(--font-ui); letter-spacing: 0.22em; color: var(--muted); }\n.cal__vline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--line); }\n.cal__hline { position: absolute; left: 0; right: 0; height: 1px; background: var(--line); }\n.cal__hour { position: absolute; right: 100%; margin-right: 16px; transform: translateY(-50%); font: 500 13px var(--font-ui); color: var(--faint); }\n.blk {\n  position: absolute; border-radius: 8px; padding: 7px 12px;\n  font: 600 15px/1.25 var(--font-ui); color: #C9CFDE;\n  background: var(--slate); box-shadow: inset 0 0 0 1px var(--slate-edge);\n  opacity: 0; overflow: hidden; white-space: nowrap;\n}\n.blk__time { display: block; font: 500 12px var(--font-ui); opacity: 0.72; }\n.blk--conflict { box-shadow: inset 0 0 0 2px var(--alert); color: #F0B9AC; }\n.blk--focus {\n  background: linear-gradient(160deg, var(--dawn1), var(--dawn2));\n  color: #2A1206; box-shadow: none;\n  font: 650 18px/1.2 var(--font-display);\n}\n.blk--focus .blk__time { font: 600 12px var(--font-ui); opacity: 0.8; }\n\n/* ---------- act 1 ---------- */\n.a1-kicker {\n  position: absolute; left: 50%; top: 112px; transform: translateX(-50%);\n  font: 600 18px var(--font-ui); letter-spacing: 0.34em; color: var(--faint); opacity: 0;\n}\n.a1-h1 { top: 420px; z-index: 5; }\n\n/* ---------- act 2 ---------- */\n.a2-h1 {\n  position: absolute; left: 50%; top: 380px; transform: translateX(-50%);\n  width: 1700px; text-align: center;\n  font: 650 118px/1.04 var(--font-display); letter-spacing: -0.015em;\n  opacity: 0;\n}\n.a2-wordmark {\n  position: absolute; top: 84px; left: 50%; transform: translateX(-50%);\n  display: flex; align-items: center; gap: 16px;\n  font: 650 30px var(--font-display); letter-spacing: 0.04em; opacity: 0;\n}\n.a2-copy { position: absolute; left: 240px; top: 400px; width: 780px; opacity: 0; }\n.a2-copy__big { font: 650 56px/1.15 var(--font-display); margin-bottom: 36px; }\n.a2-line { font: 500 30px/1.4 var(--font-ui); color: var(--muted); margin-bottom: 14px; opacity: 0; }\n.a2-line strong { color: var(--dawn1); font-weight: 600; }\n.a2-col {\n  position: absolute; left: 1170px; top: 270px; width: 430px; height: 620px;\n  border: 1px solid var(--line); border-radius: 16px; padding: 24px; overflow: hidden;\n  background: rgba(17, 22, 38, 0.6); opacity: 0;\n}\n.a2-col__head { font: 600 14px var(--font-ui); letter-spacing: 0.22em; color: var(--muted); margin-bottom: 8px; }\n.a2-col__rule { position: absolute; left: 24px; right: 24px; height: 1px; background: var(--line); }\n.a2-mono {\n  position: absolute; left: 24px; right: 24px; top: 118px; height: 268px;\n  border-radius: 14px; padding: 22px;\n  background: linear-gradient(160deg, var(--dawn1), var(--dawn2));\n  color: #2A1206; font: 650 34px var(--font-display);\n  box-shadow: 0 24px 60px rgba(255, 92, 57, 0.25);\n}\n.a2-mono small { display: block; font: 600 15px var(--font-ui); margin-top: 8px; opacity: 0.82; }\n.a2-moved {\n  position: absolute; left: 24px; right: 24px; top: 460px; height: 56px;\n  border-radius: 10px; background: var(--slate); box-shadow: inset 0 0 0 1px var(--slate-edge);\n  padding: 9px 14px; font: 600 15px var(--font-ui); color: #C9CFDE; opacity: 0;\n}\n.a2-moved small { display: block; font: 500 12px var(--font-ui); opacity: 0.72; }\n\n/* ---------- act 3 ---------- */\n.app {\n  position: absolute; left: 260px; top: 128px; width: 1400px; height: 824px;\n  background: var(--surface); border: 1px solid rgba(244, 239, 230, 0.12);\n  border-radius: 18px; box-shadow: 0 40px 120px rgba(0, 0, 0, 0.55); opacity: 0;\n}\n.app__bar { height: 56px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 8px; padding: 0 22px; }\n.dot { width: 12px; height: 12px; border-radius: 50%; background: #2A3147; }\n.app__title { margin-left: 14px; font: 600 16px var(--font-ui); color: var(--muted); }\n.app__brand { margin-left: auto; display: flex; align-items: center; gap: 10px; font: 650 17px var(--font-display); color: var(--ink); }\n.app__brand .logo-mark { width: 22px; height: 11px; border-radius: 11px 11px 0 0; }\n.app__brand .logo-mark::after { height: 2px; top: calc(100% + 2px); }\n.app__body { display: flex; height: calc(100% - 57px); }\n.app__side { width: 240px; border-right: 1px solid var(--line); padding: 20px 14px; }\n.side__item { font: 500 15px var(--font-ui); color: var(--muted); padding: 10px 14px; border-radius: 8px; margin-bottom: 4px; }\n.side__item--on { background: rgba(255, 140, 80, 0.13); color: var(--dawn1); font-weight: 600; }\n.app__main { flex: 1; position: relative; padding: 26px 36px; }\n.day-h { font: 650 28px var(--font-display); }\n.day-sub { font: 500 14px var(--font-ui); color: var(--muted); margin-top: 5px; }\n.tl { position: absolute; left: 104px; right: 44px; top: 128px; bottom: 116px; }\n.tl__row { position: absolute; left: 0; right: 0; height: 1px; background: var(--line); }\n.tl__lbl { position: absolute; right: 100%; margin-right: 16px; transform: translateY(-50%); font: 500 13px var(--font-ui); color: var(--faint); }\n.evt { position: absolute; left: 0; right: 0; border-radius: 10px; padding: 10px 16px; font: 600 16px var(--font-ui); }\n.evt--meet { background: var(--slate); box-shadow: inset 0 0 0 1px var(--slate-edge); color: #C9CFDE; }\n.evt--focus {\n  background: linear-gradient(160deg, var(--dawn1), var(--dawn2));\n  color: #2A1206; font: 650 22px var(--font-display);\n}\n.evt__tag { display: block; font: 700 11px var(--font-ui); letter-spacing: 0.12em; opacity: 0.78; margin-top: 5px; }\n.evt--ghost { background: transparent; border: 2px dashed var(--slate-edge); color: var(--muted); opacity: 0; }\n.toast {\n  position: absolute; top: 96px; right: 40px; width: 396px;\n  background: #1A2034; border: 1px solid var(--slate-edge); border-radius: 12px;\n  padding: 16px 18px; opacity: 0; z-index: 5;\n}\n.toast--ok { border-color: rgba(255, 179, 107, 0.55); }\n.toast__t { font: 600 16px var(--font-ui); }\n.toast__t em { font-style: normal; color: var(--dawn1); }\n.toast__s { font: 500 13px var(--font-ui); color: var(--muted); margin-top: 5px; }\n.hud {\n  position: absolute; left: 36px; right: 36px; bottom: 26px; height: 66px;\n  background: #0D1120; border: 1px solid var(--line); border-radius: 12px;\n  display: flex; align-items: center; gap: 26px; padding: 0 26px; opacity: 0;\n}\n.hud__lbl { font: 700 12px var(--font-ui); letter-spacing: 0.16em; color: var(--dawn1); }\n.hud__timer { font: 650 26px ui-monospace, monospace; }\n.hud__meta { font: 500 14px var(--font-ui); color: var(--muted); }\n.card-pr {\n  position: absolute; right: 40px; bottom: 116px; width: 320px;\n  background: #15240F00; background: #142013; border: 1px solid rgba(122, 220, 130, 0.35);\n  border-radius: 12px; padding: 14px 18px; opacity: 0; z-index: 5;\n  font: 600 16px var(--font-ui); color: #A9E8AE;\n}\n.card-pr small { display: block; font: 500 13px var(--font-ui); color: #7FA886; margin-top: 4px; }\n.a3-h2 { top: 60px; font-size: 72px; width: 1400px; }\n\n/* ---------- outro ---------- */\n.o-quiet {\n  position: absolute; left: 50%; top: 480px; transform: translateX(-50%);\n  width: 1300px; text-align: center;\n  font: 500 46px/1.35 var(--font-display); color: var(--muted); opacity: 0;\n}\n.o-logo {\n  position: absolute; left: 50%; top: 420px; transform: translateX(-50%);\n  display: flex; align-items: center; gap: 30px; opacity: 0;\n}\n.o-logo .logo-mark { width: 68px; height: 34px; border-radius: 34px 34px 0 0; }\n.o-logo .logo-mark::after { height: 4px; top: calc(100% + 5px); }\n.o-name { font: 650 104px var(--font-display); letter-spacing: 0.015em; }\n.o-tag { position: absolute; left: 50%; top: 610px; transform: translateX(-50%); font: 500 42px var(--font-display); color: var(--dawn1); opacity: 0; }\n.o-cta {\n  position: absolute; left: 50%; top: 716px; transform: translateX(-50%);\n  font: 600 22px var(--font-ui); color: #0B0E17;\n  background: var(--ink); padding: 17px 36px; border-radius: 999px; opacity: 0; white-space: nowrap;\n}\n"}],"folders":[]},{"folder":"js","files":[{"name":"act1.js","content":"// ACT 1 — THE PAIN. A week grid assembles; 17 meetings shred it.\nimport { GRID, DAYS, MEETINGS, dayW, hourH, dayX, hourY, fmtH } from './constants.js';\nimport { seg, slam, shakeAmp, lerp, easeOutCubic, easeInCubic } from './engine.js';\n\n// Shared calendar builder — Act 3 reuses it with calm blocks (the rhyme).\nexport function buildCal(defs) {\n  const cal = document.createElement('div');\n  cal.className = 'cal';\n  cal.style.cssText = `left:${GRID.x}px;top:${GRID.y}px;width:${GRID.w}px;height:${GRID.h}px;`;\n  let html = '';\n  DAYS.forEach((d, i) => {\n    html += `<div class=\"cal__dayhead\" style=\"left:${dayX(i) + dayW / 2}px;transform:translateX(-50%)\">${d}</div>`;\n  });\n  for (let i = 0; i <= GRID.days; i++) {\n    html += `<div class=\"cal__vline\" style=\"left:${dayX(i)}px\"></div>`;\n  }\n  for (let h = GRID.hourStart; h <= GRID.hourEnd; h += 2) {\n    html += `<div class=\"cal__hline\" style=\"top:${hourY(h)}px\"></div>`;\n    html += `<div class=\"cal__hour\" style=\"top:${hourY(h)}px\">${fmtH(h)}</div>`;\n  }\n  cal.innerHTML = html;\n  const blocks = defs.map((m) => {\n    const el = document.createElement('div');\n    el.className = 'blk' + (m.conflict ? ' blk--conflict' : '') + (m.focus ? ' blk--focus' : '');\n    el.style.cssText = `left:${dayX(m.d) + 6}px;top:${hourY(m.h) + 3}px;width:${dayW - 12}px;height:${m.len * hourH - 6}px;`;\n    const time = m.time || `${fmtH(m.h)} · ${m.len * 60}m`;\n    el.innerHTML = m.len >= 1 ? `${m.label}<span class=\"blk__time\">${time}</span>` : m.label;\n    cal.appendChild(el);\n    return { m, el };\n  });\n  return { cal, blocks };\n}\n\nexport const act1 = {\n  range: [0, 480],\n  build(root, api) {\n    this.api = api;\n    const { cal, blocks } = buildCal(MEETINGS);\n    this.cal = cal;\n    this.blocks = blocks;\n    root.appendChild(cal);\n\n    this.kicker = document.createElement('div');\n    this.kicker.className = 'a1-kicker';\n    this.kicker.textContent = 'A WEEK, AS USUAL';\n    root.appendChild(this.kicker);\n\n    this.scrim = document.createElement('div');\n    this.scrim.className = 'scrim';\n    root.appendChild(this.scrim);\n\n    this.h1 = document.createElement('div');\n    this.h1.className = 'headline a1-h1';\n    this.h1.innerHTML = 'Your calendar works for<br>everyone but you.';\n    root.appendChild(this.h1);\n\n    this.root = root;\n  },\n  update(f) {\n    // Grid draws in 8–40\n    const rev = seg(f, 8, 40, easeOutCubic);\n    this.cal.style.clipPath = `inset(0 ${(1 - rev) * 100}% 0 0)`;\n\n    // Kicker 15–300\n    this.kicker.style.opacity = seg(f, 15, 28) - seg(f, 295, 312);\n\n    // Meetings slam in, each with a shake impulse\n    let shx = 0, shy = 0;\n    for (const { m, el } of this.blocks) {\n      const t = slam(f, m.f, 9);\n      el.style.opacity = f >= m.f ? 1 : 0;\n      el.style.transform = `scale(${lerp(1.7, 1, t)})`;\n      const a = shakeAmp(f, m.f, 9, 7);\n      if (a) { shx += a * Math.sin(f * 1.9); shy += a * Math.cos(f * 2.7); }\n    }\n    this.api.setShake(shx, shy);\n\n    // Headline 345 over scrim\n    this.scrim.style.opacity = seg(f, 335, 355) * 0.62;\n    const ht = slam(f, 345, 10);\n    this.h1.style.opacity = f >= 345 ? 1 : 0;\n    this.h1.style.transform = `translateX(-50%) translateY(${lerp(46, 0, ht)}px)`;\n    if (f >= 345 && f < 354) this.api.flash((1 - (f - 345) / 9) * 0.16);\n\n    // 435–479: compress + dim into the vacuum before the drop\n    const c = seg(f, 435, 479, easeInCubic);\n    this.root.style.filter = `brightness(${1 - c * 0.6})`;\n    this.cal.style.transform = `scale(${1 - c * 0.1})`;\n  },\n};\n"},{"name":"act2.js","content":"// ACT 2 — THE TURN. The drop. Dawn light; focus gets booked first.\nimport { seg, slam, shakeAmp, lerp, easeOutExpo } from './engine.js';\n\nexport const act2 = {\n  range: [480, 900],\n  build(root, api) {\n    this.api = api;\n\n    this.glow = document.createElement('div');\n    this.glow.className = 'glow';\n    root.appendChild(this.glow);\n\n    this.h1 = document.createElement('div');\n    this.h1.className = 'a2-h1';\n    this.h1.innerHTML = 'Focus gets booked first.';\n    root.appendChild(this.h1);\n\n    this.wordmark = document.createElement('div');\n    this.wordmark.className = 'a2-wordmark';\n    this.wordmark.innerHTML = '<span class=\"logo-mark\"></span><span>Daybreak</span>';\n    root.appendChild(this.wordmark);\n\n    this.copy = document.createElement('div');\n    this.copy.className = 'a2-copy';\n    this.copy.innerHTML = `\n      <div class=\"a2-copy__big\">Daybreak reserves your deep work before the week fills.</div>\n      <div class=\"a2-line\" data-l=\"1\">Meetings <strong>route around it</strong> — not the other way round.</div>\n      <div class=\"a2-line\" data-l=\"2\">Automatically. <strong>Every week.</strong></div>`;\n    root.appendChild(this.copy);\n    this.lines = [...this.copy.querySelectorAll('.a2-line')];\n    this.big = this.copy.querySelector('.a2-copy__big');\n\n    this.col = document.createElement('div');\n    this.col.className = 'a2-col';\n    this.col.innerHTML = `\n      <div class=\"a2-col__head\">MONDAY</div>\n      <div class=\"a2-col__rule\" style=\"top:108px\"></div>\n      <div class=\"a2-col__rule\" style=\"top:400px\"></div>\n      <div class=\"a2-mono\" id=\"a2mono\">Deep work<small>9:00 – 11:30 · held weekly</small></div>\n      <div class=\"a2-moved\" id=\"a2moved\">Sync<small>moved to 1:30 PM by Daybreak</small></div>`;\n    root.appendChild(this.col);\n    this.mono = this.col.querySelector('#a2mono');\n    this.moved = this.col.querySelector('#a2moved');\n  },\n  update(f) {\n    // Dawn field, on from the cut, swelling\n    this.glow.style.opacity = 0.45 + seg(f, 480, 580) * 0.45;\n\n    // H1 slams on the drop frame (480), holds, hands off at ~590\n    const ht = slam(f, 480, 9);\n    const out = seg(f, 585, 607);\n    this.h1.style.opacity = (f >= 480 ? 1 : 0) * (1 - out);\n    this.h1.style.transform = `translateX(-50%) scale(${lerp(1.22, 1, ht)}) translateY(${out * -60}px)`;\n    if (f >= 480 && f < 490) this.api.flash((1 - (f - 480) / 10) * 0.38);\n\n    // Wordmark from 540, stays\n    this.wordmark.style.opacity = seg(f, 540, 562);\n\n    // Column + copy from 600\n    this.col.style.opacity = seg(f, 600, 616);\n    this.copy.style.opacity = seg(f, 604, 620);\n    this.big.style.opacity = seg(f, 604, 624);\n\n    // Monolith drops in 608–622 and LANDS\n    const drop = seg(f, 608, 622, easeOutExpo);\n    this.mono.style.opacity = f >= 608 ? 1 : 0;\n    this.mono.style.transform = `translateY(${lerp(-620, 0, drop)}px)`;\n    const a = shakeAmp(f, 621, 10, 7);\n    this.api.setShake(a * Math.sin(f * 2.1), a * Math.cos(f * 2.9));\n    if (f >= 621 && f < 629) this.api.flash((1 - (f - 621) / 8) * 0.2);\n\n    // Supporting lines at 720 / 810; moved-meeting chip at 740\n    this.lines[0].style.opacity = seg(f, 720, 738);\n    this.lines[0].style.transform = `translateY(${lerp(18, 0, seg(f, 720, 738))}px)`;\n    this.lines[1].style.opacity = seg(f, 810, 828);\n    this.lines[1].style.transform = `translateY(${lerp(18, 0, seg(f, 810, 828))}px)`;\n    this.moved.style.opacity = seg(f, 740, 758);\n  },\n};\n"},{"name":"act3.js","content":"// ACT 3 — THE PROOF. One journey: Monday 8:54 → invite deflected → work shipped.\n// Then the Act-1 grid returns, transformed (same geometry — the rhyme).\nimport { CALM_FOCUS, CALM_MEET } from './constants.js';\nimport { buildCal } from './act1.js';\nimport { seg, slam, lerp, easeOutCubic } from './engine.js';\n\nconst ROWS = [8, 9, 10, 11, 12, 13];\nconst rowY = (h) => ((h - 8) / 5) * 100; // % within .tl for 8:00–13:00\n\nexport const act3 = {\n  range: [900, 1440],\n  build(root, api) {\n    this.api = api;\n\n    // --- phase A: the app ---\n    this.app = document.createElement('div');\n    this.app.className = 'app';\n    this.app.innerHTML = `\n      <div class=\"app__bar\">\n        <span class=\"dot\"></span><span class=\"dot\"></span><span class=\"dot\"></span>\n        <span class=\"app__title\">Monday, June 15 · 8:54 AM</span>\n        <span class=\"app__brand\"><span class=\"logo-mark\"></span>Daybreak</span>\n      </div>\n      <div class=\"app__body\">\n        <div class=\"app__side\">\n          <div class=\"side__item side__item--on\">Today</div>\n          <div class=\"side__item\">Week</div>\n          <div class=\"side__item\">Focus rules</div>\n          <div class=\"side__item\">Rituals</div>\n        </div>\n        <div class=\"app__main\">\n          <div class=\"day-h\">Monday</div>\n          <div class=\"day-sub\">1 focus block held · 2 meetings</div>\n          <div class=\"tl\">\n            ${ROWS.map((h) => `\n              <div class=\"tl__row\" style=\"top:${rowY(h)}%\"></div>\n              <div class=\"tl__lbl\" style=\"top:${rowY(h)}%\">${h <= 12 ? h : h - 12}:00</div>`).join('')}\n            <div class=\"evt evt--meet\" style=\"top:${rowY(8.5)}%;height:${rowY(8.95) - rowY(8.5)}%\">Standup · 8:30</div>\n            <div class=\"evt evt--focus\" style=\"top:${rowY(9)}%;height:${rowY(11.5) - rowY(9)}%\">\n              Ship onboarding flow\n              <span class=\"evt__tag\">9:00 – 11:30 · RESERVED BY DAYBREAK</span>\n            </div>\n          </div>\n          <div class=\"toast\" id=\"toastIn\">\n            <div class=\"toast__t\">Invite — Marketing sync</div>\n            <div class=\"toast__s\">Today 9:30 AM · conflicts with your focus block</div>\n          </div>\n          <div class=\"toast toast--ok\" id=\"toastOk\">\n            <div class=\"toast__t\"><em>Daybreak proposed 1:30 PM</em></div>\n            <div class=\"toast__s\">Your focus block stays untouched</div>\n          </div>\n          <div class=\"card-pr\" id=\"cardPr\">PR #214 merged ✓<small>onboarding-flow → main · 11:26 AM</small></div>\n          <div class=\"hud\" id=\"hud\">\n            <span class=\"hud__lbl\">FOCUS</span>\n            <span class=\"hud__timer\" id=\"hudTimer\">1:42:06</span>\n            <span class=\"hud__meta\">14 notifications held · Slack snoozed</span>\n          </div>\n        </div>\n      </div>`;\n    root.appendChild(this.app);\n    this.toastIn = this.app.querySelector('#toastIn');\n    this.toastOk = this.app.querySelector('#toastOk');\n    this.cardPr = this.app.querySelector('#cardPr');\n    this.hud = this.app.querySelector('#hud');\n    this.hudTimer = this.app.querySelector('#hudTimer');\n\n    // --- phase B: the transformed week (Act-1 geometry) ---\n    this.glow = document.createElement('div');\n    this.glow.className = 'glow';\n    root.appendChild(this.glow);\n\n    const { cal, blocks } = buildCal([...CALM_FOCUS, ...CALM_MEET]);\n    this.week = cal;\n    this.weekBlocks = blocks;\n    this.week.style.opacity = 0;\n    root.appendChild(cal);\n\n    this.h2 = document.createElement('div');\n    this.h2.className = 'headline a3-h2';\n    this.h2.textContent = 'Five mornings. Yours again.';\n    root.appendChild(this.h2);\n  },\n  update(f) {\n    const phaseB = f >= 1320;\n\n    // --- phase A ---\n    this.app.style.display = phaseB ? 'none' : 'block';\n    if (!phaseB) {\n      const i = seg(f, 900, 918, easeOutCubic);\n      this.app.style.opacity = i;\n      this.app.style.transform = `translateY(${lerp(34, 0, i)}px)`;\n\n      // Toast 1 in at 1020, swaps for the counter-proposal at 1080\n      const t1in = seg(f, 1020, 1034, easeOutCubic);\n      const t1out = seg(f, 1080, 1092);\n      this.toastIn.style.opacity = t1in * (1 - t1out);\n      this.toastIn.style.transform = `translateX(${lerp(46, 0, t1in)}px)`;\n      const t2in = seg(f, 1086, 1100, easeOutCubic);\n      const t2out = seg(f, 1270, 1290);\n      this.toastOk.style.opacity = t2in * (1 - t2out);\n      this.toastOk.style.transform = `translateX(${lerp(46, 0, t2in)}px)`;\n\n      // Focus HUD at 1140, timer ticks with the frame clock\n      this.hud.style.opacity = seg(f, 1140, 1156);\n      if (f >= 1140) {\n        const s = 6 + Math.floor((f - 1140) / 30);\n        this.hudTimer.textContent = `1:42:${String(s % 60).padStart(2, '0')}`;\n      }\n\n      // PR merged at 1200\n      const pr = slam(f, 1200, 8);\n      this.cardPr.style.opacity = f >= 1200 ? 1 : 0;\n      this.cardPr.style.transform = `scale(${lerp(1.18, 1, pr)})`;\n      if (f >= 1200 && f < 1207) this.api.flash((1 - (f - 1200) / 7) * 0.1);\n    }\n\n    // --- phase B: CRASH at 1320 — the grid returns, calm ---\n    this.glow.style.opacity = phaseB ? 0.35 + seg(f, 1320, 1380) * 0.4 : 0;\n    this.week.style.opacity = phaseB ? 1 : 0;\n    this.h2.style.opacity = 0;\n    if (phaseB) {\n      this.api.flash((1 - seg(f, 1320, 1338)) * 0.5);\n      for (let i = 0; i < this.weekBlocks.length; i++) {\n        const { m, el } = this.weekBlocks[i];\n        const f0 = m.focus ? 1324 + m.d * 6 : 1360 + (i % 5) * 4;\n        const t = seg(f, f0, f0 + 18, easeOutCubic);\n        el.style.opacity = t;\n        el.style.transform = `scale(${lerp(0.92, 1, t)})`;\n      }\n      const h = seg(f, 1380, 1398, easeOutCubic);\n      this.h2.style.opacity = h;\n      this.h2.style.transform = `translateX(-50%) translateY(${lerp(20, 0, h)}px)`;\n    }\n  },\n};\n"},{"name":"audio.js","content":"// SCORE — Web Audio synth, 120 BPM (all anchors land on beats).\n// Hard anchors: DROP @ 16.000s, CRASH @ 44.000s (the only one), FINAL HIT @ 54.000s.\n// Event-list scheduler → works for live playback from any offset AND offline QA render.\n\nconst BEAT = 0.5; // 120 BPM\n\n// ---------- voices ----------\nfunction noiseBuf(ctx) {\n  if (ctx.__nb) return ctx.__nb;\n  const b = ctx.createBuffer(1, ctx.sampleRate * 2, ctx.sampleRate);\n  const d = b.getChannelData(0);\n  for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;\n  ctx.__nb = b;\n  return b;\n}\nfunction noiseSrc(ctx, when, dur) {\n  const s = ctx.createBufferSource();\n  s.buffer = noiseBuf(ctx);\n  s.loop = true;\n  s.start(when);\n  s.stop(when + dur + 0.1);\n  return s;\n}\nfunction env(ctx, when, a, peak, dur) {\n  const g = ctx.createGain();\n  g.gain.setValueAtTime(0.0001, when);\n  g.gain.exponentialRampToValueAtTime(Math.max(peak, 0.0001), when + Math.max(a, 0.003));\n  g.gain.exponentialRampToValueAtTime(0.0001, when + dur);\n  return g;\n}\nconst V = {\n  kick(ctx, out, when, e) {\n    const o = ctx.createOscillator();\n    o.frequency.setValueAtTime(155, when);\n    o.frequency.exponentialRampToValueAtTime(46, when + 0.12);\n    const g = env(ctx, when, 0.003, e.g ?? 0.8, 0.28);\n    let node = o;\n    if (e.muffled) {\n      const f = ctx.createBiquadFilter();\n      f.type = 'lowpass'; f.frequency.value = 220;\n      o.connect(f); node = f;\n    }\n    node.connect(g).connect(out);\n    o.start(when); o.stop(when + 0.32);\n  },\n  hat(ctx, out, when, e) {\n    const s = noiseSrc(ctx, when, 0.07);\n    const f = ctx.createBiquadFilter();\n    f.type = 'highpass'; f.frequency.value = 6500;\n    const g = env(ctx, when, 0.002, e.g ?? 0.1, 0.06);\n    s.connect(f).connect(g).connect(out);\n  },\n  bass(ctx, out, when, e) {\n    const o = ctx.createOscillator();\n    o.type = 'sawtooth'; o.frequency.value = e.fq;\n    const f = ctx.createBiquadFilter();\n    f.type = 'lowpass'; f.frequency.value = e.cut ?? 520; f.Q.value = 4;\n    const g = env(ctx, when, 0.006, e.g ?? 0.3, e.dur ?? 0.22);\n    o.connect(f).connect(g).connect(out);\n    o.start(when); o.stop(when + (e.dur ?? 0.22) + 0.05);\n  },\n  pluck(ctx, out, when, e) {\n    const o = ctx.createOscillator();\n    o.type = 'triangle'; o.frequency.value = e.fq;\n    const g = env(ctx, when, 0.003, e.g ?? 0.08, e.dur ?? 0.24);\n    o.connect(g).connect(out);\n    o.start(when); o.stop(when + (e.dur ?? 0.24) + 0.05);\n  },\n  pad(ctx, out, when, e) {\n    const dur = e.dur ?? 3.4, g0 = e.g ?? 0.085;\n    for (const fq of e.notes) {\n      for (const det of [-5, 5]) {\n        const o = ctx.createOscillator();\n        o.type = 'sawtooth'; o.frequency.value = fq; o.detune.value = det;\n        const f = ctx.createBiquadFilter();\n        f.type = 'lowpass'; f.frequency.value = e.cut ?? 1150;\n        const g = ctx.createGain();\n        g.gain.setValueAtTime(0.0001, when);\n        g.gain.linearRampToValueAtTime(g0 / e.notes.length, when + Math.min(1, dur * 0.3));\n        g.gain.setValueAtTime(g0 / e.notes.length, when + dur * 0.7);\n        g.gain.linearRampToValueAtTime(0.0001, when + dur);\n        o.connect(f).connect(g).connect(out);\n        o.start(when); o.stop(when + dur + 0.1);\n      }\n    }\n  },\n  drone(ctx, out, when, e) {\n    const dur = e.dur;\n    for (const fq of [55, 55.6]) {\n      const o = ctx.createOscillator();\n      o.type = 'sawtooth'; o.frequency.value = fq;\n      const f = ctx.createBiquadFilter();\n      f.type = 'lowpass';\n      f.frequency.setValueAtTime(130, when);\n      f.frequency.linearRampToValueAtTime(950, when + dur);\n      const g = ctx.createGain();\n      g.gain.setValueAtTime(0.0001, when);\n      g.gain.linearRampToValueAtTime((e.g ?? 0.11) / 2, when + 2);\n      g.gain.setValueAtTime((e.g ?? 0.11) / 2, when + dur - 0.12);\n      g.gain.linearRampToValueAtTime(0.0001, when + dur);\n      o.connect(f).connect(g).connect(out);\n      o.start(when); o.stop(when + dur + 0.05);\n    }\n  },\n  riser(ctx, out, when, e) {\n    const dur = e.dur ?? 1.8;\n    const s = noiseSrc(ctx, when, dur);\n    const f = ctx.createBiquadFilter();\n    f.type = 'bandpass'; f.Q.value = 1.1;\n    f.frequency.setValueAtTime(300, when);\n    f.frequency.exponentialRampToValueAtTime(3800, when + dur);\n    const g = ctx.createGain();\n    g.gain.setValueAtTime(0.0001, when);\n    g.gain.exponentialRampToValueAtTime(e.g ?? 0.2, when + dur);\n    g.gain.setValueAtTime(0.0001, when + dur + 0.001); // hard cut into the vacuum\n    s.connect(f).connect(g).connect(out);\n  },\n  crash(ctx, out, when, e) {\n    const s = noiseSrc(ctx, when, 1.9);\n    const f = ctx.createBiquadFilter();\n    f.type = 'highpass'; f.frequency.value = 3200;\n    const g = env(ctx, when, 0.004, e.g ?? 0.6, 1.8);\n    s.connect(f).connect(g).connect(out);\n  },\n  boom(ctx, out, when, e) {\n    const o = ctx.createOscillator();\n    o.frequency.setValueAtTime(72, when);\n    o.frequency.exponentialRampToValueAtTime(30, when + 0.5);\n    const g = env(ctx, when, 0.004, e.g ?? 0.95, 1.3);\n    o.connect(g).connect(out);\n    o.start(when); o.stop(when + 1.4);\n  },\n  stab(ctx, out, when, e) {\n    V.pad(ctx, out, when, { notes: e.notes, dur: e.dur ?? 2.4, g: e.g ?? 0.3, cut: 1600 });\n  },\n};\n\n// ---------- the score ----------\nconst A1 = 55, C2 = 65.41, D2 = 73.42, E2 = 82.41, F1s = 46.25;\nconst CH = {\n  Amaj9: [220, 277.18, 329.63, 493.88],\n  Dmaj9: [146.83, 185.0, 220, 329.63],\n  Fsm11: [185.0, 220, 277.18, 329.63],\n  Eadd9: [164.81, 207.65, 246.94, 369.99],\n  Awide: [110, 220, 277.18, 329.63, 440],\n};\n\nexport function genEvents() {\n  const ev = [];\n  const add = (t, type, p = {}) => ev.push({ t, type, ...p });\n\n  // ACT 1 (0–15.6): ticking dread\n  add(0, 'drone', { dur: 15.6, g: 0.11 });\n  for (let t = 0.5; t < 14; t += 0.5) add(t, 'hat', { g: 0.05 + (t / 14) * 0.13 });\n  for (let t = 8.25; t < 14; t += 0.5) add(t, 'hat', { g: 0.07 });\n  for (let t = 2; t < 14; t += 1) add(t, 'kick', { g: 0.32, muffled: true });\n  add(13.8, 'riser', { dur: 1.85, g: 0.22 }); // ends 15.65 → vacuum 15.65–16.0\n\n  // DROP (16–30): full pattern\n  add(16, 'boom', { g: 0.9 });\n  for (let t = 16; t < 30; t += 0.5) add(t, 'kick', { g: 0.8 });\n  for (let t = 16.25; t < 30; t += 0.5) add(t, 'hat', { g: 0.14 });\n  const roots = [A1, A1, C2, D2, A1, A1, C2];\n  for (let bar = 0; bar < 7; bar++) {\n    const r = roots[bar], t0 = 16 + bar * 2;\n    for (const [dt, oct] of [[0, 1], [0.5, 1], [0.75, 2], [1.0, 1], [1.5, 2], [1.75, 1]]) {\n      add(t0 + dt, 'bass', { fq: r * oct, g: 0.3, dur: 0.22 });\n    }\n  }\n  const arp = [220, 329.63, 440, 329.63, 523.25, 440, 329.63, 261.63];\n  for (let i = 0; i < 56; i++) add(16 + i * 0.25, 'pluck', { fq: arp[i % 8], g: 0.07 });\n\n  // ACT 3 LIFT (30–44): half-time, warm pads\n  for (let t = 30; t < 43.5; t += 1) add(t, 'kick', { g: 0.5 });\n  for (let t = 30.5; t < 43.5; t += 0.5) add(t, 'hat', { g: 0.07 });\n  const prog = [[30, CH.Amaj9, 110], [33.5, CH.Dmaj9, 73.42], [37, CH.Fsm11, 92.5], [40.5, CH.Eadd9, 82.41]];\n  for (const [t, notes, root] of prog) {\n    add(t, 'pad', { notes, dur: 3.5, g: 0.12 });\n    add(t, 'bass', { fq: root, g: 0.16, dur: 3.3, cut: 300 });\n  }\n  add(42.6, 'riser', { dur: 1.35, g: 0.2 });\n\n  // CRASH @ 44 — the only one. Then strip back.\n  add(44, 'crash', { g: 0.7 });\n  add(44, 'boom', { g: 1.0 });\n  add(44.1, 'pad', { notes: CH.Amaj9, dur: 5, g: 0.11 });\n  add(49.2, 'pad', { notes: CH.Dmaj9, dur: 4.4, g: 0.09 });\n  for (const [t, fq] of [[45.5, 660], [47.25, 554.37], [49, 440], [51, 659.26]]) {\n    add(t, 'pluck', { fq, g: 0.05, dur: 0.5 });\n  }\n\n  // FINAL HIT @ 54 — stamp, then air.\n  add(54, 'boom', { g: 0.9 });\n  add(54, 'crash', { g: 0.3 });\n  add(54, 'stab', { notes: CH.Awide, dur: 2.6, g: 0.3 });\n  add(56.2, 'pad', { notes: [220, 277.18, 329.63], dur: 3.0, g: 0.05 });\n\n  return ev.sort((a, b) => a.t - b.t);\n}\n\nexport function makeChain(ctx) {\n  const g = ctx.createGain();\n  g.gain.value = 0.9;\n  const c = ctx.createDynamicsCompressor();\n  g.connect(c);\n  c.connect(ctx.destination);\n  return g;\n}\n\nexport function scheduleScore(ctx, dest, events, startAt, fromT) {\n  for (const e of events) {\n    if (e.t < fromT - 0.02) continue;\n    V[e.type](ctx, dest, startAt + (e.t - fromT), e);\n  }\n}\n\n// ---------- engineer's QA: offline render + RMS probe at every anchor ----------\nexport async function qaScore() {\n  const sr = 22050;\n  const oc = new OfflineAudioContext(1, sr * 60, sr);\n  scheduleScore(oc, makeChain(oc), genEvents(), 0, 0);\n  const buf = await oc.startRendering();\n  const d = buf.getChannelData(0);\n  const rms = (t0, t1) => {\n    let s = 0, n = 0;\n    for (let i = Math.floor(t0 * sr); i < Math.min(d.length, Math.floor(t1 * sr)); i++) { s += d[i] * d[i]; n++; }\n    return n ? Math.sqrt(s / n) : 1e-7;\n  };\n  const db = (v) => +(20 * Math.log10(Math.max(v, 1e-7))).toFixed(1);\n  const pre = db(rms(15.7, 15.98)), post = db(rms(16.0, 16.35));\n  const preC = db(rms(43.55, 43.95)), postC = db(rms(44.0, 44.35));\n  const preH = db(rms(53.5, 53.95)), postH = db(rms(54.0, 54.35));\n  return {\n    drop: { pre, post, step: +(post - pre).toFixed(1), pass: post - pre >= 20 },\n    crash: { pre: preC, post: postC, step: +(postC - preC).toFixed(1) },\n    hit: { pre: preH, post: postH, step: +(postH - preH).toFixed(1) },\n    tail: db(rms(59.2, 60)),\n    peak: db(d.reduce((m, v) => Math.max(m, Math.abs(v)), 0)),\n  };\n}\n"},{"name":"constants.js","content":"// Shared constants — geometry survives cuts (the act-1/act-3 rhyme lives here).\nexport const FPS = 30;\nexport const DURATION_S = 60;\nexport const TOTAL_FRAMES = FPS * DURATION_S; // 1800\n\n// Calendar geometry shared by Act 1 (chaos) and Act 3 (calm) — identical, so the layout rhymes.\nexport const GRID = { x: 280, y: 270, w: 1360, h: 610, days: 5, hourStart: 8, hourEnd: 18 };\nexport const DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI'];\n\nexport const dayW = GRID.w / GRID.days;\nexport const hourH = GRID.h / (GRID.hourEnd - GRID.hourStart);\nexport const dayX = (d) => d * dayW;                    // relative to .cal\nexport const hourY = (h) => (h - GRID.hourStart) * hourH;\nexport const fmtH = (h) => {\n  const hh = Math.floor(h), mm = Math.round((h - hh) * 60);\n  const ap = hh >= 12 ? 'PM' : 'AM';\n  const h12 = ((hh + 11) % 12) + 1;\n  return `${h12}${mm ? ':' + String(mm).padStart(2, '0') : ''} ${ap}`;\n};\n\n// Act 1 — the shredded week. f = slam frame.\nexport const MEETINGS = [\n  { d: 0, h: 9,    len: 0.5, label: 'Standup',      f: 75 },\n  { d: 0, h: 9.5,  len: 0.5, label: 'Sync',          f: 95 },\n  { d: 0, h: 11,   len: 1,   label: '1:1',           f: 115 },\n  { d: 1, h: 9,    len: 0.5, label: 'Standup',       f: 135 },\n  { d: 1, h: 10,   len: 1.5, label: 'Planning',      f: 155 },\n  { d: 1, h: 14,   len: 1,   label: 'Review',        f: 175 },\n  { d: 2, h: 9,    len: 0.5, label: 'Standup',       f: 195 },\n  { d: 2, h: 9.5,  len: 1,   label: 'Alignment',     f: 215 },\n  { d: 2, h: 13,   len: 1,   label: 'All-hands',     f: 235 },\n  { d: 2, h: 15,   len: 0.5, label: 'Quick chat',    f: 255 },\n  { d: 3, h: 9,    len: 0.5, label: 'Standup',       f: 272 },\n  { d: 3, h: 10.5, len: 1,   label: 'Status',        f: 288 },\n  { d: 3, h: 14,   len: 1,   label: 'Sync (again)',  f: 304, conflict: true },\n  { d: 3, h: 14.5, len: 1,   label: 'Pre-meeting',   f: 318, conflict: true },\n  { d: 4, h: 9,    len: 0.5, label: 'Standup',       f: 330 },\n  { d: 4, h: 11,   len: 1,   label: 'Retro',         f: 342 },\n  { d: 4, h: 15,   len: 1.5, label: 'Check-in',      f: 352 },\n];\n\n// Act 3 — the same week, owned. Five dawn mornings + a compact afternoon.\nexport const CALM_FOCUS = DAYS.map((_, d) => ({\n  d, h: 9, len: 2.5, label: 'Deep work', time: '9:00 – 11:30', focus: true,\n}));\nexport const CALM_MEET = [\n  { d: 0, h: 13.5, len: 0.5, label: 'Standup' },\n  { d: 1, h: 14,   len: 1,   label: 'Team sync' },\n  { d: 2, h: 13.5, len: 0.5, label: 'Standup' },\n  { d: 3, h: 14,   len: 1,   label: 'Reviews' },\n  { d: 4, h: 13.5, len: 0.5, label: 'Standup' },\n];\n\n// Cue sheet (seconds). hard = music anchor.\nexport const CUES = [\n  { t: 0.0,  label: 'Tick pulse begins' },\n  { t: 0.27, label: 'Grid draws in' },\n  { t: 2.5,  label: 'Meetings slam in' },\n  { t: 11.5, label: 'H1: works for everyone but you' },\n  { t: 14.5, label: 'Compress + riser + vacuum' },\n  { t: 16.0, label: 'DROP', hard: true },\n  { t: 18.0, label: 'Wordmark' },\n  { t: 20.3, label: 'Monolith lands' },\n  { t: 30.0, label: 'Act 3: Monday 8:54' },\n  { t: 34.0, label: 'Invite toast' },\n  { t: 36.0, label: 'Counter-proposed 1:30' },\n  { t: 40.0, label: 'PR merged' },\n  { t: 44.0, label: 'CRASH', hard: true },\n  { t: 46.0, label: 'Five mornings. Yours again.' },\n  { t: 54.0, label: 'HIT', hard: true },\n  { t: 56.0, label: 'Own the morning.' },\n  { t: 57.2, label: 'CTA' },\n];\n"},{"name":"engine.js","content":"// Frame-accurate timeline engine, 30fps over 1800 frames.\nexport const FPS = 30;\n\nexport const clamp = (v, a, b) => (v < a ? a : v > b ? b : v);\nexport const lerp = (a, b, t) => a + (b - a) * t;\nexport const easeOutExpo = (t) => (t >= 1 ? 1 : 1 - Math.pow(2, -10 * t));\nexport const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);\nexport const easeInCubic = (t) => t * t * t;\nexport const easeInOutCubic = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);\n\n// Normalised progress of frame f through [f0, f1].\nexport const seg = (f, f0, f1, ease) => {\n  const t = clamp((f - f0) / (f1 - f0), 0, 1);\n  return ease ? ease(t) : t;\n};\n\n// Hard impact: 0 before f0, expo-settles over `dur` frames.\nexport const slam = (f, f0, dur = 8) => (f < f0 ? 0 : seg(f, f0, f0 + dur, easeOutExpo));\n\n// Decaying shake amplitude for an impact at f0.\nexport const shakeAmp = (f, f0, dur = 9, amp = 7) =>\n  f >= f0 && f < f0 + dur ? amp * (1 - (f - f0) / dur) : 0;\n\nexport class Timeline {\n  constructor(totalFrames, onFrame, onState) {\n    this.total = totalFrames;\n    this.onFrame = onFrame;\n    this.onState = onState || (() => {});\n    this.playing = false;\n    this.time = 0; // seconds\n    this._raf = null;\n  }\n  get frame() {\n    return Math.min(this.total - 1, Math.floor(this.time * FPS));\n  }\n  get duration() {\n    return this.total / FPS;\n  }\n  play() {\n    if (this.playing) return;\n    if (this.time >= this.duration - 1e-6) this.time = 0;\n    this.playing = true;\n    this._t0 = performance.now() - this.time * 1000;\n    this._tick();\n    this.onState('play');\n  }\n  pause() {\n    if (!this.playing) return;\n    this.playing = false;\n    cancelAnimationFrame(this._raf);\n    this.onState('pause');\n  }\n  seek(sec) {\n    this.time = clamp(sec, 0, this.duration);\n    if (this.playing) this._t0 = performance.now() - this.time * 1000;\n    this.onFrame(this.frame);\n  }\n  _tick() {\n    if (!this.playing) return;\n    this.time = (performance.now() - this._t0) / 1000;\n    if (this.time >= this.duration) {\n      this.time = this.duration;\n      this.onFrame(this.total - 1);\n      this.playing = false;\n      this.onState('end');\n      return;\n    }\n    this.onFrame(this.frame);\n    this._raf = requestAnimationFrame(() => this._tick());\n  }\n}\n"},{"name":"main.js","content":"// Wire-up: scenes, timeline, audio sync, controls, QA hooks.\nimport { TOTAL_FRAMES, CUES } from './constants.js';\nimport { FPS, Timeline } from './engine.js';\nimport { act1 } from './act1.js';\nimport { act2 } from './act2.js';\nimport { act3 } from './act3.js';\nimport { outro } from './outro.js';\nimport { genEvents, makeChain, scheduleScore, qaScore } from './audio.js';\n\nconst stage = document.getElementById('stage');\nconst shakeEl = document.getElementById('shake');\nconst flashEl = document.getElementById('flash');\nconst wrap = document.getElementById('stageWrap');\nconst playBtn = document.getElementById('playBtn');\nconst restartBtn = document.getElementById('restartBtn');\nconst scrub = document.getElementById('scrub');\nconst readout = document.getElementById('readout');\nconst cueMarkers = document.getElementById('cueMarkers');\n\nconst api = {\n  setShake: (x, y) => { shakeEl.style.transform = `translate(${x}px, ${y}px)`; },\n  flash: (a) => { flashEl.style.opacity = a; },\n};\n\nconst scenes = [act1, act2, act3, outro];\nconst roots = { act1, act2, act3, outro };\nfor (const id of Object.keys(roots)) roots[id].build(document.getElementById(id), api);\n\n// ---------- render ----------\nfunction render(f) {\n  api.setShake(0, 0);\n  api.flash(0);\n  for (const s of scenes) {\n    const on = f >= s.range[0] && f < s.range[1] || (s === outro && f >= s.range[0]);\n    const el = document.getElementById(Object.keys(roots).find((k) => roots[k] === s));\n    el.classList.toggle('on', on);\n    el.setAttribute('aria-hidden', String(!on));\n    if (on) s.update(f);\n  }\n  scrub.value = f;\n  const t = f / FPS;\n  const mm = String(Math.floor(t / 60)).padStart(2, '0');\n  const ss = String(Math.floor(t % 60)).padStart(2, '0');\n  const d = Math.floor((t % 1) * 10);\n  readout.textContent = `${mm}:${ss}.${d} · f${f}`;\n}\n\n// ---------- audio ----------\nconst events = genEvents();\nlet actx = null;\nfunction startAudio(fromSec) {\n  stopAudio();\n  actx = new (window.AudioContext || window.webkitAudioContext)();\n  scheduleScore(actx, makeChain(actx), events, actx.currentTime + 0.06, fromSec);\n}\nfunction stopAudio() {\n  if (actx) { actx.close(); actx = null; }\n}\n\n// ---------- timeline ----------\nconst tl = new Timeline(TOTAL_FRAMES, render, (state) => {\n  if (state === 'play') playBtn.textContent = 'Pause';\n  if (state === 'pause') playBtn.textContent = 'Play';\n  if (state === 'end') playBtn.textContent = 'Replay';\n});\n\nplayBtn.addEventListener('click', () => {\n  if (tl.playing) { tl.pause(); stopAudio(); }\n  else { tl.play(); startAudio(tl.time); }\n});\nrestartBtn.addEventListener('click', () => {\n  tl.seek(0);\n  if (tl.playing) startAudio(0);\n  else { stopAudio(); render(0); }\n});\n\nlet wasPlaying = false;\nscrub.addEventListener('input', () => {\n  if (tl.playing) { wasPlaying = true; tl.pause(); stopAudio(); }\n  tl.seek(Number(scrub.value) / FPS);\n});\nscrub.addEventListener('change', () => {\n  if (wasPlaying) { wasPlaying = false; tl.play(); startAudio(tl.time); }\n});\ndocument.addEventListener('keydown', (e) => {\n  if (e.code === 'Space' && e.target.tagName !== 'INPUT') {\n    e.preventDefault();\n    playBtn.click();\n  }\n});\n\n// ---------- cue markers ----------\nfor (const c of CUES.filter((c) => c.hard)) {\n  const m = document.createElement('div');\n  m.className = 'cue-mark';\n  m.style.left = `${(c.t / 60) * 100}%`;\n  m.innerHTML = `<span>${c.label}</span>`;\n  cueMarkers.appendChild(m);\n}\n\n// ---------- fit stage ----------\nfunction fit() {\n  const r = wrap.getBoundingClientRect();\n  stage.style.setProperty('--fit', Math.min(r.width / 1920, r.height / 1080));\n}\nwindow.addEventListener('resize', fit);\nfit();\nrender(0);\n\n// ---------- QA hooks ----------\nwindow.__seek = (f) => { tl.pause(); stopAudio(); tl.seek(f / FPS); };\nwindow.__qaScore = qaScore;\n"},{"name":"outro.js","content":"// CLOSE — quiet line, logo stamp on the final hit, tagline, CTA.\nimport { seg, slam, shakeAmp, lerp } from './engine.js';\n\nexport const outro = {\n  range: [1440, 1800],\n  build(root, api) {\n    this.api = api;\n\n    this.glow = document.createElement('div');\n    this.glow.className = 'glow';\n    root.appendChild(this.glow);\n\n    this.quiet = document.createElement('div');\n    this.quiet.className = 'o-quiet';\n    this.quiet.textContent = 'The morning is the most valuable real estate in your week.';\n    root.appendChild(this.quiet);\n\n    this.logo = document.createElement('div');\n    this.logo.className = 'o-logo';\n    this.logo.innerHTML = '<span class=\"logo-mark\"></span><span class=\"o-name\">Daybreak</span>';\n    root.appendChild(this.logo);\n\n    this.tag = document.createElement('div');\n    this.tag.className = 'o-tag';\n    this.tag.textContent = 'Own the morning.';\n    root.appendChild(this.tag);\n\n    this.cta = document.createElement('div');\n    this.cta.className = 'o-cta';\n    this.cta.textContent = 'daybreak.app — start free';\n    root.appendChild(this.cta);\n  },\n  update(f) {\n    this.glow.style.opacity = 0.2 + seg(f, 1620, 1660) * 0.35;\n\n    // Quiet line 1470–1600\n    const q = seg(f, 1470, 1494) - seg(f, 1576, 1600);\n    this.quiet.style.opacity = q;\n\n    // FINAL HIT @ 1620 — the stamp\n    const st = slam(f, 1620, 7);\n    this.logo.style.opacity = f >= 1620 ? 1 : 0;\n    this.logo.style.transform = `translateX(-50%) scale(${lerp(1.45, 1, st)})`;\n    if (f >= 1620 && f < 1632) this.api.flash((1 - (f - 1620) / 12) * 0.5);\n    const a = shakeAmp(f, 1620, 10, 8);\n    this.api.setShake(a * Math.sin(f * 2.3), a * Math.cos(f * 3.1));\n\n    // Tagline 1680, CTA 1716\n    const tg = seg(f, 1680, 1700);\n    this.tag.style.opacity = tg;\n    this.tag.style.transform = `translateX(-50%) translateY(${lerp(16, 0, tg)}px)`;\n    const ct = seg(f, 1716, 1736);\n    this.cta.style.opacity = ct;\n    this.cta.style.transform = `translateX(-50%) translateY(${lerp(14, 0, ct)}px)`;\n  },\n};\n"}],"folders":[]},{"folder":"other","files":[{"name":"main.sketch","content":""}],"folders":[]}]},"variants":null,"createdAt":"2026-06-11T12:10:53.061Z","updatedAt":"2026-06-11T12:10:57.785Z"}}