@dryui/theme-wizard 4.0.0 → 5.0.1

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 (47) hide show
  1. package/package.json +5 -5
  2. package/dist/actions.d.ts +0 -4
  3. package/dist/actions.js +0 -9
  4. package/dist/components/AlphaSlider.svelte +0 -13
  5. package/dist/components/AlphaSlider.svelte.d.ts +0 -9
  6. package/dist/components/ContrastBadge.svelte +0 -22
  7. package/dist/components/ContrastBadge.svelte.d.ts +0 -8
  8. package/dist/components/HsbPicker.svelte +0 -304
  9. package/dist/components/HsbPicker.svelte.d.ts +0 -9
  10. package/dist/components/StepIndicator.svelte +0 -87
  11. package/dist/components/StepIndicator.svelte.d.ts +0 -7
  12. package/dist/components/TokenPreview.svelte +0 -55
  13. package/dist/components/TokenPreview.svelte.d.ts +0 -8
  14. package/dist/components/WizardShell.svelte +0 -140
  15. package/dist/components/WizardShell.svelte.d.ts +0 -15
  16. package/dist/engine/derivation.d.ts +0 -282
  17. package/dist/engine/derivation.js +0 -1445
  18. package/dist/engine/derivation.test.d.ts +0 -1
  19. package/dist/engine/derivation.test.js +0 -956
  20. package/dist/engine/export-css.d.ts +0 -32
  21. package/dist/engine/export-css.js +0 -90
  22. package/dist/engine/export-css.test.d.ts +0 -1
  23. package/dist/engine/export-css.test.js +0 -78
  24. package/dist/engine/index.d.ts +0 -10
  25. package/dist/engine/index.js +0 -6
  26. package/dist/engine/palette.d.ts +0 -16
  27. package/dist/engine/palette.js +0 -44
  28. package/dist/engine/presets.d.ts +0 -13
  29. package/dist/engine/presets.js +0 -124
  30. package/dist/engine/url-codec.d.ts +0 -53
  31. package/dist/engine/url-codec.js +0 -243
  32. package/dist/engine/url-codec.test.d.ts +0 -1
  33. package/dist/engine/url-codec.test.js +0 -137
  34. package/dist/index.d.ts +0 -15
  35. package/dist/index.js +0 -19
  36. package/dist/state.svelte.d.ts +0 -104
  37. package/dist/state.svelte.js +0 -574
  38. package/dist/steps/BrandColor.svelte +0 -216
  39. package/dist/steps/BrandColor.svelte.d.ts +0 -6
  40. package/dist/steps/Personality.svelte +0 -319
  41. package/dist/steps/Personality.svelte.d.ts +0 -3
  42. package/dist/steps/PreviewExport.svelte +0 -115
  43. package/dist/steps/PreviewExport.svelte.d.ts +0 -9
  44. package/dist/steps/Shape.svelte +0 -121
  45. package/dist/steps/Shape.svelte.d.ts +0 -18
  46. package/dist/steps/Typography.svelte +0 -115
  47. package/dist/steps/Typography.svelte.d.ts +0 -18
@@ -1,32 +0,0 @@
1
- import type { ThemeTokens } from './derivation.js';
2
- export interface GenerateCssOptions {
3
- personalityTokens?: Record<string, string>;
4
- shapeTokens?: Record<string, string>;
5
- shadowTokens?: {
6
- light: Record<string, string>;
7
- dark: Record<string, string>;
8
- };
9
- }
10
- /**
11
- * Generate CSS files from a ThemeTokens object.
12
- *
13
- * Returns:
14
- * defaultCss — `:root { ... }` block with all light tokens.
15
- * darkCss — `[data-theme="dark"] { ... }` block plus `.theme-auto` media query.
16
- */
17
- export declare function generateCss(theme: ThemeTokens, options?: GenerateCssOptions): {
18
- defaultCss: string;
19
- darkCss: string;
20
- };
21
- /**
22
- * Trigger a browser download of the combined CSS as a single file.
23
- */
24
- export declare function downloadCss(theme: ThemeTokens, filename?: string, options?: GenerateCssOptions): void;
25
- /**
26
- * Copy the combined CSS to the clipboard.
27
- */
28
- export declare function copyCss(theme: ThemeTokens, options?: GenerateCssOptions): Promise<void>;
29
- /**
30
- * Return a JSON string containing the light and dark token maps.
31
- */
32
- export declare function exportJson(theme: ThemeTokens): string;
@@ -1,90 +0,0 @@
1
- /**
2
- * Generate CSS files from a ThemeTokens object.
3
- *
4
- * Returns:
5
- * defaultCss — `:root { ... }` block with all light tokens.
6
- * darkCss — `[data-theme="dark"] { ... }` block plus `.theme-auto` media query.
7
- */
8
- export function generateCss(theme, options) {
9
- // Build default.css (:root block)
10
- const defaultLines = [':root {'];
11
- for (const [token, value] of Object.entries(theme.light)) {
12
- defaultLines.push(` ${token}: ${value};`);
13
- }
14
- // Shadow tokens (light mode)
15
- if (options?.shadowTokens) {
16
- for (const [token, value] of Object.entries(options.shadowTokens.light)) {
17
- defaultLines.push(` ${token}: ${value};`);
18
- }
19
- }
20
- // Shape tokens (mode-independent, go in :root)
21
- if (options?.shapeTokens) {
22
- for (const [token, value] of Object.entries(options.shapeTokens)) {
23
- defaultLines.push(` ${token}: ${value};`);
24
- }
25
- }
26
- // Personality tokens (use semantic refs, mode-independent, go in :root)
27
- if (options?.personalityTokens) {
28
- for (const [token, value] of Object.entries(options.personalityTokens)) {
29
- defaultLines.push(` ${token}: ${value};`);
30
- }
31
- }
32
- defaultLines.push('}');
33
- const defaultCss = defaultLines.join('\n');
34
- // Build dark.css ([data-theme="dark"] + .theme-auto media query)
35
- const darkEntries = Object.entries(theme.dark);
36
- const darkShadowEntries = options?.shadowTokens ? Object.entries(options.shadowTokens.dark) : [];
37
- const darkLines = ['[data-theme="dark"] {'];
38
- for (const [token, value] of darkEntries) {
39
- darkLines.push(` ${token}: ${value};`);
40
- }
41
- for (const [token, value] of darkShadowEntries) {
42
- darkLines.push(` ${token}: ${value};`);
43
- }
44
- darkLines.push('}');
45
- darkLines.push('');
46
- darkLines.push('.theme-auto {');
47
- darkLines.push(' @media (prefers-color-scheme: dark) {');
48
- darkLines.push(' & {');
49
- for (const [token, value] of darkEntries) {
50
- darkLines.push(` ${token}: ${value};`);
51
- }
52
- for (const [token, value] of darkShadowEntries) {
53
- darkLines.push(` ${token}: ${value};`);
54
- }
55
- darkLines.push(' }');
56
- darkLines.push(' }');
57
- darkLines.push('}');
58
- const darkCss = darkLines.join('\n');
59
- return { defaultCss, darkCss };
60
- }
61
- /**
62
- * Trigger a browser download of the combined CSS as a single file.
63
- */
64
- export function downloadCss(theme, filename = 'dryui-theme.css', options) {
65
- const { defaultCss, darkCss } = generateCss(theme, options);
66
- const combined = `${defaultCss}\n\n${darkCss}`;
67
- const blob = new Blob([combined], { type: 'text/css' });
68
- const url = URL.createObjectURL(blob);
69
- const a = document.createElement('a');
70
- a.href = url;
71
- a.download = filename;
72
- document.body.appendChild(a);
73
- a.click();
74
- document.body.removeChild(a);
75
- URL.revokeObjectURL(url);
76
- }
77
- /**
78
- * Copy the combined CSS to the clipboard.
79
- */
80
- export async function copyCss(theme, options) {
81
- const { defaultCss, darkCss } = generateCss(theme, options);
82
- const combined = `${defaultCss}\n\n${darkCss}`;
83
- await navigator.clipboard.writeText(combined);
84
- }
85
- /**
86
- * Return a JSON string containing the light and dark token maps.
87
- */
88
- export function exportJson(theme) {
89
- return JSON.stringify({ light: theme.light, dark: theme.dark }, null, 2);
90
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,78 +0,0 @@
1
- // apps/docs/src/lib/theme-wizard/export-css.test.ts
2
- import { describe, test, expect } from 'bun:test';
3
- import { generateCss, exportJson } from './export-css.js';
4
- import { generateTheme } from './derivation.js';
5
- const defaultBrand = generateTheme({ h: 230, s: 65, b: 85 });
6
- describe('generateCss', () => {
7
- test('defaultCss contains a :root block', () => {
8
- const { defaultCss } = generateCss(defaultBrand);
9
- expect(defaultCss).toContain(':root {');
10
- });
11
- test('defaultCss contains all light tokens', () => {
12
- const { defaultCss } = generateCss(defaultBrand);
13
- for (const [token] of Object.entries(defaultBrand.light)) {
14
- expect(defaultCss).toContain(token);
15
- }
16
- });
17
- test('defaultCss light token values are present', () => {
18
- const { defaultCss } = generateCss(defaultBrand);
19
- for (const [token, value] of Object.entries(defaultBrand.light)) {
20
- expect(defaultCss).toContain(`${token}: ${value}`);
21
- }
22
- });
23
- test('darkCss contains [data-theme="dark"] block', () => {
24
- const { darkCss } = generateCss(defaultBrand);
25
- expect(darkCss).toContain('[data-theme="dark"] {');
26
- });
27
- test('darkCss contains all dark tokens', () => {
28
- const { darkCss } = generateCss(defaultBrand);
29
- for (const [token] of Object.entries(defaultBrand.dark)) {
30
- expect(darkCss).toContain(token);
31
- }
32
- });
33
- test('darkCss contains .theme-auto media query', () => {
34
- const { darkCss } = generateCss(defaultBrand);
35
- expect(darkCss).toContain('.theme-auto {');
36
- expect(darkCss).toContain('@media (prefers-color-scheme: dark)');
37
- });
38
- test('darkCss .theme-auto block uses & selector', () => {
39
- const { darkCss } = generateCss(defaultBrand);
40
- expect(darkCss).toContain('& {');
41
- });
42
- test('darkCss includes dark token values inside .theme-auto', () => {
43
- const { darkCss } = generateCss(defaultBrand);
44
- // Pick an arbitrary dark token and verify it appears twice (in both blocks)
45
- const firstToken = Object.keys(defaultBrand.dark)[0] ?? '--dry-color-text-strong';
46
- const occurrences = darkCss.split(firstToken).length - 1;
47
- expect(occurrences).toBeGreaterThanOrEqual(2);
48
- });
49
- test('different brand colors produce different CSS', () => {
50
- const oceanTheme = generateTheme({ h: 200, s: 80, b: 70 });
51
- const { defaultCss: defaultDefault } = generateCss(defaultBrand);
52
- const { defaultCss: defaultOcean } = generateCss(oceanTheme);
53
- expect(defaultDefault).not.toBe(defaultOcean);
54
- });
55
- });
56
- describe('exportJson', () => {
57
- test('returns valid JSON', () => {
58
- const json = exportJson(defaultBrand);
59
- expect(() => JSON.parse(json)).not.toThrow();
60
- });
61
- test('JSON contains light and dark keys', () => {
62
- const parsed = JSON.parse(exportJson(defaultBrand));
63
- expect(parsed).toHaveProperty('light');
64
- expect(parsed).toHaveProperty('dark');
65
- });
66
- test('JSON light tokens match theme', () => {
67
- const parsed = JSON.parse(exportJson(defaultBrand));
68
- for (const [token, value] of Object.entries(defaultBrand.light)) {
69
- expect(parsed.light[token]).toBe(value);
70
- }
71
- });
72
- test('JSON dark tokens match theme', () => {
73
- const parsed = JSON.parse(exportJson(defaultBrand));
74
- for (const [token, value] of Object.entries(defaultBrand.dark)) {
75
- expect(parsed.dark[token]).toBe(value);
76
- }
77
- });
78
- });
@@ -1,10 +0,0 @@
1
- export { hsbToHsl, hslToHsb, hslToRgb, hslToHex, hexToHsl, cssColorToRgb, relativeLuminance, contrastRatio, meetsContrast, luminanceFromHsl, contrastBetweenCssColors, measureForegroundOnSurface, compareForegroundAcrossSurfaces, apcaSrgbToY, apcaContrast, apcaContrastBetweenCssColors, meetsApca, generateThemeModel, generateTheme } from './derivation.js';
2
- export type { HSL, HSB, BrandInput, ThemeOptions, ThemeTokens, SystemTone, ThemeReference, ThemeModeLadder, TransparentNeutralLadder, TransparentBrandLadder, TransparentToneLadder, TransparentPrimitiveLadders, LiteralTransparentNeutralLadder, LiteralTransparentToneLadder, LiteralTransparentPrimitiveLadders, SolidSurfaceSteps, SolidSurfaceRoles, SolidSurfacePalette, SolidPrimitiveLadders, InteractiveStateRecipe, InteractionStateRecipes, BrandCandidateAssessment, BrandPolicy, ForegroundSurfaceThresholds, ForegroundSurfaceAssessment, ForegroundSurfaceComparison, ThemeAuditCheck, ThemeAudit, PhotoTemperatureGuidance, ThemeModelLayer, ThemeModel } from './derivation.js';
3
- export { generateCss, downloadCss, copyCss, exportJson } from './export-css.js';
4
- export type { GenerateCssOptions } from './export-css.js';
5
- export { encodeTheme, decodeTheme, encodeRecipe, decodeRecipe } from './url-codec.js';
6
- export type { DecodedTheme, WizardRecipe } from './url-codec.js';
7
- export { generatePalette, textToBrand } from './palette.js';
8
- export type { PaletteResult } from './palette.js';
9
- export { PRESETS, RECIPE_PRESETS } from './presets.js';
10
- export type { Preset, RecipePreset } from './presets.js';
@@ -1,6 +0,0 @@
1
- // Engine — pure functions, zero Svelte/rune dependencies. Safe for server-side use.
2
- export { hsbToHsl, hslToHsb, hslToRgb, hslToHex, hexToHsl, cssColorToRgb, relativeLuminance, contrastRatio, meetsContrast, luminanceFromHsl, contrastBetweenCssColors, measureForegroundOnSurface, compareForegroundAcrossSurfaces, apcaSrgbToY, apcaContrast, apcaContrastBetweenCssColors, meetsApca, generateThemeModel, generateTheme } from './derivation.js';
3
- export { generateCss, downloadCss, copyCss, exportJson } from './export-css.js';
4
- export { encodeTheme, decodeTheme, encodeRecipe, decodeRecipe } from './url-codec.js';
5
- export { generatePalette, textToBrand } from './palette.js';
6
- export { PRESETS, RECIPE_PRESETS } from './presets.js';
@@ -1,16 +0,0 @@
1
- import { type BrandInput } from './derivation.js';
2
- export interface PaletteResult {
3
- swatches: string[];
4
- bg: string;
5
- accent: string;
6
- }
7
- /**
8
- * Generate a 4-color display palette from a brand HSB input.
9
- * Sweeps from a deep complement through the brand to a light accent.
10
- */
11
- export declare function generatePalette(brand: BrandInput): PaletteResult;
12
- /**
13
- * Deterministically map arbitrary text to a BrandInput.
14
- * Same text always produces the same color.
15
- */
16
- export declare function textToBrand(text: string): BrandInput;
@@ -1,44 +0,0 @@
1
- import { hsbToHsl, hslToHex } from './derivation.js';
2
- /**
3
- * Generate a 4-color display palette from a brand HSB input.
4
- * Sweeps from a deep complement through the brand to a light accent.
5
- */
6
- export function generatePalette(brand) {
7
- const h = brand.h;
8
- const specs = [
9
- { hOff: -105, s: 58, b: 45 },
10
- { hOff: -50, s: 72, b: 58 },
11
- { hOff: -18, s: 80, b: 72 },
12
- { hOff: 25, s: 65, b: 82 }
13
- ];
14
- const swatches = specs.map(({ hOff, s, b }) => {
15
- const hue = (((h + hOff) % 360) + 360) % 360;
16
- const hsl = hsbToHsl(hue, s / 100, b / 100);
17
- return hslToHex(hsl.h, hsl.s, hsl.l);
18
- });
19
- // Keep background in the dark blue-indigo range, with subtle brand influence
20
- const bgHue = 230 + Math.sin((h / 360) * Math.PI * 2) * 25;
21
- const bgHsl = hsbToHsl(bgHue, 0.5, 0.14);
22
- const bg = hslToHex(bgHsl.h, bgHsl.s, bgHsl.l);
23
- const accent = swatches[3];
24
- return { swatches, bg, accent };
25
- }
26
- /**
27
- * Deterministically map arbitrary text to a BrandInput.
28
- * Same text always produces the same color.
29
- */
30
- export function textToBrand(text) {
31
- const normalized = text.trim().toLowerCase();
32
- if (!normalized)
33
- return { h: 230, s: 65, b: 85 };
34
- let hash = 0;
35
- for (let i = 0; i < normalized.length; i++) {
36
- hash = ((hash << 5) - hash + normalized.charCodeAt(i)) | 0;
37
- }
38
- hash = Math.abs(hash);
39
- return {
40
- h: hash % 360,
41
- s: 55 + (hash % 30),
42
- b: 60 + ((hash >> 8) % 25)
43
- };
44
- }
@@ -1,13 +0,0 @@
1
- import type { BrandInput } from './derivation.js';
2
- import type { WizardRecipe } from './url-codec.js';
3
- export interface Preset {
4
- name: string;
5
- brandInput: BrandInput;
6
- }
7
- export declare const PRESETS: Preset[];
8
- export interface RecipePreset {
9
- name: string;
10
- description: string;
11
- recipe: WizardRecipe;
12
- }
13
- export declare const RECIPE_PRESETS: RecipePreset[];
@@ -1,124 +0,0 @@
1
- export const PRESETS = [
2
- {
3
- name: 'Default',
4
- brandInput: { h: 230, s: 65, b: 85 }
5
- },
6
- {
7
- name: 'Ocean',
8
- brandInput: { h: 200, s: 80, b: 70 }
9
- },
10
- {
11
- name: 'Forest',
12
- brandInput: { h: 145, s: 60, b: 55 }
13
- },
14
- {
15
- name: 'Sunset',
16
- brandInput: { h: 25, s: 80, b: 90 }
17
- },
18
- {
19
- name: 'Rose',
20
- brandInput: { h: 340, s: 65, b: 85 }
21
- },
22
- {
23
- name: 'Lavender',
24
- brandInput: { h: 270, s: 45, b: 80 }
25
- },
26
- {
27
- name: 'Midnight',
28
- brandInput: { h: 240, s: 80, b: 35 }
29
- },
30
- {
31
- name: 'Ember',
32
- brandInput: { h: 10, s: 85, b: 75 }
33
- }
34
- ];
35
- export const RECIPE_PRESETS = [
36
- {
37
- name: 'Default',
38
- description: 'Balanced starting point',
39
- recipe: {
40
- brand: { h: 230, s: 65, b: 85 },
41
- personality: 'structured',
42
- typography: { fontPreset: 'System', scale: 'default' },
43
- shape: { radiusPreset: 'soft', radiusScale: 1, density: 'default' },
44
- shadows: { preset: 'elevated', intensity: 1, tintBrand: true }
45
- }
46
- },
47
- {
48
- name: 'Dashboard',
49
- description: 'Dense, data-first interface',
50
- recipe: {
51
- brand: { h: 200, s: 80, b: 70 },
52
- personality: 'structured',
53
- typography: { fontPreset: 'Geometric', scale: 'compact' },
54
- shape: { radiusPreset: 'sharp', radiusScale: 1, density: 'compact' },
55
- shadows: { preset: 'subtle', intensity: 1, tintBrand: false }
56
- }
57
- },
58
- {
59
- name: 'Editorial',
60
- description: 'Warm, readable, content-forward',
61
- recipe: {
62
- brand: { h: 340, s: 65, b: 85 },
63
- personality: 'clean',
64
- typography: { fontPreset: 'Serif', scale: 'spacious' },
65
- shape: { radiusPreset: 'soft', radiusScale: 1, density: 'spacious' },
66
- shadows: { preset: 'flat', intensity: 1, tintBrand: true }
67
- }
68
- },
69
- {
70
- name: 'Terminal',
71
- description: 'Precise, monospace, technical',
72
- recipe: {
73
- brand: { h: 145, s: 60, b: 55 },
74
- personality: 'minimal',
75
- typography: { fontPreset: 'Mono', scale: 'compact' },
76
- shape: { radiusPreset: 'sharp', radiusScale: 1, density: 'compact' },
77
- shadows: { preset: 'flat', intensity: 1, tintBrand: false }
78
- }
79
- },
80
- {
81
- name: 'Playful',
82
- description: 'Warm, rounded, approachable',
83
- recipe: {
84
- brand: { h: 25, s: 80, b: 90 },
85
- personality: 'rich',
86
- typography: { fontPreset: 'Humanist', scale: 'default' },
87
- shape: { radiusPreset: 'pill', radiusScale: 1, density: 'default' },
88
- shadows: { preset: 'deep', intensity: 1, tintBrand: true }
89
- }
90
- },
91
- {
92
- name: 'Corporate',
93
- description: 'Professional and measured',
94
- recipe: {
95
- brand: { h: 230, s: 65, b: 85 },
96
- personality: 'structured',
97
- typography: { fontPreset: 'Classical', scale: 'default' },
98
- shape: { radiusPreset: 'soft', radiusScale: 1, density: 'default' },
99
- shadows: { preset: 'elevated', intensity: 1, tintBrand: false }
100
- }
101
- },
102
- {
103
- name: 'Midnight',
104
- description: 'Dark, atmospheric, layered',
105
- recipe: {
106
- brand: { h: 270, s: 45, b: 80 },
107
- personality: 'rich',
108
- typography: { fontPreset: 'System', scale: 'default' },
109
- shape: { radiusPreset: 'rounded', radiusScale: 1, density: 'default' },
110
- shadows: { preset: 'deep', intensity: 1, tintBrand: true }
111
- }
112
- },
113
- {
114
- name: 'Ember',
115
- description: 'Bold, warm, high contrast',
116
- recipe: {
117
- brand: { h: 10, s: 85, b: 75 },
118
- personality: 'structured',
119
- typography: { fontPreset: 'Geometric', scale: 'default' },
120
- shape: { radiusPreset: 'rounded', radiusScale: 1, density: 'default' },
121
- shadows: { preset: 'elevated', intensity: 1, tintBrand: true }
122
- }
123
- }
124
- ];
@@ -1,53 +0,0 @@
1
- import type { BrandInput, ThemeOptions } from './derivation.js';
2
- import type { NeutralMode, Personality, RadiusPreset, Density, ShadowPreset, TypeScale, FontPreset } from '../state.svelte.js';
3
- export interface DecodedTheme {
4
- brand: BrandInput;
5
- options?: ThemeOptions;
6
- }
7
- export interface WizardRecipe {
8
- brand: BrandInput;
9
- personality?: Personality;
10
- neutralMode?: NeutralMode;
11
- statusHues?: {
12
- error?: number;
13
- warning?: number;
14
- success?: number;
15
- info?: number;
16
- };
17
- typography?: {
18
- fontPreset: FontPreset;
19
- scale: TypeScale;
20
- };
21
- shape?: {
22
- radiusPreset: RadiusPreset;
23
- radiusScale: number;
24
- density: Density;
25
- };
26
- shadows?: {
27
- preset: ShadowPreset;
28
- intensity: number;
29
- tintBrand: boolean;
30
- };
31
- }
32
- /**
33
- * Encode wizard input state into a compact URL-safe string.
34
- *
35
- * Format: `h{hue}s{sat}b{bri}` with optional `-n` neutral-mode flag and
36
- * `-e{error}w{warning}s{success}i{info}` status suffix.
37
- *
38
- * Examples:
39
- * Default brand only: `h230s65b85`
40
- * Brand + status hue overrides: `h230s65b85-e350w45`
41
- */
42
- export declare function encodeTheme(input: {
43
- brand: BrandInput;
44
- options?: ThemeOptions;
45
- }): string;
46
- /**
47
- * Decode a compact theme string back into brand input and options.
48
- *
49
- * Throws if the string is not a valid encoded theme.
50
- */
51
- export declare function decodeTheme(encoded: string): DecodedTheme;
52
- export declare function encodeRecipe(recipe: WizardRecipe): string;
53
- export declare function decodeRecipe(encoded: string): WizardRecipe;