/* LLPP Mobile — PDR Logic visual identity tuned for native-app patterns.
 *
 * Scope: only loaded by /m/* pages. No desktop pollution. Variables mirror
 * the desktop palette so light-mode looks immediately recognizable;
 * dark-mode is a re-map for techs working outdoors in bright sun (where
 * cream/light surfaces wash out) and indoors at night. */

:root {
  /* Light theme — matches desktop LLPP_Desktop_v1.html palette */
  --cream:        #F5F0E8;
  --cream-dark:   #EDE8DC;
  --cream-darker: #E0D9CC;
  --gold:         #C9A84C;
  --gold-light:   #E2C97A;
  --gold-dim:     #8A6E2F;
  --gold-pale:    #F5EDD0;
  --ink:          #1A1710;
  --ink-mid:      #3D3A30;
  --ink-soft:     #6B6557;
  --border:       rgba(26,23,16,0.08);
  --border-strong:rgba(26,23,16,0.16);
  --red:          #C0392B;
  --green:        #2D6A4F;
  --amber:        #C9A84C;
  --surface:      #FFFFFF;
  --surface-2:    var(--cream);
  --shadow-sm:    0 1px 3px rgba(26,23,16,0.06);
  --shadow-md:    0 4px 14px rgba(26,23,16,0.10);
  --shadow-up:    0 -6px 22px rgba(26,23,16,0.12);

  /* Layout */
  --nav-h:        62px;
  --header-h:     52px;
  --safe-top:     env(safe-area-inset-top, 0px);
  --safe-bot:     env(safe-area-inset-bottom, 0px);

  /* Typography */
  --f-display:    'Cinzel', Georgia, serif;
  --f-screen:     'Cormorant SC', Georgia, serif;
  --f-body:       'DM Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --f-mono:       'DM Mono', ui-monospace, monospace;
}

/* Dark theme — reachable via [data-theme=dark] or @media. Ink-dominant
 * with the same gold accent so the brand reads on both. Surfaces are warm
 * not blue-black to keep the "PDR Logic" warmth. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme=light]) {
    --cream:        #1A1710;
    --cream-dark:   #14110B;
    --cream-darker: #0F0D08;
    --gold-pale:    rgba(201,168,76,0.12);
    --ink:          #F5F0E8;
    --ink-mid:      #C9C2B2;
    --ink-soft:     #8B8678;
    --border:       rgba(245,240,232,0.08);
    --border-strong:rgba(245,240,232,0.18);
    --surface:      #221E16;
    --surface-2:    #1A1710;
    --shadow-sm:    0 1px 3px rgba(0,0,0,0.4);
    --shadow-md:    0 4px 14px rgba(0,0,0,0.45);
    --shadow-up:    0 -6px 22px rgba(0,0,0,0.55);
  }
}
:root[data-theme=dark] {
  --cream:        #1A1710;
  --cream-dark:   #14110B;
  --cream-darker: #0F0D08;
  --gold-pale:    rgba(201,168,76,0.12);
  --ink:          #F5F0E8;
  --ink-mid:      #C9C2B2;
  --ink-soft:     #8B8678;
  --border:       rgba(245,240,232,0.08);
  --border-strong:rgba(245,240,232,0.18);
  --surface:      #221E16;
  --surface-2:    #1A1710;
  --shadow-sm:    0 1px 3px rgba(0,0,0,0.4);
  --shadow-md:    0 4px 14px rgba(0,0,0,0.45);
  --shadow-up:    0 -6px 22px rgba(0,0,0,0.55);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* Controls are NOT text.
   Jon, 2026-08-21: tapping the close X did nothing, and long-pressing it
   raised "Copy / Translate / Select All / Share" with the X highlighted.
   That is Android's TEXT SELECTION menu — the glyph inside the button was
   selectable, so a press started a selection gesture and the WebView
   cancelled the pointer sequence before the tap could complete. Any button
   whose label is a character (✕, ‹, ›, emoji) is exposed to this. */
button, .btn, .icon-btn, .wtc-side, .wtc-shutter, .tile-lock, .ph-remove,
.label-chip, .wiz-tile, .bn-item {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
html, body {
  margin: 0;
  padding: 0;
  background: var(--surface-2);
  color: var(--ink);
  font-family: var(--f-body);
  font-size: 16px; /* iOS focus-zoom guard */
  line-height: 1.4;
  overscroll-behavior-y: contain;
  -webkit-font-smoothing: antialiased;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
  padding-bottom: calc(var(--nav-h) + var(--safe-bot));
}

/* ── App header ─────────────────────────────────────── */
.app-header {
  position: sticky; top: 0; z-index: 50;
  height: calc(var(--header-h) + var(--safe-top));
  padding-top: var(--safe-top);
  background: var(--surface);
  border-bottom: 0.5px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  padding-left: 16px; padding-right: 12px;
}
.app-header .brand {
  font-family: var(--f-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.32em;
  color: var(--gold-dim);
  text-transform: uppercase;
}
.app-header .brand-mark {
  width: 18px; height: 18px;
  vertical-align: middle;
  margin-right: 6px;
  position: relative; top: -1px;
}
.app-header .header-actions {
  display: flex; align-items: center; gap: 10px;
}
.icon-btn {
  /* 44px is the minimum reliable touch target; 38 made the close X fiddly to
     hit even before the layout-shift bug that made it drop taps entirely.
     touch-action stops the browser holding the tap back to test for a
     double-tap zoom. */
  width: 44px; height: 44px;
  touch-action: manipulation;
  border: none; background: transparent;
  border-radius: 50%;
  color: var(--ink-mid);
  font-size: 18px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.15s;
}
.icon-btn:active { background: rgba(201,168,76,0.18); }

/* ── View container ─────────────────────────────────── */
.view {
  display: none;
  min-height: calc(100dvh - var(--header-h) - var(--nav-h) - var(--safe-bot) - var(--safe-top));
}
.view.active { display: block; }
.view-pad { padding: 18px 16px; }
.screen-title {
  font-family: var(--f-screen);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink);
  margin: 4px 0 16px;
}
.section-title {
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 18px 0 8px;
  font-weight: 600;
}

/* ── Cards ───────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
  box-shadow: var(--shadow-sm);
  margin-bottom: 10px;
  transition: transform 0.15s, box-shadow 0.15s;
}
.card:active {
  transform: scale(0.985);
  box-shadow: var(--shadow-md);
}
.card-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.card-title { font-size: 14px; font-weight: 500; color: var(--ink); }
.card-sub   { font-size: 11px; color: var(--ink-soft); margin-top: 3px; }

/* ── Status badges ──────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 9px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  padding: 0 8px; height: 20px;
  border-radius: 10px;
  border: 0.5px solid currentColor;
}
.b-new      { color: var(--red); background: rgba(192,57,43,0.08); }
.b-progress { color: var(--green); background: rgba(45,106,79,0.08); }
.b-returned { color: var(--gold-dim); background: rgba(201,168,76,0.10); }
.b-review   { color: var(--amber); background: rgba(201,168,76,0.10); }
.b-complete { color: var(--ink-soft); background: rgba(107,101,87,0.08); }

/* ── Bottom nav ─────────────────────────────────────── */
.bottom-nav {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
  height: calc(var(--nav-h) + var(--safe-bot));
  padding-bottom: var(--safe-bot);
  background: var(--surface);
  border-top: 0.5px solid var(--border);
  display: grid;
  grid-template-columns: repeat(5, 1fr);   /* home | search | action | messages | me */
  box-shadow: var(--shadow-up);
}
.bn-item {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  background: none; border: none;
  color: var(--ink-soft);
  font-family: var(--f-body);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 6px 4px 4px;
  position: relative;
  transition: color 0.15s;
}
.bn-item .bn-icon {
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
}
.bn-item .bn-icon svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.4; }
.bn-item.active { color: var(--gold-dim); }
.bn-item.active::after {
  content: '';
  position: absolute; top: 0; left: 28%; right: 28%; height: 2px;
  background: var(--gold);
  border-radius: 0 0 2px 2px;
}
.bn-item:active { color: var(--gold); }

/* The center "compose" item is visually distinct — bigger, gold-filled.
 * Mirrors the iOS / Android "primary action" pattern so techs find New
 * Submission instantly. */
.bn-item.compose .bn-icon {
  width: 44px; height: 44px;
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-dim) 100%);
  color: white;
  border-radius: 50%;
  margin-top: -16px;
  box-shadow: 0 4px 12px rgba(201,168,76,0.45);
  border: 3px solid var(--surface);
}
.bn-item.compose .bn-icon svg { width: 22px; height: 22px; stroke: white; stroke-width: 2; }
.bn-item.compose { color: var(--gold-dim); }
/* The action is the one item that carries its own words — an unlabelled
   circle reads as "add something", not "start a new submission". Slightly
   smaller and tighter than the tab labels so two words fit one line. */
.bn-item.compose {
  font-size: 8px;
  letter-spacing: 0.06em;
  line-height: 1.1;
  gap: 2px;
}

/* ── Buttons ─────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--f-body);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  padding: 13px 18px;
  border-radius: 8px;
  border: 0.5px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  min-height: 48px;
  transition: transform 0.1s, background 0.15s;
}
.btn:active { transform: scale(0.97); background: var(--cream-darker); }
.btn-primary {
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-dim) 100%);
  color: white;
  border-color: transparent;
  box-shadow: 0 2px 8px rgba(201,168,76,0.35);
}
.btn-primary:active { background: var(--gold-dim); }
.btn-block { width: 100%; }
.btn-ghost { background: transparent; border-color: var(--border); }
.btn-danger { color: var(--red); border-color: rgba(192,57,43,0.4); background: rgba(192,57,43,0.05); }

/* ── Form fields ─────────────────────────────────────── */
.field { margin-bottom: 14px; }
.field-label {
  display: block;
  font-size: 9px;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 6px;
  font-weight: 600;
}
.field-input {
  width: 100%;
  padding: 14px 14px;
  font-size: 16px;
  font-family: var(--f-body);
  background: var(--surface);
  color: var(--ink);
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field-input:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(201,168,76,0.18);
}

/* ── Bottom sheet ───────────────────────────────────── */
.sheet-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(26,23,16,0.5);
  opacity: 0; pointer-events: none;
  transition: opacity 0.25s;
}
.sheet-overlay.open { opacity: 1; pointer-events: auto; }
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 101;
  /* NEVER UNDER THE STATUS BAR. The sheet is bottom-anchored, so a TALL
     step pushes its header to the top of the viewport — and with no top inset
     reserved, the close X ended up partly inside the system status-bar strip,
     where taps go to Android and never reach the page. Steps 4 and 5 are the
     tall ones (photo grid), which is why only those failed: tapping the X did
     nothing, tapping a few pixels lower worked every time (Jon, 2026-08-26).
     Every other rule here already reserves --safe-top; this one did not. */
  max-height: min(92dvh, calc(100dvh - var(--safe-top) - 8px));
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  box-shadow: var(--shadow-up);
  transform: translateY(100%);
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
  padding-bottom: var(--safe-bot);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sheet.open { transform: translateY(0%); }

/* Touch zone wrapping the visible 38×4 bar. The bar itself stays small
 * for visual subtlety, but the wrapper gives a 48px+ tall grab area
 * (Apple/Material minimum tap target) so swipe-down-to-dismiss works
 * with normal-sized fingers. touch-action:none stops the browser from
 * intercepting the gesture as a scroll. */
.sheet-grab {
  padding: 14px 30%;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.sheet-grab:active { cursor: grabbing; }
.sheet-handle {
  width: 38px; height: 4px;
  background: var(--border-strong);
  border-radius: 2px;
  pointer-events: none; /* taps go to the .sheet-grab parent */
}
.sheet-header {
  padding: 6px 18px 12px;
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 0.5px solid var(--border);
}
.sheet-title {
  font-family: var(--f-screen);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink);
}
/* The last field still needs slack below it, or scrollIntoView({block:'center'})
   hits the bottom of the scroll range and leaves the field under the keyboard.
   But this was a flat 55vh — half a screen of dead space sitting there whether
   the keyboard was open or not (Jon, 2026-08-21).
   The slack is now added only while the keyboard is actually up, and sized to
   the keyboard, via --kb-pad in bindKeyboardFollow(). Default 0. */
.sheet-body { padding: 16px 18px 24px; }
#compose-sheet .sheet-body {
  padding-bottom: calc(24px + var(--kb-pad, 0px));
  transition: padding-bottom 0.12s ease-out;
}

/* ── Camera capture grid ────────────────────────────── */
.cam-cta {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 100%;
  padding: 26px 16px;
  border: 1.5px dashed var(--gold);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--gold-pale) 0%, var(--surface) 100%);
  color: var(--gold-dim);
  cursor: pointer;
  font-family: var(--f-screen);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.04em;
  margin-bottom: 14px;
}
.cam-cta .cam-icon { font-size: 32px; margin-bottom: 8px; }
.cam-cta-sub { font-family: var(--f-body); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--ink-soft); margin-top: 4px; }

.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
.photo-tile {
  aspect-ratio: 1;
  background: var(--cream-darker);
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}
.photo-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Non-image attachment (PDF / doc) shown in the same square grid as photos.
   Before this, a PDF picked into the wizard had url:null and rendered as a
   broken <img>. */
.doc-tile {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 4px;
  padding: 6px;
  background: var(--gold-pale);
  border: 0.5px solid var(--border-strong);
  text-align: center;
}
.doc-tile-ico { font-size: 24px; line-height: 1; }
.doc-tile-name {
  font-family: var(--f-body);
  font-size: 9px;
  line-height: 1.25;
  color: var(--ink-mid);
  word-break: break-word;
}
.photo-tile .ph-remove {
  position: absolute; top: 4px; right: 4px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(26,23,16,0.7);
  color: white;
  font-size: 12px; line-height: 1;
  border: none;
  cursor: pointer;
}

/* ── Wizard step transitions ────────────────────────── */
.wizard-steps { position: relative; min-height: 300px; }
.wiz-step { display: none; animation: fadeIn 0.25s ease; }
.wiz-step.active { display: block; }
@keyframes fadeIn {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}
.wiz-progress {
  display: flex; gap: 5px; margin-bottom: 18px;
}
.wiz-progress .seg {
  flex: 1; height: 3px; border-radius: 2px; background: var(--border-strong);
  transition: background 0.2s;
}
.wiz-progress .seg.done { background: var(--gold); }
.wiz-actions { display: flex; gap: 8px; margin-top: 20px; }
.wiz-actions .btn { flex: 1; }

/* ── Toast ───────────────────────────────────────────── */
.toast {
  position: fixed;
  left: 12px; right: 12px;
  /* Anchored just below the header so the keyboard / cam-permission
   * dialog never cover it. The header is sticky-top, so we offset by
   * its height + safe-area inset. */
  top: calc(var(--header-h) + var(--safe-top) + 10px);
  background: var(--ink);
  color: var(--cream);
  padding: 14px 18px;
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.45;
  z-index: 200;
  box-shadow: var(--shadow-md);
  /* ⛔ THIS WAS `pointer-events: auto` AND IT ATE THE CLOSE X ON STEPS 4/5.
   *
   * Jon, 2026-08-26: "IF I CLICK RIGHT ON THE X IT DOESNT RESPOND. IF I
   * INTENTIONALLY CLICK SLIGHTLY BELOW IT WORKS EVERYTIME."
   *
   * #toast is a PERMANENT element (index.html) — showToast only toggles
   * `.show`, which moves opacity and transform. It is never display:none and
   * never leaves the hit-test tree. `opacity: 0` hides a thing from the EYE,
   * not from elementFromPoint. So an invisible full-width bar sat at
   * z-index 200 — above the sheet's 101 — permanently swallowing every tap
   * inside its band.
   *
   * The band is `top: --header-h + --safe-top + 10px` ≈ y 96-143 on an S22.
   * The close X occupies y 103-147. Overlap covers the button's top ~40px and
   * leaves a few live pixels at the bottom: "slightly below works every time."
   *
   * Why steps 4 and 5 only: the sheet is BOTTOM-anchored, so the tall steps
   * push its header UP into that fixed band. Steps 1-3 are short and their
   * header sits clear of it. The earlier "sheet running under the status bar"
   * theory had the right neighbourhood and the wrong occupant — it was never
   * Android's status bar, it was this.
   *
   * Found by a throwaway on-device hit-test (window-level pointer capture +
   * elementsFromPoint at the button's top/middle/bottom), since removed. Four
   * theories had already died against the source without touching a handset;
   * one readout named the culprit outright, so it is preserved here:
   *   X top    (344,107): div#toast.toast > button#compose-close-btn...
   *   X middle (344,127): div#toast.toast > button#compose-close-btn...
   *   X bottom (344,147): button#compose-close-btn.icon-btn > ...
   * ...and no `@X` marker in the event log at all — the button never heard it.
   *
   * A toast nobody can see must not be touchable. Interactivity now rides
   * with visibility on `.show`, so tap-to-dismiss still works exactly as
   * before while the toast is actually up. */
  pointer-events: none;
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity 0.2s, transform 0.2s;
  text-align: center;
  word-break: break-word;
}
.toast.show {
  opacity: 1;
  transform: translateY(0);
  /* Visible == tappable. This is what keeps the onclick tap-to-dismiss on
     #toast working; it is inert only while hidden. */
  pointer-events: auto;
}
/* The SECOND half of the same geometry problem (Jon, 2026-08-26).
 *
 * Making a hidden toast inert stops it eating taps, but a VISIBLE one still
 * lands in the same band as the sheet header on the tall steps — so tapping
 * the close X would dismiss the toast instead of closing the sheet. Milder
 * (you can see it, and it clears itself in 1.8s) but the same root geometry.
 *
 * Narrow rather than move. Shifting the toast DOWN would park it over the
 * form fields the tech is typing into, which is worse than what it fixes.
 * Both sheet headers put their 44px X at `right: 18px`, so the X column is
 * right 18-62px; stopping the toast at 68px clears it with 6px to spare and
 * costs a barely perceptible amount of width on a short, centred message.
 * Nothing else in that band is interactive — just the sheet title.
 *
 * Applied by showToast() whenever any `.sheet.open` exists, so it covers
 * #detail-sheet too, which has an identically positioned X. */
.toast.clear-sheet-x { right: 68px; }
.toast::after {
  content: '✕';
  position: absolute;
  top: 6px; right: 8px;
  font-size: 13px;
  color: var(--ink-soft);
}

/* ── Empty state ─────────────────────────────────────── */
.empty {
  text-align: center;
  padding: 48px 24px;
  color: var(--ink-soft);
}
.empty .empty-mark {
  font-size: 36px;
  opacity: 0.4;
  margin-bottom: 10px;
}
.empty .empty-text { font-size: 13px; line-height: 1.55; }

/* ── Loading skeleton ─────────────────────────────────── */
.skel {
  background: linear-gradient(90deg, var(--cream-dark) 0%, var(--cream-darker) 50%, var(--cream-dark) 100%);
  background-size: 200% 100%;
  animation: shimmer 1.4s infinite linear;
  border-radius: 8px;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ── Refresh icon spinner (replaces pull-to-refresh) ─── */
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
#refresh-btn.spinning { animation: spin 0.8s infinite linear; color: var(--gold); }

/* ── Submission detail (full-screen overlay) ──────────
 * Slides in from the right and sits above the bottom-nav. Pinned header
 * with back chevron + title + status badge. Body scrolls. Tabs (when
 * supplements exist) sit between the header and body. */
.detail-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 70;                        /* above views, below bottom-nav so PWA back-gestures still work */
  background: var(--surface-2);
  transform: translateX(100%);
  transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.detail-overlay.open { transform: translateX(0); }
.detail-overlay .detail-header {
  flex-shrink: 0;
  height: calc(var(--header-h) + var(--safe-top));
  padding: var(--safe-top) 12px 0 8px;
  background: var(--surface);
  border-bottom: 0.5px solid var(--border);
  display: flex; align-items: center; gap: 10px;
}
.detail-back {
  font-size: 28px; color: var(--gold-dim);
  padding: 0; line-height: 1;
}
.detail-title-wrap { flex: 1; min-width: 0; }
.detail-title {
  font-family: var(--f-screen);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.detail-subtitle {
  font-size: 10px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.detail-tabs {
  display: flex; flex-wrap: nowrap; gap: 6px;
  padding: 8px 12px;
  background: var(--surface);
  border-bottom: 0.5px solid var(--border);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.detail-tabs .case-tab {
  padding: 5px 11px;
  font-size: 10px;
  font-family: var(--f-body);
  letter-spacing: 0.06em;
  border: 0.5px solid var(--border-strong);
  background: transparent;
  color: var(--ink-mid);
  border-radius: 14px;
  cursor: pointer;
  white-space: nowrap;
}
.detail-tabs .case-tab.active {
  background: var(--gold);
  color: white;
  border-color: var(--gold);
}
.detail-body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 14px calc(var(--nav-h) + var(--safe-bot) + 14px);
  -webkit-overflow-scrolling: touch;
}

/* Detail content blocks */
.dt-section { margin-bottom: 16px; }
.dt-section-title {
  font-size: 9px;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 600;
  margin-bottom: 8px;
}
.dt-row {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 10px;
  padding: 8px 0;
  border-bottom: 0.5px solid var(--border);
  font-size: 13px;
}
.dt-row:last-child { border-bottom: none; }
.dt-row .k { color: var(--ink-soft); font-size: 11px; flex: 0 0 auto; min-width: 80px; }
.dt-row .v { color: var(--ink); text-align: right; word-break: break-word; flex: 1; }

/* File gallery on detail page — 3-col grid mirroring the wizard, but
 * grouped by photo_category like the desktop attachments view. */
.dt-photo-group { margin-bottom: 12px; }
.dt-photo-group-label {
  font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-mid); font-weight: 600;
  margin-bottom: 6px;
  display: flex; align-items: center; gap: 6px;
}
.dt-photo-group .photo-grid { gap: 4px; }
.dt-pdf {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  background: var(--surface);
  border: 0.5px solid var(--border-strong);
  border-radius: 6px;
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--ink);
  text-decoration: none;
}
.dt-pdf .pdf-icon { font-size: 18px; }

/* Message thread on detail */
.msg-thread {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: 8px;
  padding: 10px 8px;
  max-height: 50vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.msg-empty {
  text-align: center; padding: 20px; color: var(--ink-soft); font-size: 12px;
}
.msg-row { margin-bottom: 10px; display: flex; }
.msg-row.mine { justify-content: flex-end; }
.msg-bubble {
  max-width: 80%;
  padding: 8px 12px;
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
  word-wrap: break-word;
}
.msg-row.mine .msg-bubble {
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-dim) 100%);
  color: white;
  border-color: transparent;
}
.msg-meta {
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  margin-bottom: 3px;
  text-align: left;
}
.msg-row.mine .msg-meta { text-align: right; }
.msg-round-divider {
  display: flex; align-items: center; gap: 8px;
  margin: 12px 4px 6px;
  font-size: 8px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 600;
}
.msg-round-divider .line {
  flex: 1; height: 0; border-top: 0.5px dashed var(--border-strong);
}

/* Send-message input row */
.msg-send-row {
  display: flex; gap: 6px; margin-top: 10px;
}
.msg-send-row textarea {
  flex: 1;
  padding: 10px 12px;
  font-size: 16px;
  font-family: var(--f-body);
  background: var(--surface);
  color: var(--ink);
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  resize: none;
  outline: none;
  min-height: 44px;
  max-height: 120px;
}
.msg-send-row textarea:focus { border-color: var(--gold); }
.msg-send-row .btn { min-height: 44px; padding: 0 16px; }

/* Messages tab (list of submissions with unread badges) */
.msg-list-row {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 8px;
  margin-bottom: 8px;
  cursor: pointer;
  transition: transform 0.1s;
}
.msg-list-row:active { transform: scale(0.98); }
.msg-list-row .lr-body { flex: 1; min-width: 0; }
.msg-list-row .lr-title { font-size: 13px; color: var(--ink); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-list-row .lr-preview { font-size: 11px; color: var(--ink-soft); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-list-row .lr-unread {
  background: var(--gold);
  color: white;
  border-radius: 10px;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 700;
  min-width: 22px;
  text-align: center;
}

/* ─────────────────────────────────────────────────────────────────────────
   Native ML Kit barcode scanner overlay support.
   When @capacitor-mlkit/barcode-scanning's startScan() is active, the
   plugin renders the camera preview behind the WebView. To make it
   visible, html + body need to be transparent and the scanner overlay's
   solid #000 background needs to be removed. We add `.native-scan-active`
   to both html and body before startScan, and strip it on stop.
   ───────────────────────────────────────────────────────────────────── */
html.native-scan-active,
body.native-scan-active {
  background: transparent !important;
}
/* Hide every direct child of body except the scanner overlay so the
   underlying camera feed has nothing rendered above it (the wizard
   form, bottom nav, etc. would otherwise show through). visibility
   (not display:none) preserves layout so wizard state is intact when
   the scanner closes. */
/* ⛔⛔ `#toast` IS EXEMPT, AND IT WAS NOT — EVERY CAMERA FAILURE MESSAGE WAS
   INVISIBLE (found 2026-08-27 evening).

   `#toast` is a direct child of <body>, so this rule put
   `visibility: hidden !important` on it for the whole time a camera was open.
   Not "painted underneath" — explicitly hidden, with !important, so nothing
   could override it. Six showToast calls fire from walkthrough.js while the
   camera is up, and NOT ONE of them has ever been seen:

     - "Camera stopped responding — tap Done and reopen it. That shot was NOT
        saved."   ← added THIS MORNING as the fix for the wedged shutter
     - "Capture failed — that shot was NOT saved. Try again."
     - "Camera did not come back — tap Done and reopen"
     - "Camera failed to start - using the phone camera instead"

   ★ THE FIX FOR SILENT PHOTO LOSS WAS ITSELF SILENT. The messages that fire
   AFTER the camera closes ("N damage photos added") were visible the whole
   time — so the tech got the reassurance and never the warning. Exactly
   backwards.

   ⚠️ It also means "I saw no error toasts" is not evidence of anything about
   a capture session. It could not have been seen either way. */
body.native-scan-active > *:not(#vin-scanner):not(#doc-scanner):not(#wt-camera):not(#toast) {
  visibility: hidden !important;
}
/* Visible is not enough: `.toast` is z-index 200 and #wt-camera is 310, so it
   would be hidden a second time by paint order. Above the camera, below the
   thank-you modal (500) — the same reasoning .confirm-backdrop (400) already
   documents for itself. */
body.native-scan-active #toast { z-index: 460; }
/* ⭐ AND INERT OVER A CAMERA, DELIBERATELY. `.toast.show` turns
   `pointer-events: auto` back on for tap-to-dismiss; over a full-screen
   camera that would put a live bar across the top of the frame, in front of
   the controls. A toast eating taps is the exact bug that cost 2026-08-26 —
   do not reintroduce it for the sake of a dismiss gesture nobody needs here.
   It still self-clears on its timer. */
body.native-scan-active #toast.show { pointer-events: none; }
body.native-scan-active #vin-scanner,
body.native-scan-active #doc-scanner,
body.native-scan-active #doc-scanner .doc-cam-wrap {
  background: transparent !important;
}
body.native-scan-active #vin-scanner-controls {
  background: rgba(0, 0, 0, 0.7) !important;
}
body.native-scan-active #vin-scanner-aimtext,
body.native-scan-active #vin-scanner-status {
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85);
}
/* When the document scanner runs in native mode (camera-preview), the
   <video> element isn't used — hide it so it doesn't render the broken
   play-button placeholder over the native camera layer underneath. */
body.native-scan-active #doc-video {
  display: none !important;
}

/* ─────────────────────────────────────────────────────────────────────────
   VIN scanner reticle — orientation-aware sizing.
   Portrait default (HTML inline): 92% × 24% letterbox — fits long-rectangle
   labels naturally. Square PDF417 labels also fit, just don't fill the width.
   Landscape: 92% × 55% — accommodates rotated long labels OR square labels.
   ───────────────────────────────────────────────────────────── */
@media (orientation: landscape) {
  #vin-scanner-reticle {
    left: 4% !important;
    right: 4% !important;
    height: 55% !important;
  }
  /* Pull the aim instruction pill up so it doesn't crowd the reticle. */
  #vin-scanner-aimtext-wrap {
    top: calc(env(safe-area-inset-top, 0px) + 56px) !important;
  }
  #vin-scanner-aimtext { font-size: 18px !important; }
  #vin-scanner-status  { font-size: 11px !important; }
}

/* ── Document scanner overlay (multi-page paper capture) ───
   Live camera preview with auto-edge-detection. Used by the wizard's
   "Scan Estimate" step for capturing multi-page paperwork (insurance
   estimates, customer paperwork, repair orders). Adapted from the
   /assistant/ scanner — same detection engine (brightness + saturation
   projection, motion check, ready-frame counting, Adobe-Scan shadow
   removal) with a mobile-friendly chrome (gold pill aim text,
   full-screen overlay, big shutter, page count). */
#doc-scanner {
  position: fixed; inset: 0; z-index: 305;
  background: #000;
  display: none;
  flex-direction: column;
}
#doc-scanner.active { display: flex; }
.doc-cam-wrap {
  position: relative;
  flex: 1;
  background: #000;
  overflow: hidden;
}
.doc-cam-wrap video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
#doc-detect-canvas {
  position: absolute; inset: 0;
  pointer-events: none;
  width: 100%; height: 100%;
}
.doc-corners {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 2;
}
.doc-corner {
  position: absolute;
  width: 40px; height: 40px;
  /* Gold by default — matches PDR Logic brand and gives the user a clear
     "point camera here" frame the moment the scanner opens. Brightens to
     pale gold on detection, switches to green when auto-capture is armed. */
  border: 2.5px solid #C9A84C;
  transition: border-color 0.25s, border-width 0.25s;
  filter: drop-shadow(0 1px 4px rgba(0,0,0,0.6));
}
/* Bracket geometry — iteration 4 (2026-05-01).
   Slightly wider and shorter per Jon's iteration 3 feedback.
   Now: 1.5% sides, 21%/21% top/bottom = 97% × 58%.
   Crop pixel bounds in JS MUST match — see _docCropAndFinalize. */
.doc-corner.tl { top: 21%; left: 1.5%;  border-right: none; border-bottom: none; border-top-left-radius: 4px; }
.doc-corner.tr { top: 21%; right: 1.5%; border-left: none;  border-bottom: none; border-top-right-radius: 4px; }
.doc-corner.bl { bottom: 21%; left: 1.5%;  border-right: none; border-top: none; border-bottom-left-radius: 4px; }
.doc-corner.br { bottom: 21%; right: 1.5%; border-left: none;  border-top: none; border-bottom-right-radius: 4px; }
.doc-corner.detected { border-color: #E2C97A; border-width: 3.5px; }
.doc-corner.ready    { border-color: #6BCF8E; border-width: 4px; }
#doc-flash {
  position: absolute; inset: 0;
  background: white;
  opacity: 0;
  transition: opacity 0.18s;
  pointer-events: none;
  z-index: 3;
}
#doc-flash.fire { opacity: 0.85; }

/* Top bar — cancel + step indicator */
.doc-topbar {
  position: absolute; top: 0; left: 0; right: 0;
  z-index: 5;
  padding: calc(var(--safe-top) + 12px) 16px 14px;
  background: linear-gradient(180deg, rgba(0,0,0,0.78), rgba(0,0,0,0));
  display: flex; align-items: center; gap: 12px;
}
.doc-topbar .doc-close {
  width: 42px; height: 42px;
  border-radius: 21px;
  border: 1px solid rgba(255,255,255,0.25);
  background: rgba(0,0,0,0.6);
  color: white;
  font-size: 20px;
  cursor: pointer;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
#doc-step-badge {
  flex: 1;
  text-align: center;
  background: rgba(26,23,16,0.85);
  color: #E2C97A;
  padding: 13px 16px;
  border-radius: 24px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-family: var(--f-body);
  border: 1.5px solid #C9A84C;
  box-shadow: 0 2px 12px rgba(0,0,0,0.4);
}
.doc-topbar .doc-spacer { width: 42px; flex-shrink: 0; }

/* Aim pill (centered below top bar) */
.doc-aim {
  position: absolute;
  top: calc(var(--safe-top) + 78px);
  left: 0; right: 0;
  display: flex; justify-content: center;
  padding: 0 16px;
  pointer-events: none;
  z-index: 5;
}
.doc-aim > div {
  background: rgba(26,23,16,0.88);
  border: 1.5px solid #C9A84C;
  padding: 12px 22px;
  border-radius: 14px;
  text-align: center;
  max-width: 92%;
  box-shadow: 0 4px 14px rgba(0,0,0,0.45);
  transition: border-color 0.25s;
}
.doc-aim.ready > div { border-color: #6BCF8E; }
#doc-aim-title {
  color: #E2C97A;
  font-family: var(--f-screen);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.2;
}
#doc-aim-sub {
  color: #E2C97A;
  font-size: 12px;
  font-weight: 600;
  margin-top: 6px;
  opacity: 0.88;
  letter-spacing: 0.02em;
  font-family: var(--f-body);
}
.doc-aim.ready #doc-aim-title { color: #C5F2D5; }
.doc-aim.ready #doc-aim-sub   { color: #C5F2D5; }

/* Bottom controls — shutter + page count + cancel */
.doc-controls {
  position: absolute; bottom: 0; left: 0; right: 0;
  z-index: 5;
  padding: 18px 16px calc(var(--safe-bot) + 18px);
  background: linear-gradient(0deg, rgba(0,0,0,0.85), rgba(0,0,0,0));
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.doc-side {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}
.doc-side-left  { align-items: flex-start;  }
.doc-side-right { align-items: flex-end;   }
.doc-pill {
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.18);
  color: white;
  padding: 10px 14px;
  border-radius: 18px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  font-family: var(--f-body);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
}
.doc-pill:disabled { opacity: 0.42; cursor: not-allowed; }
.doc-pill .doc-pill-num {
  font-family: var(--f-screen);
  font-size: 16px;
  color: var(--gold-light);
  font-weight: 700;
  letter-spacing: 0;
}
.doc-shutter {
  width: 78px; height: 78px;
  border-radius: 39px;
  background: white;
  border: 5px solid rgba(255,255,255,0.4);
  cursor: pointer;
  flex-shrink: 0;
  box-shadow: 0 4px 18px rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  outline: none;
  transition: transform 0.1s;
}
.doc-shutter:active { transform: scale(0.92); }
.doc-shutter > span {
  display: block;
  width: 62px; height: 62px;
  border-radius: 31px;
  background: white;
  border: 2px solid #1A1710;
}

/* Preview state — after capture, show the scanned page + actions */
#doc-preview {
  position: absolute; inset: 0;
  background: #1A1710;
  display: none;
  flex-direction: column;
  z-index: 6;
}
#doc-preview.active { display: flex; }
.doc-preview-topbar {
  padding: calc(var(--safe-top) + 14px) 16px 12px;
  display: flex; align-items: center; gap: 12px;
  background: linear-gradient(180deg, rgba(0,0,0,0.65), rgba(26,23,16,0));
}
.doc-preview-topbar .doc-preview-title {
  flex: 1;
  text-align: center;
  color: #E2C97A;
  font-family: var(--f-screen);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.04em;
}
#doc-preview-img-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px 14px;
  overflow: auto;
}
#doc-preview-img-wrap img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 6px;
  box-shadow: 0 6px 22px rgba(0,0,0,0.4);
}
.doc-preview-actions {
  padding: 14px 12px calc(var(--safe-bot) + 18px);
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px;
  background: rgba(0,0,0,0.40);
  border-top: 1px solid rgba(255,255,255,0.06);
}
.doc-preview-actions button {
  padding: 14px 6px;
  border: 1px solid rgba(255,255,255,0.18);
  background: rgba(0,0,0,0.55);
  color: white;
  border-radius: 10px;
  font-family: var(--f-body);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1.3;
}
.doc-preview-actions .doc-act-primary {
  background: linear-gradient(135deg, #C9A84C 0%, #8A6E2F 100%);
  border-color: transparent;
  color: white;
}
.doc-preview-actions button:active { transform: scale(0.97); }

/* ── Supplement-confirmation checkbox ────────────────────
   Custom-styled because native checkbox + accent-color is inconsistent
   across Android WebView versions (some builds render the box black
   even when the page sets accent-color). This appearance:none approach
   is bulletproof: red unchecked, PDR green when checked, ✓ glyph drawn
   via ::after. */
.wiz-supp-check {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid #C0392B;
  border-radius: 4px;
  background: white;
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
  margin: 1px 0 0;
  transition: background 0.15s, border-color 0.15s;
}
.wiz-supp-check:checked {
  background: #2D6A4F;
  border-color: #2D6A4F;
}
.wiz-supp-check:checked::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}

/* Scanner preview action buttons — color-coded by intent. */
.doc-preview-actions .doc-act-retake {
  background: linear-gradient(135deg, #C0392B 0%, #922B21 100%);
  border-color: transparent;
  color: white;
}
.doc-preview-actions .doc-act-add {
  background: linear-gradient(135deg, #2D6A4F 0%, #1F4A38 100%);
  border-color: transparent;
  color: white;
}

/* The walkthrough overlay reuses .detail-overlay for its chrome, but that
   class sits at z-index 70 — BELOW the compose sheet (101). Launched from
   wizard step 5 it therefore opened behind the sheet and looked like the
   card did nothing at all. Lift it above the sheet, but keep it under the
   VIN/document scanners (305) so a scanner opened later still wins. */
#wt-overlay { z-index: 302; }

/* ── Vehicle Walkthrough ──────────────────────────────────────────────
   Card sits on wizard step 5; rows live in the full-screen overlay that
   walkthrough.js injects. Deliberately reuses .detail-overlay for the
   overlay chrome so the push-from-right animation, header and safe-area
   handling match the submission detail view. */
.wt-card {
  border: 1.5px dashed var(--gold);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--gold-pale) 0%, var(--surface) 100%);
  padding: 14px 16px;
  cursor: pointer;
}
.wt-card:active { transform: scale(0.99); }
.wt-card-done { border-style: solid; border-color: var(--green); }
.wt-card-top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 10px;
}
.wt-card-title {
  font-family: var(--f-screen);
  font-size: 17px; font-weight: 500; letter-spacing: 0.04em;
  color: var(--gold-dim);
}
.wt-card-done .wt-card-title { color: var(--green); }
.wt-card-count {
  font-family: var(--f-mono);
  font-size: 12px; color: var(--ink-soft); white-space: nowrap;
}
.wt-bar {
  height: 4px; border-radius: 2px;
  background: var(--cream-darker);
  overflow: hidden;
}
.wt-bar-fill {
  height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, var(--gold) 0%, var(--gold-dim) 100%);
  transition: width 0.25s ease;
}
.wt-card-done .wt-bar-fill { background: var(--green); }
.wt-card-sub {
  font-family: var(--f-body);
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft); margin-top: 8px;
}

.wt-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 4px;
  border-bottom: 0.5px solid var(--border);
  cursor: pointer;
}
.wt-row:active { background: var(--cream-dark); }
.wt-thumb {
  width: 54px; height: 54px; flex-shrink: 0;
  border-radius: 8px;
  background: var(--cream-darker);
  border: 0.5px solid var(--border-strong);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.wt-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wt-thumb-ico { font-size: 20px; opacity: 0.45; }
.wt-row-done .wt-thumb { border-color: var(--green); }
.wt-meta { flex: 1; min-width: 0; }
.wt-label {
  font-family: var(--f-body);
  font-size: 14px; font-weight: 500; color: var(--ink);
  margin-bottom: 2px;
}
.wt-hint {
  font-family: var(--f-body);
  font-size: 11px; line-height: 1.35; color: var(--ink-soft);
}
.wt-chev { font-size: 22px; color: var(--ink-soft); flex-shrink: 0; padding: 0 4px; }
.wt-clear {
  flex-shrink: 0;
  width: 26px; height: 26px; border-radius: 50%;
  background: rgba(26,23,16,0.06);
  border: 0.5px solid var(--border-strong);
  color: var(--ink-soft);
  font-size: 12px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}

/* Low-emphasis text link under a primary CTA — the "I already have these
   files" path, deliberately quieter than the camera button above it. */
.wiz-secondary-link {
  display: block;
  text-align: center;
  padding: 10px 8px 14px;
  font-family: var(--f-body);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--gold-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.wiz-secondary-link:active { opacity: 0.6; }

/* ── Stay-in-camera walkthrough overlay ───────────────────────────────
   Sits above everything (the compose sheet is 101, the doc/VIN scanners
   305) and stays transparent so camera-preview, which renders BEHIND the
   WebView with toBack:true, shows through. Only the chrome is painted. */
#wt-camera {
  position: fixed; inset: 0; z-index: 310;
  display: none;
  background: transparent;
}
#wt-camera.active { display: block; }

.wtc-top {
  position: absolute; top: 0; left: 0; right: 0;
  padding: calc(var(--safe-top) + 14px) 18px 18px;
  background: linear-gradient(180deg, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0) 100%);
  text-align: center;
  pointer-events: none;
}
.wtc-count {
  font-family: var(--f-body);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold-light);
  margin-bottom: 6px;
}
.wtc-label {
  font-family: var(--f-screen);
  font-size: 30px; font-weight: 500; letter-spacing: 0.04em;
  color: #fff;
  text-shadow: 0 2px 12px rgba(0,0,0,0.8);
  line-height: 1.1;
}
.wtc-hint {
  font-family: var(--f-body);
  font-size: 12px; color: rgba(255,255,255,0.85);
  margin-top: 6px;
  text-shadow: 0 1px 6px rgba(0,0,0,0.9);
}

/* Full-screen white blink on capture — the only feedback that a shot landed,
   since the camera never closes between shots. */
.wtc-flash {
  position: absolute; inset: 0;
  background: #fff; opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease-out;
}
.wtc-flash.fire { opacity: 0.85; transition: none; }

.wtc-bottom {
  position: absolute; bottom: 0; left: 0; right: 0;
  padding: 18px 16px calc(var(--safe-bot) + 16px);
  background: linear-gradient(0deg, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0) 100%);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.wtc-row {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; max-width: 420px;
}
.wtc-shutter {
  width: 78px; height: 78px; border-radius: 50%;
  background: #fff; border: 5px solid rgba(255,255,255,0.4);
  padding: 0; cursor: pointer; outline: none;
  box-shadow: 0 4px 18px rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.1s;
}
.wtc-shutter:active { transform: scale(0.92); }
.wtc-shutter:disabled { opacity: 0.5; }
.wtc-shutter span {
  display: block; width: 62px; height: 62px; border-radius: 50%;
  background: #fff; border: 2px solid var(--ink);
}
/* Done / ‹ Back / Portrait-Landscape — ONE FAMILY OF CONTROLS.
   Jon, 2026-08-22: "Back, Done, Landscape/Portrait buttons need better
   sizing/presence."

   They were three different things. Done and Back were bare white text on the
   live preview — no background, no border, ~38px tall — while Rotate already
   had a dark chip and a border. So the least important control was the most
   visible one, and the two that matter were the ones that vanished against a
   white roof or a chrome bumper.

   Now all three are the same pill: a dark scrim so they never depend on what
   is behind them, and 44px minimum so they are a real target for someone
   holding a phone in one hand at arm's length over a panel. */
.wtc-side, .wtc-rotate {
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.38);
  border-radius: 999px;
  color: #fff;
  font-family: var(--f-body); letter-spacing: 0.06em;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  /* Legible even where the scrim sits over a blown-out highlight. */
  text-shadow: 0 1px 4px rgba(0,0,0,0.8);
  transition: transform 0.1s ease, background 0.1s ease;
}
.wtc-side:active, .wtc-rotate:active {
  transform: scale(0.94);
  background: rgba(0,0,0,0.75);
}
/* THE SIZE GOES INTO HEIGHT, NOT WIDTH — and that is a constraint, not taste.
   The shutter is centred by having equal flex space either side of it. Widening
   these two pushed the left group past its share and shoved the shutter 43px
   off centre on a 375px screen (measured; it was 7px before). Two 84px pills +
   a 78px shutter simply do not fit symmetrically at that width.
   Height is free — it costs the row nothing horizontally — so the touch target
   grows 38px → 44px and the presence comes from the pill, not the footprint. */
.wtc-side {
  font-size: 14px;
  padding: 12px;
  min-width: 68px;
  min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
.wtc-rotate {
  /* POSITIONED IN BOTH ORIENTATIONS. This only ever had a position inside the
     landscape media query, so in portrait it fell into normal flow behind the
     preview and was effectively gone — once you switched to portrait there was
     no way back to landscape (Jon, 2026-08-21). */
  position: absolute; z-index: 3;
  left: calc(16px + max(16px, env(safe-area-inset-left, 0px)));
  /* Clear of the shutter row — at 18px it sat on top of Done/Back/Skip. The
     landscape block below re-anchors it to that layout's rail. */
  bottom: calc(126px + var(--safe-bot));
  font-size: 14px;
  padding: 11px 16px;
  min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Landscape capture layout — concept A, 2026-08-20.
   The controls FLOAT over the preview instead of occupying their own column.
   A solid 132px strip was stealing a quarter of the width from the image; the
   preview is the product here, the chrome is not.

   THE TRAP THIS MUST RESPECT: in landscape Samsung draws its nav bar
   (back/home/recents) down the RIGHT edge, OVER the WebView. The shutter sat
   on top of the Home button until we inset for it. Clearance is
   max(48px, env(...)) — not env() alone, because a WebView that reports no
   inset would put the shutter back on the Home button. */
@media (orientation: landscape) {
  /* Header is a floating caption, not a full-width band — and it must sit
     ABOVE the letterbox bars, which previously painted over the shot name. */
  .wtc-top {
    z-index: 3;
    /* Explicit left/right: the base rule anchors this to BOTH edges, so
       `left:auto` alone re-anchored the caption to the right-hand side. */
    left: 0; right: auto;
    width: auto;
    max-width: 58%;
    padding: calc(var(--safe-top) + 10px) 16px 12px calc(16px + env(safe-area-inset-left, 0px));
    text-align: left;
    background: linear-gradient(180deg, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0) 100%);
  }
  .wtc-label { font-size: 20px; }
  .wtc-hint { font-size: 11px; }
  .wtc-count { margin-bottom: 2px; }

  /* Floating control rail: no background plate, just the controls, so the
     image runs underneath them. */
  .wtc-bottom {
    z-index: 3;
    top: 0; bottom: 0; left: auto; right: 0;
    width: auto;
    padding: 14px calc(22px + max(48px, env(safe-area-inset-right, 0px))) 14px 10px;
    background: none;
    justify-content: center;
    gap: 0;
  }
  .wtc-row {
    flex-direction: column-reverse;   /* Done low, shutter centre, Skip high */
    align-items: center;
    justify-content: center;
    gap: 24px;
    width: auto;
  }
  .wtc-shutter {
    width: 66px; height: 66px;
    box-shadow: 0 2px 14px rgba(0,0,0,0.75);   /* legible over a bright panel */
  }
  .wtc-shutter span { width: 52px; height: 52px; }
  /* Landscape keeps the pill but trims the width — the rail is a narrow strip
     down the right edge and every pixel it takes comes off the preview. Height
     stays a real target: there is room for it, the rail centres two 42px
     buttons and a 66px shutter inside 384px with space to spare. */
  .wtc-side {
    min-width: 0; padding: 10px 16px;
    font-size: 14px; letter-spacing: 0.06em;
    min-height: 42px;
  }

  .wtc-rotate {
    position: absolute; z-index: 3;
    left: calc(16px + max(16px, env(safe-area-inset-left, 0px)));
    bottom: calc(14px + var(--safe-bot));
    padding: 10px 15px; font-size: 13px;
    min-height: 42px;
  }

  /* Letterbox bars, sized in JS from the real preview rect. Behind the chrome
     (z-index 1) so they can never clip the shot name again. */
  .wtc-band {
    position: absolute; top: 0; bottom: 0;
    z-index: 1;
    background: #000;
    pointer-events: none;
  }
  .wtc-band-left  { left: 0; }
  .wtc-band-right { right: 0; }
}

/* Left-handed variant: mirror the landscape control column to the other edge.
   Only the side changes — order, sizes and spacing stay identical, so muscle
   memory transfers if a shop switches a phone between techs. */
@media (orientation: landscape) {
  #wt-camera.cam-side-left .wtc-bottom {
    left: 0; right: auto;
    padding: 14px 10px 14px calc(22px + max(48px, env(safe-area-inset-left, 0px)));
  }
  #wt-camera.cam-side-left .wtc-top {
    left: auto; right: 0;
    padding-left: 16px;
    padding-right: calc(16px + env(safe-area-inset-right, 0px));
    text-align: right;
  }
  #wt-camera.cam-side-left .wtc-rotate {
    left: auto;
    right: calc(16px + max(16px, env(safe-area-inset-right, 0px)));
  }

  #vin-scanner.cam-side-left #vin-scanner-controls {
    left: 0 !important; right: auto !important;
    background: linear-gradient(90deg, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0) 100%) !important;
  }
}
/* Live preview on the getUserMedia path. Unlike camera-preview (which renders
   a native layer BEHIND a transparent WebView), this is an ordinary element,
   so CSS owns the sizing — no measured rects, no hand-painted letterbox, and
   nothing to strand when the device rotates.
   object-fit:contain shows the WHOLE sensor frame, which is the point: the
   tech must see everything that will be captured, not a centre crop. */
#wtc-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  z-index: 0;
}
/* ⛔ ATTEMPT 1 FAILED — Jon, 2026-08-26: "play button is still there on both".
   The vendor pseudo-elements below are kept because they cost nothing and may
   catch other WebView versions, but they DO NOT suppress it on the S22. The
   real fix is .live below: hide the element unless it is actually painting
   frames. The doc scanner reached the same conclusion — its comment says the
   placeholder was beaten by display:none on the element, not by styling the
   control. Styling the control is the thing that does not work.

   THE BRIEF BLACK-AND-WHITE PLAY BUTTON (Jon, 2026-08-26: "It gets a black and
   white 'play' button that shows up briefly when switching").
   This is the WebView's OWN media control, not anything we draw. A <video>
   that is momentarily not playing gets the built-in start-playback overlay —
   and switching lens, rotating, or applying a Dent Logic constraint all stall
   the stream for a frame or two.
   The doc scanner hit the same placeholder and could hide the element outright
   (#doc-video, above) because its real preview is a native layer behind the
   WebView. We cannot do that here: on the stream path this element IS the
   preview. So suppress the control instead of the video.
   Several vendor pseudo-elements are listed because which one draws the
   overlay differs by WebView version, and an unrecognised selector is simply
   ignored. */
#wtc-video::-webkit-media-controls,
#wtc-video::-webkit-media-controls-panel,
#wtc-video::-webkit-media-controls-start-playback-button,
#wtc-video::-webkit-media-controls-overlay-play-button,
#wtc-video::-webkit-media-controls-play-button {
  display: none !important;
  -webkit-appearance: none !important;
  opacity: 0 !important;
}

/* ATTEMPT 2 — the one that addresses the actual mechanism.
   The placeholder is drawn by the WebView whenever the element is NOT painting
   frames. So make the element invisible in exactly that state: `.live` is
   added on `playing` and removed on pause/waiting/stalled/emptied. The parent
   #wt-camera is black, so a stall now reads as a black frame instead of a
   play triangle — which is what a camera briefly re-negotiating should look
   like anyway.
   ⚠️ THE DANGEROUS FAILURE MODE IS A PERMANENTLY BLACK CAMERA, which is far
   worse than a cosmetic play button. wtStartStream therefore force-adds
   `.live` on a timer if `playing` never fires — see the safety net there. */
/* ⚠️ NO TRANSITION HERE, DELIBERATELY. A 0.12s fade was written first and
   removed: a transition that never runs leaves the element stuck at its START
   value, i.e. an invisible preview. That is not theoretical — it showed up
   immediately in testing, because a page whose visibilityState is "hidden"
   stops compositing and freezes the transition at opacity 0 indefinitely.
   Backgrounding and resuming the app is the same class of event.
   The upside was a cosmetic fade; the downside was a black camera. Hard cut. */
#wtc-video { opacity: 0; }
#wtc-video.live { opacity: 1; }

/* One row per rear camera in the lens picker. */
.lens-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 8px;
  background: var(--surface);
  border: 0.5px solid var(--border-strong);
  border-radius: 10px;
  font-family: var(--f-body);
  font-size: 14px;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.lens-row.selected {
  border-color: var(--gold);
  background: var(--gold-pale);
}
.lens-row .tick { color: var(--gold-dim); font-size: 16px; width: 18px; flex-shrink: 0; }
.lens-row .lens-name { flex: 1; min-width: 0; }
.lens-row .lens-sub {
  display: block;
  font-size: 11px; color: var(--ink-soft); margin-top: 2px;
}

/* ── First-run camera setup ───────────────────────────────────────────
   Reuses .detail-overlay for chrome. Sits above the sheet (101) so it can
   run over a wizard, but below the cameras (305+) so a scanner opened from
   inside setup still wins. */
#cam-setup { z-index: 300; }
#cam-setup .detail-body { padding: 22px 20px calc(24px + var(--safe-bot)); }
.cs-hero { font-size: 46px; text-align: center; margin: 10px 0 18px; }
.cs-h {
  font-family: var(--f-screen);
  font-size: 22px; font-weight: 500; letter-spacing: 0.03em;
  color: var(--ink); margin-bottom: 8px; line-height: 1.25;
}
.cs-p {
  font-family: var(--f-body);
  font-size: 13px; line-height: 1.65; color: var(--ink-soft);
  margin-bottom: 16px;
}
.cs-video {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 10px;
  background: #000;
  object-fit: cover;
  display: block;
}

/* VIN reticle turns green the moment the frame actually looks like a label.
   Without this the tech cannot distinguish "hasn't seen it yet" from "broken"
   — which is exactly the confusion the long-label failure produced. */
#vin-scanner-reticle.locked {
  border-color: var(--green) !important;
  box-shadow: 0 0 0 3px rgba(45,106,79,0.35), 0 0 24px rgba(45,106,79,0.4) !important;
  transition: border-color 0.15s, box-shadow 0.15s;
}

/* Reticle morphs onto the detected barcode band rather than sitting at a fixed
   size — Jon: "why cant we make the frame morph on the longer label?" A fixed
   box cannot fit both a wide Ford label and a sideways Jeep one, and it gives
   the tech no feedback about what the scanner has actually found. */
#vin-scanner-reticle.morphed {
  transition: left 0.18s ease, top 0.18s ease, width 0.18s ease, height 0.18s ease,
              border-color 0.15s, box-shadow 0.15s;
}

/* ── Review-step action tiles ─────────────────────────────────────────
   Jon, 2026-08-21: the walkthrough card, damage photos, document scan and
   "choose files" were four different shapes — a dashed block, a small ghost
   button, a text link and a custom card. Four ways of saying "tap here" on
   one screen reads as four unrelated things.

   One row-shaped tile for all of them: fixed height, icon left, label and
   sub-label right. Row rather than the old stacked block because four stacked
   blocks would push the review summary off the screen entirely. */
.wiz-tile {
  display: flex; align-items: center; gap: 14px;
  width: 100%;
  min-height: 84px;          /* grew with the larger sub-label */
  padding: 14px 16px;
  margin-bottom: 10px;
  border: 1.5px dashed var(--gold);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--gold-pale) 0%, var(--surface) 100%);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
}
.wiz-tile:active { transform: scale(0.99); }
.wiz-tile-icon {
  width: 30px; height: 30px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--gold-dim);
}
/* Same treatment as the bottom nav — thin stroked line work. Emoji were the
   only place in the app breaking that, and they render differently on every
   platform, so the product looked inconsistent depending on the phone. */
.wiz-tile-icon svg {
  width: 26px; height: 26px;
  stroke: currentColor; fill: none;
  stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round;
}
.wiz-tile-done .wiz-tile-icon { color: var(--green); }
.wiz-tile-body { flex: 1; min-width: 0; }
.wiz-tile-title {
  display: block;            /* spans are inline — without this the sub-label
                                ran straight into the title */
  font-family: var(--f-screen);
  font-size: 16px; font-weight: 500; letter-spacing: 0.03em;
  color: var(--gold-dim);
  line-height: 1.2;
}
.wiz-tile-sub {
  display: block;
  font-family: var(--f-body);
  /* Was 10px uppercase in --ink-soft, which read as decoration rather than
     information. These lines tell the tech what each button actually does, so
     they are now body-sized, sentence case and in the mid ink. Uppercase at
     small sizes costs legibility for a bay-lit phone held at arm's length. */
  font-size: 12.5px;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ink-mid);
  margin-top: 5px;
  line-height: 1.4;
}
.wiz-tile-meta {
  font-family: var(--f-mono);
  font-size: 12px; color: var(--ink-soft);
  flex-shrink: 0;
}
/* Secondary tiles (existing files) sit quieter than the capture actions —
   same footprint, less pull. */
/* Completed state, used by the walkthrough once every shot is captured. */
/* Three states, read off the border alone:
     dashed gold  — nothing captured yet
     solid gold   — started, not finished
     solid green  — complete
   The border goes from broken to whole as the work fills in, so green
   keeps meaning FINISHED rather than merely touched. Without the middle
   state a walkthrough at 2/14 looked exactly like one at 0/14. */
.wiz-tile-progress { border-style: solid; border-color: var(--gold); }
.wiz-tile-done { border-style: solid; border-color: var(--green); }
.wiz-tile-done .wiz-tile-title { color: var(--green); }
.wiz-tile .wt-bar { margin-top: 7px; }

/* Facility shown as a fact rather than a control, when there is nothing to
   choose. */
.wiz-static-field {
  padding: 13px 14px;
  border: 0.5px solid var(--border);
  border-radius: 8px;
  background: var(--cream-dark);
  font-size: 15px;
  color: var(--ink);
}

/* Individual / Business segmented toggle. Most owners are people, so Business
   reveals an extra field rather than the form carrying an empty Company box on
   every job. */
.owner-type {
  display: flex; gap: 8px; margin-bottom: 14px;
}
.owner-type-btn {
  flex: 1;
  padding: 11px 8px;
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface);
  font-family: var(--f-body);
  font-size: 13px; letter-spacing: 0.06em;
  color: var(--ink-mid);
  cursor: pointer;
}
.owner-type-btn.active {
  background: var(--gold-pale);
  border-color: var(--gold);
  color: var(--gold-dim);
  font-weight: 500;
}

/* ── Autocomplete menu (insurance company) ──────────────────────────────
   Replaces the native <datalist>, which Android renders as an OS overlay we
   cannot size, position or bound — it covered the form from the sheet header
   down past Claim #.

   THREE ROWS, HARD. 3 x 46px + borders is a little over 140px, so at worst it
   reaches the field immediately below and no further. Anything taller and we
   are back to the bug. The list scrolls inside itself when there are more
   matches; it never grows to fit them. */
.ac-menu {
  position: absolute;
  top: 100%; left: 0; right: 0;
  z-index: 20;
  margin-top: 4px;
  max-height: 141px;          /* 3 rows; the 4th is half-visible = "scroll me" */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* scrolling the list must not scroll the sheet */
  background: var(--surface);
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0,0,0,0.22);
}
.ac-menu[hidden] { display: none; }
.ac-item {
  display: flex; align-items: center;
  width: 100%;
  min-height: 46px;
  padding: 12px 14px;
  background: none; border: none;
  border-bottom: 0.5px solid var(--border);
  font-family: var(--f-body); font-size: 15px; color: var(--ink);
  text-align: left; cursor: pointer;
  touch-action: manipulation;
}
.ac-item:last-child { border-bottom: none; }
.ac-item:active { background: var(--cream); }
/* The matched span of an existing carrier, so you can see WHY it is offered. */
.ac-item b { font-weight: 700; color: var(--gold-dim); }
/* "Use X as a new carrier" — last row, visually distinct, and PINNED.
   It is the row that answers "am I typing something that isn't in the list?",
   so it is the one row that must never be the one you have to scroll to find.
   Unpinned it sat 4th behind three matches and was invisible exactly when a
   tech was typing a carrier we have never seen. Opaque background because the
   matches scroll underneath it. */
.ac-item.ac-new {
  position: sticky; bottom: 0;
  background: var(--surface);
  color: var(--gold-dim);
  font-size: 14px;
  border-top: 0.5px solid var(--border-strong);
}

/* Internal-only photos — blocked from every outbound email.
   The badge is always visible rather than hidden behind a menu: a tech needs
   to see at a glance which shots will reach the adjuster, and an email cannot
   be recalled once it has gone. */
.tile-lock {
  position: absolute; top: 4px; left: 4px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(26,23,16,0.62);
  border: none;
  font-size: 13px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
  z-index: 2;
  /* RECESSIVE BY DEFAULT. Most photos are sendable, and the badge on the
     normal case is a control, not a warning — at full strength on every tile
     it reads as a grid of flagged photos (Jon, 2026-08-22: "images still show
     locked"). The withheld state below overrides all three of colour, opacity
     and the icon's shape, so the difference does not rest on any one of
     them. */
  color: #fff;
  opacity: 0.62;
}
.tile-lock svg { display: block; }
.photo-tile.tile-internal { outline: 2px solid var(--red); outline-offset: -2px; }
.photo-tile.tile-internal img { opacity: 0.55; }
/* Withheld: a GOLD padlock (Jon, 2026-08-22). Deliberately NOT gold-on-red —
   the two colours are close in value and the glyph muddies against the fill.
   The circle goes near-black so the gold reads at 26px, and the loud part of
   the signal stays where it already worked: the red tile outline, the dimmed
   photo and the INTERNAL tag. */
.photo-tile.tile-internal .tile-lock {
  background: rgba(20,17,12,0.86);
  color: var(--gold);
  opacity: 1;
}
.tile-internal-tag {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: var(--red); color: #fff;
  font-family: var(--f-body);
  font-size: 8px; letter-spacing: 0.14em; text-transform: uppercase;
  text-align: center; padding: 2px 0;
}


/* ── Confirm dialog ──────────────────────────────────────────────
   Deliberately NOT window.confirm(). A WebView offers "prevent this page
   from creating more dialogs" after a few native prompts; if a tech ticks
   it, confirm() starts returning false with no UI at all — the guard would
   vanish silently AND the sheet would stop closing. This is the last thing
   between a tech and losing a submission with photos in it, so it cannot
   depend on a browser affordance we do not control.
   Above #wt-overlay (302) so it still shows over an open camera. */
.confirm-backdrop {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  opacity: 0; pointer-events: none;
  transition: opacity 0.16s ease;
}
.confirm-backdrop.open { opacity: 1; pointer-events: auto; }
.confirm-card {
  width: 100%; max-width: 340px;
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 22px 20px 16px;
  box-shadow: 0 18px 50px rgba(0,0,0,0.32);
  transform: translateY(8px) scale(0.98);
  transition: transform 0.16s ease;
}
.confirm-backdrop.open .confirm-card { transform: none; }
.confirm-title {
  font-family: var(--f-display);
  font-size: 18px; color: var(--ink); margin-bottom: 8px;
}
.confirm-body {
  font-family: var(--f-body);
  font-size: 14px; line-height: 1.5; color: var(--ink-mid);
  margin-bottom: 20px;
}
.confirm-actions { display: flex; flex-direction: column; gap: 8px; }
.confirm-actions .btn { width: 100%; justify-content: center; min-height: 46px; }
.btn-danger {
  /* #8E2C2C measured 3.45:1 against white — fine for large text, short of the
     4.5:1 normal-text bar. Darkened to clear it in both themes; the button's
     contrast is against its OWN background so it holds either way. */
  background: #662020; color: #FFF; border: none;
}


/* Caption on a preview tile — says WHICH of the fourteen shots this is, so a
   grid of near-identical panel photos is still readable. */
.ph-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.72), rgba(0,0,0,0));
  color: #fff;
  font-family: var(--f-body);
  font-size: 9px; letter-spacing: 0.06em;
  padding: 10px 4px 3px;
  text-align: center;
  pointer-events: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* This class was being applied to locked tiles but had NO rule, so a blocked
   photo looked identical to a sendable one except for a small bottom tag.
   On the one feature where a mistake means disclosure, the state has to be
   obvious at a glance across a full grid. */
.tile-internal {
  outline: 2px solid var(--red);
  outline-offset: -2px;
}
.tile-internal img { opacity: 0.55; }
/* The internal tag sits at the bottom; move the caption above it so they do
   not stack on the same pixels. */
.tile-internal .ph-cap { bottom: 13px; }
/* 26px was too small for a control whose mis-tap silently UNLOCKS a photo the
   tech meant to block. */
.tile-lock { width: 34px; height: 34px; font-size: 15px; touch-action: manipulation; }


/* Preset label picker — chips rather than a dropdown, because the tech is
   choosing with one thumb while holding a document in the other hand. */
.label-chips {
  display: flex; flex-wrap: wrap; gap: 7px;
  margin-bottom: 18px;
}
.label-chip {
  font-family: var(--f-body);
  font-size: 13px;
  padding: 9px 13px;
  min-height: 38px;
  border-radius: 999px;
  border: 0.5px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  touch-action: manipulation;
}
.label-chip.on {
  background: var(--gold-dim);
  border-color: var(--gold-dim);
  color: #1A1710;
  font-weight: 500;
}
/* The caption on a loose file is a control, so it has to look like one. */
.ph-cap-btn {
  border: none;
  cursor: pointer;
  font-family: var(--f-body);
  touch-action: manipulation;
  padding-top: 14px;
}
.ph-cap-empty { color: var(--gold-dim); }
/* On a document tile there is no photo behind it, so the gradient has nothing
   to sit over — give it its own ground and put it at the top. */
.doc-tile .ph-cap-btn {
  top: 0; bottom: auto;
  background: rgba(0,0,0,0.30);
  padding: 4px;
}


/* Side groups either side of the shutter. Both flex:1, so the shutter stays
   optically centred even though the left group holds two controls (Done,
   Back) and the right holds one (Skip). */
.wtc-group {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 4px, not 2px: as bare text labels these read as one control, and as
     bordered pills they would touch. Not more than 4 — every pixel here comes
     straight out of the shutter's centring (see .wtc-side). */
  gap: 4px;
}
@media (orientation: landscape) {
  /* The rail is a reversed column, so each group becomes a reversed column too
     — Done lowest, Back above it, matching the portrait left-to-right order. */
  .wtc-group {
    flex-direction: column-reverse;
    gap: 14px;
  }
}




/* ── Thank-you screen ────────────────────────────────────
   The desktop card is a fixed 460px with 52/60px padding, which would not fit
   a phone — this keeps the same elements and proportions but sizes to the
   viewport. Above the lightbox (310) and the confirm dialog (400) so nothing
   can cover it. */
#thank-you-modal {
  display: none;
  position: fixed; inset: 0; z-index: 500;
  background: rgba(26,23,16,0.78);
  align-items: center; justify-content: center;
  padding: 24px;
}
#thank-you-modal.show { display: flex; }
.ty-card {
  background: #F3EEE3;
  width: 100%; max-width: 380px;
  padding: 34px 26px 24px;
  text-align: center;
  border-radius: 4px;
  animation: fadeInUp 0.45s ease-out;
}
@keyframes fadeInUp { 0% { opacity: 0; transform: translateY(18px); } 100% { opacity: 1; transform: none; } }
@keyframes diamondSpin {
  0%   { transform: rotateY(0deg) scale(1); }
  25%  { transform: rotateY(90deg) scale(0.9); }
  50%  { transform: rotateY(180deg) scale(1); }
  75%  { transform: rotateY(270deg) scale(0.9); }
  100% { transform: rotateY(360deg) scale(1); }
}
.ty-diamond-wrap { perspective: 800px; margin-bottom: -6px; }
.ty-diamond {
  width: 64px; height: 64px; margin: 0 auto 6px;
  animation: diamondSpin 3s ease-in-out infinite;
  filter: drop-shadow(0 4px 12px rgba(201,168,76,0.3));
}
/* The logo PNG carries artwork above the wordmark; the crop shows the text
   only, same trick the desktop card uses, scaled for the narrower card. */
.ty-logo-crop { width: 230px; margin: 0 auto 16px; overflow: hidden; height: 80px; }
.ty-logo { width: 230px; display: block; margin-top: -69px; }
.ty-title {
  font-family: var(--f-display);
  font-size: 21px; color: #1A1710; letter-spacing: 0.06em; margin-bottom: 6px;
}
.ty-facility {
  font-family: var(--f-display);
  font-size: 17px; font-weight: 600; color: #8A6E2F;
  letter-spacing: 0.06em; margin-bottom: 16px;
}
.ty-rule { width: 40px; height: 1px; background: #C9A84C; margin: 0 auto 16px; }
.ty-body {
  font-family: var(--f-body);
  font-size: 13.5px; color: #3D3A30; line-height: 1.7;
}
.ty-dismiss {
  font-family: var(--f-body);
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: #6B6557; margin-top: 18px;
}

/* ── DENT LOGIC ──────────────────────────────────────────────────────
   One toggle in the damage camera that pulls exposure down so a PDR light
   stops blowing out the panel. Measured cause on RO 6044: full-frame auto
   exposure swinging mean luminance 29 → 153 between consecutive shots, with
   up to 6% of the frame clipped to pure white.

   Sits ABOVE the shutter row, inside the same dark gradient, so it is under
   the same thumb as Done and Back without competing with the shutter for the
   centre. Same pill family as .wtc-side deliberately — it is a camera control,
   not a new kind of thing.

   ON is gold, not merely "pressed": a tech glancing down mid-shot needs to
   know at once whether it is active, and a subtle state change on a live
   camera feed is no signal at all. */
.wtc-dent {
  /* A ROW IN THE BOTTOM BAR, NOT AN OVERLAY. It was absolutely positioned,
     which put it over the middle of the shot and on top of the rotate button.
     As a flex child of .wtc-bottom it stacks above the shutter row, inside the
     existing gradient, and cannot shift the shutter. */
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; max-width: 420px;
}
.wtc-dent-btn {
  min-height: 44px; padding: 0 20px;
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.38);
  border-radius: 999px;
  color: #fff;
  font-family: var(--f-body); font-size: 13px; letter-spacing: 0.08em;
  text-shadow: 0 1px 4px rgba(0,0,0,0.8);
  cursor: pointer; white-space: nowrap; touch-action: manipulation;
  transition: transform 0.1s ease, background 0.1s ease, color 0.1s ease;
}
.wtc-dent-btn:active { transform: scale(0.94); }
.wtc-dent-btn.on {
  background: var(--gold, #C9A84C);
  border-color: rgba(255,255,255,0.7);
  color: #1A1710;
  text-shadow: none;
  font-weight: 600;
}
/* Debug-only stepper for dialling the value in on real panels. Hidden unless
   llpp_debug is set, and it goes away once the default is frozen. */
.wtc-dent-dbg {
  display: flex; align-items: center; gap: 6px;
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.28);
  border-radius: 999px; padding: 0 6px; min-height: 44px;
}
.wtc-dent-dbg button {
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(255,255,255,0.14); border: none; color: #fff;
  font-size: 17px; line-height: 1; cursor: pointer; touch-action: manipulation;
}
.wtc-dent-dbg b {
  color: #fff; font-family: var(--f-body); font-size: 13px;
  min-width: 34px; text-align: center; font-variant-numeric: tabular-nums;
}


/* The Dent Logic row makes the bottom bar ~54px taller, and .wtc-rotate is
   absolutely positioned at an offset tuned for the bar WITHOUT it — so the two
   overlapped (measured 642-686 vs 664-708). Lift rotate only while that row is
   present; walkthrough mode is unaffected. */
#wt-camera.has-dent .wtc-rotate { bottom: calc(196px + var(--safe-bot)); }
@media (orientation: landscape) {
  #wt-camera.has-dent .wtc-rotate { bottom: calc(14px + var(--safe-bot)); }
}

/* ── Google Drive handoff wait sheet (Android app, 2026-09-14) ─────────
   Shown while the tech picks the estimate in Chrome (see
   netlify/functions/drive-handoff.js). z-index 150: above the compose sheet
   (101) it is launched from, BELOW the toast (200) so "Downloading…" and
   error toasts stay readable on top of it. Built by
   _gdriveHandoffShowSheet() in m/js/app.js — no markup in index.html. */
.gdh-overlay {
  position: fixed; inset: 0; z-index: 150;
  display: none; align-items: flex-end; justify-content: center;
  background: rgba(26,23,16,0.55);
}
.gdh-overlay.open { display: flex; }
.gdh-card {
  width: 100%; max-width: 520px;
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  box-shadow: var(--shadow-up);
  padding: 22px 20px calc(20px + var(--safe-bot));
  text-align: center;
}
.gdh-spinner {
  width: 34px; height: 34px; margin: 4px auto 14px;
  border-radius: 50%;
  border: 3px solid var(--border-strong);
  border-top-color: var(--gold);
  animation: gdh-spin 0.9s linear infinite;
}
@keyframes gdh-spin { to { transform: rotate(360deg); } }
.gdh-title {
  font-family: var(--f-screen);
  font-size: 20px; font-weight: 500; letter-spacing: 0.04em;
  color: var(--ink);
  margin-bottom: 8px;
}
.gdh-text {
  font-size: 13px; line-height: 1.6; color: var(--ink-soft);
  margin: 0 auto 18px; max-width: 360px;
}
.gdh-text strong { color: var(--ink-mid); }
.gdh-btn {
  display: block; width: 100%;
  padding: 14px; border-radius: 8px;
  font-family: inherit; font-size: 12px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  cursor: pointer;
}
/* display:block above would beat the hidden attribute; the sheet hides these
   while it's checking / attaching (_gdriveHandoffSheetState). */
.gdh-btn[hidden] { display: none; }
.gdh-btn-primary {
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-dim) 100%);
  color: #fff; border: none; margin-bottom: 8px;
}
.gdh-btn-ghost {
  background: var(--surface); color: var(--ink-mid);
  border: 0.5px solid var(--border-strong);
}
