@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,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 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 · 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>
|