71 Commits
Author SHA1 Message Date
tiennm99 209ad9103b feat: add hand-designed bonus levels after the Microban set
Bonus layouts live in their own module so the license boundary stays
clean: Microban levels remain under Skinner's redistribution grant,
while the hand-designed ones fall under the repository's main LICENSE.

A new levels module concatenates both sets into one index space, so
progress-store keys and level numbering continue unbroken. Level select
tags bonus entries so they read as distinct from the stock set.
2026-09-03 12:25:48 +07:00
tiennm99 45fbd9b171 build: move from pnpm to npm
Replace pnpm-lock.yaml with package-lock.json. The security overrides move to
package.json#overrides with their range operators intact, so advisory floors
stay floors. allowBuilds becomes package.json#allowScripts where the listed
package is actually in the tree.
2026-08-17 13:15:10 +07:00
dependabot[bot] b2ce412339 build(deps): bump fast-uri from 3.1.2 to 4.1.1 (#25)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 4.1.1.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v4.1.1)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 15:26:52 +07:00
tiennm99 26e2f091da chore: remove dependabot version-update config 2026-07-25 14:14:44 +07:00
tiennm99 c8da732f96 build: move pnpm overrides to workspace config 2026-06-23 15:02:30 +07:00
dependabot[bot] a03ed3ec81 build(deps): bump actions checkout
Updates actions/checkout from v4 to v6 for the GitHub Pages deployment workflow.
2026-06-23 15:00:07 +07:00
dependabot[bot] 58c0388aa4 build(deps-dev): bump svelte and vite
Updates Svelte to 5.56.2 and Vite to 8.0.16. Resolves the open Vite Dependabot security alerts.
2026-06-23 14:59:59 +07:00
tiennm99 473b485d07 build(deps): bump vite to 8.0.14 and vite-plugin-svelte to 7.1.2 (#20) 2026-05-23 18:44:00 +07:00
dependabot[bot] 71d0c4caba build(deps-dev): bump svelte (#17) 2026-05-23 18:40:22 +07:00
dependabot[bot] 609ea79f65 build(deps): bump actions/setup-node from 4 to 6 (#16) 2026-05-23 18:08:45 +07:00
dependabot[bot] fdefafc9c5 build(deps): bump actions/configure-pages from 4 to 6 (#15) 2026-05-23 18:08:41 +07:00
dependabot[bot] 5a25c0d163 build(deps): bump actions/upload-pages-artifact from 3 to 5 (#14) 2026-05-23 18:08:37 +07:00
dependabot[bot] a66180c478 build(deps): bump actions/deploy-pages from 4 to 5 (#13) 2026-05-23 18:08:34 +07:00
dependabot[bot] 8f9fc33611 build(deps): bump pnpm/action-setup from 4 to 6 (#12) 2026-05-23 18:08:30 +07:00
dependabot[bot] 62464e8bf0 build(deps-dev): bump svelte from 5.55.5 to 5.55.7 (#10) 2026-05-23 18:08:19 +07:00
tiennm99 4dc80fb5aa chore: add dependabot config (#11) 2026-05-23 10:48:46 +07:00
tiennm99 db79c1ae45 chore(ci): bump node to 24 2026-05-13 10:52:31 +07:00
tiennm99 10998e5284 fix(ci): bump Node.js to 22 required by pnpm@11.1.1 2026-05-13 10:33:49 +07:00
tiennm99 675ff283bf fix(ci): remove pnpm version override conflicting with packageManager 2026-05-13 10:28:46 +07:00
tiennm99 6ce9fcfdf1 chore: migrate from npm to pnpm 2026-05-13 10:19:34 +07:00
tiennm99 258af0cf57 fix(deps): override fast-uri to >=3.1.2
Resolves dependabot alerts #13 and #14 (GHSA-v39h-62p7-jpjc,
path traversal + host confusion via percent-encoded segments).
fast-uri pulled transitively via vite-plugin-pwa > workbox-build > ajv.
2026-05-09 12:21:51 +07:00
tiennm99 a8f75e78c7 fix(levels): keep paginator clear of the fixed site footer
The level-select pager is the last child of .screen and lands near the
viewport bottom on shorter screens, where the position: fixed
.site-footer ("made by miti99") was overlapping it. Add a
margin-bottom of calc(48px + safe-area-inset-bottom) so the pager
always sits above the footer's hit area.
2026-04-29 00:18:07 +07:00
tiennm99 b8db785422 fix(mobile): claw back vertical/horizontal space in landscape
Tighten everything that fights the board for pixels in landscape:

- #app top padding 12 -> 6 (saves 6px vertical).
- .game gap 10 -> 6 (saves 4px between HUD and board).
- D-pad arrows 48 -> 44px and action button height 48 -> 44px (still
  above the 44pt HIG minimum). Dock width shrinks 160 -> 150px and
  internal d-pad height 102 -> 94px.
- Dock now justify-content: flex-end so the action stack and d-pad
  cluster at the bottom of the column (natural thumb arc), leaving
  empty space above instead of mid-column.

computeTileSize landscape branch: chromeY 76 -> 64, chromeX
240 -> 186 to match the slimmer chrome reservation. Net board win on
800x360 phones: ~47 -> 50 tile (+6%); on Pixel 7 landscape Microban 1
already hits the 56px cap and stays there. Tall puzzles still bound
by viewport height — no CSS-only path past that without scroll.

All changes gated behind @media (pointer: coarse) and (orientation:
landscape). Portrait and desktop untouched.
2026-04-29 00:18:07 +07:00
tiennm99 9341105f23 docs(plans): log portrait/landscape layout brainstorm 2026-04-29 00:05:01 +07:00
tiennm99 0fa7a58521 feat(mobile): portrait upward bias + landscape side-by-side layout
Portrait: add asymmetric padding-bottom: clamp(16px, 6vh, 56px) on
.play-stack so safe-center distributes the slack into the centroid,
lifting the play-group ~28px upward. Counters the perceptual "low"
feel caused by HUD top-weight + dock visual mass even though the
group is geometrically near-centered.

Landscape: flip .play-group to flex-direction: row so the dock
occupies a 160px right column instead of stacking below the board.
.mobile-dock switches to flex-direction: column with
justify-content: space-between (action stack on top, d-pad at bottom
for the natural two-handed thumb arc). On a Pixel 7 landscape
viewport this lifts Microban 1's tile size from 19px to 56px (3x
bigger) and brings the giant finale levels back into a playable
range.

computeTileSize now branches on orientation: landscape uses
chromeY=76 (HUD only) and reserves 160px of width for the side dock;
portrait keeps chromeY=295 (HUD + in-flow dock); desktop unchanged.
Orientation matchMedia listener wired into the existing pointer
listener so rotation reflows the tile budget live.

Touch targets unchanged (48px arrows, 48px action buttons). All
recent fixes (mistouch, top-aligned d-pad, LVLS grid, play-group
center) preserved — only the OUTER flex direction flips on
landscape; internal MobileControls grids are unaffected.
2026-04-29 00:05:01 +07:00
tiennm99 e08beb0c81 docs(plans): log play-group centering design report 2026-04-28 23:48:13 +07:00
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 478931ab22 docs(plans): log round-2 mobile dpad spacing follow-up review 2026-04-28 23:11:49 +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 1530afabfd docs(plans): log mobile dpad spacing review and tightening plan 2026-04-28 22:33:12 +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 2f5b184344 docs: log mobile mistouch fix and add plan artifacts 2026-04-28 11:10:05 +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 9b55be4cfb chore(plans): drop archived plans for shipped work
Work covered by these plans is already in git history and the changelog:
sokoban-overhaul (2026-04-11), svelte-migration (2026-04-12),
mobile-comfort (2026-04-27), review-fixes-and-dep-prs (2026-04-27).
2026-04-28 11:09:44 +07:00
tiennm99 68a808ec7e chore: drop orphan Phaser-era assets
bg.png (295 KB) and logo.png (24 KB) had zero references in src,
index.html, or any CSS, but matched the workbox precache glob and
shipped to every install. Removing them drops the precache from
555 KB to 235 KB (-58%). Update the docs note.

Also add the review-fixes plan to plans/.
2026-04-27 21:15:46 +07:00
tiennm99 f0490682ab chore(deps): pin serialize-javascript via npm overrides
Workbox/vite-plugin-pwa pull in @rollup/plugin-terser, which depends on
older serialize-javascript versions with a known RCE/DoS chain.
Overriding to >=7.0.5 surgically clears the chain without dropping
vite-plugin-pwa to the 0.19.x major. npm audit now reports 0
vulnerabilities; build is unaffected (build-only deps).
2026-04-27 21:03:25 +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
dependabot[bot] 78eac20994 build(deps): bump rollup from 4.40.0 to 4.60.2 (#5)
Bumps [rollup](https://github.com/rollup/rollup) from 4.40.0 to 4.60.2.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.40.0...v4.60.2)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.60.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 21:02:16 +07:00
dependabot[bot] 254514c2a6 build(deps-dev): bump postcss from 8.5.3 to 8.5.12 (#7)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.3 to 8.5.12.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.3...8.5.12)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 20:59:13 +07:00
dependabot[bot] bc9854f16e build(deps): bump picomatch from 4.0.2 to 4.0.4 (#4)
Bumps [picomatch](https://github.com/micromatch/picomatch) from 4.0.2 to 4.0.4.
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.2...4.0.4)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 20:58:23 +07:00
tiennm99 44ba72b36a docs: housekeeping after whole-project review
- README: drop incorrect 'syncs across all devices' claim about
  localStorage progress.
- package.json: description still said Phaser 3; it's Svelte 5 now.
- Mark the two old plans as completed (work shipped long ago).
- Add the 4 review reports to plans/reports/.
2026-04-27 20:51:40 +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 fe2d98e710 docs: document mobile comfort overhaul
Update codebase summary, system architecture, changelog, roadmap and
PDR to cover the new mobile input layer, haptics, gesture blocking
and PWA. Refresh README features. Add the brainstorm, plan and review
artifacts under plans/.
2026-04-27 20:38:23 +07:00
tiennm99 374422abdd feat(pwa): full offline support via vite-plugin-pwa
Register VitePWA in the production config with autoUpdate, Nord theme
colors, /sokoban/ start_url and scope, 192/512 plus a maskable icon.
Workbox precaches the built bundle so subsequent visits run offline.

Generate matching pwa-192/512 and apple-touch-icon PNGs in public/ so
"Add to Home Screen" picks up a proper icon on iOS and Android.
2026-04-27 20:38:23 +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
dependabot[bot] a1beee1dd0 build(deps-dev): bump vite from 6.3.6 to 6.4.2 (#3)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.6 to 6.4.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 17:56:04 +07:00
tiennm99 501fc7bc76 feat: add code-server dev profile
Adds npm run dev:codeserver with Vite config tuned for code-server's
/absproxy/<port>/ route: host binding, allowedHosts from env, and HMR
over wss on clientPort 443. Host/port read from .env.local (gitignored);
.env.example documents required vars.
2026-04-20 16:01:04 +07:00