Skip to content

Live theme + animation + language preview site (and the FX theming bugs it found) - #29

Open
kienbb wants to merge 6 commits into
lewislulu:mainfrom
kienbb:feat/preview-site
Open

Live theme + animation + language preview site (and the FX theming bugs it found)#29
kienbb wants to merge 6 commits into
lewislulu:mainfrom
kienbb:feat/preview-site

Conversation

@kienbb

@kienbb kienbb commented Jul 16, 2026

Copy link
Copy Markdown

What this adds

A live preview site at preview/, published via GitHub Pages, where every theme
and 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 effect
travel to it over postMessage, so nothing reloads when you switch. That was
mostly free: runtime.js already spoke this protocol for presenter mode
(?preview=N + preview-goto / preview-theme), so this reuses it rather than
inventing 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.

  1. Canvas FX never followed the theme. _util.js built its palette from
    --ok and --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 and
    red. Worse, nine modules washed their motion trails with hard-coded black
    invisible on a dark deck, but it fogs minimal-white to solid black in about
    a 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 old
    palette forever. Fixed at the root: the palette is now the theme's decorative
    triad --accent/-2/-3, U.fade() washes with --bg, and fx-runtime rebuilds
    effects on the theme link's load.

  2. .counter never ran in preview mode. The count-up lived past the early
    return preview mode takes, so demo-deck slide 4 sat on a permanent 0% — in
    the preview site and in presenter mode, which renders its CURRENT/NEXT cards
    through the same path.

  3. data-theme-base was only read from <html> though decks here declare it
    on <body>; it survived on a fallback.

On the palette change

U.palette returns 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
--bad painted red dots across a deck whose whole point is greyscale.

This is a contract change. Any custom FX indexing pal[3] will break;
orbit-ring was the only one in-tree and now uses pal[i % pal.length] like its
siblings. Documented in references/animations.md. Happy to revert to a
5-colour pool if you'd rather keep the status colours.

matrix-rain keeps its green deliberately — themed matrix rain isn't matrix
rain — and is now commented as such so it doesn't read as a miss.

Also included

  • assets/i18n.js — optional data-i18n layer, off unless a deck opts in.
    The default language stays inline, so a deck still reads correctly over
    file:// where fetch is blocked; only switching away needs a server.
    examples/demo-deck is now zh + en + vi, which is what makes the preview able
    to show a theme under ideographic vs Latin text.
  • .nojekyll — mandatory. Jekyll drops files starting with _, which would
    silently break all 20 canvas FX via fx/_util.js. The workflow fails the build
    if it goes missing.
  • Demo-deck's cover said "24 themes · 30 layouts · 25 animations"; corrected to
    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.html as well as the preview; the counter climbs
through 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

kienntpixon and others added 6 commits July 15, 2026 12:27
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants