@rokkit/cli 1.1.14 → 1.1.16

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.
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Derive a safe localStorage key from an npm package name.
3
+ * Strips the scope, keeps case, maps unsafe chars to '-'. Returns '' when there
4
+ * is no usable name — the caller then omits `storageKey` rather than baking in a
5
+ * hardcoded default (the consumer config / library fallback decides instead).
6
+ * @param {string} [name]
7
+ * @returns {string}
8
+ */
9
+ export function storageKeyFromName(name?: string): string;
1
10
  /**
2
11
  * Serialize a rokkit config object to a JS module string with a header comment.
3
12
  * @param {Record<string, unknown>} config
@@ -31,7 +40,8 @@ export function generateChartConfig({ chartColors, chartShades }: {
31
40
  /**
32
41
  * Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
33
42
  * @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
34
- * includeChart?: boolean, chartColors?: string, chartShades?: string }} [opts]
43
+ * includeChart?: boolean, chartColors?: string, chartShades?: string,
44
+ * storageKey?: string }} [opts]
35
45
  * @returns {Record<string, unknown>}
36
46
  */
37
47
  export function generateZenSumiConfig(opts?: {
@@ -41,6 +51,7 @@ export function generateZenSumiConfig(opts?: {
41
51
  includeChart?: boolean;
42
52
  chartColors?: string;
43
53
  chartShades?: string;
54
+ storageKey?: string;
44
55
  }): Record<string, unknown>;
45
56
  /**
46
57
  * Build a Rokkit config object from the user's init choices.
@@ -48,10 +59,11 @@ export function generateZenSumiConfig(opts?: {
48
59
  * optional `icons` and `chart` sections.
49
60
  * @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
50
61
  * iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
51
- * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string }} opts
62
+ * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string,
63
+ * storageKey?: string }} opts
52
64
  * @returns {Record<string, unknown>}
53
65
  */
54
- export function generateConfig({ palette, customColors, icons, iconPath, iconStyle, themes, defaultTheme, switcher, includeChart, chartColors, chartShades }: {
66
+ export function generateConfig({ palette, customColors, icons, iconPath, iconStyle, themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey }: {
55
67
  palette: string;
56
68
  customColors?: Record<string, string>;
57
69
  icons?: string;
@@ -63,6 +75,7 @@ export function generateConfig({ palette, customColors, icons, iconPath, iconSty
63
75
  includeChart?: boolean;
64
76
  chartColors?: string;
65
77
  chartShades?: string;
78
+ storageKey?: string;
66
79
  }): Record<string, unknown>;
67
80
  /**
68
81
  * Generate the content for uno.config.js.
@@ -75,22 +88,6 @@ export function generateUnoConfig(): string;
75
88
  * @returns {string[]}
76
89
  */
77
90
  export function generateAppCssImports(themes: string[]): string[];
78
- /**
79
- * Generate the flash-prevention script for app.html.
80
- *
81
- * Themes flip on `[data-mode="dark"]` only — there is no `@media
82
- * (prefers-color-scheme)` fallback — so every switcher needs a script that
83
- * resolves the mode pre-paint (mirrors `@rokkit/unocss`'s `themeHook`):
84
- * - `system` tracks the OS preference directly (no persisted toggle).
85
- * - `manual` / `full` read the persisted mode and resolve a stored-or-default
86
- * `'system'`/`'auto'` against the OS preference (default: follow the OS).
87
- *
88
- * @param {string} switcher — 'system' | 'manual' | 'full'
89
- * @param {string} [storageKey='rokkit-theme']
90
- * @param {string} [defaultStyle='rokkit']
91
- * @returns {string} the `<script>` tag to inject after `<body>`
92
- */
93
- export function generateInitScript(switcher: string, storageKey?: string, defaultStyle?: string): string;
94
91
  /**
95
92
  * Interactive init command — prompts the user, writes config files.
96
93
  * @param {Record<string, unknown>} [_opts]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/cli",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Command line utilities for icon bundling and conversion.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@mdi/svg": "^7.4.47",
30
+ "@rokkit/unocss": "1.1.16",
30
31
  "sade": "^1.8.1",
31
32
  "@iconify/tools": "5.0.3",
32
33
  "@iconify/types": "^2.0.0",
@@ -0,0 +1,224 @@
1
+ ---
2
+ name: skin-system-rokkit
3
+ description: Use when switching color skins (which palette backs paper/ink/primary/accent) in a Rokkit (Svelte 5) app — the vibe.skin state, the data-skin mechanism, themable sync, defining skins in rokkit.config.js (skins:), built-in skins, SkinSwitcherToggle, and the skinnable action for dynamic/runtime skins.
4
+ ---
5
+
6
+ # Rokkit Skin System
7
+
8
+ A **skin** is a named colormap — it selects which palette fills each semantic role
9
+ (`surface`, `ink`, `primary`, `accent`, …). Swapping the skin changes the entire palette
10
+ backing `--paper`, `--ink`, `--primary`, `--accent`, and all their derivatives. Named tokens
11
+ (`bg-paper`, `text-ink`, `bg-primary`) keep working unchanged; only the palette under them
12
+ shifts.
13
+
14
+ **Skin vs style vs mode:**
15
+
16
+ | Dimension | Controls | Example values |
17
+ |-----------|----------|----------------|
18
+ | **skin** | which palette backs the roles | `default` · `ocean` · `violet` |
19
+ | **style** | which visual theme (border radius, shadow, surface recipe) | `rokkit` · `minimal` · `zen-sumi` |
20
+ | **mode** | light or dark rendering | `light` · `dark` |
21
+
22
+ These three are orthogonal — a skin switch does not change the style or mode.
23
+
24
+ ---
25
+
26
+ ## Defining skins
27
+
28
+ Declare named skins in `rokkit.config.js` under `skins:`. Each key is a skin name; each
29
+ value maps role names to palette names. **Use `skins:` OR `skin:`, not both** — `skins:`
30
+ wins when both are present.
31
+
32
+ ```js
33
+ // rokkit.config.js
34
+ export default {
35
+ skins: {
36
+ default: {
37
+ surface: { light: 'kami', dark: 'sumi' }, // dual-palette → emits dark block
38
+ ink: { light: 'kami', dark: 'sumi' },
39
+ primary: 'shu',
40
+ accent: 'shu',
41
+ success: 'hisui', warning: 'kohaku', danger: 'shu', error: 'shu', info: 'kohaku',
42
+ },
43
+ ocean: { surface: 'slate', ink: 'slate', primary: 'sky', accent: 'teal' },
44
+ violet: { surface: 'zinc', ink: 'zinc', primary: 'violet', accent: 'indigo' },
45
+ rose: { surface: 'stone', ink: 'stone', primary: 'rose', accent: 'orange' },
46
+ emerald: { surface: 'slate', ink: 'slate', primary: 'emerald', accent: 'cyan' },
47
+ },
48
+ // ... palettes, colorSpace, tokens, etc.
49
+ }
50
+ ```
51
+
52
+ **Dual-palette roles** (`{ light, dark }`) tell the preset to emit both a light block and a
53
+ `[data-mode='dark']` override block. Single-palette roles (e.g. `primary: 'shu'`) stay
54
+ constant across modes — correct for brand accents.
55
+
56
+ ### Built-in skins (available out of the box)
57
+
58
+ The preset ships five built-in skins in `@rokkit/unocss`. Their `[data-skin='name']` CSS
59
+ blocks are emitted automatically — no config needed to use them:
60
+
61
+ | Skin | Surface | Ink | Primary | Accent |
62
+ |------|---------|-----|---------|--------|
63
+ | `default` | (DEFAULT_THEME_MAPPING) | `slate` | — | — |
64
+ | `ocean` | `slate` | `slate` | `sky` | `teal` |
65
+ | `violet` | `zinc` | `zinc` | `violet` | `indigo` |
66
+ | `rose` | `stone` | `stone` | `rose` | `orange` |
67
+ | `emerald` | `slate` | `slate` | `emerald` | `cyan` |
68
+
69
+ Consumer `skins:` in `rokkit.config.js` merge on top and can override or extend these.
70
+
71
+ ---
72
+
73
+ ## Switching at runtime
74
+
75
+ Three moving parts: **`vibe.skin`** (reactive state), **`themable`** (writes `data-skin` to
76
+ DOM), **`SkinSwitcherToggle`** (ready-made control).
77
+
78
+ ### 1. Wire once in the root layout
79
+
80
+ Set `vibe.allowedSkins` **before** `themable` runs — persisted skin can only restore if the
81
+ value is already in the allowed list when `themable` calls `theme.load(storageKey)`.
82
+
83
+ ```svelte
84
+ <!-- src/routes/+layout.svelte -->
85
+ <script>
86
+ import { vibe } from '@rokkit/states'
87
+ import { themable } from '@rokkit/actions'
88
+
89
+ // MUST be set before themable so a persisted skin restores correctly.
90
+ vibe.allowedSkins = ['default', 'ocean', 'violet', 'rose', 'emerald']
91
+ </script>
92
+
93
+ <svelte:body use:themable={{ theme: vibe, storageKey: 'app-theme' }} />
94
+ ```
95
+
96
+ `themable` writes `data-skin={vibe.skin}` on `<body>` (and mirrors to `<html>`) reactively.
97
+ When `storageKey` is provided it also loads/saves the persisted skin.
98
+
99
+ ### 2. Drop in the toggle
100
+
101
+ ```svelte
102
+ <script>
103
+ import { SkinSwitcherToggle } from '@rokkit/app'
104
+ </script>
105
+
106
+ <!-- Reads vibe.allowedSkins by default -->
107
+ <SkinSwitcherToggle />
108
+
109
+ <!-- Or pass an explicit list (same names as skins: in config) -->
110
+ <SkinSwitcherToggle skins={['default', 'ocean', 'violet', 'rose', 'emerald']} />
111
+
112
+ <!-- With labels -->
113
+ <SkinSwitcherToggle
114
+ skins={[
115
+ { name: 'default', label: 'Zen Sumi' },
116
+ { name: 'ocean', label: 'Ocean' },
117
+ { name: 'violet', label: 'Violet' },
118
+ ]}
119
+ showLabels
120
+ size="md"
121
+ />
122
+ ```
123
+
124
+ **`SkinSwitcherToggle` props:**
125
+
126
+ | Prop | Type | Default | Purpose |
127
+ |------|------|---------|---------|
128
+ | `skins` | `Array<string \| { name, label?, icon? }>` | `vibe.allowedSkins` | skin options |
129
+ | `labels` | `Record<string, string>` | `{}` | display labels keyed by name |
130
+ | `showLabels` | `boolean` | `false` | show label text in the toggle |
131
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | control size |
132
+ | `disabled` | `boolean` | `false` | disable the control |
133
+ | `onchange` | `(skin: string) => void` | — | callback after switching |
134
+
135
+ The component sets `vibe.skin` directly — no extra wiring needed.
136
+
137
+ ### 3. Switch programmatically
138
+
139
+ ```svelte
140
+ <button onclick={() => (vibe.skin = 'ocean')}>Ocean</button>
141
+ ```
142
+
143
+ `vibe.skin`'s setter validates against `vibe.allowedSkins` — the assignment is a no-op if
144
+ the skin name is not in the allowed list.
145
+
146
+ ---
147
+
148
+ ## How the CSS works
149
+
150
+ The UnoCSS preset emits one CSS block per skin:
151
+
152
+ ```css
153
+ /* default skin on :root — always active as fallback */
154
+ :root {
155
+ --paper: oklch(0.985 0.005 85);
156
+ --ink: oklch(0.220 0.012 50);
157
+ --primary: oklch(0.580 0.150 35);
158
+ /* ... all named tokens ... */
159
+ }
160
+
161
+ /* named skin blocks — activated by data-skin attribute */
162
+ [data-skin='ocean'] {
163
+ --paper: /* slate.50 */;
164
+ --ink: /* slate.900 */;
165
+ --primary: /* sky.500 */;
166
+ /* ... */
167
+ }
168
+
169
+ /* dark mode override for dual-palette skins (single compound selector) */
170
+ [data-mode='dark'][data-skin='ocean'] {
171
+ --paper: /* slate.950 (inverted) */;
172
+ --ink: /* slate.50 (inverted) */;
173
+ }
174
+ ```
175
+
176
+ `themable` keeps `data-skin` on `<body>` (and `<html>`) in sync with `vibe.skin`. CSS
177
+ specificity does the rest — no JavaScript skin loops, no `classList` juggling.
178
+
179
+ **The old `skin-{name}` class is removed.** Do not use `class="skin-ocean"` — it no longer
180
+ exists. `data-skin='ocean'` is the only mechanism.
181
+
182
+ ---
183
+
184
+ ## Dynamic / custom skins (skinnable)
185
+
186
+ For skins that are not pre-baked in config (server-configured palettes, user-customized
187
+ color pickers, multi-tenant theming), use the `skinnable` action from `@rokkit/actions`.
188
+ It applies CSS custom properties inline on a specific element, scoped to that element's
189
+ subtree.
190
+
191
+ ```svelte
192
+ <script>
193
+ import { skinnable } from '@rokkit/actions'
194
+
195
+ // varMap: CSS var name → complete color value
196
+ const brandVars = {
197
+ '--primary': 'oklch(0.60 0.18 260)',
198
+ '--accent': 'oklch(0.55 0.15 200)',
199
+ '--paper': 'oklch(0.97 0.005 260)',
200
+ '--ink': 'oklch(0.22 0.010 260)',
201
+ }
202
+ </script>
203
+
204
+ <div use:skinnable={brandVars}>
205
+ <!-- descendants resolve CSS vars from inline styles -->
206
+ </div>
207
+ ```
208
+
209
+ `skinnable` is complementary to the `data-skin` cascade, not a replacement. Use
210
+ `vibe.skin` + `themable` for app-wide named skins; use `skinnable` for scoped or
211
+ runtime-only color overrides.
212
+
213
+ ---
214
+
215
+ ## Common mistakes
216
+
217
+ | Mistake | Why it fails | Fix |
218
+ |---------|-------------|-----|
219
+ | `vibe.allowedSkins` set after `themable` runs | persisted skin can't restore; `vibe.skin = storedValue` is a no-op because the list defaults to `['default']` only | set `vibe.allowedSkins` before `<svelte:body use:themable ...>` in the root layout |
220
+ | `class="skin-ocean"` on an element | `skin-{name}` class was removed | use `data-skin='ocean'` attribute, or let `themable` write it via `vibe.skin` |
221
+ | Hand-rolling `<style>` injection for a skin | brittle, order-dependent, bypasses named-token aliases | define in `rokkit.config.js` `skins:` + let the preset emit `[data-skin='name']` blocks |
222
+ | `vibe.skin = 'ocean'` with no `allowedSkins` set | setter rejects unknown values | set `vibe.allowedSkins = ['default', 'ocean', ...]` first |
223
+ | `skins:` and `skin:` both in config | `skins:` silently wins; `skin:` is ignored | pick one |
224
+ | Single-palette `surface` in a skin (`surface: 'slate'`) expecting dark-mode flip | no `[data-mode='dark']` block is emitted for single-palette roles | use dual-palette `surface: { light: 'kami', dark: 'sumi' }` for dark support |
package/src/doctor.js CHANGED
@@ -5,11 +5,11 @@ import { loadConfig as loadParsedConfig } from './config.js'
5
5
  import {
6
6
  generateUnoConfig,
7
7
  generateAppCssImports,
8
- generateInitScript,
9
8
  generateChartConfig,
10
9
  generateConfig,
11
10
  serializeRokkitConfig
12
11
  } from './init.js'
12
+ import { themeInitScript } from '@rokkit/unocss/hooks'
13
13
 
14
14
  const KNOWN_THEMES = [
15
15
  'rokkit',
@@ -220,8 +220,15 @@ function applyPatchHtml(cwd, label) {
220
220
  const htmlPath = resolve(cwd, 'src/app.html')
221
221
  if (!existsSync(htmlPath)) return false
222
222
  const html = readFileSync(htmlPath, 'utf-8')
223
- const script = generateInitScript('manual', 'rokkit-theme')
224
- if (!script || html.includes('rokkit-theme')) return false
223
+ // Use the project's configured storageKey so the injected script reads the
224
+ // same localStorage slot the app persists to — don't hardcode a key.
225
+ const configPath = resolve(cwd, 'rokkit.config.js')
226
+ const storageKey = existsSync(configPath)
227
+ ? readFileSync(configPath, 'utf-8').match(/storageKey:\s*['"]([^'"]+)['"]/)?.[1]
228
+ : undefined
229
+ const marker = storageKey || 'rokkit-theme'
230
+ if (html.includes(marker)) return false
231
+ const script = themeInitScript({ storageKey })
225
232
  const patched = html.replace(/(<body[^>]*>)/, `$1\n${script}`)
226
233
  writeFileSync(htmlPath, patched)
227
234
  console.info(` Fixed: ${label}`)
package/src/init.js CHANGED
@@ -4,6 +4,7 @@ import { writeFileSync, readFileSync, existsSync } from 'fs'
4
4
  import { resolve } from 'path'
5
5
  import { execFileSync } from 'child_process'
6
6
  import { detectPackageManager, buildInstallCommand } from './upgrade.js'
7
+ import { themeInitScript } from '@rokkit/unocss/hooks'
7
8
 
8
9
  const ROKKIT_PACKAGES = ['@rokkit/ui', '@rokkit/unocss', '@rokkit/themes', '@rokkit/icons']
9
10
  const LOCKFILES = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json']
@@ -25,6 +26,9 @@ const NAMED_TOKEN_HEADER = `/**
25
26
  *
26
27
  * tokens: 'core' emits the named vocabulary; 'extended' also emits the full
27
28
  * 11-shade palette ladder per role (for charts / data-viz).
29
+ *
30
+ * Also: \`skins\` (named alternates → \`[data-skin]\` blocks), \`switcher\`
31
+ * ('system'|'manual'|'full'), \`storageKey\`, \`shape\`, \`typography\`.
28
32
  */
29
33
  `
30
34
 
@@ -34,6 +38,21 @@ const OKLCH_PALETTES_NOTE = `/**
34
38
  */
35
39
  `
36
40
 
41
+ /**
42
+ * Derive a safe localStorage key from an npm package name.
43
+ * Strips the scope, keeps case, maps unsafe chars to '-'. Returns '' when there
44
+ * is no usable name — the caller then omits `storageKey` rather than baking in a
45
+ * hardcoded default (the consumer config / library fallback decides instead).
46
+ * @param {string} [name]
47
+ * @returns {string}
48
+ */
49
+ export function storageKeyFromName(name) {
50
+ return String(name ?? '')
51
+ .replace(/^@/, '')
52
+ .replace(/[^a-zA-Z0-9_-]+/g, '-')
53
+ .replace(/^-+|-+$/g, '')
54
+ }
55
+
37
56
  /**
38
57
  * Serialize a rokkit config object to a JS module string with a header comment.
39
58
  * @param {Record<string, unknown>} config
@@ -201,11 +220,12 @@ export function generateChartConfig({ chartColors, chartShades }) {
201
220
  /**
202
221
  * Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
203
222
  * @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
204
- * includeChart?: boolean, chartColors?: string, chartShades?: string }} [opts]
223
+ * includeChart?: boolean, chartColors?: string, chartShades?: string,
224
+ * storageKey?: string }} [opts]
205
225
  * @returns {Record<string, unknown>}
206
226
  */
207
227
  export function generateZenSumiConfig(opts = {}) {
208
- const { themes, defaultTheme, switcher, includeChart, chartColors, chartShades } = opts
228
+ const { themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey } = opts
209
229
  const config = {
210
230
  palettes: ZEN_SUMI_PALETTES,
211
231
  colorSpace: 'oklch',
@@ -230,7 +250,7 @@ export function generateZenSumiConfig(opts = {}) {
230
250
  themes: themes && themes.length ? themes : ['rokkit', 'zen-sumi'],
231
251
  defaultTheme: defaultTheme || 'zen-sumi',
232
252
  switcher: switcher || 'full',
233
- storageKey: 'rokkit-theme'
253
+ ...(storageKey ? { storageKey } : {})
234
254
  }
235
255
  if (includeChart) config.chart = generateChartConfig({ chartColors, chartShades })
236
256
  return config
@@ -242,7 +262,8 @@ export function generateZenSumiConfig(opts = {}) {
242
262
  * optional `icons` and `chart` sections.
243
263
  * @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
244
264
  * iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
245
- * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string }} opts
265
+ * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string,
266
+ * storageKey?: string }} opts
246
267
  * @returns {Record<string, unknown>}
247
268
  */
248
269
  export function generateConfig({
@@ -256,20 +277,22 @@ export function generateConfig({
256
277
  switcher,
257
278
  includeChart,
258
279
  chartColors,
259
- chartShades
280
+ chartShades,
281
+ storageKey
260
282
  }) {
261
283
  if (palette === 'zen-sumi') {
262
- return generateZenSumiConfig({ themes, defaultTheme, switcher, includeChart, chartColors, chartShades })
284
+ return generateZenSumiConfig({ themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey })
263
285
  }
264
286
 
265
287
  const config = {
266
288
  skin: resolveSkin(palette, customColors),
267
289
  colorSpace: 'rgb',
268
290
  tokens: 'core',
291
+ shape: { radius: 'soft' },
269
292
  themes,
270
293
  defaultTheme: defaultTheme || themes[0],
271
294
  switcher,
272
- storageKey: 'rokkit-theme'
295
+ ...(storageKey ? { storageKey } : {})
273
296
  }
274
297
 
275
298
  const iconConfig = {}
@@ -289,11 +312,12 @@ export function generateConfig({
289
312
  * @returns {string}
290
313
  */
291
314
  export function generateUnoConfig() {
292
- return `import { defineConfig } from 'unocss'
315
+ return `import { defineConfig, transformerDirectives } from 'unocss'
293
316
  import { presetRokkit } from '@rokkit/unocss'
294
317
  import config from './rokkit.config.js'
295
318
 
296
319
  export default defineConfig({
320
+ transformers: [transformerDirectives()],
297
321
  presets: [presetRokkit(config)]
298
322
  })
299
323
  `
@@ -312,50 +336,6 @@ export function generateAppCssImports(themes) {
312
336
  return lines
313
337
  }
314
338
 
315
- /**
316
- * Generate the flash-prevention script for app.html.
317
- *
318
- * Themes flip on `[data-mode="dark"]` only — there is no `@media
319
- * (prefers-color-scheme)` fallback — so every switcher needs a script that
320
- * resolves the mode pre-paint (mirrors `@rokkit/unocss`'s `themeHook`):
321
- * - `system` tracks the OS preference directly (no persisted toggle).
322
- * - `manual` / `full` read the persisted mode and resolve a stored-or-default
323
- * `'system'`/`'auto'` against the OS preference (default: follow the OS).
324
- *
325
- * @param {string} switcher — 'system' | 'manual' | 'full'
326
- * @param {string} [storageKey='rokkit-theme']
327
- * @param {string} [defaultStyle='rokkit']
328
- * @returns {string} the `<script>` tag to inject after `<body>`
329
- */
330
- export function generateInitScript(switcher, storageKey = 'rokkit-theme', defaultStyle = 'rokkit') {
331
- if (switcher === 'system') {
332
- return ` <script>
333
- (function () {
334
- try {
335
- document.body.dataset.mode =
336
- matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
337
- } catch (e) {}
338
- })()
339
- </script>`
340
- }
341
-
342
- const setStyle =
343
- switcher === 'full' ? `b.dataset.style = t.style || '${defaultStyle}'\n ` : ''
344
-
345
- return ` <script>
346
- (function () {
347
- try {
348
- var t = JSON.parse(localStorage.getItem('${storageKey}') || '{}')
349
- var b = document.body
350
- ${setStyle}var m = t.mode || 'system'
351
- if (m === 'system' || m === 'auto')
352
- m = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
353
- b.dataset.mode = m
354
- } catch (e) {}
355
- })()
356
- </script>`
357
- }
358
-
359
339
  const PROMPTS_CONFIG = [
360
340
  {
361
341
  type: 'select',
@@ -375,12 +355,6 @@ const PROMPTS_CONFIG = [
375
355
  message: 'Primary color (Tailwind palette name)',
376
356
  initial: 'orange'
377
357
  },
378
- {
379
- type: (_, values) => (values.palette === 'custom' ? 'text' : null),
380
- name: 'secondary',
381
- message: 'Secondary color',
382
- initial: 'pink'
383
- },
384
358
  {
385
359
  type: (_, values) => (values.palette === 'custom' ? 'text' : null),
386
360
  name: 'accent',
@@ -578,19 +552,26 @@ export async function init(_opts = {}, adapters = {}) {
578
552
  if (response.palette === 'custom') {
579
553
  response.customColors = {
580
554
  primary: response.primary,
581
- secondary: response.secondary,
582
555
  accent: response.accent,
583
556
  surface: response.surface
584
557
  }
585
558
  }
586
559
 
560
+ let appName
561
+ try {
562
+ appName = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8')).name
563
+ } catch {
564
+ appName = undefined
565
+ }
566
+ response.storageKey = storageKeyFromName(appName)
567
+
587
568
  const config = generateConfig(response)
588
569
 
589
570
  writeRokkitConfig(cwd, config)
590
571
  writeUnoConfig(cwd)
591
572
  writeAppCss(cwd, generateAppCssImports(config.themes))
592
573
 
593
- const initScript = generateInitScript(config.switcher, config.storageKey, config.defaultTheme)
574
+ const initScript = themeInitScript({ storageKey: config.storageKey, defaultStyle: config.defaultTheme })
594
575
  if (initScript) writeAppHtml(cwd, initScript, config.storageKey)
595
576
 
596
577
  console.info('\nDone! Run `rokkit doctor` to verify your setup.')