Commit Graph
22 Commits
Author SHA1 Message Date
tiennm99 16520d125d fix(mobile): wrap board + controls in centered play-group
Group [board + 8px gap + MobileControls] inside a single .play-group
div and vertically center it in the space below the HUD on touch
devices. The dock no longer hangs at the bottom edge of the viewport;
instead the whole [board + controls] reads as one unit floating in the
middle of the screen.

Implementation: a new .play-stack passthrough flex container fills the
remaining column space; on coarse pointers it uses
justify-content: safe center to center .play-group, which itself goes
flex: 0 1 auto with max-height: 100%. The "safe" keyword falls through
to flex-start when the group overflows, so tall finale puzzles still
scroll inside .board-wrap instead of clipping above the HUD.

Replaces the prior coarse-only .board-wrap { align-items: flex-end }
strategy and the now-redundant .board { margin-top: auto } belt-and-
suspenders rule in Board.svelte. Desktop layout unchanged.
2026-04-28 23:48:13 +07:00
tiennm99 179f052c89 fix(mobile): align LVLS button with d-pad's bottom arrow row
Restructure .dock-left from a 3-button column-flex into a 2x2 grid:
UNDO and RESET share the top row, LVLS spans the full bottom row via
grid-column: 1 / -1. Both columns now share 48px row heights and a 6px
gap so UNDO/RESET line up with UP and LVLS lines up with the
LEFT/DOWN/RIGHT row.

Bump .action height 44 -> 48px on coarse pointers so action rows match
the d-pad's 48px arrows. Still well above the 44pt HIG minimum. With
the action stack now equal to the d-pad's natural height (102px), the
dock no longer needs the previous round's asymmetric flex-end layout
to look balanced.
2026-04-28 23:36:53 +07:00
tiennm99 8c0a45bd3c fix(mobile): bump dock bottom padding 12 -> 20px
Lifts the d-pad and action stack further off the screen edge on
Android (where safe-area-inset-bottom is 0). iOS devices still
compound the inset on top so home-indicator clearance is unchanged.
2026-04-28 23:15:36 +07:00
tiennm99 18d63d07c7 fix(mobile): close dock-internal gap by top-aligning the d-pad
Round 1 anchored the board to .board-wrap's bottom but exposed a 32px
gap caused by .mobile-dock { align-items: flex-end } combined with
.dock-left (148px tall) being taller than .dpad (116px). With both
columns bottom-aligned, the d-pad's top sat ~32px below the action
stack's top, so the arrows still felt far from the board.

Switch the dock to align-items: stretch so each column picks its own
cross-axis position: action stack stays bottom-anchored (closer to the
thumb), d-pad uses align-self: flex-start to hug the dock top (closer
to the board). Movement is the primary interaction; UNDO/RESET are
secondary. Shrink arrows 56 -> 48px (still well above 44pt HIG) and
bump grid gap 4 -> 6px.

Trim the JS computeTileSize coarse-pointer chrome margin 220 -> 195 to
reflect the slimmer dock so the board can grow into the freed space on
tall puzzles. Add an explicit margin-top: auto on .board inside a
coarse media query in Board.svelte as a belt-and-suspenders cross-axis
push that survives future parent-flex refactors.

Combined with the round-1 changes, the board-bottom to arrow-top
distance drops from ~42px to ~10px on touch viewports. Desktop is
untouched (all changes gated by @media (pointer: coarse)).
2026-04-28 23:11:43 +07:00
tiennm99 f1a60f973f fix(mobile): tighten board-to-dock spacing on touch devices
Short puzzles (e.g. Microban 1) showed ~300px of invisible dead space
between the board and the on-screen D-pad because <Board> is fixed-pixel
and rendered top-left inside a flex-grown wrapper. Anchor .board-wrap
contents to flex-end on coarse pointers so the gap collapses against
the dock instead of below the board. Also drop the redundant 24px #app
bottom padding (the dock already pads safe-area-inset-bottom) and
tighten the .game flex gap from 16px to 10px on coarse pointers.

All changes gated behind @media (pointer: coarse). Desktop layout is
untouched.
2026-04-28 22:33:06 +07:00
tiennm99 d5b8aeff27 fix(footer): keep desktop footer visible during gameplay
Previously hid the footer on the game view to avoid overlap with the
on-screen D-pad on mobile. Render it on every view now and only suppress
it via @media (pointer: coarse) when in-game, so desktop always shows
the credit line while touch devices keep the mistouch-safe behavior.
2026-04-28 21:45:23 +07:00
tiennm99 c80b9cedb6 fix(mobile): kill footer mistouch and dock/board overlap
Footer link was overlapping the on-screen D-pad and action stack tap
zones during gameplay. Hide the footer on the game view and respect
safe-area-inset on the screens where it stays.

MobileControls drops position:fixed and lives in a flex row at the
bottom of GameView's flex column. .board-wrap is now flex:1 1 auto
with min-height:0 so puzzles scroll inside their wrapper, never under
the controls. Magic max-height:calc(100vh - 260px) gone.

RESET on mobile is now two-tap armed (label flips to TAP AGAIN, red
background, 2s timeout) so a stray thumb cannot wipe a puzzle. The R
keyboard shortcut is unchanged.

D-pad arrows fire on pointerdown and auto-repeat at 130ms while held,
matching the keyboard REPEAT_MS. preventDefault on pointerdown blocks
the synthesized click double-fire. $effect cleanup clears intervals
on unmount.

Armed RESET text switched to white (4.85:1 on --danger) to clear
WCAG-AA; using --bg failed at 3.1:1.
2026-04-28 11:09:57 +07:00
tiennm99 0570ca75f8 refactor: dedup dialog CSS and tighten focus management
- DonateModal: focus the dialog on open so screen readers announce it
  and Tab cycles within; restore focus to the previously-active element
  on close.
- Move shared overlay/dialog scaffolding to app.css so GameView's win
  dialog and DonateModal stop duplicating the same backdrop and frame.
- Move touch-action and tap-highlight neutralization to a global
  button rule; drop the per-component copies.
- level-parser: collapse the key/cellKey alias - export cellKey
  directly.
- BoardModel.isSolved: inline the empty-box-set guard into the return.
- LevelSelectView: completedCount is read once and never reassigned;
  drop the $state wrapper.
- Board: hoist the wall-trim DIRS array out of $derived so it isn't
  rebuilt every reactive recomputation.
2026-04-27 21:03:22 +07:00
tiennm99 dff4ea9eab fix: address review-pass findings
- GameView: ignore game keys (Esc/R/U/Z) while the donate modal is open
  so its own Escape no longer doubles as a navigate-to-levels.
- GameView: tag the win dialog with role/aria-modal/aria-labelledby so
  screen readers announce it correctly.
- LevelSelectView: read the progress snapshot once per page render
  instead of two localStorage parses per visible tile (40 reads -> 1).
- progress-store: add snapshot() method for batch reads.
- vite-plugin-pwa: include jpg in the workbox precache glob so the
  donation QR keeps working offline.
2026-04-27 20:51:37 +07:00
tiennm99 bb720327a0 feat(mobile): comfort overhaul - D-pad, gesture blocking, haptics
Add on-screen D-pad (bottom-right) and action stack (UNDO / RESET /
LVLS, bottom-left), shown only on coarse-pointer devices. Hide the
duplicated header actions on touch.

Block iOS/Android browser quirks: pull-to-refresh, double-tap zoom,
long-press selection and callout. Game stays inside the viewport via
overscroll-behavior and per-element touch-action rules; viewport zoom
is left enabled for accessibility.

Add navigator.vibrate wrapper (haptics.pulse) and fire short pulses on
box push and longer pulse on level solve. Silent no-op where the API
is missing.

Reserve room for the bottom controls in computeTileSize on coarse
pointer; raise minTile to 16 for legibility on small phones.
2026-04-27 20:37:27 +07:00
tiennm99 dc1d51c253 feat: add donate QR modal and site footer (#6)
Menu exposes a Donate button; the win dialog offers a Buy Me a Coffee
link. Both open a shared modal showing the VietQR code from
public/assets/qr.jpg. A persistent footer credits miti99.com.
2026-04-18 21:54:20 +07:00
tiennm99 4d69ef979d fix: prevent left-side clipping on large boards
Replace flex centering on scroll container with margin auto on the
board element so overflowing boards remain fully scrollable.
2026-04-12 09:47:09 +07:00
tiennm99 d46ccb6d50 fix: prevent large levels from overflowing the viewport
Add scrollable overflow to board wrapper, use safe centering to avoid
top-cropping, and lower minimum tile size from 16px to 10px.
2026-04-12 09:35:28 +07:00
tiennm99 8a3d4b4a9d feat!: rewrite on Svelte 5, drop Phaser
Replace Phaser 3 with Svelte 5 as the rendering and UI layer. The
framework-agnostic core (level parser, board model, progress store,
microban level data) moves from src/game/core → src/lib/core with zero
code changes. Scenes and the hand-rolled button factory are gone; in
their place:

- src/App.svelte            root router (menu / levels / game)
- src/views/MenuView        title + play + progress + hints
- src/views/LevelSelectView paginated 5x4 grid with native <button>s
- src/views/GameView        owns BoardModel, handles input, HUD, win
- src/views/Board           purely presentational DOM renderer
- src/views/AppButton       shared themed wrapper for native <button>
- src/app.css               Nord palette ported to CSS variables

GameView uses a non-reactive BoardModel ref and syncs plain snapshot
fields (player, boxes, moves, won) into $state after every mutation —
Board consumes only plain props, so Svelte reactivity stays predictable
and the core class stays framework-agnostic. GameView is keyed on
levelIndex in App, so changing level remounts with fresh state.

Native <button> everywhere kills the click-hitbox class of bugs.
Animations are now CSS transform transitions (110ms) instead of tweens.

Bundle shrinks from ~1.5 MB Phaser to ~65 kB JS / 23 kB gzipped — about
60x smaller. Removed: phaser, terser, src/game, log.js (analytics
ping), phasermsg vite plugin, manual Phaser chunks, terser config,
public/style.css. Scripts simplified to dev/build.

Docs updated: codebase summary, architecture, code standards,
changelog, roadmap, README.
2026-04-12 00:50:46 +07:00
tiennm99 2ee7ac886a feat(levels): ship the full 155-puzzle Microban set
Expand the level data from the first 100 Microban layouts to the complete
set, up through the two oversized finale mazes ("Take the long way home"
and "The Dungeon"). Responsive tile sizing already handles the large
grids — no rendering changes needed.

Update credit language in README, PDR, changelog, roadmap, codebase
summary, menu subtitle, and the data file header to reflect the full
set. Credit for David W. Skinner's layouts remains prominent; ownership
of the engine, UI, parser, and progression code stays explicit.

Verified all 155 layouts parse cleanly, have a player, matching box/goal
counts, and none start already solved.
2026-04-11 23:36:34 +07:00
tiennm99 97d43d0d57 docs: scope Microban credit to level layouts only
Reword README, PDR, menu scene, and the data file header so the Microban
credit clearly covers only the puzzle layouts borrowed from David W.
Skinner. The parser, renderer, UI, and progression code are original, and
ownership is now stated explicitly. Attribution for Skinner is preserved
everywhere it appeared.
2026-04-11 22:55:07 +07:00
tiennm99 34eaf1b2a7 fix(ui): make buttons reliably clickable
Remove the scale-on-press animation from the shared button factory. Scaling
the container also scaled its hit rectangle, so any small pointer drift
between pointerdown and pointerup landed outside the shrunken hitbox and
the click was silently dropped. Releasing off the button also left it
visually stuck at 0.96 scale.

Fire the callback on pointerdown instead — snappy, no release tracking,
identical behaviour for mouse, touch, and fast clicks. Hover colour change
still provides press feedback.
2026-04-11 22:55:02 +07:00
tiennm99 1d2fff6bf8 feat: ship 100 Microban levels with overhauled UI and modular core
Replace the 3 hand-crafted JSON levels with the full Microban set (100 solvable
puzzles by David W. Skinner) stored as XSB text and parsed at runtime. Rework
the game into kebab-case modules under a 200-LOC budget:

- core/level-parser: XSB parse with flood-fill interior detection
- core/board-model: pure move/undo/win logic, Phaser-free
- core/progress-store: localStorage persistence with graceful fallback
- core/theme: Nord palette, fonts, responsive tile sizer
- ui/button-factory: one rounded button impl with hover/press
- ui/board-renderer: animated tile/wall/goal/box/player drawing
- scenes: menu, paginated level select (5x4 grid, 5 pages), game

Add WASD movement, U/Z undo, R restart, Esc to menu, live move counter,
best-move tracking per level, win overlay with next/levels actions, and a
radial-gradient CSS backdrop.

Drop the dead Arcade Physics wiring, the broken manual shutdown/destroy code,
the unused main.js self-import, and the hardcoded 3-level registry state.

Add docs/ and refresh README.
2026-04-11 21:58:00 +07:00
tiennm99 0fe4e7cbcf fix: not response after change level 2025-04-27 11:51:49 +07:00
tiennm99 d0140de19a refactor: use default format from vite web bundler 2025-04-27 11:46:30 +07:00
tiennm99 b7a38ce3da fix(pages): load levels 2025-04-27 00:57:51 +07:00
tiennm99 361f558e19 feat: init 2025-04-26 23:07:35 +07:00