| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <title>Huashu-Design · 品牌资产协议 5 步硬流程</title>
- <script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
- <script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
- <script src="https://unpkg.com/@babel/standalone@7.25.6/babel.min.js"></script>
- <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=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,300;1,6..72,400;1,6..72,500&family=Noto+Serif+SC:wght@300;400;500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
- <style>
- * { box-sizing: border-box; margin: 0; padding: 0; }
- html, body { width: 100%; height: 100%; overflow: hidden; }
- body {
- background: #0c0c0c;
- font-family: 'Newsreader', 'Noto Serif SC', Georgia, serif;
- color: #1a1a1a;
- -webkit-font-smoothing: antialiased;
- text-rendering: optimizeLegibility;
- }
- </style>
- </head>
- <body>
- <div id="root"></div>
- <!-- animations.jsx inlined -->
- <script type="text/babel">
- (function() {
- const { createContext, useContext, useState, useEffect, useRef, useCallback } = React;
- const TimeContext = createContext({ time: 0, duration: 10, playing: false });
- const SpriteContext = createContext(null);
- const Easing = {
- linear: t => t,
- easeIn: t => t * t,
- easeOut: t => 1 - (1 - t) * (1 - t),
- easeInOut: t => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
- spring: t => {
- const c = (2 * Math.PI) / 3;
- return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c) + 1;
- },
- };
- function interpolate(t, input, output, easing) {
- const [inStart, inEnd] = input;
- const [outStart, outEnd] = output;
- if (t <= inStart) return outStart;
- if (t >= inEnd) return outEnd;
- let progress = (t - inStart) / (inEnd - inStart);
- if (easing) progress = easing(progress);
- return outStart + (outEnd - outStart) * progress;
- }
- function useTime() { return useContext(TimeContext).time; }
- function useSprite() {
- const sprite = useContext(SpriteContext);
- return sprite || { t: 0, elapsed: 0, duration: 0 };
- }
- function Stage({ duration = 10, width = 1920, height = 1080, loop = true, children, bgColor = '#fff' }) {
- const [time, setTime] = useState(0);
- const [playing, setPlaying] = useState(true);
- const [scale, setScale] = useState(1);
- const rafRef = useRef(null);
- const effectiveLoop = (typeof window !== 'undefined' && window.__recording) ? false : loop;
- useEffect(() => {
- function updateScale() {
- const vw = window.innerWidth;
- const vh = window.innerHeight - 56;
- const s = Math.min(vw / width, vh / height);
- setScale(s);
- }
- updateScale();
- window.addEventListener('resize', updateScale);
- return () => window.removeEventListener('resize', updateScale);
- }, [width, height]);
- useEffect(() => {
- if (!playing) return;
- let cancelled = false;
- let last = null;
- function tick(now) {
- if (cancelled) return;
- if (last === null) {
- last = now;
- if (typeof window !== 'undefined') window.__ready = true;
- }
- const delta = (now - last) / 1000;
- last = now;
- setTime(prev => {
- const next = prev + delta;
- if (next >= duration) return effectiveLoop ? 0 : duration - 0.001;
- return next;
- });
- rafRef.current = requestAnimationFrame(tick);
- }
- const start = () => { if (!cancelled) rafRef.current = requestAnimationFrame(tick); };
- if (document.fonts && document.fonts.ready) document.fonts.ready.then(start); else start();
- return () => { cancelled = true; cancelAnimationFrame(rafRef.current); };
- }, [playing, duration, effectiveLoop]);
- const progress = time / duration;
- const ctx = { time, duration, playing, setPlaying, setTime };
- const canvasStyle = {
- position: 'absolute',
- top: '50%',
- left: '50%',
- transformOrigin: 'center center',
- width,
- height,
- background: bgColor,
- overflow: 'hidden',
- transform: `translate(-50%, -50%) scale(${scale})`,
- };
- return (
- <TimeContext.Provider value={ctx}>
- <div style={{position:'fixed', inset:0, background:'#0c0c0c', display:'flex', flexDirection:'column'}}>
- <div style={{flex:1, position:'relative', overflow:'hidden'}}>
- <div style={canvasStyle}>{children}</div>
- </div>
- <div className="no-record" style={{position:'fixed', bottom:0, left:0, right:0, background:'rgba(0,0,0,0.8)', backdropFilter:'blur(10px)', padding:'12px 20px', display:'flex', alignItems:'center', gap:16, color:'#fff', fontSize:12, zIndex:100}}>
- <button onClick={()=>setPlaying(p=>!p)} style={{background:'none', border:'1px solid rgba(255,255,255,0.3)', color:'#fff', padding:'6px 14px', borderRadius:4, cursor:'pointer', fontSize:12}}>{playing?'⏸ 暂停':'▶ 播放'}</button>
- <button onClick={()=>setTime(0)} style={{background:'none', border:'1px solid rgba(255,255,255,0.3)', color:'#fff', padding:'6px 14px', borderRadius:4, cursor:'pointer', fontSize:12}}>⏮ 开始</button>
- <div style={{fontFamily:'ui-monospace, monospace', fontVariantNumeric:'tabular-nums', minWidth:90}}>{time.toFixed(2)}s / {duration.toFixed(2)}s</div>
- <div style={{flex:1, height:4, background:'rgba(255,255,255,0.2)', borderRadius:2, position:'relative'}}>
- <div style={{position:'absolute', top:0, left:0, height:'100%', width:`${progress*100}%`, background:'#fff', borderRadius:2}} />
- </div>
- </div>
- </div>
- </TimeContext.Provider>
- );
- }
- function Sprite({ start = 0, end, children, style }) {
- const { time } = useContext(TimeContext);
- const actualEnd = end == null ? Infinity : end;
- if (time < start || time >= actualEnd) return null;
- const duration = actualEnd - start;
- const elapsed = time - start;
- const t = duration === 0 ? 1 : Math.max(0, Math.min(1, elapsed / duration));
- const spriteValue = { t, elapsed, duration, start, end: actualEnd };
- return (
- <SpriteContext.Provider value={spriteValue}>
- <div style={{position:'absolute', inset:0, ...style}}>{children}</div>
- </SpriteContext.Provider>
- );
- }
- window.Animations = { Stage, Sprite, useTime, useSprite, Easing, interpolate };
- })();
- </script>
- <!-- Demo scene -->
- <script type="text/babel">
- const { Stage, Sprite, useTime, useSprite, Easing, interpolate } = window.Animations;
- // ── Design tokens ─────────────────────────────────────────
- const CREAM = '#FAF6EF';
- const INK = '#1a1a1a';
- const TERRA = '#C04A1A';
- const ASH = '#6b6b6b';
- const LINE = '#d9d2c5';
- const OLIVE = '#6a6b4e';
- const serif = "'Newsreader', 'Noto Serif SC', Georgia, serif";
- const sans = "'Inter', -apple-system, sans-serif";
- const mono = "'JetBrains Mono', ui-monospace, monospace";
- // ── Scene 1: Trigger (0 – 3s) ─────────────────────────────
- function Scene1_Trigger() {
- const { elapsed } = useSprite();
- const labelOp = interpolate(elapsed, [0, 0.5], [0, 1]);
- const titleY = interpolate(elapsed, [0, 1], [30, 0], Easing.easeOut);
- const titleOp = interpolate(elapsed, [0.2, 1], [0, 1]);
- const brandsOp = interpolate(elapsed, [1, 1.6], [0, 1]);
- const switchOp = interpolate(elapsed, [1.9, 2.4], [0, 1]);
- const fadeOut = interpolate(elapsed, [2.6, 3], [1, 0]);
- const brands = ['Kimi', 'Linear', 'Lovart', 'Stripe'];
- return (
- <div style={{position:'absolute', inset:0, background:CREAM, opacity: fadeOut,
- display:'flex', alignItems:'center', justifyContent:'center', flexDirection:'column'}}>
- <div style={{fontFamily: mono, fontSize: 12, letterSpacing:'0.4em',
- color: TERRA, marginBottom: 28, opacity: labelOp}}>
- 品 牌 资 产 协 议 · BRAND PROTOCOL
- </div>
- <div style={{fontFamily: serif, fontSize: 120, fontWeight: 500, color: INK,
- lineHeight: 1, letterSpacing:'-0.01em',
- opacity: titleOp, transform: `translateY(${titleY}px)`}}>
- 涉及具体品牌<span style={{color: TERRA, fontStyle:'italic'}}>?</span>
- </div>
- <div style={{display:'flex', gap: 48, marginTop: 64, opacity: brandsOp}}>
- {brands.map((b, i) => (
- <span key={i} style={{fontFamily: serif, fontStyle:'italic',
- fontSize: 44, color: ASH, letterSpacing:'0.02em'}}>
- {b}
- </span>
- ))}
- </div>
- <div style={{marginTop: 72, opacity: switchOp, textAlign:'center'}}>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 34,
- color: TERRA, letterSpacing:'0.02em'}}>
- 先停下——走 5 步硬流程
- </div>
- <div style={{height:1, background: TERRA, width: 180, margin:'18px auto 0'}} />
- </div>
- </div>
- );
- }
- // ── Scene 2: Step 1 & 2 (3 – 7s) ──────────────────────────
- function Scene2_AskAndSearch() {
- const { elapsed } = useSprite();
- const headOp = interpolate(elapsed, [0, 0.4], [0, 1]);
- const leftOp = interpolate(elapsed, [0.3, 0.9], [0, 1]);
- const leftY = interpolate(elapsed, [0.3, 0.9], [20, 0], Easing.easeOut);
- const rightOp = interpolate(elapsed, [0.8, 1.4], [0, 1]);
- const rightY = interpolate(elapsed, [0.8, 1.4], [20, 0], Easing.easeOut);
- const fadeOut = interpolate(elapsed, [3.5, 4], [1, 0]);
- // cursor sweeping through search paths 1.6 → 3.2
- const paths = [
- '<brand>.com/brand',
- '<brand>.com/press',
- 'brand.<brand>.com',
- '<brand>.github.io/brand',
- ];
- // current active index based on time
- const activeIdx = Math.min(3, Math.max(0, Math.floor((elapsed - 1.6) / 0.4)));
- return (
- <div style={{position:'absolute', inset:0, background:CREAM, opacity: fadeOut,
- padding:'72px 120px', display:'flex', flexDirection:'column'}}>
- <div style={{display:'flex', justifyContent:'space-between',
- alignItems:'baseline', opacity: headOp, marginBottom: 48}}>
- <div style={{fontFamily: serif, fontSize: 44, fontWeight: 500, color: INK}}>
- Step 1 & 2 · 问 · 搜
- </div>
- <div style={{fontFamily: mono, fontSize: 11, color: ASH, letterSpacing:'0.25em'}}>
- 01 / 05 → 02 / 05
- </div>
- </div>
- <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap: 48, flex:1}}>
- {/* Left: Step 1 Ask */}
- <div style={{opacity: leftOp, transform:`translateY(${leftY}px)`,
- display:'flex', flexDirection:'column', gap: 24}}>
- <div style={{display:'flex', alignItems:'baseline', gap: 18}}>
- <div style={{fontFamily: mono, fontSize: 11, color: TERRA,
- letterSpacing:'0.3em'}}>STEP · 01</div>
- <div style={{fontFamily: serif, fontSize: 40, color: INK,
- fontWeight: 500}}>问</div>
- </div>
- <div style={{background:'#fff', border:`1px solid ${LINE}`,
- padding:'32px 34px', position:'relative'}}>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 64,
- color: TERRA, lineHeight:1, position:'absolute',
- top: 8, left: 14}}>「</div>
- <div style={{fontFamily: serif, fontSize: 24, color: INK,
- lineHeight: 1.55, paddingLeft: 36}}>
- 这个品牌有 <span style={{fontStyle:'italic', color: TERRA}}>brand guidelines</span> 吗?
- </div>
- <div style={{height:1, background: LINE, margin:'22px 0'}} />
- <div style={{fontFamily: serif, fontSize: 20, color: ASH,
- lineHeight: 1.6, paddingLeft: 36}}>
- 有的话直接给我;没有我去搜。
- </div>
- </div>
- <div style={{fontFamily: mono, fontSize: 11, color: ASH,
- letterSpacing:'0.15em', marginTop: 6}}>
- ▸ 用户提供 > AI 猜测
- </div>
- </div>
- {/* Right: Step 2 Search */}
- <div style={{opacity: rightOp, transform:`translateY(${rightY}px)`,
- display:'flex', flexDirection:'column', gap: 24}}>
- <div style={{display:'flex', alignItems:'baseline', gap: 18}}>
- <div style={{fontFamily: mono, fontSize: 11, color: TERRA,
- letterSpacing:'0.3em'}}>STEP · 02</div>
- <div style={{fontFamily: serif, fontSize: 40, color: INK,
- fontWeight: 500}}>搜 官 方 品 牌 页</div>
- </div>
- <div style={{background:'#1a1a1a', color:'#e8e3d6',
- padding:'24px 28px', fontFamily: mono, fontSize: 16,
- lineHeight: 1.9, flex: 1}}>
- <div style={{color:'#6b6b6b', fontSize: 11,
- letterSpacing:'0.2em', marginBottom: 14}}>
- $ HTTP GET · typical paths
- </div>
- {paths.map((p, i) => {
- const isActive = i === activeIdx && elapsed > 1.6 && elapsed < 3.4;
- const visited = i < activeIdx;
- return (
- <div key={i} style={{
- color: isActive ? TERRA : (visited ? '#8a8878' : '#e8e3d6'),
- background: isActive ? 'rgba(192,74,26,0.12)' : 'transparent',
- padding:'2px 8px', marginLeft:-8,
- display:'flex', alignItems:'baseline', gap: 14}}>
- <span style={{color: isActive ? TERRA : '#555', fontSize: 13}}>
- {isActive ? '▸' : (visited ? '✓' : ' ')}
- </span>
- <span style={{letterSpacing:'0.02em'}}>{p}</span>
- </div>
- );
- })}
- </div>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 18,
- color: ASH, marginTop: 6}}>
- 按顺序试,中断也要记 404 状态
- </div>
- </div>
- </div>
- </div>
- );
- }
- // ── Scene 3: Step 3 · Three fallbacks (7 – 12s) ───────────
- function Scene3_Fallbacks() {
- const { elapsed } = useSprite();
- const headOp = interpolate(elapsed, [0, 0.4], [0, 1]);
- const cardsOp = interpolate(elapsed, [0.3, 1], [0, 1]);
- const fadeOut = interpolate(elapsed, [4.5, 5], [1, 0]);
- // Card state: 0 idle, 1 active/lit, 2 failed/gray, 3 success
- // Card 1 active 1.2→2.0, then fails at 2.0
- // Card 2 active 2.2→3.0, then fails at 3.0
- // Card 3 active 3.2→4.2, succeeds at 4.2
- const card1State = elapsed < 1.2 ? 0 : elapsed < 2.0 ? 1 : 2;
- const card2State = elapsed < 2.2 ? 0 : elapsed < 3.0 ? 1 : (elapsed < 3.2 ? 2 : 2);
- const card3State = elapsed < 3.2 ? 0 : elapsed < 4.2 ? 1 : 3;
- const captionOp = interpolate(elapsed, [4.2, 4.6], [0, 1]);
- const cards = [
- { n: '01', title: 'SVG 文件', cmd: 'curl -o logo.svg <url>', status: '最理想', state: card1State },
- { n: '02', title: '官网 HTML', cmd: 'curl -A "Mozilla/5.0" \\\n -L <url>', status: '80% 场景必用', state: card2State },
- { n: '03', title: '产品截图取色', cmd: 'macOS Preview · 吸管', status: 'App 必走', state: card3State },
- ];
- return (
- <div style={{position:'absolute', inset:0, background:CREAM, opacity: fadeOut,
- padding:'70px 100px 60px', display:'flex', flexDirection:'column'}}>
- <div style={{opacity: headOp, marginBottom: 40}}>
- <div style={{display:'flex', alignItems:'baseline', gap: 18, marginBottom: 6}}>
- <div style={{fontFamily: mono, fontSize: 11, color: TERRA,
- letterSpacing:'0.3em'}}>STEP · 03</div>
- <div style={{fontFamily: serif, fontSize: 18, color: ASH,
- fontStyle:'italic'}}>下 · download</div>
- </div>
- <div style={{fontFamily: serif, fontSize: 60, fontWeight: 500,
- color: INK, letterSpacing:'-0.01em'}}>
- 三条兜底路径
- </div>
- <div style={{height:1, background: LINE, width:'100%', marginTop: 20}} />
- </div>
- <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr',
- gap: 28, flex:1, opacity: cardsOp}}>
- {cards.map((c, i) => {
- const isIdle = c.state === 0;
- const isActive = c.state === 1;
- const isFailed = c.state === 2;
- const isSuccess = c.state === 3;
- const borderColor = isSuccess ? TERRA :
- isActive ? INK :
- isFailed ? '#c4bda7' : LINE;
- const borderWidth = (isActive || isSuccess) ? 2 : 1;
- const op = isFailed ? 0.42 : 1;
- const bg = isSuccess ? '#fffaf3' : '#fff';
- return (
- <div key={i} style={{
- background: bg,
- border:`${borderWidth}px solid ${borderColor}`,
- opacity: op,
- padding:'28px 28px 24px',
- display:'flex', flexDirection:'column',
- position:'relative',
- transition: 'none',
- }}>
- <div style={{display:'flex', justifyContent:'space-between',
- alignItems:'baseline', marginBottom: 20}}>
- <div style={{fontFamily: serif, fontSize: 56, fontWeight: 300,
- color: isSuccess ? TERRA : INK, lineHeight:1}}>
- {c.n}
- </div>
- <div style={{fontFamily: mono, fontSize: 10, letterSpacing:'0.2em',
- color: isSuccess ? TERRA : isFailed ? ASH : INK}}>
- {isIdle && 'READY'}
- {isActive && '▸ TRYING…'}
- {isFailed && '× FAILED'}
- {isSuccess && '✓ GOT IT'}
- </div>
- </div>
- <div style={{fontFamily: serif, fontSize: 28, fontWeight: 500,
- color: INK, marginBottom: 14, letterSpacing:'-0.005em'}}>
- {c.title}
- </div>
- <div style={{background:'#1a1a1a', color:'#e8e3d6',
- fontFamily: mono, fontSize: 12, padding:'14px 16px',
- whiteSpace:'pre-wrap', lineHeight: 1.6,
- marginBottom: 20, borderLeft: `2px solid ${isSuccess ? TERRA : '#333'}`}}>
- {c.cmd}
- </div>
- <div style={{marginTop:'auto', borderTop:`1px solid ${LINE}`,
- paddingTop: 14, display:'flex', justifyContent:'space-between',
- alignItems:'baseline'}}>
- <div style={{fontFamily: serif, fontStyle:'italic',
- fontSize: 15, color: ASH}}>
- 场景
- </div>
- <div style={{fontFamily: serif, fontSize: 16,
- color: isSuccess ? TERRA : INK, fontWeight: 500}}>
- {c.status}
- </div>
- </div>
- {isSuccess && (
- <div style={{position:'absolute', top:-10, right:-10,
- width: 28, height: 28, borderRadius:'50%', background: TERRA,
- color:'#fff', display:'flex', alignItems:'center',
- justifyContent:'center', fontFamily: serif, fontSize: 14,
- fontWeight: 600}}>
- ✓
- </div>
- )}
- </div>
- );
- })}
- </div>
- <div style={{opacity: captionOp, marginTop: 22, textAlign:'center',
- fontFamily: serif, fontStyle:'italic', fontSize: 22, color: INK}}>
- 前一条失败,<span style={{color: TERRA}}>立刻</span>走下一条——不要停
- </div>
- </div>
- );
- }
- // ── Scene 4: Step 4 · Grep colors (12 – 17s) ──────────────
- function Scene4_GrepColors() {
- const { elapsed } = useSprite();
- const headOp = interpolate(elapsed, [0, 0.4], [0, 1]);
- const cmdOp = interpolate(elapsed, [0.4, 0.9], [0, 1]);
- const fadeOut = interpolate(elapsed, [4.5, 5], [1, 0]);
- const results = [
- { count: 47, hex: '#1783FF', label: 'Kimi primary' },
- { count: 32, hex: '#FAFAFA', label: 'background' },
- { count: 18, hex: '#1a1a1a', label: 'ink' },
- { count: 12, hex: '#FF6B35', label: 'accent' },
- { count: 8, hex: '#6A6B4E', label: 'muted' },
- ];
- // Stagger each result row, starting at t=1.5
- const rowStart = 1.5;
- const rowStep = 0.32;
- const captionOp = interpolate(elapsed, [3.6, 4.1], [0, 1]);
- return (
- <div style={{position:'absolute', inset:0, background:'#1a1a1a', opacity: fadeOut,
- padding:'64px 100px', display:'flex', flexDirection:'column',
- color:'#e8e3d6'}}>
- <div style={{display:'flex', justifyContent:'space-between',
- alignItems:'baseline', marginBottom: 36, opacity: headOp}}>
- <div>
- <div style={{fontFamily: mono, fontSize: 11, color: TERRA,
- letterSpacing:'0.3em', marginBottom: 4}}>STEP · 04</div>
- <div style={{fontFamily: serif, fontSize: 54, fontWeight: 500,
- color:'#faf6ef', letterSpacing:'-0.01em'}}>
- Grep 色 值
- </div>
- </div>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 18,
- color:'#8a8878', textAlign:'right'}}>
- 频次排序 = 品牌色权重<br/>
- <span style={{fontFamily: mono, fontSize: 10, letterSpacing:'0.2em'}}>
- / DON'T GUESS · COUNT
- </span>
- </div>
- </div>
- {/* Command */}
- <div style={{opacity: cmdOp, background:'#0e0e0e',
- border:'1px solid #333', padding:'20px 26px',
- fontFamily: mono, fontSize: 15, lineHeight: 1.7,
- marginBottom: 28}}>
- <div style={{color: TERRA, fontSize: 10, letterSpacing:'0.2em',
- marginBottom: 10}}>$ COMMAND</div>
- <div style={{color:'#e8e3d6'}}>
- <span style={{color:'#888'}}>$</span>{' '}
- <span style={{color:'#7dd3fc'}}>grep</span>{' '}
- <span style={{color:'#fbbf24'}}>-hoE</span>{' '}
- <span style={{color:'#a5f3c5'}}>'#[0-9A-Fa-f]{'{6}'}'</span>{' '}
- <span style={{color:'#e8e3d6'}}>assets/*.{'{svg,html}'}</span> \<br/>
- {' '}<span style={{color:'#666'}}>|</span>{' '}
- <span style={{color:'#7dd3fc'}}>sort</span>{' '}
- <span style={{color:'#666'}}>|</span>{' '}
- <span style={{color:'#7dd3fc'}}>uniq -c</span>{' '}
- <span style={{color:'#666'}}>|</span>{' '}
- <span style={{color:'#7dd3fc'}}>sort -rn</span>{' '}
- <span style={{color:'#666'}}>|</span>{' '}
- <span style={{color:'#7dd3fc'}}>head -20</span>
- </div>
- </div>
- {/* Results + swatches */}
- <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap: 60,
- flex: 1}}>
- {/* Left: result list */}
- <div style={{fontFamily: mono, fontSize: 18, lineHeight: 2}}>
- <div style={{color:'#555', fontSize: 10, letterSpacing:'0.2em',
- marginBottom: 12}}>▸ RESULT · top 5</div>
- {results.map((r, i) => {
- const visible = elapsed > (rowStart + i * rowStep);
- if (!visible) return <div key={i} style={{height: 36}}/>;
- const fadeIn = interpolate(elapsed,
- [rowStart + i*rowStep, rowStart + i*rowStep + 0.3],
- [0, 1]);
- return (
- <div key={i} style={{opacity: fadeIn, display:'flex',
- alignItems:'center', gap: 20}}>
- <span style={{color:'#fbbf24', minWidth: 40, textAlign:'right'}}>
- {String(r.count).padStart(3, ' ').replace(/ /g, '\u00A0')}
- </span>
- <span style={{color:'#e8e3d6'}}>{r.hex}</span>
- <span style={{color:'#666', fontSize: 14}}>←</span>
- <span style={{color:'#8a8878', fontStyle:'italic',
- fontFamily: serif, fontSize: 16}}>{r.label}</span>
- </div>
- );
- })}
- </div>
- {/* Right: color swatches */}
- <div style={{display:'flex', flexDirection:'column', gap: 14}}>
- <div style={{color:'#555', fontFamily: mono, fontSize: 10,
- letterSpacing:'0.2em', marginBottom: 2}}>▸ PALETTE</div>
- {results.map((r, i) => {
- const visible = elapsed > (rowStart + i * rowStep);
- if (!visible) return <div key={i} style={{height: 54}}/>;
- const fadeIn = interpolate(elapsed,
- [rowStart + i*rowStep, rowStart + i*rowStep + 0.3],
- [0, 1]);
- const w = interpolate(elapsed,
- [rowStart + i*rowStep, rowStart + i*rowStep + 0.5],
- [0, r.count * 9]);
- return (
- <div key={i} style={{opacity: fadeIn, display:'flex',
- alignItems:'center', gap: 14, height: 50}}>
- <div style={{width: 50, height: 50, background: r.hex,
- border:'1px solid #333', flexShrink: 0}} />
- <div style={{height: 24, background: r.hex,
- width: `${w}px`, opacity: 0.7}} />
- <div style={{fontFamily: mono, fontSize: 12,
- color:'#8a8878'}}>×{r.count}</div>
- </div>
- );
- })}
- </div>
- </div>
- {/* Key caption */}
- <div style={{opacity: captionOp, textAlign:'center', marginTop: 18,
- paddingTop: 18, borderTop:'1px solid #333'}}>
- <span style={{fontFamily: serif, fontStyle:'italic', fontSize: 26,
- color: TERRA}}>
- 不要凭记忆猜品牌色
- </span>
- <span style={{fontFamily: serif, fontSize: 20, color:'#8a8878',
- marginLeft: 14}}>
- ——频次说了算
- </span>
- </div>
- </div>
- );
- }
- // ── Scene 5: Step 5 · brand-spec.md (17 – 22s) ────────────
- function Scene5_SpecFile() {
- const { elapsed } = useSprite();
- const headOp = interpolate(elapsed, [0, 0.4], [0, 1]);
- const mdOp = interpolate(elapsed, [0.3, 0.9], [0, 1]);
- const mdX = interpolate(elapsed, [0.3, 0.9], [-40, 0], Easing.easeOut);
- const cssOp = interpolate(elapsed, [0.9, 1.5], [0, 1]);
- const cssX = interpolate(elapsed, [0.9, 1.5], [40, 0], Easing.easeOut);
- const arrowOp = interpolate(elapsed, [2.2, 2.7], [0, 1]);
- const captionOp = interpolate(elapsed, [3.4, 3.9], [0, 1]);
- const fadeOut = interpolate(elapsed, [4.5, 5], [1, 0]);
- return (
- <div style={{position:'absolute', inset:0, background:CREAM, opacity: fadeOut,
- padding:'64px 80px 48px', display:'flex', flexDirection:'column'}}>
- <div style={{opacity: headOp, marginBottom: 28, display:'flex',
- justifyContent:'space-between', alignItems:'baseline'}}>
- <div>
- <div style={{fontFamily: mono, fontSize: 11, color: TERRA,
- letterSpacing:'0.3em', marginBottom: 4}}>STEP · 05 · FINAL</div>
- <div style={{fontFamily: serif, fontSize: 54, fontWeight: 500,
- color: INK, letterSpacing:'-0.01em'}}>
- 固化为 <span style={{fontStyle:'italic'}}>brand-spec.md</span>
- </div>
- </div>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 18,
- color: ASH, textAlign:'right'}}>
- 单一真相源 · single source of truth
- </div>
- </div>
- <div style={{display:'grid', gridTemplateColumns:'1.1fr 0.9fr',
- gap: 40, flex:1, position:'relative'}}>
- {/* Left: .md preview */}
- <div style={{opacity: mdOp, transform:`translateX(${mdX}px)`,
- background:'#fff', border:`1px solid ${LINE}`,
- display:'flex', flexDirection:'column'}}>
- <div style={{padding:'12px 20px', borderBottom:`1px solid ${LINE}`,
- display:'flex', justifyContent:'space-between', alignItems:'center',
- background:'#f7f2e8'}}>
- <div style={{fontFamily: mono, fontSize: 11, color: ASH,
- letterSpacing:'0.15em'}}>
- ▸ brand-spec.md
- </div>
- <div style={{fontFamily: mono, fontSize: 10, color: TERRA,
- letterSpacing:'0.15em'}}>
- ✓ COMMITTED
- </div>
- </div>
- <div style={{padding:'22px 28px', fontFamily: mono, fontSize: 12,
- lineHeight: 1.75, color: INK, flex:1, overflow:'hidden'}}>
- <div style={{color: ASH}}>---</div>
- <div><span style={{color: OLIVE}}>brand</span>: Kimi</div>
- <div><span style={{color: OLIVE}}>fetched</span>: 2026-04-20</div>
- <div style={{color: ASH}}>---</div>
- <div style={{height: 10}} />
- <div style={{fontFamily: serif, fontSize: 20, fontWeight: 500,
- marginBottom: 4}}>## 色板</div>
- <div>- primary: <span style={{color: TERRA}}>#1783FF</span> (47)</div>
- <div>- bg: <span style={{color: TERRA}}>#FAFAFA</span> (32)</div>
- <div>- ink: <span style={{color: TERRA}}>#1a1a1a</span> (18)</div>
- <div>- accent: <span style={{color: TERRA}}>#FF6B35</span> (12)</div>
- <div style={{height: 12}} />
- <div style={{fontFamily: serif, fontSize: 20, fontWeight: 500,
- marginBottom: 4}}>## 字型</div>
- <div>- display: <span style={{color: OLIVE}}>Newsreader</span></div>
- <div>- body: <span style={{color: OLIVE}}>Inter</span></div>
- <div>- mono: <span style={{color: OLIVE}}>JetBrains Mono</span></div>
- <div style={{height: 12}} />
- <div style={{fontFamily: serif, fontSize: 20, fontWeight: 500,
- marginBottom: 4}}>## 签名细节</div>
- <div>- 1px hairlines</div>
- <div>- 圆角半径统一 4px</div>
- <div style={{height: 12}} />
- <div style={{fontFamily: serif, fontSize: 20, fontWeight: 500,
- marginBottom: 4, color: TERRA}}>## 禁区</div>
- <div style={{color: ASH}}>- 不用紫渐变 / emoji / 4px 以上 shadow</div>
- </div>
- </div>
- {/* Right: CSS vars + consumers */}
- <div style={{opacity: cssOp, transform:`translateX(${cssX}px)`,
- display:'flex', flexDirection:'column', gap: 20}}>
- <div style={{background:'#0e0e0e', color:'#e8e3d6',
- fontFamily: mono, fontSize: 14, padding:'22px 26px',
- lineHeight: 1.7, flex: 1}}>
- <div style={{color:'#666', fontSize: 10, letterSpacing:'0.2em',
- marginBottom: 12}}>/* tokens.css */</div>
- <div><span style={{color:'#7dd3fc'}}>:root</span> {'{'}</div>
- <div> <span style={{color:'#a5f3c5'}}>--brand-primary</span>: <span style={{color:'#fbbf24'}}>#1783FF</span>;</div>
- <div> <span style={{color:'#a5f3c5'}}>--brand-bg</span>: <span style={{color:'#fbbf24'}}>#FAFAFA</span>;</div>
- <div> <span style={{color:'#a5f3c5'}}>--brand-ink</span>: <span style={{color:'#fbbf24'}}>#1a1a1a</span>;</div>
- <div> <span style={{color:'#a5f3c5'}}>--brand-accent</span>: <span style={{color:'#fbbf24'}}>#FF6B35</span>;</div>
- <div>{'}'}</div>
- </div>
- {/* Arrow cascade */}
- <div style={{opacity: arrowOp, position:'relative',
- background:'#fff', border:`1px solid ${LINE}`,
- padding:'20px 24px'}}>
- <div style={{fontFamily: mono, fontSize: 10, color: TERRA,
- letterSpacing:'0.2em', marginBottom: 12}}>▸ CONSUMERS</div>
- <div style={{display:'flex', alignItems:'center', gap: 16,
- fontFamily: mono, fontSize: 13, color: INK, flexWrap:'wrap'}}>
- <span style={{padding:'4px 10px', border:`1px solid ${TERRA}`,
- color: TERRA}}>brand-spec.md</span>
- <span style={{color: TERRA}}>→</span>
- <span style={{padding:'4px 10px', border:`1px solid ${INK}`}}>:root</span>
- <span style={{color: TERRA}}>→</span>
- <span style={{padding:'4px 10px', background: INK, color:'#fff'}}>all .html</span>
- </div>
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 15,
- color: ASH, marginTop: 14, lineHeight: 1.5}}>
- 一个文件改动,所有 HTML 自动跟随。
- </div>
- </div>
- </div>
- </div>
- <div style={{opacity: captionOp, marginTop: 22, textAlign:'center',
- fontFamily: serif, fontStyle:'italic', fontSize: 22, color: INK}}>
- 全协议的<span style={{color: TERRA}}>制胜点</span>——让所有 CSS 引用这份文件
- </div>
- </div>
- );
- }
- // ── Scene 6: Final (22 – 24s) ─────────────────────────────
- function Scene6_Final() {
- const { elapsed } = useSprite();
- const fadeIn = interpolate(elapsed, [0, 0.5], [0, 1], Easing.easeOut);
- const titleY = interpolate(elapsed, [0, 0.8], [24, 0], Easing.easeOut);
- const lineW = interpolate(elapsed, [0.5, 1.3], [0, 580]);
- const subOp = interpolate(elapsed, [0.8, 1.4], [0, 1]);
- return (
- <div style={{position:'absolute', inset:0, background:CREAM, opacity: fadeIn,
- display:'flex', alignItems:'center', justifyContent:'center',
- flexDirection:'column'}}>
- <div style={{fontFamily: mono, fontSize: 12, letterSpacing:'0.4em',
- color: TERRA, marginBottom: 24}}>
- 01 → 02 → 03 → 04 → <span style={{color: INK}}>05 · DONE</span>
- </div>
- <div style={{fontFamily: serif, fontSize: 92, fontWeight: 500,
- color: INK, lineHeight: 1.1, letterSpacing:'-0.01em',
- textAlign:'center', transform: `translateY(${titleY}px)`}}>
- <span style={{fontStyle:'italic', color: TERRA}}>15 分钟</span>的投资<br/>
- 省下 <span style={{fontStyle:'italic'}}>1–2 小时</span> 返工
- </div>
- <div style={{height:1, background: INK, width: lineW, marginTop: 40}} />
- <div style={{fontFamily: serif, fontStyle:'italic', fontSize: 24,
- color: ASH, marginTop: 28, opacity: subOp,
- letterSpacing:'0.02em'}}>
- 这是稳定性最便宜的投资。
- </div>
- </div>
- );
- }
- // ── Watermark ─────────────────────────────────────────────
- function Watermark() {
- return (
- <div style={{position:'absolute', bottom: 24, right: 32,
- fontSize: 11, color: 'rgba(0,0,0,0.38)', letterSpacing:'0.15em',
- fontFamily: mono, pointerEvents:'none', zIndex: 100}}>
- Created by Huashu-Design
- </div>
- );
- }
- // ── Main composition ──────────────────────────────────────
- function App() {
- return (
- <Stage duration={24} width={1920} height={1080} bgColor={CREAM}>
- <Sprite start={0} end={3}><Scene1_Trigger /></Sprite>
- <Sprite start={3} end={7}><Scene2_AskAndSearch /></Sprite>
- <Sprite start={7} end={12}><Scene3_Fallbacks /></Sprite>
- <Sprite start={12} end={17}><Scene4_GrepColors /></Sprite>
- <Sprite start={17} end={22}><Scene5_SpecFile /></Sprite>
- <Sprite start={22} end={24}><Scene6_Final /></Sprite>
- <Watermark />
- </Stage>
- );
- }
- ReactDOM.createRoot(document.getElementById('root')).render(<App />);
- </script>
- </body>
- </html>
|