/*
 * Buildr installer PWA — app shell + sign-up + login + home
 * (CR-BUILDR-A001-23).
 *
 * Class names and values are taken verbatim from the ratified App cards
 * (ds-bundle-app-r1/app/{signup,login,home-navy-variant}.html) — requires
 * tokens.css loaded first.
 *
 * LOGIN — BF-12 resolved 2026-09-07 (Drew's ruling: the card was stale, the
 * phone+SMS backend was right). `login.html` is now redrawn round 2 for the
 * flow `app/routers/auth.py` (CR-21) actually serves: phone in, 6-digit SMS
 * code back, no password, no email anywhere. Six states, each a real API
 * outcome — phone entry · unknown number (404) · couldn't send (502/
 * offline) · code entry · wrong-or-expired (401) · three tries used
 * (`MAX_ATTEMPTS=3`, fail-closed, counted client-side — the API gives no
 * other signal). `.field-err`/`.banner-hold`/`.code-box.void` below are the
 * card's own additions for the phone-field error and the fail-closed hold
 * state; everything else is Signup's own primitives, unchanged.
 */

/* ---------------------------------------------------------------------
   Shared phone-frame-free layout: this app is a real mobile viewport, not
   a card preview, so screens fill min-height:100vh directly.
   --------------------------------------------------------------------- */

.installer-shell { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  /* viewport-fit=cover (index.html) lets the page run under the notch, in
     the installed PWA and in the Capacitor shell alike — so every top-of-
     screen surface owes the status bar its inset back. */
  padding: calc(var(--sp-3) + env(safe-area-inset-top, 0px)) var(--sp-4) var(--sp-3);
  background: var(--paper); border-bottom: 2px solid var(--dust);
  flex-shrink: 0; position: sticky; top: 0; z-index: 10;
}
.topbar-brand { display: flex; align-items: center; gap: 10px; }
.topbar-badge { width: 32px; height: 32px; flex-shrink: 0; display: block; }
.topbar-name { font-family: var(--font-brand); font-weight: 800; font-size: 17px; letter-spacing: .02em; color: var(--ink); }
.topbar-app-label {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-mute); padding: 3px 8px; border-radius: var(--r-full);
  background: var(--board); margin-left: 2px;
}
/* CR-BUILDR-A007-5 — the top-bar entry, same declaration block as the
   retired .topbar-logout (card frame A: "same button, new word and
   glyph"), renamed under the class the build now uses. */
.topbar-account {
  display: flex; align-items: center; gap: 6px; min-height: 44px; min-width: 44px; padding: 8px 14px;
  border: 2px solid var(--dust); border-radius: var(--r-md); background: var(--paper); color: var(--ink-soft);
  font-size: 13px; font-weight: 700; font-family: var(--font-ui); cursor: pointer;
}
.topbar-account:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* CR-BUILDR-A016-7 · NEW, RATIFIED 2026-09-19 — the top-bar button while
   Account is open: same block, filled navy (paper on navy 14.4:1). */
.topbar-account.is-open { background: var(--navy); border-color: var(--navy-edge); color: var(--paper); }

/* ---- Account screen (CR-BUILDR-A007-5) — the card's frame B rows,
   cloned from Home's .order-row metrics (64px, hairline, tap-gap), plus
   the one new atom: a switch in Buildr tokens, Apple HIG proportions. ---- */
.icon-lg { width: 22px; height: 22px; }
.acct-list { list-style: none; margin: 0; padding: 0; width: 100%; }
.acct-row {
  display: flex; align-items: center; justify-content: space-between; padding: 13px 2px;
  border-bottom: 1px solid var(--dust); gap: var(--tap-gap); min-height: var(--tap-min);
}
.acct-row-l { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.acct-label { font-size: 15px; font-weight: 700; color: var(--ink); }
.acct-sub { font-size: 12.5px; color: var(--ink-mute); line-height: 1.4; }
/* CR-BUILDR-A016-8 · NEW, RATIFIED 2026-09-19 — the switch row's confirmed
   line: the helper line's slot, in go, for 2.5s after a successful scan. */
.acct-sub.ok { color: var(--go-deep); display: flex; align-items: center; gap: var(--sp-1); }
.acct-sub.ok .icon { width: 14px; height: 14px; }
.acct-btn {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: var(--tap-min); padding: 13px 2px;
  background: none; border: none; border-bottom: 1px solid var(--dust); font-family: var(--font-ui);
  font-size: 15px; font-weight: 700; color: var(--navy); cursor: pointer; text-align: left;
}
.acct-btn .icon { width: 18px; height: 18px; }
.acct-btn.danger { color: var(--danger-deep); }
/* CR-BUILDR-A016-7 · NEW, RATIFIED 2026-09-19 — the leaving block: Sign out
   (a button) and Delete account (the plain row, as today) together at the
   bottom. Retires the prior danger-row wrapper class. */
.acct-leave { margin-top: var(--sp-8); }
.acct-leave .btn-cta .icon { width: 20px; height: 20px; }
.acct-leave-del { margin-top: var(--tap-gap); }

/* ---- r3.1 · Support (CR-BUILDR-A008-4): the action row as a real link, with a
   secondary line (the number in the sign-in card's .num) and an outbound glyph.
   .acct-btn as ratified is untouched; every rule below is scoped to the anchor
   form or to the group wrapper (card's own r3.1 <style> comment). ---- */
.acct-group { margin-top: var(--sp-6); }
a.acct-btn { text-decoration: none; }
.acct-btn .acct-row-l { flex: 1; }
.acct-btn .acct-sub { font-weight: 400; }
.acct-btn .acct-ext { width: 16px; height: 16px; color: var(--ink-mute); margin-left: auto; }
@media (hover: hover) and (pointer: fine) { a.acct-btn:hover { background: var(--board); } }
a.acct-btn:active, a.acct-btn.is-pressed { background: var(--board); }
a.acct-btn:focus-visible, a.acct-btn.is-focus { outline: 2px solid var(--navy); outline-offset: 2px; border-radius: var(--r-sm); }

.switch {
  width: 51px; height: 31px; flex-shrink: 0; border-radius: var(--r-full); border: none; padding: 2px;
  background: var(--chalk); cursor: pointer; display: flex; align-items: center; justify-content: flex-start;
  transition: background var(--dur-2) var(--ease);
}
.switch-knob {
  width: 27px; height: 27px; border-radius: 50%; background: var(--paper); display: block;
  transition: transform var(--dur-2) var(--ease);
}
.switch[aria-checked="true"] { background: var(--navy); }
.switch[aria-checked="true"] .switch-knob { transform: translateX(20px); }
.switch:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .switch, .switch-knob { transition: none; } }

/* ---- CR-BUILDR-A016-8 · NEW, RATIFIED 2026-09-19 · the offer sheet, the
   app's first. Tokens only. Cloned verbatim from the ratified card
   (face-id-offer.html) except: the card's `.sheet-scrim { position:
   absolute }` is frame chrome (it sits inside a phone-frame preview) —
   in the real app it is `position: fixed` over the whole window, with the
   rest of the shell inert underneath. `.sheet`'s bottom padding uses
   env(safe-area-inset-bottom) in place of the card's .safe-bottom spacer
   div. `--col-left`/`--col-w` (CR-BUILDR-A016-9's own contract) are read
   with phone-width fallbacks so this is a no-op until that branch defines
   them. ---- */
.sheet-scrim {
  position: fixed; inset: 0; z-index: 20; display: flex; align-items: flex-end;
  background: color-mix(in srgb, var(--ink) 50%, transparent);
  padding-left: var(--col-left, 0px); width: var(--col-w, 100%);
}
.sheet {
  width: 100%; background: var(--paper); border-top: 2px solid var(--dust);
  border-radius: var(--r-lg) var(--r-lg) 0 0; padding: var(--sp-6) var(--sp-4) 0;
  padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  animation: sheetIn var(--dur-2) var(--ease);
}
@keyframes sheetIn {
  from { opacity: 0; transform: translateY(100%); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .sheet { animation: sheetInReduced var(--dur-2) var(--ease); }
}
@keyframes sheetInReduced { from { opacity: 0; } to { opacity: 1; } }
.sheet-title { font-family: var(--font-ui); font-size: 19px; font-weight: 800; color: var(--ink); margin: 0 0 var(--sp-2); outline: none; }
.sheet-line { font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.5; margin: 0 0 var(--sp-5); }
.sheet-line.last { margin-bottom: var(--sp-2); }
.sheet-err { margin: 0 0 var(--sp-4); }
.sheet-ok { display: flex; align-items: center; gap: var(--sp-2); color: var(--go-deep); }
.sheet-ok .sheet-title { margin: 0; }
.sheet-ok + .sheet-line { margin-top: var(--sp-2); }

#viewHost { flex: 1; padding: var(--sp-5) var(--sp-4) var(--sp-12); max-width: 640px; margin: 0 auto; width: 100%; }
/* BF-166 — Home pins the circle + tab bar: the height chain below is Home-only.
   The live DOM is #viewHost > .installer-view > .home-screen (app.js renderView mounts a
   `.installer-view` node), so each link of the chain is scoped through it. */
.installer-shell:has(> #viewHost > .installer-view > .home-screen) { height: 100dvh; overflow: hidden; }
#viewHost:has(> .installer-view > .home-screen) { display: flex; flex-direction: column; min-height: 0; padding-bottom: 0; }
#viewHost > .installer-view:has(> .home-screen) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

/* ---------------------------------------------------------------------
 * CR-BUILDR-A016-9 — desktop frame + "made for your phone" panel +
 * landscape cover. Buildr does not get a desktop layout: on a wide
 * window the one phone app sits in a fixed-width column on a quiet
 * backdrop; below 600px nothing here changes anything (phone defaults).
 *
 * --col-left / --col-w is a CONTRACT: every future position:fixed
 * element (the CR-BUILDR-A016-4 bottom sheet is the first) reads these
 * exact variable names, with these exact phone defaults, to stay
 * aligned with #app. Do not rename them, do not move them off :root.
 * Ratified card: D-desktop/app/desktop-frame.html + landscape-cover.html
 * (Drew, 2026-09-19, as drawn).
 * --------------------------------------------------------------------- */
:root { --col-w: 100%; --col-left: 0px; }

@media (min-width: 600px) {
  html, body { background: var(--board); }
  :root { --col-w: 430px; --col-left: calc(50% - 215px); }
  #app {
    width: var(--col-w); margin-left: var(--col-left); min-height: 100dvh;
    background: var(--paper); box-shadow: 0 0 0 var(--edge) var(--dust);
  }
  /* Classic (space-taking) scrollbars shift body's 50% relative to a
     fixed element's 50% by the scrollbar width — reserve the gutter so
     #app and every --col-left consumer resolve to the same edge. */
  html { scrollbar-gutter: stable; }
}

/* "Buildr is made for your phone" panel — build state D3a only (no
 * public store listing on record): title, lede, QR, address, foot line.
 * No .ph-badges, no badge slot — those are drawn-not-built (D3b/D3c). */
.ph-panel { display: none; }

@media (min-width: 900px) and (min-height: 560px) {
  html:not(.is-native) { --col-left: calc(50% - 407px); }
  html:not(.is-native) .ph-panel {
    display: block; position: fixed; left: calc(50% + 87px); top: 50%;
    transform: translateY(-50%); width: 320px;
  }
}

.ph-title {
  font-family: var(--font-brand); font-weight: 800; font-size: var(--t-h2); line-height: 1.12;
  letter-spacing: -.01em; color: var(--ink); margin: 0 0 var(--sp-3); text-wrap: balance;
}
.ph-lede { font-size: var(--t-sm); line-height: 1.5; color: var(--ink-soft); margin: 0 0 var(--sp-6); }
.ph-qr {
  width: 172px; height: 172px; padding: var(--sp-3); background: var(--paper);
  border: var(--edge) solid var(--dust); border-radius: var(--r-md);
}
.qr { display: block; width: 100%; height: 100%; fill: var(--ink); }
.ph-url { font-size: var(--t-sm); font-weight: 600; color: var(--ink); margin: var(--sp-3) 0 0; }
.ph-foot { font-size: var(--t-xs); color: var(--ink-mute); margin: var(--sp-6) 0 0; }

/* Landscape cover — the ONE case native/installed orientation-lock config
 * (CR-BUILDR-A016-1) cannot reach: a plain browser tab on a phone turned
 * on its side. Shows only when all five conditions hold; never on
 * desktop, never on a tablet, never on an upright phone with the
 * keyboard open. The cover is the ONE element allowed inset: 0. */
.ls-cover { display: none; }
.ls-art { width: 132px; height: 104px; display: block; overflow: visible; }
.ls-art .was { stroke: var(--chalk); stroke-width: 2.5; fill: none; stroke-linejoin: round; stroke-dasharray: 5 5; }
.ls-art .turn { stroke: var(--ink-mute); stroke-width: 2.5; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.ls-art .now { stroke: var(--navy); stroke-width: 2.5; fill: var(--paper); stroke-linejoin: round; }
.ls-art .now-bar { stroke: var(--navy); stroke-width: 2.5; stroke-linecap: round; }
.ls-line {
  font-family: var(--font-ui); font-size: var(--t-h3); font-weight: 800; letter-spacing: -.01em;
  color: var(--ink); margin: 0; line-height: 1.25;
}

@media (orientation: landscape) and (min-width: 560px) and (max-height: 500px) and (pointer: coarse) and (hover: none) {
  .ls-cover {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--sp-5); padding: var(--sp-6); text-align: center;
    position: fixed; inset: 0; z-index: 1000; background: var(--paper);
  }
  #app, .ph-panel { visibility: hidden; }
}

.installer-notbuilt { text-align: center; margin: 64px auto; max-width: 360px; }
.installer-notbuilt h2 { font-size: 16px; font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.installer-notbuilt p { font-size: 13px; color: var(--ink-mute); margin: 0; }
.installer-notbuilt a { display: inline-block; margin-top: 16px; color: var(--navy); font-weight: 700; font-size: 13px; text-decoration: none; }

/* ---------------------------------------------------------------------
   Brand mark (used inline wherever the ratified cards show the Buildr B)
   --------------------------------------------------------------------- */

.brand-badge-svg rect { fill: var(--orange); }
.brand-badge-svg text { fill: #fff; }

/* ---------------------------------------------------------------------
   Sign-up (card-verbatim, ds-bundle-app-r1/app/signup.html)
   --------------------------------------------------------------------- */

.signup-screen { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; background: var(--paper); }
.login-hero { flex: 1 1 auto; padding: calc(40px + env(safe-area-inset-top, 0px)) 20px 0; text-align: center; }

/* Keyboard open — the screen makes room, it does not hide its face.
   MEASURED on Drew's iPhone 17 in Chrome (the browser he actually uses; its
   address bar is at the TOP, unlike Safari's, so it leaves less room):
     keyboard closed -> innerHeight 681
     keyboard open   -> innerHeight 379   (the keyboard takes 298px)
   Round one (PR #48) closed the 50px deficit by collapsing the logo to
   nothing and dropping the foot-note. Drew, 2026-09-14: the first screen a
   person ever sees must not lose its brand mark to a keyboard — that reads
   as amateur. So this round keeps everything on screen and does two things:
     1. the mark SHRINKS (56 -> 32px, same as the topbar badge) and the
        vertical rhythm tightens: hero pad 40->12, logo gap 18->10, copy gap
        28->10, h2 20->18px. Content above the field: ~310px -> ~228px.
     2. the .foot stops being sticky while the keyboard is up. A pinned foot
        is the only thing that can ever land ON the field; in flow it cannot,
        whatever else the screen grows (Turnstile row, an error line). Where
        the content fits the 379px (native shell, App Check — no Turnstile
        row) the foot sits exactly where the pinned one did. Where it does
        not (web, Turnstile's 65px row), the CTA is one short scroll below
        the field — and the keyboard's own Go key submits the form anyway
        (enterkeyhint="go" on the phone input).
   `.kb-open` is set from visualViewport, which Drew's own measurement proves
   Chrome iOS reports correctly. */
/* Animatable properties only — `display: none` cannot transition, and the
   shrink should read as the screen making room, not as a flicker. 180ms is
   below the keyboard's own animation so the two feel like one movement. */
.login-hero, .login-logo, .login-hero h2, .login-hero > p, .foot, .foot-note {
  transition: padding 180ms ease, margin 180ms ease, width 180ms ease, height 180ms ease,
              opacity 140ms ease, font-size 180ms ease, transform 180ms ease;
}
.kb-open .login-hero { padding-top: calc(12px + env(safe-area-inset-top, 0px)); }
.kb-open .login-logo { width: 32px; height: 32px; margin-bottom: 10px; }
.kb-open .login-hero > p { margin-bottom: 10px; }
.kb-open .login-hero h2 { margin-bottom: 4px; font-size: 18px; }
.kb-open .foot { position: static; padding-top: 10px; }

/* Someone who asked the system not to animate gets the same layout, instantly. */
@media (prefers-reduced-motion: reduce) {
  .login-hero, .login-logo, .login-hero h2, .login-hero > p, .foot, .foot-note { transition: none; }
}
.login-logo { width: 56px; height: 56px; margin: 0 auto 18px; display: block; }
.login-hero h2 { font-size: 20px; font-weight: 800; color: var(--ink); margin: 0 0 6px; font-family: var(--font-ui); }
.login-hero > p { font-size: 13px; color: var(--ink-soft); margin: 0 0 28px; line-height: 1.5; }

/* CR-BUILDR-A025-1 — the force-update screen's art box (ratified card
   force-update.html, PROPOSED block). The Lottie composition is 620x320;
   the box takes the hero's width up to 380px and keeps that ratio, so
   nothing below it moves while it loads. The hero becomes a flex column
   here so the box takes the leftover height with auto margins: on a tall
   phone the cat floats in the middle of the white, on a short one it
   tucks under the paragraph. Decorative: aria-hidden, the h2 carries the
   meaning. */
.login-hero.fu-hero { display: flex; flex-direction: column; }
.fu-art { width: 100%; max-width: 380px; aspect-ratio: 620 / 320; margin: auto; }
.fu-art svg { display: block; }

.field-big { margin-bottom: 14px; text-align: left; }
.field-big label {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; color: var(--ink-mute);
  text-transform: uppercase; letter-spacing: .08em; display: block; margin-bottom: 6px;
}
.field-big label .opt { font-weight: 500; text-transform: none; letter-spacing: 0; }
.field-big input {
  width: 100%; min-height: 56px; padding: 0 14px; border: 2px solid var(--dust); border-radius: var(--r-md);
  font-size: 16px; color: var(--ink); background: var(--paper); font-family: var(--font-ui); box-sizing: border-box;
}
.field-big input::placeholder { color: var(--chalk); }
.field-big input:focus-visible { outline: none; border-color: var(--navy); box-shadow: 0 0 0 3px rgba(19,38,92,.15); }

/* Turnstile — CR-BUILDR-A001-32, verbatim from the ratified card
   (ds-bundle-app-r1/app/signup.html's own .turnstile/.ts-box/.ts-txt/
   .ts-brand rules) — a 56px row between Email and the self-declare note.
   Deliberately quieter than the CTA; never an interstitial. */
.turnstile {
  display: flex; align-items: center; gap: 10px; min-height: 56px; padding: 0 12px; margin-top: 4px;
  border: 2px solid var(--dust); border-radius: var(--r-md); background: var(--paper); text-align: left;
  width: 100%; font-family: var(--font-ui); cursor: pointer; box-sizing: border-box;
}
.ts-box {
  width: 22px; height: 22px; border-radius: var(--r-sm); border: 2px solid var(--dust); background: var(--paper);
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
}
.ts-box svg { width: 12px; height: 12px; stroke: transparent; stroke-width: 4; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.turnstile.done .ts-box { border-color: var(--go-deep); background: var(--go-deep); }
.turnstile.done .ts-box svg { stroke: #fff; }
.ts-txt { flex: 1; font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
.turnstile.done .ts-txt { color: var(--go-deep); }
.ts-brand { font-family: var(--font-mono); font-size: 9px; line-height: 1.35; color: var(--ink-mute); text-align: right; flex-shrink: 0; }
.turnstile:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* Cloudflare's own explicit-render widget mounts INSIDE #suTurnstile — no
   .turnstile/.ts-box chrome then applies (Cloudflare draws its own frame);
   those classes exist only for the dev-mode fallback control below. */
#suTurnstile iframe { border-radius: var(--r-md); }

.self-declare {
  background: var(--board); border: 1px solid var(--dust); border-radius: var(--r-md); padding: 10px 12px;
  font-size: 11.5px; color: var(--ink-soft); margin-top: 12px; line-height: 1.5; text-align: left;
}

.foot {
  flex-shrink: 0; position: sticky; bottom: 0; z-index: 2;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--dust); background: var(--paper);
}
.btn-cta {
  width: 100%; min-height: 64px; border: 2px solid var(--navy-edge); border-radius: var(--r-md);
  background: var(--navy); color: #fff; font-size: 16.5px; font-weight: 800; font-family: var(--font-ui);
  cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;
}
/* CR-BUILDR-A008-2 — the destructive primary: the same button filled with
   the one colour company law reserves for it (card's own comment,
   ds-bundle-app-r3). Declared BEFORE .blocked below so a blocked
   destructive button is grey, never red — its colour appears only when it
   can act (card fix 1, the cascade-ordering defect its own self-render
   caught and corrected before ratification). */
.btn-cta.btn-danger { background: var(--danger-deep); border-color: var(--danger-deep); }
.btn-cta:disabled, .btn-cta.blocked { background: var(--board); border-color: var(--dust); color: var(--ink-mute); cursor: not-allowed; }
.btn-cta:focus-visible { outline: 2px solid var(--navy-edge); outline-offset: 2px; }
.foot-note { text-align: center; font-family: var(--font-mono); font-size: 11px; color: var(--ink-mute); margin: 8px 0 0; }

/* ---------------------------------------------------------------------
   Sign-up · service area (card-verbatim, ds-bundle-app CR-BUILDR-A025-4/5
   signup-service-area.html) — the PROPOSED block, four rules, is the
   whole change: the grid inside .login-hero is left-aligned content in a
   centred hero (same as .field-big's own text-align: left); the Order
   screen's own group rhythm (order-flow.css:129, 26px) separates the city
   grid from the exit button under it; an option that spans both columns
   (Elsewhere in the GTA / Outside the GTA / Elsewhere in Ontario) uses the
   grid's own two tracks, joined; the kept-details card on the not-yet
   screen sits under the paragraph with the paragraph's own 28px gap above
   it (already there) and the sign-up screen's 12px note gap below.
   --------------------------------------------------------------------- */
.area-grid { text-align: left; }
.area-grid + .area-grid { margin-top: 26px; }
.opt-btn.span { grid-column: 1 / -1; }
.login-hero .sum-card { text-align: left; margin-bottom: 0; }
/* Fix round 2 (proof-report-2, minor) — the save-error banner sits after
   the last .area-grid (the Outside the GTA button), not under a form
   field like every other .code-err.field-err use, so that rule's own
   margin-top: -4px (shell.css:516) would pull it up into the button. Same
   26px group-rhythm value as .area-grid + .area-grid two lines above —
   no new spacing value, the existing one applied to a new neighbour.
   Fix round 4 (proof-report-3, F1) — this rule and .code-err.field-err
   (shell.css:523) both have specificity (0,2,0), and .code-err.field-err
   comes later in the file, so IT was winning and the banner rendered
   with margin-top: -4px (still overlapping the button). Adding
   .code-err to this selector raises it to (0,3,0) so it wins regardless
   of source order. */
.area-grid + .code-err.area-save-err { margin-top: 26px; }

.signup-login-link {
  display: block; width: 100%; text-align: center; margin-top: 12px; min-height: 44px; padding: 10px 4px;
  background: none; border: none; font-size: 13px; font-weight: 700; color: var(--navy);
  font-family: var(--font-ui); cursor: pointer;
}

/* code entry (screens 2a/2b, shared by signup + login) */
.code-wrap { position: relative; margin-top: 4px; }
.code-input {
  position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; z-index: 2;
  border: none; background: none; font-size: 16px; letter-spacing: 1em; text-align: center;
  font-family: var(--font-mono); color: transparent; caret-color: transparent;
}
.code-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin-top: 4px; }
.code-box {
  height: 64px; border: 2px solid var(--dust); border-radius: var(--r-md); background: var(--paper);
  display: flex; align-items: center; justify-content: center; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; font-size: 21px; font-weight: 600; color: var(--ink);
}
.code-box.filled { border-color: var(--navy); }
.code-box.caret { border-color: var(--navy); box-shadow: 0 0 0 3px rgba(19,38,92,.15); }
.code-box.err { border-color: var(--danger-deep); color: var(--danger-deep); }
.code-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; text-align: left; }
.code-err {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 10px; padding: 10px 12px;
  border: 2px solid var(--danger-deep); border-radius: var(--r-md); background: var(--paper);
  text-align: left; font-size: 12.5px; color: var(--danger-deep); line-height: 1.4;
}
.code-err[hidden] { display: none; }
.code-err svg { width: 16px; height: 16px; stroke: var(--danger-deep); fill: none; stroke-width: 2; flex-shrink: 0; margin-top: 1px; }
/* CR-BUILDR-A008-2 — frame B5's variant: the line sits ABOVE the Delete
   account row inside .acct-leave-del (CR-BUILDR-A016-7), so its margin
   points down instead of up (card's own .code-err.above rule, byte-identical). */
.code-err.above { margin: 0 0 10px; }
.code-link {
  background: none; border: none; padding: 9px 2px; min-height: 44px; font-family: var(--font-ui);
  font-size: 12.5px; font-weight: 700; color: var(--navy); display: inline-flex; align-items: center; cursor: pointer;
}
.code-link:disabled { color: var(--ink-mute); cursor: default; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* screen 3 — done receipt (scoped, shares nothing with order-done's success) */
.signup-screen .success { flex: 1 1 auto; padding: 60px 24px 0; text-align: center; }
.signup-screen .success-icon {
  width: 72px; height: 72px; border-radius: 50%; background: var(--paper); border: 2px solid var(--go-deep);
  display: flex; align-items: center; justify-content: center; margin: 0 auto 18px;
}
.signup-screen .success-icon svg { width: 34px; height: 34px; stroke: var(--go-deep); fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.signup-screen .success h2 { font-size: 19px; font-weight: 800; color: var(--ink); margin: 0 0 6px; }
.signup-screen .success p { font-size: 13px; color: var(--ink-soft); margin: 0 0 4px; line-height: 1.5; }

/* ---------------------------------------------------------------------
   Welcome (signed-in) — Direction A "the opening draws itself", r2
   (Carbon proposal card welcome-r2.html, Drew ratified 2026-09-14 with
   two amendments: the panel slides; both witness lines carry inches).
   Replaces the tick + two restating sentences with the product itself,
   drawn in the app's own weights, and three lines that are each a real
   screen. The .foot/.btn-cta block is the ratified one, untouched.
   --------------------------------------------------------------------- */
.wa { flex: 1 1 auto; display: flex; flex-direction: column; padding: calc(26px + env(safe-area-inset-top, 0px)) 24px 12px; text-align: center; min-height: 0; }

.wa-art { width: 206px; height: 147px; margin: 0 auto 18px; display: block; overflow: visible; }
/* Structural strokes are --ink-mute (4.93:1 on paper — the frame in --chalk
   measured 1.57:1, a faint line on a sunlit phone); glass detail is --dust
   (decorative, carries no information); the sliding panel is --navy, the
   language's interactive colour, because that panel is the installer's
   choice. The handle is the one warm mark: --orange-ink, 6px, the way a shop
   drawing marks the operable part — not chrome, not the logo. */
.wa-art .frame { stroke: var(--ink-mute); stroke-width: 2.5; fill: none; stroke-linejoin: round; }
.wa-art .panel { stroke: var(--navy); stroke-width: 2.5; fill: none; stroke-linejoin: round; }
.wa-art .glass { stroke: var(--dust); stroke-width: 1.5; fill: none; }
.wa-art .wit { stroke: var(--ink-mute); stroke-width: 1.5; fill: none; stroke-linecap: round; }
.wa-art .dim { font-family: var(--font-mono); font-size: 11px; font-weight: 600; fill: var(--ink-mute); letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.wa-art .grip { stroke: var(--orange-ink); stroke-width: 3; stroke-linecap: round; }

/* Entrance: pathLength=1 on every drawn element, so one dash rule fits all;
   stroke-dashoffset is paint-only. Three strokes ~600ms, then the handle and
   the numbers fade in, then the rows rise. Settled by ~1.1s. */
.wa-art .draw { stroke-dasharray: 1; stroke-dashoffset: 1; animation: wa-draw 560ms var(--ease) forwards; }
.wa-art .d1 { animation-delay: 40ms; }
.wa-art .d2 { animation-delay: 150ms; }
.wa-art .d3 { animation-delay: 250ms; }
.wa-art .fade { opacity: 0; animation: wa-fade 240ms var(--ease) 620ms forwards; }
@keyframes wa-draw { to { stroke-dashoffset: 0; } }
@keyframes wa-fade { to { opacity: 1; } }

/* Amendment 1 — the panel slides. The one idle motion on the screen and the
   product's own gesture. Starts at 900ms, after the entrance has settled;
   3.8s ease-in-out cycle, 22 units of travel in a 210-unit viewBox, still for
   ~1.5s of every cycle at each end. transform on one <g> (panel + glazing bar
   + handle) — composited, no layout. It cannot reach the frame: 10 units
   clear at rest, 58 units clear at full travel. */
.wa-art .slide { transform-box: fill-box; transform-origin: center; animation: wa-slide 3800ms ease-in-out 900ms infinite; }
@keyframes wa-slide {
  0%, 10% { transform: translateX(0); }
  42%, 58% { transform: translateX(-22px); }
  90%, 100% { transform: translateX(0); }
}

.wa h2 { font-size: 21px; font-weight: 800; color: var(--ink); margin: 0 0 6px; letter-spacing: -.01em; }
.wa .lede { font-size: 13px; color: var(--ink-soft); margin: 0 0 20px; line-height: 1.5; }

.wa-list { list-style: none; margin: 0; padding: 0; text-align: left; border-top: 1px solid var(--dust); }
.wa-list li {
  display: flex; gap: 12px; align-items: flex-start; padding: 11px 2px; border-bottom: 1px solid var(--dust);
  opacity: 0; transform: translateY(8px); animation: wa-in 300ms var(--ease) forwards;
}
.wa-list li:nth-child(1) { animation-delay: 660ms; }
.wa-list li:nth-child(2) { animation-delay: 720ms; }
.wa-list li:nth-child(3) { animation-delay: 780ms; }
@keyframes wa-in { to { opacity: 1; transform: translateY(0); } }
.wa-list .ic { width: 20px; height: 20px; color: var(--navy); margin-top: 1px; }
.wa-list .tx { flex: 1; min-width: 0; }
.wa-list .tx b { display: block; font-size: 13.5px; font-weight: 700; color: var(--ink); line-height: 1.35; margin-bottom: 2px; }
.wa-list .tx span { display: block; font-size: 12px; color: var(--ink-mute); line-height: 1.45; }

/* Reduced motion: everything present, finished, instantly; the panel rests
   in the position the drawing was composed in. */
@media (prefers-reduced-motion: reduce) {
  .wa-art .draw { stroke-dashoffset: 0; animation: none; }
  .wa-art .fade { opacity: 1; animation: none; }
  .wa-art .slide { animation: none; transform: translateX(0); }
  .wa-list li { opacity: 1; transform: none; animation: none; }
}

/* ---------------------------------------------------------------------
   Login (phone-flow shell — see file header note) — reuses the same
   login-hero/.field-big/.foot/.btn-cta primitives as signup, plus the
   card's own banner + waiting-item treatment for "code sent, waiting".
   --------------------------------------------------------------------- */

.login-page-wrap { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; background: var(--paper); }

/* phone-field inline error (login.html card, states 1b/1c) — same grammar
   as .code-err, offset to sit directly under the field it describes. */
.field-big input.err { border-color: var(--danger-deep); }
.code-err.field-err { margin-top: -4px; margin-bottom: 14px; }

/* the fail-closed hold state (login.html card, state 2c: three tries used).
   --hold-deep, not --danger-deep — this is a stop, not the installer's
   error (the same outline-not-fill treatment as the Confirm card's
   pickup-unavailable warning). */
.banner-hold {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 10px; padding: 10px 12px;
  border: 2px solid var(--hold-deep); border-radius: var(--r-md); background: var(--paper);
  text-align: left; font-size: 12.5px; color: var(--hold-deep); line-height: 1.4;
}
.banner-hold svg { width: 16px; height: 16px; stroke: var(--hold-deep); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; margin-top: 1px; }

/* CR-BUILDR-A025-1 — the remote-message notice on Home (ratified card
   force-update.html, PROPOSED block). `.banner-hold`'s own geometry
   (flex, 8px gap, 10px/12px pad, 2px edge, r-md, 12.5px/1.4) carrying a
   TONE instead of one fixed colour, plus a dismiss target: .info takes
   .self-declare's colours (a note, not a stop), .warn takes
   .banner-hold's own hold-deep (a hold, not the installer's error).
   Never navy (interactive), never danger (nothing the installer did is
   wrong). The dismiss button is order-flow.css's own .io-app-bar-close
   glyph shape (two crossed lines, never a typed x) in a 44px target. */
.notice {
  display: flex; align-items: center; gap: 8px; width: 100%; min-height: 56px;
  padding: 6px 6px 6px 12px; border: 2px solid var(--dust); border-radius: var(--r-md);
  background: var(--board); text-align: left; font-size: 12.5px; color: var(--ink-soft); line-height: 1.4;
}
.notice > svg { width: 16px; height: 16px; stroke: var(--ink-mute); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
.notice > span { flex: 1; min-width: 0; }
.notice.warn { border-color: var(--hold-deep); background: var(--paper); color: var(--hold-deep); }
.notice.warn > svg { stroke: var(--hold-deep); }
.notice-close {
  width: 44px; height: 44px; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--r-md); color: var(--ink-mute); cursor: pointer;
}
.notice.warn .notice-close { color: var(--hold-deep); }
.notice-close svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; }
.notice-close:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* Dismiss: the block fades (opacity only) and is removed; reduced motion
   -> removed at once. No entrance motion: the message arrives with the
   screen, from the same fetch that painted it. */
.notice.is-leaving { opacity: 0; transition: opacity var(--dur-2) var(--ease); }
@media (prefers-reduced-motion: reduce) { .notice.is-leaving { transition: none; } }

.code-box.void { background: var(--board); border-color: var(--dust); }
.code-meta.solo { justify-content: center; }

.spinner {
  width: 15px; height: 15px; border-radius: 50%; border: 2px solid rgba(255,255,255,.4);
  border-top-color: #fff; animation: spin .7s linear infinite; flex-shrink: 0; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------------
   Home / order list (card-verbatim, home-navy-variant.html)
   --------------------------------------------------------------------- */


.home-screen { display: flex; flex-direction: column; align-items: center; gap: var(--sp-6); flex: 1 1 auto; min-height: 0; }

.order-circle {
  width: 172px; height: 172px; border-radius: 50%; border: 2px solid var(--navy-edge);
  background: var(--navy); color: #fff; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; cursor: pointer; font-family: var(--font-ui);
}
.order-circle span { font-size: 28px; font-weight: 800; letter-spacing: -.01em; line-height: 1.1; text-align: center; }
.order-circle:focus-visible { outline: 3px solid var(--navy-edge); outline-offset: 3px; }
.orders-block { width: 100%; display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; min-height: 0; }
.g-label { align-self: flex-start; font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-mute); margin: 0; }
.home-orders-list { list-style: none; margin: 0; padding: 0; width: 100%; }
.home-orders-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; padding-bottom: calc(var(--sp-12) + env(safe-area-inset-bottom, 0px)); }
.seg:not([hidden]) ~ .home-orders-body { border-top: 1px solid var(--dust); }

/* CR-BUILDR-A016-5 — THE TAB BAR (RATIFIED by Drew 2026-09-19 with the
   E-home-tabs card; it was already ratified as candidate chrome on
   home-r2:95-100, values unchanged, its card-only prefix dropped). Replaces
   CR-BUILDR-A006-15's `.sec-head` (deleted by this CR). A two-segment
   control in the .pill grammar (mono caps) on a board track. */
.seg {
  display: flex; width: 100%; padding: 3px; background: var(--board); border: 1px solid var(--dust); border-radius: var(--r-full);
}
.seg[hidden] { display: none; }
.seg-btn { flex: 1; min-height: 44px; border-radius: var(--r-full); border: 2px solid transparent; background: none;
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-mute); cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; }
.seg-btn.on { background: var(--paper); color: var(--navy); border-color: var(--dust); }
/* THE ONE NEW LINE in the tab bar (ratified with this card): the count is full ink while its tab is NOT the open one, so "something is
   waiting" is the darkest thing on the bar; on the open tab it is simply part of the navy label. */
.seg-count { font-variant-numeric: tabular-nums; color: var(--ink); }
.seg-btn.on .seg-count { color: inherit; }
.seg-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.home-orders-loading, .home-orders-empty {
  text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--ink-mute); font-size: 13px;
}
/* CR-BUILDR-A016-5 — this line now teaches where a door goes (README "Said
   plainly" #4): `--chalk` on paper measured 1.57:1; `--ink-mute` measures
   4.93:1. Ratified with the card, 2026-09-19. */
.home-orders-empty-sub { color: var(--ink-mute); font-size: 12.5px; }
.home-orders-error { color: var(--danger-deep); }

/* CR-BUILDR-A022-7 — Home empty state r2 (Buildr Claude Design project
   d9115afa-7967-4fe5-a5d8-fefac0ad27e5, app/home-empty-r2.html, ratified by
   Drew 2026-09-26). Four rules, the whole change: the drawing brings its
   own air so the block loses the two-line version's top pad; the title is
   `.wa h2`'s own grammar (shell.css .wa h2 above) since it sits under the
   same drawing it sits under on Welcome; the sub line keeps this file's own
   -sub rule and gains the lede's 20px gap before the rows; the rows get the
   block's left/right air back (the block is centered text, the rows are a
   list). `.wa-art`/`.wa-list` and their @keyframes are untouched — this CR
   only reuses them (see views/door-art.js). Fix round 1 (proof/carbon) — the
   five values below have an EXACT token match in tokens.css; swapped from
   raw px so this rule reads from the same scale as every other rule in the
   file: 8px/4px -> --sp-2/--sp-1, 21px -> --t-h3 (identical to .wa h2's own
   font-size), 20px -> --sp-5, the rows' 8px inset -> --sp-2 again. */
.home-orders-empty.first-run { padding: var(--sp-2) var(--sp-1) 0; }
.home-orders-empty .home-empty-title { font-size: var(--t-h3); font-weight: 800; color: var(--ink); margin: 0 0 6px; letter-spacing: -.01em; line-height: 1.2; }
.home-orders-empty.first-run .home-orders-empty-sub { margin: 0 0 var(--sp-5); }
.home-orders-empty .wa-list { margin: 0 var(--sp-2); }

.order-row { display: flex; align-items: center; justify-content: space-between; padding: 13px 2px; border-bottom: 1px solid var(--dust); gap: var(--tap-gap); min-height: var(--tap-min); cursor: pointer; }
.order-row:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.order-row-l { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tag {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); background: var(--board); align-self: flex-start; padding: 2px 7px; border-radius: var(--r-sm);
  margin-bottom: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.order-prod { font-size: 15px; font-weight: 700; color: var(--ink); }
.order-sub { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12px; color: var(--ink-mute); }
.pill { font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; padding: 5px 10px; border-radius: var(--r-full); flex-shrink: 0; background: var(--paper); }
.pill.ready { border: 2px solid var(--go-deep); color: var(--go-deep); }
.pill.infab { border: 2px solid var(--chalk); color: var(--ink-soft); }
.pill.done  { border: 2px solid var(--dust); color: var(--ink-mute); background: var(--board); }
.pill.pending { border: 2px solid var(--hold-deep); color: var(--hold-deep); }
.pill.cancelled { border: 2px solid var(--ink); color: var(--paper); background: var(--ink); }
/* CR-BUILDR-A006-16 — unpaid = the order exists only in Buildr and waits on the
   installer's own payment: the one state where the next move is his, so the
   one pill in the hold family that is filled. Value verbatim from the
   ratified card (ds-bundle-app-r5/app/home-r2.html:85). Paper on hold-deep
   7.13:1. */
.pill.unpaid { border: 2px solid var(--hold-deep); color: var(--paper); background: var(--hold-deep); }
/* CR-BUILDR-A016-5 — a door saved on the phone, never an order: navy outline,
   never filled (that tone is reserved for "the next move is his own money"). */
.pill.saved { border: 2px solid var(--navy); color: var(--navy); }

/* CR-BUILDR-A006-16 — the unpaid detail's one acknowledgement line ("Order
   deleted." / the delete failure is shown inline instead, see .code-err).
   Card ref: ds-bundle-app-r5/app/home-r2.html:89-91. Reserves 16px so rows
   never jump when it appears; the hold tone is unused by this CR (only the
   success acknowledgement is in scope; the failure line uses .code-err) but
   is kept because it is part of the card's single .list-status declaration. */
.list-status { font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em; color: var(--ink-mute);
  min-height: 16px; line-height: 16px; margin: -4px 0 0; align-self: stretch; }
.list-status.hold { color: var(--hold-deep); }

/* CR-BUILDR-A006-17 — pull-to-refresh. Values verbatim from the ratified
   card (ds-bundle-app-r5/app/home-r2.html:147-157): the slot opens above the
   screen's content while the finger pulls; the ring is the shell's own
   spinner geometry (.spinner above, --navy arc on --dust track) in the
   list's colours. .home-screen carries the translate during the pull (JS
   sets an inline transform per the motion table's "live" tracking; the
   settle/release transitions are also set inline since their duration
   depends on which phase just ended, per the card's own table). */
.ptr-slot { align-self: stretch; display: flex; align-items: center; justify-content: center; overflow: hidden;
  margin: -24px 0 0; height: 0; }
.ptr-ring { width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--dust); border-top-color: var(--navy); flex-shrink: 0; }
.ptr-slot.pulling { height: 56px; }
.ptr-slot.refreshing { height: 44px; }
.ptr-slot.refreshing .ptr-ring { animation: spin .7s linear infinite; }
@media (prefers-reduced-motion: reduce) { .ptr-slot.refreshing .ptr-ring { animation: none; } }

/* The pointer-fine alternative (WCAG 2.5.1 — a drag is a path gesture, so a
   tap must reach the same outcome): the card's "Refresh" .code-link, shown
   only on hover-and-fine-pointer devices (a touch device has the gesture
   itself). Sits at the right of the first header row, per the card. */
.ptr-refresh-link { display: none; }
@media (hover: hover) and (pointer: fine) {
  .ptr-refresh-link { display: inline-flex; align-self: flex-end; margin: -6px 0 -4px; }
}

/* The scroller the pull gesture (and the existing Order-dock listener) bind
   to must not fight the browser's own pull-to-refresh/overscroll glow —
   the card's own rule (ds-bundle-app-r5 motion table notes). */
#viewHost { overscroll-behavior-y: contain; }
