commit 17a224be27bfdd1f1a81c7f70a9f0b8cf1ed45ca Author: rogerwang2008 Date: Tue Nov 10 21:04:17 2026 +0800 chore: 初始化项目仓库 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a3c7d0e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# 统一换行符,避免 Windows/Linux 之间来回转换 +* text=auto eol=lf + +*.ttf binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..314e800 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# 操作系统 +.DS_Store +Thumbs.db +desktop.ini + +# 编辑器 / IDE +.idea/ +.vscode/ +*.iml + +# 临时文件 +*.log +.tmp-* diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ad6f2f --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# 落日 · 文字游戏 + +一个 28 列 × 44 行的汉字文字画单页。黑底、每格一个汉字,空白处由淡色「天」铺满;画面包含晚霞云、落日、飞鸟、天线与城市楼群,上方以整格扣出的黑色标题框居中呈现「关于「甲子」的虚构故事」。 + +## 效果概述 + +- 28 列 × 44 行,严格按字符网格排布,全页文字统一字号。 +- 空白格自动填充淡色「天」,形成低对比背景纹理。 +- 元素按层绘制:云 → 落日 → 飞鸟 → 天线 → 楼群。楼群最后绘制,保证太阳在无楼处仍保留完整圆弧。 +- 标题区域按整格对齐扣空,并叠放黑色面板,居中显示白色标题。 +- 使用 Zpix 像素字体;正文行通过负字距补偿,把单字步进压回 1em。 + +## 目录结构 + +``` +. +├─ index.html # 页面骨架 +├─ assets/ +│ ├─ css/style.css # 布局、字号与各元素配色 +│ ├─ js/scene.js # 场景数据与网格渲染 +│ └─ fonts/zpix.ttf # Zpix 像素字体 +└─ references/a.html # 原始参考页 +``` + +## 运行 + +直接打开 `index.html` 即可。若需通过本地服务器预览: + +```bash +python -m http.server 8000 +# 浏览器访问 http://localhost:8000 +``` + +## 调整指南 + +### 构图(assets/js/scene.js) + +| 数据 | 含义 | +| --- | --- | +| `clouds` | 云:三行 3/5/3 的云形,x/y 为最宽一行的左上角 | +| `sun` | 落日:逐行圆形切片,数组顺序对应顶部的红到底部的黄 | +| `birds` | 飞鸟:每只占 2 格(麻雀 / 海鸥 / 乌鸦) | +| `antennas` | 天线:单列「线」,从 y 向下叠 h 格 | +| `buildings` | 楼群:x/y/w/h,n 对应 `.bld-n` | +| `TITLE` | 标题文案、起始行、行高、左右留格数 | + +坐标为 0–27 列、0–43 行;越界会直接抛错提示。 + +### 样式(assets/css/style.css) + +- 整体字号:`.scene` 的 `font-size`,所有元素统一继承。 +- 背景「天」:`.sky`。 +- 云:`.cloud` 的明度与 `.cloud-*` 的色相。 +- 落日:`.sun` 的整体明度(当前为原色 90%),`.sun-t1` 至 `.sun-t17` 为逐行色阶。 +- 飞鸟:`.bird-sparrow`、`.bird-gull`、`.bird-crow`。 +- 天线:`.ant`。 +- 楼群:`.bld` 与 `.bld-1` 至 `.bld-8`。 +- 标题:`.title-panel`(黑色矩形)与 `.title-panel .title`(白色文字及字号)。 + +## 实现说明 + +- `scene.js` 先建立 28×44 网格并全部填「天」,再按数组覆盖元素;同 class 的连续字符合并为单个 `span` 输出。 +- 标题区把对应格子清空,并在同一位置叠放绝对定位的黑色面板;边缘与字符整格对齐,不会出现半个字。 +- Zpix 单字步进为 13/12 em,因此 `.line` 使用 `letter-spacing: -0.083333em` 补偿,保证 28 列恰好落在 28em 宽内。 + +## 字体许可 + +`assets/fonts/zpix.ttf` 为第三方 Zpix 像素字体,版权归原作者所有;使用或分发前请遵循其原始许可说明。 diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..a915181 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,115 @@ +@font-face { + font-family: "Zpix"; + src: url("../fonts/zpix.ttf") format("truetype"); + font-display: swap; +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + background: #000; +} + +body { + min-height: 100dvh; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + font-family: "Zpix", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", + "Noto Sans SC", "Source Han Sans SC", sans-serif; + -webkit-text-size-adjust: 100%; +} + +/* 28 列 × 44 行文字网格,1em = 1 个汉字; + 字号在这里统一控制,所有元素(含天线)字号完全一致 */ +.scene { + position: relative; + font-size: min(3.4vw, calc(100dvh / 44)); + line-height: 1em; + width: 28em; + height: 44em; + user-select: none; + font-kerning: none; +} + +.line { + display: block; + height: 1em; + line-height: 1em; + white-space: pre; + /* Zpix 单字步进为 13/12 em,补偿回 1em,保证 28 列正好落在 28em 内 */ + letter-spacing: -0.083333em; +} + +/* —— 标题区:按整格扣出的黑色矩形,左右各留 3 格 —— */ +.title-panel { + position: absolute; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + background: #000; +} + +.title-panel .title { + margin: 0; + color: #fff; + font-size: 1.6em; + font-weight: 400; + line-height: 1; + letter-spacing: 0; + white-space: nowrap; +} + +/* —— 背景:空白格里的淡色「天」 —— */ +.sky { color: rgba(190, 200, 240, 0.15); } + +/* —— 云:固定颜色,明度取太阳与黑底之间的一半,色相沿用晚霞色 —— */ +.cloud { color: color-mix(in srgb, var(--cloud, #ff9ebb) 35%, #000); } +.cloud-pink { --cloud: #ff9ebb; } +.cloud-violet { --cloud: #b98cff; } +.cloud-orange { --cloud: #ffa95c; } +.cloud-rose { --cloud: #ff85b3; } +.cloud-lilac { --cloud: #c98cff; } + +/* —— 落日:太阳逐行色调,t1 顶部偏红 → t17 底部偏黄;整体明度为原色的 90% —— */ +.sun { color: color-mix(in srgb, var(--tone, #ff5a36) 90%, #000); } +.sun-t1 { --tone: #ff5a36; } +.sun-t2 { --tone: #ff5a36; } +.sun-t3 { --tone: #ff6533; } +.sun-t4 { --tone: #ff7330; } +.sun-t5 { --tone: #ff812d; } +.sun-t6 { --tone: #ff8f2b; } +.sun-t7 { --tone: #ff9d29; } +.sun-t8 { --tone: #ffaa2a; } +.sun-t9 { --tone: #ffb62d; } +.sun-t10 { --tone: #ffc132; } +.sun-t11 { --tone: #ffcb38; } +.sun-t12 { --tone: #ffd33f; } +.sun-t13 { --tone: #ffda46; } +.sun-t14 { --tone: #ffe04e; } +.sun-t15 { --tone: #ffe556; } +.sun-t16 { --tone: #ffe95e; } +.sun-t17 { --tone: #ffec66; } + +/* —— 飞鸟 —— */ +.bird-sparrow { color: #e0a45c; } +.bird-gull { color: #a0c8f0; } +.bird-crow { color: #b0a8c8; } + +/* —— 天线 —— */ +.ant { color: #8a8a9e; } + +/* —— 楼群:灰色系,明度与原棕色一致 —— */ +.bld { color: #3f3f3f; } +.bld-1 { color: #3b3b3b; } +.bld-2 { color: #343434; } +.bld-3 { color: #464646; } +.bld-4 { color: #393939; } +.bld-5 { color: #4e4e4e; } +.bld-6 { color: #414141; } +.bld-7 { color: #494949; } +.bld-8 { color: #353535; } diff --git a/assets/fonts/zpix.ttf b/assets/fonts/zpix.ttf new file mode 100644 index 0000000..2d39d9f Binary files /dev/null and b/assets/fonts/zpix.ttf differ diff --git a/assets/js/scene.js b/assets/js/scene.js new file mode 100644 index 0000000..a0380e8 --- /dev/null +++ b/assets/js/scene.js @@ -0,0 +1,160 @@ +/* 落日文字画 · 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: 'gull' }, + { x: 26, y: 2, kind: 'gull' }, + { x: 1, y: 4, kind: 'sparrow' }, + { x: 3, y: 8, kind: 'crow' }, + { x: 6, y: 13, kind: 'gull' }, + { x: 9, y: 15, kind: 'sparrow' }, + { x: 26, y: 5, kind: 'crow' }, + { x: 17, y: 9, kind: 'sparrow' }, + { x: 21, y: 12, kind: 'gull' }, + { x: 19, y: 15, kind: 'crow' }, + ]; + + /* 天线:单列「线」,从 y 向下叠 h 格;楼顶在第 31 行,露出部分为 3 / 4 格 */ + const antennas = [ + { x: 7, y: 28, h: 3 }, + { x: 22, y: 27, h: 4 }, + ]; + + /* 楼群: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: '关于「甲子」的虚构故事', + top: 14, + height: 5, + side: 3, + }; + + const BIRD_WORDS = { sparrow: '麻雀', gull: '海鸥', 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 } of antennas) { + for (let i = 0; i < h; i++) put(x, y + i, '线', 'ant'); + } + + 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 += `${text}`; + i = j; + } + return `
${line}
`; + }) + .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'; + title.textContent = TITLE.text; + + panel.append(title); + scene.append(panel); +})(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..e3237f4 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + +落日 · 文字游戏 + + + + + +
+ + + diff --git a/references/a.html b/references/a.html new file mode 100644 index 0000000..2aee556 --- /dev/null +++ b/references/a.html @@ -0,0 +1,314 @@ + + + + + +落日 · 文字游戏 + + + + +
+ + + + +
 云云云  +云云云云云 + 云云云 
+ + +
 云云云  +云云云云云 + 云云云 
+ + +
 云云云  +云云云云云 + 云云云 
+ + +
 云云云  +云云云云云 + 云云云 
+ + +
 云云云  +云云云云云 + 云云云 
+ + + + +
日日
+
日日日日日日日日
+
日日日日日日日日日日
+
日日日日日日日日日日日日
+
日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日
+
日日日日日日日日日日日日日日
+
日日日日日日日日日日日日
+
日日日日日日日日日日
+
日日日日日日日日
+
日日
+ + + + + +
麻雀
+
海鸥
+
乌鸦
+
麻雀
+
海鸥
+
乌鸦
+
麻雀
+
海鸥
+
乌鸦
+
麻雀
+ + +
乌鸦
+
海鸥
+
麻雀
+
乌鸦
+
海鸥
+
麻雀
+
乌鸦
+
海鸥
+
麻雀
+
乌鸦
+ + + + +
天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线
+ +
天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线 +天线
+ + + + + +
楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼
+ + +
楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼
+ + +
楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼
+ + +
楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼
+ + +
楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼
+ + +
楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼 +楼楼楼
+ + +
楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼
+ + +
楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼 +楼楼楼楼
+ +
+ + + \ No newline at end of file