@motion-proto/live-tokens 0.85.0 → 0.87.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 +55 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/editor/component-editor/SlidePagerEditor.svelte +118 -0
- package/src/editor/component-editor/VideoFrameEditor.svelte +73 -0
- package/src/editor/component-editor/VideoLightboxEditor.svelte +108 -0
- package/src/editor/component-editor/registry.ts +40 -1
- package/src/editor/core/sketch/sketchLayer.ts +7 -0
- package/src/editor/overlay/LiveEditorOverlay.svelte +45 -52
- package/src/editor/pages/ComponentEditorPage.svelte +1 -1
- package/src/editor/ui/ThemePanel.svelte +9 -53
- package/src/live-tokens/data/themes/autumn.json +97 -0
- package/src/live-tokens/data/themes/halloween.json +97 -0
- package/src/live-tokens/data/themes/midnight-study.json +97 -0
- package/src/live-tokens/data/themes/ocean.json +97 -0
- package/src/live-tokens/data/themes/royal-velvet.json +97 -0
- package/src/live-tokens/data/themes/sketchy.json +97 -0
- package/src/live-tokens/data/themes/spring-meadow.json +97 -0
- package/src/live-tokens/data/themes/sunset.json +97 -0
- package/src/system/assets/slide-04.webp +0 -0
- package/src/system/assets/slide-05.webp +0 -0
- package/src/system/assets/slide-06.webp +0 -0
- package/src/system/assets/watch-navigation-poster.webp +0 -0
- package/src/system/assets/watch-navigation.mp4 +0 -0
- package/src/system/components/ImageLightbox.svelte +20 -21
- package/src/system/components/SlidePager.svelte +516 -0
- package/src/system/components/VideoFrame.svelte +284 -0
- package/src/system/components/VideoLightbox.svelte +454 -0
- package/src/testing-js/{chunk-RDHCBQ6I.js → chunk-FTLASW2R.js} +366 -2
- package/src/testing-js/chunk-FTLASW2R.js.map +1 -0
- package/src/testing-js/{chunk-XV5CYADO.js → chunk-NXRFTTVL.js} +10 -6
- package/src/testing-js/chunk-NXRFTTVL.js.map +1 -0
- package/src/testing-js/component-behavior.contract.js +1 -1
- package/src/testing-js/component-editor.contract.js +1 -1
- package/src/testing-js/component-render.contract.js +1 -1
- package/src/testing-js/index.d.ts +2 -2
- package/src/testing-js/index.js +2 -2
- package/src/testing-js/page-compliance.contract.js +1 -1
- package/src/testing-js/{vitest-BMLIbDs2.d.ts → vitest-5vWtqwWe.d.ts} +2 -2
- package/src/testing-js/vitest.d.ts +1 -1
- package/src/testing-js/vitest.js +2 -2
- package/src/testing-js/chunk-RDHCBQ6I.js.map +0 -1
- package/src/testing-js/chunk-XV5CYADO.js.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
|
|
3
|
+
|
|
4
|
+
export const catalogue = {
|
|
5
|
+
description: 'A poster frame that plays its clip in place when clicked.',
|
|
6
|
+
whenToUse:
|
|
7
|
+
'footage whose motion is the point, standing in a layout where a still would otherwise sit.',
|
|
8
|
+
whenNotToUse: [
|
|
9
|
+
{ when: 'a picture whose detail is the point.', use: 'imagelightbox' },
|
|
10
|
+
{ when: 'decoration.', use: 'image' },
|
|
11
|
+
{ when: 'a clip the reader opens over the page from a thumbnail.', use: 'videolightbox' }
|
|
12
|
+
],
|
|
13
|
+
constraints: ['A clip the deploy does not serve falls back to its poster.'],
|
|
14
|
+
props: {
|
|
15
|
+
chrome:
|
|
16
|
+
'`badge` shows the play badge and hands playback to the native controls. `none` drops both, and a click toggles play.',
|
|
17
|
+
fit: '`fill` scales the clip to the frame. `natural` holds it at its own size on the frame’s ground, which wants width and height.',
|
|
18
|
+
loop: 'true repeats the clip.'
|
|
19
|
+
}
|
|
20
|
+
} satisfies CatalogueEntry;
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<script lang="ts">
|
|
24
|
+
interface Props {
|
|
25
|
+
/** The clip. Served as a static file, so a missing one is survivable. */
|
|
26
|
+
src: string;
|
|
27
|
+
/** The still shown until play, and the fallback when the clip is absent. */
|
|
28
|
+
poster: string;
|
|
29
|
+
alt?: string;
|
|
30
|
+
/** Natural size of the poster. Sets the box before anything loads. */
|
|
31
|
+
width?: number;
|
|
32
|
+
height?: number;
|
|
33
|
+
maxWidth?: number | string;
|
|
34
|
+
loop?: boolean;
|
|
35
|
+
/** "none" drops the badge and the native controls; a click then toggles play. */
|
|
36
|
+
chrome?: 'badge' | 'none';
|
|
37
|
+
/** "natural" holds the clip at its own pixel size on the frame's ground. Needs width and height. */
|
|
38
|
+
fit?: 'fill' | 'natural';
|
|
39
|
+
class?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
src,
|
|
44
|
+
poster,
|
|
45
|
+
alt = '',
|
|
46
|
+
width = undefined,
|
|
47
|
+
height = undefined,
|
|
48
|
+
maxWidth = undefined,
|
|
49
|
+
loop = false,
|
|
50
|
+
chrome = 'badge',
|
|
51
|
+
fit = 'fill',
|
|
52
|
+
class: className = ''
|
|
53
|
+
}: Props = $props();
|
|
54
|
+
|
|
55
|
+
let videoEl: HTMLVideoElement | undefined = $state();
|
|
56
|
+
let started = $state(false);
|
|
57
|
+
let playing = $state(false);
|
|
58
|
+
// A clip the deploy does not serve falls back to the poster. A HEAD answers
|
|
59
|
+
// that on its own; a load error would not, since it fires the same way for a
|
|
60
|
+
// codec the browser cannot decode. An SPA rewrite answers 200 with
|
|
61
|
+
// index.html for a path it has no file for, so the content type settles it.
|
|
62
|
+
let missing = $state(false);
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
let live = true;
|
|
66
|
+
fetch(src, { method: 'HEAD' })
|
|
67
|
+
.then((res) => {
|
|
68
|
+
const type = res.headers.get('content-type') ?? '';
|
|
69
|
+
if (live) missing = !res.ok || !type.startsWith('video/');
|
|
70
|
+
})
|
|
71
|
+
.catch(() => {
|
|
72
|
+
if (live) missing = true;
|
|
73
|
+
});
|
|
74
|
+
return () => {
|
|
75
|
+
live = false;
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const ratio = $derived(width && height ? `${width} / ${height}` : undefined);
|
|
80
|
+
const cap = $derived(typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth);
|
|
81
|
+
|
|
82
|
+
function start() {
|
|
83
|
+
started = true;
|
|
84
|
+
videoEl?.play();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toggle() {
|
|
88
|
+
if (!videoEl) return;
|
|
89
|
+
started = true;
|
|
90
|
+
if (videoEl.paused) videoEl.play();
|
|
91
|
+
else videoEl.pause();
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<div class="videoframe" style:max-width={cap}>
|
|
96
|
+
<div
|
|
97
|
+
class="videoframe-frame sketch-surface {className}"
|
|
98
|
+
style:aspect-ratio={ratio}
|
|
99
|
+
data-playing={started ? 'true' : null}
|
|
100
|
+
>
|
|
101
|
+
<div class="videoframe-clip" class:is-natural={fit === 'natural'}>
|
|
102
|
+
{#if missing}
|
|
103
|
+
<img src={poster} {alt} {width} {height} />
|
|
104
|
+
{:else}
|
|
105
|
+
<!-- No caption track ships with the source footage. -->
|
|
106
|
+
<!-- svelte-ignore a11y_media_has_caption -->
|
|
107
|
+
<video
|
|
108
|
+
bind:this={videoEl}
|
|
109
|
+
{src}
|
|
110
|
+
{poster}
|
|
111
|
+
{loop}
|
|
112
|
+
preload="metadata"
|
|
113
|
+
playsinline
|
|
114
|
+
controls={started && chrome === 'badge'}
|
|
115
|
+
onplay={() => (playing = true)}
|
|
116
|
+
onpause={() => (playing = false)}
|
|
117
|
+
></video>
|
|
118
|
+
{/if}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{#if !missing && (chrome === 'none' || !started)}
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
class="videoframe-trigger"
|
|
125
|
+
onclick={chrome === 'none' ? toggle : start}
|
|
126
|
+
>
|
|
127
|
+
<span class="visually-hidden">{playing ? 'Pause' : 'Play'}: {alt}</span>
|
|
128
|
+
{#if chrome === 'badge'}
|
|
129
|
+
<span class="videoframe-badge sketch-chip" aria-hidden="true">
|
|
130
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5.5v13l11-6.5z" /></svg>
|
|
131
|
+
</span>
|
|
132
|
+
{/if}
|
|
133
|
+
</button>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<style>
|
|
139
|
+
:global(:root) {
|
|
140
|
+
--videoframe-frame-surface: var(--surface-neutral-lowest);
|
|
141
|
+
/* The ground behind the clip. Only shows where the clip leaves the frame
|
|
142
|
+
unfilled, so it wants the footage's own background colour. */
|
|
143
|
+
--videoframe-clip-surface: var(--color-transparent);
|
|
144
|
+
--videoframe-frame-border: var(--border-neutral);
|
|
145
|
+
--videoframe-frame-border-width: var(--border-width-1);
|
|
146
|
+
--videoframe-frame-radius: var(--radius-2xl);
|
|
147
|
+
|
|
148
|
+
--videoframe-badge-default-surface: var(--surface-brand-lower);
|
|
149
|
+
--videoframe-badge-default-border: var(--border-brand-medium);
|
|
150
|
+
--videoframe-badge-default-border-width: var(--border-width-2);
|
|
151
|
+
--videoframe-badge-default-icon: var(--text-primary);
|
|
152
|
+
--videoframe-badge-default-size: var(--space-64);
|
|
153
|
+
--videoframe-badge-default-icon-size: var(--icon-size-4xl);
|
|
154
|
+
--videoframe-badge-duration: var(--duration-200);
|
|
155
|
+
--videoframe-badge-easing: var(--ease-out-quad);
|
|
156
|
+
|
|
157
|
+
--videoframe-badge-hover-surface: var(--surface-brand);
|
|
158
|
+
--videoframe-badge-hover-border: var(--border-brand-strong);
|
|
159
|
+
--videoframe-badge-hover-icon: var(--text-primary);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.videoframe {
|
|
163
|
+
display: block;
|
|
164
|
+
width: 100%;
|
|
165
|
+
margin-inline: auto;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.videoframe-frame {
|
|
169
|
+
position: relative;
|
|
170
|
+
width: 100%;
|
|
171
|
+
background: var(--videoframe-frame-surface);
|
|
172
|
+
border: var(--videoframe-frame-border-width) solid var(--videoframe-frame-border);
|
|
173
|
+
border-radius: var(--videoframe-frame-radius);
|
|
174
|
+
|
|
175
|
+
--sketch-fill: var(--videoframe-frame-surface);
|
|
176
|
+
--sketch-stroke: var(--videoframe-frame-border);
|
|
177
|
+
--sketch-hatch-color: var(--videoframe-frame-border);
|
|
178
|
+
--sketch-radius: var(--videoframe-frame-radius);
|
|
179
|
+
--sketch-shadow: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* The clip lives one level in: the sketch layer forces the drawn part's
|
|
183
|
+
overflow visible, which would let square media corners escape the frame. */
|
|
184
|
+
.videoframe-clip {
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
background: var(--videoframe-clip-surface);
|
|
187
|
+
border-radius: var(--sketch-radius, var(--videoframe-frame-radius));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.videoframe-clip img,
|
|
191
|
+
.videoframe-clip video {
|
|
192
|
+
display: block;
|
|
193
|
+
width: 100%;
|
|
194
|
+
height: 100%;
|
|
195
|
+
object-fit: contain;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Absolute, so the frame's aspect-ratio box stays the pad the clip sits in. */
|
|
199
|
+
.videoframe-clip.is-natural {
|
|
200
|
+
position: absolute;
|
|
201
|
+
inset: 0;
|
|
202
|
+
display: grid;
|
|
203
|
+
place-items: center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.videoframe-clip.is-natural img,
|
|
207
|
+
.videoframe-clip.is-natural video {
|
|
208
|
+
width: auto;
|
|
209
|
+
height: auto;
|
|
210
|
+
max-width: 100%;
|
|
211
|
+
max-height: 100%;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.videoframe-trigger {
|
|
215
|
+
position: absolute;
|
|
216
|
+
inset: 0;
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
padding: 0;
|
|
221
|
+
background: none;
|
|
222
|
+
border: none;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.videoframe-trigger:focus-visible {
|
|
227
|
+
outline: none;
|
|
228
|
+
box-shadow: var(--ring-focus-md);
|
|
229
|
+
border-radius: var(--sketch-radius, var(--videoframe-frame-radius));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.videoframe-badge {
|
|
233
|
+
box-sizing: border-box;
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
justify-content: center;
|
|
237
|
+
width: var(--videoframe-badge-default-size);
|
|
238
|
+
height: var(--videoframe-badge-default-size);
|
|
239
|
+
color: var(--videoframe-badge-default-icon);
|
|
240
|
+
background: var(--videoframe-badge-default-surface);
|
|
241
|
+
border: var(--videoframe-badge-default-border-width) solid
|
|
242
|
+
var(--videoframe-badge-default-border);
|
|
243
|
+
border-radius: var(--radius-full);
|
|
244
|
+
transition:
|
|
245
|
+
background var(--videoframe-badge-duration) var(--videoframe-badge-easing),
|
|
246
|
+
border-color var(--videoframe-badge-duration) var(--videoframe-badge-easing),
|
|
247
|
+
color var(--videoframe-badge-duration) var(--videoframe-badge-easing);
|
|
248
|
+
|
|
249
|
+
--sketch-fill: var(--videoframe-badge-default-surface);
|
|
250
|
+
--sketch-stroke: var(--videoframe-badge-default-border);
|
|
251
|
+
--sketch-hatch-color: var(--videoframe-badge-default-border);
|
|
252
|
+
--sketch-radius: var(--radius-full);
|
|
253
|
+
--sketch-shadow: none;
|
|
254
|
+
/* Travel is stated in px, and the play triangle is small enough to tear. */
|
|
255
|
+
--sketch-icon-off: var(--sketch-icon-soft);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.videoframe-badge svg {
|
|
259
|
+
width: var(--videoframe-badge-default-icon-size);
|
|
260
|
+
height: var(--videoframe-badge-default-icon-size);
|
|
261
|
+
/* The triangle's mass sits left of centre; nudge it back onto the axis. */
|
|
262
|
+
margin-inline-start: 6%;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.videoframe-trigger:hover .videoframe-badge,
|
|
266
|
+
.videoframe-frame.force-hover .videoframe-badge {
|
|
267
|
+
color: var(--videoframe-badge-hover-icon);
|
|
268
|
+
background: var(--videoframe-badge-hover-surface);
|
|
269
|
+
border-color: var(--videoframe-badge-hover-border);
|
|
270
|
+
|
|
271
|
+
--sketch-fill: var(--videoframe-badge-hover-surface);
|
|
272
|
+
--sketch-stroke: var(--videoframe-badge-hover-border);
|
|
273
|
+
--sketch-hatch-color: var(--videoframe-badge-hover-border);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.visually-hidden {
|
|
277
|
+
position: absolute;
|
|
278
|
+
width: 1px;
|
|
279
|
+
height: 1px;
|
|
280
|
+
overflow: hidden;
|
|
281
|
+
clip-path: inset(50%);
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
}
|
|
284
|
+
</style>
|