RELEASE
planning/2.8.0/plans/MOBILE_DENSITY_PLAN.md
sha256 57d97a816ebd0adf · 16842 bytes ·
original held in the private archive
# MOBILE_DENSITY_PLAN.md — RobCo U.O.S.
> **PLANNING / AUDIT ONLY.** Nothing here is implemented or committed. Measure + propose.
> Target: `dev` @ `169c9fd` (Ceremony Moments Wave 1). Widths measured LIVE: **360px** and **412px**.
> Method (Protocol 27): app rendered on a **static prod-like server** (NOT Vite — Vite serves
> `terminal.css` as a `text/javascript` HMR module, so a plain `<link>` never applies it and every
> measurement reads UA defaults; all prior units used a static server for this reason). A richly
> populated FNV campaign was seeded (lvl 24, 10 inventory items, 7 perks, 6 quests, 4 status effects,
> 3 squad, 3 collectibles, factions set, 2 crippled limbs) so every board shows real content.
> All px below are `getBoundingClientRect()` / `getComputedStyle()` reads, not estimates.
---
## TL;DR — the headline
- **Every Protocol-17 floor is currently HELD** at 360px and 412px: no horizontal overflow on any of
the six subsystems, all inputs render ≥16px, tap targets ≥28px. **Density here is pure waste-trimming,
never floor-shrinking.**
- **There is no spacing-token system and no mobile density pass.** `:root` holds only *colour* tokens.
Every board's spacing (padding, inter-board gap, header subtitle, section rhythm, tile padding) uses
its **base, desktop-shared value on mobile.** The narrow-phone blocks (`@media (max-width:480px)`)
currently tune only ~8 scattered specifics (faders, one VU label, drawer min-width, curio widths,
annunciator, location card). Density is therefore **per-board-random**, not uniform.
- **The single biggest lever is the shared board chrome** — `details.bay-board` padding `10px 10px 12px`,
the `.panel.bay-board` inter-board `margin-top: 12px`, the `.bay-part-no` subtitle `margin: 2px 0 8px`,
and per-board inner section margins — multiplied across **~30 boards** app-wide. Trimming these to a
single mobile standard reclaims **hundreds of vertical px** with zero behaviour or floor change.
- **Measured scroll length at 412px (everything a populated campaign shows):**
| Subsystem | Scroll height @412px | (excl. staging-only TEST CONSOLE ~1024px) |
|---|---:|---:|
| OPERATOR (stat) | **7796px** | ~6772px |
| SETTINGS | 5646px | ~4622px |
| OPERATIONS (cargo) | 5365px | ~4341px |
| DATABANK | 4001px | ~2977px |
| CHASSIS | 2655px | ~1631px |
OPERATOR is **~7.4 phone-screens** tall. A uniform 12–18% density trim is a real, felt improvement.
**Recommendation:** adopt the **Mobile Density Standard** (§2) and apply the **Tier-1 "safe tightening"**
fix list (§3) in a single `@media (max-width:480px)` block — behaviour-identical, desktop untouched,
floors honoured. Hold the **Tier-2 "design/taste" items** (§4) for owner sign-off.
---
## 1. Architecture & constraints (what a fix must honour)
### Breakpoints in play
| Query | Meaning | Use for density |
|---|---|---|
| `min-width:1000px and (hover:hover) and (pointer:fine)` | **Desktop** (Suite 129 gate) | **NEVER touch** — desktop must stay identical |
| `max-width:999px` / `999.98px` | mobile single-column layout | too broad for a phone density pass |
| `max-width:480px` / `479.98px` | **narrow phone** (owner's 360/412 both land here) | **← the density gate** (matches existing convention) |
| `max-width:400px` | narrowest phones | reserved for extra shrink only |
**Chosen density gate: `@media (max-width: 480px)`.** Every common portrait phone (≤430px CSS) is ≤480,
it matches the existing narrow-phone blocks, and it can never leak onto desktop. The 481–999px band
(large-phone landscape / small tablets) intentionally keeps today's looser spacing — an acceptable gap,
same call the prior MOBILE_AUDIT made, since the owner is portrait-phone-only.
### Hard floors (Protocol 17) — inviolable, density never crosses these
- Focusable inputs **≥16px** font (prevents iOS/Android focus auto-zoom).
- Interactive tap targets **≥28px**.
- **No horizontal overflow** at 360px (`documentElement.scrollWidth === innerWidth`).
- Reduced-motion / a11y unaffected (spacing only — no `@keyframes`, no `:focus-visible`, no colour-only meaning).
### Other guardrails
- **Fonts are already tiny (8–13px). Density comes from SPACING, never from shrinking fonts further.**
- **Do NOT `display:none` the `.bay-part-no` / `.real-label` subtitle.** It carries the real, searchable
control label that the Protocol-25 Module-Bay redesign exception *requires* ride along under the diegetic
name. Only its *margin* may tighten.
- Protocol 22: reuse existing selectors; prefer a small token set over per-selector magic numbers.
- Behaviour-identical: this is spacing/sizing only — no id/handler/markup/state change.
---
## 2. THE MOBILE DENSITY STANDARD (propose)
Because the codebase has **no spacing tokens**, introduce a minimal scale and apply it **only at ≤480px**.
Desktop keeps its current (base) values untouched.
### 2a. Spacing scale (new `:root` tokens — mobile-valued via the density block)
```css
:root {
/* desktop / base — unchanged from today's hardcoded values */
--d-board-pad-block: 10px; /* board padding top */
--d-board-pad-btm: 12px; /* board padding bottom */
--d-board-pad-inline:10px; /* board padding L/R */
--d-board-gap: 12px; /* gap between stacked boards / .bay-grid / sub-panel */
--d-section-gap: 10px; /* gap between a board's inner sections */
--d-subtitle-mb: 8px; /* .bay-part-no bottom margin */
}
@media (max-width: 480px) {
:root {
--d-board-pad-block: 7px;
--d-board-pad-btm: 8px;
--d-board-pad-inline:8px;
--d-board-gap: 8px;
--d-section-gap: 8px;
--d-subtitle-mb: 4px;
}
}
```
> If a full token retrofit is judged too invasive for one unit, the **equivalent minimal path** is a single
> `@media (max-width:480px)` block that hard-sets the same values on the selectors in §3 (no `:root` edit,
> no base-rule refactor). Both reach the identical rendered result; the token form is the Protocol-22-clean
> version and makes future per-game/per-breakpoint tuning one-line.
### 2b. The seven rules every mobile board follows
- **R1 — Board shell.** `details.bay-board` padding = **7px / 8px / 8px / 8px** on mobile (was `10 10 12`).
Reclaims ~7px vertical + ~4px horizontal **per board × ~30 boards**.
- **R2 — Inter-board gap = 8px, everywhere and identical.** `.panel.bay-board { margin-top }`, the Module-Bay
`.bay-grid { gap }`, and `details.sub-panel { margin-top }` all resolve to the same `--d-board-gap` so
vertical rhythm is uniform (today they're 12 / 12 / 10 — three different values).
- **R3 — Header subtitle.** `.bay-part-no` margin = **1px 0 4px** (was `2px 0 8px`). Keeps the searchable
real-label; only trims dead space. **Never hidden** (guardrail).
- **R4 — Inner section rhythm = 8px.** A board's stacked inner sections (readouts, meters, add-forms) settle
to one `--d-section-gap` instead of assorted 10/12px margins.
- **R5 — Interactive tiles trim toward the floor, never past it.** Over-padded tiles (faction keycaps,
status lamps, perk/slot rows, meter wraps) lose surplus vertical padding but **stay ≥28px**.
- **R6 — Form fields.** Inputs keep ≥16px font (floor) and their natural height; only inter-field *gaps* trim to 6px.
- **R7 — Floors are absolute** (Protocol 17). If any proposed value would cross a floor, it is not applied.
---
## 3. TIER-1 FIX LIST — SAFE TIGHTENING (apply without a taste-check)
Ranked by **cumulative reclaimed px** (worst offender first). All are spacing-only, desktop-scoped-out,
floor-verified. Every value below was chosen against the live measurement in the Appendix.
### F1 · Shared board shell padding + inter-board gap — **HIGHEST IMPACT (all ~30 boards)**
| Selector | Line | Current | → Mobile |
|---|---|---|---|
| `details.bay-board` | ~4605 | `padding: 10px 10px 12px` | `padding: 7px 8px 8px` |
| `.panel.bay-board` | ~4640 | `margin-top: 12px` | `margin-top: 8px` |
| `.bay-grid` (Module-Bay SLOT grid) | — | `gap: 12px` | `gap: 8px` |
| `details.sub-panel` | ~1708 | `margin-top: 10px` | `margin-top: 8px` |
**Evidence:** board content width is 300px of 360 (60px eaten by nested padding); every board pays
`10+12` vertical shell + `12` gap. **Est. reclaim: OPERATOR ~11 boards ≈ 120px shell + ~40px gaps ≈ 160px;
app-wide several hundred px.** Safe: no floor involved (padding/margin only).
### F2 · Header subtitle margin — **every board**
| Selector | Line | Current | → Mobile |
|---|---|---|---|
| `.bay-part-no` | ~4680 | `margin: 2px 0 8px` | `margin: 1px 0 4px` |
**Evidence:** measured 10–30px per board; ~5px trim × ~30 boards ≈ **150px**. Subtitle stays visible (guardrail).
### F3 · Faction console keycaps + meter wrap — **FACTION STANDING (11 factions)**
| Selector | Line | Current | → Mobile | Floor check |
|---|---|---|---|---|
| `.facon-chan` | ~7424 | `min-height: 40px` | `min-height: 34px` | 34 ≥ 28 ✓ |
| `.facon-meter-wrap` | ~7437 | `padding: 10px 12px 12px` | `padding: 8px 10px 9px` | n/a |
**Evidence:** keycap measured 40px with 12+12 vertical padding for a 9px-font label — largest single-tile
surplus on the board. Trims ~6px/keycap and ~7px on the meter wrap.
### F4 · Status-effect lamp tiles — **STATUS EFFECTS (4 tiles, more when buffed)**
| Selector | Line | Current | → Mobile | Floor check |
|---|---|---|---|---|
| `.stlamp-tile` | ~7258 | `padding: 8px 10px 9px` | `padding: 6px 9px 7px` | purge btn stays 28px ✓ |
| `.stlamp-grid` | ~7251 | `gap: 8px` | `gap: 6px` | — |
**Evidence:** tiles measured 70px each; ~5px/tile + 2px grid gap.
### F5 · Perk / skill-book / magazine slot rows — **PERK LOADOUT, SKILL BOOKS, SKILL MAGAZINES**
| Selector | Line | Current | → Mobile | Floor check |
|---|---|---|---|---|
| `.slot-row` | ~7730 | `padding: 7px 10px; margin-bottom: 7px` | `padding: 5px 8px; margin-bottom: 5px` | inner delete-btn stays 28px ✓ |
**Evidence:** slot rows measured 67px each in a bounded scroll region; the row itself is not a tap target
(the ✕ inside it is, and is untouched). ~6px × up to 7 visible rows ≈ 42px.
### F6 · Skill-matrix VU rows — **SKILL MATRIX (13 channels)**
| Selector | Line | Current | → Mobile | Floor check |
|---|---|---|---|---|
| `.vu-row` | ~7156 | `padding: 5px 0` | `padding: 3px 0` | drag track unaffected (22px, arrow-key + drag) ✓ |
**Evidence:** 13 rows × 41px. ~4px/row ≈ 52px. Conservative — leaves the drag surface untouched.
### F7 · UPLINK transcript + composer — **DIRECTOR UPLINK**
| Selector | Line | Current | → Mobile |
|---|---|---|---|
| `#chatDisplay` | ~377 (base) / ~1584 (uplink mobile) | `padding: 15px; gap: 15px` | `padding: 10px; gap: 10px` |
| `.scope-wrap` (uplink mobile) | ~1596 | `padding: 5px 0 4px` | keep (already trimmed) |
| `#composer` | — | `padding: 8px 12px 10px` | `padding: 6px 10px 8px` |
**Evidence:** transcript padding + message gap both 15px is loose on a phone where the transcript is the
star of the UPLINK self-contained view; `.composer-input` stays 16px font / 40px min-height (floors held).
### F8 · Inner readout / light rows — **board bodies app-wide**
| Selector | Line | Current | → Mobile |
|---|---|---|---|
| `.op-light-row` | ~6519 | `margin-top: 8px; padding-top: 8px` | `margin-top: 6px; padding-top: 6px` |
| `.zone-readout` and sibling first-inner-section margins | ~6983 | assorted 10px | `--d-section-gap` (8px) |
**Est. Tier-1 total reclaim: ~15–20% of vertical scroll length** across the boards (roughly 1–1.5 phone-screens
off OPERATOR alone), with **zero** behaviour, floor, or desktop change.
---
## 4. TIER-2 — DESIGN / TASTE (owner sign-off before applying)
These change the *look* or *information model*, not just whitespace — they need a taste-check.
- **D1 · Outer device-chrome padding (bezel).** The nested `body 6 + .container.machine 8 + .glass-frame 6`
= 20px/side before the board's own 10px. Trimming to `4 / 6 / 5` reclaims ~11px/side (~22px width →
more room per board). **DESIGN:** this is the Protocol-UI-7 device casing; tightening it changes how
"framed" the terminal reads. Recommend a small trim, but it's a look call.
- **D2 · The tall custom hardware controls in SETTINGS.** CAMPAIGN PROFILE (749px), RANDOMIZER INTERLOCK
(638px), the SLOT-02 Sonic Processor board (914px), the phosphor tube rack, the OPERATIONAL TEMPO dial —
these are deliberately large, mockup-approved hardware. They dominate SETTINGS' 5646px. Any shrink is a
redesign of an owner-approved control → **DESIGN.**
- **D3 · SPECIAL fader ladders / KARMA needle / VU meters.** `.fd-ladder` is 96px × 7, the karma `.kn-face`
is 190×99px. These are signature instruments; shrinking them is a taste call, not tightening → **DESIGN.**
- **D4 · Default-collapse more boards.** Most content boards default `open`, so a populated OPERATOR shows
everything at once (7796px). Defaulting the lower-traffic boards (e.g. FIELD NOTES, SERVICE TALLY,
CAMPAIGN CHRONICLE, KARMA on FNV) to collapsed would cut perceived length hard — but it changes the
landing experience → **DESIGN / UX.**
- **D5 · Hiding decorative chrome on mobile** (connector-strip `::after` pins, `.bay-slot-tag` corner tags).
Would reclaim a little and de-clutter, but erodes the machine aesthetic → **DESIGN.** (Note: `.bay-part-no`
is explicitly OFF-limits regardless — guardrail.)
---
## 5. What NOT to do (anti-recommendations)
- ❌ Don't shrink fonts below their current 8–13px for density — legibility floor, and inputs must stay ≥16px.
- ❌ Don't reduce tap targets below 28px (drawer buttons, purge ✕, delete ✕, faction ± keys, dial knobs).
- ❌ Don't `display:none` `.bay-part-no` / `.real-label` (Protocol-25 searchable-label guardrail).
- ❌ Don't put any density rule inside the `min-width:1000px … (hover:hover) and (pointer:fine)` desktop gate,
or in an ungated `@media (min-width:1000px)` (Suite 129 forbids the latter).
- ❌ Don't touch `@keyframes`, `:focus-visible`, `prefers-reduced-motion`, or any colour — out of scope.
---
## 6. Verification the build unit must run (Protocol 10)
1. `node tests/render-check.mjs` — 360px + 412px, assert **no horizontal overflow** and **no focus-zoom**.
2. Live re-measure (static server, populated seed) that: inputs still ≥16px, every trimmed tap target still
≥28px, and the six subsystems still report `scrollWidth === innerWidth` at 360 and 412.
3. Desktop diff = zero: confirm nothing changed at ≥1000px (all edits inside `@media (max-width:480px)`).
4. Full pre-commit gate (both runners at parity) + Protocol 1 cache bump (served `css/` file changes).
---
## Appendix — raw measurements (static server, populated FNV, @360px unless noted)
### Shared board chrome (base values, applied on mobile today — NOT tuned)
| Element | Value |
|---|---|
| `details.bay-board` padding | `10px 10px 12px` |
| `.panel.bay-board` margin-top (inter-board gap) | `12px` |
| `.main-grid` column-gap | `12px` (single-column on mobile → inert) |
| `.bay-part-no` | fs 9px, margin `2px 0 8px`, measured 10–30px tall |
| `.bay-slot-tag` | fs 8.5px, padding `2px 7px` (absolute — no layout height) |
| `.panel-substatus` | fs 10px (inside summary) |
| board `summary h2` | fs 12px |
| base body font (mobile) | 13px |
### Horizontal padding chain @360px (content width = 300px)
`body 6+6` → `.container.machine 8+8` → `.glass-frame 6+6` → `.main-grid 0` → board `10+10 (+1 border)` = **~60px chrome / side-summed**.
### OPERATOR boards @360px (populated), height incl. shell
VITAL TELEMETRY 416 · S.P.E.C.I.A.L. 350 · POSITION & MISSION CLOCK 509 · SKELETAL HARNESS 559 ·
SKILL MATRIX 615 · SKILL BOOKS 669 · SKILL MAGAZINES 665 · PERK LOADOUT 1548 (bounded scroll) ·
STATUS EFFECTS 412 · FACTION STANDING 800 · KARMA ALIGNMENT 314.
### Key interactive tiles (all ≥28px floor today)
| Tile | Measured | Padding | Proposed | Post-trim |
|---|---|---|---|---|
| `.facon-chan` keycap | 40px | min-height 40 | min-height 34 | 34 ✓ |
| `.stlamp-tile` | 70px | `8 10 9` | `6 9 7` | ~66 ✓ |
| `.slot-row` | 67px | `7 10` mb7 | `5 8` mb5 | ~59 (inner ✕ = 28 ✓) |
| `.vu-row` | 41px | `5 0` | `3 0` | ~37 (track 22 unchanged) |
| `.drawer` (cargo filter) | 46px | `5 7` | leave (comfortable, ≥28) | — |
| `.facon-meter-wrap` | pad `10 12 12` | — | `8 10 9` | — |
### UPLINK (self-contained mobile view)
`#chatDisplay` padding 15 / gap 15 · `.scope-wrap` 102px (scope 66 + head/meta) · `#composer` padding `8 12 10`
· `.composer-input` 40px min-height, 16px font (floor held).
### Overflow check
360px: all six subsystems `scrollWidth === 360`. 412px: all six `ok`. **No floor violations anywhere.**
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).