@bytechain.cn/colamd-themes 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.claude/skills/colamd-themes-preview.skill.md +333 -0
  2. package/.claude/skills/colamd-themes.skill.md +155 -0
  3. package/.hermes/skills/tools/colamd-themes/SKILL.md +159 -0
  4. package/.openclaw/skills/colamd-themes/SKILL.md +166 -0
  5. package/.opencode/skills/colamd-themes/SKILL.md +162 -0
  6. package/.trae/rules/colamd-themes.md +150 -0
  7. package/CHANGELOG.md +102 -0
  8. package/README.md +373 -0
  9. package/README_CN.md +373 -0
  10. package/RELEASENOTE.md +177 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +208 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/color-systems.d.ts +17 -0
  15. package/dist/color-systems.js +251 -0
  16. package/dist/color-systems.js.map +1 -0
  17. package/dist/color-utils.d.ts +9 -0
  18. package/dist/color-utils.js +112 -0
  19. package/dist/color-utils.js.map +1 -0
  20. package/dist/contrast.d.ts +15 -0
  21. package/dist/contrast.js +63 -0
  22. package/dist/contrast.js.map +1 -0
  23. package/dist/docx-style-paradigm.d.ts +153 -0
  24. package/dist/docx-style-paradigm.js +640 -0
  25. package/dist/docx-style-paradigm.js.map +1 -0
  26. package/dist/export-cli.d.ts +10 -0
  27. package/dist/export-cli.js +266 -0
  28. package/dist/export-cli.js.map +1 -0
  29. package/dist/extractors/base.d.ts +6 -0
  30. package/dist/extractors/base.js +3 -0
  31. package/dist/extractors/base.js.map +1 -0
  32. package/dist/extractors/docx-extractor.d.ts +52 -0
  33. package/dist/extractors/docx-extractor.js +370 -0
  34. package/dist/extractors/docx-extractor.js.map +1 -0
  35. package/dist/extractors/pdf-extractor.d.ts +66 -0
  36. package/dist/extractors/pdf-extractor.js +430 -0
  37. package/dist/extractors/pdf-extractor.js.map +1 -0
  38. package/dist/extractors/url-extractor.d.ts +104 -0
  39. package/dist/extractors/url-extractor.js +761 -0
  40. package/dist/extractors/url-extractor.js.map +1 -0
  41. package/dist/generator.d.ts +4 -0
  42. package/dist/generator.js +71 -0
  43. package/dist/generator.js.map +1 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/mermaid-presets.d.ts +14 -0
  48. package/dist/mermaid-presets.js +122 -0
  49. package/dist/mermaid-presets.js.map +1 -0
  50. package/dist/models.d.ts +106 -0
  51. package/dist/models.js +92 -0
  52. package/dist/models.js.map +1 -0
  53. package/dist/renderer.d.ts +70 -0
  54. package/dist/renderer.js +204 -0
  55. package/dist/renderer.js.map +1 -0
  56. package/dist/templates/template.css +498 -0
  57. package/dist/templates/theme-paradigm.md +658 -0
  58. package/dist/templates/theme-template.hbs +370 -0
  59. package/dist/theme-store.d.ts +37 -0
  60. package/dist/theme-store.js +90 -0
  61. package/dist/theme-store.js.map +1 -0
  62. package/dist/validators.d.ts +26 -0
  63. package/dist/validators.js +144 -0
  64. package/dist/validators.js.map +1 -0
  65. package/package.json +36 -0
  66. package/skills/build-skills.sh +166 -0
  67. package/skills/colamd-themes/SKILL.md +148 -0
  68. package/src/cli.ts +200 -0
  69. package/src/color-systems.ts +289 -0
  70. package/src/color-utils.ts +120 -0
  71. package/src/contrast.ts +66 -0
  72. package/src/docx-style-paradigm.ts +811 -0
  73. package/src/export-cli.ts +301 -0
  74. package/src/extractors/base.ts +8 -0
  75. package/src/extractors/docx-extractor.ts +417 -0
  76. package/src/extractors/pdf-extractor.ts +527 -0
  77. package/src/extractors/url-extractor.ts +927 -0
  78. package/src/generator.ts +88 -0
  79. package/src/index.ts +29 -0
  80. package/src/mermaid-presets.ts +142 -0
  81. package/src/models.ts +212 -0
  82. package/src/renderer.ts +277 -0
  83. package/src/templates/template.css +498 -0
  84. package/src/templates/theme-paradigm.md +658 -0
  85. package/src/templates/theme-template.hbs +370 -0
  86. package/src/theme-store.ts +112 -0
  87. package/src/types/pdf-parse.d.ts +12 -0
  88. package/src/validators.ts +176 -0
  89. package/themes/.gitkeep +0 -0
  90. package/themes/academic-paper-pt.css +1805 -0
  91. package/themes/academic-paper.css +1099 -0
  92. package/themes/academic-style.css +2430 -0
  93. package/themes/colly.css +370 -0
  94. package/themes/elegant.css +671 -0
  95. package/themes/forest-ink.css +664 -0
  96. package/themes/guizang.css +732 -0
  97. package/themes/meeting-minutes.css +370 -0
  98. package/themes/pixso-design.css +1261 -0
  99. package/themes/salvator-blog.css +370 -0
  100. package/themes/swiss-design copy.css +746 -0
  101. package/themes/swiss-design.css +746 -0
  102. package/themes/template.css +498 -0
  103. package/themes/trae-blue.css +737 -0
  104. package/themes-preview/colly-preview.html +623 -0
  105. package/themes-preview/demo-content.md +187 -0
  106. package/themes-preview/meeting-minutes-preview.html +625 -0
  107. package/themes-preview/salvator-blog-preview.html +623 -0
  108. package/tsconfig.json +18 -0
@@ -0,0 +1,88 @@
1
+ /** CSS theme file generator — v3.0 seed palette pipeline. */
2
+
3
+ import Handlebars from "handlebars";
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
5
+ import { join, dirname, isAbsolute } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import type { ThemeStyle } from "./models.js";
8
+ import { paletteToSeed } from "./models.js";
9
+ import { toRgba, normalizeThemeStyle } from "./color-utils.js";
10
+ import { matchColorSystem } from "./color-systems.js";
11
+ import { selectMermaidPreset } from "./mermaid-presets.js";
12
+ import { validateSeedPalette, summarizeIssues } from "./validators.js";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const templatePath = join(__dirname, "templates", "theme-template.hbs");
16
+
17
+ // ── Handlebars helpers ──
18
+
19
+ Handlebars.registerHelper("rgba", (hex: unknown, alpha: unknown) => {
20
+ return toRgba(String(hex), Number(alpha));
21
+ });
22
+
23
+ let compiledTemplate: HandlebarsTemplateDelegate<ThemeStyle> | null = null;
24
+
25
+ function getTemplate(): HandlebarsTemplateDelegate<ThemeStyle> {
26
+ if (!compiledTemplate) {
27
+ const source = readFileSync(templatePath, "utf-8");
28
+ compiledTemplate = Handlebars.compile(source, { noEscape: true });
29
+ }
30
+ return compiledTemplate;
31
+ }
32
+
33
+ // ── Pipeline ──
34
+
35
+ export function generateCss(style: ThemeStyle): string {
36
+ // 1. Map legacy palette → seed palette if seed is still default
37
+ if (style.seed.accent === "#6366f1") {
38
+ style.seed = paletteToSeed(style.palette);
39
+ }
40
+
41
+ // 2. Normalize from legacy extractor path
42
+ normalizeThemeStyle(style);
43
+
44
+ // 3. Match color system
45
+ if (!style.colorSystemId) {
46
+ const cs = matchColorSystem(style.seed.accent, style.seed.surface);
47
+ style.colorSystemId = cs.id;
48
+ }
49
+
50
+ // 4. Select Mermaid preset
51
+ if (!style.mermaidPresetId) {
52
+ const mp = selectMermaidPreset(style.seed.surface, style.seed.accent);
53
+ style.mermaidPresetId = mp.id;
54
+ }
55
+
56
+ // 5. Validate against paradigm
57
+ const issues = validateSeedPalette(style.seed);
58
+ if (issues.filter((i) => i.level === "MUST" || i.level === "MUST_NOT").length > 0) {
59
+ console.warn("Paradigm validation issues:", summarizeIssues(issues));
60
+ }
61
+
62
+ // 6. Render
63
+ const template = getTemplate();
64
+ return template(style);
65
+ }
66
+
67
+ export function writeThemeFile(
68
+ style: ThemeStyle,
69
+ target: string = "themes"
70
+ ): string {
71
+ const css = generateCss(style);
72
+ const safeName = style.name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-");
73
+
74
+ let filePath: string;
75
+ if (target.endsWith(".css")) {
76
+ filePath = isAbsolute(target) ? target : join(process.cwd(), target);
77
+ } else {
78
+ const dirPath = isAbsolute(target) ? target : join(process.cwd(), target);
79
+ if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
80
+ filePath = join(dirPath, `${safeName}.css`);
81
+ }
82
+
83
+ const parent = dirname(filePath);
84
+ if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
85
+
86
+ writeFileSync(filePath, css, "utf-8");
87
+ return filePath;
88
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ /** ColaMD Theme Development Tool. */
2
+
3
+ export * from "./models.js";
4
+ export * from "./color-utils.js";
5
+ export * from "./generator.js";
6
+ export { UrlExtractor } from "./extractors/url-extractor.js";
7
+ export { DocxExtractor } from "./extractors/docx-extractor.js";
8
+ export { PdfExtractor } from "./extractors/pdf-extractor.js";
9
+ export type { Extractor } from "./extractors/base.js";
10
+
11
+ export {
12
+ ACADEMIC_PAPER_DEFAULTS,
13
+ BUSINESS_REPORT_DEFAULTS,
14
+ TECHNICAL_DOC_DEFAULTS,
15
+ generatePrintFriendlyGrayscale,
16
+ generateMorandiPrintPalette,
17
+ validateDocumentStyle,
18
+ normalizeForPrinting,
19
+ getRecommendedPreset,
20
+ DOCUMENT_STYLE_PRESETS,
21
+ } from "./docx-style-paradigm.js";
22
+
23
+ export type {
24
+ DocumentStyleParadigm,
25
+ FontStyleDefinition,
26
+ TableCellStyle,
27
+ PrintOptimization,
28
+ StyleValidationIssue,
29
+ } from "./docx-style-paradigm.js";
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Mermaid 20-variable presets for light, dark, and elegant theme modes.
3
+ * Based on ColaMD-extend reference themes (elegant.css, forest-ink.css, guizang.css).
4
+ */
5
+
6
+ import { isLightMode, hslHue } from "./contrast.js";
7
+
8
+ export interface MermaidPreset {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ variables: Record<string, string>;
13
+ }
14
+
15
+ // ═══════════════════════════════════════════════════════════
16
+ // Light Preset — warm paper tone (from elegant.css style)
17
+ // ═══════════════════════════════════════════════════════════
18
+
19
+ const lightPreset: MermaidPreset = {
20
+ id: "light",
21
+ name: "Light",
22
+ description: "亮色主题 Mermaid 预设 — 暖色调,节点用浅色填充、accent 描边",
23
+ variables: {
24
+ "mermaid-background": "var(--seed-panel)",
25
+ "mermaid-border-color": "var(--seed-border)",
26
+ "mermaid-border-radius": "var(--radius-md)",
27
+ "mermaid-padding": "20px",
28
+ "mermaid-font-family": "var(--font-mermaid)",
29
+ "mermaid-font-size": "var(--font-size-mermaid)",
30
+ "mermaid-node-fill": "var(--seed-panel)",
31
+ "mermaid-node-stroke": "var(--seed-accent)",
32
+ "mermaid-node-stroke-width": "1.5px",
33
+ "mermaid-node-text": "var(--seed-ink)",
34
+ "mermaid-edge-stroke": "var(--seed-ink-muted)",
35
+ "mermaid-edge-stroke-width": "1.5px",
36
+ "mermaid-cluster-fill": "rgba accent 0.06",
37
+ "mermaid-cluster-stroke": "var(--seed-accent)",
38
+ "mermaid-label-text": "var(--seed-ink)",
39
+ "mermaid-edge-label-text": "var(--seed-ink-muted)",
40
+ "mermaid-edge-label-bg": "var(--seed-surface)",
41
+ "mermaid-title-text": "var(--seed-ink)",
42
+ "mermaid-axis-text": "var(--seed-ink-muted)",
43
+ "mermaid-highlight": "var(--seed-accent)",
44
+ "mermaid-person-stroke": "var(--seed-ink)",
45
+ "mermaid-person-fill": "var(--seed-panel)",
46
+ "mermaid-label-offset-y": "0px",
47
+ },
48
+ };
49
+
50
+ // ═══════════════════════════════════════════════════════════
51
+ // Dark Preset — dark background (for dark themes)
52
+ // ═══════════════════════════════════════════════════════════
53
+
54
+ const darkPreset: MermaidPreset = {
55
+ id: "dark",
56
+ name: "Dark",
57
+ description: "暗色主题 Mermaid 预设 — 深色背景,亮色节点",
58
+ variables: {
59
+ "mermaid-background": "var(--seed-panel-alt)",
60
+ "mermaid-border-color": "var(--seed-border-strong)",
61
+ "mermaid-border-radius": "var(--radius-md)",
62
+ "mermaid-padding": "20px",
63
+ "mermaid-font-family": "var(--font-mermaid)",
64
+ "mermaid-font-size": "var(--font-size-mermaid)",
65
+ "mermaid-node-fill": "var(--seed-panel)",
66
+ "mermaid-node-stroke": "var(--seed-accent-light)",
67
+ "mermaid-node-stroke-width": "1.5px",
68
+ "mermaid-node-text": "var(--seed-ink)",
69
+ "mermaid-edge-stroke": "var(--seed-ink-muted)",
70
+ "mermaid-edge-stroke-width": "1.5px",
71
+ "mermaid-cluster-fill": "rgba accent 0.08",
72
+ "mermaid-cluster-stroke": "var(--seed-border-strong)",
73
+ "mermaid-label-text": "var(--seed-ink)",
74
+ "mermaid-edge-label-text": "var(--seed-ink-muted)",
75
+ "mermaid-edge-label-bg": "var(--seed-surface)",
76
+ "mermaid-title-text": "var(--seed-ink)",
77
+ "mermaid-axis-text": "var(--seed-ink-muted)",
78
+ "mermaid-highlight": "var(--seed-accent)",
79
+ "mermaid-person-stroke": "var(--seed-ink)",
80
+ "mermaid-person-fill": "var(--seed-panel)",
81
+ "mermaid-label-offset-y": "0px",
82
+ },
83
+ };
84
+
85
+ // ═══════════════════════════════════════════════════════════
86
+ // Elegant Preset — warm toned (for warm ink/paper themes)
87
+ // ═══════════════════════════════════════════════════════════
88
+
89
+ const elegantPreset: MermaidPreset = {
90
+ id: "elegant",
91
+ name: "Elegant",
92
+ description: "典雅暖调 Mermaid 预设 — 米色底 + 暖棕点缀",
93
+ variables: {
94
+ "mermaid-background": "var(--seed-panel)",
95
+ "mermaid-border-color": "var(--seed-border)",
96
+ "mermaid-border-radius": "var(--radius-md)",
97
+ "mermaid-padding": "20px",
98
+ "mermaid-font-family": "var(--font-mermaid)",
99
+ "mermaid-font-size": "var(--font-size-mermaid)",
100
+ "mermaid-node-fill": "var(--seed-panel)",
101
+ "mermaid-node-stroke": "var(--seed-accent)",
102
+ "mermaid-node-stroke-width": "1.5px",
103
+ "mermaid-node-text": "var(--seed-ink)",
104
+ "mermaid-edge-stroke": "var(--seed-ink-muted)",
105
+ "mermaid-edge-stroke-width": "1.5px",
106
+ "mermaid-cluster-fill": "rgba accent 0.04",
107
+ "mermaid-cluster-stroke": "var(--seed-accent)",
108
+ "mermaid-label-text": "var(--seed-ink)",
109
+ "mermaid-edge-label-text": "var(--seed-ink-muted)",
110
+ "mermaid-edge-label-bg": "var(--seed-surface)",
111
+ "mermaid-title-text": "var(--seed-ink)",
112
+ "mermaid-axis-text": "var(--seed-ink-muted)",
113
+ "mermaid-highlight": "var(--seed-accent)",
114
+ "mermaid-person-stroke": "var(--seed-ink)",
115
+ "mermaid-person-fill": "var(--seed-panel)",
116
+ "mermaid-label-offset-y": "0px",
117
+ },
118
+ };
119
+
120
+ // ── Registry ──
121
+
122
+ export const MERMAID_PRESETS: MermaidPreset[] = [lightPreset, darkPreset, elegantPreset];
123
+
124
+ export function getMermaidPreset(id: string): MermaidPreset | undefined {
125
+ return MERMAID_PRESETS.find((p) => p.id === id);
126
+ }
127
+
128
+ /** Auto-select Mermaid preset based on background luminance. */
129
+ export function selectMermaidPreset(
130
+ bgHex: string,
131
+ accentHex: string
132
+ ): MermaidPreset {
133
+ const light = isLightMode(bgHex);
134
+ if (!light) return darkPreset;
135
+
136
+ // For light themes: check accent warmth — warm accent → elegant, cool → light
137
+ const hue = hslHue(accentHex);
138
+ // Warm hues: red (0-30), orange (30-50), brown is low sat orange
139
+ if (hue >= 0 && hue <= 50) return elegantPreset;
140
+
141
+ return lightPreset;
142
+ }
package/src/models.ts ADDED
@@ -0,0 +1,212 @@
1
+ /** Theme style data models — v3.0 Seed Palette paradigm. */
2
+
3
+ // ═══════════════════════════════════════════════════════════
4
+ // Legacy model (kept for extractor compatibility)
5
+ // ═══════════════════════════════════════════════════════════
6
+
7
+ export interface ColorPalette {
8
+ bgColor: string;
9
+ textColor: string;
10
+ headingColor: string;
11
+ borderColor: string;
12
+ accentColor: string;
13
+ codeBg: string;
14
+ codeColor: string;
15
+ codeBlockBg: string;
16
+ codeBlockText: string;
17
+ blockquoteBg: string;
18
+ blockquoteBorder: string;
19
+ tableHeaderBg: string;
20
+ selectionBg: string;
21
+ textMuted: string;
22
+ }
23
+
24
+ export interface Typography {
25
+ bodyFontFamily: string;
26
+ headingFontFamily: string;
27
+ codeFontFamily: string;
28
+ baseFontSize: string;
29
+ baseLineHeight: number;
30
+ headingWeight: number;
31
+ }
32
+
33
+ export interface Spacing {
34
+ contentMaxWidth: string;
35
+ contentPadding: string;
36
+ paragraphMargin: string;
37
+ headingMarginTop: string;
38
+ headingMarginBottom: string;
39
+ }
40
+
41
+ export interface SyntaxHighlighting {
42
+ comment: string;
43
+ punctuation: string;
44
+ keyword: string;
45
+ booleanBuiltinTag: string;
46
+ string: string;
47
+ number: string;
48
+ functionClass: string;
49
+ operator: string;
50
+ variable: string;
51
+ regex: string;
52
+ }
53
+
54
+ // ═══════════════════════════════════════════════════════════
55
+ // v3.0 Seed Palette model
56
+ // ═══════════════════════════════════════════════════════════
57
+
58
+ export interface SeedPalette {
59
+ accent: string;
60
+ accentLight: string;
61
+ accentDark: string;
62
+ surface: string;
63
+ panel: string;
64
+ panelAlt: string;
65
+ ink: string;
66
+ inkMuted: string;
67
+ inkDim: string;
68
+ border: string;
69
+ borderStrong: string;
70
+ }
71
+
72
+ export interface SeedTypography {
73
+ fontBody: string;
74
+ fontHeading: string;
75
+ fontCode: string;
76
+ fontMermaid: string;
77
+ fontSizeRoot: string; // e.g. "16px"
78
+ fontScaleH1: number;
79
+ fontScaleH2: number;
80
+ fontScaleH3: number;
81
+ fontScaleH4: number;
82
+ fontScaleH5: number;
83
+ fontScaleH6: number;
84
+ fontSizeMermaid: string;
85
+ lineHeightBody: number;
86
+ lineHeightHeading: number;
87
+ lineHeightCode: number;
88
+ }
89
+
90
+ export interface ThemeStyle {
91
+ name: string;
92
+ description: string;
93
+ /** Legacy — populated by extractors, then mapped to seed */
94
+ palette: ColorPalette;
95
+ typography: Typography;
96
+ spacing: Spacing;
97
+ syntax: SyntaxHighlighting;
98
+ /** v3.0 seed palette */
99
+ seed: SeedPalette;
100
+ seedTypography: SeedTypography;
101
+ /** Selected presets */
102
+ colorSystemId: string;
103
+ mermaidPresetId: string;
104
+ /** Metadata */
105
+ sourceUrl: string;
106
+ sourceType: string;
107
+ /** Computed radius and width */
108
+ radiusSm: string;
109
+ radiusMd: string;
110
+ radiusLg: string;
111
+ contentWidth: string;
112
+ }
113
+
114
+ // ═══════════════════════════════════════════════════════════
115
+ // Defaults
116
+ // ═══════════════════════════════════════════════════════════
117
+
118
+ export function defaultPalette(): ColorPalette {
119
+ return {
120
+ bgColor: "#ffffff", textColor: "#000000", headingColor: "",
121
+ borderColor: "#cccccc", accentColor: "#0000ff", codeBg: "#f5f5f5",
122
+ codeColor: "", codeBlockBg: "#1e1e1e", codeBlockText: "#d4d4d4",
123
+ blockquoteBg: "#f9f9f9", blockquoteBorder: "",
124
+ tableHeaderBg: "#f0f0f0", selectionBg: "", textMuted: "#666666",
125
+ };
126
+ }
127
+
128
+ export function defaultTypography(): Typography {
129
+ return {
130
+ bodyFontFamily: "Georgia, serif", headingFontFamily: "",
131
+ codeFontFamily: "'JetBrains Mono', 'Fira Code', monospace",
132
+ baseFontSize: "16px", baseLineHeight: 1.9, headingWeight: 700,
133
+ };
134
+ }
135
+
136
+ export function defaultSpacing(): Spacing {
137
+ return {
138
+ contentMaxWidth: "820px", contentPadding: "30px 60px 100px",
139
+ paragraphMargin: "0.8em 0", headingMarginTop: "1.8em",
140
+ headingMarginBottom: "0.6em",
141
+ };
142
+ }
143
+
144
+ export function defaultSyntax(): SyntaxHighlighting {
145
+ return { comment: "", punctuation: "", keyword: "", booleanBuiltinTag: "",
146
+ string: "", number: "", functionClass: "", operator: "", variable: "", regex: "" };
147
+ }
148
+
149
+ export function defaultSeedPalette(): SeedPalette {
150
+ return {
151
+ accent: "#6366f1", accentLight: "#eef2ff", accentDark: "#4f46e5",
152
+ surface: "#ffffff", panel: "#f8f9fb", panelAlt: "#f1f3f5",
153
+ ink: "#1a1d26", inkMuted: "#6b7280", inkDim: "#9ca3af",
154
+ border: "#e5e7eb", borderStrong: "#d1d5db",
155
+ };
156
+ }
157
+
158
+ export function defaultSeedTypography(): SeedTypography {
159
+ return {
160
+ fontBody: `"Inter", "SF Pro Text", -apple-system, "Noto Serif SC", "Source Han Serif SC", Georgia, serif`,
161
+ fontHeading: `"Inter", "SF Pro Display", -apple-system, "Noto Sans SC", "PingFang SC", sans-serif`,
162
+ fontCode: `"JetBrains Mono", "SF Mono", "Fira Code", Menlo, Consolas, monospace`,
163
+ fontMermaid: `var(--font-heading)`,
164
+ fontSizeRoot: "16px",
165
+ fontScaleH1: 2.0, fontScaleH2: 1.5, fontScaleH3: 1.25,
166
+ fontScaleH4: 1.1, fontScaleH5: 1.0, fontScaleH6: 0.95,
167
+ fontSizeMermaid: "13px",
168
+ lineHeightBody: 1.75, lineHeightHeading: 1.35, lineHeightCode: 1.6,
169
+ };
170
+ }
171
+
172
+ // ═══════════════════════════════════════════════════════════
173
+ // Factory
174
+ // ═══════════════════════════════════════════════════════════
175
+
176
+ export function makeThemeStyle(name: string, sourceType: string, sourceUrl: string): ThemeStyle {
177
+ return {
178
+ name,
179
+ description: `Theme extracted from ${sourceType}`,
180
+ palette: defaultPalette(),
181
+ typography: defaultTypography(),
182
+ spacing: defaultSpacing(),
183
+ syntax: defaultSyntax(),
184
+ seed: defaultSeedPalette(),
185
+ seedTypography: defaultSeedTypography(),
186
+ colorSystemId: "",
187
+ mermaidPresetId: "",
188
+ sourceUrl,
189
+ sourceType,
190
+ radiusSm: "3px",
191
+ radiusMd: "6px",
192
+ radiusLg: "10px",
193
+ contentWidth: "820px",
194
+ };
195
+ }
196
+
197
+ /** Map legacy ColorPalette to SeedPalette. */
198
+ export function paletteToSeed(palette: ColorPalette): SeedPalette {
199
+ return {
200
+ accent: palette.accentColor,
201
+ accentLight: palette.selectionBg || palette.accentColor,
202
+ accentDark: palette.accentColor,
203
+ surface: palette.bgColor,
204
+ panel: palette.codeBg,
205
+ panelAlt: palette.codeBlockBg,
206
+ ink: palette.textColor,
207
+ inkMuted: palette.textMuted,
208
+ inkDim: palette.textMuted,
209
+ border: palette.borderColor,
210
+ borderStrong: palette.borderColor,
211
+ };
212
+ }