创意预览页:宝宝占领首页(demo/occupy)、宝宝把倒计时改了(demo/countdown),头图分层素材 demo/_hero
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BIN
demo/_hero/below.jpg
Normal file
BIN
demo/_hero/below.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 372 KiB |
BIN
demo/_hero/bg.jpg
Normal file
BIN
demo/_hero/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
demo/_hero/pill.png
Normal file
BIN
demo/_hero/pill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
demo/_hero/prog.png
Normal file
BIN
demo/_hero/prog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
demo/_hero/week.png
Normal file
BIN
demo/_hero/week.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
142
demo/countdown/index.html
Normal file
142
demo/countdown/index.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||||
<title>宝宝把倒计时改了 · 创意预览</title>
|
||||
<!-- 2026-10-03 创意预览(不进正式首页):「宝宝把倒计时改了」—— 设计为每天一次的小彩蛋。
|
||||
头图拆层同 demo/occupy(胶囊里的「67」单独用 HTML 渲染,才能做翻牌)。
|
||||
时间线:1s 后一只小胖手从羊水囊那边伸过来 → 手指按住「67」往下拨,个位 7 滚成 6(66)→
|
||||
手停住、冒个问号(发现不对)→ 偷偷往上拨回 67 → 手飞快缩回去 → 胶囊旁冒一句「我有点着急。」带一滴汗。
|
||||
正式上线时按北京时间「每天首次打开首页」播一次(localStorage 记日期);本预览页每次打开都播,并给「再看一次」。
|
||||
?rec=1:由 __set(t) 逐帧驱动(生成动图用)。 -->
|
||||
<style>
|
||||
html,body{margin:0;background:#F8F3EF;-webkit-tap-highlight-color:transparent;font-family:-apple-system,"PingFang SC","Noto Sans SC",sans-serif}
|
||||
.page{position:relative;max-width:480px;margin:0 auto;overflow:hidden}
|
||||
.hero-wrap{position:relative;width:100%;overflow:hidden}
|
||||
.stage{position:absolute;left:0;top:0;width:375px;height:270px;transform-origin:0 0;overflow:hidden}
|
||||
.stage>*{position:absolute}
|
||||
.bg{left:0;top:0;width:375px;height:270px}
|
||||
.pill{left:14px;top:52px;width:159.69px;height:42px}
|
||||
.week{left:14px;top:86px;width:110.9px;height:70px}
|
||||
.prog{left:14px;top:150px;width:192px;height:36px}
|
||||
/* 「67」:十位固定,个位是一条竖排数字带,translateY 滚动 */
|
||||
.num{left:120.6px;top:63px;height:20px;transform-origin:50% 60%;display:flex;font:900 13.5px/20px -apple-system,"PingFang SC","Noto Sans SC",sans-serif;
|
||||
color:#FFF3C4;text-shadow:0 1px 3px rgba(120,30,10,.28)}
|
||||
.num .win{display:inline-block;height:20px;overflow:hidden;position:relative;width:.62em}
|
||||
.num .reel{display:flex;flex-direction:column;will-change:transform}
|
||||
.num .reel i{font-style:normal;height:20px;text-align:center}
|
||||
.num.hl{color:#fff}
|
||||
.hand{left:0;top:0;width:150px;height:46px;transform-origin:9px 14px;will-change:transform;
|
||||
filter:drop-shadow(0 0 .8px #fff) drop-shadow(0 0 .6px #fff) drop-shadow(0 3px 3px rgba(120,40,20,.28))}
|
||||
.q{left:0;top:0;font:900 16px/1 -apple-system,"PingFang SC",sans-serif;color:#fff;text-shadow:0 1px 3px rgba(120,30,10,.35);opacity:0}
|
||||
.bubble{left:152px;top:90px;padding:7px 12px 8px 12px;border-radius:4px 14px 14px 14px;background:#fff;white-space:nowrap;
|
||||
font-size:13px;font-weight:800;color:#D9583A;letter-spacing:.3px;box-shadow:0 6px 16px rgba(150,60,30,.22);
|
||||
transform-origin:0 0;opacity:0;display:flex;align-items:center;gap:5px}
|
||||
.bubble::after{content:"";position:absolute;left:8px;top:-5px;border:6px solid transparent;border-bottom-color:#fff;border-top:0}
|
||||
.bubble svg{width:12px;height:14px}
|
||||
.below{display:block;width:100%}
|
||||
.replay{position:absolute;left:50%;transform:translateX(-50%);z-index:5;border:0;padding:7px 16px;border-radius:999px;
|
||||
font:600 12.5px -apple-system,"PingFang SC",sans-serif;color:#C2563B;background:rgba(255,255,255,.85);
|
||||
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 6px 16px rgba(180,80,50,.16),inset 0 0 0 1px #fff;
|
||||
opacity:0;pointer-events:none;transition:opacity .3s}
|
||||
.replay.on{opacity:1;pointer-events:auto}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="hero-wrap" id="wrap">
|
||||
<div class="stage" id="stage">
|
||||
<img class="bg" src="../_hero/bg.jpg" alt="">
|
||||
<img class="pill" src="../_hero/pill.png" alt="">
|
||||
<div class="num" id="num"><span>6</span><span class="win"><span class="reel" id="reel"><i>8</i><i>7</i><i>6</i></span></span></div>
|
||||
<img class="week" src="../_hero/week.png" alt="">
|
||||
<img class="prog" src="../_hero/prog.png" alt="">
|
||||
|
||||
<!-- 小胖手(手指朝左):前臂从右边伸进来,食指伸直,其余手指蜷着 -->
|
||||
<svg class="hand" id="hand" viewBox="0 0 150 46" aria-hidden="true">
|
||||
<path d="M150 14 L58 17 Q50 18 50 23 Q50 28 58 29 L150 33 Z" fill="#FFD6BF"/>
|
||||
<path d="M86 18.5 q2 4.5 0 9 M96 18.2 q2 4.8 0 9.6" stroke="#F4BCA0" stroke-width="1.8" fill="none" stroke-linecap="round"/>
|
||||
<ellipse cx="44" cy="25" rx="16" ry="13" fill="#FFDCC8"/>
|
||||
<ellipse cx="38" cy="34" rx="7" ry="5" fill="#FFD2BA"/>
|
||||
<ellipse cx="47" cy="36" rx="6.5" ry="4.6" fill="#FFD2BA"/>
|
||||
<path d="M32 31 q-3 3 1 6" stroke="#F4BCA0" stroke-width="1.6" fill="none" stroke-linecap="round"/>
|
||||
<path d="M33 19 L10 15" stroke="#FFDCC8" stroke-width="9" stroke-linecap="round"/>
|
||||
<ellipse cx="9.5" cy="14.5" rx="2.6" ry="2.2" fill="#FFF1E8"/>
|
||||
<path d="M44 13 q6 -4 10 2" stroke="#FFDCC8" stroke-width="7" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<div class="q" id="q">?</div>
|
||||
|
||||
<div class="bubble" id="bubble">
|
||||
<span>我有点着急。</span>
|
||||
<svg viewBox="0 0 12 14" aria-hidden="true"><path d="M6 1 C6 1 1.5 7 1.5 9.3 A4.5 4.5 0 0 0 10.5 9.3 C10.5 7 6 1 6 1z" fill="#8FD0F2"/><ellipse cx="4.6" cy="9.4" rx="1.1" ry="1.6" fill="#fff" opacity=".8"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<button class="replay" id="replay">再看一次</button>
|
||||
</div>
|
||||
<img class="below" src="../_hero/below.jpg" alt="">
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const REC = /[?&]rec=1/.test(location.search);
|
||||
const $ = id => document.getElementById(id);
|
||||
const wrap = $('wrap'), stage = $('stage'), hand = $('hand'), reel = $('reel'), num = $('num'), q = $('q'), bubble = $('bubble'), replay = $('replay');
|
||||
function fit() {
|
||||
const w = Math.min(wrap.parentNode.clientWidth, 480), s = w / 375;
|
||||
stage.style.transform = 'scale(' + s + ')'; wrap.style.height = 250 * s + 'px';
|
||||
replay.style.top = 214 * s + 'px';
|
||||
}
|
||||
fit(); addEventListener('resize', fit);
|
||||
|
||||
const cl = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
||||
const seg = (t, a, b) => cl((t - a) / (b - a));
|
||||
const lerp = (a, b, u) => a + (b - a) * u;
|
||||
const eio = u => u < .5 ? 2 * u * u : 1 - Math.pow(-2 * u + 2, 2) / 2;
|
||||
const eo = u => 1 - Math.pow(1 - u, 3);
|
||||
const pop = u => u <= 0 ? 0 : 1 + Math.exp(-6 * u) * Math.sin(14 * u) * .35 - Math.exp(-14 * u);
|
||||
const T = 8.4;
|
||||
|
||||
// 指尖坐标(头图坐标系)。手的 SVG 指尖在 (9,14)
|
||||
const TIP_HOME = [236, 74], TIP_UP = [135, 61], TIP_DN = [135, 71];
|
||||
function frame(t) {
|
||||
let tip = TIP_HOME.slice(), ang = 0, digit = 1; // reel 位置:0=8,1=7,2=6
|
||||
// 1.0–1.25 从羊水囊边探出一点,停一下;1.25–1.9 伸到「67」上方
|
||||
if (t < 1.0) tip = [400, 74];
|
||||
else if (t < 1.25) tip = [lerp(400, 250, eo(seg(t, 1.0, 1.25))), 74];
|
||||
else if (t < 1.4) tip = [250, 74];
|
||||
else if (t < 1.95) { const u = eio(seg(t, 1.4, 1.95)); tip = [lerp(250, TIP_UP[0], u), lerp(74, TIP_UP[1], u) - Math.sin(u * Math.PI) * 6]; }
|
||||
// 1.95–2.35 往下拨:7 → 6
|
||||
else if (t < 2.35) { const u = eio(seg(t, 1.95, 2.35)); tip = [TIP_UP[0], lerp(TIP_UP[1], TIP_DN[1], u)]; digit = 1 + u; ang = -8 * u; }
|
||||
// 2.35–3.3 停住,看一眼……发现不对(问号、手抖两下)
|
||||
else if (t < 3.3) { tip = TIP_DN.slice(); digit = 2; ang = -8; const w = seg(t, 2.8, 3.25); tip[0] += Math.sin(w * Math.PI * 6) * 1.6 * (1 - w); }
|
||||
// 3.3–3.55 手指绕到下面,3.55–3.85 偷偷往上拨回 7
|
||||
else if (t < 3.55) { const u = eio(seg(t, 3.3, 3.55)); tip = [lerp(TIP_DN[0], TIP_DN[0] + 2, u), lerp(TIP_DN[1], TIP_DN[1] + 3, u)]; digit = 2; ang = lerp(-8, 6, u); }
|
||||
else if (t < 3.85) { const u = eio(seg(t, 3.55, 3.85)); tip = [TIP_DN[0] + 2, lerp(TIP_DN[1] + 3, TIP_UP[1], u)]; digit = 2 - u; ang = lerp(6, 10, u); }
|
||||
// 3.85–4.25 飞快缩回去
|
||||
else if (t < 4.3) { const u = seg(t, 3.9, 4.25); tip = [lerp(TIP_UP[0], 420, u * u), lerp(TIP_UP[1], 70, u)]; ang = lerp(10, 0, u); }
|
||||
else tip = [420, 70];
|
||||
|
||||
hand.style.transform = 'translate(' + (tip[0] - 9).toFixed(1) + 'px,' + (tip[1] - 14).toFixed(1) + 'px) rotate(' + ang.toFixed(1) + 'deg)';
|
||||
reel.style.transform = 'translateY(' + (-digit * 20).toFixed(2) + 'px)';
|
||||
num.classList.toggle('hl', t > 1.95 && t < 3.85);
|
||||
const hk = seg(t, 1.85, 2.05) * (1 - seg(t, 3.9, 4.15)); // 被拨的时候数字微微放大,好让人看见
|
||||
num.style.transform = 'scale(' + (1 + .22 * hk).toFixed(3) + ')';
|
||||
// 问号
|
||||
const qi = seg(t, 2.55, 2.75), qo = seg(t, 3.25, 3.4);
|
||||
q.style.opacity = Math.min(qi, 1 - qo);
|
||||
q.style.transform = 'translate(' + (TIP_DN[0] + 16) + 'px,' + (38 - qi * 4) + 'px) scale(' + (.6 + .4 * pop(qi)).toFixed(3) + ')';
|
||||
// 气泡 4.35 → 7.4
|
||||
const bIn = seg(t, 4.35, 4.85), bOut = seg(t, 7.2, 7.45);
|
||||
bubble.style.opacity = Math.min(cl(bIn * 3), 1 - bOut);
|
||||
bubble.style.transform = 'scale(' + (bIn > 0 ? Math.max(0, pop(bIn * 1.5)) * (1 - bOut * .2) : 0).toFixed(3) + ')';
|
||||
replay.classList.toggle('on', !REC && t > T - .4);
|
||||
}
|
||||
|
||||
if (REC) { window.__set = frame; window.__T = T; frame(0); window.__ready = true; return; }
|
||||
let start = performance.now();
|
||||
(function loop(now) { frame(Math.min((now - start) / 1000, T)); requestAnimationFrame(loop); })(start);
|
||||
replay.addEventListener('click', () => { start = performance.now() - 300; });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
211
demo/occupy/index.html
Normal file
211
demo/occupy/index.html
Normal file
@@ -0,0 +1,211 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||||
<title>宝宝今天占领首页 · 创意预览</title>
|
||||
<!-- 2026-10-03 创意预览(不进正式首页):「宝宝今天占领首页」。
|
||||
头图拆成几层(背景 / 倒计时胶囊 / 「30周+3天」/ 进度条,均为首页预览版截图抠出来的透明层),
|
||||
卡通宝宝用代码画(SVG)。时间线:1s 后宝宝从右下角爬出来 → 把「30周+3天」顶歪 → 跳上去坐着、戴上小皇冠 →
|
||||
冒一句「今天这里归我管。」→ 几秒后跳下来爬走,数字弹回原位。
|
||||
整段由 frame(t) 一个纯函数按时间算出全部位置,?rec=1 时由 __set(t) 逐帧驱动(生成动图用)。 -->
|
||||
<style>
|
||||
html,body{margin:0;background:#F8F3EF;-webkit-tap-highlight-color:transparent;font-family:-apple-system,"PingFang SC","Noto Sans SC",sans-serif}
|
||||
.page{position:relative;max-width:480px;margin:0 auto;overflow:hidden}
|
||||
.hero-wrap{position:relative;width:100%;overflow:hidden}
|
||||
.stage{position:absolute;left:0;top:0;width:375px;height:270px;transform-origin:0 0;overflow:hidden}
|
||||
.stage>*{position:absolute}
|
||||
.bg{left:0;top:0;width:375px;height:270px}
|
||||
.pill{left:14px;top:52px;width:159.69px;height:42px}
|
||||
.num{left:121.6px;top:63px;width:16px;height:20px;line-height:20px;font:900 13.5px/20px -apple-system,"PingFang SC","Noto Sans SC",sans-serif;
|
||||
color:#FFF3C4;text-shadow:0 1px 3px rgba(120,30,10,.28);text-align:center}
|
||||
.week{left:14px;top:86px;width:110.9px;height:70px;transform-origin:14px 60px;will-change:transform}
|
||||
.prog{left:14px;top:150px;width:192px;height:36px}
|
||||
.baby{filter:drop-shadow(0 0 .8px #fff) drop-shadow(0 0 .8px #fff) drop-shadow(0 0 .6px #fff) drop-shadow(0 3px 3px rgba(120,40,20,.3));will-change:transform;opacity:0}
|
||||
#crawl{width:104px;height:80px;left:0;top:0}
|
||||
#sit{width:74px;height:81px;left:0;top:0;transform-origin:50% 80%}
|
||||
.bubble{left:104px;top:30px;padding:7px 12px 8px;border-radius:14px 14px 14px 4px;background:#fff;white-space:nowrap;
|
||||
font-size:13px;font-weight:800;color:#D9583A;letter-spacing:.3px;box-shadow:0 6px 16px rgba(150,60,30,.22);
|
||||
transform-origin:0 100%;opacity:0}
|
||||
.bubble::after{content:"";position:absolute;left:-5px;bottom:4px;border:6px solid transparent;border-right-color:#fff;border-left:0}
|
||||
.below{display:block;width:100%}
|
||||
.replay{position:absolute;left:50%;transform:translateX(-50%);z-index:5;border:0;padding:7px 16px;border-radius:999px;
|
||||
font:600 12.5px -apple-system,"PingFang SC",sans-serif;color:#C2563B;background:rgba(255,255,255,.85);
|
||||
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 6px 16px rgba(180,80,50,.16),inset 0 0 0 1px #fff;
|
||||
opacity:0;pointer-events:none;transition:opacity .3s}
|
||||
.replay.on{opacity:1;pointer-events:auto}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="hero-wrap" id="wrap">
|
||||
<div class="stage" id="stage">
|
||||
<img class="bg" src="../_hero/bg.jpg" alt="">
|
||||
<img class="pill" src="../_hero/pill.png" alt="">
|
||||
<div class="num">67</div>
|
||||
<img class="prog" src="../_hero/prog.png" alt="">
|
||||
<img class="week" id="week" src="../_hero/week.png" alt="">
|
||||
|
||||
<!-- 爬行宝宝(侧面,头朝左)。四肢用粗圆头线段画,好绕肩/髋转动 -->
|
||||
<svg class="baby" id="crawl" viewBox="0 0 130 100" aria-hidden="true">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||
<g id="cLegB"><path d="M90 66 L97 88 L113 89" stroke="#EDB293" stroke-width="14"/></g>
|
||||
<g id="cArmB"><path d="M47 60 L43 87" stroke="#EDB293" stroke-width="12"/></g>
|
||||
</g>
|
||||
<ellipse cx="72" cy="58" rx="29" ry="20" fill="#FFD9C4"/>
|
||||
<path d="M58 44 q14 -6 28 0" stroke="#F7C4A9" stroke-width="3" fill="none" stroke-linecap="round"/>
|
||||
<ellipse cx="95" cy="59" rx="15" ry="18" fill="#fff"/>
|
||||
<path d="M85 44 q-6 15 0 30" stroke="#F3D7CB" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||
<circle cx="99" cy="52" r="2.2" fill="#FFC2B4"/><circle cx="104" cy="62" r="2.2" fill="#FFC2B4"/><circle cx="96" cy="67" r="2.2" fill="#FFC2B4"/>
|
||||
<g stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||
<g id="cLegF"><path d="M86 68 L91 91 L108 93" stroke="#FFD2BA" stroke-width="15"/></g>
|
||||
<g id="cArmF"><path d="M54 62 L51 90" stroke="#FFD2BA" stroke-width="13"/></g>
|
||||
</g>
|
||||
<g id="cHead">
|
||||
<circle cx="34" cy="40" r="25" fill="#FFDCC8"/>
|
||||
<circle cx="50" cy="42" r="5.5" fill="#FFCDB4"/>
|
||||
<path d="M28 16 q4 -9 12 -5 q-7 1 -6 7" fill="#8E5A44"/>
|
||||
<path d="M37 15.5 q6 -5 11 1" stroke="#8E5A44" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||
<circle cx="21" cy="38" r="3.4" fill="#4A2A20"/><circle cx="22.2" cy="36.8" r="1.2" fill="#fff"/>
|
||||
<ellipse cx="25" cy="48" rx="5.5" ry="3.8" fill="#FF9C93" opacity=".55"/>
|
||||
<path d="M11 47 q4 4 8 1" stroke="#B45A42" stroke-width="2.2" fill="none" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- 坐着的宝宝(正面),一手叉腰一手举拳,头上小皇冠 -->
|
||||
<svg class="baby" id="sit" viewBox="0 0 110 120" aria-hidden="true">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||
<g id="sLegL"><path d="M43 90 L38 110" stroke="#FFD2BA" stroke-width="15"/></g>
|
||||
<g id="sLegR"><path d="M67 90 L72 110" stroke="#FFD2BA" stroke-width="15"/></g>
|
||||
</g>
|
||||
<ellipse cx="37" cy="113" rx="9" ry="5.5" fill="#FFCDB4"/><ellipse cx="73" cy="113" rx="9" ry="5.5" fill="#FFCDB4"/>
|
||||
<ellipse cx="55" cy="80" rx="24" ry="21" fill="#FFD9C4"/>
|
||||
<path d="M31 84 q24 22 48 0 q2 14 -24 16 q-26 -2 -24 -16z" fill="#fff"/>
|
||||
<path d="M32 84 q23 12 46 0" stroke="#F3D7CB" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||
<g stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||
<path d="M35 70 L22 79 L33 89" stroke="#FFD2BA" stroke-width="11"/>
|
||||
<g id="sArmR"><path d="M75 70 L88 58 L90 44" stroke="#FFD2BA" stroke-width="11"/></g>
|
||||
</g>
|
||||
<g id="sFist"><circle cx="90" cy="41" r="7" fill="#FFCDB4"/></g>
|
||||
<circle cx="55" cy="40" r="27" fill="#FFDCC8"/>
|
||||
<circle cx="29" cy="42" r="5" fill="#FFCDB4"/><circle cx="81" cy="42" r="5" fill="#FFCDB4"/>
|
||||
<path d="M50 14 q4 -9 12 -5 q-7 1 -6 7" fill="#8E5A44"/>
|
||||
<path d="M44 41 q2.5 -4 5 0" stroke="#4A2A20" stroke-width="2.6" fill="none" stroke-linecap="round"/>
|
||||
<path d="M61 41 q2.5 -4 5 0" stroke="#4A2A20" stroke-width="2.6" fill="none" stroke-linecap="round"/>
|
||||
<path d="M42 33 l7 2 M68 33 l-7 2" stroke="#8E5A44" stroke-width="2" stroke-linecap="round"/>
|
||||
<ellipse cx="40" cy="49" rx="5.5" ry="3.6" fill="#FF9C93" opacity=".55"/><ellipse cx="70" cy="49" rx="5.5" ry="3.6" fill="#FF9C93" opacity=".55"/>
|
||||
<path d="M49 50 q6 7 12 0 z" fill="#C2563B" stroke="#C2563B" stroke-width="1.6" stroke-linejoin="round"/>
|
||||
<g id="crown" transform="rotate(-10 55 12)">
|
||||
<path d="M42 18 L40 4 L48 11 L55 1 L62 11 L70 4 L68 18 Z" fill="#FFD25A" stroke="#F0A93A" stroke-width="1.6" stroke-linejoin="round"/>
|
||||
<circle cx="55" cy="13" r="2" fill="#F06A45"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="bubble" id="bubble">今天这里归我管。</div>
|
||||
</div>
|
||||
<button class="replay" id="replay">再看一次</button>
|
||||
</div>
|
||||
<img class="below" src="../_hero/below.jpg" alt="">
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const REC = /[?&]rec=1/.test(location.search);
|
||||
const $ = id => document.getElementById(id);
|
||||
const wrap = $('wrap'), stage = $('stage'), week = $('week'), crawl = $('crawl'), sit = $('sit'), bubble = $('bubble'), replay = $('replay');
|
||||
const crown = $('crown');
|
||||
function fit() {
|
||||
const w = Math.min(wrap.parentNode.clientWidth, 480), s = w / 375;
|
||||
stage.style.transform = 'scale(' + s + ')'; wrap.style.height = 250 * s + 'px';
|
||||
replay.style.top = 214 * s + 'px';
|
||||
}
|
||||
fit(); addEventListener('resize', fit);
|
||||
|
||||
const cl = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
||||
const seg = (t, a, b) => cl((t - a) / (b - a));
|
||||
const lerp = (a, b, u) => a + (b - a) * u;
|
||||
const eio = u => u < .5 ? 2 * u * u : 1 - Math.pow(-2 * u + 2, 2) / 2;
|
||||
const eo = u => 1 - Math.pow(1 - u, 3);
|
||||
const spring = u => u <= 0 ? 0 : 1 - Math.exp(-7 * u) * Math.cos(13 * u); // 带回弹的 0→1
|
||||
const rot = (id, a, cx, cy) => $(id).setAttribute('transform', 'rotate(' + a.toFixed(2) + ' ' + cx + ' ' + cy + ')');
|
||||
const pop = (u) => u <= 0 ? 0 : 1 + Math.exp(-6 * u) * Math.sin(14 * u) * .35 - Math.exp(-14 * u) * 1;
|
||||
|
||||
// 关键位置(逻辑像素,375 宽头图坐标)
|
||||
const C0 = [384, 152], C1 = [114, 84]; // 爬行起点(右下角外)→ 顶到「30周」右侧
|
||||
const SIT = [26, 33]; // 坐姿宝宝左上角(坐在「30」顶上)
|
||||
const T = 9.6; // 一轮总时长
|
||||
|
||||
function frame(t) {
|
||||
// ---- 爬进来 1.0 → 2.7 ----
|
||||
let cx = C0[0], cy = C0[1], cOp = 0, flip = 1, gait = 0;
|
||||
const uIn = seg(t, 1.0, 2.7);
|
||||
if (t >= 1.0 && t < 3.05) {
|
||||
const u = eo(uIn);
|
||||
cx = lerp(C0[0], C1[0], u); cy = lerp(C0[1], C1[1], u);
|
||||
cOp = 1; gait = t * 9;
|
||||
if (t > 2.7) { const p = seg(t, 2.7, 2.95); cx = C1[0] - 9 * Math.sin(p * Math.PI * .5); gait = 2.7 * 9 + p * 3; } // 用头顶一下
|
||||
}
|
||||
// ---- 被顶歪:2.75 起数字块向左倒 -9°,带回弹 ----
|
||||
let wa = 0, wx = 0;
|
||||
if (t >= 2.75) {
|
||||
const s = spring(seg(t, 2.75, 3.6) * 1.15);
|
||||
wa = -9 * s; wx = -5 * s;
|
||||
}
|
||||
// ---- 跳上去 3.05 → 3.45(爬姿换坐姿,抛物线)----
|
||||
let sx = SIT[0], sy = SIT[1], sOp = 0, sRot = -6, sSq = 1;
|
||||
if (t >= 3.05 && t < 7.0) {
|
||||
const u = seg(t, 3.05, 3.45);
|
||||
const fx = C1[0] - 9 - 4, fy = C1[1] - 14;
|
||||
sx = lerp(fx, SIT[0], eio(u)); sy = lerp(fy, SIT[1], eio(u)) - Math.sin(u * Math.PI) * 34;
|
||||
sOp = 1;
|
||||
if (u >= 1) { const q = seg(t, 3.45, 3.85); sSq = 1 - Math.exp(-8 * q) * Math.sin(q * 18) * .12; } // 落座压扁回弹
|
||||
}
|
||||
// ---- 坐着:晃腿、举拳 ----
|
||||
const sit_t = t - 3.45;
|
||||
rot('sLegL', Math.sin(t * 7) * 14, 43, 90); rot('sLegR', Math.sin(t * 7 + 2.2) * 14, 67, 90);
|
||||
const fist = sit_t > 0 ? Math.sin(sit_t * 9) * 8 * Math.exp(-Math.max(0, sit_t - 1.4)) : 0;
|
||||
rot('sArmR', fist, 75, 70); rot('sFist', fist, 75, 70);
|
||||
const cr = seg(t, 3.55, 3.85);
|
||||
crown.setAttribute('transform', 'translate(0 ' + ((1 - eo(cr)) * -16).toFixed(1) + ') rotate(-10 55 12)');
|
||||
crown.style.opacity = cr;
|
||||
// ---- 气泡 3.75 → 6.6 ----
|
||||
const bIn = seg(t, 3.75, 4.25), bOut = seg(t, 6.45, 6.7);
|
||||
bubble.style.opacity = Math.min(cl(bIn * 3), 1 - bOut);
|
||||
bubble.style.transform = 'scale(' + (bIn > 0 ? Math.max(0, pop(bIn * 1.5)) * (1 - bOut * .2) : 0).toFixed(3) + ')';
|
||||
// ---- 跳下来 6.75 → 7.15,再爬走 7.15 → 8.5,数字块弹回 ----
|
||||
if (t >= 6.75 && t < 7.15) {
|
||||
const u = seg(t, 6.75, 7.15);
|
||||
sx = lerp(SIT[0], 132, eio(u)); sy = lerp(SIT[1], 112, eio(u)) - Math.sin(u * Math.PI) * 30;
|
||||
sOp = 1; sRot = lerp(-6, 8, u);
|
||||
}
|
||||
if (t >= 7.15 && t < 8.6) {
|
||||
const u = seg(t, 7.15, 8.5);
|
||||
cx = lerp(128, 396, eio(u)); cy = lerp(118, 158, eio(u)); cOp = 1; flip = -1; gait = t * 10; sOp = 0;
|
||||
}
|
||||
if (t >= 7.0 && t < 7.15) sOp = 1;
|
||||
if (t >= 6.8) {
|
||||
const s = spring(seg(t, 6.8, 7.8) * 1.15);
|
||||
wa = -9 * (1 - s); wx = -5 * (1 - s);
|
||||
}
|
||||
if (t < 3.05 || t >= 7.15) sOp = 0;
|
||||
if (t >= 3.05 && t < 7.15) cOp = 0;
|
||||
|
||||
week.style.transform = 'translateX(' + wx.toFixed(2) + 'px) rotate(' + wa.toFixed(2) + 'deg)';
|
||||
crawl.style.opacity = cOp;
|
||||
const bob = Math.abs(Math.sin(gait)) * -2.4;
|
||||
crawl.style.transform = 'translate(' + cx.toFixed(1) + 'px,' + (cy + bob).toFixed(1) + 'px) scaleX(' + flip + ')';
|
||||
rot('cArmF', Math.sin(gait) * 20, 54, 62); rot('cArmB', -Math.sin(gait) * 20, 47, 60);
|
||||
rot('cLegF', -Math.sin(gait) * 16, 86, 68); rot('cLegB', Math.sin(gait) * 16, 90, 66);
|
||||
rot('cHead', Math.sin(gait * 2) * 2.5, 40, 55);
|
||||
sit.style.opacity = sOp;
|
||||
sit.style.transform = 'translate(' + sx.toFixed(1) + 'px,' + sy.toFixed(1) + 'px) rotate(' + sRot + 'deg) scale(' + (1 / sSq).toFixed(3) + ',' + sSq.toFixed(3) + ')';
|
||||
replay.classList.toggle('on', !REC && t > T - .4);
|
||||
}
|
||||
|
||||
if (REC) { window.__set = frame; window.__T = T; frame(0); window.__ready = true; return; }
|
||||
let start = performance.now() - 0;
|
||||
(function loop(now) { const t = (now - start) / 1000; frame(Math.min(t, T)); requestAnimationFrame(loop); })(start);
|
||||
replay.addEventListener('click', () => { start = performance.now() - 300; });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user