@motion-proto/live-tokens 0.80.0 → 0.82.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/CHANGELOG.md +35 -0
- package/package.json +17 -1
- package/src/demo/Demo.svelte +97 -0
- package/src/demo/Demo.svelte.d.ts +7 -0
- package/src/demo/DemoBar.svelte +75 -0
- package/src/demo/FloatingTokenTags.css +284 -0
- package/src/demo/FloatingTokenTags.svelte +570 -0
- package/src/demo/LabeledSelect.svelte +243 -0
- package/src/demo/Section.svelte +46 -0
- package/src/demo/SketchSelect.svelte +58 -0
- package/src/demo/SourceLinks.svelte +156 -0
- package/src/demo/TestingLoops.svelte +876 -0
- package/src/demo/TestingLoops.svelte.d.ts +4 -0
- package/src/demo/ThemeSelect.svelte +52 -0
- package/src/demo/sections/SectionClaude.svelte +192 -0
- package/src/demo/sections/SectionFooter.svelte +67 -0
- package/src/demo/sections/SectionGetStarted.svelte +100 -0
- package/src/demo/sections/SectionHero.svelte +201 -0
- package/src/demo/sections/SectionKit.svelte +410 -0
- package/src/demo/sections/SectionLayers.svelte +54 -0
- package/src/demo/sections/SectionOffering.svelte +135 -0
- package/src/demo/sketchCarry.ts +91 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte +42 -19
- package/src/editor/skill-atlas/atlasLink.ts +43 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Button from '../../system/components/Button.svelte';
|
|
3
|
+
import SectionDivider from '../../system/components/SectionDivider.svelte';
|
|
4
|
+
import SegmentedControl from '../../system/components/SegmentedControl.svelte';
|
|
5
|
+
import Panel from '../../system/components/Panel.svelte';
|
|
6
|
+
import Section from '../Section.svelte';
|
|
7
|
+
|
|
8
|
+
let { prose = true }: { prose?: boolean } = $props();
|
|
9
|
+
|
|
10
|
+
type Override = 'shape' | 'color';
|
|
11
|
+
|
|
12
|
+
let shape = $state(false);
|
|
13
|
+
let color = $state(false);
|
|
14
|
+
// Tracks the most-recently-applied override so the SegmentedControl can
|
|
15
|
+
// single-highlight it while the underlying applied set stays additive.
|
|
16
|
+
let lastApplied = $state<Override | null>(null);
|
|
17
|
+
|
|
18
|
+
const segments = [
|
|
19
|
+
{ value: 'default', label: 'Default', icon: 'fas fa-circle' },
|
|
20
|
+
{ value: 'shape', label: 'Shape', icon: 'fas fa-shapes' },
|
|
21
|
+
{ value: 'color', label: 'Color', icon: 'fas fa-droplet' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const variants = ['primary', 'secondary', 'outline', 'success', 'danger', 'warning'] as const;
|
|
25
|
+
|
|
26
|
+
const shapeOverride: Record<string, string> = (() => {
|
|
27
|
+
const out: Record<string, string> = {};
|
|
28
|
+
for (const v of variants) {
|
|
29
|
+
out[`--button-${v}-radius`] = 'var(--radius-sm)';
|
|
30
|
+
out[`--button-${v}-border-width`] = 'var(--border-width-2)';
|
|
31
|
+
out[`--button-${v}-text-font-family`] = 'var(--font-serif)';
|
|
32
|
+
out[`--button-${v}-text-font-size`] = 'var(--font-size-2xl)';
|
|
33
|
+
out[`--button-${v}-padding`] = 'var(--space-6)';
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
const colorOverride: Record<string, string> = {
|
|
39
|
+
'--button-primary-surface': 'var(--surface-special-high)',
|
|
40
|
+
'--button-primary-border': 'var(--border-special)',
|
|
41
|
+
'--button-primary-hover-surface': 'var(--surface-special-higher)',
|
|
42
|
+
'--button-primary-hover-border': 'var(--border-special-strong)',
|
|
43
|
+
|
|
44
|
+
'--button-outline-border': 'var(--border-special)',
|
|
45
|
+
'--button-outline-text': 'var(--text-special)',
|
|
46
|
+
'--button-outline-hover-border': 'var(--border-special-strong)',
|
|
47
|
+
|
|
48
|
+
'--button-success-surface': 'var(--surface-accent-low)',
|
|
49
|
+
'--button-success-border': 'var(--border-accent)',
|
|
50
|
+
'--button-success-text': 'var(--text-accent)',
|
|
51
|
+
'--button-success-hover-surface': 'var(--surface-accent-higher)',
|
|
52
|
+
'--button-success-hover-border': 'var(--border-accent-strong)',
|
|
53
|
+
|
|
54
|
+
'--button-warning-surface': 'var(--surface-info-low)',
|
|
55
|
+
'--button-warning-border': 'var(--border-info)',
|
|
56
|
+
'--button-warning-text': 'var(--text-info)',
|
|
57
|
+
'--button-warning-hover-surface': 'var(--surface-info-high)',
|
|
58
|
+
'--button-warning-hover-border': 'var(--border-info-medium)',
|
|
59
|
+
|
|
60
|
+
'--button-danger-surface': 'var(--surface-brand-low)',
|
|
61
|
+
'--button-danger-border': 'var(--border-brand)',
|
|
62
|
+
'--button-danger-text': 'var(--text-brand)',
|
|
63
|
+
'--button-danger-hover-surface': 'var(--surface-brand-high)',
|
|
64
|
+
'--button-danger-hover-border': 'var(--border-brand-medium)',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let stageStyle = $derived.by(() => {
|
|
68
|
+
const merged: Record<string, string> = {};
|
|
69
|
+
if (shape) Object.assign(merged, shapeOverride);
|
|
70
|
+
if (color) Object.assign(merged, colorOverride);
|
|
71
|
+
return Object.entries(merged)
|
|
72
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
73
|
+
.join('; ');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// The highlighted segment tracks the most-recently-applied override that
|
|
77
|
+
// is still on. When the last-applied override gets toggled off, the
|
|
78
|
+
// highlight falls back to whatever else is still on, or Default.
|
|
79
|
+
let activeSegment = $derived.by(() => {
|
|
80
|
+
if (lastApplied === 'shape' && shape) return 'shape';
|
|
81
|
+
if (lastApplied === 'color' && color) return 'color';
|
|
82
|
+
if (shape) return 'shape';
|
|
83
|
+
if (color) return 'color';
|
|
84
|
+
return 'default';
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function handleSelect(v: string) {
|
|
88
|
+
if (v === 'default') {
|
|
89
|
+
shape = false;
|
|
90
|
+
color = false;
|
|
91
|
+
lastApplied = null;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (v === 'shape') {
|
|
95
|
+
shape = !shape;
|
|
96
|
+
lastApplied = shape ? 'shape' : color ? 'color' : null;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (v === 'color') {
|
|
100
|
+
color = !color;
|
|
101
|
+
lastApplied = color ? 'color' : shape ? 'shape' : null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<Section
|
|
107
|
+
class="kit-section"
|
|
108
|
+
title="The Kit"
|
|
109
|
+
eyebrow="What's inside"
|
|
110
|
+
description="A basic set of design system components connected to a token library."
|
|
111
|
+
gap="var(--space-8)"
|
|
112
|
+
>
|
|
113
|
+
<div class="kit-grid">
|
|
114
|
+
<div class="kit-item kit-lead sketch-container">
|
|
115
|
+
<div class="kit-head sketch-surface">
|
|
116
|
+
<i class="fas fa-pen-ruler kit-icon"></i>
|
|
117
|
+
<span class="kit-title">Live editor</span>
|
|
118
|
+
</div>
|
|
119
|
+
<span class="kit-rule sketch-rule" aria-hidden="true"></span>
|
|
120
|
+
<div class="kit-body" class:prose>
|
|
121
|
+
<p>
|
|
122
|
+
Edit tokens and components on top of the page and watch every change cascade immediately.
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="kit-item">
|
|
128
|
+
<div class="kit-head">
|
|
129
|
+
<i class="fas fa-palette kit-icon"></i>
|
|
130
|
+
<span class="kit-title">Design tokens</span>
|
|
131
|
+
</div>
|
|
132
|
+
<span class="kit-rule sketch-rule" aria-hidden="true"></span>
|
|
133
|
+
<div class="kit-body" class:prose><p>A complete set of atomic CSS variables for styling.</p></div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="kit-item">
|
|
137
|
+
<div class="kit-head">
|
|
138
|
+
<i class="fas fa-puzzle-piece kit-icon"></i>
|
|
139
|
+
<span class="kit-title">Component library</span>
|
|
140
|
+
</div>
|
|
141
|
+
<span class="kit-rule sketch-rule" aria-hidden="true"></span>
|
|
142
|
+
<div class="kit-body" class:prose><p>A customizable set of components linked to the tokens.</p></div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="kit-item">
|
|
146
|
+
<div class="kit-head">
|
|
147
|
+
<i class="fas fa-cube kit-icon"></i>
|
|
148
|
+
<span class="kit-title">Ships as CSS</span>
|
|
149
|
+
</div>
|
|
150
|
+
<span class="kit-rule sketch-rule" aria-hidden="true"></span>
|
|
151
|
+
<div class="kit-body" class:prose><p>Standard output. The editor stays in development.</p></div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="kit-preview">
|
|
156
|
+
<div class="swap-block">
|
|
157
|
+
<div class="swap-control">
|
|
158
|
+
<SectionDivider title="Swap tokens with a click" variant="sm" />
|
|
159
|
+
<SegmentedControl {segments} value={activeSegment} onchange={handleSelect} />
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<Panel style={stageStyle}>
|
|
163
|
+
<div class="stage">
|
|
164
|
+
<span class="stage-eyebrow">Token demo</span>
|
|
165
|
+
<div class="swap-row">
|
|
166
|
+
<div class="stage-cell"><Button>Primary</Button></div>
|
|
167
|
+
<div class="stage-cell"><Button variant="secondary">Secondary</Button></div>
|
|
168
|
+
<div class="stage-cell"><Button variant="outline">Outline</Button></div>
|
|
169
|
+
<div class="stage-cell"><Button variant="success" icon="fas fa-check">Success</Button></div>
|
|
170
|
+
<div class="stage-cell"><Button variant="warning" icon="fas fa-bolt">Warning</Button></div>
|
|
171
|
+
<div class="stage-cell"><Button variant="danger" icon="fas fa-xmark">Danger</Button></div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</Panel>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</Section>
|
|
178
|
+
|
|
179
|
+
<style lang="scss">
|
|
180
|
+
@use '../../system/styles/slot-prose' as *;
|
|
181
|
+
|
|
182
|
+
/* The section spans page columns 2–11, so its tracks are the page grid
|
|
183
|
+
minus one column on each side; each item then lands on exactly three page
|
|
184
|
+
columns and the row stops at column 9, leaving column 10 open on the right. */
|
|
185
|
+
.kit-grid {
|
|
186
|
+
display: grid;
|
|
187
|
+
grid-template-columns: repeat(calc(var(--columns-count) - 2), 1fr);
|
|
188
|
+
column-gap: var(--columns-gutter);
|
|
189
|
+
row-gap: var(--space-32);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.kit-item {
|
|
193
|
+
grid-column: span 3;
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.kit-head {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: var(--space-12);
|
|
202
|
+
padding: 0 0 var(--space-12);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Title, body and icon mirror the Card typography used in the Layers section. */
|
|
206
|
+
.kit-icon {
|
|
207
|
+
font-size: var(--icon-size-2xl);
|
|
208
|
+
color: var(--text-primary);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.kit-title {
|
|
212
|
+
color: var(--text-primary);
|
|
213
|
+
font-family: var(--font-sans);
|
|
214
|
+
font-size: var(--font-size-2xl);
|
|
215
|
+
font-weight: var(--font-weight-medium);
|
|
216
|
+
line-height: var(--line-height-normal);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.kit-body {
|
|
220
|
+
padding: var(--space-12) 0 0;
|
|
221
|
+
font-family: var(--font-sans);
|
|
222
|
+
font-size: var(--font-size-xl);
|
|
223
|
+
line-height: var(--line-height-normal);
|
|
224
|
+
font-weight: var(--font-weight-normal);
|
|
225
|
+
color: var(--text-secondary);
|
|
226
|
+
|
|
227
|
+
@include slot-prose;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* The pink hairline is the section's whole structural device: a horizontal
|
|
231
|
+
rule under each item's title, and a vertical rule splitting the lead.
|
|
232
|
+
It is an element rather than a border on .kit-head so that Sketch mode can
|
|
233
|
+
redraw it — the effect displaces boxes, and a border is not one. */
|
|
234
|
+
.kit-rule {
|
|
235
|
+
height: var(--border-width-2);
|
|
236
|
+
background: var(--border-brand);
|
|
237
|
+
--sketch-fill: var(--border-brand);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.kit-lead .kit-rule {
|
|
241
|
+
display: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Lead spans the full row; icon + title sit in the first column track, copy
|
|
245
|
+
fills the rest, mirroring the three columns below. */
|
|
246
|
+
/* Subgrid inherits the parent's 10 tracks so the head and copy align to real
|
|
247
|
+
columns: head centered on tracks 1–3 (page cols 2–4), copy on tracks 4–9
|
|
248
|
+
(page cols 5–10), with the last track left open like the items below. */
|
|
249
|
+
.kit-lead {
|
|
250
|
+
grid-column: 1 / -1;
|
|
251
|
+
display: grid;
|
|
252
|
+
grid-template-columns: subgrid;
|
|
253
|
+
align-items: center;
|
|
254
|
+
border-radius: var(--radius-lg);
|
|
255
|
+
/* Light brand wash strongest behind the "Live editor" head, dissolving
|
|
256
|
+
across the full block to nothing at the right edge — head and copy both
|
|
257
|
+
sit inside the fade. Declared once and handed to the sketch layer too:
|
|
258
|
+
the effect blanks the real background, so a wash named only here would
|
|
259
|
+
vanish when Sketch mode is on. */
|
|
260
|
+
--kit-lead-wash: linear-gradient(
|
|
261
|
+
90deg,
|
|
262
|
+
color-mix(in srgb, var(--color-brand-500) 21%, transparent),
|
|
263
|
+
transparent
|
|
264
|
+
);
|
|
265
|
+
background: var(--kit-lead-wash);
|
|
266
|
+
--sketch-fill: var(--kit-lead-wash);
|
|
267
|
+
--sketch-stroke: transparent;
|
|
268
|
+
/* The block draws no outline, and hatching takes its ink from the outline
|
|
269
|
+
unless a part names its own. */
|
|
270
|
+
--sketch-hatch-color: var(--color-brand-500);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* The head only carries a surface of its own when stacked (see the 960
|
|
274
|
+
breakpoint). Here it has none, and --sketch-fill inherits, so without this
|
|
275
|
+
the layer would draw the lead's wash a second time behind the head. */
|
|
276
|
+
.kit-lead .kit-head {
|
|
277
|
+
grid-column: 1 / span 3;
|
|
278
|
+
justify-content: center;
|
|
279
|
+
gap: var(--space-16);
|
|
280
|
+
padding: var(--space-32) 0;
|
|
281
|
+
--sketch-fill: transparent;
|
|
282
|
+
--sketch-stroke: transparent;
|
|
283
|
+
--sketch-radius: 0px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.kit-lead .kit-icon {
|
|
287
|
+
font-size: var(--icon-size-4xl, 3rem);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.kit-lead .kit-title {
|
|
291
|
+
font-size: var(--font-size-4xl);
|
|
292
|
+
font-weight: var(--font-weight-bold);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.kit-lead .kit-body {
|
|
296
|
+
grid-column: 4 / span 6;
|
|
297
|
+
padding: var(--space-32) 0;
|
|
298
|
+
font-size: var(--font-size-2xl);
|
|
299
|
+
color: var(--text-primary);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Controls sit above; the panel below is just the demo surface. */
|
|
303
|
+
/* Same 10-track grid as the cards so the block lands on real columns. */
|
|
304
|
+
.kit-preview {
|
|
305
|
+
margin-top: var(--space-32);
|
|
306
|
+
display: grid;
|
|
307
|
+
grid-template-columns: repeat(calc(var(--columns-count) - 2), 1fr);
|
|
308
|
+
column-gap: var(--columns-gutter);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Tracks 1–9 (page cols 2–10), leaving the last column open like the items
|
|
312
|
+
above. Label pins to the left column, control to the right. */
|
|
313
|
+
.swap-block {
|
|
314
|
+
grid-column: 1 / 10;
|
|
315
|
+
display: flex;
|
|
316
|
+
flex-direction: column;
|
|
317
|
+
align-items: stretch;
|
|
318
|
+
gap: var(--space-12);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.swap-control {
|
|
322
|
+
margin-top: var(--space-32);
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: row;
|
|
325
|
+
flex-wrap: wrap;
|
|
326
|
+
align-items: center;
|
|
327
|
+
justify-content: space-between;
|
|
328
|
+
gap: var(--space-24);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Use the sm divider purely as a styled label beside the control: drop its
|
|
332
|
+
built-in margin, and hide its hairline (gated by --_divider-hairline, which
|
|
333
|
+
no longer matches any @container query when set to none). */
|
|
334
|
+
.swap-control :global(.section-divider) {
|
|
335
|
+
margin: 0;
|
|
336
|
+
--_divider-hairline: none;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Two cells of one width, shrink-wrapped to the left. `width: max-content`
|
|
340
|
+
leaves the fr tracks an indefinite parent to resolve against, so both take
|
|
341
|
+
the wider select's size instead of splitting the block. */
|
|
342
|
+
.stage {
|
|
343
|
+
display: flex;
|
|
344
|
+
flex-direction: column;
|
|
345
|
+
align-items: flex-start;
|
|
346
|
+
gap: var(--space-12);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.stage-eyebrow {
|
|
350
|
+
align-self: center;
|
|
351
|
+
font-family: var(--font-sans);
|
|
352
|
+
font-size: var(--font-size-md);
|
|
353
|
+
font-weight: var(--font-weight-medium);
|
|
354
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
355
|
+
text-transform: uppercase;
|
|
356
|
+
color: var(--text-secondary);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.swap-row {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-wrap: wrap;
|
|
362
|
+
gap: var(--space-12);
|
|
363
|
+
justify-content: center;
|
|
364
|
+
align-items: center;
|
|
365
|
+
/* Reserved so toggling Shape (taller buttons) doesn't reflow the panel. */
|
|
366
|
+
min-height: 4rem;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.stage-cell {
|
|
370
|
+
width: 8.5rem;
|
|
371
|
+
display: flex;
|
|
372
|
+
align-items: center;
|
|
373
|
+
justify-content: center;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@media (max-width: 960px) {
|
|
377
|
+
.kit-grid {
|
|
378
|
+
grid-template-columns: 1fr;
|
|
379
|
+
}
|
|
380
|
+
.kit-item {
|
|
381
|
+
grid-column: auto;
|
|
382
|
+
}
|
|
383
|
+
.swap-block {
|
|
384
|
+
grid-column: 1 / -1;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.kit-lead {
|
|
388
|
+
display: flex;
|
|
389
|
+
flex-direction: column;
|
|
390
|
+
align-items: stretch;
|
|
391
|
+
background: none;
|
|
392
|
+
--sketch-fill: none;
|
|
393
|
+
}
|
|
394
|
+
/* Stacked: the head gets its own tinted bar instead of the fade. It draws
|
|
395
|
+
no outline, so the hatching takes the lead block's ink. */
|
|
396
|
+
.kit-lead .kit-head {
|
|
397
|
+
justify-content: flex-start;
|
|
398
|
+
gap: var(--space-12);
|
|
399
|
+
padding: var(--space-16) var(--space-20);
|
|
400
|
+
--kit-head-tint: color-mix(in srgb, var(--color-brand-500) 12%, transparent);
|
|
401
|
+
background: var(--kit-head-tint);
|
|
402
|
+
--sketch-fill: var(--kit-head-tint);
|
|
403
|
+
--sketch-hatch-color: var(--color-brand-500);
|
|
404
|
+
}
|
|
405
|
+
.kit-lead .kit-body {
|
|
406
|
+
padding: var(--space-16) var(--space-20);
|
|
407
|
+
font-size: var(--font-size-xl);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Card from '../../system/components/Card.svelte';
|
|
3
|
+
import Section from '../Section.svelte';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<Section
|
|
7
|
+
class="layers"
|
|
8
|
+
title="Two composable layers."
|
|
9
|
+
eyebrow="Under the hood"
|
|
10
|
+
description="The same two layers you just saw move — now with the wiring that connects them."
|
|
11
|
+
gap="var(--space-8)"
|
|
12
|
+
>
|
|
13
|
+
<div class="arch">
|
|
14
|
+
<Card class="arch-card arch-base" hover={false} icon="fas fa-layer-group" title="Tokens make the foundation">
|
|
15
|
+
<p>
|
|
16
|
+
Raw CSS variables for every primitive, grouped by family and function. Edit one value and every consumer updates at once.
|
|
17
|
+
</p>
|
|
18
|
+
</Card>
|
|
19
|
+
|
|
20
|
+
<Card class="arch-card arch-component" hover={false} icon="fas fa-shapes" title="Components build upon it">
|
|
21
|
+
<p>
|
|
22
|
+
Each component property is assigned a token. Property names describe purpose. Assign a new token and the component changes.
|
|
23
|
+
</p>
|
|
24
|
+
</Card>
|
|
25
|
+
</div>
|
|
26
|
+
</Section>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
.arch {
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: repeat(2, 1fr);
|
|
32
|
+
column-gap: var(--columns-gutter);
|
|
33
|
+
row-gap: var(--space-32);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.arch :global(.arch-card) {
|
|
37
|
+
height: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.arch :global(code) {
|
|
41
|
+
font-family: var(--font-mono);
|
|
42
|
+
font-size: 0.9em;
|
|
43
|
+
color: var(--text-primary);
|
|
44
|
+
background: var(--tint-low);
|
|
45
|
+
padding: var(--space-2) var(--space-6);
|
|
46
|
+
border-radius: var(--radius-sm);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media (max-width: 960px) {
|
|
50
|
+
.arch {
|
|
51
|
+
grid-template-columns: 1fr;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import offeringUrl from '../../system/assets/offering.webp';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<section class="offering">
|
|
6
|
+
<img class="offering-image" src={offeringUrl} alt="" />
|
|
7
|
+
|
|
8
|
+
<figure class="offering-quote">
|
|
9
|
+
<blockquote>
|
|
10
|
+
<p>
|
|
11
|
+
<span class="open-quote" aria-hidden="true">“</span>I built live
|
|
12
|
+
tokens to help me quickly set up and style single-page apps and microsites.
|
|
13
|
+
I found it useful. I hope you do too.<span class="close-quote" aria-hidden="true">”</span>
|
|
14
|
+
</p>
|
|
15
|
+
</blockquote>
|
|
16
|
+
<figcaption>-Mark</figcaption>
|
|
17
|
+
</figure>
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
.offering {
|
|
22
|
+
grid-column: 1 / -1;
|
|
23
|
+
display: grid;
|
|
24
|
+
grid-template-columns: repeat(var(--columns-count), 1fr);
|
|
25
|
+
column-gap: var(--columns-gutter);
|
|
26
|
+
position: relative;
|
|
27
|
+
z-index: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Roughly a third of the content width, parked on the left edge so it
|
|
31
|
+
overlaps the left-hand card above without dominating the section. */
|
|
32
|
+
.offering-image {
|
|
33
|
+
grid-column: 2 / 7;
|
|
34
|
+
grid-row: 1;
|
|
35
|
+
display: block;
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: auto;
|
|
38
|
+
border-radius: var(--radius-lg);
|
|
39
|
+
z-index: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Sits in the open space to the right of the image. No glass background.
|
|
43
|
+
align-self centers it on the image, then translateY drops it by half the
|
|
44
|
+
image's upward overlap (the extra -space-64 in Demo.svelte) so it lands in
|
|
45
|
+
the middle of the actual gap between the cards above and Get Started below. */
|
|
46
|
+
.offering-quote {
|
|
47
|
+
grid-column: 7 / -2;
|
|
48
|
+
grid-row: 1;
|
|
49
|
+
align-self: center;
|
|
50
|
+
transform: translateY(calc(var(--space-64) / 2));
|
|
51
|
+
margin: 0;
|
|
52
|
+
/* Align body text to the card text above. The cards split this same span
|
|
53
|
+
in half, so the right card's left edge sits half a gutter right of line 7;
|
|
54
|
+
its body text is then inset by the card's --space-20 padding. Match both,
|
|
55
|
+
and the open quote dangles left into this padding (text-indent on p). */
|
|
56
|
+
padding-left: calc(var(--columns-gutter) / 2 + var(--space-20));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.offering-quote blockquote {
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0;
|
|
62
|
+
border: none;
|
|
63
|
+
background: none;
|
|
64
|
+
border-radius: 0;
|
|
65
|
+
color: inherit;
|
|
66
|
+
font-style: normal;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.offering-quote p {
|
|
70
|
+
font-family: var(--font-serif);
|
|
71
|
+
font-size: var(--font-size-3xl);
|
|
72
|
+
font-weight: var(--font-weight-normal);
|
|
73
|
+
color: var(--text-primary);
|
|
74
|
+
line-height: 1.2;
|
|
75
|
+
margin: 0;
|
|
76
|
+
hyphens: none;
|
|
77
|
+
text-indent: -0.55em;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.open-quote,
|
|
81
|
+
.close-quote {
|
|
82
|
+
font-family: var(--font-serif);
|
|
83
|
+
color: var(--color-brand-300);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.open-quote {
|
|
87
|
+
margin-right: 0.2em;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.close-quote {
|
|
91
|
+
margin-left: 0.05em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.offering-quote figcaption {
|
|
95
|
+
margin-top: 0;
|
|
96
|
+
font-family: var(--font-serif);
|
|
97
|
+
font-weight: var(--font-weight-normal);
|
|
98
|
+
font-size: var(--font-size-3xl);
|
|
99
|
+
font-variation-settings: 'opsz' 144, 'SOFT' 100;
|
|
100
|
+
letter-spacing: 0.01em;
|
|
101
|
+
color: var(--text-primary);
|
|
102
|
+
text-align: right;
|
|
103
|
+
padding-right: 0.4em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (max-width: 960px) {
|
|
107
|
+
.offering-image {
|
|
108
|
+
grid-column: 2 / 7;
|
|
109
|
+
}
|
|
110
|
+
.offering-quote {
|
|
111
|
+
grid-column: 7 / -2;
|
|
112
|
+
}
|
|
113
|
+
.offering-quote p {
|
|
114
|
+
font-size: var(--font-size-xl);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@media (max-width: 600px) {
|
|
119
|
+
.offering-image {
|
|
120
|
+
grid-column: 1 / -1;
|
|
121
|
+
grid-row: 1;
|
|
122
|
+
}
|
|
123
|
+
.offering-quote {
|
|
124
|
+
grid-column: 1 / -1;
|
|
125
|
+
grid-row: 2;
|
|
126
|
+
align-self: auto;
|
|
127
|
+
width: auto;
|
|
128
|
+
margin: var(--space-16) 0 0;
|
|
129
|
+
padding-left: 0;
|
|
130
|
+
}
|
|
131
|
+
.offering-quote p {
|
|
132
|
+
font-size: var(--font-size-4xl);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The theme and the sketchstyle are two separate picks in the demo, but the
|
|
2
|
+
// package holds the sketch layer inside the theme: applying one runs
|
|
3
|
+
// `openThemeSketchSettings`, which overwrites the live dials with whatever the new
|
|
4
|
+
// theme carries, or turns the effect off for a theme carrying none. A visitor
|
|
5
|
+
// who picked Pencil and then tried three themes lost Pencil three times.
|
|
6
|
+
//
|
|
7
|
+
// So the theme select records the look it is leaving, and each document
|
|
8
|
+
// restores it once the apply lands. Each, not just this one: the editor overlay
|
|
9
|
+
// is a second instance of the sketch store in an iframe, it hydrates the
|
|
10
|
+
// applied theme on its own (over BroadcastChannel, so after `applyTheme`
|
|
11
|
+
// resolves here), and it paints the host page as well as its own preview.
|
|
12
|
+
// Restoring only in this document would be overwritten by that copy a tick
|
|
13
|
+
// later — the record in localStorage is what both of them read.
|
|
14
|
+
import { get } from 'svelte/store';
|
|
15
|
+
import { hasPersistedSketchState } from '../editor/core/sketch';
|
|
16
|
+
import {
|
|
17
|
+
sketchBaseline,
|
|
18
|
+
sketchEnabled,
|
|
19
|
+
sketchSettings,
|
|
20
|
+
selectedSketchStyleId,
|
|
21
|
+
} from '../editor/core/sketch/sketchStore';
|
|
22
|
+
import { THEME_SKETCH_ID, type SketchStyleSettings } from '../editor/core/sketch/sketchStyles';
|
|
23
|
+
import { THEME_APPLIED_EVENT } from '../editor/core/themes/themeDocumentSync';
|
|
24
|
+
|
|
25
|
+
const CARRY_KEY = 'lt-demo.sketchCarry';
|
|
26
|
+
|
|
27
|
+
// Long enough for the broadcast to reach the editor frame, short enough that a
|
|
28
|
+
// theme opened later from the editor's own Theme panel — where the sketchstyle
|
|
29
|
+
// IS the theme's, and loading one means taking it — never reads a record left
|
|
30
|
+
// over from the demo.
|
|
31
|
+
const CARRY_WINDOW_MS = 4000;
|
|
32
|
+
|
|
33
|
+
interface Carry {
|
|
34
|
+
token: string;
|
|
35
|
+
at: number;
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
name: string;
|
|
38
|
+
settings: SketchStyleSettings;
|
|
39
|
+
baseline: SketchStyleSettings | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Hold the live sketchstyle across the theme apply that follows. */
|
|
43
|
+
export function carrySketch(): void {
|
|
44
|
+
// A browser that has decided nothing has no pick to keep, and letting the
|
|
45
|
+
// theme seed it is the only way a theme's own sketchstyle ever shows.
|
|
46
|
+
if (!hasPersistedSketchState()) return;
|
|
47
|
+
// "Theme" is a pick that means follow the theme, so it follows this one too.
|
|
48
|
+
if (get(selectedSketchStyleId) === THEME_SKETCH_ID) return;
|
|
49
|
+
const record: Carry = {
|
|
50
|
+
token: Math.random().toString(36).slice(2),
|
|
51
|
+
at: Date.now(),
|
|
52
|
+
enabled: get(sketchEnabled),
|
|
53
|
+
name: get(selectedSketchStyleId),
|
|
54
|
+
settings: get(sketchSettings),
|
|
55
|
+
baseline: get(sketchBaseline),
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
localStorage.setItem(CARRY_KEY, JSON.stringify(record));
|
|
59
|
+
} catch {
|
|
60
|
+
// Storage blocked (private mode); the theme's sketchstyle wins, as before.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readCarry(): Carry | null {
|
|
65
|
+
try {
|
|
66
|
+
const raw = localStorage.getItem(CARRY_KEY);
|
|
67
|
+
if (!raw) return null;
|
|
68
|
+
const record = JSON.parse(raw) as Carry;
|
|
69
|
+
return Date.now() - record.at < CARRY_WINDOW_MS ? record : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function installSketchCarry(): void {
|
|
76
|
+
let handled = '';
|
|
77
|
+
document.addEventListener(THEME_APPLIED_EVENT, () => {
|
|
78
|
+
const record = readCarry();
|
|
79
|
+
if (!record || record.token === handled) return;
|
|
80
|
+
handled = record.token;
|
|
81
|
+
// `themeSketchSettings` stays as the apply left it: what the open theme
|
|
82
|
+
// holds is what makes the restored style read as off the theme, and what
|
|
83
|
+
// fills the picker's Unsaved row.
|
|
84
|
+
selectedSketchStyleId.set(record.name);
|
|
85
|
+
sketchBaseline.set(record.baseline);
|
|
86
|
+
sketchSettings.set(record.settings);
|
|
87
|
+
// Last: every write above repaints, and this is the one that decides
|
|
88
|
+
// whether anything is painted at all.
|
|
89
|
+
sketchEnabled.set(record.enabled);
|
|
90
|
+
});
|
|
91
|
+
}
|