@gitruck/cli 1.1.5 → 1.1.7
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.
- package/AGENT.md +16 -7
- package/CHANGELOG.md +15 -0
- package/README.en.md +723 -711
- package/README.md +16 -5
- package/dist/index.js +212 -172
- package/package.json +3 -2
- package/skills/gtrk-ai-drama/SKILL.md +268 -267
- package/skills/gtrk-cover/SKILL.md +112 -111
- package/skills/gtrk-cover/assets/workbench-template.html +1088 -955
- package/skills/gtrk-cover/references/checklist.md +82 -81
- package/skills/gtrk-cover/references/workbench-guide.md +128 -99
- package/skills/gtrk-food-recap/SKILL.md +122 -104
- package/skills/gtrk-live-slicing/SKILL.md +227 -227
- package/skills/gtrk-matrix/SKILL.md +1 -0
- package/skills/gtrk-mg/SKILL.md +243 -242
- package/skills/gtrk-music-visualizer/SKILL.md +95 -94
- package/skills/gtrk-oralcut/SKILL.md +30 -29
- package/skills/gtrk-splitter/SKILL.md +247 -246
- package/skills/gtrk-style-maker/SKILL.md +6 -0
- package/skills/gtrk-tools/SKILL.md +1 -0
- package/skills/gtrk-transcript/SKILL.md +1 -0
- package/skills/gtrk-travel-recap/SKILL.md +16 -1
|
@@ -1,955 +1,1088 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>示例标题 · 封面排版工作台</title>
|
|
6
|
-
<!-- ↑ 替换位:当期 T1 -->
|
|
7
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
-
<!-- 替换位:字体族按栏目风格资产增删(衬线/黑体/手写体等) -->
|
|
10
|
-
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@700;900&family=Noto+Sans+SC:wght@400;700;900&family=Cormorant+Garamond:ital,wght@0,500;1,400&family=JetBrains+Mono:wght@400;600&family=Long+Cang&display=swap" rel="stylesheet">
|
|
11
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
12
|
-
<style>
|
|
13
|
-
*, *::before, *::after { box-sizing: border-box; }
|
|
14
|
-
/* 替换位:色板变量——变量名固定、换值即换装(语义见 workbench-guide §3)。以下为中性默认值 */
|
|
15
|
-
:root {
|
|
16
|
-
--midnight: #14161E;
|
|
17
|
-
--indigo: #232838;
|
|
18
|
-
--amber: #7A869E;
|
|
19
|
-
--amber-soft: #9AA5BC;
|
|
20
|
-
--bone: #ECEEF3;
|
|
21
|
-
--ink: #0A0C12;
|
|
22
|
-
--panel: #161923;
|
|
23
|
-
--panel-2: #1E2230;
|
|
24
|
-
--border: #2C3145;
|
|
25
|
-
--muted: #8B92A8;
|
|
26
|
-
--text: #E4E7F0;
|
|
27
|
-
}
|
|
28
|
-
html, body {
|
|
29
|
-
margin: 0; padding: 0;
|
|
30
|
-
background: #07080D; color: var(--text);
|
|
31
|
-
font-family: "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
|
32
|
-
font-size: 14px; min-height: 100vh; overflow: hidden;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
header.app-head {
|
|
36
|
-
padding: 20px 32px 14px;
|
|
37
|
-
border-bottom: 1px solid var(--border);
|
|
38
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
39
|
-
gap: 24px; flex-wrap: wrap;
|
|
40
|
-
}
|
|
41
|
-
header h1 {
|
|
42
|
-
font-family: "Noto Sans SC", sans-serif;
|
|
43
|
-
font-weight: 900; font-size: 20px; letter-spacing: 2px;
|
|
44
|
-
margin: 0; color: var(--bone);
|
|
45
|
-
}
|
|
46
|
-
header h1 .ink { color: var(--amber); }
|
|
47
|
-
header .sub { font-size: 11px; color: var(--muted); letter-spacing: 1px; margin-left: 12px; }
|
|
48
|
-
header .meta { display: flex; gap: 8px; align-items: center; font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--muted); }
|
|
49
|
-
header .meta .pill { border: 1px solid var(--border); padding: 4px 10px; border-radius: 999px; color: var(--amber-soft); }
|
|
50
|
-
|
|
51
|
-
main {
|
|
52
|
-
display: grid;
|
|
53
|
-
grid-template-columns: 220px 1fr 340px;
|
|
54
|
-
min-height: calc(100vh - 60px);
|
|
55
|
-
}
|
|
56
|
-
.tabs { border-right: 1px solid var(--border); padding: 20px 14px; background: #0A0C13; overflow-y: auto; max-height: calc(100vh - 60px); }
|
|
57
|
-
.tab-title { font-size: 10px; letter-spacing: 2.5px; color: var(--muted); margin: 0 0 10px 4px; text-transform: uppercase; font-family: "JetBrains Mono", monospace; }
|
|
58
|
-
.tab {
|
|
59
|
-
display: block; width: 100%; text-align: left;
|
|
60
|
-
padding: 12px; background: transparent;
|
|
61
|
-
border: 1px solid transparent; color: var(--text);
|
|
62
|
-
cursor: pointer; border-radius: 5px; margin-bottom: 6px;
|
|
63
|
-
font-family: inherit; font-size: 13px; transition: all 0.15s;
|
|
64
|
-
}
|
|
65
|
-
.tab:hover { background: var(--panel); border-color: var(--border); }
|
|
66
|
-
.tab.active { background: var(--panel-2); border-color: var(--amber); color: var(--bone); }
|
|
67
|
-
.tab .ratio { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--amber); letter-spacing: 1px; }
|
|
68
|
-
.tab .desc { font-size: 10px; color: var(--muted); margin-top: 3px; }
|
|
69
|
-
.tabs .divider { height: 1px; background: var(--border); margin: 16px 0; }
|
|
70
|
-
|
|
71
|
-
.upload-btn {
|
|
72
|
-
display: block; width: 100%; padding: 10px; border-radius: 5px;
|
|
73
|
-
background: transparent; border: 1px dashed var(--border); color: var(--muted);
|
|
74
|
-
cursor: pointer; font-size: 11px; font-family: inherit;
|
|
75
|
-
transition: all 0.15s; text-align: center;
|
|
76
|
-
}
|
|
77
|
-
.upload-btn:hover { border-color: var(--amber); color: var(--amber); }
|
|
78
|
-
.upload-btn input { display: none; }
|
|
79
|
-
.hint { font-size: 10px; color: var(--muted); line-height: 1.7; margin-top: 10px; padding: 10px; background: var(--panel); border-radius: 4px; border-left: 2px solid var(--amber); }
|
|
80
|
-
.reset-btn {
|
|
81
|
-
display: block; width: 100%; padding: 9px; margin-top: 8px;
|
|
82
|
-
background: transparent; color: var(--muted);
|
|
83
|
-
border: 1px solid var(--border); border-radius: 5px;
|
|
84
|
-
font-family: inherit; font-size: 11px; cursor: pointer; letter-spacing: 1px;
|
|
85
|
-
transition: all 0.15s;
|
|
86
|
-
}
|
|
87
|
-
.reset-btn:hover { color: var(--amber); border-color: var(--amber); }
|
|
88
|
-
|
|
89
|
-
.stage {
|
|
90
|
-
padding: 26px 32px 50px;
|
|
91
|
-
display: flex; align-items: flex-start; justify-content: center;
|
|
92
|
-
background: radial-gradient(ellipse at top, rgba(35,40,56,0.25), transparent 60%), #07080D;
|
|
93
|
-
overflow: auto;
|
|
94
|
-
}
|
|
95
|
-
/* 工程注意:transform 只缩视觉不缩布局——占位靠 fitStage 的负 margin 收缩,勿改回直接设小宽高 */
|
|
96
|
-
.canvas-wrap {
|
|
97
|
-
position: relative;
|
|
98
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(122,134,158,0.14);
|
|
99
|
-
transform-origin: top left;
|
|
100
|
-
}
|
|
101
|
-
.canvas-wrap .dim-tag { position: absolute; top: -24px; left: 0; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--muted); letter-spacing: 1.5px; }
|
|
102
|
-
.canvas-wrap .dim-tag .num { color: var(--amber); }
|
|
103
|
-
|
|
104
|
-
.cover { position: relative; overflow: hidden; background: var(--midnight); font-family: "Noto Sans SC", sans-serif; }
|
|
105
|
-
.cover[data-size="16_9"] { width: 2400px; height: 1350px; }
|
|
106
|
-
.cover[data-size="4_3"] { width: 2400px; height: 1800px; }
|
|
107
|
-
.cover[data-size="3_4"] { width: 1500px; height: 2000px; }
|
|
108
|
-
.cover .bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; user-select: none; pointer-events: none; }
|
|
109
|
-
.cover { display: none; }
|
|
110
|
-
.cover.active { display: block; }
|
|
111
|
-
|
|
112
|
-
/* 所有文字图层的通用交互样式(引擎层,勿改) */
|
|
113
|
-
.layer-text {
|
|
114
|
-
position: absolute;
|
|
115
|
-
cursor: grab;
|
|
116
|
-
user-select: none;
|
|
117
|
-
transition: outline 0.12s;
|
|
118
|
-
outline: 0 dashed transparent;
|
|
119
|
-
outline-offset: 8px;
|
|
120
|
-
white-space: nowrap;
|
|
121
|
-
}
|
|
122
|
-
.layer-text:hover { outline: 3px dashed rgba(122,134,158,0.5); }
|
|
123
|
-
.layer-text.selected { outline: 5px dashed rgba(122,134,158,0.95); }
|
|
124
|
-
.layer-text.dragging { cursor: grabbing; outline: 5px solid rgba(122,134,158,0.95); }
|
|
125
|
-
|
|
126
|
-
/* 替换位:T1
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
font-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
text-
|
|
136
|
-
|
|
137
|
-
0 0
|
|
138
|
-
0 0
|
|
139
|
-
0 0 20px rgba(200,210,230,0.14),
|
|
140
|
-
0 0 45px rgba(200,210,230,0.10),
|
|
141
|
-
0
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* 替换位:T2 副标题风格 */
|
|
145
|
-
.t2 {
|
|
146
|
-
font-family: "Noto Sans SC", sans-serif;
|
|
147
|
-
font-weight: 700;
|
|
148
|
-
color: rgba(236,238,243,0.88);
|
|
149
|
-
letter-spacing: 0.4em;
|
|
150
|
-
text-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
|
151
|
-
z-index: 9;
|
|
152
|
-
}
|
|
153
|
-
/* 替换位:T5 装饰英文风格 */
|
|
154
|
-
.t5 {
|
|
155
|
-
font-family: "Cormorant Garamond", serif;
|
|
156
|
-
font-style: italic;
|
|
157
|
-
font-weight: 500;
|
|
158
|
-
color: rgba(236,238,243,0.5);
|
|
159
|
-
letter-spacing: 0.3em;
|
|
160
|
-
text-transform: uppercase;
|
|
161
|
-
z-index: 8;
|
|
162
|
-
}
|
|
163
|
-
/* 替换位:T4 品牌戳风格(手写签名感;用户没给栏目名则 visible:false、不编内容) */
|
|
164
|
-
.t4 {
|
|
165
|
-
font-family: "Long Cang", cursive;
|
|
166
|
-
font-weight: 400;
|
|
167
|
-
color: rgba(236,238,243,0.72);
|
|
168
|
-
letter-spacing: 0.12em;
|
|
169
|
-
text-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
|
170
|
-
z-index: 8;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/* 装饰件:薄内框(默认关,按当期决策开)。浅底尺寸的深色覆写见 workbench-guide §5 */
|
|
174
|
-
.inner-frame {
|
|
175
|
-
position: absolute; inset: 50px;
|
|
176
|
-
border: 2px solid rgba(236,238,243,0.30);
|
|
177
|
-
pointer-events: none; z-index: 5;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/* 装饰件:四角断线框(默认关) */
|
|
181
|
-
.corner {
|
|
182
|
-
position: absolute; width: 80px; height: 80px;
|
|
183
|
-
border: 1.5px solid rgba(236,238,243,0.28);
|
|
184
|
-
pointer-events: none; z-index: 5;
|
|
185
|
-
}
|
|
186
|
-
.corner.tl { top: 60px; left: 60px; border-right: 0; border-bottom: 0; }
|
|
187
|
-
.corner.tr { top: 60px; right: 60px; border-left: 0; border-bottom: 0; }
|
|
188
|
-
.corner.bl { bottom: 60px; left: 60px; border-right: 0; border-top: 0; }
|
|
189
|
-
.corner.br { bottom: 60px; right: 60px; border-left: 0; border-top: 0; }
|
|
190
|
-
|
|
191
|
-
.controls { border-left: 1px solid var(--border); padding: 20px 16px; background: #0A0C13; overflow-y: auto; max-height: calc(100vh - 60px); }
|
|
192
|
-
.ctrl-group { margin-bottom: 18px; }
|
|
193
|
-
.ctrl-group h3 {
|
|
194
|
-
font-size: 10px; letter-spacing: 2.5px; color: var(--muted);
|
|
195
|
-
margin: 0 0 10px; text-transform: uppercase;
|
|
196
|
-
font-family: "JetBrains Mono", monospace;
|
|
197
|
-
display: flex; align-items: center; gap: 8px;
|
|
198
|
-
}
|
|
199
|
-
.ctrl-group h3::after { content: ''; flex: 1; height: 1px; background: var(--border); }
|
|
200
|
-
.row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; gap: 8px; }
|
|
201
|
-
.row label { font-size: 12px; color: var(--text); min-width: 60px; flex: 0 0 auto; }
|
|
202
|
-
.row .val { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--amber); min-width: 44px; text-align: right; }
|
|
203
|
-
.row input[type="range"] { flex: 1; accent-color: var(--amber); height: 3px; }
|
|
204
|
-
.row input[type="text"], .row select {
|
|
205
|
-
flex: 1; background: var(--panel); color: var(--text);
|
|
206
|
-
border: 1px solid var(--border); border-radius: 4px;
|
|
207
|
-
padding: 6px 8px; font-family: inherit; font-size: 12px;
|
|
208
|
-
}
|
|
209
|
-
.row input[type="text"]:focus, .row select:focus { outline: none; border-color: var(--amber); }
|
|
210
|
-
.row input[type="checkbox"] { accent-color: var(--amber); }
|
|
211
|
-
|
|
212
|
-
/* 通用 layer block(引擎层,勿改) */
|
|
213
|
-
.layer-block {
|
|
214
|
-
border: 1px solid var(--border);
|
|
215
|
-
border-radius: 6px;
|
|
216
|
-
padding: 9px 11px;
|
|
217
|
-
margin-bottom: 7px;
|
|
218
|
-
background: var(--panel);
|
|
219
|
-
cursor: pointer;
|
|
220
|
-
transition: all 0.15s;
|
|
221
|
-
}
|
|
222
|
-
.layer-block:hover { border-color: rgba(122,134,158,0.55); }
|
|
223
|
-
.layer-block.selected { border-color: var(--amber); background: var(--panel-2); box-shadow: 0 0 0 1px rgba(122,134,158,0.35); }
|
|
224
|
-
.layer-block .head {
|
|
225
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
226
|
-
margin-bottom: 7px; gap: 6px;
|
|
227
|
-
}
|
|
228
|
-
.layer-block .char-preview {
|
|
229
|
-
font-family: "Noto Sans SC", sans-serif;
|
|
230
|
-
font-weight: 900; font-size: 22px;
|
|
231
|
-
color: var(--bone);
|
|
232
|
-
width: 30px; height: 30px;
|
|
233
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
234
|
-
background: var(--midnight); border-radius: 4px;
|
|
235
|
-
flex-shrink: 0;
|
|
236
|
-
}
|
|
237
|
-
.layer-block .tag-preview {
|
|
238
|
-
font-family: "JetBrains Mono", monospace;
|
|
239
|
-
font-size: 10px; color: var(--amber);
|
|
240
|
-
padding: 6px 10px;
|
|
241
|
-
background: var(--midnight); border-radius: 4px;
|
|
242
|
-
letter-spacing: 1px; flex-shrink: 0;
|
|
243
|
-
}
|
|
244
|
-
.layer-block .pos-tag {
|
|
245
|
-
font-family: "JetBrains Mono", monospace;
|
|
246
|
-
font-size: 10px; color: var(--muted);
|
|
247
|
-
letter-spacing: 1px;
|
|
248
|
-
}
|
|
249
|
-
.layer-block .vis-toggle {
|
|
250
|
-
font-family: "JetBrains Mono", monospace;
|
|
251
|
-
font-size: 10px; color: var(--muted);
|
|
252
|
-
display: flex; align-items: center; gap: 4px;
|
|
253
|
-
}
|
|
254
|
-
.layer-block .mini-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
|
255
|
-
.layer-block .mini-row:last-child { margin-bottom: 0; }
|
|
256
|
-
.layer-block .mini-row label { font-size: 10px; color: var(--muted); min-width: 14px; font-family: "JetBrains Mono", monospace; }
|
|
257
|
-
.layer-block .mini-row input[type="number"] {
|
|
258
|
-
width: 66px;
|
|
259
|
-
background: var(--ink); color: var(--amber);
|
|
260
|
-
border: 1px solid var(--border); border-radius: 3px;
|
|
261
|
-
padding: 4px 6px;
|
|
262
|
-
font-family: "JetBrains Mono", monospace; font-size: 11px;
|
|
263
|
-
}
|
|
264
|
-
.layer-block .mini-row input[type="number"]:focus { outline: none; border-color: var(--amber); }
|
|
265
|
-
.layer-block .mini-row input[type="text"] {
|
|
266
|
-
flex: 1; background: var(--ink); color: var(--text);
|
|
267
|
-
border: 1px solid var(--border); border-radius: 3px;
|
|
268
|
-
padding: 4px 6px;
|
|
269
|
-
font-family: inherit; font-size: 11px;
|
|
270
|
-
}
|
|
271
|
-
.layer-block .mini-row input[type="text"]:focus { outline: none; border-color: var(--amber); }
|
|
272
|
-
|
|
273
|
-
.export-btn {
|
|
274
|
-
display: block; width: 100%;
|
|
275
|
-
padding: 13px; margin-top: 6px;
|
|
276
|
-
background: var(--amber); color: #fff;
|
|
277
|
-
border: none; border-radius: 6px;
|
|
278
|
-
font-family: inherit; font-weight: 700; font-size: 13px;
|
|
279
|
-
cursor: pointer; letter-spacing: 2px; transition: all 0.15s;
|
|
280
|
-
}
|
|
281
|
-
.export-btn:hover { background: var(--amber-soft); transform: translateY(-1px); }
|
|
282
|
-
.export-btn:disabled { background: var(--border); color: var(--muted); cursor: wait; transform: none; }
|
|
283
|
-
|
|
284
|
-
.notes { font-size: 10px; color: var(--muted); line-height: 1.75; padding: 10px; background: var(--panel); border-radius: 4px; border-left: 2px solid var(--amber); }
|
|
285
|
-
.notes strong { color: var(--amber-soft); }
|
|
286
|
-
.notes kbd { background: var(--ink); border: 1px solid var(--border); border-radius: 3px; padding: 1px 5px; font-size: 10px; color: var(--amber-soft); font-family: "JetBrains Mono", monospace; }
|
|
287
|
-
|
|
288
|
-
@media (max-width: 1200px) {
|
|
289
|
-
main { grid-template-columns: 200px 1fr 300px; }
|
|
290
|
-
}
|
|
291
|
-
@media (max-width: 900px) {
|
|
292
|
-
main { grid-template-columns: 1fr; }
|
|
293
|
-
.tabs, .controls { border: none; max-height: none; }
|
|
294
|
-
.tabs { display: flex; overflow-x: auto; padding: 12px; }
|
|
295
|
-
.tab { flex-shrink: 0; width: 160px; margin-right: 6px; margin-bottom: 0; }
|
|
296
|
-
}
|
|
297
|
-
</style>
|
|
298
|
-
</head>
|
|
299
|
-
<body>
|
|
300
|
-
|
|
301
|
-
<header class="app-head">
|
|
302
|
-
<div>
|
|
303
|
-
<!-- 替换位:当期 T1 与英文副题 -->
|
|
304
|
-
<h1>示例 <span class="ink">·</span> 标题<span class="sub">TEMPLATE — 封面排版工作台</span></h1>
|
|
305
|
-
</div>
|
|
306
|
-
<div class="meta">
|
|
307
|
-
<!-- 替换位:布局名 / 色板名 -->
|
|
308
|
-
<span class="pill">布局待定</span>
|
|
309
|
-
<span class="pill">色板待注入</span>
|
|
310
|
-
<span class="pill">拖拽 / 滚轮 / 一键导出</span>
|
|
311
|
-
</div>
|
|
312
|
-
</header>
|
|
313
|
-
|
|
314
|
-
<main>
|
|
315
|
-
<aside class="tabs">
|
|
316
|
-
<p class="tab-title">// 尺寸</p>
|
|
317
|
-
<button class="tab active" data-size="16_9">
|
|
318
|
-
<div class="ratio">16 : 9</div>
|
|
319
|
-
<div class="desc">横版 · 布局按当期标注</div>
|
|
320
|
-
</button>
|
|
321
|
-
<button class="tab" data-size="4_3">
|
|
322
|
-
<div class="ratio">4 : 3</div>
|
|
323
|
-
<div class="desc">横版饱满 · 布局按当期标注</div>
|
|
324
|
-
</button>
|
|
325
|
-
<button class="tab" data-size="3_4">
|
|
326
|
-
<div class="ratio">3 : 4</div>
|
|
327
|
-
<div class="desc">竖版 · 布局按当期标注</div>
|
|
328
|
-
</button>
|
|
329
|
-
|
|
330
|
-
<div class="divider"></div>
|
|
331
|
-
<p class="tab-title">// 替换背景</p>
|
|
332
|
-
<label class="upload-btn">
|
|
333
|
-
上传当前尺寸的背景图
|
|
334
|
-
<input type="file" id="bg-upload" accept="image/*">
|
|
335
|
-
</label>
|
|
336
|
-
<p class="hint">
|
|
337
|
-
背景图已默认载入三张。抽到新版本想替换,在此上传只影响当前激活尺寸。
|
|
338
|
-
</p>
|
|
339
|
-
|
|
340
|
-
<div class="divider"></div>
|
|
341
|
-
<button class="reset-btn" id="resetAllBtn">↺ 恢复当前尺寸默认布局</button>
|
|
342
|
-
</aside>
|
|
343
|
-
|
|
344
|
-
<section class="stage" id="stage">
|
|
345
|
-
<div class="canvas-wrap" id="canvasWrap">
|
|
346
|
-
<div class="dim-tag"><span id="dimLabel">2400 × 1350</span> <span class="num">— 16:9</span></div>
|
|
347
|
-
|
|
348
|
-
<!-- 替换位:char* div 数量与 T1_KEYS 一致(当期 T1 字数驱动),initial 逐字填 -->
|
|
349
|
-
<div class="cover active" data-size="16_9">
|
|
350
|
-
<img class="bg" src="__BG_16_9__" alt="">
|
|
351
|
-
<div class="inner-frame"></div>
|
|
352
|
-
<div class="corner tl"></div><div class="corner tr"></div>
|
|
353
|
-
<div class="corner bl"></div><div class="corner br"></div>
|
|
354
|
-
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
355
|
-
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
356
|
-
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
357
|
-
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
358
|
-
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
359
|
-
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
360
|
-
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
<div class="cover" data-size="4_3">
|
|
364
|
-
<img class="bg" src="__BG_4_3__" alt="">
|
|
365
|
-
<div class="inner-frame"></div>
|
|
366
|
-
<div class="corner tl"></div><div class="corner tr"></div>
|
|
367
|
-
<div class="corner bl"></div><div class="corner br"></div>
|
|
368
|
-
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
369
|
-
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
370
|
-
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
371
|
-
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
372
|
-
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
373
|
-
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
374
|
-
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
375
|
-
</div>
|
|
376
|
-
|
|
377
|
-
<div class="cover" data-size="3_4">
|
|
378
|
-
<img class="bg" src="__BG_3_4__" alt="">
|
|
379
|
-
<div class="inner-frame"></div>
|
|
380
|
-
<div class="corner tl"></div><div class="corner tr"></div>
|
|
381
|
-
<div class="corner bl"></div><div class="corner br"></div>
|
|
382
|
-
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
383
|
-
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
384
|
-
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
385
|
-
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
386
|
-
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
387
|
-
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
388
|
-
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
389
|
-
</div>
|
|
390
|
-
</div>
|
|
391
|
-
</section>
|
|
392
|
-
|
|
393
|
-
<aside class="controls">
|
|
394
|
-
<div class="ctrl-group">
|
|
395
|
-
<h3>T1 文案</h3>
|
|
396
|
-
<div class="row">
|
|
397
|
-
<!-- 替换位:value 与 maxlength 按当期 T1;与 state.t1Text 保持一致 -->
|
|
398
|
-
<input type="text" id="t1-text" value="示例标题" maxlength="4"
|
|
399
|
-
style="font-family:'Noto Sans SC',sans-serif; font-weight:700; letter-spacing:0.3em; font-size:14px;">
|
|
400
|
-
</div>
|
|
401
|
-
<p class="notes" style="margin-top:6px;">
|
|
402
|
-
改这里 <strong>同步到全部三张</strong>;每字独立位置/字号在下方调。
|
|
403
|
-
</p>
|
|
404
|
-
</div>
|
|
405
|
-
|
|
406
|
-
<div class="ctrl-group">
|
|
407
|
-
<h3>T1 逐字微调 <span style="font-size:9px; color:var(--amber); margin-left:4px;">画布上可拖</span></h3>
|
|
408
|
-
<div id="char-blocks"></div>
|
|
409
|
-
</div>
|
|
410
|
-
|
|
411
|
-
<div class="ctrl-group">
|
|
412
|
-
<h3>T1 全局样式</h3>
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
<
|
|
428
|
-
|
|
429
|
-
<option value="
|
|
430
|
-
<option value="
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
</div>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
</
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
document.getElementById('t1-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
document.
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
document.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>示例标题 · 封面排版工作台</title>
|
|
6
|
+
<!-- ↑ 替换位:当期 T1 -->
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<!-- 替换位:字体族按栏目风格资产增删(衬线/黑体/手写体等) -->
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@700;900&family=Noto+Sans+SC:wght@400;700;900&family=Cormorant+Garamond:ital,wght@0,500;1,400&family=JetBrains+Mono:wght@400;600&family=Long+Cang&display=swap" rel="stylesheet">
|
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
12
|
+
<style>
|
|
13
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
14
|
+
/* 替换位:色板变量——变量名固定、换值即换装(语义见 workbench-guide §3)。以下为中性默认值 */
|
|
15
|
+
:root {
|
|
16
|
+
--midnight: #14161E;
|
|
17
|
+
--indigo: #232838;
|
|
18
|
+
--amber: #7A869E;
|
|
19
|
+
--amber-soft: #9AA5BC;
|
|
20
|
+
--bone: #ECEEF3;
|
|
21
|
+
--ink: #0A0C12;
|
|
22
|
+
--panel: #161923;
|
|
23
|
+
--panel-2: #1E2230;
|
|
24
|
+
--border: #2C3145;
|
|
25
|
+
--muted: #8B92A8;
|
|
26
|
+
--text: #E4E7F0;
|
|
27
|
+
}
|
|
28
|
+
html, body {
|
|
29
|
+
margin: 0; padding: 0;
|
|
30
|
+
background: #07080D; color: var(--text);
|
|
31
|
+
font-family: "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
|
32
|
+
font-size: 14px; min-height: 100vh; overflow: hidden;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
header.app-head {
|
|
36
|
+
padding: 20px 32px 14px;
|
|
37
|
+
border-bottom: 1px solid var(--border);
|
|
38
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
39
|
+
gap: 24px; flex-wrap: wrap;
|
|
40
|
+
}
|
|
41
|
+
header h1 {
|
|
42
|
+
font-family: "Noto Sans SC", sans-serif;
|
|
43
|
+
font-weight: 900; font-size: 20px; letter-spacing: 2px;
|
|
44
|
+
margin: 0; color: var(--bone);
|
|
45
|
+
}
|
|
46
|
+
header h1 .ink { color: var(--amber); }
|
|
47
|
+
header .sub { font-size: 11px; color: var(--muted); letter-spacing: 1px; margin-left: 12px; }
|
|
48
|
+
header .meta { display: flex; gap: 8px; align-items: center; font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--muted); }
|
|
49
|
+
header .meta .pill { border: 1px solid var(--border); padding: 4px 10px; border-radius: 999px; color: var(--amber-soft); }
|
|
50
|
+
|
|
51
|
+
main {
|
|
52
|
+
display: grid;
|
|
53
|
+
grid-template-columns: 220px 1fr 340px;
|
|
54
|
+
min-height: calc(100vh - 60px);
|
|
55
|
+
}
|
|
56
|
+
.tabs { border-right: 1px solid var(--border); padding: 20px 14px; background: #0A0C13; overflow-y: auto; max-height: calc(100vh - 60px); }
|
|
57
|
+
.tab-title { font-size: 10px; letter-spacing: 2.5px; color: var(--muted); margin: 0 0 10px 4px; text-transform: uppercase; font-family: "JetBrains Mono", monospace; }
|
|
58
|
+
.tab {
|
|
59
|
+
display: block; width: 100%; text-align: left;
|
|
60
|
+
padding: 12px; background: transparent;
|
|
61
|
+
border: 1px solid transparent; color: var(--text);
|
|
62
|
+
cursor: pointer; border-radius: 5px; margin-bottom: 6px;
|
|
63
|
+
font-family: inherit; font-size: 13px; transition: all 0.15s;
|
|
64
|
+
}
|
|
65
|
+
.tab:hover { background: var(--panel); border-color: var(--border); }
|
|
66
|
+
.tab.active { background: var(--panel-2); border-color: var(--amber); color: var(--bone); }
|
|
67
|
+
.tab .ratio { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--amber); letter-spacing: 1px; }
|
|
68
|
+
.tab .desc { font-size: 10px; color: var(--muted); margin-top: 3px; }
|
|
69
|
+
.tabs .divider { height: 1px; background: var(--border); margin: 16px 0; }
|
|
70
|
+
|
|
71
|
+
.upload-btn {
|
|
72
|
+
display: block; width: 100%; padding: 10px; border-radius: 5px;
|
|
73
|
+
background: transparent; border: 1px dashed var(--border); color: var(--muted);
|
|
74
|
+
cursor: pointer; font-size: 11px; font-family: inherit;
|
|
75
|
+
transition: all 0.15s; text-align: center;
|
|
76
|
+
}
|
|
77
|
+
.upload-btn:hover { border-color: var(--amber); color: var(--amber); }
|
|
78
|
+
.upload-btn input { display: none; }
|
|
79
|
+
.hint { font-size: 10px; color: var(--muted); line-height: 1.7; margin-top: 10px; padding: 10px; background: var(--panel); border-radius: 4px; border-left: 2px solid var(--amber); }
|
|
80
|
+
.reset-btn {
|
|
81
|
+
display: block; width: 100%; padding: 9px; margin-top: 8px;
|
|
82
|
+
background: transparent; color: var(--muted);
|
|
83
|
+
border: 1px solid var(--border); border-radius: 5px;
|
|
84
|
+
font-family: inherit; font-size: 11px; cursor: pointer; letter-spacing: 1px;
|
|
85
|
+
transition: all 0.15s;
|
|
86
|
+
}
|
|
87
|
+
.reset-btn:hover { color: var(--amber); border-color: var(--amber); }
|
|
88
|
+
|
|
89
|
+
.stage {
|
|
90
|
+
padding: 26px 32px 50px;
|
|
91
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
92
|
+
background: radial-gradient(ellipse at top, rgba(35,40,56,0.25), transparent 60%), #07080D;
|
|
93
|
+
overflow: auto;
|
|
94
|
+
}
|
|
95
|
+
/* 工程注意:transform 只缩视觉不缩布局——占位靠 fitStage 的负 margin 收缩,勿改回直接设小宽高 */
|
|
96
|
+
.canvas-wrap {
|
|
97
|
+
position: relative;
|
|
98
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(122,134,158,0.14);
|
|
99
|
+
transform-origin: top left;
|
|
100
|
+
}
|
|
101
|
+
.canvas-wrap .dim-tag { position: absolute; top: -24px; left: 0; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--muted); letter-spacing: 1.5px; }
|
|
102
|
+
.canvas-wrap .dim-tag .num { color: var(--amber); }
|
|
103
|
+
|
|
104
|
+
.cover { position: relative; overflow: hidden; background: var(--midnight); font-family: "Noto Sans SC", sans-serif; }
|
|
105
|
+
.cover[data-size="16_9"] { width: 2400px; height: 1350px; }
|
|
106
|
+
.cover[data-size="4_3"] { width: 2400px; height: 1800px; }
|
|
107
|
+
.cover[data-size="3_4"] { width: 1500px; height: 2000px; }
|
|
108
|
+
.cover .bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; user-select: none; pointer-events: none; }
|
|
109
|
+
.cover { display: none; }
|
|
110
|
+
.cover.active { display: block; }
|
|
111
|
+
|
|
112
|
+
/* 所有文字图层的通用交互样式(引擎层,勿改) */
|
|
113
|
+
.layer-text {
|
|
114
|
+
position: absolute;
|
|
115
|
+
cursor: grab;
|
|
116
|
+
user-select: none;
|
|
117
|
+
transition: outline 0.12s;
|
|
118
|
+
outline: 0 dashed transparent;
|
|
119
|
+
outline-offset: 8px;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
}
|
|
122
|
+
.layer-text:hover { outline: 3px dashed rgba(122,134,158,0.5); }
|
|
123
|
+
.layer-text.selected { outline: 5px dashed rgba(122,134,158,0.95); }
|
|
124
|
+
.layer-text.dragging { cursor: grabbing; outline: 5px solid rgba(122,134,158,0.95); }
|
|
125
|
+
|
|
126
|
+
/* 替换位:T1 主字风格——默认字体/字重与 script 顶部 T1_DEFAULT_FONT 保持一致(首帧无闪);发光 rgba 与 applyT1Style() 内保持同色。
|
|
127
|
+
描边缺省 0:思源黑/宋这类重叠轮廓的特黑字体一加 text-stroke 会把部件内部轮廓全描出来(镂空积木),托底改靠下面的暗色多层 text-shadow */
|
|
128
|
+
.t1 {
|
|
129
|
+
font-family: "Noto Sans SC", sans-serif;
|
|
130
|
+
font-weight: 900;
|
|
131
|
+
color: #FFFFFF;
|
|
132
|
+
line-height: 1;
|
|
133
|
+
letter-spacing: 0;
|
|
134
|
+
z-index: 10;
|
|
135
|
+
-webkit-text-stroke: 0 transparent;
|
|
136
|
+
text-shadow:
|
|
137
|
+
0 0 6px rgba(20,22,30,0.9),
|
|
138
|
+
0 0 14px rgba(20,22,30,0.8),
|
|
139
|
+
0 0 20px rgba(200,210,230,0.14),
|
|
140
|
+
0 0 45px rgba(200,210,230,0.10),
|
|
141
|
+
0 10px 28px rgba(0,0,0,0.6);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* 替换位:T2 副标题风格 */
|
|
145
|
+
.t2 {
|
|
146
|
+
font-family: "Noto Sans SC", sans-serif;
|
|
147
|
+
font-weight: 700;
|
|
148
|
+
color: rgba(236,238,243,0.88);
|
|
149
|
+
letter-spacing: 0.4em;
|
|
150
|
+
text-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
|
151
|
+
z-index: 9;
|
|
152
|
+
}
|
|
153
|
+
/* 替换位:T5 装饰英文风格 */
|
|
154
|
+
.t5 {
|
|
155
|
+
font-family: "Cormorant Garamond", serif;
|
|
156
|
+
font-style: italic;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
color: rgba(236,238,243,0.5);
|
|
159
|
+
letter-spacing: 0.3em;
|
|
160
|
+
text-transform: uppercase;
|
|
161
|
+
z-index: 8;
|
|
162
|
+
}
|
|
163
|
+
/* 替换位:T4 品牌戳风格(手写签名感;用户没给栏目名则 visible:false、不编内容) */
|
|
164
|
+
.t4 {
|
|
165
|
+
font-family: "Long Cang", cursive;
|
|
166
|
+
font-weight: 400;
|
|
167
|
+
color: rgba(236,238,243,0.72);
|
|
168
|
+
letter-spacing: 0.12em;
|
|
169
|
+
text-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
|
170
|
+
z-index: 8;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* 装饰件:薄内框(默认关,按当期决策开)。浅底尺寸的深色覆写见 workbench-guide §5 */
|
|
174
|
+
.inner-frame {
|
|
175
|
+
position: absolute; inset: 50px;
|
|
176
|
+
border: 2px solid rgba(236,238,243,0.30);
|
|
177
|
+
pointer-events: none; z-index: 5;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* 装饰件:四角断线框(默认关) */
|
|
181
|
+
.corner {
|
|
182
|
+
position: absolute; width: 80px; height: 80px;
|
|
183
|
+
border: 1.5px solid rgba(236,238,243,0.28);
|
|
184
|
+
pointer-events: none; z-index: 5;
|
|
185
|
+
}
|
|
186
|
+
.corner.tl { top: 60px; left: 60px; border-right: 0; border-bottom: 0; }
|
|
187
|
+
.corner.tr { top: 60px; right: 60px; border-left: 0; border-bottom: 0; }
|
|
188
|
+
.corner.bl { bottom: 60px; left: 60px; border-right: 0; border-top: 0; }
|
|
189
|
+
.corner.br { bottom: 60px; right: 60px; border-left: 0; border-top: 0; }
|
|
190
|
+
|
|
191
|
+
.controls { border-left: 1px solid var(--border); padding: 20px 16px; background: #0A0C13; overflow-y: auto; max-height: calc(100vh - 60px); }
|
|
192
|
+
.ctrl-group { margin-bottom: 18px; }
|
|
193
|
+
.ctrl-group h3 {
|
|
194
|
+
font-size: 10px; letter-spacing: 2.5px; color: var(--muted);
|
|
195
|
+
margin: 0 0 10px; text-transform: uppercase;
|
|
196
|
+
font-family: "JetBrains Mono", monospace;
|
|
197
|
+
display: flex; align-items: center; gap: 8px;
|
|
198
|
+
}
|
|
199
|
+
.ctrl-group h3::after { content: ''; flex: 1; height: 1px; background: var(--border); }
|
|
200
|
+
.row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; gap: 8px; }
|
|
201
|
+
.row label { font-size: 12px; color: var(--text); min-width: 60px; flex: 0 0 auto; }
|
|
202
|
+
.row .val { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--amber); min-width: 44px; text-align: right; }
|
|
203
|
+
.row input[type="range"] { flex: 1; accent-color: var(--amber); height: 3px; }
|
|
204
|
+
.row input[type="text"], .row select {
|
|
205
|
+
flex: 1; background: var(--panel); color: var(--text);
|
|
206
|
+
border: 1px solid var(--border); border-radius: 4px;
|
|
207
|
+
padding: 6px 8px; font-family: inherit; font-size: 12px;
|
|
208
|
+
}
|
|
209
|
+
.row input[type="text"]:focus, .row select:focus { outline: none; border-color: var(--amber); }
|
|
210
|
+
.row input[type="checkbox"] { accent-color: var(--amber); }
|
|
211
|
+
|
|
212
|
+
/* 通用 layer block(引擎层,勿改) */
|
|
213
|
+
.layer-block {
|
|
214
|
+
border: 1px solid var(--border);
|
|
215
|
+
border-radius: 6px;
|
|
216
|
+
padding: 9px 11px;
|
|
217
|
+
margin-bottom: 7px;
|
|
218
|
+
background: var(--panel);
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
transition: all 0.15s;
|
|
221
|
+
}
|
|
222
|
+
.layer-block:hover { border-color: rgba(122,134,158,0.55); }
|
|
223
|
+
.layer-block.selected { border-color: var(--amber); background: var(--panel-2); box-shadow: 0 0 0 1px rgba(122,134,158,0.35); }
|
|
224
|
+
.layer-block .head {
|
|
225
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
226
|
+
margin-bottom: 7px; gap: 6px;
|
|
227
|
+
}
|
|
228
|
+
.layer-block .char-preview {
|
|
229
|
+
font-family: "Noto Sans SC", sans-serif;
|
|
230
|
+
font-weight: 900; font-size: 22px;
|
|
231
|
+
color: var(--bone);
|
|
232
|
+
width: 30px; height: 30px;
|
|
233
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
234
|
+
background: var(--midnight); border-radius: 4px;
|
|
235
|
+
flex-shrink: 0;
|
|
236
|
+
}
|
|
237
|
+
.layer-block .tag-preview {
|
|
238
|
+
font-family: "JetBrains Mono", monospace;
|
|
239
|
+
font-size: 10px; color: var(--amber);
|
|
240
|
+
padding: 6px 10px;
|
|
241
|
+
background: var(--midnight); border-radius: 4px;
|
|
242
|
+
letter-spacing: 1px; flex-shrink: 0;
|
|
243
|
+
}
|
|
244
|
+
.layer-block .pos-tag {
|
|
245
|
+
font-family: "JetBrains Mono", monospace;
|
|
246
|
+
font-size: 10px; color: var(--muted);
|
|
247
|
+
letter-spacing: 1px;
|
|
248
|
+
}
|
|
249
|
+
.layer-block .vis-toggle {
|
|
250
|
+
font-family: "JetBrains Mono", monospace;
|
|
251
|
+
font-size: 10px; color: var(--muted);
|
|
252
|
+
display: flex; align-items: center; gap: 4px;
|
|
253
|
+
}
|
|
254
|
+
.layer-block .mini-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
|
255
|
+
.layer-block .mini-row:last-child { margin-bottom: 0; }
|
|
256
|
+
.layer-block .mini-row label { font-size: 10px; color: var(--muted); min-width: 14px; font-family: "JetBrains Mono", monospace; }
|
|
257
|
+
.layer-block .mini-row input[type="number"] {
|
|
258
|
+
width: 66px;
|
|
259
|
+
background: var(--ink); color: var(--amber);
|
|
260
|
+
border: 1px solid var(--border); border-radius: 3px;
|
|
261
|
+
padding: 4px 6px;
|
|
262
|
+
font-family: "JetBrains Mono", monospace; font-size: 11px;
|
|
263
|
+
}
|
|
264
|
+
.layer-block .mini-row input[type="number"]:focus { outline: none; border-color: var(--amber); }
|
|
265
|
+
.layer-block .mini-row input[type="text"] {
|
|
266
|
+
flex: 1; background: var(--ink); color: var(--text);
|
|
267
|
+
border: 1px solid var(--border); border-radius: 3px;
|
|
268
|
+
padding: 4px 6px;
|
|
269
|
+
font-family: inherit; font-size: 11px;
|
|
270
|
+
}
|
|
271
|
+
.layer-block .mini-row input[type="text"]:focus { outline: none; border-color: var(--amber); }
|
|
272
|
+
|
|
273
|
+
.export-btn {
|
|
274
|
+
display: block; width: 100%;
|
|
275
|
+
padding: 13px; margin-top: 6px;
|
|
276
|
+
background: var(--amber); color: #fff;
|
|
277
|
+
border: none; border-radius: 6px;
|
|
278
|
+
font-family: inherit; font-weight: 700; font-size: 13px;
|
|
279
|
+
cursor: pointer; letter-spacing: 2px; transition: all 0.15s;
|
|
280
|
+
}
|
|
281
|
+
.export-btn:hover { background: var(--amber-soft); transform: translateY(-1px); }
|
|
282
|
+
.export-btn:disabled { background: var(--border); color: var(--muted); cursor: wait; transform: none; }
|
|
283
|
+
|
|
284
|
+
.notes { font-size: 10px; color: var(--muted); line-height: 1.75; padding: 10px; background: var(--panel); border-radius: 4px; border-left: 2px solid var(--amber); }
|
|
285
|
+
.notes strong { color: var(--amber-soft); }
|
|
286
|
+
.notes kbd { background: var(--ink); border: 1px solid var(--border); border-radius: 3px; padding: 1px 5px; font-size: 10px; color: var(--amber-soft); font-family: "JetBrains Mono", monospace; }
|
|
287
|
+
|
|
288
|
+
@media (max-width: 1200px) {
|
|
289
|
+
main { grid-template-columns: 200px 1fr 300px; }
|
|
290
|
+
}
|
|
291
|
+
@media (max-width: 900px) {
|
|
292
|
+
main { grid-template-columns: 1fr; }
|
|
293
|
+
.tabs, .controls { border: none; max-height: none; }
|
|
294
|
+
.tabs { display: flex; overflow-x: auto; padding: 12px; }
|
|
295
|
+
.tab { flex-shrink: 0; width: 160px; margin-right: 6px; margin-bottom: 0; }
|
|
296
|
+
}
|
|
297
|
+
</style>
|
|
298
|
+
</head>
|
|
299
|
+
<body>
|
|
300
|
+
|
|
301
|
+
<header class="app-head">
|
|
302
|
+
<div>
|
|
303
|
+
<!-- 替换位:当期 T1 与英文副题 -->
|
|
304
|
+
<h1>示例 <span class="ink">·</span> 标题<span class="sub">TEMPLATE — 封面排版工作台</span></h1>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="meta">
|
|
307
|
+
<!-- 替换位:布局名 / 色板名 -->
|
|
308
|
+
<span class="pill">布局待定</span>
|
|
309
|
+
<span class="pill">色板待注入</span>
|
|
310
|
+
<span class="pill">拖拽 / 滚轮 / 一键导出</span>
|
|
311
|
+
</div>
|
|
312
|
+
</header>
|
|
313
|
+
|
|
314
|
+
<main>
|
|
315
|
+
<aside class="tabs">
|
|
316
|
+
<p class="tab-title">// 尺寸</p>
|
|
317
|
+
<button class="tab active" data-size="16_9">
|
|
318
|
+
<div class="ratio">16 : 9</div>
|
|
319
|
+
<div class="desc">横版 · 布局按当期标注</div>
|
|
320
|
+
</button>
|
|
321
|
+
<button class="tab" data-size="4_3">
|
|
322
|
+
<div class="ratio">4 : 3</div>
|
|
323
|
+
<div class="desc">横版饱满 · 布局按当期标注</div>
|
|
324
|
+
</button>
|
|
325
|
+
<button class="tab" data-size="3_4">
|
|
326
|
+
<div class="ratio">3 : 4</div>
|
|
327
|
+
<div class="desc">竖版 · 布局按当期标注</div>
|
|
328
|
+
</button>
|
|
329
|
+
|
|
330
|
+
<div class="divider"></div>
|
|
331
|
+
<p class="tab-title">// 替换背景</p>
|
|
332
|
+
<label class="upload-btn">
|
|
333
|
+
上传当前尺寸的背景图
|
|
334
|
+
<input type="file" id="bg-upload" accept="image/*">
|
|
335
|
+
</label>
|
|
336
|
+
<p class="hint">
|
|
337
|
+
背景图已默认载入三张。抽到新版本想替换,在此上传只影响当前激活尺寸。
|
|
338
|
+
</p>
|
|
339
|
+
|
|
340
|
+
<div class="divider"></div>
|
|
341
|
+
<button class="reset-btn" id="resetAllBtn">↺ 恢复当前尺寸默认布局</button>
|
|
342
|
+
</aside>
|
|
343
|
+
|
|
344
|
+
<section class="stage" id="stage">
|
|
345
|
+
<div class="canvas-wrap" id="canvasWrap">
|
|
346
|
+
<div class="dim-tag"><span id="dimLabel">2400 × 1350</span> <span class="num">— 16:9</span></div>
|
|
347
|
+
|
|
348
|
+
<!-- 替换位:char* div 数量与 T1_KEYS 一致(当期 T1 字数驱动),initial 逐字填 -->
|
|
349
|
+
<div class="cover active" data-size="16_9">
|
|
350
|
+
<img class="bg" src="__BG_16_9__" alt="">
|
|
351
|
+
<div class="inner-frame"></div>
|
|
352
|
+
<div class="corner tl"></div><div class="corner tr"></div>
|
|
353
|
+
<div class="corner bl"></div><div class="corner br"></div>
|
|
354
|
+
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
355
|
+
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
356
|
+
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
357
|
+
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
358
|
+
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
359
|
+
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
360
|
+
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div class="cover" data-size="4_3">
|
|
364
|
+
<img class="bg" src="__BG_4_3__" alt="">
|
|
365
|
+
<div class="inner-frame"></div>
|
|
366
|
+
<div class="corner tl"></div><div class="corner tr"></div>
|
|
367
|
+
<div class="corner bl"></div><div class="corner br"></div>
|
|
368
|
+
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
369
|
+
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
370
|
+
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
371
|
+
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
372
|
+
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
373
|
+
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
374
|
+
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div class="cover" data-size="3_4">
|
|
378
|
+
<img class="bg" src="__BG_3_4__" alt="">
|
|
379
|
+
<div class="inner-frame"></div>
|
|
380
|
+
<div class="corner tl"></div><div class="corner tr"></div>
|
|
381
|
+
<div class="corner bl"></div><div class="corner br"></div>
|
|
382
|
+
<div class="layer-text t1" data-layer-key="char1">示</div>
|
|
383
|
+
<div class="layer-text t1" data-layer-key="char2">例</div>
|
|
384
|
+
<div class="layer-text t1" data-layer-key="char3">标</div>
|
|
385
|
+
<div class="layer-text t1" data-layer-key="char4">题</div>
|
|
386
|
+
<div class="layer-text t2" data-layer-key="t2">副标题钩子示例</div>
|
|
387
|
+
<div class="layer-text t5" data-layer-key="t5">SUBTITLE</div>
|
|
388
|
+
<div class="layer-text t4" data-layer-key="t4">品牌戳未提供</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</section>
|
|
392
|
+
|
|
393
|
+
<aside class="controls">
|
|
394
|
+
<div class="ctrl-group">
|
|
395
|
+
<h3>T1 文案</h3>
|
|
396
|
+
<div class="row">
|
|
397
|
+
<!-- 替换位:value 与 maxlength 按当期 T1;与 state.t1Text 保持一致 -->
|
|
398
|
+
<input type="text" id="t1-text" value="示例标题" maxlength="4"
|
|
399
|
+
style="font-family:'Noto Sans SC',sans-serif; font-weight:700; letter-spacing:0.3em; font-size:14px;">
|
|
400
|
+
</div>
|
|
401
|
+
<p class="notes" style="margin-top:6px;">
|
|
402
|
+
改这里 <strong>同步到全部三张</strong>;每字独立位置/字号在下方调。
|
|
403
|
+
</p>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<div class="ctrl-group">
|
|
407
|
+
<h3>T1 逐字微调 <span style="font-size:9px; color:var(--amber); margin-left:4px;">画布上可拖</span></h3>
|
|
408
|
+
<div id="char-blocks"></div>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<div class="ctrl-group">
|
|
412
|
+
<h3>T1 全局样式</h3>
|
|
413
|
+
<!-- 字体:下拉首项恒为当期默认(skill 注入,见 script 顶部 T1_DEFAULT_FONT);其余来自用户本机——
|
|
414
|
+
打开即用候选表探测已装字体填入;「读取本机字体」经 Local Font Access API 取全量(需授权,Chromium);
|
|
415
|
+
两条都没列出的字体在下方输入框敲名字回车 -->
|
|
416
|
+
<div class="row">
|
|
417
|
+
<label>字体</label>
|
|
418
|
+
<select id="t1-font"></select>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="row">
|
|
421
|
+
<button class="reset-btn" id="font-scan-btn" style="margin-top:0;">⌕ 读取本机字体(全量,需授权一次)</button>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="row">
|
|
424
|
+
<input type="text" id="t1-font-free" placeholder="或输入本机字体名,回车应用">
|
|
425
|
+
</div>
|
|
426
|
+
<div class="row">
|
|
427
|
+
<label>字重</label>
|
|
428
|
+
<select id="t1-weight">
|
|
429
|
+
<option value="400">常规 400</option>
|
|
430
|
+
<option value="700">粗体 700</option>
|
|
431
|
+
<option value="900">特黑 900</option>
|
|
432
|
+
</select>
|
|
433
|
+
</div>
|
|
434
|
+
<p class="notes" id="font-note" style="margin-top:4px;">字体列表读的是<strong>你本机</strong>装的字;导出用同一字体。</p>
|
|
435
|
+
<div class="row">
|
|
436
|
+
<label>描边粗细</label>
|
|
437
|
+
<input type="range" id="t1-stroke" min="0" max="8" step="0.5" value="0">
|
|
438
|
+
<span class="val" id="t1-stroke-val">0</span>
|
|
439
|
+
</div>
|
|
440
|
+
<p class="notes" style="margin-top:-2px;">描边缺省 0:思源黑 / 宋这类特黑字体一加描边会镂空成积木(描的是部件内部轮廓)。单轮廓字体才适合开。</p>
|
|
441
|
+
<div class="row">
|
|
442
|
+
<!-- 替换位:发光名与默认值跟色板叫(银辉/琥珀/…) -->
|
|
443
|
+
<label>外发光</label>
|
|
444
|
+
<input type="range" id="t1-glow" min="0" max="100" step="5" value="20">
|
|
445
|
+
<span class="val" id="t1-glow-val">20%</span>
|
|
446
|
+
</div>
|
|
447
|
+
<div class="row">
|
|
448
|
+
<label>字色</label>
|
|
449
|
+
<!-- 替换位:字色选项按栏目色板给 2~3 档 -->
|
|
450
|
+
<select id="t1-color">
|
|
451
|
+
<option value="#FFFFFF" selected>纯白 #FFFFFF</option>
|
|
452
|
+
<option value="#ECEEF3">浅灰白 #ECEEF3</option>
|
|
453
|
+
<option value="#7A869E">强调色 #7A869E</option>
|
|
454
|
+
</select>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="row">
|
|
457
|
+
<label>统一字号</label>
|
|
458
|
+
<input type="range" id="t1-size-all" min="280" max="800" step="10" value="520">
|
|
459
|
+
<span class="val" id="t1-size-all-val">520</span>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<div class="ctrl-group">
|
|
464
|
+
<h3>附加图层 <span style="font-size:9px; color:var(--amber); margin-left:4px;">同样可拖</span></h3>
|
|
465
|
+
<div id="sub-blocks"></div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="ctrl-group">
|
|
469
|
+
<h3>装饰件</h3>
|
|
470
|
+
<!-- 替换位:默认开关按当期装饰件决策;不用的装饰件保持关闭即可 -->
|
|
471
|
+
<div class="row">
|
|
472
|
+
<label>薄内框</label>
|
|
473
|
+
<input type="checkbox" id="inner-show">
|
|
474
|
+
</div>
|
|
475
|
+
<div class="row">
|
|
476
|
+
<label>四角断线框</label>
|
|
477
|
+
<input type="checkbox" id="corner-show">
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div class="ctrl-group">
|
|
482
|
+
<h3>导出</h3>
|
|
483
|
+
<button class="export-btn" id="exportBtn">导出当前封面 PNG</button>
|
|
484
|
+
<button class="export-btn" id="exportAllBtn" style="margin-top:8px; background: transparent; color: var(--amber); border: 1.5px solid var(--amber);">一键导出全部三张</button>
|
|
485
|
+
<p class="notes" style="margin-top: 10px;">
|
|
486
|
+
<strong>导出规格:</strong>原分辨率 PNG<br>
|
|
487
|
+
16:9 → 2400 × 1350<br>
|
|
488
|
+
4 : 3 → 2400 × 1800<br>
|
|
489
|
+
3 : 4 → 1500 × 2000<br><br>
|
|
490
|
+
<strong>操作提示:</strong><br>
|
|
491
|
+
· 画布上 <kbd>拖任何字</kbd> 直接改位置<br>
|
|
492
|
+
· 点字选中后 <kbd>↑↓←→</kbd> 精调 1px<br>
|
|
493
|
+
· 按住 <kbd>Shift</kbd> + 方向键走 10px<br>
|
|
494
|
+
· 数值框选中后 <kbd>滚轮</kbd> 微调,<kbd>Shift</kbd>+滚轮 ×10<br>
|
|
495
|
+
· 一键导出首次会请求「允许下载多个文件」,点允许即可<br>
|
|
496
|
+
· 布局状态在内存里,调好就导出、导出前别刷新<br>
|
|
497
|
+
· 字体:下拉是本机已装字体;换字后字重按最近字面匹配<br>
|
|
498
|
+
</p>
|
|
499
|
+
</div>
|
|
500
|
+
</aside>
|
|
501
|
+
</main>
|
|
502
|
+
|
|
503
|
+
<script>
|
|
504
|
+
// ===== 替换位:T1 字数驱动(当期 T1 为 N 字 → T1_KEYS 列 N 项 + 各 cover 放 N 个 char div + maxlength=N + DEFAULTS 各含 N 条)=====
|
|
505
|
+
const T1_KEYS = ['char1', 'char2', 'char3', 'char4'];
|
|
506
|
+
const T1_LEN = T1_KEYS.length;
|
|
507
|
+
|
|
508
|
+
// ===== 替换位:当期默认字(skill 按栏目封面风格资产 / 中性默认做的审美决策)——字体下拉首项;与 CSS .t1 的 font-family / font-weight 一致 =====
|
|
509
|
+
const T1_DEFAULT_FONT = { label: '当期默认(skill 注入)', family: '"Noto Sans SC", sans-serif', weight: 900 };
|
|
510
|
+
|
|
511
|
+
// 本机字体候选表(引擎层,勿按栏目改):无 Local Font Access API 时逐个探测是否已安装。常见中文字体,英文名 / 中文名两写法都列,
|
|
512
|
+
// 装了哪种名字就命中哪种。想让某款字体不靠授权也能出现在下拉里,加到这里即可。
|
|
513
|
+
const LOCAL_FONT_CANDIDATES = [
|
|
514
|
+
'Microsoft YaHei', '微软雅黑', 'PingFang SC', '苹方-简', 'Source Han Sans SC', '思源黑体', 'Source Han Sans CN', '思源黑体 CN',
|
|
515
|
+
'Noto Sans SC', 'Noto Sans CJK SC', 'Source Han Serif SC', '思源宋体', 'Source Han Serif CN', 'Noto Serif SC', 'Noto Serif CJK SC',
|
|
516
|
+
'HarmonyOS Sans SC', 'Alibaba PuHuiTi', '阿里巴巴普惠体', 'Alibaba PuHuiTi 3.0', 'MiSans', 'OPPO Sans', 'HONOR Sans CN',
|
|
517
|
+
'Smiley Sans', '得意黑', 'ZCOOL QingKe HuangYou', '站酷庆科黄油体', 'ZCOOL KuaiLe', '站酷快乐体', 'ZCOOL XiaoWei', '站酷小薇体',
|
|
518
|
+
'站酷高端黑', '站酷酷黑', '站酷文艺体', 'LXGW WenKai', '霞鹜文楷', 'Sarasa Gothic SC', '更纱黑体 SC',
|
|
519
|
+
'Alimama ShuHeiTi', '阿里妈妈数黑体', 'Alimama DongFangDaKai', '阿里妈妈东方大楷', 'Alimama FangYuanTi VF', '阿里妈妈方圆体',
|
|
520
|
+
'FZLanTingHei-R-GBK', '方正兰亭黑简体', '方正粗黑宋简体', '方正粗谭黑简体', '方正清刻本悦宋简体', '汉仪旗黑', 'HYQiHei', '汉仪文黑',
|
|
521
|
+
'SimHei', '黑体', 'SimSun', '宋体', 'KaiTi', '楷体', 'FangSong', '仿宋', 'DengXian', '等线', 'Microsoft JhengHei', '微软正黑体',
|
|
522
|
+
'STXihei', '华文细黑', 'STHeiti', '华文黑体', 'STKaiti', '华文楷体', 'STSong', '华文宋体', 'STZhongsong', '华文中宋',
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
// ===== 本机字体:探测 / 读取 / 填下拉(引擎层,勿改) =====
|
|
526
|
+
const _fontProbe = (() => {
|
|
527
|
+
const ctx = document.createElement('canvas').getContext('2d');
|
|
528
|
+
const SAMPLE = '封面工作台Wg字体测试1234';
|
|
529
|
+
const BASES = ['monospace', 'serif', 'sans-serif'];
|
|
530
|
+
const baseW = BASES.map(b => { ctx.font = `72px ${b}`; return ctx.measureText(SAMPLE).width; });
|
|
531
|
+
// 宽度对比法:同一串文本,"候选, 通用族" 与 "通用族" 宽度不同即说明候选已安装并参与了渲染。
|
|
532
|
+
// 不用 document.fonts.check()——它对系统字体恒返回 true,判不了装没装。
|
|
533
|
+
return (family) => BASES.some((b, i) => { ctx.font = `72px "${family}", ${b}`; return Math.abs(ctx.measureText(SAMPLE).width - baseW[i]) > 0.5; });
|
|
534
|
+
})();
|
|
535
|
+
function isCjkName(name) { return /[\u3400-\u9fff]/.test(name) || LOCAL_FONT_CANDIDATES.includes(name); }
|
|
536
|
+
function sortFonts(list) {
|
|
537
|
+
return list.slice().sort((a, b) => {
|
|
538
|
+
const ca = isCjkName(a) ? 0 : 1, cb = isCjkName(b) ? 0 : 1;
|
|
539
|
+
return ca !== cb ? ca - cb : a.localeCompare(b, 'zh-Hans-CN');
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function fillFontSelect(families, keepValue) {
|
|
543
|
+
const sel = document.getElementById('t1-font');
|
|
544
|
+
const current = keepValue ?? sel.value;
|
|
545
|
+
sel.innerHTML = '';
|
|
546
|
+
const def = document.createElement('option');
|
|
547
|
+
def.value = T1_DEFAULT_FONT.family; def.textContent = T1_DEFAULT_FONT.label;
|
|
548
|
+
sel.appendChild(def);
|
|
549
|
+
families.forEach(fam => {
|
|
550
|
+
if (fam === T1_DEFAULT_FONT.family) return;
|
|
551
|
+
const o = document.createElement('option');
|
|
552
|
+
o.value = `"${fam.replace(/"/g, '')}", sans-serif`; o.textContent = fam; o.style.fontFamily = `"${fam}"`;
|
|
553
|
+
sel.appendChild(o);
|
|
554
|
+
});
|
|
555
|
+
if (current) {
|
|
556
|
+
if (![...sel.options].some(o => o.value === current)) {
|
|
557
|
+
const o = document.createElement('option');
|
|
558
|
+
o.value = current; o.textContent = current.replace(/^"([^"]*)".*$/, '$1') || current;
|
|
559
|
+
sel.appendChild(o);
|
|
560
|
+
}
|
|
561
|
+
sel.value = current;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
let _probedFonts = [];
|
|
565
|
+
function probeLocalFonts() {
|
|
566
|
+
_probedFonts = sortFonts([...new Set(LOCAL_FONT_CANDIDATES.filter(_fontProbe))]);
|
|
567
|
+
fillFontSelect(_probedFonts);
|
|
568
|
+
return _probedFonts;
|
|
569
|
+
}
|
|
570
|
+
async function scanLocalFonts() {
|
|
571
|
+
const note = document.getElementById('font-note');
|
|
572
|
+
const btn = document.getElementById('font-scan-btn');
|
|
573
|
+
if (!window.queryLocalFonts) {
|
|
574
|
+
const n = probeLocalFonts().length;
|
|
575
|
+
note.innerHTML = `这个浏览器没有本机字体读取接口(Chrome / Edge 才有)。已按候选表探测到 <strong>${n}</strong> 款已装字体;没列出的在输入框敲名字。`;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
btn.disabled = true; btn.textContent = '读取中…';
|
|
579
|
+
try {
|
|
580
|
+
const list = await window.queryLocalFonts();
|
|
581
|
+
const apiFams = [...new Set(list.map(f => f.family))];
|
|
582
|
+
if (!apiFams.length) throw Object.assign(new Error('empty'), { name: 'EmptyResult' }); // 空结果按失败退化,别把探测结果清掉
|
|
583
|
+
const fams = sortFonts([...new Set([...apiFams, ..._probedFonts])]); // 并集:接口没列到的探测项也留着
|
|
584
|
+
fillFontSelect(fams);
|
|
585
|
+
note.innerHTML = `已读取本机 <strong>${apiFams.length}</strong> 款字体(并入探测结果共 ${fams.length} 款,中文名排前)。导出用同一字体。`;
|
|
586
|
+
btn.textContent = '✓ 已读取本机字体';
|
|
587
|
+
} catch (err) {
|
|
588
|
+
const n = probeLocalFonts().length;
|
|
589
|
+
const why = err && err.name === 'EmptyResult' ? '接口返回空列表(多半是这个浏览器没给授权)' : `授权被拒或读取失败(${err && err.name ? err.name : 'error'})`;
|
|
590
|
+
note.innerHTML = `${why}。保留候选表探测到的 <strong>${n}</strong> 款已装字体;没列出的在输入框敲名字,或换 Chrome / Edge 直开再读。`;
|
|
591
|
+
btn.disabled = false; btn.textContent = '⌕ 读取本机字体(全量,需授权一次)';
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function applyFreeFont() {
|
|
595
|
+
const input = document.getElementById('t1-font-free');
|
|
596
|
+
const fam = input.value.trim().replace(/^["']|["']$/g, '');
|
|
597
|
+
if (!fam) return;
|
|
598
|
+
const sel = document.getElementById('t1-font');
|
|
599
|
+
const value = `"${fam}", sans-serif`;
|
|
600
|
+
if (![...sel.options].some(o => o.value === value)) {
|
|
601
|
+
const o = document.createElement('option'); o.value = value; o.textContent = fam; sel.appendChild(o);
|
|
602
|
+
}
|
|
603
|
+
sel.value = value;
|
|
604
|
+
applyT1Style();
|
|
605
|
+
document.getElementById('font-note').innerHTML = _fontProbe(fam)
|
|
606
|
+
? `已应用「${escapeHtml(fam)}」。`
|
|
607
|
+
: `已应用「${escapeHtml(fam)}」,但探测不到这款字体已安装——画面若没变化,核对一下字体名(以系统字体面板里的名字为准)。`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ===== 替换位:默认布局配方——从 layout-recipes 选配方起步,按实际画面气口微调;注意分发平台安全区 =====
|
|
611
|
+
const DEFAULTS = {
|
|
612
|
+
'16_9': {
|
|
613
|
+
canvas: { w: 2400, h: 1350 },
|
|
614
|
+
layers: {
|
|
615
|
+
char1: { type: 't1', pos: '左上', left: 140, top: 200, size: 520 },
|
|
616
|
+
char2: { type: 't1', pos: '左下', left: 260, top: 720, size: 520 },
|
|
617
|
+
char3: { type: 't1', pos: '右上', left: 1740, top: 200, size: 520 },
|
|
618
|
+
char4: { type: 't1', pos: '右下', left: 1620, top: 720, size: 520 },
|
|
619
|
+
t2: { type: 't2', pos: '底部居中', text: '副标题钩子示例', left: 910, top: 1130, size: 46, visible: true },
|
|
620
|
+
t5: { type: 't5', pos: '顶部居中', text: 'SUBTITLE', left: 1020, top: 80, size: 34, visible: true },
|
|
621
|
+
t4: { type: 't4', pos: '左下角', text: '品牌戳未提供', left: 70, top: 1156, size: 44, visible: false },
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
'4_3': {
|
|
625
|
+
canvas: { w: 2400, h: 1800 },
|
|
626
|
+
layers: {
|
|
627
|
+
char1: { type: 't1', pos: '左上', left: 110, top: 150, size: 560 },
|
|
628
|
+
char2: { type: 't1', pos: '左中', left: 300, top: 700, size: 560 },
|
|
629
|
+
char3: { type: 't1', pos: '右中', left: 1540, top: 540, size: 560 },
|
|
630
|
+
char4: { type: 't1', pos: '右下', left: 1730, top: 1090, size: 560 },
|
|
631
|
+
t2: { type: 't2', pos: '右下角', text: '副标题钩子示例', left: 1680, top: 1680, size: 44, visible: true },
|
|
632
|
+
t5: { type: 't5', pos: '左上角', text: 'SUBTITLE', left: 130, top: 75, size: 36, visible: true },
|
|
633
|
+
t4: { type: 't4', pos: '左下角', text: '品牌戳未提供', left: 150, top: 1700, size: 40, visible: false },
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
'3_4': {
|
|
637
|
+
canvas: { w: 1500, h: 2000 },
|
|
638
|
+
layers: {
|
|
639
|
+
char1: { type: 't1', pos: '左上', left: 180, top: 140, size: 440 },
|
|
640
|
+
char2: { type: 't1', pos: '右上', left: 880, top: 140, size: 440 },
|
|
641
|
+
char3: { type: 't1', pos: '左下', left: 180, top: 1380, size: 440 },
|
|
642
|
+
char4: { type: 't1', pos: '右下', left: 880, top: 1380, size: 440 },
|
|
643
|
+
t2: { type: 't2', pos: '中段留白', text: '副标题钩子示例', left: 475, top: 620, size: 44, visible: true },
|
|
644
|
+
t5: { type: 't5', pos: '底部居中', text: 'SUBTITLE', left: 550, top: 1880, size: 34, visible: true },
|
|
645
|
+
t4: { type: 't4', pos: '左上角', text: '品牌戳未提供', left: 100, top: 40, size: 38, visible: false },
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// 替换位:附加图层组(加图层四步见 workbench-guide §4)
|
|
651
|
+
const SUB_LABELS = { t2: 'T2 副标题', t5: 'T5 英文', t4: 'T4 品牌戳' };
|
|
652
|
+
const SUB_KEYS = ['t2', 't5', 't4'];
|
|
653
|
+
|
|
654
|
+
const state = {
|
|
655
|
+
activeSize: '16_9',
|
|
656
|
+
t1Text: '示例标题', // 替换位:与 #t1-text 的 value 一致
|
|
657
|
+
selectedLayerKey: null,
|
|
658
|
+
data: JSON.parse(JSON.stringify(DEFAULTS)),
|
|
659
|
+
currentScale: 1,
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
function fitStage() {
|
|
663
|
+
const stage = document.getElementById('stage');
|
|
664
|
+
const wrap = document.getElementById('canvasWrap');
|
|
665
|
+
const size = DEFAULTS[state.activeSize].canvas;
|
|
666
|
+
const headH = document.querySelector('header.app-head').offsetHeight;
|
|
667
|
+
const availW = stage.clientWidth - 64;
|
|
668
|
+
const availH = window.innerHeight - headH - 120;
|
|
669
|
+
const scale = Math.min(availW / size.w, availH / size.h, 1);
|
|
670
|
+
state.currentScale = scale;
|
|
671
|
+
wrap.style.transform = `scale(${scale})`;
|
|
672
|
+
wrap.style.width = size.w + 'px';
|
|
673
|
+
wrap.style.height = size.h + 'px';
|
|
674
|
+
// 负 margin 把布局占位收缩到缩放后尺寸(transform 只缩视觉不缩布局,占位不缩会把页面撑出滚动条)
|
|
675
|
+
wrap.style.marginRight = -(size.w * (1 - scale)) + 'px';
|
|
676
|
+
wrap.style.marginBottom = -(size.h * (1 - scale)) + 'px';
|
|
677
|
+
document.getElementById('dimLabel').textContent = `${size.w} × ${size.h}`;
|
|
678
|
+
document.querySelector('.dim-tag .num').textContent = `— ${state.activeSize.replace('_', ':')}`;
|
|
679
|
+
}
|
|
680
|
+
window.addEventListener('resize', fitStage);
|
|
681
|
+
|
|
682
|
+
// ===== 应用图层位置和字号 =====
|
|
683
|
+
function applyLayers() {
|
|
684
|
+
const cover = document.querySelector('.cover.active');
|
|
685
|
+
const layers = state.data[state.activeSize].layers;
|
|
686
|
+
Object.entries(layers).forEach(([key, ls]) => {
|
|
687
|
+
const el = cover.querySelector(`.layer-text[data-layer-key="${key}"]`);
|
|
688
|
+
if (!el) return;
|
|
689
|
+
el.style.left = ls.left + 'px';
|
|
690
|
+
el.style.top = ls.top + 'px';
|
|
691
|
+
el.style.fontSize = ls.size + 'px';
|
|
692
|
+
// 非 T1 图层的 text 和 visible(对任意附加图层通用)
|
|
693
|
+
if (ls.type !== 't1') {
|
|
694
|
+
document.querySelectorAll(`.cover .layer-text[data-layer-key="${key}"]`).forEach(n => {
|
|
695
|
+
n.textContent = ls.text;
|
|
696
|
+
n.style.display = ls.visible ? 'block' : 'none';
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function applyT1Text() {
|
|
703
|
+
const raw = document.getElementById('t1-text').value;
|
|
704
|
+
const text = (raw + ' '.repeat(T1_LEN)).slice(0, T1_LEN);
|
|
705
|
+
state.t1Text = text;
|
|
706
|
+
document.querySelectorAll('.cover').forEach(cover => {
|
|
707
|
+
T1_KEYS.forEach((k, i) => {
|
|
708
|
+
const el = cover.querySelector(`.layer-text[data-layer-key="${k}"]`);
|
|
709
|
+
if (el) el.textContent = text.charAt(i);
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
document.querySelectorAll('.char-block .char-preview').forEach((el, i) => {
|
|
713
|
+
el.textContent = text.charAt(i);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function applyT1Style() {
|
|
718
|
+
const stroke = parseFloat(document.getElementById('t1-stroke').value) || 0;
|
|
719
|
+
const glow = document.getElementById('t1-glow').value / 100;
|
|
720
|
+
const color = document.getElementById('t1-color').value;
|
|
721
|
+
const family = document.getElementById('t1-font').value || T1_DEFAULT_FONT.family;
|
|
722
|
+
const weight = document.getElementById('t1-weight').value || String(T1_DEFAULT_FONT.weight);
|
|
723
|
+
document.getElementById('t1-stroke-val').textContent = stroke;
|
|
724
|
+
document.getElementById('t1-glow-val').textContent = (glow * 100 | 0) + '%';
|
|
725
|
+
document.querySelectorAll('.t1').forEach(el => {
|
|
726
|
+
el.style.fontFamily = family;
|
|
727
|
+
el.style.fontWeight = weight;
|
|
728
|
+
el.style.webkitTextStroke = stroke > 0 ? `${stroke}px var(--midnight)` : '0 transparent';
|
|
729
|
+
el.style.color = color;
|
|
730
|
+
// 替换位:发光 rgba 与 CSS .t1 的 text-shadow 保持同色(前两层暗晕是托底,不随栏目变)
|
|
731
|
+
el.style.textShadow = `
|
|
732
|
+
0 0 6px rgba(20,22,30,0.9),
|
|
733
|
+
0 0 14px rgba(20,22,30,0.8),
|
|
734
|
+
0 0 20px rgba(200,210,230,${glow * 0.7}),
|
|
735
|
+
0 0 45px rgba(200,210,230,${glow * 0.5}),
|
|
736
|
+
0 10px 28px rgba(0,0,0,0.6)
|
|
737
|
+
`;
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function applyT1SizeAll() {
|
|
742
|
+
const size = parseInt(document.getElementById('t1-size-all').value);
|
|
743
|
+
document.getElementById('t1-size-all-val').textContent = size;
|
|
744
|
+
T1_KEYS.forEach(k => {
|
|
745
|
+
state.data[state.activeSize].layers[k].size = size;
|
|
746
|
+
});
|
|
747
|
+
applyLayers();
|
|
748
|
+
renderCharBlocks();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function applyCorner() {
|
|
752
|
+
const show = document.getElementById('corner-show').checked;
|
|
753
|
+
document.querySelectorAll('.cover.active .corner').forEach(c => {
|
|
754
|
+
c.style.display = show ? 'block' : 'none';
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function applyInner() {
|
|
759
|
+
const show = document.getElementById('inner-show').checked;
|
|
760
|
+
document.querySelectorAll('.cover.active .inner-frame').forEach(c => {
|
|
761
|
+
c.style.display = show ? 'block' : 'none';
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// ===== 右栏 T1 逐字 block =====
|
|
766
|
+
function renderCharBlocks() {
|
|
767
|
+
const container = document.getElementById('char-blocks');
|
|
768
|
+
const layers = state.data[state.activeSize].layers;
|
|
769
|
+
container.innerHTML = '';
|
|
770
|
+
T1_KEYS.forEach((k, i) => {
|
|
771
|
+
const ls = layers[k];
|
|
772
|
+
const block = document.createElement('div');
|
|
773
|
+
block.className = 'layer-block';
|
|
774
|
+
block.dataset.layerKey = k;
|
|
775
|
+
if (state.selectedLayerKey === k) block.classList.add('selected');
|
|
776
|
+
block.innerHTML = `
|
|
777
|
+
<div class="head">
|
|
778
|
+
<span class="char-preview">${state.t1Text.charAt(i)}</span>
|
|
779
|
+
<span class="pos-tag">${ls.pos}</span>
|
|
780
|
+
</div>
|
|
781
|
+
<div class="mini-row">
|
|
782
|
+
<label>X</label>
|
|
783
|
+
<input type="number" data-field="left" value="${Math.round(ls.left)}" step="5">
|
|
784
|
+
<label>Y</label>
|
|
785
|
+
<input type="number" data-field="top" value="${Math.round(ls.top)}" step="5">
|
|
786
|
+
</div>
|
|
787
|
+
<div class="mini-row">
|
|
788
|
+
<label>号</label>
|
|
789
|
+
<input type="number" data-field="size" value="${ls.size}" step="10" min="100" max="900">
|
|
790
|
+
</div>
|
|
791
|
+
`;
|
|
792
|
+
bindLayerBlock(block, ls, k);
|
|
793
|
+
container.appendChild(block);
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// ===== 右栏附加图层 block =====
|
|
798
|
+
function renderSubBlocks() {
|
|
799
|
+
const container = document.getElementById('sub-blocks');
|
|
800
|
+
const layers = state.data[state.activeSize].layers;
|
|
801
|
+
container.innerHTML = '';
|
|
802
|
+
SUB_KEYS.forEach(k => {
|
|
803
|
+
const ls = layers[k];
|
|
804
|
+
const block = document.createElement('div');
|
|
805
|
+
block.className = 'layer-block';
|
|
806
|
+
block.dataset.layerKey = k;
|
|
807
|
+
if (state.selectedLayerKey === k) block.classList.add('selected');
|
|
808
|
+
const labelText = SUB_LABELS[k];
|
|
809
|
+
block.innerHTML = `
|
|
810
|
+
<div class="head">
|
|
811
|
+
<span class="tag-preview">${labelText}</span>
|
|
812
|
+
<span class="pos-tag">${ls.pos}</span>
|
|
813
|
+
<label class="vis-toggle" title="显示 / 隐藏">
|
|
814
|
+
<input type="checkbox" data-field="visible" ${ls.visible ? 'checked' : ''}> 显示
|
|
815
|
+
</label>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="mini-row">
|
|
818
|
+
<input type="text" data-field="text" value="${escapeHtml(ls.text)}">
|
|
819
|
+
</div>
|
|
820
|
+
<div class="mini-row">
|
|
821
|
+
<label>X</label>
|
|
822
|
+
<input type="number" data-field="left" value="${Math.round(ls.left)}" step="5">
|
|
823
|
+
<label>Y</label>
|
|
824
|
+
<input type="number" data-field="top" value="${Math.round(ls.top)}" step="5">
|
|
825
|
+
</div>
|
|
826
|
+
<div class="mini-row">
|
|
827
|
+
<label>号</label>
|
|
828
|
+
<input type="number" data-field="size" value="${ls.size}" step="2" min="14" max="200">
|
|
829
|
+
</div>
|
|
830
|
+
`;
|
|
831
|
+
bindLayerBlock(block, ls, k);
|
|
832
|
+
container.appendChild(block);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function bindLayerBlock(block, ls, key) {
|
|
837
|
+
block.addEventListener('click', (e) => {
|
|
838
|
+
if (e.target.tagName === 'INPUT') return;
|
|
839
|
+
selectLayer(key);
|
|
840
|
+
});
|
|
841
|
+
block.querySelectorAll('input').forEach(input => {
|
|
842
|
+
input.addEventListener('click', (e) => e.stopPropagation());
|
|
843
|
+
input.addEventListener('focus', () => selectLayer(key));
|
|
844
|
+
if (input.type === 'number') {
|
|
845
|
+
input.addEventListener('wheel', (e) => {
|
|
846
|
+
if (document.activeElement !== input) return;
|
|
847
|
+
e.preventDefault();
|
|
848
|
+
const step = (parseFloat(input.step) || 1) * (e.shiftKey ? 10 : 1);
|
|
849
|
+
const dir = e.deltaY < 0 ? 1 : -1;
|
|
850
|
+
input.value = String((parseFloat(input.value) || 0) + dir * step);
|
|
851
|
+
input.dispatchEvent(new Event('input'));
|
|
852
|
+
}, { passive: false });
|
|
853
|
+
}
|
|
854
|
+
input.addEventListener('input', (e) => {
|
|
855
|
+
const field = e.target.dataset.field;
|
|
856
|
+
if (field === 'visible') {
|
|
857
|
+
ls.visible = e.target.checked;
|
|
858
|
+
} else if (field === 'text') {
|
|
859
|
+
ls.text = e.target.value;
|
|
860
|
+
} else {
|
|
861
|
+
const val = parseFloat(e.target.value);
|
|
862
|
+
if (!isNaN(val)) ls[field] = val;
|
|
863
|
+
}
|
|
864
|
+
applyLayers();
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function escapeHtml(s) {
|
|
870
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function selectLayer(key) {
|
|
874
|
+
state.selectedLayerKey = key;
|
|
875
|
+
document.querySelectorAll('.layer-text').forEach(el => el.classList.remove('selected'));
|
|
876
|
+
document.querySelectorAll('.layer-block').forEach(el => el.classList.remove('selected'));
|
|
877
|
+
if (key) {
|
|
878
|
+
const el = document.querySelector(`.cover.active .layer-text[data-layer-key="${key}"]`);
|
|
879
|
+
if (el) el.classList.add('selected');
|
|
880
|
+
const block = document.querySelector(`.layer-block[data-layer-key="${key}"]`);
|
|
881
|
+
if (block) block.classList.add('selected');
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// ===== 拖拽(引擎层,勿改) =====
|
|
886
|
+
let dragState = null;
|
|
887
|
+
document.addEventListener('mousedown', (e) => {
|
|
888
|
+
const layer = e.target.closest('.layer-text');
|
|
889
|
+
if (!layer || !layer.closest('.cover.active')) return;
|
|
890
|
+
const key = layer.dataset.layerKey;
|
|
891
|
+
selectLayer(key);
|
|
892
|
+
const ls = state.data[state.activeSize].layers[key];
|
|
893
|
+
dragState = {
|
|
894
|
+
key, startX: e.clientX, startY: e.clientY,
|
|
895
|
+
startLeft: ls.left, startTop: ls.top,
|
|
896
|
+
};
|
|
897
|
+
layer.classList.add('dragging');
|
|
898
|
+
e.preventDefault();
|
|
899
|
+
});
|
|
900
|
+
document.addEventListener('mousemove', (e) => {
|
|
901
|
+
if (!dragState) return;
|
|
902
|
+
const dx = (e.clientX - dragState.startX) / state.currentScale;
|
|
903
|
+
const dy = (e.clientY - dragState.startY) / state.currentScale;
|
|
904
|
+
const ls = state.data[state.activeSize].layers[dragState.key];
|
|
905
|
+
ls.left = dragState.startLeft + dx;
|
|
906
|
+
ls.top = dragState.startTop + dy;
|
|
907
|
+
applyLayers();
|
|
908
|
+
const block = document.querySelector(`.layer-block[data-layer-key="${dragState.key}"]`);
|
|
909
|
+
if (block) {
|
|
910
|
+
const lInput = block.querySelector('input[data-field="left"]');
|
|
911
|
+
const tInput = block.querySelector('input[data-field="top"]');
|
|
912
|
+
if (lInput) lInput.value = Math.round(ls.left);
|
|
913
|
+
if (tInput) tInput.value = Math.round(ls.top);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
document.addEventListener('mouseup', () => {
|
|
917
|
+
if (dragState) {
|
|
918
|
+
document.querySelectorAll('.layer-text.dragging').forEach(el => el.classList.remove('dragging'));
|
|
919
|
+
dragState = null;
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// ===== 方向键(引擎层,勿改) =====
|
|
924
|
+
document.addEventListener('keydown', (e) => {
|
|
925
|
+
if (!state.selectedLayerKey) return;
|
|
926
|
+
const tag = document.activeElement?.tagName;
|
|
927
|
+
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) return;
|
|
928
|
+
const step = e.shiftKey ? 10 : 1;
|
|
929
|
+
const ls = state.data[state.activeSize].layers[state.selectedLayerKey];
|
|
930
|
+
if (!ls) return;
|
|
931
|
+
let handled = true;
|
|
932
|
+
if (e.key === 'ArrowLeft') ls.left -= step;
|
|
933
|
+
else if (e.key === 'ArrowRight') ls.left += step;
|
|
934
|
+
else if (e.key === 'ArrowUp') ls.top -= step;
|
|
935
|
+
else if (e.key === 'ArrowDown') ls.top += step;
|
|
936
|
+
else handled = false;
|
|
937
|
+
if (handled) {
|
|
938
|
+
e.preventDefault();
|
|
939
|
+
applyLayers();
|
|
940
|
+
renderCharBlocks();
|
|
941
|
+
renderSubBlocks();
|
|
942
|
+
selectLayer(state.selectedLayerKey);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// ===== tab 切换 =====
|
|
947
|
+
document.querySelectorAll('.tab').forEach(tab => {
|
|
948
|
+
tab.addEventListener('click', () => {
|
|
949
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
950
|
+
document.querySelectorAll('.cover').forEach(c => c.classList.remove('active'));
|
|
951
|
+
tab.classList.add('active');
|
|
952
|
+
state.activeSize = tab.dataset.size;
|
|
953
|
+
document.querySelector(`.cover[data-size="${state.activeSize}"]`).classList.add('active');
|
|
954
|
+
state.selectedLayerKey = null;
|
|
955
|
+
const defSize = DEFAULTS[state.activeSize].layers.char1.size;
|
|
956
|
+
document.getElementById('t1-size-all').value = defSize;
|
|
957
|
+
document.getElementById('t1-size-all-val').textContent = defSize;
|
|
958
|
+
fitStage();
|
|
959
|
+
applyLayers();
|
|
960
|
+
applyT1Style();
|
|
961
|
+
applyCorner();
|
|
962
|
+
applyInner();
|
|
963
|
+
renderCharBlocks();
|
|
964
|
+
renderSubBlocks();
|
|
965
|
+
});
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
document.getElementById('t1-text').addEventListener('input', applyT1Text);
|
|
969
|
+
['t1-stroke', 't1-glow', 't1-color', 't1-font', 't1-weight'].forEach(id =>
|
|
970
|
+
document.getElementById(id).addEventListener('input', applyT1Style));
|
|
971
|
+
document.getElementById('font-scan-btn').addEventListener('click', scanLocalFonts);
|
|
972
|
+
document.getElementById('t1-font-free').addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); applyFreeFont(); } });
|
|
973
|
+
document.getElementById('t1-size-all').addEventListener('input', applyT1SizeAll);
|
|
974
|
+
document.getElementById('corner-show').addEventListener('input', applyCorner);
|
|
975
|
+
document.getElementById('inner-show').addEventListener('input', applyInner);
|
|
976
|
+
|
|
977
|
+
document.getElementById('resetAllBtn').addEventListener('click', () => {
|
|
978
|
+
state.data[state.activeSize] = JSON.parse(JSON.stringify(DEFAULTS[state.activeSize]));
|
|
979
|
+
state.selectedLayerKey = null;
|
|
980
|
+
applyLayers();
|
|
981
|
+
renderCharBlocks();
|
|
982
|
+
renderSubBlocks();
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
document.getElementById('bg-upload').addEventListener('change', e => {
|
|
986
|
+
const file = e.target.files[0];
|
|
987
|
+
if (!file) return;
|
|
988
|
+
const reader = new FileReader();
|
|
989
|
+
reader.onload = ev => {
|
|
990
|
+
document.querySelector('.cover.active .bg').src = ev.target.result;
|
|
991
|
+
};
|
|
992
|
+
reader.readAsDataURL(file);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
// ===== 导出(引擎层,勿改) =====
|
|
996
|
+
document.getElementById('exportBtn').addEventListener('click', async () => {
|
|
997
|
+
const btn = document.getElementById('exportBtn');
|
|
998
|
+
btn.disabled = true;
|
|
999
|
+
btn.textContent = '正在渲染…';
|
|
1000
|
+
const cover = document.querySelector('.cover.active');
|
|
1001
|
+
const wrap = document.getElementById('canvasWrap');
|
|
1002
|
+
const originalTransform = wrap.style.transform;
|
|
1003
|
+
wrap.style.transform = 'scale(1)';
|
|
1004
|
+
document.querySelectorAll('.layer-text').forEach(el => el.classList.remove('selected', 'dragging'));
|
|
1005
|
+
try {
|
|
1006
|
+
const canvas = await html2canvas(cover, {
|
|
1007
|
+
backgroundColor: null, scale: 1, useCORS: true, logging: false,
|
|
1008
|
+
width: cover.offsetWidth, height: cover.offsetHeight,
|
|
1009
|
+
});
|
|
1010
|
+
const link = document.createElement('a');
|
|
1011
|
+
const txt = state.t1Text.replace(/ /g, '').trim() || '封面';
|
|
1012
|
+
link.download = `${txt}_${state.activeSize.replace('_', 'x')}.png`;
|
|
1013
|
+
link.href = canvas.toDataURL('image/png');
|
|
1014
|
+
link.click();
|
|
1015
|
+
btn.textContent = '✓ 导出成功';
|
|
1016
|
+
setTimeout(() => { btn.textContent = '导出当前封面 PNG'; btn.disabled = false; }, 1500);
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
console.error(err);
|
|
1019
|
+
btn.textContent = '导出失败,看控制台';
|
|
1020
|
+
btn.disabled = false;
|
|
1021
|
+
} finally {
|
|
1022
|
+
wrap.style.transform = originalTransform;
|
|
1023
|
+
if (state.selectedLayerKey) selectLayer(state.selectedLayerKey);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// ===== 一键导出全部三张(引擎层,勿改) =====
|
|
1028
|
+
document.getElementById('exportAllBtn').addEventListener('click', async () => {
|
|
1029
|
+
const btn = document.getElementById('exportAllBtn');
|
|
1030
|
+
const originalSize = state.activeSize;
|
|
1031
|
+
btn.disabled = true;
|
|
1032
|
+
const wrap = document.getElementById('canvasWrap');
|
|
1033
|
+
const originalTransform = wrap.style.transform;
|
|
1034
|
+
wrap.style.transform = 'scale(1)';
|
|
1035
|
+
document.querySelectorAll('.layer-text').forEach(el => el.classList.remove('selected', 'dragging'));
|
|
1036
|
+
try {
|
|
1037
|
+
for (const size of ['16_9', '4_3', '3_4']) {
|
|
1038
|
+
btn.textContent = `正在渲染 ${size.replace('_', ':')} …`;
|
|
1039
|
+
document.querySelectorAll('.cover').forEach(c => c.classList.remove('active'));
|
|
1040
|
+
const cover = document.querySelector(`.cover[data-size="${size}"]`);
|
|
1041
|
+
cover.classList.add('active');
|
|
1042
|
+
state.activeSize = size;
|
|
1043
|
+
applyLayers();
|
|
1044
|
+
applyCorner();
|
|
1045
|
+
applyInner();
|
|
1046
|
+
await new Promise(r => setTimeout(r, 80));
|
|
1047
|
+
const canvas = await html2canvas(cover, {
|
|
1048
|
+
backgroundColor: null, scale: 1, useCORS: true, logging: false,
|
|
1049
|
+
width: cover.offsetWidth, height: cover.offsetHeight,
|
|
1050
|
+
});
|
|
1051
|
+
const link = document.createElement('a');
|
|
1052
|
+
const txt = state.t1Text.replace(/ /g, '').trim() || '封面';
|
|
1053
|
+
link.download = `${txt}_${size.replace('_', 'x')}.png`;
|
|
1054
|
+
link.href = canvas.toDataURL('image/png');
|
|
1055
|
+
link.click();
|
|
1056
|
+
await new Promise(r => setTimeout(r, 400));
|
|
1057
|
+
}
|
|
1058
|
+
btn.textContent = '✓ 三张已导出';
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
console.error(err);
|
|
1061
|
+
btn.textContent = '导出失败,看控制台';
|
|
1062
|
+
} finally {
|
|
1063
|
+
wrap.style.transform = originalTransform;
|
|
1064
|
+
const backTab = document.querySelector(`.tab[data-size="${originalSize}"]`);
|
|
1065
|
+
if (backTab) backTab.click();
|
|
1066
|
+
setTimeout(() => { btn.textContent = '一键导出全部三张'; btn.disabled = false; }, 1800);
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
function init() {
|
|
1071
|
+
fitStage();
|
|
1072
|
+
applyT1Text();
|
|
1073
|
+
applyLayers();
|
|
1074
|
+
document.getElementById('t1-weight').value = String(T1_DEFAULT_FONT.weight);
|
|
1075
|
+
probeLocalFonts(); // 打开即探测候选表(无需授权);全量本机字体点按钮读
|
|
1076
|
+
applyT1Style();
|
|
1077
|
+
applyCorner();
|
|
1078
|
+
applyInner();
|
|
1079
|
+
renderCharBlocks();
|
|
1080
|
+
renderSubBlocks();
|
|
1081
|
+
}
|
|
1082
|
+
document.fonts.ready.then(init);
|
|
1083
|
+
setTimeout(fitStage, 100);
|
|
1084
|
+
setTimeout(fitStage, 600);
|
|
1085
|
+
</script>
|
|
1086
|
+
|
|
1087
|
+
</body>
|
|
1088
|
+
</html>
|