Files
jiazi-gala-poster-demo/assets/js/scene.js
T
rogerwang2008 6085fc24d3 feat: 完善文字画视觉并新增 PDF 导出
- 调整飞鸟、楼群与标题配色和布局
- 新增 uv + Playwright PDF 导出脚本,四边各留 1em
- 页面标题改为「封面」
2026-11-10 21:22:49 +08:00

172 lines
5.1 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.
/* 落日文字画 · 28 × 44 网格
构图全部由下方数组描述;空白格自动用「天」铺满。
修改元素只需调整数组中的坐标与尺寸,行列范围:x 0–27,y 0–43。 */
(() => {
const COLS = 28;
const ROWS = 44;
/* 云:x/y 为最宽一行的左上角,三行 3/5/3;上下拉开分布 */
const clouds = [
{ x: 10, y: 2, tone: 'pink' },
{ x: 18, y: 6, tone: 'violet' },
{ x: 1, y: 10, tone: 'orange' },
{ x: 23, y: 13, tone: 'lilac' },
{ x: 4, y: 16, tone: 'rose' },
];
/* 落日:整体下移贴近楼群,形成日落;
色调按数组顺序,顶部偏红 → 底部偏黄 */
const sun = [
{ y: 18, x: 13, w: 2 },
{ y: 19, x: 10, w: 8 },
{ y: 20, x: 9, w: 10 },
{ y: 21, x: 8, w: 12 },
{ y: 22, x: 7, w: 14 },
{ y: 23, x: 7, w: 14 },
{ y: 24, x: 6, w: 16 },
{ y: 25, x: 6, w: 16 },
{ y: 26, x: 6, w: 16 },
{ y: 27, x: 6, w: 16 },
{ y: 28, x: 6, w: 16 },
{ y: 29, x: 7, w: 14 },
{ y: 30, x: 7, w: 14 },
{ y: 31, x: 8, w: 12 },
{ y: 32, x: 9, w: 10 },
{ y: 33, x: 10, w: 8 },
{ y: 34, x: 13, w: 2 },
];
/* 飞鸟:数量减少,间距不等,仅少量成对;上下拉开分布 */
const birds = [
{ x: 0, y: 2, kind: 'magpie' },
{ x: 26, y: 2, kind: 'magpie' },
{ x: 1, y: 4, kind: 'sparrow' },
{ x: 3, y: 8, kind: 'crow' },
{ x: 6, y: 13, kind: 'magpie' },
{ x: 9, y: 15, kind: 'sparrow' },
{ x: 26, y: 5, kind: 'crow' },
{ x: 17, y: 9, kind: 'sparrow' },
{ x: 21, y: 12, kind: 'magpie' },
{ x: 19, y: 15, kind: 'crow' },
];
/* 天线:单列「线」,从 y 向下叠 h 格;最右楼顶另加一根深色 */
const antennas = [
{ x: 7, y: 28, h: 3 },
{ x: 22, y: 27, h: 4 },
{ x: 26, y: 34, h: 3, cls: 'ant ant-dark' },
];
/* 楼群:n 对应 .bld-n,最终按 x/y/w/h 铺满「楼」(高度裁到第 44 行) */
const buildings = [
{ n: 1, x: 0, y: 35, w: 3, h: 9 },
{ n: 2, x: 3, y: 39, w: 3, h: 5 },
{ n: 3, x: 6, y: 31, w: 4, h: 13 },
{ n: 4, x: 10, y: 36, w: 3, h: 8 },
{ n: 5, x: 13, y: 30, w: 4, h: 14 },
{ n: 6, x: 17, y: 34, w: 3, h: 10 },
{ n: 7, x: 20, y: 31, w: 4, h: 13 },
{ n: 8, x: 24, y: 37, w: 4, h: 7 },
];
/* 标题区:整格扣出的居中矩形,左右各留 3 格 */
const TITLE = {
text: '关于「甲子」的虚构故事',
emphasis: '甲子',
top: 14,
height: 5,
side: 3,
};
const BIRD_WORDS = { sparrow: '麻雀', magpie: '喜鹊', crow: '乌鸦' };
const grid = Array.from({ length: ROWS }, () =>
Array.from({ length: COLS }, () => ({ ch: '天', cls: 'sky' }))
);
const put = (x, y, ch, cls) => {
if (x < 0 || x >= COLS || y < 0 || y >= ROWS) {
throw new Error(`元素超出网格范围:(${x}, ${y})`);
}
grid[y][x] = { ch, cls };
};
const putRow = (y, x, n, ch, cls) => {
for (let i = 0; i < n; i++) put(x + i, y, ch, cls);
};
for (const { x, y, tone } of clouds) {
const cls = `cloud cloud-${tone}`;
putRow(y, x + 1, 3, '云', cls);
putRow(y + 1, x, 5, '云', cls);
putRow(y + 2, x + 1, 3, '云', cls);
}
sun.forEach(({ y, x, w }, i) => {
const tone = `sun-t${i + 1}`;
for (let dx = 0; dx < w; dx++) put(x + dx, y, '日', `sun ${tone}`);
});
for (const { x, y, kind } of birds) {
const [a, b] = BIRD_WORDS[kind];
put(x, y, a, `bird-${kind}`);
put(x + 1, y, b, `bird-${kind}`);
}
for (const { x, y, h, cls = 'ant' } of antennas) {
for (let i = 0; i < h; i++) put(x, y + i, '线', cls);
}
for (const { n, x, y, w, h } of buildings) {
for (let j = 0; j < h; j++) putRow(y + j, x, w, '楼', `bld bld-${n}`);
}
for (let y = TITLE.top; y < TITLE.top + TITLE.height; y++) {
for (let x = TITLE.side; x < COLS - TITLE.side; x++) {
grid[y][x] = { ch: ' ', cls: 'cut' };
}
}
/* 把同 class 的连续字符合并成一个 span,减少 DOM 节点 */
const html = grid
.map((row) => {
let line = '';
for (let i = 0; i < COLS; ) {
let j = i + 1;
while (j < COLS && row[j].cls === row[i].cls) j++;
const text = row.slice(i, j).map((cell) => cell.ch).join('');
line += `<span class="${row[i].cls}">${text}</span>`;
i = j;
}
return `<div class="line">${line}</div>`;
})
.join('');
const scene = document.querySelector('.scene');
scene.innerHTML = html;
const panel = document.createElement('div');
panel.className = 'title-panel';
panel.style.top = `${TITLE.top}em`;
panel.style.left = `${TITLE.side}em`;
panel.style.width = `${COLS - TITLE.side * 2}em`;
panel.style.height = `${TITLE.height}em`;
const title = document.createElement('h1');
title.className = 'title';
const [before, after] = TITLE.text.split(TITLE.emphasis);
if (after === undefined) {
title.textContent = TITLE.text;
} else {
const em = document.createElement('span');
em.className = 'title-em';
em.textContent = TITLE.emphasis;
title.append(before, em, after);
}
panel.append(title);
scene.append(panel);
})();