diff --git a/apps/web/package.json b/apps/web/package.json index b7afaa7..d0629a6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -25,6 +25,7 @@ "@angular/cli": "22.0.9", "@angular/compiler-cli": "22.0.8", "jsdom": "^28.0.0", + "playwright": "^1.62.1", "prettier": "^3.8.1", "typescript": "6.0.3", "vitest": "^4.0.8" diff --git a/apps/web/src/app/features/world/world-page.component.scss b/apps/web/src/app/features/world/world-page.component.scss index 019a46a..2a3db5d 100644 --- a/apps/web/src/app/features/world/world-page.component.scss +++ b/apps/web/src/app/features/world/world-page.component.scss @@ -11,7 +11,7 @@ .world-page__scene { position: relative; isolation: isolate; - min-block-size: clamp(30rem, 67vh, 44rem); + min-block-size: clamp(20rem, calc(100dvh - 25rem), 44rem); overflow: hidden; border: 1px solid var(--ar-border); background-color: #151718; diff --git a/apps/web/src/app/features/world/world.store.ts b/apps/web/src/app/features/world/world.store.ts index 9c2a81b..04343bc 100644 --- a/apps/web/src/app/features/world/world.store.ts +++ b/apps/web/src/app/features/world/world.store.ts @@ -241,6 +241,6 @@ export class WorldStore implements OnDestroy { } private toErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : 'Unable to load world state.'; + return error instanceof Error ? error.message : 'Weltzustand konnte nicht geladen werden.'; } } diff --git a/docs/verification/first-visible-slice-1366x768.png b/docs/verification/first-visible-slice-1366x768.png new file mode 100644 index 0000000..a6f17ec Binary files /dev/null and b/docs/verification/first-visible-slice-1366x768.png differ diff --git a/docs/verification/first-visible-slice-1440x900.png b/docs/verification/first-visible-slice-1440x900.png new file mode 100644 index 0000000..fc8c96d Binary files /dev/null and b/docs/verification/first-visible-slice-1440x900.png differ diff --git a/docs/verification/first-visible-slice-1920x1080.png b/docs/verification/first-visible-slice-1920x1080.png new file mode 100644 index 0000000..0ad1072 Binary files /dev/null and b/docs/verification/first-visible-slice-1920x1080.png differ diff --git a/docs/verification/first-visible-slice-fidelity.md b/docs/verification/first-visible-slice-fidelity.md new file mode 100644 index 0000000..f645614 --- /dev/null +++ b/docs/verification/first-visible-slice-fidelity.md @@ -0,0 +1,180 @@ +# First Visible Vertical Slice — Fidelity Ledger + +Reference: `docs/references/world-travel-screen.png` (concept mock, Graufurt hub with +full RPG chrome: currencies, mail/friends/settings icons, "Shop" nav item, region +detail panel with encounters/rewards, "Gebiet erkunden" action). + +Renders (this task, post-fix): `docs/verification/first-visible-slice-1920x1080.png`, +`docs/verification/first-visible-slice-1440x900.png`, +`docs/verification/first-visible-slice-1366x768.png`. All three captured with +Playwright/Chromium at `http://localhost:4200/world`, live API + seeded Postgres, +with Verbrannte Straße selected as the travel target (matches the reference's +state: travel panel populated). + +Scope reminder: the reference is a full-game concept mock. This slice implements +only the demo character, two locations, and travel — currencies, mail/social, +settings, Shop, encounters/rewards, and "Gebiet erkunden" exploration are +out of scope by design (see Task 1–10 rulings) and are recorded below as +deliberate deviations, not defects. + +## Comparison points + +### 1. Shell proportions and persistent regions + +- Reference: top bar (portrait, name/level, HP bar, currencies, icon row) + + left icon nav (Karte/Jagd/Quests/Inventar/Charakter/Shop) + center map + + right context panel + footer (chat/system line, world/online/time). +- Render: top bar (portrait, name/level, HP bar, brand wordmark) + left nav + (Karte enabled; Jagd/Quests/Inventar/Charakter present but disabled, + `aria-label="… ist noch nicht verfügbar"`) + center map + right + "Gebietsinfo" panel + footer (status dot + text, ornament, region name). +- Result: structural grid matches (top bar / nav+main+context / footer). + Currency row, mail/friends/settings/logout icons, and "Shop" nav item are + absent — deliberate, no such systems exist yet. No fix needed. + +### 2. Artwork dominance and crop + +- Reference: large painterly Graufurt gate artwork fills the map region, + `background-size: cover`-style dominant visual, quiet areas reserved for + path/nodes/panel. +- Render: `.world-page__scene` uses the project's own generated + Südtor/Aschenstraße artwork (`/images/backgrounds/Suedtor.png` / + `Aschestrasse.png`), `background-size: cover`, same compositional role. + Visible and readable at all three captured sizes. +- Result: matches intent. Different concrete artwork (project-specific, + generated per the style guide) is expected, not a defect. + +### 3. Typography hierarchy and control typography + +- Reference: large serif location title overlaid top-left of the map, green + safety subtitle, short descriptive paragraph in the same overlay; serif + headers in the side panel. +- Render: serif (`Georgia`) location title top-left of the map + (`.world-page__location-title`); description/safety/level facts live in + the right "Gebietsinfo" panel instead of a second overlay block on the + map. Control typography (buttons, dl labels) uses the app's sans body + font with small-caps-style eyebrows ("REISEN", "GEBIETSINFO"). +- Result: title hierarchy matches (serif, large, top-left). The map-overlay + vs. side-panel placement of description text is an established + information-architecture choice from earlier tasks (Task 8/9), not a + regression introduced here — left as a recorded deviation, not fixed, + since re-architecting panel content is outside this task's evidenced-defect + scope. + +### 4. Dark palette, bronze borders, and blue/current state + +- Reference: anthracite/black panels, bronze/gold hairline borders, blue ring + on the current-location node, gold ring + gold path on selected/target. +- Render: `--ar-bg`/`--ar-panel` dark palette, `--ar-border`/ + `--ar-border-highlight` bronze/gold borders on top bar, panels, and travel + box; `.location-node--current` renders a blue marker ring (confirmed in all + three captures, "Aktueller Ort" label green), selected/target node renders + gold ring + gold dashed path (confirmed, "Ausgewähltes Ziel" label gold). +- Result: matches the style guide's colour semantics (blue = current/active + navigation, gold = selection/important action). No fix needed. + +### 5. Node/path legibility + +- Reference: node names + short state label beneath each node, dashed gold + connecting path. +- Render: same pattern — `location-node__name` + `location-node__state` + ("Aktueller Ort" / "Ausgewähltes Ziel" / "Erreichbar"), dashed gold SVG + path between the two nodes. Legible with sufficient text-shadow contrast + against the background artwork at all three captured sizes. +- Result: matches. No fix needed. + +### 6. Travel panel placement and action prominence + +- Reference: travel panel overlays the bottom edge of the map itself, as a + floating box, with a full-width primary blue button. +- Render: `.world-page__travel-panel` is a separate block directly beneath + the map (not overlaid on top of it), centered, `min(29rem, 100%)` wide, + with a full-width primary button (`Reise beginnen` / `Zur Verbrannte + Straße reisen?`) using the gold-bordered, blue-tinted button style. +- Result: placement differs (below vs. overlaid on the map) — an established + layout choice from earlier tasks, not introduced or regressed by this + task. Action prominence (full-width primary button, clear label, disabled + state while travelling) matches reference intent. Not treated as a + material mismatch requiring a redesign in this verification-only task. + +### 7. Context-panel information density + +- Reference: region title, recommended level, "Mögliche Begegnungen" icon + row, description, "Mögliche Belohnungen" icon row, "Gebiet erkunden" + button. +- Render: "GEBIETSINFO" eyebrow, location name, selected-target note, + artwork thumbnail, description paragraph, facts list (recommended level / + safety / hunting availability). +- Result: reduced density is intentional — encounters, rewards, and area + exploration are later-gameplay systems explicitly out of scope for this + slice (per the plan's deliberate limitations). Not a defect. + +### 8. Responsive behaviour at all three sizes + +- Evidence (pre-fix): measured via a Playwright DOM probe — + `document.documentElement.scrollHeight` vs. `window.innerHeight`: + - 1920×1080: docHeight 1098 vs. viewport 1080 (18px overflow; footer's + bottom 18px clipped below the fold). + - 1440×900: docHeight 997 vs. viewport 900 (97px overflow; travel button + and footer entirely below the fold). + - 1366×768: docHeight 909 vs. viewport 768 (141px overflow; travel button + and footer entirely below the fold, ~half the travel panel cut off). + - Root cause: `.world-page__scene { min-block-size: clamp(30rem, 67vh, + 44rem); }` sized the map purely off viewport height without accounting + for the fixed chrome around it (top bar + paddings + travel panel + + footer ≈ 395px), so the three required desktop sizes all overflowed the + viewport and pushed the footer (and, at the two smaller sizes, the + travel button) out of view without scrolling. + - **Fixed** in `apps/web/src/app/features/world/world-page.component.scss`: + changed to `min-block-size: clamp(20rem, calc(100dvh - 25rem), 44rem)`, + which sizes the map to the remaining space after the known chrome height + (with a small safety margin), while keeping the existing 20–44rem floor + and ceiling. + - Evidence (post-fix), same probe: 1920×1080 → 1080 vs. 1080 (0 overflow); + 1440×900 → 900 vs. 900 (0 overflow); 1366×768 → 768 vs. 768 (0 + overflow). Re-captured screenshots (see paths above) show the top bar, + nav, map with both nodes, full travel panel including the primary + button, context panel, and footer all fully visible with no clipping at + all three sizes. +- Result: **material mismatch found and fixed.** + +## Above-the-fold copy diff + +Every visible string across all three renders was checked against the +brief's allowed-copy categories (API content, requested navigation labels, +travel labels, restrained system/footer text): + +| Text | Source | Category | +|---|---|---| +| "Aric Duskwalker", "Stufe 1", "100 / 100" | API (`/api/characters/me`) | API content | +| "Südtor von Graufurt", "Verbrannte Straße", description paragraph, "1–1", "Sicherer Ort", "Keine Jagd" | API (`/api/world/current-location`) | API content | +| "Verbrannte Straße", "00:00:10", "Niedrig" | API (`/api/world/current-location` connections) | API content | +| "Karte", "Jagd", "Quests", "Inventar", "Charakter" | side nav | requested navigation labels | +| "REISEN", "Zur … reisen?", "Ziel", "Reisezeit", "Gefahr", "Reise beginnen", "Ankunft in", "REISE LÄUFT", "Reise läuft", "Wähle einen erreichbaren Ort auf der Karte." | travel panel | travel labels | +| "GEBIETSINFO", "Ausgewähltes Ziel: …", "Empfohlene Stufe", "Sicherheit", "Jagd", "Aktueller Ort", "Ausgewähltes Ziel", "Erreichbar" | context panel / node labels | restrained system text | +| "Verbindung bereit", "Aschenfelder", "Ashen Realms" | footer / top bar | restrained system/footer text | +| "Weltzustand wird geladen…", "Weltkarte wird vorbereitet.", "Erneut versuchen" | loading/empty states | restrained system text | +| "Unable to load world state." | error fallback (`world.store.ts`) | **violation — English string in an otherwise fully German UI** | + +**Fixed:** `apps/web/src/app/features/world/world.store.ts`, +`toErrorMessage()` fallback changed from `'Unable to load world state.'` to +`'Weltzustand konnte nicht geladen werden.'`, matching the German tone/case +used everywhere else in the app (e.g. `"Weltzustand wird geladen…"`). +Verified live: killed the API process, reloaded `/world`, confirmed the +in-shell error box now reads "Weltzustand konnte nicht geladen werden." with +no `window.alert()` dialog fired (Playwright `page.on('dialog', …)` listener +recorded zero dialogs across every step of the verification pass). + +No invented, promotional, or placeholder copy was found anywhere in the +above-the-fold content. + +## Summary + +One functional/visual defect found and fixed (responsive overflow/clipping +at all three required desktop sizes — item 8). One copy defect found and +fixed (English fallback error string — copy diff table above). All other +comparison points either matched the reference's visual language directly, +or reflect deliberate, already-ruled-on scope reductions from earlier tasks +(no currencies/social/settings/Shop, reduced context-panel density, no +map-overlay description block, travel panel placed below rather than over +the map) that this verification-only task did not redesign. diff --git a/package-lock.json b/package-lock.json index 9c508d7..ea0483e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -758,6 +758,7 @@ "@angular/cli": "22.0.9", "@angular/compiler-cli": "22.0.8", "jsdom": "^28.0.0", + "playwright": "^1.62.1", "prettier": "^3.8.1", "typescript": "6.0.3", "vitest": "^4.0.8" @@ -18472,6 +18473,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",