Files
arah2/web/src/shell.css
T
araharah-executor 13ba0bbee5 feat(07-02): landscape cockpit geometry block — CSS-only, tokens binding (PX-04/D-03)
- @media (orientation: landscape) and (max-height: 560px) block appends to shell.css
- banner 72px min-height + safe-left/right edge padding via max(); plate 40px/glyph 28px
- strip rides under 72px chrome; guide-list 100dvh-bounded; bottom register inside landscape height
- sanctioned literal list extended in the cockpit header comment
- no radius/shadow/blur/transition, no MapLibre rule; theme audit stays clean
2026-10-25 03:52:17 +08:00

249 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* shell.css — the Rams chrome (UI-SPEC §Shell Layout & Register Bands).
* -----------------------------------------------------------------------
* Every value is a token reference (var(--color-*) / var(--spacing-*) /
* var(--type-*)); the only literals are the machined-border geometry the
* UI-SPEC enumerates (1px/2px rules, 8px lamp/gantry/notch, 40x40 plate,
-2px hit slop, 320px card max-width, 20px dot as SVG r=10; Phase 5 adds:
chip plate 28px tall + -8px hit slop (44px target), origin ring 24px/2px,
destination pin 12px square + 3px×8px tail (20px glyph), route line
widths 9/5/3 map-space paint, the 64px control-clearance composite,
160px name-chip max-width, 3px focus bracket). Zero
* roundedness (the one `border-radius: 0` reset pins MapLibre's chrome),
* zero transition/animation — state changes are instant hard swaps.
*
* D-10 / PX-02: NO MapLibre rule participates in theme slots. The map
* style is Rams-constant; only --shell-bg / --shell-on-surface re-slot
* behind [data-tg-theme].
*/
/* tokens.css wins; mockups lose. Value discipline per theme/src/components.css lines 9-15. */
@import "@araharah/theme/dist/rams.css";
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
background: var(--shell-bg);
color: var(--shell-on-surface);
font: var(--type-body-md);
overflow: hidden;
}
/* PX-02 dark-slot activation (extends theme.css's media-query default) */
[data-tg-theme="light"] { --shell-bg: var(--color-surface); --shell-on-surface: var(--color-on-surface); }
[data-tg-theme="dark"] { --shell-bg: var(--color-surface-dark); --shell-on-surface: var(--color-inverse-on-surface); }
[data-tg-theme="dark"] .card { background: var(--color-inverse-surface); color: var(--color-inverse-on-surface); border-color: var(--color-inverse-on-surface); }
[data-tg-theme="dark"] .card-header { border-bottom-color: var(--color-inverse-on-surface); color: var(--color-inverse-on-surface); }
/* NO MapLibre rule here, by design (D-10 / RESEARCH Pitfall 7). */
/* Research Open Question 1 — WebGL2 absent: shell renders over a Rams-tinted field */
body[data-map="absent"] main { background: var(--color-surface-dim); }
/* map register: full-bleed, the dominant surface */
main { position: fixed; inset: 0; }
#map { position: absolute; inset: 0; }
/* MapLibre's stylesheet lands AFTER shell.css in the bundle (map.ts imports
it at module scope), so the Rams chip override needs the #map-scoped
specificity to win the cascade — flat matte-black chip, no translucency. */
#map .maplibregl-ctrl-attrib { border-radius: 0; background: var(--color-matte-black); color: var(--color-pure-white); font: var(--type-label-sm); letter-spacing: var(--type-label-sm-tracking); text-transform: uppercase; }
#map .maplibregl-ctrl-attrib a { color: var(--color-pure-white); }
#map .maplibregl-ctrl-attrib-button { background-color: var(--color-matte-black); }
/* top register: status strip */
.strip {
position: absolute; top: calc(var(--safe-top, 0px) + var(--spacing-space-sm)); left: 0; right: 0;
display: flex; align-items: center; justify-content: space-between;
padding: var(--spacing-space-sm) var(--spacing-space-md);
min-height: 32px;
background: var(--color-matte-black); color: var(--color-inverse-on-surface);
font: var(--type-label-sm); letter-spacing: var(--type-label-sm-tracking); text-transform: uppercase;
}
.strip .left { display: flex; align-items: center; gap: var(--spacing-space-xs); }
.strip .lamp { width: 8px; height: 8px; border: 1px solid var(--color-pure-white); }
.strip .lamp[data-state="acquiring"], .strip .lamp[data-state="validating"], .strip .lamp[data-state="awaiting"] { background: var(--color-signal-orange); }
.strip .lamp[data-state="live"] { background: var(--color-olive-green); }
.strip .lamp[data-state="lost"], .strip .lamp[data-state="denied"] { background: var(--color-functional-red); }
.strip .telemetry { font: var(--type-label-sm); }
/* center register: boot / denied / lost card */
.card-stack { position: absolute; left: var(--spacing-margin); right: var(--spacing-margin); top: 50%; transform: translateY(-50%); max-width: 320px; margin-inline: auto; }
.card .gantry { width: 8px; height: 8px; border: 1px solid var(--color-matte-black); }
.card .gantry[data-fill="on"] { background: var(--color-signal-orange); }
.card-actions { display: flex; gap: var(--spacing-space-sm); padding-top: var(--spacing-space-sm); }
/* Device-gate feedback (2026-10-24): shell cards place text elements as direct
.card children — the theme's .card carries no body padding of its own, so
text sat at the card edges. Token-only inline padding on direct children;
the header plate keeps its own. */
.card > .text-headline-md,
.card > .text-body-md,
.card > .text-body-sm { padding-inline: var(--spacing-space-md); }
.card > .text-headline-md { padding-top: var(--spacing-space-sm); }
.card > .text-body-sm:last-child { padding-bottom: var(--spacing-space-sm); }
.card > .card-actions { padding-inline: var(--spacing-space-md); padding-bottom: var(--spacing-space-sm); }
.card > .card-header + .text-headline-md { padding-top: var(--spacing-space-xs); }
/* bottom register: recenter control (40x40 plate, >=44x44 hit target) */
.recenter {
position: absolute; right: var(--spacing-margin); bottom: calc(var(--safe-bottom, 0px) + var(--spacing-space-sm));
width: 40px; height: 40px; padding: 0; border: 0; border-radius: 0;
background: var(--color-matte-black); color: var(--color-pure-white);
display: flex; align-items: center; justify-content: center;
z-index: 2; /* above MapLibre's own chrome; the attrib chip lives bottom-left */
}
.recenter::after { content: ""; position: absolute; inset: -2px; }
.recenter .notch { width: 8px; height: 8px; background: var(--color-signal-orange); }
.recenter[data-follow="free"] .notch { display: none; }
/* UI-SPEC §Follow/Recenter Control, third notch state: live-but-LOST (no fix).
soft-beige, NOT red — red is reserved for the strip/degraded registers; the notch
reads "unlocked, not error" (RESEARCH Pitfall 8 orange discipline). */
.recenter[data-fix="lost"] .notch { background: var(--color-soft-beige); }
.recenter:active { background: var(--color-signal-orange); color: var(--color-matte-black); }
/* position marker (DOM Marker children) */
.pos-accuracy { fill: var(--color-matte-black); fill-opacity: 0.06; stroke: var(--color-matte-black); stroke-opacity: 0.30; stroke-width: 1; }
.pos-dot { fill: var(--color-matte-black); }
.pos-cone { fill: var(--color-signal-orange); }
.pos-group[data-stale="true"] { opacity: 0.4; }
/* route state stack (second register holder — UI-SPEC §State Coexistence).
The Phase 3 .card-stack logic stays byte-identical; the route surface gets
its OWN stack so the pending card (center) and the signal-lost card
(bottom, Phase 3 stack) can coexist in different registers. */
.route-stack { position: absolute; left: var(--spacing-margin); right: var(--spacing-margin); max-width: 320px; margin-inline: auto; }
.route-stack[data-register="center"] { top: 50%; transform: translateY(-50%); }
.route-stack[data-register="bottom"] { top: auto; transform: none; bottom: calc(var(--safe-bottom, 0px) + 64px); }
/* route card body rows — token rhythm composing with the existing .card child
rules (NOT overrides; UI-SPEC post-approval padding addendum) */
.route-telemetry { display: flex; align-items: baseline; gap: var(--spacing-space-xs); padding-inline: var(--spacing-space-md); padding-top: var(--spacing-space-xs); }
.route-diff { padding-inline: var(--spacing-space-md); padding-top: var(--spacing-space-xs); }
.route-chips { display: flex; gap: var(--spacing-space-xs); padding-inline: var(--spacing-space-md); padding-top: var(--spacing-space-sm); }
.route-chips:last-child { padding-bottom: var(--spacing-space-sm); }
/* chips (UI-SPEC chip contract): 28px plate, 44px hit target, instant invert.
No persistent selected-chip state — the selected route lives in the card. */
.route-chip { position: relative; border: 0; border-radius: 0; padding: var(--spacing-space-xs) var(--spacing-space-sm); background: var(--color-matte-black); color: var(--color-pure-white); font: var(--type-label-sm); letter-spacing: var(--type-label-sm-tracking); text-transform: uppercase; white-space: nowrap; }
.route-chip::after { content: ""; position: absolute; inset: -8px; }
.route-chip:active { background: var(--color-signal-orange); color: var(--color-matte-black); }
.route-chip:focus-visible { outline: 3px solid var(--color-signal-orange); outline-offset: 2px; }
/* endpoint markers (map furniture — SVG chrome, machined geometry) */
.route-origin-ring circle { fill: none; stroke: var(--color-matte-black); stroke-width: 2; }
.route-dest-pin-glyph rect { fill: var(--color-matte-black); }
.route-dest-pin-glyph polygon { fill: var(--color-matte-black); }
.route-dest-pin-glyph .inset-rule { fill: none; stroke: var(--color-pure-white); stroke-width: 1; }
.route-dest-namechip { position: absolute; bottom: calc(100% + var(--spacing-space-sm)); left: 50%; transform: translateX(-50%); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; background: var(--color-matte-black); color: var(--color-pure-white); font: var(--type-label-sm); letter-spacing: var(--type-label-sm-tracking); text-transform: uppercase; padding: var(--spacing-space-xs) var(--spacing-space-sm); }
/* No CSS transition or animation anywhere — state changes are instant hard swaps. */
/* DEBUG-ROUND probe styles removed at the device-matrix exit (2026-10-23). */
/* ————— guidance cockpit (Phase 6, 06-UI-SPEC): the maneuver banner, the
compacted strip, the maneuver list, the guidance-card rows + progress bar.
Token vars ONLY (the sanctioned machined-geometry literal list: banner
min-height 96px, arrow plate 48px + glyph 32px, compact strip 24px, row
48px + glyph 24px, next-row lamp 8px, panel max 320px/480px, bar 4px, 1px
rules). States are instant hard swaps — zero motion, zero depth, zero
rounded corners (buttons are radius-reset in the theme base layer).
Sanctioned list extended by Phase 7 (07-02/PX-04/D-03) — the landscape
literals: landscape banner min-height 72px, plate 40px + glyph 28px,
landscape chrome offset 104px. ————— */
/* the banner band: full-bleed matte-black chassis under the safe inset; the
whole banner is the list toggle (the 96px band is the >=44px hit target) */
.guide-banner {
position: absolute; top: calc(var(--safe-top, 0px) + var(--spacing-space-sm)); left: 0; right: 0;
min-height: 96px; padding: var(--spacing-space-sm) var(--spacing-space-md);
display: flex; flex-direction: column; gap: var(--spacing-space-xs);
align-items: stretch; text-align: left; border: 0; font: inherit;
background: var(--color-matte-black); color: var(--color-pure-white);
border-bottom: 1px solid var(--color-chassis-subtle); /* banner→strip separator */
z-index: 3; /* above the list tap-out — the banner stays the toggle (CLOSE) */
}
.guide-banner:focus-visible { outline: 3px solid var(--color-signal-orange); outline-offset: 2px; } /* the route-chip bracket precedent */
.guide-banner .guide-top { display: flex; align-items: center; justify-content: space-between; color: var(--color-chassis-subtle); }
.guide-banner .guide-body { display: flex; align-items: center; gap: var(--spacing-space-sm); flex: 1; min-width: 0; }
.guide-banner .guide-plate { width: 48px; height: 48px; flex: none; display: flex; align-items: center; justify-content: center; background: var(--color-surface-dark); color: var(--color-signal-orange); }
.guide-banner .guide-plate svg { width: 32px; height: 32px; display: block; }
.guide-banner .guide-measures { display: flex; flex-direction: column; gap: var(--spacing-space-xs); flex: 1; min-width: 0; }
.guide-banner .guide-distance-row { display: flex; align-items: baseline; gap: var(--spacing-space-xs); }
.guide-banner .guide-distance { color: var(--color-pure-white); } /* the glance datum — maximum contrast ink */
.guide-banner .guide-unit { color: var(--color-chassis-subtle); }
.guide-banner .guide-street-row { display: flex; align-items: center; gap: var(--spacing-space-xs); min-width: 0; }
.guide-banner .guide-street { color: var(--color-soft-beige); text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guide-banner .guide-gantry { width: 8px; height: 8px; flex: none; border: 1px solid var(--color-chassis-subtle); }
.guide-banner .guide-gantry[data-fill="on"] { background: var(--color-signal-orange); }
/* strip compaction during guidance — LAYOUT ONLY (the shell root carries
data-guidance="active"); lamp, word and degradation states byte-identical */
[data-guidance="active"] .strip {
top: calc(var(--safe-top, 0px) + var(--spacing-space-sm) + 96px + 1px);
min-height: 24px;
padding-top: var(--spacing-space-xs); padding-bottom: var(--spacing-space-xs);
}
[data-guidance="active"] .strip .telemetry { display: none; } /* accuracy/fix-age are not glance data at speed */
/* the maneuver list: transparent tap-out + card panel over the map —
no dim layer (boot-card precedent); any map tap collapses the list */
.guide-list-wrap { position: absolute; inset: 0; z-index: 2; display: flex; align-items: flex-start; justify-content: center; pointer-events: none; }
.guide-list-tapout { position: absolute; inset: 0; background: transparent; pointer-events: auto; }
.guide-list { pointer-events: auto; position: relative; width: calc(100% - 2 * var(--spacing-margin)); max-width: 320px; max-height: 480px; overflow-y: auto; padding-bottom: var(--spacing-space-sm); margin-top: calc(var(--safe-top, 0px) + var(--spacing-space-sm) + 96px + 1px + 24px + var(--spacing-space-sm)); }
.guide-row { display: flex; align-items: center; gap: var(--spacing-space-sm); min-height: 48px; padding: var(--spacing-space-sm) var(--spacing-space-md); }
.guide-row + .guide-row { border-top: 1px solid var(--color-matte-black); }
[data-tg-theme="dark"] .guide-row + .guide-row { border-top-color: var(--color-inverse-on-surface); }
.guide-row .guide-row-glyph { display: flex; align-items: center; flex: none; color: var(--color-matte-black); }
[data-tg-theme="dark"] .guide-row .guide-row-glyph { color: var(--color-inverse-on-surface); }
.guide-row .guide-row-glyph svg { width: 24px; height: 24px; display: block; }
.guide-row[data-row-state="next"] .guide-row-glyph { color: var(--color-signal-orange); }
.guide-row[data-row-state="past"] .guide-row-glyph { color: var(--color-outline); }
.guide-row .guide-row-street { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guide-row[data-row-state="past"] .guide-row-street,
.guide-row[data-row-state="past"] .guide-row-dist { color: var(--color-outline); } /* muted history, not hidden */
.guide-row .guide-row-dist { margin-left: auto; flex: none; }
.guide-row .guide-lamp { width: 8px; height: 8px; flex: none; background: var(--color-signal-orange); }
/* guidance-card instruments (composing with the existing .card child rules —
no overrides; the confirm card's bottom placement is the guide-confirm
class hook, read by the mount) */
.guide-progress { height: 4px; margin: var(--spacing-space-xs) var(--spacing-space-md) 0; background: var(--color-chassis-subtle); border: 1px solid var(--color-matte-black); } /* checkbox geometry — distinguishable without color */
[data-tg-theme="dark"] .guide-progress { border-color: var(--color-inverse-on-surface); }
.guide-progress-fill { height: 100%; background: var(--color-signal-orange); }
/* ————— Phase 7 landscape geometry (PX-04/D-03) —————
Landscape is an ADDITIONAL CONSTRAINT, never a separate screen: portrait
remains the default and every rule below only ADAPTS the cockpit/banner/
card geometry. The map cartography stays Rams-constant (D-10) — NO
MapLibre rule appears in this block. Every value is a token reference or
a sanctioned geometry literal; the map's cartography is untouched. */
@media (orientation: landscape) and (max-height: 560px) {
/* the banner band: shorter in landscape, and the horizontal safe insets
are honoured on both edges (max() with the token padding as the floor) */
.guide-banner {
min-height: 72px;
padding-left: max(var(--spacing-space-md), var(--safe-left, 0px));
padding-right: max(var(--spacing-space-md), var(--safe-right, 0px));
}
.guide-banner .guide-plate { width: 40px; height: 40px; }
.guide-banner .guide-plate svg { width: 28px; height: 28px; }
/* the compact strip rides directly under the shorter banner */
[data-guidance="active"] .strip {
top: calc(var(--safe-top, 0px) + var(--spacing-space-sm) + 72px + 1px);
}
/* the maneuver list: a landscape-height-bounded panel that clears the
banner and the strip instead of overlapping them */
.guide-list {
max-height: calc(100dvh - 104px - var(--safe-top, 0px) - var(--safe-bottom, 0px));
margin-top: calc(var(--safe-top, 0px) + var(--spacing-space-sm) + 72px + 1px + 24px + var(--spacing-space-sm));
}
/* the bottom register (the guidance card / the confirm card) sits inside
the landscape height instead of overflowing past it */
.route-stack[data-register="bottom"] {
bottom: calc(var(--safe-bottom, 0px) + var(--spacing-space-sm));
}
.route-stack[data-register="center"] { max-width: 320px; }
}