mirror of
https://github.com/aculix/huashu-design.git
synced 2026-10-27 07:15:54 +00:00
feat: V2 重制 9 个演示 GIF + 新增英文版 + hero 升级到 v10
亮点: - 统一 VI:全部对齐 hero-v10 色板 (#000000 + #D97757) 和 Brand Reveal 米色面板 signature - 文字克制:每个 demo 控制在 20-48 字 / 15-36 词,从解释性文字转为锚点 - 中英平行:英文版不是字幕翻译,画面元素重新设计 - 高清 GIF:1280×720 · 25fps · full palette · sierra2_4a dither - Concept-driven:每个 demo 都有清晰的核心传达隐喻,哑剧测试可过 文件变更: - 9 个能力/流程 demo 全部 V2 重做(c1-c6, w1-w3),中英双版 - hero animation 从 v9 升级到 v10(中英双版) - README.md 引用更新 v9 → v10 - README.en.md 所有 GIF 加 -en 后缀 + note 更新 - 移除冗余:20 个 -60fps.mp4(README 未引用)+ 18 个 demo MP4 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b876d634ee
commit
fed775c447
22 files changed
+19476
-6547
No files matched your search
+15
-15
@@ -27,22 +27,22 @@ npx skills add alchaincyf/huashu-design
|
|||||||
|
|
||||||
[See it work](#demo-gallery) · [Install](#install) · [What it does](#what-it-does) · [How it works](#core-mechanics) · [vs. Claude Design](#vs-claude-design)
|
[See it work](#demo-gallery) · [Install](#install) · [What it does](#what-it-does) · [How it works](#core-mechanics) · [vs. Claude Design](#vs-claude-design)
|
||||||
|
|
||||||
> 📖 **Note for English readers**: this skill is built by a Chinese-speaking developer for a Chinese-speaking audience. The skill's agent prompts (`SKILL.md`, `references/*.md`) and all demo assets below are in **Chinese** — they work with English tasks just fine (the agent is bilingual), but the visible text in screenshots/GIFs is Chinese. The core design principles translate universally.
|
> 📖 **Note for English readers**: this skill is built by a Chinese-speaking developer. The skill's agent prompts (`SKILL.md`, `references/*.md`) are in Chinese but the agent is bilingual — works fine with English tasks. The demos below are the English parallel versions; the Chinese ones are in the default-named files (see the Chinese [README.md](README.md)).
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<video src="demos/hero-animation-v9.mp4" autoplay muted loop playsinline width="100%">
|
<video src="demos/hero-animation-v10-en.mp4" autoplay muted loop playsinline width="100%">
|
||||||
Your browser doesn't support inline video. <a href="demos/hero-animation-v9.mp4">Download MP4</a>.
|
Your browser doesn't support inline video. <a href="demos/hero-animation-v10-en.mp4">Download MP4</a>.
|
||||||
</video>
|
</video>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><sub>▲ 10-second hero animation showing what huashu-design does (<a href="demos/hero-animation-v9.mp4">download MP4</a> if autoplay doesn't work)</sub></p>
|
<p align="center"><sub>▲ 10-second hero animation showing what huashu-design does (<a href="demos/hero-animation-v10-en.mp4">download MP4</a> if autoplay doesn't work)</sub></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="demos/w3-fallback-advisor.gif" alt="Fallback design advisor · 3 differentiated directions from 20 design philosophies" width="100%">
|
<img src="demos/w3-fallback-advisor-en.gif" alt="Fallback design advisor · 3 differentiated directions from 20 design philosophies" width="100%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -82,61 +82,61 @@ No buttons, no panels, no Figma plugin. Agent-agnostic — drops into Claude Cod
|
|||||||
|
|
||||||
## Demo Gallery
|
## Demo Gallery
|
||||||
|
|
||||||
> The demos below use Chinese text in UI but the mechanics apply to any language.
|
> English parallel versions of the demos. Chinese versions live at the default filenames (see the Chinese README).
|
||||||
|
|
||||||
### Design Direction Advisor
|
### Design Direction Advisor
|
||||||
|
|
||||||
The fallback for vague briefs: pick 3 differentiated directions from 5 schools × 20 philosophies, generate all 3 demos in parallel, let the user choose.
|
The fallback for vague briefs: pick 3 differentiated directions from 5 schools × 20 philosophies, generate all 3 demos in parallel, let the user choose.
|
||||||
|
|
||||||
<p align="center"><img src="demos/w3-fallback-advisor.gif" width="100%"></p>
|
<p align="center"><img src="demos/w3-fallback-advisor-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### iOS App Prototype
|
### iOS App Prototype
|
||||||
|
|
||||||
Pixel-accurate iPhone 15 Pro body (Dynamic Island / status bar / Home Indicator) · state-driven multi-screen navigation · real images pulled from Wikimedia/Met/Unsplash · Playwright click tests before delivery.
|
Pixel-accurate iPhone 15 Pro body (Dynamic Island / status bar / Home Indicator) · state-driven multi-screen navigation · real images pulled from Wikimedia/Met/Unsplash · Playwright click tests before delivery.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c1-ios-prototype.gif" width="100%"></p>
|
<p align="center"><img src="demos/c1-ios-prototype-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### Motion Design Engine
|
### Motion Design Engine
|
||||||
|
|
||||||
Stage + Sprite time-slice model · `useTime` / `useSprite` / `interpolate` / `Easing` — four APIs cover every animation need · one command exports MP4 / GIF / 60fps-interpolated / BGM-scored finals.
|
Stage + Sprite time-slice model · `useTime` / `useSprite` / `interpolate` / `Easing` — four APIs cover every animation need · one command exports MP4 / GIF / 60fps-interpolated / BGM-scored finals.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c3-motion-design.gif" width="100%"></p>
|
<p align="center"><img src="demos/c3-motion-design-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### HTML Slides → Editable PPTX
|
### HTML Slides → Editable PPTX
|
||||||
|
|
||||||
HTML decks for browser presentation · `html2pptx.js` reads DOM computed styles and translates each element into real PowerPoint objects · exports are **actual text frames**, not image-bed fakes.
|
HTML decks for browser presentation · `html2pptx.js` reads DOM computed styles and translates each element into real PowerPoint objects · exports are **actual text frames**, not image-bed fakes.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c2-slides-pptx.gif" width="100%"></p>
|
<p align="center"><img src="demos/c2-slides-pptx-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### Tweaks · Live Variation Switching
|
### Tweaks · Live Variation Switching
|
||||||
|
|
||||||
Colors / typography / information density parameterized · side panel toggle · pure-frontend + `localStorage` persistence · survives reload.
|
Colors / typography / information density parameterized · side panel toggle · pure-frontend + `localStorage` persistence · survives reload.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c4-tweaks.gif" width="100%"></p>
|
<p align="center"><img src="demos/c4-tweaks-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### Infographic / Data Viz
|
### Infographic / Data Viz
|
||||||
|
|
||||||
Magazine-grade typography · precise CSS Grid columns · `text-wrap: pretty` typographic details · driven by real data · exports to vector PDF / 300dpi PNG / SVG.
|
Magazine-grade typography · precise CSS Grid columns · `text-wrap: pretty` typographic details · driven by real data · exports to vector PDF / 300dpi PNG / SVG.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c5-infographic.gif" width="100%"></p>
|
<p align="center"><img src="demos/c5-infographic-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### 5-Dimension Expert Critique
|
### 5-Dimension Expert Critique
|
||||||
|
|
||||||
Philosophical coherence · visual hierarchy · execution craft · functionality · innovation — each scored 0–10 · radar-chart visualization · outputs Keep / Fix / Quick Wins punch list.
|
Philosophical coherence · visual hierarchy · execution craft · functionality · innovation — each scored 0–10 · radar-chart visualization · outputs Keep / Fix / Quick Wins punch list.
|
||||||
|
|
||||||
<p align="center"><img src="demos/c6-expert-review.gif" width="100%"></p>
|
<p align="center"><img src="demos/c6-expert-review-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### Junior Designer Workflow
|
### Junior Designer Workflow
|
||||||
|
|
||||||
No heroic one-shot attempts: start with assumptions + placeholders + reasoning, show it to the user early, then iterate. Fixing a misunderstanding early is 100× cheaper than fixing it late.
|
No heroic one-shot attempts: start with assumptions + placeholders + reasoning, show it to the user early, then iterate. Fixing a misunderstanding early is 100× cheaper than fixing it late.
|
||||||
|
|
||||||
<p align="center"><img src="demos/w2-junior-designer.gif" width="100%"></p>
|
<p align="center"><img src="demos/w2-junior-designer-en.gif" width="100%"></p>
|
||||||
|
|
||||||
### Core Asset Protocol · 5-step hard process
|
### Core Asset Protocol · 5-step hard process
|
||||||
|
|
||||||
Mandatory whenever the task involves a specific brand: ask → search → download (three fallback paths) → verify + extract → write `brand-spec.md` covering **logo, product shots, UI screenshots, colors, fonts** — all required assets, not just colors.
|
Mandatory whenever the task involves a specific brand: ask → search → download (three fallback paths) → verify + extract → write `brand-spec.md` covering **logo, product shots, UI screenshots, colors, fonts** — all required assets, not just colors.
|
||||||
|
|
||||||
<p align="center"><img src="demos/w1-brand-protocol.gif" width="100%"></p>
|
<p align="center"><img src="demos/w1-brand-protocol-en.gif" width="100%"></p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ npx skills add alchaincyf/huashu-design
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="demos/hero-animation-v9.gif" alt="huashu-design Hero · 打字 → 选方向 → 画廊展开 → 聚焦 → 品牌显形" width="100%">
|
<img src="demos/hero-animation-v10.gif" alt="huashu-design Hero · 打字 → 选方向 → 画廊展开 → 聚焦 → 品牌显形" width="100%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><sub>
|
<p align="center"><sub>
|
||||||
▲ 25 秒 · Terminal → 4 方向 → Gallery ripple → 4 次 Focus → Brand reveal<br>
|
▲ 25 秒 · Terminal → 4 方向 → Gallery ripple → 4 次 Focus → Brand reveal<br>
|
||||||
👉 <a href="https://www.huasheng.ai/huashu-design-hero/">访问带音效的 HTML 互动版</a> ·
|
👉 <a href="https://www.huasheng.ai/huashu-design-hero/">访问带音效的 HTML 互动版</a> ·
|
||||||
<a href="demos/hero-animation-v9.mp4">下载 MP4(含 BGM+SFX · 10MB)</a>
|
<a href="demos/hero-animation-v10.mp4">下载 MP4(含 BGM+SFX · 10MB)</a>
|
||||||
</sub></p>
|
</sub></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
+1124
-703
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1025
-948
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1103
-525
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,989 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>c4-tweaks · Slide. See it morph. (English)</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;1,8..60,300..700&family=Noto+Serif+SC:wght@300;400;500;600;700&family=Inter:wght@100;200;300;400;500;600;700;800&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);
|
||||||
|
--hairline: rgba(255,255,255,0.12);
|
||||||
|
--accent: #D97757;
|
||||||
|
--accent-deep: #B85D3D;
|
||||||
|
|
||||||
|
/* Mock landing page · warm variant (initial state) */
|
||||||
|
--warm-bg: #F6EFE6;
|
||||||
|
--warm-panel: #FFFFFF;
|
||||||
|
--warm-ink: #1A1918;
|
||||||
|
--warm-dim: #8B867E;
|
||||||
|
--warm-hair: rgba(0,0,0,0.08);
|
||||||
|
--warm-accent: #D97757;
|
||||||
|
|
||||||
|
/* Mock landing page · cool variant (after slider 1) */
|
||||||
|
--cool-bg: #0E1620;
|
||||||
|
--cool-panel: #17222E;
|
||||||
|
--cool-ink: #E8EEF5;
|
||||||
|
--cool-dim: #7A8A9B;
|
||||||
|
--cool-hair: rgba(255,255,255,0.08);
|
||||||
|
--cool-accent: #5A8CB8;
|
||||||
|
|
||||||
|
--serif-en: "Source Serif 4", Georgia, serif;
|
||||||
|
--serif-cn: "Noto Serif SC", "Source Serif 4", Georgia, serif;
|
||||||
|
--sans: "Inter", -apple-system, "PingFang SC", "HarmonyOS Sans 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: translate(-50%, -50%);
|
||||||
|
transform-origin: center center;
|
||||||
|
background: var(--bg);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Film grain */
|
||||||
|
.grain {
|
||||||
|
position: absolute; inset: 0;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(rgba(255,255,255,0.02) 1px, transparent 1px);
|
||||||
|
background-size: 3px 3px;
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Watermark */
|
||||||
|
.watermark {
|
||||||
|
position: absolute;
|
||||||
|
top: 44px; left: 56px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.16);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-mark {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 44px; right: 56px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.12);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Main composition ============ */
|
||||||
|
.composition {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1080px 500px;
|
||||||
|
gap: 80px;
|
||||||
|
padding: 130px 120px 140px 140px;
|
||||||
|
align-items: center;
|
||||||
|
perspective: 2400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Design preview (left) ---- */
|
||||||
|
.preview-frame {
|
||||||
|
position: relative;
|
||||||
|
width: 1080px;
|
||||||
|
height: 800px;
|
||||||
|
border-radius: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transform: rotateX(6deg) rotateY(-4deg);
|
||||||
|
box-shadow:
|
||||||
|
0 50px 120px rgba(0,0,0,0.6),
|
||||||
|
0 0 0 1px rgba(255,255,255,0.06);
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform, background;
|
||||||
|
transition: background 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.preview-frame.warm {
|
||||||
|
background: var(--warm-bg);
|
||||||
|
}
|
||||||
|
.preview-frame.cool {
|
||||||
|
background: var(--cool-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Browser chrome top bar */
|
||||||
|
.browser-chrome {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px 22px;
|
||||||
|
border-bottom: 1px solid var(--warm-hair);
|
||||||
|
background: var(--warm-panel);
|
||||||
|
transition: all 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .browser-chrome {
|
||||||
|
background: var(--cool-panel);
|
||||||
|
border-bottom-color: var(--cool-hair);
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
width: 11px; height: 11px; border-radius: 50%;
|
||||||
|
background: rgba(0,0,0,0.14);
|
||||||
|
}
|
||||||
|
.cool .dot { background: rgba(255,255,255,0.14); }
|
||||||
|
.url-bar {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 14px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(0,0,0,0.04);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--warm-dim);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
transition: all 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .url-bar {
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
color: var(--cool-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero content */
|
||||||
|
.preview-body {
|
||||||
|
padding: 54px 72px 60px 72px;
|
||||||
|
color: var(--warm-ink);
|
||||||
|
transition: color 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .preview-body { color: var(--cool-ink); }
|
||||||
|
|
||||||
|
.preview-eyebrow {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--warm-accent);
|
||||||
|
transition: color 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .preview-eyebrow { color: var(--cool-accent); }
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 86px;
|
||||||
|
line-height: 1.02;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
transition: font-family 240ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
font-weight 240ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
letter-spacing 240ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.preview-title .em {
|
||||||
|
color: var(--warm-accent);
|
||||||
|
font-style: italic;
|
||||||
|
transition: color 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .preview-title .em { color: var(--cool-accent); }
|
||||||
|
|
||||||
|
.preview-frame.sans .preview-title {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 200;
|
||||||
|
letter-spacing: -0.045em;
|
||||||
|
}
|
||||||
|
.preview-frame.sans .preview-title .em {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-sub {
|
||||||
|
margin-top: 24px;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 720px;
|
||||||
|
color: var(--warm-dim);
|
||||||
|
transition: color 280ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
font-family 240ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .preview-sub { color: var(--cool-dim); }
|
||||||
|
.preview-frame.sans .preview-sub {
|
||||||
|
font-family: var(--sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Density cards grid */
|
||||||
|
.card-grid {
|
||||||
|
margin-top: 54px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 18px;
|
||||||
|
transition: grid-template-columns 280ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
gap 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.preview-frame.dense .card-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-auto-rows: minmax(72px, auto);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 22px 22px 24px 22px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(0,0,0,0.035);
|
||||||
|
border: 1px solid var(--warm-hair);
|
||||||
|
transition: all 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .card {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border-color: var(--cool-hair);
|
||||||
|
}
|
||||||
|
.preview-frame.dense .card {
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 28px; height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--warm-accent);
|
||||||
|
opacity: 0.16;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
transition: all 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .card-icon { background: var(--cool-accent); }
|
||||||
|
.preview-frame.dense .card-icon {
|
||||||
|
width: 18px; height: 18px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--warm-ink);
|
||||||
|
letter-spacing: -0.005em;
|
||||||
|
transition: color 280ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
font-family 240ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
font-size 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .card-title { color: var(--cool-ink); }
|
||||||
|
.preview-frame.sans .card-title {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.preview-frame.dense .card-title {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--warm-dim);
|
||||||
|
transition: all 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.cool .card-text { color: var(--cool-dim); }
|
||||||
|
.preview-frame.sans .card-text { font-family: var(--sans); }
|
||||||
|
.preview-frame.dense .card-text {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.3;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra cards (hidden in sparse mode) */
|
||||||
|
.card.extra {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.92);
|
||||||
|
transition: opacity 240ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
transform 240ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
background 280ms cubic-bezier(.2,.8,.2,1),
|
||||||
|
border-color 280ms cubic-bezier(.2,.8,.2,1);
|
||||||
|
pointer-events: none;
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.preview-frame.dense .card.extra {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
max-height: 120px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Slider panel (right) ---- */
|
||||||
|
.slider-panel {
|
||||||
|
position: relative;
|
||||||
|
width: 500px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anchor-line {
|
||||||
|
position: absolute;
|
||||||
|
top: -80px;
|
||||||
|
left: 8px;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 26px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--ink-80);
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.anchor-line .em {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.slider-name {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
color: var(--ink-80);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.slider-value {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track */
|
||||||
|
.track {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--hairline);
|
||||||
|
}
|
||||||
|
.track-fill {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 10%;
|
||||||
|
background: var(--accent);
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tick marks */
|
||||||
|
.ticks {
|
||||||
|
position: absolute;
|
||||||
|
inset: -4px 0 -4px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.tick {
|
||||||
|
width: 1px;
|
||||||
|
height: 10px;
|
||||||
|
background: rgba(255,255,255,0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Knob */
|
||||||
|
.knob {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 10%;
|
||||||
|
width: 26px; height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--ink);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0,0,0,0.6),
|
||||||
|
0 8px 24px rgba(0,0,0,0.5);
|
||||||
|
will-change: left, transform, box-shadow;
|
||||||
|
}
|
||||||
|
.knob.active {
|
||||||
|
box-shadow: 0 0 0 2px var(--accent),
|
||||||
|
0 0 30px rgba(217,119,87,0.45),
|
||||||
|
0 8px 24px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor */
|
||||||
|
.cursor {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px; height: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: left, top, opacity;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.cursor svg { width: 100%; height: 100%; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8)); }
|
||||||
|
|
||||||
|
/* ---- Brand reveal ---- */
|
||||||
|
/* Stage dimmer: fades the composition out just before the panel slides in */
|
||||||
|
.stage-dimmer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: #000000;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 40;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.brand-panel {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: #F5F4F0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 50;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.brand-wordmark {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 72px;
|
||||||
|
font-weight: 100;
|
||||||
|
font-variation-settings: "wght" 100;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: #1A1918;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
will-change: opacity, transform, font-variation-settings, font-weight;
|
||||||
|
}
|
||||||
|
.brand-wordmark .accent { color: #D97757; font-weight: inherit; }
|
||||||
|
|
||||||
|
.brand-line {
|
||||||
|
/* Flex-centered, 60px below wordmark (line-height 1 @ 72px → descender + 24 gap) */
|
||||||
|
margin-top: 60px;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
background: #D97757;
|
||||||
|
align-self: center;
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="stage" id="stage">
|
||||||
|
|
||||||
|
<div class="grain"></div>
|
||||||
|
<div class="watermark">HUASHU · DESIGN</div>
|
||||||
|
<div class="version-mark">V2 · 2026</div>
|
||||||
|
|
||||||
|
<div class="composition">
|
||||||
|
|
||||||
|
<!-- LEFT: design preview -->
|
||||||
|
<div class="preview-frame warm" id="preview">
|
||||||
|
<div class="browser-chrome">
|
||||||
|
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||||||
|
<div class="url-bar">yourbrand.design</div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-eyebrow">Agent Studio</div>
|
||||||
|
<div class="preview-title">Built for <span class="em">them</span>.<br/>Who never sleep.</div>
|
||||||
|
<div class="preview-sub">A design system that ships while you rest — ready before you open the file.</div>
|
||||||
|
|
||||||
|
<div class="card-grid" id="cardGrid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Brand Assets</div>
|
||||||
|
<div class="card-text">Logos, palettes, type — one source of truth.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Prototype</div>
|
||||||
|
<div class="card-text">One sentence in, a clickable app out.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Motion</div>
|
||||||
|
<div class="card-text">Timeline is code. Swap 25 for 60 fps.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Slides</div>
|
||||||
|
<div class="card-text">HTML is PPTX.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Infographic</div>
|
||||||
|
<div class="card-text">Data in, magazine out.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Review</div>
|
||||||
|
<div class="card-text">Five axes. Honest punch list.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Advisor</div>
|
||||||
|
<div class="card-text">Three roads. You pick.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Junior</div>
|
||||||
|
<div class="card-text">Show first. Polish later.</div>
|
||||||
|
</div>
|
||||||
|
<div class="card extra">
|
||||||
|
<div class="card-icon"></div>
|
||||||
|
<div class="card-title">Protocol</div>
|
||||||
|
<div class="card-text">Five steps. No skip.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT: slider panel -->
|
||||||
|
<div class="slider-panel" id="panel">
|
||||||
|
|
||||||
|
<div class="anchor-line" id="anchor">
|
||||||
|
Slide. <span class="em">See it morph.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slider 1 · palette -->
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-label">
|
||||||
|
<span class="slider-name">Palette</span>
|
||||||
|
<span class="slider-value" id="val1">warm</span>
|
||||||
|
</div>
|
||||||
|
<div class="track">
|
||||||
|
<div class="ticks">
|
||||||
|
<span class="tick"></span><span class="tick"></span><span class="tick"></span>
|
||||||
|
<span class="tick"></span><span class="tick"></span>
|
||||||
|
</div>
|
||||||
|
<div class="track-fill" id="fill1"></div>
|
||||||
|
<div class="knob" id="knob1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slider 2 · type -->
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-label">
|
||||||
|
<span class="slider-name">Type</span>
|
||||||
|
<span class="slider-value" id="val2">serif</span>
|
||||||
|
</div>
|
||||||
|
<div class="track">
|
||||||
|
<div class="ticks">
|
||||||
|
<span class="tick"></span><span class="tick"></span><span class="tick"></span>
|
||||||
|
<span class="tick"></span><span class="tick"></span>
|
||||||
|
</div>
|
||||||
|
<div class="track-fill" id="fill2"></div>
|
||||||
|
<div class="knob" id="knob2"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slider 3 · density -->
|
||||||
|
<div class="slider-item">
|
||||||
|
<div class="slider-label">
|
||||||
|
<span class="slider-name">Density</span>
|
||||||
|
<span class="slider-value" id="val3">sparse</span>
|
||||||
|
</div>
|
||||||
|
<div class="track">
|
||||||
|
<div class="ticks">
|
||||||
|
<span class="tick"></span><span class="tick"></span><span class="tick"></span>
|
||||||
|
<span class="tick"></span><span class="tick"></span>
|
||||||
|
</div>
|
||||||
|
<div class="track-fill" id="fill3"></div>
|
||||||
|
<div class="knob" id="knob3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cursor -->
|
||||||
|
<div class="cursor" id="cursor">
|
||||||
|
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2 2 L2 16 L6 12 L9 18 L11 17 L8 11 L14 11 Z"
|
||||||
|
fill="white" stroke="#000" stroke-width="1.2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stage dimmer (fades scene to black before panel sweeps in) -->
|
||||||
|
<div class="stage-dimmer" id="stageDimmer"></div>
|
||||||
|
|
||||||
|
<!-- Brand reveal layer -->
|
||||||
|
<div class="brand-panel" id="brandPanel">
|
||||||
|
<div class="brand-wordmark" id="brandMark">huashu<span class="accent">-</span>design</div>
|
||||||
|
<div class="brand-line" id="brandLine"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// ---------- Fit stage ----------
|
||||||
|
const stage = document.getElementById('stage');
|
||||||
|
function rescale() {
|
||||||
|
const s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
|
||||||
|
stage.style.transform = `translate(-50%, -50%) scale(${s})`;
|
||||||
|
}
|
||||||
|
rescale();
|
||||||
|
window.addEventListener('resize', rescale);
|
||||||
|
|
||||||
|
// ---------- Animation ----------
|
||||||
|
const DURATION = 10.0; // seconds
|
||||||
|
|
||||||
|
const preview = document.getElementById('preview');
|
||||||
|
const panel = document.getElementById('panel');
|
||||||
|
const anchor = document.getElementById('anchor');
|
||||||
|
const cursor = document.getElementById('cursor');
|
||||||
|
|
||||||
|
const knob1 = document.getElementById('knob1');
|
||||||
|
const knob2 = document.getElementById('knob2');
|
||||||
|
const knob3 = document.getElementById('knob3');
|
||||||
|
const fill1 = document.getElementById('fill1');
|
||||||
|
const fill2 = document.getElementById('fill2');
|
||||||
|
const fill3 = document.getElementById('fill3');
|
||||||
|
const val1 = document.getElementById('val1');
|
||||||
|
const val2 = document.getElementById('val2');
|
||||||
|
const val3 = document.getElementById('val3');
|
||||||
|
|
||||||
|
const stageDimmer = document.getElementById('stageDimmer');
|
||||||
|
const brandPanel = document.getElementById('brandPanel');
|
||||||
|
const brandMark = document.getElementById('brandMark');
|
||||||
|
const brandLine = document.getElementById('brandLine');
|
||||||
|
|
||||||
|
// Easings
|
||||||
|
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);
|
||||||
|
|
||||||
|
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
||||||
|
function lerp(t, t0, t1, v0, v1, ease) {
|
||||||
|
if (t <= t0) return v0;
|
||||||
|
if (t >= t1) return v1;
|
||||||
|
const k = (t - t0) / (t1 - t0);
|
||||||
|
return v0 + (v1 - v0) * (ease ? ease(k) : k);
|
||||||
|
}
|
||||||
|
function clampLerp(t, t0, t1) {
|
||||||
|
if (t <= t0) return 0;
|
||||||
|
if (t >= t1) return 1;
|
||||||
|
return (t - t0) / (t1 - t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knob motion — drag feel: first 70% is a cubic ease (hand moving),
|
||||||
|
// final 15% is overshoot + snap to target (magnetic arrival).
|
||||||
|
function knobMotion(t, t0, t1, fromPct, toPct) {
|
||||||
|
if (t <= t0) return fromPct;
|
||||||
|
if (t >= t1) return toPct;
|
||||||
|
const k = (t - t0) / (t1 - t0);
|
||||||
|
const direction = toPct > fromPct ? 1 : -1;
|
||||||
|
const range = Math.abs(toPct - fromPct);
|
||||||
|
|
||||||
|
if (k < 0.72) {
|
||||||
|
// Main drag: cubic easeInOut feels like a hand moving
|
||||||
|
const e = cubicInOut(k / 0.72);
|
||||||
|
return fromPct + (toPct - fromPct) * e;
|
||||||
|
} else if (k < 0.85) {
|
||||||
|
// Overshoot past target by ~2%
|
||||||
|
const overK = (k - 0.72) / 0.13;
|
||||||
|
const overshoot = 2.2;
|
||||||
|
return toPct + direction * overshoot * Math.sin(overK * Math.PI);
|
||||||
|
} else {
|
||||||
|
// Settled at target
|
||||||
|
return toPct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeline (seconds, 10s total)
|
||||||
|
const T = {
|
||||||
|
stage_in: [0.0, 1.0], // frame + panel appear
|
||||||
|
anchor_in: [0.8, 1.4],
|
||||||
|
|
||||||
|
// Slider 1 · palette: warm → cool (1.2s → 3.2s) — arrive at 3.0s
|
||||||
|
s1_cursor_to: [1.3, 1.9],
|
||||||
|
s1_drag: [1.9, 2.9],
|
||||||
|
s1_settle: [2.9, 3.1],
|
||||||
|
|
||||||
|
// Slider 2 · type: serif → sans
|
||||||
|
s2_cursor_to: [3.2, 3.7],
|
||||||
|
s2_drag: [3.7, 4.7],
|
||||||
|
s2_settle: [4.7, 4.9],
|
||||||
|
|
||||||
|
// Slider 3 · density: sparse → dense
|
||||||
|
s3_cursor_to: [5.0, 5.5],
|
||||||
|
s3_drag: [5.5, 6.5],
|
||||||
|
s3_settle: [6.5, 6.7],
|
||||||
|
|
||||||
|
hold: [6.7, 8.0],
|
||||||
|
|
||||||
|
// Brand reveal (cream walloff · aligned with hero-v10 signature)
|
||||||
|
scene_out: [8.0, 8.3], // main composition fade to black (0.3s)
|
||||||
|
brand_panel: [8.3, 8.7], // cream panel sweeps up from bottom, expoOut (0.4s)
|
||||||
|
brand_mark: [8.7, 9.3], // wordmark: wght 100→500 + y 20→0 + opacity 0→1 (0.6s)
|
||||||
|
brand_line: [9.3, 9.7], // orange line expands 0→280 from center (0.4s)
|
||||||
|
brand_hold: [9.7, 10.0], // hold final frame
|
||||||
|
};
|
||||||
|
|
||||||
|
// Slider-to-state logic. Value-changes happen at settle start.
|
||||||
|
let state = { palette: 'warm', type: 'serif', density: 'sparse' };
|
||||||
|
let lastStateHash = '';
|
||||||
|
function updatePreview() {
|
||||||
|
preview.classList.remove('warm', 'cool', 'sans', 'dense');
|
||||||
|
if (state.palette === 'warm') preview.classList.add('warm');
|
||||||
|
else preview.classList.add('cool');
|
||||||
|
if (state.type === 'sans') preview.classList.add('sans');
|
||||||
|
if (state.density === 'dense') preview.classList.add('dense');
|
||||||
|
}
|
||||||
|
updatePreview();
|
||||||
|
|
||||||
|
function setKnobState(knob, active) {
|
||||||
|
if (active) knob.classList.add('active');
|
||||||
|
else knob.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValueLabel(el, text) {
|
||||||
|
if (el.textContent !== text) el.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Cursor path (in composition coords) ----------
|
||||||
|
// Composition uses grid: left column 1220 + 60 gap, panel is at right.
|
||||||
|
// We'll position cursor using .composition-relative absolute positioning.
|
||||||
|
// Cursor is child of .composition, whose padding is 130/100/140/140.
|
||||||
|
// So coords relative to .composition padding-box.
|
||||||
|
// Simpler: cursor is absolute in .stage coords since parent composition
|
||||||
|
// covers full stage. Use inline style left/top in px.
|
||||||
|
|
||||||
|
// Anchor positions (rough — will fine-tune):
|
||||||
|
const CURSOR_PARK = { x: 1900, y: 1080 }; // off-screen bottom-right
|
||||||
|
// Slider tracks: panel starts around x≈1420, width 520. Each track spans that width.
|
||||||
|
// We'll measure actual rect at first tick.
|
||||||
|
let sliderRects = null;
|
||||||
|
function measureRects() {
|
||||||
|
const stageRect = stage.getBoundingClientRect();
|
||||||
|
const scale = stageRect.width / 1920;
|
||||||
|
const getTrackBox = (id) => {
|
||||||
|
const el = document.getElementById(id).parentElement; // .track
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
left: (r.left - stageRect.left) / scale,
|
||||||
|
top: (r.top - stageRect.top) / scale,
|
||||||
|
width: r.width / scale,
|
||||||
|
height: r.height / scale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sliderRects = {
|
||||||
|
s1: getTrackBox('knob1'),
|
||||||
|
s2: getTrackBox('knob2'),
|
||||||
|
s3: getTrackBox('knob3'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionCursor(x, y, opacity) {
|
||||||
|
cursor.style.left = x + 'px';
|
||||||
|
cursor.style.top = y + 'px';
|
||||||
|
cursor.style.opacity = opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
function knobLeft(id, pct) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
el.style.left = pct + '%';
|
||||||
|
}
|
||||||
|
function fillWidth(id, pct) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
el.style.width = pct + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tick / render
|
||||||
|
let startTs = null;
|
||||||
|
let frameCount = 0;
|
||||||
|
|
||||||
|
function tick(ts) {
|
||||||
|
if (!startTs) startTs = ts;
|
||||||
|
const t = (ts - startTs) / 1000;
|
||||||
|
|
||||||
|
// Measure rects once
|
||||||
|
if (!sliderRects && frameCount > 1) {
|
||||||
|
measureRects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Stage in ---
|
||||||
|
const stageK = clampLerp(t, T.stage_in[0], T.stage_in[1]);
|
||||||
|
const stageOp = cubicOut(stageK);
|
||||||
|
preview.style.opacity = stageOp;
|
||||||
|
preview.style.transform = `rotateX(${lerp(t, T.stage_in[0], T.stage_in[1], 10, 6, cubicOut)}deg) rotateY(-4deg) translateY(${lerp(t, T.stage_in[0], T.stage_in[1], 20, 0, expoOut)}px)`;
|
||||||
|
panel.style.opacity = stageOp;
|
||||||
|
panel.style.transform = `translateX(${lerp(t, T.stage_in[0], T.stage_in[1], 30, 0, expoOut)}px)`;
|
||||||
|
|
||||||
|
// Anchor
|
||||||
|
const aK = clampLerp(t, T.anchor_in[0], T.anchor_in[1]);
|
||||||
|
anchor.style.opacity = cubicOut(aK);
|
||||||
|
anchor.style.transform = `translateY(${lerp(t, T.anchor_in[0], T.anchor_in[1], 10, 0, expoOut)}px)`;
|
||||||
|
|
||||||
|
// Snap point: when knob reaches target (72% of drag duration)
|
||||||
|
const s1SnapT = T.s1_drag[0] + (T.s1_drag[1] - T.s1_drag[0]) * 0.72;
|
||||||
|
const s2SnapT = T.s2_drag[0] + (T.s2_drag[1] - T.s2_drag[0]) * 0.72;
|
||||||
|
const s3SnapT = T.s3_drag[0] + (T.s3_drag[1] - T.s3_drag[0]) * 0.72;
|
||||||
|
|
||||||
|
// --- Slider 1: palette ---
|
||||||
|
// Knob 10% → 90%
|
||||||
|
const k1pct = knobMotion(t, T.s1_drag[0], T.s1_drag[1], 10, 90);
|
||||||
|
knobLeft('knob1', k1pct); fillWidth('fill1', k1pct);
|
||||||
|
setKnobState(knob1, t >= T.s1_cursor_to[0] && t < T.s1_settle[1] + 0.2);
|
||||||
|
if (t >= s1SnapT && state.palette !== 'cool') {
|
||||||
|
state.palette = 'cool'; updatePreview(); setValueLabel(val1, 'cool');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Slider 2: type ---
|
||||||
|
const k2pct = knobMotion(t, T.s2_drag[0], T.s2_drag[1], 10, 90);
|
||||||
|
knobLeft('knob2', k2pct); fillWidth('fill2', k2pct);
|
||||||
|
setKnobState(knob2, t >= T.s2_cursor_to[0] && t < T.s2_settle[1] + 0.2);
|
||||||
|
if (t >= s2SnapT && state.type !== 'sans') {
|
||||||
|
state.type = 'sans'; updatePreview(); setValueLabel(val2, 'sans');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Slider 3: density ---
|
||||||
|
const k3pct = knobMotion(t, T.s3_drag[0], T.s3_drag[1], 10, 90);
|
||||||
|
knobLeft('knob3', k3pct); fillWidth('fill3', k3pct);
|
||||||
|
setKnobState(knob3, t >= T.s3_cursor_to[0] && t < T.s3_settle[1] + 0.2);
|
||||||
|
if (t >= s3SnapT && state.density !== 'dense') {
|
||||||
|
state.density = 'dense'; updatePreview(); setValueLabel(val3, 'dense');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cursor choreography ---
|
||||||
|
if (sliderRects) {
|
||||||
|
const r1 = sliderRects.s1, r2 = sliderRects.s2, r3 = sliderRects.s3;
|
||||||
|
// Positions of knob at 10% and 90%
|
||||||
|
const k1Start = { x: r1.left + r1.width * 0.10, y: r1.top + r1.height/2 };
|
||||||
|
const k1End = { x: r1.left + r1.width * 0.90, y: r1.top + r1.height/2 };
|
||||||
|
const k2Start = { x: r2.left + r2.width * 0.10, y: r2.top + r2.height/2 };
|
||||||
|
const k2End = { x: r2.left + r2.width * 0.90, y: r2.top + r2.height/2 };
|
||||||
|
const k3Start = { x: r3.left + r3.width * 0.10, y: r3.top + r3.height/2 };
|
||||||
|
const k3End = { x: r3.left + r3.width * 0.90, y: r3.top + r3.height/2 };
|
||||||
|
|
||||||
|
let cx = CURSOR_PARK.x, cy = CURSOR_PARK.y, co = 0;
|
||||||
|
|
||||||
|
if (t < T.s1_cursor_to[0]) {
|
||||||
|
// still off-screen (or just appeared)
|
||||||
|
cx = CURSOR_PARK.x; cy = CURSOR_PARK.y; co = 0;
|
||||||
|
} else if (t < T.s1_cursor_to[1]) {
|
||||||
|
// cursor flies to s1 knob start
|
||||||
|
const k = clampLerp(t, T.s1_cursor_to[0], T.s1_cursor_to[1]);
|
||||||
|
const e = cubicOut(k);
|
||||||
|
cx = lerp(t, T.s1_cursor_to[0], T.s1_cursor_to[1], CURSOR_PARK.x, k1Start.x, cubicOut);
|
||||||
|
cy = lerp(t, T.s1_cursor_to[0], T.s1_cursor_to[1], CURSOR_PARK.y, k1Start.y, cubicOut);
|
||||||
|
co = e;
|
||||||
|
} else if (t < T.s1_drag[1]) {
|
||||||
|
// dragging s1
|
||||||
|
cx = r1.left + (r1.width * k1pct / 100);
|
||||||
|
cy = r1.top + r1.height/2;
|
||||||
|
co = 1;
|
||||||
|
} else if (t < T.s2_cursor_to[0]) {
|
||||||
|
cx = k1End.x; cy = k1End.y; co = 1;
|
||||||
|
} else if (t < T.s2_cursor_to[1]) {
|
||||||
|
cx = lerp(t, T.s2_cursor_to[0], T.s2_cursor_to[1], k1End.x, k2Start.x, cubicInOut);
|
||||||
|
cy = lerp(t, T.s2_cursor_to[0], T.s2_cursor_to[1], k1End.y, k2Start.y, cubicInOut);
|
||||||
|
co = 1;
|
||||||
|
} else if (t < T.s2_drag[1]) {
|
||||||
|
cx = r2.left + (r2.width * k2pct / 100);
|
||||||
|
cy = r2.top + r2.height/2;
|
||||||
|
co = 1;
|
||||||
|
} else if (t < T.s3_cursor_to[0]) {
|
||||||
|
cx = k2End.x; cy = k2End.y; co = 1;
|
||||||
|
} else if (t < T.s3_cursor_to[1]) {
|
||||||
|
cx = lerp(t, T.s3_cursor_to[0], T.s3_cursor_to[1], k2End.x, k3Start.x, cubicInOut);
|
||||||
|
cy = lerp(t, T.s3_cursor_to[0], T.s3_cursor_to[1], k2End.y, k3Start.y, cubicInOut);
|
||||||
|
co = 1;
|
||||||
|
} else if (t < T.s3_drag[1]) {
|
||||||
|
cx = r3.left + (r3.width * k3pct / 100);
|
||||||
|
cy = r3.top + r3.height/2;
|
||||||
|
co = 1;
|
||||||
|
} else if (t < T.hold[1]) {
|
||||||
|
// fade out cursor
|
||||||
|
cx = k3End.x; cy = k3End.y;
|
||||||
|
co = lerp(t, T.s3_drag[1], T.hold[1], 1, 0, cubicOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
positionCursor(cx, cy, co);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Brand reveal (cream walloff · aligned with hero-v10 signature) ---
|
||||||
|
// 1) Scene dimmer: composition fades to black (0.3s)
|
||||||
|
const soK = clampLerp(t, T.scene_out[0], T.scene_out[1]);
|
||||||
|
stageDimmer.style.opacity = cubicOut(soK);
|
||||||
|
|
||||||
|
// 2) Cream panel sweeps up from bottom, expoOut (0.4s)
|
||||||
|
const bpK = clampLerp(t, T.brand_panel[0], T.brand_panel[1]);
|
||||||
|
const panelY = lerp(t, T.brand_panel[0], T.brand_panel[1], 100, 0, expoOut);
|
||||||
|
brandPanel.style.transform = `translateY(${panelY}%)`;
|
||||||
|
|
||||||
|
// 3) Wordmark: font-weight 100→500 + y 20→0 + opacity 0→1, expoOut (0.6s)
|
||||||
|
const bmK = clampLerp(t, T.brand_mark[0], T.brand_mark[1]);
|
||||||
|
const bmE = expoOut(bmK);
|
||||||
|
const wght = 100 + (500 - 100) * bmE;
|
||||||
|
brandMark.style.opacity = bmE;
|
||||||
|
brandMark.style.transform = `translateY(${20 * (1 - bmE)}px)`;
|
||||||
|
brandMark.style.fontWeight = Math.round(wght);
|
||||||
|
brandMark.style.fontVariationSettings = `"wght" ${wght.toFixed(0)}`;
|
||||||
|
|
||||||
|
// 4) Orange line: width 0→280 from center, cubicOut (0.4s)
|
||||||
|
const blK = clampLerp(t, T.brand_line[0], T.brand_line[1]);
|
||||||
|
brandLine.style.width = (280 * cubicOut(blK)) + 'px';
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
|
|
||||||
|
// Loop or stop
|
||||||
|
if (t < DURATION) {
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
} else {
|
||||||
|
if (window.__recording === true) {
|
||||||
|
// recording mode: hold last frame
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Restart after 1s pause (for manual viewing)
|
||||||
|
setTimeout(() => {
|
||||||
|
startTs = null;
|
||||||
|
state = { palette: 'warm', type: 'serif', density: 'sparse' };
|
||||||
|
updatePreview();
|
||||||
|
setValueLabel(val1, 'warm'); setValueLabel(val2, 'serif'); setValueLabel(val3, 'sparse');
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}, 900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation after fonts ready
|
||||||
|
const startAnim = () => {
|
||||||
|
requestAnimationFrame((ts) => {
|
||||||
|
startTs = ts;
|
||||||
|
window.__ready = true; // signal for render-video.js
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(startAnim);
|
||||||
|
} else {
|
||||||
|
setTimeout(startAnim, 500);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+964
-737
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,816 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>c5-infographic · Data → Typography (EN)</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;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">
|
||||||
|
<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;
|
||||||
|
|
||||||
|
/* Brand Reveal */
|
||||||
|
--cd-bg: #F5F4F0;
|
||||||
|
--cd-panel: #FFFFFF;
|
||||||
|
--cd-ink: #1A1918;
|
||||||
|
--cd-dim: #8B867E;
|
||||||
|
|
||||||
|
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
|
||||||
|
--serif-cn: "Noto Serif SC", "Songti SC", "Source Han Serif SC", 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;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
.stage {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
width: 1920px; height: 1080px;
|
||||||
|
transform-origin: center center;
|
||||||
|
background: var(--bg);
|
||||||
|
overflow: hidden;
|
||||||
|
/* Subtle film grain via SVG — 2% opacity */
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse at 20% 30%, rgba(217,119,87,0.025), transparent 50%),
|
||||||
|
radial-gradient(ellipse at 80% 70%, rgba(217,119,87,0.018), transparent 55%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark {
|
||||||
|
position: absolute;
|
||||||
|
top: 40px; left: 48px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--ink);
|
||||||
|
opacity: 0.16;
|
||||||
|
text-transform: uppercase;
|
||||||
|
z-index: 400;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
.watermark.on-light { color: var(--cd-ink); opacity: 0.35; }
|
||||||
|
|
||||||
|
.v2-mark {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40px; right: 48px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--ink);
|
||||||
|
opacity: 0.16;
|
||||||
|
z-index: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Split layout ============ */
|
||||||
|
.split-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 120px; top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 440px;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-block {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: var(--ink-60);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.json-block .k { color: var(--ink-80); }
|
||||||
|
.json-block .s { color: var(--accent); }
|
||||||
|
.json-block .n { color: var(--ink); font-weight: 500; }
|
||||||
|
.json-block .p { color: var(--muted); }
|
||||||
|
|
||||||
|
.json-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pipe arrow from JSON → infographic */
|
||||||
|
.pipe {
|
||||||
|
position: absolute;
|
||||||
|
left: 580px; top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 90px; height: 2px;
|
||||||
|
background: linear-gradient(to right, var(--hairline), var(--accent), var(--hairline));
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.pipe::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: -4px; top: 50%;
|
||||||
|
transform: translateY(-50%) rotate(45deg);
|
||||||
|
width: 8px; height: 8px;
|
||||||
|
border-right: 2px solid var(--accent);
|
||||||
|
border-top: 2px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Infographic (right side) ============ */
|
||||||
|
.infographic {
|
||||||
|
position: absolute;
|
||||||
|
right: 100px; top: 72px;
|
||||||
|
width: 1120px; height: 936px;
|
||||||
|
background: #0A0A0A;
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
padding: 56px 64px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(18px);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ig-masthead {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
border-bottom: 1px solid var(--hairline);
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.ig-masthead .issue {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.ig-masthead .issue .orange { color: var(--accent); }
|
||||||
|
.ig-masthead .dept {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
color: var(--ink-60);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ig-display {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 96px;
|
||||||
|
line-height: 1.0;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
color: var(--ink);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
text-wrap: pretty;
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1, "kern" 1;
|
||||||
|
}
|
||||||
|
.ig-display .en {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--accent);
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1, "swsh" 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ig-deck {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--ink-60);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
margin-bottom: 44px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid of 5 stats */
|
||||||
|
.ig-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.3fr 1fr 1fr 1fr;
|
||||||
|
gap: 32px;
|
||||||
|
margin-bottom: 44px;
|
||||||
|
}
|
||||||
|
.ig-cell {
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
border-top: 2px solid var(--ink);
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
.ig-cell.accent { border-top-color: var(--accent); }
|
||||||
|
.ig-cell .label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.26em;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.ig-cell .label .en {
|
||||||
|
font-family: var(--mono);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.26em;
|
||||||
|
}
|
||||||
|
.ig-cell .big {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 72px;
|
||||||
|
line-height: 0.92;
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
font-variant-numeric: oldstyle-nums proportional-nums;
|
||||||
|
font-feature-settings: "onum" 1, "pnum" 1, "kern" 1;
|
||||||
|
}
|
||||||
|
.ig-cell.accent .big { color: var(--accent); }
|
||||||
|
.ig-cell .big .unit {
|
||||||
|
font-size: 28px;
|
||||||
|
color: var(--ink-60);
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
.ig-cell .sub {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ink-60);
|
||||||
|
line-height: 1.4;
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1;
|
||||||
|
letter-spacing: 0.005em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comparison bars */
|
||||||
|
.ig-bars {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 140px 1fr 80px;
|
||||||
|
gap: 18px 24px;
|
||||||
|
row-gap: 18px;
|
||||||
|
border-top: 1px solid var(--hairline);
|
||||||
|
padding-top: 28px;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.ig-bars .row-label {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--ink-80);
|
||||||
|
letter-spacing: 0.005em;
|
||||||
|
}
|
||||||
|
.ig-bars .row-label.highlight { color: var(--accent); font-weight: 500; }
|
||||||
|
.ig-bars .row-bar {
|
||||||
|
height: 6px;
|
||||||
|
background: var(--hairline);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ig-bars .row-bar .fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0; bottom: 0;
|
||||||
|
background: var(--ink-80);
|
||||||
|
width: 0%;
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
.ig-bars .row-bar .fill.accent { background: var(--accent); }
|
||||||
|
.ig-bars .row-val {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--ink);
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: oldstyle-nums tabular-nums;
|
||||||
|
font-feature-settings: "onum" 1, "tnum" 1;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ig-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40px; left: 64px; right: 64px;
|
||||||
|
display: flex; justify-content: space-between; align-items: baseline;
|
||||||
|
border-top: 1px solid var(--hairline);
|
||||||
|
padding-top: 16px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.ig-footer .folio { color: var(--ink-60); letter-spacing: 0.32em; }
|
||||||
|
|
||||||
|
/* ============ Typography detail zoom ============ */
|
||||||
|
.detail-zoom {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
background: radial-gradient(ellipse at center, #0A0A0A, #000000);
|
||||||
|
z-index: 250;
|
||||||
|
}
|
||||||
|
.detail-word {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 320px;
|
||||||
|
line-height: 0.9;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--ink);
|
||||||
|
/* Enable OpenType ligatures, discretionary ligatures, swashes */
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1, "swsh" 1, "salt" 1, "calt" 1;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
.detail-word .fi {
|
||||||
|
/* fi ligature is default with "liga" */
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.detail-annotation {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% + 170px); left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.detail-annotation .dot {
|
||||||
|
color: var(--accent);
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callout lines pointing to ligature */
|
||||||
|
.callout {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.callout svg { overflow: visible; display: block; }
|
||||||
|
|
||||||
|
/* ============ Brand Reveal ============ */
|
||||||
|
.brand-wall {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--cd-bg);
|
||||||
|
z-index: 300;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.brand-wordmark {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 132px;
|
||||||
|
font-weight: 200;
|
||||||
|
color: var(--cd-ink);
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.92);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1;
|
||||||
|
}
|
||||||
|
.brand-wordmark .dot { color: var(--accent); padding: 0 10px; font-weight: 300; }
|
||||||
|
.brand-underline {
|
||||||
|
margin-top: 28px;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
background: var(--accent);
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
.brand-cn {
|
||||||
|
margin-top: 30px;
|
||||||
|
font-family: var(--serif-cn);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--cd-dim);
|
||||||
|
letter-spacing: 0.4em;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="stage" id="stage">
|
||||||
|
|
||||||
|
<div class="watermark" id="watermark">HUASHU · DESIGN</div>
|
||||||
|
<div class="v2-mark">V2 · 2026</div>
|
||||||
|
|
||||||
|
<!-- Left: JSON data -->
|
||||||
|
<div class="split-left" id="splitLeft" style="opacity:0">
|
||||||
|
<div class="json-label" id="jsonLabel">DATA → benchmarks.json</div>
|
||||||
|
<pre class="json-block" id="jsonBlock"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pipe arrow -->
|
||||||
|
<div class="pipe" id="pipe"></div>
|
||||||
|
|
||||||
|
<!-- Right: Infographic -->
|
||||||
|
<div class="infographic" id="infographic">
|
||||||
|
|
||||||
|
<div class="ig-masthead" id="igMasthead">
|
||||||
|
<div class="issue">Issue № 05 <span class="orange">· AI Benchmarks</span> · Q2 2026</div>
|
||||||
|
<div class="dept">FRONTIER REPORT</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="ig-display" id="igDisplay">
|
||||||
|
The Age of<br>
|
||||||
|
<span class="en">benchmarks</span>.
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="ig-deck" id="igDeck">
|
||||||
|
Five frontier models, five numbers, one uncomfortable truth.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="ig-grid" id="igGrid">
|
||||||
|
<div class="ig-cell accent" data-cell="0">
|
||||||
|
<div class="label">Leader <span class="en">· Q2</span></div>
|
||||||
|
<div class="big">Claude 4.7</div>
|
||||||
|
<div class="sub">Sonnet, 1M ctx · Anthropic</div>
|
||||||
|
</div>
|
||||||
|
<div class="ig-cell" data-cell="1">
|
||||||
|
<div class="label"><span class="en">SWE-bench</span></div>
|
||||||
|
<div class="big">77<span class="unit">.2%</span></div>
|
||||||
|
<div class="sub">coding, verified split</div>
|
||||||
|
</div>
|
||||||
|
<div class="ig-cell" data-cell="2">
|
||||||
|
<div class="label"><span class="en">GPQA</span></div>
|
||||||
|
<div class="big">84<span class="unit">.5</span></div>
|
||||||
|
<div class="sub">diamond, graduate science</div>
|
||||||
|
</div>
|
||||||
|
<div class="ig-cell" data-cell="3">
|
||||||
|
<div class="label">Price <span class="en">· input</span></div>
|
||||||
|
<div class="big">$3<span class="unit">/M</span></div>
|
||||||
|
<div class="sub">per million tokens, typical</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ig-bars" id="igBars">
|
||||||
|
<div class="row-label highlight">Claude 4.7 Sonnet</div>
|
||||||
|
<div class="row-bar"><div class="fill accent" data-w="77.2"></div></div>
|
||||||
|
<div class="row-val">77.2</div>
|
||||||
|
|
||||||
|
<div class="row-label">GPT-5 Turbo</div>
|
||||||
|
<div class="row-bar"><div class="fill" data-w="74.8"></div></div>
|
||||||
|
<div class="row-val">74.8</div>
|
||||||
|
|
||||||
|
<div class="row-label">Gemini 3 Pro</div>
|
||||||
|
<div class="row-bar"><div class="fill" data-w="71.3"></div></div>
|
||||||
|
<div class="row-val">71.3</div>
|
||||||
|
|
||||||
|
<div class="row-label">GLM-5</div>
|
||||||
|
<div class="row-bar"><div class="fill" data-w="68.9"></div></div>
|
||||||
|
<div class="row-val">68.9</div>
|
||||||
|
|
||||||
|
<div class="row-label">Kimi k3</div>
|
||||||
|
<div class="row-bar"><div class="fill" data-w="66.4"></div></div>
|
||||||
|
<div class="row-val">66.4</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ig-footer" id="igFooter">
|
||||||
|
<span>Set in Source Serif 4 & JetBrains Mono</span>
|
||||||
|
<span class="folio">P. 05</span>
|
||||||
|
<span>Data · 2026 Q2, public benchmarks</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detail zoom: Typography ligature -->
|
||||||
|
<div class="detail-zoom" id="detailZoom">
|
||||||
|
<div class="detail-word" id="detailWord">bench<span class="fi">ma</span>rks</div>
|
||||||
|
<div class="callout" id="callout" style="display:none"></div>
|
||||||
|
<div class="detail-annotation" id="detailAnnotation">
|
||||||
|
SOURCE SERIF 4 <span class="dot">·</span> ITALIC <span class="dot">·</span> OLDSTYLE FIGURES
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Brand Reveal -->
|
||||||
|
<div class="brand-wall" id="brandWall">
|
||||||
|
<div class="brand-wordmark" id="brandWord">huashu<span class="dot">·</span>design</div>
|
||||||
|
<div class="brand-underline" id="brandLine"></div>
|
||||||
|
<div class="brand-cn" id="brandCn">D A T A · T Y P O G R A P H Y</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ---------- Scale stage to viewport ----------
|
||||||
|
const stage = document.getElementById('stage');
|
||||||
|
function fitStage() {
|
||||||
|
const s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
|
||||||
|
stage.style.transform = `translate(-50%, -50%) scale(${s})`;
|
||||||
|
}
|
||||||
|
fitStage();
|
||||||
|
window.addEventListener('resize', fitStage);
|
||||||
|
|
||||||
|
// ---------- 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 cubicOut = t => 1 - Math.pow(1 - t, 3);
|
||||||
|
const cubicInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
|
||||||
|
const lerp = (t, a, b, c, d, ease=x=>x) => {
|
||||||
|
if (b === a) return c;
|
||||||
|
const k = Math.max(0, Math.min(1, (t - a) / (b - a)));
|
||||||
|
return c + (d - c) * ease(k);
|
||||||
|
};
|
||||||
|
const seg = (t, a, b) => Math.max(0, Math.min(1, (t - a) / (b - a)));
|
||||||
|
|
||||||
|
// ---------- Refs ----------
|
||||||
|
const splitLeft = document.getElementById('splitLeft');
|
||||||
|
const jsonLabel = document.getElementById('jsonLabel');
|
||||||
|
const jsonBlock = document.getElementById('jsonBlock');
|
||||||
|
const pipe = document.getElementById('pipe');
|
||||||
|
const infographic = document.getElementById('infographic');
|
||||||
|
const igMasthead = document.getElementById('igMasthead');
|
||||||
|
const igDisplay = document.getElementById('igDisplay');
|
||||||
|
const igDeck = document.getElementById('igDeck');
|
||||||
|
const igGrid = document.getElementById('igGrid');
|
||||||
|
const igCells = igGrid.querySelectorAll('.ig-cell');
|
||||||
|
const igBars = document.getElementById('igBars');
|
||||||
|
const igBarFills = igBars.querySelectorAll('.fill');
|
||||||
|
const igFooter = document.getElementById('igFooter');
|
||||||
|
const detailZoom = document.getElementById('detailZoom');
|
||||||
|
const detailWord = document.getElementById('detailWord');
|
||||||
|
const detailAnnotation = document.getElementById('detailAnnotation');
|
||||||
|
const callout = document.getElementById('callout');
|
||||||
|
const brandWall = document.getElementById('brandWall');
|
||||||
|
const brandWord = document.getElementById('brandWord');
|
||||||
|
const brandLine = document.getElementById('brandLine');
|
||||||
|
const brandCn = document.getElementById('brandCn');
|
||||||
|
const watermark = document.getElementById('watermark');
|
||||||
|
|
||||||
|
// ---------- JSON content (for progressive reveal) ----------
|
||||||
|
const jsonRaw = [
|
||||||
|
'{',
|
||||||
|
' "issue": "2026-Q2",',
|
||||||
|
' "leader": "Claude 4.7",',
|
||||||
|
' "models": [',
|
||||||
|
' { "name": "Claude 4.7", "swe": 77.2 },',
|
||||||
|
' { "name": "GPT-5 Turbo", "swe": 74.8 },',
|
||||||
|
' { "name": "Gemini 3 Pro", "swe": 71.3 },',
|
||||||
|
' { "name": "GLM-5", "swe": 68.9 },',
|
||||||
|
' { "name": "Kimi k3", "swe": 66.4 }',
|
||||||
|
' ],',
|
||||||
|
' "gpqa_top": 84.5,',
|
||||||
|
' "price_per_M": 3',
|
||||||
|
'}'
|
||||||
|
];
|
||||||
|
|
||||||
|
function formatJson(lines) {
|
||||||
|
return lines.map(line => {
|
||||||
|
return line
|
||||||
|
.replace(/"([a-zA-Z_]+)":/g, '<span class="k">"$1"</span>:')
|
||||||
|
.replace(/: "([^"]+)"/g, ': <span class="s">"$1"</span>')
|
||||||
|
.replace(/: ([0-9.]+)/g, ': <span class="n">$1</span>')
|
||||||
|
.replace(/([{}\[\],])/g, '<span class="p">$1</span>');
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Timeline ----------
|
||||||
|
const DURATION = 10.0;
|
||||||
|
|
||||||
|
// SFX cue points (played back in ffmpeg post-processing, not browser):
|
||||||
|
// t=0.35 → keyboard/type-fast.mp3 (data entering)
|
||||||
|
// t=2.15 → container/card-snap.mp3 (infographic settles)
|
||||||
|
// t=6.75 → transition/whoosh-fast.mp3 (zoom-in to typography)
|
||||||
|
// t=8.70 → impact/logo-reveal.mp3 (brand reveal chime)
|
||||||
|
const sfxFired = new Set();
|
||||||
|
function fireOnce(key) {
|
||||||
|
if (sfxFired.has(key)) return;
|
||||||
|
sfxFired.add(key);
|
||||||
|
// cue emitted for post-processing; no in-browser playback
|
||||||
|
}
|
||||||
|
|
||||||
|
let startTime = null;
|
||||||
|
let raf;
|
||||||
|
|
||||||
|
function tick(now) {
|
||||||
|
if (startTime == null) startTime = now;
|
||||||
|
const t = (now - startTime) / 1000;
|
||||||
|
|
||||||
|
// ── Beat 1: 0-2s · JSON data appears, types in ─────────
|
||||||
|
// JSON label fade in
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 0.15, 0.55));
|
||||||
|
jsonLabel.style.opacity = k;
|
||||||
|
splitLeft.style.opacity = '1';
|
||||||
|
}
|
||||||
|
// Progressive type-reveal: reveal N lines of JSON by time
|
||||||
|
{
|
||||||
|
const totalLines = jsonRaw.length;
|
||||||
|
const k = seg(t, 0.3, 1.9);
|
||||||
|
const linesShown = Math.floor(k * totalLines);
|
||||||
|
const shown = jsonRaw.slice(0, Math.max(0, linesShown));
|
||||||
|
jsonBlock.innerHTML = formatJson(shown);
|
||||||
|
if (linesShown >= 3 && t < 1.9) fireOnce('datain');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pipe arrow (1.8 → 2.2) ─────────────────────────────
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 1.8, 2.2));
|
||||||
|
pipe.style.opacity = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Beat 2a: 2.0-3.2s · Infographic canvas arrives ─────
|
||||||
|
{
|
||||||
|
const k = expoOut(seg(t, 2.0, 2.8));
|
||||||
|
infographic.style.opacity = k;
|
||||||
|
infographic.style.transform = `translateY(${lerp(t, 2.0, 2.8, 18, 0, expoOut)}px)`;
|
||||||
|
if (t > 2.1) fireOnce('settle');
|
||||||
|
}
|
||||||
|
// Masthead
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 2.6, 3.1));
|
||||||
|
igMasthead.style.opacity = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Beat 2b: 3.0-4.2s · Display headline appears ──────
|
||||||
|
{
|
||||||
|
const k = expoOut(seg(t, 3.0, 3.8));
|
||||||
|
igDisplay.style.opacity = k;
|
||||||
|
igDisplay.style.transform = `translateY(${lerp(t, 3.0, 3.8, 16, 0, expoOut)}px)`;
|
||||||
|
}
|
||||||
|
// Deck line (italic)
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 3.6, 4.2));
|
||||||
|
igDeck.style.opacity = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Beat 2c: 4.0-5.2s · Grid cells (ripple, 4 cells) ──
|
||||||
|
igCells.forEach((cell, i) => {
|
||||||
|
const start = 4.0 + i * 0.12;
|
||||||
|
const end = start + 0.5;
|
||||||
|
const k = expoOut(seg(t, start, end));
|
||||||
|
cell.style.opacity = k;
|
||||||
|
cell.style.transform = `translateY(${lerp(t, start, end, 14, 0, expoOut)}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Beat 2d: 5.2-6.4s · Comparison bars grow ─────────
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 5.1, 5.4));
|
||||||
|
igBars.style.opacity = k;
|
||||||
|
}
|
||||||
|
igBarFills.forEach((fill, i) => {
|
||||||
|
const start = 5.3 + i * 0.08;
|
||||||
|
const end = start + 0.7;
|
||||||
|
const w = parseFloat(fill.getAttribute('data-w'));
|
||||||
|
const pct = lerp(t, start, end, 0, w, expoOut);
|
||||||
|
fill.style.width = pct + '%';
|
||||||
|
});
|
||||||
|
// Footer
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 6.0, 6.6));
|
||||||
|
igFooter.style.opacity = k * 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Beat 2e: 6.6-8.2s · Zoom to typography detail ────
|
||||||
|
if (t >= 6.6 && t < 8.3) {
|
||||||
|
const k = expoOut(seg(t, 6.6, 7.4));
|
||||||
|
// Infographic scales up and fades — simulate push-in
|
||||||
|
const scale = lerp(t, 6.6, 7.4, 1, 3.4, expoOut);
|
||||||
|
const ty = lerp(t, 6.6, 7.4, 0, -140, expoOut);
|
||||||
|
infographic.style.transform = `translateY(${ty}px) scale(${scale})`;
|
||||||
|
infographic.style.opacity = String(1 - k * 0.85);
|
||||||
|
splitLeft.style.opacity = String(1 - k);
|
||||||
|
pipe.style.opacity = String(1 - k);
|
||||||
|
|
||||||
|
// Detail zoom fades in
|
||||||
|
const k2 = expoOut(seg(t, 7.0, 7.7));
|
||||||
|
detailZoom.style.opacity = k2;
|
||||||
|
// Word subtle scale-in (starts from 0.96)
|
||||||
|
detailWord.style.transform = `scale(${lerp(t, 7.0, 7.9, 0.96, 1.0, expoOut)})`;
|
||||||
|
|
||||||
|
// SFX at 6.7
|
||||||
|
if (t > 6.7) fireOnce('zoom');
|
||||||
|
|
||||||
|
// Callout + annotation (7.5 → 8.1)
|
||||||
|
const k3 = cubicOut(seg(t, 7.6, 8.1));
|
||||||
|
callout.style.opacity = k3;
|
||||||
|
detailAnnotation.style.opacity = k3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Beat 3: 8.2-10s · Brand reveal ───────────────────
|
||||||
|
// Detail zoom fades under brand wall
|
||||||
|
if (t >= 8.1) {
|
||||||
|
const k = cubicOut(seg(t, 8.1, 8.5));
|
||||||
|
detailZoom.style.opacity = String(Math.max(0, 1 - k));
|
||||||
|
}
|
||||||
|
// Brand wall slides up from bottom
|
||||||
|
{
|
||||||
|
const k = expoOut(seg(t, 8.1, 8.7));
|
||||||
|
brandWall.style.transform = `translateY(${lerp(t, 8.1, 8.7, 100, 0, expoOut)}%)`;
|
||||||
|
brandWall.style.opacity = k > 0 ? '1' : '0';
|
||||||
|
if (k > 0.55) watermark.classList.add('on-light');
|
||||||
|
else watermark.classList.remove('on-light');
|
||||||
|
}
|
||||||
|
// Wordmark
|
||||||
|
{
|
||||||
|
const k = expoOut(seg(t, 8.6, 9.2));
|
||||||
|
brandWord.style.opacity = k;
|
||||||
|
brandWord.style.transform = `scale(${lerp(t, 8.6, 9.2, 0.92, 1.0, expoOut)})`;
|
||||||
|
if (t > 8.65) fireOnce('chime');
|
||||||
|
}
|
||||||
|
// Underline
|
||||||
|
{
|
||||||
|
const k = expoOut(seg(t, 9.0, 9.6));
|
||||||
|
brandLine.style.width = (280 * k) + 'px';
|
||||||
|
}
|
||||||
|
// CN tagline
|
||||||
|
{
|
||||||
|
const k = cubicOut(seg(t, 9.3, 9.9));
|
||||||
|
brandCn.style.opacity = k * 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop / hold
|
||||||
|
if (t < DURATION) {
|
||||||
|
raf = requestAnimationFrame(tick);
|
||||||
|
} else {
|
||||||
|
if (!window.__recording) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Reset
|
||||||
|
startTime = null;
|
||||||
|
sfxFired.clear();
|
||||||
|
jsonBlock.innerHTML = '';
|
||||||
|
splitLeft.style.opacity = '0';
|
||||||
|
pipe.style.opacity = '0';
|
||||||
|
infographic.style.opacity = '0';
|
||||||
|
infographic.style.transform = 'translateY(18px) scale(1)';
|
||||||
|
igMasthead.style.opacity = '0';
|
||||||
|
igDisplay.style.opacity = '0';
|
||||||
|
igDeck.style.opacity = '0';
|
||||||
|
igBars.style.opacity = '0';
|
||||||
|
igFooter.style.opacity = '0';
|
||||||
|
igCells.forEach(c => { c.style.opacity = '0'; });
|
||||||
|
igBarFills.forEach(f => { f.style.width = '0%'; });
|
||||||
|
detailZoom.style.opacity = '0';
|
||||||
|
callout.style.opacity = '0';
|
||||||
|
detailAnnotation.style.opacity = '0';
|
||||||
|
brandWall.style.transform = 'translateY(100%)';
|
||||||
|
brandWall.style.opacity = '0';
|
||||||
|
brandWord.style.opacity = '0';
|
||||||
|
brandLine.style.width = '0';
|
||||||
|
brandCn.style.opacity = '0';
|
||||||
|
watermark.classList.remove('on-light');
|
||||||
|
raf = requestAnimationFrame(tick);
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__seek = function(s) {
|
||||||
|
startTime = performance.now() - s * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for fonts, then start
|
||||||
|
(document.fonts ? document.fonts.ready : Promise.resolve()).then(() => {
|
||||||
|
requestAnimationFrame((now) => {
|
||||||
|
startTime = now;
|
||||||
|
window.__ready = true;
|
||||||
|
raf = requestAnimationFrame(tick);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+795
-749
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,885 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>c6 · Five Axes · One Punch List</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;1,8..60,300..700&family=Noto+Serif+SC:wght@200;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">
|
||||||
|
<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-panel: #FFFFFF;
|
||||||
|
--cd-ink: #1A1918;
|
||||||
|
|
||||||
|
--serif-zh: "Noto Serif SC", "Songti SC", serif;
|
||||||
|
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
|
||||||
|
--sans: "Inter", -apple-system, "PingFang SC", "HarmonyOS Sans 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Film grain */
|
||||||
|
.stage::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
|
||||||
|
opacity: 0.02;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome */
|
||||||
|
.mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; left: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
.mark-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; right: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
.title-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 108px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main composition: camera wrapper for push-in at Beat 3 */
|
||||||
|
.camera {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
transform-origin: 1000px 940px; /* center of Fix first-row */
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ LEFT: under-review artwork ============ */
|
||||||
|
.subject {
|
||||||
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
|
top: 310px;
|
||||||
|
width: 640px;
|
||||||
|
height: 460px;
|
||||||
|
background: #0B0B0B;
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform, filter;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
.subject::after {
|
||||||
|
/* subtle inner vignette */
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
box-shadow: inset 0 0 120px rgba(0,0,0,0.6);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.subject-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
top: 18px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
color: var(--muted);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.subject-dot {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 18px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 3;
|
||||||
|
box-shadow: 0 0 10px rgba(217,119,87,0.6);
|
||||||
|
}
|
||||||
|
/* Subject wireframe: abstract design mockup */
|
||||||
|
.subject-canvas {
|
||||||
|
position: absolute;
|
||||||
|
inset: 50px 36px 36px;
|
||||||
|
}
|
||||||
|
.wf-h1 {
|
||||||
|
width: 62%;
|
||||||
|
height: 18px;
|
||||||
|
background: rgba(255,255,255,0.28);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.wf-h2 {
|
||||||
|
width: 38%;
|
||||||
|
height: 10px;
|
||||||
|
background: rgba(255,255,255,0.14);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.wf-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.wf-row .bar {
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(255,255,255,0.10);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.wf-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
.wf-card {
|
||||||
|
height: 82px;
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
border: 1px solid rgba(255,255,255,0.06);
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.wf-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 12px; top: 14px;
|
||||||
|
width: 40%;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(255,255,255,0.22);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.wf-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 12px; bottom: 16px;
|
||||||
|
width: 64%;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(255,255,255,0.10);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.wf-card.accent { border-color: rgba(217,119,87,0.55); background: rgba(217,119,87,0.06); }
|
||||||
|
.wf-card.accent::before { background: var(--accent); }
|
||||||
|
.wf-foot {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.wf-chip {
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 11px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 68px;
|
||||||
|
}
|
||||||
|
.wf-chip.wide { width: 120px; }
|
||||||
|
|
||||||
|
/* ============ Light sweep ============ */
|
||||||
|
.sweep {
|
||||||
|
position: absolute;
|
||||||
|
left: 130px;
|
||||||
|
top: 250px;
|
||||||
|
width: 680px;
|
||||||
|
height: 140px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(217,119,87,0) 0%,
|
||||||
|
rgba(217,119,87,0.12) 20%,
|
||||||
|
rgba(255,220,200,0.62) 50%,
|
||||||
|
rgba(217,119,87,0.18) 80%,
|
||||||
|
rgba(217,119,87,0) 100%);
|
||||||
|
filter: blur(14px);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 4;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.sweep-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
|
top: 310px;
|
||||||
|
width: 640px;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(255,220,200,0.2) 10%,
|
||||||
|
rgba(255,220,200,0.9) 50%,
|
||||||
|
rgba(255,220,200,0.2) 90%,
|
||||||
|
transparent 100%);
|
||||||
|
filter: blur(0.6px);
|
||||||
|
box-shadow: 0 0 14px rgba(217,119,87,0.8), 0 0 30px rgba(217,119,87,0.3);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 6;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ RIGHT: radar chart ============ */
|
||||||
|
.radar-wrap {
|
||||||
|
position: absolute;
|
||||||
|
right: 280px;
|
||||||
|
top: 200px;
|
||||||
|
width: 520px;
|
||||||
|
height: 520px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.radar-wrap svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.radar-grid path {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255,255,255,0.10);
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
.radar-spoke {
|
||||||
|
stroke: rgba(255,255,255,0.08);
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
.radar-poly {
|
||||||
|
fill: rgba(217,119,87,0.16);
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.radar-point {
|
||||||
|
fill: var(--accent);
|
||||||
|
stroke: #1A1918;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
.radar-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
fill: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.radar-label-zh {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
fill: var(--ink);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
.radar-score {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
fill: var(--accent);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radar-title {
|
||||||
|
position: absolute;
|
||||||
|
right: 280px;
|
||||||
|
top: 160px;
|
||||||
|
width: 520px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.radar-score-total {
|
||||||
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
|
top: 170px;
|
||||||
|
width: 640px;
|
||||||
|
text-align: left;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.radar-score-total .score-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.radar-score-total .score-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.radar-score-total .score-num {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 72px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.radar-score-total .score-num .accent { color: var(--accent); }
|
||||||
|
.radar-score-total .score-total {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Single Fix row (Concept Card lean) ============ */
|
||||||
|
.fix-lane {
|
||||||
|
position: absolute;
|
||||||
|
left: 150px;
|
||||||
|
bottom: 120px;
|
||||||
|
width: 1620px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.fix-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--hairline);
|
||||||
|
}
|
||||||
|
.fix-mark {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.fix-zh {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.fix-count {
|
||||||
|
margin-left: auto;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-row {
|
||||||
|
position: relative;
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--ink);
|
||||||
|
line-height: 1.45;
|
||||||
|
padding: 12px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.fix-row .idx {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
flex: 0 0 40px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
.fix-row .mono {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 26px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.fix-row .arrow {
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-severity {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid rgba(217,119,87,0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
vertical-align: 3px;
|
||||||
|
}
|
||||||
|
.fix-pulse {
|
||||||
|
position: absolute;
|
||||||
|
inset: 4px -12px 4px -12px;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity;
|
||||||
|
box-shadow: 0 0 24px rgba(217,119,87,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Brand Reveal (hero-v10 signature) ============ */
|
||||||
|
.stage-dimmer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: #000000;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 40;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.brand-panel {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: #F5F4F0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 50;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.brand-wordmark {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 72px;
|
||||||
|
font-weight: 100;
|
||||||
|
font-variation-settings: "wght" 100;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: #1A1918;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
will-change: opacity, transform, font-variation-settings, font-weight;
|
||||||
|
}
|
||||||
|
.brand-wordmark .accent { color: #D97757; font-weight: inherit; }
|
||||||
|
.brand-line {
|
||||||
|
margin-top: 60px;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
background: #D97757;
|
||||||
|
align-self: center;
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stage" id="stage">
|
||||||
|
<div class="mark">HUASHU · DESIGN</div>
|
||||||
|
<div class="mark-right">V2 · 2026</div>
|
||||||
|
|
||||||
|
<div class="title-line" id="titleLine">c6 · Expert Review · Five Axes</div>
|
||||||
|
|
||||||
|
<div class="camera" id="camera">
|
||||||
|
<!-- Subject: design under review -->
|
||||||
|
<div class="subject" id="subject">
|
||||||
|
<div class="subject-label">SUBJECT · DRAFT_V3</div>
|
||||||
|
<div class="subject-dot"></div>
|
||||||
|
<div class="subject-canvas">
|
||||||
|
<div class="wf-h1"></div>
|
||||||
|
<div class="wf-h2"></div>
|
||||||
|
<div class="wf-row"><div class="bar" style="width:24%"></div><div class="bar" style="width:14%"></div><div class="bar" style="width:20%"></div></div>
|
||||||
|
<div class="wf-row"><div class="bar" style="width:30%"></div><div class="bar" style="width:10%"></div></div>
|
||||||
|
<div class="wf-grid">
|
||||||
|
<div class="wf-card"></div>
|
||||||
|
<div class="wf-card accent"></div>
|
||||||
|
<div class="wf-card"></div>
|
||||||
|
</div>
|
||||||
|
<div class="wf-foot">
|
||||||
|
<div class="wf-chip wide"></div>
|
||||||
|
<div class="wf-chip"></div>
|
||||||
|
<div class="wf-chip"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scanning light -->
|
||||||
|
<div class="sweep" id="sweep"></div>
|
||||||
|
<div class="sweep-line" id="sweepLine"></div>
|
||||||
|
|
||||||
|
<!-- Radar chart (right) -->
|
||||||
|
<div class="radar-title" id="radarTitle">Five-Axis Diagnosis · Radar</div>
|
||||||
|
<div class="radar-wrap" id="radarWrap">
|
||||||
|
<svg viewBox="-270 -270 540 540" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Grid rings (5 levels) -->
|
||||||
|
<g class="radar-grid" id="radarGrid"></g>
|
||||||
|
<!-- Spokes to 5 axes -->
|
||||||
|
<g id="radarSpokes"></g>
|
||||||
|
<!-- Filled polygon -->
|
||||||
|
<polygon id="radarPoly" class="radar-poly" points="" />
|
||||||
|
<!-- Points -->
|
||||||
|
<g id="radarPoints"></g>
|
||||||
|
<!-- Axis labels -->
|
||||||
|
<g id="radarLabels"></g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="radar-score-total" id="radarTotal">
|
||||||
|
<div class="score-row">
|
||||||
|
<div class="score-num"><span id="scoreNum">0</span><span class="accent">/50</span></div>
|
||||||
|
<div>
|
||||||
|
<div class="score-label">OVERALL · PASSED</div>
|
||||||
|
<div class="score-total">WEIGHTED · 7.4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Single Fix row: Concept Card lean -->
|
||||||
|
<div class="fix-lane" id="fixLane">
|
||||||
|
<div class="fix-head">
|
||||||
|
<span class="fix-mark">FIX</span>
|
||||||
|
<span class="fix-zh">Fix</span>
|
||||||
|
<span class="fix-count">01 / 01</span>
|
||||||
|
</div>
|
||||||
|
<div class="fix-row">
|
||||||
|
<span class="idx">01</span>
|
||||||
|
<span><span class="fix-severity">⚡</span>Tracking <span class="mono">0.02</span><span class="arrow"> → </span><span class="mono">0.04em</span></span>
|
||||||
|
<div class="fix-pulse" id="fixPulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Brand Reveal (hero-v10 signature) -->
|
||||||
|
<div class="stage-dimmer" id="stageDimmer"></div>
|
||||||
|
<div class="brand-panel" id="brandPanel">
|
||||||
|
<div class="brand-wordmark" id="brandMark">huashu<span class="accent">-</span>design</div>
|
||||||
|
<div class="brand-line" id="brandLine"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Auto-scale
|
||||||
|
function fitStage() {
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
|
fitStage();
|
||||||
|
window.addEventListener('resize', fitStage);
|
||||||
|
|
||||||
|
// Easings
|
||||||
|
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);
|
||||||
|
|
||||||
|
function lerp(t, a, b, easing) {
|
||||||
|
if (t <= 0) return a;
|
||||||
|
if (t >= 1) return b;
|
||||||
|
const e = easing ? easing(t) : t;
|
||||||
|
return a + (b - a) * e;
|
||||||
|
}
|
||||||
|
function seg(time, start, end) {
|
||||||
|
if (time <= start) return 0;
|
||||||
|
if (time >= end) return 1;
|
||||||
|
return (time - start) / (end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ Build radar SVG ============
|
||||||
|
const RADIUS = 210;
|
||||||
|
const AXES = [
|
||||||
|
{ zh: 'Philosophy', en: 'PHILOSOPHY', score: 8 },
|
||||||
|
{ zh: 'Hierarchy', en: 'HIERARCHY', score: 6 },
|
||||||
|
{ zh: 'Execution', en: 'EXECUTION', score: 8 },
|
||||||
|
{ zh: 'Function', en: 'FUNCTION', score: 7 },
|
||||||
|
{ zh: 'Innovation', en: 'INNOVATION', score: 8 },
|
||||||
|
];
|
||||||
|
const N = AXES.length;
|
||||||
|
|
||||||
|
function axisPoint(i, r) {
|
||||||
|
// Start at top (-90deg), clockwise
|
||||||
|
const angle = -Math.PI / 2 + (2 * Math.PI * i) / N;
|
||||||
|
return [Math.cos(angle) * r, Math.sin(angle) * r];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid rings (polygons at 5 levels)
|
||||||
|
const gridG = document.getElementById('radarGrid');
|
||||||
|
for (let level = 1; level <= 5; level++) {
|
||||||
|
const r = (RADIUS * level) / 5;
|
||||||
|
const pts = [];
|
||||||
|
for (let i = 0; i < N; i++) {
|
||||||
|
const [x, y] = axisPoint(i, r);
|
||||||
|
pts.push(`${x.toFixed(2)},${y.toFixed(2)}`);
|
||||||
|
}
|
||||||
|
const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
||||||
|
poly.setAttribute('points', pts.join(' '));
|
||||||
|
poly.setAttribute('fill', 'none');
|
||||||
|
poly.setAttribute('stroke', level === 5 ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.07)');
|
||||||
|
poly.setAttribute('stroke-width', '1');
|
||||||
|
gridG.appendChild(poly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spokes
|
||||||
|
const spokesG = document.getElementById('radarSpokes');
|
||||||
|
for (let i = 0; i < N; i++) {
|
||||||
|
const [x, y] = axisPoint(i, RADIUS);
|
||||||
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||||
|
line.setAttribute('x1', 0);
|
||||||
|
line.setAttribute('y1', 0);
|
||||||
|
line.setAttribute('x2', x.toFixed(2));
|
||||||
|
line.setAttribute('y2', y.toFixed(2));
|
||||||
|
line.setAttribute('class', 'radar-spoke');
|
||||||
|
spokesG.appendChild(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels (position outside). ZH sits at a base radial distance; EN stacks
|
||||||
|
// below it with a fixed vertical offset to avoid overlap on the side axes.
|
||||||
|
const labelsG = document.getElementById('radarLabels');
|
||||||
|
AXES.forEach((axis, i) => {
|
||||||
|
const angle = -Math.PI / 2 + (2 * Math.PI * i) / N;
|
||||||
|
const dirX = Math.cos(angle);
|
||||||
|
const dirY = Math.sin(angle);
|
||||||
|
|
||||||
|
// text-anchor based on horizontal direction
|
||||||
|
let anchor = 'middle';
|
||||||
|
if (dirX > 0.3) anchor = 'start';
|
||||||
|
else if (dirX < -0.3) anchor = 'end';
|
||||||
|
|
||||||
|
const baseRadial = RADIUS + 36;
|
||||||
|
const [bx, by] = axisPoint(i, baseRadial);
|
||||||
|
|
||||||
|
// Title Case serif italic label (only one per axis in EN)
|
||||||
|
const zhText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
|
zhText.setAttribute('x', bx.toFixed(2));
|
||||||
|
zhText.setAttribute('y', by.toFixed(2));
|
||||||
|
zhText.setAttribute('text-anchor', anchor);
|
||||||
|
zhText.setAttribute('dominant-baseline', 'middle');
|
||||||
|
zhText.setAttribute('class', 'radar-label-zh');
|
||||||
|
zhText.textContent = axis.zh;
|
||||||
|
labelsG.appendChild(zhText);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Points (initial: center)
|
||||||
|
const pointsG = document.getElementById('radarPoints');
|
||||||
|
const pointEls = AXES.map((axis, i) => {
|
||||||
|
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||||
|
circle.setAttribute('cx', 0);
|
||||||
|
circle.setAttribute('cy', 0);
|
||||||
|
circle.setAttribute('r', 5);
|
||||||
|
circle.setAttribute('class', 'radar-point');
|
||||||
|
circle.setAttribute('opacity', '0');
|
||||||
|
pointsG.appendChild(circle);
|
||||||
|
return circle;
|
||||||
|
});
|
||||||
|
const radarPoly = document.getElementById('radarPoly');
|
||||||
|
|
||||||
|
// ============ Timeline (10s) ============
|
||||||
|
// Beat 1 (0-2s): title + subject enters
|
||||||
|
// Beat 2 (2-8s):
|
||||||
|
// 2.0-3.8: light sweep top → bottom (1.8s)
|
||||||
|
// 3.2-4.8: radar grid fades in + polygon + points grow from center
|
||||||
|
// 4.8-5.2: score count up
|
||||||
|
// 5.0-6.0: Keep col ripple in
|
||||||
|
// 5.5-6.5: Fix col ripple in
|
||||||
|
// 6.0-7.0: Quick Wins col ripple in
|
||||||
|
// 7.0-8.0: hold
|
||||||
|
// Beat 3 (8-10s): push-in camera to fix[0] + pulse (8-9), brand reveal (8.0-10.0)
|
||||||
|
|
||||||
|
const titleLine = document.getElementById('titleLine');
|
||||||
|
const subject = document.getElementById('subject');
|
||||||
|
const sweep = document.getElementById('sweep');
|
||||||
|
const sweepLine = document.getElementById('sweepLine');
|
||||||
|
const radarTitle = document.getElementById('radarTitle');
|
||||||
|
const radarWrap = document.getElementById('radarWrap');
|
||||||
|
const radarTotal = document.getElementById('radarTotal');
|
||||||
|
const scoreNum = document.getElementById('scoreNum');
|
||||||
|
const fixLane = document.getElementById('fixLane');
|
||||||
|
const fixPulse = document.getElementById('fixPulse');
|
||||||
|
const camera = document.getElementById('camera');
|
||||||
|
const stageDimmer = document.getElementById('stageDimmer');
|
||||||
|
const brandPanel = document.getElementById('brandPanel');
|
||||||
|
const brandMark = document.getElementById('brandMark');
|
||||||
|
const brandLine = document.getElementById('brandLine');
|
||||||
|
|
||||||
|
const DURATION = 10.0;
|
||||||
|
let startTime = null;
|
||||||
|
let loop = true;
|
||||||
|
if (window.__recording === true) loop = false;
|
||||||
|
|
||||||
|
function tick(now) {
|
||||||
|
if (startTime === null) startTime = now;
|
||||||
|
let t = (now - startTime) / 1000;
|
||||||
|
|
||||||
|
if (t >= DURATION) {
|
||||||
|
if (loop) { startTime = now; t = 0; }
|
||||||
|
else { t = DURATION; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title fade in/out
|
||||||
|
const titleIn = seg(t, 0.2, 1.2);
|
||||||
|
const titleOut = seg(t, 7.6, 8.0);
|
||||||
|
titleLine.style.opacity = Math.min(cubicOut(titleIn), 1 - titleOut);
|
||||||
|
titleLine.style.transform = `translateX(-50%) translateY(${lerp(titleIn, -6, 0, cubicOut)}px)`;
|
||||||
|
|
||||||
|
// Subject appears Beat 1
|
||||||
|
const subjectIn = seg(t, 0.4, 1.8);
|
||||||
|
subject.style.opacity = expoOut(subjectIn);
|
||||||
|
subject.style.transform = `translateY(${lerp(subjectIn, 14, 0, expoOut)}px)`;
|
||||||
|
|
||||||
|
// Subject dims after sweep completes (during Beat 2 to keep focus right)
|
||||||
|
const subjectDim = seg(t, 4.4, 5.6);
|
||||||
|
const dimFactor = lerp(subjectDim, 1.0, 0.38, cubicInOut);
|
||||||
|
subject.style.filter = `saturate(${lerp(subjectDim, 1.0, 0.5, cubicInOut)}) brightness(${dimFactor})`;
|
||||||
|
|
||||||
|
// Light sweep: 2.0-3.8 top to bottom
|
||||||
|
const sweepProgress = seg(t, 2.0, 3.8);
|
||||||
|
const sweepOp = (t < 2.0 || t > 4.2) ? 0 :
|
||||||
|
(t < 2.2 ? seg(t, 2.0, 2.2) :
|
||||||
|
t < 3.7 ? 1 :
|
||||||
|
1 - seg(t, 3.7, 4.2));
|
||||||
|
sweep.style.opacity = sweepOp * 0.95;
|
||||||
|
sweepLine.style.opacity = sweepOp * 1.0;
|
||||||
|
// Move from y=250 to y=700 (subject top 310 to bottom 770)
|
||||||
|
const sweepY = lerp(sweepProgress, -70, 410, cubicInOut);
|
||||||
|
sweep.style.transform = `translateY(${sweepY}px)`;
|
||||||
|
sweepLine.style.transform = `translateY(${sweepY + 70}px)`;
|
||||||
|
|
||||||
|
// Radar title + wrap appear 3.2
|
||||||
|
const radarIn = seg(t, 3.2, 4.0);
|
||||||
|
radarTitle.style.opacity = cubicOut(radarIn);
|
||||||
|
radarWrap.style.opacity = cubicOut(radarIn);
|
||||||
|
radarWrap.style.transform = `scale(${lerp(radarIn, 0.92, 1.0, expoOut)})`;
|
||||||
|
|
||||||
|
// Radar grid strokes already visible once wrap fades; animate grid via stroke-dasharray trick would be overkill.
|
||||||
|
// Instead, grow polygon + points from center (3.6-4.8)
|
||||||
|
const polyGrow = seg(t, 3.6, 4.8);
|
||||||
|
const polyT = expoOut(polyGrow);
|
||||||
|
const polyPts = [];
|
||||||
|
AXES.forEach((axis, i) => {
|
||||||
|
const targetR = (axis.score / 10) * RADIUS;
|
||||||
|
const r = targetR * polyT;
|
||||||
|
const [x, y] = axisPoint(i, r);
|
||||||
|
polyPts.push(`${x.toFixed(2)},${y.toFixed(2)}`);
|
||||||
|
const pt = pointEls[i];
|
||||||
|
pt.setAttribute('cx', x.toFixed(2));
|
||||||
|
pt.setAttribute('cy', y.toFixed(2));
|
||||||
|
pt.setAttribute('opacity', polyT.toFixed(2));
|
||||||
|
});
|
||||||
|
radarPoly.setAttribute('points', polyPts.join(' '));
|
||||||
|
|
||||||
|
// EN labels fade in slightly later
|
||||||
|
const enLabelIn = seg(t, 4.2, 4.8);
|
||||||
|
document.querySelectorAll('[data-type="en-label"]').forEach(el => {
|
||||||
|
el.setAttribute('opacity', cubicOut(enLabelIn).toFixed(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Score count up 4.6-5.4, target total = 37
|
||||||
|
const scoreT = seg(t, 4.6, 5.4);
|
||||||
|
const total = AXES.reduce((s, a) => s + a.score, 0); // 37
|
||||||
|
const shown = Math.round(lerp(scoreT, 0, total, cubicOut));
|
||||||
|
scoreNum.textContent = shown;
|
||||||
|
radarTotal.style.opacity = cubicOut(seg(t, 4.4, 5.0));
|
||||||
|
|
||||||
|
// Fix lane ripple in (5.3-6.1)
|
||||||
|
const fixRip = seg(t, 5.3, 6.1);
|
||||||
|
fixLane.style.opacity = expoOut(fixRip);
|
||||||
|
fixLane.style.transform = `translateY(${lerp(fixRip, 24, 0, expoOut)}px)`;
|
||||||
|
|
||||||
|
// Beat 3: Push-in camera to Fix row + pulse (7.4-8.0)
|
||||||
|
const pushT = seg(t, 7.4, 8.0);
|
||||||
|
const scale = lerp(pushT, 1.0, 1.18, cubicInOut);
|
||||||
|
camera.style.transform = `scale(${scale})`;
|
||||||
|
|
||||||
|
// Fix pulse border: blink 2 times between 7.6-8.0
|
||||||
|
const pulseOp = t < 7.6 ? 0 :
|
||||||
|
t < 8.0 ? (0.4 + 0.6 * Math.abs(Math.sin((t - 7.6) * Math.PI * 2.4))) :
|
||||||
|
0;
|
||||||
|
fixPulse.style.opacity = pulseOp;
|
||||||
|
|
||||||
|
// ============ Brand Reveal (hero-v10 signature, aligned) ============
|
||||||
|
// [T-2.0 → T-1.7s] i.e. 8.0-8.3: scene fade to black (0.3s)
|
||||||
|
const soK = seg(t, 8.0, 8.3);
|
||||||
|
stageDimmer.style.opacity = cubicOut(soK);
|
||||||
|
const sceneFade = seg(t, 8.0, 8.3);
|
||||||
|
camera.style.opacity = 1 - cubicOut(sceneFade);
|
||||||
|
|
||||||
|
// [T-1.7 → T-1.3s] i.e. 8.3-8.7: cream panel slides from bottom (0.4s, expoOut)
|
||||||
|
const panelT = seg(t, 8.3, 8.7);
|
||||||
|
const panelY = lerp(panelT, 100, 0, expoOut);
|
||||||
|
brandPanel.style.transform = `translateY(${panelY}%)`;
|
||||||
|
|
||||||
|
// [T-1.3 → T-0.7s] i.e. 8.7-9.3: wordmark wght 100→500 + y 20→0 + opacity 0→1 (0.6s)
|
||||||
|
const markT = seg(t, 8.7, 9.3);
|
||||||
|
const markE = expoOut(markT);
|
||||||
|
const wght = 100 + (500 - 100) * markE;
|
||||||
|
brandMark.style.opacity = markE;
|
||||||
|
brandMark.style.transform = `translateY(${20 * (1 - markE)}px)`;
|
||||||
|
brandMark.style.fontWeight = Math.round(wght);
|
||||||
|
brandMark.style.fontVariationSettings = `"wght" ${wght.toFixed(0)}`;
|
||||||
|
|
||||||
|
// [T-0.7 → T-0.3s] i.e. 9.3-9.7: orange line width 0→280 (0.4s, cubicOut)
|
||||||
|
const lineT = seg(t, 9.3, 9.7);
|
||||||
|
brandLine.style.width = `${lerp(lineT, 0, 280, cubicOut)}px`;
|
||||||
|
|
||||||
|
// [T-0.3 → T] hold
|
||||||
|
|
||||||
|
if (!window.__ready) window.__ready = true;
|
||||||
|
|
||||||
|
if (loop || t < DURATION) requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
(document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve())
|
||||||
|
.then(() => requestAnimationFrame(tick));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+850
-608
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,684 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>w1 · Brand Protocol · Five steps, no skipping</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;1,8..60,300..700&family=Noto+Serif+SC:wght@200;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">
|
||||||
|
<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-panel: #FFFFFF;
|
||||||
|
--cd-ink: #1A1918;
|
||||||
|
|
||||||
|
--serif-zh: "Noto Serif SC", "Songti SC", serif;
|
||||||
|
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
|
||||||
|
--sans: "Inter", -apple-system, "PingFang SC", "HarmonyOS Sans 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Film grain texture (very subtle) */
|
||||||
|
.stage::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
|
||||||
|
opacity: 0.02;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome · watermark */
|
||||||
|
.mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; left: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
.mark-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; right: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Title (centered, small, top) ====== */
|
||||||
|
.title-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 128px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Chain · 5 cards connected by a line ====== */
|
||||||
|
.chain {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 1680px;
|
||||||
|
height: 360px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The connecting line behind the cards */
|
||||||
|
.chain-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 140px;
|
||||||
|
right: 140px;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(217,119,87,0.0) 2%,
|
||||||
|
rgba(217,119,87,0.8) 12%,
|
||||||
|
rgba(217,119,87,0.8) 88%,
|
||||||
|
rgba(217,119,87,0.0) 98%,
|
||||||
|
transparent 100%);
|
||||||
|
transform-origin: left center;
|
||||||
|
transform: scaleX(0);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
width: 248px;
|
||||||
|
height: 320px;
|
||||||
|
background: rgba(255,255,255,0.02);
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32px 20px 26px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.active {
|
||||||
|
border-color: rgba(217,119,87,0.6);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(217,119,87,0.35),
|
||||||
|
0 30px 60px -30px rgba(217,119,87,0.35),
|
||||||
|
0 10px 24px -10px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.card.active .card-num {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-glyph {
|
||||||
|
width: 88px;
|
||||||
|
height: 88px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-label {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.card-label .zh {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 36px;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glyph · Step 1 · Ask (question mark inside a circle, drawn minimal) */
|
||||||
|
.g-ask {
|
||||||
|
width: 80px; height: 80px;
|
||||||
|
border: 1px solid var(--ink-60);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 44px;
|
||||||
|
color: var(--ink-80);
|
||||||
|
position: relative;
|
||||||
|
transition: border-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
.card.active .g-ask { border-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
/* Glyph · Step 2 · Search (magnifier with crosshair) */
|
||||||
|
.g-search {
|
||||||
|
width: 80px; height: 80px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.g-search .ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px; left: 10px;
|
||||||
|
width: 52px; height: 52px;
|
||||||
|
border: 1px solid var(--ink-60);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
.g-search .handle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px; right: 6px;
|
||||||
|
width: 22px; height: 1px;
|
||||||
|
background: var(--ink-60);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transform-origin: right center;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
.g-search .dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 26px; left: 26px;
|
||||||
|
width: 4px; height: 4px;
|
||||||
|
background: var(--muted);
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s, background 0.3s;
|
||||||
|
}
|
||||||
|
.card.active .g-search .ring { border-color: var(--accent); }
|
||||||
|
.card.active .g-search .handle { background: var(--accent); }
|
||||||
|
.card.active .g-search .dot { opacity: 1; background: var(--accent); }
|
||||||
|
|
||||||
|
/* Glyph · Step 3 · Grab (download arrow into a tray) */
|
||||||
|
.g-grab {
|
||||||
|
width: 80px; height: 80px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.g-grab .arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px; left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 1px; height: 36px;
|
||||||
|
background: var(--ink-60);
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
.g-grab .arrow::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px; left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
width: 14px; height: 14px;
|
||||||
|
border-right: 1px solid currentColor;
|
||||||
|
border-bottom: 1px solid currentColor;
|
||||||
|
color: var(--ink-60);
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
.g-grab .tray {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px; left: 12px; right: 12px;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid var(--ink-60);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
.card.active .g-grab .arrow { background: var(--accent); }
|
||||||
|
.card.active .g-grab .arrow::before { color: var(--accent); }
|
||||||
|
.card.active .g-grab .tray { border-color: var(--accent); }
|
||||||
|
|
||||||
|
/* Glyph · Step 4 · Grep (terminal-like code with highlighted match) */
|
||||||
|
.g-grep {
|
||||||
|
width: 100px; height: 80px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.g-grep .line { white-space: nowrap; }
|
||||||
|
.g-grep .hit {
|
||||||
|
color: var(--accent);
|
||||||
|
background: rgba(217,119,87,0.12);
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glyph · Step 5 · Lock (a file with lines) */
|
||||||
|
.g-lock {
|
||||||
|
width: 72px; height: 86px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.g-lock .file {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border: 1px solid var(--ink-60);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
.g-lock .fold {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px; right: -1px;
|
||||||
|
width: 18px; height: 18px;
|
||||||
|
background: var(--bg);
|
||||||
|
border-left: 1px solid var(--ink-60);
|
||||||
|
border-bottom: 1px solid var(--ink-60);
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
.g-lock .row {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--muted);
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
.g-lock .row.r1 { top: 22px; width: 40px; }
|
||||||
|
.g-lock .row.r2 { top: 34px; width: 48px; }
|
||||||
|
.g-lock .row.r3 { top: 46px; width: 32px; }
|
||||||
|
.g-lock .row.r4 { top: 58px; width: 44px; }
|
||||||
|
.g-lock .row.r5 { top: 70px; width: 28px; background: var(--accent); }
|
||||||
|
.card.active .g-lock .file { border-color: var(--accent); }
|
||||||
|
.card.active .g-lock .fold { border-color: var(--accent); }
|
||||||
|
|
||||||
|
/* ====== Final · brand-spec.md file ====== */
|
||||||
|
.final-file {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(0.9);
|
||||||
|
width: 520px;
|
||||||
|
background: var(--cd-bg);
|
||||||
|
color: var(--cd-ink);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 38px 44px 42px;
|
||||||
|
opacity: 0;
|
||||||
|
box-shadow:
|
||||||
|
0 40px 90px -30px rgba(217,119,87,0.4),
|
||||||
|
0 20px 50px -20px rgba(0,0,0,0.6),
|
||||||
|
0 0 0 1px rgba(217,119,87,0.3);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.final-file .file-name {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--accent-deep);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.final-file .file-name::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px; height: 6px;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.final-file .h1 {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0 0 18px;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
}
|
||||||
|
.final-file .kv {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: rgba(26,25,24,0.65);
|
||||||
|
}
|
||||||
|
.final-file .kv .k { color: var(--accent-deep); }
|
||||||
|
.final-file .kv .swatch {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px; height: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.final-file .caret {
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px; height: 14px;
|
||||||
|
background: var(--accent);
|
||||||
|
vertical-align: -2px;
|
||||||
|
margin-left: 2px;
|
||||||
|
animation: blink 1.1s steps(2) infinite;
|
||||||
|
}
|
||||||
|
@keyframes blink { 50% { opacity: 0; } }
|
||||||
|
|
||||||
|
/* Brand reveal (final 2 sec, keeps with Motion Spec) */
|
||||||
|
.brand-sheet {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--cd-bg);
|
||||||
|
transform: translateY(100%);
|
||||||
|
will-change: transform;
|
||||||
|
z-index: 80;
|
||||||
|
}
|
||||||
|
.brand-reveal {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 81;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.brand-reveal .wordmark {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: 128px;
|
||||||
|
letter-spacing: -0.045em;
|
||||||
|
color: var(--cd-ink);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.brand-reveal .wordmark .accent { color: var(--accent); }
|
||||||
|
.brand-reveal .underline {
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent);
|
||||||
|
margin-top: 36px;
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stage" id="stage">
|
||||||
|
<div class="mark">HUASHU · DESIGN</div>
|
||||||
|
<div class="mark-right">V2 · 2026</div>
|
||||||
|
|
||||||
|
<div class="title-line" id="titleLine">w1 · brand protocol</div>
|
||||||
|
|
||||||
|
<div class="chain">
|
||||||
|
<div class="chain-line" id="chainLine"></div>
|
||||||
|
|
||||||
|
<div class="card" data-step="1">
|
||||||
|
<div class="card-num">STEP 01</div>
|
||||||
|
<div class="card-glyph"><div class="g-ask">?</div></div>
|
||||||
|
<div class="card-label">
|
||||||
|
<div class="zh">Ask</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-step="2">
|
||||||
|
<div class="card-num">STEP 02</div>
|
||||||
|
<div class="card-glyph">
|
||||||
|
<div class="g-search">
|
||||||
|
<div class="ring"></div>
|
||||||
|
<div class="handle"></div>
|
||||||
|
<div class="dot"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-label">
|
||||||
|
<div class="zh">Search</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-step="3">
|
||||||
|
<div class="card-num">STEP 03</div>
|
||||||
|
<div class="card-glyph">
|
||||||
|
<div class="g-grab">
|
||||||
|
<div class="arrow"></div>
|
||||||
|
<div class="tray"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-label">
|
||||||
|
<div class="zh">Grab</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-step="4">
|
||||||
|
<div class="card-num">STEP 04</div>
|
||||||
|
<div class="card-glyph">
|
||||||
|
<div class="g-grep">
|
||||||
|
<div class="line">#F5F4F0</div>
|
||||||
|
<div class="line"><span class="hit">#D97757</span></div>
|
||||||
|
<div class="line">#1A1918</div>
|
||||||
|
<div class="line">#FFFFFF</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-label">
|
||||||
|
<div class="zh">Grep</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-step="5">
|
||||||
|
<div class="card-num">STEP 05</div>
|
||||||
|
<div class="card-glyph">
|
||||||
|
<div class="g-lock">
|
||||||
|
<div class="file"></div>
|
||||||
|
<div class="fold"></div>
|
||||||
|
<div class="row r1"></div>
|
||||||
|
<div class="row r2"></div>
|
||||||
|
<div class="row r3"></div>
|
||||||
|
<div class="row r4"></div>
|
||||||
|
<div class="row r5"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-label">
|
||||||
|
<div class="zh">Lock</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="final-file" id="finalFile">
|
||||||
|
<div class="file-name">brand-spec.md</div>
|
||||||
|
<div class="h1">Assets locked in<span class="caret"></span></div>
|
||||||
|
<div class="kv">
|
||||||
|
<div><span class="k">logo</span> · assets/logo.svg</div>
|
||||||
|
<div><span class="k">hero</span> · product-hero.png</div>
|
||||||
|
<div><span class="k">accent</span> · <span class="swatch" style="background:#D97757"></span>#D97757</div>
|
||||||
|
<div><span class="k">bg</span> · <span class="swatch" style="background:#000;border:1px solid rgba(0,0,0,0.15)"></span>#000000</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="brand-sheet" id="brandSheet"></div>
|
||||||
|
<div class="brand-reveal" id="brandReveal">
|
||||||
|
<div class="wordmark">huashu<span class="accent"> · </span>design</div>
|
||||||
|
<div class="underline" id="brandUnderline"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── Auto-scale stage to viewport ─────────────────
|
||||||
|
function fitStage() {
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
|
fitStage();
|
||||||
|
window.addEventListener('resize', fitStage);
|
||||||
|
|
||||||
|
// ── Easing functions ─────────────────
|
||||||
|
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);
|
||||||
|
|
||||||
|
function lerp(t, a, b, easing) {
|
||||||
|
if (t <= 0) return a;
|
||||||
|
if (t >= 1) return b;
|
||||||
|
const e = easing ? easing(t) : t;
|
||||||
|
return a + (b - a) * e;
|
||||||
|
}
|
||||||
|
function seg(time, start, end) {
|
||||||
|
if (time <= start) return 0;
|
||||||
|
if (time >= end) return 1;
|
||||||
|
return (time - start) / (end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timeline (total 12s) ─────────────────
|
||||||
|
// Beat 1 (0-2s) · Beat 2 (2-10s) · Beat 3 (10-12s)
|
||||||
|
//
|
||||||
|
// Card schedule:
|
||||||
|
// Card 1 enter 0.8-1.6s, active 1.6-3.0
|
||||||
|
// Card 2 enter 2.4-3.2s, active 3.2-4.6
|
||||||
|
// Card 3 enter 4.0-4.8s, active 4.8-6.2
|
||||||
|
// Card 4 enter 5.6-6.4s, active 6.4-7.8
|
||||||
|
// Card 5 enter 7.2-8.0s, active 8.0-9.4
|
||||||
|
// All cards stay visible (frozen after active ends)
|
||||||
|
//
|
||||||
|
// Line draws 0.6-8.0s (while cards come in)
|
||||||
|
// Title fades in 0.2-1.2, fades out 9.6-10.0
|
||||||
|
// Final file: 8.8-9.8 scale in, hold to 10.0
|
||||||
|
// Brand reveal: 10.0-12.0
|
||||||
|
|
||||||
|
const cards = Array.from(document.querySelectorAll('.card'));
|
||||||
|
const cardTimings = [
|
||||||
|
{ enter: [0.8, 1.6], active: [1.6, 3.0] },
|
||||||
|
{ enter: [2.4, 3.2], active: [3.2, 4.6] },
|
||||||
|
{ enter: [4.0, 4.8], active: [4.8, 6.2] },
|
||||||
|
{ enter: [5.6, 6.4], active: [6.4, 7.8] },
|
||||||
|
{ enter: [7.2, 8.0], active: [8.0, 9.4] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const titleLine = document.getElementById('titleLine');
|
||||||
|
const chainLine = document.getElementById('chainLine');
|
||||||
|
const finalFile = document.getElementById('finalFile');
|
||||||
|
const brandSheet = document.getElementById('brandSheet');
|
||||||
|
const brandReveal = document.getElementById('brandReveal');
|
||||||
|
const brandUnderline = document.getElementById('brandUnderline');
|
||||||
|
|
||||||
|
const DURATION = 12.0;
|
||||||
|
let startTime = null;
|
||||||
|
let loop = true;
|
||||||
|
|
||||||
|
// Honor recording flag
|
||||||
|
if (window.__recording === true) loop = false;
|
||||||
|
|
||||||
|
function tick(now) {
|
||||||
|
if (startTime === null) startTime = now;
|
||||||
|
let t = (now - startTime) / 1000;
|
||||||
|
|
||||||
|
if (t >= DURATION) {
|
||||||
|
if (loop) { startTime = now; t = 0; }
|
||||||
|
else { t = DURATION; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const titleIn = seg(t, 0.2, 1.2);
|
||||||
|
const titleOut = seg(t, 9.6, 10.0);
|
||||||
|
const titleOpacity = Math.min(cubicOut(titleIn), 1 - titleOut);
|
||||||
|
titleLine.style.opacity = Math.max(0, titleOpacity);
|
||||||
|
titleLine.style.transform = `translateX(-50%) translateY(${lerp(titleIn, -8, 0, cubicOut)}px)`;
|
||||||
|
|
||||||
|
// Chain line — grows left→right as cards arrive
|
||||||
|
const lineT = seg(t, 0.6, 8.0);
|
||||||
|
chainLine.style.transform = `scaleX(${cubicInOut(lineT)})`;
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
cards.forEach((card, i) => {
|
||||||
|
const { enter, active } = cardTimings[i];
|
||||||
|
const enterT = seg(t, enter[0], enter[1]);
|
||||||
|
|
||||||
|
const baseOp = expoOut(enterT);
|
||||||
|
const ty = lerp(enterT, 20, 0, expoOut);
|
||||||
|
|
||||||
|
// Active state during the card's "spotlight" window
|
||||||
|
const isActive = t >= active[0] && t <= active[1];
|
||||||
|
card.classList.toggle('active', isActive);
|
||||||
|
|
||||||
|
// Cards dim to 25% when final file starts zooming in (8.8-9.6),
|
||||||
|
// then fade fully when brand reveal takes over (10.0-10.4)
|
||||||
|
const dimT = seg(t, 8.8, 9.6);
|
||||||
|
const exitT = seg(t, 10.0, 10.4);
|
||||||
|
const dimFactor = lerp(dimT, 1.0, 0.22, cubicInOut);
|
||||||
|
const finalOp = baseOp * dimFactor * (1 - exitT);
|
||||||
|
|
||||||
|
if (dimT > 0) card.classList.remove('active');
|
||||||
|
|
||||||
|
card.style.opacity = finalOp;
|
||||||
|
card.style.transform = `translateY(${ty - 10 * exitT}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chain line also dims when final file zooms, fades with cards at 10.0-10.4
|
||||||
|
const chainDim = seg(t, 8.8, 9.6);
|
||||||
|
const chainExit = seg(t, 10.0, 10.4);
|
||||||
|
chainLine.style.opacity = lerp(chainDim, 1, 0.22, cubicInOut) * (1 - chainExit);
|
||||||
|
|
||||||
|
// Final file: 8.8-9.8 scale+fade in, then 9.8-10.2 scale+settle, hold to ~10.0
|
||||||
|
const finalInT = seg(t, 8.8, 9.8);
|
||||||
|
const finalScale = lerp(finalInT, 0.88, 1.0, expoOut);
|
||||||
|
const finalOp = cubicOut(finalInT);
|
||||||
|
// fade final file out into brand reveal
|
||||||
|
const finalOut = seg(t, 10.0, 10.6);
|
||||||
|
finalFile.style.opacity = finalOp * (1 - finalOut);
|
||||||
|
finalFile.style.transform = `translate(-50%, -50%) scale(${finalScale * (1 - finalOut * 0.04)})`;
|
||||||
|
|
||||||
|
// Brand reveal — sheet slides up from bottom 10.0-10.6, wordmark fades in 10.6-11.4, underline 11.4-11.9
|
||||||
|
const sheetT = seg(t, 10.0, 10.6);
|
||||||
|
brandSheet.style.transform = `translateY(${lerp(sheetT, 100, 0, expoOut)}%)`;
|
||||||
|
|
||||||
|
const wordT = seg(t, 10.6, 11.4);
|
||||||
|
brandReveal.style.opacity = cubicOut(wordT);
|
||||||
|
// NOTE: no scale transform on .brand-reveal — it would compound with the
|
||||||
|
// underline width animation and make the line appear mis-placed. Instead,
|
||||||
|
// scale the wordmark alone via font-variation-settings-safe approach: none here.
|
||||||
|
|
||||||
|
const underT = seg(t, 11.4, 11.9);
|
||||||
|
brandUnderline.style.width = `${lerp(underT, 0, 280, expoOut)}px`;
|
||||||
|
|
||||||
|
// Mark as ready for recorder on first frame
|
||||||
|
if (!window.__ready) window.__ready = true;
|
||||||
|
|
||||||
|
if (loop || t < DURATION) requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
// Wait for fonts before first paint so Serif glyphs are correct
|
||||||
|
(document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve())
|
||||||
|
.then(() => requestAnimationFrame(tick));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+642
-733
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,983 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>w2 · Rough draft now beats perfect draft later</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;1,8..60,300..700&family=Noto+Serif+SC:wght@200;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">
|
||||||
|
<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;
|
||||||
|
--bad: #6E3A2E; /* 失败暗红调,不刺眼 */
|
||||||
|
--bad-strong: #C85A42; /* 失败叉号强调,对比度提升 */
|
||||||
|
--cool: rgba(255,255,255,0.42); /* 冷色参考线(左路径) */
|
||||||
|
--cd-bg: #F5F4F0;
|
||||||
|
--cd-panel: #FFFFFF;
|
||||||
|
--cd-ink: #1A1918;
|
||||||
|
|
||||||
|
--serif-zh: "Noto Serif SC", "Songti SC", serif;
|
||||||
|
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
|
||||||
|
--sans: "Inter", -apple-system, "PingFang SC", "HarmonyOS Sans 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Film grain */
|
||||||
|
.stage::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
|
||||||
|
opacity: 0.02;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome · watermark */
|
||||||
|
.mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; left: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
.mark-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px; right: 64px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
opacity: 0.16;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
.title-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 112px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splitter — horizontal line dividing the two halves */
|
||||||
|
.splitter {
|
||||||
|
position: absolute;
|
||||||
|
left: 160px;
|
||||||
|
right: 160px;
|
||||||
|
top: 50%;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--hairline);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left center;
|
||||||
|
will-change: transform;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
.splitter-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 0 28px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.32em;
|
||||||
|
color: var(--muted);
|
||||||
|
z-index: 6;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
* TOP HALF · 闷头一把梭(3 hours, all at once)
|
||||||
|
* ====================================================== */
|
||||||
|
.half-top {
|
||||||
|
position: absolute;
|
||||||
|
top: 200px;
|
||||||
|
left: 160px;
|
||||||
|
right: 160px;
|
||||||
|
height: 300px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.half-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.half-label .tag {
|
||||||
|
padding: 3px 10px;
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
border-radius: 2px;
|
||||||
|
color: var(--ink-60);
|
||||||
|
}
|
||||||
|
.half-top .half-label .tag { border-color: rgba(160,74,56,0.4); color: rgba(200,120,100,0.85); }
|
||||||
|
.half-label .zh {
|
||||||
|
font-family: var(--serif-zh);
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--ink-80);
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single huge terminal panel */
|
||||||
|
.terminal-big {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background: rgba(20, 20, 20, 1);
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(255,255,255,0.02),
|
||||||
|
0 40px 80px -30px rgba(0,0,0,0.7);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tty-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-bottom: 1px solid var(--hairline);
|
||||||
|
background: rgba(255,255,255,0.02);
|
||||||
|
}
|
||||||
|
.tty-head .d {
|
||||||
|
width: 10px; height: 10px; border-radius: 50%;
|
||||||
|
background: var(--hairline);
|
||||||
|
}
|
||||||
|
.tty-title {
|
||||||
|
margin-left: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.tty-body {
|
||||||
|
padding: 28px 30px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: rgba(255,255,255,0.86);
|
||||||
|
}
|
||||||
|
.tty-body .line {
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.tty-body .prompt { color: var(--accent); margin-right: 10px; }
|
||||||
|
.tty-body .dim { color: var(--muted); }
|
||||||
|
|
||||||
|
/* The long running progress bar (simulated "3-hour render") */
|
||||||
|
.progress-row {
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ink-60);
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--hairline);
|
||||||
|
border-radius: 2px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-bar-fill {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
width: 0%;
|
||||||
|
will-change: width, background;
|
||||||
|
}
|
||||||
|
.progress-bar.failed .progress-bar-fill {
|
||||||
|
background: var(--bad-strong);
|
||||||
|
}
|
||||||
|
.progress-pct {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
min-width: 54px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.progress-hours {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
.progress-row.failed {
|
||||||
|
color: var(--bad-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Big X overlay for failure stamp */
|
||||||
|
.fail-stamp {
|
||||||
|
position: absolute;
|
||||||
|
right: 32px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) rotate(-8deg);
|
||||||
|
width: 120px; height: 120px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.fail-stamp svg { width: 100%; height: 100%; }
|
||||||
|
.fail-stamp .stamp-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -22px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.32em;
|
||||||
|
color: var(--bad-strong);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
* BOTTOM HALF · 尽早 show(small iterations)
|
||||||
|
* ====================================================== */
|
||||||
|
.half-bot {
|
||||||
|
position: absolute;
|
||||||
|
top: 580px;
|
||||||
|
left: 160px;
|
||||||
|
right: 160px;
|
||||||
|
height: 340px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.half-bot .half-label .tag {
|
||||||
|
border-color: rgba(217,119,87,0.35);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iter-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
align-items: flex-end;
|
||||||
|
height: 240px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iter-panel {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(20, 20, 20, 1);
|
||||||
|
border: 1px solid var(--hairline);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.iter-panel .ip-head {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-bottom: 1px solid var(--hairline);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
color: var(--muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.iter-panel .ip-version {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.iter-panel .ip-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
/* Rough mockup blocks that grow more detailed each iteration */
|
||||||
|
.iter-panel .m-block {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--dim);
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.iter-panel .m-block.accent { background: var(--accent); opacity: 0.8; }
|
||||||
|
.iter-panel .m-block.short { width: 40%; }
|
||||||
|
.iter-panel .m-block.med { width: 70%; }
|
||||||
|
.iter-panel .m-block.full { width: 100%; }
|
||||||
|
.iter-panel .m-block.tall { height: 24px; }
|
||||||
|
.iter-panel .m-block.big { height: 40px; }
|
||||||
|
|
||||||
|
.iter-panel .nod {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 14px;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
.iter-panel .nod svg {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
stroke: var(--accent);
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
.iter-panel .ip-minutes {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rising curve visualization for bottom half */
|
||||||
|
.curve-wrap {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 340px;
|
||||||
|
height: 180px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.curve-wrap svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.curve-wrap .axis {
|
||||||
|
stroke: var(--hairline);
|
||||||
|
stroke-width: 1;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.curve-wrap .curve-path {
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 2;
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.curve-wrap .curve-dot {
|
||||||
|
fill: var(--accent);
|
||||||
|
r: 3;
|
||||||
|
}
|
||||||
|
.curve-wrap .curve-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
fill: var(--muted);
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
* BEAT 3 · Full comparison chart crossfade
|
||||||
|
* ====================================================== */
|
||||||
|
.final-chart {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 1280px;
|
||||||
|
height: 620px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
z-index: 60;
|
||||||
|
}
|
||||||
|
.final-chart svg {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.final-chart .axis {
|
||||||
|
stroke: var(--hairline);
|
||||||
|
stroke-width: 1;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.final-chart .axis-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
fill: var(--muted);
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
}
|
||||||
|
.final-chart .tick-label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
fill: var(--dim);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
.final-chart .curve-a {
|
||||||
|
stroke: var(--cool);
|
||||||
|
stroke-width: 2;
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.final-chart .curve-a-dash {
|
||||||
|
stroke: var(--bad-strong);
|
||||||
|
stroke-width: 2.5;
|
||||||
|
fill: none;
|
||||||
|
stroke-dasharray: 5 7;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
.final-chart .curve-b {
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 3;
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.final-chart .curve-b-glow {
|
||||||
|
stroke: var(--accent);
|
||||||
|
stroke-width: 6;
|
||||||
|
fill: none;
|
||||||
|
opacity: 0.18;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.final-chart .curve-dot {
|
||||||
|
fill: var(--accent);
|
||||||
|
}
|
||||||
|
.final-chart .fail-dot {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--bad-strong);
|
||||||
|
stroke-width: 2.5;
|
||||||
|
}
|
||||||
|
.final-chart .cool-dot {
|
||||||
|
fill: var(--cool);
|
||||||
|
}
|
||||||
|
.final-chart .anchor-label {
|
||||||
|
font-family: var(--serif-zh);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.final-chart .anchor-en {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
* BRAND REVEAL — 统一动作
|
||||||
|
* ====================================================== */
|
||||||
|
.brand-sheet {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--cd-bg);
|
||||||
|
transform: translateY(100%);
|
||||||
|
will-change: transform;
|
||||||
|
z-index: 80;
|
||||||
|
}
|
||||||
|
.brand-reveal {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 81;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.brand-reveal .wordmark {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: 128px;
|
||||||
|
letter-spacing: -0.045em;
|
||||||
|
color: var(--cd-ink);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.brand-reveal .wordmark .accent { color: var(--accent-deep); }
|
||||||
|
.brand-reveal .underline {
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent);
|
||||||
|
margin-top: 36px;
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stage" id="stage">
|
||||||
|
<div class="mark">HUASHU · DESIGN</div>
|
||||||
|
<div class="mark-right">V2 · 2026</div>
|
||||||
|
|
||||||
|
<div class="title-line" id="titleLine">w2 · rough draft now beats perfect draft later</div>
|
||||||
|
|
||||||
|
<!-- Splitter -->
|
||||||
|
<div class="splitter" id="splitter"></div>
|
||||||
|
<div class="splitter-label" id="splitterLabel">VS</div>
|
||||||
|
|
||||||
|
<!-- ============ TOP HALF: All-at-once ============ -->
|
||||||
|
<div class="half-top" id="halfTop">
|
||||||
|
<div class="half-label">
|
||||||
|
<span class="tag">A</span>
|
||||||
|
<span class="zh" style="font-family: var(--serif-en); font-style: italic; letter-spacing: 0.01em;">All-at-once</span>
|
||||||
|
<span style="font-family: var(--mono); color: var(--muted); letter-spacing: 0.18em; font-size: 11px; margin-left: auto;">3 HOUR SESSION</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-big">
|
||||||
|
<div class="tty-head">
|
||||||
|
<div class="d"></div><div class="d"></div><div class="d"></div>
|
||||||
|
<div class="tty-title">designer@studio · 3h session</div>
|
||||||
|
</div>
|
||||||
|
<div class="tty-body">
|
||||||
|
<div class="line" id="ttyL1"><span class="prompt">$</span>build final_design.html <span class="dim">// v1.0 · ship it all at once</span></div>
|
||||||
|
<div class="progress-row" id="progRow">
|
||||||
|
<div class="progress-bar" id="progBar">
|
||||||
|
<div class="progress-bar-fill" id="progFill"></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-pct" id="progPct">0%</span>
|
||||||
|
<span class="progress-hours" id="progHours">03:00:00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fail-stamp" id="failStamp">
|
||||||
|
<svg viewBox="0 0 120 120">
|
||||||
|
<circle cx="60" cy="60" r="52" fill="none" stroke="#A04A38" stroke-width="3"/>
|
||||||
|
<path d="M 38 38 L 82 82 M 82 38 L 38 82" stroke="#A04A38" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
<div class="stamp-text">REJECTED</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ BOTTOM HALF: Show early ============ -->
|
||||||
|
<div class="half-bot" id="halfBot">
|
||||||
|
<div class="half-label">
|
||||||
|
<span class="tag">B</span>
|
||||||
|
<span class="zh" style="font-family: var(--serif-en); font-style: italic; letter-spacing: 0.01em;">Show early</span>
|
||||||
|
<span style="font-family: var(--mono); color: var(--muted); letter-spacing: 0.18em; font-size: 11px; margin-left: auto;">SMALL ITERATIONS</span>
|
||||||
|
</div>
|
||||||
|
<div class="iter-row">
|
||||||
|
<div class="iter-panel" id="iter1">
|
||||||
|
<div class="ip-head">
|
||||||
|
<span>draft · v1</span>
|
||||||
|
<span class="ip-version">15 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="ip-body">
|
||||||
|
<div class="m-block short"></div>
|
||||||
|
<div class="m-block med"></div>
|
||||||
|
<div class="m-block short"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nod" id="nod1">
|
||||||
|
<svg viewBox="0 0 16 16"><path d="M3 8 L7 12 L13 4"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="iter-panel" id="iter2">
|
||||||
|
<div class="ip-head">
|
||||||
|
<span>draft · v2</span>
|
||||||
|
<span class="ip-version">25 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="ip-body">
|
||||||
|
<div class="m-block full tall"></div>
|
||||||
|
<div class="m-block med"></div>
|
||||||
|
<div class="m-block short"></div>
|
||||||
|
<div class="m-block med accent"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nod" id="nod2">
|
||||||
|
<svg viewBox="0 0 16 16"><path d="M3 8 L7 12 L13 4"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="iter-panel" id="iter3">
|
||||||
|
<div class="ip-head">
|
||||||
|
<span>draft · v3</span>
|
||||||
|
<span class="ip-version">35 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="ip-body">
|
||||||
|
<div class="m-block full big"></div>
|
||||||
|
<div class="m-block full tall accent"></div>
|
||||||
|
<div class="m-block med"></div>
|
||||||
|
<div class="m-block full"></div>
|
||||||
|
<div class="m-block short"></div>
|
||||||
|
</div>
|
||||||
|
<div class="nod" id="nod3">
|
||||||
|
<svg viewBox="0 0 16 16"><path d="M3 8 L7 12 L13 4"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ Beat 3 · Final comparison chart ============ -->
|
||||||
|
<div class="final-chart" id="finalChart">
|
||||||
|
<svg viewBox="0 0 1280 620" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<!-- Axes -->
|
||||||
|
<line class="axis" x1="110" y1="60" x2="110" y2="520"/>
|
||||||
|
<line class="axis" x1="110" y1="520" x2="1200" y2="520"/>
|
||||||
|
|
||||||
|
<!-- Y-axis label -->
|
||||||
|
<text class="axis-label" x="58" y="290" transform="rotate(-90 58 290)" text-anchor="middle">QUALITY</text>
|
||||||
|
<!-- X-axis label -->
|
||||||
|
<text class="axis-label" x="655" y="570" text-anchor="middle">TIME</text>
|
||||||
|
|
||||||
|
<!-- Tick marks -->
|
||||||
|
<text class="tick-label" x="110" y="545" text-anchor="middle">0</text>
|
||||||
|
<text class="tick-label" x="290" y="545" text-anchor="middle">15m</text>
|
||||||
|
<text class="tick-label" x="480" y="545" text-anchor="middle">25m</text>
|
||||||
|
<text class="tick-label" x="680" y="545" text-anchor="middle">35m</text>
|
||||||
|
<text class="tick-label" x="1200" y="545" text-anchor="middle">3h</text>
|
||||||
|
|
||||||
|
<!-- Curve A (All-at-once): flat crawl near zero, late spike, then crash -->
|
||||||
|
<path class="curve-a" id="curveA"
|
||||||
|
d="M 110 500 L 400 495 L 700 490 L 1000 485 L 1140 180" />
|
||||||
|
<path class="curve-a-dash" id="curveACrash"
|
||||||
|
d="M 1140 180 L 1200 510" />
|
||||||
|
<circle class="fail-dot" id="failDot" cx="1140" cy="180" r="9"/>
|
||||||
|
<g id="failX" opacity="0">
|
||||||
|
<line x1="1130" y1="170" x2="1150" y2="190" stroke="#C85A42" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="1150" y1="170" x2="1130" y2="190" stroke="#C85A42" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text class="anchor-label" x="1200" y="150" fill="#C85A42" text-anchor="end" style="font-family: var(--serif-en); font-style: italic;">All-at-once</text>
|
||||||
|
<text class="anchor-en" x="1200" y="170" fill="#C85A42" text-anchor="end">REJECTED</text>
|
||||||
|
|
||||||
|
<!-- Curve B (Show early): steady step rise across first 35 min -->
|
||||||
|
<path class="curve-b-glow" id="curveBGlow"
|
||||||
|
d="M 110 500 L 290 380 L 480 270 L 680 140" />
|
||||||
|
<path class="curve-b" id="curveB"
|
||||||
|
d="M 110 500 L 290 380 L 480 270 L 680 140" />
|
||||||
|
<circle class="curve-dot" cx="290" cy="380" r="6"/>
|
||||||
|
<circle class="curve-dot" cx="480" cy="270" r="6"/>
|
||||||
|
<circle class="curve-dot" cx="680" cy="140" r="8"/>
|
||||||
|
|
||||||
|
<text class="anchor-label" x="680" y="115" fill="#D97757" text-anchor="middle" style="font-family: var(--serif-en); font-style: italic;">Show early</text>
|
||||||
|
<text class="anchor-en" x="680" y="96" fill="#D97757" text-anchor="middle">SHIPPED</text>
|
||||||
|
|
||||||
|
<text class="tick-label" x="555" y="477" text-anchor="middle" fill="rgba(255,255,255,0.3)" style="letter-spacing: 0.12em;">— 3 hours silence —</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Brand reveal -->
|
||||||
|
<div class="brand-sheet" id="brandSheet"></div>
|
||||||
|
<div class="brand-reveal" id="brandReveal">
|
||||||
|
<div class="wordmark">huashu<span class="accent"> · </span>design</div>
|
||||||
|
<div class="underline" id="brandUnderline"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Auto-scale stage
|
||||||
|
function fitStage() {
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
|
fitStage();
|
||||||
|
window.addEventListener('resize', fitStage);
|
||||||
|
|
||||||
|
// Easings
|
||||||
|
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 cubicIn = t => t * t * t;
|
||||||
|
|
||||||
|
function lerp(t, a, b, easing) {
|
||||||
|
if (t <= 0) return a;
|
||||||
|
if (t >= 1) return b;
|
||||||
|
const e = easing ? easing(t) : t;
|
||||||
|
return a + (b - a) * e;
|
||||||
|
}
|
||||||
|
function seg(time, start, end) {
|
||||||
|
if (time <= start) return 0;
|
||||||
|
if (time >= end) return 1;
|
||||||
|
return (time - start) / (end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────
|
||||||
|
// Timeline — total 12s (Beat 1: 0-2 · Beat 2: 2-10 · Beat 3: 10-12)
|
||||||
|
//
|
||||||
|
// 0.0-0.6 title + splitter grow
|
||||||
|
// 0.6-1.4 two half-labels fade in (top first, then bot)
|
||||||
|
// 1.4-2.0 top terminal line 1 types; bot panel 1 enters
|
||||||
|
//
|
||||||
|
// Top track (闷头):
|
||||||
|
// 2.0-7.8 progress bar crawls from 0 to 99% (slow, painful)
|
||||||
|
// 7.8-8.4 stuck at 99%
|
||||||
|
// 8.4-8.9 fail stamp lands + bar turns red + bar drops to 0
|
||||||
|
//
|
||||||
|
// Bottom track (尽早):
|
||||||
|
// 2.0-2.6 iter1 enters, nod1 appears @ 2.8
|
||||||
|
// 3.6-4.2 iter2 enters, nod2 appears @ 4.4
|
||||||
|
// 5.6-6.2 iter3 enters, nod3 appears @ 6.4 (final tick — biggest)
|
||||||
|
//
|
||||||
|
// 8.8-9.8 both halves dim; final chart crossfades in
|
||||||
|
// (curves draw via stroke-dasharray)
|
||||||
|
// 9.8-10.4 chart settles, anchor labels bloom
|
||||||
|
// 10.0-12.0 brand reveal (sheet + wordmark + underline)
|
||||||
|
// ────────────────────────────────────
|
||||||
|
|
||||||
|
const el = {
|
||||||
|
title: document.getElementById('titleLine'),
|
||||||
|
splitter: document.getElementById('splitter'),
|
||||||
|
splitterLb: document.getElementById('splitterLabel'),
|
||||||
|
halfTop: document.getElementById('halfTop'),
|
||||||
|
halfBot: document.getElementById('halfBot'),
|
||||||
|
ttyL1: document.getElementById('ttyL1'),
|
||||||
|
progRow: document.getElementById('progRow'),
|
||||||
|
progBar: document.getElementById('progBar'),
|
||||||
|
progFill: document.getElementById('progFill'),
|
||||||
|
progPct: document.getElementById('progPct'),
|
||||||
|
progHours: document.getElementById('progHours'),
|
||||||
|
failStamp: document.getElementById('failStamp'),
|
||||||
|
iter1: document.getElementById('iter1'),
|
||||||
|
iter2: document.getElementById('iter2'),
|
||||||
|
iter3: document.getElementById('iter3'),
|
||||||
|
nod1: document.getElementById('nod1'),
|
||||||
|
nod2: document.getElementById('nod2'),
|
||||||
|
nod3: document.getElementById('nod3'),
|
||||||
|
finalChart: document.getElementById('finalChart'),
|
||||||
|
brandSheet: document.getElementById('brandSheet'),
|
||||||
|
brandReveal:document.getElementById('brandReveal'),
|
||||||
|
brandUnder: document.getElementById('brandUnderline'),
|
||||||
|
curveA: document.getElementById('curveA'),
|
||||||
|
curveACrash:document.getElementById('curveACrash'),
|
||||||
|
curveB: document.getElementById('curveB'),
|
||||||
|
curveBGlow: document.getElementById('curveBGlow'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Precompute path lengths for draw-on animation
|
||||||
|
const lenA = el.curveA.getTotalLength();
|
||||||
|
const lenACrash = el.curveACrash.getTotalLength();
|
||||||
|
const lenB = el.curveB.getTotalLength();
|
||||||
|
|
||||||
|
el.curveA.style.strokeDasharray = `${lenA} ${lenA}`;
|
||||||
|
el.curveA.style.strokeDashoffset = lenA;
|
||||||
|
el.curveACrash.style.strokeDasharray = `${lenACrash} ${lenACrash}`;
|
||||||
|
el.curveACrash.style.strokeDashoffset = lenACrash;
|
||||||
|
el.curveB.style.strokeDasharray = `${lenB} ${lenB}`;
|
||||||
|
el.curveB.style.strokeDashoffset = lenB;
|
||||||
|
el.curveBGlow.style.strokeDasharray = `${lenB} ${lenB}`;
|
||||||
|
el.curveBGlow.style.strokeDashoffset = lenB;
|
||||||
|
|
||||||
|
// Also precompute chart dot selections (hide initially)
|
||||||
|
const chartDots = el.finalChart.querySelectorAll('circle');
|
||||||
|
const chartAnchors = el.finalChart.querySelectorAll('.anchor-label, .anchor-en');
|
||||||
|
const chartTicks = el.finalChart.querySelectorAll('.tick-label, .axis-label');
|
||||||
|
|
||||||
|
const DURATION = 12.0;
|
||||||
|
let startTime = null;
|
||||||
|
let loop = true;
|
||||||
|
if (window.__recording === true) loop = false;
|
||||||
|
|
||||||
|
function tick(now) {
|
||||||
|
if (startTime === null) startTime = now;
|
||||||
|
let t = (now - startTime) / 1000;
|
||||||
|
|
||||||
|
if (t >= DURATION) {
|
||||||
|
if (loop) { startTime = now; t = 0; }
|
||||||
|
else { t = DURATION; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────── Title
|
||||||
|
const titleIn = seg(t, 0.1, 1.0);
|
||||||
|
const titleOut = seg(t, 9.2, 9.8);
|
||||||
|
el.title.style.opacity = Math.max(0, Math.min(cubicOut(titleIn), 1 - titleOut));
|
||||||
|
|
||||||
|
// ────── Splitter (fade out earlier so Beat 3 is clean)
|
||||||
|
const splitT = seg(t, 0.0, 0.8);
|
||||||
|
const splitOut = seg(t, 8.4, 8.9);
|
||||||
|
el.splitter.style.transform = `scaleX(${expoOut(splitT) * (1 - splitOut)})`;
|
||||||
|
const splitLabelT = seg(t, 0.4, 1.0);
|
||||||
|
const splitLabelOut = seg(t, 8.2, 8.7);
|
||||||
|
el.splitterLb.style.opacity = Math.max(0, Math.min(cubicOut(splitLabelT), 1 - splitLabelOut));
|
||||||
|
|
||||||
|
// ────── Halves fade in / out (fade out earlier to clear for Beat 3 chart)
|
||||||
|
const topIn = seg(t, 0.6, 1.4);
|
||||||
|
const topOut = seg(t, 8.4, 9.0);
|
||||||
|
el.halfTop.style.opacity = Math.max(0, Math.min(cubicOut(topIn), 1 - topOut));
|
||||||
|
|
||||||
|
const botIn = seg(t, 1.0, 1.8);
|
||||||
|
const botOut = seg(t, 8.4, 9.0);
|
||||||
|
el.halfBot.style.opacity = Math.max(0, Math.min(cubicOut(botIn), 1 - botOut));
|
||||||
|
|
||||||
|
// ────── TOP track: terminal line + progress bar
|
||||||
|
const ttyL1In = seg(t, 1.4, 1.8);
|
||||||
|
el.ttyL1.style.opacity = cubicOut(ttyL1In);
|
||||||
|
|
||||||
|
// Progress bar appears @ 1.8, starts crawling 2.0-7.8, stuck 7.8-8.4, fails @ 8.4
|
||||||
|
const progRowIn = seg(t, 1.8, 2.2);
|
||||||
|
el.progRow.style.opacity = cubicOut(progRowIn);
|
||||||
|
|
||||||
|
let pct = 0;
|
||||||
|
let hoursTxt = '03:00:00';
|
||||||
|
if (t >= 2.0 && t < 7.8) {
|
||||||
|
const p = seg(t, 2.0, 7.8);
|
||||||
|
// Easing: starts fast, slows down to 99% (mimics the "last 10% takes forever" trope)
|
||||||
|
pct = 99 * (1 - Math.pow(1 - p, 2.2));
|
||||||
|
const remaining = Math.max(0, (1 - p) * 3 * 60 * 60);
|
||||||
|
const hh = String(Math.floor(remaining / 3600)).padStart(2, '0');
|
||||||
|
const mm = String(Math.floor((remaining % 3600) / 60)).padStart(2, '0');
|
||||||
|
const ss = String(Math.floor(remaining % 60)).padStart(2, '0');
|
||||||
|
hoursTxt = `${hh}:${mm}:${ss}`;
|
||||||
|
} else if (t >= 7.8 && t < 8.4) {
|
||||||
|
pct = 99;
|
||||||
|
// Micro-jitter to show "stuck"
|
||||||
|
const jitter = Math.sin(t * 30) * 0.1;
|
||||||
|
pct = 99 + jitter;
|
||||||
|
hoursTxt = '00:00:12';
|
||||||
|
} else if (t >= 8.4 && t < 8.7) {
|
||||||
|
// Fail animation — pct stays at 99 briefly then snaps to 0
|
||||||
|
pct = 99;
|
||||||
|
hoursTxt = '— REJECTED —';
|
||||||
|
} else if (t >= 8.7) {
|
||||||
|
pct = 0;
|
||||||
|
hoursTxt = '— REJECTED —';
|
||||||
|
}
|
||||||
|
|
||||||
|
el.progFill.style.width = `${pct}%`;
|
||||||
|
el.progPct.textContent = `${Math.floor(Math.max(0, pct))}%`;
|
||||||
|
el.progHours.textContent = hoursTxt;
|
||||||
|
|
||||||
|
// Fail state toggle
|
||||||
|
if (t >= 8.4) {
|
||||||
|
el.progBar.classList.add('failed');
|
||||||
|
el.progRow.classList.add('failed');
|
||||||
|
} else {
|
||||||
|
el.progBar.classList.remove('failed');
|
||||||
|
el.progRow.classList.remove('failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail stamp lands at 8.4
|
||||||
|
const stampIn = seg(t, 8.4, 8.7);
|
||||||
|
if (stampIn > 0) {
|
||||||
|
el.failStamp.style.opacity = cubicOut(stampIn);
|
||||||
|
const scale = lerp(stampIn, 1.6, 1.0, expoOut);
|
||||||
|
el.failStamp.style.transform = `translateY(-50%) rotate(-8deg) scale(${scale})`;
|
||||||
|
} else {
|
||||||
|
el.failStamp.style.opacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────── BOTTOM track: 3 iter panels
|
||||||
|
const iterTimings = [
|
||||||
|
{ enter: [2.0, 2.6], nod: [2.8, 3.2] },
|
||||||
|
{ enter: [3.6, 4.2], nod: [4.4, 4.8] },
|
||||||
|
{ enter: [5.6, 6.2], nod: [6.4, 6.9] },
|
||||||
|
];
|
||||||
|
|
||||||
|
[el.iter1, el.iter2, el.iter3].forEach((panel, i) => {
|
||||||
|
const { enter } = iterTimings[i];
|
||||||
|
const p = seg(t, enter[0], enter[1]);
|
||||||
|
const op = expoOut(p);
|
||||||
|
const ty = lerp(p, 20, 0, expoOut);
|
||||||
|
panel.style.opacity = op;
|
||||||
|
panel.style.transform = `translateY(${ty}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
[el.nod1, el.nod2, el.nod3].forEach((n, i) => {
|
||||||
|
const { nod } = iterTimings[i];
|
||||||
|
const p = seg(t, nod[0], nod[1]);
|
||||||
|
const op = expoOut(p);
|
||||||
|
const scale = lerp(p, 0.4, 1.0, expoOut);
|
||||||
|
n.style.opacity = op;
|
||||||
|
n.style.transform = `scale(${scale})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ────── Beat 3 · final chart crossfade (chart appears as halves fade)
|
||||||
|
const chartIn = seg(t, 8.5, 9.2);
|
||||||
|
el.finalChart.style.opacity = cubicOut(chartIn);
|
||||||
|
|
||||||
|
const curveBT = seg(t, 8.8, 9.8);
|
||||||
|
el.curveB.style.strokeDashoffset = lenB * (1 - expoOut(curveBT));
|
||||||
|
el.curveBGlow.style.strokeDashoffset = lenB * (1 - expoOut(curveBT));
|
||||||
|
|
||||||
|
const curveAT = seg(t, 8.9, 9.7);
|
||||||
|
el.curveA.style.strokeDashoffset = lenA * (1 - cubicOut(curveAT));
|
||||||
|
const curveACrashT = seg(t, 9.7, 9.95);
|
||||||
|
el.curveACrash.style.strokeDashoffset = lenACrash * (1 - expoOut(curveACrashT));
|
||||||
|
const failXT = seg(t, 9.65, 9.85);
|
||||||
|
const failXEl = document.getElementById('failX');
|
||||||
|
if (failXEl) {
|
||||||
|
failXEl.style.opacity = cubicOut(failXT);
|
||||||
|
failXEl.style.transform = `scale(${lerp(failXT, 1.6, 1.0, expoOut)})`;
|
||||||
|
failXEl.style.transformOrigin = '1140px 180px';
|
||||||
|
}
|
||||||
|
|
||||||
|
chartDots.forEach((dot, i) => {
|
||||||
|
const dotT = seg(t, 9.0 + i * 0.12, 9.3 + i * 0.12);
|
||||||
|
dot.style.opacity = cubicOut(dotT);
|
||||||
|
});
|
||||||
|
chartAnchors.forEach((a) => {
|
||||||
|
const aT = seg(t, 9.5, 9.95);
|
||||||
|
a.style.opacity = cubicOut(aT);
|
||||||
|
});
|
||||||
|
chartTicks.forEach((tk) => {
|
||||||
|
const tkT = seg(t, 8.7, 9.3);
|
||||||
|
tk.style.opacity = cubicOut(tkT) * 0.9;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ────── Brand reveal 10.0-12.0
|
||||||
|
const sheetT = seg(t, 10.0, 10.6);
|
||||||
|
el.brandSheet.style.transform = `translateY(${lerp(sheetT, 100, 0, expoOut)}%)`;
|
||||||
|
|
||||||
|
const wordT = seg(t, 10.6, 11.4);
|
||||||
|
el.brandReveal.style.opacity = cubicOut(wordT);
|
||||||
|
|
||||||
|
const underT = seg(t, 11.4, 11.9);
|
||||||
|
el.brandUnder.style.width = `${lerp(underT, 0, 280, expoOut)}px`;
|
||||||
|
|
||||||
|
// Mark ready for recorder
|
||||||
|
if (!window.__ready) window.__ready = true;
|
||||||
|
|
||||||
|
if (loop || t < DURATION) requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
(document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve())
|
||||||
|
.then(() => requestAnimationFrame(tick));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+944
-806
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,647 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>w3 · Fallback Advisor (English)</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;1,8..60,300..700&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-en: "Source Serif 4", Georgia, serif;
|
||||||
|
--sans: "Inter", -apple-system, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Watermarks */
|
||||||
|
.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 — English uses Serif Display */
|
||||||
|
.top-title {
|
||||||
|
position: absolute;
|
||||||
|
top: 82px; left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 46px;
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--ink-80);
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
z-index: 120;
|
||||||
|
line-height: 1.12;
|
||||||
|
}
|
||||||
|
.top-title .accent { color: var(--accent); font-style: italic; }
|
||||||
|
|
||||||
|
.sub-caption {
|
||||||
|
position: absolute;
|
||||||
|
top: 148px; left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.34em;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
z-index: 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Philosophy wall */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
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-main {
|
||||||
|
font-family: var(--serif-en);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 1.08;
|
||||||
|
color: var(--ink);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.fg-card .title-sub {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-60);
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.fg-card .feature {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 18px;
|
||||||
|
border-top: 1px solid var(--hairline);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.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 .dot { color: var(--accent); font-style: normal; padding: 0 6px; }
|
||||||
|
.brand-mark .accent { color: var(--accent); font-style: italic; }
|
||||||
|
.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>
|
||||||
|
|
||||||
|
<!-- English version: parallel rewrite, fewer words, more breathing room -->
|
||||||
|
<div class="top-title" id="topTitle">
|
||||||
|
Not sure? <span class="accent">Here are 3 roads.</span>
|
||||||
|
</div>
|
||||||
|
<div class="sub-caption" id="subCaption">20 Philosophies · 3 Directions</div>
|
||||||
|
|
||||||
|
<div class="scan-light" id="scanLight"></div>
|
||||||
|
|
||||||
|
<div class="wall-viewport" id="wallViewport">
|
||||||
|
<div class="wall-grid" id="wallGrid">
|
||||||
|
<!-- 20 cells injected by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fg-row" id="fgRow">
|
||||||
|
<div class="fg-card" id="card1">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="label">Road 01 · Eastern Space</div>
|
||||||
|
<div class="title-main">Kenya Hara</div>
|
||||||
|
<div class="title-sub">Ma / Emptiness</div>
|
||||||
|
<div class="feature">Terracotta · Vast whitespace · Paper grain</div>
|
||||||
|
</div>
|
||||||
|
<div class="thumb-wrap" id="thumb1">
|
||||||
|
<img src="demo-takram.png" alt="demo takram" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fg-card" id="card2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="label">Road 02 · Information Architecture</div>
|
||||||
|
<div class="title-main">Pentagram</div>
|
||||||
|
<div class="title-sub">Grid / Rigor</div>
|
||||||
|
<div class="feature">Strict grid · High contrast · Editorial</div>
|
||||||
|
</div>
|
||||||
|
<div class="thumb-wrap" id="thumb2">
|
||||||
|
<img src="demo-pentagram.png" alt="demo pentagram" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fg-card" id="card3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="label">Road 03 · Experimental Edge</div>
|
||||||
|
<div class="title-main">David Carson</div>
|
||||||
|
<div class="title-sub">Raw / Punk</div>
|
||||||
|
<div class="feature">Broken type · Brutal geometry · Visual shock</div>
|
||||||
|
</div>
|
||||||
|
<div class="thumb-wrap" id="thumb3">
|
||||||
|
<img src="demo-build.png" alt="demo build" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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(){
|
||||||
|
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 — identical structure to zh.html (designer names are brand identifiers, kept as-is)
|
||||||
|
const PHILOSOPHIES = [
|
||||||
|
{ name: 'Pentagram', glyph: 'grid' },
|
||||||
|
{ name: 'M. Vignelli', glyph: 'bars' },
|
||||||
|
{ name: 'Apple HIG', glyph: 'radius' },
|
||||||
|
{ name: 'Spin', glyph: 'slash' },
|
||||||
|
{ name: 'Build', glyph: 'type' },
|
||||||
|
{ name: 'Field.io', glyph: 'wave' },
|
||||||
|
{ name: 'Active Theory',glyph: 'orbit' },
|
||||||
|
{ name: 'Hi-Res!', glyph: 'dots' },
|
||||||
|
{ name: 'Locomotive', glyph: 'arrow' },
|
||||||
|
{ name: 'Takram', glyph: 'circle' },
|
||||||
|
{ 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' },
|
||||||
|
{ 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' },
|
||||||
|
];
|
||||||
|
const SELECTED = [10, 0, 15];
|
||||||
|
|
||||||
|
function makeGlyph(kind){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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)));
|
||||||
|
|
||||||
|
const T_TOTAL = 12.0;
|
||||||
|
const expoOut = t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
||||||
|
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;
|
||||||
|
|
||||||
|
const topTitle = document.getElementById('topTitle');
|
||||||
|
const subCap = document.getElementById('subCaption');
|
||||||
|
const wallViewport = document.getElementById('wallViewport');
|
||||||
|
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){
|
||||||
|
t = Math.max(0, Math.min(T_TOTAL, t));
|
||||||
|
|
||||||
|
// Ripple in 20 cells
|
||||||
|
const rippleStart = 0.15;
|
||||||
|
cells.forEach(cell => {
|
||||||
|
const d = parseFloat(cell.dataset.dist);
|
||||||
|
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})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scan light
|
||||||
|
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);
|
||||||
|
const py = lerp(-180, 820, cubicInOut(scanT));
|
||||||
|
scanLight.style.transform = `translateY(${py}px)`;
|
||||||
|
} else {
|
||||||
|
scanLight.style.opacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light up selected, dim others
|
||||||
|
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 {
|
||||||
|
if (t >= lightStart) {
|
||||||
|
const dimmedOpacity = lerp(0.85, 0.08, lightE);
|
||||||
|
cell.style.opacity = dimmedOpacity.toFixed(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Foreground cards break out
|
||||||
|
const breakStart = 4.8;
|
||||||
|
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;
|
||||||
|
const cT = clamp01((t - breakStart - stagger) / 0.85);
|
||||||
|
const cE = expoOut(cT);
|
||||||
|
card.style.opacity = cE.toFixed(3);
|
||||||
|
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 wall background
|
||||||
|
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)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo thumbnails grow
|
||||||
|
const thumbStart = 6.6;
|
||||||
|
[thumb1, thumb2, thumb3].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);
|
||||||
|
const h = lerp(0, 250, ttE);
|
||||||
|
thumb.style.height = `${h}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Top title fade
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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); }
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(0);
|
||||||
|
window.__ready = true;
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
|
||||||
|
window.__pause = function(){ paused = true; };
|
||||||
|
window.__resume = function(){
|
||||||
|
if (!paused) return;
|
||||||
|
paused = false; startTime = null;
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
window.__setTime = function(t){ paused = true; tick(t); };
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+678
-721
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user