Live theme + animation + language preview site (and the FX theming bugs it found) - #29
Open
kienbb wants to merge 6 commits into
Open
Live theme + animation + language preview site (and the FX theming bugs it found)#29kienbb wants to merge 6 commits into
kienbb wants to merge 6 commits into
Conversation
…ub Pages Adds preview/, a control panel for reviewing every theme and animation on a real slide without installing the skill. Published to GitHub Pages via a workflow that ships the repo as-is (no build step). The slide is a live iframe of examples/demo-deck. Theme, language, slide and animation all travel over postMessage to the runtime already inside it, so nothing reloads — runtime.js already spoke most of this protocol for presenter mode, so this mostly reuses it rather than inventing a parallel one. - preview/: control panel (36-theme rail with real palette swatches, language segmented control, slide dots, effect picker), plus anim-stage.html, a rig that hosts all 47 animations. Each animation is mapped to a subject that actually shows it off — a generic text block can't demo .anim-typewriter (needs one nowrap line), .anim-stagger-list (animates children) or .anim-path-draw (needs SVG strokes). - assets/i18n.js: optional data-i18n layer. The default language stays inline, so a deck opened over file:// — where fetch() is blocked — still reads correctly; only switching away needs a server. - examples/demo-deck: marked up with data-i18n and translated to en + vi. Its Chart.js canvas now redraws on theme change (a canvas can't re-skin itself from CSS variables) and re-labels on language change. - runtime.js: adds preview-replay, and accepts data-theme-base on <body> as well as <html> — decks in this repo declare it on <body>, which the previous lookup missed and only survived via the theme-link fallback. - .nojekyll: mandatory. Jekyll drops files starting with an underscore, which would silently break all 20 canvas FX via fx/_util.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slide 4 of demo-deck has no anim-* class at all — its only motion is the .counter element counting 0 -> 92. That count is JS-driven, and the code doing it sat inside go(), which is on the normal navigation path, well past the early return that preview mode takes. So in preview mode the counter never ran and the slide sat on a permanent "0%". This predates the preview site: presenter mode (S) renders its CURRENT / NEXT cards through the same ?preview=N path, so the presenter has always been shown "0%" for that slide while the audience saw it count up. Hoists the count-up into a shared animateCounters(slide) used by both paths rather than copying it, and calls it from preview mode on load, on preview-goto and on preview-replay. Verified it climbs through intermediate values (0/37/62/78/87/91/92) rather than snapping, in preview mode, on replay, on revisit, and unchanged in normal mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three bugs, one symptom: canvas FX did not look like the theme they ran on. 1. Dead tokens. _util.js built its palette from --ok and --danger, which are defined in exactly zero files — every theme names them --good and --bad. So two of five palette slots silently fell back to a hard-coded green and red, in all 11 modules that use U.palette. 2. The palette was the wrong idea. Rather than repoint those names, drop the status triad entirely: --good/--warn/--bad carry meaning (charts, diffs, pros/cons) and mean nothing in a particle burst, while actively fighting restrained themes — minimal-white's accents are three greys, so borrowing its --bad painted red dots over a deck whose whole point is greyscale. The pool is now the theme's decorative triad, --accent/-2/-3, which is what themes design to sit together (most build --grad from exactly these). --accent-3 had never been used by any FX. 3. Theme swaps never re-initialised anything. A canvas samples its colors once at init and cannot re-skin itself the way a CSS rule does, and nothing was watching — so pressing T on any slide with a [data-fx] left the effect painting the previous theme's palette forever. fx-runtime now rebuilds active effects on the theme <link>'s load event, which is the moment the new variables become readable. Supporting changes: - fx-runtime is now the single owner of every [data-fx] lifecycle (__hpxStop / __hpxInit exported); preview/anim-stage.html no longer calls HPX[name] itself. Two creators on one host left an untracked second canvas whose stale colors were what actually showed. - stopFxIn snapshots the host's children at init so it can remove module-injected nodes without touching deck-author markup. Several modules append DOM and never remove it in stop(), which re-init would otherwise stack up. - orbit-ring: index the palette with pal[i % pal.length] like every sibling module instead of hard-coding pal[0..4], and stop hard-coding a violet glow and a #fff core dot that was invisible on every light theme. - U.alpha() to re-alpha a theme color for glows/trails. Verified by pixel-sampling the canvas across themes: cyberpunk-neon paints #ff2bd6/#00f0ff/#f9f871, terminal-green #00ff88/#67ffd0/#b6ff6b, tokyo-night #7aa2f7/#bb9af7/#7dcfff, minimal-white nothing saturated. All 20 FX single instance, no leaks, no errors; confirmed on templates/animation-showcase.html, not only the preview site. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
replayAnims() forced a synchronous layout per animated element — remove class, read offsetWidth, re-add, per node — after walking every element on the slide to find them. A single forced layout already flushes the whole document, so the reflow is now taken once, between stripping all the classes and restoring them, and [class*="anim-"] pre-filters the walk (the exact prefix test still decides, since that selector also matches e.g. "no-anim-x"). preview/app.js: - Extract paintStage() / repaintAll(); which controls belong to a stage was spelled out three times, in setStage, applyHash and boot. - applyHash no longer shoves state back to its previous value just to get past setStage's early-return; it repaints and remounts from the state readHash has already produced. - Cache the two stage nodes: a ResizeObserver fires continuously while a window is dragged and fitStage was re-querying both on every frame. Design resolution is now DESIGN_W/DESIGN_H rather than 1280/720 inline four times. - THEME_IDS / THEME_BY_ID built once in buildThemeRail. Holding T rebuilt a 36-element id array per repeat and paintStatus linear-scanned for the label. Verified: T walks all 36 themes and wraps exactly once with rail, status bar and iframe in sync; stagger-list still replays (opacity 1 -> 0 -> 1) and keeps its class; slide 4's counter still rewinds and recounts; all four hash transitions (boot, ->anim, ->theme, ->theme again) and both stage tabs still resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to 60b0bbf, which added the theme-following helpers and then applied them to exactly one of twenty modules. The claim in that commit message — "canvas effects actually follow the theme" — was not true when it landed. Nine modules build motion trails by painting a translucent rect over the canvas each frame instead of clearing it, and every one of them hard-coded that rect to black (or a near-black tint). On a dark deck this is invisible and looks deliberate. On minimal-white it fogs a white slide to *solid black* in about a second, headline and all — a far worse theme violation than the violet glow that did get fixed. I surveyed these last time, saw rgba(0,0,0,.25), and waved it through as "a legitimate trail overlay" without once looking at it on a light theme. - U.fade(el, a) washes with the theme's --bg; U.bg(el) exposes the token. Applied to starfield, firework, galaxy-swirl, sparkle-trail, magnetic-field, matrix-rain, shockwave, data-stream, gradient-blob. - Hard-coded accents replaced where the theme variable was already in scope and simply unused: knowledge-graph, neural-net, chain-react, shockwave, counter-explosion. No literal colors remain in any module except matrix-rain's greens, which are the reference itself and are now commented as deliberate. Also fixed, all found by review of the preview site itself: - "Open in new tab" always opened the deck on its own hard-coded aurora: theme travelled only over postMessage. runtime.js preview mode now reads ?theme= (charset-restricted — it is concatenated into a stylesheet path), so a deck preview URL is self-contained and shareable. - Switching language restarted any running canvas effect, because render() was passed straight to addEventListener and the Event object landed in its new `force` parameter — an Event is truthy, so the guard never held. Wrapped the listener. Language switches now leave the effect running; replay and theme swaps still restart it, as they must. - writeHash ran replaceState on every keypress. Firefox rate-limits the History API to ~200 calls/10s and key autorepeat reaches that in seconds, after which the URL silently stops matching the screen. Coalesced to one write per frame: 40 keypresses now produce 1 call. - SLIDE_COUNT was hard-coded to 8; the count is now read from the deck on load, so a 9th slide can't end up with no dot and unreachable by arrow. - anim-stage's counter is a knowing twin of runtime's animateCounters (the rig deliberately doesn't load runtime.js — a second owner on one slide is the bug class this whole series has been about). Aligned to runtime's contract and documented rather than left silently drifted at 1400ms. - Documented why i18n.js accepts postMessage without an origin check. Verified by pixel-sampling a canvas corner across all 9 washers on a light and a dark theme: the wash converges on #ffffff under minimal-white and #06091c under aurora; zero light themes fog to black, zero dark themes wash to white. All 47 effects mount with one handle and one canvas, no leaks, no JS errors; all 8 slides reachable; dots match the deck's real slide count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview site was developed on a fork, so the README/SKILL links and the GitHub links on the landing and preview pages named that fork. On this branch they name this repository instead — the URL they describe only exists once this is merged and Pages is switched on here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A live preview site at
preview/, published via GitHub Pages, where every themeand animation can be reviewed on a real slide without installing the skill —
and the runtime fixes that reviewing them turned up.
The slide is an iframe of
examples/demo-deck. Theme, language, slide and effecttravel to it over
postMessage, so nothing reloads when you switch. That wasmostly free:
runtime.jsalready spoke this protocol for presenter mode(
?preview=N+preview-goto/preview-theme), so this reuses it rather thaninventing a parallel one.
Try it: the site is the fastest way to answer "which theme should I use?" —
a question SKILL.md currently answers in prose. A link can carry an exact state:
preview/#stage=theme&theme=tokyo-night&lang=en&slide=6.Three runtime bugs it exposed
These are independent of the preview site — they affect any deck.
Canvas FX never followed the theme.
_util.jsbuilt its palette from--okand--danger, which are defined in zero files (themes name them--good/--bad), so 2 of 5 slots always fell back to a hard-coded green andred. Worse, nine modules washed their motion trails with hard-coded black —
invisible on a dark deck, but it fogs
minimal-whiteto solid black in abouta second, headline and all. And nothing re-initialised an effect on a theme
swap, so pressing T on a
[data-fx]slide left it painting the oldpalette forever. Fixed at the root: the palette is now the theme's decorative
triad
--accent/-2/-3,U.fade()washes with--bg, andfx-runtimerebuildseffects on the theme link's
load..counternever ran in preview mode. The count-up lived past the earlyreturn preview mode takes, so demo-deck slide 4 sat on a permanent
0%— inthe preview site and in presenter mode, which renders its CURRENT/NEXT cards
through the same path.
data-theme-basewas only read from<html>though decks here declare iton
<body>; it survived on a fallback.On the palette change
U.palettereturns 3 colors instead of 5, and no longer includes--good/--warn/--bad. Those are a semantic triad (charts, diffs,pros/cons); green/amber/red mean nothing in a confetti cannon and actively fight
restrained themes —
minimal-white's accents are three greys, so borrowing its--badpainted red dots across a deck whose whole point is greyscale.This is a contract change. Any custom FX indexing
pal[3]will break;orbit-ringwas the only one in-tree and now usespal[i % pal.length]like itssiblings. Documented in
references/animations.md. Happy to revert to a5-colour pool if you'd rather keep the status colours.
matrix-rainkeeps its green deliberately — themed matrix rain isn't matrixrain — and is now commented as such so it doesn't read as a miss.
Also included
assets/i18n.js— optionaldata-i18nlayer, off unless a deck opts in.The default language stays inline, so a deck still reads correctly over
file://wherefetchis blocked; only switching away needs a server.examples/demo-deckis now zh + en + vi, which is what makes the preview ableto show a theme under ideographic vs Latin text.
.nojekyll— mandatory. Jekyll drops files starting with_, which wouldsilently break all 20 canvas FX via
fx/_util.js. The workflow fails the buildif it goes missing.
36/31/27.
To publish after merging
Settings → Pages → Source: GitHub Actions. The workflow ships the repo as-is
— no build step. Until then the README links 404; say the word and I'll drop them
from this PR.
Verification
Behaviour was checked by driving a real browser and measuring, not by eye:
canvas pixels sampled per theme (cyberpunk paints
#ff2bd6/#00f0ff/#f9f871,minimal-white nothing saturated, the trail wash converges on each theme's
--bg);all 47 effects mount with one handle and one canvas, no leaks, no JS errors, on
templates/animation-showcase.htmlas well as the preview; the counter climbsthrough intermediate values rather than snapping; all 8 slides reachable.
Vietnamese was included partly as a typographic stress test — stacked diacritics
at 180px are the hardest thing to render in this system. Nothing clipped in any
of the 36 themes.
🤖 Generated with Claude Code