Files
jiazi-gala-poster-demo/assets/js/page3.js
T
rogerwang2008 07c2ab1f25 feat: page3 铺「黑」背景并统一配色,page1 落日修正为正圆
page3 空白格按 page1「天」的明度铺「黑」,nametag 与「演职员表」四周整格避让
(标题留白缩矮一格后居中,牌子整体上移一格);电线、麻雀、草、消防栓配色并入
熔岩暖色族。page1 去掉太阳上下端帽,行宽按半径 8 的圆逐行取整。
标题非强调部分改用与两页小字一致的 #e6dac6。
2026-11-11 04:10:59 +08:00

196 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 第 3 页场景数据
前景:演职员表与 nametag;背景:电线、麻雀、草、火山。
空白格铺淡色「黑」,nametag 与标题四周的空白格保持留空。 */
(() => {
/* 固定种子的噪声,保证每次渲染一致 */
const noise = (x, y) => {
let h = Math.imul(x, 374761393) + Math.imul(y, 668265263);
h = Math.imul(h ^ (h >>> 13), 1274126177);
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
};
/* 火山:占据下半屏,下 2/3 为山体、上 1/3 为熔岩棋盘格 */
const volcano = [];
const VOLCANO_TOP = 21;
const VOLCANO_BOTTOM = 42;
/* 每列的熔岩下沿,形成不规则的熔岩流 */
const lavaBottom = Array.from({ length: 28 }, (_, x) => {
return VOLCANO_TOP + 6 + Math.floor(noise(x, 3) * 3);
});
[5, 13, 21].forEach((x) => {
lavaBottom[x] += 4 + Math.floor(noise(x, 9) * 3);
});
/* 两层熔岩的分界:按列小幅随机起伏 */
const lavaSplit = Array.from({ length: 28 }, (_, x) => {
const offset = Math.round((noise(x, 13) - 0.5) * 4);
return Math.max(
VOLCANO_TOP + 2,
Math.min(VOLCANO_TOP + 5, VOLCANO_TOP + 3 + offset)
);
});
/* 主熔岩区以下为山体,两侧做对称平滑处理 */
const ROCK_START = VOLCANO_TOP + 7;
for (let y = VOLCANO_TOP; y <= VOLCANO_BOTTOM; y++) {
/* 距底部 5 格起铺满整幅宽度,避免山体过陡 */
const fullWidthAt = VOLCANO_BOTTOM - 5;
const t = (y - VOLCANO_TOP) / (fullWidthAt - VOLCANO_TOP);
const baseWidth =
y >= fullWidthAt ? 28 : Math.round(10 + 18 * Math.min(1, Math.max(0, t)));
let width;
let left;
if (y < ROCK_START) {
/* 熔岩区保持原有随机形状 */
const jitter =
y >= fullWidthAt ? 0 : Math.round((noise(0, y) - 0.5) * 2);
width = Math.max(4, Math.min(28, baseWidth + jitter));
left = Math.floor((28 - width) / 2);
left += Math.round((noise(1, y) - 0.5) * 2);
left = Math.max(0, Math.min(28 - width, left));
if (y === VOLCANO_TOP + 1) left = Math.max(0, left - 1);
} else {
/* 山体左右对称,宽度取偶数和,保留低频小幅随机 */
const wobble = Math.round((noise(7, Math.floor(y / 3)) - 0.5) * 2);
let w = y >= fullWidthAt ? 28 : Math.min(28, baseWidth + wobble);
w -= w % 2;
width = Math.max(4, w);
left = (28 - width) / 2;
}
/* 顶部火山口:仅两行凹下,第一行缺口更宽 */
const craterGap = y < ROCK_START ? [4, 2][y - VOLCANO_TOP] || 0 : 0;
const gapStart = craterGap ? Math.floor((width - craterGap) / 2) : 0;
for (let i = 0; i < width; i++) {
if (craterGap && i >= gapStart && i < gapStart + craterGap) continue;
const x = left + i;
const isLava = y <= lavaBottom[x];
const isUpperLava = isLava && y <= lavaSplit[x];
volcano.push({
text: isLava ? ((x + y) % 2 === 0 ? '熔' : '岩') : '石',
x,
y,
cls: isLava ? (isUpperLava ? 'lava-a' : 'lava-b') : 'rock',
});
}
}
/* 第十行左侧补一个「熔」 */
const row10 = volcano.filter((cell) => cell.y === VOLCANO_TOP + 9);
const row10Left = Math.min(...row10.map((cell) => cell.x));
volcano.push({
text: '熔',
x: row10Left - 1,
y: VOLCANO_TOP + 9,
cls: 'lava-b',
});
/* 扣空:整格留白,供前景文字与 nametag 避让(先扣空,其余背景字仍可覆盖) */
const clear = (x, y, w, h) =>
Array.from({ length: h }, (_, j) => ({
text: ' '.repeat(w),
x,
y: y + j,
cls: 'void',
}));
const NAMES = [
'郑滕飞', '秦雪莹', '关宸宇', '王天晓', '谢雨舟', '储明威', '庞熙若',
'刘婉莹', '陈彦淇', '古雅文', '周胤伯', '郑佳欣', '娄敬庭', '张加弛',
'李昀芷', '吴红霓', '鲍馨仪', '刘润泽', '李若熙', '马希予', '袁立桓',
'蒋子模', '李有睿', '王海心', '朱丽烨', '朱彦阳', '汪楚钧', '吴玥',
'张梓轩', '罗午阳', '郭柯言', '王之饶', '应玥', '湛茜茜', '黄钰涵',
'虞韫嘉',
];
const TAG = {
cols: 4,
rows: 9,
width: 5,
height: 2,
gapX: 1,
gapY: 1,
left: 2,
top: 11,
};
/* 每个牌子占 5 × 2 格 */
const tagClear = [];
for (let row = 0; row < TAG.rows; row++) {
for (let col = 0; col < TAG.cols; col++) {
tagClear.push(
...clear(
TAG.left + col * (TAG.width + TAG.gapX),
TAG.top + row * (TAG.height + TAG.gapY),
TAG.width,
TAG.height
)
);
}
}
TextScene.render({
cols: 28,
rows: 44,
skyChar: '黑',
skyClass: 'ink',
glyphs: [
/* 前景避让:演职员表(x10.8–17.2 / y4.7–6.3)四周留约半格,再是全部牌子 */
...clear(9, 4, 10, 3),
...tagClear,
/* 电线:整行贯穿页面 */
{ text: '电线'.repeat(14), x: 0, y: 8, cls: 'wire' },
/* 麻雀:竖排于从左到右第四格,落在电线上方 */
{ text: '麻雀', x: 3, y: 6, cls: 'sparrow', vertical: true },
/* 火山铺在底部背景,草、消防栓在其上层 */
...volcano,
/* 草:最底部一排;第 1、8、15、27 列加高一格 */
...[0, 7, 14, 26].map((x) => ({ text: '草', x, y: 42, cls: 'grass' })),
{ text: '草'.repeat(28), x: 0, y: 43, cls: 'grass' },
/* 消防栓:最后三行分散竖排九根,左侧留 2 格 */
...[2, 5, 8, 10, 13, 16, 19, 23, 25].map((x) => ({
text: '消防栓',
x,
y: 41,
cls: 'hydrant',
vertical: true,
})),
],
});
const scene = document.querySelector('.scene');
const credits = document.createElement('h1');
credits.className = 'credits';
credits.textContent = '演职员表';
scene.append(credits);
/* 36 个 nametag:4 列 × 9 行,补满 */
let nameIndex = 0;
for (let row = 0; row < TAG.rows; row++) {
for (let col = 0; col < TAG.cols; col++) {
const name = NAMES[nameIndex++];
if (!name) break;
const tag = document.createElement('div');
tag.className = 'nametag';
tag.style.left = `${TAG.left + col * (TAG.width + TAG.gapX)}em`;
tag.style.top = `${TAG.top + row * (TAG.height + TAG.gapY)}em`;
const label = document.createElement('span');
label.className = 'nametag-name';
label.textContent = name;
tag.append(label);
scene.append(tag);
}
}
})();