# Labyrinth — Wooden Tilt Maze Game

A production-ready, dependency-free HTML5/Canvas tilt-maze game built for
`gamesji.com/labyrinth`. Pure HTML5, CSS3 and vanilla ES6 — no frameworks,
no CDNs, no build step.

## v2 changes (fixes from testing feedback)

1. **Gyroscope not moving the ball / 📱 icon doing nothing** — the most
   likely cause is that the page was opened over `file://` or plain `http://`.
   Modern mobile browsers only deliver real device-orientation data over a
   **secure origin (HTTPS, or localhost)** — over an insecure origin they
   silently send nothing, with no error. `js/ui.js` now detects this: if no
   real sensor data arrives within ~2 seconds of enabling, it automatically
   reverts to the joystick and shows a clear message explaining why, instead
   of leaving the ball unresponsive with no explanation. **Test this over
   HTTPS (or `localhost`) for gyroscope to actually work.**
2. **About section split out** — moved to its own page, `about.html`. The
   game page (`index.html`) now contains only the game.
3. **Joystick not working** — this was a real bug, now fixed: the joystick's
   on-screen graphic was using two centering methods at once (a CSS margin
   *and* a transform) which fought each other, and its drag position wasn't
   converted into the game viewport's own coordinate space. Both are fixed
   in `css/style.css` and `js/ui.js`.
4. **Fit-to-screen / responsive in both orientations** — the board no longer
   uses a fixed aspect-ratio box (which didn't reliably match landscape
   phones). The game viewport is now a flexible full-screen shell that fills
   whatever space is available in either orientation; the canvas's internal
   fit-to-container logic (already orientation-agnostic) does the rest.
5. **Mobile/gyro icon not working** — same root cause and fix as #1, plus:
   choosing "Gyroscope" from the Settings radio previously only flipped a
   setting without actually starting the sensor listener - both the icon and
   the Settings option now go through the same controller.
6. **Ad space** — down to a single placement: a full-screen interstitial
   (`#interstitial-ad` in `index.html`) that `js/ui.js` shows every 5 minutes
   of session time, pausing the game underneath. See "The single ad
   placement" below for an important caveat on the "every 5 minutes" part.

## What's in the box

```
labyrinth/                     ← upload this whole folder so it serves at /labyrinth/
├── index.html                  game page ONLY - no content, no banner ads
├── about.html                   How to Play / Controls / Tips / FAQ (moved out of the game page)
├── manifest.json                PWA manifest
├── service-worker.js             offline caching (app-shell strategy)
├── css/style.css
├── js/
│   ├── physics.js               gravity-via-tilt, friction, collision, sliding/bounce
│   ├── levels.js                 100-level procedural maze generator (see below)
│   ├── audio.js                  all sound synthesized with Web Audio - no sound files
│   ├── game.js                   game loop, rendering, save/load
│   └── ui.js                     DOM wiring: buttons, keyboard, touch, gyroscope, interstitial ad
├── icons/                        favicon, app icons, maskable icon (generated, original)
├── og-image.png                   social preview image (1200×630)
└── homepage-card-snippet.html     reusable game-card markup for your homepage/listing pages

site-root-files/                ← do NOT put these inside /labyrinth/ - see below
├── robots.txt
└── sitemap.xml
```

## Deploy in three steps

1. Upload the **contents** of `labyrinth/` to your server so the game is
   reachable at `https://gamesji.com/labyrinth/`.
2. Merge `site-root-files/robots.txt` and `sitemap.xml` into your **domain
   root** (`gamesji.com/robots.txt`, `gamesji.com/sitemap.xml`) — a
   `robots.txt` or `sitemap.xml` placed inside `/labyrinth/` is invisible to
   crawlers, since both files are only ever read from the site root. If you
   already have either file, add the `<url>` entry / `Sitemap:` line into
   your existing one rather than overwriting it.
3. Serve over **HTTPS**. This is not optional for full functionality — the
   gyroscope, the install prompt, and the service worker all silently do
   nothing over an insecure origin. `localhost` also counts as secure for
   local testing, but a plain IP address or `file://` does not.

## The single ad placement

The brief asked for one ad space, shown full-screen every 5 minutes. Two
things worth knowing:

- **What's built**: `js/ui.js` has a session timer that shows
  `#interstitial-ad`, pauses the game, holds for 5 seconds (standard
  interstitial practice - not instantly skippable), then enables a Continue
  button. It repeats every 5 minutes. This part works today with no ad
  network at all.
- **What still needs your ad network**: standard Google AdSense `<ins>`
  units don't have a built-in "fire every 5 minutes" trigger - that timing
  is exactly what the code above provides. Drop your interstitial/Ad
  Manager creative (or another ad network's interstitial call) inside the
  `.ad-placeholder` div in `index.html`, in place of the placeholder text.

## Levels: procedurally generated, not 100 hand-drawn files

Rather than shipping 100 hand-authored JSON layouts (slow to build well,
easy to introduce an unsolvable level by hand), `levels.js` hand-designs
the *system*: a seeded maze algorithm guarantees every layout is fully
connected (solvable) by construction, and a tier-based ruleset controls
which mechanics appear, how dense they are, and how tight the passages
get, as difficulty rises:

| Tier | Levels | Introduces |
|---|---|---|
| Easy | 1–20 | walls, dead ends, long corridors only |
| Medium | 21–40 | holes, ice, speed pads, checkpoints |
| Hard | 41–60 | + spikes, sticky floor, wind, moving obstacles |
| Expert | 61–80 | + switches, timed gates, keys/doors, rotating barriers |
| Insane | 81–100 | + teleport portals, largest boards, tightest passages |

Every level id always regenerates the exact same layout (it's seeded), and
gated levels (switch/key + gate) are constructed so the switch or key is
always reachable without crossing its own gate. All 100 levels pass an
automated connectivity + invariant check (dev-only test scripts, not shipped).

## Configuring "Related / Popular / Newest Games"

`homepage-card-snippet.html` has the card markup and styling, but the
actual related/popular/newest game data isn't something this deliverable
can know — it depends on your real catalog. The snippet has one example
card for wiring reference; swap it for a loop over your real games rather
than shipping it as-is, so you don't ship a dead link.

## Regenerating the icons

Icons are original, generated art (wood-case + metallic-ball motif, no
copied logos or box art) — not photos or third-party assets. If you want
to change the color scheme or motif, regenerate with Pillow:
`pip install Pillow` then adapt the drawing code (kept simple on purpose
so it's easy to re-theme).

## Browser support & known limitations

- **Gyroscope needs HTTPS (or localhost).** See the v2 note at the top -
  this is almost certainly why it looked broken during testing.
- **Audio**: browsers block sound until the first tap/click/keypress —
  this is standard browser policy, not a bug.
- **Accessibility**: all HUD controls, sliders, and modals are real,
  keyboard- and screen-reader-accessible DOM elements. The canvas gameplay
  itself (a real-time physics/motor task) has an `aria-label` describing
  the goal and controls, but real-time visual ball-rolling gameplay is not
  fully accessible to screen readers — this is an inherent limitation of
  the genre, not something this build glosses over.
- Tested logic against Node (maze connectivity, gate reachability, physics
  stability). Full cross-browser device QA (actual gyroscope hardware,
  various touchscreens/notches) still needs a real HTTPS-hosted device pass.

## License

Defaults to all-rights-reserved for GamesJi.com — see `LICENSE` for the
one-line rationale and how to swap it for an open-source license instead.
