/* =========================================================================
   Guide 2D — visual system
   Rules held throughout:
     - large type, high contrast, minimal chrome (readable under glare,
       on a cheap tablet, and legible if the same layout is ever pushed
       to a HUD/glasses form factor)
     - colour is signal only. Exactly two signal colours:
         --accent   = "you are here" / active navigation
         --critical = safety-critical, nothing else
     - nothing decorative, nothing animated for its own sake
   ========================================================================= */

:root {
  --bg:        #eceef1;
  --surface:   #ffffff;
  --surface-2: #f4f5f7;
  --ink:       #14171c;
  --ink-2:     #4a515c;
  --line:      #c9ced6;
  --line-2:    #e2e5ea;
  --accent:    #0b5cd5;
  --accent-bg: #dbe8fd;
  --critical:  #b3261e;
  --critical-bg:#fbe4e2;
  --done:      #1a6b3c;
  --shadow:    0 1px 2px rgba(0,0,0,.14), 0 6px 18px rgba(0,0,0,.10);

  --topbar-h: 58px;
  --locbar-h: 52px;
  --sidebar-w: 340px;

  --fs-base: 17px;
  --radius: 6px;
}

html[data-theme="dark"] {
  --bg:        #0d1013;
  --surface:   #171b20;
  --surface-2: #1f242b;
  --ink:       #f2f4f7;
  --ink-2:     #aab3bf;
  --line:      #39414b;
  --line-2:    #262c34;
  --accent:    #6ea8ff;
  --accent-bg: #12304f;
  --critical:  #ff8a80;
  --critical-bg:#4a1512;
  --done:      #6cd39a;
  --shadow:    0 1px 2px rgba(0,0,0,.5), 0 6px 18px rgba(0,0,0,.45);
}

/* High-contrast mode: heavier rules, pure black/white, no soft greys.
   Aimed at bright sunlight on site and at HUD-style rendering. */
html[data-theme="hc"] {
  --bg:        #000000;
  --surface:   #000000;
  --surface-2: #000000;
  --ink:       #ffffff;
  --ink-2:     #ffffff;
  --line:      #ffffff;
  --line-2:    #7a7a7a;
  --accent:    #6fd3ff;
  --accent-bg: #003347;
  --critical:  #ff5f52;
  --critical-bg:#3d0000;
  --done:      #55e58a;
  --shadow:    none;
  --fs-base: 18px;
}
html[data-theme="hc"] .btn,
html[data-theme="hc"] .iconbtn,
html[data-theme="hc"] .idx-row { border-width: 2px; }

@media (prefers-color-scheme: dark) {
  html[data-theme="auto"] {
    --bg:#0d1013; --surface:#171b20; --surface-2:#1f242b;
    --ink:#f2f4f7; --ink-2:#aab3bf; --line:#39414b; --line-2:#262c34;
    --accent:#6ea8ff; --accent-bg:#12304f; --critical:#ff8a80;
    --critical-bg:#4a1512; --done:#6cd39a;
    --shadow: 0 1px 2px rgba(0,0,0,.5), 0 6px 18px rgba(0,0,0,.45);
  }
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--ink);
  font: var(--fs-base)/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}
body { display: flex; flex-direction: column; overflow: hidden; }

button { font: inherit; color: inherit; }

.btn {
  min-height: 42px; padding: 0 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  font-weight: 600; cursor: pointer; white-space: nowrap;
}
.btn:hover { background: var(--surface-2); }
.btn:disabled { opacity: .45; cursor: default; }
/* Quiet = available in one tap, but not competing with the document for
   attention. Marking a section is not the primary action; reading is. */
/* Utilities, not destinations. They keep their full target size and their
   labels; they simply stop competing with the document for attention until
   somebody looks for them. */
.btn-quiet { background: transparent; color: var(--ink-2); font-weight: 600;
  border-color: transparent; }
.btn-quiet:hover, .btn-quiet:focus-visible {
  background: var(--surface-2); color: var(--ink); border-color: var(--line); }
a.btn { text-decoration: none; display: inline-flex; align-items: center; }
.btn-danger { border-color: var(--critical); color: var(--critical); }
.btn-strong { background: var(--accent); border-color: var(--accent); color: #fff; }
html[data-theme="dark"] .btn-strong, html[data-theme="hc"] .btn-strong { color: #04121f; }
.btn-strong:hover { filter: brightness(1.08); background: var(--accent); }

.iconbtn {
  width: 42px; height: 42px; flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); cursor: pointer;
  display: grid; place-items: center;
}
.iconbtn { opacity: .68; }
.iconbtn:hover, .iconbtn:focus-visible { background: var(--surface-2); opacity: 1; }
.iconbtn[data-state="on"], .iconbtn[aria-expanded="true"] { opacity: 1; }
.glyph { font-size: 22px; line-height: 1; }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ---------------------------------------------------------------- top bar */
.topbar {
  flex: 0 0 auto; height: var(--topbar-h);
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px;
  background: var(--surface); border-bottom: 1px solid var(--line);
  z-index: 40;
}
.docid { min-width: 0; flex: 0 1 auto; }
.docid-title { font-weight: 700; font-size: 16px; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.docid-meta { font-size: 12.5px; color: var(--ink-2); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }

.search { flex: 1 1 auto; min-width: 120px; position: relative; display: flex; }
.search input {
  width: 100%; min-height: 42px; padding: 0 72px 0 12px;
  font: inherit; font-size: 16px;
  color: var(--ink); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.search-count {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font-size: 13px; color: var(--ink-2); pointer-events: none;
}
.topbar-actions { display: flex; gap: 8px; flex: 0 0 auto; }

/* ------------------------------------------------------------ location bar */
.locbar {
  flex: 0 0 auto; min-height: var(--locbar-h);
  display: flex; align-items: center; gap: 12px;
  padding: 6px 12px;
  /* This bar is permanent, so a saturated fill across the full width is a
     permanent shout - and it spent the accent colour on furniture rather than
     on signal. The tint is gone; the section number carries the emphasis. */
  background: var(--surface-2); border-bottom: 1px solid var(--line);
  z-index: 35;
}
.loc-main { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.loc-label { font-size: 20px; font-weight: 700; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; color: var(--accent); }
.loc-pos { font-size: 13.5px; color: var(--ink-2); white-space: nowrap; }
.loc-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.crit-flag {
  display: inline-block; padding: 2px 8px; border-radius: 3px;
  background: var(--critical); color: #fff;
  font-size: 12px; font-weight: 800; letter-spacing: .08em;
}
html[data-theme="dark"] .crit-flag, html[data-theme="hc"] .crit-flag { color: #2a0603; }

/* Secondary to reading, but still plainly a button. */
.locbar .btn { background: var(--surface); }
.btn-mark { color: var(--ink-2); }
.btn-mark[data-state="done"] { border-color: var(--done); color: var(--done); }
.btn-mark[data-state="done"]::before { content: "\2713  "; font-weight: 800; }

/* ----------------------------------------------------------------- layout */
.layout { flex: 1 1 auto; display: flex; min-height: 0; position: relative; }

/* ------------------------------------------------------------ index panel */
.sidebar {
  flex: 0 0 var(--sidebar-w); width: var(--sidebar-w);
  display: flex; flex-direction: column; min-height: 0;
  background: var(--surface); border-right: 1px solid var(--line);
  z-index: 30;
}
.sidebar-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--line-2);
}
.sidebar-title { font-weight: 700; font-size: 15px; flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.index, .results { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  padding: 0 0 40px; }
.results { padding: 6px 8px 40px; }

/* A contents row is a crop of the document's own opening line for that clause,
   at a size you can read. The clause number is part of the crop because it is
   printed at the start of that line - so there is no separate number column,
   no heading field and no thumbnail. One thing per row. */
/* Rows sit flush against each other with no card, no rounding and no gap. The
   crops are white because the page is white, and 132 separated white slabs read
   as 132 objects to deal with. Butted together they merge into one continuous
   pale column - which is what the list actually is: a strip of the document.
   Same information, same targets, a fraction of the visual noise. */
.idx-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 8px; width: 100%;
  padding: 2px 10px 2px 8px;
  background: transparent; border: 0; border-radius: 0;
  text-align: left; cursor: pointer; position: relative;
}
.idx-row + .idx-row::after {
  content: ""; position: absolute; left: 8px; right: 10px; top: 0; height: 1px;
  background: var(--line-2); opacity: .55;
}
.idx-row:hover { background: var(--surface-2); }

/* Marked at the edge, never tinted over the words - the row was previously
   filled with the accent, which put a colour wash across the document's own
   type. A bar in the margin says the same thing and touches nothing. */
.idx-row.is-current::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent);
}
.idx-row.is-current { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.idx-row[data-level="2"] { padding-left: 18px; }
.idx-row[data-level="3"] { padding-left: 30px; }
.idx-row[data-level="4"] { padding-left: 42px; }

.idx-strip {
  display: block; width: 100%; height: auto; min-height: 20px;
  background: #fff; border-radius: 2px;
  /* The crop runs off the right edge under a fade - the same shape as a
     truncated list item, so it reads as "there is more", not as "clipped". */
  -webkit-mask-image: linear-gradient(to right, #000 82%, transparent 100%);
          mask-image: linear-gradient(to right, #000 82%, transparent 100%);
}
/* Crops are never re-coloured for the theme, in any mode. The page is white in
   the approved document and stays white here; inverting it would be the app
   changing how the controlled document looks. */

/* Clearly not a crop of the page, because there is nothing on the page to crop.
   Says so, rather than leaving the operator to wonder why two rows match. */
.idx-plain { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; min-width: 0; }
.idx-plain-num { font-weight: 700; font-size: 15px; }
.idx-plain-note { font-size: 12px; color: var(--ink-2); font-style: italic;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.idx-state { display: flex; align-items: center; gap: 6px; }
.dot-crit { width: 10px; height: 10px; border-radius: 50%; background: var(--critical); }
.tick { font-weight: 800; color: var(--done); font-size: 18px; }

/* ------------------------------------------------------------ search hits */
.res-row {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 8px; margin-bottom: 6px;
  background: transparent; border: 1px solid var(--line-2); border-radius: var(--radius);
}
.res-row:hover { background: var(--surface-2); }
.res-head { display: flex; justify-content: space-between; gap: 8px;
  font-size: 13.5px; font-weight: 700; margin-bottom: 5px; }
.res-page { color: var(--ink-2); font-weight: 600; }
.res-crop { display: block; width: 100%; height: auto; border-radius: 3px;
  border: 1px solid var(--line-2); background: #fff; }
.res-empty { padding: 16px 10px; color: var(--ink-2); }

/* --------------------------------------------------------------- document */
.viewer {
  flex: 1 1 auto; min-width: 0; overflow-y: auto; overscroll-behavior-y: contain;
  padding: 18px 12px 60px;
  /* No smooth scrolling: a jump should land, not fly. Animating across three
     pages is slower to read and makes "where am I now" ambiguous while it
     runs - and it silently swallowed programmatic jumps. */
  -webkit-overflow-scrolling: touch;
}
.pages { display: flex; flex-direction: column; align-items: center; gap: 18px; }

.page {
  position: relative; background: #fff; box-shadow: var(--shadow);
  border: 1px solid var(--line); flex: 0 0 auto;
}
.page canvas { display: block; width: 100%; height: 100%; }
.page-num {
  position: absolute; top: -1px; left: -1px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 0 0 var(--radius) 0;
  font-size: 12px; font-weight: 700; color: var(--ink-2);
  padding: 2px 8px; z-index: 3; pointer-events: none;
}
.page.is-pending::after {
  content: "Rendering page…"; position: absolute; inset: 0;
  display: grid; place-items: center; color: #6b727d; font-size: 14px;
}

.overlay { position: absolute; inset: 0; z-index: 1; pointer-events: none; }

/* Region overlays: the map's rectangles, drawn on top of the real page.
   They never cover the text - only outline / tint it. */
.region {
  position: absolute; border-radius: 3px; pointer-events: none;
  border: 2px solid transparent; transition: background-color .15s, border-color .15s;
}
/* "You are here" is marked at the edge, not painted over the words. The page
   has to keep looking like the page. */
.region.is-current { border-color: transparent; border-left: 5px solid var(--accent);
  background: rgba(11,92,213,.05); }
html[data-theme="dark"] .region.is-current,
html[data-theme="hc"] .region.is-current { background: rgba(110,168,255,.09); }
.region.is-target { border-color: var(--accent); background: rgba(11,92,213,.22); }
.region.is-done { border-left: 5px solid var(--done); }
.region.is-critical { border-right: 5px solid var(--critical); }

.hit {
  position: absolute; pointer-events: none; border-radius: 2px;
  background: rgba(255,196,0,.42); mix-blend-mode: multiply;
}
html[data-theme="dark"] .hit, html[data-theme="hc"] .hit {
  background: rgba(255,214,64,.5); mix-blend-mode: screen;
}
.hit.is-active { background: rgba(255,150,0,.6); outline: 2px solid var(--critical); }

/* pdf.js text layer — real, selectable document text. */
.textLayer {
  position: absolute; inset: 0; overflow: clip; opacity: 1; line-height: 1;
  text-align: initial; text-size-adjust: none; forced-color-adjust: none;
  transform-origin: 0 0; caret-color: CanvasText; z-index: 2;
}
.textLayer :is(span, br) {
  color: transparent; position: absolute; white-space: pre;
  cursor: text; transform-origin: 0% 0%;
}
.textLayer > :not(.markedContent),
.textLayer .markedContent span:not(.markedContent) { z-index: 1; }
.textLayer span.markedContent { top: 0; height: 0; }
.textLayer ::selection { background: rgba(11,92,213,.35); }
.textLayer .endOfContent {
  position: absolute; inset: 100% 0 0; z-index: 0; cursor: default;
  user-select: none;
}
.textLayer.selecting .endOfContent { top: 0; }

.viewer-foot {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  text-align: center; color: var(--ink-2); font-size: 13px; padding: 26px 0 10px;
}

/* Return-to-where-you-were. Present only after a jump. */
.undock {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 14px; z-index: 26;
  min-height: 46px; padding: 0 18px; cursor: pointer;
  background: var(--ink); color: var(--bg);
  border: 1px solid var(--ink); border-radius: 24px;
  font-weight: 700; font-size: 15px; box-shadow: var(--shadow);
}
.undock:hover { filter: brightness(1.15); }

/* Floating over the document permanently at full strength made it a fourth
   thing on screen. It fades back when untouched and returns on approach - the
   control never moves and is never hidden, so it stays findable by muscle
   memory rather than by being loud. */
.zoomdock {
  position: absolute; right: 14px; bottom: 14px; z-index: 25;
  display: flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px; box-shadow: var(--shadow);
  opacity: .45; transition: opacity .18s ease;
}
.zoomdock:hover, .zoomdock:focus-within { opacity: 1; }
@media (pointer: coarse) { .zoomdock { opacity: .8; } }
/* High contrast exists for bright sunlight on site. Dimming anything there
   defeats the entire point of the mode, so nothing fades in it. */
html[data-theme="hc"] .zoomdock, html[data-theme="hc"] .iconbtn { opacity: 1; }
.zoomval { font-size: 13px; font-variant-numeric: tabular-nums; min-width: 46px; text-align: center; }

/* ------------------------------------------------------------- focus lens */
.focus {
  position: fixed; inset: 0; z-index: 60;
  background: var(--bg); display: flex; flex-direction: column;
}
.focus-bar, .focus-nav {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; background: var(--surface); border-bottom: 1px solid var(--line);
}
.focus-nav { border-bottom: none; border-top: 1px solid var(--line); justify-content: space-between; }
.focus-loc { margin-left: auto; margin-right: auto; display: flex; align-items: baseline;
  gap: 10px; font-size: 20px; font-weight: 700; }
.focus-hint { font-size: 12.5px; color: var(--ink-2); text-align: center; }
.focus-stage {
  flex: 1 1 auto; min-height: 0; display: grid; place-items: center;
  padding: 14px; overflow: auto; background: var(--bg);
}
.focus-stage canvas {
  max-width: 100%; max-height: 100%;
  background: #fff; border: 1px solid var(--line); box-shadow: var(--shadow);
}

/* ----------------------------------------------------------------- modals */
.modal {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(8,10,13,.6); display: grid; place-items: center; padding: 16px;
}
.modal-card {
  width: min(560px, 100%); max-height: 90vh; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.modal-card.wide { width: min(820px, 100%); }
.modal-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--line-2);
  font-size: 18px; font-weight: 700;
}
.modal-head.crit { background: var(--critical-bg); }
.modal-body { flex: 1 1 auto; overflow-y: auto; padding: 16px; }
.modal-foot {
  flex: 0 0 auto; display: flex; gap: 8px; align-items: center;
  padding: 12px 16px; border-top: 1px solid var(--line-2); flex-wrap: wrap;
}
.spacer { flex: 1 1 auto; }

.confirm-crop canvas { width: 100%; height: auto; display: block;
  border: 1px solid var(--line); border-radius: 3px; background: #fff; }
.confirm-check { display: flex; gap: 10px; align-items: flex-start;
  margin-top: 16px; font-size: 16px; cursor: pointer; }
.confirm-check input { width: 24px; height: 24px; flex: 0 0 auto; accent-color: var(--accent); }

/* ----------------------------------------------------------------- record */
.rec-head { margin: 0 0 4px; font-size: 20px; }
.rec-sub { color: var(--ink-2); font-size: 14px; margin-bottom: 10px; }
/* Says what this log is and is not. It has to stay legible on the printout,
   because the printout is the copy that travels away from the app. */
.rec-disclaimer {
  font-size: 13px; line-height: 1.45; color: var(--ink-2);
  border-left: 3px solid var(--line-2); padding: 8px 0 8px 12px;
  margin-bottom: 16px; max-width: 62ch;
}
.rec-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px,1fr));
  gap: 10px; margin-bottom: 18px; }
.rec-stat { border: 1px solid var(--line-2); border-radius: var(--radius); padding: 10px 12px; }
.rec-stat b { display: block; font-size: 26px; font-variant-numeric: tabular-nums; }
.rec-stat span { font-size: 12.5px; color: var(--ink-2); }
table.rec { width: 100%; border-collapse: collapse; font-size: 14px; }
table.rec th, table.rec td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line-2);
  vertical-align: top; }
table.rec th { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-2); }
table.rec td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.tag-done { color: var(--done); font-weight: 700; }
.tag-crit { color: var(--critical); font-weight: 700; }
.tag-none { color: var(--ink-2); }

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 90;
  background: var(--ink); color: var(--bg);
  padding: 10px 16px; border-radius: var(--radius); font-weight: 600;
  box-shadow: var(--shadow); max-width: 90vw;
}

.scrim { position: absolute; inset: 0; background: rgba(8,10,13,.5); z-index: 29; }

/* Denser rows scan better but tap worse. On touch (and gloves) the rows get
   real target height without changing how they read. */
@media (pointer: coarse) {
  .idx-row { padding-top: 9px; padding-bottom: 9px; }
  .res-row { padding: 12px 10px; }
}

/* ------------------------------------------------------------- responsive */
.only-narrow { display: none; }

@media (max-width: 900px) {
  .only-narrow { display: grid; }
  .btn.only-narrow { display: inline-flex; align-items: center; justify-content: center; }
  .sidebar {
    position: absolute; inset: 0 auto 0 0; width: min(88vw, 400px);
    transform: translateX(-102%); transition: transform .18s ease-out;
    box-shadow: var(--shadow);
  }
  .sidebar.is-open { transform: none; }
  .docid { display: none; }
  /* On a phone the top bar keeps only what is used constantly: contents,
     search, the escape hatch to the real PDF, and contrast. The record moves
     to the foot of the document, which is where you reach for it anyway. */
  .topbar-actions #btn-record { display: none; }
  #btn-original { padding: 0 10px; font-size: 15px; }
}

@media (max-width: 560px) {
  :root { --topbar-h: 54px; }
  .locbar { flex-wrap: wrap; }
  .loc-label { font-size: 18px; }
  .zoomdock { bottom: 10px; right: 10px; }
}

/* Phone-height drawer instead of side panel is handled by the same panel;
   on very short screens the index gets the full height. */

@media print {
  /* The log is printed from whatever theme the device is in, and on a dark
     one every token in this card resolves to pale-on-dark: illegible on paper
     and a wasted sheet. Force the light palette for print only. Nothing here
     touches a PDF crop - the log carries none, and re-colouring the document
     itself is never allowed. */
  html, html[data-theme="dark"], html[data-theme="hc"], html[data-theme="auto"] {
    --surface: #fff; --surface-2: #fff;
    --ink: #000; --ink-2: #333;
    --line: #999; --line-2: #ccc;
    --done: #000; --critical: #000; --critical-bg: #fff;
    --accent: #000; --accent-bg: #fff;
  }
  .modal-card, .modal-head, .modal-body { background: #fff !important; color: #000; }
  .crit-flag, .tag-crit { background: none !important; color: #000 !important; border: 1px solid #999; padding: 0 4px; }

  body > *:not(#record-modal) { display: none !important; }
  #record-modal { position: static; background: none; padding: 0; display: block; }
  .modal-card { border: none; box-shadow: none; max-height: none; width: 100%; }
  .modal-foot { display: none; }
  html, body { overflow: visible; height: auto; background: #fff; color: #000; }

  /* The printed sheet used to carry a heavy ruled header and a control block,
     which made it look like an extract of the approved document rather than
     somebody's working notes. Printed plainly instead, with the disclaimer
     given real weight - on paper it is the only context the reader gets. */
  .modal-head { border-bottom: 1px solid #999; font-weight: 600; }
  .rec-disclaimer {
    border-left: none; border: 1px solid #999; border-radius: 4px;
    padding: 10px 12px; color: #000; font-size: 12px; max-width: none;
  }
  .rec-grid { grid-template-columns: repeat(3, 1fr); }
  .rec-stat { border-color: #bbb; }
  .rec-stat b { font-size: 18px; font-weight: 600; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* A dropped PDF is opened locally and never uploaded; the cue should say
   "this is a place to drop something", not "uploading". */
body.is-dropping::after {
  content: "";
  position: fixed; inset: 8px; z-index: 60; pointer-events: none;
  border: 3px dashed var(--accent); border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* ----------------------------------------------------------- gestures */
/* The self-view is small on purpose. It is not a feature to look at, it is
   there so the operator can tell they are in frame and can tell the camera is
   live - two things a label would only claim. It sits opposite the zoom dock so
   neither covers the other, and above the document rather than inside it. */
.gest-view {
  position: fixed; left: 14px; bottom: 14px; z-index: 45;
  width: 132px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line); background: #000;
  box-shadow: 0 4px 16px rgba(0,0,0,.28);
}
.gest-view video {
  display: block; width: 100%; height: auto;
  transform: scaleX(-1);          /* a mirror, because that is what people expect */
}
.gest-live {
  position: absolute; top: 7px; left: 7px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--critical);
  box-shadow: 0 0 0 2px rgba(0,0,0,.45);
}
.gest-note {
  position: absolute; right: 0; bottom: 0; left: 0;
  font-size: 10.5px; letter-spacing: .02em; text-align: center;
  padding: 2px 0 3px; color: #fff; background: rgba(0,0,0,.55);
}
#gest-canvas { display: none; }

/* Colour is signal only, so an armed camera uses the accent the app already
   means "active" by - not a new colour with a new meaning to learn. */
#btn-gesture[data-state="on"] {
  border-color: var(--accent); color: var(--accent);
}

/* Confirmation that a sweep was understood. It has to be readable at arm's
   length and gone before it becomes clutter - so it is large, brief, and
   carries no text to read. */
/* The arrow does two jobs. While the hold is filling it grows and brightens in
   proportion, so a one-second wait reads as counting rather than as lag - the
   operator learns the timing by watching it once. When the document actually
   starts moving it snaps to solid, which is a different enough state to be read
   at arm's length without looking directly at it. */
.gest-cue {
  --p: 0;
  position: fixed; left: 50%; top: 50%; z-index: 46;
  font-size: 72px; line-height: 1; font-weight: 700;
  color: var(--accent); pointer-events: none;
  text-shadow: 0 2px 12px rgba(0,0,0,.35);
  opacity: 0;
  transform: translate(-50%, -50%) scale(.7);
  transition: opacity .12s linear, transform .12s linear;
}
.gest-cue.is-on {
  opacity: calc(.22 + .55 * var(--p));
  transform: translate(-50%, -50%) scale(calc(.7 + .25 * var(--p)));
}
.gest-cue.is-live {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity .18s ease, transform .18s ease;
}

/* Loading ~20MB of hand model is not instant; the control must show it is
   working rather than look like it ignored the tap. */
#btn-gesture[data-state="busy"] { opacity: .55; }

@media (max-width: 900px) {
  .gest-view { width: 96px; left: 10px; bottom: 10px; }
}
