1
0

hero-animation-v10-en.html 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>Huashu Design · Here's to the Agents (v10)</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; /* terracotta — 致敬 Anthropic 血统 */
  19. --accent-deep: #B85D3D;
  20. /* Claude Design palette — Act 0 专用 */
  21. --cd-bg: #F5F4F0;
  22. --cd-panel: #FFFFFF;
  23. --cd-ink: #1A1918;
  24. --cd-dim: #8B867E;
  25. --cd-hair: rgba(0,0,0,0.08);
  26. --cd-hair-strong: rgba(0,0,0,0.16);
  27. --cd-green: #2D4A3A;
  28. --cd-green-deep: #1E3428;
  29. --cd-green-soft: #3F5E4D;
  30. --serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
  31. --sans: "Inter", -apple-system, "PingFang SC", "HarmonyOS Sans SC", system-ui, sans-serif;
  32. --mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
  33. }
  34. html, body {
  35. margin: 0; padding: 0;
  36. background: #000;
  37. overflow: hidden;
  38. font-family: var(--sans);
  39. color: var(--ink);
  40. -webkit-font-smoothing: antialiased;
  41. }
  42. * { box-sizing: border-box; }
  43. .stage {
  44. position: fixed;
  45. top: 50%; left: 50%;
  46. width: 1920px; height: 1080px;
  47. transform-origin: center center;
  48. background: var(--bg);
  49. overflow: hidden;
  50. }
  51. .scene {
  52. position: absolute; inset: 0;
  53. display: flex; align-items: center; justify-content: center;
  54. opacity: 0;
  55. visibility: hidden;
  56. will-change: opacity, transform;
  57. }
  58. .scene.visible { visibility: visible; }
  59. /* ============ Act 1 ============ */
  60. .act1 {
  61. flex-direction: column;
  62. gap: 40px;
  63. }
  64. .hero-line {
  65. font-family: var(--sans);
  66. font-size: 132px;
  67. font-weight: 200;
  68. letter-spacing: -0.045em;
  69. color: var(--ink);
  70. text-align: center;
  71. line-height: 1.02;
  72. will-change: transform, opacity, font-variation-settings;
  73. }
  74. .hero-line .accent { color: var(--accent); font-weight: inherit; }
  75. .not-line {
  76. font-family: var(--sans);
  77. font-size: 96px;
  78. font-weight: 200;
  79. letter-spacing: -0.035em;
  80. color: var(--ink);
  81. text-align: center;
  82. line-height: 1.08;
  83. }
  84. .not-line .strike {
  85. color: var(--muted);
  86. text-decoration: line-through;
  87. text-decoration-thickness: 3px;
  88. text-decoration-color: var(--accent);
  89. }
  90. /* ============ Abstract GUI icons (no real product screenshots) ============ */
  91. .gui-glyph {
  92. position: absolute;
  93. opacity: 0;
  94. will-change: opacity, transform, filter;
  95. }
  96. .gui-glyph.click {
  97. /* Mouse cursor arrow */
  98. width: 120px; height: 120px;
  99. display: flex; align-items: center; justify-content: center;
  100. }
  101. .gui-glyph.click::before {
  102. content: '';
  103. width: 40px; height: 40px;
  104. border: 2px solid var(--muted);
  105. border-radius: 50%;
  106. position: absolute;
  107. animation: clickring 0.8s ease-out forwards;
  108. animation-play-state: paused;
  109. }
  110. @keyframes clickring {
  111. 0% { transform: scale(0.5); opacity: 0.8; }
  112. 100% { transform: scale(2.2); opacity: 0; }
  113. }
  114. .gui-glyph.click svg { width: 56px; height: 56px; position: relative; z-index: 2; }
  115. .gui-glyph.drag {
  116. /* Slider */
  117. width: 400px; height: 48px;
  118. display: flex; align-items: center;
  119. gap: 0;
  120. }
  121. .gui-glyph.drag .track {
  122. flex: 1;
  123. height: 3px;
  124. background: var(--hairline);
  125. border-radius: 2px;
  126. position: relative;
  127. }
  128. .gui-glyph.drag .fill {
  129. position: absolute;
  130. height: 100%;
  131. background: var(--muted);
  132. width: 30%;
  133. border-radius: 2px;
  134. }
  135. .gui-glyph.drag .thumb {
  136. position: absolute;
  137. width: 24px; height: 24px;
  138. background: var(--ink);
  139. border: 1px solid var(--muted);
  140. border-radius: 50%;
  141. top: 50%;
  142. left: 30%;
  143. transform: translate(-50%, -50%);
  144. }
  145. .gui-glyph.folder {
  146. /* Window frame w/ file list */
  147. width: 420px; height: 260px;
  148. background: rgba(255,255,255,0.02);
  149. border: 1px solid var(--hairline);
  150. border-radius: 10px;
  151. overflow: hidden;
  152. }
  153. .gui-glyph.folder .head {
  154. padding: 12px 16px;
  155. border-bottom: 1px solid var(--hairline);
  156. display: flex; gap: 8px;
  157. }
  158. .gui-glyph.folder .head .dot {
  159. width: 9px; height: 9px; border-radius: 50%;
  160. background: var(--hairline);
  161. }
  162. .gui-glyph.folder .row {
  163. padding: 10px 16px;
  164. font-family: var(--mono);
  165. font-size: 13px;
  166. color: var(--muted);
  167. display: flex;
  168. justify-content: space-between;
  169. border-bottom: 1px solid var(--hairline);
  170. }
  171. .gui-glyph.folder .row:last-child { border-bottom: none; }
  172. .gui-glyph.folder .row .meta {
  173. color: var(--dim);
  174. }
  175. /* ============ Act 2 ============ */
  176. .act2 {
  177. flex-direction: column;
  178. }
  179. .terminal {
  180. width: 1180px;
  181. border-radius: 16px;
  182. background: rgba(20, 20, 20, 1);
  183. border: 1px solid var(--hairline);
  184. overflow: hidden;
  185. box-shadow:
  186. 0 0 0 1px rgba(255,255,255,0.02),
  187. 0 60px 120px -30px rgba(217,119,87,0.15);
  188. }
  189. .tty-head {
  190. display: flex; align-items: center; gap: 9px;
  191. padding: 18px 22px;
  192. background: rgba(255,255,255,0.02);
  193. border-bottom: 1px solid var(--hairline);
  194. }
  195. .tty-head .d {
  196. width: 13px; height: 13px; border-radius: 50%;
  197. background: var(--hairline);
  198. }
  199. .tty-head .d.red { background: #5a2a2a; }
  200. .tty-head .d.yellow { background: #5a4a2a; }
  201. .tty-head .d.green { background: #2a5a35; }
  202. .tty-title {
  203. margin-left: 16px;
  204. color: var(--muted);
  205. font-size: 14px;
  206. font-family: var(--mono);
  207. letter-spacing: 0.02em;
  208. }
  209. .tty-body {
  210. padding: 44px 36px;
  211. font-family: var(--mono);
  212. font-size: 26px;
  213. line-height: 1.6;
  214. color: rgba(255,255,255,0.86);
  215. min-height: 160px;
  216. }
  217. .prompt { color: var(--accent); margin-right: 12px; }
  218. .typed { white-space: pre; }
  219. .cursor {
  220. display: inline-block;
  221. width: 12px; height: 28px;
  222. background: var(--accent);
  223. vertical-align: -5px;
  224. margin-left: 3px;
  225. }
  226. /* Gallery (v6 structure, dark theme) */
  227. .gallery-viewport {
  228. position: absolute;
  229. inset: 0;
  230. overflow: hidden;
  231. perspective: 2400px;
  232. perspective-origin: 50% 45%;
  233. }
  234. .gallery-canvas {
  235. position: absolute;
  236. top: 50%; left: 50%;
  237. width: 4320px;
  238. height: 2520px;
  239. transform-origin: center center;
  240. transform-style: preserve-3d;
  241. will-change: transform;
  242. display: grid;
  243. grid-template-columns: repeat(8, 1fr);
  244. gap: 40px;
  245. padding: 60px;
  246. }
  247. .gallery-card {
  248. background: #1a1a1a;
  249. border-radius: 14px;
  250. padding: 6px;
  251. overflow: hidden;
  252. border: 1px solid var(--hairline);
  253. box-shadow:
  254. 0 20px 60px -20px rgba(0, 0, 0, 0.6),
  255. 0 6px 18px -6px rgba(0, 0, 0, 0.4);
  256. aspect-ratio: 16 / 9;
  257. will-change: opacity, filter;
  258. }
  259. .gallery-card.depth-near {
  260. box-shadow:
  261. 0 32px 80px -22px rgba(0, 0, 0, 0.8),
  262. 0 10px 24px -8px rgba(217, 119, 87, 0.12);
  263. }
  264. .gallery-card.depth-far {
  265. box-shadow:
  266. 0 14px 40px -16px rgba(0, 0, 0, 0.4),
  267. 0 4px 12px -4px rgba(0, 0, 0, 0.25);
  268. }
  269. .gallery-card img {
  270. width: 100%; height: 100%;
  271. object-fit: cover;
  272. display: block;
  273. border-radius: 9px;
  274. }
  275. /* Overlay statements (on top of gallery) */
  276. .over-statement {
  277. position: absolute;
  278. inset: 0;
  279. display: flex;
  280. align-items: center;
  281. justify-content: center;
  282. pointer-events: none;
  283. z-index: 50;
  284. opacity: 0;
  285. }
  286. .over-statement .text {
  287. font-family: var(--sans);
  288. font-size: 84px;
  289. font-weight: 200;
  290. letter-spacing: -0.035em;
  291. color: var(--ink);
  292. text-align: center;
  293. line-height: 1.08;
  294. text-shadow: 0 8px 40px rgba(0,0,0,0.8);
  295. padding: 0 40px;
  296. max-width: 1400px;
  297. }
  298. .over-statement .text .accent { color: var(--accent); }
  299. /* ============ Act 3 ============ */
  300. .act3 {
  301. flex-direction: column;
  302. gap: 0;
  303. }
  304. .statement-big {
  305. font-family: var(--sans);
  306. font-size: 160px;
  307. font-weight: 100;
  308. letter-spacing: -0.05em;
  309. color: var(--ink);
  310. text-align: center;
  311. line-height: 1;
  312. will-change: opacity, transform, font-variation-settings;
  313. }
  314. .statement-big .accent { color: var(--accent); font-weight: inherit; }
  315. .brand-wordmark {
  316. font-family: var(--sans);
  317. font-size: 140px;
  318. font-weight: 100;
  319. font-variation-settings: "wght" 100;
  320. letter-spacing: -0.045em;
  321. color: var(--ink);
  322. text-align: center;
  323. line-height: 1;
  324. will-change: font-variation-settings, opacity, transform;
  325. }
  326. .brand-wordmark .accent { color: var(--accent); font-weight: inherit; }
  327. .farewell-quote {
  328. margin-top: 44px;
  329. font-family: var(--serif-en);
  330. font-style: italic;
  331. font-weight: 300;
  332. font-size: 36px;
  333. color: var(--accent);
  334. letter-spacing: 0.005em;
  335. text-align: center;
  336. will-change: opacity, transform;
  337. }
  338. .farewell-cn {
  339. margin-top: 18px;
  340. font-family: var(--serif-en);
  341. font-weight: 300;
  342. font-size: 18px;
  343. color: var(--muted);
  344. letter-spacing: 0.24em;
  345. text-align: center;
  346. }
  347. .brand-url {
  348. margin-top: 48px;
  349. font-size: 14px;
  350. color: var(--muted);
  351. font-family: var(--mono);
  352. letter-spacing: 0.16em;
  353. text-align: center;
  354. }
  355. /* Watermark (subtle, always on during Act 2/3) */
  356. .watermark {
  357. position: absolute;
  358. bottom: 28px;
  359. right: 36px;
  360. font-family: var(--mono);
  361. font-size: 10px;
  362. letter-spacing: 0.24em;
  363. text-transform: uppercase;
  364. color: rgba(255,255,255,0.22);
  365. z-index: 100;
  366. opacity: 0;
  367. transition: opacity 0.6s;
  368. pointer-events: none;
  369. }
  370. .watermark.visible { opacity: 1; }
  371. /* ============ Act 0 — Claude Design 致敬(+讽刺) ============ */
  372. .act0 {
  373. background: #0a0a0a;
  374. }
  375. .cd-browser {
  376. position: absolute;
  377. top: 50%; left: 50%;
  378. width: 1640px;
  379. height: 920px;
  380. transform: translate(-50%, -50%);
  381. background: var(--cd-bg);
  382. border-radius: 14px;
  383. overflow: hidden;
  384. box-shadow:
  385. 0 0 0 1px rgba(255,255,255,0.04),
  386. 0 60px 160px -40px rgba(0,0,0,0.8),
  387. 0 24px 60px -20px rgba(0,0,0,0.6);
  388. will-change: transform, opacity, filter;
  389. }
  390. .cd-chrome {
  391. display: flex; align-items: center;
  392. height: 48px;
  393. padding: 0 18px;
  394. background: #EDEBE5;
  395. border-bottom: 1px solid var(--cd-hair);
  396. gap: 14px;
  397. }
  398. .cd-traffic { display: flex; gap: 8px; }
  399. .cd-traffic .d {
  400. width: 12px; height: 12px; border-radius: 50%;
  401. background: #D9D4CB;
  402. }
  403. .cd-traffic .d.r { background: #E8A5A0; }
  404. .cd-traffic .d.y { background: #E8D0A0; }
  405. .cd-traffic .d.g { background: #A5D0B0; }
  406. .cd-urlbar {
  407. flex: 1;
  408. max-width: 520px;
  409. margin: 0 auto;
  410. height: 28px;
  411. background: #F9F7F2;
  412. border: 1px solid var(--cd-hair);
  413. border-radius: 6px;
  414. display: flex; align-items: center; justify-content: center;
  415. font-family: var(--sans);
  416. font-size: 13px;
  417. color: var(--cd-dim);
  418. letter-spacing: 0;
  419. }
  420. .cd-urlbar .lock {
  421. width: 10px; height: 10px;
  422. margin-right: 8px;
  423. border: 1.5px solid var(--cd-dim);
  424. border-radius: 2px;
  425. position: relative;
  426. }
  427. .cd-urlbar .lock::before {
  428. content: '';
  429. position: absolute;
  430. top: -5px; left: 50%;
  431. transform: translateX(-50%);
  432. width: 6px; height: 6px;
  433. border: 1.5px solid var(--cd-dim);
  434. border-bottom: none;
  435. border-radius: 3px 3px 0 0;
  436. }
  437. .cd-tabs-row {
  438. display: flex;
  439. height: 42px;
  440. padding: 0 24px;
  441. background: var(--cd-bg);
  442. border-bottom: 1px solid var(--cd-hair);
  443. align-items: center;
  444. gap: 6px;
  445. }
  446. .cd-tab {
  447. height: 28px;
  448. padding: 0 14px;
  449. display: flex; align-items: center;
  450. font-family: var(--sans);
  451. font-size: 12px;
  452. color: var(--cd-dim);
  453. border-radius: 6px;
  454. gap: 8px;
  455. white-space: nowrap;
  456. }
  457. .cd-tab.active {
  458. background: #FFFFFF;
  459. color: var(--cd-ink);
  460. font-weight: 500;
  461. box-shadow: 0 1px 2px rgba(0,0,0,0.04);
  462. }
  463. .cd-tab .dot {
  464. width: 6px; height: 6px; border-radius: 50%;
  465. background: var(--cd-green);
  466. }
  467. .cd-topbar-right {
  468. margin-left: auto;
  469. display: flex; align-items: center; gap: 12px;
  470. font-family: var(--sans);
  471. font-size: 12px;
  472. color: var(--cd-dim);
  473. }
  474. .cd-topbar-right .btn {
  475. padding: 6px 12px;
  476. background: var(--cd-ink);
  477. color: #FFFFFF;
  478. border-radius: 6px;
  479. font-weight: 500;
  480. }
  481. .cd-topbar-right .btn.ghost {
  482. background: transparent;
  483. color: var(--cd-ink);
  484. border: 1px solid var(--cd-hair-strong);
  485. }
  486. .cd-body {
  487. display: grid;
  488. grid-template-columns: 440px 1fr;
  489. height: calc(920px - 48px - 42px);
  490. }
  491. /* Chat panel */
  492. .cd-chat {
  493. background: var(--cd-bg);
  494. border-right: 1px solid var(--cd-hair);
  495. padding: 28px 24px;
  496. display: flex;
  497. flex-direction: column;
  498. gap: 18px;
  499. overflow: hidden;
  500. }
  501. .cd-msg { display: flex; gap: 10px; align-items: flex-start; }
  502. .cd-avatar {
  503. width: 26px; height: 26px;
  504. border-radius: 50%;
  505. display: flex; align-items: center; justify-content: center;
  506. font-family: var(--sans);
  507. font-size: 11px;
  508. font-weight: 600;
  509. flex-shrink: 0;
  510. }
  511. .cd-avatar.user {
  512. background: #E8E4DC;
  513. color: var(--cd-ink);
  514. }
  515. .cd-avatar.claude {
  516. background: var(--cd-ink);
  517. color: #FFFFFF;
  518. }
  519. .cd-bubble {
  520. font-family: var(--sans);
  521. font-size: 13px;
  522. line-height: 1.55;
  523. color: var(--cd-ink);
  524. max-width: 100%;
  525. }
  526. .cd-bubble .dim { color: var(--cd-dim); }
  527. .cd-tweaks {
  528. margin-top: auto;
  529. padding: 16px 18px;
  530. background: #FFFFFF;
  531. border: 1px solid var(--cd-hair);
  532. border-radius: 10px;
  533. }
  534. .cd-tweaks-title {
  535. font-family: var(--sans);
  536. font-size: 11px;
  537. font-weight: 600;
  538. letter-spacing: 0.08em;
  539. text-transform: uppercase;
  540. color: var(--cd-dim);
  541. margin-bottom: 14px;
  542. }
  543. .cd-tweak-row {
  544. display: flex; align-items: center;
  545. gap: 12px;
  546. margin-bottom: 12px;
  547. }
  548. .cd-tweak-row:last-child { margin-bottom: 0; }
  549. .cd-tweak-label {
  550. font-family: var(--sans);
  551. font-size: 12px;
  552. color: var(--cd-ink);
  553. width: 72px;
  554. flex-shrink: 0;
  555. }
  556. .cd-tweak-track {
  557. flex: 1;
  558. height: 4px;
  559. background: #E8E4DC;
  560. border-radius: 2px;
  561. position: relative;
  562. }
  563. .cd-tweak-thumb {
  564. position: absolute;
  565. top: 50%;
  566. width: 16px; height: 16px;
  567. background: #FFFFFF;
  568. border: 1.5px solid var(--cd-ink);
  569. border-radius: 50%;
  570. transform: translate(-50%, -50%);
  571. will-change: left;
  572. }
  573. .cd-color-dots {
  574. display: flex; gap: 6px;
  575. }
  576. .cd-color-dot {
  577. width: 16px; height: 16px;
  578. border-radius: 50%;
  579. border: 1.5px solid transparent;
  580. cursor: default;
  581. }
  582. .cd-color-dot.active {
  583. border-color: var(--cd-ink);
  584. }
  585. .cd-input {
  586. margin-top: 14px;
  587. height: 40px;
  588. padding: 0 14px;
  589. background: #FFFFFF;
  590. border: 1px solid var(--cd-hair);
  591. border-radius: 8px;
  592. display: flex; align-items: center;
  593. font-family: var(--sans);
  594. font-size: 12px;
  595. color: var(--cd-dim);
  596. }
  597. /* Canvas panel */
  598. .cd-canvas {
  599. background: #FAF9F5;
  600. padding: 40px;
  601. overflow: hidden;
  602. display: flex; align-items: center; justify-content: center;
  603. position: relative;
  604. }
  605. .cd-poster {
  606. width: 780px;
  607. aspect-ratio: 4 / 3;
  608. background: var(--cd-green);
  609. border-radius: 8px;
  610. padding: 48px 56px;
  611. color: #F5F2E8;
  612. display: grid;
  613. grid-template-columns: 1.2fr 1fr;
  614. gap: 48px;
  615. box-shadow: 0 40px 80px -30px rgba(0,0,0,0.4);
  616. position: relative;
  617. overflow: hidden;
  618. }
  619. .cd-poster::before {
  620. content: '';
  621. position: absolute;
  622. top: -60px; right: -60px;
  623. width: 220px; height: 220px;
  624. background: radial-gradient(circle, rgba(245,242,232,0.10), transparent 70%);
  625. }
  626. .cd-poster-left { position: relative; z-index: 2; }
  627. .cd-poster-eyebrow {
  628. font-family: var(--sans);
  629. font-size: 11px;
  630. font-weight: 500;
  631. letter-spacing: 0.22em;
  632. text-transform: uppercase;
  633. opacity: 0.65;
  634. margin-bottom: 28px;
  635. }
  636. .cd-poster-title {
  637. font-family: var(--serif-en);
  638. font-size: 76px;
  639. font-weight: 500;
  640. line-height: 0.95;
  641. letter-spacing: -0.02em;
  642. margin-bottom: 20px;
  643. }
  644. .cd-poster-sub {
  645. font-family: var(--sans);
  646. font-size: 14px;
  647. opacity: 0.75;
  648. line-height: 1.5;
  649. margin-bottom: 40px;
  650. }
  651. .cd-poster-pines {
  652. display: flex; gap: 10px;
  653. opacity: 0.35;
  654. }
  655. .cd-pine {
  656. width: 0; height: 0;
  657. border-left: 10px solid transparent;
  658. border-right: 10px solid transparent;
  659. border-bottom: 20px solid #F5F2E8;
  660. position: relative;
  661. }
  662. .cd-pine::after {
  663. content: '';
  664. position: absolute;
  665. bottom: -24px; left: 50%;
  666. transform: translateX(-50%);
  667. width: 3px; height: 6px;
  668. background: #F5F2E8;
  669. }
  670. .cd-schedule {
  671. background: rgba(245,242,232,0.08);
  672. border: 1px solid rgba(245,242,232,0.15);
  673. border-radius: 6px;
  674. padding: 20px 22px;
  675. position: relative;
  676. z-index: 2;
  677. }
  678. .cd-schedule-title {
  679. font-family: var(--sans);
  680. font-size: 10px;
  681. font-weight: 600;
  682. letter-spacing: 0.18em;
  683. text-transform: uppercase;
  684. opacity: 0.6;
  685. margin-bottom: 14px;
  686. }
  687. .cd-schedule-row {
  688. display: flex; justify-content: space-between;
  689. font-family: var(--sans);
  690. font-size: 12px;
  691. padding: 8px 0;
  692. border-bottom: 1px solid rgba(245,242,232,0.10);
  693. }
  694. .cd-schedule-row:last-child { border-bottom: none; }
  695. .cd-schedule-row .time { opacity: 0.65; font-variant-numeric: tabular-nums; }
  696. /* Caption for Act 0 */
  697. .cd-caption {
  698. position: absolute;
  699. bottom: 100px;
  700. left: 50%;
  701. transform: translateX(-50%);
  702. font-family: var(--sans);
  703. font-size: 88px;
  704. font-weight: 200;
  705. letter-spacing: -0.035em;
  706. color: var(--ink);
  707. text-align: center;
  708. opacity: 0;
  709. z-index: 60;
  710. text-shadow: 0 10px 50px rgba(0,0,0,0.9);
  711. will-change: opacity, transform;
  712. }
  713. .cd-caption .period { color: var(--accent); }
  714. /* Act 0.5 — pivot */
  715. .act05 {
  716. flex-direction: column;
  717. }
  718. .pivot-line {
  719. font-family: var(--sans);
  720. font-size: 112px;
  721. font-weight: 200;
  722. letter-spacing: -0.04em;
  723. color: var(--ink);
  724. text-align: center;
  725. line-height: 1.05;
  726. will-change: opacity, transform, font-variation-settings;
  727. }
  728. .pivot-line .accent { color: var(--accent); font-weight: inherit; }
  729. .pivot-line .faint { color: var(--muted); }
  730. </style>
  731. </head>
  732. <body>
  733. <div class="stage" id="stage">
  734. <div class="watermark" id="watermark">Created by Huashu-Design</div>
  735. <!-- ========== Act 0: Claude Design 致敬 ========== -->
  736. <div class="scene act0" id="act0ClaudeDesign">
  737. <div class="cd-browser" id="cdBrowser">
  738. <!-- Chrome bar -->
  739. <div class="cd-chrome">
  740. <div class="cd-traffic">
  741. <span class="d r"></span><span class="d y"></span><span class="d g"></span>
  742. </div>
  743. <div class="cd-urlbar"><span class="lock"></span>claude.ai/design</div>
  744. <div style="width: 56px;"></div>
  745. </div>
  746. <!-- Tabs row -->
  747. <div class="cd-tabs-row">
  748. <div class="cd-tab active"><span class="dot"></span>Company offsite html</div>
  749. <div class="cd-tab">Dashboard exploration</div>
  750. <div class="cd-tab">Landing v2</div>
  751. <div class="cd-topbar-right">
  752. <span>100%</span>
  753. <span class="btn ghost">Export</span>
  754. <span class="btn">Share</span>
  755. </div>
  756. </div>
  757. <!-- Body: split chat + canvas -->
  758. <div class="cd-body">
  759. <div class="cd-chat">
  760. <div class="cd-msg">
  761. <div class="cd-avatar user">Y</div>
  762. <div class="cd-bubble">Make a welcome guide for our company retreat.</div>
  763. </div>
  764. <div class="cd-msg">
  765. <div class="cd-avatar claude">C</div>
  766. <div class="cd-bubble">I've designed a 1-page landscape welcome guide for your planning day. It includes a branded cover with pine trees, a two-column schedule, and activity cards.<br/><br/><span class="dim">Toggle the Tweaks to adjust accent color, headline size, and density.</span></div>
  767. </div>
  768. <div class="cd-tweaks">
  769. <div class="cd-tweaks-title">Tweaks</div>
  770. <div class="cd-tweak-row">
  771. <div class="cd-tweak-label">Accent</div>
  772. <div class="cd-color-dots">
  773. <div class="cd-color-dot" style="background:#2D4A3A;" id="cdDot1"></div>
  774. <div class="cd-color-dot active" style="background:#D97757;" id="cdDot2"></div>
  775. <div class="cd-color-dot" style="background:#3F5E8A;" id="cdDot3"></div>
  776. <div class="cd-color-dot" style="background:#8B6F4A;" id="cdDot4"></div>
  777. </div>
  778. </div>
  779. <div class="cd-tweak-row">
  780. <div class="cd-tweak-label">Headline</div>
  781. <div class="cd-tweak-track"><div class="cd-tweak-thumb" id="cdThumb1" style="left: 58%;"></div></div>
  782. </div>
  783. <div class="cd-tweak-row">
  784. <div class="cd-tweak-label">Density</div>
  785. <div class="cd-tweak-track"><div class="cd-tweak-thumb" id="cdThumb2" style="left: 40%;"></div></div>
  786. </div>
  787. </div>
  788. <div class="cd-input">Describe what you want next…</div>
  789. </div>
  790. <div class="cd-canvas">
  791. <div class="cd-poster" id="cdPoster">
  792. <div class="cd-poster-left">
  793. <div class="cd-poster-eyebrow">Anthropic Labs · Planning Day</div>
  794. <div class="cd-poster-title">HEMLARK<br/>RETREAT '26</div>
  795. <div class="cd-poster-sub">June 14 · Full Day<br/>Pine Valley Lodge</div>
  796. <div class="cd-poster-pines">
  797. <div class="cd-pine"></div>
  798. <div class="cd-pine"></div>
  799. <div class="cd-pine"></div>
  800. <div class="cd-pine"></div>
  801. </div>
  802. </div>
  803. <div class="cd-schedule">
  804. <div class="cd-schedule-title">Schedule</div>
  805. <div class="cd-schedule-row"><span>Breakfast</span><span class="time">9:00</span></div>
  806. <div class="cd-schedule-row"><span>Kickoff</span><span class="time">10:00</span></div>
  807. <div class="cd-schedule-row"><span>Workshops</span><span class="time">10:30</span></div>
  808. <div class="cd-schedule-row"><span>Lunch</span><span class="time">12:30</span></div>
  809. <div class="cd-schedule-row"><span>Hike</span><span class="time">14:00</span></div>
  810. <div class="cd-schedule-row"><span>Dinner</span><span class="time">18:00</span></div>
  811. </div>
  812. </div>
  813. </div>
  814. </div>
  815. </div>
  816. <div class="cd-caption" id="cdCaption">It's beautiful<span class="period">.</span></div>
  817. </div>
  818. <!-- ========== Act 0.5: Pivot ========== -->
  819. <div class="scene act05" id="act05Pivot">
  820. <div class="pivot-line" id="pivotLine">
  821. But it isn't the <span class="accent">future</span>.
  822. </div>
  823. </div>
  824. <!-- ========== Act 1 ========== -->
  825. <div class="scene act1" id="act1a">
  826. <div class="hero-line" id="heroLine">
  827. Here's to the <span class="accent">Agents</span>.
  828. </div>
  829. </div>
  830. <div class="scene act1" id="act1b">
  831. <!-- "Not the ones who click." + abstract mouse -->
  832. <div class="gui-glyph click" id="glyphClick" style="left: 50%; top: 62%; transform: translate(-50%, -50%);">
  833. <svg viewBox="0 0 24 24" fill="none">
  834. <path d="M4 2l6 18 3-8 8-3L4 2z" stroke="rgba(255,255,255,0.55)" stroke-width="1.4" fill="rgba(255,255,255,0.12)" stroke-linejoin="round"/>
  835. </svg>
  836. </div>
  837. <div class="not-line" id="notLine1" style="position: absolute; top: 28%; left: 50%; transform: translateX(-50%); white-space: nowrap;">
  838. Not the ones who <span class="strike">click</span>.
  839. </div>
  840. </div>
  841. <div class="scene act1" id="act1c">
  842. <!-- "Not the ones who drag." + slider -->
  843. <div class="gui-glyph drag" id="glyphDrag" style="left: 50%; top: 62%; transform: translate(-50%, -50%);">
  844. <div class="track">
  845. <div class="fill"></div>
  846. <div class="thumb" id="sliderThumb"></div>
  847. </div>
  848. </div>
  849. <div class="not-line" id="notLine2" style="position: absolute; top: 28%; left: 50%; transform: translateX(-50%); white-space: nowrap;">
  850. Not the ones who <span class="strike">drag</span>.
  851. </div>
  852. </div>
  853. <div class="scene act1" id="act1d">
  854. <!-- "Not the ones who wait..." + folder window -->
  855. <div class="gui-glyph folder" id="glyphFolder" style="left: 50%; top: 62%; transform: translate(-50%, -50%);">
  856. <div class="head">
  857. <span class="d"></span><span class="d"></span><span class="d"></span>
  858. </div>
  859. <div class="row"><span>design-v1.fig</span><span class="meta">42 KB</span></div>
  860. <div class="row"><span>design-v2-final.fig</span><span class="meta">58 KB</span></div>
  861. <div class="row"><span>design-v2-FINAL-final.fig</span><span class="meta">61 KB</span></div>
  862. <div class="row"><span>design-v3.fig</span><span class="meta">65 KB</span></div>
  863. </div>
  864. <div class="not-line" id="notLine3" style="position: absolute; top: 22%; left: 50%; transform: translateX(-50%); white-space: nowrap; font-size: 72px;">
  865. Not the ones who <span class="strike">wait for you to open the file</span>.
  866. </div>
  867. </div>
  868. <!-- ========== Act 2 ========== -->
  869. <div class="scene act2" id="act2Terminal">
  870. <div class="terminal" id="terminal">
  871. <div class="tty-head">
  872. <span class="d red"></span>
  873. <span class="d yellow"></span>
  874. <span class="d green"></span>
  875. <span class="tty-title">huashu — claude code</span>
  876. </div>
  877. <div class="tty-body">
  878. <span class="prompt">$</span><span class="typed" id="typed"></span><span class="cursor" id="cursor"></span>
  879. </div>
  880. </div>
  881. </div>
  882. <div class="scene" id="act2Gallery">
  883. <div class="gallery-viewport">
  884. <div class="gallery-canvas" id="galleryCanvas"></div>
  885. </div>
  886. </div>
  887. <div class="over-statement" id="overStmt1">
  888. <div class="text">The ones who design<br/>while you <span class="accent">sleep</span>.</div>
  889. </div>
  890. <div class="over-statement" id="overStmt2">
  891. <div class="text">The ones who ship<br/>while you're in a <span class="accent">meeting</span>.</div>
  892. </div>
  893. <!-- ========== Act 3 ========== -->
  894. <div class="scene act3" id="act3Medium">
  895. <div class="statement-big" id="stmtMedium">
  896. <span class="accent">Agent</span> is the<br/>new medium.
  897. </div>
  898. </div>
  899. <div class="scene act3" id="act3Brand">
  900. <div class="brand-wordmark" id="wordmark">huashu<span class="accent">-</span>design</div>
  901. <div class="farewell-quote" id="farewell">For them, we built this.</div>
  902. <div class="farewell-cn" id="farewellCn">· 为 他 们 · 我 们 造 了 这 个 ·</div>
  903. <div class="brand-url" id="url">huasheng.ai/huashu-design-hero</div>
  904. </div>
  905. </div>
  906. <script>
  907. (function() {
  908. // ---------- Fit stage ----------
  909. const stage = document.getElementById('stage');
  910. function rescale() {
  911. const s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
  912. stage.style.transform = `translate(-50%, -50%) scale(${s})`;
  913. }
  914. rescale();
  915. window.addEventListener('resize', rescale);
  916. const SLIDE_FILES = [
  917. 'preview-01-cover.png','preview-02-quote.png','preview-03-intro.png','preview-04-toc.png',
  918. 'preview-05-divider-1.png','preview-06-seldon.png','preview-07-human-psych-limit.png','preview-08-ai-vs-human.png',
  919. 'preview-09-divider-2.png','preview-10-personas.png','preview-11-four-puzzles.png','preview-12-phenomena-1-2.png',
  920. 'preview-13-phenomena-3-4.png','preview-14-five-voices.png','preview-15-divider-3.png','preview-16-persona-selection.png',
  921. 'preview-17-persona-space.png','preview-18-emergent-misalignment.png','preview-19-inoculation.png','preview-20-emotion.png',
  922. 'preview-21-dosage.png','preview-22-steering.png','preview-23-expression-vs-impact.png','preview-24-concept-injection.png',
  923. 'preview-25-consciousness-prob.png','preview-26-divider-4.png','preview-27-cot-faithfulness.png','preview-28-alignment-faking.png',
  924. 'preview-29-divider-5.png','preview-30-open-questions.png','preview-31-giving-back.png','preview-32-closing.png',
  925. ];
  926. const BASE = '../../../2026.04-AI心理学/演讲PPT-北大/';
  927. // ---------- Build gallery ----------
  928. const COLS = 8, ROWS = 6, COUNT = COLS * ROWS;
  929. const galleryCanvas = document.getElementById('galleryCanvas');
  930. const galleryCards = [];
  931. for (let i = 0; i < COUNT; i++) {
  932. const slideIdx = i % 32;
  933. const card = document.createElement('div');
  934. card.className = 'gallery-card';
  935. const zIdx = Math.sin(i * 1.7) * 22 + Math.cos(i * 0.73) * 14;
  936. if (zIdx > 12) card.classList.add('depth-near');
  937. else if (zIdx < -12) card.classList.add('depth-far');
  938. const img = document.createElement('img');
  939. img.src = BASE + SLIDE_FILES[slideIdx];
  940. img.onerror = () => { img.src = BASE + 'preview-01-cover.png'; };
  941. card.appendChild(img);
  942. galleryCanvas.appendChild(card);
  943. galleryCards.push(card);
  944. }
  945. for (let i = 0; i < 32; i++) {
  946. const im = new Image();
  947. im.src = BASE + SLIDE_FILES[i];
  948. }
  949. // ---------- Easings ----------
  950. const easeOut = t => 1 - Math.pow(1 - t, 3);
  951. const expoOut = t => (t <= 0) ? 0 : (t >= 1) ? 1 : 1 - Math.pow(2, -10 * t);
  952. const easeInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
  953. function lerp(time, start, end, fromV, toV, easing) {
  954. if (time <= start) return fromV;
  955. if (time >= end) return toV;
  956. let p = (time - start) / (end - start);
  957. if (easing) p = easing(p);
  958. return fromV + (toV - fromV) * p;
  959. }
  960. function clampLerp(time, start, end) {
  961. if (time <= start) return 0;
  962. if (time >= end) return 1;
  963. return (time - start) / (end - start);
  964. }
  965. // ---------- Timeline (30s) ----------
  966. const T = {
  967. DURATION: 30.0,
  968. // ===== Act 0: Claude Design 致敬 (0 - 4s) =====
  969. a0_in: [0.3, 1.2], // browser fade + scale in
  970. a0_hold: [1.2, 3.4], // tweaks 自动动
  971. a0_out: [3.4, 4.0], // browser 退场
  972. cd_tweak_anim: [1.4, 3.3], // tweaks thumb 自动拖动窗口
  973. cd_accent_switch: [2.1, 2.5], // accent color dot 切换到深绿
  974. cd_caption_in: [1.6, 2.2],
  975. cd_caption_hold:[2.2, 3.3],
  976. cd_caption_out: [3.3, 3.8],
  977. // ===== Act 0.5: Pivot (3.9 - 5.2s) =====
  978. a05_in: [3.9, 4.6],
  979. a05_hold: [4.6, 4.9],
  980. a05_out: [4.9, 5.3],
  981. // ===== Act 1 (shifted +5s) =====
  982. a1a_in: [5.3, 6.3], // "Here's to the Agents."
  983. a1a_hold:[6.3, 7.8],
  984. a1a_out: [7.8, 8.3],
  985. a1b_in: [8.2, 8.9], // "Not the ones who click."
  986. a1b_hold:[8.9, 10.3],
  987. a1b_out: [10.3, 10.8],
  988. a1c_in: [10.7, 11.3], // "Not the ones who drag."
  989. a1c_hold:[11.3, 12.5],
  990. a1c_out: [12.5, 13.0],
  991. a1d_in: [12.9, 13.5], // "Not the ones who wait..."
  992. a1d_hold:[13.5, 15.2],
  993. a1d_out: [15.2, 15.7],
  994. // ===== Act 2 (shifted +5s) =====
  995. a2tty_in: [15.6, 16.2], // terminal in
  996. a2type: [16.4, 18.6],
  997. a2tty_out:[18.9, 19.4],
  998. a2gal_in: [19.1, 19.9], // gallery ripple start
  999. ripple: [19.9, 21.6],
  1000. panStart: 20.2,
  1001. a2gal_out:[25.5, 26.2],
  1002. // Overlay statements on gallery
  1003. stmt1: [21.7, 23.4], // "design while you sleep"
  1004. stmt2: [23.7, 25.4], // "ship while you're in a meeting"
  1005. // ===== Act 3 (shifted +5s) =====
  1006. a3med_in: [26.1, 27.0], // "Agent is the new medium"
  1007. a3med_hold:[27.0, 28.0],
  1008. a3med_out:[28.0, 28.4],
  1009. a3brand_in: [28.3, 29.0],
  1010. brand_morph: [28.7, 29.4],
  1011. a3farewell_in: [29.0, 29.6],
  1012. a3cn_in: [29.3, 29.8],
  1013. a3url_in: [29.5, 30.0],
  1014. };
  1015. // ---------- Elements ----------
  1016. const scenes = {
  1017. a0: document.getElementById('act0ClaudeDesign'),
  1018. a05: document.getElementById('act05Pivot'),
  1019. a1a: document.getElementById('act1a'),
  1020. a1b: document.getElementById('act1b'),
  1021. a1c: document.getElementById('act1c'),
  1022. a1d: document.getElementById('act1d'),
  1023. a2tty: document.getElementById('act2Terminal'),
  1024. a2gal: document.getElementById('act2Gallery'),
  1025. a3med: document.getElementById('act3Medium'),
  1026. a3brand: document.getElementById('act3Brand'),
  1027. };
  1028. const cdBrowser = document.getElementById('cdBrowser');
  1029. const cdCaption = document.getElementById('cdCaption');
  1030. const cdThumb1 = document.getElementById('cdThumb1');
  1031. const cdThumb2 = document.getElementById('cdThumb2');
  1032. const cdDot1 = document.getElementById('cdDot1');
  1033. const cdDot2 = document.getElementById('cdDot2');
  1034. const cdPoster = document.getElementById('cdPoster');
  1035. const pivotLine = document.getElementById('pivotLine');
  1036. const overs = {
  1037. stmt1: document.getElementById('overStmt1'),
  1038. stmt2: document.getElementById('overStmt2'),
  1039. };
  1040. const heroLine = document.getElementById('heroLine');
  1041. const notLine1 = document.getElementById('notLine1');
  1042. const notLine2 = document.getElementById('notLine2');
  1043. const notLine3 = document.getElementById('notLine3');
  1044. const glyphClick = document.getElementById('glyphClick');
  1045. const glyphDrag = document.getElementById('glyphDrag');
  1046. const sliderThumb = document.getElementById('sliderThumb');
  1047. const glyphFolder = document.getElementById('glyphFolder');
  1048. const terminal = document.getElementById('terminal');
  1049. const typed = document.getElementById('typed');
  1050. const cursor = document.getElementById('cursor');
  1051. const stmtMedium = document.getElementById('stmtMedium');
  1052. const wordmark = document.getElementById('wordmark');
  1053. const farewell = document.getElementById('farewell');
  1054. const farewellCn = document.getElementById('farewellCn');
  1055. const urlEl = document.getElementById('url');
  1056. const watermark = document.getElementById('watermark');
  1057. const COMMAND = '/huashu-design 做一份发布会PPT';
  1058. // ---------- Gallery transforms ----------
  1059. const GALLERY_TILT = 'perspective(2400px) rotateX(14deg) rotateY(-10deg) rotateZ(-2deg)';
  1060. const GALLERY_SCALE = 0.94;
  1061. function galleryTransform(dx, dy, extraScale = 1) {
  1062. return `translate(-50%, -50%) translate(${dx}px, ${dy}px) scale(${GALLERY_SCALE * extraScale}) ${GALLERY_TILT}`;
  1063. }
  1064. // ---------- Helpers to show/hide scenes ----------
  1065. function showScene(key, opacity) {
  1066. const el = scenes[key];
  1067. if (opacity > 0.001) el.classList.add('visible');
  1068. else el.classList.remove('visible');
  1069. el.style.opacity = opacity;
  1070. }
  1071. function showOver(key, opacity) {
  1072. const el = overs[key];
  1073. el.style.opacity = opacity;
  1074. }
  1075. // ---------- Render ----------
  1076. function render(t) {
  1077. // ============ Act 0: Claude Design 致敬 ============
  1078. if (t < T.a0_out[1]) {
  1079. let op;
  1080. if (t < T.a0_in[1]) op = lerp(t, T.a0_in[0], T.a0_in[1], 0, 1, expoOut);
  1081. else if (t < T.a0_out[0]) op = 1;
  1082. else op = lerp(t, T.a0_out[0], T.a0_out[1], 1, 0, easeOut);
  1083. showScene('a0', op);
  1084. // Browser: subtle breathing scale + exit shrink
  1085. const scaleIn = lerp(t, T.a0_in[0], T.a0_in[1], 0.94, 1.0, expoOut);
  1086. let scaleOut = 1.0;
  1087. let blurOut = 0;
  1088. if (t >= T.a0_out[0]) {
  1089. const p = clampLerp(t, T.a0_out[0], T.a0_out[1]);
  1090. scaleOut = 1.0 - 0.08 * p;
  1091. blurOut = 6 * p;
  1092. }
  1093. const finalScale = Math.min(scaleIn, scaleOut);
  1094. cdBrowser.style.transform = `translate(-50%, -50%) scale(${finalScale})`;
  1095. cdBrowser.style.filter = blurOut > 0.1 ? `blur(${blurOut}px)` : '';
  1096. // Tweaks thumb 自动拖动(模拟用户在调节)
  1097. const tw = clampLerp(t, T.cd_tweak_anim[0], T.cd_tweak_anim[1]);
  1098. // Headline slider: 58% → 72% → 62%
  1099. let headlinePct;
  1100. if (tw < 0.5) headlinePct = 58 + (72 - 58) * easeInOut(tw * 2);
  1101. else headlinePct = 72 + (62 - 72) * easeInOut((tw - 0.5) * 2);
  1102. cdThumb1.style.left = headlinePct + '%';
  1103. // Density slider: 40% → 55%
  1104. const densityPct = 40 + 15 * easeInOut(tw);
  1105. cdThumb2.style.left = densityPct + '%';
  1106. // Accent 从橙切换到深绿(模拟用户在选色)
  1107. const switched = t >= T.cd_accent_switch[0];
  1108. if (switched) {
  1109. cdDot1.classList.add('active');
  1110. cdDot2.classList.remove('active');
  1111. // Poster 颜色跟着变
  1112. cdPoster.style.background = 'var(--cd-green)';
  1113. } else {
  1114. cdDot1.classList.remove('active');
  1115. cdDot2.classList.add('active');
  1116. cdPoster.style.background = '#B85D3D';
  1117. }
  1118. // Caption "It's beautiful."
  1119. let capOp = 0;
  1120. if (t >= T.cd_caption_in[0] && t < T.cd_caption_out[1]) {
  1121. if (t < T.cd_caption_in[1]) capOp = clampLerp(t, T.cd_caption_in[0], T.cd_caption_in[1]);
  1122. else if (t < T.cd_caption_out[0]) capOp = 1;
  1123. else capOp = 1 - clampLerp(t, T.cd_caption_out[0], T.cd_caption_out[1]);
  1124. }
  1125. const capRise = lerp(t, T.cd_caption_in[0], T.cd_caption_in[1], 14, 0, expoOut);
  1126. cdCaption.style.opacity = capOp;
  1127. cdCaption.style.transform = `translateX(-50%) translateY(${capRise}px)`;
  1128. } else {
  1129. showScene('a0', 0);
  1130. }
  1131. // ============ Act 0.5: Pivot — "But it isn't the future." ============
  1132. if (t >= T.a05_in[0] - 0.1 && t < T.a05_out[1]) {
  1133. let op;
  1134. if (t < T.a05_in[1]) op = lerp(t, T.a05_in[0], T.a05_in[1], 0, 1, expoOut);
  1135. else if (t < T.a05_out[0]) op = 1;
  1136. else op = lerp(t, T.a05_out[0], T.a05_out[1], 1, 0, easeOut);
  1137. showScene('a05', op);
  1138. const rise = lerp(t, T.a05_in[0], T.a05_in[1], 16, 0, expoOut);
  1139. pivotLine.style.transform = `translate3d(0, ${rise}px, 0)`;
  1140. // Subtle weight morph on "But it isn't the future."
  1141. const morph = expoOut(clampLerp(t, T.a05_in[0], T.a05_in[1] + 0.3));
  1142. const w = 120 + (300 - 120) * morph;
  1143. pivotLine.style.fontVariationSettings = `"wght" ${w.toFixed(0)}`;
  1144. pivotLine.style.fontWeight = Math.round(w);
  1145. } else {
  1146. showScene('a05', 0);
  1147. }
  1148. // ============ Act 1a: "Here's to the Agents." ============
  1149. if (t >= T.a1a_in[0] - 0.1 && t < T.a1a_out[1]) {
  1150. let op;
  1151. if (t < T.a1a_in[1]) op = lerp(t, T.a1a_in[0], T.a1a_in[1], 0, 1, expoOut);
  1152. else if (t < T.a1a_out[0]) op = 1;
  1153. else op = lerp(t, T.a1a_out[0], T.a1a_out[1], 1, 0, easeOut);
  1154. showScene('a1a', op);
  1155. // Weight morph 100 → 400 on "Here's to the Agents."
  1156. const morph = expoOut(clampLerp(t, T.a1a_in[0], T.a1a_in[1] + 0.6));
  1157. const w = 100 + (400 - 100) * morph;
  1158. heroLine.style.fontVariationSettings = `"wght" ${w.toFixed(0)}`;
  1159. heroLine.style.fontWeight = Math.round(w);
  1160. // Subtle rise
  1161. const rise = lerp(t, T.a1a_in[0], T.a1a_in[1], 18, 0, expoOut);
  1162. heroLine.style.transform = `translate3d(0, ${rise}px, 0)`;
  1163. } else {
  1164. showScene('a1a', 0);
  1165. }
  1166. // ============ Act 1b: Not the ones who click. ============
  1167. if (t >= T.a1b_in[0] - 0.1 && t < T.a1b_out[1]) {
  1168. let op;
  1169. if (t < T.a1b_in[1]) op = lerp(t, T.a1b_in[0], T.a1b_in[1], 0, 1, expoOut);
  1170. else if (t < T.a1b_out[0]) op = 1;
  1171. else op = lerp(t, T.a1b_out[0], T.a1b_out[1], 1, 0, easeOut);
  1172. showScene('a1b', op);
  1173. // Animate the click glyph: appear, then trigger click ring + shake
  1174. const glyphIn = clampLerp(t, T.a1b_in[0] + 0.15, T.a1b_in[1]);
  1175. glyphClick.style.opacity = expoOut(glyphIn);
  1176. // Shake at mid-hold
  1177. const clickT = t - (T.a1b_in[1] + 0.3);
  1178. if (clickT > 0 && clickT < 0.4) {
  1179. glyphClick.style.transform = `translate(-50%, -50%) translate(${Math.sin(clickT * 60) * 3}px, 0)`;
  1180. } else {
  1181. glyphClick.style.transform = `translate(-50%, -50%)`;
  1182. }
  1183. // Strike the word "click" at halfway through hold
  1184. const strikeOn = t >= T.a1b_in[1] + 0.5;
  1185. notLine1.classList.toggle('struck', strikeOn);
  1186. } else {
  1187. showScene('a1b', 0);
  1188. glyphClick.style.opacity = 0;
  1189. }
  1190. // ============ Act 1c: Not the ones who drag. ============
  1191. if (t >= T.a1c_in[0] - 0.1 && t < T.a1c_out[1]) {
  1192. let op;
  1193. if (t < T.a1c_in[1]) op = lerp(t, T.a1c_in[0], T.a1c_in[1], 0, 1, expoOut);
  1194. else if (t < T.a1c_out[0]) op = 1;
  1195. else op = lerp(t, T.a1c_out[0], T.a1c_out[1], 1, 0, easeOut);
  1196. showScene('a1c', op);
  1197. const glyphIn = clampLerp(t, T.a1c_in[0] + 0.15, T.a1c_in[1]);
  1198. glyphDrag.style.opacity = expoOut(glyphIn);
  1199. // Animate slider thumb 30% → 70% position during hold
  1200. const dragT = clampLerp(t, T.a1c_hold[0], T.a1c_hold[1] - 0.2);
  1201. const leftPct = 30 + 40 * easeInOut(dragT);
  1202. sliderThumb.style.left = leftPct + '%';
  1203. const fillEl = glyphDrag.querySelector('.fill');
  1204. if (fillEl) fillEl.style.width = leftPct + '%';
  1205. } else {
  1206. showScene('a1c', 0);
  1207. glyphDrag.style.opacity = 0;
  1208. }
  1209. // ============ Act 1d: Not the ones who wait for you to open the file. ============
  1210. if (t >= T.a1d_in[0] - 0.1 && t < T.a1d_out[1]) {
  1211. let op;
  1212. if (t < T.a1d_in[1]) op = lerp(t, T.a1d_in[0], T.a1d_in[1], 0, 1, expoOut);
  1213. else if (t < T.a1d_out[0]) op = 1;
  1214. else op = lerp(t, T.a1d_out[0], T.a1d_out[1], 1, 0, easeOut);
  1215. showScene('a1d', op);
  1216. const glyphIn = clampLerp(t, T.a1d_in[0] + 0.15, T.a1d_in[1]);
  1217. glyphFolder.style.opacity = expoOut(glyphIn);
  1218. } else {
  1219. showScene('a1d', 0);
  1220. glyphFolder.style.opacity = 0;
  1221. }
  1222. // ============ Act 2 Terminal ============
  1223. if (t >= T.a2tty_in[0] - 0.1 && t < T.a2tty_out[1]) {
  1224. let op;
  1225. if (t < T.a2tty_in[1]) op = lerp(t, T.a2tty_in[0], T.a2tty_in[1], 0, 1, expoOut);
  1226. else if (t < T.a2tty_out[0]) op = 1;
  1227. else op = lerp(t, T.a2tty_out[0], T.a2tty_out[1], 1, 0, easeOut);
  1228. showScene('a2tty', op);
  1229. const rise = lerp(t, T.a2tty_in[0], T.a2tty_in[1], 28, 0, expoOut);
  1230. terminal.style.transform = `translate3d(0, ${rise}px, 0)`;
  1231. // Typing
  1232. if (t < T.a2type[0]) typed.textContent = '';
  1233. else if (t < T.a2type[1]) {
  1234. const p = (t - T.a2type[0]) / (T.a2type[1] - T.a2type[0]);
  1235. const n = Math.floor(p * COMMAND.length);
  1236. typed.textContent = COMMAND.slice(0, n);
  1237. } else typed.textContent = COMMAND;
  1238. cursor.style.opacity = (Math.floor(t * 2.5) % 2 === 0) ? 1 : 0.25;
  1239. } else {
  1240. showScene('a2tty', 0);
  1241. }
  1242. // ============ Act 2 Gallery + statements ============
  1243. if (t >= T.a2gal_in[0] - 0.1 && t < T.a2gal_out[1]) {
  1244. let op;
  1245. if (t < T.a2gal_in[1]) op = lerp(t, T.a2gal_in[0], T.a2gal_in[1], 0, 1, expoOut);
  1246. else if (t < T.a2gal_out[0]) op = 1;
  1247. else op = lerp(t, T.a2gal_out[0], T.a2gal_out[1], 1, 0, easeOut);
  1248. showScene('a2gal', op);
  1249. // Pan
  1250. const panT = Math.max(0, t - T.panStart);
  1251. const panX = Math.sin(panT * 0.12) * 180 - panT * 6;
  1252. const panY = Math.cos(panT * 0.09) * 100 - panT * 4;
  1253. const cX = Math.max(-600, Math.min(600, panX));
  1254. const cY = Math.max(-400, Math.min(400, panY));
  1255. // Ripple
  1256. const inRipple = t < T.ripple[1];
  1257. const rippleP = clampLerp(t, T.ripple[0], T.ripple[1]);
  1258. const galScale = inRipple ? (1.25 - 0.31 * expoOut(rippleP)) : 1.0;
  1259. galleryCanvas.style.transform = galleryTransform(cX, cY, galScale);
  1260. // Per-card ripple entry
  1261. galleryCards.forEach((card, i) => {
  1262. let entryOp = 1;
  1263. if (inRipple) {
  1264. const col = i % COLS, row = Math.floor(i / COLS);
  1265. const dc = col - (COLS - 1) / 2, dr = row - (ROWS - 1) / 2;
  1266. const dist = Math.sqrt(dc * dc + dr * dr);
  1267. const maxDist = Math.sqrt(((COLS - 1) / 2) ** 2 + ((ROWS - 1) / 2) ** 2);
  1268. const delay = (dist / maxDist) * 0.8;
  1269. const localT = Math.max(0, (t - T.ripple[0] - delay) / 0.7);
  1270. entryOp = expoOut(Math.min(1, localT));
  1271. }
  1272. // Dim when statements are active
  1273. const stmt1Active = t >= T.stmt1[0] && t < T.stmt1[1];
  1274. const stmt2Active = t >= T.stmt2[0] && t < T.stmt2[1];
  1275. const dimAmount = stmt1Active || stmt2Active ? 0.55 : 0;
  1276. if (dimAmount > 0) {
  1277. card.style.opacity = entryOp * (1 - dimAmount);
  1278. card.style.filter = `brightness(${1 - 0.3 * dimAmount}) saturate(${1 - 0.4 * dimAmount})`;
  1279. } else {
  1280. card.style.opacity = entryOp < 1 ? entryOp : '';
  1281. card.style.filter = '';
  1282. }
  1283. });
  1284. } else {
  1285. showScene('a2gal', 0);
  1286. }
  1287. // Overlay statement 1: "design while you sleep"
  1288. {
  1289. let op = 0;
  1290. if (t >= T.stmt1[0] && t < T.stmt1[1]) {
  1291. const inP = expoOut(clampLerp(t, T.stmt1[0], T.stmt1[0] + 0.4));
  1292. const outP = easeOut(clampLerp(t, T.stmt1[1] - 0.4, T.stmt1[1]));
  1293. op = inP * (1 - outP);
  1294. }
  1295. showOver('stmt1', op);
  1296. }
  1297. // Overlay statement 2: "ship while meeting"
  1298. {
  1299. let op = 0;
  1300. if (t >= T.stmt2[0] && t < T.stmt2[1]) {
  1301. const inP = expoOut(clampLerp(t, T.stmt2[0], T.stmt2[0] + 0.4));
  1302. const outP = easeOut(clampLerp(t, T.stmt2[1] - 0.4, T.stmt2[1]));
  1303. op = inP * (1 - outP);
  1304. }
  1305. showOver('stmt2', op);
  1306. }
  1307. // ============ Act 3 Medium ============
  1308. if (t >= T.a3med_in[0] - 0.1 && t < T.a3med_out[1]) {
  1309. let op;
  1310. if (t < T.a3med_in[1]) op = lerp(t, T.a3med_in[0], T.a3med_in[1], 0, 1, expoOut);
  1311. else if (t < T.a3med_out[0]) op = 1;
  1312. else op = lerp(t, T.a3med_out[0], T.a3med_out[1], 1, 0, easeOut);
  1313. showScene('a3med', op);
  1314. const morph = expoOut(clampLerp(t, T.a3med_in[0], T.a3med_in[1] + 0.4));
  1315. const w = 100 + (300 - 100) * morph;
  1316. stmtMedium.style.fontVariationSettings = `"wght" ${w.toFixed(0)}`;
  1317. stmtMedium.style.fontWeight = Math.round(w);
  1318. const rise = lerp(t, T.a3med_in[0], T.a3med_in[1], 24, 0, expoOut);
  1319. stmtMedium.style.transform = `translate3d(0, ${rise}px, 0)`;
  1320. } else {
  1321. showScene('a3med', 0);
  1322. }
  1323. // ============ Act 3 Brand ============
  1324. if (t >= T.a3brand_in[0] - 0.1) {
  1325. const op = clampLerp(t, T.a3brand_in[0], T.a3brand_in[1]);
  1326. showScene('a3brand', op);
  1327. // Wordmark weight morph
  1328. const morphP = expoOut(clampLerp(t, T.brand_morph[0], T.brand_morph[1]));
  1329. const wght = 100 + (700 - 100) * morphP;
  1330. wordmark.style.fontVariationSettings = `"wght" ${wght.toFixed(0)}`;
  1331. wordmark.style.fontWeight = Math.round(wght);
  1332. const wRise = lerp(t, T.a3brand_in[0], T.a3brand_in[1], 20, 0, expoOut);
  1333. wordmark.style.transform = `translate3d(0, ${wRise}px, 0)`;
  1334. // Farewell quote
  1335. const fOp = clampLerp(t, T.a3farewell_in[0], T.a3farewell_in[1]);
  1336. const fRise = lerp(t, T.a3farewell_in[0], T.a3farewell_in[1], 12, 0, expoOut);
  1337. farewell.style.opacity = fOp;
  1338. farewell.style.transform = `translate3d(0, ${fRise}px, 0)`;
  1339. // CN subtitle
  1340. const cnOp = clampLerp(t, T.a3cn_in[0], T.a3cn_in[1]);
  1341. farewellCn.style.opacity = cnOp;
  1342. // URL
  1343. const uOp = clampLerp(t, T.a3url_in[0], T.a3url_in[1]);
  1344. urlEl.style.opacity = uOp;
  1345. } else {
  1346. showScene('a3brand', 0);
  1347. }
  1348. // Watermark: visible during Act 2-3
  1349. if (t >= T.a2tty_in[0] && t < T.DURATION - 0.2) {
  1350. watermark.classList.add('visible');
  1351. } else {
  1352. watermark.classList.remove('visible');
  1353. }
  1354. }
  1355. // ---------- Driver ----------
  1356. let manualT = null;
  1357. let startMs = null;
  1358. let hasFinishedOnce = false;
  1359. function tick(now) {
  1360. if (manualT != null) render(manualT);
  1361. else {
  1362. if (startMs == null) startMs = now;
  1363. const elapsed = (now - startMs) / 1000;
  1364. const recording = window.__recording === true;
  1365. let t;
  1366. if (recording) {
  1367. // Non-looping: clamp at DURATION, hold on final frame
  1368. t = Math.min(elapsed, T.DURATION - 0.001);
  1369. if (elapsed >= T.DURATION && !hasFinishedOnce) hasFinishedOnce = true;
  1370. } else {
  1371. t = elapsed % T.DURATION;
  1372. }
  1373. render(t);
  1374. }
  1375. requestAnimationFrame(tick);
  1376. }
  1377. requestAnimationFrame(tick);
  1378. // For frame-accurate rendering
  1379. window.__setTime = function(t) {
  1380. manualT = t;
  1381. render(t);
  1382. };
  1383. window.__resume = function() { manualT = null; startMs = null; };
  1384. window.__duration = T.DURATION;
  1385. window.__render = render;
  1386. window.__ready = true;
  1387. })();
  1388. </script>
  1389. </body>
  1390. </html>