@taloswork/ose-shared 0.0.4 → 0.0.5
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/mcp/.gitkeep +1 -1
- package/mcp/gen/mcp.json +2 -2
- package/package.json +3 -3
- package/skills/.gitkeep +1 -1
- package/skills/document/SKILL.md +7 -1
- package/skills/document/bin/deck-build.cjs +257 -23
- package/skills/document/bin/deck-qa.cjs +0 -0
- package/skills/document/bin/doc-convert.cjs +0 -0
- package/skills/document/bin/doc-fill.cjs +0 -0
- package/skills/document/bin/doc-gen.cjs +17 -1
- package/skills/document/bin/doc-run.cjs +0 -0
- package/skills/document/bin/doc-scan.cjs +131 -18
- package/skills/document/bin/doc-text.cjs +146 -10
- package/skills/document/bin/doc-validate.cjs +0 -0
- package/skills/document/bin/doc-xml.cjs +0 -0
- package/skills/document/bin/docx-annotate.cjs +0 -0
- package/skills/document/bin/html2pptx.cjs +17 -1
- package/skills/document/bin/ocr-worker.cjs +0 -0
- package/skills/document/bin/ocr.cjs +0 -0
- package/skills/document/bin/pdf-form.cjs +0 -0
- package/skills/document/bin/pdf-images.cjs +0 -0
- package/skills/document/bin/pdf-ops.cjs +0 -0
- package/skills/document/bin/pdf-render.cjs +0 -0
- package/skills/document/bin/pdf-tables.cjs +0 -0
- package/skills/document/bin/pptx-clone.cjs +0 -0
- package/skills/document/bin/pptx-embed-fonts.cjs +0 -0
- package/skills/document/bin/pptx-set-image.cjs +0 -0
- package/skills/document/bin/pptx-slide.cjs +0 -0
- package/skills/document/bin/pptx-theme.cjs +0 -0
- package/skills/document/bin/pptx-tokenize.cjs +0 -0
- package/skills/document/bin/xlsx-query.cjs +0 -0
- package/skills/document/bin/xlsx-recalc.cjs +0 -0
- package/skills/document/deck-design.md +5 -1
- package/skills/document/layouts/README.md +43 -10
- package/skills/document/layouts/brand-values.tpl +6 -8
- package/skills/document/layouts/color-palette.tpl +6 -10
- package/skills/document/layouts/gantt.tpl +4 -4
- package/skills/document/layouts/logo-guidelines.tpl +8 -8
- package/skills/document/layouts/map-distribution.tpl +5 -5
- package/skills/document/layouts/map-quad.tpl +7 -7
- package/skills/document/layouts/radar.tpl +4 -4
- package/skills/document/layouts/ring-progress.tpl +4 -4
- package/skills/document/layouts/slots.json +1 -763
- package/skills/document/layouts/steps-vertical.tpl +4 -4
- package/skills/document/layouts/team-row.tpl +5 -11
- package/skills/document/layouts/toc-panel.tpl +4 -7
- package/skills/document/layouts/type-specimen.tpl +9 -9
- package/skills/document/styles/anthropic/skin.css +1 -4
- package/skills/document/styles/apple/skin.css +1 -4
- package/skills/document/styles/black-gold/skin.css +1 -4
- package/skills/document/styles/branding/skin.css +1 -4
- package/skills/document/styles/cinereel/skin.css +1 -4
- package/skills/document/styles/cool/skin.css +1 -4
- package/skills/document/styles/hand-drawn/skin.css +1 -4
- package/skills/document/styles/ivory-gold/skin.css +1 -4
- package/skills/document/styles/magazine-ink/skin.css +0 -3
- package/skills/document/styles/mckinsey/skin.css +1 -4
- package/skills/document/styles/mono/skin.css +1 -4
- package/skills/document/styles/olive-gold/skin.css +1 -4
- package/skills/document/styles/swiss-grid/skin.css +0 -3
|
@@ -17,6 +17,19 @@ function loadMupdf() {
|
|
|
17
17
|
}
|
|
18
18
|
return _mupdf;
|
|
19
19
|
}
|
|
20
|
+
var MATH_CHARS = /[=∑∫√≤≥≠±∞πθλμσΩ×÷∂∇→←↔∈∉⊂⊃∀∃]/g;
|
|
21
|
+
function textMathHint(text) {
|
|
22
|
+
if (!text)
|
|
23
|
+
return false;
|
|
24
|
+
const hits = (text.match(MATH_CHARS) || []).length;
|
|
25
|
+
return hits >= 3 || hits / Math.max(text.length, 1) > 0.02;
|
|
26
|
+
}
|
|
27
|
+
function textTableHint(text) {
|
|
28
|
+
if (!text)
|
|
29
|
+
return false;
|
|
30
|
+
const gaps = (text.match(/ {2,}|\t/g) || []).length;
|
|
31
|
+
return gaps >= 6;
|
|
32
|
+
}
|
|
20
33
|
async function openDoc(bytes) {
|
|
21
34
|
const mupdf = await loadMupdf();
|
|
22
35
|
const doc = mupdf.Document.openDocument(bytes, "application/pdf");
|
|
@@ -25,17 +38,140 @@ async function openDoc(bytes) {
|
|
|
25
38
|
}
|
|
26
39
|
return { mupdf, doc };
|
|
27
40
|
}
|
|
28
|
-
async function
|
|
41
|
+
async function analyzePdfDocument(bytes, options = {}) {
|
|
42
|
+
const wantText = options.text !== false;
|
|
43
|
+
const wantSignals = options.signals === true;
|
|
29
44
|
const { doc } = await openDoc(bytes);
|
|
30
|
-
const
|
|
45
|
+
const pageCount = doc.countPages();
|
|
46
|
+
const signals = [];
|
|
31
47
|
let out = "";
|
|
32
|
-
for (let i = 0; i <
|
|
33
|
-
const
|
|
34
|
-
|
|
48
|
+
for (let i = 0; i < pageCount; i++) {
|
|
49
|
+
const page = doc.loadPage(i);
|
|
50
|
+
if (wantText) {
|
|
51
|
+
out += `
|
|
35
52
|
[page ${i + 1}]
|
|
36
|
-
${
|
|
53
|
+
${page.toStructuredText().asText()}`;
|
|
54
|
+
}
|
|
55
|
+
if (wantSignals) {
|
|
56
|
+
signals.push(pageSignalsFrom(page, i + 1));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { text: wantText ? out.trim() : "", pageCount, signals };
|
|
60
|
+
}
|
|
61
|
+
async function extractPdfText(bytes) {
|
|
62
|
+
const { text, pageCount } = await analyzePdfDocument(bytes, { text: true });
|
|
63
|
+
return { text, pageCount };
|
|
64
|
+
}
|
|
65
|
+
function countVectorOperators(content) {
|
|
66
|
+
let ops = 0;
|
|
67
|
+
let curves = 0;
|
|
68
|
+
let fills = 0;
|
|
69
|
+
let i = 0;
|
|
70
|
+
const n = content.length;
|
|
71
|
+
while (i < n) {
|
|
72
|
+
const ch = content[i];
|
|
73
|
+
if (ch === "(") {
|
|
74
|
+
let depth = 1;
|
|
75
|
+
i++;
|
|
76
|
+
while (i < n && depth > 0) {
|
|
77
|
+
if (content[i] === "\\")
|
|
78
|
+
i++;
|
|
79
|
+
else if (content[i] === "(")
|
|
80
|
+
depth++;
|
|
81
|
+
else if (content[i] === ")")
|
|
82
|
+
depth--;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === "<" && content[i + 1] !== "<") {
|
|
88
|
+
while (i < n && content[i] !== ">")
|
|
89
|
+
i++;
|
|
90
|
+
i++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (ch === "I" && content[i + 1] === "D" && isDelimiter(content[i + 2])) {
|
|
94
|
+
const end = content.indexOf("EI", i + 2);
|
|
95
|
+
i = end === -1 ? n : end + 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isDelimiter(ch)) {
|
|
99
|
+
const start = i;
|
|
100
|
+
while (i < n && !isDelimiter(content[i]))
|
|
101
|
+
i++;
|
|
102
|
+
const token = content.slice(start, i);
|
|
103
|
+
switch (token) {
|
|
104
|
+
case "m":
|
|
105
|
+
case "l":
|
|
106
|
+
case "re":
|
|
107
|
+
ops++;
|
|
108
|
+
break;
|
|
109
|
+
case "c":
|
|
110
|
+
case "v":
|
|
111
|
+
case "y":
|
|
112
|
+
ops++;
|
|
113
|
+
curves++;
|
|
114
|
+
break;
|
|
115
|
+
case "f":
|
|
116
|
+
case "F":
|
|
117
|
+
case "f*":
|
|
118
|
+
fills++;
|
|
119
|
+
break;
|
|
120
|
+
case "B":
|
|
121
|
+
case "B*":
|
|
122
|
+
case "b":
|
|
123
|
+
case "b*":
|
|
124
|
+
fills++;
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
return { ops, curves, fills };
|
|
134
|
+
}
|
|
135
|
+
function isDelimiter(ch) {
|
|
136
|
+
return ch === void 0 || ch === " " || ch === "\n" || ch === "\r" || ch === " " || ch === "\f" || ch === "\0" || ch === "[" || ch === "]" || ch === "/" || ch === "{" || ch === "}";
|
|
137
|
+
}
|
|
138
|
+
function pageContentStream(page) {
|
|
139
|
+
try {
|
|
140
|
+
const obj = page.getObject?.();
|
|
141
|
+
const contents = obj?.get?.("Contents");
|
|
142
|
+
if (!contents)
|
|
143
|
+
return "";
|
|
144
|
+
const streams = contents.isArray?.() && contents.get ? Array.from({ length: contents.length ?? 0 }, (_, i) => contents.get(i)) : [contents];
|
|
145
|
+
let out = "";
|
|
146
|
+
for (const stream of streams) {
|
|
147
|
+
const buf = stream?.readStream?.();
|
|
148
|
+
const bytes = buf?.asUint8Array?.();
|
|
149
|
+
if (bytes)
|
|
150
|
+
out += Buffer.from(bytes).toString("latin1");
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
} catch {
|
|
154
|
+
return "";
|
|
37
155
|
}
|
|
38
|
-
|
|
156
|
+
}
|
|
157
|
+
function pageSignalsFrom(page, pageNumber) {
|
|
158
|
+
const json = JSON.parse(page.toStructuredText("preserve-images").asJSON());
|
|
159
|
+
const blocks = json.blocks ?? [];
|
|
160
|
+
const text = blocks.flatMap((b) => (b.lines ?? []).map((l) => l.text ?? "")).join(" ");
|
|
161
|
+
const [x0, y0, x1, y1] = page.getBounds();
|
|
162
|
+
const pageArea = Math.max((x1 - x0) * (y1 - y0), 1);
|
|
163
|
+
const imgArea = blocks.filter((b) => b.type === "image").reduce((a, b) => a + Math.max((b.bbox?.w ?? 0) * (b.bbox?.h ?? 0), 0), 0);
|
|
164
|
+
const vector = countVectorOperators(pageContentStream(page));
|
|
165
|
+
return {
|
|
166
|
+
page: pageNumber,
|
|
167
|
+
textChars: text.trim().length,
|
|
168
|
+
imageCoverage: Math.min(imgArea / pageArea, 1),
|
|
169
|
+
vectorOps: vector.ops,
|
|
170
|
+
vectorCurves: vector.curves,
|
|
171
|
+
vectorFills: vector.fills,
|
|
172
|
+
mathHint: textMathHint(text),
|
|
173
|
+
tableHint: textTableHint(text)
|
|
174
|
+
};
|
|
39
175
|
}
|
|
40
176
|
|
|
41
177
|
// apps/admin/data/skills.lib/document/bin-src/doc-text.mjs
|
|
@@ -62,12 +198,12 @@ async function main() {
|
|
|
62
198
|
process.stdout.write(raw.toString("utf-8"));
|
|
63
199
|
return;
|
|
64
200
|
}
|
|
65
|
-
let
|
|
201
|
+
let extraction;
|
|
66
202
|
try {
|
|
67
|
-
|
|
203
|
+
extraction = await extractPdfText(new Uint8Array(raw));
|
|
68
204
|
} catch (e) {
|
|
69
205
|
die(`extraction failed: ${e.message}`);
|
|
70
206
|
}
|
|
71
|
-
process.stdout.write(text ?? "");
|
|
207
|
+
process.stdout.write(extraction?.text ?? "");
|
|
72
208
|
}
|
|
73
209
|
main();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -147501,13 +147501,26 @@ async function convertLayoutToPptx(snapshot) {
|
|
|
147501
147501
|
warnings.push(`${where} (chart): needs labels + a series with values \u2014 dropped`);
|
|
147502
147502
|
break;
|
|
147503
147503
|
}
|
|
147504
|
+
const points = series.map((s, si) => {
|
|
147505
|
+
const name = s.name ?? "";
|
|
147506
|
+
const raw = s.values;
|
|
147507
|
+
const at = `${where} (chart): series ${si + 1}${name ? ` "${name}"` : ""}`;
|
|
147508
|
+
if (raw.length !== labels.length) {
|
|
147509
|
+
warnings.push(`${at} has ${raw.length} value(s) for ${labels.length} label(s) \u2014 ${raw.length < labels.length ? "the trailing categories will be blank" : "the extra values are ignored"}`);
|
|
147510
|
+
}
|
|
147511
|
+
const values = raw.map(Number);
|
|
147512
|
+
const bad = values.filter((v) => !Number.isFinite(v)).length;
|
|
147513
|
+
if (bad)
|
|
147514
|
+
warnings.push(`${at} has ${bad} non-numeric value(s) \u2014 plotted as 0`);
|
|
147515
|
+
return { name, labels, values: values.map((v) => Number.isFinite(v) ? v : 0) };
|
|
147516
|
+
});
|
|
147504
147517
|
const horizontal = spec.type === "barH" || spec.horizontal === true;
|
|
147505
147518
|
const rawType = spec.type === "barH" ? "bar" : spec.type;
|
|
147506
147519
|
const type = ["bar", "line", "pie", "doughnut", "area", "radar", "scatter"].includes(rawType) ? rawType : "bar";
|
|
147507
147520
|
const ramp = (Array.isArray(spec.colors) && spec.colors.length ? spec.colors : DEFAULT_CHART_RAMP).map((c) => cssColorToHex(c) ?? String(c).replace("#", "")).filter(Boolean);
|
|
147508
147521
|
const axis = cssColorToHex(spec.axisColor) ?? "44474F";
|
|
147509
147522
|
const multi = series.length > 1;
|
|
147510
|
-
slide.addChart(type,
|
|
147523
|
+
slide.addChart(type, points, {
|
|
147511
147524
|
x: frame.x,
|
|
147512
147525
|
y: frame.y,
|
|
147513
147526
|
w: frame.w,
|
|
@@ -147521,6 +147534,9 @@ async function convertLayoutToPptx(snapshot) {
|
|
|
147521
147534
|
valAxisLineColor: "C9D0DA",
|
|
147522
147535
|
valGridLine: { color: "E2E8F0", style: "dash", size: 0.5 },
|
|
147523
147536
|
...horizontal ? { barDir: "bar" } : {},
|
|
147537
|
+
// `showValues: true` in the data-chart prints each point's number on
|
|
147538
|
+
// the mark (opt-in: on a dense series the labels collide).
|
|
147539
|
+
...spec.showValues === true ? { showValue: true } : {},
|
|
147524
147540
|
...multi || type === "pie" || type === "doughnut" ? { showLegend: true, legendPos: "b" } : {}
|
|
147525
147541
|
});
|
|
147526
147542
|
recordShape(el);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -23,7 +23,11 @@ tagged role · structure · density · has-image. `styles/README.md` catalogs th
|
|
|
23
23
|
(the paint). Those two READMEs are the source of truth for what exists — don't rely on
|
|
24
24
|
counts quoted elsewhere. Pick layouts by **P1/P2/P3** (structure → density → **≤2 dedup**), paste
|
|
25
25
|
each `layouts/<id>.tpl` into `deck.html`, fill real content (leave color to the skin),
|
|
26
|
-
and render with `--style <skin>` — see the **⭐ PREFERRED workflow** below.
|
|
26
|
+
and render with `--style <skin>` — see the **⭐ PREFERRED workflow** below. A layout with a
|
|
27
|
+
`{{#rows}}` block (gantt · radar · ring-progress · map-distribution · team-row ·
|
|
28
|
+
brand-values · color-palette · steps-vertical · toc-panel) is filled through `deck-build`
|
|
29
|
+
instead of by hand — it repeats the block per row and DERIVES the figure geometry from
|
|
30
|
+
your numbers (see `layouts/README.md` → "How to fill"). Authoring
|
|
27
31
|
every slide from zero is the #1 "AI-generated" tell; compose from the catalog first.
|
|
28
32
|
|
|
29
33
|
## html2pptx — HTML/CSS is the layout engine
|
|
@@ -22,6 +22,39 @@ mckinsey / apple / anthropic / …) and use it on every slide. Never mix skins.
|
|
|
22
22
|
Per role: **cover** once; **toc** by chapter count; **section** reused per chapter;
|
|
23
23
|
**ending** once, by purpose.
|
|
24
24
|
|
|
25
|
+
## How to fill (the deck-build contract)
|
|
26
|
+
|
|
27
|
+
`layouts/slots.json` is the machine-readable index of what each layout takes. A spec
|
|
28
|
+
slide fills a layout through three inputs:
|
|
29
|
+
|
|
30
|
+
- **`slots`** — one value per `{{name|default}}` → `{"layout":"cover","slots":{"title":"…"}}`
|
|
31
|
+
- **`rows`** — a variable-length list per `{{#key}}…{{/key}}` block; each row fills that
|
|
32
|
+
block's own fields, and `{{_n}}`/`{{_nn}}` number the copies for you.
|
|
33
|
+
- **`chart`** — replaces the layout's `data-chart` (a native, editable pptx chart). An
|
|
34
|
+
array fills several charts in document order.
|
|
35
|
+
|
|
36
|
+
**Figure layouts take INPUT fields the template never prints** — deck-build derives the
|
|
37
|
+
geometry from them, so the drawing always matches the numbers:
|
|
38
|
+
|
|
39
|
+
| layout | rows | fields (spec) | derived |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `gantt` | `cols` · `tasks` | `label` · `name`,`from`,`to` (1-based column span, inclusive) | bar offset + length |
|
|
42
|
+
| `ring-progress` | `rings` | `label`,`pct`,`note` | arc dash offset + printed % |
|
|
43
|
+
| `map-distribution` | `regions` · `pins` | `name`,`pct` · `x`,`y` (slide px, pin centre) | pin position |
|
|
44
|
+
| `radar` | `metrics` | `name`,`score` | **the radar chart itself** |
|
|
45
|
+
|
|
46
|
+
`radar` is marked `"chart": "derived"` in slots.json: its chart is built from `metrics`,
|
|
47
|
+
so pass rows and never `chart` for it.
|
|
48
|
+
|
|
49
|
+
Plain list layouts take rows too: `team-row.members{name,role}` ·
|
|
50
|
+
`brand-values.values{name,body}` · `color-palette.swatches{name,hex}` ·
|
|
51
|
+
`steps-vertical.steps{name,body}` · `toc-panel.entries{name,desc}`.
|
|
52
|
+
|
|
53
|
+
Anything the spec gets wrong — unknown layout/slot/field, a repeat block with no rows, a
|
|
54
|
+
figure row missing its numbers — **fails the build** (exit 1, nothing written) rather than
|
|
55
|
+
shipping the template's demo content. Regenerate the index with `node scripts/gen-slots.mjs`
|
|
56
|
+
after touching a `.tpl`.
|
|
57
|
+
|
|
25
58
|
## Catalog
|
|
26
59
|
|
|
27
60
|
Each slide carries `data-layout="<id>"` (drives the diversity lint + traceability).
|
|
@@ -66,31 +99,31 @@ Each slide carries `data-layout="<id>"` (drives the diversity lint + traceabilit
|
|
|
66
99
|
| `section-stat` | section | ghost-number + hero stat | light | text-only | 章节页(带关键数字) |
|
|
67
100
|
| `transition` | transition | minimal big-number break | light | text-only | 纯过渡页(整册复用一个) |
|
|
68
101
|
| `funnel` | content · data | narrowing funnel stages | medium (4) | text-only | 转化漏斗 |
|
|
69
|
-
| `radar` | content · data | radar
|
|
70
|
-
| `ring-progress` | content · data |
|
|
102
|
+
| `radar` | content · data | native radar chart + score list (rows) | medium (4–6) | diagram (chart) | 能力雷达 / 多维评估 |
|
|
103
|
+
| `ring-progress` | content · data | ring gauges (rows) | medium (2–4) | diagram | 环形进度 / 完成率 |
|
|
71
104
|
| `icon-row` | content | icon row (3–4) | medium (4) | text-only | 图标行 · 能力/特性(L16) |
|
|
72
|
-
| `gantt` | content · data | gantt schedule bars | dense | text-only | 甘特图 / 项目排期 |
|
|
105
|
+
| `gantt` | content · data | gantt schedule bars (rows) | dense | text-only | 甘特图 / 项目排期 |
|
|
73
106
|
| `org-tree` | content | org hierarchy tree | medium (4) | text-only | 组织架构树 |
|
|
74
|
-
| `map-distribution` | content · data | map + pins + legend | medium | **map slot** | 地图分布 / 区域占比 |
|
|
75
|
-
| `map-quad` | content · data | dot-matrix world map + 4 corner % callouts | medium (4) | diagram (map) | 全球分布(
|
|
107
|
+
| `map-distribution` | content · data | map + pins + legend (rows) | medium | **map slot** | 地图分布 / 区域占比 |
|
|
108
|
+
| `map-quad` | content · data | dot-matrix world map + 4 FIXED corner % callouts | medium (4) | diagram (map) | 全球分布(四角固定;条目数可变用 `map-distribution`) |
|
|
76
109
|
| `toc-agenda` | toc | single-column agenda | 5–7 items | text-only | 议程(单列,vs 双列 `toc`) |
|
|
77
|
-
| `toc-panel` | toc | left title + dark side panel |
|
|
110
|
+
| `toc-panel` | toc | left title + dark side panel (rows) | 3–5 entries | text-only | 目录(左标题+右深色面板) |
|
|
78
111
|
| `stats-chart` | content · data | title + paragraph + panel bar chart | dense | diagram (bar) | 统计页(深色面板横向条形图) |
|
|
79
112
|
| `blocks-2x2` | content | 2x2 blocks grid | medium (4) | text-only | 2×2 区块网格 |
|
|
80
113
|
| `editorial-panel` | content | editorial panel layout | medium | text-only | 编辑面板版式 |
|
|
81
114
|
| `ending-contact` | ending | closing + contact cards | light | text-only | 结尾 + 联系方式卡 |
|
|
82
115
|
| `data-table` | content · data | table (header + rows×cols) | dense | text-only | 数据表 / 多维对照 |
|
|
83
|
-
| `steps-vertical` | content | vertical numbered process | medium (
|
|
116
|
+
| `steps-vertical` | content | vertical numbered process (rows) | medium (3–5) | text-only | 竖排流程步骤 |
|
|
84
117
|
| `pyramid` | content | stacked hierarchy / funnel | medium (4) | text-only | 层级 / 漏斗结构 |
|
|
85
118
|
| `cover-minimal` | cover | centered ── ● ── divider | — | text-only (bg) | 极简封面(居中线点线+大写拉丁副标,mono 最佳) |
|
|
86
119
|
| `numbered-brief` | content | 2-col brief + numbered discs | medium (4) | text-only | 简报页(左论述+引用 / 右实心编号盘要点) |
|
|
87
120
|
| `banner-columns` | content | top banner + 3 text columns | dense (3 cols) | text-only | 通栏标题+3栏项目符号(使用说明/须知/多方案,深色皮肤最佳) |
|
|
88
|
-
| `color-palette` | content | swatch row + name/hex labels | medium (
|
|
121
|
+
| `color-palette` | content | swatch row + name/hex labels (rows) | medium (4–6) | text-only | 配色方案(品牌手册;色块走 skin ramp 按 nth-child,HEX 可替换) |
|
|
89
122
|
| `type-specimen` | content | Aa specimen + samples | medium | text-only | 字体规范(大 Aa + 字重 + 标题/正文/字符集) |
|
|
90
123
|
| `logo-guidelines` | content | logo lockup + variants | medium | text-only | Logo 规范(主标+留白区 / 反白 / 单色变体) |
|
|
91
124
|
| `dos-donts` | content | do ✓ / don't ✗ two-column | medium (4+4) | text-only | Do & Don't(推荐/避免双栏) |
|
|
92
|
-
| `brand-values` | content |
|
|
93
|
-
| `team-row` | content |
|
|
125
|
+
| `brand-values` | content | value cards (rows) | medium (2–4) | text-only | 品牌主张(编号圆+准则×3) |
|
|
126
|
+
| `team-row` | content | portrait slots + name/role (rows) | medium (3–5) | **photo slots** | 团队成员(一排 3–5 人像 + 姓名/职位) |
|
|
94
127
|
|
|
95
128
|
## Semantic class contract (what a skin must define)
|
|
96
129
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
<!-- layouts/brand-values — structure-only (paint via skin). 品牌手册"品牌主张"
|
|
1
|
+
<!-- layouts/brand-values — structure-only (paint via skin). 品牌手册"品牌主张":每张卡 编号圆({{_nn}}) + 准则标题 + 说明. rows.values{name,body} — 3 张最佳,2–4 张自适应. 复用既有 class(.surface/.step-circle/.step-title/.step-body). data-layout drives P1/P2/P3 + dedup. -->
|
|
2
2
|
<section class="slide" data-layout="brand-values" style="position:relative;width:1280px;height:720px;overflow:hidden"><div class="slide-backdrop"></div>
|
|
3
|
-
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">BRAND VALUES</span></div>
|
|
4
|
-
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px"
|
|
5
|
-
<div class="steps" style="position:absolute;left:96px;right:96px;top:280px;bottom:96px;display:
|
|
6
|
-
<div class="step surface" style="padding:34px 32px;display:flex;flex-direction:column"><div class="step-circle" style="width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;font-weight:700;line-height:1">01</div><div class="step-title" style="font-size:22px;font-weight:700;font-family:var(--font-head);margin-top:22px"
|
|
7
|
-
<div class="step surface" style="padding:34px 32px;display:flex;flex-direction:column"><div class="step-circle" style="width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;font-weight:700;line-height:1">02</div><div class="step-title" style="font-size:22px;font-weight:700;font-family:var(--font-head);margin-top:22px">克制 · Minimal</div><p class="step-body" style="font-size:15px;line-height:1.7;margin-top:10px">一句准则说明,表达品牌的核心态度,替换为真实内容。</p></div>
|
|
8
|
-
<div class="step surface" style="padding:34px 32px;display:flex;flex-direction:column"><div class="step-circle" style="width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;font-weight:700;line-height:1">03</div><div class="step-title" style="font-size:22px;font-weight:700;font-family:var(--font-head);margin-top:22px">可持续 · Sustainable</div><p class="step-body" style="font-size:15px;line-height:1.7;margin-top:10px">一句准则说明,表达品牌的核心态度,替换为真实内容。</p></div>
|
|
3
|
+
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">{{eyebrow|BRAND VALUES}}</span></div>
|
|
4
|
+
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px">{{title|品牌主张}}</h2>
|
|
5
|
+
<div class="steps" style="position:absolute;left:96px;right:96px;top:280px;bottom:96px;display:flex;gap:28px">
|
|
6
|
+
{{#values}}<div class="step surface" style="flex:1;padding:34px 32px;display:flex;flex-direction:column"><div class="step-circle" style="width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;font-weight:700;line-height:1">{{_nn|01}}</div><div class="step-title" style="font-size:22px;font-weight:700;font-family:var(--font-head);margin-top:22px">{{name|占位准则 · Value}}</div><p class="step-body" style="font-size:15px;line-height:1.7;margin-top:10px">{{body|一句准则说明,表达品牌的核心态度,替换为真实内容。}}</p></div>{{/values}}
|
|
9
7
|
</div>
|
|
10
|
-
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em"
|
|
8
|
+
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em">{{footer|报告标题 / 章节}}</div><div class="deck-pageno" style="position:absolute;right:96px;bottom:17px;font-size:12px">{{pageno|01}}</div>
|
|
11
9
|
</section>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
<!-- layouts/color-palette — structure-only (paint via skin). 品牌手册"配色方案"
|
|
1
|
+
<!-- layouts/color-palette — structure-only (paint via skin). 品牌手册"配色方案":一排等宽色板 + 名称 + HEX 标签. rows.swatches{name,hex} — 4–6 色最佳. 色块填色走 skin 的 ramp 变量(.swatch via .pal-item:nth-child),不硬编码;HEX 是可替换文本. data-layout drives P1/P2/P3 + dedup. -->
|
|
2
2
|
<section class="slide" data-layout="color-palette" style="position:relative;width:1280px;height:720px;overflow:hidden"><div class="slide-backdrop"></div>
|
|
3
|
-
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">COLOR PALETTE</span></div>
|
|
4
|
-
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px"
|
|
5
|
-
<div class="palette" style="position:absolute;left:96px;right:96px;top:264px;bottom:92px;display:
|
|
6
|
-
<div class="pal-item" style="display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px"
|
|
7
|
-
<div class="pal-item" style="display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px">辅助 · Secondary</div><div class="pal-hex" style="font-size:13px;margin-top:4px;letter-spacing:.06em;font-family:ui-monospace,'SF Mono',Menlo,monospace">#B79C6E</div></div>
|
|
8
|
-
<div class="pal-item" style="display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px">强调 · Accent</div><div class="pal-hex" style="font-size:13px;margin-top:4px;letter-spacing:.06em;font-family:ui-monospace,'SF Mono',Menlo,monospace">#5E7D5A</div></div>
|
|
9
|
-
<div class="pal-item" style="display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px">中性 · Neutral</div><div class="pal-hex" style="font-size:13px;margin-top:4px;letter-spacing:.06em;font-family:ui-monospace,'SF Mono',Menlo,monospace">#A9A288</div></div>
|
|
10
|
-
<div class="pal-item" style="display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px">墨 · Ink</div><div class="pal-hex" style="font-size:13px;margin-top:4px;letter-spacing:.06em;font-family:ui-monospace,'SF Mono',Menlo,monospace">#26261F</div></div>
|
|
3
|
+
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">{{eyebrow|COLOR PALETTE}}</span></div>
|
|
4
|
+
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px">{{title|配色方案}}</h2>
|
|
5
|
+
<div class="palette" style="position:absolute;left:96px;right:96px;top:264px;bottom:92px;display:flex;gap:20px">
|
|
6
|
+
{{#swatches}}<div class="pal-item" style="flex:1;display:flex;flex-direction:column"><div class="swatch" style="flex:1;border-radius:var(--card-radius);min-height:180px"></div><div class="pal-name" style="font-size:16px;font-weight:700;margin-top:16px">{{name|占位色 · Name}}</div><div class="pal-hex" style="font-size:13px;margin-top:4px;letter-spacing:.06em;font-family:ui-monospace,'SF Mono',Menlo,monospace">{{hex|#000000}}</div></div>{{/swatches}}
|
|
11
7
|
</div>
|
|
12
|
-
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em"
|
|
8
|
+
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em">{{footer|报告标题 / 章节}}</div><div class="deck-pageno" style="position:absolute;right:96px;bottom:17px;font-size:12px">{{pageno|01}}</div>
|
|
13
9
|
</section>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- layouts/gantt — structure-only variant (paint via skin). -->
|
|
2
|
-
<section class="slide" data-layout="gantt" style="position:relative;width:1280px;height:720px;overflow:hidden"><div class="slide-backdrop"></div><div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">SCHEDULE</span></div><h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px"
|
|
3
|
-
<div style="position:absolute;left:96px;right:96px;top:284px"><div style="display:grid;grid-template-columns:180px 1fr;align-items:center;padding-bottom:10px"><div></div><div style="display:
|
|
4
|
-
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em"
|
|
1
|
+
<!-- layouts/gantt — structure-only variant (paint via skin). rows.cols{label} = the timeline header; rows.tasks{name,from,to} = one bar per task, spanning columns from..to (1-based, inclusive) — deck-build derives _left/_width so the bar always matches the numbers. -->
|
|
2
|
+
<section class="slide" data-layout="gantt" style="position:relative;width:1280px;height:720px;overflow:hidden"><div class="slide-backdrop"></div><div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">{{eyebrow|SCHEDULE}}</span></div><h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px">{{title|项目排期}}</h2>
|
|
3
|
+
<div style="position:absolute;left:96px;right:96px;top:284px"><div style="display:grid;grid-template-columns:180px 1fr;align-items:center;padding-bottom:10px"><div></div><div style="display:flex">{{#cols}}<div class="gantt-grid" style="flex:1;font-size:12px;padding-left:6px;border-left:1px solid var(--hair)">{{label|Q1}}</div>{{/cols}}</div></div>{{#tasks}}<div style="display:grid;grid-template-columns:180px 1fr;align-items:center;height:52px"><div class="dt-key" style="font-size:15px;font-weight:600">{{name|占位任务}}</div><div class="gantt-track" style="position:relative;height:20px;border-radius:var(--card-radius)"><div class="gantt-bar" style="position:absolute;top:0;bottom:0;left:{{_left|0}}%;width:{{_width|25}}%;border-radius:var(--card-radius)"></div></div></div>{{/tasks}}</div>
|
|
4
|
+
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em">{{footer|报告标题 / 章节}}</div><div class="deck-pageno" style="position:absolute;right:96px;bottom:17px;font-size:12px">{{pageno|01}}</div></section>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<!-- layouts/logo-guidelines — structure-only (paint via skin). 品牌手册"Logo 规范":左 主标 + 虚线留白区;右 三种变体(主标/反白/单色). 字标/反白底/单色走 skin 变量(.logo-mark/.logo-tile-dark/.logo-mark-rev/.logo-mark-mono/.clearspace). data-layout drives P1/P2/P3 + dedup. -->
|
|
1
|
+
<!-- layouts/logo-guidelines — structure-only (paint via skin). 品牌手册"Logo 规范":左 主标 + 虚线留白区;右 三种变体(主标/反白/单色). {{wordmark}} 填一次,四处字标同步. 字标/反白底/单色走 skin 变量(.logo-mark/.logo-tile-dark/.logo-mark-rev/.logo-mark-mono/.clearspace). data-layout drives P1/P2/P3 + dedup. -->
|
|
2
2
|
<section class="slide" data-layout="logo-guidelines" style="position:relative;width:1280px;height:720px;overflow:hidden"><div class="slide-backdrop"></div>
|
|
3
|
-
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">LOGO</span></div>
|
|
4
|
-
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px">Logo
|
|
3
|
+
<div class="eyebrow-row" style="position:absolute;left:96px;top:132px;display:flex;align-items:center;gap:14px"><span class="accent-bar" style="width:30px;height:1px;display:inline-block"></span><span class="eyebrow" style="font-size:13px;letter-spacing:.28em">{{eyebrow|LOGO}}</span></div>
|
|
4
|
+
<h2 class="deck-title" style="position:absolute;left:92px;top:158px;font-size:48px;letter-spacing:-.4px">{{title|Logo 规范}}</h2>
|
|
5
5
|
<!-- left: primary logo + clear space -->
|
|
6
|
-
<div class="surface" style="position:absolute;left:96px;top:264px;width:560px;bottom:92px;display:flex;align-items:center;justify-content:center"><div class="clearspace" style="padding:56px 72px;border-radius:var(--card-radius)"><div class="logo-mark" style="font-family:var(--font-head);font-size:56px;font-weight:700;letter-spacing:.04em">BRAND</div><div class="surface-label" style="font-size:13px;text-align:center;margin-top:8px;letter-spacing:.24em">STUDIO</div></div></div>
|
|
6
|
+
<div class="surface" style="position:absolute;left:96px;top:264px;width:560px;bottom:92px;display:flex;align-items:center;justify-content:center"><div class="clearspace" style="padding:56px 72px;border-radius:var(--card-radius)"><div class="logo-mark" style="font-family:var(--font-head);font-size:56px;font-weight:700;letter-spacing:.04em">{{wordmark|BRAND}}</div><div class="surface-label" style="font-size:13px;text-align:center;margin-top:8px;letter-spacing:.24em">{{tagline|STUDIO}}</div></div></div>
|
|
7
7
|
<!-- right: 3 variants -->
|
|
8
8
|
<div style="position:absolute;left:692px;right:96px;top:264px;bottom:92px;display:flex;flex-direction:column;gap:18px">
|
|
9
|
-
<div class="surface" style="flex:1;display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">BRAND</div><span class="surface-label" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em"
|
|
10
|
-
<div class="logo-tile-dark" style="flex:1;border-radius:var(--card-radius);display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark-rev" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">BRAND</div><span class="logo-mark-rev" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em;opacity:.7"
|
|
11
|
-
<div class="surface" style="flex:1;display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark-mono" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">BRAND</div><span class="surface-label" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em"
|
|
9
|
+
<div class="surface" style="flex:1;display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">{{wordmark|BRAND}}</div><span class="surface-label" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em">{{v1_label|主标 · Primary}}</span></div>
|
|
10
|
+
<div class="logo-tile-dark" style="flex:1;border-radius:var(--card-radius);display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark-rev" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">{{wordmark|BRAND}}</div><span class="logo-mark-rev" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em;opacity:.7">{{v2_label|反白 · Reverse}}</span></div>
|
|
11
|
+
<div class="surface" style="flex:1;display:flex;align-items:center;justify-content:center;position:relative"><div class="logo-mark-mono" style="font-family:var(--font-head);font-size:32px;font-weight:700;letter-spacing:.04em">{{wordmark|BRAND}}</div><span class="surface-label" style="position:absolute;left:18px;bottom:12px;font-size:11px;letter-spacing:.14em">{{v3_label|单色 · Mono}}</span></div>
|
|
12
12
|
</div>
|
|
13
|
-
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em"
|
|
13
|
+
<div class="rule rule-foot" style="position:absolute;left:96px;right:96px;bottom:40px;height:1px"></div><div class="deck-footer" style="position:absolute;left:96px;bottom:17px;font-size:12px;letter-spacing:.1em">{{footer|报告标题 / 章节}}</div><div class="deck-pageno" style="position:absolute;right:96px;bottom:17px;font-size:12px">{{pageno|01}}</div>
|
|
14
14
|
</section>
|