@motion-proto/live-tokens 0.61.0 → 0.63.0
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.
- package/.claude/skills/live-tokens-adjust-geometry/SKILL.md +4 -4
- package/.claude/skills/live-tokens-build-page/SKILL.md +21 -3
- package/.claude/skills/live-tokens-create-component/SKILL.md +16 -48
- package/.claude/skills/live-tokens-create-component/references/fixed-overlays.md +10 -1
- package/.claude/skills/live-tokens-create-component/references/intrinsics.md +7 -5
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +1 -1
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +50 -0
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +16 -9
- package/.claude/skills/live-tokens-pair-fonts/SKILL.md +8 -6
- package/.claude/skills/live-tokens-pick-component/SKILL.md +19 -5
- package/CHANGELOG.md +114 -0
- package/bin/migrate.mjs +6 -2
- package/dist-plugin/adjust/index.cjs +1 -1
- package/dist-plugin/adjust/index.d.cts +1 -1
- package/dist-plugin/adjust/index.d.ts +1 -1
- package/dist-plugin/adjust/index.js +1 -1
- package/dist-plugin/{chunk-232GZGQU.js → chunk-NDJJORKJ.js} +342 -5
- package/dist-plugin/{chunk-Y5CNFSSV.js → chunk-RVE3MNKM.js} +1 -1
- package/dist-plugin/{chunk-OIOXU7FR.js → chunk-ZHPX7ZYQ.js} +83 -25
- package/dist-plugin/{dataPaths-CRfD1LdA.d.ts → dataPaths-DZUzVv8H.d.cts} +3 -3
- package/dist-plugin/{dataPaths-CRfD1LdA.d.cts → dataPaths-DZUzVv8H.d.ts} +3 -3
- package/dist-plugin/fontPairing/index.cjs +1 -1
- package/dist-plugin/fontPairing/index.d.cts +1 -1
- package/dist-plugin/fontPairing/index.d.ts +1 -1
- package/dist-plugin/fontPairing/index.js +1 -1
- package/dist-plugin/generateColorsAndType/index.cjs +1 -1
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.js +1 -1
- package/dist-plugin/index.cjs +461 -58
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +31 -23
- package/dist-plugin/migrateData/index.cjs +350 -9
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +9 -5
- package/dist-plugin/tokensCssMigrations/index.cjs +83 -25
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +2 -2
- package/package.json +4 -2
- package/src/app/site.css +32 -0
- package/src/editor/component-editor/CardEditor.svelte +7 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +25 -2
- package/src/editor/core/preview/lookPreview.ts +46 -5
- package/src/editor/core/productionPulse.ts +6 -2
- package/src/editor/core/sketch/maskField.ts +75 -39
- package/src/editor/core/sketch/sketchLayer.ts +32 -11
- package/src/editor/core/sketch/sketchStore.ts +311 -86
- package/src/editor/core/sketch/{sketchPresetService.ts → sketchStyleService.ts} +14 -14
- package/src/editor/core/sketch/{sketchPresets.ts → sketchStyles.ts} +27 -28
- package/src/editor/core/themes/themeDocumentSync.ts +2 -0
- package/src/editor/core/themes/themeInit.ts +19 -1
- package/src/editor/core/themes/themeService.ts +7 -2
- package/src/editor/core/themes/themeTypes.ts +5 -0
- package/src/editor/docs/content/editing-tokens.md +1 -1
- package/src/editor/docs/content/sketch-mode.md +34 -18
- package/src/editor/docs/content/themes-workflow.md +30 -21
- package/src/editor/docs/content/where-themes-live.md +9 -5
- package/src/editor/docs/content.generated.ts +4 -4
- package/src/editor/overlay/LiveTokensRouter.svelte +8 -0
- package/src/editor/ui/EditorViewSwitcher.svelte +3 -3
- package/src/editor/ui/ThemePanel.svelte +47 -1
- package/src/editor/ui/sections/textStyles.ts +29 -1
- package/src/editor/ui/sketch/SketchPreview.svelte +3 -3
- package/src/editor/ui/sketch/SketchTab.svelte +120 -134
- package/src/live-tokens/data/colors-and-type/midnight-study.json +40 -30
- package/src/live-tokens/data/themes/autumn.json +3 -3
- package/src/live-tokens/data/themes/halloween.json +3 -3
- package/src/live-tokens/data/themes/midnight-study.json +61 -49
- package/src/live-tokens/data/themes/ocean.json +3 -3
- package/src/live-tokens/data/themes/royal-velvet.json +3 -3
- package/src/live-tokens/data/themes/sketchy.json +3 -3
- package/src/live-tokens/data/themes/spring-meadow.json +3 -3
- package/src/live-tokens/data/themes/sunset.json +3 -3
- package/src/system/components/Card.svelte +27 -9
- package/src/system/components/FloatingTokenTags.css +10 -8
- package/src/system/components/ImageLightbox.svelte +5 -2
- package/src/system/components/SectionDivider.svelte +3 -3
- package/src/system/components/SegmentedControl.svelte +11 -9
- package/src/system/styles/tokens.css +28 -5
- package/template/src/pages/Home.svelte +1 -11
|
@@ -4,26 +4,29 @@
|
|
|
4
4
|
import UISegmentedControl from '../UISegmentedControl.svelte';
|
|
5
5
|
import SketchDial from './SketchDial.svelte';
|
|
6
6
|
import SketchRange from './SketchRange.svelte';
|
|
7
|
-
import { buildMaskUri
|
|
7
|
+
import { buildMaskUri } from '../../core/sketch/maskField';
|
|
8
8
|
import SketchPreview from './SketchPreview.svelte';
|
|
9
9
|
import { onMount } from 'svelte';
|
|
10
10
|
import UIPillButton from '../UIPillButton.svelte';
|
|
11
11
|
import UIReveal, { REVEAL_MS } from '../UIReveal.svelte';
|
|
12
12
|
import { scrollSectionIntoView } from '../scrollSection';
|
|
13
|
-
import {
|
|
13
|
+
import { SKETCH_STYLES } from '../../core/sketch/sketchStyles';
|
|
14
|
+
import { openThemeSlug } from '../../core/store/editorConfigStore';
|
|
14
15
|
import {
|
|
15
16
|
sketchEnabled,
|
|
16
|
-
|
|
17
|
+
setSketchEnabled,
|
|
18
|
+
sketchOffLook,
|
|
19
|
+
sketchStyleName,
|
|
17
20
|
sketchDirty,
|
|
18
21
|
sketchSettings,
|
|
19
|
-
|
|
22
|
+
selectSketchStyle,
|
|
20
23
|
updateSketchSettings,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
savedSketchStyles,
|
|
25
|
+
refreshSavedSketchStyles,
|
|
26
|
+
selectSavedSketchStyle,
|
|
27
|
+
saveCurrentSketchStyle,
|
|
28
|
+
deleteSavedSketchStyle,
|
|
29
|
+
USER_STYLE_PREFIX,
|
|
27
30
|
} from '../../core/sketch/sketchStore';
|
|
28
31
|
|
|
29
32
|
interface Props {
|
|
@@ -37,6 +40,11 @@
|
|
|
37
40
|
|
|
38
41
|
let s = $derived($sketchSettings);
|
|
39
42
|
|
|
43
|
+
// Save is disabled for the protected Default theme (ThemePanel.svelte);
|
|
44
|
+
// Save As is the gesture actually on offer there, so the off-look copy has
|
|
45
|
+
// to name it instead.
|
|
46
|
+
let activeIsProtected = $derived($openThemeSlug === 'default');
|
|
47
|
+
|
|
40
48
|
type SectionKey = 'border' | 'fill' | 'shape' | 'icons' | 'noise';
|
|
41
49
|
|
|
42
50
|
const SECTION_KEYS: SectionKey[] = ['border', 'fill', 'shape', 'icons', 'noise'];
|
|
@@ -81,8 +89,8 @@
|
|
|
81
89
|
const ROUGHNESS = ['smooth', 'grainy', 'rough'] as const;
|
|
82
90
|
|
|
83
91
|
/** A layer's own wavelength: the multiple, and the length it resolves to
|
|
84
|
-
against the shared wave. The ratio is what a
|
|
85
|
-
are what you can picture. */
|
|
92
|
+
against the shared wave. The ratio is what a sketchstyle carries; the
|
|
93
|
+
pixels are what you can picture. */
|
|
86
94
|
const against = (multiple: number) => `${Math.round(s.wobble * multiple)}px`;
|
|
87
95
|
|
|
88
96
|
/** The wave's shape, named rather than numbered: 1 is the field as the filter
|
|
@@ -110,52 +118,52 @@
|
|
|
110
118
|
+ (s.waveform > 1 ? ` · ${waveformLabel(s.waveform)}` : ''),
|
|
111
119
|
});
|
|
112
120
|
|
|
113
|
-
/** Shipped and saved
|
|
114
|
-
picking a saved one clears the shipped selection natively. */
|
|
115
|
-
const
|
|
121
|
+
/** Shipped and saved sketchstyles are one choice, so they share one radio
|
|
122
|
+
group: picking a saved one clears the shipped selection natively. */
|
|
123
|
+
const STYLE_RADIO_GROUP = 'sketchstyle';
|
|
116
124
|
|
|
117
125
|
/** What the readout under the grid says. The selection survives a dial move,
|
|
118
126
|
so the base keeps its name here and the blurb reports the drift instead. */
|
|
119
127
|
let active = $derived(
|
|
120
128
|
$sketchDirty
|
|
121
129
|
? { name: s.label, blurb: `Modified from ${s.label}. Save it to keep it.` }
|
|
122
|
-
: { name: s.label, blurb: s.blurb || 'Your saved
|
|
130
|
+
: { name: s.label, blurb: s.blurb || 'Your saved sketchstyle.' },
|
|
123
131
|
);
|
|
124
132
|
|
|
125
133
|
let naming = $state(false);
|
|
126
134
|
let draftName = $state('');
|
|
127
|
-
let
|
|
135
|
+
let styleError = $state('');
|
|
128
136
|
|
|
129
137
|
onMount(() => {
|
|
130
|
-
// No dev plugin (a built preview, say) means no saved
|
|
131
|
-
// missing door, not a fault worth reporting in the tab.
|
|
132
|
-
|
|
138
|
+
// No dev plugin (a built preview, say) means no saved sketchstyles. That
|
|
139
|
+
// is a missing door, not a fault worth reporting in the tab.
|
|
140
|
+
refreshSavedSketchStyles().catch(() => {});
|
|
133
141
|
});
|
|
134
142
|
|
|
135
143
|
function startNaming() {
|
|
136
|
-
draftName = s.label && $
|
|
137
|
-
|
|
144
|
+
draftName = s.label && $sketchStyleName.startsWith(USER_STYLE_PREFIX) ? s.label : '';
|
|
145
|
+
styleError = '';
|
|
138
146
|
naming = true;
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
async function commitSave(e: SubmitEvent) {
|
|
142
150
|
e.preventDefault();
|
|
143
151
|
try {
|
|
144
|
-
await
|
|
152
|
+
await saveCurrentSketchStyle(draftName);
|
|
145
153
|
naming = false;
|
|
146
154
|
draftName = '';
|
|
147
|
-
|
|
155
|
+
styleError = '';
|
|
148
156
|
} catch (err) {
|
|
149
|
-
|
|
157
|
+
styleError = err instanceof Error ? err.message : 'Save failed';
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
async function
|
|
161
|
+
async function runStyleAction(action: Promise<unknown>) {
|
|
154
162
|
try {
|
|
155
163
|
await action;
|
|
156
|
-
|
|
164
|
+
styleError = '';
|
|
157
165
|
} catch (err) {
|
|
158
|
-
|
|
166
|
+
styleError = err instanceof Error ? err.message : 'That did not work';
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
169
|
|
|
@@ -191,7 +199,12 @@
|
|
|
191
199
|
ceiling is a few blobs across a card, which is where it still reads as
|
|
192
200
|
uneven ink. */
|
|
193
201
|
const SIZE = { min: 20, max: 250 };
|
|
194
|
-
const BLUR_MAX =
|
|
202
|
+
const BLUR_MAX = 64;
|
|
203
|
+
/** Squared response. A blur only reads as a different blur while it is small,
|
|
204
|
+
so a linear walk to 64px would spend a tenth of the dial on everything
|
|
205
|
+
legible and the rest sliding between shades of gone. */
|
|
206
|
+
const blurPos = (px: number) => Math.round(Math.sqrt(px / BLUR_MAX) * 100);
|
|
207
|
+
const blurVal = (t: number) => Math.round(BLUR_MAX * (t / 100) ** 2 * 4) / 4;
|
|
195
208
|
|
|
196
209
|
/** Tones the field is flattened into, in the order the dial walks them:
|
|
197
210
|
smooth, then coarsening all the way down to two. The stored value is the
|
|
@@ -213,36 +226,31 @@
|
|
|
213
226
|
let outputMin = $derived(Math.round(s.maskOutputMin * 100));
|
|
214
227
|
let outputMax = $derived(Math.round(s.maskOutputMax * 100));
|
|
215
228
|
let stepPos = $derived(Math.max(0, STEP_TONES.indexOf(s.maskPosterize)));
|
|
229
|
+
let blurStep = $derived(blurPos(s.maskSoftness));
|
|
216
230
|
const setOutput = (min: number, max: number) =>
|
|
217
231
|
updateSketchSettings({ maskOutputMin: min / 100, maskOutputMax: max / 100 });
|
|
218
232
|
|
|
219
|
-
// The
|
|
220
|
-
// sample card, so blob size here is blob size on the components and the
|
|
221
|
-
// card's shadow is always on show: coverage that ate it would be a bug you
|
|
222
|
-
// saw at once.
|
|
233
|
+
// The finished field, the last stage and the one the components get.
|
|
223
234
|
let maskPreview = $derived(buildMaskUri(s));
|
|
224
|
-
// The field itself after each stage, white for ink.
|
|
225
|
-
let stageTiles = $derived(
|
|
226
|
-
(['noise', 'output', 'blur'] as const).map((stage) => ({ stage, uri: buildMaskUri(s, 9, stage) })),
|
|
227
|
-
);
|
|
228
235
|
</script>
|
|
229
236
|
|
|
230
237
|
<div class="sketch-tab">
|
|
231
238
|
<header class="head">
|
|
232
239
|
<div class="head-row">
|
|
233
|
-
<h2 class="section-title">
|
|
240
|
+
<h2 class="section-title">Sketchstyle</h2>
|
|
234
241
|
<div class="switch">
|
|
235
242
|
<Toggle
|
|
236
243
|
label="Sketch mode"
|
|
237
244
|
labelFirst
|
|
238
245
|
checked={$sketchEnabled}
|
|
239
|
-
onchange={
|
|
246
|
+
onchange={setSketchEnabled}
|
|
240
247
|
/>
|
|
241
248
|
<UIInfoPopover title="Sketch mode">
|
|
242
249
|
<p>
|
|
243
|
-
An effect layer over the active theme. It
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
An effect layer over the active theme. It repaints each component's fill and
|
|
251
|
+
outline from the tokens that component already owns, then pushes them around a
|
|
252
|
+
shared noise field. Save in the Theme panel folds the dials into the open theme,
|
|
253
|
+
or Save As when the open theme is Motion Proto, the read-only built-in one.
|
|
246
254
|
</p>
|
|
247
255
|
<p>
|
|
248
256
|
While it is on, the effect applies to the page behind this editor as well as to the
|
|
@@ -256,28 +264,46 @@
|
|
|
256
264
|
<div class="readout" aria-live="polite">
|
|
257
265
|
<span class="readout-name">{active.name}</span>
|
|
258
266
|
<p class="readout-blurb">{active.blurb}</p>
|
|
267
|
+
{#if $sketchOffLook}
|
|
268
|
+
{#if $sketchEnabled}
|
|
269
|
+
<p class="readout-off-look">
|
|
270
|
+
{#if activeIsProtected}
|
|
271
|
+
Motion Proto is read-only, and these dials are ahead of it. Use Save As in the
|
|
272
|
+
Theme panel to keep them in a new theme.
|
|
273
|
+
{:else}
|
|
274
|
+
These dials are ahead of the saved theme. Save it in the Theme panel to fold them
|
|
275
|
+
in.
|
|
276
|
+
{/if}
|
|
277
|
+
</p>
|
|
278
|
+
{:else}
|
|
279
|
+
<p class="readout-off-look">
|
|
280
|
+
The theme carries a sketchstyle this page is not painting. Saving now would drop
|
|
281
|
+
it.
|
|
282
|
+
</p>
|
|
283
|
+
{/if}
|
|
284
|
+
{/if}
|
|
259
285
|
</div>
|
|
260
286
|
|
|
261
287
|
<div class="presets">
|
|
262
|
-
{#each Object.entries(
|
|
288
|
+
{#each Object.entries(SKETCH_STYLES) as [name, style] (name)}
|
|
263
289
|
<label class="preset">
|
|
264
290
|
<input
|
|
265
291
|
type="radio"
|
|
266
|
-
name={
|
|
292
|
+
name={STYLE_RADIO_GROUP}
|
|
267
293
|
value={name}
|
|
268
|
-
checked={$
|
|
269
|
-
onchange={() =>
|
|
294
|
+
checked={$sketchStyleName === name}
|
|
295
|
+
onchange={() => selectSketchStyle(name)}
|
|
270
296
|
/>
|
|
271
|
-
<span class="preset-name">{
|
|
297
|
+
<span class="preset-name">{style.label}</span>
|
|
272
298
|
</label>
|
|
273
299
|
{/each}
|
|
274
300
|
</div>
|
|
275
301
|
|
|
276
302
|
<div class="saved">
|
|
277
303
|
<div class="saved-head">
|
|
278
|
-
<span class="band-label">Saved</span>
|
|
304
|
+
<span class="band-label">Saved sketchstyles</span>
|
|
279
305
|
<UIPillButton size="compact" icon="fa-floppy-disk" onclick={startNaming}>
|
|
280
|
-
Save
|
|
306
|
+
Save as sketchstyle…
|
|
281
307
|
</UIPillButton>
|
|
282
308
|
</div>
|
|
283
309
|
|
|
@@ -287,8 +313,8 @@
|
|
|
287
313
|
<input
|
|
288
314
|
class="save-name"
|
|
289
315
|
bind:value={draftName}
|
|
290
|
-
placeholder="
|
|
291
|
-
aria-label="
|
|
316
|
+
placeholder="Sketchstyle name"
|
|
317
|
+
aria-label="Sketchstyle name"
|
|
292
318
|
autofocus
|
|
293
319
|
/>
|
|
294
320
|
<UIPillButton size="compact" variant="primary" type="submit">Save</UIPillButton>
|
|
@@ -296,17 +322,17 @@
|
|
|
296
322
|
</form>
|
|
297
323
|
{/if}
|
|
298
324
|
|
|
299
|
-
{#if $
|
|
325
|
+
{#if $savedSketchStyles.length > 0}
|
|
300
326
|
<div class="presets">
|
|
301
|
-
{#each $
|
|
327
|
+
{#each $savedSketchStyles as saved (saved.fileName)}
|
|
302
328
|
<div class="saved-item">
|
|
303
329
|
<label class="preset">
|
|
304
330
|
<input
|
|
305
331
|
type="radio"
|
|
306
|
-
name={
|
|
307
|
-
value={
|
|
308
|
-
checked={$
|
|
309
|
-
onchange={() =>
|
|
332
|
+
name={STYLE_RADIO_GROUP}
|
|
333
|
+
value={USER_STYLE_PREFIX + saved.fileName}
|
|
334
|
+
checked={$sketchStyleName === USER_STYLE_PREFIX + saved.fileName}
|
|
335
|
+
onchange={() => runStyleAction(selectSavedSketchStyle(saved.fileName))}
|
|
310
336
|
/>
|
|
311
337
|
<span class="preset-name">{saved.name}</span>
|
|
312
338
|
</label>
|
|
@@ -315,15 +341,15 @@
|
|
|
315
341
|
class="saved-delete"
|
|
316
342
|
title="Delete {saved.name}"
|
|
317
343
|
aria-label="Delete {saved.name}"
|
|
318
|
-
onclick={() =>
|
|
344
|
+
onclick={() => runStyleAction(deleteSavedSketchStyle(saved.fileName))}
|
|
319
345
|
><i class="fas fa-xmark"></i></button>
|
|
320
346
|
</div>
|
|
321
347
|
{/each}
|
|
322
348
|
</div>
|
|
323
349
|
{/if}
|
|
324
350
|
|
|
325
|
-
{#if
|
|
326
|
-
<p class="saved-error">{
|
|
351
|
+
{#if styleError}
|
|
352
|
+
<p class="saved-error">{styleError}</p>
|
|
327
353
|
{/if}
|
|
328
354
|
</div>
|
|
329
355
|
</div>
|
|
@@ -538,21 +564,7 @@
|
|
|
538
564
|
</div>
|
|
539
565
|
|
|
540
566
|
<figure class="scope" class:muted={!s.maskOn}>
|
|
541
|
-
<div class="
|
|
542
|
-
<div
|
|
543
|
-
class="ink"
|
|
544
|
-
style:mask-image={maskPreview}
|
|
545
|
-
style:mask-size="{MASK_TILE}px {MASK_TILE}px"
|
|
546
|
-
></div>
|
|
547
|
-
</div>
|
|
548
|
-
<div class="stages">
|
|
549
|
-
{#each stageTiles as { stage, uri } (stage)}
|
|
550
|
-
<figure>
|
|
551
|
-
<div class="tile" style:mask-image={uri} style:mask-size="{MASK_TILE}px {MASK_TILE}px"></div>
|
|
552
|
-
<figcaption>{stage}</figcaption>
|
|
553
|
-
</figure>
|
|
554
|
-
{/each}
|
|
555
|
-
</div>
|
|
567
|
+
<div class="field" style:mask-image={maskPreview}></div>
|
|
556
568
|
</figure>
|
|
557
569
|
|
|
558
570
|
<h5>Noise</h5>
|
|
@@ -580,10 +592,13 @@
|
|
|
580
592
|
onchange={(v) => updateSketchSettings({ maskOctaves: v })}
|
|
581
593
|
/>
|
|
582
594
|
|
|
583
|
-
<h5>
|
|
595
|
+
<h5>Levels</h5>
|
|
584
596
|
<p class="group-note">
|
|
585
|
-
|
|
586
|
-
|
|
597
|
+
The field runs black to white before these apply. Output states the
|
|
598
|
+
range of ink the mask paints between, 0 bare to 100 whole, and the
|
|
599
|
+
whole field is squeezed into it: raise the low handle and the
|
|
600
|
+
darkest patch rises with it, drop the high handle and the densest
|
|
601
|
+
falls. Nothing is ever cut off at a handle.
|
|
587
602
|
</p>
|
|
588
603
|
<SketchRange
|
|
589
604
|
label="Output" low={outputMin} high={outputMax} min={0} max={100} step={1}
|
|
@@ -602,10 +617,11 @@
|
|
|
602
617
|
|
|
603
618
|
<h5>Blur</h5>
|
|
604
619
|
<SketchDial
|
|
605
|
-
label="Blur" value={
|
|
620
|
+
label="Blur" value={blurStep} min={0} max={100} step={1}
|
|
606
621
|
readout={px(s.maskSoftness)}
|
|
607
|
-
|
|
608
|
-
|
|
622
|
+
ends={['hard', 'clouded']}
|
|
623
|
+
hint="Softens the blotch edges. Few tones with a little blur give a hard blotch with a soft rim; wound right up the field goes to weather."
|
|
624
|
+
onchange={(v) => updateSketchSettings({ maskSoftness: blurVal(v) })}
|
|
609
625
|
/>
|
|
610
626
|
</div>
|
|
611
627
|
</div>
|
|
@@ -804,9 +820,9 @@
|
|
|
804
820
|
}
|
|
805
821
|
|
|
806
822
|
|
|
807
|
-
/* Grid, saved band and readout are one control: which
|
|
808
|
-
that
|
|
809
|
-
caption under the tab. */
|
|
823
|
+
/* Grid, saved band and readout are one control: which sketchstyle is on, and
|
|
824
|
+
what that sketchstyle is. Splitting them left the description reading as a
|
|
825
|
+
stray caption under the tab. */
|
|
810
826
|
.picker {
|
|
811
827
|
display: flex;
|
|
812
828
|
flex-direction: column;
|
|
@@ -999,6 +1015,13 @@
|
|
|
999
1015
|
color: var(--ui-text-secondary);
|
|
1000
1016
|
}
|
|
1001
1017
|
|
|
1018
|
+
.readout-off-look {
|
|
1019
|
+
margin: var(--ui-space-2) 0 0;
|
|
1020
|
+
font-size: var(--ui-font-size-sm);
|
|
1021
|
+
font-weight: var(--ui-font-weight-medium);
|
|
1022
|
+
color: var(--ui-text-primary);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1002
1025
|
.dormant {
|
|
1003
1026
|
margin: 0;
|
|
1004
1027
|
font-size: var(--ui-font-size-sm);
|
|
@@ -1196,69 +1219,32 @@
|
|
|
1196
1219
|
pointer-events: none;
|
|
1197
1220
|
}
|
|
1198
1221
|
|
|
1222
|
+
/* The ground the field is cut out of, so a masked-away pixel reads as black
|
|
1223
|
+
rather than as the panel. */
|
|
1199
1224
|
.scope {
|
|
1200
1225
|
margin: 0;
|
|
1201
|
-
display: flex;
|
|
1202
|
-
flex-direction: column;
|
|
1203
|
-
gap: var(--ui-space-4);
|
|
1204
|
-
transition: opacity var(--ui-transition-base);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
.scope.muted {
|
|
1208
|
-
opacity: 0.4;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
/* The shadow sits on the card and the mask on the fill inside it: the same
|
|
1212
|
-
split the fill layer makes, so what this shows is what the page does. */
|
|
1213
|
-
.sample {
|
|
1214
|
-
height: 6rem;
|
|
1215
|
-
padding: var(--ui-space-16) var(--ui-space-24);
|
|
1216
1226
|
border: 1px solid var(--ui-border-low);
|
|
1217
1227
|
border-radius: var(--ui-radius-sm);
|
|
1218
1228
|
background: var(--ui-surface-lowest);
|
|
1219
1229
|
overflow: hidden;
|
|
1230
|
+
transition: opacity var(--ui-transition-base);
|
|
1220
1231
|
}
|
|
1221
1232
|
|
|
1222
|
-
.
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
.stages {
|
|
1227
|
-
display: grid;
|
|
1228
|
-
grid-template-columns: repeat(3, 1fr);
|
|
1229
|
-
gap: var(--ui-space-8);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
.stages figure {
|
|
1233
|
-
margin: 0;
|
|
1233
|
+
.scope.muted {
|
|
1234
|
+
opacity: 0.4;
|
|
1234
1235
|
}
|
|
1235
1236
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1237
|
+
/* One readout of the finished field: the whole tile, repeated across the
|
|
1238
|
+
strip, white on that ground so 0 reads as black and 100 as white. Neither
|
|
1239
|
+
was readable before. The field was painted at page-px size, which showed a
|
|
1240
|
+
crop off the top of the tile that never reached the bright end, over an ink
|
|
1241
|
+
colour that could not have painted it if it had. */
|
|
1242
|
+
.field {
|
|
1243
|
+
height: 7rem;
|
|
1239
1244
|
background: var(--ui-text-primary);
|
|
1240
1245
|
mask-mode: luminance;
|
|
1241
1246
|
mask-repeat: repeat;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
.stages figcaption {
|
|
1245
|
-
margin-top: var(--ui-space-2);
|
|
1246
|
-
font-size: var(--ui-font-size-xs);
|
|
1247
|
-
text-align: center;
|
|
1248
|
-
text-transform: uppercase;
|
|
1249
|
-
letter-spacing: 0.06em;
|
|
1250
|
-
color: var(--ui-text-tertiary);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
.ink {
|
|
1254
|
-
height: 100%;
|
|
1255
|
-
border-radius: var(--ui-radius-md);
|
|
1256
|
-
/* An ink colour, not a surface one: the point of the swatch is the
|
|
1257
|
-
blotches, and the surface rungs sit within a shade or two of the ground
|
|
1258
|
-
under them, which showed the finished field as a flat dark rectangle. */
|
|
1259
|
-
background: var(--ui-text-muted);
|
|
1260
|
-
mask-mode: luminance;
|
|
1261
|
-
mask-repeat: repeat;
|
|
1247
|
+
mask-size: contain;
|
|
1262
1248
|
}
|
|
1263
1249
|
|
|
1264
1250
|
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Midnight Study",
|
|
3
|
-
"createdAt": "2026-08-
|
|
4
|
-
"updatedAt": "2026-08-
|
|
3
|
+
"createdAt": "2026-08-27T17:01:37.984Z",
|
|
4
|
+
"updatedAt": "2026-08-27T17:01:37.984Z",
|
|
5
5
|
"editorConfigs": {
|
|
6
6
|
"Brand": {
|
|
7
7
|
"baseColor": {
|
|
@@ -2262,10 +2262,6 @@
|
|
|
2262
2262
|
}
|
|
2263
2263
|
},
|
|
2264
2264
|
"cssVariables": {
|
|
2265
|
-
"--gradient-1": "linear-gradient(90deg, var(--color-brand-400) 0%, var(--color-brand-700) 100%)",
|
|
2266
|
-
"--gradient-2": "linear-gradient(135deg, var(--color-special-500) 0%, var(--color-brand-500) 100%)",
|
|
2267
|
-
"--gradient-3": "linear-gradient(90deg, var(--color-brand-500) 0%, var(--color-accent-500) 100%)",
|
|
2268
|
-
"--gradient-4": "linear-gradient(180deg, var(--color-canvas-600) 0%, var(--color-canvas-900) 100%)",
|
|
2269
2265
|
"--badge-trait-surface": "var(--surface-brand)",
|
|
2270
2266
|
"--badge-trait-text": "var(--text-brand)",
|
|
2271
2267
|
"--badge-trait-text-font-family": "var(--font-sans)",
|
|
@@ -2316,7 +2312,15 @@
|
|
|
2316
2312
|
"--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
|
|
2317
2313
|
"--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
|
|
2318
2314
|
"--shadow-xl": "6px 7px 13px hsla(237, 18%, 3%, 0.9)",
|
|
2319
|
-
"--shadow-2xl": "8px 9px 16px hsla(237, 18%, 3%, 0.9)"
|
|
2315
|
+
"--shadow-2xl": "8px 9px 16px hsla(237, 18%, 3%, 0.9)",
|
|
2316
|
+
"--gradient-1": "linear-gradient(90deg, var(--color-brand-400) 0%, var(--color-brand-700) 100%)",
|
|
2317
|
+
"--gradient-1-stops": "var(--color-brand-400) 0%, var(--color-brand-700) 100%",
|
|
2318
|
+
"--gradient-2": "linear-gradient(135deg, var(--color-special-500) 0%, var(--color-brand-500) 100%)",
|
|
2319
|
+
"--gradient-2-stops": "var(--color-special-500) 0%, var(--color-brand-500) 100%",
|
|
2320
|
+
"--gradient-3": "linear-gradient(90deg, var(--color-brand-500) 0%, var(--color-accent-500) 100%)",
|
|
2321
|
+
"--gradient-3-stops": "var(--color-brand-500) 0%, var(--color-accent-500) 100%",
|
|
2322
|
+
"--gradient-4": "linear-gradient(180deg, color-mix(in srgb, var(--color-canvas-600) 49%, transparent) 0%, var(--color-canvas-850) 14.2%, var(--color-canvas-900) 83.8%)",
|
|
2323
|
+
"--gradient-4-stops": "color-mix(in srgb, var(--color-canvas-600) 49%, transparent) 0%, var(--color-canvas-850) 14.2%, var(--color-canvas-900) 83.8%"
|
|
2320
2324
|
},
|
|
2321
2325
|
"fontSources": [
|
|
2322
2326
|
{
|
|
@@ -2469,25 +2473,6 @@
|
|
|
2469
2473
|
]
|
|
2470
2474
|
}
|
|
2471
2475
|
],
|
|
2472
|
-
"harmonyAxes": [
|
|
2473
|
-
{
|
|
2474
|
-
"hue": 300,
|
|
2475
|
-
"family": "Brand"
|
|
2476
|
-
},
|
|
2477
|
-
{
|
|
2478
|
-
"hue": 340,
|
|
2479
|
-
"family": "Accent"
|
|
2480
|
-
},
|
|
2481
|
-
{
|
|
2482
|
-
"hue": 268,
|
|
2483
|
-
"family": "Canvas"
|
|
2484
|
-
},
|
|
2485
|
-
{
|
|
2486
|
-
"hue": 210,
|
|
2487
|
-
"family": null
|
|
2488
|
-
}
|
|
2489
|
-
],
|
|
2490
|
-
"schemaVersion": 3,
|
|
2491
2476
|
"gradients": [
|
|
2492
2477
|
{
|
|
2493
2478
|
"variable": "--gradient-1",
|
|
@@ -2541,13 +2526,38 @@
|
|
|
2541
2526
|
"stops": [
|
|
2542
2527
|
{
|
|
2543
2528
|
"position": 0,
|
|
2544
|
-
"color": "--color-canvas-600"
|
|
2529
|
+
"color": "--color-canvas-600",
|
|
2530
|
+
"opacity": 49
|
|
2545
2531
|
},
|
|
2546
2532
|
{
|
|
2547
|
-
"position":
|
|
2533
|
+
"position": 14.2,
|
|
2534
|
+
"color": "--color-canvas-850",
|
|
2535
|
+
"opacity": 100
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
"position": 83.8,
|
|
2548
2539
|
"color": "--color-canvas-900"
|
|
2549
2540
|
}
|
|
2550
2541
|
]
|
|
2551
2542
|
}
|
|
2552
|
-
]
|
|
2553
|
-
|
|
2543
|
+
],
|
|
2544
|
+
"harmonyAxes": [
|
|
2545
|
+
{
|
|
2546
|
+
"hue": 300,
|
|
2547
|
+
"family": "Brand"
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
"hue": 340,
|
|
2551
|
+
"family": "Accent"
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
"hue": 268,
|
|
2555
|
+
"family": "Canvas"
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
"hue": 210,
|
|
2559
|
+
"family": null
|
|
2560
|
+
}
|
|
2561
|
+
],
|
|
2562
|
+
"schemaVersion": 5
|
|
2563
|
+
}
|
|
@@ -3530,7 +3530,7 @@
|
|
|
3530
3530
|
"--sectiondivider-lg-description": "--text-alternate",
|
|
3531
3531
|
"--sectiondivider-lg-eyebrow": "--text-brand",
|
|
3532
3532
|
"--sectiondivider-lg-border": "--color-transparent",
|
|
3533
|
-
"--sectiondivider-lg-title-outline-color": "
|
|
3533
|
+
"--sectiondivider-lg-title-outline-color": "transparent",
|
|
3534
3534
|
"--sectiondivider-lg-hairline-color": "--border-brand-medium",
|
|
3535
3535
|
"--sectiondivider-md-title-font-family": "--font-display",
|
|
3536
3536
|
"--sectiondivider-md-title-font-weight": "--font-weight-bold",
|
|
@@ -3559,7 +3559,7 @@
|
|
|
3559
3559
|
"--sectiondivider-md-description": "--text-secondary",
|
|
3560
3560
|
"--sectiondivider-md-eyebrow": "--text-tertiary",
|
|
3561
3561
|
"--sectiondivider-md-border": "--color-transparent",
|
|
3562
|
-
"--sectiondivider-md-title-outline-color": "
|
|
3562
|
+
"--sectiondivider-md-title-outline-color": "transparent",
|
|
3563
3563
|
"--sectiondivider-md-hairline-color": "--border-brand-medium",
|
|
3564
3564
|
"--sectiondivider-sm-title-font-family": "--font-display",
|
|
3565
3565
|
"--sectiondivider-sm-title-font-weight": "--font-weight-medium",
|
|
@@ -3588,7 +3588,7 @@
|
|
|
3588
3588
|
"--sectiondivider-sm-description": "--text-secondary",
|
|
3589
3589
|
"--sectiondivider-sm-eyebrow": "--text-tertiary",
|
|
3590
3590
|
"--sectiondivider-sm-border": "--color-transparent",
|
|
3591
|
-
"--sectiondivider-sm-title-outline-color": "
|
|
3591
|
+
"--sectiondivider-sm-title-outline-color": "transparent",
|
|
3592
3592
|
"--sectiondivider-sm-hairline-color": "color-mix(in srgb, var(--border-brand-medium) 50%, transparent)"
|
|
3593
3593
|
}
|
|
3594
3594
|
},
|
|
@@ -3569,7 +3569,7 @@
|
|
|
3569
3569
|
"--sectiondivider-lg-description": "--text-alternate",
|
|
3570
3570
|
"--sectiondivider-lg-eyebrow": "--text-brand",
|
|
3571
3571
|
"--sectiondivider-lg-border": "--color-transparent",
|
|
3572
|
-
"--sectiondivider-lg-title-outline-color": "
|
|
3572
|
+
"--sectiondivider-lg-title-outline-color": "transparent",
|
|
3573
3573
|
"--sectiondivider-lg-hairline-color": "--border-brand-medium",
|
|
3574
3574
|
"--sectiondivider-md-title-font-family": "--font-display",
|
|
3575
3575
|
"--sectiondivider-md-title-font-weight": "--font-weight-bold",
|
|
@@ -3598,7 +3598,7 @@
|
|
|
3598
3598
|
"--sectiondivider-md-description": "--text-secondary",
|
|
3599
3599
|
"--sectiondivider-md-eyebrow": "--text-tertiary",
|
|
3600
3600
|
"--sectiondivider-md-border": "--color-transparent",
|
|
3601
|
-
"--sectiondivider-md-title-outline-color": "
|
|
3601
|
+
"--sectiondivider-md-title-outline-color": "transparent",
|
|
3602
3602
|
"--sectiondivider-md-hairline-color": "--border-brand-medium",
|
|
3603
3603
|
"--sectiondivider-sm-title-font-family": "--font-display",
|
|
3604
3604
|
"--sectiondivider-sm-title-font-weight": "--font-weight-medium",
|
|
@@ -3627,7 +3627,7 @@
|
|
|
3627
3627
|
"--sectiondivider-sm-description": "--text-secondary",
|
|
3628
3628
|
"--sectiondivider-sm-eyebrow": "--text-tertiary",
|
|
3629
3629
|
"--sectiondivider-sm-border": "--color-transparent",
|
|
3630
|
-
"--sectiondivider-sm-title-outline-color": "
|
|
3630
|
+
"--sectiondivider-sm-title-outline-color": "transparent",
|
|
3631
3631
|
"--sectiondivider-sm-hairline-color": "color-mix(in srgb, var(--border-brand-medium) 50%, transparent)"
|
|
3632
3632
|
}
|
|
3633
3633
|
},
|