DOM ENGINEV0.21.3FIZLET

0.21.3 RELEASE NOTES · LATEST

Fizlet 0.21.3

By Chris Cooley ↗ · · CHANGELOG.md ↗

  • Site: the origin hero, the owner's notes (pending owner review).
    • The open laptop reads from above: the lid is as wide as the base at the hinge, foreshortened, with a hinge bar.
    • He is athletic: broad shoulders, a V-shaped back, and arms with deltoid, biceps and forearm shapes that taper to the wrists. The head and headphones are unchanged.
    • Typing: each hand types on random keys from a seeded generator. Each key lights up in the look's accent and fades. In design mode the right hand goes to the trackpad.
    • New buttons: tap the plant (a can pours, drops fade, it perks up), the notebook (he writes code or draws a doodle) or the lamp (an independent light switch).
    • The phone plays country, rock or pop, each a one-bar loop on the audio clock. A tap changes the style at the next bar line; a double-tap pauses.
    • Every new part takes Enter and Space and is announced. Under reduced motion it changes state with no animation.
    • Live chunk 28,156 → 32,748 B gzip; inline poster 5,120 → 5,987 B. Test: tests/origin.mjs.
  • @fzlt/core/sound: play(cue, intensity, at?) and time. at plays a cue at that time on the audio clock, and time reads the clock (-1 before the first gesture). A loop can schedule ahead and never drift. Additive: every existing call plays as before. 1,042 → 1,062 B gzip.
  • Bricks: "Shipped in" checked against every game's latest release tag (pending owner review). Each brick's used list in site/bricks.mjs now names what really ships it, read from the games' vendored @fzlt/core imports at their tags: hud and match (Sherman Bytes 1.1.0's skirmish scoreboard), touch (the arcade games, the DOM Battlefield and Blitz sites, fizlet.com), timeline (fizlet.com, the DOM VIRUS site), collide (board-page's text grid), fx, sprite and slot (the DOM Battlefield site's hero battle), layout and view-canvas (the bricks index's brick graph). Swarm Protocol is no longer counted under Board UI (it doesn't import it), board-page credits Byte Runner and Sherman Bytes through @fzlt/core/arcade, and game versions are the latest tags, named once (GAME). No status changes: the BETA bricks that now ship say what COMPLETE still asks. The README and home page say Sherman Bytes 1.1, and DOM Battlefield is "on Fizlet since 1.0".
  • Site: one head pattern, the home section heads' spacing everywhere (pending owner review). An index page's title (Docs, Guides, Daily, the archive, Contest, News, Notes, People, Make and the redirect stubs) sat flush on its eyebrow and its lead at 32 px: the section head's spacing only reached h2s, and h1 { margin: 0 } won. The head is now one shared rule on tokens (--head-gap 16 px eyebrow → title, --head-lead-gap 18 px title → lead, --head-size for a section head's title); brick, note, doc, legal, brand, quick start and people heads take the same tokens (brick pages 14 → 16 px, people 12 → 16 px). A site test checks the gaps on a sample of every head at 1440 and 390.
  • Bricks: the status sticker sits at the card's top-right (pending owner review). On the brick cards (home 02 and /bricks/, the roadmap cards too) the status sticker moves from beside the symbol tile to the far end of the tile's row: the card's top-right corner, inside the border and padding, where the kernel's KERNEL slab sits. The per-brick tilt and nudge, the BETA peel and the kernel's single KERNEL sticker are unchanged. On small phones (≤ 420 px) the two-up home cards keep the sticker beside the tile; /bricks/ cards keep the right. The quick view, the brick page eyebrow, the status key and the periodic tiles are unchanged (no spare room there). Test: tests/brick-cards.mjs, the sticker's right edge within the card's padding of its right edge at 1440 and 1024.
  • Site: the roadmap is up to date (pending owner review). Section 09 now shows what shipped since 2026-10-31, each tick backed by a merged PR or a release tag. E9 On npm is DONE (@fzlt/core 0.21.2 from the release workflow with provenance, and the API snapshot test). E10 Diagrams is IN PROGRESS (layout and view-canvas in 0.21.0, and the live brick graph that settles at 0 frames). E3, E4, E7 and E8 gain ticks or items: Battlefield Blitz 1.0, no shared/ copies, presence lobby and resume, Blitz 2 to 4 players, DOM Crossfire squad co-op, the Battlefield landing hero and the DOM VIRUS labs and scenes on Fizlet, touch, and Sherman Bytes on the skirmish preset. Every item now has a stable key (E9.npm). A PR that finishes one says so with a Roadmap: <key> line (CONTRIBUTING.md and the PR template). Phases after the launch scope may now move off PLANNED when a box is ticked (tests/roadmap.test.mjs).
  • Site: the home hero says "Small footprint. Big performance.", the bricks keep "Logic & style.", and the container grows on big screens (pending owner review). Home and /bricks/ never share a headline (owner): HOME_HEADLINE in site/layout.mjs is the home hero's bigger-picture line about size and speed, sized so each phrase holds one line from 320 to 1920 px; HEADLINE ("Logic & style. / The bricks do the rest.", the "&" in the command ink, a step lighter) is the /bricks/ hero's and fzlt.dev's. The container token (--page) is 1200 px up to a 1277 px window, then 94vw, capped at 1520 px (1354 px at 1440, 1520 px at 1920); the hero's copy column takes 1.15 parts to the layers card's 1 (was 1 to 1.05). Prose keeps a 70ch measure where it ran full width (gallery prompts, doc recipes, brand captions). Test: the hero headline block in tests/site.mjs.
  • Release train 0.21.3 (#206 + #212, with #194, #215, #213 and #226's CueName fix), measured together on main. gz, main → train: kernel 3,413 → 3,372 B, page build 7,128 → 7,094 B (10 B under its 7,104 budget), perf 2,064 → 2,019 B (budget 2,064 → 2,022), grid 1,603 → 1,652 B (budget 1,637 → 1,664), fx 2,797 → 2,792 B, slot 1,819 → 1,849 B, timeline 2,120 → 2,137 B, presence 2,224 → 2,197 B, presence/check 4,994 → 4,870 B, arcade 24,327 → 24,143 B (budget 24,399 → 24,150), arcade card 18,334 → 18,192 B (budget 18,361 → 18,201: #206's 18,179 plus #218's +22 B), board-page 4,136 → 4,103 B, css-clock 5,368 B (budget 5,632 → 5,566). The per-PR numbers below are each PR on its own base.
  • Size (S8, size/treeshake; supersedes #205): the page build has 154 B of headroom under its 7 KB cap. No behaviour change. esbuild keeps a top-level const as a variable in any file with imports (and a local one declared after other code), so the numbers the engine and the bricks index with now ship as literals named in a comment (2 /* STEP */): the engine's slots, pause bits and phases, and the slots and kinds in hud, match, pad, perf, presence, camera, board-page and arcade (exported values unchanged). EventRing and Pool private fields are one letter (s, f: the API snapshot shows them, the public API is unchanged), the ledger entry's flags are hs/hc, and fx's shape numbers come before TAU. Measured gz: kernel 3,413 → 3,332 B, page build 7,128 → 7,014 B, arcade 24,280 → 24,031 B, arcade card 18,295 → 18,113 B, presence/check −176 B, camera −69 B. Budgets lowered to lock it in: page build 7,168 → 7,104, arcade 24,399 → 24,150, arcade card 18,361 → 18,179, css-clock 5,632 → 5,566, board-page 4,193 → 4,160, perf 2,048 → 2,022, pad 2,126 → 2,120.
  • src/ is erasable TypeScript, enforced. tsconfig.json sets erasableSyntaxOnly and tests/erasable.test.mjs runs every source file through Node's type stripper, so a const enum, an enum, a namespace or a parameter property fails CI instead of breaking games that vendor src/ (byte-runner, null-invaders, sherman-bytes). @fzlt/core/perf's FrameRing had a parameter property; cap is now a plain field (same API). The site's own tsconfig turns the flag off for gallery pieces.
  • Hardening from the S6 pressure test (P2-01 to P2-04), pending owner review. @fzlt/core (kernel): destroy() closes the engine's ledger (Ledger.restore(close?), new optional argument): a late text/style/attr/cls write is refused (returns false, touches nothing) and a late add()d node leaves when the caller's task ends, so Recall stays exact (P2-03); advance(sec) throws RangeError: fizlet: sec must be a finite number on Infinity, NaN or a non-number instead of stopping the engine for good (P2-04). @fzlt/core/grid: path, flood, los and hit throw fizlet/grid: cell … is not on the board on an id that is not an integer on the board (they looped forever, P2-01), and a bad size says "is not a board" (P2-02). Bytes: kernel 3,413 → 3,453, page build 7,128 → 7,167 (+55 B for the fixes, −16 B paid down elsewhere), grid 1,603 → 1,652 (budget 1,664), perf 2,042 → 2,019 (−23 B, so #173's +22 B fits the 2,048 budget). The fuzz (npm run test:stress -- fuzz --fixed) holds the four fixed cases.
  • @fzlt/core/timeline: steps(n) lands on whole steps only. Every easing is sampled into a 256-step table and read by blending two neighbouring samples; for a steps easing, the cell across a step's edge blended two steps, so a 13-frame sprite strip (steps(13) over 0..13, 1.4 s) read 1.619, 5.857, 6.143 and 10.381 at 60 Hz. A steps track now reads floor(p × n) / n straight from its progress (no table, no call, no allocation): whole steps only, all n + 1, the last one at the end, as CSS steps(n, end). Other easings are unchanged. Test: timeline: steps(n) lands on whole steps only (fails on 0.20.0). Timeline 2,060 → 2,085 B gz (budget 2,304).
  • @fzlt/core/slot: Recall is byte-exact for accessible scenes (pending owner review). For a scene with accessible: true the slot removed role="img" and the ledger set it again on destroy(), so it came back last and the attribute order differed (the 3 pending 2015 gallery entries reported recallExact: false). It now changes role="img" to role="figure" in place, through the ledger, and Recall puts "img" back in the same spot: outerHTML is identical. figure is a <figure>'s own role, so assistive tech gets the same thing on the engine's figure shape, and on the box shape (div.ae-slot) the aria-label now sits on a role that takes a name. slot 1,819 → 1,822 B gz, page build 7,128 → 7,131 B.
  • @fzlt/core/fx: word particles no longer allocate per frame, and a word kind sprays a range (fizlet#209, pending owner review). draw() built a new font string for every word particle on every frame (1,409 B/frame on the 2015 headphones arcade); each pixel size's string is now built once and kept. And fillText/strokeText box a fractional x, y in Chromium (12 B a call), so a word now draws at 0, 0 after a translate, as a turned particle already did. A 60-word spray in Chromium 153 (CDP heap sampling, as measureAnim): 3,833 → 0.6 B/frame; in Node, 100k drawn frames: 40 → 0 young GCs. emit() takes an optional word range w, wn after scale, like the colour range (it only ever sprayed words[0]). Additive. fx 2,797 → 2,829 B gz, page build 7,128 → 7,163 B (cap 7,168).
  • @fzlt/core/sound: a custom player's type now says it gets your own cue names. SoundOptions.play takes CueName (a built-in Cue or one of your notes), matching what it already received; the cast is gone. A test pins that a notes entry named like an Object.prototype key (constructor) plays as a plain cue. 0 B.
  • Site: people pages line up (pending owner review). On every profile the stat row spans the content column, edge to edge with the cards under it, and the three cards (What they make, Favorite games, Off the board) are a grid again, side by side on wide screens and stacked on narrow ones, one block gap apart (--block-gap, the space the contract rules and contest cards use). A stray selector had folded the cards into the Daily contest panel's padding and width. Both stats are bare numbers with the unit in the label ("bytes of live code"). The site test checks the shared edges and the gap on each people page at 1440, 1024 and 390.
  • Site: the header nav is quiet text, with more room between items (pending owner review). The top-level items and the menu rows no longer draw as half-raised keys (a filled face with a cyan base edge) on hover, open or the current page. Hover lights the text; an open menu flips its chevron and takes --k-sys-ink; the current page or section has a thin --k-sys-ink underline (aria-current unchanged); a menu row gets a faint tint on hover. Keyboard focus keeps the site's ring. The gap between items is 14 px (was 4), and the nav still fits on one line at 961 px, just above the phone menu. GitHub, theme and Start stay keys.
  • Site: "Cookie settings" is a plain text link (pending owner review). In the footer it was a Board UI key between the copyright and the byline, taller than the line and off its baseline; on the privacy and cookies pages it drew as underlined text on a key base. One control now (site/layout.mjs cookieSettings, .text-btn in site.css): still a <button> that reopens the consent banner, styled as a text link with no face, base or lift. In the footer it ends the meta line (© 2026 Chris Cooley · MIT licence · no runtime dependencies · Cookie settings), on its baseline and visible at every width; in running text it is an ordinary link.
  • Brand: the owner's new FIZLET and fzlt logos, and one mark and lockup everywhere (pending owner review). /brand/ draws the two new logos (redrawn as outlines from the owner's art, one function each in scripts/brand-marks.mjs) on dark and light, as live art and SVG downloads: FIZLET, the mark in its square frame closed by the F; fzlt, the mark's pixels and lowercase fzlt. Ink is --ink, the pop --k-attack. The site mark (site/layout.mjs MARK, mark()) is now that art: frame and squares in the text colour, no tile or grid, in the header, the footer, fzlt.dev, the favicon, og.png and the sticker keys. One lockup function (lockup()) draws the header, footer, /brand/ and fzlt.dev lockups; FIZLET sits at 0.075em tracking, nudged -0.06em, and the version is a small tilted --k-sys sticker, its edge and hard shadow the shared --sticker-edge / --sticker-shadow tokens (--ink until they land) so they read on both themes (on phones it drops under FIZLET, a size down, so the 44 px header keys keep their room). Clear space is now the mark's outlined square; minimum 24 px for both logos.
  • Site: the origin hero in two looks, a redrawn coder, and a cup you can play with (pending owner review). "Code. Sleep. Drink. Repeat." is now inspired by the original 2015 portfolio, with Fizlet flair: a 2015 / Fizlet key in the scene's key row (aria-pressed, look in origin:state, look in mount, data-look on the slot) switches the flat 2015 palette and the Fizlet look, cyberpunk × Tron from the Board UI tokens with the desk grid, neon edges and pixel patterns (default). He wears a backwards trucker cap seen from above (mesh, foam panel, the bill toward you), the headphones' band goes over it with a cup on each ear, and his arms bend at the elbow (two bones, a tapered forearm, hands). Every tap squashes on the next frame and pops; hit areas are 44 px or more. The cup: tap to sip, double-tap to refill (a pour, then steam), drag to move it (it lifts, its shadow grows), flick to knock it over (it spills, then rights itself, refilled); Enter, R, K and the arrows on the cup. Reduced motion changes state with no animation; every toy settles. Live chunk 20,922 → 28,156 B gzip; inline poster 3,775 → 5,120 B (one markup, both looks; the poster's saucer shows again). Test: tests/origin.mjs.
  • Site: Before AI: 2015, the Fizlet columns play again after Recall (pending owner review). Recall gave the page back but left both Fizlet columns dead, and the remake's play switch stuck on pause: its class flips were not in the ledger and its listeners outlived the engines. Every class flip now goes through the strip's ledger and every listener goes with its engine, so Recall is exact whatever was pressed; after it, a press on a column starts it fresh (no load intro) and acts, any number of times. Site tests: three Recall-then-play rounds (also under reduced motion) and three play/pause rounds on the remake's switch and the arcade's play key.
  • Site: 3D stickers keep their edge and shadow on the dark theme (pending owner review). Every sticker's die-cut edge and hard shadow read from one token pair, --sticker-edge and --sticker-shadow (site.css :root): the light ink and the muted light on the dark theme, the ink rim on the light theme. The brick status stickers and the kernel slab, section and gallery numbers (.eyebrow .index), the gallery's NEW / APPROVED / HERO / WINNER stickers, the phase and people stickers, the win badge, the status and phase pills, the periodic tiles' stickers and the kit's slam sticker on the site all use it. Text stays the dark ink on the bright face.
  • Home: the spec sheet's size chips are small brick tiles (pending owner review). The wall of "@fzlt/core/… KB" chips with a green bar under each is now two tidy grids (sizeChips in site/brick-ui.mjs): the bricks you import, each with its symbol tile in its own colour (from site/bricks.mjs, the same as the cards and the periodic table), its short name ("input", "presence/check") and its gzip size as the number, with "@fzlt/core/…" said once in the heading; then the kernel, the split sizes (ui js, ui css) and the builds (the arcade card, the page build), where a build gets a neutral dashed tile and no symbol. The bar is gone: each tile fills from the bottom to the share of its budget, and the share is in the chip's text for screen readers and in its title on hover. The docs budgets table shows the same symbol tiles. Sizes stay the build's.
  • Bricks: status stickers sit beside the symbol tile, each slapped on its own way; BETA peels; the kernel gets a stamped slab (pending owner review). On the home page's brick cards and on bricks/, the status sticker moves off the card's top border into the open space right of the symbol tile, centred on its row. Its tilt (2 to 6 degrees either way) and a nudge of a few px come from the brick's id (stickerLook in site/brick-ui.mjs, a hash: the same on every build, no Math.random, and translate/rotate never move the layout). BETA stickers peel: one corner cut and folded back as a lighter underside with a soft shadow, and two faint scuffs, in CSS only (the DOM VIRUS game sticker's recipe); the label is still the only text. The kernel's KERNEL sticker is a bigger purple slab in the card's top-right corner, inside its border (no sticker hangs off a card or a tile), with a stamp's inner ring and the kernel card's double shadow. Hover and focus press the stickers flat (no motion under reduced motion). The same rules at every size: the periodic table's tiles, the quick view, a brick's page and the roadmap cards. On phones a long label wraps under the tile, never over the name. No sticker hangs off a card; on the periodic table's small tiles the sticker still sits on the top edge. The kernel shows only its KERNEL sticker everywhere (card, tile, legend, quick view, its page): it is the core, so complete goes without saying (its status stays complete in the data). A sub-heading after a brick grid ("In-house / experimental") keeps the page's 48 px block gap. Text contrast on every sticker face is 5.4:1 or more in both themes.
  • Gallery: the genre minis are playable: compact keys that never cover the action (pending owner review). In Redline, Dirt Lab, Bag Beat and Toast Tell the keys were sized in container units on a figure that was no size container, so they grew with the window (GAS took half the stage), and the page's Board UI button rule outranked their sheets. One small shared helper, site/gallery/pieces/keys.ts, now makes them: 44 px Board UI keys in a 56 px strip on the stage's bottom edge, under the floor line (site.css, once); mute at the strip's left end on a wide stage, in the top corner on a square or tall one. The four lay themselves out per stage class (phone shape 1 / 1): the art keeps above the strip. A press anywhere on the stage is GAS or PUNCH; Space and the arrows still play. Dirt Lab's palette is a compact text row (FLAT, RAMP, BUMP, GAP, RACE), and it is back under the contest line: 10,245 → 10,199 B live. Redline 8,825 → 8,888 B, Bag Beat 8,167 → 8,278 B, Toast Tell 9,706 → 9,813 B; Freeze Plan's keys (CSS only, 0 B) are a 44 px pair at the bottom centre. Test: tests/minis-controls.mjs (in test:site), all 16 minis at 1440, 390 and 360.
  • Site: the home page's three-phase cards (03) lose their left border (pending owner review). READ, STEP and RENDER are plain Board UI 3D cards now: a neutral rim, the phase colour on the hard base below and on a "PHASE 1/2/3" sticker on the top edge (the people and gallery sticker look). STEP's "× 0–4" is a small chip; the purple frame and grid around the cards are gone. A site test checks every side of each card has the same border width.
  • Site: footer brick chips, compact symbols with no overlap on phones (pending owner review). On a touch screen the footer's "Kernel and bricks" chips drew as tall, overlapping tiles: the footer's 44 px text-link rule (.foot-map ul:not(.map-chips) a) also matched the chips, and the chip rule turned them into two-row grids. Each chip is now the symbol in its brick's colour (30 px tall, 44 px wide on touch, the hit area grown into the row gap), wrapping with a small gap; hover and focus show the name as a tooltip, and the name stays the accessible name. The release chips under Notes lose the same overlap. A test at 360, 390 and 1440 (tests/site-nav.mjs).
  • @fzlt/core/presence: resume after a server restart (pending owner review). join() resumes on 1011, 1012 and 1014 as well as 1006, as the guide says (the server holds the seat); 1000, 1001, 1005, 1008, 1009 and 3000 to 4999 still end the match. A test per code. presence 2,207 → 2,224 B gz (budget 2,240).
  • @fzlt/core/presence: play passes need nothing new on the client (pending owner review). A page joins with join(url, signal, ["fizlet-rooms", "pass." + pass, "device." + key]), and a resume offers the pass and the device key again, with resume.<token> after them, never in a URL (a test proves it). The pass server side is in-house; the subprotocols and the sign-in check are in Build your own room server ↗. The tests' WebSocket stand-in for join() is one helper now.
  • Site: the launch logo animation, Fizlet → fzlt (the name shift). Two Board UI keys, FIZ and LET, read FIZLET, ride into a press and come out as fiz + lt; the E and the i drop out on the floor and the pair reads fzlt; the press stacks it on a wall of bricks (input, collide, sprite, timeline, fx) and it ends on the lockup Fizlet™ · fzlt™, with one caption line. A gallery hero (launch day) with its own full-screen page, demos/name-shift/. Kernel + @fzlt/core/timeline, one timeline moving a few custom properties through the ledger; it plays once in view, settles to 0 rAF, and the Replay key plays it again. Reduced motion: the final lockup, still (the poster). Recall exact. Embeddable in any slot. The lockup fits its frame with any fonts: capped by the frame's width in CSS (the poster), and measured once at layout and scaled down to fit in the live scene. Live chunk 5,729 B gzip, 7,944 B with the gallery loader (budget 9,728 B). Test: tests/name-shift.mjs (in npm run test:browser).
  • @fzlt/core/presence: the lobby and resume on the client (pending owner review). join() takes { retry, lobby }: lobby(start, seat, seated, size) on each LOBBY before the start, and the host's start(seats, mode) sends START; the join result carries the host's mode. A dropped link (1006) reconnects on its own with the seat's resume token from WELCOME (subprotocol resume.<hex> next to the access token, never the URL; 6 tries from 250 ms, about 16 s; the token in memory only); a transport's close() closes 1000, a leave. net.away shows seats reconnecting (AWAY, this one too while its link retries); on RESUME presence resends its recent inputs. The room server side is in-house; the protocol is in Build your own room server ↗, whose "what presence speaks" now describes this. presence 1,834 → 2,207 B gz (budget 2,048 → 2,240).
  • Notes: new note, Why fzlt (31 October 2026): why Fizlet's short mark and npm scope is fzlt, the npm i @fzlt/core install line, the fzlt alias, and the planned fzlt.dev and fzlt.app. The notes are regenerated from the content model, so a brick label in the lockstep note now reads @fzlt/core/presence/check.
  • Site: Fizlet positioning, with ™ marks. fizlet.com and the README now say what Fizlet is: the building blocks for animation and games on the DOM, each brick a task written hundreds of times, so a piece boils down to logic and style. New hero ("Logic and style. / The bricks do the rest."), final call, section 03 ("One frame, three phases"), footer, social card text, the home, docs, notes and gallery intros and descriptions. The old "Tiny physics for your page" tagline is gone from the footer, the home title and the README. "The page is the board" is credited to DOM VIRUS as its approach, one use of Fizlet. Fizlet™ on first prominent use (home hero, footer, README title), the footer lockup shows the short mark fzlt™ beside FIZLET, and the footer says "Fizlet and fzlt are trademarks of Chris Cooley." The footer copyright reads "© 2026 Chris Cooley" (was coded.design); the byline still links coded.design as his site.
  • Site: a brand page at /brand/. The FIZLET wordmark and the fzlt short mark, the lockups (Fizlet™ · fzlt™, npm i @fzlt/core, fzlt.dev), a sticker set, why fzlt, the two voices, ™ and casing rules, clear space and minimum sizes, don'ts, the colour tokens as swatches (read from src/ui/tokens.json) and the Board UI kit live. Every mark downloads as a self-contained SVG on dark and light, generated at build time from one source (scripts/brand-marks.mjs) that the page also draws inline. The page rests at 0 rAF; the sticker slam is the kit's own and settles, and stays still under reduced motion.
  • Both marks on npm: a fzlt bin, Fizlet™ (fzlt™) in the description, and a fzlt.dev page. npx @fzlt/core (or fzlt once installed) prints the version, the install line, fizlet.com and every brick's gzip size against its budget (--json for machines). Zero dependencies, node: only; its numbers are dist/sizes.json, written by the build from scripts/sizes.mjs (the same measurement as npm run size), never measured at run time. Not an export; no budget moves. Keywords add fzlt and fizlet; the README gets an npm badge. npm run site:fzlt builds the fzlt.dev landing page into site-dist-fzlt/ (static, no script, light and dark from the system, on the kit tokens); not deployed. npm run test:pack now also runs the bin installed and through npx from the tarball.
  • Site: the brick graph fits phones (/bricks/). Labels no longer overlap: when the layout rests, a one-shot pass moves each label to the cheapest of four sides of its brick (nudged up or down a little), on typed arrays, deterministic, no allocation. Nothing clips: the view fits the world box grown round any dot or label (widths measured once) that sticks out of it, so desktop looks the same. tests/brick-graph.mjs checks every label inside the canvas and no two overlapping at 360, 390 and 1440, also with the longest label held at the right edge. No brick changes.
  • Multiplayer is in-house; a guide to build your own room server. @fzlt/core/presence and @fzlt/core/presence/check stay in the package unchanged (MIT, BETA) and move into an "In-house / experimental" group wherever bricks are listed (the bricks page, the home page, the README table, llms-full.txt): "Used in our games. Not a supported multiplayer service." The room server (rooms/) leaves this repo; its history stays here, and we keep running it for our own games, privately. Its CI step and the rooms-load / rooms-wake stress wrappers go with it. /guides/multiplayer-rooms/ becomes Build your own room server (/guides/build-your-own-room-server/; the old URL redirects): what presence speaks, the full wire protocol as the contract (subprotocol fizlet-rooms, the 12-byte message and its eight kinds, seats, the lobby, resume, the access check), the one-room-one-object pattern and a threat checklist. The roadmap says "Multiplayer: in-house. DIY guide for your own server."; E12 builds on presence (in-house). The lockstep note links the guide.
  • Site and README: @fzlt/core is on npm (0.21.2, 2026-10-31), so the home quick start, the brick pages and the docs drop "coming soon" / "not published yet", and the README's pre-publish note says when it went out. Building from source stays as the no-npm option.
  • Site: smaller gallery pages, same pixels (size sprint, re-based #208). The gallery stages' inline PNG posters are packed losslessly (scripts/png-pack.mjs: an indexed PNG at the smallest bit depth, pixels compared after packing; kept as-is past 256 colours; scripts/gallery-posters.mjs packs what it writes; a test keeps every stage packed): 17 of the 34 posters packed. The pages' inline <style> blocks are minified at build like site.css. Gallery page 1 first load 531,997 → 520,651 B gzip, page 2 92,521 → 76,754 B; all HTML 1,465,898 → 1,428,045 B gzip. No JS or CSS file changes.
  • Site: /bricks/ has a new hero, the Fizlet periodic table. Every brick, 27 shipped and 8 planned, is a Board UI key in a column for its family (kernel, input, network, motion, render, game, page), showing its number, its two-letter symbol and, for a shipped brick, its size measured on this build. presence and presence/check (in-house) have their own In-house column, with its note. Planned bricks are dashed. Press one (click, tap, Enter or Space) and the key goes down, lights in its colour, says PRESSED, and a short quick view flies in: name, status, size against budget, one line, its import and a link to its page. On phones it is a bottom sheet, and the table scrolls inside its own box. Arrow keys move between bricks and Esc closes. It runs 0 frames at rest and after use; under reduced motion the quick view just appears. Its script is 914 B gzip (periodic.js, measured). Each tile follows the brick cards: the status sticker on its top edge and the size with a colour dot in the top-right corner. Each brick's symbol, number and family live in site/bricks.mjs, with the symbol rule in a comment. Symbols are lowercase like the fzlt mark: the first letter, then the next consonant (kr kernel, fx fx, sl slot). Brick chips show the symbol and open to the name on hover and focus: footer chips, gallery "bricks used", roadmap needs and docs recipes. Brick cards carry the symbol as a small tile. One sticker per brick, in the kit's sticker style, is drawn at build time as a downloadable SVG (bricks/stickers/) and shown on a sticker sheet. The FILET factory moves down the page as a smaller animation section.
  • Site fix: brick card names no longer break inside a word on phones (they showed "collid e", "came ra"). A name now breaks only after a "/", and phones get a smaller size; a test checks every card at 360 and 390 px.

Every release