Commit c9f98b2
cards demo: fall back to a flat card when WebGL is unavailable (#1075)
## Why
A visitor whose browser can't create a WebGL context got Next's
"Application error: a client-side exception has occurred" in place of
the whole cards playground at `docs.lightspark.com/cards/demo`. The
console showed `THREE.WebGLRenderer: A WebGL context could not be
created ... GL_VENDOR = Disabled`. This happens when hardware
acceleration is off, the GPU is blocklisted, or a managed machine
disables WebGL, and Chrome no longer falls back to a software renderer.
The 3D card threw during render and nothing caught it, so React
unmounted the page.
## What changed
- `CardStageBoundary` wraps the 3D stage on the playground (`AppPanel`)
and the share page (`ShareCard`). When the 3D card throws, only the card
is replaced; the configure panel, phone, and API panel stay up.
- `FlatCard` shows the card's faces painted by the same `paintFront` and
`paintBack` the 3D mesh uses, as two canvases turned in CSS 3D. Upright
cards are turned back from the texture's quarter turn.
- `FlatCardStage` runs the flat card on the same `CardMotion` (drag,
fling, tilt, idle bob, decline shake), the same intro blueprint, the
flight into the phone's card slot, and the flight into the share frame.
- `FlatExporter` implements a new `CardFrameSource` interface (the part
of `CardExporter` the share code uses). It projects the faces through
the 3D exporter's camera with a mesh of affine triangle draws on a 2D
canvas, so share stills, all four poses, the hand treatment, and the
spin video render without WebGL.
- `StageGL` draws the same dot lattice (`dotLayout`) on a 2D canvas when
it can't get a WebGL context. There is no ripple or phone-glass
refraction in that case.
- The rest placement, phone-slot flight, screen clip, and share-frame
flight moved from `CardRig` into `stagePlacement.ts` so both stages use
one copy. The math is unchanged.
## Risk to the WebGL experience
The 3D path runs the same code as before plus an idle error boundary.
There is no WebGL probe on load; the boundary alone detects the failure.
The `CardRig` frame loop was compared line by line with the old inline
code. First Load JS for `/` goes from 568 kB to 573 kB and for
`/c/[slug]` from 377 kB to 380 kB.
One behavior change: any error thrown inside the 3D stage now swaps in
the flat card instead of showing the error page. The error is still
logged with `console.error`.
Duplicate code between `FlatCardStage`, `CardStage`, and `ShareCard`
(the per-frame placement, the intro clock, a few effects) is left for a
follow-up so this PR doesn't rework the 3D frame loop further.
## Test plan
- Chrome with `--disable-gpu --disable-software-rasterizer`, against the
dev server and embedded in local Mintlify: the playground loads with the
flat card, the intro plays, the card drags and flings, presets and
uploaded art repaint it, portrait works, and Issue card, Reveal, and
Lock fly it into the phone.
- The same Chrome: the share sheet opens with the card in its frame;
square, post, and hand stills render; Angled, Turned, and Back poses
render with card art; the spin video encodes (5 s, about 6 s to render).
- The same Chrome: `/c/acme-bank` shows the flat card with the dot grid,
intro, and drag.
- Chrome with the GPU on: the 3D intro, drag, flows into the phone,
share sheet, 3D stills, and the share page behave as before.
- Chrome with the GPU on, with `getContext` returning null for the
renderer's context only: the boundary swaps in the flat card and the
page stays up.
- `tsc --noEmit` reports no new errors, and `next build` passes.
- Not yet tested: Safari and iOS Safari, with or without WebGL.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent d202e24 commit c9f98b2
21 files changed
Lines changed: 1229 additions & 229 deletions
File tree
- components/grid-cards-demo/src
- app
- components
- AppPanel
- CardStage
- card3d
- export
- glass-gl
Lines changed: 90 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
34 | 37 | | |
35 | 38 | | |
36 | 39 | | |
| |||
92 | 95 | | |
93 | 96 | | |
94 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
95 | 103 | | |
96 | 104 | | |
97 | 105 | | |
| |||
220 | 228 | | |
221 | 229 | | |
222 | 230 | | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
223 | 279 | | |
224 | 280 | | |
225 | 281 | | |
| |||
246 | 302 | | |
247 | 303 | | |
248 | 304 | | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
272 | 332 | | |
273 | | - | |
| 333 | + | |
274 | 334 | | |
275 | 335 | | |
276 | 336 | | |
277 | 337 | | |
278 | 338 | | |
279 | 339 | | |
280 | 340 | | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
281 | 346 | | |
282 | 347 | | |
283 | 348 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
Lines changed: 32 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
40 | | - | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
| |||
83 | 85 | | |
84 | 86 | | |
85 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
86 | 105 | | |
87 | 106 | | |
88 | 107 | | |
| |||
136 | 155 | | |
137 | 156 | | |
138 | 157 | | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
147 | 168 | | |
148 | 169 | | |
149 | 170 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
456 | 456 | | |
457 | 457 | | |
458 | 458 | | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
0 commit comments