@motion-proto/live-tokens 0.67.1 → 0.68.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 (63) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/generate-theme.mjs +5 -4
  3. package/dist-plugin/adjust/index.d.cts +1 -1
  4. package/dist-plugin/adjust/index.d.ts +1 -1
  5. package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2UX6EVVA.js} +25 -25
  6. package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.cts} +1 -1
  7. package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.ts} +1 -1
  8. package/dist-plugin/fontPairing/index.d.cts +1 -1
  9. package/dist-plugin/fontPairing/index.d.ts +1 -1
  10. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  11. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  12. package/dist-plugin/index.cjs +27 -27
  13. package/dist-plugin/index.d.cts +1 -1
  14. package/dist-plugin/index.d.ts +1 -1
  15. package/dist-plugin/index.js +5 -5
  16. package/dist-plugin/migrateData/index.cjs +25 -25
  17. package/dist-plugin/migrateData/index.d.cts +1 -1
  18. package/dist-plugin/migrateData/index.d.ts +1 -1
  19. package/dist-plugin/migrateData/index.js +1 -1
  20. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  21. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  22. package/package.json +4 -4
  23. package/src/editor/bootstrap.ts +6 -6
  24. package/src/editor/component-editor/ImageLightboxEditor.svelte +1 -1
  25. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
  26. package/src/editor/core/fonts/fontLoader.ts +2 -2
  27. package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
  28. package/src/editor/core/productionPulse.ts +3 -3
  29. package/src/editor/core/sketch/index.ts +52 -44
  30. package/src/editor/core/sketch/maskField.ts +9 -9
  31. package/src/editor/core/sketch/sketchLayer.ts +9 -9
  32. package/src/editor/core/sketch/sketchRegistry.ts +34 -34
  33. package/src/editor/core/sketch/sketchStore.ts +98 -83
  34. package/src/editor/core/sketch/sketchStyleService.ts +4 -4
  35. package/src/editor/core/sketch/sketchStyles.ts +28 -28
  36. package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
  37. package/src/editor/core/themes/loadRows.ts +8 -8
  38. package/src/editor/core/themes/themeDocumentSync.ts +2 -2
  39. package/src/editor/core/themes/themeInit.ts +2 -2
  40. package/src/editor/core/themes/themeService.ts +17 -17
  41. package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
  42. package/src/editor/core/themes/themeTypes.ts +10 -6
  43. package/src/editor/docs/content/getting-started.md +1 -1
  44. package/src/editor/docs/content/sketch-mode.md +22 -17
  45. package/src/editor/docs/content/themes-workflow.md +11 -11
  46. package/src/editor/docs/content/where-themes-live.md +6 -6
  47. package/src/editor/docs/content.generated.ts +4 -4
  48. package/src/editor/index.ts +2 -2
  49. package/src/editor/ui/ThemePanel.svelte +52 -52
  50. package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
  51. package/src/editor/ui/sketch/SketchTab.svelte +64 -38
  52. package/src/live-tokens/data/sketch-styles/dry.json +5 -5
  53. package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
  54. package/src/live-tokens/data/themes/autumn.json +1 -1
  55. package/src/live-tokens/data/themes/halloween.json +1 -1
  56. package/src/live-tokens/data/themes/midnight-study.json +1 -1
  57. package/src/live-tokens/data/themes/ocean.json +1 -1
  58. package/src/live-tokens/data/themes/royal-velvet.json +1 -1
  59. package/src/live-tokens/data/themes/sketchy.json +1 -1
  60. package/src/live-tokens/data/themes/spring-meadow.json +1 -1
  61. package/src/live-tokens/data/themes/sunset.json +1 -1
  62. package/src/system/backdrop/backdrop.ts +1 -1
  63. package/src/system/components/SectionDivider.svelte +1 -1
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- // The root of the look hierarchy: one Theme panel that owns the open theme,
2
+ // The root of the theme hierarchy: one Theme panel that owns the open theme,
3
3
  // with the colors and type and the components as parts that report rather
4
4
  // than manage.
5
5
  //
@@ -15,7 +15,7 @@
15
15
  getProductionTheme,
16
16
  loadTheme,
17
17
  applyTheme,
18
- adoptLook,
18
+ adoptTheme,
19
19
  freshName,
20
20
  saveAsTheme,
21
21
  saveActiveTheme,
@@ -27,8 +27,8 @@
27
27
  THEME_APPLIED_EVENT,
28
28
  type AppliedThemeDetail,
29
29
  } from '../core/themes/themeDocumentSync';
30
- import { countComponentsOffLook, lookProductionState } from '../core/themes/lookSummary';
31
- import { commitPreview, previewTheme, previewColorsAndType, revertPreview } from '../core/preview/lookPreview';
30
+ import { countComponentsOffTheme, themeProductionState } from '../core/themes/themeSummary';
31
+ import { commitPreview, previewTheme, previewColorsAndType, revertPreview } from '../core/preview/themePreview';
32
32
  import {
33
33
  deleteColorsAndType,
34
34
  hydrateColorsAndType,
@@ -58,7 +58,7 @@
58
58
  markComponentSaved,
59
59
  } from '../core/store/editorStore';
60
60
  import { openThemeSlug } from '../core/store/editorConfigStore';
61
- import { sketchOffLook, themeSketchStyle } from '../core/sketch/sketchStore';
61
+ import { sketchOffTheme, themeSketchSettings } from '../core/sketch/sketchStore';
62
62
  import { editorView } from '../core/store/editorViewStore';
63
63
  import {
64
64
  componentActiveRevision,
@@ -100,7 +100,7 @@
100
100
 
101
101
  let currentDisplayName = $state('Motion Proto');
102
102
  let activeIsProtected = $derived($openThemeSlug === 'default');
103
- let activeRowId = $derived(loadRowId('look', $openThemeSlug));
103
+ let activeRowId = $derived(loadRowId('theme', $openThemeSlug));
104
104
 
105
105
  type SaveState = 'idle' | 'saving' | 'saved' | 'error';
106
106
  const setSaveStatus = (s: SaveState) => (saveStatus = s);
@@ -108,7 +108,7 @@
108
108
  let dirtyComponentCount = $derived(
109
109
  Object.values($componentDirty).filter(Boolean).length,
110
110
  );
111
- let unsavedEdits = $derived($colorsAndTypeDirty || dirtyComponentCount > 0 || $sketchOffLook);
111
+ let unsavedEdits = $derived($colorsAndTypeDirty || dirtyComponentCount > 0 || $sketchOffTheme);
112
112
 
113
113
  async function refreshFiles() {
114
114
  try {
@@ -127,7 +127,7 @@
127
127
  // Deleting the open theme repoints active without going through Load
128
128
  // or Save, both of which already set this; this is the path that
129
129
  // makes sketchStore's "set by every path that opens a theme" true.
130
- themeSketchStyle.set(active.sketchStyle);
130
+ themeSketchSettings.set(active.sketchSettings);
131
131
  }
132
132
  } catch {
133
133
  // silent
@@ -161,7 +161,7 @@
161
161
 
162
162
  let components: ComponentSummary[] = $state([]);
163
163
 
164
- let componentsOffLook = $derived(countComponentsOffLook(components));
164
+ let componentsOffTheme = $derived(countComponentsOffTheme(components));
165
165
 
166
166
  async function refreshComponents() {
167
167
  try {
@@ -314,7 +314,7 @@
314
314
  }
315
315
  }
316
316
 
317
- // ── Adopt: ship the whole look ────────────────────────────────────────
317
+ // ── Adopt: ship the whole theme ────────────────────────────────────────
318
318
  //
319
319
  // One action at the root, because production is one saved theme: Adopt saves
320
320
  // the open one and publishes it whole. The component editors' Adopt runs the
@@ -325,7 +325,7 @@
325
325
  const setAdoptStatus = (s: AdoptStatus) => (adoptStatus = s);
326
326
 
327
327
  let production = $derived(
328
- lookProductionState({
328
+ themeProductionState({
329
329
  openTheme: $openThemeSlug,
330
330
  productionTheme: $productionTheme?._fileName ?? null,
331
331
  unpublished: unsavedEdits || $liveMovedSinceBake,
@@ -346,7 +346,7 @@
346
346
  /**
347
347
  * Adopt publishes what is saved, so the flow is flush, save, ship. The
348
348
  * protected Default theme cannot record what shipped: the user clicked
349
- * Adopt, and which file holds the look is bookkeeping they should not have
349
+ * Adopt, and which file holds the theme is bookkeeping they should not have
350
350
  * to think about, so it forks to a theme of their own first.
351
351
  */
352
352
  async function runAdopt(saveComponents = false) {
@@ -362,7 +362,7 @@
362
362
  } else {
363
363
  await saveActiveTheme(currentDisplayName);
364
364
  }
365
- await adoptLook();
365
+ await adoptTheme();
366
366
  // The panel's own production pulse re-reads identity, the production
367
367
  // theme and the component summary.
368
368
  bumpProductionRevision();
@@ -388,7 +388,7 @@
388
388
  let previewRow: LoadRow | null = $state(null);
389
389
  // `$state.raw`: the preview engine structuredClones these, and a deep `$state`
390
390
  // proxy is not cloneable. Both are only ever reassigned whole.
391
- let previewLook: Theme | null = $state.raw(null);
391
+ let previewedTheme: Theme | null = $state.raw(null);
392
392
  let previewLayer: ColorsAndType | null = $state.raw(null);
393
393
  let colorsOnly = $state(false);
394
394
  let effectiveColorsOnly = $derived(isColorsOnly(previewRow, colorsOnly));
@@ -397,47 +397,47 @@
397
397
  function cancelPreview() {
398
398
  revertPreview();
399
399
  previewRow = null;
400
- previewLook = null;
400
+ previewedTheme = null;
401
401
  previewLayer = null;
402
402
  }
403
403
 
404
404
  function acceptPreview() {
405
405
  commitPreview();
406
406
  previewRow = null;
407
- previewLook = null;
407
+ previewedTheme = null;
408
408
  previewLayer = null;
409
409
  }
410
410
 
411
411
  /** Paint whatever is selected, through the engine the mode calls for. A
412
- * theme's colors and type are the slice embedded in it, so one look
412
+ * theme's colors and type are the slice embedded in it, so one theme
413
413
  * previews either way. */
414
414
  async function repaint(): Promise<void> {
415
415
  if (previewLayer) {
416
416
  previewColorsAndType(previewLayer);
417
417
  return;
418
418
  }
419
- if (!previewLook) return;
420
- if (effectiveColorsOnly) previewColorsAndType(previewLook.colorsAndType);
421
- else await previewTheme(previewLook);
419
+ if (!previewedTheme) return;
420
+ if (effectiveColorsOnly) previewColorsAndType(previewedTheme.colorsAndType);
421
+ else await previewTheme(previewedTheme);
422
422
  }
423
423
 
424
424
  async function handleSelect(row: LoadRow) {
425
425
  if (row.fileName === previewRow?.fileName) return;
426
- // The open theme in whole-look mode is what the page already shows. In
426
+ // The open theme in whole-theme mode is what the page already shows. In
427
427
  // colors-only mode it is a real operation: it puts the theme's own colors
428
428
  // back over whatever the user has edited.
429
- if (row.kind === 'look' && row.slug === $openThemeSlug && !isColorsOnly(row, colorsOnly)) {
429
+ if (row.kind === 'theme' && row.slug === $openThemeSlug && !isColorsOnly(row, colorsOnly)) {
430
430
  cancelPreview();
431
431
  return;
432
432
  }
433
433
  try {
434
- const look = row.kind === 'look' ? await loadTheme(row.slug) : null;
434
+ const theme = row.kind === 'theme' ? await loadTheme(row.slug) : null;
435
435
  const layer = row.kind === 'layer' ? await loadColorsAndType(row.slug) : null;
436
436
  // The window may have closed during the fetch; painting then would leave
437
437
  // a preview on screen with no Save or Cancel in sight.
438
438
  if (!showFileList) return;
439
439
  previewRow = row;
440
- previewLook = look;
440
+ previewedTheme = theme;
441
441
  previewLayer = layer;
442
442
  await repaint();
443
443
  } catch (err) {
@@ -457,7 +457,7 @@
457
457
  await commitColorsOnly();
458
458
  return;
459
459
  }
460
- await commitWholeLook();
460
+ await commitWholeTheme();
461
461
  }
462
462
 
463
463
  function filledNote(filled: ThemeFillReport): string | null {
@@ -467,7 +467,7 @@
467
467
  + `The editor filled them from the current defaults.`;
468
468
  }
469
469
 
470
- async function commitWholeLook() {
470
+ async function commitWholeTheme() {
471
471
  const row = previewRow;
472
472
  if (!row) return;
473
473
  if (unsavedEdits) {
@@ -477,7 +477,7 @@
477
477
  if (!ok) return;
478
478
  }
479
479
  // The exact theme being opened is already painted. Hand that preview to
480
- // the store load without restoring the old look across the request.
480
+ // the store load without restoring the old theme across the request.
481
481
  acceptPreview();
482
482
  let result: Awaited<ReturnType<typeof applyTheme>>;
483
483
  try {
@@ -501,8 +501,8 @@
501
501
  // The selection remains visible after a failed request; put its preview
502
502
  // back so the dialog and page continue to agree.
503
503
  previewRow = row;
504
- previewLook = row.kind === 'look' ? await loadTheme(row.slug).catch(() => null) : null;
505
- if (previewLook) await repaint();
504
+ previewedTheme = row.kind === 'theme' ? await loadTheme(row.slug).catch(() => null) : null;
505
+ if (previewedTheme) await repaint();
506
506
  return;
507
507
  }
508
508
 
@@ -517,7 +517,7 @@
517
517
  await saveAsTheme(freshName('my-theme', taken), 'My Theme');
518
518
  await refreshActive();
519
519
  }
520
- await adoptLook();
520
+ await adoptTheme();
521
521
  bumpProductionRevision();
522
522
  flashStatus(setAdoptStatus, 'done');
523
523
  } catch (err) {
@@ -537,7 +537,7 @@
537
537
  const row = previewRow;
538
538
  // Read the payload before the revert clears it. The read doors mark where
539
539
  // they answered from; the buffer holds content alone.
540
- const picked = previewLayer ?? previewLook?.colorsAndType ?? null;
540
+ const picked = previewLayer ?? previewedTheme?.colorsAndType ?? null;
541
541
  if (!row || !picked) return;
542
542
  const { _fileName, _source, ...buffer } = picked;
543
543
  if (unsavedEdits) {
@@ -548,7 +548,7 @@
548
548
  }
549
549
  // Hand the page back to the store before loading. The renderer diffs
550
550
  // against its own last-applied set, which never saw the preview's direct
551
- // writes; starting from the live look makes the load land exactly as it
551
+ // writes; starting from the live theme makes the load land exactly as it
552
552
  // does with no preview in play.
553
553
  cancelPreview();
554
554
  showFileList = false;
@@ -633,14 +633,14 @@
633
633
  if (row.isProtected) return;
634
634
  if (row.kind === 'layer') return deleteColorsFile(row);
635
635
  // Deleting the open theme is legal: the server materialises only the
636
- // deltas needed to keep the look on screen. Where open lands depends on
636
+ // deltas needed to keep the theme on screen. Where open lands depends on
637
637
  // something the client can't see — deleting a local copy that shadows a
638
638
  // shipped theme restores the shipped one and keeps naming it, while
639
639
  // deleting a local-only theme sends open back to Motion Proto.
640
640
  const wasActive = row.slug === $openThemeSlug;
641
641
  const ok = window.confirm(
642
642
  wasActive
643
- ? `Delete theme "${row.name}"? The editor moves to the version shipped with the package if there is one, otherwise Motion Proto. The look on screen stays as it is.`
643
+ ? `Delete theme "${row.name}"? The editor moves to the version shipped with the package if there is one, otherwise Motion Proto. What is on screen stays as it is.`
644
644
  : `Delete theme "${row.name}"?`,
645
645
  );
646
646
  if (!ok) return;
@@ -687,18 +687,18 @@
687
687
  }
688
688
  </script>
689
689
 
690
- <div class="look-panel">
690
+ <div class="theme-panel">
691
691
  <div class="mfm-header">
692
692
  <span class="mfm-header-label">Theme</span>
693
693
  <UIInfoPopover title="Theme" ariaLabel="About the theme">
694
694
  <p>
695
- A <strong>theme</strong> is a whole look in one file: the colors and type plus a setting for every component.
695
+ A <strong>theme</strong> is a whole theme in one file: the colors and type plus a setting for every component.
696
696
  </p>
697
697
  <p>
698
- It holds its own copy of that data, so the theme you open is the whole look, and one theme can never break another.
698
+ It holds its own copy of that data, so the theme you open is the whole theme, and one theme can never break another.
699
699
  </p>
700
700
  <p>
701
- <strong>Load</strong> opens the list. Picking a theme shows it on the page as a preview, so you can try each look with nothing written to disk. Pick another to compare, or <strong>Cancel</strong> to go back to where you were.
701
+ <strong>Load</strong> opens the list. Picking a theme shows it on the page as a preview, so you can try each theme with nothing written to disk. Pick another to compare, or <strong>Cancel</strong> to go back to where you were.
702
702
  </p>
703
703
  <p>
704
704
  <strong>Save</strong> opens and adopts the previewed theme: the editor works on it from then on and production ships it immediately. Previewing and cancelling never change what your site ships.
@@ -781,7 +781,7 @@
781
781
  disabled={activeIsProtected || saveStatus === 'saving'}
782
782
  title={activeIsProtected
783
783
  ? 'Motion Proto is read-only. Use Save As to capture under a new name.'
784
- : 'Update this theme from the look on screen'}
784
+ : 'Update this theme from the theme on screen'}
785
785
  >
786
786
  <i
787
787
  class="fas"
@@ -794,7 +794,7 @@
794
794
  {#if saveStatus === 'idle'}Save{:else if saveStatus === 'saving'}Saving{:else if saveStatus === 'saved'}Saved{:else}Error{/if}
795
795
  </span>
796
796
  </button>
797
- <button class="mfm-btn mfm-btn-row" onclick={openSaveAs} title="Save the current look as a new theme">
797
+ <button class="mfm-btn mfm-btn-row" onclick={openSaveAs} title="Save the theme on screen as a new theme">
798
798
  <i class="fas fa-copy"></i>
799
799
  <span>Save As…</span>
800
800
  </button>
@@ -818,8 +818,8 @@
818
818
  </div>
819
819
  </div>
820
820
 
821
- <!-- Parts of the look: what each holds now, no lifecycle of its own. -->
822
- <div class="look-parts">
821
+ <!-- Parts of the theme: what each holds now, no lifecycle of its own. -->
822
+ <div class="theme-parts">
823
823
  <div class="part-head part-static">
824
824
  <span class="part-label">Colors &amp; Type</span>
825
825
  <span class="part-summary">
@@ -845,8 +845,8 @@
845
845
  <span class="part-label">Components</span>
846
846
  <span class="part-summary">
847
847
  <span class="part-summary-sep">·</span>
848
- {#if componentsOffLook > 0}
849
- <span class="part-summary-count">{componentsOffLook}</span>
848
+ {#if componentsOffTheme > 0}
849
+ <span class="part-summary-count">{componentsOffTheme}</span>
850
850
  <span>off the theme</span>
851
851
  {:else}
852
852
  <span>in sync</span>
@@ -868,10 +868,10 @@
868
868
  <span class="part-label">Sketchstyle</span>
869
869
  <span class="part-summary">
870
870
  <span class="part-summary-sep">·</span>
871
- {#if $sketchOffLook}
871
+ {#if $sketchOffTheme}
872
872
  <span>off the theme</span>
873
- {:else if $themeSketchStyle}
874
- <span class="part-summary-text">{$themeSketchStyle.label}</span>
873
+ {:else if $themeSketchSettings}
874
+ <span class="part-summary-text">{$themeSketchSettings.label}</span>
875
875
  {:else}
876
876
  <span>none</span>
877
877
  {/if}
@@ -892,7 +892,7 @@
892
892
  It travels with the theme when you save, load or share it.
893
893
  </p>
894
894
  <p>
895
- It does not reach a production build. Adopt publishes the crisp look; the drawing
895
+ It does not reach a production build. Adopt publishes the crisp theme; the drawing
896
896
  stays a preview.
897
897
  </p>
898
898
  </UIInfoPopover>
@@ -939,7 +939,7 @@
939
939
  onload={handleSelect}
940
940
  ondelete={handleDelete}
941
941
  onexport={(row) => handleExport(row)}
942
- canExport={(row) => row.kind === 'look'}
942
+ canExport={(row) => row.kind === 'theme'}
943
943
  exportTitle={(row) => `Export "${row.name}" as a file you can share`}
944
944
  >
945
945
  {#snippet options()}
@@ -979,7 +979,7 @@
979
979
  line-height: 1.5;
980
980
  }
981
981
 
982
- .look-panel {
982
+ .theme-panel {
983
983
  --mfm-active: #5aa85e;
984
984
  --mfm-rail-neutral: var(--ui-border);
985
985
  --mfm-rail-active: var(--mfm-active);
@@ -1081,7 +1081,7 @@
1081
1081
  font-size: 0.8em;
1082
1082
  }
1083
1083
 
1084
- /* Production state of the whole look, under the name it ships as. */
1084
+ /* Production state of the whole theme, under the name it ships as. */
1085
1085
  .mfm-prod-status {
1086
1086
  display: inline-flex;
1087
1087
  align-items: center;
@@ -1214,7 +1214,7 @@
1214
1214
 
1215
1215
  /* Parts. Each reads as a row belonging to the card above, so they sit inside
1216
1216
  the panel with the same left inset the card's rail gives its content. */
1217
- .look-parts {
1217
+ .theme-parts {
1218
1218
  display: flex;
1219
1219
  flex-direction: column;
1220
1220
  gap: var(--ui-space-4);
@@ -6,10 +6,10 @@
6
6
  import Callout from '../../../system/components/Callout.svelte';
7
7
  import Toggle from '../../../system/components/Toggle.svelte';
8
8
  import { setSketchScope } from '../../core/sketch/sketchLayer';
9
- import type { SketchStyle } from '../../core/sketch/sketchStyles';
9
+ import type { SketchStyleSettings } from '../../core/sketch/sketchStyles';
10
10
 
11
11
  interface Props {
12
- settings: SketchStyle;
12
+ settings: SketchStyleSettings;
13
13
  enabled: boolean;
14
14
  }
15
15
 
@@ -14,18 +14,19 @@
14
14
  import {
15
15
  sketchEnabled,
16
16
  setSketchEnabled,
17
- sketchOffLook,
18
- sketchStyleName,
17
+ sketchOffTheme,
18
+ selectedSketchStyleId,
19
19
  sketchDirty,
20
20
  sketchSettings,
21
- selectSketchStyle,
22
21
  updateSketchSettings,
23
22
  refreshSavedSketchStyles,
24
23
  saveCurrentSketchStyle,
25
24
  saveSelectedSketchStyle,
26
25
  deleteSavedSketchStyle,
27
26
  } from '../../core/sketch/sketchStore';
28
- import { sketchLooks } from '../../core/sketch/sketchRegistry';
27
+ import { sketchStyles } from '../../core/sketch/sketchRegistry';
28
+ import { setSketch, unsavedSketchStyle } from '../../core/sketch';
29
+ import { THEME_SKETCH_ID } from '../../core/sketch/sketchStyles';
29
30
 
30
31
  interface Props {
31
32
  /** The rail's last jump. A fresh object per click, not a bare id, so
@@ -38,8 +39,13 @@
38
39
 
39
40
  let s = $derived($sketchSettings);
40
41
 
42
+ /** The theme's own style sits in the grid like any other row, but it is the
43
+ one row with no file behind it, so Save has nothing to write over. Save As
44
+ is what gives it one, after which it is an ordinary sketchstyle. */
45
+ let onUnsavedSketchStyle = $derived($selectedSketchStyleId === THEME_SKETCH_ID);
46
+
41
47
  // Save is disabled for the protected Default theme (ThemePanel.svelte);
42
- // Save As is the gesture actually on offer there, so the off-look copy has
48
+ // Save As is the gesture actually on offer there, so the off-style copy has
43
49
  // to name it instead.
44
50
  let activeIsProtected = $derived($openThemeSlug === 'default');
45
51
 
@@ -50,7 +56,7 @@
50
56
 
51
57
  /** All closed to start: twenty-odd dials in one scroll is a wall, and the
52
58
  summary on each trigger row answers most questions without opening
53
- anything. Each section then stands alone, because shaping a look means
59
+ anything. Each section then stands alone, because shaping a style means
54
60
  reaching between them — the border's detail against the noise it
55
61
  multiplies, the fill's travel against the border's.
56
62
 
@@ -124,43 +130,49 @@
124
130
  });
125
131
 
126
132
  /** Shipped and saved sketchstyles are one choice, so they share one radio
127
- group: picking a saved one clears the shipped selection natively. */
133
+ group: picking a saved one clears the shipped selection natively.
134
+
135
+ Picks go through `setSketch`, the same door a page picker uses, so one is
136
+ the whole gesture: the style is applied, the effect comes on, and the
137
+ page's own picker follows. Selecting without applying would leave the two
138
+ pickers disagreeing about what the page is drawn with. */
128
139
  const STYLE_RADIO_GROUP = 'sketchstyle';
129
140
 
130
141
  /** What the readout under the grid says. The selection survives a dial move,
131
142
  so the base keeps its name here and the blurb reports the drift instead. */
132
143
  let active = $derived(
133
144
  $sketchDirty
134
- ? { name: s.label, blurb: `Modified from ${s.label}. Save it to keep it.` }
145
+ ? {
146
+ name: s.label,
147
+ blurb: onUnsavedSketchStyle
148
+ ? 'Modified from settings this theme carries. Save As keeps them as a sketchstyle.'
149
+ : `Modified from ${s.label}. Save it to keep it.`,
150
+ }
135
151
  : { name: s.label, blurb: s.blurb || 'Your saved sketchstyle.' },
136
152
  );
137
153
 
138
- let selectedLook = $derived($sketchLooks.find((l) => l.id === $sketchStyleName));
154
+ let selectedSketchStyle = $derived($sketchStyles.find((l) => l.id === $selectedSketchStyleId));
139
155
  /** Offered whenever the dials have moved off the sketchstyle they name. A
140
- look backed by a project file is written over. A shipped one's file lives
156
+ style backed by a project file is written over. A shipped one's file lives
141
157
  in the package, so saving it writes this project's own under the same
142
158
  name, which is how a project comes to own its Pencil: the id is the same,
143
159
  so the new file takes the shipped one's place in the grid rather than
144
160
  sitting beside it. Nothing is lost either way, since deleting that file
145
161
  uncovers the packaged one again. */
146
- let canSave = $derived(!!selectedLook && $sketchDirty);
162
+ let canSave = $derived(!!selectedSketchStyle && $sketchDirty);
147
163
  let saveTitle = $derived(
148
- !selectedLook
149
- ? 'Pick a sketchstyle to save over'
150
- : !$sketchDirty
151
- ? 'No changes to save'
152
- : selectedLook.source === 'file'
153
- ? `Save over ${selectedLook.label}`
154
- : `Keep these dials as this project's ${selectedLook.label}`,
164
+ onUnsavedSketchStyle
165
+ ? 'These settings belong to no sketchstyle file. Save As keeps them as one.'
166
+ : !selectedSketchStyle
167
+ ? 'Pick a sketchstyle to save over'
168
+ : !$sketchDirty
169
+ ? 'No changes to save'
170
+ : `Save over ${selectedSketchStyle.label}`,
155
171
  );
156
172
 
157
173
  function saveInPlace() {
158
- if (!selectedLook) return;
159
- runStyleAction(
160
- selectedLook.source === 'file'
161
- ? saveSelectedSketchStyle()
162
- : saveCurrentSketchStyle(selectedLook.label),
163
- );
174
+ if (!selectedSketchStyle) return;
175
+ runStyleAction(saveSelectedSketchStyle());
164
176
  }
165
177
 
166
178
  let naming = $state(false);
@@ -324,9 +336,9 @@
324
336
  <div class="readout" aria-live="polite">
325
337
  <span class="readout-name">{active.name}</span>
326
338
  <p class="readout-blurb">{active.blurb}</p>
327
- {#if $sketchOffLook}
339
+ {#if $sketchOffTheme}
328
340
  {#if $sketchEnabled}
329
- <p class="readout-off-look">
341
+ <p class="readout-off-style">
330
342
  {#if activeIsProtected}
331
343
  Motion Proto is read-only, and these dials are ahead of it. Use Save As in the
332
344
  Theme panel to keep them in a new theme.
@@ -336,7 +348,7 @@
336
348
  {/if}
337
349
  </p>
338
350
  {:else}
339
- <p class="readout-off-look">
351
+ <p class="readout-off-style">
340
352
  The theme carries a sketchstyle this page is not painting. Saving now would drop
341
353
  it.
342
354
  </p>
@@ -345,25 +357,39 @@
345
357
  </div>
346
358
 
347
359
  <div class="presets">
348
- {#each $sketchLooks as look (look.id)}
360
+ {#if $unsavedSketchStyle}
361
+ <div class="saved-item">
362
+ <label class="preset">
363
+ <input
364
+ type="radio"
365
+ name={STYLE_RADIO_GROUP}
366
+ value={$unsavedSketchStyle.id}
367
+ checked={onUnsavedSketchStyle}
368
+ onchange={() => setSketch(THEME_SKETCH_ID)}
369
+ />
370
+ <span class="preset-name">{$unsavedSketchStyle.label}</span>
371
+ </label>
372
+ </div>
373
+ {/if}
374
+ {#each $sketchStyles as style (style.id)}
349
375
  <div class="saved-item">
350
376
  <label class="preset">
351
377
  <input
352
378
  type="radio"
353
379
  name={STYLE_RADIO_GROUP}
354
- value={look.id}
355
- checked={$sketchStyleName === look.id}
356
- onchange={() => selectSketchStyle(look.id)}
380
+ value={style.id}
381
+ checked={$selectedSketchStyleId === style.id}
382
+ onchange={() => setSketch(style.id)}
357
383
  />
358
- <span class="preset-name">{look.label}</span>
384
+ <span class="preset-name">{style.label}</span>
359
385
  </label>
360
- {#if look.source === 'file'}
386
+ {#if style.source === 'file'}
361
387
  <button
362
388
  type="button"
363
389
  class="saved-delete"
364
- title="Delete {look.label}"
365
- aria-label="Delete {look.label}"
366
- onclick={() => runStyleAction(deleteSavedSketchStyle(look.id))}
390
+ title="Delete {style.label}"
391
+ aria-label="Delete {style.label}"
392
+ onclick={() => runStyleAction(deleteSavedSketchStyle(style.id))}
367
393
  ><i class="fas fa-xmark"></i></button>
368
394
  {/if}
369
395
  </div>
@@ -665,7 +691,7 @@
665
691
  />
666
692
  </div>
667
693
  </div>
668
- {#if !s.maskBlobLinked}
694
+ {#if s.maskBlobX !== s.maskBlobY}
669
695
  <SketchDial
670
696
  label="Rotation" value={s.maskAngle} min={0} max={180} step={1}
671
697
  readout={`${blobAngle}°`}
@@ -1097,7 +1123,7 @@
1097
1123
  color: var(--ui-text-secondary);
1098
1124
  }
1099
1125
 
1100
- .readout-off-look {
1126
+ .readout-off-style {
1101
1127
  margin: var(--ui-space-2) 0 0;
1102
1128
  font-size: var(--ui-font-size-sm);
1103
1129
  font-weight: var(--ui-font-weight-medium);
@@ -21,11 +21,11 @@
21
21
  "pooling": 1.5,
22
22
  "strokeInk": 0.4,
23
23
  "maskOn": true,
24
- "maskBlobX": 150,
25
- "maskBlobY": 150,
24
+ "maskBlobX": 170,
25
+ "maskBlobY": 170,
26
26
  "maskBlobLinked": true,
27
27
  "maskAngle": 0,
28
- "maskOutputMin": 0.24,
28
+ "maskOutputMin": 0.57,
29
29
  "maskOutputMax": 0.91,
30
30
  "maskOctaves": 2,
31
31
  "maskGrain": "fractal",
@@ -42,6 +42,6 @@
42
42
  "iconMaskOn": true,
43
43
  "iconMaskScale": 3.8
44
44
  },
45
- "updatedAt": "2026-08-28T15:47:36.934Z",
45
+ "updatedAt": "2026-08-28T21:19:51.383Z",
46
46
  "createdAt": "2026-08-28T15:47:36.934Z"
47
- }
47
+ }
@@ -20,15 +20,15 @@
20
20
  "pressureMod": 0.45,
21
21
  "pooling": 0.8,
22
22
  "strokeInk": 1,
23
- "maskOn": false,
24
- "maskBlobX": 100,
25
- "maskBlobY": 100,
26
- "maskBlobLinked": true,
27
- "maskAngle": 0,
28
- "maskOutputMin": 0.45,
23
+ "maskOn": true,
24
+ "maskBlobX": 10,
25
+ "maskBlobY": 600,
26
+ "maskBlobLinked": false,
27
+ "maskAngle": 150,
28
+ "maskOutputMin": 0.35,
29
29
  "maskOutputMax": 1,
30
30
  "maskOctaves": 2,
31
- "maskGrain": "fractal",
31
+ "maskGrain": "turbulence",
32
32
  "maskPosterize": 1,
33
33
  "maskSoftness": 1.5,
34
34
  "jitterX": 1.5,
@@ -42,6 +42,6 @@
42
42
  "iconMaskOn": false,
43
43
  "iconMaskScale": 1
44
44
  },
45
- "updatedAt": "2026-08-28T15:47:36.934Z",
45
+ "updatedAt": "2026-08-28T21:17:18.232Z",
46
46
  "createdAt": "2026-08-28T15:47:36.934Z"
47
- }
47
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "Autumn",
3
3
  "createdAt": "2026-08-13T10:21:46.867Z",
4
4
  "updatedAt": "2026-08-19T22:39:32.785Z",
5
- "schemaVersion": 4,
5
+ "schemaVersion": 5,
6
6
  "colorsAndType": {
7
7
  "name": "Autumn",
8
8
  "createdAt": "2026-08-12T20:21:21.128Z",
@@ -2,7 +2,7 @@
2
2
  "name": "Halloween",
3
3
  "createdAt": "2026-08-13T10:21:46.867Z",
4
4
  "updatedAt": "2026-08-19T22:39:32.785Z",
5
- "schemaVersion": 4,
5
+ "schemaVersion": 5,
6
6
  "colorsAndType": {
7
7
  "name": "Halloween",
8
8
  "createdAt": "2026-08-12T20:13:46.209Z",