create-substrate 0.1.4 → 0.1.5
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/dist/index.js +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +57 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/surfaces/3d-scene.d.ts.map +1 -1
- package/dist/surfaces/3d-scene.js +414 -67
- package/dist/surfaces/3d-scene.js.map +1 -1
- package/dist/surfaces/animation.d.ts.map +1 -1
- package/dist/surfaces/animation.js +95 -103
- package/dist/surfaces/animation.js.map +1 -1
- package/dist/surfaces/blank.d.ts.map +1 -1
- package/dist/surfaces/blank.js +28 -16
- package/dist/surfaces/blank.js.map +1 -1
- package/dist/surfaces/canvas-2d.d.ts.map +1 -1
- package/dist/surfaces/canvas-2d.js +369 -92
- package/dist/surfaces/canvas-2d.js.map +1 -1
- package/dist/surfaces/data-vis.d.ts.map +1 -1
- package/dist/surfaces/data-vis.js +262 -98
- package/dist/surfaces/data-vis.js.map +1 -1
- package/dist/surfaces/image-gen.d.ts.map +1 -1
- package/dist/surfaces/image-gen.js +354 -92
- package/dist/surfaces/image-gen.js.map +1 -1
- package/dist/surfaces/node-editor.d.ts.map +1 -1
- package/dist/surfaces/node-editor.js +559 -114
- package/dist/surfaces/node-editor.js.map +1 -1
- package/package.json +3 -2
- package/skills/canvas-2d/SKILL.md +1 -1
- package/skills/composing-panels/SKILL.md +8 -8
- package/skills/scaffold-playground/SKILL.md +4 -4
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
export const animation = {
|
|
2
2
|
components: [
|
|
3
|
-
"toolbar",
|
|
4
3
|
"panel",
|
|
5
4
|
"pane",
|
|
6
5
|
"timeline",
|
|
6
|
+
"animation-store",
|
|
7
7
|
"easing-curve-editor",
|
|
8
|
-
"
|
|
8
|
+
"easing",
|
|
9
9
|
"number-input",
|
|
10
|
-
"history-controls",
|
|
11
10
|
"status-bar",
|
|
12
11
|
],
|
|
13
12
|
extraDeps: {
|
|
14
13
|
gsap: "^3.12.0",
|
|
15
14
|
},
|
|
16
|
-
app: () => `import {
|
|
17
|
-
import {
|
|
18
|
-
Toolbar,
|
|
19
|
-
ToolbarToggleGroup,
|
|
20
|
-
ToolbarToggle,
|
|
21
|
-
ToolbarSeparator,
|
|
22
|
-
} from "@/components/substrate/toolbar"
|
|
15
|
+
app: () => `import { useEffect, useState } from "react"
|
|
23
16
|
import { Panel } from "@/components/substrate/panel"
|
|
24
17
|
import {
|
|
25
18
|
Pane,
|
|
@@ -27,18 +20,19 @@ import {
|
|
|
27
20
|
PaneLabel,
|
|
28
21
|
PaneContent,
|
|
29
22
|
} from "@/components/substrate/pane"
|
|
30
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
Timeline,
|
|
25
|
+
TimelineRuler,
|
|
26
|
+
TimelinePlayhead,
|
|
27
|
+
TimelineTrack,
|
|
28
|
+
TimelineKeyframe,
|
|
29
|
+
TimelineTransport,
|
|
30
|
+
} from "@/components/substrate/timeline"
|
|
31
31
|
import { EasingCurveEditor } from "@/components/substrate/easing-curve-editor"
|
|
32
32
|
import { NumberInput } from "@/components/substrate/number-input"
|
|
33
|
-
import { Slider } from "@/components/substrate/slider"
|
|
34
|
-
import { HistoryControls } from "@/components/substrate/history-controls"
|
|
35
33
|
import { StatusBar, StatusBarSection, StatusBarItem } from "@/components/substrate/status-bar"
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
RiPauseLine,
|
|
39
|
-
RiStopLine,
|
|
40
|
-
RiSkipBackLine,
|
|
41
|
-
} from "@remixicon/react"
|
|
34
|
+
import { useAnimationStore } from "@/stores/substrate/animation-store"
|
|
35
|
+
import { evaluateCubicBezier, type CubicBezier } from "@/lib/substrate/easing"
|
|
42
36
|
|
|
43
37
|
interface Keyframe {
|
|
44
38
|
id: string
|
|
@@ -52,14 +46,15 @@ interface Track {
|
|
|
52
46
|
keyframes: Keyframe[]
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
|
|
49
|
+
// Keyframe times are in seconds, matching the animation store
|
|
50
|
+
const TRACKS: Track[] = [
|
|
56
51
|
{
|
|
57
52
|
id: "position-x",
|
|
58
53
|
label: "Position X",
|
|
59
54
|
keyframes: [
|
|
60
|
-
{ id: "k1", time: 0, value:
|
|
61
|
-
{ id: "k2", time:
|
|
62
|
-
{ id: "k3", time:
|
|
55
|
+
{ id: "k1", time: 0, value: -150 },
|
|
56
|
+
{ id: "k2", time: 2.5, value: 150 },
|
|
57
|
+
{ id: "k3", time: 5, value: -150 },
|
|
63
58
|
],
|
|
64
59
|
},
|
|
65
60
|
{
|
|
@@ -67,53 +62,52 @@ const INITIAL_TRACKS: Track[] = [
|
|
|
67
62
|
label: "Opacity",
|
|
68
63
|
keyframes: [
|
|
69
64
|
{ id: "k4", time: 0, value: 100 },
|
|
70
|
-
{ id: "k5", time:
|
|
71
|
-
{ id: "k6", time:
|
|
65
|
+
{ id: "k5", time: 1.5, value: 30 },
|
|
66
|
+
{ id: "k6", time: 4, value: 100 },
|
|
72
67
|
],
|
|
73
68
|
},
|
|
74
69
|
]
|
|
75
70
|
|
|
76
71
|
export default function App() {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
72
|
+
const duration = useAnimationStore((s) => s.duration)
|
|
73
|
+
const currentTime = useAnimationStore((s) => s.currentTime)
|
|
74
|
+
const isPlaying = useAnimationStore((s) => s.isPlaying)
|
|
75
|
+
const zoomLevel = useAnimationStore((s) => s.zoomLevel)
|
|
76
|
+
const selectedKeyframeIds = useAnimationStore((s) => s.selectedKeyframeIds)
|
|
77
|
+
const selectKeyframe = useAnimationStore((s) => s.selectKeyframe)
|
|
78
|
+
const setCurrentTime = useAnimationStore((s) => s.setCurrentTime)
|
|
79
|
+
const setDuration = useAnimationStore((s) => s.setDuration)
|
|
80
|
+
const [easing, setEasing] = useState<CubicBezier>({ x1: 0.4, y1: 0, x2: 0.2, y2: 1 })
|
|
84
81
|
|
|
82
|
+
// Advance the store's clock while playing, looping at the end
|
|
85
83
|
useEffect(() => {
|
|
86
|
-
if (!
|
|
87
|
-
if (animRef.current) cancelAnimationFrame(animRef.current)
|
|
88
|
-
return
|
|
89
|
-
}
|
|
84
|
+
if (!isPlaying) return
|
|
90
85
|
|
|
91
|
-
let frame =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (animRef.current) cancelAnimationFrame(animRef.current)
|
|
86
|
+
let frame = 0
|
|
87
|
+
let last = performance.now()
|
|
88
|
+
const tick = (now: number) => {
|
|
89
|
+
const state = useAnimationStore.getState()
|
|
90
|
+
const next = state.currentTime + (now - last) / 1000
|
|
91
|
+
last = now
|
|
92
|
+
state.setCurrentTime(next >= state.duration ? 0 : next)
|
|
93
|
+
frame = requestAnimationFrame(tick)
|
|
100
94
|
}
|
|
101
|
-
|
|
95
|
+
frame = requestAnimationFrame(tick)
|
|
96
|
+
return () => cancelAnimationFrame(frame)
|
|
97
|
+
}, [isPlaying])
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
const opacity = interpolate(tracks[1].keyframes, currentFrame) / 100
|
|
99
|
+
const posX = interpolate(TRACKS[0].keyframes, currentTime, easing)
|
|
100
|
+
const opacity = interpolate(TRACKS[1].keyframes, currentTime, easing) / 100
|
|
106
101
|
|
|
107
102
|
return (
|
|
108
103
|
<div className="flex h-screen flex-col bg-background text-foreground">
|
|
109
|
-
<div className="
|
|
104
|
+
<div className="flex flex-1 overflow-hidden">
|
|
110
105
|
{/* Preview stage */}
|
|
111
106
|
<div className="flex flex-1 items-center justify-center bg-canvas">
|
|
112
107
|
<div
|
|
113
|
-
ref={boxRef}
|
|
114
108
|
className="size-20 rounded-lg bg-primary"
|
|
115
109
|
style={{
|
|
116
|
-
transform: \`translateX(\${posX
|
|
110
|
+
transform: \`translateX(\${posX}px)\`,
|
|
117
111
|
opacity,
|
|
118
112
|
}}
|
|
119
113
|
/>
|
|
@@ -130,80 +124,78 @@ export default function App() {
|
|
|
130
124
|
</Pane>
|
|
131
125
|
<Pane>
|
|
132
126
|
<PaneHeader>
|
|
133
|
-
<PaneLabel>
|
|
127
|
+
<PaneLabel>Playback</PaneLabel>
|
|
134
128
|
</PaneHeader>
|
|
135
129
|
<PaneContent>
|
|
136
130
|
<div className="grid grid-cols-2 gap-2">
|
|
137
|
-
<NumberInput
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
<NumberInput
|
|
132
|
+
label="T"
|
|
133
|
+
value={currentTime}
|
|
134
|
+
onChange={setCurrentTime}
|
|
135
|
+
min={0}
|
|
136
|
+
max={duration}
|
|
137
|
+
step={0.1}
|
|
138
|
+
suffix="s"
|
|
139
|
+
/>
|
|
140
|
+
<NumberInput
|
|
141
|
+
label="D"
|
|
142
|
+
value={duration}
|
|
143
|
+
onChange={setDuration}
|
|
144
|
+
min={1}
|
|
145
|
+
max={30}
|
|
146
|
+
step={0.5}
|
|
147
|
+
suffix="s"
|
|
148
|
+
/>
|
|
142
149
|
</div>
|
|
143
150
|
</PaneContent>
|
|
144
151
|
</Pane>
|
|
145
152
|
</Panel>
|
|
146
153
|
</div>
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
tooltip={playing ? "Pause" : "Play"}
|
|
170
|
-
shortcut="Space"
|
|
171
|
-
onPressedChange={() => setPlaying(!playing)}
|
|
172
|
-
>
|
|
173
|
-
{playing ? <RiPauseLine className="size-4" /> : <RiPlayLine className="size-4" />}
|
|
174
|
-
</ToolbarToggle>
|
|
175
|
-
<ToolbarToggle
|
|
176
|
-
value="stop"
|
|
177
|
-
tooltip="Stop"
|
|
178
|
-
onPressedChange={() => { setPlaying(false); setCurrentFrame(0) }}
|
|
179
|
-
>
|
|
180
|
-
<RiStopLine className="size-4" />
|
|
181
|
-
</ToolbarToggle>
|
|
182
|
-
</Toolbar>
|
|
155
|
+
<Timeline>
|
|
156
|
+
<TimelineRuler duration={duration} zoomLevel={zoomLevel} />
|
|
157
|
+
<div className="relative flex-1 overflow-y-auto">
|
|
158
|
+
{TRACKS.map((track) => (
|
|
159
|
+
<TimelineTrack key={track.id} label={track.label}>
|
|
160
|
+
{track.keyframes.map((keyframe) => (
|
|
161
|
+
<TimelineKeyframe
|
|
162
|
+
key={keyframe.id}
|
|
163
|
+
time={keyframe.time}
|
|
164
|
+
duration={duration}
|
|
165
|
+
zoomLevel={zoomLevel}
|
|
166
|
+
selected={selectedKeyframeIds.has(keyframe.id)}
|
|
167
|
+
onSelect={() => selectKeyframe(keyframe.id)}
|
|
168
|
+
/>
|
|
169
|
+
))}
|
|
170
|
+
</TimelineTrack>
|
|
171
|
+
))}
|
|
172
|
+
<TimelinePlayhead time={currentTime} duration={duration} zoomLevel={zoomLevel} />
|
|
173
|
+
</div>
|
|
174
|
+
<TimelineTransport />
|
|
175
|
+
</Timeline>
|
|
183
176
|
|
|
184
177
|
<StatusBar>
|
|
185
178
|
<StatusBarSection>
|
|
186
|
-
<StatusBarItem>
|
|
187
|
-
<StatusBarItem>State: {
|
|
188
|
-
</StatusBarSection>
|
|
189
|
-
<StatusBarSection>
|
|
190
|
-
<HistoryControls size="sm" />
|
|
179
|
+
<StatusBarItem>Time: {currentTime.toFixed(2)}s / {duration.toFixed(2)}s</StatusBarItem>
|
|
180
|
+
<StatusBarItem>State: {isPlaying ? "Playing" : "Stopped"}</StatusBarItem>
|
|
191
181
|
</StatusBarSection>
|
|
192
182
|
</StatusBar>
|
|
193
183
|
</div>
|
|
194
184
|
)
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
|
|
187
|
+
// Value at a given time, easing between the surrounding keyframes
|
|
188
|
+
function interpolate(keyframes: Keyframe[], time: number, easing: CubicBezier): number {
|
|
198
189
|
if (keyframes.length === 0) return 0
|
|
199
|
-
if (
|
|
200
|
-
|
|
190
|
+
if (time <= keyframes[0].time) return keyframes[0].value
|
|
191
|
+
const last = keyframes[keyframes.length - 1]
|
|
192
|
+
if (time >= last.time) return last.value
|
|
201
193
|
|
|
202
194
|
for (let i = 0; i < keyframes.length - 1; i++) {
|
|
203
195
|
const a = keyframes[i]
|
|
204
196
|
const b = keyframes[i + 1]
|
|
205
|
-
if (
|
|
206
|
-
const t = (
|
|
197
|
+
if (time >= a.time && time <= b.time) {
|
|
198
|
+
const t = evaluateCubicBezier(easing, (time - a.time) / (b.time - a.time))
|
|
207
199
|
return a.value + (b.value - a.value) * t
|
|
208
200
|
}
|
|
209
201
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.js","sourceRoot":"","sources":["../../src/surfaces/animation.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAkB;IACtC,UAAU,EAAE;QACV,
|
|
1
|
+
{"version":3,"file":"animation.js","sourceRoot":"","sources":["../../src/surfaces/animation.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAkB;IACtC,UAAU,EAAE;QACV,OAAO;QACP,MAAM;QACN,UAAU;QACV,iBAAiB;QACjB,qBAAqB;QACrB,QAAQ;QACR,cAAc;QACd,YAAY;KACb;IACD,SAAS,EAAE;QACT,IAAI,EAAE,SAAS;KAChB;IACD,GAAG,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6LZ;CACA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blank.d.ts","sourceRoot":"","sources":["../../src/surfaces/blank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"blank.d.ts","sourceRoot":"","sources":["../../src/surfaces/blank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,KAAK,EAAE,aAkFnB,CAAA"}
|
package/dist/surfaces/blank.js
CHANGED
|
@@ -12,11 +12,14 @@ import {
|
|
|
12
12
|
Toolbar,
|
|
13
13
|
ToolbarToggleGroup,
|
|
14
14
|
ToolbarToggle,
|
|
15
|
-
ToolbarSeparator,
|
|
16
15
|
} from "@/components/substrate/toolbar"
|
|
17
16
|
import { Panel } from "@/components/substrate/panel"
|
|
18
17
|
import { Surface } from "@/components/substrate/surface"
|
|
19
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
EmptyState,
|
|
20
|
+
EmptyStateTitle,
|
|
21
|
+
EmptyStateDescription,
|
|
22
|
+
} from "@/components/substrate/empty-state"
|
|
20
23
|
import { StatusBar, StatusBarSection, StatusBarItem } from "@/components/substrate/status-bar"
|
|
21
24
|
import { RiCursorLine, RiHand } from "@remixicon/react"
|
|
22
25
|
|
|
@@ -27,24 +30,33 @@ export default function App() {
|
|
|
27
30
|
<div className="flex h-screen flex-col bg-background text-foreground">
|
|
28
31
|
<div className="relative flex flex-1 overflow-hidden">
|
|
29
32
|
<Panel position="left">
|
|
30
|
-
<EmptyState
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
<EmptyState>
|
|
34
|
+
<EmptyStateTitle>Left panel</EmptyStateTitle>
|
|
35
|
+
<EmptyStateDescription>
|
|
36
|
+
Add layers, assets, or navigation here
|
|
37
|
+
</EmptyStateDescription>
|
|
38
|
+
</EmptyState>
|
|
34
39
|
</Panel>
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
{/* Surface fills its nearest positioned parent */}
|
|
42
|
+
<div className="relative flex-1">
|
|
43
|
+
<Surface>
|
|
44
|
+
<EmptyState>
|
|
45
|
+
<EmptyStateTitle>Your surface here</EmptyStateTitle>
|
|
46
|
+
<EmptyStateDescription>
|
|
47
|
+
Replace this with your rendering engine — canvas, Three.js, timeline, or anything else
|
|
48
|
+
</EmptyStateDescription>
|
|
49
|
+
</EmptyState>
|
|
50
|
+
</Surface>
|
|
51
|
+
</div>
|
|
42
52
|
|
|
43
53
|
<Panel position="right">
|
|
44
|
-
<EmptyState
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
<EmptyState>
|
|
55
|
+
<EmptyStateTitle>Right panel</EmptyStateTitle>
|
|
56
|
+
<EmptyStateDescription>
|
|
57
|
+
Add property controls and inspectors here
|
|
58
|
+
</EmptyStateDescription>
|
|
59
|
+
</EmptyState>
|
|
48
60
|
</Panel>
|
|
49
61
|
</div>
|
|
50
62
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blank.js","sourceRoot":"","sources":["../../src/surfaces/blank.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAkB;IAClC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,SAAS;QACT,aAAa;QACb,YAAY;KACb;IACD,GAAG,EAAE,GAAG,EAAE,CAAC
|
|
1
|
+
{"version":3,"file":"blank.js","sourceRoot":"","sources":["../../src/surfaces/blank.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAkB;IAClC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,SAAS;QACT,aAAa;QACb,YAAY;KACb;IACD,GAAG,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwEZ;CACA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-2d.d.ts","sourceRoot":"","sources":["../../src/surfaces/canvas-2d.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"canvas-2d.d.ts","sourceRoot":"","sources":["../../src/surfaces/canvas-2d.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,QAAQ,EAAE,aAmatB,CAAA"}
|