/* Tablet Viewer — "buildings3" skin.
   Warm off-white page, white cards with hairline borders, near-black ink text,
   a deep brand-red accent used sparingly. Flat and editorial: no heavy shadows.
   This is a visual restyle only — class names and behaviour are unchanged. */

:root {
  --b3-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --ink:   #1a1a1a;   /* primary text */
  --ink-2: #6b6a67;   /* secondary text */
  --ink-3: #9c9a96;   /* tertiary / hints */
  --ink-4: #c9c7c3;   /* faint */

  --line:   rgba(0, 0, 0, .07);   /* hairline borders */
  --line-2: rgba(0, 0, 0, .045);

  --accent:      #a3120c;   /* deep brand red — primary accent */
  --accent-soft: #fbe4e2;   /* soft red highlight/bg */

  --dark: #191915;

  --warn: #b06a10;  --warn-soft: #fbeeda;
  --good: #15884b;
  --info: #1f5f8f;  --info-soft: #e2eef7;  /* neutral "under way" tone — not a warning, not done */

  --bg:     #f3f4f1;   /* warm off-white page background */
  --panel:  #fff;      /* cards/panels */
  --panel-2: #f0efed;  /* insets */

  --radius: 14px;

  /* Sticky top-bar height. Content row is the 48px controls + 12px bottom padding + 1px
     border; the top padding grows with the notch safe-area inset. Overridden in
     high-contrast mode where the controls grow to 56px. */
  --topbar-h: calc(61px + max(12px, env(safe-area-inset-top)));
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--b3-font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none; /* no long-press callout on a kiosk */
}

/* Demo-mode banner — in-theme (soft red), still clearly a banner */
.demo-banner {
  padding: 8px 14px;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-soft);
  border-bottom: 1px solid var(--line);
}

.hidden { display: none !important; }
.muted { color: var(--ink-2); }
.small { font-size: 0.85rem; }

/* Top bar */
.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  padding-top: max(12px, env(safe-area-inset-top));
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
  min-height: 60px;
}
.crumb {
  flex: 1;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.status { display: flex; align-items: center; gap: 8px; color: var(--ink-2); font-size: 0.85rem; cursor: default; }
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-4); flex: none; }
.dot.online { background: var(--good); }
.dot.offline { background: var(--warn); }
.dot.syncing { background: var(--info); animation: pulse 1s infinite; }
@keyframes pulse { 50% { opacity: 0.35; } }

.icon-btn {
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 12px;
  min-width: 48px;
  min-height: 48px;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.icon-btn:active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

.topbar-mark { flex: none; width: 22px; height: 22px; }
/* Too tight next to a visible back button at 360px — drop the mark rather
   than risk the topbar wrapping/overflowing (crumb/status still need room). */
#backBtn:not(.hidden) + .topbar-mark { display: none; }

.main { padding: 20px 16px; padding-bottom: 32px; }
/* Outer content bound. Wide enough that a thumbnail grid keeps GAINING COLUMNS
   on a big tablet / desktop instead of stranding empty margin (the old 1100px
   cap wasted ~800px at 1920). Inner surfaces that must stay a readable width
   (the slot list, the progress column, the door-card sheet) carry their own,
   tighter max-width, so only the image grids run this wide. */
.view { width: 100%; max-width: 1600px; margin: 0 auto; }

/* Grids — auto-fill (not auto-fit) so a sparse grid keeps tile-sized cards
   instead of stretching a few tiles across the whole row. */
.grid { display: grid; gap: 14px; }
.grid-trades { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.grid-files { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
/* Full-width stage heading inside the files grid (e.g. Site Photos: Before …
   Final). Spans every column so each group's thumbnails sit beneath its label. */
.grid-group-header {
  grid-column: 1 / -1;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 6px 2px 0;
  border-bottom: 1px solid var(--line);
}

.tile {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px 16px;
  min-height: 124px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.12s, background 0.12s;
}
.tile:active { border-color: var(--accent); background: var(--accent-soft); }
.tile .tile-icon { font-size: 2.4rem; line-height: 1; }
.tile .tile-name { font-size: 1.05rem; font-weight: 600; color: var(--ink); letter-spacing: -0.01em; word-break: break-word; }
.tile .tile-sub { font-size: 0.78rem; color: var(--ink-3); }

/* File thumbnails */
.thumb {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 1 / 1;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.12s;
}
.thumb:active { border-color: var(--accent); }
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb .ph { font-size: 2.4rem; color: var(--ink-4); }
.thumb .badge {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: rgba(255, 255, 255, 0.93);
  border-top: 1px solid var(--line);
  color: var(--ink);
  font-size: 0.72rem; font-weight: 500;
  padding: 5px 7px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.thumb .pill {
  position: absolute; top: 7px; right: 7px;
  background: var(--accent); color: #fff;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.04em;
  padding: 3px 7px; border-radius: 999px;
}
.thumb.pending { opacity: 0.55; }

/* Tap-to-retry overlay for a thumbnail that failed to load (never a broken img) */
.thumb .retry {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 4px;
  background: var(--panel);
  color: var(--ink-3);
  font-size: 0.72rem; text-align: center;
  cursor: pointer;
}
.thumb .retry .retry-icon { font-size: 1.6rem; line-height: 1; color: var(--accent); }
.thumb .retry:active { background: var(--panel-2); }

/* Loading skeletons (trade tiles + thumbnails) — a calm shimmer, no layout jump */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--panel-2);
}
.skeleton::after {
  content: "";
  position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    rgba(0, 0, 0, 0.045),
    transparent
  );
  animation: shimmer 1.25s ease-in-out infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .skeleton::after { animation: none; }
  .dot.syncing { animation: none; }
}

.empty { text-align: center; color: var(--ink-3); margin-top: 40px; font-size: 1.05rem; }

/* Pairing / waiting cards */
.pair-card {
  max-width: 520px; margin: 8vh auto 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 44px 32px;
  text-align: center;
}
.pair-card h1 { margin: 0 0 8px; font-size: 1.7rem; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
.pair-kicker {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 6px;
}
.pair-card .share-link-row { margin-top: 18px; }
.pair-code {
  font-size: clamp(3rem, 16vw, 6rem);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--accent);
  margin: 28px 0;
  font-variant-numeric: tabular-nums;
}
.spinner {
  width: 42px; height: 42px; margin: 28px auto 0;
  border: 4px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Full-screen viewer — an immersive warm-dark stage so photos/PDFs read well,
   with chrome that stays in the buildings3 family (warm dark, red accents). */
.viewer {
  position: fixed; inset: 0; z-index: 100;
  background: var(--dark);
  display: flex; flex-direction: column;
}
.viewer-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; padding-top: max(12px, env(safe-area-inset-top));
  background: rgba(25, 25, 21, 0.92);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  color: #f3f4f1;
  z-index: 2;
}
.viewer-bar .icon-btn {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.14);
  color: #f3f4f1;
}
.viewer-bar .icon-btn:active { background: var(--accent); border-color: var(--accent); color: #fff; }
.viewer-title { flex: 1; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.viewer-count { color: rgba(243, 244, 241, 0.6); font-variant-numeric: tabular-nums; }
.viewer-stage {
  flex: 1; position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  touch-action: none;
}
.viewer-stage img {
  max-width: 100%; max-height: 100%;
  transform-origin: 0 0;
  will-change: transform;
  -webkit-user-drag: none;
}
.viewer-stage .pdf-scroll {
  position: absolute; inset: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y pinch-zoom;
  padding: 12px 0;
  text-align: center;
}
.viewer-stage .pdf-scroll canvas {
  display: block; margin: 0 auto 12px; max-width: 100%; background: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}
/* One page box per PDF page: it owns the layout width, the canvas fills it, and
   the link layer is positioned over it in percentages — so hit areas stay on
   their printed text when the box shrinks at phone width or on rotation. */
.viewer-stage .pdf-page { position: relative; max-width: 100%; margin: 0 auto 12px; }
.viewer-stage .pdf-page canvas { width: 100%; margin: 0; }
.viewer-stage .pdf-links { position: absolute; inset: 0; overflow: hidden; }
/* Faintly tinted so a contractor can SEE what is tappable on a printed plan.
   Deliberately the neutral link-blue (--info), never --accent: a red wash over
   a plan reads as "something is wrong here". */
.viewer-stage .pdf-link {
  position: absolute; display: block;
  background: rgba(31, 95, 143, 0.14);
  border-radius: 2px;
}
.viewer-stage .pdf-link:hover, .viewer-stage .pdf-link:active {
  background: rgba(31, 95, 143, 0.32);
}
.viewer-loading { color: rgba(243, 244, 241, 0.7); font-size: 1.05rem; }
.viewer-error { display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; padding: 24px; }
.viewer-retry {
  background: var(--accent); color: #fff;
  border: none; border-radius: 12px;
  padding: 12px 28px; font-size: 1rem; font-weight: 600;
  min-height: 48px; cursor: pointer;
}
.viewer-retry:active { opacity: 0.85; }

.nav-btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 3;
  background: rgba(25, 25, 21, 0.55);
  color: #fff; border: none;
  width: 56px; height: 84px;
  font-size: 2.4rem; line-height: 1;
  cursor: pointer;
}
.nav-prev { left: 0; border-radius: 0 12px 12px 0; }
.nav-next { right: 0; border-radius: 12px 0 0 12px; }
.nav-btn:active { background: var(--accent); }
/* A disabled arrow must not swallow taps: it floats over the document, and on a
   plan (a single-file slot, so both arrows are disabled) it would otherwise
   steal a tap from a supplier link sitting underneath it. */
.nav-btn:disabled { opacity: 0.2; pointer-events: none; }

/* Toast */
.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  background: var(--dark); color: #f3f4f1;
  padding: 12px 20px; border-radius: 999px;
  font-size: 0.92rem; z-index: 200;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
}

/* Deploy notice — sits below the full-screen viewer (z-index 100) so it never
   overlays a file mid-view; see public/js/updatecheck.js. */
.updnotice {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; padding-bottom: max(10px, env(safe-area-inset-bottom));
  background: var(--dark); color: #f3f4f1;
  font-size: 0.85rem;
  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
}
.updnotice-text { flex: 1; min-width: 0; }
.updnotice-reload {
  flex: none;
  background: var(--accent); color: #fff; border: none;
  border-radius: 6px; padding: 6px 12px;
  font-size: 0.85rem; font-weight: 600; cursor: pointer;
}
.updnotice-dismiss {
  flex: none;
  background: transparent; color: #f3f4f1; border: none;
  font-size: 1.1rem; line-height: 1; padding: 4px 6px; cursor: pointer;
}

/* Unsupported / download card in the full-screen viewer */
.viewer-unsupported {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  text-align: center; padding: 24px;
}
.viewer-unsupported .unsupported-icon { font-size: 3.2rem; line-height: 1; }
.viewer-unsupported .unsupported-name {
  font-size: 1.1rem; font-weight: 600; color: #f3f4f1; word-break: break-word;
}
.viewer-unsupported .unsupported-type { font-size: 0.9rem; }
.viewer-unsupported a.viewer-retry { text-decoration: none; display: inline-block; }

/* Hidden diagnostics panel (opened by a discreet 5-tap on the status area) */
.diag {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(25, 25, 21, 0.5);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.diag.hidden { display: none; }
.diag-card {
  background: var(--card, #fff); color: var(--ink);
  border: 1px solid var(--line); border-radius: 16px;
  width: 100%; max-width: 460px; max-height: 85vh; overflow: auto;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
  padding: 18px 20px;
}
.diag-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.diag-close {
  background: none; border: 1px solid var(--line); border-radius: 8px;
  width: 36px; height: 36px; font-size: 1rem; color: var(--ink-2); cursor: pointer;
}
.diag-row {
  display: flex; justify-content: space-between; gap: 16px;
  padding: 8px 0; border-bottom: 1px solid var(--line-2); font-size: 0.9rem;
}
.diag-k { color: var(--ink-2); flex: none; }
.diag-v { color: var(--ink); text-align: right; word-break: break-word; }
.diag-v.diag-warn { color: var(--accent); font-weight: 600; }
.diag-actions { margin-top: 14px; display: flex; gap: 10px; }
.diag-btn {
  background: var(--accent); color: #fff; border: none; border-radius: 10px;
  padding: 10px 18px; font-size: 0.92rem; font-weight: 600; min-height: 44px; cursor: pointer;
}
.diag-btn:active { opacity: 0.85; }

/* ===========================================================================
   Field usability + onboarding polish
   - High-contrast / large toggle (persisted per tablet)
   - Pairing QR (offline, self-hosted generator)
   - Orientation resilience (portrait + landscape)
   =========================================================================== */

/* Topbar "large / high-contrast" toggle — a big, one-thumb target. */
.hc-toggle {
  flex: none;
  min-width: 48px; min-height: 48px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.hc-toggle .hc-glyph { font-size: 1.15rem; font-weight: 700; line-height: 1; }
.hc-toggle:active { border-color: var(--accent); color: var(--accent); }
.hc-toggle[aria-pressed="true"] {
  background: var(--ink); color: #fff; border-color: var(--ink);
}

/* Pairing QR — a scannable code so the operator never has to type six digits. */
.pair-qr { display: flex; justify-content: center; margin: 8px auto 2px; }
.pair-qr-svg {
  width: min(58vw, 250px); height: auto;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 8px;
}
.pair-qr-hint { margin: 4px 0 0; }
.pair-retry {
  margin-top: 22px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 22px;
  font-size: 0.95rem; font-weight: 600; font-family: inherit;
  min-height: 48px; cursor: pointer;
}
.pair-retry:active { border-color: var(--accent); color: var(--accent); }
.pair-retry:disabled { opacity: 0.5; }

/* A touch bigger + bolder count in the viewer for arm's-length reading. */
.viewer-count { font-size: 0.95rem; font-weight: 600; }

/* --- High-contrast / large mode (html.hc) --------------------------------- */
/* Deepen the buildings3 palette (no new brand colours) and scale the rem-based
   type ramp up via the root font-size, so type, padding and controls all grow
   together for bright sunlight / older eyes. */
:root.hc {
  --ink:   #000000;
  --ink-2: #2b2a27;
  --ink-3: #3d3b37;
  --ink-4: #6f6d68;
  --line:   rgba(0, 0, 0, 0.30);
  --line-2: rgba(0, 0, 0, 0.18);
  --accent:      #8a0f0a;
  --accent-soft: #f7d3d0;
  --bg:     #ffffff;
  --panel:  #ffffff;
  --panel-2: #ebe9e5;
  /* Controls grow to 56px in this mode, so the top bar (and the tab bar that
     sticks beneath it) grows too. Keep them flush. */
  --topbar-h: calc(69px + max(12px, env(safe-area-inset-top)));
}
html.hc { font-size: 116%; }

:root.hc .crumb { font-weight: 700; }
:root.hc .status { font-size: 0.95rem; color: var(--ink-2); }
:root.hc .icon-btn,
:root.hc .hc-toggle { min-width: 56px; min-height: 56px; border-width: 2px; }
:root.hc .tile { border-width: 2px; min-height: 150px; }
:root.hc .tile .tile-sub { color: var(--ink-2); }
:root.hc .grid-trades { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
:root.hc .grid-files { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
:root.hc .thumb { border-width: 2px; }
:root.hc .thumb .badge {
  font-size: 0.82rem; font-weight: 600;
  background: rgba(255, 255, 255, 0.98);
  color: var(--ink);
}
:root.hc .thumb .pill { font-size: 0.68rem; padding: 4px 9px; }
/* Empty / loading / offline states stay legible with real contrast. */
:root.hc .empty { color: var(--ink-2); font-weight: 600; }
:root.hc .demo-banner { color: #fff; background: var(--accent); }
/* Full-screen viewer chrome — brighter labels, larger, more opaque nav. */
:root.hc .viewer-count { color: #ffffff; font-weight: 700; }
:root.hc .viewer-title { color: #ffffff; }
:root.hc .nav-btn { background: rgba(0, 0, 0, 0.82); width: 64px; height: 96px; }
:root.hc .viewer-bar .icon-btn { border-width: 2px; }

/* --- Orientation resilience ----------------------------------------------- */
/* Short landscape (tablet on its side): keep the pairing card + QR fully on
   screen without scrolling, and lean the code/QR into the available height. */
@media (orientation: landscape) and (max-height: 600px) {
  .pair-card { margin-top: 3vh; padding: 24px 28px; }
  .pair-code { margin: 12px 0; }
  .pair-qr-svg { width: min(34vh, 200px); }
}

/* --- Project Hub: "who are you" + slot cards ------------------------------- */
/* The trade tab bar is gone: a job no longer opens on a tab nobody chose (and
   at 390px only three of its six tabs fitted on screen anyway — the rest were
   off the right edge). The first screen asks instead, one bubble per trade
   stacked down the page, and the shared material is the last bubble so choosing
   is also the way in to the plans and photos.

   Colour: ink and the neutral panel tokens only. Nothing here signals with the
   brand red — a trade you have not picked yet is not a warning. */
.choose-root { max-width: 560px; margin: 0 auto; padding: 8px 2px 40px; }
.choose-head { margin: 6px 4px 20px; }
.choose-eyebrow {
  margin: 0; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.choose-site {
  margin: 4px 0 0; font-size: 1.35rem; font-weight: 600; letter-spacing: -0.02em;
  color: var(--ink); overflow-wrap: anywhere;
}
.choose-ask { margin: 14px 0 0; font-size: 1.05rem; font-weight: 600; color: var(--ink-2); }

.choose-list { display: flex; flex-direction: column; gap: 12px; }
.choose-bubble {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--line);
  /* A bubble, not a filing tab: fully rounded, and tall enough to hit in gloves
     (comfortably past the 44px minimum). */
  border-radius: 999px;
  padding: 14px 22px;
  min-height: 68px;
  cursor: pointer;
}
.choose-bubble:active { background: var(--panel-2); border-color: var(--ink-4); }
.choose-bubble-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.choose-bubble-label { font-size: 1.1rem; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.choose-bubble-hint { margin-top: 2px; font-size: 0.82rem; color: var(--ink-2); overflow-wrap: anywhere; }
.choose-bubble-chev { flex: none; color: var(--ink-3); font-size: 1.3rem; }
/* The shared-material bubble is the one that isn't a trade — set apart by fill,
   not by colour, so it reads as "and everything else" without shouting. */
.choose-bubble.shared { background: var(--panel-2); }
/* Which bubble this device opens by itself. A note, never a lock. */
.choose-bubble.remembered { border-color: var(--ink-4); }
.choose-bubble-mark {
  flex: none;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
}
.choose-bubble.shared .choose-bubble-mark { background: var(--panel); }
.choose-foot { margin: 22px 6px 0; font-size: 0.82rem; color: var(--ink-3); text-align: center; }

/* The way back out of a remembered choice, said in words above the slot list.
   Remembering is only kind if leaving is obvious, so this sits at the top of
   every trade's screen and does the same thing as the topbar back arrow. */
/* Aligned with .slot-list below it — same max-width and side padding, so the
   bar and the cards share one left edge at every width. */
.choice-bar-wrap { max-width: 1160px; margin: 0 auto; padding: 16px 16px 0; }
.choice-bar {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
  background: var(--panel-2);
  border: 1px solid var(--line);
  /* Not a pill: its second line wraps on a narrow phone, and a 999px radius
     around two lines of text reads as a mistake. */
  border-radius: var(--radius);
  padding: 12px 16px;
  min-height: 52px;
  cursor: pointer;
}
.choice-bar:active { background: var(--panel); border-color: var(--ink-4); }
.choice-bar-back { flex: none; font-size: 1.2rem; color: var(--ink-2); }
.choice-bar-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.choice-bar-label { font-size: 0.95rem; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.choice-bar-sub { margin-top: 1px; font-size: 0.78rem; color: var(--ink-2); overflow-wrap: anywhere; }

/* The hub opens to this list of ordered action cards. It's the first thing a
   phone/tablet visitor sees, so it drives the "use more horizontal space" ask:
   a responsive grid that is one readable column on a phone and gains columns on
   a tablet/desktop (2 at ~768px, 3 by ~1100px) rather than a lone 760px column
   stranded in the middle of a wide screen. `min(340px, 100%)` keeps each card a
   comfortable width while guaranteeing no horizontal overflow on a narrow phone
   (the classic auto-fill min-track overflow). Reading order stays row-major, so
   Site Snapshot stays first / top-left. */
.slot-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  align-content: start;
  gap: 12px;
  padding: 16px;
  max-width: 1160px;
  margin: 0 auto;
}
.slot-card {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
  min-height: 64px;
  cursor: pointer;
  text-decoration: none;
}
.slot-card:active { background: var(--panel-2); }
.slot-card .slot-icon { font-size: 1.4rem; flex: none; width: 28px; text-align: center; }
.slot-card .slot-body { flex: 1; min-width: 0; }
.slot-card .slot-label { font-size: 1.02rem; font-weight: 600; color: var(--ink); }
.slot-card .slot-sub { font-size: 0.82rem; color: var(--ink-2); margin-top: 2px; }
.slot-card .slot-chev { color: var(--ink-3); font-size: 1.2rem; flex: none; }
.slot-card.link .slot-chev { color: var(--accent); }

/* Disabled/empty states — deliberately distinct from each other. */
.slot-card.disabled {
  cursor: default;
  background: var(--panel-2);
  border-style: dashed;
  color: var(--ink-3);
}
.slot-card.disabled .slot-label { color: var(--ink-2); font-weight: 600; }
.slot-card.disabled .slot-icon { color: var(--ink-4); }
.slot-card.disabled.placeholder { border-color: var(--warn); }
.slot-card.disabled.placeholder .slot-sub { color: var(--warn); }
.slot-card.disabled.deferred .slot-sub { font-style: italic; }
/* "Not uploaded yet": a slot simply waiting for a file — a clearer sub than the
   faint "Not required on this job", so a fresh project reads as expectant, not
   dismissed. */
.slot-card.disabled.not-uploaded .slot-sub { color: var(--ink-2); }

/* Landing page actions. */
.landing-card { text-align: left; }
.landing-logo { display: block; width: min(260px, 70%); margin: 0 auto 28px; }
.landing-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.landing-btn {
  display: block;
  text-align: center;
  text-decoration: none;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 12px;
  padding: 14px 18px;
  font-weight: 600;
}
.landing-btn.secondary { background: var(--panel); color: var(--ink); border-color: var(--line); }

/* High-contrast / large mode. */
:root.hc .choose-bubble { min-height: 76px; }
:root.hc .choose-bubble-label { font-size: 1.2rem; }
:root.hc .choose-bubble-hint { font-size: 0.95rem; }
:root.hc .choice-bar { min-height: 60px; }
:root.hc .choice-bar-label { font-size: 1.05rem; }
:root.hc .slot-card { border-width: 2px; min-height: 72px; }
:root.hc .slot-card .slot-label { font-size: 1.1rem; }

/* The Site Snapshot door-card slot reads as an action (not a file). */
.slot-card.doorcard .slot-chev { color: var(--accent); }

/* Inline .docx viewer (docx-preview): pages scroll on the dark stage. */
.viewer-stage .docx-scroll {
  position: absolute; inset: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y pinch-zoom;
}
.viewer-stage .docx-scroll .docx-wrapper { background: transparent; padding: 12px 0; }

/* --- Site Snapshot door card (/d/<token>) --------------------------------- */
/* App-rendered, print-friendly. On screen it's a warm card; when printed it
   fills a sheet cleanly (see @media print below) as the physical door card. */
.dc-print-btn {
  appearance: none;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px; /* thumb-sized tap target, was ~37px */
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 9px 14px;
  border-radius: 10px;
  cursor: pointer;
  margin-left: auto;
}
.dc-print-btn:active { opacity: 0.85; }

.doorcard-sheet {
  display: block;
  max-width: 760px;
  margin: 16px auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px 30px 22px;
}
.dc-head { border-bottom: 2px solid var(--ink); padding-bottom: 14px; margin-bottom: 18px; }
.dc-logo { display: block; height: 26px; width: auto; margin-bottom: 12px; }
.dc-kicker {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--accent);
}
.dc-siteid { margin: 6px 0 2px; font-size: 1.9rem; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); }
.dc-building { font-size: 1rem; color: var(--ink-2); }

.dc-sec-title {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-2); margin: 20px 0 8px;
}
.dc-contacts, .dc-section { break-inside: avoid; }
.dc-contact { display: flex; justify-content: space-between; gap: 16px; padding: 7px 0; border-bottom: 1px solid var(--line-2); }
.dc-contact:last-child { border-bottom: none; }
.dc-role { color: var(--ink-2); font-size: 0.92rem; }
.dc-contact-val { text-align: right; }
.dc-name { font-weight: 600; color: var(--ink); }
.dc-mobile {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 44px; /* thumb-sized tap target for the tel: link, was ~20px */
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
  font-size: 1.05rem;
}

.dc-rows { margin: 0; display: grid; grid-template-columns: minmax(140px, 34%) 1fr; gap: 0; }
.dc-label { color: var(--ink-2); font-size: 0.92rem; padding: 7px 12px 7px 0; border-bottom: 1px solid var(--line-2); }
.dc-value { color: var(--ink); font-size: 0.98rem; padding: 7px 0; border-bottom: 1px solid var(--line-2); white-space: pre-line; margin: 0; }
.dc-rows dt:last-of-type, .dc-rows dd:last-of-type { border-bottom: none; }

.dc-photos-link { color: var(--accent); font-weight: 600; text-decoration: none; }
.dc-empty { color: var(--ink-3); font-style: italic; padding: 12px 0; }
.dc-foot { margin-top: 22px; padding-top: 12px; border-top: 1px solid var(--line); color: var(--ink-3); font-size: 0.76rem; }

/* Site photos (against each field, keyless upload) — grouped like the text
   sections above. Always rendered (even zero-photo fields, so a contractor
   can attach the first one), print collapses fields with nothing to show. */
.dc-photo-section { break-inside: avoid; }
.dc-photofield { padding: 10px 0; border-bottom: 1px solid var(--line-2); break-inside: avoid; }
.dc-photofield:last-child { border-bottom: none; }
.dc-photofield-label { padding: 0 0 6px; border: none; }

@media print {
  .dc-photofield-empty { display: none; }
  .vr-photo-add, .vr-photo-remove { display: none !important; }
  /* A group with nothing but empty fields would otherwise print a bare
     heading — collapse it too. Degrades gracefully (heading-only) on a
     browser without :has() support. */
  .dc-photo-section:not(:has(.dc-photofield:not(.dc-photofield-empty))) { display: none; }
}

:root.hc .dc-siteid { font-size: 2.1rem; }
:root.hc .dc-value, :root.hc .dc-name { font-weight: 700; }

/* Phone door card: a fixed 140px label column leaves the value barely ~150px on
   a 360–430px screen (values like "Loading-bay door on Tyler Lane (keypad …)"
   wrap to 3–4 lines). Below 440px, stack the label above the value so each gets
   the full width; tighten the sheet padding to reclaim edge space. */
@media (max-width: 440px) {
  .doorcard-sheet { padding: 22px 18px 18px; }
  .dc-rows { grid-template-columns: 1fr; }
  .dc-label {
    padding: 8px 0 0;
    border-bottom: none;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.02em;
  }
  .dc-value { padding: 2px 0 10px; }
}

@media print {
  /* Print cleanly as the physical door card: drop chrome, flatten the sheet. */
  html, body { background: #fff; height: auto; }
  .dc-noprint, .topbar, .toast, .updnotice { display: none !important; }
  .main { padding: 0; }
  .doorcard-sheet { margin: 0; max-width: none; border: none; border-radius: 0; padding: 0; }
  .dc-mobile, .dc-photos-link, .dc-kicker { color: #000; }
  .dc-head { border-bottom-color: #000; }
  a { text-decoration: none; }
}
:root.hc .slot-card.disabled .slot-sub { color: var(--ink-2); }

/* ============================================================================
   Update progress — the contractor loop. Phone-first, big thumb-sized tick
   targets, three taps. Shared by the personal page (/u/<token>) and the in-hub
   pick-your-name flow. Uses the buildings3 tokens defined at the top.
   ============================================================================ */
.pg-root { max-width: 640px; margin: 0 auto; padding: 4px 2px 40px; }
.pg-head { margin: 6px 2px 18px; }
.pg-title { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.02em; margin: 0; color: var(--ink); }
.pg-sub { margin: 4px 0 0; color: var(--ink-2); font-size: 0.95rem; }
.pg-empty { text-align: center; color: var(--ink-3); margin-top: 40px; font-size: 1.05rem; padding: 0 16px; }

/* pick-your-name list */
.pg-picker { display: flex; flex-direction: column; gap: 12px; }
.pg-pick-btn {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  width: 100%; text-align: left; font-family: inherit; cursor: pointer;
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 20px 18px; min-height: 64px; color: var(--ink);
}
.pg-pick-btn:active { border-color: var(--accent); background: var(--accent-soft); }
.pg-pick-name { font-size: 1.15rem; font-weight: 600; letter-spacing: -0.01em; }
.pg-pick-trade { font-size: 0.85rem; color: var(--ink-2); text-transform: capitalize; }

/* task cards */
.pg-job-title { font-size: 0.82rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2); margin: 22px 2px 8px; }
.pg-task {
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 16px 16px 14px; margin-bottom: 14px;
}
.pg-task.busy { opacity: 0.6; }
/* Deep-linked task from a 7pm/7am nudge (#task=…) — draw the eye to it. */
.pg-task-focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.pg-task-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.pg-task-name { font-size: 1.1rem; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
.pg-task-status { flex: none; text-align: right; }
.pg-badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 0.72rem; font-weight: 600; }
.pg-badge-none { background: var(--panel-2); color: var(--ink-3); }
.pg-badge-set { background: var(--good-soft, #e3f3ea); color: var(--good); }
.pg-logged { display: block; font-size: 0.7rem; color: var(--ink-3); margin-top: 3px; }
/* the personal page's upcoming list: the job + planned dates under a name,
   Today's tickable cards first, then each later day's view-only cards */
.pg-task-head { min-width: 0; }
.pg-task-name { overflow-wrap: anywhere; }
.pg-task-site { margin-top: 3px; font-size: 0.85rem; font-weight: 600; color: var(--ink-2); overflow-wrap: anywhere; }
.pg-task-when { margin-top: 2px; font-size: 0.85rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.pg-day-title { font-size: 0.82rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2); margin: 20px 2px 8px; }
.pg-day-today > .pg-day-title { color: var(--ink); }
.pg-day-empty { margin: 0 2px 14px; color: var(--ink-3); }
.pg-coming { margin-top: 30px; padding-top: 4px; border-top: 1px solid var(--line); }
.pg-coming-title { font-size: 1.15rem; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); margin: 18px 2px 0; }
.pg-later {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; margin-bottom: 8px;
}
.pg-later .pg-task-name { font-size: 1rem; }
.pg-horizon { margin: 18px 2px 0; font-size: 0.82rem; color: var(--ink-3); }

/* the tick options — big targets, wrap to a grid on a phone */
.pg-ticks { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; }
.pg-tick {
  appearance: none; font-family: inherit; cursor: pointer;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel-2);
  color: var(--ink); font-size: 1rem; font-weight: 600; padding: 16px 8px; min-height: 56px;
}
.pg-tick:active { border-color: var(--accent); }
/* Default "current" highlight (25/50/75% progress, and No work today) reads as
   under-way/neutral, never as an alarm — red is reserved for Blocked below. */
.pg-tick.current { border-color: var(--info); background: var(--info-soft); color: var(--info); }
.pg-tick[disabled] { opacity: 0.5; cursor: default; }
/* the two non-% outcomes read differently from a low % */
.pg-tick-blocked { color: var(--warn); }
.pg-tick-blocked.current { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
.pg-tick-no_work { color: var(--ink-2); }
.pg-tick-complete.current { background: var(--good-soft, #e3f3ea); border-color: var(--good); color: var(--good); }

/* the "Thanks — logged …" confirmation on the card */
.pg-confirm { margin-top: 12px; padding: 10px 12px; border-radius: 10px; font-size: 0.9rem; font-weight: 600; }
.pg-confirm.ok { background: var(--good-soft, #e3f3ea); color: var(--good); }
.pg-confirm.err { background: var(--accent-soft); color: var(--accent); }
/* "How many more days?" — the one extra step on a task's last planned day */
.pg-more { margin-top: 12px; }
.pg-more-q { margin: 0; font-weight: 700; }
/* "Done - undo?" — the 10 minutes after a Complete, before any date moves */
.pg-undo {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 12px;
  margin-top: 12px; padding: 10px 12px; border-radius: 12px;
  background: var(--good-soft, #e3f3ea); border: 1px solid var(--good);
}
.pg-undo-q { margin: 0; font-weight: 700; color: var(--good); }
.pg-undo-btn { grid-row: span 2; min-width: 96px; background: var(--panel); }
.pg-undo-note { margin: 0; font-size: 0.8rem; color: var(--ink-2); }
/* "Ready for you" — at the top of a contractor's own link */
.pg-notices { display: flex; flex-direction: column; gap: 8px; margin: 0 0 16px; }
.pg-notice {
  padding: 12px 14px; border-radius: 12px; background: var(--info-soft);
  border: 1px solid var(--info); border-left-width: 4px;
}
.pg-notice-title { margin: 0; font-weight: 700; color: var(--info); font-size: 1.02rem; }
.pg-notice-sub { margin: 3px 0 0; color: var(--ink-2); font-size: 0.9rem; word-break: break-word; }

/* slot card accents for the app-native progress slot */
.slot-card.progress .slot-icon { color: var(--good); }

/* quiet preview of the contractor's own reminder texts — context, not the main
   event, so small/muted and collapsed by default (native <details>). */
.pg-nudge-preview { margin-top: 18px; padding: 10px 12px; border-radius: 10px; background: var(--panel-2); color: var(--ink-2); font-size: 0.82rem; }
.pg-nudge-preview summary { cursor: pointer; font-weight: 600; color: var(--ink-3); }
.pg-nudge-preview p { margin: 8px 0 0; line-height: 1.4; word-break: break-word; white-space: pre-line; }
.pg-nudge-preview strong { color: var(--ink-2); }

/* large / high-contrast mode bumps the tap targets further */
:root.hc .pg-tick { font-size: 1.15rem; min-height: 64px; }
:root.hc .pg-task-name, :root.hc .pg-pick-name { font-weight: 700; }

/* ============================================================================
   Project Tasks — read-only GanttPRO task list (progress % + assignees). Same
   card language as Update progress, minus any tick controls.
   ============================================================================ */
.pt-root { max-width: 640px; margin: 0 auto; padding: 4px 2px 40px; }
.pt-head { margin: 6px 2px 18px; }
.pt-title { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.02em; margin: 0; color: var(--ink); }
.pt-sub { margin: 4px 0 0; color: var(--ink-2); font-size: 0.95rem; }
.pt-empty { text-align: center; color: var(--ink-3); margin-top: 40px; font-size: 1.05rem; padding: 0 16px; }

.pt-list { display: flex; flex-direction: column; gap: 14px; }
.pt-task {
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 16px 16px 14px;
}
.pt-task-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.pt-task-name { font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
.pt-task-pct { flex: none; font-size: 0.95rem; font-weight: 700; color: var(--ink-2); }

.pt-bar { margin-top: 10px; height: 8px; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
/* In-progress reads as neutral/under-way (--info), never alarm-red; complete
   turns green — the two must stay obviously distinct at a glance. */
.pt-bar-fill { height: 100%; border-radius: 999px; background: var(--info); }
.pt-bar-fill.done { background: var(--good); }

.pt-task-assignee { margin-top: 8px; font-size: 0.85rem; color: var(--ink-2); }
/* When a task is due. Sits directly under the name because it is the second
   thing a contractor needs after "what" — ink, never a status colour: a date is
   information, not an alarm. */
.pt-task-date { margin-top: 4px; font-size: 0.88rem; font-weight: 600; color: var(--ink-2); }
.pt-task-date.none { font-weight: 400; color: var(--ink-3); }

/* An empty list says WHICH empty it is. */
.pt-empty-box { margin-top: 36px; text-align: center; padding: 0 16px; }
.pt-empty-box .pt-empty { margin-top: 0; }
.pt-empty-sub { margin: 8px 0 0; color: var(--ink-3); font-size: 0.9rem; }

/* slot card accents for the app-native tasks slot */
.slot-card.tasks .slot-icon { color: var(--accent); }

:root.hc .pt-task-name { font-weight: 700; }

/* ============================================================================
   Variation Request — native, submission-only capture form. A field-for-field
   replica of the external variation.eeq.co.nz /new form (see
   data/tv-variation-capture/report.md), rendered in the buildings3 skin.
   ============================================================================ */
.vr-root { max-width: 640px; margin: 0 auto; padding: 4px 2px 60px; }
.vr-head { margin: 6px 2px 18px; }
.vr-title { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.02em; margin: 0; color: var(--ink); }
.vr-sub { margin: 4px 0 0; color: var(--ink-2); font-size: 0.95rem; }

.vr-form { display: flex; flex-direction: column; gap: 16px; }
.vr-section {
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 18px 16px; display: flex; flex-direction: column; gap: 14px;
}
.vr-section.hidden { display: none; }
.vr-section-title {
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--accent); margin: 0; border-bottom: 1px solid var(--line-2); padding-bottom: 8px;
}
.vr-prm-block .vr-section-title { color: var(--warn); }

.vr-field { display: flex; flex-direction: column; gap: 4px; }
.vr-label { font-size: 0.85rem; font-weight: 700; color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.02em; }
.vr-hint { margin: 0; font-size: 0.78rem; color: var(--ink-3); }

.vr-input {
  width: 100%; box-sizing: border-box; font-family: inherit; font-size: 1rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; color: var(--ink); min-height: 48px;
}
.vr-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.vr-textarea { min-height: 88px; resize: vertical; }
select.vr-input { appearance: auto; }

.vr-toggle-group { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.vr-toggle {
  appearance: none; font-family: inherit; cursor: pointer;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2);
  color: var(--ink); font-size: 0.92rem; font-weight: 600; padding: 12px 6px; min-height: 44px;
}
.vr-toggle.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.vr-file-input { font-size: 0.9rem; }
.vr-attach-list { display: flex; flex-direction: column; gap: 6px; }
.vr-attach-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--panel-2); border-radius: 8px; padding: 8px 10px; font-size: 0.85rem;
}
.vr-attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vr-attach-remove {
  appearance: none; border: none; background: none; color: var(--accent);
  font-size: 0.8rem; font-weight: 600; cursor: pointer; flex: none;
}
.vr-attach-err { color: var(--accent); font-size: 0.82rem; margin: 0; }

/* Site Snapshot photo capture (per-field camera evidence, see snapshotphotos.js).
   Uploading is a neutral/in-progress state (--info, not --accent) — matches the
   tick-state color contract (red is reserved for an actual error/press-feedback,
   never a benign "still working" state). */
.vr-photos { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px; margin-top: 6px; }
.vr-photo-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.vr-photo-thumb {
  position: relative; width: 64px; height: 64px; border-radius: 10px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--line); flex: none;
}
.vr-photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A saved photo's thumbnail is a tap target that opens it full-size (see
   photolightbox.js) — reset button chrome so it fills the thumb exactly like
   the bare <img> did before. */
.vr-photo-view {
  display: block; width: 100%; height: 100%; padding: 0; margin: 0; border: none;
  background: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
/* `.viewer-stage`'s `touch-action: none` exists so viewer.js can drive its own
   pointer-based pinch/pan. The photo lightbox ships none of that machinery, so
   it must hand pinch-zoom back to the browser — otherwise a photo is capped at
   fit-to-screen and a door number inside it can never be magnified. */
#photoLightbox .viewer-stage { touch-action: auto; }
.vr-photo-remove {
  position: absolute; top: 2px; right: 2px; appearance: none; border: none; cursor: pointer;
  width: 22px; height: 22px; border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff;
  font-size: 0.9rem; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.vr-photo-thumb-pending { display: flex; flex-direction: column; }
.vr-photo-thumb-pending img { opacity: 0.5; }
.vr-photo-status {
  position: absolute; inset: 0; margin: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; font-size: 0.68rem; font-weight: 700; color: var(--info); background: var(--info-soft);
}
.vr-photo-thumb-err {
  width: auto; min-width: 120px; height: auto; min-height: 64px; padding: 6px 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-color: var(--accent);
}
.vr-photo-err { color: var(--accent); font-size: 0.76rem; margin: 0; text-align: center; }
/* A failed DELETE on an EXISTING saved photo (see snapshotphotos.js) is a
   different situation from vr-photo-thumb-err above: that class is a
   text-only upload-error chip with nothing inside to constrain, so letting it
   grow (width/height: auto) is fine. This tile still holds a full photo — a
   Site Snapshot photo can be a 3024x4032 original — so the fixed 64px square
   must never be relaxed, or the photo lays out at its natural size and blows
   the tile past its space with no way back except a reload. Dim the photo and
   show a small corner dot instead of resizing anything; the remove button
   (top-right) stays visible and clickable to retry. */
.vr-photo-thumb-delete-err { border-color: var(--accent); }
.vr-photo-thumb-delete-err .vr-photo-view { opacity: 0.5; }
.vr-photo-thumb-delete-err::before {
  content: ''; position: absolute; bottom: 2px; left: 2px; z-index: 1;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px var(--panel);
}
.vr-photo-retry {
  appearance: none; border: 1px solid var(--accent); background: none; color: var(--accent);
  font-size: 0.76rem; font-weight: 700; border-radius: 8px; padding: 4px 10px; cursor: pointer; min-height: 30px;
}
.vr-photo-add {
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  min-height: 44px; min-width: 44px; padding: 0 12px; border-radius: 10px;
  border: 1px dashed var(--line-2); color: var(--ink-2); font-size: 0.85rem; font-weight: 600;
}
.vr-photo-add:active { background: var(--panel-2); }
.vr-photo-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* A PDF (or any non-image) preview shows a plain name chip instead of an
   image thumb — an <img> can't render PDF bytes. Auto-sized like the error
   tile above, not the fixed 64px image square. See snapshotphotos.js
   appendPreview (compliance document capture, kind 'docupload'). */
.vr-photo-thumb:has(.vr-photo-doc-icon) {
  width: auto; height: auto; min-width: 96px; padding: 10px 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.vr-photo-doc-icon { font-size: 1.6rem; line-height: 1; }
.vr-photo-doc-name {
  margin: 0; font-size: 0.68rem; color: var(--ink-2); text-align: center;
  max-width: 110px; overflow-wrap: break-word; word-break: break-word;
}
.vr-photo-thumb-pending .vr-photo-doc-icon { opacity: 0.5; }

:root.hc .vr-photo-thumb { width: 76px; height: 76px; }
:root.hc .vr-photo-add { min-height: 52px; font-size: 0.95rem; }

.vr-form-err {
  background: var(--accent-soft); color: var(--accent); border-radius: 10px;
  padding: 10px 12px; font-size: 0.9rem; font-weight: 600; margin: 0;
}
.vr-submit {
  appearance: none; font-family: inherit; cursor: pointer; border: none;
  background: var(--accent); color: #fff; font-size: 1.05rem; font-weight: 700;
  border-radius: 12px; padding: 16px; min-height: 52px;
}
.vr-submit:active { opacity: 0.85; }
.vr-submit[disabled] { opacity: 0.6; cursor: default; }

.vr-confirm-screen { text-align: center; padding: 60px 20px; }
.vr-confirm-icon { font-size: 2.4rem; margin-bottom: 10px; }

/* slot card accent for the app-native variation slot */
.slot-card.variation .slot-icon { color: var(--warn); }

:root.hc .vr-input { font-size: 1.08rem; min-height: 54px; }
:root.hc .vr-toggle { font-size: 1rem; min-height: 52px; }
:root.hc .vr-submit { font-size: 1.15rem; min-height: 60px; }

/* ============================================================================
   Photo Upload — browse the job's site photos stage by stage, and add to a
   stage. The thumbnail cells are the shared .thumb ones from the files grid
   (ui.js fileCell) and the capture control reuses the Site Snapshot
   .vr-photo-* classes verbatim (renderPhotoControl is the SAME shared
   function, see stagephotos.js) — only the per-stage chrome below is new.
   ============================================================================ */
/* Wider than a reading column: this page is a photo grid, so it follows the
   files grid's bound rather than the 640px prose one it used as a picker. */
.sg-root { max-width: 1600px; margin: 0 auto; padding: 4px 2px 60px; }
.sg-empty { text-align: center; color: var(--ink-3); margin-top: 40px; font-size: 1.05rem; padding: 0 16px; }
/* The Shared tab's view-only sets: an explanation, not a warning — ink, no colour. */
.sg-viewonly { color: var(--ink-2); margin: 4px 0 12px; font-size: 1rem; }

.sg-stage { margin: 0 0 30px; }
.sg-stage-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 0 2px 6px; margin-bottom: 12px; border-bottom: 1px solid var(--line);
}
.sg-stage-name { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; color: var(--ink); }
.sg-stage-count { font-size: 0.8rem; color: var(--ink-3); white-space: nowrap; }
/* One big preview per row on a phone (a 360px screen gives a ~328px tile), two
   from ~430px, three from ~644px. Bigger than the Reference-tab grid on
   purpose: the point of this screen is recognising the subject WITHOUT opening
   it, and at 150px one bare concrete floor looks like the next. The tile still
   draws the shared 400px `/thumb/` asset — see public/js/ui.js fileCell. */
.sg-grid {
  display: grid; gap: 14px; margin-bottom: 14px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* The grid is always in the DOM so a photo taken on this screen has somewhere
   to join without a rebuild; with nothing in it, it draws nothing. */
.sg-grid:empty { display: none; }

/* slot card accent for the app-native stage photo slot */
.slot-card.stagephoto .slot-icon { color: var(--info); }
/* A set with photos shows its first one in place of the icon (slotcards.js),
   so a full set and an empty one differ before anyone reads the count. */
.slot-card .slot-icon.slot-thumb { width: 40px; height: 40px; margin: -6px -4px -6px -8px; } /* lays out as the 28px icon, so labels stay aligned */
.slot-card .slot-icon.slot-thumb img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; border-radius: 6px;
}

/* slot card accent for the app-native compliance document upload slot */
.slot-card.docupload .slot-icon { color: var(--good); }

/* Comments under each defect photo (stagephotos.js commentThread). */
.pc-item { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.pc-thread { display: flex; flex-direction: column; gap: 8px; }
.pc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.pc-comment { background: var(--panel-2); border-radius: 8px; padding: 8px 10px; }
.pc-meta { margin: 0 0 2px; font-size: 0.78rem; font-weight: 700; color: var(--ink-2); }
.pc-body { margin: 0; font-size: 0.95rem; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.pc-none { margin: 0; font-size: 0.85rem; color: var(--ink-3); }
/* An empty commentable set says where comments will appear (stagephotos.js). */
.sg-comment-hint { margin: 0 2px 12px; font-size: 0.95rem; color: var(--ink-2); }
.pc-form { display: flex; flex-direction: column; gap: 6px; }
.pc-send {
  appearance: none; font-family: inherit; cursor: pointer; min-height: 44px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  color: var(--ink); font-size: 0.95rem; font-weight: 600;
}
.pc-send:disabled { opacity: 0.6; cursor: default; }
.pc-err { margin: 0; font-size: 0.82rem; color: var(--accent); }
.pc-err:empty { display: none; }

/* The X on a Defects photo, its confirm, and a refusal (stagephotos.js
   deletableTile). The X sits ON the photo so it is plainly this photo's (top
   LEFT: the top right carries the thumbnail's own "NEW" pill); the
   confirm opens under it so the photo stays in view while deciding. A refused
   delete is written INSIDE the tile, over the name strip, and the photo stays. */
.sg-photo-wrap { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.sg-photo { position: relative; }
.sg-delete {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  appearance: none; border: none; cursor: pointer; padding: 0;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(0, 0, 0, 0.6); color: #fff;
  font-family: inherit; font-size: 1.6rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.sg-delete:focus-visible { outline: 3px solid var(--info); outline-offset: 2px; }
.sg-delete:disabled { opacity: 0.45; cursor: default; }
.sg-delete-failed .thumb { border-color: var(--accent); border-width: 2px; }
.sg-delete-err {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; margin: 0;
  padding: 8px 10px; background: var(--panel); border-top: 2px solid var(--accent);
  border-radius: 0 0 var(--radius) var(--radius);
  color: var(--accent); font-size: 0.82rem; font-weight: 600; line-height: 1.3;
}
.sg-delete-err:empty { display: none; }
.sg-confirm {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 12px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
.sg-confirm[hidden] { display: none; }
.sg-confirm-text { margin: 0; font-size: 0.95rem; color: var(--ink); }
.sg-confirm-comments { color: var(--ink-2); }
.sg-confirm-comments:empty { display: none; }
.sg-confirm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.sg-confirm-delete, .sg-confirm-keep {
  appearance: none; font-family: inherit; cursor: pointer; min-height: 44px;
  padding: 0 16px; border-radius: 10px; font-size: 0.95rem; font-weight: 600;
}
.sg-confirm-delete { border: 1px solid var(--ink); background: var(--ink); color: var(--panel); }
.sg-confirm-keep { border: 1px solid var(--line); background: var(--panel); color: var(--ink); }
.sg-confirm-delete:disabled, .sg-confirm-keep:disabled { opacity: 0.6; cursor: default; }
:root.hc .sg-delete { background: #000; }

:root.hc .sg-stage-name { font-size: 1.2rem; }
:root.hc .sg-stage-count { font-size: 1rem; }

/* ===========================================================================
   Site Safety — the native acknowledgment form.
   The long acknowledgment list (.fq-ack) is the reusable piece: the PS3 has
   the same shape with four times as many rows, so it has to work collapsed.
   =========================================================================== */
.ss-root { padding: 14px 16px 40px; max-width: 720px; margin: 0 auto; }
.ss-title { font-size: 20px; line-height: 1.25; margin: 4px 0 6px; }
.ss-desc { color: var(--ink-2); margin: 0 0 14px; }
.ss-loading { padding: 24px 0; color: var(--ink-2); }

.ss-state {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; margin-bottom: 16px; background: var(--panel);
}
.ss-state.ss-none { color: var(--ink-2); }
.ss-state.ss-signed { border-color: var(--good); background: #f1f9f4; }
.ss-filenote { margin: 6px 0 0; color: var(--ink-2); font-size: 13px; }

/* --- the SHARED tab's Site Safety: the whole form, read ---------------------
   View only (the captain, 2026-09-23), and no roster of who signed. The
   explanation is INK, not a warning colour — the same treatment as the
   view-only photo sets (.sg-viewonly): this is how it works, not something that
   went wrong. Every matrix row is laid open: there is nothing to tick, so
   nothing earns a collapse. */
.ss-viewonly { color: var(--ink-2); margin: 4px 0 16px; font-size: 1rem; }
.ss-subtitle { font-size: 16px; margin: 22px 0 8px; }
.ss-doc { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 16px; }
.ss-doc-q::marker { font-weight: 600; }
.ss-doc-q > .fq-label { margin-bottom: 8px; }
.ss-doc-rows {
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel); overflow: hidden;
}
.ss-doc-row { padding: 12px 14px; }
.ss-doc-row + .ss-doc-row { border-top: 1px solid var(--line-2); }
.ss-doc-activity { margin: 0 0 8px; font-weight: 600; line-height: 1.35; }
.ss-doc-row .fq-ackline:last-child { margin-bottom: 0; }
.ss-doc-choices { margin: 0; padding-left: 20px; line-height: 1.45; }
.ss-doc-confirm { margin: 0; line-height: 1.45; }
.ss-hazards { display: flex; flex-direction: column; gap: 10px; }
.ss-hazard {
  margin: 0; border: 1px solid var(--line); border-radius: 12px; padding: 11px 14px;
  background: var(--panel); line-height: 1.45;
}
.ss-hazard-none { color: var(--ink-2); margin: 0; }

.ss-questions { display: flex; flex-direction: column; gap: 18px; }
.ss-actions { margin-top: 22px; }
.ss-submit {
  width: 100%; min-height: 52px; font-size: 16px; font-weight: 600;
  border: 0; border-radius: 12px; background: var(--ink); color: #fff; cursor: pointer;
}
.ss-submit:disabled { opacity: .55; cursor: default; }
.ss-msg { margin-top: 12px; padding: 10px 12px; border-radius: 10px; background: var(--panel-2); }
.ss-msg.error { background: var(--warn-soft); color: var(--warn); }

/* --- council form: the filing note, then working / done / failed + preview --- */
.ss-state.ss-unfiled { border: 2px solid var(--warn); background: var(--warn-soft); }
.ss-state.ss-unfiled strong { color: var(--warn); }
.cf-filing-note { margin: 22px 0 0; color: var(--ink-2); line-height: 1.45; }
.cf-filing-note + .ss-actions { margin-top: 10px; }
.cf-result {
  margin-top: 16px; padding: 14px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--panel); line-height: 1.45;
}
.cf-result.hidden { display: none; }
.cf-result p { margin: 8px 0 0; }
.cf-result-head { display: block; font-size: 17px; }
.cf-small { font-size: 13px; color: var(--ink-2); }
.cf-result.cf-working { border-color: var(--info); background: var(--info-soft); }
.cf-result.cf-check { border: 2px solid var(--info); }
.cf-result.cf-check .cf-result-head { color: var(--info); }
.cf-result.cf-done { border-color: var(--good); }
.cf-result.cf-done .cf-result-head { color: var(--good); }
/* Loud on purpose: a failed filing must never pass for a filed certificate. */
.cf-result.cf-failed { border: 3px solid var(--warn); background: var(--warn-soft); }
.cf-result.cf-failed .cf-result-head { color: var(--warn); font-size: 22px; letter-spacing: .02em; }
.cf-btn {
  display: block; width: 100%; min-height: 48px; margin-top: 12px;
  font: inherit; font-weight: 600; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--ink); background: var(--panel); color: var(--ink);
}
.cf-btn-primary { background: var(--ink); color: #fff; }
.cf-preview { margin-top: 12px; background: var(--panel-2); border-radius: 10px; padding: 8px; }
.cf-preview-note { margin: 6px 0; color: var(--ink-2); }
.cf-preview .pdf-scroll { touch-action: pan-x pan-y pinch-zoom; }
.cf-preview .pdf-page { position: relative; max-width: 100%; margin: 0 auto 8px; }
.cf-preview .pdf-page canvas { display: block; width: 100%; background: #fff; box-shadow: 0 1px 6px rgba(0, 0, 0, .2); }
.cf-preview .pdf-links { position: absolute; inset: 0; overflow: hidden; }
.cf-preview .pdf-link { position: absolute; display: block; }

/* --- shared question controls (formrender.js) --- */
.fq { display: flex; flex-direction: column; gap: 8px; }
.fq.hidden { display: none; }
.fq-label { font-weight: 600; line-height: 1.35; white-space: pre-line; }
.fq input[type="text"], .fq input[type="date"], .fq textarea {
  width: 100%; box-sizing: border-box; min-height: 48px;
  padding: 12px 14px; font: inherit; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
}
.fq textarea { min-height: 96px; resize: vertical; }
.fq-missing .fq-label, .fq-missing > .fq-label { color: var(--accent); }
.fq-missing input[type="text"], .fq-missing textarea,
.fq-missing .fq-acklist, .fq-missing .fq-confirmrow, .fq-missing .sigpad-box { border-color: var(--accent); }

/* A council form's drawn signature (signaturepad.js). 3:1, the pad's own
   shape, so a stroke lands where the finger is. No scrolling while drawing. */
.sigpad { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.sigpad-box {
  position: relative; width: 100%; aspect-ratio: 3 / 1;
  background: #fff; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
.sigpad-canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; }
.sigpad-hint {
  position: absolute; left: 14px; bottom: 10px; color: var(--ink-2); pointer-events: none;
}
.sigpad-clear {
  min-height: 44px; padding: 10px 14px; font: inherit; color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
}

.fq-choicelist { display: flex; flex-direction: column; gap: 8px; }
.fq-choicerow, .fq-confirmrow {
  display: flex; align-items: flex-start; gap: 12px; min-height: 48px;
  padding: 12px 14px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer;
}
.fq-choicerow input, .fq-confirmrow input { width: 22px; height: 22px; margin: 1px 0 0; flex: none; }
.fq-choicetext, .fq-confirmtext { line-height: 1.4; }

/* --- the long acknowledgment list --- */
.fq-ackcount { color: var(--ink-2); font-size: 13px; }
.fq-ackcount.all-done { color: var(--good); font-weight: 600; }
.fq-acklist {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--panel);
}
.fq-ackrow + .fq-ackrow { border-top: 1px solid var(--line-2); }
.fq-ackinput { position: absolute; opacity: 0; pointer-events: none; }
/* Collapsed, a row is ONE LINE — which is what lets seven fit on a phone and
   thirty scroll sanely. */
.fq-ackhead {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 52px; padding: 10px 14px; text-align: left;
  font: inherit; color: var(--ink); background: none; border: 0; cursor: pointer;
}
.fq-ackmark {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  background: var(--panel-2); display: grid; place-items: center;
  font-size: 13px; font-weight: 600; line-height: 1; color: var(--ink-2);
}
.fq-ackrow.acknowledged .fq-ackmark { background: var(--good); color: #fff; }
.fq-ackactivity { flex: 1; line-height: 1.35; }
.fq-ackchev { flex: none; color: var(--ink-2); transition: transform .15s ease; }
.fq-ackrow.open .fq-ackchev { transform: rotate(90deg); }
.fq-ackbody { display: none; padding: 0 14px 14px 46px; }
.fq-ackrow.open .fq-ackbody { display: block; }
.fq-ackline { margin: 0 0 10px; line-height: 1.45; }
.fq-acklabel {
  display: block; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-2); margin-bottom: 2px;
}
.fq-acktick {
  width: 100%; min-height: 48px; font: inherit; font-weight: 600;
  color: #fff; background: var(--ink); border: 0; border-radius: 10px; cursor: pointer;
}
.fq-acktick.done { background: var(--panel-2); color: var(--ink-2); font-weight: 400; }

@media (max-width: 400px) {
  .ss-root { padding: 12px 12px 40px; }
  .fq-ackbody { padding-left: 14px; }
}
