| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- <!doctype html>
- <html lang="zh-Hans">
- <head>
- <meta charset="utf-8" />
- <title>w3 · Fallback Advisor(中文版)</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,300..700&family=Noto+Serif+SC:wght@300;400;500;600&family=Inter:wght@200;300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
- <style>
- :root {
- --bg: #000000;
- --ink: #FFFFFF;
- --ink-80: rgba(255,255,255,0.82);
- --ink-60: rgba(255,255,255,0.58);
- --muted: rgba(255,255,255,0.40);
- --dim: rgba(255,255,255,0.18);
- --hairline: rgba(255,255,255,0.12);
- --accent: #D97757;
- --accent-deep: #B85D3D;
- --cd-bg: #F5F4F0;
- --cd-ink: #1A1918;
- --serif-cn: "Noto Serif SC", "Songti SC", serif;
- --serif-en: "Source Serif 4", Georgia, serif;
- --sans: "Inter", -apple-system, "PingFang SC", system-ui, sans-serif;
- --mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
- }
- html, body {
- margin: 0; padding: 0;
- background: #000;
- overflow: hidden;
- font-family: var(--sans);
- color: var(--ink);
- -webkit-font-smoothing: antialiased;
- }
- * { box-sizing: border-box; }
- .stage {
- position: fixed;
- top: 50%; left: 50%;
- width: 1920px; height: 1080px;
- transform-origin: center center;
- background: var(--bg);
- overflow: hidden;
- }
- /* ============ Watermark ============ */
- .watermark-tl {
- position: absolute;
- top: 40px; left: 56px;
- font-family: var(--mono);
- font-size: 12px;
- letter-spacing: 0.2em;
- color: rgba(255,255,255,0.16);
- z-index: 200;
- pointer-events: none;
- text-transform: uppercase;
- }
- .watermark-br {
- position: absolute;
- bottom: 32px; right: 40px;
- font-family: var(--mono);
- font-size: 10px;
- letter-spacing: 0.24em;
- color: rgba(255,255,255,0.14);
- z-index: 200;
- pointer-events: none;
- text-transform: uppercase;
- }
- /* ============ Top Title ============ */
- .top-title {
- position: absolute;
- top: 88px; left: 50%;
- transform: translateX(-50%);
- font-family: var(--serif-cn);
- font-weight: 300;
- font-size: 42px;
- letter-spacing: 0.02em;
- color: var(--ink-80);
- text-align: center;
- opacity: 0;
- will-change: opacity, transform;
- z-index: 120;
- }
- .top-title .accent { color: var(--accent); font-weight: 400; }
- .sub-caption {
- position: absolute;
- top: 148px; left: 50%;
- transform: translateX(-50%);
- font-family: var(--sans);
- font-weight: 300;
- font-size: 15px;
- letter-spacing: 0.32em;
- color: var(--muted);
- text-transform: uppercase;
- opacity: 0;
- will-change: opacity;
- z-index: 120;
- }
- /* ============ Philosophy Wall (4 rows × 5 cols) ============ */
- .wall-viewport {
- position: absolute;
- top: 50%; left: 50%;
- transform: translate(-50%, -50%);
- width: 1480px;
- height: 760px;
- perspective: 2400px;
- perspective-origin: 50% 50%;
- will-change: transform, opacity, filter;
- }
- .wall-grid {
- position: absolute;
- inset: 0;
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- grid-template-rows: repeat(4, 1fr);
- gap: 18px;
- transform: rotateX(10deg) rotateY(-6deg);
- transform-style: preserve-3d;
- will-change: transform, opacity;
- }
- .cell {
- position: relative;
- background: #0f0f0f;
- border: 1px solid var(--hairline);
- border-radius: 8px;
- overflow: hidden;
- opacity: 0;
- will-change: opacity, transform, filter;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- padding: 14px 16px;
- }
- /* abstract glyph per cell — geometric, no imagery */
- .cell .glyph {
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: none;
- }
- .cell .name {
- position: relative;
- font-family: var(--mono);
- font-size: 11px;
- letter-spacing: 0.08em;
- color: var(--muted);
- z-index: 2;
- align-self: flex-end;
- }
- .cell .num {
- position: relative;
- font-family: var(--mono);
- font-size: 10px;
- color: var(--dim);
- letter-spacing: 0.1em;
- z-index: 2;
- }
- /* Selected cells — lit up */
- .cell.selected {
- border-color: var(--accent);
- background: #1a0f0a;
- }
- .cell.selected .name { color: var(--accent); }
- /* ============ Scan light ============ */
- .scan-light {
- position: absolute;
- left: -5%;
- right: -5%;
- top: -15%;
- height: 200px;
- background: linear-gradient(
- 180deg,
- rgba(217, 119, 87, 0) 0%,
- rgba(217, 119, 87, 0.18) 40%,
- rgba(255, 220, 200, 0.45) 50%,
- rgba(217, 119, 87, 0.18) 60%,
- rgba(217, 119, 87, 0) 100%
- );
- filter: blur(8px);
- z-index: 80;
- opacity: 0;
- will-change: opacity, transform;
- pointer-events: none;
- }
- /* ============ Foreground 3 cards ============ */
- .fg-row {
- position: absolute;
- top: 50%; left: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- gap: 56px;
- opacity: 0;
- will-change: opacity;
- z-index: 100;
- }
- .fg-card {
- width: 440px;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- opacity: 0;
- transform: translateZ(-800px) scale(0.4);
- will-change: opacity, transform;
- }
- .fg-card .card-body {
- background: #0f0f0f;
- border: 1px solid var(--accent);
- border-radius: 12px;
- padding: 32px 30px;
- box-shadow:
- 0 30px 80px -20px rgba(217,119,87,0.25),
- 0 10px 30px -10px rgba(0,0,0,0.6);
- }
- .fg-card .label {
- font-family: var(--mono);
- font-size: 11px;
- letter-spacing: 0.18em;
- color: var(--accent);
- text-transform: uppercase;
- margin-bottom: 14px;
- }
- .fg-card .title-cn {
- font-family: var(--serif-cn);
- font-size: 36px;
- font-weight: 400;
- letter-spacing: 0.01em;
- line-height: 1.15;
- color: var(--ink);
- margin-bottom: 10px;
- }
- .fg-card .title-en {
- font-family: var(--serif-en);
- font-style: italic;
- font-weight: 300;
- font-size: 17px;
- letter-spacing: 0.01em;
- color: var(--ink-60);
- margin-bottom: 22px;
- }
- .fg-card .feature {
- font-family: var(--sans);
- font-size: 14px;
- font-weight: 300;
- letter-spacing: 0.02em;
- color: var(--muted);
- line-height: 1.6;
- padding-top: 18px;
- border-top: 1px solid var(--hairline);
- }
- .fg-card .thumb-wrap {
- margin-top: 14px;
- height: 0;
- overflow: hidden;
- border-radius: 10px;
- background: #0a0a0a;
- border: 1px solid var(--hairline);
- opacity: 0;
- will-change: opacity, height;
- }
- .fg-card .thumb-wrap img {
- width: 100%;
- display: block;
- }
- /* ============ Brand Reveal (米色盖层) ============ */
- .brand-panel {
- position: absolute;
- inset: 0;
- background: var(--cd-bg);
- opacity: 0;
- transform: translateY(100%);
- will-change: opacity, transform;
- z-index: 300;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- }
- .brand-mark {
- font-family: var(--serif-en);
- font-style: italic;
- font-weight: 300;
- font-size: 112px;
- letter-spacing: -0.02em;
- color: var(--cd-ink);
- opacity: 0;
- transform: scale(0.92);
- will-change: opacity, transform;
- line-height: 1;
- }
- .brand-mark .accent { color: var(--accent); font-style: italic; }
- .brand-mark .dot { color: var(--accent); font-style: normal; padding: 0 6px; }
- .brand-underline {
- margin-top: 34px;
- height: 2px;
- width: 0;
- background: var(--accent);
- will-change: width;
- }
- .brand-tag {
- margin-top: 22px;
- font-family: var(--mono);
- font-size: 12px;
- letter-spacing: 0.32em;
- color: rgba(26,25,24,0.54);
- text-transform: uppercase;
- opacity: 0;
- will-change: opacity;
- }
- </style>
- </head>
- <body>
- <div class="stage" id="stage">
- <!-- 水印 -->
- <div class="watermark-tl">HUASHU · DESIGN</div>
- <div class="watermark-br">V2 · 2026 · w3</div>
- <!-- 顶部标题 -->
- <div class="top-title" id="topTitle">
- 不知道要什么?<span class="accent">先给你 3 个方向</span>
- </div>
- <div class="sub-caption" id="subCaption">20 Philosophies · 3 Directions</div>
- <!-- 扫描光 -->
- <div class="scan-light" id="scanLight"></div>
- <!-- 4×5 哲学墙 -->
- <div class="wall-viewport" id="wallViewport">
- <div class="wall-grid" id="wallGrid">
- <!-- 20 cells injected by JS -->
- </div>
- </div>
- <!-- 前景 3 张方向卡 -->
- <div class="fg-row" id="fgRow">
- <!-- card 1: Kenya Hara · 东方极简 -->
- <div class="fg-card" id="card1">
- <div class="card-body">
- <div class="label">方向 01 · 东方空间</div>
- <div class="title-cn">原研哉式留白</div>
- <div class="title-en">Kenya Hara</div>
- <div class="feature">赤土橙 · 大量留白 · 宣纸质感</div>
- </div>
- <div class="thumb-wrap" id="thumb1">
- <img src="demo-takram.png" alt="demo takram" />
- </div>
- </div>
- <!-- card 2: Pentagram · 信息建筑 -->
- <div class="fg-card" id="card2">
- <div class="card-body">
- <div class="label">方向 02 · 信息建筑</div>
- <div class="title-cn">Pentagram 秩序</div>
- <div class="title-en">Pentagram</div>
- <div class="feature">强网格 · 高对比 · 理性版式</div>
- </div>
- <div class="thumb-wrap" id="thumb2">
- <img src="demo-pentagram.png" alt="demo pentagram" />
- </div>
- </div>
- <!-- card 3: David Carson · 实验先锋 -->
- <div class="fg-card" id="card3">
- <div class="card-body">
- <div class="label">方向 03 · 实验先锋</div>
- <div class="title-cn">David Carson 式</div>
- <div class="title-en">Experimental Edge</div>
- <div class="feature">破格排印 · 粗野几何 · 视觉冲击</div>
- </div>
- <div class="thumb-wrap" id="thumb3">
- <img src="demo-build.png" alt="demo build" />
- </div>
- </div>
- </div>
- <!-- Brand Reveal -->
- <div class="brand-panel" id="brandPanel">
- <div class="brand-mark" id="brandMark">huashu<span class="dot">·</span><span class="accent">design</span></div>
- <div class="brand-underline" id="brandUnderline"></div>
- <div class="brand-tag" id="brandTag">HTML as Designer's Medium</div>
- </div>
- </div>
- <script>
- (function(){
- // ============ Stage auto-scale ============
- function scaleStage(){
- const stage = document.getElementById('stage');
- const sx = window.innerWidth / 1920;
- const sy = window.innerHeight / 1080;
- const s = Math.min(sx, sy);
- stage.style.transform = `translate(-50%, -50%) scale(${s})`;
- }
- window.addEventListener('resize', scaleStage);
- scaleStage();
- // ============ 20 Philosophies ============
- // 4 rows × 5 cols = 20. Selected: idx 0 (Pentagram), idx 9 (Kenya Hara), idx 12 (David Carson)
- const PHILOSOPHIES = [
- // row 1 — 信息建筑派
- { name: 'Pentagram', glyph: 'grid' },
- { name: 'M. Vignelli', glyph: 'bars' },
- { name: 'Apple HIG', glyph: 'radius' },
- { name: 'Spin', glyph: 'slash' },
- { name: 'Build', glyph: 'type' },
- // row 2 — 运动诗学派
- { name: 'Field.io', glyph: 'wave' },
- { name: 'Active Theory',glyph: 'orbit' },
- { name: 'Hi-Res!', glyph: 'dots' },
- { name: 'Locomotive', glyph: 'arrow' },
- { name: 'Takram', glyph: 'circle' },
- // row 3 — 极简/东方
- { name: 'Kenya Hara', glyph: 'ma' },
- { name: 'D. Rams', glyph: 'square' },
- { name: 'J. Ive', glyph: 'arc' },
- { name: 'J. Morrison', glyph: 'minimal' },
- { name: 'S. Ogata', glyph: 'line' },
- // row 4 — 实验 & 海报
- { name: 'D. Carson', glyph: 'collage' },
- { name: 'S. Sagmeister',glyph: 'stamp' },
- { name: 'P. Scher', glyph: 'poster' },
- { name: 'M. Glaser', glyph: 'heart' },
- { name: 'K. Sato', glyph: 'logo' },
- ];
- // selected indices — 3 differentiated directions
- const SELECTED = [10, 0, 15]; // Kenya Hara, Pentagram, David Carson
- function makeGlyph(kind){
- // Simple geometric SVG glyphs — one per cell, no real logos
- const svgs = {
- grid: `<svg viewBox="0 0 100 60" width="78%" height="62%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1" fill="none">
- <rect x="6" y="8" width="28" height="18"/><rect x="38" y="8" width="28" height="18"/><rect x="70" y="8" width="24" height="44"/>
- <rect x="6" y="30" width="60" height="22"/></g></svg>`,
- bars: `<svg viewBox="0 0 100 60" width="78%" height="62%"><g fill="rgba(255,255,255,0.22)">
- <rect x="10" y="40" width="8" height="16"/><rect x="22" y="28" width="8" height="28"/><rect x="34" y="16" width="8" height="40"/>
- <rect x="46" y="24" width="8" height="32"/><rect x="58" y="10" width="8" height="46"/><rect x="70" y="34" width="8" height="22"/>
- <rect x="82" y="22" width="8" height="34"/></g></svg>`,
- radius: `<svg viewBox="0 0 100 60" width="72%" height="58%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1.2" fill="none">
- <rect x="14" y="10" width="72" height="40" rx="20" ry="20"/></g></svg>`,
- slash: `<svg viewBox="0 0 100 60" width="78%" height="62%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1.4" fill="none" stroke-linecap="square">
- <path d="M 14 50 L 52 10"/><path d="M 36 50 L 74 10"/><path d="M 58 50 L 86 22"/></g></svg>`,
- type: `<svg viewBox="0 0 100 60" width="78%" height="62%"><text x="50" y="42" text-anchor="middle" font-family="Source Serif 4, serif" font-size="40" font-style="italic" fill="rgba(255,255,255,0.22)">Aa</text></svg>`,
- wave: `<svg viewBox="0 0 100 60" width="82%" height="62%"><path d="M 6 30 Q 20 8, 34 30 T 62 30 T 90 30" stroke="rgba(255,255,255,0.22)" stroke-width="1.3" fill="none"/></svg>`,
- orbit: `<svg viewBox="0 0 100 60" width="74%" height="62%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1.1" fill="none"><ellipse cx="50" cy="30" rx="36" ry="14"/><ellipse cx="50" cy="30" rx="14" ry="22"/><circle cx="50" cy="30" r="2" fill="rgba(255,255,255,0.32)"/></g></svg>`,
- dots: `<svg viewBox="0 0 100 60" width="78%" height="62%"><g fill="rgba(255,255,255,0.22)"><circle cx="14" cy="18" r="2"/><circle cx="30" cy="18" r="2"/><circle cx="46" cy="18" r="2"/><circle cx="62" cy="18" r="2"/><circle cx="78" cy="18" r="2"/><circle cx="14" cy="30" r="2"/><circle cx="30" cy="30" r="2"/><circle cx="46" cy="30" r="3"/><circle cx="62" cy="30" r="2"/><circle cx="78" cy="30" r="2"/><circle cx="14" cy="42" r="2"/><circle cx="30" cy="42" r="2"/><circle cx="46" cy="42" r="2"/><circle cx="62" cy="42" r="2"/><circle cx="78" cy="42" r="2"/></g></svg>`,
- arrow: `<svg viewBox="0 0 100 60" width="78%" height="52%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1.2" fill="none" stroke-linecap="square"><path d="M 14 30 L 80 30"/><path d="M 68 18 L 82 30 L 68 42"/></g></svg>`,
- circle: `<svg viewBox="0 0 100 60" width="62%" height="62%"><circle cx="50" cy="30" r="22" stroke="rgba(255,255,255,0.22)" stroke-width="1.2" fill="none"/></svg>`,
- ma: `<svg viewBox="0 0 100 60" width="72%" height="62%"><g fill="none" stroke="rgba(255,255,255,0.22)" stroke-width="0.9"><rect x="18" y="14" width="64" height="32"/></g><circle cx="50" cy="30" r="1.4" fill="rgba(255,255,255,0.32)"/></svg>`,
- square: `<svg viewBox="0 0 100 60" width="62%" height="62%"><rect x="30" y="10" width="40" height="40" stroke="rgba(255,255,255,0.22)" stroke-width="1.2" fill="none"/></svg>`,
- arc: `<svg viewBox="0 0 100 60" width="78%" height="62%"><path d="M 14 46 Q 50 6, 86 46" stroke="rgba(255,255,255,0.22)" stroke-width="1.3" fill="none"/></svg>`,
- minimal: `<svg viewBox="0 0 100 60" width="78%" height="32%"><line x1="18" y1="30" x2="82" y2="30" stroke="rgba(255,255,255,0.22)" stroke-width="1.2"/></svg>`,
- line: `<svg viewBox="0 0 100 60" width="78%" height="62%"><g stroke="rgba(255,255,255,0.22)" stroke-width="0.9" fill="none"><line x1="14" y1="16" x2="86" y2="16"/><line x1="14" y1="30" x2="86" y2="30"/><line x1="14" y1="44" x2="60" y2="44"/></g></svg>`,
- collage: `<svg viewBox="0 0 100 60" width="82%" height="62%"><g fill="none" stroke="rgba(255,255,255,0.22)" stroke-width="1"><rect x="8" y="8" width="24" height="18" transform="rotate(-8 20 17)"/><rect x="36" y="18" width="28" height="20" transform="rotate(5 50 28)"/><rect x="60" y="6" width="32" height="24" transform="rotate(-4 76 18)"/></g><text x="50" y="56" text-anchor="middle" font-family="Source Serif 4, serif" font-size="14" font-style="italic" fill="rgba(255,255,255,0.3)">RAY</text></svg>`,
- stamp: `<svg viewBox="0 0 100 60" width="70%" height="62%"><g stroke="rgba(255,255,255,0.22)" stroke-width="1.2" fill="none"><circle cx="50" cy="30" r="22"/><text x="50" y="35" text-anchor="middle" font-family="Source Serif 4" font-size="16" font-weight="500" fill="rgba(255,255,255,0.3)">S</text></g></svg>`,
- poster: `<svg viewBox="0 0 100 60" width="82%" height="62%"><g fill="rgba(255,255,255,0.22)"><rect x="8" y="8" width="22" height="44"/><rect x="34" y="8" width="22" height="44"/><rect x="60" y="8" width="22" height="44"/></g></svg>`,
- heart: `<svg viewBox="0 0 100 60" width="58%" height="58%"><path d="M 50 48 C 30 32, 18 20, 30 14 C 40 10, 50 22, 50 22 C 50 22, 60 10, 70 14 C 82 20, 70 32, 50 48 Z" fill="rgba(217,119,87,0.28)"/></svg>`,
- logo: `<svg viewBox="0 0 100 60" width="60%" height="60%"><circle cx="50" cy="30" r="20" stroke="rgba(255,255,255,0.22)" stroke-width="1.3" fill="none"/><circle cx="50" cy="30" r="6" fill="rgba(255,255,255,0.22)"/></svg>`,
- };
- return svgs[kind] || svgs.minimal;
- }
- // Build the wall
- const wallGrid = document.getElementById('wallGrid');
- PHILOSOPHIES.forEach((p, idx) => {
- const cell = document.createElement('div');
- cell.className = 'cell';
- cell.dataset.idx = idx;
- const row = Math.floor(idx / 5);
- const col = idx % 5;
- // precompute distance from grid center (2, 1.5)
- const dr = row - 1.5;
- const dc = col - 2;
- const dist = Math.sqrt(dr * dr + dc * dc);
- cell.dataset.dist = dist.toFixed(3);
- cell.innerHTML = `
- <div class="glyph">${makeGlyph(p.glyph)}</div>
- <div class="num">${String(idx + 1).padStart(2, '0')}</div>
- <div class="name">${p.name}</div>
- `;
- wallGrid.appendChild(cell);
- });
- const cells = Array.from(wallGrid.querySelectorAll('.cell'));
- const maxDist = Math.max(...cells.map(c => parseFloat(c.dataset.dist)));
- // ============ Timeline ============
- const T_TOTAL = 12.0; // seconds (flow type w)
- const fps = 25;
- const frameDur = 1 / fps;
- // Easing
- const expoOut = t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
- const expoIn = t => t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
- const cubicInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2;
- const cubicOut = t => 1 - Math.pow(1 - t, 3);
- const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
- const clamp01 = v => clamp(v, 0, 1);
- const lerp = (a, b, t) => a + (b - a) * t;
- // Element refs
- const topTitle = document.getElementById('topTitle');
- const subCap = document.getElementById('subCaption');
- const wallViewport = document.getElementById('wallViewport');
- const wallGridEl = wallGrid;
- const scanLight = document.getElementById('scanLight');
- const fgRow = document.getElementById('fgRow');
- const card1 = document.getElementById('card1');
- const card2 = document.getElementById('card2');
- const card3 = document.getElementById('card3');
- const thumb1 = document.getElementById('thumb1');
- const thumb2 = document.getElementById('thumb2');
- const thumb3 = document.getElementById('thumb3');
- const brandPanel = document.getElementById('brandPanel');
- const brandMark = document.getElementById('brandMark');
- const brandUnderline = document.getElementById('brandUnderline');
- const brandTag = document.getElementById('brandTag');
- function tick(t){
- // Clamp
- t = Math.max(0, Math.min(T_TOTAL, t));
- // ========== Phase 1: 0 - 2.5s — Ripple in 20 cells ==========
- const rippleStart = 0.15;
- const rippleSpan = 1.8;
- cells.forEach(cell => {
- const d = parseFloat(cell.dataset.dist);
- // delay scaled by distance-from-center (hero v10 formula)
- const delay = (d / maxDist) * 0.85;
- const cellT = clamp01((t - rippleStart - delay * 0.55) / 0.7);
- const eased = expoOut(cellT);
- const idx = parseInt(cell.dataset.idx, 10);
- const isSel = SELECTED.includes(idx);
- cell.style.opacity = (eased * (isSel ? 1.0 : 0.85)).toFixed(3);
- const ty = lerp(30, 0, eased);
- const scale = lerp(0.88, 1, eased);
- cell.style.transform = `translateY(${ty}px) scale(${scale})`;
- });
- // ========== Phase 2: 2.5 - 4.0s — scan light sweeps down ==========
- const scanStart = 2.6;
- const scanEnd = 4.0;
- const scanT = clamp01((t - scanStart) / (scanEnd - scanStart));
- if (scanT > 0 && scanT < 1) {
- scanLight.style.opacity = Math.min(1, Math.sin(scanT * Math.PI) * 1.3).toFixed(3);
- // travel from top to bottom across the wall (-150 to 860px within wallViewport-ish)
- const py = lerp(-180, 820, cubicInOut(scanT));
- scanLight.style.transform = `translateY(${py}px)`;
- } else {
- scanLight.style.opacity = 0;
- }
- // ========== Phase 3: 4.0 - 4.8s — 3 cells light up, others dim ==========
- const lightStart = 4.0;
- const lightEnd = 4.8;
- const lightT = clamp01((t - lightStart) / (lightEnd - lightStart));
- const lightE = expoOut(lightT);
- cells.forEach(cell => {
- const idx = parseInt(cell.dataset.idx, 10);
- const isSel = SELECTED.includes(idx);
- if (isSel) {
- cell.classList.toggle('selected', lightT > 0.05);
- } else {
- // dim non-selected from 0.85 → 0.08
- const base = 0.85;
- const dimmedOpacity = lerp(base, 0.08, lightE);
- // only override after ripple is done
- if (t >= lightStart) {
- cell.style.opacity = dimmedOpacity.toFixed(3);
- }
- }
- });
- // ========== Phase 4: 4.8 - 6.5s — 3 cells break out to foreground ==========
- // We don't literally move the wall cells; we fade in fg-cards "bursting from the wall"
- const breakStart = 4.8;
- const breakEnd = 6.5;
- const breakT = clamp01((t - breakStart) / (breakEnd - breakStart));
- const breakE = expoOut(breakT);
- if (t >= breakStart - 0.1) {
- fgRow.style.opacity = 1;
- } else {
- fgRow.style.opacity = 0;
- }
- [card1, card2, card3].forEach((card, i) => {
- const stagger = i * 0.18; // pop × 3 staggered
- const cT = clamp01((t - breakStart - stagger) / 0.85);
- const cE = expoOut(cT);
- card.style.opacity = cE.toFixed(3);
- // Z-rush: from translateZ(-800) to 0, scale 0.4 → 1
- const tz = lerp(-800, 0, cE);
- const sc = lerp(0.45, 1, cE);
- const ty = lerp(40, 0, cE);
- card.style.transform = `translateZ(${tz}px) scale(${sc}) translateY(${ty}px)`;
- });
- // Dim the wall (behind) when cards come forward
- if (t >= breakStart) {
- const dimT = clamp01((t - breakStart) / 0.9);
- const dimE = expoOut(dimT);
- wallViewport.style.opacity = lerp(1, 0.25, dimE).toFixed(3);
- wallViewport.style.filter = `blur(${lerp(0, 6, dimE).toFixed(1)}px)`;
- } else {
- wallViewport.style.opacity = 1;
- wallViewport.style.filter = 'blur(0px)';
- }
- // ========== Phase 5: 6.5 - 9.5s — thumbnails grow below each card ==========
- const thumbStart = 6.6;
- const thumbs = [thumb1, thumb2, thumb3];
- thumbs.forEach((thumb, i) => {
- const stagger = i * 0.32;
- const ttT = clamp01((t - thumbStart - stagger) / 1.0);
- const ttE = cubicOut(ttT);
- thumb.style.opacity = ttE.toFixed(3);
- // height from 0 to 250px
- const h = lerp(0, 250, ttE);
- thumb.style.height = `${h}px`;
- });
- // ========== Top title fade in 7.2 - 8.0 ==========
- const titleStart = 7.2;
- const titleT = clamp01((t - titleStart) / 0.9);
- const titleE = cubicOut(titleT);
- topTitle.style.opacity = titleE.toFixed(3);
- topTitle.style.transform = `translateX(-50%) translateY(${lerp(-14, 0, titleE)}px)`;
- subCap.style.opacity = (titleE * 0.95).toFixed(3);
- // ========== Phase 6: 9.8 - 12.0s — Brand Reveal ==========
- const brandStart = 9.8;
- const panelT = clamp01((t - brandStart) / 0.7);
- const panelE = expoOut(panelT);
- brandPanel.style.opacity = panelE.toFixed(3);
- brandPanel.style.transform = `translateY(${lerp(100, 0, panelE)}%)`;
- const markStart = 10.3;
- const markT = clamp01((t - markStart) / 0.6);
- const markE = expoOut(markT);
- brandMark.style.opacity = markE.toFixed(3);
- brandMark.style.transform = `scale(${lerp(0.92, 1, markE)})`;
- const ulStart = 10.7;
- const ulT = clamp01((t - ulStart) / 0.55);
- brandUnderline.style.width = `${lerp(0, 280, expoOut(ulT))}px`;
- const tagStart = 11.1;
- const tagT = clamp01((t - tagStart) / 0.5);
- brandTag.style.opacity = cubicOut(tagT).toFixed(3);
- }
- // ============ Animation loop ============
- window.__ready = false;
- window.__duration = T_TOTAL;
- let startTime = null;
- let paused = false;
- const recording = window.__recording === true;
- function loop(now){
- if (paused) return;
- if (startTime === null) startTime = now;
- const t = (now - startTime) / 1000;
- tick(t);
- if (t < T_TOTAL) {
- requestAnimationFrame(loop);
- } else if (!recording) {
- startTime = now;
- requestAnimationFrame(loop);
- }
- }
- // First-frame sync BEFORE requesting next frame
- tick(0);
- window.__ready = true;
- requestAnimationFrame(loop);
- // Pause raf loop — tests & recorder call this before seeking
- window.__pause = function(){ paused = true; };
- window.__resume = function(){
- if (!paused) return;
- paused = false;
- startTime = null;
- requestAnimationFrame(loop);
- };
- // Expose for video recorder (scripts/render-video.js uses __setTime)
- window.__setTime = function(t){ paused = true; tick(t); };
- })();
- </script>
- </body>
- </html>
|