@loworbitstudio/visor-theme-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-r7ae3WP2.js';
2
+
3
+ /**
4
+ * Adapter types for the Visor theme engine.
5
+ *
6
+ * Adapters transform the theme engine's intermediate output into
7
+ * consumer-specific CSS for different frameworks and contexts.
8
+ */
9
+
10
+ /** Input provided to all adapters — the theme engine's intermediate data. */
11
+ interface AdapterInput {
12
+ primitives: GeneratedPrimitives;
13
+ tokens: SemanticTokens;
14
+ config: ResolvedThemeConfig;
15
+ }
16
+ /** Base options shared by all adapters. */
17
+ interface AdapterOptions {
18
+ /** Include FOWT prevention script usage comment (default: true for NextJS) */
19
+ includeFowt?: boolean;
20
+ }
21
+ /** Options specific to the NextJS adapter. */
22
+ interface NextJSAdapterOptions extends AdapterOptions {
23
+ /** Include Google Fonts @import statements (default: true) */
24
+ includeFontImports?: boolean;
25
+ }
26
+ /** Options specific to the Deck adapter. */
27
+ interface DeckAdapterOptions extends AdapterOptions {
28
+ /** Override the scope class name (default: .deck--{kebab-theme-name}) */
29
+ scopeClass?: string;
30
+ }
31
+ /** Options specific to the Docs adapter. */
32
+ interface DocsAdapterOptions extends AdapterOptions {
33
+ /** Include font imports at the top (default: true) */
34
+ includeFontImports?: boolean;
35
+ }
36
+ /** Supported adapter names. */
37
+ type AdapterName = "nextjs" | "fumadocs" | "deck" | "docs";
38
+
39
+ /**
40
+ * NextJS Adapter
41
+ *
42
+ * Generates CSS custom properties formatted for Next.js projects:
43
+ * - @import for Google Fonts (before @layer per CSS spec)
44
+ * - @layer declarations for specificity ordering
45
+ * - Primitives, light/dark adaptive tokens in layers
46
+ * - FOWT usage comment
47
+ */
48
+
49
+ /**
50
+ * Generate Next.js-formatted CSS from theme engine output.
51
+ *
52
+ * Output order (CSS spec compliant):
53
+ * 1. @import (Google Fonts)
54
+ * 2. @layer order declaration
55
+ * 3. @layer visor-primitives { ... }
56
+ * 4. @layer visor-adaptive { light + dark }
57
+ */
58
+ declare function nextjsAdapter(input: AdapterInput, options?: NextJSAdapterOptions): string;
59
+
60
+ /**
61
+ * Fumadocs Adapter
62
+ *
63
+ * Generates the Section 4 (framework bridge) CSS that maps Visor
64
+ * semantic tokens to fumadocs --color-fd-* custom properties.
65
+ * Replaces the manually written bridge sections in theme CSS files.
66
+ */
67
+
68
+ /**
69
+ * Generate fumadocs bridge CSS from theme engine output.
70
+ *
71
+ * Produces .dark and html:not(.dark) blocks with --color-fd-* tokens
72
+ * wrapped in @layer visor-bridge.
73
+ */
74
+ declare function fumadocsAdapter(input: AdapterInput): string;
75
+
76
+ /**
77
+ * Deck Adapter
78
+ *
79
+ * Generates CSS scoped under a .deck--{theme-name} class for pitch decks
80
+ * where multiple themes may coexist on one page. All tokens are nested
81
+ * under the scope class — no :root selectors.
82
+ */
83
+
84
+ /**
85
+ * Generate scoped CSS for a deck theme.
86
+ *
87
+ * All tokens are nested under .deck--{theme-name} with no :root selectors.
88
+ * Dark mode uses .dark .deck--{name} selectors.
89
+ */
90
+ declare function deckAdapter(input: AdapterInput, options?: DeckAdapterOptions): string;
91
+
92
+ /**
93
+ * Docs Adapter
94
+ *
95
+ * Generates class-scoped CSS for the Visor docs site (fumadocs).
96
+ * Output matches the hand-written theme CSS files in packages/docs/app/.
97
+ *
98
+ * Structure:
99
+ * 1. Font imports (@import Google Fonts, @font-face Visor Fonts)
100
+ * 2. .{slug}-theme { } — all primitives (colors, spacing, radius, typography,
101
+ * shadows, motion, misc)
102
+ * 3. .dark .{slug}-theme { } — dark semantic tokens
103
+ * @media (prefers-color-scheme: dark) { .{slug}-theme:not(.light) { } }
104
+ * 4. html:not(.dark) .{slug}-theme { } — light semantic tokens
105
+ * 5. Fumadocs bridge — .dark/.light scoped to theme class
106
+ */
107
+
108
+ /**
109
+ * Generate docs-site CSS for a theme.
110
+ *
111
+ * Output is class-scoped (.{slug}-theme) with no @layer wrapping,
112
+ * matching the hand-written theme files in packages/docs/app/.
113
+ */
114
+ declare function docsAdapter(input: AdapterInput, options?: DocsAdapterOptions): string;
115
+
116
+ /**
117
+ * CSS @layer utilities for adapter output.
118
+ *
119
+ * Establishes a specificity ordering so theme overrides work
120
+ * without !important. Layers are adapter-only — the base
121
+ * generateFullBundleCss output is not wrapped in layers.
122
+ */
123
+ /** Layer order declaration — must appear before any @layer blocks. */
124
+ declare const LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
125
+ /**
126
+ * Wrap CSS content in a named @layer block.
127
+ */
128
+ declare function wrapInLayer(layerName: string, css: string): string;
129
+
130
+ /**
131
+ * Fumadocs Bridge Token Mapping
132
+ *
133
+ * Maps fumadocs --color-fd-* tokens to Visor semantic token references.
134
+ * Used by the fumadocs adapter to auto-generate Section 4 (framework bridge)
135
+ * of theme CSS files.
136
+ */
137
+ interface FumadocsBridgeEntry {
138
+ /** The Visor semantic token name (e.g., "page", "primary") */
139
+ visorToken: string;
140
+ /** Which semantic token category to look up */
141
+ category: "text" | "surface" | "border" | "interactive";
142
+ }
143
+ /**
144
+ * Static mapping from fumadocs tokens to Visor semantic tokens.
145
+ *
146
+ * Reference: packages/docs/app/neutral-theme.css Section 4
147
+ */
148
+ declare const FUMADOCS_BRIDGE_MAP: Record<string, FumadocsBridgeEntry>;
149
+
150
+ export { type AdapterInput, type AdapterName, type AdapterOptions, type DeckAdapterOptions, type DocsAdapterOptions, FUMADOCS_BRIDGE_MAP, type FumadocsBridgeEntry, LAYER_ORDER, type NextJSAdapterOptions, deckAdapter, docsAdapter, fumadocsAdapter, nextjsAdapter, wrapInLayer };
@@ -0,0 +1,537 @@
1
+ import {
2
+ FULL_SHADE_STEPS,
3
+ SELECTIVE_SHADE_STEPS,
4
+ buildVisorFontUrl,
5
+ generateDarkCss,
6
+ generateLightCss,
7
+ generatePrimitivesCss,
8
+ header,
9
+ resolveThemeFonts,
10
+ sectionComment
11
+ } from "../chunk-ZLXFCNYF.js";
12
+
13
+ // src/adapters/layers.ts
14
+ var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
15
+ function wrapInLayer(layerName, css) {
16
+ const trimmed = css.trim();
17
+ if (!trimmed) return "";
18
+ return `@layer ${layerName} {
19
+ ${trimmed}
20
+ }`;
21
+ }
22
+
23
+ // src/adapters/nextjs.ts
24
+ function nextjsAdapter(input, options) {
25
+ const includeFontImports = options?.includeFontImports ?? true;
26
+ const includeFowt = options?.includeFowt ?? true;
27
+ const lines = [];
28
+ lines.push(header("Visor Theme \u2014 NextJS Adapter"));
29
+ if (includeFontImports && input.config.typography) {
30
+ const fontResult = resolveThemeFonts(input.config.typography);
31
+ const googleFonts = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono].filter(
32
+ (r) => r !== null && r.source === "google-fonts"
33
+ );
34
+ const seenUrls = /* @__PURE__ */ new Set();
35
+ for (const font of googleFonts) {
36
+ if (font?.cssUrl && !seenUrls.has(font.cssUrl)) {
37
+ seenUrls.add(font.cssUrl);
38
+ lines.push(`@import url("${font.cssUrl}");`);
39
+ }
40
+ }
41
+ if (seenUrls.size > 0) {
42
+ lines.push("");
43
+ lines.push(
44
+ "/*",
45
+ " * Note: If using next/font, remove the @import above and configure",
46
+ " * fonts in your layout.tsx to avoid double-loading. See:",
47
+ " * https://nextjs.org/docs/app/building-your-application/optimizing/fonts",
48
+ " */"
49
+ );
50
+ lines.push("");
51
+ }
52
+ const visorFonts = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono].filter(
53
+ (r) => r !== null && r.source === "visor-fonts"
54
+ );
55
+ const seenVisorFamilies = /* @__PURE__ */ new Set();
56
+ for (const font of visorFonts) {
57
+ if (seenVisorFamilies.has(font.family)) continue;
58
+ seenVisorFamilies.add(font.family);
59
+ for (const weight of font.weights) {
60
+ const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
61
+ lines.push(`@font-face {`);
62
+ lines.push(` font-family: "${font.family}";`);
63
+ lines.push(` src: url("${url}") format("woff2");`);
64
+ lines.push(` font-weight: ${weight};`);
65
+ lines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
66
+ lines.push(` font-display: ${font.display};`);
67
+ lines.push(`}`);
68
+ lines.push("");
69
+ }
70
+ }
71
+ }
72
+ lines.push(LAYER_ORDER);
73
+ lines.push("");
74
+ const primitivesBody = stripHeader(
75
+ generatePrimitivesCss(input.primitives, input.config)
76
+ );
77
+ lines.push(wrapInLayer("visor-primitives", primitivesBody));
78
+ lines.push("");
79
+ const lightBody = stripHeader(generateLightCss(input.tokens));
80
+ const darkBody = stripHeader(generateDarkCss(input.tokens));
81
+ lines.push(
82
+ wrapInLayer("visor-adaptive", lightBody + "\n\n" + darkBody)
83
+ );
84
+ if (includeFowt) {
85
+ lines.push("");
86
+ lines.push(
87
+ "/*",
88
+ " * FOWT Prevention: Add this blocking script to your <head> before",
89
+ " * any stylesheets to prevent flash of wrong theme:",
90
+ " *",
91
+ " * import { FOWT_SCRIPT } from '@loworbitstudio/visor-theme-engine/fowt';",
92
+ " *",
93
+ " * In your layout.tsx <head>:",
94
+ " * <script>",
95
+ " * {FOWT_SCRIPT}",
96
+ " * </script>",
97
+ " */"
98
+ );
99
+ }
100
+ return lines.join("\n") + "\n";
101
+ }
102
+ function stripHeader(css) {
103
+ const headerEndMarker = "============================================ */";
104
+ const idx = css.indexOf(headerEndMarker);
105
+ if (idx === -1) return css;
106
+ return css.slice(idx + headerEndMarker.length).trim();
107
+ }
108
+
109
+ // src/adapters/fumadocs-map.ts
110
+ var FUMADOCS_BRIDGE_MAP = {
111
+ "fd-background": { visorToken: "page", category: "surface" },
112
+ "fd-foreground": { visorToken: "primary", category: "text" },
113
+ "fd-card": { visorToken: "card", category: "surface" },
114
+ "fd-card-foreground": { visorToken: "primary", category: "text" },
115
+ "fd-border": { visorToken: "default", category: "border" },
116
+ "fd-muted": { visorToken: "muted", category: "surface" },
117
+ "fd-muted-foreground": { visorToken: "secondary", category: "text" },
118
+ "fd-accent": { visorToken: "accent-default", category: "surface" },
119
+ "fd-accent-foreground": {
120
+ visorToken: "primary-text",
121
+ category: "interactive"
122
+ },
123
+ "fd-primary": { visorToken: "accent-default", category: "surface" },
124
+ "fd-primary-foreground": {
125
+ visorToken: "primary-text",
126
+ category: "interactive"
127
+ },
128
+ "fd-secondary": { visorToken: "muted", category: "surface" },
129
+ "fd-secondary-foreground": { visorToken: "primary", category: "text" },
130
+ "fd-popover": { visorToken: "card", category: "surface" },
131
+ "fd-popover-foreground": { visorToken: "primary", category: "text" },
132
+ "fd-ring": { visorToken: "focus", category: "border" }
133
+ };
134
+
135
+ // src/adapters/fumadocs.ts
136
+ function fumadocsAdapter(input) {
137
+ const lines = [];
138
+ lines.push(header("Visor Theme \u2014 Fumadocs Bridge"));
139
+ const darkDecls = [];
140
+ const lightDecls = [];
141
+ for (const [fdToken, entry] of Object.entries(FUMADOCS_BRIDGE_MAP)) {
142
+ const tokenMap = input.tokens[entry.category];
143
+ const value = tokenMap?.[entry.visorToken];
144
+ if (!value) {
145
+ darkDecls.push(` /* --color-${fdToken}: unmapped */`);
146
+ lightDecls.push(` /* --color-${fdToken}: unmapped */`);
147
+ continue;
148
+ }
149
+ darkDecls.push(` --color-${fdToken}: ${value.dark};`);
150
+ lightDecls.push(` --color-${fdToken}: ${value.light};`);
151
+ }
152
+ const bridgeCss = [
153
+ ".dark {",
154
+ ...darkDecls,
155
+ "}",
156
+ "",
157
+ "html:not(.dark) {",
158
+ ...lightDecls,
159
+ "}"
160
+ ].join("\n");
161
+ lines.push(wrapInLayer("visor-bridge", bridgeCss));
162
+ lines.push("");
163
+ return lines.join("\n") + "\n";
164
+ }
165
+
166
+ // src/adapters/deck.ts
167
+ var FULL_SCALE_ROLES = ["primary", "accent", "neutral"];
168
+ var SELECTIVE_SCALE_ROLES = [
169
+ "success",
170
+ "warning",
171
+ "error",
172
+ "info"
173
+ ];
174
+ function toKebabCase(name) {
175
+ return name.toLowerCase().replace(/\s+/g, "-");
176
+ }
177
+ function generateScopedPrimitives(primitives, config) {
178
+ const decls = [];
179
+ decls.push("--color-white: #ffffff;");
180
+ decls.push("--color-black: #000000;");
181
+ const allRoles = [...FULL_SCALE_ROLES, ...SELECTIVE_SCALE_ROLES];
182
+ for (const role of allRoles) {
183
+ const scale = primitives[role];
184
+ const steps = FULL_SCALE_ROLES.includes(role) ? FULL_SHADE_STEPS : SELECTIVE_SHADE_STEPS;
185
+ for (const step of steps) {
186
+ const value = scale[step];
187
+ decls.push(`--color-${role}-${step}: ${value};`);
188
+ }
189
+ }
190
+ const multipliers = [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24];
191
+ for (const m of multipliers) {
192
+ const px = config.spacing.base * m;
193
+ const rem = px === 0 ? "0" : `${px / 16}rem`;
194
+ decls.push(`--spacing-${m}: ${rem};`);
195
+ }
196
+ decls.push("--radius-none: 0;");
197
+ decls.push(`--radius-sm: ${config.radius.sm / 16}rem;`);
198
+ decls.push(`--radius-md: ${config.radius.md / 16}rem;`);
199
+ decls.push(`--radius-lg: ${config.radius.lg / 16}rem;`);
200
+ decls.push(`--radius-xl: ${config.radius.xl / 16}rem;`);
201
+ decls.push(`--radius-full: ${config.radius.pill}px;`);
202
+ decls.push(`--font-heading: ${config.typography.heading.family};`);
203
+ decls.push(`--font-display: ${config.typography.display.family};`);
204
+ decls.push(`--font-sans: ${config.typography.body.family};`);
205
+ decls.push(`--font-body: ${config.typography.body.family};`);
206
+ decls.push(`--font-mono: ${config.typography.mono.family};`);
207
+ return decls;
208
+ }
209
+ function generateSemanticDecls(tokens, mode) {
210
+ const decls = [];
211
+ for (const [name, values] of Object.entries(tokens.text)) {
212
+ decls.push(`--text-${name}: ${values[mode]};`);
213
+ }
214
+ for (const [name, values] of Object.entries(tokens.surface)) {
215
+ decls.push(`--surface-${name}: ${values[mode]};`);
216
+ }
217
+ for (const [name, values] of Object.entries(tokens.border)) {
218
+ decls.push(`--border-${name}: ${values[mode]};`);
219
+ }
220
+ for (const [name, values] of Object.entries(tokens.interactive)) {
221
+ decls.push(`--interactive-${name}: ${values[mode]};`);
222
+ }
223
+ return decls;
224
+ }
225
+ function deckAdapter(input, options) {
226
+ const scopeClass = options?.scopeClass ?? `.deck--${toKebabCase(input.config.name)}`;
227
+ const lines = [];
228
+ lines.push(header(`Visor Theme \u2014 Deck Adapter (${scopeClass})`));
229
+ const primDecls = generateScopedPrimitives(input.primitives, input.config);
230
+ const lightDecls = generateSemanticDecls(input.tokens, "light");
231
+ const lightBlock = [
232
+ `${scopeClass} {`,
233
+ sectionComment("Primitives"),
234
+ ...primDecls.map((d) => ` ${d}`),
235
+ "",
236
+ sectionComment("Semantic (light)"),
237
+ ...lightDecls.map((d) => ` ${d}`),
238
+ "}"
239
+ ].join("\n");
240
+ const darkDecls = generateSemanticDecls(input.tokens, "dark");
241
+ const darkBlock = [
242
+ `.dark ${scopeClass} {`,
243
+ ...darkDecls.map((d) => ` ${d}`),
244
+ "}",
245
+ "",
246
+ `@media (prefers-color-scheme: dark) {`,
247
+ ` ${scopeClass}:not(.light) {`,
248
+ ...darkDecls.map((d) => ` ${d}`),
249
+ ` }`,
250
+ `}`
251
+ ].join("\n");
252
+ lines.push(wrapInLayer("visor-adaptive", lightBlock + "\n\n" + darkBlock));
253
+ lines.push("");
254
+ return lines.join("\n") + "\n";
255
+ }
256
+
257
+ // src/adapters/docs.ts
258
+ var FULL_SCALE_ROLES2 = ["primary", "accent", "neutral"];
259
+ var SELECTIVE_SCALE_ROLES2 = ["success", "warning", "error", "info"];
260
+ function toKebabCase2(name) {
261
+ return name.toLowerCase().replace(/\s+/g, "-");
262
+ }
263
+ function generateColorDecls(primitives) {
264
+ const decls = [];
265
+ decls.push("--color-white: #ffffff;");
266
+ decls.push("--color-black: #000000;");
267
+ const allRoles = [...FULL_SCALE_ROLES2, ...SELECTIVE_SCALE_ROLES2];
268
+ for (const role of allRoles) {
269
+ const scale = primitives[role];
270
+ const steps = FULL_SCALE_ROLES2.includes(role) ? FULL_SHADE_STEPS : SELECTIVE_SHADE_STEPS;
271
+ for (const step of steps) {
272
+ const value = scale[step];
273
+ decls.push(`--color-${role}-${step}: ${value};`);
274
+ }
275
+ }
276
+ return decls;
277
+ }
278
+ function generateSpacingDecls(config) {
279
+ const multipliers = [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24];
280
+ return multipliers.map((m) => {
281
+ const px = config.spacing.base * m;
282
+ const rem = px === 0 ? "0" : `${px / 16}rem`;
283
+ return `--spacing-${m}: ${rem};`;
284
+ });
285
+ }
286
+ function generateRadiusDecls(config) {
287
+ return [
288
+ "--radius-none: 0;",
289
+ `--radius-sm: ${config.radius.sm / 16}rem; /* ${config.radius.sm}px */`,
290
+ `--radius-md: ${config.radius.md / 16}rem; /* ${config.radius.md}px */`,
291
+ `--radius-lg: ${config.radius.lg / 16}rem; /* ${config.radius.lg}px */`,
292
+ `--radius-xl: ${config.radius.xl / 16}rem; /* ${config.radius.xl}px */`,
293
+ `--radius-2xl: ${config.radius.xl * 1.333 / 16}rem; /* ${Math.round(config.radius.xl * 1.333)}px */`,
294
+ `--radius-3xl: ${config.radius.xl * 2 / 16}rem; /* ${config.radius.xl * 2}px */`,
295
+ `--radius-full: ${config.radius.pill}px;`
296
+ ];
297
+ }
298
+ function generateTypographyDecls(config) {
299
+ const decls = [];
300
+ decls.push(`--font-display: ${config.typography.display.family};`);
301
+ decls.push(`--font-sans: ${config.typography.body.family};`);
302
+ decls.push(`--font-heading: var(--font-sans);`);
303
+ decls.push(`--font-body: ${config.typography.body.family};`);
304
+ decls.push(`--font-mono: ${config.typography.mono.family};`);
305
+ const fontSizes = {
306
+ xs: 12,
307
+ sm: 14,
308
+ base: 16,
309
+ lg: 18,
310
+ xl: 20,
311
+ "2xl": 24,
312
+ "3xl": 30,
313
+ "4xl": 36
314
+ };
315
+ for (const [name, px] of Object.entries(fontSizes)) {
316
+ decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
317
+ }
318
+ decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
319
+ decls.push("--font-weight-medium: 500;");
320
+ decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
321
+ decls.push("--font-weight-bold: 700;");
322
+ const lineHeights = {
323
+ none: 1,
324
+ tight: 1.25,
325
+ snug: 1.375,
326
+ normal: 1.5,
327
+ relaxed: 1.625,
328
+ loose: 2
329
+ };
330
+ for (const [name, value] of Object.entries(lineHeights)) {
331
+ decls.push(`--line-height-${name}: ${value};`);
332
+ }
333
+ decls.push("--letter-spacing-normal: 0.05em;");
334
+ return decls;
335
+ }
336
+ function generateShadowDecls(config) {
337
+ return [
338
+ `--shadow-xs: ${config.shadows.xs};`,
339
+ `--shadow-sm: ${config.shadows.sm};`,
340
+ `--shadow-md: ${config.shadows.md};`,
341
+ `--shadow-lg: ${config.shadows.lg};`,
342
+ `--shadow-xl: ${config.shadows.xl};`
343
+ ];
344
+ }
345
+ function generateMotionDecls(config) {
346
+ return [
347
+ `--motion-duration-100: ${config.motion["duration-fast"]};`,
348
+ "--motion-duration-150: 150ms;",
349
+ `--motion-duration-200: ${config.motion["duration-normal"]};`,
350
+ "--motion-duration-300: 300ms;",
351
+ `--motion-duration-500: ${config.motion["duration-slow"]};`,
352
+ "--motion-duration-800: 800ms;",
353
+ "--motion-easing-linear: linear;",
354
+ "--motion-easing-ease-in: cubic-bezier(0.4, 0, 1, 1);",
355
+ "--motion-easing-ease-out: cubic-bezier(0, 0, 0.2, 1);",
356
+ `--motion-easing-ease-in-out: ${config.motion.easing};`,
357
+ "--motion-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);"
358
+ ];
359
+ }
360
+ function generateMiscDecls() {
361
+ return [
362
+ "--border-width-1: 1px;",
363
+ "--border-width-2: 2px;",
364
+ "--border-width-3: 3px;",
365
+ "--border-width-4: 4px;",
366
+ "--z-base: 0;",
367
+ "--z-raised: 1;",
368
+ "--z-dropdown: 1000;",
369
+ "--z-sticky: 1100;",
370
+ "--z-modal: 1300;",
371
+ "--z-popover: 1400;",
372
+ "--z-toast: 1500;",
373
+ "--overlay-bg: rgba(0, 0, 0, 0.5);",
374
+ "--focus-ring-width: 2px;",
375
+ "--focus-ring-offset: 2px;"
376
+ ];
377
+ }
378
+ function generateSemanticDecls2(tokens, mode) {
379
+ const decls = [];
380
+ for (const [name, values] of Object.entries(tokens.text)) {
381
+ decls.push(`--text-${name}: ${values[mode]};`);
382
+ }
383
+ for (const [name, values] of Object.entries(tokens.surface)) {
384
+ decls.push(`--surface-${name}: ${values[mode]};`);
385
+ }
386
+ for (const [name, values] of Object.entries(tokens.border)) {
387
+ decls.push(`--border-${name}: ${values[mode]};`);
388
+ }
389
+ for (const [name, values] of Object.entries(tokens.interactive)) {
390
+ decls.push(`--interactive-${name}: ${values[mode]};`);
391
+ }
392
+ return decls;
393
+ }
394
+ function generateFumadocsBridgeDecls(tokens, mode) {
395
+ const decls = [];
396
+ for (const [fdToken, entry] of Object.entries(FUMADOCS_BRIDGE_MAP)) {
397
+ const tokenMap = tokens[entry.category];
398
+ const value = tokenMap?.[entry.visorToken];
399
+ if (!value) {
400
+ decls.push(`/* --color-${fdToken}: unmapped */`);
401
+ } else {
402
+ decls.push(`--color-${fdToken}: ${value[mode]};`);
403
+ }
404
+ }
405
+ return decls;
406
+ }
407
+ function block(selector, decls) {
408
+ return [`${selector} {`, ...decls.map((d) => ` ${d}`), "}"].join("\n");
409
+ }
410
+ function sectionComment2(label) {
411
+ return `
412
+ /* --- ${label} --- */`;
413
+ }
414
+ function docsAdapter(input, options) {
415
+ const slug = toKebabCase2(input.config.name);
416
+ const scopeClass = `.${slug}-theme`;
417
+ const includeFontImports = options?.includeFontImports ?? true;
418
+ const lines = [];
419
+ if (includeFontImports && input.config.typography) {
420
+ const fontResult = resolveThemeFonts(input.config.typography);
421
+ const fontSlots = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono];
422
+ const seenUrls = /* @__PURE__ */ new Set();
423
+ for (const font of fontSlots) {
424
+ if (font && font.source === "google-fonts" && font.cssUrl && !seenUrls.has(font.cssUrl)) {
425
+ seenUrls.add(font.cssUrl);
426
+ lines.push(`@import url("${font.cssUrl}");`);
427
+ lines.push("");
428
+ }
429
+ }
430
+ const seenFamilies = /* @__PURE__ */ new Set();
431
+ for (const font of fontSlots) {
432
+ if (font && font.source === "visor-fonts" && !seenFamilies.has(font.family)) {
433
+ seenFamilies.add(font.family);
434
+ for (const weight of font.weights) {
435
+ const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
436
+ lines.push("@font-face {");
437
+ lines.push(` font-family: "${font.family}";`);
438
+ lines.push(` src: url("${url}") format("woff2");`);
439
+ lines.push(` font-weight: ${weight};`);
440
+ lines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
441
+ lines.push(` font-display: ${font.display};`);
442
+ lines.push("}");
443
+ lines.push("");
444
+ }
445
+ }
446
+ }
447
+ }
448
+ lines.push("\n/* \u2500\u2500 Section 1: Shared tokens (mode-independent) \u2500\u2500 */");
449
+ const sharedDecls = [
450
+ "min-height: 100vh;",
451
+ "font-size: 1rem;",
452
+ `background: var(--surface-page, var(--surface-background));`,
453
+ "color: var(--text-primary);",
454
+ "font-family: var(--font-sans);"
455
+ ];
456
+ lines.push(sectionComment2("Primitive: Colors"));
457
+ lines.push(block(scopeClass, [
458
+ ...sharedDecls,
459
+ "",
460
+ ...generateColorDecls(input.primitives)
461
+ ]));
462
+ lines.push("");
463
+ lines.push(sectionComment2("Primitive: Spacing"));
464
+ lines.push(block(scopeClass, generateSpacingDecls(input.config)));
465
+ lines.push("");
466
+ lines.push(sectionComment2("Primitive: Border Radius"));
467
+ lines.push(block(scopeClass, generateRadiusDecls(input.config)));
468
+ lines.push("");
469
+ lines.push(sectionComment2("Primitive: Typography"));
470
+ lines.push(block(scopeClass, generateTypographyDecls(input.config)));
471
+ lines.push("");
472
+ lines.push(sectionComment2("Primitive: Shadows"));
473
+ lines.push(block(scopeClass, generateShadowDecls(input.config)));
474
+ lines.push("");
475
+ lines.push(sectionComment2("Primitive: Motion"));
476
+ lines.push(block(scopeClass, generateMotionDecls(input.config)));
477
+ lines.push("");
478
+ lines.push(sectionComment2("Primitive: Miscellaneous"));
479
+ lines.push(block(scopeClass, generateMiscDecls()));
480
+ lines.push("");
481
+ lines.push("\n/* \u2500\u2500 Section 2: Dark mode overrides \u2500\u2500 */");
482
+ const darkDecls = generateSemanticDecls2(input.tokens, "dark");
483
+ const categories = ["Text", "Surface", "Border", "Interactive"];
484
+ const categoryDecls = [
485
+ Object.entries(input.tokens.text).map(([n, v]) => `--text-${n}: ${v.dark};`),
486
+ Object.entries(input.tokens.surface).map(([n, v]) => `--surface-${n}: ${v.dark};`),
487
+ Object.entries(input.tokens.border).map(([n, v]) => `--border-${n}: ${v.dark};`),
488
+ Object.entries(input.tokens.interactive).map(([n, v]) => `--interactive-${n}: ${v.dark};`)
489
+ ];
490
+ for (let i = 0; i < categories.length; i++) {
491
+ lines.push(sectionComment2(`Adaptive: ${categories[i]} (dark) \u2014 manual toggle`));
492
+ lines.push(block(`.dark ${scopeClass}`, categoryDecls[i]));
493
+ lines.push("");
494
+ }
495
+ const pcsCategories = [
496
+ { label: "Text", entries: Object.entries(input.tokens.text).map(([n, v]) => `--text-${n}: ${v.dark};`) },
497
+ { label: "Surface", entries: Object.entries(input.tokens.surface).map(([n, v]) => `--surface-${n}: ${v.dark};`) },
498
+ { label: "Border", entries: Object.entries(input.tokens.border).map(([n, v]) => `--border-${n}: ${v.dark};`) },
499
+ { label: "Interactive", entries: Object.entries(input.tokens.interactive).map(([n, v]) => `--interactive-${n}: ${v.dark};`) }
500
+ ];
501
+ for (const cat of pcsCategories) {
502
+ lines.push(sectionComment2(`Adaptive: ${cat.label} (dark) \u2014 prefers-color-scheme`));
503
+ const inner = block(`${scopeClass}:not(.light)`, cat.entries);
504
+ lines.push(`@media (prefers-color-scheme: dark) {
505
+ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
506
+ }`);
507
+ lines.push("");
508
+ }
509
+ lines.push("\n/* \u2500\u2500 Section 3: Light mode overrides \u2500\u2500 */");
510
+ const lightCategoryDecls = [
511
+ { label: "Text", entries: Object.entries(input.tokens.text).map(([n, v]) => `--text-${n}: ${v.light};`) },
512
+ { label: "Surface", entries: Object.entries(input.tokens.surface).map(([n, v]) => `--surface-${n}: ${v.light};`) },
513
+ { label: "Border", entries: Object.entries(input.tokens.border).map(([n, v]) => `--border-${n}: ${v.light};`) },
514
+ { label: "Interactive", entries: Object.entries(input.tokens.interactive).map(([n, v]) => `--interactive-${n}: ${v.light};`) }
515
+ ];
516
+ for (const cat of lightCategoryDecls) {
517
+ lines.push(sectionComment2(`Adaptive: ${cat.label} (light)`));
518
+ lines.push(block(`html:not(.dark) ${scopeClass}`, cat.entries));
519
+ lines.push("");
520
+ }
521
+ lines.push(sectionComment2("Fumadocs bridge: dark"));
522
+ lines.push(block(`.dark ${scopeClass}`, generateFumadocsBridgeDecls(input.tokens, "dark")));
523
+ lines.push("");
524
+ lines.push(sectionComment2("Fumadocs bridge: light"));
525
+ lines.push(block(`html:not(.dark) ${scopeClass}`, generateFumadocsBridgeDecls(input.tokens, "light")));
526
+ lines.push("");
527
+ return lines.join("\n") + "\n";
528
+ }
529
+ export {
530
+ FUMADOCS_BRIDGE_MAP,
531
+ LAYER_ORDER,
532
+ deckAdapter,
533
+ docsAdapter,
534
+ fumadocsAdapter,
535
+ nextjsAdapter,
536
+ wrapInLayer
537
+ };