143 lines
9.2 KiB
HTML
143 lines
9.2 KiB
HTML
<!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>
|