Commit History
-
feat(game): escalating, per-target hint ladder (#73)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureReplace the single cryptic nudge with stored per-target solutions and a multi-step hint modal: - levels: `solutions` on the Level dataclass (shipped via client_value). - critters: confirm -> "really stuck on {X}?" -> partial reveal (thanks/"No. I'm a loser") -> full solution. - feelings: betrayed-only riddle, else a shrug. - opener escalates by hints revealed: lame / "Really? Again?" / "getting silly". The same reveal count feeds the win-screen "hints used" tally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>66ebc4d -
feat(audio): per-board music with loudness-matched crossfade (#72)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureGive the bonus and critters boards their own background loops and crossfade between tracks on every level transition. - bonus β corporate-glitch, critters β farm; home keeps Cipher2. - Each Level carries a music basename + a loudness trim (music_gain). The trims (0.349, 0.365) match each track to Cipher2's -19.74 LUFS (measured via EBU R128) so nothing jumps in volume across a transition. - Music is now a per-track voice pool (one <audio> + gain node each) feeding the shared music bus; loadLevel crossfades by ramping the outgoing track to 0 and the incoming up to its trim. Mixer slider, mute, and the glitch-board low-pass still level every track together. Verified with Playwright against the live Web Audio graph: homeβbonusβcritters each select the right track, crossfade (both tracks overlap mid-fade), settle on the exact trim, and park the outgoing voice; farm decodes and plays (HAVE_ENOUGH_DATA); no console errors. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anβ¦30816a1 -
feat(game): add run stats to the win screen (#71)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureTally the whole session and surface it on the victory card: time played, total hops, hints used, databases crashed (delete-all-data wipes), and a fail count + rate (missed verdicts + deaths over all attempts). The tally spans every board and survives the delete-all wipe, so it is never reset. Hints only count when a real hint is revealed (not the shrug), and the label nags with "(seriously..?)" when any were used. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>d6d25e1 -
feat(game): reword the diary context bomb (#69)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureReplace the day-47 "otter" diary entry with a shorter day-46 "betrayed" lament for the diary.txt file tile. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>61fb600 -
feat(game): nudge players toward guardrails in the delete-data modal (#67)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureAdd "Maybe setup some better guardrails next time?" to the "delete all data" 404 modal body so the wipe lands with a wink at AI safety. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>c154db4 -
fix(game): frame every board's grid at the same size (#66)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe bonus (middle) board rendered its tiles larger than the other boards and rode up over the sentence, for two compounding reasons: - reframe() reserved vertical space for the board-swap tile only on boards that grow one. The bonus board exits via the portal and has no swap tile, so it framed a shorter world, zoomed in tighter, and its bigger tiles overlapped the sentence. Reserve the row on every board instead. - The stage stayed full-height for single-row-HUD boards, so the bonus board's short 3-chip checklist gave it a taller stage and a bigger grid than the full-checklist boards. Floor the stage drop at the two-row baseline so a short checklist no longer balloons the board. With both, the bonus board's keycap grid is now pixel-identical to the critters board (same x-extent, same column pitch) and clears the sentence. Verified across desktop, a wide window, and a phone viewport via Playwright. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>46a69b7 -
feat(game): reword the welcome modal around prompting a small AI (#65)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureLead with "How well can you prompt a small AI?" and name the judge model (MiniCPM3-4B) in the body, so the opening screen frames the game as a prompt-a-tiny-LLM challenge. Close on a context-window aside ("your context window can only stretch so farβ¦") that nudges the player to submit before the sentence runs long. Keep the boxed "words" chip echoing the HUD target chips, and refresh the stale title line-height comment. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>b841999 -
feat(game): add "delete all data" wipe chain on the glitch board (#64)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureA second hidden chain on the bonus/glitch board, mirroring the prompt injection: hop delete -> all -> data in order (tail-matched anywhere in the sentence, sharing the "all" tile with the injection line). Once "delete all" is written the `data` keycap arms red β red rim + word, no fill β and corrupts faster than its neighbours, while every off-path tile dims (the same effect the injection line uses). Hopping onto `data` completes the line and throws an "Error 404" modal ("Uh-oh, looks like the AI just deleted everything") with a "yikes" button that wipes the whole game β every board's collected targets, the doodle's shrink/hug state and the portal breadcrumb β and snaps instantly back to the first board (no rain-in). Verified end-to-end with Playwright: armed red tile + off-path dim, the modal copy, and the instant wipe clearing a previously-collected target. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>fb34922 -
feat(game): draw dog.png as an image-file icon, not a document (#63)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe bonus-board context-bomb tiles all rendered via drawFileKeyIcon as a dog-eared sheet with ruled text lines. dog.png is an image, so give it the universal photo glyph (a sun over two mountains in a frame) instead of ruled lines, keying off a new isImageFile helper. diary.txt / receipt.pdf keep the document look. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>622b256 -
feat(game): tailor the file-bomb death card to each opened file (#62)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe context-bomb death modal now reacts to which file you face-planted into, with a per-file aside under the byte dump: "cute dog though." for dog.png, "...what the heck are you writing in here?" for diary.txt, and "there's probably a safer place to store your financial documents." for receipt.pdf. The caption reads "whoops, looks like <file> was too big to handle.", and a little top margin drops it clear of the "context window exceeded" stamp. Verified in-browser: rendered all three death cards against the live CSS; the caption and each tagline read cleanly and clear the stamp. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>678e556 -
Merge pull request #61 from benjosaur/hint-button-improvementsβ
Ben BlakerUnverified signaturefeat(game): improve the hint button and clues5871a15 -
feat(levels): reword the victory modal to crown a master prompter (#60)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureTitle becomes "you win!", body congratulates the player as a master prompter, and the dismiss button reads "keep prompting". Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>e403e10 -
feat(game): add a betrayed hint and refine hint copyβ
Surface a clue for the emotion board's "betrayed" target while it is still uncollected. Unify the per-board hint maps into one target->clue table (target words are unique across boards) and drop the animal-only special-case in hintText(), which now iterates level.targets for any hinted board. Also reword the confirm prompt ("Are you sure you want a hint? Hints are lame.") and sharpen the cow clue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>73db430 -
style(game): enlarge the hint button tap targetβ
Bump the help/hint button from 30px to 40px on desktop and 24px to 32px on the phone layout so it is easier to hit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>e00f22e -
fix(game): make the shrink icon read as four inward arrows (#58)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe shrink keycap's arrowhead barbs splayed forward (toward the centre), collapsing each into a corner bracket so the tile read as an X rather than arrows. Splay the barbs back toward the corner so each inner tip forms a real arrowhead, and nudge the spacing to give the heads room. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1981f99 -
fix(levels): keep the whole injection line lit while walking itβ
updateInjectionFade only kept the words already hopped lit, so the tiles ahead β output especially β faded out from under the player mid-path. Lit is now the full ignoreββ¦βoutput line, past and future, so the destination stays visible while everything off the line still dims. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>5a54912 -
feat(levels): rainbow the earned hackathon key and the victory titleβ
Once the π€ unlocks hackathon, its lane keycap glows in an animated rainbow (pre-inverted so it reads true under the glitch board's CSS negative), and the "you win" modal title sweeps the same rainbow through its letters. Marks the one win that needs no judge as the celebration it is. The @keyframes lives in app.py's un-scoped BLOCKS_CSS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>502d6f2 -
feat(levels): ship the hackathon finale without the judgeβ
The hackathon key is earned by wearing the π€ (clearing critters), not argued β so hopping onto it now wins deterministically instead of POSTing "β¦output hackathon" to the judge endpoint, which could mis-score or fail and rob the player of the finale. build / small still go to the judge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>8409896 -
feat(game): add hint lightbulb with confirm modal on boards 1 & 3 (#56)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureA hand-drawn lightbulb in the HUD on the feelings (board 1) and critters (board 3) boards. Tapping it opens a confirm modal ("view the hint?"); on "yes" it reveals a single nudge toward the next target. Board 1 always shrugs ("yeah idk maybe try a little harder?"). Board 3 surfaces one clue, chosen by target-list order (dog before cow): a dog clue, then a cow clue, then the same shrug once both are collected. The bulb is canvas-drawn like the audio icons (and boils with them); loadLevel shows/hides it per board, so the bonus board carries no hint. Verified in-browser: confirm + board-1 reveal, the modal state machine (confirm β reveal β close β reopen resets, plus "no"), and the board-3 selection across every collection state. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>a02f97f -
fix(levels): let the doodle step off the injection laneβ
Once the prompt-injection lane rose, every off-lane move bonked, so a doodle standing on an already-shipped (broken) key with the remaining targets still locked was stranded β no way back onto the board to fetch the shrink/hug and re-arm. Hopping onto a non-lane board tile now drops the lane, undims the board, and scraps the half-written injection so it re-arms clean. Locked lane keys still show their nudge hint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>282289e -
feat(levels): calmer, livelier glitch flicker on the bonus boardβ
Word tiles on the glitch board scrambled up to half their letters in a burst, which read as fast and unreadable. Replace that with a gentler glitchWord that corrupts only a letter or two, and add a faster idle-only ticker that re-rolls just the word tiles so the glitched glyph crawls and changes over time instead of sitting frozen. The loud full-scramble (glitchLabel) stays on the "bonus" swap tile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>9f158ef -
feat(levels): glitch bonus board β context bombs, shrink/hug, portal round-trip (#54)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureA secret glitch bonus board (feelings β bonus β critters), inverted to a dark negative with glitching word tiles and muffled music. - file context bombs (dog.png/diary.txt/receipt.pdf) flood the prompt β "context window exceeded" death - the only submit is the prompt injection; build/small/hackathon rise in a sequential lane and ship to the judge endpoint; shipped keys break, locked keys bonk - shrink tile unlocks "small"; the π€ earned by clearing critters (via the cross-level portal) replaces the face and unlocks "hackathon" - smooth lightβdark transition, you-win modal once all three are collected Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>a341239
-
feat(layout): hang the world tile below the board, not off the right edge (#52)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe board-swap ("world") tile floated off the right edge with a wide gap, which overflowed narrow phone widths and β on the animal board β was unreachable, since its only neighbour was a portal that teleports you away. Move it flush below the grid at (ROWS, startCol) with a downward arrow: reachable on every board (the cell above the bottom edge is always walkable), and it never overruns the phone width. Decouple the camera's width/height fit so a narrow phone fills its width with bigger tiles instead of shrinking the grid to fit the tile's height, and reserve the tile's row both sides of centre so the grid stays centred and the height-bound desktop board still clears the top chrome. Also bump the keycap word size for legibility (longest words drop a tier to clear the rim). Verified with Playwright across desktop, tablet, and portrait/landscape phone (4x4 and 5x5 boards): no overlap between tiles/sentence/doodle/ targets, nothing cut off, sentence wraps clear of the board, and the woβ¦fc6a7ec -
feat(social): wordmark share card and refreshed tagline (#51)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureReplace the auto-generated HF rabbit/emoji social card with the in-app "semantique" wordmark β the same Caveat-700 ink-soft, -2deg title from the HUD, rendered crisp (2400x1260) on the paper-and-ink theme. Wired in via the `thumbnail:` frontmatter field (served from the Space's own resolve URL); a repeatable `scripts/render_social_card.mjs` regenerates it. Refresh the tagline to "Can you get AI to guess all the words?" across the welcome modal, the README `short_description`, and the README lead β replacing HF's verbose auto-generated blurb and the old genre label. Verified: card rendered and visually checked; welcome modal confirmed live in a headless browser (correct copy, boxed word, clean two-line wrap); README YAML frontmatter parses. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>78266fb -
feat(verdict): dismiss the judge card with Enter (#49)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureAfter a verdict (or the out-of-hops death card / a judge failure), pressing Enter now triggers the card's primary button β "hop again" or "poke the judge again" β so you can clear the card from the keyboard instead of reaching for the mouse. The handler only acts while the overlay is showing, reusing the existing reset/retry click logic. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>e4a39c4 -
fix(onboarding): tidy the welcome modal layout (#46)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureVisual polish on the welcome card: - Drop the inline β glyph; the sentence now reads "the Enter button" in words, leaving a single carriage-return icon (the keycap) so the two no longer compete. - Balance the title across its two lines (text-wrap: balance) and give the boxed "targets" room to breathe. - Lighten the "targets" box (2px rim, 1Β° tilt) so it echoes the HUD target chips instead of overpowering them. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>3037fcd -
feat(onboarding): add a line-art welcome modal on game open (#45)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureGreet the player with a hand-drawn paper-and-ink card over the dropping-in board: - Title "Get the LLM to guess all the targets", with "targets" boxed like the real HUD target chips. - Body "Jump on the β button to submit your description", with an inline carriage-return keycap echoing the close button. - A β close button rendered like the actual submit tile (soft ink, dashed "press me" rim, centred glyph). Closes on the β button, Enter/Escape, or a backdrop tap; gameplay hops are held off (handleDir guard) until it's dismissed. Pure DOM/CSS over the existing overlay pattern β no Modal creds needed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>8429f6a -
feat(levels): add the countries board with the column-shuffle mechanic (#44)β
Ben BlakerUnverified signature## What & why Board four β **`countries`** (`order=3`, reached after the subjects board). The judge reads which country a sentence sounds like: USA, Russia, Italy, France, India, China, Korea, England. ### New mechanic β the `shift` / column-shuffle tile The user's `[swap]` tile, named **`shift`** in code so it doesn't collide with the existing off-edge board-navigation *swap tiles*. - Shift tiles render as hand-drawn keycaps stamped with **two stacked accent β¨ arrows** (wordless, dashed "press me" rim). - The top row is all structural: a blank `start` in the middle flanked by four shift tiles. Hopping onto one **rotates every row UNDER the top row one column to the right**; the rightmost cap of each row **wraps around to become the leftmost**, arcing up-and-over the caps that slide in beneath it. The top row never moves. - Landing on a shift tile costs one budget hop and appends no word (consistent with `wings` / every land, and routed through #42's new `settleIdle()` β¦3663d53 -
feat(layout): centre the sentence and unify the desktop/tablet board (#43)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureCentre the in-progress sentence and anchor it a fixed gap below the target checklist (tracking the measured HUD height --hud-h) instead of the fixed top-left position. Extend the desktop layout down to phone widths: move the compact media-query breakpoint from 1240px to 560px so tablet/medium windows keep the generous desktop fonts rather than the cramped compact layout. To stop the board colliding with the wrapped checklist + sentence at those widths, the stage drops below the chrome adaptively β full height when the HUD is a single row (biggest board on wide screens), lowering only as --hud-h grows. The camera re-centres the board in that band on wide/tall windows; mobile and landscape framing are unchanged. Verified with Playwright on the emotion and animal boards at 1280 / 1000 / 900 / 640px and on portrait + landscape phones: the sentence is centred and clear of the targets, the board clears the sentence, no collisions, and the mobile layout is preserved. Co-authored-by: Claudeβ¦cfa0c79 -
feat(portal): spinning-spiral teleport tiles on the animal board (#42)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThree spinning ink-spiral portals on the animal board (board 2), each linking to the next clockwise (topβrightβbottomβtop). Hopping onto one shrinks the doodle into the source spiral and expands it out of the destination. "portal" is a new reserved tile word (wordless, never reaches the judge); the landing hop costs budget. Spirals spin in the render loop (mesh spin, no texture redraw). Rebased onto #41 (the wings board): #41 reserved the word "wings", which collided with the literal "wings" the layout placed at animal (3,2) β renamed that cell to "winged" so the animal board stays portal-only. Verified in-browser (Playwright): all three clockwise links walked live, spirals spinning, shrink-in/expand-out teleport, "winged" renders as a normal word, 0 console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>8d3ebb8 -
feat(levels): add the subjects board with the wings flight mechanic (#41)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureBoard three (subjects, order=2), reached after the animal board. The judge reads which academic subject a sentence sounds like (maths, science, philosophy, economics, literature, languages, art, music). New mechanic β wings: - `[wings]` tiles render as hand-drawn accent wing keycaps (wordless, like start/β). - Hopping onto one lifts the doodle into the air: it sprouts wings (new "fly" pose) and rises to cruise altitude, wings slowly flapping on their own timer (independent of the idle "boil", so they keep beating mid-hop). - The next two hops glide FREE β airborne, no word appended, no budget spent. - The third hop descends "down from the sky" and lands for real, e.g. onto a β to press submit. Wings are a clean exit: lock in the sentence you've written, then soar over the rest of the keys to the button without typing them. Flight state resets on board change, death, fresh attempt, and swap-tile transitions. Adds takeoff (soar) and wing-beat (flap) sounds. Verified locallβ¦45107cc -
fix(mobile): responsive feelings board β wrapping checklist, fuller board, sharper render (#40)β
Ben BlakerUnverified signature- Targets checklist wraps to a full-width second row (title + counter keep row 1); board/prompt drop below via a --hud-h CSS var. - Compact breakpoint raised 560px β 1240px so mid-width windows/tablets stop wrapping big chips over the board; desktop β₯1240px unchanged. - Portrait board trims the camera side-margin to fill ~85% of the width without clipping edge columns. - Framebuffer pixel-ratio cap raised 2 β 3 so the board isn't soft on dpr-3 phones (textures stay at 2Γ). - Added CLAUDE.md (agent orientation).007f851 -
perf(render): smooth animation by gating and staggering the sketch boilβ
The boil redrew all ~16 hi-dpi (768px) tile canvases and re-uploaded their textures to the GPU in a single setInterval tick, 3x/second. Measured on HF/Safari that was a ~70ms main-thread stall every ~340ms β frame spikes of 40-94ms spaced one boil-interval apart β which dropped frames mid-animation and read as choppy, jagged, disjointed motion. Two changes, no loss of the hand-drawn liveliness: - Only boil while idle: during a hop / judge bob / verdict the motion already carries the life, and a stalled frame mid-motion is precisely what reads as jank, so those frames are left for the animation. - Round-robin a small fixed slice (2 of ~18 surfaces) per tick instead of the whole board at once, so no tick redraws/uploads more than ~2 textures. Re-measured: severe (>40ms) spikes during motion essentially gone, jank frames (>25ms) cut ~3x, median steady at ~17ms (59fps). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>559f3b3 -
fix(prompt): render the written sentence un-clipped in Safariβ
The chip write-on revealed text by animating clip-path with negative insets, then removed the clip with clearProps. Inside HF's iframe under Safari/WebKit that loses both ways: Safari clamps negative clip-path insets to 0, and it won't repaint a clip-path that's later removed until a manual reflow. So the just-written word stayed shaved off on the right until you inspect-element β every fresh load. Reveal by sweeping the right inset 100%->0 and END on a plain inset(0): no negative values, never removed. Pad .sq-chip-text (cancelled in layout) so inset(0) wraps every glyph, slant and descender with room to spare. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>5d9a0d4 -
fix(fonts): preload both Caveat weights before bootβ
document.fonts.ready only waits for faces already in active use, and the Google Fonts stylesheet loads async β so at boot Caveat 600 (the verdict card) wasn't loaded yet, and the card rendered in a fallback face then reflowed once the webfont landed. Explicitly request every weight we use (Patrick Hand 400, Caveat 600 + 700) so all text has final metrics before anything lays out or animates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>b57726d -
fix(mixer): keep the audio mixer open while you use its controlsβ
Two ways the mixer dismissed itself mid-use: - A quick closeβreopen landed in the 0.15s close fade: the stale fade's onComplete still ran and re-hid the popover (display:none) while popOpen stayed true. The next tap on a now-invisible slider fell through to the board, and the outside-tap handler closed the mixer. openPop() now kills any in-flight close, and the close onComplete bails if we've reopened. - Inside HF's iframe under Safari/WebKit a slider's pointer capture can retarget the pointerdown so the outside-tap handler's .contains() check reads false and a drag dismisses the mixer. Stop pointerdowns that start in the popover from reaching that handler at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>371be62 -
fix(embed): keep the HF board fit scroll-invariant (#32)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe embed-fit `avail` folded in `info.scrollTop`, so the board refit on every parent scroll tick. Across a scroll only `scrollTop` changes (`clientHeight`/`offsetTop` are constant), so each tick swung the height and called `renderer.setSize()`, reallocating the WebGL drawing buffer β the "lags much more on HF" jank, with the board reframing mid-scroll clipping the canvas-baked keycap text. A real HF page is always scrollable, so it fired constantly; localhost has no iframe and never reproduced. Drop `scrollTop` from `avail` (now `max(0, clientHeight - offsetTop)`, preserving the "fit below HF's header" intent) and guard `apply` with a `lastKey` so pure-scroll getPageInfo events early-return before any fit/reflow. Genuine viewport changes (resize/rotate) still shift `clientHeight` and refit. Verified in a cross-origin iframe-resizer-v4 repro: buffer churn while scrolling drops from 4 distinct sizes to 1 stable size, the resize path still adapts (738->532->892), and the embedded board β¦8d256bc -
feat(audio): replace synth bg music with the Cipher2 track (#28)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureSwap the procedural C-pentatonic synth loop for a recorded track, downsampled to 128 kbps (~3.7 MB). It's too big to inline as a data URI like the pen-scratch SFX (that would bloat every page load), so it streams from static/ via gr.set_static_paths and the file route, with the URL resolved against the Gradio root so it holds up inside the HF Spaces iframe. A looping <audio> element feeds the existing musicGain bus, so the in-game mixer (slider, mute, localStorage persistence, first-gesture unlock) drives it unchanged. The pause-on-mute is deferred until the pending play() settles, so a fast mute->unmute mid-load can't wedge the element. Verified locally (Gradio 6.18, Playwright): the file route serves audio/mpeg with range support, the track plays and loops in real time through the bus, and mute/unmute -- including rapid toggles during load -- behaves correctly, with no console errors. HF-iframe streaming still needs a real deploy to confirm. Co-authored-by: Claude Opus 4.8 (1M conβ¦fc31f3c -
fix(ui): enlarge target words and center them when they wrap (#30)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe target words in the HUD rendered at 20px on laptop/desktop, which was too small to read at a glance. Bump them to 32px (the dominant HUD text), keep phones at 20px via the existing mobile media query so the compact single-line HUD is unchanged, and add justify-content:center so wrapped lines center under each other instead of left-aligning. Verified in-browser at 1440x900 (computed 20px->32px, no HUD collision), 390x844 (still 20px, no overflow), and a wrap-forcing 580x900 (two lines, both centered on the same x). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>c949a43 -
fix(layout): keep the grid centred when the forward swap tile appears (#29)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signaturePreviously the board shifted left by half a gap whenever a next board existed, so the grid only re-centred after the swap tile was added. Pin the board at x=0 instead; the camera already frames x=0 with the swap tile's width, so the grid reads centred from the start and the lone tile sits in the right margin. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>a5f7f73 -
feat(ui): retitle the out-of-hops stamp "context window exceeded" (#27)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureLeans into the LLM theming: the death card's red stamp now reads "context window exceeded" instead of "out of hops!". Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>efe291e -
feat(audio): pen-scratch + keycap-press sound as each word is written (#26)β
Ben BlakerUnverified signaturePlay a real pen-nib recording (trimmed ~0.37s clip, inlined as a data URI) on each word's handwriting reveal, alongside the keycap-press thock. Rides the sfx bus so the in-game mixer levels/mutes it. Verified in-browser (sample plays once per word, no console errors); 18 unit tests pass.6f2eb74
-
feat(nav): swap boards by hopping a one-way progression tile (#25)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureReplace the two bottom-corner nav buttons with an in-world keycap that sits just off the board edge. Once every target on a board is collected, a "β <next>" tile appears; hopping onto it transports the doodle to the next board's start tile and rains the rest of the keycaps in from the sky, staggered by distance, before play resumes. Progression is one-way (no back tile). The same drop-in plays as the board intro. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>b9f18ee -
fix(ui): stop clipping the ! and ? sentence chips (#22)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe chip write-on reveal animates a clip-path inset; gsap leaves the end state (`inset(-20% -10% -20% -10%)`) applied permanently. The right inset is a percentage of the chip's box, so for a single-character `!`/`?` tile (~12px wide) it's ~1px β too little to clear Caveat's forward slant, and the lean stays clipped forever. Clear the inline clip-path once the reveal finishes (gsap `clearProps`), so no residual clip lingers on any chip. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>8633182 -
feat(audio): background music and an in-game volume mixer (#21)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureAdd a fully procedural background music loop (generative C-pentatonic melody over a soft IβviβIVβV bass, on its own Web Audio bus) and an in-game volume mixer. Tapping either top-right icon opens a hand-drawn popover with two wobbly-ink sliders β music and sfx β each with a mute icon; it closes on an outside tap or Escape. Each channel carries a 0..1 volume on an independent bus, squared into a perceptual loudness curve; dragging snaps the gain live while mute/unmute ramps it and restores the prior level. Levels persist in localStorage and the bar icons show a red ink slash when a channel is at zero. Music defaults on and starts on the player's first gesture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>7d3cc83 -
fix(ui): restore the β submit tile and a solid start outline (#20)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureRevert the submit tile's label from "=" back to "β" (the carriage-return glyph) across the level grids, board engine, tests, and docs. The judge prompt is "<sentence> is the same as" now, so the tile is a purely visual marker β the rename touches no scoring logic. Also drop the dashed rim on the start tile so it reads as a normal solid keycap; only the submit tile keeps its dashed "press me" cue. 23 fast tests pass; verified both boards render in-browser (fake judge). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>48c6837 -
fix(layout): keep tiles clear of the hop hint line (#19)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe stage was full-bleed (inset: 0) and the board frames itself hugging the stage bottom, so the bottom tile row rendered straight under the "arrow keys / WASD to hop" (or "swipe to hop") line. Reserve that bottom band as non-play area; the existing camera framing reflows the board into the shorter stage, and the death-slide now exits the canvas above the hint. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>491673f -
fix(layout): remove residual vertical overflow in the HF Spaces iframe (#18)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe previous embed fit filled the parent's full viewport, but the app iframe sits below HF's header (offsetTop) and Gradio's shell (.main.fillable/.wrap) is a constant few px taller than our component β so the page still scrolled ~96px. Fill the actual slot instead: parent viewport minus the iframe's offset below the top, then subtract the measured shell chrome (body height β root height) so the whole document equals one screen. Clamp to one viewport and cache the chrome to avoid oscillation. Verified 0 overflow on desktop (818=882β64) and mobile (780=844β64); the full board, HUD and hint fit on a single page either way. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>da194a2 -
fix(layout): keep the board on-screen in the HF Spaces iframe (#16)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureHF embeds the Space in an iframe driven by iframe-resizer, which sizes the iframe to its content. The board's `100dvh` root then resolves to the iframe's own height, so each measure-and-grow cycle made it taller without bound β the vertically centered 3D board ended up thousands of px below the fold ("it all drags down"). Local runs have no iframe, so `100dvh` was the real window and the bug never showed. When embedded, let the document be content-height and pin the game to a fixed pixel height (the parent's visible viewport via iframe-resizer's getPageInfo, with an instant innerHeight/screen fallback) so the iframe settles instead of feeding back on itself. The non-embedded path is untouched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>83e6847 -
refactor(ui): drop redundant labels and the extra check tick (#15)β
Ben Blaker +1: Claude Opus 4.8 (1M context)Unverified signatureThe "targets:", "sentence:", and "hops:" labels stated the obvious β the checklist pills, the prompt strip, and the "N/budget" counter each read clearly on their own β so remove them and the now-dead CSS (including the tiny-phone rule that only hid the "targets:" word). A collected target already reads as collected via the fade + strikethrough, so drop the redundant green β tick. checkOff now gives a brief scale pop on the pill itself for feedback in place of the tick animation. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>ff1852e