Skip to content

Commit 84c2599

Browse files
Add GPU adapter preview examples
Provide runnable Three.js and vgpu demos for validating the new animate and effect APIs in Motion's shared render loop. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7b0ac88 commit 84c2599

11 files changed

Lines changed: 826 additions & 2 deletions

‎dev/html/main.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
import "./style.css"
22

3-
document.querySelector("#app").innerHTML = ``
3+
document.querySelector("#app").innerHTML = `
4+
<h1>Motion playground</h1>
5+
<p>GPU adapter previews</p>
6+
<div class="card">
7+
<a href="/three-animate.html">Three.js animate()</a>
8+
<a href="/three-effects.html">Three.js effects</a>
9+
<a href="/vgpu.html">vgpu uniforms</a>
10+
</div>
11+
`

‎dev/html/package.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"motion": "^13.1.1",
1515
"motion-dom": "^13.1.1",
1616
"react": "^18.3.1",
17-
"react-dom": "^18.3.1"
17+
"react-dom": "^18.3.1",
18+
"three": "^0.185.1",
19+
"vgpu": "^0.3.1"
1820
},
1921
"devDependencies": {
2022
"vite": "^6.4.2"

‎dev/html/src/gpu-adapters.css‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
:root {
2+
color: #f5f5f7;
3+
background: #09090b;
4+
font-family: Inter, ui-sans-serif, system-ui, -apple-system,
5+
BlinkMacSystemFont, "Segoe UI", sans-serif;
6+
}
7+
8+
* {
9+
box-sizing: border-box;
10+
}
11+
12+
body {
13+
min-height: 100vh;
14+
margin: 0;
15+
}
16+
17+
main {
18+
display: grid;
19+
min-height: 100vh;
20+
grid-template-rows: auto 1fr;
21+
}
22+
23+
header {
24+
display: flex;
25+
align-items: center;
26+
justify-content: space-between;
27+
gap: 24px;
28+
padding: 20px 24px;
29+
border-bottom: 1px solid #27272a;
30+
background: rgba(9, 9, 11, 0.9);
31+
}
32+
33+
h1 {
34+
margin: 0 0 5px;
35+
font-size: 18px;
36+
}
37+
38+
p {
39+
margin: 0;
40+
color: #a1a1aa;
41+
font-size: 13px;
42+
}
43+
44+
nav,
45+
.controls {
46+
display: flex;
47+
align-items: center;
48+
gap: 8px;
49+
flex-wrap: wrap;
50+
}
51+
52+
a,
53+
button {
54+
border: 1px solid #3f3f46;
55+
border-radius: 8px;
56+
background: #18181b;
57+
color: #fafafa;
58+
font: inherit;
59+
font-size: 13px;
60+
text-decoration: none;
61+
}
62+
63+
a {
64+
padding: 7px 10px;
65+
}
66+
67+
button {
68+
padding: 9px 13px;
69+
cursor: pointer;
70+
}
71+
72+
a:hover,
73+
button:hover {
74+
border-color: #71717a;
75+
background: #27272a;
76+
}
77+
78+
button.primary {
79+
border-color: #7c3aed;
80+
background: #6d28d9;
81+
}
82+
83+
.stage {
84+
position: relative;
85+
min-height: 0;
86+
}
87+
88+
canvas {
89+
display: block;
90+
width: 100%;
91+
height: 100%;
92+
}
93+
94+
.panel {
95+
position: absolute;
96+
z-index: 1;
97+
right: 20px;
98+
bottom: 20px;
99+
left: 20px;
100+
display: flex;
101+
align-items: center;
102+
justify-content: space-between;
103+
gap: 16px;
104+
padding: 12px;
105+
border: 1px solid rgba(255, 255, 255, 0.12);
106+
border-radius: 12px;
107+
background: rgba(9, 9, 11, 0.78);
108+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
109+
backdrop-filter: blur(16px);
110+
}
111+
112+
label {
113+
display: flex;
114+
align-items: center;
115+
gap: 8px;
116+
color: #d4d4d8;
117+
font-size: 12px;
118+
}
119+
120+
input[type="range"] {
121+
width: 140px;
122+
accent-color: #8b5cf6;
123+
}
124+
125+
output,
126+
code {
127+
color: #c4b5fd;
128+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
129+
font-size: 12px;
130+
}
131+
132+
.error {
133+
display: grid;
134+
min-height: 100%;
135+
place-items: center;
136+
padding: 30px;
137+
color: #fca5a5;
138+
text-align: center;
139+
}
140+
141+
@media (max-width: 720px) {
142+
header,
143+
.panel {
144+
align-items: flex-start;
145+
flex-direction: column;
146+
}
147+
148+
nav {
149+
display: none;
150+
}
151+
}

‎dev/html/src/three-animate.js‎

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { cancelFrame, frame } from "motion"
2+
import { animate } from "motion/three"
3+
import * as THREE from "three"
4+
import "./gpu-adapters.css"
5+
6+
const canvas = document.querySelector("canvas")
7+
const status = document.querySelector("#status")
8+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
9+
const scene = new THREE.Scene()
10+
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100)
11+
12+
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
13+
renderer.setClearColor("#09090b")
14+
camera.position.set(0, 0, 7)
15+
16+
scene.add(new THREE.HemisphereLight("#c4b5fd", "#111827", 3))
17+
18+
const meshMaterial = new THREE.MeshStandardMaterial({
19+
color: "#8b5cf6",
20+
metalness: 0.25,
21+
roughness: 0.2,
22+
transparent: true,
23+
})
24+
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1, 2), meshMaterial)
25+
mesh.position.x = -1.5
26+
scene.add(mesh)
27+
28+
const uniforms = {
29+
progress: { value: 0 },
30+
tint: { value: new THREE.Color("#22d3ee") },
31+
}
32+
const shaderMaterial = new THREE.ShaderMaterial({
33+
uniforms,
34+
transparent: true,
35+
vertexShader: `
36+
varying vec2 vUv;
37+
38+
void main() {
39+
vUv = uv;
40+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
41+
}
42+
`,
43+
fragmentShader: `
44+
varying vec2 vUv;
45+
uniform float progress;
46+
uniform vec3 tint;
47+
48+
void main() {
49+
float edge = smoothstep(progress - 0.18, progress + 0.18, vUv.x);
50+
vec3 color = mix(tint, vec3(0.12, 0.05, 0.32), edge);
51+
gl_FragColor = vec4(color, 1.0);
52+
}
53+
`,
54+
})
55+
const uniformMesh = new THREE.Mesh(
56+
new THREE.PlaneGeometry(1.8, 1.8, 24, 24),
57+
shaderMaterial
58+
)
59+
uniformMesh.position.x = 1.45
60+
scene.add(uniformMesh)
61+
62+
let meshActive = false
63+
let uniformActive = false
64+
65+
document.querySelector("#move").addEventListener("click", () => {
66+
meshActive = !meshActive
67+
status.textContent = "Animating object and material properties"
68+
69+
animate(
70+
mesh,
71+
meshActive
72+
? {
73+
x: -0.8,
74+
rotateX: 35,
75+
rotateY: 180,
76+
scale: 1.25,
77+
color: "#f43f5e",
78+
opacity: 0.65,
79+
}
80+
: {
81+
x: -1.5,
82+
rotateX: 0,
83+
rotateY: 0,
84+
scale: 1,
85+
color: "#8b5cf6",
86+
opacity: 1,
87+
},
88+
{ type: "spring", visualDuration: 0.7, bounce: 0.25 }
89+
).then(() => {
90+
status.textContent = "Object animation complete"
91+
})
92+
})
93+
94+
document.querySelector("#uniform").addEventListener("click", () => {
95+
uniformActive = !uniformActive
96+
status.textContent = "Animating shader uniforms"
97+
98+
animate(
99+
uniforms,
100+
{
101+
progress: uniformActive ? 1 : 0,
102+
tint: uniformActive ? "#fbbf24" : "#22d3ee",
103+
},
104+
{ duration: 1, ease: "easeInOut" }
105+
).then(() => {
106+
status.textContent = "Uniform animation complete"
107+
})
108+
})
109+
110+
document.querySelector("#reset").addEventListener("click", () => {
111+
meshActive = true
112+
uniformActive = true
113+
document.querySelector("#move").click()
114+
document.querySelector("#uniform").click()
115+
})
116+
117+
function resize() {
118+
const { clientWidth, clientHeight } = canvas
119+
renderer.setSize(clientWidth, clientHeight, false)
120+
camera.aspect = clientWidth / clientHeight
121+
camera.updateProjectionMatrix()
122+
}
123+
124+
function render() {
125+
renderer.render(scene, camera)
126+
}
127+
128+
resize()
129+
new ResizeObserver(resize).observe(canvas)
130+
frame.render(render, true)
131+
132+
window.addEventListener("pagehide", () => {
133+
cancelFrame(render)
134+
mesh.geometry.dispose()
135+
uniformMesh.geometry.dispose()
136+
meshMaterial.dispose()
137+
shaderMaterial.dispose()
138+
renderer.dispose()
139+
})

0 commit comments

Comments
 (0)