/* ============================================================
   RoofBros — components.css
   App UI components — each maps 1:1 to a future RN component.
   Load order: tokens → base → components → device → docs/page.
   ============================================================ */

/* ---------- buttons (spec: Outfit 500 Medium, 15px) ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-ui); font-variation-settings: 'wght' 500; font-size: var(--t-body);
  border: none; cursor: pointer; border-radius: var(--r-pill);
  padding: 14px 28px; min-height: var(--tap-min);
}
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:active { background: var(--accent-press); }
.btn-secondary {
  background: transparent; color: var(--ink);
  border: 1px solid var(--line-ink);
  padding: 12px 24px; min-height: var(--tap-secondary);
}
.btn-compact { padding: 8px 14px; min-height: var(--tap-compact-visual); font-size: var(--t-body); }
/* destructive action — err outline (err #D92D20 on white = 4.83:1, passes). Never
   filled like the primary, and always physically separated from it (Nielsen #3). */
.btn-danger { background: transparent; color: var(--err); border: 1px solid var(--err); padding: 12px 24px; min-height: var(--tap-secondary); }
.btn-danger:active { background: var(--err-tint); }
.btn-full { width: 100%; }
.btn:disabled { opacity: 0.38; cursor: not-allowed; }
/* async/loading state — every commit action ("Create account", "Save quote",
   "Submit request") confirms within 400ms (Doherty): the label names the work
   ("Saving") and three dots pulse. Button keeps its size so layout never jumps;
   input is blocked while busy. RN: label + <ActivityIndicator>, with
   accessibilityState={{ busy: true }}. Full-screen waits use .loader (0A2);
   this is button-local feedback. */
.btn.loading { pointer-events: none; }
.btn.loading .dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; animation: btn-dot 1s ease-in-out infinite; }
.btn.loading .dot + .dot { margin-left: -3px; }
.btn.loading .dot:nth-of-type(2) { animation-delay: 0.16s; }
.btn.loading .dot:nth-of-type(3) { animation-delay: 0.32s; }
@keyframes btn-dot { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .btn.loading .dot { animation: none; opacity: 0.7; } }

/* ---------- chips, pills, status ---------- */
.chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-caption); padding: 8px 14px; min-height: 44px;
  border: 1px solid var(--line-ink); color: var(--body); background: var(--surface);
  border-radius: var(--r-pill);
  /* a chip is always ONE line at its natural width. Without these two, a long
     label ("Urban Shingles") wraps to two lines and that chip stops matching
     the others — and because flex items shrink by default, the row squeezed
     the chips to fit instead of scrolling. flex: none + nowrap means the row
     overflows and scrolls (see .chip-row), which is the intended behaviour. */
  flex: none; white-space: nowrap;
}
.chip.on { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }  /* selected = light maroon touch (tint + text), NOT a solid fill — a solid fill would compete with the CTA. Matches swatch.sel / opt-card.sel */
.status {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-micro); letter-spacing: 0.05em; text-transform: uppercase;
  padding: 4px 10px;
  border-radius: var(--r-pill);
}
.status.measured { background: var(--panel); color: var(--body); }
.status.quoted { background: var(--link-tint); color: var(--link); } /* slate-blue: a priced quote is informational — distinct from grey Measured, amber Requested, green Delivered. The one status use of the wayfinding blue, so all four states read apart. Amber aging-dot still pops on it. */
.status.requested { background: var(--warn-tint); color: var(--warn); } /* delivery requested = in flight — the one active amber state between quoted and delivered */
.status.delivery { background: var(--ok-tint); color: var(--ok); } /* delivered = job done, green */
.status.error { background: var(--err-tint); color: var(--err); }
/* age-dot is never colour-alone (WCAG 1.4.1): every instance carries
   role="img" + aria-label naming the idle state; RN uses accessibilityLabel */
.age-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }

/* ---------- inputs ---------- */
.field label {
  display: block; font-family: var(--font-ui); font-variation-settings: 'wght' 500;
  font-size: var(--t-body); color: var(--body); margin-bottom: 6px;
}
.field .control {
  display: flex; align-items: center; gap: 10px;
  border: none; background: var(--field);
  border-radius: var(--r-structure);
  padding: 12px 16px; min-height: 52px;
  font-family: var(--font-ui); font-size: var(--t-heading); color: var(--ink);
}
.field .control.placeholder { color: var(--placeholder); }
.field .control.focus { background: var(--field-active); outline: 1.5px solid var(--field-active-line); outline-offset: -1.5px; }

/* ---------- grouped input ("construction": related fields as one unit,
   Gestalt common-region — country+phone, email+password, etc.) ---------- */
.input-group { background: var(--field); border-radius: var(--r-structure); overflow: hidden; }
.ig-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; min-height: 66px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}
.ig-row:last-child { border-bottom: none; }
.ig-row.focus { background: var(--field-active); outline: 1.5px solid var(--field-active-line); outline-offset: -1.5px; }
.ig-row .ig-t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ig-row .ig-t label {
  font-family: var(--font-ui); font-variation-settings: 'wght' 500;
  font-size: var(--t-body); color: var(--body);  /* on the grey field: --muted measured 3.81:1 (fails AA), --body = 7.5:1 — matches .field label */
}
.ig-row .ig-t .val {
  font-family: var(--font-ui); font-size: var(--t-heading); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ig-row .ig-t .ph, .ig-row .ig-t .val.ph { color: var(--placeholder); }
.ig-row > .ic { color: var(--muted); flex: none; }

/* ---------- READ-ONLY value ----------
   A fact the roofer can read but not change: the email a code went to, the
   address materials are going to. It deliberately does NOT wear the field
   costume — in this system the grey fill (--field) IS the affordance meaning
   "tap to type", so borrowing it for a value nobody can edit invites a tap
   that does nothing. No fill — but it does get a box; see Shape below.

   Text stays at FULL contrast (13.4:1), because this is information the screen
   depends on — the roofer has to read which email the code went to. That's the
   difference from DISABLED (.otp.disabled, .btn:disabled), which dims a real
   control that will become available and has nothing to read yet. Dimming a
   value you must read is the mistake this replaces: .ig-row.locked ran the
   email at 3.49:1 and its label at 2.60:1, both under AA.

   Three states, three appearances:
     grey fill      = editable, tap to type
     white+outline  = editable, focused
     no fill        = read-only, information only
     dimmed         = disabled control, will become available

   Shape: a square OUTLINED box — never a bottom rule. A lone bottom border is
   the Material underlined-field look: it reads as a text input you can tap,
   which is the opposite of what this row is, and it's a third shape language
   next to "square = structure, pill = touch". Outlined and unfilled keeps the
   no-fill read-only rule while still reading as structure.
   (The old rule paired border-bottom with .ro-row:last-of-type to strip the
   last divider — but :last-of-type is by ELEMENT, not class, and every real
   use is a single row followed by another div, so it never once matched: all
   8 instances rendered a divider dividing nothing. Adjacent rows now fuse via
   the sibling selector, which actually works.) */
.ro-row { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--line-soft); border-radius: var(--r-structure); }
.ro-row + .ro-row { border-top: none; }
.ro-row > .ic { width: 18px; height: 18px; color: var(--muted); flex: none; }
.ro-row .t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ro-row label { font-family: var(--font-ui); font-variation-settings: 'wght' 500; font-size: var(--t-caption); color: var(--muted); }
.ro-row .val { font-family: var(--font-ui); font-size: var(--t-heading); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------- OTP code entry (6 boxes) — same field system as every input: grey at rest, white + soft border when active ---------- */
.otp { display: flex; gap: 8px; }
.otp .box {
  flex: 1; height: 60px; border-radius: var(--r-structure);
  background: var(--field); display: grid; place-items: center;
  font-family: var(--font-display); font-variation-settings: 'wght' 600;
  font-size: var(--t-data); color: var(--ink); font-variant-numeric: tabular-nums;
}
.otp .box.active { background: var(--field-active); outline: 1.5px solid var(--field-active-line); outline-offset: -1.5px; }
.otp .box .caret { width: 2px; height: 26px; background: var(--ink); }
.otp.disabled .box { opacity: 0.38; }
.otp.error .box { background: var(--field-active); outline: 1.5px solid var(--err); outline-offset: -1.5px; color: var(--err); }
.field-error { margin: 8px 0 0; font-family: var(--font-ui); font-size: var(--t-caption); color: var(--err); }

/* ---------- notice banner (measurement-failed, low-confidence, etc.) ---------- */
.banner {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--warn-tint); border: 1px solid var(--warn);
  border-radius: var(--r-structure); padding: 12px 14px;
}
.banner .ic { width: 20px; height: 20px; flex: none; color: var(--warn); margin-top: 1px; }
.banner p { font-family: var(--font-ui); font-size: var(--t-caption); line-height: 1.5; color: var(--warn); }
.banner b { font-variation-settings: 'wght' 600; }

/* social sign-in: the fast path, offered before the manual form (Jakob's Law —
   this ordering matches the convention every user already knows). Apple's mark
   is a CSS-mask icon like the rest of our system (masks ignore fill, so it
   still inherits ink); Google's is genuinely 4-colour and can't go through a
   single-colour mask, so it's an inline SVG kept at its own real brand colours. */
.social-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.social-row .btn { gap: 8px; padding-left: 12px; padding-right: 12px; }
.social-row .ic, .social-row svg { width: 20px; height: 20px; flex: none; }
.auth-divider { display: flex; align-items: center; gap: 12px; margin: 4px 0; font-family: var(--font-ui); font-size: var(--t-caption); color: var(--muted); }
.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--line-soft); }

/* ---------- figure tile (measurement data) ---------- */
.fig {
  background: var(--panel); border: 1px solid var(--line-soft); padding: 12px 14px;
}
.fig .v {
  font-family: var(--font-display); font-variation-settings: 'wght' 600;
  font-variant-numeric: tabular-nums; font-size: var(--t-data); color: var(--ink);
  letter-spacing: -0.01em; line-height: 1.1;
}
.fig .v small { font-size: var(--t-caption); color: var(--body); }  /* --muted was 3.96:1 on the panel bg; --body = 7.7:1 */
.fig .k {
  font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-micro); letter-spacing: 0.09em; text-transform: uppercase; color: var(--body);
  margin-top: 2px;
}

/* ---------- tile swatch (square — they read as tiles) ---------- */
.swatch { width: 44px; height: 44px; border: 1px solid var(--line-soft); cursor: pointer; position: relative; flex: none; appearance: none; -webkit-appearance: none; padding: 0; }
.swatch.sel { outline: 2px solid var(--accent); outline-offset: 1px; }
/* .cmp — the two colours loaded into the compare view. In play on the split, but
   NEITHER is chosen: on that screen the roofer is weighing two equals, so marking
   one "picked" invents a hierarchy they don't have. The choice is asked for at
   commit time (C2a's sheet) instead of guessed here. Deliberately not maroon and
   deliberately no checkmark — maroon+check means "chosen" everywhere in this app,
   and an earlier build put that same marker on both compare swatches, which left
   nothing on screen saying which colour Build quote would carry. */
.swatch.cmp { outline: 2px solid var(--line-ink); outline-offset: 1px; }
.swatch.sel::after {
  content: ''; position: absolute; inset: 0;
  background: #fff;
  -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12.5l5.5 5.5L20 6.5"/></svg>'); mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12.5l5.5 5.5L20 6.5"/></svg>');
  -webkit-mask-size: 16px; mask-size: 16px;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}

/* ---------- option card (single-select choice: job type, paint brand —
   a decision, not a text field, so the selected state borrows the same
   "maroon = active" language as .chip.on / .swatch.sel rather than the
   neutral field-focus grey) ---------- */
.opt-card {
  display: flex; align-items: center; gap: 12px; text-align: left; width: 100%;
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--r-structure); padding: 14px 16px;
}
.opt-card .ic { width: 24px; height: 24px; flex: none; color: var(--muted); }
.opt-card .ot { flex: 1; min-width: 0; }
/* a colour option (C2a's sheet): the swatch takes the leading icon's place, at its
   normal 44px because here the colour IS the content — after judging colours as
   pictures on the split, choosing them back out of a list of names would throw away
   the only thing the visualiser earned. .side is the left/right tie-back to the
   split, so the mapping from sheet row to screen half needs no thought. */
.opt-card > .swatch { cursor: inherit; }
.opt-card .side { flex: none; font-family: var(--font-ui); font-size: var(--t-caption); color: var(--muted); }
.opt-card b {
  display: block; font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-heading); color: var(--ink);
}
.opt-card span {
  display: block; font-family: var(--font-ui); font-size: var(--t-caption);
  color: var(--muted); margin-top: 2px;
}
.opt-card.sel { border-color: var(--accent); outline: 1.5px solid var(--accent); outline-offset: -1.5px; background: var(--accent-tint); }
.opt-card.sel .ic { color: var(--accent); }  /* icon recolours; label stays ink — same rule as .tabbar .t.on */
.opt-card.sel span { color: var(--body); }   /* --muted (3.38:1) fails on the tint bg; --body = 6.62:1 verified */

/* ---------- in-screen layout & app components ---------- */
/* bottom padding matches the welcome so every screen has the same gap above the home indicator / tab bar */
.appbody { flex: 1; overflow: hidden; display: flex; flex-direction: column; padding: 0 16px 16px; gap: 14px; }
.screen-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-top: 24px; }
.screen-title h5 { font-size: var(--t-display); }
.icon-btn {
  width: 44px; height: 44px; display: grid; place-items: center;
  border: 1px solid var(--line-ink); background: var(--surface); color: var(--ink); font-size: var(--t-body);
  border-radius: var(--r-pill);
  appearance: none; -webkit-appearance: none; padding: 0; cursor: pointer;  /* works as <button> — real element, so screen readers announce it */
}
/* horizontally scrollable (was overflow:hidden, which silently clipped any
   status added later); scrollbar hidden like .tour-track — the cut-off chip
   at the edge is the scroll affordance */
.chip-row { display: flex; align-items: center; gap: 8px; overflow-x: auto; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }

/* NOTE: there is deliberately no glossary/tooltip pattern. Trade terms
   (starters, three ways, sarking, point works) are the audience's own
   vocabulary — a roofer who needs "sarking" defined isn't the user. Removed
   2026-07-15; explaining a tradesman's words back to him reads as talking
   down, which is the same failure as marketing-speak. */

/* step indicator — 4 thin segments (Job · Site · Measure · Quote) filled up to
   the current phase. Visibility of status (Nielsen #1) across the wizard spine.
   Phase 3 is the roof measurement however it's obtained (AI reveal B2/B3, or
   the manual Roof dimensions screen); phase 4 is everything that builds the
   quote — colour/material choice AND the materials list — which is why the
   Restoration paint step (C0) and the New-Install tile steps (E1/E2) both sit
   at 4, not one at 3 and one at 4.
   Detours aren't wizard steps and omit the bar entirely: the visualiser (C2)
   and the measurement editor (B3a). */
.stepbar { display: flex; gap: 5px; }
.stepbar i { flex: 1; height: 3px; border-radius: var(--r-pill); background: var(--line-soft); }
.stepbar i.on { background: var(--accent); }

/* modal scrim + confirm dialog (discard-on-close). Scrim is a backdrop, not a
   drop-shadow, so it doesn't break the flat/no-shadow rule. */
.scrim { position: absolute; inset: 0; z-index: 40; background: rgba(0, 0, 0, 0.55); display: grid; place-items: center; padding: 0 20px; }
/* bottom-sheet variant — slides from the bottom, rounded top only (OS sheet
   convention, --r-sheet). Used for pick-one-of-a-few actions, e.g. D1b's
   Add photo (Take photo / Choose from library). */
.scrim.sheet { place-items: end stretch; padding: 0; }
.scrim.sheet .dialog { border-radius: var(--r-sheet) var(--r-sheet) 0 0; padding-bottom: 28px; }
.dialog { width: 100%; background: var(--surface); border-radius: var(--r-sheet); padding: 22px 20px 18px; }
.dialog h4 { font-family: var(--font-display); font-variation-settings: 'wght' 600; font-size: var(--t-title); color: var(--ink); margin: 0; }
.dialog p { margin: 8px 0 0; font-family: var(--font-ui); font-size: var(--t-body); line-height: 1.5; color: var(--body); }
.dialog-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }

/* search field: what the icon-btn opens into. Same fill/height/field language
   as every standalone .field .control — square, not the OS-native rounded bar */
.search-field {
  flex: 1; display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 12px 16px; background: var(--field-active);
  outline: 1.5px solid var(--field-active-line); outline-offset: -1.5px;
}
.search-field .ic { width: 18px; height: 18px; color: var(--muted); flex: none; }
.search-field span { font-family: var(--font-ui); font-size: var(--t-heading); color: var(--placeholder); }

/* job card */
.job-card {
  background: var(--surface); border: 1px solid var(--line-soft);
  padding: 12px; display: flex; gap: 12px; align-items: center;
}
.job-card .thumb {
  width: 56px; height: 56px; flex: none; overflow: hidden;
  border: 1px solid var(--line-soft); border-radius: var(--r-structure);
}
.job-card .thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.job-card .jt { flex: 1; min-width: 0; }
.job-card .jt b {
  display: block; font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  color: var(--ink); font-size: var(--t-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.job-card .jt span { font-family: var(--font-ui); font-size: var(--t-caption); color: var(--muted); }
.job-card .jr { text-align: right; flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.job-card .jr .val { font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-variant-numeric: tabular-nums; color: var(--ink); font-size: var(--t-body); }

/* aerial imagery mock */
.aerial {
  position: relative; overflow: hidden; flex: none;
  background: linear-gradient(120deg, #B9CDB4 0%, #A5BC9E 55%, #B2C7AC 100%);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-structure);
}
.aerial::before { content: ''; position: absolute; left: 7%; bottom: 7%; width: 26%; height: 17%; background: rgba(45,32,18,0.25); }
/* real photo variant — B1/B2/B3 only (confirming/measuring a real address).
   C2/D1 keep the flat gradient + solid .roof colour swap: that's the
   recolour illustration, a different job from "here's a real aerial view". */
.aerial.photo {
  background-image: url('../assets/photos/aerial-suburb.jpg');
  background-size: cover; background-position: center;
}
.aerial.photo::before { display: none; }
.aerial .roof {
  position: absolute; inset: 15% 13% 18% 15%;
  background: var(--roof-colour, #B4573F);
  clip-path: polygon(0 32%, 52% 0, 100% 20%, 100% 74%, 58% 100%, 0 82%);
}
.aerial .roof::after {
  content: ''; position: absolute; inset: 0;
  clip-path: polygon(0 32%, 52% 0, 100% 20%, 100% 74%, 58% 100%, 0 82%);
  background: linear-gradient(160deg, rgba(255,255,255,0.30), transparent 45%, rgba(0,0,0,0.20));
}
/* recolourable TILE TEXTURE for the visualiser: base colour + barrel-tile ridges
   (course shadows + rounded highlights) painted in translucent black/white — so
   ANY --roof-colour keeps the tile shape and shadows. This is the CSS stand-in
   for the RN/Skia luminance-preserving recolour the build will use. */
.aerial .roof.tiled {
  background-color: var(--roof-colour, #B4573F);
  background-image:
    repeating-linear-gradient(0deg, transparent 0 22px, rgba(0,0,0,0.17) 22px 26px),
    repeating-linear-gradient(90deg,
      rgba(0,0,0,0.26) 0px, rgba(0,0,0,0.05) 4px, rgba(255,255,255,0.16) 9px,
      rgba(0,0,0,0.05) 14px, rgba(0,0,0,0.26) 18px);
}
/* VISUALISER STAGE (.aerial.vis): a full-bleed rectangular tile sample framed by
   a hairline border — a clean material preview, not the aerial view. Reuses
   .roof + .tiled but drops the hexagon clip so the tiles fill edge to edge. */
.aerial.vis { background: var(--surface); }
.aerial.vis::before { display: none; }                 /* drop the building-shadow blob */
.aerial.vis .roof { inset: 0; clip-path: none; }       /* tile fills edge-to-edge, framed only by the hairline border */
.aerial.vis .roof::after { clip-path: none; }
/* COMPARE = a drag slider: colour A fills, colour B is clipped to the right of a
   draggable divider. Drag the handle to reveal more of either colour. */
.aerial.vis .roof.side-b { inset: 0 0 0 50%; }
.aerial.vis .cmp-divider { position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; background: #fff; transform: translateX(-50%); z-index: 2; }
.aerial.vis .cmp-handle {
  position: absolute; top: 50%; left: 50%; z-index: 3; width: 38px; height: 38px;
  transform: translate(-50%, -50%); border-radius: 50%; background: #fff;
  border: 1px solid var(--line-soft); display: grid; place-items: center; cursor: ew-resize;
}
.aerial.vis .cmp-handle .ic { width: 18px; height: 18px; color: var(--accent); }
/* colour name(s) below the preview (single = one, compare = two) */
.vis-label { text-align: center; font-family: var(--font-ui); }
.vis-label .vl b { font-variation-settings: 'wght' 600; font-size: var(--t-heading); color: var(--ink); }
.vis-label .vl span { display: block; font-size: var(--t-caption); color: var(--muted); }
.vis-label.pair { display: flex; justify-content: space-between; align-items: baseline; text-align: left; }
.vis-label.pair .vl.b { text-align: right; }
.vis-label.pair .vl b { font-size: var(--t-body); }
.aerial .badge {
  position: absolute; top: 10px; right: 10px;
  background: rgba(45,32,18,0.82); color: #fff;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-size: var(--t-micro);
  padding: 4px 12px;
  border-radius: var(--r-pill);
}
.aerial .badge.low { background: var(--warn); }   /* low-confidence measurement — amber, white on warn = 5.8:1 */
/* visualiser caption — colour name set over the recoloured roof (presentation
   mode: this is the screen the roofer turns to the customer). The gradient is a
   legibility scrim on the image, not a UI shadow, so the flat rule still holds. */
.aerial .vis-caption {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 34px 16px 14px;
  background: linear-gradient(to top, rgba(45, 32, 18, 0.85), rgba(45, 32, 18, 0.45) 55%, transparent);
}
.aerial .vis-caption b { display: block; font-family: var(--font-display); font-variation-settings: 'wght' 600; font-size: var(--t-title); color: #fff; letter-spacing: -0.01em; }
.aerial .vis-caption span { font-family: var(--font-ui); font-size: var(--t-caption); color: rgba(255, 255, 255, 0.88); }
.aerial .pin {
  position: absolute; left: 50%; top: 42%; width: 32px; height: 40px;
  transform: translate(-50%, -100%); /* anchor at the pin's tip, not its centre */
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21s-7-5.8-7-11a7 7 0 0 1 14 0c0 5.2-7 11-7 11z" fill="%237D1017" stroke="%23fff" stroke-width="1.3"/></svg>');
  background-size: contain; background-repeat: no-repeat;
}

/* line items */
.li-group {
  font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-micro); letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent);
  padding-top: 8px;
}
.li { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.li:last-child { border-bottom: none; }
.li .n { flex: 1; font-family: var(--font-ui); font-variation-settings: 'wght' 600; color: var(--ink); font-size: var(--t-body); }
.li .n small { display: block; font-variation-settings: 'wght' 400; font-size: var(--t-micro); color: var(--muted); }
/* a line whose SPEC is a choice, not just a note: ridge type and batten type are
   real calculator inputs (ridgeType / battenType) that swing the quantity —
   terracotta ridges 2.1/m vs A-Line 2.4/m is +16% on ridge caps; 8-per-bundle
   vs 12-per-bundle is -31% on battens. Shown on the line whose number they
   change, dotted to mark it tappable; tapping opens the two options in a
   bottom sheet. Defaults come from Profile → Quote defaults, so the common
   case needs no taps at all. */
.li .n small.spec-pick { color: var(--body); text-decoration: underline dotted; text-decoration-color: var(--line-ink); text-underline-offset: 2px; cursor: pointer; }
.li .q { font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-variant-numeric: tabular-nums; color: var(--ink); font-size: var(--t-body); }
.li > .ic { width: 18px; height: 18px; color: var(--muted); flex: none; }  /* trailing chevron on tappable rows (D1a quotes) */
/* editable quantity — a tap-to-edit field that opens a numeric keypad. Steppers
   were dropped: nudging a calculator-derived count like 2,875 by ±1 is unusable,
   and for a precise value a big keypad beats tiny +/- for gloved hands. Grey field
   = "this is editable", matching every other input in the app. */
.qedit {
  min-width: 82px; padding: 10px 14px; text-align: right;
  background: var(--field); border-radius: var(--r-structure);
  font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-variant-numeric: tabular-nums;
  color: var(--ink); font-size: var(--t-body);
  border: none; cursor: pointer; appearance: none; -webkit-appearance: none;
  min-height: 44px;
}
.qedit:active { background: var(--field-active); outline: 1.5px solid var(--field-active-line); outline-offset: -1.5px; }
/* per-line pricing — "a defensible price fast" (PRODUCT.md): every line shows
   unit price × qty = line total under its editable quantity, so the roofer can
   defend the figure at the kitchen table line by line. --body, not --muted:
   11px money must hold 4.5:1 comfortably. */
.li .lr { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: none; }
.li .lp { font-family: var(--font-ui); font-size: var(--t-micro); color: var(--body); font-variant-numeric: tabular-nums; }
.li .lp b { font-variation-settings: 'wght' 600; color: var(--ink); }

/* D1a quote cards — each quote opens read-only AND carries its own Send, so
   which price goes to the customer is never ambiguous on a two-quote job */
.qcard { border: 1px solid var(--line-soft); background: var(--surface); margin-bottom: 10px; }
/* press state to match every other tappable surface (.hub .h, .chip.on, .cl-card,
   .opt-card) — the quote row was the one tappable card that never responded to touch */
.qcard:active { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }
.qcard-open {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 13px 14px; background: none; border: none; text-align: left; /* evened out — the 10px bottom was clearance for the removed submit foot */
  cursor: pointer; appearance: none; -webkit-appearance: none; font-family: var(--font-ui);
}
.qcard-open .n { flex: 1; font-variation-settings: 'wght' 600; color: var(--ink); font-size: var(--t-body); }
.qcard-open .n small { display: block; font-variation-settings: 'wght' 400; font-size: var(--t-micro); color: var(--muted); }
.qcard-open .q { font-variation-settings: 'wght' 600; font-variant-numeric: tabular-nums; color: var(--ink); font-size: var(--t-body); }
.qcard-open .ic { width: 18px; height: 18px; color: var(--muted); flex: none; }
.qcard-foot { padding: 0 14px 12px; display: flex; justify-content: flex-end; }

/* success toast — a flat ink bar + green check: the "Saved to Jobs" auto-save
   confirmation. Transient in the app (auto-dismisses ~2.5s); shown static here.
   No shadow — a solid bar, matching the flat system. */
.toast {
  position: absolute; left: 14px; right: 14px; bottom: 26px; z-index: 6;
  display: flex; align-items: center; gap: 9px;
  background: var(--ink); color: #fff;
  font-family: var(--font-ui); font-variation-settings: 'wght' 500; font-size: var(--t-caption);
  padding: 12px 14px;
}
.toast .ic { width: 16px; height: 16px; color: #58C98A; flex: none; }

/* destructive text action (Delete job on a detail screen) — subtle, centred,
   never a big red button: deletion shouldn't shout louder than the primary action */
.delete-job {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin-top: 14px; padding: 6px;
  background: none; border: none; cursor: pointer;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600;
  font-size: var(--t-caption); color: var(--err);
}
.delete-job .ic { width: 15px; height: 15px; }

/* checkbox — square + flat, matching the structural language (e.g. "project is urgent") */
.check-row { display: flex; align-items: center; gap: 10px; padding: 10px 2px; font-family: var(--font-ui); font-size: var(--t-body); color: var(--ink); cursor: pointer; }
.check-row .cbox { width: 20px; height: 20px; flex: none; border: 1.5px solid var(--line-ink); background: var(--surface); }
.check-row .cbox.on { background: var(--accent); border-color: var(--accent); position: relative; }
.check-row .cbox.on::after { content: ''; position: absolute; left: 6px; top: 2px; width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); }

/* totals bar */
.total-bar {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--panel); border: 1px solid var(--line-soft); padding: 12px 14px;
}
.total-bar b { font-family: var(--font-display); font-variation-settings: 'wght' 600; font-variant-numeric: tabular-nums; color: var(--ink); font-size: var(--t-data); letter-spacing: -0.01em; }
.total-bar span { font-family: var(--font-ui); font-size: var(--t-caption); color: var(--body); line-height: 1.4; }

/* timeline */
.timeline { display: flex; flex-direction: column; }
.timeline .ev { position: relative; padding: 0 0 16px 22px; font-family: var(--font-ui); font-size: var(--t-caption); color: var(--muted); }
.timeline .ev::before { content: ''; position: absolute; left: 3px; top: 4px; width: 9px; height: 9px; border-radius: 50%; background: var(--line-soft); }
.timeline .ev::after { content: ''; position: absolute; left: 7px; top: 16px; bottom: 0; width: 1px; background: var(--line-soft); }
.timeline .ev:last-child::after { display: none; }
.timeline .ev.done::before { background: var(--ok); }
.timeline .ev.now::before { background: var(--accent); outline: 3px solid var(--accent-tint); }
.timeline .ev b { color: var(--ink); font-variation-settings: 'wght' 600; }

/* hub cards (job detail) — real <button>s on D1: each tile opens its own
   screen (Measurement → B3 view, Quotes → D1a, Photos → D1b, Notes → D1c) */
.hub { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.hub .h {
  background: var(--surface); border: 1px solid var(--line-soft); padding: 12px;
  appearance: none; -webkit-appearance: none; text-align: left; cursor: pointer; width: 100%;
  font-family: var(--font-ui);
}
.hub .h:active { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }
.hub .h b { display: block; font-family: var(--font-ui); font-variation-settings: 'wght' 600; color: var(--ink); font-size: var(--t-body); }
.hub .h span { font-family: var(--font-ui); font-size: var(--t-micro); color: var(--muted); }

/* segmented control — a padded grey track, each segment (incl. selected)
   its own floating pill, not a flush-fill rectangle with divider lines */
.seg {
  display: flex; gap: 3px; padding: 3px;
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
}
.seg span {
  flex: 1; text-align: center; padding: 10px 0;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-size: var(--t-body); color: var(--body);
  border-radius: var(--r-pill);
}
/* selected segment lifts to a white pill defined by the soft warm hairline
   (`--line-soft`) — not the harsh ink border — with ink text; unselected stay
   body. Built from the system's neutral tokens (panel · surface · line-soft ·
   ink/body), no maroon, so the CTA stays the single accent moment. */
.seg span.on { background: var(--surface); color: var(--ink); outline: 1px solid var(--line-soft); outline-offset: -1px; }

/* progress stages (measuring theatre) — a CONNECTED vertical stepper: round
   markers linked by a line that fills green as steps complete, and a live pulse
   ring on the active step (the labor-illusion moment the screen is built around) */
.stage-list { display: flex; flex-direction: column; }
.stage {
  display: flex; align-items: flex-start; gap: 14px; position: relative;
  padding-bottom: 22px; font-family: var(--font-ui); font-size: var(--t-body);
  line-height: 24px; color: var(--muted);
}
.stage:last-child { padding-bottom: 0; }
/* connector line from this marker down to the next */
.stage::before { content: ''; position: absolute; left: 11px; top: 26px; bottom: 0; width: 2px; background: var(--line-soft); }
.stage:last-child::before { display: none; }
.stage.done::before { background: var(--ok); }
.stage .m {
  width: 24px; height: 24px; flex: none; border-radius: 50%; position: relative; z-index: 1;
  display: grid; place-items: center; border: 2px solid var(--line-soft); background: var(--surface);
  font-size: var(--t-caption);
}
.stage.done { color: var(--ink); }
.stage.done .m { background: var(--ok); border-color: var(--ok); color: #fff; }  /* solid green + white check */
.stage.now { color: var(--ink); font-variation-settings: 'wght' 600; }
.stage.now .m { border-color: var(--accent); color: var(--accent); }
/* live pulse ring — signals "measuring right now", not a spinner */
.stage.now .m::after {
  content: ''; position: absolute; inset: -2px; border-radius: 50%; border: 2px solid var(--accent);
  animation: stage-pulse 1.7s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
@keyframes stage-pulse { 0% { transform: scale(1); opacity: 0.5; } 100% { transform: scale(2.1); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .stage.now .m::after { animation: none; opacity: 0; } }
.progress { height: 3px; background: var(--panel-2); position: relative; }
.progress i { position: absolute; inset: 0 auto 0 0; background: var(--accent); }

/* tab bar */
.tabbar {
  flex: none; height: 66px; border-top: 1px solid var(--line-ink);
  background: var(--surface);
  /* 4 equal slots, all uniform: Jobs · New(+) · Deliveries · Profile */
  display: grid; grid-template-columns: repeat(4, 1fr); align-items: center; justify-items: center;
}
.tabbar .t {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-size: var(--t-caption);
  color: var(--ink);                                 /* inactive nav = ink (our black, never pure #000) */
}
.tabbar .t.on .ic { background: var(--accent); }     /* selected: ONLY the icon turns maroon; label stays ink */

/* ---------- profile / settings ---------- */
/* identity lives in a bordered surface card — the same container language as the
   .set-box groups below, so the header reads as an intentional "business card" */
.prof-card {
  display: flex; gap: 14px; align-items: center;
  border: 1px solid var(--line-soft); background: var(--surface); padding: 16px 14px;
}
/* avatar: a maroon monogram tile — square like a roof tile, the one deliberate
   spot of accent up top (maroon on tint = 8:1, verified); the corner badge
   signals a settable business logo */
.prof-avatar {
  position: relative; width: 60px; height: 60px; flex: none;
  background: var(--accent-tint); border: 1px solid var(--accent); color: var(--accent);
  display: grid; place-items: center; border-radius: var(--r-structure);
}
.prof-avatar > span {
  font-family: var(--font-display); font-variation-settings: 'wght' 600;
  font-size: 23px; letter-spacing: 0.03em;
}
.prof-edit {
  position: absolute; right: -6px; bottom: -6px;
  width: 24px; height: 24px; display: grid; place-items: center; padding: 0;
  background: var(--surface); border: 1px solid var(--line-ink); border-radius: 50%;
  cursor: pointer; appearance: none; -webkit-appearance: none;
}
.prof-edit .ic { width: 13px; height: 13px; color: var(--ink); }
.prof-id b { display: block; font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-size: var(--t-title); color: var(--ink); }
.prof-id span { display: block; font-family: var(--font-ui); font-size: var(--t-caption); color: var(--muted); }
.prof-id span:first-of-type { color: var(--body); }   /* business name one tier above the email */
.set-group { font-family: var(--font-ui); font-variation-settings: 'wght' 600; font-size: var(--t-micro); letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted); padding: 16px 0 8px; }
/* rows live inside .set-box — the same bordered surface container the quote and
   delivery lists use — so a grouped list looks the same everywhere in the app */
.set-box { border: 1px solid var(--line-soft); background: var(--surface); padding: 0 14px; }
.set-row {
  display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px; padding: 11px 0;
  border: none; border-bottom: 1px solid var(--line-soft); background: none; cursor: pointer; text-align: left;
  appearance: none; -webkit-appearance: none; font-family: var(--font-ui);
}
.set-row:last-child { border-bottom: none; }
.set-row .lbl { flex: 1; font-size: var(--t-body); font-variation-settings: 'wght' 600; color: var(--ink); }  /* matches .li .n */
.set-row .val { font-size: var(--t-body); color: var(--muted); font-variant-numeric: tabular-nums; }
.set-row .ic { width: 18px; height: 18px; color: var(--muted); flex: none; }
.set-row.danger .lbl { color: var(--err); }   /* sign out — err #D92D20 on white = 4.83:1, verified */
