@sliiu/present-themes 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ # 第三方许可证
2
+
3
+ 公开包自有代码的版权人是 Sliiu Inc,使用 AGPL-3.0-or-later,全文是同目录的 `AGPL-3.0.txt`。版权行见 `COPYRIGHT`。下面这些库不是 SlimPPT 写的,许可证保持原样。版本是整理这份声明时锁文件里的版本。
4
+
5
+ 各依赖包自带的 `LICENSE` 随 npm 安装保留。这里记下标识,以及不能被商业授权盖住的那几份。
6
+
7
+ ## 不能并进商业授权
8
+
9
+ `mtx-decompressor@1.6.0`(`@ppt/parser`)是 MPL-2.0,作者 ChristopherVR。全文是 `MPL-2.0.txt`。只约束这个库的文件,可以和闭源程序放在一起,不能改成专有许可。
10
+
11
+ `@ffmpeg/ffmpeg@0.12.15` 和 `@ffmpeg/util@0.12.2` 是 MIT,作者 Jerome Wu。编辑器在 `packages/editor/src/video-export.ts` 里另从 `https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm` 加载内核。`0.12.6` 的 npm 元数据写着 MIT。该 WebAssembly 的构建串包含 libx264、libx265 和 libmp3lame,实际是 GPL-2.0-or-later。GPL-2.0 全文是 `GPL-2.0.txt`;“or later” 也可以选用 GNU GPL 3.0。商业授权不含这段内核。
12
+
13
+ ## Apache-2.0
14
+
15
+ 全文是 `Apache-2.0.txt`。可以放进 AGPL 程序,文件本身仍是 Apache-2.0。
16
+
17
+ | 包 | 版本 | 版权声明 |
18
+ |---|---|---|
19
+ | `ai`、`@ai-sdk/anthropic`、`@ai-sdk/google`、`@ai-sdk/mcp`、`@ai-sdk/openai`、`@ai-sdk/openai-compatible`,以及它们带上的 `@ai-sdk/gateway`、`@ai-sdk/provider`、`@ai-sdk/provider-utils`、`@vercel/oidc` | 见附录 | Copyright 2023 Vercel, Inc. |
20
+ | `@paddleocr/paddleocr-js` | 0.4.2 | PaddlePaddle Authors |
21
+ | `@techstark/opencv-js` | 4.10.0-release.1 | Apache-2.0 |
22
+ | `tesseract.js`、`tesseract.js-core` | 7.0.0 | Apache-2.0 |
23
+ | `emf-converter` | 4.8.0 | ChristopherVR |
24
+
25
+ ## 字体
26
+
27
+ `katex@0.18.9` 的代码是 MIT,Copyright (c) 2013-2020 Khan Academy and other contributors。随包的字体文件是 SIL OFL 1.1,全文是 `OFL-1.1.txt`。字体不能改许可,可以随软件分发;用这些字体排出来的幻灯片不因此变成 OFL。
28
+
29
+ 主题只引用 Google Fonts 的 CSS,字体文件不打进包。
30
+
31
+ 网站壳自带的那份字体许可证是另一份 OFL,不进公开包。
32
+
33
+ ## 其余直接依赖
34
+
35
+ | 包 | 版本 | 许可证 | 版权声明 |
36
+ |---|---|---|---|
37
+ | `fast-xml-parser` | 5.11.1 | MIT | Copyright (c) 2017 Amit Kumar Gupta |
38
+ | `fflate` | 0.8.3 | MIT | Copyright (c) 2026 Arjun Barrett |
39
+ | `three` | 0.186.0 | MIT | Copyright © 2010-2026 three.js authors |
40
+ | `zod` | 4.6.5 | MIT | Copyright (c) 2025 Colin McDonnell |
41
+ | `@modelcontextprotocol/sdk` | 1.30.1 | MIT | Copyright (c) 2024 Anthropic, PBC |
42
+ | `lucide` | 1.48.0 | ISC | Copyright (c) 2026 Lucide Icons and Contributors。其中来自 Feather 的图标另为 MIT,Copyright (c) 2013-present Cole Bemis |
43
+ | `clipper-lib` | 6.4.2 | Boost-1.0 | Copyright Angus Johnson 2010-2017。使用、修改和分发遵循 Boost Software License 1.0 |
44
+ | `js-yaml` | 4.3.2 | MIT | Copyright (C) 2011-2015 by Vitaly Puzrin |
45
+ | `onnxruntime-web`、`onnxruntime-common` | 1.30.0 | MIT | 版权声明以包内 LICENSE 为准 |
46
+
47
+ `@modelcontextprotocol/sdk` 会带上 `express`、`hono` 这些服务端库。它们随 `@ppt/ai` 安装,许可证见附录,不因此把网关算进公开包。
48
+
49
+ `json-schema@0.4.0` 是 AFL-2.1 或 BSD-3-Clause。这里取 BSD-3-Clause。
50
+
51
+ ## 不在这份声明里
52
+
53
+ 网关及其依赖、`tests/pptx-viewer` 依赖的 `pptx-react-viewer`(Apache-2.0)、构建工具(`lightningcss`、`sharp`、`axe-core`、Sentry 命令行)。这些不进公开包。
54
+
55
+ ## 附录:公开包生产依赖
56
+
57
+ `animation`、`core`、`parser`、`serializer`、`three-bake`、`renderer`、`transition-fx`、`plugin-sdk`、`themes`、`ui`、`player`、`editor`、`ai` 的生产依赖,含传递依赖。不含 `@ppt/*` 自身,不含上面那份另行加载的 `@ffmpeg/core`。
58
+
59
+ | 包 | 版本 | 许可证 |
60
+ |---|---|---|
61
+ | `@ai-sdk/anthropic` | 4.0.62 | `Apache-2.0` |
62
+ | `@ai-sdk/gateway` | 4.0.91 | `Apache-2.0` |
63
+ | `@ai-sdk/google` | 4.0.79 | `Apache-2.0` |
64
+ | `@ai-sdk/mcp` | 2.0.57 | `Apache-2.0` |
65
+ | `@ai-sdk/openai` | 4.0.74 | `Apache-2.0` |
66
+ | `@ai-sdk/openai-compatible` | 3.0.55 | `Apache-2.0` |
67
+ | `@ai-sdk/provider` | 4.0.18 | `Apache-2.0` |
68
+ | `@ai-sdk/provider-utils` | 5.0.47 | `Apache-2.0` |
69
+ | `@exodus/bytes` | 1.16.0 | `MIT` |
70
+ | `@ffmpeg/ffmpeg` | 0.12.15 | `MIT` |
71
+ | `@ffmpeg/types` | 0.12.4 | `MIT` |
72
+ | `@ffmpeg/util` | 0.12.2 | `MIT` |
73
+ | `@hono/node-server` | 2.1.1 | `MIT` |
74
+ | `@modelcontextprotocol/sdk` | 1.30.1 | `MIT` |
75
+ | `@nodable/entities` | 3.0.0 | `MIT` |
76
+ | `@paddleocr/paddleocr-js` | 0.4.2 | `Apache-2.0` |
77
+ | `@protobufjs/aspromise` | 1.1.2 | `BSD-3-Clause` |
78
+ | `@protobufjs/base64` | 1.1.2 | `BSD-3-Clause` |
79
+ | `@protobufjs/codegen` | 2.0.5 | `BSD-3-Clause` |
80
+ | `@protobufjs/eventemitter` | 1.1.1 | `BSD-3-Clause` |
81
+ | `@protobufjs/fetch` | 1.1.1 | `BSD-3-Clause` |
82
+ | `@protobufjs/float` | 1.0.2 | `BSD-3-Clause` |
83
+ | `@protobufjs/path` | 1.1.2 | `BSD-3-Clause` |
84
+ | `@protobufjs/pool` | 1.1.0 | `BSD-3-Clause` |
85
+ | `@protobufjs/utf8` | 1.1.2 | `BSD-3-Clause` |
86
+ | `@standard-schema/spec` | 1.1.0 | `MIT` |
87
+ | `@techstark/opencv-js` | 4.10.0-release.1 | `Apache-2.0` |
88
+ | `@types/node` | 26.6.2 | `MIT` |
89
+ | `@vercel/oidc` | 3.2.0 | `Apache-2.0` |
90
+ | `@workflow/serde` | 4.1.0 | `Apache-2.0` |
91
+ | `accepts` | 2.0.0 | `MIT` |
92
+ | `ai` | 7.0.113 | `Apache-2.0` |
93
+ | `ajv` | 8.20.0 | `MIT` |
94
+ | `ajv-formats` | 3.0.1 | `MIT` |
95
+ | `anynum` | 1.0.1 | `MIT` |
96
+ | `argparse` | 2.0.1 | `Python-2.0` |
97
+ | `bmp-js` | 0.1.0 | `MIT` |
98
+ | `body-parser` | 2.3.0 | `MIT` |
99
+ | `bytes` | 3.1.2 | `MIT` |
100
+ | `call-bind-apply-helpers` | 1.0.2 | `MIT` |
101
+ | `call-bound` | 1.0.4 | `MIT` |
102
+ | `clipper-lib` | 6.4.2 | `BSL`(Boost-1.0) |
103
+ | `commander` | 15.0.0 | `MIT` |
104
+ | `content-disposition` | 1.1.0 | `MIT` |
105
+ | `content-type` | 1.0.5 | `MIT` |
106
+ | `cookie` | 1.1.1 | `MIT` |
107
+ | `cookie-signature` | 1.2.2 | `MIT` |
108
+ | `cors` | 2.8.6 | `MIT` |
109
+ | `cross-spawn` | 7.0.6 | `MIT` |
110
+ | `debug` | 4.4.3 | `MIT` |
111
+ | `depd` | 2.0.0 | `MIT` |
112
+ | `dunder-proto` | 1.0.1 | `MIT` |
113
+ | `ee-first` | 1.1.1 | `MIT` |
114
+ | `emf-converter` | 4.8.0 | `Apache-2.0` |
115
+ | `encodeurl` | 2.0.0 | `MIT` |
116
+ | `es-define-property` | 1.0.1 | `MIT` |
117
+ | `es-errors` | 1.3.0 | `MIT` |
118
+ | `es-object-atoms` | 1.1.2 | `MIT` |
119
+ | `escape-html` | 1.0.3 | `MIT` |
120
+ | `etag` | 1.8.1 | `MIT` |
121
+ | `eventsource` | 3.0.7 | `MIT` |
122
+ | `eventsource-parser` | 3.1.1 | `MIT` |
123
+ | `express` | 5.2.1 | `MIT` |
124
+ | `express-rate-limit` | 8.7.0 | `MIT` |
125
+ | `fast-deep-equal` | 3.1.3 | `MIT` |
126
+ | `fast-uri` | 3.1.8 | `BSD-3-Clause` |
127
+ | `fast-xml-builder` | 1.3.1 | `MIT` |
128
+ | `fast-xml-parser` | 5.11.1 | `MIT` |
129
+ | `fflate` | 0.8.3 | `MIT` |
130
+ | `finalhandler` | 2.1.1 | `MIT` |
131
+ | `flatbuffers` | 25.9.23 | `Apache-2.0` |
132
+ | `forwarded` | 0.2.0 | `MIT` |
133
+ | `fresh` | 2.0.0 | `MIT` |
134
+ | `function-bind` | 1.1.2 | `MIT` |
135
+ | `get-intrinsic` | 1.3.0 | `MIT` |
136
+ | `get-proto` | 1.0.1 | `MIT` |
137
+ | `gopd` | 1.2.0 | `MIT` |
138
+ | `guid-typescript` | 1.0.9 | `ISC` |
139
+ | `has-symbols` | 1.1.0 | `MIT` |
140
+ | `hasown` | 2.0.4 | `MIT` |
141
+ | `hono` | 4.13.9 | `MIT` |
142
+ | `http-errors` | 2.0.1 | `MIT` |
143
+ | `iconv-lite` | 0.7.3 | `MIT` |
144
+ | `idb-keyval` | 6.3.0 | `Apache-2.0` |
145
+ | `inherits` | 2.0.4 | `ISC` |
146
+ | `ip-address` | 10.7.2 | `MIT` |
147
+ | `ipaddr.js` | 1.9.1 | `MIT` |
148
+ | `is-promise` | 4.0.0 | `MIT` |
149
+ | `is-unsafe` | 2.0.2 | `MIT` |
150
+ | `is-url` | 1.2.4 | `MIT` |
151
+ | `isexe` | 2.0.0 | `ISC` |
152
+ | `jose` | 6.2.12 | `MIT` |
153
+ | `js-yaml` | 4.3.2 | `MIT` |
154
+ | `json-schema` | 0.4.0 | `BSD-3-Clause`(双许可,取这一支) |
155
+ | `json-schema-traverse` | 1.0.0 | `MIT` |
156
+ | `json-schema-typed` | 8.0.2 | `BSD-2-Clause` |
157
+ | `katex` | 0.18.9 | `MIT` |
158
+ | `long` | 5.3.2 | `Apache-2.0` |
159
+ | `lucide` | 1.48.0 | `ISC` |
160
+ | `math-intrinsics` | 1.1.0 | `MIT` |
161
+ | `media-typer` | 1.1.1 | `MIT` |
162
+ | `merge-descriptors` | 2.0.0 | `MIT` |
163
+ | `mime-db` | 1.54.0 | `MIT` |
164
+ | `mime-types` | 3.0.2 | `MIT` |
165
+ | `ms` | 2.1.3 | `MIT` |
166
+ | `mtx-decompressor` | 1.6.0 | `MPL-2.0` |
167
+ | `negotiator` | 1.1.0 | `MIT` |
168
+ | `node-fetch` | 2.7.0 | `MIT` |
169
+ | `object-assign` | 4.1.1 | `MIT` |
170
+ | `object-inspect` | 1.13.4 | `MIT` |
171
+ | `on-finished` | 2.4.1 | `MIT` |
172
+ | `once` | 1.4.0 | `ISC` |
173
+ | `onnxruntime-common` | 1.30.0 | `MIT` |
174
+ | `onnxruntime-web` | 1.30.0 | `MIT` |
175
+ | `opencollective-postinstall` | 2.0.3 | `MIT` |
176
+ | `parseurl` | 1.3.3 | `MIT` |
177
+ | `path-expression-matcher` | 1.6.2 | `MIT` |
178
+ | `path-key` | 3.1.1 | `MIT` |
179
+ | `path-to-regexp` | 8.4.2 | `MIT` |
180
+ | `pkce-challenge` | 5.0.1 | `MIT` |
181
+ | `platform` | 1.3.6 | `MIT` |
182
+ | `protobufjs` | 7.6.6 | `BSD-3-Clause` |
183
+ | `proxy-addr` | 2.0.8 | `MIT` |
184
+ | `punycode` | 2.3.1 | `MIT` |
185
+ | `qs` | 6.16.0 | `BSD-3-Clause` |
186
+ | `range-parser` | 1.3.0 | `MIT` |
187
+ | `raw-body` | 3.0.2 | `MIT` |
188
+ | `regenerator-runtime` | 0.13.11 | `MIT` |
189
+ | `require-from-string` | 2.0.2 | `MIT` |
190
+ | `router` | 2.2.0 | `MIT` |
191
+ | `safer-buffer` | 2.1.2 | `MIT` |
192
+ | `send` | 1.2.1 | `MIT` |
193
+ | `serve-static` | 2.2.1 | `MIT` |
194
+ | `setprototypeof` | 1.2.0 | `ISC` |
195
+ | `shebang-command` | 2.0.0 | `MIT` |
196
+ | `shebang-regex` | 3.0.0 | `MIT` |
197
+ | `side-channel` | 1.1.1 | `MIT` |
198
+ | `side-channel-list` | 1.0.1 | `MIT` |
199
+ | `side-channel-map` | 1.0.1 | `MIT` |
200
+ | `side-channel-weakmap` | 1.0.2 | `MIT` |
201
+ | `statuses` | 2.0.2 | `MIT` |
202
+ | `strnum` | 2.4.2 | `MIT` |
203
+ | `tesseract.js` | 7.0.0 | `Apache-2.0` |
204
+ | `tesseract.js-core` | 7.0.0 | `Apache-2.0` |
205
+ | `three` | 0.186.0 | `MIT` |
206
+ | `toidentifier` | 1.0.1 | `MIT` |
207
+ | `tr46` | 6.0.0 | `MIT` |
208
+ | `type-is` | 2.1.0 | `MIT` |
209
+ | `undici` | 7.29.0 | `MIT` |
210
+ | `undici-types` | 8.9.0 | `MIT` |
211
+ | `unpipe` | 1.0.0 | `MIT` |
212
+ | `vary` | 1.1.2 | `MIT` |
213
+ | `wasm-feature-detect` | 1.9.0 | `Apache-2.0` |
214
+ | `webidl-conversions` | 8.0.1 | `BSD-2-Clause` |
215
+ | `whatwg-url` | 17.1.2 | `MIT` |
216
+ | `which` | 2.0.2 | `ISC` |
217
+ | `wrappy` | 1.0.2 | `ISC` |
218
+ | `xml-naming` | 0.3.0 | `MIT` |
219
+ | `zlibjs` | 0.3.1 | `MIT` |
220
+ | `zod` | 4.6.5 | `MIT` |
221
+ | `zod-to-json-schema` | 3.25.2 | `ISC` |
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILTIN_THEME_IDS = void 0;
4
+ /** 内置主题 id — 须与 `THEMES` keys 保持一致。 */
5
+ exports.BUILTIN_THEME_IDS = [
6
+ "business",
7
+ "academic",
8
+ "creative",
9
+ "minimal",
10
+ "dark",
11
+ "tech",
12
+ "nature",
13
+ "warm",
14
+ "editorial",
15
+ "terminal",
16
+ "pastel",
17
+ "luxe",
18
+ "ink",
19
+ ];
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ // 装饰 shape 构建器 —— 供 compile.ts(AI 生成 slide)和 template.ts(新建模板)共用。
3
+ //
4
+ // 每种 ThemeDecoration 对应一套独立的视觉语言,由多个 Shape 组合实现。
5
+ // Shape 使用最小原语(rect / ellipse / line),任何渲染管线 / 序列化器均可直接处理。
6
+ //
7
+ // 约定:
8
+ // - id 前缀统一用 "dec_",切主题时 ApplyCustomThemeCmd 按前缀批量清除再重建。
9
+ // - zIndex = -1(位于 shapes 数组最底部),不遮挡内容。
10
+ // - locked 由调用方传入:template master 用 true(母版装饰不可选),
11
+ // compile.ts(slide 内装饰)用 false(用户可在画布上微调)。
12
+ // - **不要再用「左上 + 右下两枚出血大圆」这套构图**。它一度被 aurora / neon-burst /
13
+ // organic-blobs / confetti / ink-wash 共用,导致换主题只是换个颜色、feature 页
14
+ // 还每页重复同一对圆。大面积氛围元素一律走「左右出血的横向光带 / 山脊」或贴边
15
+ // 灯管,纵向收在版心之外;角部只留细线、括号、角标这类结构性小元素。
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.buildDecorationShapesShared = buildDecorationShapesShared;
18
+ const core_1 = require("@sliiu/present-core");
19
+ /**
20
+ * content 页允许保留的装饰单体最大面积占比(相对 slide 面积)。
21
+ * 实测分布:氛围光晕/大色块类(Glow/Blob/Arc/Mist/Soft)全部 >5%,
22
+ * 结构类(线/栏/括号/散点/印章)全部 <3% —— 4% 是干净的分界线。
23
+ */
24
+ const CONTENT_DECOR_MAX_AREA_RATIO = 0.04;
25
+ function buildDecorationShapesShared(dec, ctx) {
26
+ if (dec === "none")
27
+ return [];
28
+ const { palette: p, size: { width: W, height: H }, locked, idPrefix } = ctx;
29
+ let n = 0;
30
+ const nextId = () => `dec_${idPrefix}_${++n}`;
31
+ const base = () => ({
32
+ zIndex: -1,
33
+ visible: true,
34
+ locked,
35
+ opacity: 1,
36
+ transform: (0, core_1.identityTransform)(),
37
+ });
38
+ const mkRect = (bbox, color, alpha, name = "Decor") => ({
39
+ ...base(),
40
+ id: nextId(),
41
+ kind: "rect",
42
+ name,
43
+ bbox,
44
+ fill: alpha !== undefined ? { kind: "solid", color, alpha } : { kind: "solid", color },
45
+ });
46
+ const mkEllipse = (bbox, color, alpha, name = "Decor") => ({
47
+ ...base(),
48
+ id: nextId(),
49
+ kind: "ellipse",
50
+ name,
51
+ bbox,
52
+ fill: alpha !== undefined ? { kind: "solid", color, alpha } : { kind: "solid", color },
53
+ });
54
+ /**
55
+ * 渐变氛围块:从 fromAlpha 淡入到 toAlpha 的线性渐变矩形。
56
+ * 光晕类元素必须用它而不是实心椭圆 —— 实心填充没有羽化,边缘会读成一枚
57
+ * 「透镜 / 山丘」。角度沿用项目约定:0 = L→R,90 = T→B。
58
+ * 逐 stop 的 alpha 会被 serializer 完整写进 <a:gs>,导出无损。
59
+ */
60
+ const mkGlow = (bbox, color, fromAlpha, toAlpha, angle, name = "Glow") => ({
61
+ ...base(),
62
+ id: nextId(),
63
+ kind: "rect",
64
+ name,
65
+ bbox,
66
+ fill: {
67
+ kind: "linear",
68
+ angle,
69
+ stops: [
70
+ { offset: 0, color, alpha: fromAlpha },
71
+ { offset: 1, color, alpha: toAlpha },
72
+ ],
73
+ },
74
+ });
75
+ const out = [];
76
+ switch (dec) {
77
+ // ── 极光光幕(Dark 主题)──────────────────────────────────────────────────
78
+ // 上下两道全宽渐变光幕(顶部 violet 垂落、底部 cyan 升起),中间留出干净版心,
79
+ // 叠在渐变黑背景上形成极光层次。用渐变而非实心椭圆,边缘自然消隐。
80
+ case "corner-gradient-shape": // legacy 别名:渲染为 aurora(与 namedDecoration 的映射一致)
81
+ case "aurora": {
82
+ out.push(mkGlow({ x: 0, y: 0, w: W, h: H * 0.42 }, p.accent2, 0.13, 0, 90, "Aurora Veil"));
83
+ out.push(mkGlow({ x: 0, y: H * 0.48, w: W, h: H * 0.52 }, p.accent1, 0, 0.2, 90, "Aurora Veil 2"));
84
+ // 底部极细分割线
85
+ out.push(mkRect({ x: W * 0.06, y: H * 0.92, w: W * 0.88, h: 1 }, p.divider, 0.4, "Rule"));
86
+ break;
87
+ }
88
+ // ── 科技 L 形 bracket(Tech 主题)────────────────────────────────────────
89
+ // 左侧细竖条(导航轨道感)+ 右下 L 角标 + 右下小亮点(焦点坐标)。
90
+ case "tech-bracket": {
91
+ // 左侧轨道条
92
+ out.push(mkRect({ x: W * 0.028, y: H * 0.08, w: 2, h: H * 0.84 }, p.accent1, 0.55, "Rail"));
93
+ // 左侧轨道顶部亮点
94
+ out.push(mkEllipse({ x: W * 0.028 - 3, y: H * 0.08 - 3, w: 8, h: 8 }, p.accent1, 0.9, "Rail Dot"));
95
+ // 右下角 L 形 bracket:横向
96
+ out.push(mkRect({ x: W * 0.72, y: H * 0.88, w: W * 0.24, h: 2 }, p.accent2, 0.6, "Bracket H"));
97
+ // 右下角 L 形 bracket:纵向
98
+ out.push(mkRect({ x: W * 0.96 - 2, y: H * 0.72, w: 2, h: H * 0.17 }, p.accent2, 0.6, "Bracket V"));
99
+ // 角落高亮节点
100
+ out.push(mkEllipse({ x: W * 0.96 - 5, y: H * 0.88 - 3, w: 8, h: 8 }, p.accent1, 1, "Node"));
101
+ break;
102
+ }
103
+ // ── 霓虹灯管(Creative 主题)─────────────────────────────────────────────
104
+ // 顶部全宽亮条 + 右侧竖灯管 + 左下横灯管(均为「细亮条 + 外扩柔光晕」两件套)
105
+ // + 右上散点 → 霓虹夜场感。灯管贴边走,不侵占版心。
106
+ case "neon-burst": {
107
+ // 顶部亮条
108
+ out.push(mkRect({ x: 0, y: 0, w: W, h: 3 }, p.accent1, 1, "Top Bar"));
109
+ // 右侧竖灯管:柔光晕向版心方向消隐(R→L),亮管压在光晕右端
110
+ out.push(mkGlow({ x: W * 0.88, y: H * 0.16, w: W * 0.078, h: H * 0.62 }, p.accent2, 0, 0.16, 0, "Tube Halo"));
111
+ out.push(mkRect({ x: W * 0.955, y: H * 0.16, w: 3, h: H * 0.62 }, p.accent2, 0.85, "Tube"));
112
+ // 左下横灯管(短,呼应竖管):光晕向上消隐
113
+ out.push(mkGlow({ x: W * 0.06, y: H * 0.79, w: W * 0.3, h: H * 0.07 }, p.accent1, 0, 0.16, 90, "Underline Halo"));
114
+ out.push(mkRect({ x: W * 0.06, y: H * 0.86, w: W * 0.3, h: 3 }, p.accent1, 0.85, "Underline"));
115
+ // 右上散点(3 枚)
116
+ const dotPositions = [
117
+ { x: W * 0.80, y: H * 0.08 },
118
+ { x: W * 0.86, y: H * 0.05 },
119
+ { x: W * 0.92, y: H * 0.10 },
120
+ ];
121
+ for (const dp of dotPositions) {
122
+ out.push(mkEllipse({ x: dp.x, y: dp.y, w: 5, h: 5 }, p.accent3, 0.6, "Dot"));
123
+ }
124
+ break;
125
+ }
126
+ // ── 权威双条(Business 主题)─────────────────────────────────────────────
127
+ // 左侧全高纵条(主色)+ 底部全宽横条双段(主 + 次色)→ 权威、信任感。
128
+ case "authority": {
129
+ // 左侧纵条
130
+ out.push(mkRect({ x: 0, y: 0, w: 5, h: H }, p.accent1, 1, "Left Strip"));
131
+ // 底部主色大段
132
+ out.push(mkRect({ x: 0, y: H - 5, w: W * 0.72, h: 5 }, p.accent1, 1, "Bottom Bar"));
133
+ // 底部次色小段
134
+ out.push(mkRect({ x: W * 0.72, y: H - 5, w: W * 0.28, h: 5 }, p.accent2, 1, "Bottom Bar 2"));
135
+ break;
136
+ }
137
+ // ── 手稿双线(Academic 主题)─────────────────────────────────────────────
138
+ // 顶部横线 + 底部横线(双线如书页边框)+ 居中微型装饰圆点。
139
+ case "manuscript": {
140
+ // 顶部规则线
141
+ out.push(mkRect({ x: W * 0.06, y: H * 0.07, w: W * 0.88, h: 1.5 }, p.text, 0.15, "Top Rule"));
142
+ // 次级顶线(更细,稍低)
143
+ out.push(mkRect({ x: W * 0.06, y: H * 0.072, w: W * 0.88, h: 0.5 }, p.accent1, 0.25, "Top Rule 2"));
144
+ // 底部规则线
145
+ out.push(mkRect({ x: W * 0.06, y: H * 0.915, w: W * 0.88, h: 1.5 }, p.text, 0.12, "Bottom Rule"));
146
+ // 居中装饰点(顶线正中)
147
+ out.push(mkEllipse({ x: W * 0.5 - 3, y: H * 0.068, w: 6, h: 6 }, p.accent1, 0.7, "Ornament"));
148
+ // 左侧页码竖线(文稿页面感)
149
+ out.push(mkRect({ x: W * 0.038, y: H * 0.12, w: 1, h: H * 0.76 }, p.divider, 0.6, "Margin"));
150
+ break;
151
+ }
152
+ // ── 墨线单划(Minimal 主题)──────────────────────────────────────────────
153
+ // 单条精准横线,留白哲学:少即是多。
154
+ case "ink-thread": {
155
+ out.push(mkRect({ x: W * 0.08, y: H * 0.14, w: W * 0.84, h: 1 }, p.accent1, 0.35, "Thread"));
156
+ break;
157
+ }
158
+ // ── 层叠远山(Nature 主题)───────────────────────────────────────────────
159
+ // 两道左右出血的扁平山脊自下而上层叠(近深远淡)+ 顶部地平细线 → 生命感、自然感。
160
+ // 枚举值沿用 organic-blobs(已落库的主题依赖它),几何已不再是角部圆球。
161
+ case "organic-blobs": {
162
+ out.push(mkEllipse({ x: -W * 0.12, y: H * 0.72, w: W * 1.24, h: H * 0.46 }, p.accent1, 0.1, "Ridge A"));
163
+ out.push(mkEllipse({ x: -W * 0.18, y: H * 0.82, w: W * 1.36, h: H * 0.38 }, p.accent2, 0.08, "Ridge B"));
164
+ // 顶部细线(如地平线)
165
+ out.push(mkRect({ x: W * 0.06, y: H * 0.065, w: W * 0.88, h: 1 }, p.divider, 0.7, "Horizon"));
166
+ break;
167
+ }
168
+ // ── 朝阳弧(Warm 主题)───────────────────────────────────────────────────
169
+ // 底部半圆弧(只露出上半部分)+ 底部全宽横条 → 日出能量、暖意迸发。
170
+ case "sunrise-arc": {
171
+ // 朝阳大圆(圆心在 slide 底边以下,只有弧顶露出)
172
+ const r = W * 0.55;
173
+ out.push(mkEllipse({ x: W * 0.5 - r, y: H - r * 0.28, w: r * 2, h: r * 1.1 }, p.accent1, 0.12, "Sun Arc"));
174
+ // 底部实色横条
175
+ out.push(mkRect({ x: 0, y: H - 5, w: W, h: 5 }, p.accent1, 1, "Base Bar"));
176
+ // 底部次色窄条(叠加在横条上,右侧 30%)
177
+ out.push(mkRect({ x: W * 0.70, y: H - 5, w: W * 0.30, h: 5 }, p.accent3, 1, "Base Bar 2"));
178
+ break;
179
+ }
180
+ // ── 杂志报头(Editorial 主题)────────────────────────────────────────────
181
+ // 顶部粗实色报头条 + 紧贴的细分隔线 + 左侧整高页边竖线 + 底部页码块,
182
+ // 模拟杂志/报纸版式的强栏目感。
183
+ case "masthead": {
184
+ // 顶部粗报头条(文字主色)
185
+ out.push(mkRect({ x: 0, y: 0, w: W, h: 10 }, p.text, 1, "Masthead"));
186
+ // 报头条右侧强调段(accent1)
187
+ out.push(mkRect({ x: W * 0.74, y: 0, w: W * 0.26, h: 10 }, p.accent1, 1, "Masthead Accent"));
188
+ // 报头下方细分隔线
189
+ out.push(mkRect({ x: W * 0.06, y: H * 0.12, w: W * 0.88, h: 1.5 }, p.text, 0.22, "Hairline"));
190
+ // 左侧整高页边竖线(栏目分隔)
191
+ out.push(mkRect({ x: W * 0.045, y: H * 0.14, w: 1, h: H * 0.74 }, p.divider, 0.7, "Margin"));
192
+ // 底部页码块(accent1 实色小方)
193
+ out.push(mkRect({ x: W * 0.06, y: H - 14, w: 26, h: 6 }, p.accent1, 1, "Folio"));
194
+ break;
195
+ }
196
+ // ── 终端扫描线(Terminal 主题)───────────────────────────────────────────
197
+ // 左上光标块 + 命令提示符短条 + 横向扫描线(CRT 残影)+ 四角 L 形括号。
198
+ case "terminal": {
199
+ // 左上"光标"实块
200
+ out.push(mkRect({ x: W * 0.035, y: H * 0.085, w: 12, h: 22 }, p.accent1, 0.9, "Cursor"));
201
+ // 命令提示符短条(光标右侧)
202
+ out.push(mkRect({ x: W * 0.035 + 18, y: H * 0.105, w: W * 0.1, h: 3 }, p.accent2, 0.7, "Prompt"));
203
+ // 横向扫描线(CRT 残影,多条极淡)
204
+ for (let i = 0; i < 5; i++) {
205
+ out.push(mkRect({ x: 0, y: H * (0.28 + i * 0.13), w: W, h: 1 }, p.accent1, 0.05, `Scanline ${i}`));
206
+ }
207
+ // 四角 L 形括号(仅左上 + 右下,避免过满)
208
+ out.push(mkRect({ x: W * 0.03, y: H * 0.07, w: 20, h: 2 }, p.accent1, 0.5, "TL H"));
209
+ out.push(mkRect({ x: W * 0.03, y: H * 0.07, w: 2, h: 20 }, p.accent1, 0.5, "TL V"));
210
+ out.push(mkRect({ x: W * 0.97 - 20, y: H * 0.93, w: 20, h: 2 }, p.accent2, 0.5, "BR H"));
211
+ out.push(mkRect({ x: W * 0.97 - 2, y: H * 0.93 - 18, w: 2, h: 20 }, p.accent2, 0.5, "BR V"));
212
+ break;
213
+ }
214
+ // ── 彩纸礼花(Pastel 主题)───────────────────────────────────────────────
215
+ // 底部一道左右出血的柔和色带 + 散落的多彩圆点 → 活泼、亲和。
216
+ // 散点才是这套装饰的识别点,色带只负责托住画面下沿。
217
+ case "confetti": {
218
+ out.push(mkGlow({ x: 0, y: H * 0.6, w: W, h: H * 0.4 }, p.accent1, 0, 0.16, 90, "Soft Band"));
219
+ // 散落彩点(轮流用 4 个 accent)
220
+ const confettiColors = [p.accent1, p.accent2, p.accent3, p.accent4];
221
+ const confettiPos = [
222
+ { x: 0.18, y: 0.16, s: 10 }, { x: 0.34, y: 0.10, s: 7 }, { x: 0.5, y: 0.14, s: 12 },
223
+ { x: 0.66, y: 0.09, s: 8 }, { x: 0.82, y: 0.18, s: 10 }, { x: 0.9, y: 0.4, s: 7 },
224
+ { x: 0.12, y: 0.5, s: 9 }, { x: 0.24, y: 0.82, s: 8 },
225
+ ];
226
+ confettiPos.forEach((c, i) => {
227
+ out.push(mkEllipse({ x: W * c.x, y: H * c.y, w: c.s, h: c.s }, confettiColors[i % 4], 0.55, `Confetti ${i}`));
228
+ });
229
+ break;
230
+ }
231
+ // ── 鎏金细框(Luxe 主题)─────────────────────────────────────────────────
232
+ // 四边内嵌金色细线框 + 四角节点 → 高奢品牌的克制精致感。
233
+ case "gilded": {
234
+ const inset = Math.round(Math.min(W, H) * 0.05);
235
+ const lw = 1.2;
236
+ // 四边细线
237
+ out.push(mkRect({ x: inset, y: inset, w: W - inset * 2, h: lw }, p.accent1, 0.85, "Frame Top"));
238
+ out.push(mkRect({ x: inset, y: H - inset - lw, w: W - inset * 2, h: lw }, p.accent1, 0.85, "Frame Bottom"));
239
+ out.push(mkRect({ x: inset, y: inset, w: lw, h: H - inset * 2 }, p.accent1, 0.85, "Frame Left"));
240
+ out.push(mkRect({ x: W - inset - lw, y: inset, w: lw, h: H - inset * 2 }, p.accent1, 0.85, "Frame Right"));
241
+ // 四角节点小方块
242
+ const corners = [
243
+ { x: inset, y: inset }, { x: W - inset, y: inset },
244
+ { x: inset, y: H - inset }, { x: W - inset, y: H - inset },
245
+ ];
246
+ corners.forEach((c, i) => {
247
+ out.push(mkRect({ x: c.x - 3, y: c.y - 3, w: 6, h: 6 }, p.accent2, 1, `Corner ${i}`));
248
+ });
249
+ break;
250
+ }
251
+ // ── 水墨留白(Ink 主题)──────────────────────────────────────────────────
252
+ // 下半部横向远山淡墨 + 右下朱红印章方块 + 左侧竖向墨线 → 国风文气。
253
+ // 墨晕压在下半部并保持极低透明度:宣纸底 + 深色正文,任何加深都会吃掉对比度。
254
+ case "ink-wash": {
255
+ out.push(mkGlow({ x: 0, y: H * 0.55, w: W, h: H * 0.45 }, p.text, 0, 0.07, 90, "Ink Wash"));
256
+ // 远山剪影:墨韵之外留一道实体山脊线,避免整片只有均匀的灰
257
+ out.push(mkEllipse({ x: W * 0.26, y: H * 0.74, w: W * 0.62, h: H * 0.4 }, p.accent2, 0.06, "Distant Ridge"));
258
+ // 左侧竖向墨线(卷轴边)
259
+ out.push(mkRect({ x: W * 0.05, y: H * 0.14, w: 2, h: H * 0.72 }, p.text, 0.5, "Scroll Line"));
260
+ // 右下朱红印章方块(描边 + 内点)
261
+ out.push(mkRect({ x: W * 0.9, y: H * 0.8, w: 34, h: 34 }, p.accent1, 0.85, "Seal"));
262
+ out.push(mkRect({ x: W * 0.9 + 5, y: H * 0.8 + 5, w: 24, h: 24 }, p.bg, 0.9, "Seal Inner"));
263
+ out.push(mkRect({ x: W * 0.9 + 10, y: H * 0.8 + 10, w: 14, h: 14 }, p.accent1, 0.9, "Seal Core"));
264
+ break;
265
+ }
266
+ // ── 兼容:通用几何线(AI 生成主题)──────────────────────────────────────
267
+ case "geometric-lines": {
268
+ out.push(mkRect({ x: W * 0.03, y: H * 0.1, w: 2, h: H * 0.8 }, p.accent1, 0.6, "VLine"));
269
+ out.push(mkRect({ x: W * 0.5, y: H * 0.04, w: W * 0.18, h: 2 }, p.accent2, 0.5, "HLine"));
270
+ break;
271
+ }
272
+ // ── 兼容:通用底栏(AI 生成主题)────────────────────────────────────────
273
+ case "bottom-accent-bar": {
274
+ out.push(mkRect({ x: 0, y: H - 6, w: W, h: 6 }, p.accent1, undefined, "Bottom Bar"));
275
+ out.push(mkRect({ x: 0, y: H - 6, w: W * 0.18, h: 6 }, p.accent2, undefined, "Bottom Bar 2"));
276
+ break;
277
+ }
278
+ // ── 兼容:通用点阵(AI 生成主题)────────────────────────────────────────
279
+ case "dotted-grid": {
280
+ const cols = 6, rows = 4, gap = 14;
281
+ const startX = W * 0.04;
282
+ const startY = H - rows * gap - 12;
283
+ for (let r = 0; r < rows; r++) {
284
+ for (let c = 0; c < cols; c++) {
285
+ out.push(mkEllipse({ x: startX + c * gap, y: startY + r * gap, w: 3, h: 3 }, p.accent1, 0.35, `Dot ${r}-${c}`));
286
+ }
287
+ }
288
+ break;
289
+ }
290
+ }
291
+ // content 页静默化:滤掉大面积氛围元素(按几何面积判定,对所有装饰风格通用,
292
+ // 不需要逐 case 维护)。feature 页(缺省)保留完整装饰。
293
+ if (ctx.role === "content") {
294
+ const maxArea = W * H * CONTENT_DECOR_MAX_AREA_RATIO;
295
+ return out.filter((sh) => {
296
+ const b = sh.bbox;
297
+ if (!b)
298
+ return true;
299
+ return b.w * b.h <= maxArea;
300
+ });
301
+ }
302
+ return out;
303
+ }