demo/bump v3.1:右侧卡片去掉数胎动,改成「这周宝宝学会了」+ 宝宝悄悄话
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,8 @@
|
|||||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||||||
<title>宝宝在肚子里动 · 创意预览</title>
|
<title>宝宝在肚子里动 · 创意预览</title>
|
||||||
<!-- 2026-10-03 创意预览(不进正式首页):「整块头图是妈妈的肚皮」v3。
|
<!-- 2026-10-03 创意预览(不进正式首页):「整块头图是妈妈的肚皮」v3。
|
||||||
头图纹理改为 JS 现场合成(canvas):肚皮底 belly_bg.jpg + LOGO + 倒计时 + 周数 + 进度条 + 右侧「今日胎动」玻璃卡。
|
头图纹理改为 JS 现场合成(canvas):肚皮底 belly_bg.jpg + LOGO + 倒计时 + 周数 + 进度条 + 右侧「这周的宝宝」玻璃卡。
|
||||||
右侧原来是胎儿插画,现在放胎动卡:宝宝每动一下,次数 +1,下面一行写「刚刚 · 小拳头顶了一下」;
|
右侧原来是胎儿插画,现在放「这周宝宝学会了 · 睁眼看光」+ 宝宝的一句悄悄话,顶出来/被摁回去时换话;
|
||||||
卡片也画在纹理里,所以被顶到时会和字一起鼓起来变形。
|
卡片也画在纹理里,所以被顶到时会和字一起鼓起来变形。
|
||||||
WebGL 把纹理当一层肉肉的肚皮:
|
WebGL 把纹理当一层肉肉的肚皮:
|
||||||
· 宝宝时不时用不同部位往外顶:小脚丫(尖一点的小帐篷,顶上隐约几个脚趾)、小拳头(小而圆)、
|
· 宝宝时不时用不同部位往外顶:小脚丫(尖一点的小帐篷,顶上隐约几个脚趾)、小拳头(小而圆)、
|
||||||
@@ -145,14 +145,14 @@ void main(){
|
|||||||
const uS = U('S'), uF = U('F'), uK = U('K'), uG = U('G'), uP = U('P'), uJ = U('J'), uT = U('Tm');
|
const uS = U('S'), uF = U('F'), uK = U('K'), uG = U('G'), uP = U('P'), uJ = U('J'), uT = U('Tm');
|
||||||
gl.uniform2f(uS, W, H);
|
gl.uniform2f(uS, W, H);
|
||||||
|
|
||||||
/* ---------- 纹理:canvas 现场合成,胎动卡的数字和文案会变 ---------- */
|
/* ---------- 纹理:canvas 现场合成,宝宝卡的悄悄话会变 ---------- */
|
||||||
const tc = document.createElement('canvas'); tc.width = W * SC; tc.height = H * SC;
|
const tc = document.createElement('canvas'); tc.width = W * SC; tc.height = H * SC;
|
||||||
const cx = tc.getContext('2d');
|
const cx = tc.getContext('2d');
|
||||||
const SRC = { bg: 'belly_bg.jpg', top: 'top.png', pill: '../_hero/pill.png', week: '../_hero/week.png', prog: '../_hero/prog.png' };
|
const SRC = { bg: 'belly_bg.jpg', top: 'top.png', pill: '../_hero/pill.png', week: '../_hero/week.png', prog: '../_hero/prog.png' };
|
||||||
const IMG = {}; let loaded = 0;
|
const IMG = {}; let loaded = 0;
|
||||||
const tex = gl.createTexture(); let ready = false;
|
const tex = gl.createTexture(); let ready = false;
|
||||||
const FONT = '-apple-system,"PingFang SC","Noto Sans SC",sans-serif';
|
const FONT = '-apple-system,"PingFang SC","Noto Sans SC",sans-serif';
|
||||||
const card = { n: 12, line: '宝宝醒着,正在活动', ago: '' };
|
const card = { line: '我认得妈妈的声音' };
|
||||||
function rr(x, y, w, h, r) { cx.beginPath(); cx.moveTo(x + r, y); cx.arcTo(x + w, y, x + w, y + h, r); cx.arcTo(x + w, y + h, x, y + h, r); cx.arcTo(x, y + h, x, y, r); cx.arcTo(x, y, x + w, y, r); cx.closePath(); }
|
function rr(x, y, w, h, r) { cx.beginPath(); cx.moveTo(x + r, y); cx.arcTo(x + w, y, x + w, y + h, r); cx.arcTo(x + w, y + h, x, y + h, r); cx.arcTo(x, y + h, x, y, r); cx.arcTo(x, y, x + w, y, r); cx.closePath(); }
|
||||||
function compose() {
|
function compose() {
|
||||||
cx.setTransform(SC, 0, 0, SC, 0, 0);
|
cx.setTransform(SC, 0, 0, SC, 0, 0);
|
||||||
@@ -173,33 +173,32 @@ void main(){
|
|||||||
cx.shadowColor = 'transparent';
|
cx.shadowColor = 'transparent';
|
||||||
cx.drawImage(IMG.week, 14, 86, 110.9, 70);
|
cx.drawImage(IMG.week, 14, 86, 110.9, 70);
|
||||||
cx.drawImage(IMG.prog, 14, 150, 192, 36);
|
cx.drawImage(IMG.prog, 14, 150, 192, 36);
|
||||||
// 「今日胎动」玻璃卡
|
// 「这周宝宝学会了」玻璃卡:30周的发育小变化 + 宝宝的一句悄悄话(被顶/被摁时换话)
|
||||||
const X = 232, Y = 62, CW = 128, CH = 118;
|
const X = 228, Y = 60, CW = 134, CH = 122;
|
||||||
cx.save();
|
cx.save();
|
||||||
cx.shadowColor = 'rgba(150,55,30,.16)'; cx.shadowBlur = 18; cx.shadowOffsetY = 8;
|
cx.shadowColor = 'rgba(150,55,30,.16)'; cx.shadowBlur = 18; cx.shadowOffsetY = 8;
|
||||||
rr(X, Y, CW, CH, 20); cx.fillStyle = 'rgba(255,255,255,.17)'; cx.fill();
|
rr(X, Y, CW, CH, 20); cx.fillStyle = 'rgba(255,255,255,.17)'; cx.fill();
|
||||||
cx.restore();
|
cx.restore();
|
||||||
const hl = cx.createLinearGradient(X, Y, X, Y + CH); hl.addColorStop(0, 'rgba(255,255,255,.22)'); hl.addColorStop(1, 'rgba(255,255,255,.04)');
|
const hl = cx.createLinearGradient(X, Y, X, Y + CH); hl.addColorStop(0, 'rgba(255,255,255,.24)'); hl.addColorStop(1, 'rgba(255,255,255,.05)');
|
||||||
rr(X, Y, CW, CH, 20); cx.fillStyle = hl; cx.fill();
|
rr(X, Y, CW, CH, 20); cx.fillStyle = hl; cx.fill();
|
||||||
cx.lineWidth = 1; cx.strokeStyle = 'rgba(255,255,255,.55)'; rr(X + .5, Y + .5, CW - 1, CH - 1, 19.5); cx.stroke();
|
cx.lineWidth = 1; cx.strokeStyle = 'rgba(255,255,255,.55)'; rr(X + .5, Y + .5, CW - 1, CH - 1, 19.5); cx.stroke();
|
||||||
cx.shadowColor = 'rgba(120,30,10,.25)'; cx.shadowBlur = 4; cx.shadowOffsetY = 1;
|
cx.shadowColor = 'rgba(120,30,10,.25)'; cx.shadowBlur = 4; cx.shadowOffsetY = 1;
|
||||||
cx.fillStyle = '#FFF3C4'; cx.beginPath(); cx.arc(X + 17, Y + 20, 3.2, 0, 7); cx.fill();
|
cx.fillStyle = 'rgba(255,255,255,.9)'; cx.font = '600 11px ' + FONT; cx.fillText('这周宝宝学会了', X + 14, Y + 19);
|
||||||
cx.fillStyle = 'rgba(255,255,255,.95)'; cx.font = '700 12px ' + FONT; cx.fillText('今日胎动', X + 26, Y + 20.5);
|
cx.font = '900 25px ' + FONT; cx.fillStyle = '#fff'; cx.fillText('睁眼看光', X + 13, Y + 45);
|
||||||
cx.font = '900 40px ' + FONT; cx.fillStyle = '#fff';
|
cx.font = '600 10.5px ' + FONT; cx.fillStyle = 'rgba(255,255,255,.9)'; cx.fillText('晒太阳时,我能感觉到亮', X + 14, Y + 68);
|
||||||
const ns = String(card.n); cx.fillText(ns, X + 14, Y + 55);
|
|
||||||
const nw = cx.measureText(ns).width;
|
|
||||||
cx.font = '700 13px ' + FONT; cx.fillText('次', X + 18 + nw, Y + 62);
|
|
||||||
cx.shadowColor = 'transparent';
|
cx.shadowColor = 'transparent';
|
||||||
cx.fillStyle = 'rgba(255,255,255,.28)'; cx.fillRect(X + 14, Y + 82, CW - 28, 1);
|
cx.fillStyle = 'rgba(255,255,255,.28)'; cx.fillRect(X + 14, Y + 81, CW - 28, 1);
|
||||||
|
// 悄悄话:小气泡尾巴 + 一句话
|
||||||
|
cx.fillStyle = 'rgba(255,255,255,.92)'; cx.beginPath(); cx.arc(X + 18, Y + 101, 2.6, 0, 7); cx.fill();
|
||||||
cx.shadowColor = 'rgba(120,30,10,.22)'; cx.shadowBlur = 3; cx.shadowOffsetY = 1;
|
cx.shadowColor = 'rgba(120,30,10,.22)'; cx.shadowBlur = 3; cx.shadowOffsetY = 1;
|
||||||
cx.font = '600 11px ' + FONT; cx.fillStyle = 'rgba(255,255,255,.92)';
|
cx.font = '600 11px ' + FONT; cx.fillStyle = 'rgba(255,255,255,.95)';
|
||||||
let ln = card.line; while (cx.measureText(ln).width > CW - 28 && ln.length > 2) ln = ln.slice(0, -1);
|
let ln = card.line; while (cx.measureText(ln).width > CW - 40 && ln.length > 2) ln = ln.slice(0, -1);
|
||||||
cx.fillText(ln, X + 14, Y + 99);
|
cx.fillText(ln, X + 26, Y + 101.5);
|
||||||
cx.shadowColor = 'transparent';
|
cx.shadowColor = 'transparent';
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, tc);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, tc);
|
||||||
}
|
}
|
||||||
function setCard(n, line) { if (n != null) card.n = n; if (line) card.line = line; if (ready) compose(); }
|
function setCard(line) { card.line = line; if (ready) compose(); }
|
||||||
Object.keys(SRC).forEach(k => {
|
Object.keys(SRC).forEach(k => {
|
||||||
const im = new Image(); im.onload = () => {
|
const im = new Image(); im.onload = () => {
|
||||||
IMG[k] = im;
|
IMG[k] = im;
|
||||||
@@ -226,10 +225,10 @@ void main(){
|
|||||||
/* ---------- 宝宝的各个部位:形状不同,动法不同 ---------- */
|
/* ---------- 宝宝的各个部位:形状不同,动法不同 ---------- */
|
||||||
// rx/ry 椭圆半径,sharp 尖度,toe 脚趾显隐,amp 高度,dur 停留秒数,roll 是否慢慢拱着挪
|
// rx/ry 椭圆半径,sharp 尖度,toe 脚趾显隐,amp 高度,dur 停留秒数,roll 是否慢慢拱着挪
|
||||||
const PARTS = {
|
const PARTS = {
|
||||||
foot: { name: '小脚丫', verb: '蹬了一下', rx: 64, ry: 72, sharp: 1.7, toe: 1, amp: 9.5, dur: [2.4, 3.4] },
|
foot: { name: '小脚丫', say: ['妈妈,我在伸腿', '踢一脚打个招呼'], caught: '脚丫被你抓到啦', rx: 64, ry: 72, sharp: 1.7, toe: 1, amp: 9.5, dur: [2.4, 3.4] },
|
||||||
fist: { name: '小拳头', verb: '顶了一下', rx: 46, ry: 46, sharp: 1.9, toe: 0, amp: 8, dur: [1.8, 2.6] },
|
fist: { name: '小拳头', say: ['我在挥小拳头', '妈妈看我力气大'], caught: '小拳头被你握住啦', rx: 46, ry: 46, sharp: 1.9, toe: 0, amp: 8, dur: [1.8, 2.6] },
|
||||||
knee: { name: '小膝盖', verb: '拱了一下', rx: 50, ry: 86, sharp: 1.6, toe: 0, amp: 8.5, dur: [2.2, 3] },
|
knee: { name: '小膝盖', say: ['我在翻个身', '这里有点挤哦'], caught: '膝盖被摁回去啦', rx: 50, ry: 86, sharp: 1.6, toe: 0, amp: 8.5, dur: [2.2, 3] },
|
||||||
butt: { name: '小屁股', verb: '拱起来了', rx: 100, ry: 84, sharp: 1.25, toe: 0, amp: 7, dur: [3, 3.8], roll: true },
|
butt: { name: '小屁股', say: ['我在伸懒腰~', '换个姿势睡觉'], caught: '屁股被你拍到了', rx: 100, ry: 84, sharp: 1.25, toe: 0, amp: 7, dur: [3, 3.8], roll: true },
|
||||||
};
|
};
|
||||||
const ORDER = ['foot', 'fist', 'butt', 'knee'];
|
const ORDER = ['foot', 'fist', 'butt', 'knee'];
|
||||||
const found = {};
|
const found = {};
|
||||||
@@ -242,8 +241,8 @@ void main(){
|
|||||||
const jg = { x: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 }, y: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 } };
|
const jg = { x: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 }, y: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 } };
|
||||||
let mode = 'wait', wait = .5, t = 0, seq = 0;
|
let mode = 'wait', wait = .5, t = 0, seq = 0;
|
||||||
|
|
||||||
// 一半以上的概率专挑字和卡片顶(大字「30」、倒计时胶囊、进度条、slogan、胎动卡),字会被撑起来变形
|
// 一半以上的概率专挑字和卡片顶(大字「30」、倒计时胶囊、进度条、slogan、宝宝卡),字会被撑起来变形
|
||||||
const TEXT_SPOTS = [[62, 120], [100, 74], [104, 124], [96, 166], [230, 26], [292, 112], [300, 84], [150, 168]];
|
const TEXT_SPOTS = [[62, 120], [100, 74], [104, 124], [96, 166], [230, 26], [292, 104], [300, 80], [150, 168]];
|
||||||
function pickSpot() {
|
function pickSpot() {
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < 40; i++) {
|
||||||
let x, y;
|
let x, y;
|
||||||
@@ -264,7 +263,7 @@ void main(){
|
|||||||
ft.h.to = P.amp; ft.h.k = P.roll ? 70 : 140; ft.h.c = P.roll ? 9 : 9; ft.h.vel += P.roll ? 30 : 120;
|
ft.h.to = P.amp; ft.h.k = P.roll ? 70 : 140; ft.h.c = P.roll ? 9 : 9; ft.h.vel += P.roll ? 30 : 120;
|
||||||
ft.life = 0; ft.dur = P.dur[0] + Math.random() * (P.dur[1] - P.dur[0]); ft.kicked = false;
|
ft.life = 0; ft.dur = P.dur[0] + Math.random() * (P.dur[1] - P.dur[0]); ft.kicked = false;
|
||||||
mode = 'show';
|
mode = 'show';
|
||||||
setCard(card.n + 1, '刚刚' + P.name + P.verb);
|
setCard(P.say[Math.floor(Math.random() * P.say.length)]);
|
||||||
}
|
}
|
||||||
function hide(fast) {
|
function hide(fast) {
|
||||||
ft.h.to = 0;
|
ft.h.to = 0;
|
||||||
@@ -283,7 +282,7 @@ void main(){
|
|||||||
ft.flee = { dx: dx / l, dy: dy / l, t: 0 };
|
ft.flee = { dx: dx / l, dy: dy / l, t: 0 };
|
||||||
hide(true); mode = 'flee'; wait = 1.1 + Math.random() * .8;
|
hide(true); mode = 'flee'; wait = 1.1 + Math.random() * .8;
|
||||||
if (navigator.vibrate) navigator.vibrate(15);
|
if (navigator.vibrate) navigator.vibrate(15);
|
||||||
setCard(null, ft.part.name + '被摁回去啦');
|
setCard(ft.part.caught);
|
||||||
found[ft.key] = true;
|
found[ft.key] = true;
|
||||||
const chip = hunt.querySelector('[data-k="' + ft.key + '"]'); if (chip) chip.classList.add('on');
|
const chip = hunt.querySelector('[data-k="' + ft.key + '"]'); if (chip) chip.classList.add('on');
|
||||||
hunt.classList.add('pop'); setTimeout(() => hunt.classList.remove('pop'), 300);
|
hunt.classList.add('pop'); setTimeout(() => hunt.classList.remove('pop'), 300);
|
||||||
|
|||||||
Reference in New Issue
Block a user