myth-o-magic changelog
Sequential version numbers — bump in site/api/version.js. Newest first.
v1.74
- Readable footer/setup links. Reported by user: "the dark purple, blue on a dark background in game is very hard to read". Cause: in-game footer (
.status .rules-link) only colored the wrapper<span>, not the<a>tags inside — so the actual links were rendering with browser-default colors (dark blue / visited-purple), low contrast against the dark panel. Setup-screen version line had similarvar(--muted)(#8a93a6) which was also dim. Both now usevar(--ink)(#e6e8ee) at 85% opacity by default — clearly legible — and brighten tovar(--accent)+ underline on hover. Explicit:visitedrule prevents the purple-after-click problem.
v1.73
- Card art: drop fixed aspect-ratio, let it shrink. The user's last two reports established that no fixed
aspect-ratiovalue works across all viewports: 11/15 cropped text on Air, 11/12 + cover cropped the image's legs, 11/14 + contain still cut text on tighter Air viewports. Fundamental issue: a fixed aspect-ratio competes with the text stack for the card's finite height. Switched toflex: 1 1 60px; max-height: 220px(no aspect-ratio): text takes its natural height first, art fills the leftover (capped at 220). Withbackground-size: contain, the image always scales to fit the container's smaller dimension — never cropped, scaling down gracefully when card is short. Image's natural height-fit at 165 px wide is ~224 px, so capping art at 220 keeps the image filling the container's height edge-to-edge (no vertical gap between art and text, the v1.65 user requirement) while horizontal gaps stay narrow (2-23 px depending on actual art height) and blend into the dark page background. - Net effect across viewports:
- iPhone Pro Max (cards ~100 wide, slot height ample): art ≈ 135 px (image natural fit at 100 wide), small horizontal gap.
- MacBook Air (cards 165 wide, slot ~190-210 per card after text): art shrinks to fit, image scales down, full image visible.
- Larger desktops: art reaches 220 px cap, text at natural size, image fully visible.
v1.72
- God card image: no more bottom-crop. v1.69 used
aspect-ratio: 11/12+background-size: coverso the image filled the container with no gap. Side effect: cover scaled the image width-first into the wider container, cropping ~44 px of the portrait's bottom (legs/cape). Reported by user. Fix: - aspect-ratio 11/12 → 11/14 (closer to the native ~0.738 portrait aspect, but still wider than v1.65's 11/15 so text has room on MacBook Air).
- background-size cover → contain (inherited from base
.card .card-art). No crop — full portrait visible. ~5 px horizontal gap each side blends into the dark page background. - Container at 165 × 210 (vs 165 × 180 before). Still fits comfortably under the 220 px max-height cap and below the ~370 px per-board space on a 13" Air (210 art + 150 text + padding ≈ 370).
v1.71
- Asymmetric cards, take 2. v1.70 matched
.slot-emptyand.card.god-cardmax-widths and assumed that fixed it — user re-tested v1.70 and the asymmetry was still there. Deeper cause: the slots grid usedrepeat(3, minmax(0, 1fr))inside a parent column sized tomax-content. Even with matched max-widths, the1frcolumn widths can resolve differently between the two grids because the resolution algorithm walks intrinsic sizes per side. Fix: switched to fixed-pixel tracks (repeat(3, 165px)desktop,repeat(3, 100px)onmax-width: 700 px). Both players' slot grids are now structurally identical — same column geometry regardless of how many gods are in play.
v1.70
- Symmetric card widths between top and bottom player. Reported by user: "the top players cards are skinny and the bottom players cards are wide (different size god images)". Long-standing CSS bug that v1.69's wider art aspect made more obvious. Cause:
.slot-emptyhadmax-width: 130 pxwhile.card.god-cardhadmax-width: 165 px. The.slotsgrid usesrepeat(3, minmax(0, 1fr))inside a parent column sized tomax-content. When one player has god cards in play (max-content 165) and the other has empty slots (max-content 130), the parent's slots column resolves to a different width per side — and since1frdivides equally, the player with empties ends up with narrower columns even when their cards are present. Fix: set bothmax-widthvalues equal at both breakpoints (165 px desktop, 100 px mobile). Both boards now render identical card widths regardless of how many gods each player has in play.
v1.69
- Card art: shorter aspect + cover for laptop viewports. Reported by user: text at the bottom of MoM cards was getting cut off on a MacBook Air. Cause: v1.65's
aspect-ratio: 11/15made the art container ~225 px tall on 165-wide cards. Combined with the stacked p1/p2 board layout — each board gets ~half the viewport, ~370 px on a 13" Air — the text stack below the art (name + title + HP bar + stats + attack + effect + property + pips ≈ 150 px) didn't fit. Bottom of card got clipped byoverflow: hidden. Fix: aspect-ratio: 11/15→11/12. Art is now ~180 px on a 165-wide card → 30+ px more vertical headroom for text.background-size: cover(was inheritedcontain). Image fills the container edge-to-edge instead of leaving horizontal gaps with the new aspect — keeping the head/torso at the top (background-position: top center) and cropping the bottom of the portrait, which for these compositions is mostly cloak/feet.max-height: 220 → 200(cosmetic; the aspect-ratio is now the primary cap).- iPhone Pro Max layout (the v1.51-v1.52 portrait/landscape work) is unaffected; it depends on the body's
100dvhand the existing media-query scaling, not on the art aspect.
v1.67
- Setup overlay scroll, take 2. v1.66 was top-aligned but the user reported "not scrollable on iphone with safari". iOS Safari quirk: a
position: fixedelement withoverflow-y: autodoesn't scroll whenbodyhasoverflow: hidden— touch events get eaten by the body. Fix: move the scroll to the INNER.setup-contentbox (max-height: calc(100dvh - 4rem); overflow-y: auto). The box scrolls internally, the overlay stays fixed. This is the standard "modal with scrollable body" pattern that works reliably on iOS. - Removed the now-unneeded
overflow-y: autofrom.setup-overlay.
v1.66
- Setup overlay scrolls. Reported by user: "all the content does not fit on one page and there is no ability to scroll up and down for some reason so you can't see the game type menu at the top and there are cut off recent matches at the bottom." Bug:
.overlay { display: flex; align-items: center; justify-content: center }centered content vertically — combined withbody { overflow: hidden }(which the in-game layout needs) meant any overflow vanished off both ends with no scrollbar. The match-history v1.57 added enough content to push past viewport height on phones. Fix: only the.setup-overlayis nowjustify-content: flex-start; overflow-y: auto; -webkit-overflow-scrolling: touch— top-aligned, scrolls when content exceeds viewport, smooth iOS momentum scrolling. Other overlays (pass, help, game-over) are short and stay center-aligned.
v1.65
- Card text packs against the art (no gap). Follow-up to v1.63's top-aligned art. The art container was
flex: 1 1 60px max-height: 160px— it expanded to fill any leftover card height, and with the image now top-aligned that left a visible gap between the image bottom and the text below. Now the art container isflex: 0 0 autowithaspect-ratio: 11/15— sized to roughly match the native god-portrait aspect so the container ends right where the image ends. Any extra card height (from the slot grid stretching cards uniformly) falls BELOW the text now, not between art and text.
v1.64
- Auto-end-turn countdown reduced from 20 → 10 seconds. Urgent (red pulse) state now kicks in at 3 s remaining (was 5 s) so the same proportional "last 30%" feel.
v1.63
- Auto-end-turn countdown — now actually fires when stuck. Reported by user: had 0 actions, no playable god, no attachable energy, but countdown didn't start. Bug: my v1.60
isPlayerStuck()checked whether each god had room (energyAttached < energyCost) but missed MoM's per-turn rule that each god can only be energized ONCE per turn (tracked instate.energizedThisTurn). So when all in-play gods had already been energized this turn but still had room, the function thought you could attach more energy and didn't trigger. Fix:isPlayerStucknow also requires!state.energizedThisTurn.has(g.instId)to consider a god eligible for energy attachment. - God card art top-aligned.
background-position: center→top centeron.card .card-art. Portraits now hug the top of the art container — visually consistent across all cards (some have more headroom than others, and centering caused the focal point to drift).
v1.62
- Damage number polish. User feedback on v1.61: didn't like the black outline, and the number was too wide on phones (cut off off the side of the card).
- Dropped the multi-direction black
text-shadowoutline +webkit-text-stroke. Now just a soft red glow (rgba(180, 0, 0, .95)halo +rgba(248, 50, 50, .55)outer halo) plus a subtle dark shadow for depth. Cleaner look, still pops over card art. - Font size 2.3 rem → 1.6 rem desktop, 1.25 rem on
max-width: 700 px(mobile). Typical 4-5 digit numbers now fit inside the 100-px-wide mobile card. - Burst-in rotation softened (-12° → -8°), peak scale 1.4 → 1.3 (less aggressive entry to match the calmer styling).
letter-spacing: -.02emto tighten digits and save horizontal width.- Color slightly brighter (
#fb1f1f→#ff4d4d) since there's no longer a heavy outline doing the contrast work.
v1.61
- Damage number redesigned: Diablo-style. Reverting the v1.59 red translucent card overlay (per user — found it more distracting than helpful). Replacing it with a much more dramatic damage number:
- Font: bold serif (
'Cinzel', 'Times New Roman', Georgia, serif), 2.3 rem (was 1.5 rem). Heavy weight (900). - Color: fiery red
#fb1f1fwith thick black outline (4-direction text-shadow + 1.5 px webkit-text-stroke) and a subtle red glow. Reads over any card art. - Animation: bursts in scaled to 1.4× with a -12° rotation, snaps upright at 1.1×, drifts upward to 95 px (was 56), lingers ~75% of the way through, then fades. 2.5 s total.
- The .fx-damage class still applies the shake (550 ms, 7 px amplitude — unchanged from v1.59).
- The number floats higher than the card itself, so it can't be missed by the eye even on the bottom-row hand.
v1.60
- Auto-end-turn countdown. When the active player is "stuck" — no actions left, no energy they can attach (no energy in hand or all gods are full), no god they can play (no god in hand or no empty slot) — the end-turn button starts a 20-second countdown and auto-fires at 0. Button label switches to
end turn (Ns), tints yellow during the countdown, flashes red + pulses inside the last 5 s. - New
isPlayerStuck()helper checks the four ways a player could still do something productive; if none apply, countdown starts. The countdown self-cancels the moment the state changes (attach an energy that opens a new play, opponent action arrives, etc.). Doesn't fire during pass-the-device overlay, AI's turn, opponent's turn (online), or after game-over. - Pi-safe: 250 ms tick interval, no animations during count except an opacity/transform pulse on the urgent state.
v1.59
- Damage animation: longer + more obvious. User reported the v1.56 effect was "very faint and fast". Three changes:
- Bigger shake. Duration 350 ms → 550 ms, amplitude 4 px → 7 px. Reads as a noticeable jolt instead of a flicker.
- Red flash overlay. New
::beforepseudo-element on the damaged card: full-card translucent red wash with a danger-colored border, fade-in 0–10%, hold 10–60%, fade-out 60–100% over 1.4 s. Pure opacity animation on a single composited layer — Pi-safe (no extra paint cost beyond the initial layer creation). - Bigger damage number. Font 1.05 rem → 1.5 rem, white text with red glow, drifts up further (38 → 56 px), holds at peak longer, fades over 1.5 s. The cubic-bezier easing makes it pop on entry then settle.
- The .fx-damage class now stays on for 1.55 s (was 0.85) so the keyframes finish before cleanup.
- Confirmed: shows on BOTH players' screens. The v1.56 state-diff hook in
applyRemoteStatesnapshots HP before swapping in remote state, then queues damage effects for HP decreases — so when your opponent attacks, your client diffs the new state and runs the same animation locally. No extra channel needed.
v1.58
- Energy pips: empty state now visible. Reported by user: empty pips were nearly invisible. They were 6×6 px circles with
background: #2a2410(very dark brown) andborder: var(--line)(very dark blue-gray) — both colors blended into the panel background; an unfilled god looked the same as a god with the right number of pips. Changes: - Pip size 6 → 9 px, gap 2 → 3 px (more space, easier to tap on touch).
- Empty pip: hollow ring with a dim-yellow border (
rgba(250, 204, 21, .55)) on a semi-transparent dark fill. Now reads clearly as "energy slot, not yet filled". - Filled pip: same solid yellow + glow as before.
- Added a
N/Mcount text after the pips (e.g.2/3). Yellow normally, green when fully energized. Belt-and-suspenders: even if the rings are visually ambiguous at small sizes, the count is always unambiguous. - Hand-card move-cost pips (which are always empty) now also benefit — previously they were almost invisible too.
v1.57
- Match history on the setup screen. Two new sections under "all-time scores":
- Head-to-head: per-pair win/loss tally. Each row reads
Alice 3 – 1 Bob. Pairs are canonicalized (alphabetical key) so "Alice vs Bob" and "Bob vs Alice" collapse together. Sorted by total games played, descending. - Recent matches: scrolling list of last 50 finished games, most-recent first. Each row:
<when> <winner> beat <loser> [forfeit]. Time formats asXm/h/d agofor recent, ISO date for older. - New endpoint
GET /api/online/matchesreads from the existinggame_resultstable (winnername, losername, forfeited, recorded_at) via the service-role REST connection — no schema change required, no policy change required. Capped at 50 rows by default;?limit=Nquery param up to 200. - New
Online.fetchMatches()client wrapper.refreshScoreboard()now fetches scores + matches in parallel. - Scope: ONLINE matches only. Hot-seat games would need a separate localStorage log; vs-AI never recorded. Logged in BACKLOG as a follow-up. Suggested "C" by user (both list view + head-to-head) so they can pick what they prefer once they see it live.
v1.56
- Visual attack feedback (Benjamin's feature request from 2026-05-03 backlog). When a god takes damage: a short shake animation (translateX, ~350 ms) plus a floating "−NNNN" damage number that drifts up and fades. When a god is played from the hand into a slot: a quick scale-pop on the new slot. All animations are pure CSS keyframes on
transform+opacity— they go through the GPU compositor only, no layout thrash, no paint cost. One element animated at a time, ~400-800 ms each, auto-removed viasetTimeout. Pi-friendly (Benjamin runs this on a Raspberry Pi). - Status-effect badges on god cards. Top-left of each card now shows pill badges for active temporary effects:
+NNNN(Hephaestus's ally buff stacked on attack),−NNNN(Hera's-Attackdebuff on next attack),CHRM(Aphrodite's Charmspeak — next attack will hit your own ally). Tooltip on each badge explains what it means. Shielded gods continue to use the existing 🛡 corner glyph (no badge added — already covered). - Online sync via state diff. Effects fire for the OPPONENT's actions too:
applyRemoteStatesnapshots HP/slot occupancy before replacing state, then diffs to detect HP decreases (queuedamageeffect) and newly-occupied slots (queuesummoneffect). No new channel/state-row pollution; just compares old vs new locally on receive. - New module helpers:
pendingEffects[],queueEffect(),flushEffects()(drains after every render, looks up the freshly-rendered slot viadata-side/data-slot). Damage call sites inperformMovego through newdealDamage(target, side, slot, dmg)wrapper that applies + queues in one shot.
v1.54
- Hestia bug fix + balance pass. Reported by Benjamin (uplink 2026-05-04): "Hestia can use her attack on herself. It is supposed to only work on her allies. Also we should bring down the amount of health she brings to an ally. It should go down from 3000 to 2750 to make her more balanced against Hades."
- Hearth of Happiness no longer self-targets. New
canTargetSelf(property)helper alongsidemoveTargetType— defaults totrue, returnsfalseonly for Health (Hestia). Apollo (Healing) and Demeter (Defence) can still self-target since their cost-vs-benefit is more reasonable. - Validation in three places: click handler (line ~398),
tryUseMove(line ~498), and thetargetablehighlight inrenderGodInPlay(line ~958). All gate oncanTargetSelf. - AI updated too:
ai.jsbestMoveForGodnowcontinues past self when scoring Health-property targets. Previously it just penalised the score byHESTIA_SELF_DAMAGE— that was technically still picking an illegal move once Apollo/Demeter ally heal scored low. HESTIA_HP_BOOST3000 → 2750. Tunables, gods description, and README rules table all updated to match (rules-sync check passes).
v1.52
- Shared
/api/online/{join,quickjoin}endpoints now MERGE the joining player's object into existingstate.players.p2instead of full-replacing. No behavior change for MoM (existing p2 was always empty before join), but Card Battle relies on this so the host's pre-dealt p2 hand isn't clobbered when the guest joins. Also extended quickjoin with agameTypefilter so CB and MoM matchmaking pools are separate. - Rules page nav simplified: "← all games" comes first (consistent with changelog/backlog pages).
v1.50
- "← home" link added to setup-screen and in-game status row per Benjamin's uplinked feature request (2026-05-03). Previously the only way out of the game was the "leave" button (which goes to setup) or the browser back button.
v1.49
- All in-game changelog/backlog links now point at locally-hosted HTML pages (e.g.
/games/myth-o-magic/changelog.html) instead of GitHub. The repo is private, so the GitHub-rendered markdown 404'd for anyone who isn't a collaborator. The .html files are generated from the .md sources byscripts/render-game-docs.pyon every deploy.
v1.48
- Game setup screen and in-game footer now have changelog + backlog links alongside the existing rules link (matching what's on the landing page). One less click to find the bug list while in the middle of playing.
v1.46
- Landing page game listing now has per-game changelog and backlog links (alongside the existing rules link). Both link to the GitHub-rendered markdown so they always show the latest.
- Logged Benjamin's feature request via the uplink (2026-05-03): visual attack feedback on the targeted card — added to
BACKLOG.mdunder "Polish & feel".
v1.45
- AI mode: human can no longer end the AI's turn. v1.44 over-loosened the
endTurn()guard — by allowing any call whenstate.currentwas the AI, it inadvertently let the human's end-turn button click through and end the AI's turn early. Now:endTurn()accepts anasAiflag, and only calls with that flag set can end the AI's turn. The dispatcher passes{ asAi: true }; the human's button click does not. Plus the end-turn button is now disabled (greyed out) when it isn't the human's turn — clear visual signal in addition to the server-side guard.
v1.44
- AI opponent unblock fix. v1.43 had a bug where the AI got stuck "thinking" forever —
endTurn()had a guardif (!isMyTurn()) return;(intended to stop the wrong-side player from ending a turn during online play). For AI mode,isMyTurn()returns false during the AI's turn (since the human isn't acting), so when the AI dispatcher tried to end its own turn, the guard silently blocked it. AI's loop hit the safety cap (60 actions) and exited without flipping the turn back. Fix: also allowisAiPlayer(state.current)through the guard, so the AI can end its own turn.
v1.43
- AI opponent. New "vs AI" tab on the setup screen alongside Hot-seat and Online. Pick a name for yourself and one for the AI; the AI plays Player 2.
- AI is heuristic, not search-based — picks one action at a time based on current state (play gods → attach energy → use moves → end turn). Move scoring favors focus-fire on lowest-HP enemies, healing damaged allies, shielding wounded gods, charmspeak on big-threat enemies, and Hephaestus's ally buff when there are other allies to benefit. Beatable but plays sensibly. Lives in
site/games/myth-o-magic/ai.js. - Pass-the-device overlay skipped when next player is AI (no human to hand off to). Hint shows "{AI name} is thinking…" during AI turns.
- AI runs entirely client-side, no server roundtrip. ~700ms pause between actions so you can follow what it does.
v1.42
- "Clear local scores" button only shows when there are actually local scores to clear. Hidden otherwise.
v1.41
- "Clear local scores" button on the setup-screen scoreboard. Wipes the localStorage hot-seat tally on the current browser only — server-side online scores are unaffected. Useful for clearing stale test data left over from before the v1.40 server-side scoreboard.
v1.40
- Cross-device scoreboard via Supabase. Previously scores lived only in localStorage, so each browser/device had its own tally — the opponent's win-by-forfeit screen showed "0–0" because their localStorage never got the increment. New
scorestable +record_game_result()stored function (idempotent viagame_results.game_idUNIQUE) so both clients can safely call. Wins/losses now persist across browsers, private windows, and devices. - New endpoints:
POST /api/online/record-result,GET /api/online/scores. - Setup-screen scoreboard now merges local hot-seat scores with server-side online scores (server takes precedence on conflict).
- Game-over tally fetches fresh server scores after recording, so both players see the correct updated tally.
- New
site/games/myth-o-magic/online/migrations/folder with numbered migration files (001_initial_games.sql,002_scores_and_results.sql). Apply in order for incremental updates; full schema still inschema.sql(idempotent). - Schema fix:
alter publication supabase_realtime add table public.gamesis now wrapped in ado $$guard so re-running the schema doesn't error with42710: relation already member of publication. - Project README (top-level) rewritten — was still the dev-container template's generic onboarding doc; now describes benjamins-games + Myth-o-Magic specifically.
v1.39
- Setup screen (the Hot-seat / Online tabs page) now shows the version number too — previously the badge only existed inside the in-game status bar, so you couldn't tell what version was loaded until you started a game. Same tooltip (notes + git sha + boot time) as the in-game badge.
v1.38
- Online forfeit on leave. Clicking "leave" mid-game now counts as a forfeit: the leaver loses, the opponent wins. Game-over state is pushed to the server immediately (bypassing the normal debounce) so the opponent's realtime sub fires a clean game-over screen instead of the game just hanging. Win/loss recorded to the all-time scoreboard. Game-over heading reads "{Winner} wins by forfeit!".
- Existing setup-screen scoreboard already lists all-time wins/losses per player name (sorted by wins) — that satisfies "list of scores" without a new screen.
v1.37
- Bug fix: god selection. Once you selected a god to play, you couldn't deselect or switch — clicking a different friendly god either tried to use the first god's move on it or did nothing. Now: clicking the currently-selected god deselects it; clicking a different friendly god switches selection (when the selected god is an enemy-targeting attacker — for ally-targeting gods like Apollo, clicking another friendly still triggers the heal/buff as before, since that's the intended target).
- UX nit: moved the "rules" link from the right edge of the status row (right next to the end-turn button) into the bottom-right corner of the status box. Less risk of mis-clicking it during fast play.
v1.36
- All 14 gods now have hand-trimmed AI-painted art. Final 5 added: Demeter, Hestia, Hermes, Athena, Dionysus. Replaces the over-trimmed auto-cropped versions. The full pantheon is now visually consistent (uniform style, ~225×305 portraits, baked-in name labels).
v1.35
- Uplink now deduplicates filenames within a single submission. Real cause of the iOS upload failure from v1.34: when you duplicate a photo on iOS and crop it, iOS exports each duplicate with the same PHAsset UUID as the filename. Five "different" photos all arrived as
AAC02297-....jpeg→ without disambiguation they all PUT to the same GitHub path → only the first survived (and the rest 422'd). Now collisions get suffixed-2,-3, etc. so all attached files reach the inbox with distinct names.
v1.34
- Uplink
/api/submitno longer crashes withgithub 422: "sha" wasn't suppliedwhen a file at the destination path already exists (which can happen if a partial submission half-completed and the user retried). The PUT now detects that specific 422, fetches the existing file's SHA via GET, and retries the PUT as an overwrite. Idempotent.
v1.33
- Quickjoin matchmaking now filters by recency (default 5 minutes). Previously it would join the oldest open room, which could be an abandoned host from hours/days ago. Now stale rooms are skipped — quickjoin either matches a fresh open room, or hosts a new one.
v1.32
Two online-play robustness fixes:
- Refresh now resumes the game.
{ roomCode, you }are persisted to localStorage on join/host. On boot, if a saved game exists, we fetch its state from Supabase and drop straight back into play without showing the setup screen. Cleared automatically when the game ends or when you click the new "leave" button. - Resync fallback for dropped realtime subscriptions. Supabase websockets occasionally close after idle / tab-backgrounding / network blips, which silently breaks state sync. Now: every time the tab regains focus, every 12 s while visible, and on any non-SUBSCRIBED channel status, we re-fetch the canonical state via REST and apply it. Belt-and-suspenders against the "stuck game" symptom.
- New "leave" button in the status bar (online-only) — exits the room cleanly, clears the saved-game pointer, returns to setup.
v1.31
- Online host fix. While the host was waiting for someone to join,
state.players.p2wasnullandrender()threw onstate.players.p2.hand.length— the whole game silently failed to draw, leaving the host with the static "Player 1's turn" + blank board. Maderender(),renderSlots(),renderHand(), andcheckGameOver()null-safe. - Empty god-slot row now shows "waiting for opponent…" while waiting.
- Hint shows the room code while the host is waiting (so it's easy to share).
- Added
isReadyToPlay()guard so user actions silently no-op when only one player is in the room — no more accidental crashes from clicking before opponent arrives.
v1.30
- Online play UI live (Phase 2). Setup screen now has Hot-seat / Online tabs. Online tab offers three flows:
- Quick play — auto-match with anyone waiting; if nobody's around, host a new game and wait.
- Host private game — get a 4-letter code to share with a specific friend.
- Join with code — enter a friend's code to join their game.
- New
online.jsclient module wraps/api/online/*endpoints + Supabase realtime subscription (loaded lazily as ESM from esm.sh, no bundle for hot-seat-only players). - Game state syncs via Supabase realtime: every action calls
commit()which renders locally + pushes to server (debounced 80ms); incoming changes from the opponent fireapplyRemoteState()which replaces local state and re-renders. Local UI state (selection, online metadata) is preserved across remote updates. - New
quickjoinserver endpoint that finds the oldest open game and joins it as p2 — or hosts a new game if none exist. - Online-mode UX adjustments: pass-the-device overlay disabled (each player on their own device), end-turn undo disabled (race conditions with opponent),
myPerspective()helper used for face-up/face-down decisions so each player always sees their own hand face-up. - Secret-key file permissions tightened to 600.
- Existing
join.jsendpoint now expectsp2Player(the full player object) instead of a free-form patch; deep-merges intostate.players.p2so the host's data isn't overwritten.
v1.29
- 5 more hand-trimmed god images integrated: Apollo, Artemis, Ares, Hephaestus, Aphrodite. Now 9 of 14 gods have hand-trimmed art (still missing: Demeter, Hestia, Hermes, Dionysus, Athena).
- Supabase key naming aligned with their newer dashboard: env vars are now
SUPABASE_PUBLISHABLE_KEYandSUPABASE_SECRET_KEY(formerlySUPABASE_ANON_KEY/SUPABASE_SERVICE_KEY). Edge functions accept either name as fallback so older Supabase projects also work. SETUP.md andscripts/setup-supabase-env.shupdated to use the new naming.
v1.28
- Online play, phase 1: backend (no UI yet). Supabase Postgres for state storage + realtime websockets for client subscriptions. Edge functions:
POST /api/online/create— generates a room code, stores initial statePOST /api/online/join— second player joins by room codeGET /api/online/state?room=CODE— fetch current statePOST /api/online/move— submit a state updateGET /api/online/config— returns Supabase URL + anon key (for browser realtime subscription)- New
site/api/online/_supabase.jsis a tiny PostgREST helper (avoids bundling@supabase/supabase-jsserver-side). - Schema + walkthrough in
site/games/myth-o-magic/online/{schema.sql,SETUP.md}. - New
scripts/setup-supabase-env.shpushes the three Supabase keys (URL, anon, service) from~/.secrets/into the Vercel project's env vars. - Endpoints are deployed but return
500 supabase env not setuntil the user provisions Supabase + runs the env setup script. Phases 1.29–1.32 will add the UI flow, realtime subscriptions, and polish.
v1.27
- Switched card art
background-sizefromcovertocontainso the whole image shows — including the god's name label at the bottom of each card. Previouslycovercropped the bottom (and Hades/Apollo/etc were missing their label). Tradeoff: portrait images leave small dark bands left/right when the card container isn't tall enough to match the image aspect; the darkvar(--bg)background blends into the card frame so it's not distracting.
v1.26
- Replaced 4 god images (Zeus, Hera, Poseidon, Hades) and the card-back with hand-trimmed versions Benjamin uploaded via the form. Clean crops, no edge bleed, no whitespace borders. The other 9 gods still use the auto-cropped versions from v1.25 — pending hand-trimmed replacements.
v1.25
- All 14 gods now have AI-painted portrait art, cropped from a new 14-card composite Benjamin uploaded via the form. Style is uniform across the deck (each card is a portrait with the god's name labeled at the bottom). Set in
data/gods.jsas theartfield per god. Usesbackground-position: center topso the head/torso stays visible if the container has to crop. - The CSS-rendered placeholder (Greek letter on themed gradient) from v1.23 remains as the fallback if any
artfield is set back tonull.
v1.24
- In-play card layout fix. Hades and Aphrodite (and any god with a longer wrapping effect description) were getting their bottom rows clipped by the
.slots { overflow: hidden }rule. Instead of fighting the overflow, made the.card-artflex-shrinkable: text content keeps its natural height, art fills whatever's left, all god cards always render fully. Min-height 60px on the art so the placeholder Greek letter stays readable; max-height 160px so the art doesn't dominate. - Tightened line-heights and margins across the card content stack to claw back a few px.
v1.23
- Uniform CSS-rendered card art across all 14 gods. Previously 5 gods had AI-painted cards and 9 showed a single-letter placeholder — felt mismatched. Now every god uses a themed placeholder: Greek letter as the focal motif (large, glowing in the god's signature color), god's English name underneath, and a god-specific radial-gradient background (Zeus → gold, Poseidon → ocean blue, Hades → underworld purple, etc.). The 5 AI PNGs stay in
images/as historical artifacts but aren't referenced from the game; flip a god'sartfield indata/gods.jsto a portrait-only image to override the placeholder later. - Face-down card-back no longer has a visible outer border — the .card border + padding were creating a thin gray frame around the card-back image. Removed both for face-down cards so the MythoMagic logo sits cleanly edge-to-edge.
v1.22
- In-play god cards now show the attack effect description (the "what it does" line) underneath the attack name — same wording as hand cards, sourced from
data/effects.jsso it stays in sync with tunables. No more guessing what an in-play god's move actually does.
v1.21
- Hermes "Steal of the Century" — new mechanic. Now deals 2000 damage AND steals 1 energy from the target, transferring it to the friendly god most in need (lowest
energyAttached / energyCostratio, still under cap). If no ally has room, the steal is wasted. Hermes gets a new propertyStealandbaseDamage: 2000. - Pre-deploy QC:
scripts/check-rules-sync.py. Verifies thatREADME.mdand the in-game help overlay contain the same numbers asdata/tunables.jsanddata/gods.js(per-godbaseDamageoverrides). Wired intoscripts/deploy-site.shso a deploy with stale docs is blocked. The rules.html page is rendered live from data and so self-corrects — this check catches the static places. - Fixed existing Apollo drift in README ("3000 HP" → "3250 HP"; the actual heal amount has been 3250 since v1.17).
v1.20
- Display title fixed to Myth-o-Magic — hyphens around the lowercase "o", not just one between "Mytho" and "Magic". Setup screen, browser tab title, rules page heading, and landing-page link all updated.
v1.19
- Hestia balance — sacrifice HP. Each use of Hearth of Happiness now costs Hestia
HESTIA_SELF_DAMAGE(1500) of her own HP. The hearth burns. Self-targeting still nets +1500 HP and +3000 max because the boost is applied first; targeting another ally is a clear sacrifice. If Hestia drops to 0 from her own cost, she dies normally. Tunable indata/tunables.js.
v1.18
- Display title is now Mytho-Magic (capitalized M's) everywhere user-facing: setup screen heading, browser tab title, rules page heading, and landing-page link. URLs and file paths stay lowercase (
/games/myth-o-magic/) so existing bookmarks keep working.
v1.17
- Balance pass from the v1.16 analysis:
- 2-energy default damage 2500 → 3000. Buffs Hermes / Dionysus / Athena (the pure 2E attackers). Hera unaffected (per-god
baseDamage: 2000). - 3-energy default damage 4000 → 4500. Buffs Zeus / Poseidon / Artemis / Ares (the pure 3E attackers). Hades / Hephaestus unaffected (per-god overrides).
- Apollo's
HEAL_AMOUNT3000 → 3250. Modest bump to keep Apollo competitive vs Hestia.
v1.16
- Pass-the-device overlay now has a "go back" button alongside Continue. If you hit "end turn" by mistake, it restores the entire pre-end-turn state (your hand, energy attachments, actions left, draws, everything) and you're back on your turn. The undo window closes the moment the next player clicks Continue, so they can't be retroactively spied on after they've reviewed their hand.
v1.15
- Energy attachments are now capped at each god's cost — once a god is fully charged you can't waste more energy on them. The action is rejected with a log message; the energy card stays in your hand.
- Apollo's energy cost reduced 3 → 2. Faster healing access for the team.
v1.14
- Auto-mulligan on starting hand. If a player's initial 10-card hand contains fewer than
MIN_INITIAL_GODS(2) gods, the deck is reshuffled and the hand re-dealt. Capped at 10 attempts as a safety net. Eliminates the unlucky-opener case where you start with all energy cards. Tunable viadata/tunables.js.
v1.13
- Per-god
baseDamagefield added todata/gods.js— overrides the defaultT.DAMAGE_BY_COST[energyCost]for individual gods. Set for Hera (2000), Hades (3000), Hephaestus (2000) per playtest tuning. - Hera's "Hera's Wrath" now also deals damage (2000) in addition to the −1500 attack debuff. Was buff-only before.
- Hephaestus's HEPHAESTUS_BUFF corrected to 2000 (was 500). The ally bonus the user originally specified.
- Property descriptions for
-AttackandAttackupdated to reflect the damage component.
v1.12
- Rules page gods table now has a "what it does" column showing each attack's actual mechanical effect (damage amount, heal, debuff, etc.) — not just the property name. So gods with no special property show "Deal 4,000 damage" / "Deal 2,500 damage" depending on energy cost; gods with a property show the property's effect.
getEffectDescriptionextracted from game.js intodata/effects.jsso the in-game hand cards and the rules table compute it from the same single source. Tunables flow through both views identically.
v1.11
- God cards in hand now show energy-cost pips at the bottom — empty circles equal to how much energy the move needs to fire. Matches the visual language of in-play cards (which use filled pips for currently attached energy). The cost was technically already in the title line ("HP X · YE") but easy to miss.
v1.10
- Card-back MythoMagic logo no longer cropped on face-down cards. Switched
background-sizefromcover(which crops to fill) tocontain(which fits the whole image). Face-up god cards still usecoverso the portrait fills the art frame.
v1.9
- Hestia's "Health" property re-implemented per Benjamin's intent: instead of a plain heal, Hearth of Happiness now raises the target's max HP by 3000 and refills their current HP to the new maximum. Target can be self or any ally. Tunable via
HESTIA_HP_BOOSTindata/tunables.js.
v1.8
- Rules page now renders the game flow steps and win condition dynamically from
data/tunables.jsinstead of hardcoded text. So they can't drift out of sync with the actual mechanics again — change a tunable, the rules page reflects it on next load. - Help overlay text in the game also scrubbed for the same stale draw-count and win-condition references.
v1.7
- Action log moved from a floating bottom-right panel into the status bar between the boards. No more overlap with Player 1's cards. Same content, just lives where it belongs.
- Prominent "rules" link added in the status bar (next to the help button). The full rules + stats page at
/games/myth-o-magic/rules.htmlwas hard to find before — now it's one click from the game.
v1.6
- Draw 2 cards at the start of every turn (was 1). Tunable in
data/tunables.js. - Win condition simplified: lose when your in-play team is wiped, period — even if you still have gods in hand or deck. Game ends faster, more decisive. (Gated so the game can't end on turn 1 before either side has played.)
- Version badge moved from the top-left corner into the status bar. No more overlap with Player 2's header.
v1.5
- Hand cards now show what each god's attack actually does, not just the name.
- Replaced the git-SHA badge with a sequential version number (this one). Git SHA still returned by
/api/versionfor debugging. - Setup screen now appears at the start of every game, pre-filled with last-used names. Press Start to keep them, or edit and Start to change.
v1.4
- Layout reorg: gods + hand sit side-by-side per player, freeing vertical space.
- Hestia fixed: Hearth of Happiness now heals a chosen ally for 4000 HP (no damage), like Apollo.
- New
/games/myth-o-magic/rules.htmlpage rendering live gods table + properties + tunables. - Tunables extracted to
data/tunables.jsso game and rules page share one source of truth. scripts/deploy-site.shwraps Vercel deploy with proper SHA injection.
v1.3
- Revision badge in top-left corner showing deployed git SHA. (Replaced by sequential version in v1.5.)
- Player name display fix — names entered on setup screen now propagate to the in-game header.
- Card resize for legibility — bumped god card width to 150px and font to .8rem.
v1.2
- Compact layout — game fits a MacBook Air viewport without scrolling.
- Player name selector at game start; saved across matches.
- All-time win/loss tracking per player name.
- Demeter / Greenery Assist correctly buffs an ally with a one-shot 50% damage shield.
v1.1
- Contextual hint bar that updates based on what's selected.
- "?" help button + auto-show on first visit.
- Initial BACKLOG.md.
v1.0
- First playable: hot-seat 2-player, all 14 gods, draw/play/attach/use loop, win/lose detection, restart.