kasuti: stage 3 replay — client-side GIF export

Adds a "Save GIF" button to the replay panel. On click, the component
renders each step of the replay to an offscreen canvas (both fabric
panels side-by-side, FRONT/BACK labels, active-side border, dashed
pattern guides for undrawn edges, drawn stitches in thread colour,
dashed start marker, red current-vertex dot) and feeds the frames
through gifenc — dynamically imported so the ~8 KB chunk only loads
when the user actually exports.

Frame timing: 200 ms between stitches, 900 ms hold on the first and
last frames. Palette is quantised to 64 colours per frame, which keeps
a Snowflake tour (~53 frames at 684×376) around 150–250 KB. The
button flips to "Encoding…" while busy and a sonner toast reports the
file size on success or the error on failure.

https://claude.ai/code/session_01KNdXjQczMCRGMK7K3Qderw
This commit is contained in:
Claude 2026-04-22 11:40:21 +00:00
parent bf20155423
commit bd4f073cac
No known key found for this signature in database
3 changed files with 200 additions and 0 deletions

7
package-lock.json generated
View file

@ -29,6 +29,7 @@
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^11.18.2",
"gifenc": "^1.0.3",
"gray-matter": "^4.0.3",
"input-otp": "^1.4.2",
"katex": "^0.16.45",
@ -12136,6 +12137,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gifenc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
"integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==",
"license": "MIT"
},
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",