/*
 * FormFlow — agent-facing submission form.
 *
 * Use scene decides the theme, not category habit: agents work on phones,
 * outdoors in Malaysian daylight and indoors late in the day. So the palette
 * follows the device rather than picking a side, with a manual override for
 * the times the device guesses wrong.
 *
 * Single column at every width. Desktop gets more room, not a second layout —
 * an agent who learns this on a phone must not have to relearn it at a desk.
 */

:root {
  --bg:        #FFFFFF;
  --surface:   #F7F9FC;
  --page:      #E9EDF3;   /* behind the sheet, wide screens only */
  --sheet:     #FFFFFF;
  --ink:       #111827;
  --muted:     #59616F;   /* 5.9:1 on --bg */
  --line:      #DCE1E8;
  /* Control boundaries, so WCAG 1.4.11's 3:1 applies — an input's border is
     the only thing saying where the input is. Was #C3CAD4 at 1.65:1.
     #818B99 is 3.27:1, the smallest step that clears the bar with headroom;
     anything darker starts reading as a heavy outline this design does not
     want. --line stays decorative (dividers) and is deliberately lighter. */
  --line-firm: #818B99;

  --brand:     #1D4ED8;
  /* Brand as TEXT, separate from brand as a button fill. They need different
     things: white on --brand already passes, but --brand on a background is
     link text and has to clear 4.5:1 by itself. Identical to --brand in
     light, where it already does (6.35:1 worst case). */
  --link:      #1D4ED8;
  --brand-ink: #FFFFFF;
  --brand-dim: #EEF3FE;

  --danger:    #B91C1C;
  --danger-bg: #FEF2F2;
  --danger-ln: #F3C6C6;

  --ok:        #15803D;
  --ok-bg:     #F0FDF4;
  --ok-ln:     #BFE6CC;

  --warn:      #B45309;
  --warn-bg:   #FFFBEB;
  --warn-ln:   #F0DCA8;

  --focus:     #2563EB;
  --radius:    .55rem;
  --tap:       2.75rem;   /* 44px — the floor for anything a thumb hits */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:        #0F1319;
    --surface:   #161B23;
    --page:      #080B10;
    --sheet:     #131820;
    --ink:       #E7EBF1;
    --muted:     #9BA6B4;   /* 7.0:1 on --bg */
    --line:      #262D38;
    --line-firm: #667283;   /* 3.54:1 on --bg, was #39424F at 1.83:1 */

    --brand:     #2563EB;
    /* #2563EB as text is only 3.34:1 on --surface — it passed as a button
       fill and failed as a link, which is exactly why the two are now
       separate tokens. Matches --focus so dark mode has one interactive
       blue rather than two nearly identical ones. */
    --link:      #7BA5FF;
    --brand-ink: #FFFFFF;
    --brand-dim: #16203A;

    --danger:    #FCA5A5;
    --danger-bg: #2A1517;
    --danger-ln: #5B2A2C;

    --ok:        #86EFAC;
    --ok-bg:     #0F2318;
    --ok-ln:     #2A4B36;

    --warn:      #FCD34D;
    --warn-bg:   #2A2110;
    --warn-ln:   #574620;

    --focus:     #7BA5FF;
    --cal-invert: 1;
  }
}

:root[data-theme="dark"] {
  --bg:        #0F1319;
  --surface:   #161B23;
  --page:      #080B10;
  --sheet:     #131820;
  --ink:       #E7EBF1;
  --muted:     #9BA6B4;
  --line:      #262D38;
  --line-firm: #667283;
  --brand:     #2563EB;
  --link:      #7BA5FF;
  --brand-ink: #FFFFFF;
  --brand-dim: #16203A;
  --danger:    #FCA5A5;
  --danger-bg: #2A1517;
  --danger-ln: #5B2A2C;
  --ok:        #86EFAC;
  --ok-bg:     #0F2318;
  --ok-ln:     #2A4B36;
  --warn:      #FCD34D;
  --warn-bg:   #2A2110;
  --warn-ln:   #574620;
  --focus:     #7BA5FF;
  --cal-invert: 1;
}

* { box-sizing: border-box; }

html { color-scheme: light dark; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

/* The surfaces we did not draw still carry the design. */
::selection { background: var(--brand); color: var(--brand-ink); }
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: .3rem;
}
input, button, textarea, select { font: inherit; }

/* ---------------------------------------------------------------- header */
/*
 * Navigation only. The agent's name moved to the footer: it is reference
 * information, not a control, and at 375px it was competing with the links
 * for room and losing the entity to an ellipsis.
 */
/* "‹ Apps / FormFlow", then the pages — the shell LiveRoute's Leads uses, so
 * the YCE apps read as one family. Wraps at 375px: the breadcrumb takes the
 * first row and the links the second. The 1rem row gap is not slack: it keeps
 * the crumb's extended tap area (below) clear of the nav links' one. */
header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem .9rem;
  padding: .8rem 1.15rem;
  border-bottom: 1px solid var(--line);
  font-size: .9rem;
  background: var(--surface);
}
header .brand {
  color: var(--ink);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -.01em;
}
/* Grey, not link blue, because Apps leaves FormFlow for the portal. */
header .crumb { color: var(--muted); }
header .crumb:hover { color: var(--ink); }
header .crumb-sep { color: var(--muted); margin: 0 .45rem; }
header nav { display: flex; flex-wrap: wrap; gap: .5rem .9rem; margin-left: auto; }
header nav a { color: var(--link); white-space: nowrap; }
header a { text-decoration: none; }
/* The portal launcher has no nav, so nothing else pushes the toggle right. */
header .where + #theme-toggle { margin-left: auto; }
header a:hover { text-decoration: underline; text-underline-offset: 3px; }

#theme-toggle {
  width: var(--tap);
  height: var(--tap);
  min-width: var(--tap);
  padding: 0;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  cursor: pointer;
}
#theme-toggle:hover { color: var(--ink); border-color: var(--line-firm); }
#theme-toggle svg { width: 1.1rem; height: 1.1rem; }

/* Always available, never in the way. Matches the portal's own disclosure
   so the two apps do not have two ideas of what a details/summary looks
   like. */
details.help {
  margin-top: 2rem;
  font-size: .9rem;
}
details.help > summary {
  cursor: pointer;
  min-height: var(--tap);
  display: flex;
  align-items: center;
  color: var(--link);
  font-weight: 600;
}
details.help > summary::marker { color: var(--muted); }
details.help p { margin: .6rem 0; }

/* The banner's dismiss button sits inside a message block, so it must not
   inherit the full-width stacked treatment every other button gets. */
#first-run button {
  width: auto;
  min-height: var(--tap);
  margin-top: .6rem;
  padding: 0 .9rem;
}
#first-run a { color: var(--link); font-weight: 600; }

footer {
  margin-top: 2.5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  font-size: .9rem;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  align-items: baseline;
}
footer strong { color: var(--ink); font-weight: 600; }
footer a { margin-left: auto; color: var(--link); text-underline-offset: 3px; }

/* Chrome links are text, and were text-sized targets: "Apps" measured 32x19,
   Sign out 52x18. This project sets --tap at 44px and calls it "the floor for
   anything a thumb hits", and 18px high fails even WCAG 2.2's 24px AA
   minimum, never mind that.
   Extended rather than enlarged, the same trick .thumb-remove already uses —
   the hit area grows to the floor while the type stays the size the header
   was designed around. The horizontal inset stays under half the .9rem gap
   between the nav links, so neighbouring targets never overlap. */
header nav a,
header .crumb,
header .brand,
footer a { position: relative; }
header nav a::after,
header .crumb::after,
header .brand::after,
footer a::after {
  content: "";
  position: absolute;
  inset: -.85rem -.4rem;
}

/* ------------------------------------------------------------------ main */
main { max-width: 34rem; margin: 0 auto; padding: 1.15rem 1.15rem 3rem; }

section { padding: 1.6rem 0; }
/* A divider above every VISIBLE section but the first. `section + section`
   also ruled the first one whenever a hidden section preceded it (#sec-resume
   always does), which under the page title's 3px rule drew a second line. */
section:not([hidden]) ~ section { border-top: 1px solid var(--line); }
section[hidden] { display: none; }
/* The `hidden` attribute is only the browser's default `display: none`, so any
   class that sets `display` (.actions is grid, .filename flex) silently shows a
   hidden element. The OCR review's "Send this photo to an admin" row appeared
   before any upload on 2026-10-05 for exactly this reason. */
[hidden] { display: none !important; }

/* The page title, as LiveRoute's Leads draws it: a size above the step
   headings, over a 3px rule in link blue. */
h1.pagehead {
  font-size: 1.4rem;
  letter-spacing: -.015em;
  margin: .25rem 0 0;
  padding-bottom: .9rem;
  border-bottom: 3px solid var(--link);
}

/* Present for the document outline, absent from the page. Clipped rather
   than display:none, which would take it out of the accessibility tree along
   with the layout. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* More space above a heading than below it. */
h1, h2 {
  font-size: 1.0625rem;
  line-height: 1.3;
  letter-spacing: -.011em;
  margin: 0 0 .35rem;
  display: flex;
  align-items: center;
  gap: .5rem;
}
h2 .tick { color: var(--ok); display: none; }
section.done h2 .tick { display: inline-flex; }
h2 .tick svg { width: 1.05rem; height: 1.05rem; display: block; }

p.muted { color: var(--muted); font-size: .9rem; line-height: 1.5; margin: 0 0 1rem; }

label {
  display: block;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--muted);
  margin: .9rem 0 .3rem;
}

/* Radio groups are a fieldset so the question has a name every option is
   announced under — a <label for> can only point at one control, so it named
   the first radio and left the other eight of lead_source anonymous.
   Styled back to exactly what the label-plus-stack looked like: the browser
   default draws a bordered box this design never had, and min-width:0 is
   required because a fieldset otherwise refuses to shrink below its content
   and would reintroduce horizontal scroll at 375px. */
fieldset.choice-set { border: 0; margin: 0; padding: 0; min-width: 0; }
fieldset.choice-set > legend {
  display: block;
  padding: 0;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--muted);
  margin: .9rem 0 .3rem;
}

input[type=text], input[type=date], textarea {
  width: 100%;
  min-height: var(--tap);
  padding: .65rem .75rem;
  border: 1px solid var(--line-firm);
  border-radius: var(--radius);
  font-size: 1rem;
  color: var(--ink);
  background: var(--bg);
  caret-color: var(--brand);
}
input[type=text]:focus, input[type=date]:focus, textarea:focus { border-color: var(--focus); }

/* The address field. Height is set from scrollHeight in app.js, so no manual
   resize handle and no inner scrollbar -- the whole address is on screen or
   the field grows. overflow:hidden is what stops the bar flashing while it
   is being resized. */
textarea {
  resize: none;
  overflow: hidden;
  line-height: 1.4;
}
/*
 * NOT appearance:none. That was here to stop Safari sizing a date input to
 * its intrinsic width, and it also deletes Chrome's calendar button — which
 * is the only way to open the picker by mouse. min-width:0 fixes the Safari
 * problem on its own.
 */
input[type=date] { min-width: 0; }

/* The calendar button is a browser surface. Left alone it is a black glyph,
   invisible on a dark sheet. */
input[type=date]::-webkit-calendar-picker-indicator {
  filter: invert(var(--cal-invert, 0));
  cursor: pointer;
  opacity: .75;
  padding: .2rem;
}
input[type=date]:hover::-webkit-calendar-picker-indicator,
input[type=date]:focus::-webkit-calendar-picker-indicator { opacity: 1; }
/* The IC readback. These carry the same "sealed, not yours to edit" signal as
   a readonly input — dashed edge, recessed, muted — but wrap, because the
   whole point of the step is reading a full name off a card and comparing it.
   A readonly input truncated it and hid the rest behind a caret.
   .label-ish matches the `label` rule; these are <p> because there is no
   control for a <label> to point at. */
.label-ish {
  display: block;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--muted);
  margin: .9rem 0 .3rem;
}
.readback {
  margin: 0;
  min-height: var(--tap);
  padding: .65rem .75rem;
  border: 1px dashed var(--line-firm);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: 1rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

input[readonly], textarea[readonly] {
  background: var(--surface);
  color: var(--muted);
  border-style: dashed;
}

/* The chosen package.
   All five buttons re-enable after the check, so nothing said WHICH one was
   picked — and on a phone the choice scrolls out of sight the moment the
   next section opens. The section's tick says "done", not "done as what".
   Marked with a tick and a filled tint rather than colour alone, so it does
   not depend on seeing the difference between two shades of grey. */
#package-buttons button[aria-pressed="true"] {
  background: var(--brand-dim);
  border-color: var(--brand);
  color: var(--ink);
  font-weight: 600;
}
#package-buttons button[aria-pressed="true"]::before {
  content: "✓ ";
  color: var(--brand);
  font-weight: 700;
}

/* --------------------------------------------------------------- buttons */
button {
  min-height: var(--tap);
  padding: .8rem 1rem;
  border-radius: var(--radius);
  font-size: 1rem;
  font-weight: 600;
  border: 1px solid var(--brand);
  background: var(--brand);
  color: var(--brand-ink);
  cursor: pointer;
  width: 100%;
}
button:hover:not(:disabled) { filter: brightness(1.07); }
button:disabled { opacity: .5; cursor: default; }
button.secondary {
  background: var(--bg);
  color: var(--ink);
  border-color: var(--line-firm);
  font-weight: 500;
}
button.secondary:hover:not(:disabled) { background: var(--surface); filter: none; }

.actions { margin-top: 1.15rem; display: grid; gap: .5rem; }

.btn-link {
  display: block;
  text-align: center;
  background: var(--brand);
  color: var(--brand-ink);
  text-decoration: none;
  padding: .8rem 1rem;
  min-height: var(--tap);
  border-radius: var(--radius);
  font-weight: 600;
}
.btn-link:hover { filter: brightness(1.07); }

/* -------------------------------------------------------------- messages */
/*
 * Three meanings, three colours. The blocker used to render in the same
 * neutral grey as "working…", so "still needed: the back of the IC" read as
 * progress rather than as something to act on.
 */
.msg {
  padding: .7rem .85rem;
  border-radius: var(--radius);
  margin-top: .8rem;
  font-size: .9rem;
  line-height: 1.45;
  border: 1px solid transparent;
}
.msg.err   { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-ln); }
.msg.warn  { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-ln); }
.msg.ok    { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-ln); }
.msg.busy  { background: var(--surface);   color: var(--muted);  border-color: var(--line); }

.field { margin-bottom: 1rem; }
.field .err { color: var(--danger); font-size: .8125rem; margin-top: .3rem; }

.choice { display: grid; gap: .4rem; margin-top: .35rem; }
.choice label {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0;
  min-height: var(--tap);
  padding: .55rem .75rem;
  border: 1px solid var(--line-firm);
  border-radius: var(--radius);
  font-size: .95rem;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}
.choice label:hover { background: var(--surface); }
.choice label:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-dim);
}
.choice input { margin: 0; accent-color: var(--brand); width: 1.1rem; height: 1.1rem; }
.other-text { margin-top: .6rem; }

/* ------------------------------------------------------------ thumbnails */
/*
 * WhatsApp showed the agent the photo they had just sent. A file input shows
 * nothing, so until now the only way to check you had picked the right image
 * was to finish the form and read the result. These put the picture back.
 */
.thumbs { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; }
.thumb {
  position: relative;
  width: 5.5rem;
  height: 5.5rem;
  border-radius: var(--radius);
  border: 1px solid var(--line-firm);
  background: var(--surface);
  overflow: hidden;
  flex: none;
}
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A thumbnail proves a photo was picked. Only enlarging it can prove the
   right one was picked, which is the entire reason for showing it. */
button.thumb { padding: 0; cursor: zoom-in; }
button.thumb:hover { border-color: var(--brand); }

/* The in-page confirm/prompt. A sheet, not the full-bleed treatment #viewer
   gets: the viewer replaces the page because the photo IS the content,
   whereas this sits on top of work the agent is part-way through and should
   not hide it. */
dialog#ask {
  border: 1px solid var(--line);
  border-radius: .9rem;
  padding: 1.25rem;
  max-width: min(92vw, 26rem);
  background: var(--sheet, var(--bg));
  color: var(--ink);
  box-shadow: 0 18px 48px -18px rgba(0, 0, 0, .45);
}
dialog#ask::backdrop { background: rgba(0, 0, 0, .55); }
dialog#ask h2 { margin: 0 0 .5rem; }
dialog#ask p { margin: 0 0 .9rem; color: var(--muted); font-size: .9rem; line-height: 1.5; }
dialog#ask .actions { margin-top: 1.1rem; }
dialog#ask #ask-err { margin-top: .35rem; }

dialog#viewer {
  border: 0;
  padding: 0;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  background: #000;
  color: #fff;
}
dialog#viewer::backdrop { background: rgba(0, 0, 0, .85); }
dialog#viewer img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
#viewer-close {
  position: fixed;
  top: max(.9rem, env(safe-area-inset-top));
  right: .9rem;
  width: auto;
  min-width: 5rem;
  /* A near-transparent white pill over a blur is invisible against the thing
     it most often sits on: a photo of a white IC card. The scrim is its own
     background rather than a tint of whatever is behind it, so the button
     reads the same over a card, a dark SSM printout, or the dialog backdrop.
     Kept translucent enough not to hide the image underneath. */
  background: rgba(17, 24, 39, .82);
  color: #fff;
  border-color: rgba(255, 255, 255, .55);
  backdrop-filter: blur(6px);
}

/* Done offers four actions and gave them all equal weight, so "New client" —
   which throws the finished submission off screen — shouted as loudly as
   "Send to client", the thing the agent came here to do. Download stays the
   primary fill; sharing keeps a visible border because it is the second real
   action; the two navigation choices recede to text. */
#sec-done .actions button.secondary#another-pkg-btn,
#sec-done .actions button.secondary#restart-btn {
  background: none;
  border-color: transparent;
  color: var(--link);
  font-weight: 600;
}
#sec-done .actions button.secondary#another-pkg-btn:hover,
#sec-done .actions button.secondary#restart-btn:hover {
  background: var(--surface);
  border-color: var(--line);
  filter: none;
}

/* --------------------------------------------------------------- filename */
/* PH-039's one canonical name. Agents quote it when searching Drive. */
.filename {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: 1rem;
  padding: .6rem .7rem;
  border: 1px solid var(--line-firm);
  border-radius: var(--radius);
  background: var(--surface);
}
.filename code {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--ink);
  word-break: break-all;
}
.filename button.copy { width: auto; min-width: 4.5rem; flex: none; padding: .5rem .75rem; }
.thumb .pill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: .18rem .35rem;
  font-size: .6875rem;
  font-weight: 600;
  text-align: center;
  color: #fff;
  background: rgba(17, 24, 39, .74);
}
.thumb.file { display: grid; place-items: center; color: var(--muted); }

/* Remove one page. Agents photograph printouts sheet by sheet, so picking up
   the wrong one is the normal case — until now the only undo was abandoning
   the whole submission. */
.thumb-wrap { position: relative; }
.thumb-remove {
  position: absolute;
  top: -.4rem;
  right: -.4rem;
  width: 1.65rem;
  height: 1.65rem;
  min-height: 0;
  min-width: 0;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger-ln);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  /* Small on purpose so it does not cover the picture, but with a padded hit
     area so a thumb still lands on it. */
  box-shadow: 0 0 0 .35rem transparent;
}
.thumb-remove::after {
  content: "";
  position: absolute;
  inset: -.55rem;      /* pads the tap target to ~44px without growing the dot */
}
.thumb-remove svg { width: .85rem; height: .85rem; }
.thumb-remove:hover { filter: brightness(1.1); }
.thumb-remove:disabled { opacity: .5; cursor: default; }
.thumb.file svg { width: 1.6rem; height: 1.6rem; }

/*
 * Desktop gets width and a surface, not a different layout.
 *
 * The column stays a column: a form is easier to complete in one narrow
 * track, and an agent who learns this on a phone must not have to relearn it
 * at a desk. But at 1920px a 34rem column floating in bare background reads
 * as a page that failed to load rather than as a deliberate measure.
 *
 * So above the breakpoint the form sits on a sheet, and the page behind it
 * recedes. Nothing moves; the column simply stops floating. Below it,
 * mobile is untouched — no sheet, no border, edge to edge.
 */
@media (min-width: 900px) {
  body { background: var(--page); }
  header {
    background: var(--sheet);
    border-bottom-color: var(--line);
    /* Align the bar's CONTENT with the sheet's content edge. The bar still
       spans the window — it is the app's chrome — but a wordmark pinned to
       the far left of a 1920px screen while the form sits in the middle
       reads as two unrelated pages. 42rem is the sheet; 2.25rem is its
       inner padding. 100%, not 100vw: vw counts the scrollbar and the
       centred sheet does not, so on any page tall enough to scroll the bar
       sat ~7px right of the sheet's content. */
    padding-inline: calc((100% - 42rem) / 2 + 2.25rem);
  }
  main {
    max-width: 42rem;
    margin: 2rem auto 4rem;
    padding: .5rem 2.25rem 2rem;
    background: var(--sheet);
    border: 1px solid var(--line);
    border-radius: .9rem;
    /* A real offset and a soft blur, so the sheet sits above the page
       rather than being outlined on it. */
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04),
                0 12px 32px -12px rgba(15, 23, 42, .12);
  }
  input[type=text], textarea, .thumb, .filename { background: var(--bg); }
  /* The sheet's top padding is .5rem, sized for a section's own 1.6rem. */
  h1.pagehead { margin-top: 1.5rem; }

  /* Full-bleed is right on a phone and overbearing on a monitor: an IC
     blown up to 1920px is harder to read, not easier. Size to the image,
     capped, and let the backdrop carry the focus. */
  dialog#viewer {
    width: auto;
    height: auto;
    max-width: min(90vw, 60rem);
    max-height: 88vh;
    margin: auto;
    border-radius: .9rem;
    overflow: hidden;
  }
  dialog#viewer img {
    width: auto;
    height: auto;
    max-width: min(90vw, 60rem);
    max-height: 88vh;
  }
  #viewer-close { position: absolute; top: .75rem; right: .75rem; }

  /* Buttons are width:100% everywhere, which is right on a phone and absurd
     on a 672px sheet. The package step became five identical 600px-wide
     rectangles stacked 240px tall — a wall of grey at the app's single most
     important decision point.
     auto-fit rather than a fixed count: a lone button still fills its track,
     so every single-action section is untouched, and groups pair up only
     once there is genuinely room for two. */
  .actions { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }

  /* The step exists to read a name and an IC number off a card, and the
     thumbnail stayed 88px at every width — so desktop got more empty space
     rather than more of the thing the agent needs to look at, and still
     required a click into the viewer. Only in step 2: the IC-back and SSM
     strips are checklists of what has been attached, not things to read. */
  #confirm-thumbs .thumb { width: 15rem; height: 15rem; }
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) main {
    box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 14px 36px -14px rgba(0, 0, 0, .6);
  }
}
:root[data-theme="dark"] main {
  box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 14px 36px -14px rgba(0, 0, 0, .6);
}

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

/* ------------------------------------------------------------------ pager */
/*
 * My Submissions is paged rather than infinite-scrolled, because the OLDEST
 * day in the window is the last row, and that is exactly what an agent is
 * hunting for when they use this page at all.
 *
 * Targets are 2.25rem so they stay tappable on a phone without a stylus —
 * below about 2rem this becomes a game of chance one-handed.
 */
.pager {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  justify-content: center;
  align-items: center;
  margin-top: 1rem;
}
.pager button {
  /* width and flex are NOT optional here. The global `button` rule above sets
   * width:100%, which every other button in this app wants — they are all
   * full-width stacked actions. Inherited into a flex row it makes each page
   * number take a whole line, so the pager renders as a vertical stack of
   * giant buttons instead of a row of numbers. Caught by measuring a rendered
   * button (338x36 in a 375px viewport), not by reading the CSS. */
  width: auto;
  flex: 0 0 auto;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 .5rem;
  border: 1px solid var(--line-firm, #d0d0d0);
  border-radius: .4rem;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
.pager button:hover:not(:disabled) { filter: brightness(1.2); }
.pager button:disabled { opacity: .35; cursor: default; }
.pager .page-cur {
  min-width: var(--tap);
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: .4rem;
  background: var(--brand);
  color: var(--brand-ink);
}
.pager .page-gap { padding: 0 .15rem; opacity: .6; }

/* OCR admin review: the scan beside the fields it is checked against. */
.review-images img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 0 .75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}
