@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.
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+
3
+ declare const TestingLoops: Component<{ homeHref?: string }>;
4
+ export default TestingLoops;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import LabeledSelect from './LabeledSelect.svelte';
4
+ import { listThemes, applyTheme } from '../editor/core/themes/themeService';
5
+ import { openThemeSlug } from '../editor/core/store/editorConfigStore';
6
+ import { carrySketch } from './sketchCarry';
7
+
8
+ let { inline = false, size = 'default', onpick }: { inline?: boolean; size?: 'default' | 'small'; onpick?: (fileName: string) => void } = $props();
9
+
10
+ let themes = $state<{ value: string; label: string }[]>([]);
11
+ let busy = $state(false);
12
+ let error = $state('');
13
+
14
+ onMount(async () => {
15
+ const files = await listThemes();
16
+ themes = files
17
+ .map(({ fileName, name }) => ({ value: fileName, label: name || fileName }))
18
+ .sort((a, b) =>
19
+ a.value === 'default' ? -1
20
+ : b.value === 'default' ? 1
21
+ : a.label.localeCompare(b.label),
22
+ );
23
+ });
24
+
25
+ async function changeTheme(fileName: string) {
26
+ if (!fileName || busy) return;
27
+ busy = true;
28
+ error = '';
29
+ try {
30
+ carrySketch();
31
+ await applyTheme(fileName);
32
+ onpick?.(fileName);
33
+ } catch (reason) {
34
+ error = reason instanceof Error ? reason.message : 'Could not change theme';
35
+ } finally {
36
+ busy = false;
37
+ }
38
+ }
39
+ </script>
40
+
41
+ <!-- The open theme drives the selection, so applies from the Themes panel or
42
+ another tab show up here too. -->
43
+ <LabeledSelect
44
+ {inline}
45
+ {size}
46
+ label="Theme"
47
+ items={themes}
48
+ value={$openThemeSlug}
49
+ disabled={busy}
50
+ {error}
51
+ onchange={changeTheme}
52
+ />
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ import Button from '../../system/components/Button.svelte';
3
+ import CodeSnippet from '../../system/components/CodeSnippet.svelte';
4
+ import SectionDivider from '../../system/components/SectionDivider.svelte';
5
+ import Panel from '../../system/components/Panel.svelte';
6
+ import { navigate } from '../../editor/core/routing/router';
7
+
8
+ const skills = [
9
+ { icon: 'fas fa-list-check', name: 'pick-component' },
10
+ { icon: 'fas fa-table-columns', name: 'create-page' },
11
+ { icon: 'fas fa-cube', name: 'create-component' },
12
+ { icon: 'fas fa-wand-magic-sparkles', name: 'create-theme' },
13
+ { icon: 'fas fa-palette', name: 'set-colors' },
14
+ { icon: 'fas fa-font', name: 'set-type' },
15
+ { icon: 'fas fa-ruler-combined', name: 'set-geometry' },
16
+ { icon: 'fas fa-clipboard-list', name: 'check-compliance' }
17
+ ];
18
+ </script>
19
+
20
+ <section class="claude">
21
+ <SectionDivider
22
+ title="With Claude skills (of course)"
23
+ eyebrow="Pairs with Claude Code"
24
+ />
25
+
26
+ <div class="panel-col">
27
+ <Panel>
28
+ <ul class="skills">
29
+ {#each skills as skill}
30
+ <li class="skill">
31
+ <i class="{skill.icon} skill-icon"></i>
32
+ <span class="skill-name">{skill.name}</span>
33
+ </li>
34
+ {/each}
35
+ </ul>
36
+ </Panel>
37
+ </div>
38
+
39
+ <div class="copy">
40
+ <p class="intro">
41
+ LiveTokens includes eight skills to build new components, themes, and pages, and to check what you have and bring it back into line.
42
+ </p>
43
+
44
+ <div class="install">
45
+ <CodeSnippet code="npx @motion-proto/live-tokens setup-claude" />
46
+ <p class="install-caption">Run this once to copy them into your project.</p>
47
+ </div>
48
+
49
+ <div class="atlas">
50
+ <div class="atlas-actions">
51
+ <Button variant="primary" onclick={() => navigate('/skills')} icon="fas fa-diagram-project" iconPosition="left">
52
+ How do skills work?
53
+ </Button>
54
+ <Button variant="secondary" onclick={() => navigate('/testing-loops')} icon="fas fa-rotate" iconPosition="left">
55
+ Testing Loops
56
+ </Button>
57
+ </div>
58
+ <p class="install-caption">
59
+ Each skill as a decision tree, next to the lines of SKILL.md that decide it.
60
+ </p>
61
+ </div>
62
+ </div>
63
+ </section>
64
+
65
+ <style>
66
+ .claude {
67
+ grid-column: 1 / -1;
68
+ display: grid;
69
+ grid-template-columns: repeat(var(--columns-count), 1fr);
70
+ column-gap: var(--columns-gutter);
71
+ row-gap: var(--space-8);
72
+ align-items: start;
73
+ margin-bottom: var(--space-96);
74
+ }
75
+
76
+ .claude :global(.section-divider) {
77
+ grid-column: 2 / -2;
78
+ }
79
+
80
+ /* Panel sits on page columns 2, 3, 4; the copy starts on column 5, hard
81
+ against the panel's right edge. */
82
+ .panel-col {
83
+ grid-column: 2 / 5;
84
+ grid-row: 2;
85
+ align-self: start;
86
+ margin-top: var(--space-16);
87
+ }
88
+
89
+ .copy {
90
+ grid-column: 5 / 11;
91
+ grid-row: 2;
92
+ align-self: start;
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: var(--space-24);
96
+ margin-top: var(--space-16);
97
+ }
98
+
99
+ .intro {
100
+ font-family: var(--font-sans);
101
+ font-size: var(--font-size-xl);
102
+ line-height: var(--line-height-tighter);
103
+ font-weight: var(--font-weight-semibold);
104
+ color: var(--text-primary);
105
+ margin: 0;
106
+ }
107
+
108
+ .install {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--space-8);
112
+ }
113
+
114
+ .install-caption {
115
+ margin: 0;
116
+ font-family: var(--font-sans);
117
+ font-size: var(--font-size-md);
118
+ line-height: var(--line-height-normal);
119
+ color: var(--text-secondary);
120
+ }
121
+
122
+ /* No global border-box reset here, so the snippet's own padding+border would
123
+ overflow its column under content-box. Pin it to border-box to fit exactly. */
124
+ .install :global(.codesnippet) {
125
+ box-sizing: border-box;
126
+ width: 100%;
127
+ }
128
+
129
+ /* `.code` sets overflow-x: auto with overflow-y: visible, which the spec
130
+ promotes to overflow-y: auto — a 1px line-height rounding then trips a
131
+ spurious vertical scrollbar. Clamp the vertical axis; horizontal scroll
132
+ for long lines still works. */
133
+ .install :global(.codesnippet .code) {
134
+ overflow-y: hidden;
135
+ }
136
+
137
+ .atlas {
138
+ display: flex;
139
+ flex-direction: column;
140
+ align-items: flex-start;
141
+ gap: var(--space-8);
142
+ }
143
+
144
+ .atlas-actions {
145
+ display: flex;
146
+ flex-wrap: wrap;
147
+ gap: var(--space-12);
148
+ }
149
+
150
+ .skills {
151
+ width: 100%;
152
+ list-style: none;
153
+ margin: 0;
154
+ padding: 0;
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: var(--space-24);
158
+ }
159
+
160
+ .skill {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: var(--space-8);
164
+ }
165
+
166
+ /* Icon + name match the old card header sizing so the skills read identically. */
167
+ .skill-icon {
168
+ font-size: var(--icon-size-2xl);
169
+ color: var(--text-secondary);
170
+ }
171
+
172
+ .skill-name {
173
+ font-family: var(--font-sans);
174
+ font-size: var(--font-size-2xl);
175
+ font-weight: var(--font-weight-medium);
176
+ line-height: var(--line-height-normal);
177
+ color: var(--text-primary);
178
+ }
179
+
180
+ @media (max-width: 960px) {
181
+ .claude :global(.section-divider),
182
+ .panel-col,
183
+ .copy {
184
+ grid-column: 1 / -1;
185
+ }
186
+
187
+ .panel-col,
188
+ .copy {
189
+ grid-row: auto;
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import SourceLinks from '../SourceLinks.svelte';
3
+ </script>
4
+
5
+ <footer class="provenance">
6
+ <div class="colophon">
7
+ <p class="license">
8
+ Open source under the
9
+ <a href="https://github.com/motionproto/live-tokens/blob/main/LICENSE" target="_blank" rel="noopener">MIT License</a>.
10
+ </p>
11
+ <p class="copyright">© 2026 Mark Pearce</p>
12
+ </div>
13
+
14
+ <SourceLinks />
15
+ </footer>
16
+
17
+ <style>
18
+ .provenance {
19
+ grid-column: 2 / -2;
20
+ padding-top: var(--space-24);
21
+ border-top: var(--border-width-2) solid var(--border-neutral);
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ gap: var(--space-32);
26
+ }
27
+
28
+ .colophon {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: var(--space-4);
32
+ }
33
+
34
+ .license {
35
+ margin: 0;
36
+ font-family: var(--font-sans);
37
+ font-size: var(--font-size-lg);
38
+ color: var(--text-secondary);
39
+ }
40
+
41
+ .license a {
42
+ color: var(--text-brand);
43
+ text-decoration: underline;
44
+ text-underline-offset: 0.15em;
45
+ transition: color var(--duration-150, 150ms) ease;
46
+ }
47
+
48
+ .license a:hover {
49
+ color: var(--text-primary);
50
+ }
51
+
52
+ .copyright {
53
+ margin: 0;
54
+ font-family: var(--font-sans);
55
+ font-size: var(--font-size-md);
56
+ color: var(--text-secondary);
57
+ }
58
+
59
+ @media (max-width: 960px) {
60
+ .provenance {
61
+ grid-column: 1 / -1;
62
+ flex-direction: column;
63
+ align-items: flex-start;
64
+ gap: var(--space-24);
65
+ }
66
+ }
67
+ </style>
@@ -0,0 +1,100 @@
1
+ <script lang="ts">
2
+ import CodeSnippet from '../../system/components/CodeSnippet.svelte';
3
+ import SectionDivider from '../../system/components/SectionDivider.svelte';
4
+ import Panel from '../../system/components/Panel.svelte';
5
+
6
+ const scaffold = `npx @motion-proto/live-tokens create my-app
7
+ cd my-app
8
+ npm install
9
+ npm run dev`;
10
+ </script>
11
+
12
+ <section class="get-started">
13
+ <div class="gs-panel">
14
+ <Panel>
15
+ <div class="gs-content">
16
+ <SectionDivider variant="sm" title="Getting Started" />
17
+
18
+ <div class="columns">
19
+ <p class="intro">
20
+ Creating a new project gives you a starter page with the live&nbsp;editor already wired in. Run the commands,
21
+ start the dev server, and edit the design system as you build.
22
+ </p>
23
+
24
+ <div class="install">
25
+ <CodeSnippet code={scaffold} />
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </Panel>
30
+ </div>
31
+ </section>
32
+
33
+ <style>
34
+ .get-started {
35
+ grid-column: 1 / -1;
36
+ display: grid;
37
+ grid-template-columns: repeat(var(--columns-count), 1fr);
38
+ column-gap: var(--columns-gutter);
39
+ }
40
+
41
+ .gs-panel {
42
+ grid-column: 2 / -2;
43
+ /* The .kit grid can stretch this section's row past its content (min-height:
44
+ 100vh on a short page); keep the panel sized to its content regardless. */
45
+ align-self: start;
46
+ }
47
+
48
+ /* Single full-width child of the Panel's centering stage; the stage lays its
49
+ children out in a row, so the section's content must collapse to one item. */
50
+ .gs-content {
51
+ width: 100%;
52
+ }
53
+
54
+ /* SectionDivider ships with margin: var(--space-24) 0; as the first child its
55
+ top margin stacks on the stage padding — zero it out here. */
56
+ .gs-content :global(.section-divider) {
57
+ margin-top: 0;
58
+ }
59
+
60
+ /* Code column gets the larger share so the longest scaffold line fits
61
+ without triggering the snippet's scrollbar. */
62
+ .columns {
63
+ display: grid;
64
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
65
+ gap: var(--space-40);
66
+ align-items: center;
67
+ }
68
+
69
+ .intro {
70
+ font-family: var(--font-sans);
71
+ font-size: var(--font-size-xl);
72
+ line-height: var(--line-height-tighter);
73
+ font-weight: var(--font-weight-semibold);
74
+ color: var(--text-primary);
75
+ margin: 0;
76
+ }
77
+
78
+ /* No global border-box reset here, so width:100% under content-box would let
79
+ the snippet's own padding+border overflow its column and eat the panel's
80
+ right padding. Pin it to border-box so it fills the column exactly. */
81
+ .install :global(.codesnippet) {
82
+ box-sizing: border-box;
83
+ width: 100%;
84
+ }
85
+
86
+ /* `.code` sets overflow-x: auto with overflow-y: visible, which the spec
87
+ promotes to overflow-y: auto — a 1px line-height rounding then trips a
88
+ spurious vertical scrollbar. Clamp the vertical axis; horizontal scroll
89
+ for long lines still works. */
90
+ .install :global(.codesnippet .code) {
91
+ overflow-y: hidden;
92
+ }
93
+
94
+ @media (max-width: 960px) {
95
+ .columns {
96
+ grid-template-columns: 1fr;
97
+ align-items: stretch;
98
+ }
99
+ }
100
+ </style>
@@ -0,0 +1,201 @@
1
+ <script lang="ts">
2
+ import { get } from 'svelte/store';
3
+ import Button from '../../system/components/Button.svelte';
4
+ import FloatingTokenTags from '../FloatingTokenTags.svelte';
5
+ import { setEditorView } from '../../editor/core/store/editorViewStore';
6
+ import { overlayOpen } from '../../editor/overlay/overlayState';
7
+ import SourceLinks from '../SourceLinks.svelte';
8
+
9
+ const isDev = import.meta.env.DEV;
10
+
11
+ function openOverlay(view: 'tokens' | 'components') {
12
+ setEditorView(view);
13
+ if (!get(overlayOpen)) {
14
+ window.dispatchEvent(new CustomEvent('lt-overlay-toggle'));
15
+ }
16
+ }
17
+ </script>
18
+
19
+ <header class="hero">
20
+ <div class="hero-kite">
21
+ <FloatingTokenTags />
22
+ </div>
23
+
24
+ <div class="hero-text">
25
+ <h1 class="hero-title">
26
+ LiveTokens<br />
27
+ <span class="hero-italic">The design system with an editor.</span>
28
+ </h1>
29
+
30
+ <p class="hero-tagline">
31
+ Design system authoring for Svelte.<br />
32
+ Edit tokens and components directly in the browser.<br />Ship plain CSS.
33
+ </p>
34
+ </div>
35
+
36
+ <div class="hero-actions">
37
+ {#if isDev}
38
+ <div class="hero-controls">
39
+ <Button variant="secondary" onclick={() => openOverlay('components')} icon="fas fa-puzzle-piece" iconPosition="left">
40
+ Browse Components
41
+ </Button>
42
+ <Button onclick={() => openOverlay('tokens')} icon="fas fa-sliders" iconPosition="left">
43
+ Open Token Editor
44
+ </Button>
45
+ </div>
46
+ {:else}
47
+ <div class="hero-controls">
48
+ <Button variant="outline" disabled>Editor &nbsp;·&nbsp; dev only</Button>
49
+ </div>
50
+ {/if}
51
+
52
+ <div class="hero-meta">
53
+ <SourceLinks compact />
54
+ <p class="hero-byline">
55
+ by <a href="mailto:hello@motionproto.com">Mark</a> at
56
+ <a href="https://motionproto.com/" target="_blank" rel="noopener">MotionProto</a>
57
+ </p>
58
+ </div>
59
+ </div>
60
+ </header>
61
+
62
+ <style>
63
+ .hero {
64
+ grid-column: 1 / -1;
65
+ position: relative;
66
+ padding: var(--space-48) 0 var(--space-16);
67
+ display: grid;
68
+ grid-template-columns: repeat(var(--columns-count), 1fr);
69
+ column-gap: var(--columns-gutter);
70
+ }
71
+
72
+ .hero-text {
73
+ grid-column: 2 / span 5;
74
+ grid-row: 1;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: var(--space-16);
78
+ position: relative;
79
+ padding: var(--space-96) 0 0;
80
+ }
81
+
82
+ .hero-kite {
83
+ grid-column: 5 / span 8;
84
+ grid-row: 1;
85
+ height: 32rem;
86
+ margin-top: var(--space-16);
87
+ position: relative;
88
+ }
89
+
90
+ .hero-title {
91
+ font-family: var(--font-display);
92
+ font-size: var(--font-size-7xl);
93
+ font-weight: var(--font-weight-semibold);
94
+ font-variation-settings: 'opsz' 144, 'SOFT' 30;
95
+ color: var(--text-primary);
96
+ line-height: .9;
97
+ margin: 0;
98
+ }
99
+
100
+ .hero-italic {
101
+ font-family: var(--heading-xl-font-family);
102
+ font-size: var(--font-size-6xl);
103
+ font-weight: var(--font-weight-normal);
104
+ color: var(--text-brand);
105
+ font-variation-settings: 'opsz' 144, 'SOFT' 100;
106
+ white-space: nowrap;
107
+ }
108
+
109
+ .hero-tagline {
110
+ font-family: var(--body-md-font-family);
111
+ font-size: var(--font-size-2xl);
112
+ color: var(--text-primary);
113
+ line-height: 1.2;
114
+ max-width: 48rem;
115
+ margin: var(--space-8) 0 0;
116
+ }
117
+
118
+ /* One row: the CTAs on the left rail, the credits on the right, sharing a
119
+ top edge so the buttons sit on the brand-mark line. */
120
+ .hero-actions {
121
+ grid-column: 2 / -2;
122
+ grid-row: 2;
123
+ display: grid;
124
+ grid-template-columns: max-content 1fr;
125
+ align-items: start;
126
+ column-gap: var(--space-32);
127
+ margin-top: var(--space-16);
128
+ }
129
+
130
+ /* Two cells of one width. Equal `1fr` tracks floored at `max-content` take
131
+ the wider button's size in an indefinitely-sized parent, so the pair stays
132
+ even as a theme swap changes the label metrics. */
133
+ .hero-controls {
134
+ grid-column: 1;
135
+ display: grid;
136
+ grid-template-columns: repeat(2, minmax(max-content, 1fr));
137
+ column-gap: var(--space-12);
138
+ }
139
+
140
+ .hero-meta {
141
+ grid-column: 2;
142
+ display: flex;
143
+ flex-direction: column;
144
+ align-items: flex-end;
145
+ gap: var(--space-16);
146
+ text-align: right;
147
+ }
148
+
149
+ .hero-byline {
150
+ font-family: var(--font-sans);
151
+ font-size: var(--font-size-md);
152
+ font-weight: var(--font-weight-normal);
153
+ line-height: var(--line-height-normal);
154
+ color: var(--text-secondary);
155
+ margin: 0;
156
+ }
157
+
158
+ .hero-byline a {
159
+ color: var(--text-brand);
160
+ text-decoration: none;
161
+ }
162
+
163
+ .hero-byline a:hover {
164
+ text-decoration: underline;
165
+ }
166
+
167
+ @media (max-width: 960px) {
168
+ .hero-kite {
169
+ grid-column: 1 / -1;
170
+ grid-row: 1;
171
+ height: 28rem;
172
+ }
173
+ .hero-text {
174
+ grid-column: 1 / -1;
175
+ grid-row: 2;
176
+ padding: 0;
177
+ }
178
+ .hero-actions {
179
+ grid-column: 1 / -1;
180
+ grid-row: 3;
181
+ grid-template-columns: minmax(0, 1fr);
182
+ row-gap: var(--space-16);
183
+ margin-top: var(--space-12);
184
+ }
185
+ .hero-meta {
186
+ grid-column: 1;
187
+ align-items: flex-start;
188
+ text-align: left;
189
+ }
190
+ }
191
+
192
+ @media (max-width: 600px) {
193
+ .hero-title {
194
+ font-size: var(--font-size-5xl);
195
+ }
196
+ .hero-controls {
197
+ grid-template-columns: minmax(0, 1fr);
198
+ row-gap: var(--space-12);
199
+ }
200
+ }
201
+ </style>