srcdev-nuxt-components 9.1.55 → 9.1.57

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 (104) hide show
  1. package/.claude/settings.json +30 -2
  2. package/.claude/skills/components/alert-content-inner.md +60 -0
  3. package/.claude/skills/components/display-dialog.md +80 -0
  4. package/.claude/skills/components/display-prompt.md +163 -0
  5. package/.claude/skills/components/display-toast.md +235 -0
  6. package/.claude/skills/index.md +7 -3
  7. package/.claude/skills/theming-colour-ramps.md +328 -0
  8. package/.claude/skills/theming-override-default.md +115 -186
  9. package/.claude/skills/theming-partial-override.md +88 -75
  10. package/README.md +166 -0
  11. package/app/app.config.ts +44 -0
  12. package/app/assets/styles/setup/01.config/_head.css +3 -3
  13. package/app/assets/styles/setup/02.colours/_amber.css +12 -10
  14. package/app/assets/styles/setup/02.colours/_blue.css +10 -9
  15. package/app/assets/styles/setup/02.colours/_green.css +12 -11
  16. package/app/assets/styles/setup/02.colours/_orange.css +12 -10
  17. package/app/assets/styles/setup/02.colours/_red.css +10 -9
  18. package/app/assets/styles/setup/02.colours/_slate.css +12 -11
  19. package/app/assets/styles/setup/02.colours/_sunset.css +12 -10
  20. package/app/assets/styles/setup/02.colours/_theme-params.css +18 -0
  21. package/app/assets/styles/setup/02.colours/index.css +1 -0
  22. package/app/assets/styles/setup/03.theming/_default.css +87 -0
  23. package/app/assets/styles/setup/03.theming/_error.css +14 -0
  24. package/app/assets/styles/setup/03.theming/_success.css +4 -0
  25. package/app/assets/styles/setup/03.theming/_theme-slots.css +16 -0
  26. package/app/assets/styles/setup/03.theming/_warning.css +13 -0
  27. package/app/assets/styles/setup/03.theming/index.css +6 -4
  28. package/app/assets/styles/setup/03.theming/theme-ramp.css +19 -0
  29. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +0 -7
  30. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  31. package/app/assets/styles/setup/index.css +1 -1
  32. package/app/components/01.atoms/display-dialog/DisplayDialog.vue +74 -24
  33. package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +148 -14
  34. package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +3 -3
  35. package/app/components/01.atoms/prompt/DisplayPrompt.vue +113 -0
  36. package/app/components/01.atoms/prompt/stories/DisplayPrompt.stories.ts +112 -0
  37. package/app/components/01.atoms/prompt/tests/DisplayPrompt.spec.ts +205 -0
  38. package/app/components/01.atoms/toast/DisplayToast.vue +327 -0
  39. package/app/components/01.atoms/toast/DisplayToastProvider.vue +362 -0
  40. package/app/components/01.atoms/toast/stories/DisplayToast.stories.ts +195 -0
  41. package/app/components/01.atoms/toast/stories/DisplayToastProvider.stories.ts +142 -0
  42. package/app/components/01.atoms/toast/tests/DisplayToast.spec.ts +266 -0
  43. package/app/components/01.atoms/toast/tests/DisplayToastProvider.spec.ts +256 -0
  44. package/app/components/02.molecules/action-menu/ActionMenu.vue +8 -1
  45. package/app/components/02.molecules/action-menu/ActionMenuItemCore.vue +11 -3
  46. package/app/components/02.molecules/alert-content/AlertContent.vue +67 -0
  47. package/app/components/02.molecules/alert-content/AlertContentInner.vue +153 -0
  48. package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +131 -0
  49. package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +135 -0
  50. package/app/components/02.molecules/alert-content/tests/AlertContentInner.spec.ts +135 -0
  51. package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +78 -0
  52. package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +132 -0
  53. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +149 -0
  54. package/app/components/05.forms/input-button/InputButtonCore.vue +35 -39
  55. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +8 -8
  56. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +5 -5
  57. package/app/components/05.forms/input-copy/InputCopyCore.vue +7 -7
  58. package/app/components/05.forms/input-number/InputNumberCore.vue +5 -5
  59. package/app/components/05.forms/input-select/InputSelectCore.vue +6 -6
  60. package/app/components/05.forms/input-text/InputTextCore.vue +6 -6
  61. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +2 -2
  62. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +5 -5
  63. package/app/components/05.forms/pending-effect/PendingEffect.vue +60 -60
  64. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +10 -10
  65. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +4 -4
  66. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +4 -4
  67. package/app/components/display-tooltip/DisplayTooltip.vue +4 -4
  68. package/app/composables/useBodyLock.ts +21 -0
  69. package/app/composables/useDialogControls.ts +11 -11
  70. package/app/composables/useToastQueue.ts +39 -0
  71. package/app/layouts/default.vue +1 -1
  72. package/app/pages/ui/display-dialog.vue +275 -127
  73. package/app/pages/ui/display-prompt.vue +255 -52
  74. package/app/pages/ui/display-toast.vue +278 -299
  75. package/app/pages/ui/mask-element.vue +25 -3
  76. package/app/pages/ui/settings.vue +9 -35
  77. package/app/types/app-config.d.ts +41 -0
  78. package/app/types/components/display-prompt.d.ts +11 -0
  79. package/app/types/components/display-toast.d.ts +11 -9
  80. package/app/types/components/index.ts +2 -0
  81. package/app/types/components/semantic-theme.d.ts +1 -0
  82. package/nuxt.config.ts +16 -0
  83. package/package.json +8 -4
  84. package/ramps.config.mjs +28 -0
  85. package/scripts/check-ramps.mjs +50 -0
  86. package/scripts/generate-consumer-ramps.mjs +100 -0
  87. package/scripts/generate-ramps.mjs +92 -0
  88. package/app/assets/styles/setup/03.theming/default/_dark.css +0 -156
  89. package/app/assets/styles/setup/03.theming/default/_light.css +0 -166
  90. package/app/assets/styles/setup/03.theming/default/index.css +0 -2
  91. package/app/assets/styles/setup/03.theming/error/_dark.css +0 -56
  92. package/app/assets/styles/setup/03.theming/error/_light.css +0 -74
  93. package/app/assets/styles/setup/03.theming/error/index.css +0 -2
  94. package/app/assets/styles/setup/03.theming/success/_dark.css +0 -50
  95. package/app/assets/styles/setup/03.theming/success/_light.css +0 -65
  96. package/app/assets/styles/setup/03.theming/success/index.css +0 -2
  97. package/app/assets/styles/setup/03.theming/warning/_dark.css +0 -50
  98. package/app/assets/styles/setup/03.theming/warning/_light.css +0 -64
  99. package/app/assets/styles/setup/03.theming/warning/index.css +0 -2
  100. package/app/components/display-prompt/DisplayPromptCore.vue +0 -228
  101. package/app/components/display-prompt/variants/DisplayPromptError.vue +0 -53
  102. package/app/components/display-toast/DisplayToast.vue +0 -447
  103. package/app/components/display-toast/molecules/DefaultToastContent.vue +0 -163
  104. package/app/components/display-toast/stories/DisplayToast.stories.ts +0 -380
@@ -10,38 +10,12 @@
10
10
  </PageRow>
11
11
 
12
12
  <PageRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
13
- <h1 class="page-heading-1 mbe-18">Default button elements</h1>
13
+ <div class="theme-test" data-theme="error">
14
+ <h2 class="page-heading-2 mbe-18">Theme test (error)</h2>
14
15
 
15
- <div class="mb-12">
16
- <p class="page-body-normal mbe-12"><code>class="test-button" data-theme="default"</code></p>
17
- <button class="input-button-core" type="button">This is a button</button>
18
- </div>
19
- </PageRow>
20
-
21
- <PageRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
22
- <h1 class="page-heading-1 mbe-18">Primary button elements</h1>
23
-
24
- <div class="mb-12" data-theme="default">
25
- <p class="page-body-normal mbe-12"><code>class="test-button" data-theme="default"</code></p>
26
- <button class="input-button-core" type="button">This is a button</button>
27
- </div>
28
- </PageRow>
29
-
30
- <PageRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
31
- <h1 class="page-heading-1 mbe-18">Error button elements</h1>
32
-
33
- <div class="mb-12" data-theme="default" data-error>
34
- <p class="page-body-normal mbe-12"><code>class="test-button" data-theme="error"</code></p>
35
- <button class="input-button-core" type="button">This is a button</button>
36
- </div>
37
- </PageRow>
38
-
39
- <PageRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
40
- <h1 class="page-heading-1 mbe-18">Success button elements</h1>
41
-
42
- <div class="mb-12" data-theme="success">
43
- <p class="page-body-normal mbe-12"><code>class="test-button" data-theme="success"</code></p>
44
- <button class="input-button-core" type="button">This is a button</button>
16
+ <div class="mb-12">
17
+ <p class="page-body-normal mbe-12">This is test text</p>
18
+ </div>
45
19
  </div>
46
20
  </PageRow>
47
21
  </template>
@@ -65,16 +39,16 @@ useHead({
65
39
 
66
40
  <style lang="css">
67
41
  .settings-page {
68
- .test-button {
69
- /* all: unset; */
42
+ .theme-test {
43
+ margin: 2rem;
70
44
  padding: 0.6rem 1.2rem;
71
45
 
72
46
  border: var(--colour-theme-10) solid 1px;
73
47
  outline: var(--colour-theme-10) solid 1px;
74
48
  outline-offset: 3px;
75
49
  background-color: var(--colour-theme-1);
76
- color: var(--colour-theme-10);
77
- border-radius: 100vw;
50
+ color: var(--colour-theme-7);
51
+ border-radius: 0.4rem;
78
52
  }
79
53
  }
80
54
  </style>
@@ -0,0 +1,41 @@
1
+ import type { SemanticTheme, DisplayPromptTheme, DisplayToastTheme, DisplayToastPosition, DisplayToastAlignment } from "./components"
2
+
3
+ declare module "@nuxt/schema" {
4
+ interface AppConfigInput {
5
+ srcdev?: {
6
+ alertContent?: {
7
+ icons?: Partial<Record<SemanticTheme, string>>
8
+ dismissIcon?: string
9
+ }
10
+ displayPrompt?: {
11
+ theme?: DisplayPromptTheme
12
+ dismissible?: boolean
13
+ useAutoFocus?: boolean
14
+ masked?: boolean
15
+ }
16
+ displayToast?: {
17
+ appearance?: {
18
+ theme?: DisplayToastTheme
19
+ position?: DisplayToastPosition
20
+ alignment?: DisplayToastAlignment
21
+ fullWidth?: boolean
22
+ masked?: boolean
23
+ }
24
+ behavior?: {
25
+ autoDismiss?: boolean
26
+ duration?: number
27
+ revealDuration?: number
28
+ }
29
+ }
30
+ displayDialog?: {
31
+ variant?: "dialog" | "modal" | "confirm" | "alert" | "fullscreen"
32
+ justifyDialog?: "start" | "center" | "end"
33
+ alignDialog?: "start" | "center" | "end"
34
+ lockViewport?: boolean
35
+ allowContentScroll?: boolean
36
+ theme?: SemanticTheme
37
+ closeIcon?: string
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,11 @@
1
+ import type { SemanticTheme } from "./semantic-theme.d"
2
+
3
+ export type DisplayPromptTheme = SemanticTheme
4
+
5
+ export interface DisplayPromptProps {
6
+ theme?: DisplayPromptTheme
7
+ dismissible?: boolean
8
+ useAutoFocus?: boolean
9
+ masked?: boolean
10
+ styleClassPassthrough?: string | string[]
11
+ }
@@ -1,14 +1,7 @@
1
1
  import type { VNode, ComponentPublicInstance } from "vue"
2
+ import type { SemanticTheme } from "./semantic-theme.d"
2
3
 
3
- export type DisplayToastTheme =
4
- | "primary"
5
- | "secondary"
6
- | "tertiary"
7
- | "ghost"
8
- | "error"
9
- | "info"
10
- | "success"
11
- | "warning"
4
+ export type DisplayToastTheme = SemanticTheme
12
5
 
13
6
  export type DisplayToastPosition = "top" | "bottom"
14
7
  export type DisplayToastAlignment = "left" | "center" | "right"
@@ -18,6 +11,7 @@ export interface DisplayToastAppearanceConfig {
18
11
  position?: DisplayToastPosition
19
12
  alignment?: DisplayToastAlignment
20
13
  fullWidth?: boolean
14
+ masked?: boolean
21
15
  }
22
16
 
23
17
  export interface DisplayToastBehaviorConfig {
@@ -45,6 +39,14 @@ export interface DisplayToastProps {
45
39
  styleClassPassthrough?: string | string[]
46
40
  }
47
41
 
42
+ export type ToastQueueStatus = "pending" | "visible"
43
+
44
+ export interface ToastQueueEntry {
45
+ id: string
46
+ config: DisplayToastConfig
47
+ status: ToastQueueStatus
48
+ }
49
+
48
50
  export interface ToastSlots {
49
51
  default?(props?: Record<string, never>): VNode[]
50
52
  customToastIcon?(props?: Record<string, never>): VNode[]
@@ -1,4 +1,5 @@
1
1
  // Export types from the components directory
2
+ export * from "./semantic-theme.d"
2
3
  export * from "./carousel-basic.d"
3
4
  export * from "./slider-gallery.d"
4
5
  export * from "./canvas-switcher.d"
@@ -6,6 +7,7 @@ export * from "./display-chip.d"
6
7
  export * from "./tabs.d"
7
8
  export * from "./responsive-header.d"
8
9
  export * from "./display-toast.d"
10
+ export * from "./display-prompt.d"
9
11
  export * from "./qr-code.d"
10
12
  export * from "./alert-mask-core.d"
11
13
  export * from "./hero-text"
@@ -0,0 +1 @@
1
+ export type SemanticTheme = "info" | "success" | "warning" | "error"
package/nuxt.config.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
2
  import { createResolver } from "@nuxt/kit";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
3
5
 
4
6
  const { resolve } = createResolver(import.meta.url);
5
7
  const isStandalone = !!process.env.SRCDEV_STANDALONE;
8
+ const execFileAsync = promisify(execFile);
6
9
 
7
10
  export default defineNuxtConfig({
8
11
  // debug: !isProduction,
@@ -126,6 +129,19 @@ export default defineNuxtConfig({
126
129
  vue: {
127
130
  runtimeCompiler: true,
128
131
  },
132
+ // Re-generate ramps on config change during local dev.
133
+ // Guarded by isStandalone so consumers extending this layer never trigger
134
+ // generation inside node_modules (they never set SRCDEV_STANDALONE).
135
+ hooks: isStandalone
136
+ ? {
137
+ async "builder:watch"(_event, path) {
138
+ if (!path.endsWith("ramps.config.mjs")) return;
139
+ console.log("[ramps] ramps.config.mjs changed, regenerating…");
140
+ await execFileAsync("node", ["scripts/generate-ramps.mjs"], { cwd: resolve("./") });
141
+ console.log("[ramps] done.");
142
+ },
143
+ }
144
+ : {},
129
145
  compatibilityDate: "2026-05-20",
130
146
  typescript: {
131
147
  includeWorkspace: true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.55",
4
+ "version": "9.1.57",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
@@ -10,9 +10,11 @@
10
10
  "cleanall": "rm -rf node_modules && rm -rf .nuxt && rm -rf .output && rm package-lock.json",
11
11
  "reinstall": "rm -rf node_modules && npm install",
12
12
  "cleaninstall": "npm run clean && npm run reinstall",
13
- "dev": "SRCDEV_STANDALONE=true nuxt dev",
14
- "build": "SRCDEV_STANDALONE=true nuxt build",
15
- "generate": "SRCDEV_STANDALONE=true nuxt generate",
13
+ "generate:ramps": "node scripts/generate-ramps.mjs",
14
+ "check:ramps": "node scripts/check-ramps.mjs",
15
+ "dev": "npm run generate:ramps && SRCDEV_STANDALONE=true nuxt dev",
16
+ "build": "npm run generate:ramps && SRCDEV_STANDALONE=true nuxt build",
17
+ "generate": "npm run generate:ramps && SRCDEV_STANDALONE=true nuxt generate",
16
18
  "preview": "SRCDEV_STANDALONE=true nuxt preview",
17
19
  "lint": "eslint .",
18
20
  "prepare": "SRCDEV_STANDALONE=true nuxt prepare",
@@ -32,6 +34,8 @@
32
34
  ".claude/",
33
35
  "app/",
34
36
  "modules/",
37
+ "scripts/",
38
+ "ramps.config.mjs",
35
39
  "nuxt.config.ts",
36
40
  "types.d.ts"
37
41
  ],
@@ -0,0 +1,28 @@
1
+ // One source of truth for the oklch colour curve.
2
+ // Edit this file; run `npm run generate:ramps` to rebuild the CSS.
3
+
4
+ /** Lightness % at each step index 00–10 (light → dark) */
5
+ export const LIGHTNESS = [98, 94, 88, 80, 72, 64, 56, 48, 40, 32, 25];
6
+
7
+ /** Chroma multipliers at each step index 00–10.
8
+ * Actual chroma = ramp.chroma × multiplier.
9
+ * Ramps at mid-tone (06) reach full chroma; tapers toward both extremes. */
10
+ export const CHROMA_MULTIPLIERS = [0.045, 0.18, 0.32, 0.5, 0.68, 0.86, 1, 0.95, 0.86, 0.77, 0.64];
11
+
12
+ /**
13
+ * Named ramps.
14
+ * hue – oklch hue angle (degrees)
15
+ * chroma – maximum chroma (at the 06 step)
16
+ * drift – optional: rotates hue linearly across steps
17
+ * hue_i = hue + drift × (i / 10)
18
+ * e.g. drift -25 on hue 50 → step 00 = 50°, step 10 = 25°
19
+ */
20
+ export const ramps = {
21
+ blue: { hue: 255, chroma: 0.22 },
22
+ red: { hue: 30, chroma: 0.24 },
23
+ green: { hue: 157, chroma: 0.19 },
24
+ amber: { hue: 75, chroma: 0.19 },
25
+ orange: { hue: 60, chroma: 0.15 },
26
+ sunset: { hue: 50, chroma: 0.22, drift: -25 },
27
+ slate: { hue: 260, chroma: 0.02 },
28
+ };
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CI check: runs the generator then fails if any generated file changed.
4
+ * Usage: node scripts/check-ramps.mjs
5
+ */
6
+
7
+ import { execSync } from "node:child_process";
8
+ import { fileURLToPath } from "node:url";
9
+ import { dirname } from "node:path";
10
+
11
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
12
+
13
+ const GENERATED = [
14
+ "app/assets/styles/setup/02.colours/_blue.css",
15
+ "app/assets/styles/setup/02.colours/_red.css",
16
+ "app/assets/styles/setup/02.colours/_green.css",
17
+ "app/assets/styles/setup/02.colours/_amber.css",
18
+ "app/assets/styles/setup/02.colours/_orange.css",
19
+ "app/assets/styles/setup/02.colours/_sunset.css",
20
+ "app/assets/styles/setup/02.colours/_slate.css",
21
+ "app/assets/styles/setup/02.colours/_theme-params.css",
22
+ "app/assets/styles/setup/03.theming/theme-ramp.css",
23
+ ];
24
+
25
+ console.log("Running generator…");
26
+ execSync("node scripts/generate-ramps.mjs", { cwd: ROOT, stdio: "inherit" });
27
+
28
+ console.log("Checking for diff…");
29
+ const diff = execSync(`git diff --name-only ${GENERATED.join(" ")}`, { cwd: ROOT })
30
+ .toString()
31
+ .trim();
32
+
33
+ if (diff) {
34
+ console.error("\n✗ Generated files are out of date. Run `npm run generate:ramps` and commit:\n");
35
+ console.error(diff);
36
+ process.exit(1);
37
+ }
38
+
39
+ // Also check for untracked generated files
40
+ const untracked = execSync(`git ls-files --others --exclude-standard ${GENERATED.join(" ")}`, { cwd: ROOT })
41
+ .toString()
42
+ .trim();
43
+
44
+ if (untracked) {
45
+ console.error("\n✗ Generated files are new and untracked. Run `npm run generate:ramps` and commit:\n");
46
+ console.error(untracked);
47
+ process.exit(1);
48
+ }
49
+
50
+ console.log("✓ Generated files are up to date.");
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Consumer colour ramp generator.
4
+ *
5
+ * Run from your consuming app's project root:
6
+ * node node_modules/srcdev-nuxt-components/scripts/generate-consumer-ramps.mjs
7
+ *
8
+ * Reads: ramps.config.mjs in your project root
9
+ * Writes: app/assets/styles/setup/02.colours/ in your project root
10
+ *
11
+ * Your ramps.config.mjs can contain any combination of:
12
+ * - New palettes (e.g. gold, copper) — adds --gold-00..10 and --palette-gold-* vars
13
+ * - Built-in names (e.g. blue, red, green) — your generated file overrides the layer's
14
+ * values because consumer CSS loads after layer CSS in the cascade
15
+ *
16
+ * Example ramps.config.mjs:
17
+ * export const ramps = {
18
+ * gold: { hue: 85, chroma: 0.20 },
19
+ * };
20
+ *
21
+ * Produces:
22
+ * _gold.css — --gold-00 … --gold-10 (literal oklch values)
23
+ * _palette-params.css — --palette-gold-hue, --palette-gold-chroma
24
+ *
25
+ * Import both in your app/assets/styles/setup/02.colours/index.css, then
26
+ * reference --gold-09 etc. in your theme override files.
27
+ */
28
+
29
+ import { writeFileSync, mkdirSync } from "node:fs";
30
+ import { fileURLToPath, pathToFileURL } from "node:url";
31
+ import { dirname, join } from "node:path";
32
+
33
+ const __dirname = dirname(fileURLToPath(import.meta.url));
34
+ const LAYER_ROOT = join(__dirname, "..");
35
+ const CONSUMER_ROOT = process.cwd();
36
+
37
+ // Lightness / chroma curves come from the layer so all ramps share the same curve shape
38
+ const { LIGHTNESS, CHROMA_MULTIPLIERS } = await import(pathToFileURL(join(LAYER_ROOT, "ramps.config.mjs")).href);
39
+
40
+ // Consumer's palette definitions
41
+ let consumerRamps;
42
+ try {
43
+ const config = await import(pathToFileURL(join(CONSUMER_ROOT, "ramps.config.mjs")).href);
44
+ consumerRamps = config.ramps;
45
+ if (!consumerRamps || typeof consumerRamps !== "object") throw new Error("ramps export missing");
46
+ } catch {
47
+ console.error("✗ Could not load ramps.config.mjs from your project root.");
48
+ console.error(" Create one exporting a `ramps` object, e.g.:");
49
+ console.error(" export const ramps = {");
50
+ console.error(" gold: { hue: 85, chroma: 0.20 },");
51
+ console.error(" };");
52
+ process.exit(1);
53
+ }
54
+
55
+ const COLOURS_DIR = join(CONSUMER_ROOT, "app/assets/styles/setup/02.colours");
56
+ mkdirSync(COLOURS_DIR, { recursive: true });
57
+
58
+ const HEADER = "/* GENERATED — edit your ramps.config.mjs, not this file */";
59
+
60
+ /** Round to at most `places` decimal places, stripping trailing zeros. */
61
+ function r(n, places = 4) {
62
+ return parseFloat(n.toFixed(places));
63
+ }
64
+
65
+ // ─── Individual colour files ──────────────────────────────────────────────────
66
+
67
+ for (const [name, cfg] of Object.entries(consumerRamps)) {
68
+ const { hue, chroma, drift = 0 } = cfg;
69
+ const lines = [HEADER, ":where(html) {"];
70
+
71
+ for (let i = 0; i <= 10; i++) {
72
+ const step = String(i).padStart(2, "0");
73
+ const L = LIGHTNESS[i];
74
+ const C = r(chroma * CHROMA_MULTIPLIERS[i]);
75
+ const H = drift !== 0 ? r(hue + drift * (i / 10), 1) : hue;
76
+ lines.push(` --${name}-${step}: oklch(${L}% ${C} ${H});`);
77
+ }
78
+
79
+ lines.push("}");
80
+ writeFileSync(join(COLOURS_DIR, `_${name}.css`), lines.join("\n") + "\n");
81
+ console.log(` ✓ _${name}.css`);
82
+ }
83
+
84
+ // ─── _palette-params.css ─────────────────────────────────────────────────────
85
+
86
+ const paramLines = [HEADER, ":where(html) {"];
87
+ for (const [name, { hue, chroma, drift }] of Object.entries(consumerRamps)) {
88
+ paramLines.push(` --palette-${name}-hue: ${hue};`);
89
+ paramLines.push(` --palette-${name}-chroma: ${chroma};`);
90
+ if (drift !== undefined) paramLines.push(` --palette-${name}-drift: ${drift};`);
91
+ }
92
+ paramLines.push("}");
93
+ writeFileSync(join(COLOURS_DIR, "_palette-params.css"), paramLines.join("\n") + "\n");
94
+ console.log(" ✓ _palette-params.css");
95
+
96
+ console.log("");
97
+ console.log("Done. Next steps:");
98
+ console.log(" 1. Import generated files in app/assets/styles/setup/02.colours/index.css");
99
+ console.log(" 2. Reference --palette-*-hue / --palette-*-chroma in your theme override");
100
+ console.log(" 3. Use named steps (e.g. var(--gold-09)) in page-level token overrides");
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generates colour ramp CSS from ramps.config.mjs.
4
+ * Outputs:
5
+ * app/assets/styles/setup/02.colours/_{name}.css — per-ramp literal values
6
+ * app/assets/styles/setup/02.colours/_theme-params.css — --palette-*-hue/chroma vars
7
+ * app/assets/styles/setup/03.theming/theme-ramp.css — parametric --colour-theme-* slot
8
+ */
9
+
10
+ import { writeFileSync } from "node:fs";
11
+ import { fileURLToPath, pathToFileURL } from "node:url";
12
+ import { dirname, join } from "node:path";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const ROOT = join(__dirname, "..");
16
+
17
+ const { LIGHTNESS, CHROMA_MULTIPLIERS, ramps } = await import(pathToFileURL(join(ROOT, "ramps.config.mjs")).href);
18
+
19
+ const COLOURS_DIR = join(ROOT, "app/assets/styles/setup/02.colours");
20
+ const THEMING_DIR = join(ROOT, "app/assets/styles/setup/03.theming");
21
+
22
+ const HEADER = "/* GENERATED — edit ramps.config.mjs, not this file */";
23
+
24
+ /** Round to at most `places` decimal places, stripping trailing zeros. */
25
+ function r(n, places = 4) {
26
+ return parseFloat(n.toFixed(places));
27
+ }
28
+
29
+ // ─── Individual ramp files ───────────────────────────────────────────────────
30
+
31
+ for (const [name, cfg] of Object.entries(ramps)) {
32
+ const { hue, chroma, drift = 0 } = cfg;
33
+ const lines = [HEADER, ":where(html) {"];
34
+
35
+ for (let i = 0; i <= 10; i++) {
36
+ const step = String(i).padStart(2, "0");
37
+ const L = LIGHTNESS[i];
38
+ const C = r(chroma * CHROMA_MULTIPLIERS[i]);
39
+ const H = drift !== 0 ? r(hue + drift * (i / 10), 1) : hue;
40
+ lines.push(` --${name}-${step}: oklch(${L}% ${C} ${H});`);
41
+ }
42
+
43
+ lines.push("}");
44
+ writeFileSync(join(COLOURS_DIR, `_${name}.css`), lines.join("\n") + "\n");
45
+ console.log(` ✓ _${name}.css`);
46
+ }
47
+
48
+ // ─── _theme-params.css ────────────────────────────────────────────────────────
49
+
50
+ const paramLines = [HEADER, ":where(html) {"];
51
+ for (const [name, { hue, chroma, drift }] of Object.entries(ramps)) {
52
+ paramLines.push(` --palette-${name}-hue: ${hue};`);
53
+ paramLines.push(` --palette-${name}-chroma: ${chroma};`);
54
+ if (drift !== undefined) paramLines.push(` --palette-${name}-drift: ${drift};`);
55
+ }
56
+ paramLines.push("}");
57
+ writeFileSync(join(COLOURS_DIR, "_theme-params.css"), paramLines.join("\n") + "\n");
58
+ console.log(" ✓ _theme-params.css");
59
+
60
+ // ─── theme-ramp.css ───────────────────────────────────────────────────────────
61
+ // Declares --colour-theme-0..10 as oklch formulas on every element that can
62
+ // carry --theme-hue / --theme-chroma. The selector list must include every
63
+ // element that can redefine those vars so the formula re-evaluates there.
64
+ // --theme-hue-drift defaults to 0; set it (e.g. var(--palette-sunset-drift))
65
+ // to rotate hue linearly across steps, matching the ramp drift behaviour.
66
+
67
+ const rampLines = [
68
+ HEADER,
69
+ "/* Parametric theme-colour slot. Set --theme-hue and --theme-chroma on a",
70
+ " theme selector; these vars automatically pick up the right palette. */",
71
+ ":where(html, [data-theme], [data-invalid]) {",
72
+ ];
73
+
74
+ for (let i = 0; i <= 10; i++) {
75
+ const L = LIGHTNESS[i];
76
+ const M = CHROMA_MULTIPLIERS[i];
77
+ const fraction = i === 0 ? "0" : i === 10 ? "1" : `${i} / 10`;
78
+ rampLines.push(
79
+ ` --colour-theme-${i}: oklch(${L}% calc(var(--theme-chroma) * ${M}) calc(var(--theme-hue) + var(--theme-hue-drift, 0) * (${fraction})));`
80
+ );
81
+ }
82
+
83
+ // Alias out-of-range indices used by older component code
84
+ rampLines.push(" /* Aliases for legacy --colour-theme-11 / -12 references */");
85
+ rampLines.push(" --colour-theme-11: var(--colour-theme-10);");
86
+ rampLines.push(" --colour-theme-12: var(--colour-theme-10);");
87
+ rampLines.push("}");
88
+
89
+ writeFileSync(join(THEMING_DIR, "theme-ramp.css"), rampLines.join("\n") + "\n");
90
+ console.log(" ✓ theme-ramp.css");
91
+
92
+ console.log("Done.");
@@ -1,156 +0,0 @@
1
- :where(html.dark) {
2
- /* ===========================================
3
- COLOR SCALE VARIABLES
4
- Base color tokens for the default theme
5
- =========================================== */
6
- --colour-theme-1: var(--blue-01);
7
- --colour-theme-2: var(--blue-02);
8
- --colour-theme-3: var(--blue-03);
9
- --colour-theme-4: var(--blue-04);
10
- --colour-theme-5: var(--blue-05);
11
- --colour-theme-6: var(--blue-06);
12
- --colour-theme-7: var(--blue-07);
13
- --colour-theme-8: var(--blue-08);
14
- --colour-theme-9: var(--blue-09);
15
- --colour-theme-10: var(--blue-10);
16
- --colour-theme-11: var(--blue-10);
17
- --colour-theme-12: var(--blue-10);
18
-
19
- /* ===========================================
20
- GENERIC BODY VARIABLES
21
- =========================================== */
22
- --page-bg: var(--slate-08);
23
- --colour-text-default: var(--slate-01);
24
- --colour-text-accent: var(--blue-05);
25
- --colour-text-eyebrow: var(--blue-05);
26
-
27
- /* ===========================================
28
- GLASS PANEL VARIABLES
29
- =========================================== */
30
- --glass-panel-bg: rgba(12, 12, 20, 0.45);
31
- --glass-panel-border-color: rgba(255, 255, 255, 0.07);
32
- --glass-panel-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
33
- --glass-panel-highlight: rgba(255, 255, 255, 0.04);
34
-
35
- /* ===========================================
36
- HERO TEXT VARIABLES
37
- =========================================== */
38
- --hero-text-bg-img: linear-gradient(135deg, #c2a770, #b4747e, #d1bd94);
39
-
40
- /* ===========================================
41
- EYEBROW TEXT VARIABLES
42
- =========================================== */
43
- --eyebrow-text-bg-img: linear-gradient(135deg, #c2a770, #b4747e, #d1bd94);
44
-
45
- /* ===========================================
46
- STEPPER LIST VARIABLES
47
- =========================================== */
48
- --stepper-list-counter-circle-background: transparent;
49
- --stepper-list-counter-circle-text: var(--blue-02);
50
- --stepper-list-counter-circle-border: var(--blue-02);
51
-
52
- --stepper-list-counter-disc-background: var(--blue-02);
53
- --stepper-list-counter-disc-text: var(--blue-09);
54
- --stepper-list-counter-disc-border: var(--blue-02);
55
-
56
- --stepper-list-counter-square-background: transparent;
57
- --stepper-list-counter-square-text: var(--blue-02);
58
- --stepper-list-counter-square-border: var(--blue-02);
59
-
60
- --stepper-list-connector-color: var(--blue-02);
61
-
62
- --stepper-list-icon: var(--blue-02);
63
-
64
- /* ===========================================
65
- COLOUR FINDER VARIABLES
66
- =========================================== */
67
- --colour-finder-primary-colour: var(--blue-03);
68
- --colour-finder-border-colour: var(--blue-03);
69
-
70
- --colour-finder-checked-surface-colour: var(--green-10);
71
- --colour-finder-checked-stroke-colour: var(--green-03);
72
-
73
- --colour-finder-conflict-surface-colour: var(--sunset-09);
74
- --colour-finder-conflict-stroke-colour: var(--sunset-03);
75
-
76
- /* ===========================================
77
- TREATMENT CONSULTANT VARIABLES
78
- =========================================== */
79
- --treatment-consultant-primary-colour: var(--amber-02);
80
- --treatment-consultant-primary-foreground: var(--amber-02);
81
-
82
- --treatment-consultant-muted-colour: var(--amber-05);
83
- --treatment-consultant-muted-foreground: var(--amber-05);
84
-
85
- --treatment-consultant-background: var(--amber-10);
86
- --treatment-consultant-foreground: var(--amber-02);
87
-
88
- --treatment-consultant-border-colour: var(--amber-03);
89
-
90
- --treatment-consultant-checked-surface-colour: var(--green-10);
91
- --treatment-consultant-checked-stroke-colour: var(--green-03);
92
-
93
- --treatment-consultant-conflict-surface-colour: var(--sunset-09);
94
- --treatment-consultant-conflict-stroke-colour: var(--sunset-03);
95
-
96
- /* ===========================================
97
- ANCHOR LINK VARIABLES
98
- =========================================== */
99
- --colour-link-default: var(--blue-02);
100
- --colour-link-hover: var(--blue-03);
101
-
102
- /* ===========================================
103
- FORM INPUT VARIABLES
104
- Styling for form inputs and controls
105
- =========================================== */
106
-
107
- /* Input surfaces and backgrounds */
108
- --theme-input-surface: var(--slate-10);
109
- --theme-input-surface-hover: var(--slate-09);
110
-
111
- /* Input borders */
112
- --theme-input-border: var(--blue-06);
113
- --theme-input-border-hover: var(--blue-05);
114
- --theme-input-border-focus: var(--blue-04);
115
-
116
- /* Input outlines and focus */
117
- --theme-input-outline: var(--blue-06);
118
- --theme-input-outline-focus: var(--blue-04);
119
- --theme-input-visible-outline: var(--blue-04);
120
- --theme-focus-visible-shadow: 0 0 0 2px var(--blue-02);
121
-
122
- /* ===========================================
123
- INPUT TEXT VARIABLES
124
- =========================================== */
125
- --theme-input-placeholder: var(--slate-04);
126
- --theme-input-text-color-normal: var(--slate-01);
127
-
128
- /* ===========================================
129
- CHECKBOX AND RADIO BUTTON VARIABLES
130
- =========================================== */
131
- --theme-checkbox-symbol-surface: var(--theme-input-surface);
132
- --theme-checkbox-symbol-color: var(--blue-02);
133
- --theme-checkbox-decorator-color: var(--blue-02);
134
-
135
- /* ===========================================
136
- FORM BUTTON VARIABLES
137
- Styling for form buttons and controls
138
- =========================================== */
139
-
140
- /* Variant Primary */
141
- --theme-button-primary-border: var(--blue-07);
142
- --theme-button-primary-border-active: var(--blue-07);
143
- --theme-button-primary-text: var(--blue-00);
144
- --theme-button-primary-text-hover: var(--blue-00);
145
-
146
- /* Variant Secondary */
147
- --theme-button-secondary-surface: var(--blue-01);
148
- --theme-button-secondary-border-active: var(--blue-01);
149
- --theme-button-secondary-text: var(--blue-09);
150
-
151
- /* Variant Tertiary */
152
- --theme-button-tertiary-surface: transparent;
153
- --theme-button-tertiary-text: var(--blue-01);
154
- --theme-button-tertiary-border-active: var(--blue-01);
155
- --theme-button-tertiary-outline-active: var(--blue-09);
156
- }