@commonpub/layer 0.69.1 → 0.70.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.
@@ -20,7 +20,10 @@ import {
20
20
  randomizeRecipe,
21
21
  randomName,
22
22
  buildPalette,
23
- harmonyColors,
23
+ suggestPalettes,
24
+ type PaletteSuggestion,
25
+ hexToHsl,
26
+ hslToHex,
24
27
  contrast,
25
28
  wcag,
26
29
  COLOR_VIBES,
@@ -150,11 +153,35 @@ function palStrip(accent: string, scheme: HarmonyScheme, mode: 'light' | 'dark')
150
153
  const p = buildPalette({ accent, scheme, mode }).sem;
151
154
  return [p.bg, p.surface, p.accent, p.secondary, p.text];
152
155
  }
153
- /** The accent + its harmony companions the "suggested family" strip. */
154
- const familyStrip = computed<string[]>(() => [
155
- recipe.value.accent,
156
- ...harmonyColors(recipe.value.accent, recipe.value.scheme),
157
- ]);
156
+ // --- Palette options + HSL accent control (Phase 4) -------------------
157
+ // From the chosen accent, offer several ready-to-apply harmonized palettes
158
+ // (choice over abstract knobs) + an HSL slider control (less janky than the
159
+ // bare native swatch).
160
+ const palettes = computed<PaletteSuggestion[]>(() => suggestPalettes(recipe.value.accent, recipe.value.mode));
161
+ const activePalette = computed<string>(() => {
162
+ const r = recipe.value;
163
+ return (
164
+ palettes.value.find(
165
+ (p) =>
166
+ p.scheme === r.scheme &&
167
+ p.neutralHue === r.neutralHue &&
168
+ p.neutralSat === r.neutralSat &&
169
+ (p.secondary ?? undefined) === (r.secondary ?? undefined),
170
+ )?.k ?? ''
171
+ );
172
+ });
173
+ function applyPaletteSuggestion(p: PaletteSuggestion): void {
174
+ recipe.value.scheme = p.scheme;
175
+ recipe.value.neutralHue = p.neutralHue;
176
+ recipe.value.neutralSat = p.neutralSat;
177
+ recipe.value.secondary = p.secondary;
178
+ useSecondary.value = Boolean(p.secondary);
179
+ }
180
+ const accentHsl = computed(() => hexToHsl(recipe.value.accent));
181
+ function setAccentHsl(part: 'h' | 's' | 'l', v: number): void {
182
+ const c = accentHsl.value;
183
+ recipe.value.accent = hslToHex(part === 'h' ? v : c.h, part === 's' ? v : c.s, part === 'l' ? v : c.l);
184
+ }
158
185
 
159
186
  // --- Color actions -----------------------------------------------------
160
187
 
@@ -347,7 +374,31 @@ function finishWith(apply: boolean): void {
347
374
  <input type="file" accept="image/*" hidden @change="onImagePick" />
348
375
  </label>
349
376
  </span>
377
+ <span class="cpub-studio-hslrow">
378
+ <span class="cpub-studio-hsl"><span>H</span><input type="range" min="0" max="360" :value="Math.round(accentHsl.h)" aria-label="Hue" @input="setAccentHsl('h', +($event.target as HTMLInputElement).value)" /></span>
379
+ <span class="cpub-studio-hsl"><span>S</span><input type="range" min="0" max="100" :value="Math.round(accentHsl.s)" aria-label="Saturation" @input="setAccentHsl('s', +($event.target as HTMLInputElement).value)" /></span>
380
+ <span class="cpub-studio-hsl"><span>L</span><input type="range" min="0" max="100" :value="Math.round(accentHsl.l)" aria-label="Lightness" @input="setAccentHsl('l', +($event.target as HTMLInputElement).value)" /></span>
381
+ </span>
350
382
  </label>
383
+ <div class="cpub-studio-field">
384
+ <span class="cpub-studio-lbl">Palette options <span class="cpub-studio-hint">from your accent</span></span>
385
+ <div class="cpub-studio-palopts">
386
+ <button
387
+ v-for="p in palettes"
388
+ :key="p.k"
389
+ type="button"
390
+ class="cpub-studio-palopt"
391
+ :class="{ on: activePalette === p.k }"
392
+ :title="p.label"
393
+ @click="applyPaletteSuggestion(p)"
394
+ >
395
+ <span class="cpub-studio-palopt-strip">
396
+ <span v-for="(c, ci) in p.preview" :key="ci" :style="{ background: c }" />
397
+ </span>
398
+ <span class="cpub-studio-palopt-name">{{ p.label }}</span>
399
+ </button>
400
+ </div>
401
+ </div>
351
402
  <label class="cpub-studio-field">
352
403
  <span class="cpub-studio-lbl">Color family <span class="cpub-studio-hint">harmony</span></span>
353
404
  <span class="cpub-studio-seg cpub-studio-seg-wrap">
@@ -360,12 +411,6 @@ function finishWith(apply: boolean): void {
360
411
  <button v-for="n in NEUTRALS" :key="n.k" type="button" :class="{ on: neutralMode === n.k }" @click="setNeutral(n.k)">{{ n.label }}</button>
361
412
  </span>
362
413
  </label>
363
- <div class="cpub-studio-field">
364
- <span class="cpub-studio-lbl">Suggested family</span>
365
- <span class="cpub-studio-family">
366
- <span v-for="(c, i) in familyStrip" :key="i" :style="{ background: c }" :title="c" />
367
- </span>
368
- </div>
369
414
  <div class="cpub-studio-toggle-line">
370
415
  <span class="cpub-studio-lbl">Hand-pick secondary</span>
371
416
  <button type="button" class="cpub-studio-switch" :class="{ on: useSecondary }" :aria-pressed="useSecondary" @click="toggleSecondary" />
@@ -602,6 +647,19 @@ function finishWith(apply: boolean): void {
602
647
  .cpub-studio-colorrow { display: flex; gap: var(--space-2); align-items: center; }
603
648
  .cpub-studio-colorpick { width: 40px; height: 36px; padding: 0; border: var(--border-width-thin) solid var(--border2); background: none; cursor: pointer; flex-shrink: 0; }
604
649
 
650
+ .cpub-studio-hslrow { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); margin-top: 6px; }
651
+ .cpub-studio-hsl { display: flex; align-items: center; gap: 4px; }
652
+ .cpub-studio-hsl > span { font-family: var(--font-mono); font-size: var(--text-label); color: var(--text-faint); width: 10px; }
653
+ .cpub-studio-hsl input[type="range"] { flex: 1; min-width: 0; accent-color: var(--accent); }
654
+
655
+ .cpub-studio-palopts { display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr)); gap: 6px; }
656
+ .cpub-studio-palopt { display: flex; flex-direction: column; gap: 4px; background: var(--surface2); border: var(--border-width-thin) solid var(--border2); padding: 5px; cursor: pointer; }
657
+ .cpub-studio-palopt:hover { border-color: var(--text-faint); }
658
+ .cpub-studio-palopt.on { border-color: var(--accent); background: var(--accent-bg); }
659
+ .cpub-studio-palopt-strip { display: flex; height: 22px; overflow: hidden; }
660
+ .cpub-studio-palopt-strip > span { flex: 1; }
661
+ .cpub-studio-palopt-name { font-family: var(--font-mono); font-size: var(--text-label); color: var(--text-dim); text-align: center; }
662
+
605
663
  .cpub-studio-seg { display: grid; gap: 4px; grid-auto-flow: column; grid-auto-columns: 1fr; }
606
664
  .cpub-studio-seg-wrap { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); }
607
665
  .cpub-studio-seg button { background: var(--surface2); border: var(--border-width-thin) solid var(--border2); color: var(--text-dim); font-family: var(--font-mono); font-size: var(--text-label); font-weight: var(--font-weight-semibold); padding: 7px 4px; cursor: pointer; text-align: center; line-height: 1.2; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.69.1",
3
+ "version": "0.70.1",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -53,17 +53,17 @@
53
53
  "vue": "^3.4.0",
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
- "@commonpub/auth": "0.8.0",
57
- "@commonpub/config": "0.20.0",
58
56
  "@commonpub/editor": "0.7.11",
59
- "@commonpub/schema": "0.38.0",
60
57
  "@commonpub/docs": "0.6.3",
61
- "@commonpub/protocol": "0.13.0",
58
+ "@commonpub/config": "0.20.0",
62
59
  "@commonpub/learning": "0.5.2",
60
+ "@commonpub/auth": "0.8.0",
61
+ "@commonpub/protocol": "0.13.0",
63
62
  "@commonpub/server": "2.83.0",
64
- "@commonpub/theme-studio": "0.4.1",
65
- "@commonpub/ui": "0.12.1",
66
- "@commonpub/explainer": "0.7.15"
63
+ "@commonpub/theme-studio": "0.5.1",
64
+ "@commonpub/ui": "0.12.2",
65
+ "@commonpub/explainer": "0.7.15",
66
+ "@commonpub/schema": "0.38.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@testing-library/jest-dom": "^6.9.1",
package/theme/base.css CHANGED
@@ -209,6 +209,12 @@
209
209
  --shadow-lg: 6px 6px 0 var(--border);
210
210
  --shadow-xl: 8px 8px 0 var(--border);
211
211
  --shadow-accent: 4px 4px 0 var(--accent);
212
+ /* Component surface shadow. Built-in themes deliberately do NOT override these
213
+ * (only --shadow-* above), so buttons/cards keep the offset-block signature.
214
+ * Theme Studio emits them per the recipe's shadowStyle so a custom theme's
215
+ * buttons/cards reflect its archetype (neumorphic relief, soft blur, etc.). */
216
+ --shadow-block: 4px 4px 0 var(--border);
217
+ --shadow-block-sm: 2px 2px 0 var(--border);
212
218
 
213
219
  /* === TRANSITIONS === */
214
220
  --transition-fast: 0.1s ease;
@@ -27,11 +27,11 @@
27
27
  .cpub-btn-primary {
28
28
  background: var(--accent);
29
29
  color: var(--color-text-inverse);
30
- box-shadow: 4px 4px 0 var(--border);
30
+ box-shadow: var(--shadow-block);
31
31
  }
32
32
 
33
33
  .cpub-btn-primary:hover {
34
- box-shadow: 2px 2px 0 var(--border);
34
+ box-shadow: var(--shadow-block-sm);
35
35
  }
36
36
 
37
37
  .cpub-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
@@ -40,9 +40,9 @@
40
40
  background: var(--secondary);
41
41
  color: var(--color-on-secondary);
42
42
  border-color: var(--secondary);
43
- box-shadow: 4px 4px 0 var(--border);
43
+ box-shadow: var(--shadow-block);
44
44
  }
45
- .cpub-btn-secondary:hover { background: var(--secondary-hover); box-shadow: 2px 2px 0 var(--border); }
45
+ .cpub-btn-secondary:hover { background: var(--secondary-hover); box-shadow: var(--shadow-block-sm); }
46
46
  .cpub-btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }
47
47
 
48
48
  .cpub-btn-sm { padding: 4px 10px; font-size: 11px; min-height: 44px; }
@@ -89,7 +89,7 @@
89
89
  border: var(--border-width-default) solid var(--border);
90
90
  padding: 16px;
91
91
  margin-bottom: 12px;
92
- box-shadow: 4px 4px 0 var(--border);
92
+ box-shadow: var(--shadow-block);
93
93
  }
94
94
 
95
95
  .cpub-sb-title {
@@ -164,7 +164,7 @@
164
164
  border-color: var(--accent);
165
165
  background: var(--accent-bg);
166
166
  color: var(--accent);
167
- box-shadow: 2px 2px 0 var(--border);
167
+ box-shadow: var(--shadow-block-sm);
168
168
  }
169
169
 
170
170
  .cpub-page-ellipsis {