c5-infographic-en.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>c5-infographic · Data → Typography (EN)</title>
  6. <link rel="preconnect" href="https://fonts.googleapis.com">
  7. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  8. <link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,300..700;1,8..60,300..700&family=Noto+Serif+SC:wght@300;400;500;600&family=Inter:wght@100;200;300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
  9. <style>
  10. :root {
  11. --bg: #000000;
  12. --ink: #FFFFFF;
  13. --ink-80: rgba(255,255,255,0.82);
  14. --ink-60: rgba(255,255,255,0.58);
  15. --muted: rgba(255,255,255,0.40);
  16. --dim: rgba(255,255,255,0.18);
  17. --hairline: rgba(255,255,255,0.12);
  18. --accent: #D97757;
  19. --accent-deep: #B85D3D;
  20. /* Brand Reveal */
  21. --cd-bg: #F5F4F0;
  22. --cd-panel: #FFFFFF;
  23. --cd-ink: #1A1918;
  24. --cd-dim: #8B867E;
  25. --serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
  26. --serif-cn: "Noto Serif SC", "Songti SC", "Source Han Serif SC", serif;
  27. --sans: "Inter", -apple-system, "PingFang SC", system-ui, sans-serif;
  28. --mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
  29. }
  30. html, body {
  31. margin: 0; padding: 0;
  32. background: #000;
  33. overflow: hidden;
  34. font-family: var(--sans);
  35. color: var(--ink);
  36. -webkit-font-smoothing: antialiased;
  37. -moz-osx-font-smoothing: grayscale;
  38. font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  39. }
  40. * { box-sizing: border-box; }
  41. .stage {
  42. position: fixed;
  43. top: 50%; left: 50%;
  44. width: 1920px; height: 1080px;
  45. transform-origin: center center;
  46. background: var(--bg);
  47. overflow: hidden;
  48. /* Subtle film grain via SVG — 2% opacity */
  49. background-image:
  50. radial-gradient(ellipse at 20% 30%, rgba(217,119,87,0.025), transparent 50%),
  51. radial-gradient(ellipse at 80% 70%, rgba(217,119,87,0.018), transparent 55%);
  52. }
  53. .watermark {
  54. position: absolute;
  55. top: 40px; left: 48px;
  56. font-family: var(--mono);
  57. font-size: 12px;
  58. letter-spacing: 0.2em;
  59. color: var(--ink);
  60. opacity: 0.16;
  61. text-transform: uppercase;
  62. z-index: 400;
  63. transition: color 0.3s ease;
  64. }
  65. .watermark.on-light { color: var(--cd-ink); opacity: 0.35; }
  66. .v2-mark {
  67. position: absolute;
  68. bottom: 40px; right: 48px;
  69. font-family: var(--mono);
  70. font-size: 11px;
  71. letter-spacing: 0.2em;
  72. color: var(--ink);
  73. opacity: 0.16;
  74. z-index: 400;
  75. }
  76. /* ============ Split layout ============ */
  77. .split-left {
  78. position: absolute;
  79. left: 120px; top: 50%;
  80. transform: translateY(-50%);
  81. width: 440px;
  82. will-change: opacity, transform;
  83. }
  84. .json-block {
  85. font-family: var(--mono);
  86. font-size: 15px;
  87. line-height: 1.75;
  88. color: var(--ink-60);
  89. letter-spacing: 0.01em;
  90. white-space: pre;
  91. }
  92. .json-block .k { color: var(--ink-80); }
  93. .json-block .s { color: var(--accent); }
  94. .json-block .n { color: var(--ink); font-weight: 500; }
  95. .json-block .p { color: var(--muted); }
  96. .json-label {
  97. font-family: var(--mono);
  98. font-size: 10px;
  99. letter-spacing: 0.28em;
  100. color: var(--muted);
  101. text-transform: uppercase;
  102. margin-bottom: 22px;
  103. }
  104. /* Pipe arrow from JSON → infographic */
  105. .pipe {
  106. position: absolute;
  107. left: 580px; top: 50%;
  108. transform: translateY(-50%);
  109. width: 90px; height: 2px;
  110. background: linear-gradient(to right, var(--hairline), var(--accent), var(--hairline));
  111. opacity: 0;
  112. will-change: opacity;
  113. }
  114. .pipe::after {
  115. content: '';
  116. position: absolute;
  117. right: -4px; top: 50%;
  118. transform: translateY(-50%) rotate(45deg);
  119. width: 8px; height: 8px;
  120. border-right: 2px solid var(--accent);
  121. border-top: 2px solid var(--accent);
  122. }
  123. /* ============ Infographic (right side) ============ */
  124. .infographic {
  125. position: absolute;
  126. right: 100px; top: 72px;
  127. width: 1120px; height: 936px;
  128. background: #0A0A0A;
  129. border: 1px solid var(--hairline);
  130. padding: 56px 64px;
  131. opacity: 0;
  132. transform: translateY(18px);
  133. will-change: opacity, transform;
  134. overflow: hidden;
  135. }
  136. .ig-masthead {
  137. display: flex;
  138. justify-content: space-between;
  139. align-items: baseline;
  140. border-bottom: 1px solid var(--hairline);
  141. padding-bottom: 20px;
  142. margin-bottom: 36px;
  143. opacity: 0;
  144. will-change: opacity;
  145. }
  146. .ig-masthead .issue {
  147. font-family: var(--mono);
  148. font-size: 10px;
  149. letter-spacing: 0.3em;
  150. color: var(--muted);
  151. text-transform: uppercase;
  152. }
  153. .ig-masthead .issue .orange { color: var(--accent); }
  154. .ig-masthead .dept {
  155. font-family: var(--mono);
  156. font-weight: 400;
  157. font-size: 10px;
  158. letter-spacing: 0.3em;
  159. color: var(--ink-60);
  160. text-transform: uppercase;
  161. }
  162. .ig-display {
  163. font-family: var(--serif-en);
  164. font-weight: 300;
  165. font-size: 96px;
  166. line-height: 1.0;
  167. letter-spacing: -0.025em;
  168. color: var(--ink);
  169. margin-bottom: 6px;
  170. opacity: 0;
  171. will-change: opacity, transform;
  172. text-wrap: pretty;
  173. font-feature-settings: "liga" 1, "dlig" 1, "kern" 1;
  174. }
  175. .ig-display .en {
  176. font-family: var(--serif-en);
  177. font-style: italic;
  178. font-weight: 300;
  179. color: var(--accent);
  180. font-feature-settings: "liga" 1, "dlig" 1, "swsh" 1;
  181. }
  182. .ig-deck {
  183. font-family: var(--serif-en);
  184. font-style: italic;
  185. font-weight: 300;
  186. font-size: 22px;
  187. color: var(--ink-60);
  188. letter-spacing: 0.01em;
  189. margin-bottom: 44px;
  190. opacity: 0;
  191. will-change: opacity;
  192. font-feature-settings: "liga" 1, "dlig" 1;
  193. }
  194. /* Grid of 5 stats */
  195. .ig-grid {
  196. display: grid;
  197. grid-template-columns: 1.3fr 1fr 1fr 1fr;
  198. gap: 32px;
  199. margin-bottom: 44px;
  200. }
  201. .ig-cell {
  202. opacity: 0;
  203. will-change: opacity, transform;
  204. border-top: 2px solid var(--ink);
  205. padding-top: 14px;
  206. }
  207. .ig-cell.accent { border-top-color: var(--accent); }
  208. .ig-cell .label {
  209. font-family: var(--mono);
  210. font-size: 10px;
  211. font-weight: 400;
  212. color: var(--muted);
  213. letter-spacing: 0.26em;
  214. margin-bottom: 14px;
  215. text-transform: uppercase;
  216. }
  217. .ig-cell .label .en {
  218. font-family: var(--mono);
  219. text-transform: uppercase;
  220. letter-spacing: 0.26em;
  221. }
  222. .ig-cell .big {
  223. font-family: var(--serif-en);
  224. font-weight: 300;
  225. font-size: 72px;
  226. line-height: 0.92;
  227. color: var(--ink);
  228. letter-spacing: -0.03em;
  229. font-variant-numeric: oldstyle-nums proportional-nums;
  230. font-feature-settings: "onum" 1, "pnum" 1, "kern" 1;
  231. }
  232. .ig-cell.accent .big { color: var(--accent); }
  233. .ig-cell .big .unit {
  234. font-size: 28px;
  235. color: var(--ink-60);
  236. letter-spacing: 0;
  237. }
  238. .ig-cell .sub {
  239. margin-top: 12px;
  240. font-family: var(--serif-en);
  241. font-style: italic;
  242. font-size: 14px;
  243. color: var(--ink-60);
  244. line-height: 1.4;
  245. font-feature-settings: "liga" 1, "dlig" 1;
  246. letter-spacing: 0.005em;
  247. }
  248. /* Comparison bars */
  249. .ig-bars {
  250. display: grid;
  251. grid-template-columns: 140px 1fr 80px;
  252. gap: 18px 24px;
  253. row-gap: 18px;
  254. border-top: 1px solid var(--hairline);
  255. padding-top: 28px;
  256. align-items: center;
  257. opacity: 0;
  258. will-change: opacity;
  259. }
  260. .ig-bars .row-label {
  261. font-family: var(--serif-en);
  262. font-size: 16px;
  263. font-weight: 400;
  264. color: var(--ink-80);
  265. letter-spacing: 0.005em;
  266. }
  267. .ig-bars .row-label.highlight { color: var(--accent); font-weight: 500; }
  268. .ig-bars .row-bar {
  269. height: 6px;
  270. background: var(--hairline);
  271. position: relative;
  272. overflow: hidden;
  273. }
  274. .ig-bars .row-bar .fill {
  275. position: absolute;
  276. left: 0; top: 0; bottom: 0;
  277. background: var(--ink-80);
  278. width: 0%;
  279. will-change: width;
  280. }
  281. .ig-bars .row-bar .fill.accent { background: var(--accent); }
  282. .ig-bars .row-val {
  283. font-family: var(--serif-en);
  284. font-size: 16px;
  285. color: var(--ink);
  286. text-align: right;
  287. font-variant-numeric: oldstyle-nums tabular-nums;
  288. font-feature-settings: "onum" 1, "tnum" 1;
  289. letter-spacing: 0.01em;
  290. }
  291. .ig-footer {
  292. position: absolute;
  293. bottom: 40px; left: 64px; right: 64px;
  294. display: flex; justify-content: space-between; align-items: baseline;
  295. border-top: 1px solid var(--hairline);
  296. padding-top: 16px;
  297. font-family: var(--mono);
  298. font-size: 10px;
  299. letter-spacing: 0.24em;
  300. color: var(--muted);
  301. text-transform: uppercase;
  302. opacity: 0;
  303. will-change: opacity;
  304. }
  305. .ig-footer .folio { color: var(--ink-60); letter-spacing: 0.32em; }
  306. /* ============ Typography detail zoom ============ */
  307. .detail-zoom {
  308. position: absolute;
  309. inset: 0;
  310. display: flex;
  311. align-items: center;
  312. justify-content: center;
  313. opacity: 0;
  314. will-change: opacity;
  315. background: radial-gradient(ellipse at center, #0A0A0A, #000000);
  316. z-index: 250;
  317. }
  318. .detail-word {
  319. font-family: var(--serif-en);
  320. font-weight: 300;
  321. font-style: italic;
  322. font-size: 320px;
  323. line-height: 0.9;
  324. letter-spacing: -0.01em;
  325. color: var(--ink);
  326. /* Enable OpenType ligatures, discretionary ligatures, swashes */
  327. font-feature-settings: "liga" 1, "dlig" 1, "swsh" 1, "salt" 1, "calt" 1;
  328. text-rendering: optimizeLegibility;
  329. will-change: transform, opacity;
  330. }
  331. .detail-word .fi {
  332. /* fi ligature is default with "liga" */
  333. color: var(--accent);
  334. }
  335. .detail-annotation {
  336. position: absolute;
  337. top: calc(50% + 170px); left: 50%;
  338. transform: translateX(-50%);
  339. font-family: var(--mono);
  340. font-size: 12px;
  341. letter-spacing: 0.28em;
  342. color: var(--muted);
  343. text-transform: uppercase;
  344. opacity: 0;
  345. will-change: opacity;
  346. white-space: nowrap;
  347. }
  348. .detail-annotation .dot {
  349. color: var(--accent);
  350. padding: 0 8px;
  351. }
  352. /* Callout lines pointing to ligature */
  353. .callout {
  354. position: absolute;
  355. left: 50%; top: 50%;
  356. transform: translate(-50%, -50%);
  357. pointer-events: none;
  358. opacity: 0;
  359. will-change: opacity;
  360. }
  361. .callout svg { overflow: visible; display: block; }
  362. /* ============ Brand Reveal ============ */
  363. .brand-wall {
  364. position: absolute;
  365. inset: 0;
  366. background: var(--cd-bg);
  367. z-index: 300;
  368. opacity: 0;
  369. transform: translateY(100%);
  370. will-change: transform, opacity;
  371. display: flex;
  372. flex-direction: column;
  373. align-items: center;
  374. justify-content: center;
  375. }
  376. .brand-wordmark {
  377. font-family: var(--serif-en);
  378. font-size: 132px;
  379. font-weight: 200;
  380. color: var(--cd-ink);
  381. letter-spacing: -0.04em;
  382. line-height: 1;
  383. opacity: 0;
  384. transform: scale(0.92);
  385. will-change: opacity, transform;
  386. font-feature-settings: "liga" 1, "dlig" 1;
  387. }
  388. .brand-wordmark .dot { color: var(--accent); padding: 0 10px; font-weight: 300; }
  389. .brand-underline {
  390. margin-top: 28px;
  391. height: 2px;
  392. width: 0;
  393. background: var(--accent);
  394. will-change: width;
  395. }
  396. .brand-cn {
  397. margin-top: 30px;
  398. font-family: var(--serif-cn);
  399. font-size: 18px;
  400. font-weight: 300;
  401. color: var(--cd-dim);
  402. letter-spacing: 0.4em;
  403. opacity: 0;
  404. will-change: opacity;
  405. }
  406. </style>
  407. </head>
  408. <body>
  409. <div class="stage" id="stage">
  410. <div class="watermark" id="watermark">HUASHU · DESIGN</div>
  411. <div class="v2-mark">V2 · 2026</div>
  412. <!-- Left: JSON data -->
  413. <div class="split-left" id="splitLeft" style="opacity:0">
  414. <div class="json-label" id="jsonLabel">DATA &#8594; benchmarks.json</div>
  415. <pre class="json-block" id="jsonBlock"></pre>
  416. </div>
  417. <!-- Pipe arrow -->
  418. <div class="pipe" id="pipe"></div>
  419. <!-- Right: Infographic -->
  420. <div class="infographic" id="infographic">
  421. <div class="ig-masthead" id="igMasthead">
  422. <div class="issue">Issue &#8470; 05 <span class="orange">&#183; AI Benchmarks</span> &#183; Q2 2026</div>
  423. <div class="dept">FRONTIER REPORT</div>
  424. </div>
  425. <h1 class="ig-display" id="igDisplay">
  426. The Age of<br>
  427. <span class="en">benchmarks</span>.
  428. </h1>
  429. <p class="ig-deck" id="igDeck">
  430. Five frontier models, five numbers, one uncomfortable truth.
  431. </p>
  432. <div class="ig-grid" id="igGrid">
  433. <div class="ig-cell accent" data-cell="0">
  434. <div class="label">Leader <span class="en">&#183; Q2</span></div>
  435. <div class="big">Claude 4.7</div>
  436. <div class="sub">Sonnet, 1M ctx &#183; Anthropic</div>
  437. </div>
  438. <div class="ig-cell" data-cell="1">
  439. <div class="label"><span class="en">SWE-bench</span></div>
  440. <div class="big">77<span class="unit">.2%</span></div>
  441. <div class="sub">coding, verified split</div>
  442. </div>
  443. <div class="ig-cell" data-cell="2">
  444. <div class="label"><span class="en">GPQA</span></div>
  445. <div class="big">84<span class="unit">.5</span></div>
  446. <div class="sub">diamond, graduate science</div>
  447. </div>
  448. <div class="ig-cell" data-cell="3">
  449. <div class="label">Price <span class="en">&#183; input</span></div>
  450. <div class="big">$3<span class="unit">/M</span></div>
  451. <div class="sub">per million tokens, typical</div>
  452. </div>
  453. </div>
  454. <div class="ig-bars" id="igBars">
  455. <div class="row-label highlight">Claude 4.7 Sonnet</div>
  456. <div class="row-bar"><div class="fill accent" data-w="77.2"></div></div>
  457. <div class="row-val">77.2</div>
  458. <div class="row-label">GPT-5 Turbo</div>
  459. <div class="row-bar"><div class="fill" data-w="74.8"></div></div>
  460. <div class="row-val">74.8</div>
  461. <div class="row-label">Gemini 3 Pro</div>
  462. <div class="row-bar"><div class="fill" data-w="71.3"></div></div>
  463. <div class="row-val">71.3</div>
  464. <div class="row-label">GLM-5</div>
  465. <div class="row-bar"><div class="fill" data-w="68.9"></div></div>
  466. <div class="row-val">68.9</div>
  467. <div class="row-label">Kimi k3</div>
  468. <div class="row-bar"><div class="fill" data-w="66.4"></div></div>
  469. <div class="row-val">66.4</div>
  470. </div>
  471. <div class="ig-footer" id="igFooter">
  472. <span>Set in Source Serif 4 &amp; JetBrains Mono</span>
  473. <span class="folio">P. 05</span>
  474. <span>Data &#183; 2026 Q2, public benchmarks</span>
  475. </div>
  476. </div>
  477. <!-- Detail zoom: Typography ligature -->
  478. <div class="detail-zoom" id="detailZoom">
  479. <div class="detail-word" id="detailWord">bench<span class="fi">ma</span>rks</div>
  480. <div class="callout" id="callout" style="display:none"></div>
  481. <div class="detail-annotation" id="detailAnnotation">
  482. SOURCE SERIF 4 <span class="dot">·</span> ITALIC <span class="dot">·</span> OLDSTYLE FIGURES
  483. </div>
  484. </div>
  485. <!-- Brand Reveal -->
  486. <div class="brand-wall" id="brandWall">
  487. <div class="brand-wordmark" id="brandWord">huashu<span class="dot">·</span>design</div>
  488. <div class="brand-underline" id="brandLine"></div>
  489. <div class="brand-cn" id="brandCn">D A T A &#183; T Y P O G R A P H Y</div>
  490. </div>
  491. </div>
  492. <script>
  493. (() => {
  494. 'use strict';
  495. // ---------- Scale stage to viewport ----------
  496. const stage = document.getElementById('stage');
  497. function fitStage() {
  498. const s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
  499. stage.style.transform = `translate(-50%, -50%) scale(${s})`;
  500. }
  501. fitStage();
  502. window.addEventListener('resize', fitStage);
  503. // ---------- Easing ----------
  504. const expoOut = t => t >= 1 ? 1 : 1 - Math.pow(2, -10 * t);
  505. const expoIn = t => t <= 0 ? 0 : Math.pow(2, 10 * (t - 1));
  506. const cubicOut = t => 1 - Math.pow(1 - t, 3);
  507. const cubicInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
  508. const lerp = (t, a, b, c, d, ease=x=>x) => {
  509. if (b === a) return c;
  510. const k = Math.max(0, Math.min(1, (t - a) / (b - a)));
  511. return c + (d - c) * ease(k);
  512. };
  513. const seg = (t, a, b) => Math.max(0, Math.min(1, (t - a) / (b - a)));
  514. // ---------- Refs ----------
  515. const splitLeft = document.getElementById('splitLeft');
  516. const jsonLabel = document.getElementById('jsonLabel');
  517. const jsonBlock = document.getElementById('jsonBlock');
  518. const pipe = document.getElementById('pipe');
  519. const infographic = document.getElementById('infographic');
  520. const igMasthead = document.getElementById('igMasthead');
  521. const igDisplay = document.getElementById('igDisplay');
  522. const igDeck = document.getElementById('igDeck');
  523. const igGrid = document.getElementById('igGrid');
  524. const igCells = igGrid.querySelectorAll('.ig-cell');
  525. const igBars = document.getElementById('igBars');
  526. const igBarFills = igBars.querySelectorAll('.fill');
  527. const igFooter = document.getElementById('igFooter');
  528. const detailZoom = document.getElementById('detailZoom');
  529. const detailWord = document.getElementById('detailWord');
  530. const detailAnnotation = document.getElementById('detailAnnotation');
  531. const callout = document.getElementById('callout');
  532. const brandWall = document.getElementById('brandWall');
  533. const brandWord = document.getElementById('brandWord');
  534. const brandLine = document.getElementById('brandLine');
  535. const brandCn = document.getElementById('brandCn');
  536. const watermark = document.getElementById('watermark');
  537. // ---------- JSON content (for progressive reveal) ----------
  538. const jsonRaw = [
  539. '{',
  540. ' "issue": "2026-Q2",',
  541. ' "leader": "Claude 4.7",',
  542. ' "models": [',
  543. ' { "name": "Claude 4.7", "swe": 77.2 },',
  544. ' { "name": "GPT-5 Turbo", "swe": 74.8 },',
  545. ' { "name": "Gemini 3 Pro", "swe": 71.3 },',
  546. ' { "name": "GLM-5", "swe": 68.9 },',
  547. ' { "name": "Kimi k3", "swe": 66.4 }',
  548. ' ],',
  549. ' "gpqa_top": 84.5,',
  550. ' "price_per_M": 3',
  551. '}'
  552. ];
  553. function formatJson(lines) {
  554. return lines.map(line => {
  555. return line
  556. .replace(/"([a-zA-Z_]+)":/g, '<span class="k">"$1"</span>:')
  557. .replace(/: "([^"]+)"/g, ': <span class="s">"$1"</span>')
  558. .replace(/: ([0-9.]+)/g, ': <span class="n">$1</span>')
  559. .replace(/([{}\[\],])/g, '<span class="p">$1</span>');
  560. }).join('\n');
  561. }
  562. // ---------- Timeline ----------
  563. const DURATION = 10.0;
  564. // SFX cue points (played back in ffmpeg post-processing, not browser):
  565. // t=0.35 → keyboard/type-fast.mp3 (data entering)
  566. // t=2.15 → container/card-snap.mp3 (infographic settles)
  567. // t=6.75 → transition/whoosh-fast.mp3 (zoom-in to typography)
  568. // t=8.70 → impact/logo-reveal.mp3 (brand reveal chime)
  569. const sfxFired = new Set();
  570. function fireOnce(key) {
  571. if (sfxFired.has(key)) return;
  572. sfxFired.add(key);
  573. // cue emitted for post-processing; no in-browser playback
  574. }
  575. let startTime = null;
  576. let raf;
  577. function tick(now) {
  578. if (startTime == null) startTime = now;
  579. const t = (now - startTime) / 1000;
  580. // ── Beat 1: 0-2s · JSON data appears, types in ─────────
  581. // JSON label fade in
  582. {
  583. const k = cubicOut(seg(t, 0.15, 0.55));
  584. jsonLabel.style.opacity = k;
  585. splitLeft.style.opacity = '1';
  586. }
  587. // Progressive type-reveal: reveal N lines of JSON by time
  588. {
  589. const totalLines = jsonRaw.length;
  590. const k = seg(t, 0.3, 1.9);
  591. const linesShown = Math.floor(k * totalLines);
  592. const shown = jsonRaw.slice(0, Math.max(0, linesShown));
  593. jsonBlock.innerHTML = formatJson(shown);
  594. if (linesShown >= 3 && t < 1.9) fireOnce('datain');
  595. }
  596. // ── Pipe arrow (1.8 → 2.2) ─────────────────────────────
  597. {
  598. const k = cubicOut(seg(t, 1.8, 2.2));
  599. pipe.style.opacity = k;
  600. }
  601. // ── Beat 2a: 2.0-3.2s · Infographic canvas arrives ─────
  602. {
  603. const k = expoOut(seg(t, 2.0, 2.8));
  604. infographic.style.opacity = k;
  605. infographic.style.transform = `translateY(${lerp(t, 2.0, 2.8, 18, 0, expoOut)}px)`;
  606. if (t > 2.1) fireOnce('settle');
  607. }
  608. // Masthead
  609. {
  610. const k = cubicOut(seg(t, 2.6, 3.1));
  611. igMasthead.style.opacity = k;
  612. }
  613. // ── Beat 2b: 3.0-4.2s · Display headline appears ──────
  614. {
  615. const k = expoOut(seg(t, 3.0, 3.8));
  616. igDisplay.style.opacity = k;
  617. igDisplay.style.transform = `translateY(${lerp(t, 3.0, 3.8, 16, 0, expoOut)}px)`;
  618. }
  619. // Deck line (italic)
  620. {
  621. const k = cubicOut(seg(t, 3.6, 4.2));
  622. igDeck.style.opacity = k;
  623. }
  624. // ── Beat 2c: 4.0-5.2s · Grid cells (ripple, 4 cells) ──
  625. igCells.forEach((cell, i) => {
  626. const start = 4.0 + i * 0.12;
  627. const end = start + 0.5;
  628. const k = expoOut(seg(t, start, end));
  629. cell.style.opacity = k;
  630. cell.style.transform = `translateY(${lerp(t, start, end, 14, 0, expoOut)}px)`;
  631. });
  632. // ── Beat 2d: 5.2-6.4s · Comparison bars grow ─────────
  633. {
  634. const k = cubicOut(seg(t, 5.1, 5.4));
  635. igBars.style.opacity = k;
  636. }
  637. igBarFills.forEach((fill, i) => {
  638. const start = 5.3 + i * 0.08;
  639. const end = start + 0.7;
  640. const w = parseFloat(fill.getAttribute('data-w'));
  641. const pct = lerp(t, start, end, 0, w, expoOut);
  642. fill.style.width = pct + '%';
  643. });
  644. // Footer
  645. {
  646. const k = cubicOut(seg(t, 6.0, 6.6));
  647. igFooter.style.opacity = k * 0.9;
  648. }
  649. // ── Beat 2e: 6.6-8.2s · Zoom to typography detail ────
  650. if (t >= 6.6 && t < 8.3) {
  651. const k = expoOut(seg(t, 6.6, 7.4));
  652. // Infographic scales up and fades — simulate push-in
  653. const scale = lerp(t, 6.6, 7.4, 1, 3.4, expoOut);
  654. const ty = lerp(t, 6.6, 7.4, 0, -140, expoOut);
  655. infographic.style.transform = `translateY(${ty}px) scale(${scale})`;
  656. infographic.style.opacity = String(1 - k * 0.85);
  657. splitLeft.style.opacity = String(1 - k);
  658. pipe.style.opacity = String(1 - k);
  659. // Detail zoom fades in
  660. const k2 = expoOut(seg(t, 7.0, 7.7));
  661. detailZoom.style.opacity = k2;
  662. // Word subtle scale-in (starts from 0.96)
  663. detailWord.style.transform = `scale(${lerp(t, 7.0, 7.9, 0.96, 1.0, expoOut)})`;
  664. // SFX at 6.7
  665. if (t > 6.7) fireOnce('zoom');
  666. // Callout + annotation (7.5 → 8.1)
  667. const k3 = cubicOut(seg(t, 7.6, 8.1));
  668. callout.style.opacity = k3;
  669. detailAnnotation.style.opacity = k3;
  670. }
  671. // ── Beat 3: 8.2-10s · Brand reveal ───────────────────
  672. // Detail zoom fades under brand wall
  673. if (t >= 8.1) {
  674. const k = cubicOut(seg(t, 8.1, 8.5));
  675. detailZoom.style.opacity = String(Math.max(0, 1 - k));
  676. }
  677. // Brand wall slides up from bottom
  678. {
  679. const k = expoOut(seg(t, 8.1, 8.7));
  680. brandWall.style.transform = `translateY(${lerp(t, 8.1, 8.7, 100, 0, expoOut)}%)`;
  681. brandWall.style.opacity = k > 0 ? '1' : '0';
  682. if (k > 0.55) watermark.classList.add('on-light');
  683. else watermark.classList.remove('on-light');
  684. }
  685. // Wordmark
  686. {
  687. const k = expoOut(seg(t, 8.6, 9.2));
  688. brandWord.style.opacity = k;
  689. brandWord.style.transform = `scale(${lerp(t, 8.6, 9.2, 0.92, 1.0, expoOut)})`;
  690. if (t > 8.65) fireOnce('chime');
  691. }
  692. // Underline
  693. {
  694. const k = expoOut(seg(t, 9.0, 9.6));
  695. brandLine.style.width = (280 * k) + 'px';
  696. }
  697. // CN tagline
  698. {
  699. const k = cubicOut(seg(t, 9.3, 9.9));
  700. brandCn.style.opacity = k * 0.9;
  701. }
  702. // Loop / hold
  703. if (t < DURATION) {
  704. raf = requestAnimationFrame(tick);
  705. } else {
  706. if (!window.__recording) {
  707. setTimeout(() => {
  708. // Reset
  709. startTime = null;
  710. sfxFired.clear();
  711. jsonBlock.innerHTML = '';
  712. splitLeft.style.opacity = '0';
  713. pipe.style.opacity = '0';
  714. infographic.style.opacity = '0';
  715. infographic.style.transform = 'translateY(18px) scale(1)';
  716. igMasthead.style.opacity = '0';
  717. igDisplay.style.opacity = '0';
  718. igDeck.style.opacity = '0';
  719. igBars.style.opacity = '0';
  720. igFooter.style.opacity = '0';
  721. igCells.forEach(c => { c.style.opacity = '0'; });
  722. igBarFills.forEach(f => { f.style.width = '0%'; });
  723. detailZoom.style.opacity = '0';
  724. callout.style.opacity = '0';
  725. detailAnnotation.style.opacity = '0';
  726. brandWall.style.transform = 'translateY(100%)';
  727. brandWall.style.opacity = '0';
  728. brandWord.style.opacity = '0';
  729. brandLine.style.width = '0';
  730. brandCn.style.opacity = '0';
  731. watermark.classList.remove('on-light');
  732. raf = requestAnimationFrame(tick);
  733. }, 800);
  734. }
  735. }
  736. }
  737. window.__seek = function(s) {
  738. startTime = performance.now() - s * 1000;
  739. };
  740. // Wait for fonts, then start
  741. (document.fonts ? document.fonts.ready : Promise.resolve()).then(() => {
  742. requestAnimationFrame((now) => {
  743. startTime = now;
  744. window.__ready = true;
  745. raf = requestAnimationFrame(tick);
  746. });
  747. });
  748. })();
  749. </script>
  750. </body>
  751. </html>