/* Wellness Platform — console and kiosk.
 *
 * One stylesheet for two audiences with opposite needs. The console is used all
 * day by staff who want density and speed. The kiosk is used once, by someone
 * who may be unwell, holding a tablet at arm's length — so it gets large type,
 * large targets and a lot of air.
 *
 * Both share the palette, the radii and the shadows, so a clinic sees one
 * product rather than two.
 */

:root {
  /* The scale. Measured before it existed: 34 distinct font sizes, 52 distinct
     padding values and 10 radii across this file, none of them wrong on the day
     they were written and none of them agreeing with each other. Two panels
     never quite lined up because they were not built from the same ruler.

     Console surfaces pick from these and invent nothing. The kiosk deliberately
     does not -- it is read once, at arm's length, by somebody who may be unwell,
     and its larger type and 44px targets are sized for that rather than for
     density. Where a kiosk rule sets its own value, that is the reason.

     Six steps each, far enough apart that a step reads as a step. Two roles that
     were .76rem and .82rem collapsed into one: they were never doing different
     jobs, they were written on different days. */
  --t-title: 2rem;        /* a view's own heading, in the display face */
  --t-section: 1.125rem;  /* a panel inside it */
  --t-body: .9375rem;     /* table rows, paragraphs */
  --t-small: .8125rem;    /* buttons, subtitles, help */
  --t-label: .6875rem;    /* column heads and eyebrows, uppercase */
  --t-micro: .6875rem;    /* badges: 11px, and the badge is its only user */

  /* The public site's display face, shared so the product has one voice.

     Headings only, and only on the staff surfaces. The body text stays on
     system-ui for a reason that is measurable rather than conservative: this
     console asks for seven weights -- 400, 500, 550, 600, 650, 700 and 800 --
     and gets them from a variable system font. Instrument Serif and IBM Plex
     Sans are here as static files at 400, 500 and 600, so swapping the body
     face would collapse `button` (550), `h2` (650), `h1` (700) and `.ace-name`
     (800) onto a single weight and flatten the hierarchy this surface uses to
     be scannable. That is a design pass with its own measuring, not a token
     swap.

     The kiosk is excluded by name. A display serif has fine strokes, and the
     tablet is read at arm's length by somebody who may be unwell. */
  --display: 'Instrument Serif', Georgia, serif;

  /* Eight steps, not six. The first cut had 4/8/12/20/32/52 and the console
     would not sit on it: measuring every absolute padding against it forced a
     4px move in seven places, all of them clustering at 16 and 24, which is
     where a dense table's rhythm actually lands. Named in pixels rather than
     numbered, so the next value that does not fit is obvious rather than
     arriving as a ninth step called --s7.

     `em` padding is deliberately not on this scale: a button, a badge, a notice
     and a toast pad in proportion to their own text, which is what keeps a
     small control small. Converting those to fixed pixels would break the one
     relationship they were written to hold. */
  --sp-4: 4px;
  --sp-8: 8px;
  --sp-12: 12px;
  --sp-16: 16px;
  --sp-20: 20px;
  --sp-24: 24px;
  --sp-32: 32px;
  --sp-52: 52px;

  /* Outside the text scale, on purpose and by name:
       --t-display  a pairing code read across a room by somebody at the front
                    desk, not UI text sized for reading at a desk;
       .ace-tag     the footer wordmark, which is a logo lockup with its own
                    responsive sizes rather than a piece of interface;
       .scale-btn   the kiosk's 1-10 scale, sized for a tablet at arm's length.
     Anything else that wants a size outside the six steps should join this
     list with a reason, rather than quietly inventing a ninth. */
  --t-display: 2.4rem;

  --r-sm: 4px;            /* inputs, buttons, badges */
  --r-md: 8px;            /* panels and cards */
  --r-lg: 12px;           /* the larger surfaces: modals, the plan card */
  --r-pill: 999px;

  /* Three steps and a pill, and there were briefly two systems: --r-* was added
     here while --radius (12px) and --radius-sm (9px) were left in place and
     still used eight times. That is the drift this scale exists to remove,
     introduced by the change that removed it. --radius-sm folded into --r-md,
     one pixel smaller; --radius became --r-lg unchanged. */

  /* surfaces */
  --bg: #eef2f8;
  --panel: #ffffff;
  --panel-2: #f8fafd;
  --line: #dde4ee;
  --line-soft: #e9eef5;

  /* text */
  --ink: #0f1729;
  --ink-2: #33405a;
  --muted: #5f6b81;

  /* brand and states */
  --brand: #1d4e7f;
  /* The backdrop's vitals trace. It is drawn on a canvas, which cannot read a
     CSS variable by itself, so app.js reads this one and redraws when the theme
     attribute changes. It was a hardcoded `rgba(108, 171, 230, .17)` -- a blue
     chosen against the dark ground -- and it stayed that colour when somebody
     switched to light, where it composited to 1.13:1 against the page and was
     effectively gone. Every other colour on this surface comes from a token;
     this one was the exception, and it was the only hardcoded colour in
     app.js. */
  --trace: rgba(29, 78, 127, .17);
  --brand-ink: #123f68;
  --brand-deep: #16405f;
  --brand-soft: #e8f0f8;
  --ok: #0b7a4b;
  --ok-soft: #e4f4ec;
  --ok-line: #b5ddc8;
  --warn: #8a5a05;
  --warn-soft: #fdf3e3;
  --warn-line: #f0d9b8;
  --bad: #a32020;
  --bad-soft: #fbeaea;
  --bad-line: #eec6c6;

  /* Derived surfaces and inks. Every colour the console paints comes from a
     token, so the dark palette below is the only other place a colour lives. */
  --field: #ffffff;            /* the surface of an input or a plain button */
  --on-brand: #ffffff;         /* text on a filled brand, ok or danger button */
  --line-strong: #c6d0e0;      /* a border under the pointer */
  --brand-line: #cadcee;
  --neutral-soft: #eef0f4;
  --ink-ok: #085f3a;
  --ink-warn: #7a4705;
  --ink-bad: #7d2020;
  --ok-deep: #096139;
  --bad-deep: #7d1a1a;
  --bad-line-strong: #e0adad;
  /* The select arrow. The whole url() is the token: a var() written inside a
     data URI is never substituted, it just reaches the SVG parser as text. */
  --signin-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='60' viewBox='0 0 300 60'%3E%3Cg fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 40 h74 l9-17 10 34 11-46 10 41 8-12 h40' opacity='.5'/%3E%3Cpath d='M186 40 h110' opacity='.18'/%3E%3C/g%3E%3Cpath fill='%23000' d='M170 8c0 0 12 15 12 21a12 12 0 0 1-24 0c0-6 12-21 12-21z' opacity='.85'/%3E%3C/svg%3E");
  /* The ACE mark, drawn once. A spade inside an open crest, with the power ring
     and two traces knocked out of it -- the detail the full logo carries in its
     wordmark is dropped, because at footer size it is texture rather than
     letters, and the wordmark beside it already says the name. Black on nothing:
     a CSS mask reads the alpha, so the shape is what shows and the colour comes
     from whatever paints it. */
  /* The real ACE mark, as an alpha mask rather than a picture. Derived from the
     monochrome logo in ~/Desktop/Logos (CBBA33DE...): black artwork on white,
     so luminance inverts straight into alpha. Kept as a mask and not an <img>
     so the footer stays one colour with the rest of the page and follows the
     theme -- a full-colour gold logo would be the only saturated thing on a
     console screen, and would sit wrong on the dark theme.

     None of the supplied PNGs carry real transparency (all colour type 2; the
     checkerboard in the "transparent" one is painted into the pixels), which is
     why this is generated rather than used directly. Regenerate by thresholding
     the same source; 176px square is ~4x the 44px it renders at. */
  --ace-mark: url("/ace-mark.png");

  /* One droplet across the product. This token carried an older, squatter
     shape -- 18x24 against the 24x36 the public site and `--signin-mark`
     already use -- so the console's own wordmark was a different logo from
     the one on the page that sends people to it. Same path now. */
  --droplet: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='36' viewBox='0 0 24 36'%3E%3Cpath fill='%23000' d='M12 1c0 0 12 15 12 21a12 12 0 0 1-24 0c0-6 12-21 12-21z'/%3E%3C/svg%3E");
  --caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%235f6b81' d='M1.4 0 6 4.6 10.6 0 12 1.4 6 7.4 0 1.4z'/%3E%3C/svg%3E");


  --shadow: 0 1px 2px rgba(15, 30, 55, .05), 0 8px 24px -12px rgba(15, 30, 55, .18);
  --shadow-lg: 0 4px 12px rgba(15, 30, 55, .08), 0 24px 48px -24px rgba(15, 30, 55, .28);
  --ring: 0 0 0 3px rgba(29, 78, 127, .22);

  --sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  color-scheme: light;
  accent-color: var(--brand);
}

/* ---------------------------------------------------------------- dark
 *
 * Declared once, and applied only when theme.js has put data-theme="dark" on
 * <html> -- which it does before the first paint, from the operator's saved
 * choice or from the system setting. Nothing here is inside a media query, so
 * there is one copy of these values and no second copy to drift from it.
 *
 * The kiosk never sets the attribute. A questionnaire handed to somebody in a
 * treatment room stays light whatever the tablet's own setting is.
 *
 * The state hues are lifted rather than re-chosen: green still means approved
 * and red still means rejected, at the lightness a dark surface needs to keep
 * them legible.
 */
:root[data-theme="dark"] {
  --bg: #0c1320;
  --panel: #131c2b;
  --panel-2: #1a2434;
  --line: #273246;
  --line-soft: #1f2937;
  --line-strong: #3a4860;

  --ink: #e9eef7;
  --ink-2: #c2ccdc;
  --muted: #8b98af;

  --brand: #6cabe6;
  --trace: rgba(108, 171, 230, .17);
  --brand-ink: #9cc8f0;
  --brand-deep: #8ec0ee;
  --brand-soft: #16283c;
  --brand-line: #2c4763;

  --ok: #46c98c;
  --ok-soft: #0f2b20;
  --ok-line: #23583f;
  --warn: #dfa94e;
  --warn-soft: #2b2110;
  --warn-line: #59431f;
  --bad: #ef8383;
  --bad-soft: #2d1717;
  --bad-line: #5c2c2c;

  --field: #182231;
  /* A light accent carries dark text, the other way round from the light theme. */
  --on-brand: #0b1420;
  --neutral-soft: #212b3a;
  --ink-ok: #7fdcb0;
  --ink-warn: #edc27f;
  --ink-bad: #f4a8a8;
  --ok-deep: #62d6a0;
  --bad-deep: #f7bcbc;
  --bad-line-strong: #6e3636;
  --caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%238b98af' d='M1.4 0 6 4.6 10.6 0 12 1.4 6 7.4 0 1.4z'/%3E%3C/svg%3E");

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px -12px rgba(0, 0, 0, .6);
  --shadow-lg: 0 4px 12px rgba(0, 0, 0, .45), 0 24px 48px -24px rgba(0, 0, 0, .75);
  --ring: 0 0 0 3px rgba(108, 171, 230, .32);

  color-scheme: dark;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: 15px/1.55 var(--sans);
  color: var(--ink);
  /* A ground rather than a flat fill. One gradient falling away under the
     chrome, so the header reads as attached to the top of the window and the
     work floats below it. Painted once; costs no time on any pass. */
  background:
    linear-gradient(180deg, rgba(29, 78, 127, .16) 0, rgba(29, 78, 127, 0) 320px),
    var(--bg);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3 { margin: 0 0 .45em; line-height: 1.22; letter-spacing: -.011em; }
h1 { font-size: var(--t-title); font-weight: 600; }
h2 { font-size: var(--t-section); font-weight: 600; }
/* A view's own title, and only that.

   Instrument Serif ships at 400 only, so this carries no weight of its own --
   asking for 700 would get a synthesised bold, which smears at a heading's
   size. At --t-title it does not need one: 24px of serif has presence and
   reads as the title of the page.

   `h2` was given the same treatment and taken back after looking at it. A
   panel heading is --t-section, 18px, and 18px of 400-weight serif came out
   LIGHTER than the body text underneath it -- the heading stopped leading its
   own panel, and the uppercase column heads below had more presence than the
   title above them. A nurse scans down a long page by those headings. They
   keep their 650.

   Tracking goes back to none: -.011em is tuned for a system sans and pinches a
   serif. */
/* The one place this pass spends rather than cuts.
 *
 * The title was 1.5rem, which is small for a display serif -- at that size it
 * was competing with the body text rather than leading it. 2rem gives it the
 * scale it can carry, and the room above it does as much as the size: a serif
 * this quiet earns its authority from what surrounds it.
 *
 * Still 400, still once per screen. Instrument Serif ships at one weight, and
 * appearing exactly once is what keeps it meaning something. */
body:not(.kiosk) h1 {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.16;
  margin-bottom: .5em;
}
/* A view title that stands above the panels rather than inside the first one.
   Most screens put their h1 in the leading panel; the patients list opens on
   the stat row and clinic settings on a form, so theirs sit clear of both. */
.view-title { margin: 0 0 .8rem; }

/* A heading inside a panel is a label, not a third title. `Add an account` was
   1.18rem under a 1.35rem panel heading, which is two headings competing rather
   than one leading. */
h3 {
  font-size: var(--t-label); font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .08em;
}
p { margin: 0 0 .85em; }
a { color: var(--brand); text-underline-offset: 2px; }
a:hover { color: var(--brand-ink); }
hr { border: none; border-top: 1px solid var(--line); margin: 1rem 0; }

/* ---------------------------------------------------------------- controls */

button {
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--field);
  color: var(--ink);
  border-radius: var(--r-md);
  padding: .55em 1em;
  line-height: 1.35;
  transition: background .13s ease, border-color .13s ease,
              box-shadow .13s ease, transform .06s ease;
}
button:hover:not(:disabled) { background: var(--panel-2); border-color: var(--line-strong); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: .5; cursor: not-allowed; }

button.primary {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand);
}
button.primary:hover:not(:disabled) { background: var(--brand-deep); border-color: var(--brand-deep); }
button.ok { background: var(--ok); border-color: var(--ok); color: var(--on-brand); }
button.ok:hover:not(:disabled) { background: var(--ok-deep); border-color: var(--ok-deep); }
button.danger { background: var(--field); border-color: var(--bad-line); color: var(--bad); }
/* The remove control on an account row. A link-button like its neighbours so
   the row keeps one rhythm, but in the danger ink and last, because it is the
   only one of the three that cannot be undone. Sized for a pointer on a dense
   table rather than for a tablet: this is a console row, not a kiosk card. */
.link.danger { color: var(--bad); }
.link.danger:hover { color: var(--bad-deep); }
.account-remove {
  font-size: 1.15rem; line-height: 1; padding: var(--sp-4) var(--sp-8); margin-left: .1rem;
}
button.danger:hover:not(:disabled) { background: var(--bad-soft); border-color: var(--bad-line-strong); }

/* A ghost button. These sit in rows of three or four beside a record, where an
   underline on each one reads as a paragraph of links rather than as controls. */
button.link {
  border: none; background: none; color: var(--brand);
  padding: .3em .5em; font-weight: 500; border-radius: 6px;
}
button.link:hover:not(:disabled) { background: var(--brand-soft); color: var(--brand-ink); }
button.link:active:not(:disabled) { transform: none; }
button.danger-link { color: var(--bad); }
button.danger-link:hover:not(:disabled) { background: var(--bad-soft); color: var(--bad-deep); }

input, select, textarea {
  font: inherit;
  color: inherit;
  padding: var(--sp-8) var(--sp-12);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--field);
  width: 100%;
  transition: border-color .13s ease, box-shadow .13s ease;
}
input:hover, select:hover, textarea:hover { border-color: var(--line-strong); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: var(--ring);
}
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
button:focus-visible { box-shadow: var(--ring); }
textarea { min-height: 4.5em; resize: vertical; font-family: inherit; }
select {
  appearance: none;
  background-image: var(--caret);
  background-repeat: no-repeat;
  background-position: right .75em center;
  padding-right: 2.2em;
}
label {
  display: block; font-weight: 600; font-size: var(--t-small);
  margin-bottom: .35em; color: var(--muted);
  letter-spacing: .01em;
}

/* ---------------------------------------------------------------- layout */

/* Panels are separated by their border, not by a shadow. Shadow is reserved for
   the things that genuinely float above the page -- the modal, and the live
   banner that interrupts. When everything is raised, nothing is. */
.panel {
  background: linear-gradient(180deg, var(--panel-2) 0, var(--panel) 92px);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-20);
  margin-bottom: var(--sp-16);
  overflow-x: auto;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .025) inset,
              0 18px 40px -30px rgba(0, 0, 0, .85);
}
/* A panel's own heading sits on the lighter band at its top, with a hairline
   under it, so a panel reads as a titled thing rather than a box with text. */
.panel > h2:first-child {
  margin: calc(var(--sp-4) * -1) 0 var(--sp-16);
  padding-bottom: var(--sp-12);
  border-bottom: 1px solid var(--line-soft);
}

.muted { color: var(--muted); }
.small { font-size: var(--t-small); }
/* A block that is a different act from the one above it -- a form under a
   table, most often. A rule and a full step of space, so the eye stops. */
.after-rule {
  margin-top: var(--sp-32);
  padding-top: var(--sp-20);
  border-top: 1px solid var(--line);
}
.row { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.grid { display: grid; gap: .85rem; }
.grid.two { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.stack > * + * { margin-top: .65rem; }
.hidden { display: none !important; }

/* Three weights, not seven.
 *
 * This carried 400, 500, 550, 600, 650, 700 and 800. None of those was wrong on
 * its own; together they read as a surface adjusted a decision at a time rather
 * than designed, which is the difference between restraint and plainness. They
 * collapse to 400 regular, 500 for a quiet emphasis and 600 for a heading, and
 * the hierarchy that is left comes from size and space.
 *
 * Checked before collapsing rather than after: nothing bold sat between 18.66px
 * and 24px, which is the only band where WCAG counts text as large BECAUSE it
 * is bold. Everything at 700 was either already large by size -- h1 at 24, the
 * stat figure at 32, the pairing code at 38 -- or already small enough to need
 * the full 4.5:1 either way. So no contrast threshold moved, and the sweep
 * afterwards confirmed it rather than assuming it.
 *
 * `body:not(.kiosk) h1` stays at 400 and is untouched: Instrument Serif ships
 * at that weight only, and asking for more would get a synthesised bold.
 */

/* Depth is for things that float, and almost everything here already was.
 *
 * The first pass at this assumed seventeen shadows meant seventeen problems.
 * Reading them said otherwise: the focus rings are functional, the kiosk's
 * selection states are functional, and the modal, the toast and the sign-in
 * panel genuinely sit above the page, where a shadow is what says so.
 *
 * Two did not earn it, and both are in normal flow with a border and a fill
 * already separating them: the dashboard tiles and the live banner. A hairline
 * and some space were doing the work; the shadow underneath was decoration
 * pretending to be structure.
 */

/* A badge says the status; it does not shout it.
 *
 * `governance.py` has always stored these as "Draft", "Under review" and
 * "Returned for updates" -- sentence case, on the server, correctly. One line
 * of CSS was overriding that into RETURNED FOR UPDATES, which is the widest
 * thing in a review queue row and competes with the reference code beside it,
 * at exactly the moment a nurse is scanning for what needs them.
 *
 * Uppercase was buying something real, though: at 10px, caps are easier to read
 * than lowercase. So the size went up rather than the case staying. 11px in
 * sentence case is more legible than 10px in caps AND still narrower than what
 * it replaced -- which is why this stopped being a trade-off.
 *
 * `--t-micro` moved rather than the badge overriding it: the badge was its only
 * user. The thirteen other uppercase rules -- column heads, eyebrows -- are
 * untouched, because a column head is a label and a badge is a value.
 *
 * Tracking dropped from .035em to .01em. Positive tracking is what makes caps
 * readable; lowercase does not want it.
 */

/* `strong` is emphasis, not a fourth weight.
 *
 * The stylesheet declared three weights and the browser rendered four: every
 * `<strong>` fell back to the user-agent default of 700, which on this console
 * is 252 nodes -- therapy names, month labels, a subject's reference in a row.
 * The heaviest text on several screens was a weight nobody had chosen.
 *
 * Found by measuring what rendered rather than what was written. Reading the
 * stylesheet said three; the page said four.
 */
strong, b { font-weight: 600; }

/* ---------------------------------------------------------------- badges */

.badge {
  display: inline-block;
  padding: .2em .65em;
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: .01em;
  border: 1px solid transparent;
  vertical-align: 2px;
  white-space: nowrap;
}
/* Sentence case, whoever wrote the string.
 *
 * Uppercase was hiding a split: the server's labels arrive from
 * `governance.py` already capitalised -- "Draft", "Under review" -- while
 * fifteen badge strings hard-coded in app.js are lowercase: "live", "locked",
 * "resolved", "sign-off", "not offered". With the transform gone that read as
 * "Approved" beside "approved" in the same component.
 *
 * `::first-letter` rather than fifteen edits, and rather than `capitalize`,
 * which would give "Not Offered" and "Sign-Off". It applies because a badge is
 * `inline-block`, which is a block container; on a plain inline it would do
 * nothing. A badge starting with a digit -- "1 draft" -- is simply unaffected.
 */
.badge::first-letter { text-transform: uppercase; }

.badge + .badge { margin-left: .3em; }
.badge.draft { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge.under_review { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--brand-line); }
.badge.returned { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge.approved { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.badge.active { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.badge.rejected { background: var(--bad-soft); color: var(--bad); border-color: var(--bad-line); }
.badge.superseded { background: var(--neutral-soft); color: var(--muted); border-color: var(--line); }
/* A chip that carries a number or a name rather than a state. Green and red
   mean approved and rejected everywhere else in this product; nothing that is
   not a clinical state should borrow them. */
.badge.neutral { background: var(--neutral-soft); color: var(--ink-2); border-color: var(--line); }

/* Clinic account states in the owner console. */
.badge.acct-active { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-line); }
.badge.acct-trial { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--brand-line); }
.badge.acct-past_due { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge.acct-suspended { background: var(--bad-soft); color: var(--bad); border-color: var(--bad-line); }
.badge.acct-cancelled { background: var(--neutral-soft); color: var(--muted); border-color: var(--line); }

/* ---------------------------------------------------------------- notices */

.notice {
  padding: .8em 1em;
  border-radius: var(--r-md);
  border: 1px solid;
  margin-bottom: .85rem;
  line-height: 1.5;
}
.notice:last-child { margin-bottom: 0; }
.notice.error { background: var(--bad-soft); border-color: var(--bad-line); color: var(--ink-bad); }
.notice.ok { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ink-ok); }
.notice.warn { background: var(--warn-soft); border-color: var(--warn-line); color: var(--ink-warn); }
.notice.info { background: var(--brand-soft); border-color: var(--brand-line); color: var(--brand-ink); }
.notice.live {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.notice.waiting { display: flex; gap: .85rem; align-items: flex-start; text-align: left; }

/* ---------------------------------------------------------------- tables */

/* Figures line up down a column, everywhere a column exists.
 *
 * `.exact` already did this for reference codes and the outcome comparison.
 * Ordinary tables did not, so a version number, a visit count or a timestamp
 * drifted a pixel or two per row -- which is the commonest sign that nobody
 * looked closely, on exactly the screens somebody reads down: the audit trail,
 * questionnaire history, the patients list.
 *
 * Scoped to tables on purpose. Tabular figures are slightly wider and are for
 * reading down, not along; in a sentence they are the wrong choice. */
table {
  width: 100%; border-collapse: collapse; font-size: var(--t-body);
  font-variant-numeric: tabular-nums;
}
/* One row height, set by the row and not by whichever cell happens to be
   tallest. `vertical-align: top` let a three-role account stand a head above a
   one-role account beside it, and left every control floating at the top of a
   cell it did not fill. */
th, td {
  text-align: left;
  padding: var(--sp-8) var(--sp-12);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}
th {
  font-size: var(--t-label); text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 600;
  padding-top: 0;
  border-bottom-color: var(--line);
  white-space: nowrap;
}
/* A cell holding a value somebody types exactly -- a login, a reference code,
   a timestamp. Tabular figures so columns of them line up. */
td.exact, .exact {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
/* Controls belong at the end of the row, together, right-aligned, with the one
   that cannot be undone set apart from the ones that can. */
td.row-actions { text-align: right; white-space: nowrap; }
th.row-actions { text-align: right; }
.row-actions .act-split {
  display: inline-block; width: 1px; height: 14px; vertical-align: -3px;
  background: var(--line); margin: 0 var(--sp-8) 0 var(--sp-4);
}
tbody tr { transition: background .1s ease; }
tbody tr:hover { background: var(--panel-2); }
tbody tr:last-child td { border-bottom: none; }

/* ---------------------------------------------------------------- console */

/* The header is a surface, not a banner: one flat plane, a hairline, and the
   brand carried by the mark and the wordmark rather than by a field of colour.
   The tab strip sits directly under it on the same surface, so the two read as
   a single piece of chrome. */
.topbar {
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .03) inset,
              0 10px 30px -22px rgba(0, 0, 0, .9);
  padding: var(--sp-16) var(--sp-24);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.topbar .brand {
  font-weight: 600; font-size: 1.02rem; letter-spacing: -.005em;
  display: flex; align-items: center; gap: .5rem;
}
/* The droplet from the kiosk header, at wordmark size. Drawn as a mask so it
   takes the brand colour from the palette and follows it into dark. */
.topbar .brand::before {
  content: "";
  width: 13px; height: 17px; flex: none;
  background: var(--brand);
  -webkit-mask: var(--droplet) center / contain no-repeat;
  mask: var(--droplet) center / contain no-repeat;
}
.topbar .who { font-size: .83rem; color: var(--muted); margin-top: .15rem; }

/* The control panel wears a band where the console wears a surface.
 *
 * The console is a workplace: staff are in it all day, and a field of colour
 * across the top of every screen becomes noise. The control panel is a single
 * page that runs the platform, opened once and then left alone, and on Windows
 * it is the whole application -- what the Mac shows as a native window with a
 * blue header. So it announces itself the way that window does, and the two
 * builds look like one product.
 *
 * Scoped to .brandbar rather than changed on .topbar, so the console keeps the
 * flat surface it was designed with.
 */
.topbar.brandbar {
  background: var(--brand);
  color: #ffffff;
  border-bottom: 3px solid var(--brand-deep);
  padding: var(--sp-20) var(--sp-24);
  align-items: flex-start;
}
.topbar.brandbar .brand { font-size: 1.35rem; letter-spacing: -.01em; }
/* The mark is painted, not tinted: on the band it reads as white. */
.topbar.brandbar .brand::before {
  background: #ffffff;
  width: 16px; height: 21px;
}
.topbar.brandbar .who { color: #b9d0e6; font-size: .86rem; margin-top: .3rem; }

/* The status chip, on the band. A filled dot rather than a coloured box: it is
   read from across a room, where a word in a tint is not. */
.topbar.brandbar .badge {
  background: #2c6394;
  border-color: #2c6394;
  color: #ffffff;
  font-size: var(--t-small);
  padding: .34em .8em .34em .62em;
  display: inline-flex;
  align-items: center;
  gap: .42em;
  /* Sentence case here, not the uppercase every other badge uses: this is the
     one word that says whether the platform is up, and it is the same word the
     Mac window shows. Badges elsewhere label a state inside a table. */
  text-transform: none;
  letter-spacing: 0;
}
.topbar.brandbar .badge::before {
  content: "";
  width: 7px; height: 7px; border-radius: 999px; flex: none;
  background: var(--ok);
}
.topbar.brandbar .badge.rejected::before { background: var(--bad); }
.topbar.brandbar .badge.draft::before,
.topbar.brandbar .badge.under_review::before { background: var(--warn); }
/* Which build this is. Small, permanent, and the first thing a remote support
   session needs to know. */
.topbar .build {
  font-size: var(--t-label); font-weight: 500; color: var(--muted);
  margin-left: .45rem; letter-spacing: .01em; font-family: var(--mono);
}
.topbar .row button { font-weight: 500; }

.tabs {
  display: flex; gap: var(--sp-4); padding: var(--sp-8) var(--sp-24);
  background: linear-gradient(180deg, var(--panel), var(--bg));
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
  position: sticky; top: 0; z-index: 10;
}
.tabs button {
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: none;
  padding: var(--sp-8) var(--sp-12);
  color: var(--muted);
  font-weight: 600;
  font-size: var(--t-body);
}
.tabs button:hover:not(:disabled) { background: transparent; color: var(--ink-2); }
/* The active tab is lit rather than underlined: a filled pill in the brand
   ink, which is legible at a glance from across a desk. */
.tabs button.active {
  color: var(--brand);
  background: var(--brand-soft);
  border-color: var(--brand-line);
}
.tabs button:hover:not(.active) { background: var(--panel-2); color: var(--ink-2); }

/* The 4rem tail is a page-bottom gutter, not a rhythm step: it exists so the
   last panel is not flush against the viewport edge on a short screen. */
main { max-width: 1080px; margin: 0 auto; padding: var(--sp-24) var(--sp-20) 4rem; }

/* The floor is the widest label, not a round number.
   
   `Questionnaires` is one unbreakable word: 110px at this size, plus 32px of
   tile padding. The floor was 124px, so below about 800px of viewport -- an
   iPad held upright, or a browser window that is not maximised -- the tiles
   squeezed past it and `.stat`'s `overflow: hidden` cut the word to
   QUESTIONNAIR with nothing to say it had been cut. The grid was always
   willing to wrap to a second row; it was just never allowed to.
   
   `overflow-wrap` on the label is the backstop for the next label somebody
   adds. A word broken across two lines is ugly and readable; a word silently
   truncated is neither. */
.stat-row {
  display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(146px, 1fr));
  margin-bottom: 1rem;
}
.stat {
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--sp-16);
}
/* The accent every tile wears: a short rule in the brand ink, top-left. */
.stat::before {
  content: ""; position: absolute; left: var(--sp-16); top: 0;
  width: 26px; height: 2px; background: var(--brand); opacity: .85;
}
.stat .n {
  font-size: 2rem; font-weight: 600; color: var(--brand);
  line-height: 1.05; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.stat .k {
  font-size: var(--t-label); text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); font-weight: 600; margin-top: .2rem;
  overflow-wrap: anywhere;
}
/* A tile holding work that is waiting for somebody. Not decoration: it is the
   difference between a console that reports and one that tells you what to do
   next. It only ever appears on a non-zero count, so a quiet clinic sees a
   quiet page. */
.stat.needs {
  background: linear-gradient(160deg, var(--warn-soft), var(--panel));
  border-color: var(--warn-line);
}
.stat.needs::before { background: var(--warn); width: 44px; opacity: 1; }
.stat.needs .n { color: var(--ink-warn); }
.stat.needs .k { color: var(--warn); }

/* A tile that goes somewhere.
   Same face as one that does not: the affordance is the cursor, the hover and
   the focus ring, not a different look. Five tiles of which two are visibly
   "buttons" and three are not reads as two kinds of number, which is a
   distinction about plumbing rather than about the clinic.
   The base button rules have to be answered explicitly -- `button` sets its
   own padding, background and centred text, and `button:hover` is specific
   enough to flatten the gradient these tiles are drawn with. */
button.stat {
  display: block; width: 100%; text-align: left;
  font: inherit; color: inherit;
}
button.stat:hover:not(:disabled) {
  background: linear-gradient(160deg, var(--panel), var(--panel-2));
  border-color: var(--line-strong);
}
button.stat.needs:hover:not(:disabled) {
  background: linear-gradient(160deg, var(--warn-soft), var(--panel-2));
  border-color: var(--warn-line);
}

/* An empty panel is the one place on this console with room to spare, so it
   is the one place the mark can appear. The moment there is a row, the empty
   state is gone and so is this. */
.empty-state { position: relative; }
.empty-state::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 170px; height: 240px; transform: translate(-50%, -54%);
  background: var(--brand); opacity: .05; pointer-events: none;
  -webkit-mask: var(--drop-mark) center / contain no-repeat;
  mask: var(--drop-mark) center / contain no-repeat;
  --drop-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 36'%3E%3Cpath d='M12 1c0 0 12 15 12 21a12 12 0 0 1-24 0c0-6 12-21 12-21z'/%3E%3C/svg%3E");
}
.empty-state > * { position: relative; }

/* Month cards: used by the provider editor and by the patient's own copy. */
.month-card {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  margin-bottom: .8rem; overflow: hidden; background: var(--panel);
}
.month-head {
  background: var(--brand-soft); padding: .65rem .9rem;
  display: flex; justify-content: space-between; align-items: center;
  gap: .6rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--brand-line);
}
.month-head strong { color: var(--brand-ink); }
.month-body { padding: .9rem; }
.month-focus { font-size: 1.05rem; font-weight: 600; margin-bottom: .45rem; }
.therapy-line {
  display: flex; gap: .55rem; align-items: center;
  padding: var(--sp-8) 0; border-bottom: 1px solid var(--line-soft); flex-wrap: wrap;
}
.therapy-line:last-child { border-bottom: none; }
.therapy-line .name { flex: 1 1 200px; }
.therapy-line input[type=number] { width: 4.6rem; }

/* Editors: one summary row per question or therapy. */
.q-summary {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem; border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--sp-12) var(--sp-16); margin-top: .55rem; background: var(--panel-2);
  transition: border-color .13s ease, background .13s ease;
}
.q-summary:hover { border-color: var(--line-strong); background: var(--panel); }
.q-summary .row { flex: 0 0 auto; }
/* One choice in a question: the value stored against the answer, the words the
   patient reads, whether choosing it clears the rest, and the way to remove it. */
.option-row {
  display: grid; grid-template-columns: 190px 1fr auto auto; gap: .5rem;
  align-items: center; margin-bottom: .45rem;
}
.option-row input { min-width: 0; }
.option-row label { display: flex; align-items: center; gap: .3rem; white-space: nowrap; }

/* A section of the questionnaire: its own heading and settings, then the
   questions it asks. Bordered rather than raised, so the questions inside it
   stay the thing being read. */
.editor-section {
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--sp-16); margin-top: .85rem;
}
.editor-section > .row.between { margin-bottom: .6rem; }
.editor-section > .grid.two { margin-bottom: .55rem; }
.choice-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: .3rem .9rem; margin-top: .35rem;
}

/* Condition builder: each nested group steps in behind its own rule line, so
   the shape of an "all of / any of" reads at a glance without any brackets. */
.cond {
  border-left: 2px solid var(--line);
  padding-left: .75rem;
  margin-top: .5rem;
}
.cond > .row { flex-wrap: wrap; row-gap: .4rem; }
.cond select { width: auto; min-width: 8rem; max-width: 26rem; }
.cond-value.wide { flex: 1 1 100%; }
.cond h3, .cond label { margin: 0; }

/* The one page somebody meets before they are working, and the only console
   surface where presence is affordable: it is crossed once a day, not many
   times a pass, which is the arithmetic that rules ornament out everywhere
   else. So it gets a ground rather than a panel floating on flat grey -- one
   static gradient, no motion, taken from a token so it follows the theme. */
#loginView {
  min-height: 100vh;
  background: radial-gradient(74% 52% at 50% 0%, var(--brand-soft) 0%, transparent 68%);
}
.login-wrap { max-width: 400px; margin: 12vh auto var(--sp-52); padding: 0 var(--sp-20); }
/* Uppercase and quiet, scoped to this form. A field label is a label, and the
   console now says so with the same treatment everywhere it means it -- but
   only here, because the config editors' labels sit beside dense controls
   where the extra tracking would cost more than it gives. */
.login-wrap label {
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: .08em;
  font-weight: 600;
  color: var(--muted);
}
/* The file the site already self-hosts, so nothing new is shipped. A browser
   downloads a webfont only when a rule matches, and the rules that use this one
   are all `body:not(.kiosk)`, so the tablet never fetches it. */
@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/instrument-serif-400-normal.woff2') format('woff2');
}
.login-wrap h1 { font-size: 2rem; line-height: 1.1; }
/* The droplet and vitals trace the platform already wears in its header, given
   the room the sign-in screen has and the header does not. A mask rather than a
   picture, so it takes the brand colour and follows the theme. */
.login-mark {
  height: 52px;
  max-width: 300px;
  margin: 0 auto var(--sp-20);
  background: var(--brand);
  -webkit-mask: var(--signin-mark) center / contain no-repeat;
  mask: var(--signin-mark) center / contain no-repeat;
  opacity: .9;
}
.login-wrap .panel { padding: var(--sp-32); box-shadow: var(--shadow-lg); }

code, pre { font-family: var(--mono); font-size: .85em; }
pre {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--sp-12); overflow: auto;
  white-space: pre-wrap; word-break: break-all;
}
textarea.config {
  min-height: 60vh; font-family: var(--mono);
  font-size: var(--t-small); line-height: 1.5; white-space: pre;
}

/* Overlay for the attestation, version snapshots and the editors. */
.modal {
  position: fixed; inset: 0;
  background: rgba(12, 22, 40, .55);
  backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 4vh 1rem; overflow: auto; z-index: 50;
  animation: fade .14s ease;
}
.modal > .panel {
  max-width: 660px; width: 100%; margin: 0;
  box-shadow: var(--shadow-lg);
  animation: rise .18s ease;
}
/* The rule editor carries a condition builder and needs the extra room. */
.modal > .panel.wide { max-width: 880px; }
@keyframes fade { from { opacity: 0; } }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }

/* ---------------------------------------------------------------- therapy marks
   The category glyph. Small, quiet, and the same mark on the menu and on the
   plan, so a therapy is recognisable in both places. */
/* Flags that have already been dealt with. They stay on the page in full -- a
   clinical record does not hide what was decided -- but folded behind one line,
   because ten settled flags above the plan is four screens of scrolling to
   reach the thing the screen is for. */
.settled-flags > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: var(--t-small);
  padding: .3rem 0;
  list-style-position: inside;
}
.settled-flags > summary:hover { color: var(--ink-2); }
.settled-flags[open] > summary { margin-bottom: .6rem; }

/* What moved between two sittings.

   The colour follows the verdict, never the sign of the number: fatigue falling
   is `ok` and sleep falling is `bad`, and a measure nobody has declared a
   direction for is `flat` rather than guessed at. Getting that backwards on a
   clinical screen would be worse than showing nothing, so the class is decided
   on the server where the direction is known. */
.outcomes { width: 100%; }
.outcomes td, .outcomes th { vertical-align: middle; }
.delta {
  display: inline-block; font-size: var(--t-small); font-weight: 600;
  padding: .12rem .5rem; border-radius: 999px; white-space: nowrap;
  border: 1px solid;
}
.delta.ok   { background: var(--ok-soft);      color: var(--ok);    border-color: var(--ok-line); }
.delta.bad  { background: var(--bad-soft);     color: var(--bad);   border-color: var(--bad-line); }
.delta.flat { background: var(--neutral-soft); color: var(--muted); border-color: var(--line); }

/* Six months on one line. One droplet per scheduled visit, so the shape of a
   plan -- front-loaded, tapering, flat -- is visible before any scrolling. */
.plan-rail {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: .5rem; align-items: end;
  padding: .9rem .2rem .2rem;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: .9rem;
}
.plan-rail .mo { text-align: center; min-width: 0; }
.plan-rail .drops {
  display: flex; flex-wrap: wrap; gap: 2px; justify-content: center;
  color: var(--line-strong); min-height: 12px; margin-bottom: .35rem;
}
.plan-rail .mo.on .drops { color: var(--brand); }
.plan-rail .n {
  font: 600 .68rem var(--mono); letter-spacing: .06em;
  color: var(--muted); text-transform: uppercase;
}
.plan-rail .mo.on .n { color: var(--brand); }
.plan-rail .v { font-size: .74rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.rail-note { margin: -.35rem 0 .2rem; }
@media (max-width: 560px) {
  .plan-rail { grid-template-columns: repeat(3, 1fr); row-gap: .8rem; }
}

/* ---------------------------------------------------------------- empty lists
   A drawing, what would be here, and what to do about it. The cleared review
   queue is the case that matters most: it is good news, and as a bare line of
   grey text it read like a failure. */
.empty-state { text-align: center; padding: 1.9rem 1rem 1.6rem; }
.empty-state .lead {
  margin: 0 0 .2rem; font-weight: 600; font-size: var(--t-body); color: var(--ink);
}
.empty-state .small { margin: 0; }
.spot { color: var(--brand); opacity: .7; margin-bottom: .75rem; line-height: 0; }
.spot svg { display: inline-block; }
.spot.ok { color: var(--ok); opacity: .85; }
.empty-state .lead + .small { max-width: 34ch; margin: 0 auto; }

/* ---------------------------------------------------------------- toasts
 *
 * Transient feedback -- saved, published, failed -- used to be written into a
 * banner above the page, which pushed the whole view down at the moment
 * somebody was reading it. It arrives in the corner instead, and leaves on its
 * own. Anything that describes a standing condition rather than an event, like
 * a suspended account, is still a notice inside the page where it cannot be
 * missed or dismissed.
 */
.toast-stack {
  position: fixed; z-index: 60;
  right: 1rem; bottom: 1rem;
  display: flex; flex-direction: column; align-items: flex-end; gap: .5rem;
  max-width: min(26rem, calc(100vw - 2rem));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .7em .8em .7em .9em;
  border: 1px solid var(--line);
  border-left: 3px solid var(--muted);
  border-radius: var(--r-md);
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  font-size: var(--t-body); line-height: 1.45;
  animation: toast-in .18s ease;
}
.toast.ok { border-left-color: var(--ok); }
.toast.error { border-left-color: var(--bad); }
.toast.warn { border-left-color: var(--warn); }
.toast.info { border-left-color: var(--brand); }
.toast .dismiss {
  border: none; background: none; color: var(--muted);
  padding: 0 .15em; font-size: 1.05rem; line-height: 1.2; border-radius: 4px;
}
.toast .dismiss:hover:not(:disabled) { background: var(--panel-2); color: var(--ink); }
.toast.leaving { animation: toast-out .16s ease forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(4px); } }

/* ---------------------------------------------------------------- icon buttons
   A square ghost button holding a 16px glyph. Used where an action repeats down
   a list and its name would be read four times before its meaning. Every one
   still carries a title and an aria-label, so it is a button with a picture on
   it rather than a picture someone has to decode. */
.icon-btn {
  border: none; background: none; color: var(--muted);
  padding: .3em; border-radius: 6px; line-height: 0;
}
.icon-btn:hover:not(:disabled) { background: var(--panel-2); color: var(--brand); }
.icon-btn.danger:hover:not(:disabled) { background: var(--bad-soft); color: var(--bad); }
.icon-btn svg { display: block; width: 16px; height: 16px; }
.icon-btn:disabled { opacity: .35; }

/* ---------------------------------------------------------------- kiosk */

body.kiosk { background: var(--bg); }
.kiosk-wrap { max-width: 680px; margin: 0 auto; padding: 1.6rem 1.2rem 4rem; }
.kiosk-header { text-align: center; padding: 1.6rem 0 .8rem; }
.kiosk-header h1 {
  font-size: 1.45rem; color: var(--brand); letter-spacing: -.015em;
}
/* Progress, one segment per section.

   A single bar over forty-odd questions barely moves, and the number beside it
   had to say "about" because show_if makes the total genuinely change. Five
   segments give a patient a stretch that visibly fills, and the section they
   are standing in is the one that is lit. */
.progress-track {
  display: flex; gap: 4px; height: 8px; margin: .9rem 0 .35rem;
}
.progress-seg {
  flex: 1; height: 100%; background: #dce4ee; border-radius: 999px;
  overflow: hidden;
}
/* The section in hand, for anyone who cannot see which bar is part-filled. */
.progress-seg.current { box-shadow: 0 0 0 1px var(--brand-line); }
.progress-fill {
  height: 100%; background: linear-gradient(90deg, var(--brand), #2f79b8);
  width: 0; border-radius: 999px; transition: width .3s ease;
}
.progress-line { display: flex; justify-content: space-between; gap: 1rem; }
.progress-where { display: inline-flex; align-items: center; gap: .35rem; }
.progress-where strong { color: var(--ink); }

/* Check your answers. A row is question, answer, and the way back to it. */
.review-list { border-top: 1px solid var(--line-soft); margin-bottom: .4rem; }
.review-row {
  display: flex; align-items: baseline; gap: .75rem;
  padding: .35rem 0; border-bottom: 1px solid var(--line-soft);
}
.review-q { color: var(--muted); flex: 1 1 auto; min-width: 0; }
.review-a {
  font-weight: 600; text-align: right; flex: 0 1 auto;
  min-width: 0; overflow-wrap: anywhere;
}
/* 44px of height on a control a patient taps while holding the tablet. The
   row is short; the target is not. */
.review-edit {
  flex: none; display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 .15rem; background: none; border: 0; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--brand);
  text-decoration: underline; text-underline-offset: 3px;
}
/* The Prime IV letterhead, first thing on a plan: a band in the logo's own blue
   running edge to edge across the top of the plan card, taking the card's corner
   radius, with the reversed (white) logo, the "IV" knocked out so the blue shows through. Matches the PDF.
   (2026-10-08) */
.plan-letterhead {
  display: flex; align-items: center;
  background: #065DA5;   /* the blue in the clinic's own logo */
  margin: calc(-1 * var(--sp-20)) calc(-1 * var(--sp-20)) 0;
  padding: 14px var(--sp-20);
  border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
}
.plan-letterhead img { display: block; height: 52px; width: auto; }
/* On the patient's own screen the card is already white. */
.plan-logo { display: block; height: 52px; width: auto; margin: 0 0 .9rem; }

.qcard {
  background: #fff; border: 1px solid var(--line); border-radius: 16px;
  padding: 1.75rem 1.6rem; box-shadow: var(--shadow);
}
/* Arriving at a new question. 160ms because this is the one animation in the
   platform that is multiplied by 44 -- the number of questions a patient
   answers -- so a duration that reads as generous once costs a quarter of a
   minute over a questionnaire.

   No fill-mode, matching `rise` and `fade` above: the card is in its normal
   state before the animation starts and after it ends, so there is no moment
   at which a patient is looking at a question that has been made invisible on
   purpose. That also means `prefers-reduced-motion` needs no special case here
   -- zeroing the duration is genuinely all it takes, unlike the boot, which
   has delays. And transform and opacity do not capture pointer events, so
   somebody answering quickly can tap straight through it. */
.qcard-enter { animation: qcard-enter .16s cubic-bezier(.2, .7, .3, 1); }
.qcard-enter.qcard-back { animation-name: qcard-enter-back; }
@keyframes qcard-enter { from { opacity: 0; transform: translateX(14px); } }
@keyframes qcard-enter-back { from { opacity: 0; transform: translateX(-14px); } }
.qsection {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--brand); font-weight: 600; margin-bottom: .55rem;
  display: flex; align-items: center; gap: .45rem;
}
/* The section mark sits with the label, not above it: on a tablet held at arm's
   length the two read as one line telling you where you are. */
.qsection svg { flex: none; display: block; }
.qlabel { font-size: 1.35rem; font-weight: 600; line-height: 1.32; margin-bottom: .3rem; }
/* Quiet, and beside the question rather than under it: a patient scanning for
   what to do next should see it without it competing with the question. */
.qoptional {
  margin-left: .55rem; vertical-align: middle;
  font-size: .72rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: .12rem .45rem;
}
.qhelp { color: var(--muted); font-size: .93rem; margin-bottom: 1.15rem; }

.choices { display: grid; gap: .6rem; }
.choice {
  display: flex; align-items: center; gap: .85rem;
  border: 2px solid var(--line); border-radius: 13px;
  padding: 1rem 1.15rem; background: #fff; text-align: left;
  font-size: 1.05rem; width: 100%; font-weight: 500;
  transition: border-color .13s ease, background .13s ease, box-shadow .13s ease;
}
.choice:hover { border-color: #a9bdd6; background: var(--panel-2); }
.choice.selected {
  border-color: var(--brand); background: var(--brand-soft); font-weight: 600;
  box-shadow: var(--ring);
}
.choice .box {
  width: 23px; height: 23px; border: 2px solid #b6c2d4; border-radius: 7px;
  flex: 0 0 auto; display: grid; place-items: center;
  font-size: 14px; color: #fff; transition: background .13s ease, border-color .13s ease;
}
.choice.radio .box { border-radius: 50%; }
.choice.selected .box { background: var(--brand); border-color: var(--brand); }

.scale-row { display: flex; gap: .45rem; flex-wrap: wrap; justify-content: center; margin: .6rem 0; }
.scale-btn {
  width: 54px; height: 54px; border-radius: 13px; border: 2px solid var(--line);
  font-size: 1.1rem; font-weight: 600;
}
.scale-btn:hover:not(.selected) { border-color: #a9bdd6; }
.scale-btn.selected {
  background: var(--brand); border-color: var(--brand); color: #fff;
  box-shadow: var(--ring);
}
.scale-ends { display: flex; justify-content: space-between; color: var(--muted); font-size: .85rem; }

.kiosk-actions { display: flex; justify-content: space-between; gap: .85rem; margin-top: 1.5rem; }
.kiosk-actions button { padding: .9em 1.7em; font-size: 1.02rem; }
.kiosk-actions .primary { flex: 1; }
.big-number { font-size: 1.4rem; text-align: center; padding: .75em; }

.done-card { text-align: center; padding: 2.6rem 1.6rem; }
/* Drawn, not typed: the tick is an SVG so it holds its weight at this size. */
.tick { color: var(--ok); line-height: 0; }
.tick svg { display: inline-block; }
.done-card .tick { margin-bottom: .2rem; }

/* Pairing: a code read across a room and typed on a tablet. */
.code-card { text-align: center; }
.code-input {
  font-family: var(--mono); font-size: 2.2rem; letter-spacing: .34em;
  text-align: center; text-transform: uppercase;
  padding: .55em .3em; margin-top: .45rem;
}
.code-display {
  font-family: var(--mono); font-size: var(--t-display); font-weight: 600;
  letter-spacing: .28em; color: var(--brand);
  background: var(--brand-soft); border: 1px solid #cadcee;
  border-radius: var(--r-lg); padding: .5em .3em;
  text-align: center; margin: .45rem 0;
}
.tablet-url { font-family: var(--mono); font-size: 1.05rem; font-weight: 600; letter-spacing: 0; }

/* The patient's own copy of the plan. */
.goal-list { margin: .25rem 0 .45rem 1.15rem; line-height: 1.65; }
.goal-list.small { font-size: .92rem; color: var(--muted); }
.strong-label { font-weight: 600; margin-top: .55rem; }

.spinner {
  width: 20px; height: 20px; flex: 0 0 auto; margin-top: .15rem;
  border: 3px solid #cadcee; border-top-color: var(--brand);
  border-radius: 50%; animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Why a drip is on the plan. Set under the therapy it explains and in the
   reading colour rather than the muted one: this is the sentence the patient
   came for, not an aside. The clinic's own line about the drip follows it,
   quieter, because it is true of everybody. */
.why {
  margin-top: .25rem;
  color: var(--ink-2);
  font-size: .92em;
  line-height: 1.45;
}
.why.about { color: var(--muted); }
.therapy-line .why { max-width: 52ch; }
.goal-list .why { margin-bottom: .35rem; }

/* Handing the tablet back. Set apart from the plan above it, and quieter than
   the plan, because it is the room's control rather than the patient's news. */
.hand-back { border-style: dashed; }
.hand-back p { margin: 0; }
.hand-back button { width: 100%; }

/* ---------------------------------------------------------------- platform footer
 *
 * Who built the platform, on every surface it presents: the console staff work
 * in all day, the control panel that runs the host, and the questionnaire a
 * patient is handed. It is the vendor's mark, not the clinic's -- the clinic's
 * own name belongs on the record and the PDF -- so it stays quiet: muted ink,
 * small type, and a hairline that separates it from the work instead of a band
 * that competes with it.
 *
 * The mark is a mask, like every other mark here, so it takes its colour from
 * the palette and follows the theme rather than being a picture that suits only
 * one of them. The wordmark and the tagline are real text: crisp at any size,
 * selectable, readable aloud, and free.
 */
.platform-footer {
  max-width: 1080px;
  margin: 2.4rem auto 0;
  padding: var(--sp-20) var(--sp-20) var(--sp-24);
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .55rem;
  text-align: center;
}
.ace-lockup { display: flex; align-items: center; gap: .62rem; }
.ace-mark {
  /* 44px, not the 26px the drawn placeholder used. That mark was a shield and a
     power symbol and nothing else, so it read fine small; this one has "ACE"
     set inside the spade plus circuit detail, and below about 40px all of it
     collapses into a smudge. Checked at both sizes before choosing. */
  width: 44px; height: 44px; flex: none;
  background: var(--ink-2);
  -webkit-mask: var(--ace-mark) center / contain no-repeat;
  mask: var(--ace-mark) center / contain no-repeat;
  opacity: .85;
}
/* The rule the printed lockup is drawn with. It is what keeps the tagline
   reading as part of the mark rather than as a caption under it. */
.ace-words {
  border-left: 1px solid var(--line-strong);
  padding-left: .62rem;
  text-align: left;
  line-height: 1.16;
}
.ace-name {
  display: block;
  font-weight: 600; font-size: 1.02rem; letter-spacing: -.01em;
  color: var(--ink-2);
}
.ace-tag {
  display: block;
  font-size: .585rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .155em;
  color: var(--muted);
}
.platform-footer .ace-line { margin: 0; font-size: .76rem; color: var(--muted); }

/* The kiosk holds one column, and the footer holds the same one. */
body.kiosk .platform-footer { max-width: 680px; margin-top: 1.2rem; }


/* ---------------------------------------------------------------- small screens */

@media (max-width: 700px) {
  main { padding: .9rem .75rem 3rem; }
  .topbar { padding: .75rem 1rem; }
  .tabs { padding: 0 .6rem; }
  .tabs button { padding: .75em .65em; font-size: .88rem; }
  table { font-size: .84rem; }
  th, td { padding: .55em .5em; }
  .row > button, .row > a { flex: 1 1 auto; }
  /* No narrower override here any more. It was 108px, which guaranteed the
     truncation above on exactly the smallest screens. Two tiles a row with a
     readable label beats three with a cut one. */
  .modal { padding: 2vh .6rem; }
  .modal > .panel { padding: 1.1rem 1rem; }
  .notice.live { flex-direction: column; align-items: stretch; }
  .q-summary { flex-direction: column; }
  .option-row { grid-template-columns: 1fr; }
  .login-wrap { margin: 5vh auto; }

  /* ---- the three things that make a console unusable on a phone.

     None is a design preference. Each is a specific failure with a specific
     cause, and the responsive pass above fixed none of them.

     1. iOS Safari zooms the page whenever a field with type smaller than 16px
        takes focus, and this console's body type is 15px. Every tap into a box
        jumps the layout and leaves it zoomed. 16px is the threshold, not a
        taste -- at 16px the zoom does not fire. */
  input, select, textarea { font-size: 16px; }

  /* 2. A table of five columns does not fit 375px and never will. Shrinking
        the type, which the rules above already do, only makes an unreadable
        table smaller. So it keeps its shape and scrolls inside its own panel,
        and the page itself never scrolls sideways.

        Scoped with :has() to panels that actually hold a table, so a panel
        holding a form does not become a scroll container and clip something.
        Where :has() is unsupported the rule does not apply and the behaviour
        is today's -- a quiet no-op rather than a broken layout. */
  .panel:has(> table) { overflow-x: auto; }
  table { min-width: 32rem; }

  /* 3. Four link buttons in a row are a pointer-sized target on a surface
        being touched. The kiosk settled on 44px for exactly this reason; the
        console borrows it at the width where it is being touched, and gives it
        back above that, because 44px rows at a desk would cost a nurse rows
        per screen all day. */
  td.row-actions { white-space: nowrap; }
  .row-actions button.link { min-height: 44px; padding: 0 var(--sp-8); }

  /* The tab strip scrolls rather than wrapping into a second row that pushes
     the content down, and the header wraps rather than crushing the account. */
  .tabs { overflow-x: auto; }
  .tabs button { flex: 0 0 auto; }
  .topbar { flex-wrap: wrap; row-gap: var(--sp-8); }
}

@media (max-width: 560px) {
  .kiosk-wrap { padding: 1rem .8rem 3rem; }
  .qcard { padding: 1.3rem 1.1rem; }
  .qlabel { font-size: 1.18rem; }
  .kiosk-actions { flex-direction: column-reverse; }
  .kiosk-actions button { width: 100%; }
  .scale-btn { width: 47px; height: 47px; }
  .code-input { font-size: 1.75rem; letter-spacing: .22em; }
  .code-display { font-size: 1.9rem; }
}

/* ---------------------------------------------------------------- kiosk boot
   The droplet fills, the veil lifts, the questionnaire rises. Roughly 1.1s
   end to end, once per page load. Markup lives in kiosk.html; nothing in
   kiosk.js knows this exists.

   The veil ends `visibility: hidden`, not merely transparent: a full-viewport
   layer left at opacity 0 still swallows the first tap a patient makes, and on
   a questionnaire that first tap is somebody deciding to begin. */
.kiosk-boot {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  /* flex-start, not center. The droplet is centred on the band where
     .kiosk-header::before is about to render -- .kiosk-wrap's 1.6rem of top
     padding plus .kiosk-header's 1.6rem puts that band at 3.2rem, and the
     droplet is 60px tall -- so it fills exactly where the mark appears and
     hands off to it.

     Centring it in the viewport instead looked correct on a short screen and
     wrong on a tall one: on a 1024px iPad the questionnaire is top-weighted
     while the viewport's middle is well below the card, so the droplet faded
     out in the empty space under the Start button, and in landscape it faded
     out directly on top of it. This tracks the header at every screen height
     because it is derived from the same padding the header uses. */
  align-items: flex-start;
  justify-content: center;
  padding-top: calc(3.2rem - 18px);
  background: var(--bg);

  /* The resting state is GONE, and the animation is the only thing that ever
     shows it. It shipped the other way round -- opaque by default, cleared by
     the animation -- which made reaching the questionnaire depend on an
     animation running to completion.

     It does not always run. A page loaded while its tab is not visible has its
     animations held at time 0: reported from an iPad on 2026-09-14 and then
     reproduced exactly, with document.visibilityState 'hidden', currentTime
     still 0 after five seconds, opacity 1, and document.elementFromPoint over
     the code field returning this veil instead of the input. A full-viewport
     layer at z-index 60 that never clears is a kiosk nobody can start, and a
     refresh does not help because the next load does the same thing.

     Written this way the failure mode is "no animation played", which nobody
     will ever report. pointer-events: none on top of that, because a
     decorative layer has no business taking a tap under any circumstances. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  animation: kiosk-boot-veil .96s ease-out;
}
/* 44x66 rather than 45x60: the droplet is 2:3 now, and `contain` would have
   letterboxed it inside the old 3:4 box. The stroke is scaled with the viewBox
   -- 1.1 user units across 18 rendered at 45px is 2.75px on screen, and 1.5
   across 24 rendered at 44px is the same. */
.kiosk-boot svg { width: 44px; height: 66px; overflow: visible; }
.kiosk-boot-outline { fill: none; stroke: var(--brand); stroke-width: 1.5; opacity: .45; }
.kiosk-boot-fill {
  fill: var(--brand);
  /* No fill-mode here either. It is moot while the veil is hidden, but the
     rule is the rule: nothing in this boot sequence is held away from its
     resting state by a fill that only applies if the animation ran. */
  animation: kiosk-boot-fill .62s cubic-bezier(.4, 0, .3, 1);
}
/* The footer is outside .kiosk-wrap and would otherwise appear without the
   rise, a half-animated page being more noticeable than an unanimated one. */
/* Same rule, same reason: no fill-mode, and the delay lives inside the
   keyframes instead. `both` held the questionnaire at opacity 0 through its own
   .72s delay, so a page whose animations never start showed a patient nothing
   at all -- the exact failure `.qcard-enter` was deliberately written to avoid,
   committed here and not noticed because the veil was covering it anyway. */
body.kiosk .kiosk-wrap { animation: kiosk-boot-rise 1.1s cubic-bezier(.2, .7, .3, 1); }
body.kiosk .platform-footer { animation: kiosk-boot-rise 1.18s cubic-bezier(.2, .7, .3, 1); }

@keyframes kiosk-boot-veil {
  0%, 64% { opacity: 1; visibility: visible; }
  100%    { opacity: 0; visibility: hidden; }
}
@keyframes kiosk-boot-fill {
  0%   { clip-path: inset(100% 0 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}
@keyframes kiosk-boot-rise {
  0%, 65% { opacity: 0; transform: translateY(9px); }
  100%    { opacity: 1; transform: none; }
}

/* These two follow the base rule rather than sitting with the other responsive
   blocks higher up the file, and the reason is the cascade, not tidiness: the
   base .kiosk-boot rule is declared below those blocks, so an override written
   up there has the same specificity and simply loses. It read as correct and
   did nothing.

   The breakpoints are the ones that move what this is derived from, which are
   not the same: the header mark shrinks to 20px at 700px, while .kiosk-wrap's
   top padding does not drop to 1rem until 560px. */
@media (max-width: 700px) {
  .kiosk-boot { padding-top: calc(3.2rem - 15px); }
  .kiosk-boot svg { width: 38px; height: 50px; }
}
@media (max-width: 560px) {
  .kiosk-boot { padding-top: calc(2.6rem - 15px); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  /* The rule above zeroes durations and says nothing about DELAYS, which is
     the whole of this animation's timing: the veil waits .62s before it starts
     clearing and the questionnaire waits .72s before it appears. Left to the
     global rule alone, somebody who asked their tablet for less motion would
     get the one thing they asked to avoid -- three quarters of a second
     staring at a motionless droplet. So the boot is removed outright rather
     than sped up, and the page is simply there. */
  .kiosk-boot { display: none; }
  body.kiosk .kiosk-wrap,
  body.kiosk .platform-footer { animation: none; }

}

/* Printing a console page: drop the furniture, keep the record. */
@media print {
  .topbar, .tabs, .modal, button, .platform-footer { display: none !important; }
  body { background: #fff; }
  main { max-width: none; padding: 0; }
  .panel { box-shadow: none; border-color: #ccc; break-inside: avoid; }
}


/* ---------------------------------------------------------------- header motif
   The IV droplet and vitals trace, kept for the patient's own header. It used
   to run across the console's header band as well; the console now carries the
   mark at wordmark size instead, so the texture belongs to the kiosk alone --
   the one surface a patient sees, and the one with room for it. */
.kiosk-header::before {
  content: "";
  display: block;
  height: 24px;
  max-width: 300px;
  margin: 0 auto .6rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22520%22 height=%2276%22 viewBox=%220 0 520 76%22%3E %3Cdefs%3E %3ClinearGradient id=%22f%22 x1=%220%22 x2=%221%22%3E %3Cstop offset=%220%22 stop-color=%22%23ffffff%22 stop-opacity=%22.25%22/%3E %3Cstop offset=%22.28%22 stop-color=%22%23ffffff%22 stop-opacity=%221%22/%3E %3Cstop offset=%22.72%22 stop-color=%22%23ffffff%22 stop-opacity=%22.8%22/%3E %3Cstop offset=%221%22 stop-color=%22%23ffffff%22 stop-opacity=%220%22/%3E %3C/linearGradient%3E %3Cmask id=%22m%22%3E%3Crect width=%22520%22 height=%2276%22 fill=%22url(%23f)%22/%3E%3C/mask%3E %3C/defs%3E %3Cg mask=%22url(%23m)%22%3E %3Cg fill=%22none%22 stroke=%22%23ffffff%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E %3Cpath d=%22M186 46 h34 l6-13 7 27 8-37 7 33 6-10 h40%22 stroke-width=%221.8%22 opacity=%22.42%22/%3E %3Cpath d=%22M294 46 h214%22 stroke-width=%221.8%22 opacity=%22.15%22/%3E %3Cpath d=%22M356 12 v13%22 stroke-width=%221.5%22 opacity=%22.3%22/%3E %3C/g%3E %3Cg fill=%22%23ffffff%22%3E %3Cpath d=%22M356 29c0 0 9 11 9 16a9 9 0 0 1-18 0c0-5 9-16 9-16z%22 opacity=%22.32%22/%3E %3Cpath d=%22M404 16c0 0 6 8 6 11a6 6 0 0 1-12 0c0-3 6-11 6-11z%22 opacity=%22.22%22/%3E %3Cpath d=%22M446 44c0 0 5 6 5 8a5 5 0 0 1-10 0c0-2 5-8 5-8z%22 opacity=%22.16%22/%3E %3Cpath d=%22M96 20c0 0 6 7 6 10a6 6 0 0 1-12 0c0-3 6-10 6-10z%22 opacity=%22.13%22/%3E %3Ccircle cx=%22318%22 cy=%2222%22 r=%222.6%22 opacity=%22.2%22/%3E %3Ccircle cx=%22486%22 cy=%2230%22 r=%222.2%22 opacity=%22.14%22/%3E %3C/g%3E %3C/g%3E %3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: auto 100%;
  filter: brightness(0) saturate(100%) invert(27%) sepia(46%) saturate(880%) hue-rotate(179deg);
  opacity: .75;
}

@media (max-width: 700px) {
  .topbar { background-position: left -60px center, center; }
  .kiosk-header::before { max-width: 210px; height: 20px; }
}

/* ---------------------------------------------------------------- the queue
   The screen a provider lives in. Rows of work, not a table of data: a queue
   is read to answer "what is waiting and for how long", and a four-column
   table answers "what exists". Being a list rather than a table also means it
   does not scroll sideways on a phone, which matters on the one console screen
   somebody is likely to read standing up. */
.queue-list { display: flex; flex-direction: column; gap: var(--sp-8); }
.queue-item {
  display: grid;
  grid-template-columns: minmax(9rem, auto) minmax(7rem, auto) 1fr auto 1.2rem;
  gap: var(--sp-16);
  align-items: center;
  width: 100%;
  text-align: left;
  padding: var(--sp-12) var(--sp-16);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-md);
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: border-color .13s ease, background .13s ease, transform .13s ease;
}
.queue-item:hover { background: var(--panel); border-left-color: var(--brand); }
.queue-item:active { transform: translateY(1px); }
.q-ref { font-weight: 600; }
.q-edited {
  font-size: var(--t-label); color: var(--muted); margin-left: var(--sp-8);
  text-transform: uppercase; letter-spacing: .06em;
}
/* The number is the point, so it is set like one. */
.q-age { display: flex; flex-direction: column; line-height: 1.15; }
.q-age-n { font-weight: 600; font-variant-numeric: tabular-nums; }
.q-age-k {
  font-size: var(--t-label); text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted);
}
.q-who { font-size: var(--t-small); color: var(--muted); text-align: right; }
.q-go { color: var(--muted); text-align: right; }
.queue-item:hover .q-go { color: var(--brand); }

/* Waiting a day or more. The same attention colour the dashboard tiles use,
   for the same reason and with the same restraint: it appears only when it is
   true, so a queue that is being kept up with never shows it. */
.queue-item.overdue {
  border-left-color: var(--warn);
  background: linear-gradient(90deg, var(--warn-soft), var(--panel-2) 60%);
}
.queue-item.overdue .q-age-n { color: var(--ink-warn); }
.queue-item.overdue .q-age-k { color: var(--warn); }

@media (max-width: 700px) {
  .queue-item {
    grid-template-columns: 1fr auto;
    gap: var(--sp-8);
    row-gap: var(--sp-4);
  }
  .q-age { flex-direction: row; gap: var(--sp-4); align-items: baseline; }
  .q-who { text-align: left; }
  .q-go { display: none; }
}

/* -------------------------------------------------- readiness, on a plan
   The highest-stakes screen in the product says at the top whether it can be
   signed. Three states, and the colour is the same language the queue and the
   dashboard tiles speak: amber is something waiting on a person, green is
   nothing in the way, plain is information. */
/* ------------------------------------------------------------ attestation

   The signature on a clinical record. It used to be one row of a seven-row
   key/value table -- the same weight as the engine version two rows below it,
   with the attestation text set in the smallest muted style on the page, and
   the whole panel drawn identically to the provenance panel underneath it.

   This is the only place in the product where a person's name is bound to a
   clinical decision. The order and the sizes say so now instead of leaving it
   to be read off a list.

   No new type sizes: the name is --t-title, the top of the console's six-step
   scale and the same size as a view's own heading, which is the honest
   comparison. Nothing here animates; it is all paint, so it costs a nurse
   nothing on any of the many passes she makes over this page. */
.attest .sig {
  padding-bottom: var(--sp-20);
  margin-bottom: var(--sp-20);
  border-bottom: 1px solid var(--line-soft);
}
/* The rule under the name is the one on a signature line, and it is why this
   reads as a signature rather than as a large label. inline-block so it ends
   where the name ends. */
.attest .sig-name {
  display: inline-block;
  font-size: var(--t-title);
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--ink);
  padding-bottom: var(--sp-8);
  border-bottom: 2px solid var(--brand);
}
.attest .sig-by {
  margin-top: var(--sp-12);
  font-size: var(--t-body);
  color: var(--ink-2);
}
.attest .sig-when {
  margin-top: var(--sp-4);
  font-size: var(--t-small);
  color: var(--muted);
}

/* The sentence somebody put their name to, at reading size rather than as help
   text. This is the line that gets quoted back a year later. */
.attest .attest-text {
  margin: 0 0 var(--sp-16);
  padding-left: var(--sp-16);
  border-left: 3px solid var(--brand-line);
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--ink-2);
}
/* The note the approver wrote, under the words they signed and clearly not
   part of them. Indented to the quote's text rather than its rule -- the 3px
   is the border -- so the two left edges line up instead of missing by three
   pixels, and its label is its own line so the note reads as a note. */
.attest .attest-note {
  margin: 0 0 var(--sp-20) calc(var(--sp-16) + 3px);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--muted);
}
.attest .attest-note .k {
  display: block;
  margin-bottom: var(--sp-4);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: .035em;
}

/* Version, configuration, engine -- provenance, at provenance weight. These
   were interleaved with the signature at equal size, and they answer a
   different question, after it is asked. */
.attest .attest-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-8) var(--sp-16);
  margin: 0;
  padding-top: var(--sp-16);
  border-top: 1px solid var(--line-soft);
  font-size: var(--t-small);
}
.attest .attest-facts dt {
  align-self: center;
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: .035em;
  color: var(--muted);
}
.attest .attest-facts dd { margin: 0; color: var(--ink-2); }
@media (max-width: 700px) {
  /* Two columns of definition list at 375px gives the value about nine
     characters. Stack them instead. */
  .attest .attest-facts { grid-template-columns: 1fr; gap: 0; }
  .attest .attest-facts dt { margin-bottom: var(--sp-4); }
  .attest .attest-facts dd { margin-bottom: var(--sp-12); }
  .attest .attest-facts dd:last-child { margin-bottom: 0; }
}

.readiness {
  display: grid; grid-template-columns: 1.5rem 1fr auto;
  gap: var(--sp-12); align-items: start;
  padding: var(--sp-12) var(--sp-16);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--panel-2);
}
.readiness .r-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  font-weight: 600; font-size: var(--t-small); line-height: 1;
  background: var(--neutral-soft); color: var(--muted);
}
.readiness .r-say { color: var(--muted); }
.readiness.blocked {
  border-left-color: var(--warn);
  background: linear-gradient(90deg, var(--warn-soft), var(--panel-2) 62%);
}
.readiness.blocked .r-mark { background: var(--warn); color: var(--on-brand); }
.readiness.blocked strong { color: var(--ink-warn); }
.readiness.clear { border-left-color: var(--ok); }
.readiness.clear .r-mark { background: var(--ok-soft); color: var(--ink-ok); }
.readiness.advisory { border-left-color: var(--brand-line); }
@media (max-width: 700px) {
  .readiness { grid-template-columns: 1.5rem 1fr; }
  .readiness > button { grid-column: 2; justify-self: start; }
}

/* Behind everything, including the panels, and deaf to the pointer. A negative
   stacking level so every statically positioned element in the console paints
   over it without needing a z-index of its own.

   The mask softens it down the page. It was described here, and in HANDOFF, as
   masking the trace out "before the working area" -- that was never true at a
   normal window height. At 1600x844 the working area starts at y=173 and this
   mask does not begin to fade until y=287, so the whole 280px envelope of the
   trace sits inside the content band. What actually keeps a trace off a table
   of reference codes is that the panels are opaque and paint over it; the mask
   only stops it running down the page behind the lower ones. Worth knowing
   before trusting the mask to protect anything. */
.console-backdrop {
  position: fixed; inset: 0; width: 100vw; height: 100vh;
  z-index: -1; pointer-events: none;
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 34%, transparent 62%);
  mask-image: linear-gradient(180deg, #000 0, #000 34%, transparent 62%);
}
body.kiosk .console-backdrop { display: none; }
