---
phase: 6
slug: client-rebuild-mvp-gate-cli-08-hard-milestone
status: approved
shadcn_initialized: false
preset: none
created: 2026-05-09
reviewed_at: 2026-05-09
---

# Phase 6 — UI Design Contract

> Visual and interaction contract for the Phase 6 client rebuild (CLI-08 hard milestone). Generated by gsd-ui-researcher; verified by gsd-ui-checker.

**Surface inventory (CONTEXT.md verbatim — pre-locked):**

| Surface | Type | Owns |
|---------|------|------|
| `BootScene` | Phaser scene | Asset preload, `GET /api/auth/me` probe, route to `LoginScene` |
| `LoginScene` | Phaser scene + DOM `<form>` overlay (via `Phaser.GameObjects.DOMElement`) | Username / password form, fast-path spinner, inline error |
| `GameScene` | Phaser canvas | `mvp-lobby` tilemap, local player, remote players, nameplates |
| `ChatHUD` | Plain DOM `<div>` absolutely positioned over the canvas | Message log, input field (Enter/T toggle), chat-mode indicator |
| `Nameplate` | Phaser canvas `Text` per remote player + hidden `<div data-nameplate>` mirror for Playwright | Username label above remote sprite |
| `ReconnectBanner` | Plain DOM `<div>` top-of-viewport | "Reconnecting…" yellow banner; "Disconnected — click to retry" failure state |
| `ForceResetOverlay` | Plain DOM `<form>` modal-style | In-room password change after `s2c.force_password_change` (movement frozen, no disconnect) |

**Phase 6 contains zero admin UI** (CLAUDE.md Hard Rule #3 — admin moves to Phase 7 PAR-07 separate web app).

---

## Design System

| Property | Value |
|----------|-------|
| Tool | none |
| Preset | not applicable |
| Component library | none — Phaser 3.90.0 + raw DOM overlays |
| Icon library | none in MVP — Phase 7 PAR-07 admin UI may introduce one separately |
| Font (in-canvas Phaser text) | `monospace` system stack (`ui-monospace, "SF Mono", Menlo, Consolas, monospace`) |
| Font (DOM overlays — chat, banner, login form, force-reset) | `monospace` system stack (matches in-canvas text for retro consistency) |
| Pixel-art rendering | `pixelArt: true`, `roundPixels: true`, `Scale.FIT` + `autoRound: true` + `MAX_ZOOM`, `Phaser.Textures.FilterMode.NEAREST`, CSS `image-rendering: pixelated` (RESEARCH §D-12 / Pattern 1) |

**Rationale for "no design system":** Phase 6 ships ~5 DOM overlay components total. shadcn / Radix / Base UI assume a CRUD app surface; this is a Phaser game with thin chrome. Phase 7 PAR-07 (modernized admin web UI) is the appropriate place to introduce a design system if one is wanted — and it lives in a separate web surface, not the game client. Per AGENT instructions §`<shadcn_gate>`: when a project legitimately does not need a preset, `Tool: none` is the correct selection.

---

## Spacing Scale

Declared values for **DOM overlays only** (Phaser canvas content is governed by tile geometry: 44 × 40 px tiles). All values are multiples of 4.

| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Inline label↔input gap inside login form; chat message vertical gap |
| sm | 8px | Form field internal padding; chat input padding; banner internal padding |
| md | 16px | Chat HUD outer padding (bottom-left positioning offset); login form gap between fields; force-reset overlay outer padding |
| lg | 24px | Login form outer padding; force-reset overlay outer padding |
| xl | 32px | LoginScene DOM-form max-width breathing room (form is ~320 px wide; centered with ≥32 px viewport margin) |

**Exceptions:**
- Phaser canvas world-space uses tile geometry (44 px × 40 px tiles). This is asset-driven, not design-token driven.
- ReconnectBanner top offset = `0` (banner is flush with viewport top edge; internal padding `8px` vertical, `16px` horizontal).
- Nameplate-to-sprite gap = `4px` in canvas pixels (above sprite head, before scale).

---

## Typography

DOM overlays use exactly **3 sizes** and **2 weights**. Phaser in-canvas text matches the body size for visual continuity.

| Role | Size | Weight | Line Height | Where |
|------|------|--------|-------------|-------|
| Body | 16px | 400 (regular) | 1.5 | Chat messages, form labels, banner text, error text |
| Label / input | 16px | 400 (regular) | 1.5 | Login form inputs, chat input field, force-reset inputs |
| Heading | 24px | 600 (semibold) | 1.2 | Login form heading ("Log in to BN Online"); force-reset heading ("Set a new password") |
| Display | 28px | 600 (semibold) | 1.2 | LoginScene fast-path Phaser text ("Reconnecting as `<username>`…") |

**Fast-path Phaser text (`LoginScene` D-02 spinner)**: `monospace`, 28px, `setOrigin(0.5)` centered. RESEARCH §LoginScene already declares `fontSize: 24` — Phase 6 plan upgrades this to `28` for the Display role to match this contract.

**No more than 4 font sizes total. No more than 2 weights total.** Anything not in the table above is out of contract.

---

## Color

**60 / 30 / 10 split applied to DOM overlays only** (Phaser canvas colors are asset-driven — atlas pixels, signed-room background, never overridden). The 10% accent is **reserved for the primary CTA + chat-mode indicator + remote-player nameplate text only**.

| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `#0A0E1A` (very dark navy / near-black) | DOM overlay backgrounds — chat HUD background, login form background, force-reset overlay background, banner background-failure-state |
| Secondary (30%) | `#1F2937` (dark slate) | Form-field backgrounds, message-row hover states, button surfaces, divider lines (subtle 1px) |
| Accent (10%) | `#22D3EE` (cyan) | Primary CTA button, chat-mode-active cursor / input border, remote-player nameplate text |
| Destructive | `#EF4444` (red) | Inline error text in login form, fatal "Disconnected — click to retry" banner state, force-reset validation errors |
| Text on dominant | `#F3F4F6` (off-white) | All body / label / heading text rendered on `#0A0E1A` surfaces |
| Text on accent | `#0A0E1A` | Text rendered on top of an accent button |
| Reconnect banner background | `#F59E0B` (amber) | RECONNECTING state only — distinct from accent (which is for affirmative CTAs) and from destructive (which is for terminal failure) |
| Reconnect banner text | `#0A0E1A` | High-contrast on amber |

**Accent reserved for** (explicit list — no other element uses `#22D3EE`):
1. The "Log in" submit button on `LoginScene`.
2. The "Set new password" submit button on `ForceResetOverlay`.
3. The chat input border + caret color when chat-mode is active (`Enter` or `T` pressed).
4. Remote-player nameplate text in canvas (Phaser `Text` `color: '#22D3EE'`).
5. The "Send" affordance hint inside the chat input placeholder.

**Destructive reserved for**:
1. Inline form errors (login: "Wrong username or password.", "Server expects protocol v…", "Server is restarting…").
2. Final reconnect failure banner ("Disconnected — click to retry") — clickable to retry.
3. Force-reset overlay validation errors ("Passwords do not match", "Password too short").

**Phaser canvas world content** (tilemap, sprites, background) — **not governed by this color contract**. Asset-pipeline (D-14/D-15) ships canonical extracted pixels; the color choices live in the source `.aseprite` files and the extracted-from-original BMP sources. The contract only governs the chrome layered on top.

---

## Copywriting Contract

Every user-visible string in the Phase 6 chrome layer is locked here.

### Login screen (`LoginScene`)

| Element | Copy |
|---------|------|
| Heading | `Log in to BN Online` |
| Username label | `Username` |
| Username `autocomplete` | `username` (HTML attribute, mandatory — RESEARCH §Pattern 2 pitfall: do not strip) |
| Password label | `Password` |
| Password `autocomplete` | `current-password` |
| Primary CTA | `Log in` |
| Fast-path spinner | `Reconnecting as <username>…` (single ellipsis Unicode char `…`, not `...`) |
| Fast-path Esc hint (small text under spinner) | `Press Esc to log in as someone else` |
| Error: bad credentials | `Wrong username or password.` |
| Error: protocol mismatch | `Server expects protocol v<expected>; client sends v<got>. Reload the page.` |
| Error: server draining | `Server is restarting. Try again in 30 seconds.` |
| Error: network failure | `Cannot reach server. Check your connection and try again.` |

### Force-reset overlay (`ForceResetOverlay`, on `s2c.force_password_change`)

| Element | Copy |
|---------|------|
| Heading | `Set a new password` |
| Body | `Your account uses a legacy password. Choose a new one to continue.` |
| New password label | `New password` |
| Confirm label | `Confirm password` |
| Primary CTA | `Set new password` |
| Error: too short | `Password must be at least 8 characters.` |
| Error: mismatch | `Passwords do not match.` |
| Error: server rejected | `Could not update password. Try again.` |

The overlay is **non-dismissible** (no Cancel button — D-03). Movement keys are frozen until success.

### Chat HUD (`ChatHUD`)

| Element | Copy |
|---------|------|
| Input placeholder (chat-mode active) | `Press Enter to send, Esc to cancel` |
| Empty-state default (no messages received yet) | _no copy — log area is empty/transparent_ |
| Chat-mode indicator (small label above input) | `Chat` |
| Outgoing message render | `<sender>: <message>` (sender = own username from `auth.me`; rendered identically to remote messages so no special-cased "you") |
| Incoming message render | `<sender>: <message>` |
| Open hint (when not in chat-mode, faded after 5 s) | `Press T or Enter to chat` |
| Server-rejected message inline error | `Message rate-limited — wait a moment.` |

**No empty-state heading** — this is a live game HUD, not a CRUD list. Empty-default is silence by design (per D-04 Minecraft pattern: messages fade out after a few seconds; nothing visible when no messages).

### Reconnect banner (`ReconnectBanner`)

| State | Copy | Background |
|-------|------|------------|
| Reconnecting (auto-retry) | `Reconnecting…` | Amber `#F59E0B` |
| Reconnecting + countdown (≥ 5 s elapsed) | `Reconnecting… (<n>s)` | Amber `#F59E0B` |
| Final failure (cookie expired or server unreachable past grace) | `Disconnected — click to retry` | Destructive `#EF4444` |
| Successfully reconnected (transient flash, 1 s) | `Reconnected.` | Accent `#22D3EE` |

### Destructive actions in this phase

Phase 6 **ships no destructive game actions** (no delete-character, no admin kick, no bans — all deferred to Phase 7 PAR-07). The only "destructive-looking" surface is the **terminal-failure reconnect banner** which uses destructive red but is itself a recovery affordance, not a destructive confirmation. **No confirmation dialogs are required in Phase 6.**

---

## Registry Safety

| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none | not applicable — `Tool: none` |
| third-party registries | none | not applicable — no registries declared |

**Vetting record:** Not applicable; no design system / registry blocks introduced in Phase 6. Phase 7 PAR-07 (admin web UI) is the next opportunity to revisit this; if shadcn lands then, the registry safety gate runs in that phase's UI-SPEC.

---

## Cross-references

- **Pre-locked decisions consumed verbatim:** CONTEXT.md §Implementation Decisions D-01, D-02, D-03, D-04, D-05, D-06, D-12 — all visual / interaction surfaces are locked there.
- **Visual rendering primitives:** RESEARCH.md §Pattern 1 (Phaser HiDPI scale) and §Pattern 2 (LoginScene + DOMElement).
- **Network-state copy origins:** RESEARCH.md §Pattern 3 (Colyseus error codes); CONTEXT D-06 (banner state machine).
- **Asset color truth:** Asset pipeline (CONTEXT D-13/D-14/D-15/D-17) — extracted BMP / committed `.aseprite` source files own all in-canvas colors. This contract governs only the chrome.
- **Phase 7 hand-off:** PAR-04 expands chat HUD (channels, ignore/block, profanity wordlist, history). All Phase 7 chat additions must remain under the same color/typography tokens declared here unless Phase 7 UI-SPEC explicitly extends them.

---

## Pre-Populated From

| Source | Decisions Used |
|--------|---------------|
| CONTEXT.md (§Implementation Decisions) | 10 (D-01..D-06, D-12, D-15, D-17, D-18) |
| RESEARCH.md (§Pattern 1/2/3, §Code Examples) | 5 (font family, Phaser scale config, error copy, retro backdrop, monospace stack) |
| ROADMAP.md (Phase 6 success criteria) | 6 (login → game flow, prediction, chat HUD, asset pipeline, CLI-08 gate, reconnect grace) |
| REQUIREMENTS.md (CLI-01..CLI-09 + AST-01) | 10 acceptance bullets folded into surface inventory |
| `components.json` | not present — confirmed `Tool: none` |
| User input this session | 0 (every category answered by upstream artifacts; defaults applied where upstream silent) |

---

## Checker Sign-Off

- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS

**Approval:** pending
