round 4: app-shell quiz screen at <=700px — #step-body is the only scroller, #btn-next/#panel become the static bottom cluster; revert FR .opt override (keep 972f3a7); + capture/verify/pixdiff/offlan audit tooling and before/after evidence; closes QUIZ-APPSHELL-FIX.md
This commit is contained in:
1 parent
972f3a7caa
commit
7697639fb8
84 files changed
+10743
-4
No files matched your search
@@ -0,0 +1,119 @@
|
||||
# ROUND 4 FIX: quiz-screen app-shell (mobile) — pinned CTA must never cover content
|
||||
|
||||
**Spec v1 — written 2026-10-09 by Hermes. Execution: DSH, all stages, zero questions.**
|
||||
Supersedes the round-3 FR compact-button hack (revert it). The retire-label anchor fix (commit
|
||||
`f4b5698`) stays.
|
||||
|
||||
## Defect (measured by Hermes on the live site, 2026-10-09)
|
||||
|
||||
The bilingual refactor made step headers taller. On the province step the pinned sticky CTA
|
||||
(`#btn-next`, `position:sticky; bottom: calc(var(--sheet-h) + 14px)`) now floats over the last
|
||||
row of the option grid at initial scroll:
|
||||
|
||||
| viewport | lang | #btn-next rect | last row (Nunavut) rect | coverage |
|
||||
|---|---|---|---|---|
|
||||
| 390×844 | fr | [564, 614] | [570, 613] | **~92% covered** |
|
||||
| 390×844 | en | [564, 614] | [589, 632] | ~25px of top covered |
|
||||
| 360×800 / 320×700 | both | worse | worse | see verify_round3.py output |
|
||||
|
||||
Root cause: the CTA is sticky-pinned above the fixed panel and floats over whatever content sits
|
||||
at that viewport position when the option grid is taller than the space above the fixed cluster.
|
||||
The grid's position is fixed by the content above it, so padding/font tweaks cannot fix all
|
||||
breakpoints. The correct fix is an **app-shell layout**: the quiz screen becomes a flex column
|
||||
whose only scroll container is `#step-body`; the CTA and the panel become flex children of a
|
||||
fixed bottom cluster and can never overlap content at any scroll position, width, or language.
|
||||
|
||||
## Current DOM (measured — do not restructure, it is already perfect for this)
|
||||
|
||||
```
|
||||
body
|
||||
header.hdr (h=57, sticky top)
|
||||
main
|
||||
section#scr-quiz.scr (top=69, class "scr" carries the mobile padding rule)
|
||||
div.quiz-wrap
|
||||
div.quiz-main
|
||||
div.prog (h=6)
|
||||
div.step-nav (sticky top, h=56 — Back + "1 / 14")
|
||||
div#step-body (step content: title, sub, .opts)
|
||||
button#btn-next.cta (sticky bottom ≤700px, h=50)
|
||||
aside#panel.panel (position:fixed; bottom:8px, h=218 — sibling of .quiz-main)
|
||||
```
|
||||
|
||||
**The fix is CSS-only.** `#step-body` already exists as the content wrapper; `#btn-next` and
|
||||
`#panel` are already siblings in the right places. No app.js DOM changes. (If you find you need a
|
||||
one-line app.js tweak, keep it minimal and say so in the report.)
|
||||
|
||||
## Required changes (all inside `@media(max-width:700px)`, appended after the existing block)
|
||||
|
||||
```css
|
||||
/* Round 4: app-shell the quiz screen — #step-body is the only scroll container;
|
||||
#btn-next + #panel form the fixed bottom cluster. Never overlaps content. */
|
||||
#scr-quiz.scr{display:flex;flex-direction:column;height:calc(100vh - 69px);height:calc(100dvh - 69px);overflow:hidden;padding:0}
|
||||
.quiz-wrap{display:flex;flex-direction:column;flex:1;min-height:0}
|
||||
.quiz-main{display:flex;flex-direction:column;flex:1;min-height:0}
|
||||
#step-body{flex:1;overflow-y:auto;min-height:0;padding:10px 12px 16px}
|
||||
#btn-next{position:static;margin:10px 12px 12px}
|
||||
#panel{position:static;margin:0 8px calc(8px + env(safe-area-inset-bottom,0px))}
|
||||
```
|
||||
|
||||
Notes / requirements:
|
||||
1. `height:calc(100vh - 69px)` then `height:calc(100dvh - 69px)` (dvh override for mobile
|
||||
browser chrome). 69px = 57px header + 12px offset above `#scr-quiz` (measured). If the 12px
|
||||
comes from `main` padding, that padding still applies — fine, the calc already accounts for it.
|
||||
2. **No document scroll on the quiz screen**: after the change, at 320/360/390,
|
||||
`document.documentElement.scrollHeight <= window.innerHeight + 1` on EVERY quiz step
|
||||
(all 14 single / 17 couple steps — walk them). If `main`'s padding-bottom or anything else
|
||||
leaves a few px of document scroll, fix it (e.g. `main{padding-bottom:0}` at ≤700px) WITHOUT
|
||||
changing the intro or results screens visually (verify with screenshots).
|
||||
3. `#btn-next{position:static}` — the sticky rule at line ~414 (`#btn-next{position:sticky;bottom:calc(var(--sheet-h) + 14px + …)}`)
|
||||
must be overridden (your later rule wins; same specificity, later in file). The CTA must be
|
||||
fully visible at ALL times: rect within [header bottom, panel top].
|
||||
4. `#panel{position:static}` — the fixed rule (`#panel{position:fixed;bottom:8px;…}`) must be
|
||||
overridden at ≤700px. The panel becomes the last flex child of `.quiz-wrap`. Keep its visual
|
||||
inset identical (8px sides/bottom + safe-area). The panel's internal `.panel-h` sticky rule
|
||||
becomes inert — harmless, leave it.
|
||||
5. `#step-body` gets the horizontal padding the `.scr` rule used to provide
|
||||
(`padding:10px 12px 16px`). `#scr-quiz.scr`'s own padding goes to 0 (it is `padding:10px 12px
|
||||
calc(var(--sheet-h) + 14px)` today — your `#scr-quiz.scr` id+class selector beats it).
|
||||
6. `--sheet-h` / `syncSheetH` become unused at mobile — LEAVE the JS running (harmless), do not
|
||||
rip it out.
|
||||
7. **Revert** the round-3 rule `html[lang="fr"] .opt{font-size:13px;padding:12px 4px}` (and its
|
||||
comment) from the ≤700px block — superseded; EN and FR option buttons return to identical sizing.
|
||||
8. Keep everything else: sticky header, `.prog`, `.step-nav` (Back + counter, still pinned by flex
|
||||
now — its sticky rule is inert inside the non-scrolling `.quiz-main`), scrim, sheet expand,
|
||||
swipe hints, chartbox scroll on results (results screen is NOT app-shelled — only `#scr-quiz`).
|
||||
9. Desktop (>700px) must be byte-identical: all changes are inside `@media(max-width:700px)`.
|
||||
Prove it: screenshot the desktop quiz (province step + a tall step + results) at 1280×900 in
|
||||
EN and FR BEFORE and AFTER — pixel-diff must be 0 (or explain any diff).
|
||||
|
||||
## Acceptance (DSH verifies; Hermes re-verifies independently after)
|
||||
|
||||
At 320/360/390, EN and FR, on the province step AND the interests step (tall):
|
||||
1. `#btn-next` fully visible (rect.top ≥ header bottom, rect.bottom ≤ panel top) at scroll
|
||||
positions 0 / mid / max of `#step-body`.
|
||||
2. `#btn-next` never overlaps `#panel` (next.bottom ≤ panel.top + 1) or any `.opt`.
|
||||
3. At max scroll of `#step-body`, the last `.opt` is fully visible inside the scroll area
|
||||
(lastOpt.bottom ≤ stepBody.rect.bottom − 4).
|
||||
4. No document scroll on any quiz step (see 2 above).
|
||||
5. Panel fully visible; 4 chips + insight readable; sheet expand/scrim still works (tap panel-h,
|
||||
scrim appears, tap scrim, collapses).
|
||||
6. Back button + "1 / 14" counter visible at top; progress bar visible.
|
||||
7. Tests green: engine 36/36, i18n 48, smoke 56.
|
||||
8. Desktop 1280×900 EN+FR quiz + results screenshots pixel-identical to the pre-change baseline
|
||||
(baseline taken by Hermes 2026-10-09 into `audit/appshell-baseline/`).
|
||||
9. Deploy `DEPLOY-OK`, origin marker = 1; off-LAN (Midhurst troublebox) 200 + marker.
|
||||
10. Save before/after screenshots + measurements to `audit/appshell-{before,after}/`.
|
||||
|
||||
## Process
|
||||
|
||||
0. Preflight: clean tree on top of `f4b5698`; tests green; take the desktop baselines yourself
|
||||
too (1280×900 EN+FR quiz province + interests + results) into `audit/appshell-baseline/`
|
||||
(Hermes also keeps copies in scratch — compare against yours).
|
||||
1. Apply the CSS (one new media block + revert the FR rule). CSS-only.
|
||||
2. Tests + local verification battery (acceptance 1-7 at 320/360/390 EN+FR).
|
||||
3. Deploy + origin/off-LAN checks + screenshots → `audit/appshell-after/`.
|
||||
4. Commit + push; write `QUIZ-APPSHELL-REPORT.md` (root cause, geometry before/after, acceptance
|
||||
evidence, desktop pixel-diff result, any deviation).
|
||||
5. If anything fails twice, stop, keep the last good deploy live, report the blocker.
|
||||
|
||||
**Your FINAL output line must be exactly: `QUIZ_APPSHELL_DONE`**
|
||||
Reference in new issue
Block a user