/* Cerno HMS — landing dashboard (module launcher + live ops widgets).
   Ported from a Tailwind mock to self-contained CSS on the app's design tokens: theme-aware (light/dark),
   IBM Plex (self-hosted), no external CDN/fonts — CSP-clean. Colored module cards carry their hue in a
   per-card custom property (--c / --c2) set inline. */

/* width:100% stops the auto-margins from shrink-wrapping this flex item — it fills the content area
   (so the module grid gets more columns), capped on very wide screens and centred. */
.dash { width: 100%; max-width: 1600px; margin: 0 auto; display: flex; flex-direction: column; gap: 22px; }
@media (min-width: 1100px) { .dash { flex-direction: row; align-items: flex-start; } }
.dash__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 26px; }
.dash__aside { width: 100%; display: flex; flex-direction: column; gap: 18px; }
@media (min-width: 1100px) { .dash__aside { width: 320px; flex: none; position: sticky; top: 16px; } }

/* ── Live bed banner ─────────────────────────────────────────────────────── */
.dbanner { position: relative; overflow: hidden; border-radius: var(--r-lg);
    background: var(--grad-brand); color: #fff; padding: 22px 24px; box-shadow: var(--shadow-md); }
.dbanner__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.dbanner__id { display: flex; align-items: center; gap: 14px; }
.dbanner__ico { width: 46px; height: 46px; border-radius: var(--r-md); background: rgba(255,255,255,.18);
    display: grid; place-items: center; flex: none; }
.dbanner__ico svg { width: 24px; height: 24px; }
.dbanner__h { font-size: 18px; font-weight: 700; letter-spacing: .2px; margin: 0; }
.dbanner__sub { font-size: 12.5px; color: rgba(255,255,255,.8); margin-top: 2px; }
/* A single hero figure, right-aligned so the number sits hard against the panel edge and the
   label centres under it. It carried two competing figures before -- see BedBanner.razor. */
.dbanner__right { text-align: right; }
.dbanner__big { font-size: 38px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.dbanner__biglbl { font-size: 12px; color: rgba(255,255,255,.85); font-weight: 600; margin-top: 3px; }
.dbanner__chip { display: inline-flex; align-items: center; gap: 8px; background: rgba(0,0,0,.18);
    border: 1px solid rgba(255,255,255,.28); border-radius: 999px; padding: 6px 14px; font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.dbanner__dot { width: 9px; height: 9px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 8px rgba(74,222,128,.9); }
.dbanner__dot--warn { background: #fbbf24; box-shadow: 0 0 8px rgba(251,191,36,.9); }
.dbanner__dot--crit { background: #f87171; box-shadow: 0 0 8px rgba(248,113,113,.9); }
/* census line (merged in from the MIS "patients in beds" banner): occupied-of-total + occupancy % */
.dbanner__census { display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
    margin: -4px 0 16px; flex-wrap: wrap; }
.dbanner__censusnum { font-size: 26px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.dbanner__censussep { font-weight: 600; color: rgba(255,255,255,.72); margin: 0 2px 0 3px; }
.dbanner__censusnum small { font-size: 12.5px; font-weight: 600; color: rgba(255,255,255,.95); margin-left: 6px; }
/* Quieter than the "beds occupied" label beside it: it is the footnote that makes the census
   arithmetic add up, not a figure anybody is looking for. */
.dbanner__censusnum small.dbanner__unavail { color: rgba(255,255,255,.62); font-weight: 500; margin-left: 8px; }
.dbanner__censuspct { font-size: 13px; font-weight: 700; color: rgba(255,255,255,.95); font-variant-numeric: tabular-nums; }
/* Percentage and band read together, so they sit on one baseline at the end of the census row. */
.dbanner__censusright { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }

.dbanner__caps { display: flex; flex-wrap: wrap; gap: 2px 12px; justify-content: space-between; font-size: 11px; font-weight: 600; letter-spacing: .5px;
    text-transform: uppercase; color: rgba(255,255,255,.82); margin-bottom: 7px; }
.dbanner__bar { position: relative; height: 10px; border-radius: 999px; background: rgba(0,0,0,.28); overflow: hidden; }
.dbanner__fill { height: 100%; border-radius: 999px; background: #fff; transition: width .5s var(--ease, ease); }
/* graduated tick marks over the meter (the MIS banner's signature) — four evenly-spaced hairlines.
   mix-blend difference so a hairline reads over BOTH the white fill and the dark track (a plain white
   tick would vanish on the white fill in the common Comfortable band). */
.dbanner__ticks { position: absolute; inset: 0; display: flex; justify-content: space-evenly; pointer-events: none; }
.dbanner__ticks span { width: 1px; background: rgba(255,255,255,.6); mix-blend-mode: difference; }
.dbanner__fill--warn { background: #fbbf24; }
.dbanner__fill--crit { background: #f87171; }

/* ── Module sections ─────────────────────────────────────────────────────── */
.dsec__head { display: flex; align-items: center; gap: 9px; margin-bottom: 14px; color: var(--ink-soft); }
.dsec__head svg { width: 18px; height: 18px; }
.dsec__title { font-size: 15px; font-weight: 700; color: var(--ink); }
.dsec__meta { margin-left: auto; font-size: 11.5px; color: var(--ink-faint); font-family: var(--font-mono); }
.dgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px; }

.dmod { position: relative; overflow: hidden; border-radius: var(--r-lg); padding: 18px 16px 16px; min-height: 104px;
    color: #fff; text-decoration: none; box-shadow: var(--shadow-card); display: block;
    background: linear-gradient(135deg, var(--c, #4f6bed) 0%, var(--c2, var(--c, #3d55d6)) 100%);
    transition: transform var(--dur, .18s) var(--ease, ease), box-shadow var(--dur, .18s) var(--ease, ease); }
a.dmod:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
a.dmod:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.dmod__ic { position: absolute; right: 8px; bottom: 6px; width: 58px; height: 58px; opacity: .18; pointer-events: none; }
.dmod__ic svg { width: 100%; height: 100%; }
.dmod__name { position: relative; z-index: 1; font-size: 15px; font-weight: 700; line-height: 1.2; }
.dmod__sub { position: relative; z-index: 1; font-size: 11.5px; font-weight: 500; color: rgba(255,255,255,.85); margin-top: 3px; }
.dmod__pip { position: absolute; top: 12px; right: 12px; width: 8px; height: 8px; border-radius: 50%; z-index: 1;
    background: #86efac; box-shadow: 0 0 8px rgba(134,239,172,.85); }
.dmod__pip--wired { background: transparent; border: 1.5px solid rgba(255,255,255,.85); box-shadow: none; }
.dmod--soon { pointer-events: none; }
.dmod--soon::before { content: "SOON"; position: absolute; top: 11px; right: 11px; z-index: 1;
    font-size: 9px; font-weight: 800; letter-spacing: .5px; background: rgba(0,0,0,.22); border-radius: 999px; padding: 2px 7px; }
.dmod--soon { filter: grayscale(.55); opacity: .78; }

/* ── Sidebar widgets ─────────────────────────────────────────────────────── */
.dwidget { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--shadow-card); overflow: hidden; }
.dwidget__pad { padding: 18px 20px; }
.dwidget__h { font-size: 14px; font-weight: 700; color: var(--ink); margin: 0 0 16px; }

/* action required */
.dact__head { display: flex; align-items: center; gap: 9px; padding: 13px 18px; background: var(--critical-bg);
    border-bottom: 1px solid color-mix(in srgb, var(--critical-bg) 60%, var(--line)); color: #c0392b;
    font-size: 12px; font-weight: 800; letter-spacing: .6px; text-transform: uppercase; }
[data-theme="dark"] .dact__head { color: #ff8f85; }
.dact__head svg { width: 18px; height: 18px; }
.dact__item { display: flex; gap: 13px; padding: 15px 18px; border-top: 1px solid var(--line); }
.dact__item:first-child { border-top: 0; }
.dact__ic { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; flex: none; }
.dact__ic svg { width: 17px; height: 17px; }
.dact__ic--crit { background: var(--critical-bg); color: #c0392b; }
.dact__ic--warn { background: var(--urgent-bg); color: #b06d1a; }
[data-theme="dark"] .dact__ic--crit { color: #ff8f85; }
[data-theme="dark"] .dact__ic--warn { color: #e6a555; }
.dact__t { font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 3px; }
.dact__d { font-size: 11.5px; color: var(--ink-faint); line-height: 1.5; margin-bottom: 5px; }
.dact__link { font-size: 11.5px; font-weight: 700; color: var(--primary); text-decoration: none; }
.dact__link:hover { text-decoration: underline; }
.dwidget__empty { padding: 16px 18px; font-size: 12px; color: var(--ink-faint); }

/* departmental load */
.dload { display: flex; flex-direction: column; gap: 15px; }
.dload__row { }
.dload__top { display: flex; justify-content: space-between; align-items: baseline; font-size: 12.5px; margin-bottom: 5px; }
.dload__name { font-weight: 600; color: var(--ink-soft); }
.dload__val { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.dload__val--warn { color: #d9822b; font-weight: 700; }
.dload__bar { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.dload__fill { height: 100%; border-radius: 999px; background: var(--primary); }
.dload__fill--warn { background: #ef8f34; }
.dload__fill--alt { background: var(--accent, #8b5cf6); }

/* expected discharges */
.ddis__count { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; }
.ddis__badge { background: var(--surface-2); border-radius: var(--r-sm); padding: 5px 12px; text-align: center; }
.ddis__badge b { display: block; font-size: 17px; font-weight: 800; color: var(--ink); line-height: 1; font-variant-numeric: tabular-nums; }
.ddis__badge span { font-size: 9.5px; text-transform: uppercase; font-weight: 700; color: var(--ink-faint); letter-spacing: .4px; }
.ddis__list { display: flex; flex-direction: column; gap: 10px; }
.ddis__item { display: flex; align-items: center; gap: 12px; background: var(--info-bg);
    border: 1px solid color-mix(in srgb, var(--info) 16%, transparent); border-radius: var(--r-md); padding: 10px 12px; }
.ddis__av { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; flex: none;
    color: #fff; font-size: 12.5px; font-weight: 700;
    /* Deepen each avatar hue so white initials clear WCAG AA (4.5:1); the raw palette (#f97316 etc.) was ~2.3–3.9:1. */
    background: color-mix(in srgb, var(--c, var(--primary)) 60%, #000); }
.ddis__nm { font-size: 13px; font-weight: 700; color: var(--ink); }
.ddis__loc { font-size: 11px; color: var(--ink-faint); margin-top: 1px; }

/* ===== Outpatient queue ==================================================================
   Room cards for the clinician console (/queue/console) and the waiting-room display
   (/queue/board). Two surfaces, one set of tokens, because a doctor glancing at the console
   and a patient reading the wall must not see two different visual languages for the same
   room — the ticket number is the thing both are matching on.

   The board variant (.qroom--tv) only scales type. Nothing is restyled for the wall: a
   display that reorganised itself would stop being the same object the console describes. */

.qrooms { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 12px; }

.qroom { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface);
    box-shadow: var(--shadow-card), var(--card-ring); padding: 14px 15px; display: flex;
    flex-direction: column; gap: 10px; position: relative; overflow: hidden; }
/* The spine carries the room's state at a glance, in the same left-edge language as .alert. */
.qroom::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: var(--spine-w, 4px);
    background: var(--line-strong); }
.qroom--open::before { background: var(--stable-tx); }
.qroom--serving::before { background: var(--info-tx); }

.qroom__top { display: flex; align-items: baseline; gap: 8px; }
.qroom__name { font-size: 14px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.qroom__state { margin-left: auto; font-size: 11px; font-weight: 600; color: var(--ink-faint); }

/* Wraps rather than squeezing: a long clinician name beside "until 17:30" was compressing the name
   into two narrow lines while the time sat alone in the gutter. The name is the part being read. */
.qroom__doc { font-size: 12.5px; color: var(--ink-soft); display: flex; align-items: baseline; gap: 6px;
    flex-wrap: wrap; }
.qroom__doc span { white-space: nowrap; }
.qroom__doc b { color: var(--ink); font-weight: 600; }

/* The one line the whole screen exists for. Mono + tabular so a column of ticket numbers
   lines up and the eye can run down it. */
.qroom__now { display: flex; align-items: baseline; gap: 9px; }
.qroom__ticket { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    font-size: 30px; font-weight: 700; letter-spacing: -.02em; line-height: 1; color: var(--ink); }
.qroom__ticket--idle { color: var(--ink-faint); font-weight: 600; font-size: 20px; }
.qroom__nowlbl { font-size: 11px; font-weight: 600; color: var(--ink-soft); text-transform: uppercase;
    letter-spacing: .04em; }

.qroom__next { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-soft);
    border-top: 1px solid var(--line); padding-top: 9px; }
.qroom__nextno { font-family: var(--font-mono); font-weight: 700; color: var(--ink); }
.qroom__wait { margin-left: auto; }

.qroom__acts { display: flex; gap: 7px; flex-wrap: wrap; }

/* ---- wall display ---------------------------------------------------------------------- */
.qboard { --spine-w: 6px; }
.qboard .qrooms { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 18px; }
.qboard .qroom { padding: 22px 24px; gap: 14px; }
.qboard .qroom__name { font-size: 22px; }
.qboard .qroom__doc { font-size: 17px; }
.qboard .qroom__ticket { font-size: 62px; }
.qboard .qroom__ticket--idle { font-size: 34px; }
.qboard .qroom__nowlbl, .qboard .qroom__state { font-size: 14px; }
.qboard .qroom__next { font-size: 17px; padding-top: 13px; }

.qboard__head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 18px; }
.qboard__title { font-size: 30px; font-weight: 700; letter-spacing: -.02em; }
.qboard__meta { margin-left: auto; font-size: 15px; color: var(--ink-soft); }

/* ---- dashboard strip -------------------------------------------------------------------- */
.qstrip { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.qstrip__fig { display: flex; flex-direction: column; gap: 2px; }
.qstrip__n { font-size: 22px; font-weight: 700; letter-spacing: -.02em; line-height: 1;
    font-variant-numeric: tabular-nums; }
.qstrip__n--warn { color: var(--urgent-tx); }
.qstrip__l { font-size: 11.5px; color: var(--ink-soft); }

/* ===== Table density helpers =============================================================
   Generic, because the appointments table was not uniquely broken — a wide table of short
   machine-ish values wraps every column and stands three lines tall unless the short ones are
   told not to. `.nowrap` protects values that are meaningless when broken (an MRN, a ticket,
   a date); `.trunc` is for the ones that can legitimately be long, which lose their tail to an
   ellipsis and keep the whole value in a title attribute rather than pushing the table around. */

.table .nowrap, td.nowrap, th.nowrap { white-space: nowrap; }
.table .trunc { max-width: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Column widths for the appointment list.

   PERCENTAGES, not ch. A ch is relative to the font of the element it is set on, and these widths
   are set on the <th> while the values below them render in the monospace .data face -- so widths
   that looked right on the header were too narrow for the body, and with table-layout:fixed the
   content did not wrap, it SPILLED into the next column. Percentages are immune to that.

   Every cell also clips. Under fixed layout a too-long value overflows its box silently, so an MRN
   printed straight through the age beside it; clipping turns that into an ellipsis instead. */
.apptbl { table-layout: fixed; width: 100%; }
.apptbl td, .apptbl th { overflow: hidden; text-overflow: ellipsis; }
/* Weighted by what a reader is actually looking for. The first pass gave 13% to an appointment
   number and 8% to the PATIENT, so the one column somebody scans for was the only one truncated
   while a doctor name that repeats on every row had room to spare. */
.apptbl__no     { width: 11%; }
.apptbl__when   { width: 9%; }
.apptbl__pat    { width: 16%; }
.apptbl__mrn    { width: 10%; }
.apptbl__age    { width: 8%; }
.apptbl__doc    { width: 13%; }
.apptbl__type   { width: 8%; }
.apptbl__room   { width: 12%; }
/* Wide enough for the status PILL, which has its own padding on top of the word -- at 9% the
   tag was clipped mid-border and read as a rendering fault. */
.apptbl__status { width: 11%; }
/* The monospace body face is wider than the UI face at the same size; one step down keeps these
   columns legible without widening the whole table. */
.apptbl .data { font-size: 11.5px; }
.apptbl__whencell { white-space: nowrap; }
.apptbl__time { font-size: 10.5px; }

/* ===== Collapsible guidance block ========================================================
   For screens whose rules are worth stating but not worth re-reading. Closed, it costs one
   line; open, it is an ordinary card body. The lede stays visible either way because it is the
   sentence that changes what somebody does. */

.guide { padding: 0; }
.guide__sum { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding: 13px 16px; cursor: pointer; list-style: none; }
.guide__sum::-webkit-details-marker { display: none; }
/* The disclosure arrow is drawn rather than relying on the UA marker, which differs per browser
   and cannot be positioned. Rotates on open. */
.guide__sum::before { content: ""; width: 0; height: 0; flex: none; align-self: center;
    border-left: 5px solid var(--ink-faint); border-top: 4px solid transparent;
    border-bottom: 4px solid transparent; transition: transform var(--dur) var(--ease); }
.guide[open] .guide__sum::before { transform: rotate(90deg); }
.guide__title { font-size: 13px; font-weight: 700; color: var(--ink); }
.guide__lede { font-size: 12.5px; color: var(--ink-soft); }
.guide__sum:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; border-radius: var(--r-lg); }
.guide .card__body { padding-top: 0; }

/* ===== Dashboard queue panel =============================================================
   Two halves side by side: the summary figures, then the open rooms. The card carried only the
   figures before, which left two thirds of a full-width card empty — and the empty two thirds
   was exactly where "who is in which room" belonged. */

.qpanel { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 22px; align-items: start; }
@media (max-width: 900px) { .qpanel { grid-template-columns: 1fr; } }

.qstrip__of { font-size: 13px; color: var(--ink-faint); font-weight: 600; }

/* One line per open room, in as many columns as fit.

   THIS REPLACED A GRID OF CARDS. Cards read well at three rooms and become unusable at twenty — five
   rows of them turn a dashboard summary into the page. A row is ~26px against a card's ~74px, so
   twelve rooms now occupy roughly the height three cards did, and the card stops growing at twelve
   because the MARKUP caps it (Dashboard.MaxRoomsShown) rather than because the CSS hides an overflow.
   A cap the CSS enforced would be a lie; a cap the markup enforces can say "+8 more open".

   auto-FIT, not auto-fill: auto-fill keeps its empty tracks, so two open rooms sat at minimum width
   with the rest of the row blank. auto-fit collapses the empties and lets the real rows expand. */
.qlist { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1px 18px; align-content: start; }
.qlist__empty { margin: 0; align-self: center; grid-column: 1 / -1; }
.qlist__warn { color: var(--urgent-tx); }

/* Flex for the row itself, not grid: the metrics are pushed right with margin-left:auto, and since
   every row inside one grid COLUMN has the same width, they line up down that column without needing
   subgrid support. */
.qrow { display: flex; align-items: baseline; gap: 8px; padding: 4px 6px; min-width: 0;
    border-radius: var(--r-sm); border-bottom: 1px solid var(--line);
    text-decoration: none; color: inherit; transition: background-color var(--dur) var(--ease); }
.qrow:hover { background: var(--surface-2); }

/* The dot is the only colour in the row, so it carries the one state worth colouring: amber means a
   FREE room with people queued for it — the single thing a reader can act on from this card. */
.qrow__dot { width: 6px; height: 6px; border-radius: 50%; flex: none; align-self: center;
    background: var(--line-strong); }
.qrow__dot--busy { background: var(--info-tx); }
.qrow__dot--warn { background: var(--urgent-tx); }
.qrow--attention { background: color-mix(in srgb, var(--urgent) 9%, transparent); }

.qrow__room { font-size: 12px; font-weight: 650; color: var(--ink); flex: 0 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The clinician's name yields first when space runs out: the room and its queue are what the card is
   for, and the full name is on the row's title attribute either way. */
.qrow__doc { font-size: 11px; color: var(--ink-faint); flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qrow__now { margin-left: auto; font-family: var(--font-mono); font-size: 11.5px; font-weight: 700;
    color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Fixed width so the queue counts form a straight column the eye can run down. */
.qrow__n { font-size: 11px; color: var(--ink-soft); width: 2.5ch; text-align: right;
    font-variant-numeric: tabular-nums; }
/* Spans every column: as an ordinary grid item it sat centred in column one and read as a row
   belonging to that column rather than as the foot of the whole list. */
.qrow--more { grid-column: 1 / -1; justify-content: center; font-size: 11.5px; font-weight: 600;
    color: var(--primary); border-bottom: 0; padding-top: 7px; }
/* ===== Clinicians panel ==================================================================
   Deliberately plain: a list of people and one fact each. Anything more decorative would imply
   the fact is more certain than it is — see DashboardService.GetClinicianPlacementsAsync for why
   "in a room" and "has 96 inpatients" are not the same kind of statement.

   MULTI-COLUMN AND DENSE, for the same reason the room list is. A hospital that opens sixteen
   consultation rooms places sixteen clinicians, and a single column of those at 50px each is a
   500px card. Two or three columns of ~34px rows is the same information in a third of the
   height. Capped in the markup (Dashboard.MaxCliniciansShown) with an explicit "+N more". */
.clin { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 0 24px; align-content: start; }
.clin__row { display: flex; align-items: center; gap: 9px; padding: 5px 2px; min-width: 0;
    border-bottom: 1px solid var(--line); }
/* :last-child is wrong once the rows wrap into columns — it strips the rule from the last row of the
   LIST, not the last of each column, leaving one arbitrary gap. Every row keeps its rule instead. */
.clin__av { width: 24px; height: 24px; flex: none; border-radius: 50%; display: grid; place-items: center;
    background: var(--surface-3); color: var(--ink-soft); font-size: 9.5px; font-weight: 700; }
.clin__main { flex: 1; min-width: 0; }
.clin__name { font-size: 12.5px; font-weight: 600; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clin__spec { font-size: 10.5px; color: var(--ink-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A caseload, not a location — so it gets no status dot and no semantic colour. The visual
   difference from the green "In <room>" chip beside it is the point. */
.clin__load { font-size: 11.5px; color: var(--ink-soft); white-space: nowrap; flex: none; }
/* The room chip is the longest thing on the row and must not push the name out; it truncates too. */
.clin__where { flex: none; max-width: 48%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clin__more { grid-column: 1 / -1; text-align: center; padding-top: 8px;
    font-size: 11.5px; font-weight: 600; color: var(--primary); text-decoration: none; }
.clin__foot { margin-top: 11px; font-size: 11.5px; }

/* ===== Queue console — the Waiting table =================================================
   Widths so the table fills its card and the columns keep their distance. It rendered as a
   bare <table> before: no padding, no header band, and shrink-wrapped to its content so it
   used about a third of the card with the rest blank. Patient is the widest because it is the
   one column somebody scans; everything else is a short machine-ish value. */

.qtbl { table-layout: fixed; }
.qtbl td, .qtbl th { overflow: hidden; text-overflow: ellipsis; }
.qtbl__tkt    { width: 9%; }
.qtbl__mrn    { width: 13%; }
.qtbl__room   { width: 18%; }
.qtbl__time   { width: 11%; }
.qtbl__wait   { width: 10%; }
.qtbl__status { width: 13%; }

/* The ticket number is the identifier the whole screen turns on — it is what a clinician calls
   out and what the patient is holding — so it carries the weight in its own column. */
.qtbl__tktcell { font-weight: 700; color: var(--ink); }

/* A called row is being seen NOW rather than waiting, and the two are interleaved in one list
   sorted called-first. A tint separates the groups without needing a second table or a header
   row between them. */
.qtbl__row--called { background: color-mix(in srgb, var(--info) 6%, transparent); }

/* Past an hour. Was an inline style built by a ternary whose two branches were identical
   ("data" : "data") — dead code that looked like a decision. */
.qtbl__wait--long { color: var(--urgent-tx); font-weight: 700; }

/* ===== Triage chip =======================================================================
   Was a bare 10px coloured dot on the emergency worklist — no text, no title, no legend, on the
   one column that says who is seen first. Colour as the sole carrier of meaning fails WCAG 1.4.1,
   and it fails it for roughly one man in twelve who cannot separate the red from the green.
   The swatch stays (colour is genuinely faster to scan) but it no longer carries the meaning
   alone. See EmergencyDashboard.TriageOf for why the label is the stored colour NAME and not a
   triage category. */

.tri { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
    font-size: 11.5px; font-weight: 600; padding: 3px 9px 3px 7px; border-radius: 999px;
    background: var(--surface-2); color: var(--ink-soft); }
.tri__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: currentColor; }
/* -tx tones: the AA-safe TEXT variant of each hue, because the label sits on the tinted pill. */
.tri--red    { background: var(--critical-bg); color: var(--critical-tx); }
.tri--orange { background: var(--urgent-bg);   color: var(--urgent-tx); }
.tri--yellow { background: var(--urgent-bg);   color: var(--urgent-tx); }
.tri--green  { background: var(--stable-bg);   color: var(--stable-tx); }
.tri--none   { background: var(--surface-2);   color: var(--ink-faint); }

/* ===== ER worklist table ================================================================= */
.ertbl { table-layout: fixed; }
.ertbl td, .ertbl th { overflow: hidden; text-overflow: ellipsis; }
.ertbl__triage { width: 10%; }
.ertbl__uhid   { width: 10%; }
.ertbl__age    { width: 7%; }
.ertbl__visit  { width: 8%; }
.ertbl__status { width: 9%; }
.ertbl__doc    { width: 10%; }
.ertbl__bed    { width: 7%; }
.ertbl__payer  { width: 13%; }
.ertbl__when   { width: 11%; }
.ertbl__whencell { white-space: nowrap; }
.ertbl__time { font-size: 10.5px; }
.ertbl .data { font-size: 11.5px; }

/* ===== ER admitted / discharged table ====================================================
   Two column sets: the admitted view drops the discharge pair, so the widths differ between
   them and each is stated rather than left to the auto layout.

   Patient is the only column with no width — it takes whatever the fixed ones leave. The first
   pass left it 11% and truncated "Sullivan Test Sample" to "Sullivan Test S...", which is the
   wrong column to squeeze: the name is what somebody is scanning the list for, and Doctor is
   empty on 47 of these 49 rows. */
.adtbl { table-layout: fixed; }
.adtbl td, .adtbl th { overflow: hidden; text-overflow: ellipsis; }
.adtbl .data { font-size: 11.5px; }
.adtbl__uhid  { width: 13%; }
.adtbl__visit { width: 9%; }
.adtbl__age   { width: 11%; }
.adtbl__bed   { width: 9%; }
.adtbl__doc   { width: 13%; }
.adtbl__when  { width: 12%; }
.adtbl__remarks { width: 15%; }
/* On the discharged view two more columns share the row, so the wide ones give way first —
   never the patient name. */
.adtbl--discharged .adtbl__uhid  { width: 11%; }
.adtbl--discharged .adtbl__visit { width: 8%; }
.adtbl--discharged .adtbl__age   { width: 9%; }
.adtbl--discharged .adtbl__bed   { width: 7%; }
.adtbl--discharged .adtbl__doc   { width: 9%; }
.adtbl--discharged .adtbl__when  { width: 10%; }
.adtbl--discharged .adtbl__remarks { width: 14%; }

/* Available to a screen reader, invisible on screen. For a column whose header would be noise
   sighted (an action column) but which is otherwise announced as an unlabelled cell. The clip-path
   pair rather than display:none — the latter removes it from the accessibility tree too, which is
   the opposite of what this is for. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ===== Order verification table ==========================================================
   Ten columns of short machine-ish values across 300 rows. Without explicit widths the auto
   layout squeezed all of them until each broke over two lines — a visit number, a UHID, a clinic
   name and a timestamp all split mid-value, on every row. */
.ovtbl { table-layout: fixed; }
.ovtbl td, .ovtbl th { overflow: hidden; text-overflow: ellipsis; }
.ovtbl .data { font-size: 11.5px; }
/* Weighted by what carries meaning. The first pass truncated "General Practice Clinic" and
   "NAGICO INSURANCE" on every row while spending 11% on a mobile column whose demo values are
/* Widths allocated from MEASURED content, not estimated twice and corrected three times.

   Measuring the rendered table gave each column's true need (widest cell or its own header,
   whichever is larger, including the 28px of cell padding — 14px on BOTH sides, which the first
   two passes had as 14px total and so ran every minimum 14px short):

     SN 5.0 · Visit no. 9.0 · UHID 9.1 · Patient 19.3 · Age 9.6 · Doctor 15.0
     Payer 26.7 · Mobile 11.5 · Visit date 9.0 · Action 9.0        = 123% of the available width

   So the content does NOT fit, and the only real question is what gives way. The rule:

   1. IDENTIFIERS AND THE ACTION NEVER CLIP — SN, visit no., UHID, visit date and the button get
      their full measured need. A partial identifier cannot be acted on; a partial payer name can.
   2. Everything else takes what is left in order of how often it is read, and carries a title so
      the full value is one hover away. Payer's 26.7 is a single outlier name; the common
      "NAGICO INSURANCE" fits, and the two longer ones clip with a tooltip. */
.ovtbl__sn    { width: 5%; }
.ovtbl__visit { width: 10%; }
.ovtbl__uhid  { width: 10%; }
.ovtbl__pat   { width: 18%; }
.ovtbl__age   { width: 6%; }
.ovtbl__doc   { width: 15%; }
.ovtbl__payer { width: 11%; }
/* 7 and not 6: at 6% the column could not fit its own HEADER, which is a floor no data-driven
   width may go below -- an unlabelled column is worse than a clipped value. */
.ovtbl__mob   { width: 7%; }
.ovtbl__when  { width: 9%; }
.ovtbl__act   { width: 9%; }
/* Right-aligned so the action sits on the table's edge, where the eye returns after reading the
   row. overflow:visible because the button's focus ring must not be clipped by the cell. */
.ovtbl__actcell { text-align: right; overflow: visible; }
.ovtbl__whencell { white-space: nowrap; }
.ovtbl__time { font-size: 10.5px; }

/* ===== OPD worklist tables ===============================================================
   Four tables, one rule set. Widths come from measuring each column's longest real value at
   its own font, then allocating: IDENTIFIERS, TIMESTAMPS, MONEY AND ACTIONS never clip — a
   truncated identifier cannot be acted on and a truncated amount is not a smaller amount, it
   is a wrong one. Names and free text clip with a title attribute instead.

   NOTE on .table td: it sets `padding: 0 14px` and a FIXED `height: var(--row-h)`. The row
   height therefore does not change when a cell wraps, which is why "all rows are the same
   height" proves nothing here and the only reliable check is whether text is on two lines. */

/* --- /opd/appointment-cancellations — measured need 117%, so it must clip somewhere ------ */
.cxtbl { table-layout: fixed; }
.cxtbl td, .cxtbl th { overflow: hidden; text-overflow: ellipsis; }
.cxtbl__uhid  { width: 10%; }   /* MR24-002188 */
.cxtbl__pat   { width: 15%; }
.cxtbl__age   { width: 12%; }   /* "4Y 1M 25D/Male" — a paediatric age, never truncated */
.cxtbl__mob   { width: 8%; }
.cxtbl__doc   { width: 14%; }
.cxtbl__date  { width: 10%; }
.cxtbl__time  { width: 8%; }    /* "07:30 AM" broke over two lines before */
.cxtbl__payer { width: 14%; }
.cxtbl__spon  { width: 9%; }    /* fits "SELF PAY"; longer insurers clip with a tooltip */

/* --- /opd/check-in — fits at 99.9% today, stated so it keeps fitting -------------------- */
.citbl { table-layout: fixed; }
.citbl td, .citbl th { overflow: hidden; text-overflow: ellipsis; }
.citbl__no     { width: 9%; }
.citbl__date   { width: 12%; }
.citbl__time   { width: 10%; }
.citbl__pat    { width: 16%; }
.citbl__mrn    { width: 12%; }
.citbl__doc    { width: 21%; }  /* "Oluwatomilayo Bewaji Edokpa" is the widest thing here */
.citbl__status { width: 10%; }
.citbl__act    { width: 10%; }
.citbl__actcell { text-align: right; overflow: visible; }

/* --- /opd/refund ------------------------------------------------------------------------ */
.rftbl { table-layout: fixed; }
.rftbl td, .rftbl th { overflow: hidden; text-overflow: ellipsis; }
.rftbl__inv  { width: 13%; }    /* "MR24C-26-00000" broke over two lines before */
.rftbl__date { width: 10%; }
.rftbl__uhid { width: 11%; }
.rftbl__pat  { width: 23%; }    /* the one 34-char name clips; the rest fit */
.rftbl__amt  { width: 8.5%; }
.rftbl__act  { width: 9%; }
.rftbl__actcell { text-align: right; overflow: visible; }

/* --- /opd/credit-notes register --------------------------------------------------------- */
.cntbl { table-layout: fixed; }
.cntbl td, .cntbl th { overflow: hidden; text-overflow: ellipsis; }
.cntbl__no     { width: 13%; }
.cntbl__inv    { width: 9%; }
.cntbl__amt    { width: 12%; }
.cntbl__status { width: 11%; }
.cntbl__rem    { width: 37%; }  /* was starved at 13.8% by the auto layout */
.cntbl__when   { width: 18%; }
