/* The phone app's look.
 *
 * The palette stays the desktop application's own, out of App.xaml - the same
 * #B3261E for a high reading, #1B6B36 for a healthy one. Somebody who uses
 * both all day should not feel they have switched products.
 *
 * What changed is the structure. It was full-width rows separated by hairlines
 * all the way down, which is what a table looks like and reads as older than
 * it is. Content sits in cards on a tinted ground now, with room to breathe
 * and larger type, and each patient carries a coloured edge for how far their
 * report has got - the one thing on that row the eye is running down the list
 * for.
 *
 * The type is the phone's own UI font rather than a web font. This app has to
 * work when the lab PC is unreachable, and a screen that waits on
 * fonts.googleapis.com to render its text is a screen that does not. Roboto on
 * Android and San Francisco on iOS also simply look right there.
 */

:root {
  --ground: #EEF2F4;
  --surface: #FFFFFF;
  --sunk: #F4F6F8;
  --border: #DDE3E8;
  --border-soft: #EAEFF2;
  --ink: #16232A;
  --ink-soft: #3F4B54;
  --muted: #6B7681;
  --accent: #0E5A6E;
  --accent-soft: #E3EFF2;
  --high: #B3261E;
  --high-soft: #FBEBEA;
  --low: #14539E;
  --low-soft: #E9F0FA;
  --ok: #1B6B36;
  --ok-soft: #E8F3EC;

  /* Not an error and not a healthy reading either - the amber of something
     worth a second look: a report still waiting, a name that was corrected
     after the fact. */
  --warm: #A4610B;

  /* Type printed ON the accent. It has to flip with the palette, and being a
     token is what lets it: a colour whose only definition sits inside a media
     query is a colour the manual setting below cannot reach. */
  --on-accent: #FFFFFF;

  --ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --num: ui-monospace, "SF Mono", "Roboto Mono", "DejaVu Sans Mono", monospace;

  --pad: 14px;
  --radius: 14px;
  --lift: 0 1px 2px rgba(22, 35, 42, .05), 0 1px 8px rgba(22, 35, 42, .04);
}

/* ---------------------------------------------------------------------
 *  DAY, DARK, OR WHATEVER THE PHONE SAYS
 * ---------------------------------------------------------------------
 * Three states, not two. No data-theme on <html> means "follow the phone",
 * which is what everybody gets until they touch the button, and the media
 * query below is the only thing deciding for them. data-theme="dark" or
 * "light" is somebody having decided, and has to win in BOTH directions -
 * dark on a phone set to light, and light on a phone set to dark. Hence the
 * :not() guard on the media query and the stamped block after it.
 *
 * The rule that keeps this honest: every colour is declared in the plain
 * :root above and only REDEFINED here. A colour whose one definition sits
 * inside a media query simply is not there for a phone set to light, and the
 * screen renders one palette's text on the other's ground.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0B1216;
    --surface: #16232A;
    --sunk: #111C22;
    --border: #26363E;
    --border-soft: #1D2A31;
    --ink: #E8F0F3;
    --ink-soft: #C3D1D7;
    --muted: #8DA0A9;
    --accent: #5CBACD;
    --accent-soft: #15323A;
    --high: #F09088;
    --high-soft: #38211F;
    --low: #88B6E9;
    --low-soft: #172B3D;
    --ok: #72C690;
    --ok-soft: #163120;
    --warm: #E0A45C;
    --on-accent: #06161B;
    --lift: 0 1px 2px rgba(0, 0, 0, .4), 0 1px 10px rgba(0, 0, 0, .3);
  }
}

:root[data-theme="dark"] {
  --ground: #0B1216;
  --surface: #16232A;
  --sunk: #111C22;
  --border: #26363E;
  --border-soft: #1D2A31;
  --ink: #E8F0F3;
  --ink-soft: #C3D1D7;
  --muted: #8DA0A9;
  --accent: #5CBACD;
  --accent-soft: #15323A;
  --high: #F09088;
  --high-soft: #38211F;
  --low: #88B6E9;
  --low-soft: #172B3D;
  --ok: #72C690;
  --ok-soft: #163120;
  --warm: #E0A45C;
  --on-accent: #06161B;
  --lift: 0 1px 2px rgba(0, 0, 0, .4), 0 1px 10px rgba(0, 0, 0, .3);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 16px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;

  /* The page itself does not scroll; the screen inside it does. Without this
     the whole document scrolled - which on a long report took the tab bar off
     the bottom of the screen with it, so there was no way back to Collection
     without scrolling to the end of somebody's blood work. */
  overflow: hidden;
}

button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ============ sign in ============ */

/* Its own scroller, since the document no longer scrolls - a short phone with
   the keyboard up must still be able to reach the button. */
.signin {
  height: 100%;
  height: 100dvh;
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: 24px 18px;
}

.signin-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--lift);
  padding: 30px 24px 26px;
  display: flex;
  flex-direction: column;
}

.signin-card h1 {
  margin: 0;
  font-size: 28px;
  line-height: 1.2;
  letter-spacing: -.025em;
  font-weight: 680;
  text-wrap: balance;
}

.signin-sub {
  margin: 10px 0 26px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}

.signin-card label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  margin-bottom: 6px;
}

.signin-card input {
  height: 50px;
  padding: 0 15px;
  margin-bottom: 18px;
  background: var(--sunk);
  border: 1px solid transparent;
  border-radius: 12px;
  font-size: 16px;   /* 16 or more, or iOS zooms the page on focus */
  transition: border-color .12s, background .12s;
}
.signin-card input:focus {
  background: var(--surface);
  border-color: var(--accent);
  outline: none;
}

/* The password and its Show button are one field to look at, so the button
   sits inside the box rather than under it. */
.passwordbox { position: relative; }
.passwordbox input { width: 100%; padding-right: 66px; }

.peek {
  position: absolute;
  top: 9px;
  right: 8px;
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: 999px;
  background: var(--surface);
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.peek:active { background: var(--accent-soft); }

/* What this app is, in one line, before anybody signs in. It is the honest
   answer to "what can somebody do with my lab's records if they get in" -
   and it is enforced by the connection, not by this sentence (see Db.cs). */
.signin-foot {
  margin: 18px 0 0;
  text-align: center;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

.primary {
  height: 50px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 15.5px;
  font-weight: 600;
  cursor: pointer;
}
.primary:disabled { opacity: .55; }

.error {
  margin: 0 0 16px;
  padding: 11px 13px;
  background: var(--high-soft);
  color: var(--high);
  border-radius: 10px;
  font-size: 13.5px;
  line-height: 1.45;
}

/* The lab's letterhead, given room. It is the first thing anyone sees and
   the one thing on the screen that says whose lab this is. */
.signin-logo {
  max-height: 76px;
  max-width: 80%;
  align-self: flex-start;
  margin-bottom: 20px;
  object-fit: contain;
}

/* ============ shell ============ */

/* A definite height, not min-height. "flex: 1" on the screen inside only
   constrains it when this has a real height to divide up; with min-height the
   column grew to fit its content, the screen never became a scroller, and the
   document scrolled instead. dvh where it exists, because the mobile browser
   bar makes 100vh taller than what you can actually see. */
#app {
  height: 100%;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
}

.appbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: calc(16px + env(safe-area-inset-top)) var(--pad) 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-soft);
  transition: padding .16s ease;
}

/* Scrolled down: the name drops to one line and the date line goes, giving
   the list back about 30px on a short phone. Back at the top it returns. */
.appbar.condensed { padding: calc(8px + env(safe-area-inset-top)) var(--pad) 8px; }
.appbar.condensed .lab-name { font-size: 15px; }
.appbar.condensed .lab-sub { display: none; }
.appbar.condensed .lab-logo { height: 26px; }
@media (prefers-reduced-motion: reduce) { .appbar { transition: none; } }

.appbar-text { flex: 1; min-width: 0; }

.lab-logo {
  height: 36px;
  transition: height .16s ease;
  width: auto;
  max-width: 88px;
  flex: none;
  object-fit: contain;
  border-radius: 6px;
}

.lab-name {
  font-size: 21px;
  transition: font-size .16s ease;
  font-weight: 650;
  letter-spacing: -.02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lab-sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

.linkish {
  background: none;
  border: 0;
  color: var(--accent);
  font-size: 13.5px;
  font-weight: 500;
  padding: 6px;
  cursor: pointer;
}

.screen {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 22px;
}

/* ============ controls ============ */

.datebar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px var(--pad) 0;
}

.datebar input[type="date"] {
  flex: 1;
  height: 44px;
  padding: 0 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  box-shadow: var(--lift);
}

.step {
  width: 44px;
  height: 44px;
  flex: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--lift);
  font-size: 20px;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}
.step:active { background: var(--sunk); }

.searchbox {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 10px var(--pad) 0;
  padding: 0 8px 0 13px;
  height: 46px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  box-shadow: var(--lift);
}
.searchbox:focus-within { border-color: var(--accent); }

.search-icon {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
  stroke-linecap: round;
}

.filter {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: none;
  font-size: 15.5px;
  outline: none;
}

/* The box and the button that orders the list, on one line. The box carried
   its own side margins when it was alone; they belong to the row now, or the
   two controls would each hold a different edge. */
.searchrow {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 10px var(--pad) 0;
}
.searchrow .searchbox { flex: 1; min-width: 0; margin: 0; }

.iconbtn {
  flex: none;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  color: var(--ink-soft);
  box-shadow: var(--lift);
  cursor: pointer;
}
.iconbtn svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.9; }
.iconbtn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }

/* The same button in the header, where it sits between the lab's name and
   Sign out and must not out-shout either. */
.appbar .iconbtn {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  box-shadow: none;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}
.appbar .iconbtn svg { width: 17px; height: 17px; }

/* Which of the three appearance icons is showing says which setting is on,
   so the button reports its own state instead of needing a label beside it. */
#theme svg { display: none; }
#theme[data-mode="auto"] .theme-auto,
#theme[data-mode="light"] .theme-day,
#theme[data-mode="dark"] .theme-dark { display: block; }

/* A short list of choices under the button that opened it. Not a modal: it
   holds three lines, and covering the screen for three lines is a bigger
   gesture than the decision deserves. */
.menu {
  margin: 8px var(--pad) 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  box-shadow: var(--lift);
  overflow: hidden;
}
.menu-item {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 14px;
  text-align: left;
  padding: 12px 15px;
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
}
.menu-item + .menu-item { border-top: 1px solid var(--border-soft); }
.menu-item[aria-pressed="true"] { color: var(--accent); font-weight: 600; background: var(--accent-soft); }

.wide {
  width: calc(100% - var(--pad) * 2);
  margin: 10px var(--pad) 0;
  height: 44px;
  border: 1px dashed var(--border);
  border-radius: 12px;
  background: none;
  color: var(--accent);
  font-size: 14px;
  font-weight: 550;
  cursor: pointer;
}
.wide:active { background: var(--accent-soft); }

.segmented {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  margin: 14px var(--pad) 0;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  box-shadow: var(--lift);
}

.seg {
  height: 36px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}
.seg[aria-pressed="true"] {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}

/* Above a patient's results: the tests that went to an outside lab. */
.rep-outside {
  margin: 12px var(--pad) 0;
  padding: 9px 12px;
  border-left: 3px solid #7C5CBF;
  border-radius: 6px;
  background: var(--sunk);
  color: var(--ink-soft);
  font-size: 13.5px;
  line-height: 1.45;
}

.state {
  margin: 30px var(--pad);
  color: var(--muted);
  font-size: 14.5px;
  text-align: center;
  line-height: 1.55;
}

/* ============ patients ============ */

#rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px var(--pad) 0;
}

/* Each patient is a card with a coloured edge for how far their report has
   got. Status is encoded twice - as that edge and as words in the pill -
   because the edge is what the eye runs down the list for, and the words are
   what makes it mean anything to somebody who has not learned the colours. */
.row {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--lift);
  text-align: left;
  cursor: pointer;
  transition: transform .08s;
}
.row:active { transform: scale(.988); }

/* The patient's initials, ringed in how far their report has got. This
   replaces a coloured edge on the card plus their number in the same
   column - the number is on the report; the person is what the eye is
   looking for down a list. */
.ring {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 12.5px;
  font-weight: 650;
  letter-spacing: .01em;
  color: var(--muted);
  background: var(--sunk);
  box-shadow: inset 0 0 0 2px var(--border);
}
.ring.entered { color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); }
.ring.printed { color: var(--ok); box-shadow: inset 0 0 0 2px var(--ok); }
/* Every test sent to an outside lab. */
.ring.outside { color: #7C5CBF; box-shadow: inset 0 0 0 2px #7C5CBF; }

/* A dot and a word. Three filled pills down a list shout as loudly as the
   names do; this says the same thing and stays out of the way. */
.dot {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--muted);
}
.dot i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border);
  display: inline-block;
}
.dot.entered i { background: var(--accent); }
.dot.printed i { background: var(--ok); }
.dot.outside i { background: #7C5CBF; }
.dot.entered, .dot.printed, .dot.outside { color: var(--ink-soft); }

.name {
  font-size: 15.5px;
  font-weight: 550;
  letter-spacing: -.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.meta {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meta.outside-line { color: #7C5CBF; font-weight: 550; }

.right { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }

.amt {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 14.5px;
  font-weight: 600;
}

.when {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--muted);
}

/* ============ one report ============ */

.back {
  margin: 14px var(--pad) 0;
  padding: 9px 16px 9px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  box-shadow: var(--lift);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-soft);
  cursor: pointer;
}

.rep-head {
  margin: 12px var(--pad) 0;
  padding: 18px 16px;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
}
.rep-name { font-size: 21px; font-weight: 660; letter-spacing: -.02em; text-wrap: balance; }
/* The ordinary face, not the figures' monospace, and one line each for who,
   the date and the doctor - the \n between them used to run together. */
.rep-sub {
  font-size: 13.5px;
  color: var(--muted);
  margin-top: 8px;
  line-height: 1.6;
  white-space: pre-line;
}

/* A section and its readings are one object, so the heading sits on the same
   card rather than floating above a run of loose rows. */
.sec {
  margin: 10px var(--pad) 0;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  overflow: hidden;
}

.sec-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px 10px;
}
.sec-title { font-size: 12.5px; font-weight: 680; letter-spacing: .04em; text-transform: uppercase; }
.sec-code { font-family: var(--num); font-size: 11px; color: var(--muted); }

/* The lab's own printed sheet, down to the column order: the name, the
   reading with its unit, then the reference range - all left aligned to
   their column, so the eye runs down a column of readings rather than
   hunting for a number pushed to the right-hand edge of each row. */
.res, .res-head {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.85fr) minmax(0, 1.05fr);
  gap: 2px 10px;
  padding: 9px 16px;
  align-items: baseline;
}

/* The "Test Name / Result / Reference Range" heading the print puts over
   every table. Quieter than a reading and ruled off from them. */
.res-head {
  padding-top: 0;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--border);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}

.res { border-top: 1px solid var(--border-soft); }
.res-head + .res { border-top: 0; }

.res .p-name { font-size: 13.5px; overflow-wrap: anywhere; }
.res .p-val {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  font-weight: 650;
  overflow-wrap: anywhere;
}
.res .p-unit {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
}
.res .p-ref {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

/* The flag the print carries in its own column between the reading and the
   range - same words, same arrow. On a phone it goes under the reading
   instead, which is the column it belongs to. */
.res .p-val small {
  display: block;
  font-size: 10px;
  font-weight: 700;
  margin-top: 2px;
}

/* Out of range reads the way it prints: the reading itself in the flag's
   colour, not the whole row shaded. */
.res.high .p-val, .res.high .p-val small { color: var(--high); }
.res.low .p-val, .res.low .p-val small { color: var(--low); }

/* A narrative result - histopath, cytology - is prose, not a number, so it
   gets the full width instead of being squeezed into the value column. */
.narrative { padding: 12px 16px 15px; border-top: 1px solid var(--border-soft); }
.narrative .p-name { font-size: 14.5px; font-weight: 550; margin-bottom: 7px; }
.narrative .p-text {
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-soft);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ============ collection ============ */

.total {
  margin: 12px var(--pad) 0;
  padding: 22px 18px;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
}
.total-label {
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--muted);
}
.total-value {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 40px;
  font-weight: 680;
  letter-spacing: -.03em;
  margin-top: 8px;
  line-height: 1;
  color: var(--accent);
}
.total-meta { font-size: 14px; color: var(--muted); margin-top: 12px; }
.total-meta b { color: var(--ink-soft); font-weight: 600; }

/* The date range under a month total is a caption, not another heading. */
.total-range {
  margin-top: 10px;
  font-size: 13px;
  color: var(--muted);
}

.sub-label {
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--muted);
  padding: 22px var(--pad) 6px;
}

.mode {
  display: grid;
  grid-template-columns: 70px minmax(0, 1fr) 88px 36px;
  gap: 10px;
  align-items: center;
  margin: 6px var(--pad) 0;
  padding: 12px 16px;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--lift);
}
.mode-name { font-size: 14px; font-weight: 500; }
.mode-bar { height: 8px; background: var(--sunk); border-radius: 5px; overflow: hidden; }
.mode-bar i { display: block; height: 100%; background: var(--accent); border-radius: 5px; }
.mode-amt {
  text-align: right;
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  font-weight: 600;
}

.doc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 3px 14px;
  align-items: baseline;
  margin: 6px var(--pad) 0;
  padding: 12px 16px;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--lift);
}
.doc-name {
  font-size: 14px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc-count {
  grid-column: 1;
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--muted);
}
.doc-amt {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  text-align: right;
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 14.5px;
  font-weight: 600;
}

/* ============ the printed report ============ */

.pdf-row { margin: 10px var(--pad) 0; }

.pdf-actions { display: grid; grid-template-columns: 1fr; gap: 8px; }
.pdf-actions.two { grid-template-columns: 1fr 1fr; }

/* Two actions told apart by weight rather than sitting side by side as
   equals: opening it is what most people do, sending it is the one that
   leaves the building. */
.act {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 50px;
  border: 1px solid transparent;
  border-radius: 12px;
  font-size: 14.5px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--lift);
}
.act svg {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.act:disabled { opacity: .6; }

.act.primary { background: var(--accent); color: var(--on-accent); }

.act.ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--ink-soft);
  font-weight: 550;
}

/* The patient's own number. Opens the chat; it cannot carry the PDF, which
   is what Send is for - so it is quieter than both. */
.whatsapp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  margin-top: 8px;
  padding: 9px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--accent);
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
}
.whatsapp svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.whatsapp:active { background: var(--accent-soft); }

/* Home's "share this day" - the same button, as wide as the tiles above it
   and dressed like them. */
.share-day {
  box-sizing: border-box;
  width: 100%;
  margin: 10px 0 0;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 13px;
  box-shadow: var(--lift);
}

.pdf-note {
  margin-top: 10px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
}

/* ============ tabs ============ */

.tabs {
  flex: none;
  display: grid;
  /* Four - Home, Patients, Collection, Balance. Written as "however many
     there are" rather than the number, so the next one added does not push
     one off the bottom of the bar the way the fourth did. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
  background: var(--surface);
  border-top: 1px solid var(--border-soft);
}

.tab {
  padding: 8px 8px 9px;
  background: none;
  border: 0;
  border-radius: 12px;
  font-size: 11.5px;
  font-weight: 550;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.tab[aria-selected="true"] { color: var(--accent); background: var(--accent-soft); }
.tab svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.7; }

/* ============ pull down to refresh ============ */

/* A pill that follows the finger down from under the header, turns its
   arrow when letting go would fetch, and spins while the fetch is out.
   Positioned by app.js (drawPull), since where the header ends depends on
   whether it is condensed. */
.pull {
  position: fixed;
  left: 50%;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px 8px 11px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .14);
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 550;
  white-space: nowrap;
  pointer-events: none;
}
.pull svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .15s;
}
.pull .pull-spin { display: none; }
.pull.armed .pull-arrow { transform: rotate(180deg); }
.pull.busy .pull-arrow { display: none; }
.pull.busy .pull-spin { display: block; animation: pull-spin .8s linear infinite; }
@keyframes pull-spin { to { transform: rotate(360deg); } }

/* When the list on screen was fetched, at the foot of it - the time, and the
   gesture that fetches it again. */
.fresh {
  margin: 16px var(--pad) 0;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}

/* ============ offline notice ============ */

/* A newer app is on the lab PC - same place and shape as .offline, in the
   accent colour, with one button. */
.update-ready {
  position: fixed;
  left: 14px;
  right: 14px;
  bottom: calc(84px + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 10px 10px 15px;
  background: var(--accent);
  color: var(--surface);
  border-radius: 12px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .22);
  font-size: 13.5px;
  z-index: 30;
}
.update-ready[hidden] { display: none; }
.update-ready button {
  flex: none;
  padding: 8px 14px;
  border: 0;
  border-radius: 9px;
  background: var(--surface);
  color: var(--accent);
  font-weight: 650;
  font-size: 13.5px;
}

.offline {
  position: fixed;
  left: 14px;
  right: 14px;
  bottom: calc(84px + env(safe-area-inset-bottom));
  margin: 0;
  padding: 12px 15px;
  background: var(--ink);
  color: var(--surface);
  border-radius: 12px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .22);
  font-size: 13px;
  line-height: 1.45;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---------------------------------------------------------------------
 *  The bill
 * ---------------------------------------------------------------------
 * Reuses the report's cards - .sec, .sec-head - so the two views of one
 * patient are visibly the same screen. What is new is a money column:
 * amounts are set in the numeric face and right-aligned, so a list of them
 * can be read down as a column rather than scanned line by line.
 */
.bill-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  padding: 11px 16px;
  border-top: 1px solid var(--border-soft);
  align-items: baseline;
}
.bill-test { font-size: 14.5px; }
.bill-amt {
  font-family: var(--num);
  font-size: 14.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

.bill-totals { padding-bottom: 4px; }
.bill-figure {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 16px;
  border-top: 1px solid var(--border-soft);
}
.bill-figure:first-child { border-top: 0; }
.bill-figure-label { font-size: 14px; color: var(--muted); }
.bill-figure-value {
  font-family: var(--num);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* The bill amount is the figure the screen exists for; a balance still owed
   is the one that needs acting on, so it carries the same red the desktop
   uses for a reading out of range. */
.bill-figure.strong .bill-figure-value { font-size: 20px; font-weight: 650; }
.bill-figure.muted .bill-figure-value { color: var(--muted); }
.bill-figure.due .bill-figure-value { color: var(--high); font-weight: 620; }

.bill-note {
  padding: 10px 16px 14px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

.bill-print {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 11px 16px;
  border-top: 1px solid var(--border-soft);
}
.bill-by { font-size: 14.5px; }
.bill-at {
  font-family: var(--num);
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------
 *  Chips over the day's list
 * ---------------------------------------------------------------------
 * A row of filters, scrollable sideways rather than wrapped onto two lines:
 * a second line pushes the patients themselves below the fold on a small
 * phone, which is the one thing this screen exists to show.
 */
.chips {
  display: flex;
  gap: 8px;
  padding: 0 var(--pad) 2px;
  margin: 8px 0 2px;
  overflow-x: auto;
  scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }

.chip {
  flex: none;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  white-space: nowrap;
  padding: 7px 13px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
}
.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
/* Nobody on the day matches it, so it is still readable but plainly not
   worth tapping - taking it away instead would make the row jump about as
   the day changes. */
.chip:disabled { color: var(--muted); opacity: .55; cursor: default; }

/* ---------------------------------------------------------------------
 *  What is still owed
 * ---------------------------------------------------------------------
 * The same card as a patient on the day's list - ring, strip and all - with
 * the balance in red where the amount collected usually sits, because the
 * list is read for one number. It used to be bare rows straight on the
 * screen, edge to edge with no gap between them, under a red edge 1px wide.
 */
.cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px var(--pad) 0;
}
.amt.due { color: var(--high); font-weight: 620; }
.paid-of {
  font-family: var(--num);
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------
 *  Skeletons
 * ---------------------------------------------------------------------
 * A grey block the shape of what is coming, instead of the word "Loading…".
 * The screen then does not jump when the rows arrive, and a slow lab PC
 * looks slow rather than broken.
 */
.skel {
  background: var(--sunk);
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  animation: skel-pulse 1.5s ease-in-out infinite;
}
.skel-row { height: 62px; margin: 10px var(--pad) 0; }
@keyframes skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }

/* ---------------------------------------------------------------------
 *  The week behind the figure
 * ---------------------------------------------------------------------
 */
.spark {
  display: block;
  width: 100%;
  height: 62px;
  margin-top: 10px;
}
.spark .bar { fill: var(--accent); opacity: .3; }
.spark .bar.last { opacity: 1; }
.spark text {
  font-family: var(--ui);
  font-size: 7px;
  fill: var(--muted);
}

.delta {
  margin-top: 4px;
  font-size: 12.5px;
  font-weight: 550;
}
.delta.up { color: var(--ok); }
.delta.down { color: var(--muted); }

/* Somewhere for the week's chart to land. On the Day view its numbers are a
   second request, so the box goes in with the rest of the card and fills
   itself in - otherwise the chart arrives below the patient count on one
   view and above it on the other. */
.chart:empty { display: none; }

/* ---------------------------------------------------------------------
 *  Who did what
 * ---------------------------------------------------------------------
 * Four lines on the report screen: registered, last edited, report printed,
 * bill printed - each with the name of whoever did it. The desktop says the
 * same thing in one line under its top strip.
 */
.who {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: baseline;
  padding: 11px 16px;
  border-top: 1px solid var(--border-soft);
}
.who-what { font-size: 14px; color: var(--muted); }
.who-by { text-align: right; font-size: 14.5px; }
.who-by b { font-weight: 600; }
.who-at {
  display: block;
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 2px;
}
.who-none { color: var(--muted); font-size: 13.5px; }

/* ---------------------------------------------------------------------
 *  Today, before anything is opened
 * ---------------------------------------------------------------------
 * Six figures, four ways in, and the last few patients this phone looked
 * at. The whole screen is built from the two requests the app already
 * makes for the patients tab, so it costs the lab PC nothing extra.
 *
 * Two columns rather than three: six tiles at three across put the figures
 * at a size a phone held at arm's length across a counter cannot read.
 */
#home { padding: 12px var(--pad) 0; }

.tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.tile {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  padding: 13px 13px 12px;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.tile:active { transform: scale(.988); }

.tile-glyph {
  width: 30px;
  height: 30px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  margin-bottom: 9px;
}
.tile-glyph svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.9; }

.tile-value {
  font-size: 23px;
  font-weight: 640;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.tile-label { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

.glyph-accent { background: var(--accent-soft); color: var(--accent); }
.glyph-ok { background: var(--ok-soft); color: var(--ok); }
.glyph-low { background: var(--low-soft); color: var(--low); }
.glyph-high { background: var(--high-soft); color: var(--high); }

/* A heading for a group on the home screen. Lighter than .sec-title, which
   belongs to a card - these sit on the ground between cards. */
.home-label {
  font-size: 12px;
  font-weight: 680;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 20px 0 9px;
}
.home-label.with-link { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.home-label .linkish { text-transform: none; letter-spacing: 0; font-size: 12.5px; padding: 0; }

.quick { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }

.quick-item {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 13px;
  box-shadow: var(--lift);
  padding: 11px 4px 9px;
  font: inherit;
  font-size: 11px;
  font-weight: 550;
  line-height: 1.25;
  color: var(--ink-soft);
  cursor: pointer;
}
.quick-item svg {
  display: block;
  margin: 0 auto 6px;
  width: 19px;
  height: 19px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.8;
}
.quick-item:active { transform: scale(.97); }

/* ---------------------------------------------------------------------
 *  What a row offers to do
 * ---------------------------------------------------------------------
 * The card is the border and the shadow now; the row inside it gives both
 * up, or a patient would sit in two boxes. Four things in one strip: the
 * results, the money, sending it, and ringing them - all of which used to
 * need the patient opening first.
 */
.pcard {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  box-shadow: var(--lift);
  overflow: hidden;
}
.pcard .row {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}

.acts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--border-soft);
}

.rowact {
  font: inherit;
  font-size: 11.5px;
  font-weight: 550;
  color: var(--ink-soft);
  background: none;
  border: 0;
  padding: 9px 2px 10px;
  cursor: pointer;
}
.rowact + .rowact { border-left: 1px solid var(--border-soft); }
.rowact svg {
  display: block;
  margin: 0 auto 4px;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.8;
}
.rowact.whatsapp svg { stroke: var(--ok); }
.rowact:active { background: var(--sunk); }
.rowact[disabled] { color: var(--muted); opacity: .45; cursor: default; }
.rowact[disabled] svg { stroke: var(--muted); }

/* ---------------------------------------------------------------------
 *  Everything recorded against one patient
 * ---------------------------------------------------------------------
 * A rail down the left with a mark per entry. These are one thing after
 * another and the order is the information, which a plain list of rows
 * does not say.
 */
#history { padding-bottom: 8px; }

.trail { padding: 6px 16px 16px 34px; position: relative; }

.trail::before {
  content: "";
  position: absolute;
  left: 20px;
  top: 14px;
  bottom: 22px;
  width: 2px;
  background: var(--border);
}

.ev { position: relative; padding: 8px 0; }

.ev::before {
  content: "";
  position: absolute;
  left: -18px;
  top: 13px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--border);
}
.ev.latest::before { background: var(--accent); border-color: var(--accent); }

.ev-what { font-size: 14.5px; font-weight: 600; }
.ev-meta {
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}

/* The name as it stood then, shown only when it is not the name now - so a
   line appearing here always means something changed. */
.ev-detail { font-size: 12.5px; color: var(--warm, var(--high)); margin-top: 2px; }

/* The line at the foot of "Who did what" that opens the whole trail. */
.morelink {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  border-top: 1px solid var(--border-soft);
  padding: 13px 16px;
  cursor: pointer;
  text-align: left;
}
.morelink:active { background: var(--sunk); }

/* The share of the month each way of paying accounts for. The amount was
   already there; the percentage is what makes two of them comparable. */
.mode-pct {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--muted);
  text-align: right;
  min-width: 34px;
}
