/* ============================================================
   MedBase — Clinic & EHR Platform
   The shared design system, served to every page so they cannot drift.

   THE BRAND, AND WHAT EACH COLOUR IS FOR
   --------------------------------------
   MedBase Navy  #0B2545  the foundation: mastheads, navigation, all body ink.
                          It grounds the product in clinical authority.
   Data Teal     #20B2AA  the ACTION colour, and nothing else: primary buttons,
                          the active state of a toggle, the focus ring, the
                          "BASE" half of the wordmark. Used sparingly on
                          purpose — a colour that means "press this" stops
                          meaning anything once it is also used for decoration.
   Surfaces      #FFFFFF  content, and #F8F9FA (Soft Slate) behind it, which is
                          gentler than pure white across a nine-hour shift.

   AND THE ONE THING THE BRAND SHEET CANNOT DECIDE
   ----------------------------------------------
   Data Teal is a LIGHT colour: 2.6:1 against white. Measured, that means two
   hard consequences, and both are obeyed below rather than argued with.

     · It can never be text on a white page, and it can never carry white
       text on a button — white on #20B2AA is 2.62:1, which is not a contrast
       ratio, it is a rumour of one.
     · So a primary button keeps the EXACT brand teal as its fill and sets its
       label in MedBase Navy: 5.87:1, comfortably past AA, and the brand
       colour survives untouched on the element that matters most.

   Where the teal has to behave as text — a link, a hovered slot label — the
   same hue is darkened to --primary-ink (#087d76, 4.99:1) rather than
   substituted with some other colour. It is still Data Teal; it is Data Teal
   at a lightness a human eye can read.

   Every value below was measured with a contrast script, not chosen by eye,
   and the numbers in the comments are the measurements.
   ============================================================ */
:root{
  --bg:#f8f9fa;         /* Soft Slate  */
  --card:#fff;          /* Pure White  */
  --line:#dde3ea; --line-soft:#eef1f5;

  --navy:#0b2545;       /* the brand colour itself, for anything that names it */
  --ink:#0b2545;        /* 15.4:1 — the body ink IS MedBase Navy */
  --ink-2:#44556b;      /*  7.6:1 */
  --ink-3:#586e8a;      /*  4.6:1 worst case, measured on --line-soft */
  --sep:#7d8ea6;        /*  3.3:1 on Soft Slate — decorative only, but it still
                             has to be visible; the previous step measured 3.0
                             against a page that has since got lighter */

  /* Data Teal, split by the job it is doing. Same hue throughout. */
  --primary:#20b2aa;       /* THE brand teal — a fill, never text on white   */
  --primary-hover:#1ba49d; /* navy label still reads at 5.0:1                */
  --on-primary:#0b2545;    /* what sits ON a teal fill — 5.87:1              */
  --primary-ink:#087d76;   /* the same teal, dark enough to BE text — 4.99:1 */
  --primary-soft:#e9f9f8; --primary-line:#b4e4e2;

  --ok:#0c7a54;   --ok-soft:#e2f5ec;      /* 5.4:1 */
  --warn:#9c5b0a; --warn-soft:#fdf1de;    /* 5.4:1 */
  --danger:#a3232b; --danger-soft:#fdeaea;/* 7.4:1 */

  /* Visit-type hues, re-picked after the palette validator failed the previous
     set: counseling-violet vs diagnosis-blue scored CVD ΔE 1.4 (indistinguishable
     to a deuteranope) and 12.5 for normal vision — below the hard floor of 15;
     service-teal fell under the chroma floor and read as grey.
     This triple passes lightness band, chroma floor, CVD separation (worst
     all-pairs 10.0), normal-vision floor (20.1) and ≥3:1 vs surface, and each
     carries white text at ≥4.6:1 for the timeline fills. */
  --diagnosis:#2875d1;   --diagnosis-soft:#dae6f5;   --diagnosis-ink:#2365b5;
  --counseling:#cd4914;  --counseling-soft:#f4d1c3;  --counseling-ink:#a43a10;
  --service:#15855d;     --service-soft:#8ce4c5;     --service-ink:#106647;

  /* Sequential ramp for magnitude (the peak-times heatmap, the ranked bars):
     one hue, light→dark, in the NAVY hue rather than the teal one — because
     Data Teal is reserved for action, and a heatmap painted in the same
     colour as every button would quietly teach the eye that busy Tuesdays
     are clickable.

     The steps are not evenly spaced by eye, they are spaced so that EVERY
     step can carry a label: navy ink clears 4.5:1 on steps 0–4 and white
     clears it on 5–6, with nothing stranded in between. An earlier ramp had
     a step where navy read 3.7:1 and white read 4.2:1 — neither legible,
     and the cell in the middle of the busiest hour was the one you could
     not read. */
  --seq-0:#f4f7fa; --seq-1:#d9e3f0; --seq-2:#b1c8e4; --seq-3:#85acdb;
  --seq-4:#5e94d7; --seq-5:#2567b7; --seq-6:#134078;
  /* The readable ink for each step of the ramp above. It lives here, not in
     charts.js, because the ramp REVERSES between themes: in dark mode more =
     brighter, so the step that needed white on paper needs near-black on a
     dark screen. A threshold picked in JavaScript ("white from step 5") is a
     statement about one theme, and it was wrong in the other — the heatmap's
     busiest cells came out white-on-pale. Measured, every step, both themes. */
  --seq-on-0:var(--ink); --seq-on-1:var(--ink); --seq-on-2:var(--ink);
  --seq-on-3:var(--ink); --seq-on-4:var(--ink);
  --seq-on-5:var(--on-fill); --seq-on-6:var(--on-fill);

  /* ---- surfaces, shadows and hatches ------------------------------------
     These were 77 hardcoded greys scattered through the stylesheet. They are
     named here not for tidiness but because a theme can only be swapped where
     the palette is in one place — a hardcoded #f7f9fb survives every attempt
     to darken the page and shows up as a white slab at 2am.
     Numbered by how far each sits from the page, not by hue: fill-0 is barely
     off the card, fill-4 is a control track you can see the edge of. */
  --fill-0:#fbfcfe; --fill-1:#f7f9fb; --fill-2:#eff2f6;
  --fill-3:#eaeef4; --fill-4:#e5eaf1; --fill-hover:#dfe5ed;
  --fill-tint:#f2f6fc;      /* a quiet action, tinted toward the blue        */
  --fill-warm:#fffaf0;      /* a caution that must not shout                 */

  /* ---- the segmented switches (language, theme) ----
     A segmented control says which option is chosen by RAISING it out of its
     track, and "raised" is lighter than its surroundings. Both switches used
     --fill-4 for the track and --card for the chosen pill, which is exactly
     right on a light page — and upside down on a dark one, where --card is
     DARKER than --fill-4. The chosen option became a hole punched into a pale
     slab, with a black shadow deepening the hole. Two tokens instead, so the
     relationship is stated once per theme rather than inherited from tokens
     whose order flips between them. */
  /* one step deeper than --fill-4, so the chosen option is told apart by its
     fill and not by its shadow — a control whose state depends on a drop
     shadow has no state at all in forced-colours or on a bad screen */
  --switch-track:#dbe2eb;
  --switch-on:#ffffff;
  --shadow-switch:rgba(16,26,44,.12);
  /* Text ON a saturated fill. It is white here and near-black in dark mode,
     because those fills get BRIGHTER against a dark page rather than darker —
     a single "on dark" token was wrong in one theme whichever value it held. */
  --on-fill:#fff;
  --on-navy:#fff;           /* on the fixed navy hero, which never changes    */

  /* ---- the navigation rail ----
     Its own small palette, because the rail is the one surface in the console
     that is dark in BOTH themes. Reusing --navy and hoping was not enough: the
     label colour has to be measured against the rail, not against the page,
     and in dark mode the rail has to separate from a page that is already
     dark. Five tokens, checked by tools/check-contrast.py like everything
     else. */
  --rail:#0b2545;           /* MedBase Navy — the rail IS the brand colour    */
  --rail-line:#1b3a63;      /* its edge against the page                     */
  --rail-ink:#c8d8ec;       /* a section not currently open — 9.6:1 on navy   */
  --rail-ink-2:#8fa8c6;     /* the quiet line under the mark                 */
  --rail-hover:#16345c;     /* pointer over an unopened section              */

  /* Status EDGES, previously written inline beside each --*-soft */
  --ok-line:#9fd4bb; --warn-line:#edd3a6; --danger-line:#f2c9c9;

  /* The diagonal hatches on the rota: free, turnaround, dead time, held, and
     a closed hour on the heatmap. Two stops each, always used as a pair. */
  --hatch-free-a:#f0f3f7;  --hatch-free-b:#e4eaf1;
  --hatch-turn-a:#cdd6e1;  --hatch-turn-b:#dde3eb;
  --hatch-dead-a:#fbe6c6;  --hatch-dead-b:#f6d7a8;
  --hatch-held-a:#d0d8e2;  --hatch-held-b:#bec8d5;
  --hatch-shut-a:#f4f6f9;  --hatch-shut-b:#ebeef3;

  /* Depth. Navy-tinted on a light page; a dark page needs real black and more
     of it, because a navy shadow on a navy surface is not a shadow. */
  --shadow-1:rgba(16,26,44,.12);
  --shadow-2:rgba(11,37,69,.16);
  --shadow-3:rgba(16,26,44,.28);
  --shadow-card:rgba(21,96,207,.07);
  --edge:rgba(11,37,69,.14);          /* a hairline drawn INSIDE an image    */
  --chip:rgba(16,26,44,.09); --chip-hover:rgba(16,26,44,.2);
  --fade:#e5eaf1; --fade-0:rgba(229,234,241,0);   /* tab-bar scroll hint     */
  --scrim:rgba(11,37,69,.18); --scrim-0:rgba(11,37,69,0);
  --tip-ink:#fff; --tip-muted:#bbc6d4;            /* on the dark tooltip     */
  --backdrop:#5c6570;                              /* behind a printed sheet */
  /* Role badges. Assistant and owner each get a hue of their own so the two
     consoles are not distinguishable only by the word inside the pill. */
  --role-doc-soft:#efe6fb; --role-doc-ink:#5b2bb0;   /* 7.1:1 */
  --role-own-soft:#e8f0e4; --role-own-ink:#2f5d2a;   /* 6.8:1 */

  --s1:.5rem; --s2:1rem; --s3:1.5rem; --s4:2rem; --s5:3rem; --s6:4rem;
  --t-xs:.75rem; --t-sm:.8125rem; --t-md:.9375rem; --t-bd:1rem; --t-lg:1.25rem; --t-xl:1.75rem;
  --r:14px; --r-sm:10px; --tap:44px; --dur:200ms; --ease:cubic-bezier(.2,.7,.3,1);
}
*{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font:var(--t-bd)/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
  padding-bottom:env(safe-area-inset-bottom);
}
h1,h2,h3{margin:0;line-height:1.3;letter-spacing:-.011em;}

:where(a,button,input,select,[tabindex]):focus-visible{
  outline:3px solid var(--primary);outline-offset:2px;border-radius:6px;
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important;}
}
.skip{position:absolute;inset-inline-start:-9999px;top:0;z-index:99;background:var(--primary);color:var(--on-primary);
  padding:.75rem 1.25rem;border-radius:0 0 var(--r-sm) 0;font-weight:600;text-decoration:none;}
.skip:focus{inset-inline-start:0;}
/* Visually hidden, still read aloud. Pinned to the corner of its containing
   block: left at its static position, a hidden label inside a wide table sat
   at x≈600 and widened a phone's page by the width of the table, even though
   the table itself scrolled inside its box. */
.sr{position:absolute;top:0;inset-inline-start:0;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;}
/* Every horizontal scroller is also the containing block of what it scrolls,
   so nothing positioned inside it can escape and widen the page. */
.tablewrap,.tablescroll,.datescroll,.tabbar{position:relative;}
[hidden]{display:none!important;}

.wrap{max-width:58rem;margin:0 auto;padding:var(--s3) var(--s2) var(--s6);}
.wrap.wide{max-width:66rem;}

/* ---- masthead ---- */
.masthead{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  flex-wrap:wrap;margin-bottom:var(--s3);}
.brand{display:flex;align-items:center;gap:.75rem;}
/* The app mark in the masthead. Navy field, teal glyph — the logo's own
   relationship, at 5.87:1. */
.brand-mark{width:42px;height:42px;border-radius:12px;background:var(--navy);
  display:grid;place-items:center;color:var(--primary);flex-shrink:0;}
.brand-mark.staff{background:var(--navy);}
.brand-name{font-weight:700;font-size:var(--t-bd);}
.brand-sub{font-size:var(--t-sm);color:var(--ink-3);}
.crosslink{display:inline-flex;align-items:center;gap:.5rem;min-height:var(--tap);
  padding:0 1rem;border-radius:var(--r-sm);background:var(--fill-3);color:var(--ink-2);
  text-decoration:none;font-size:var(--t-sm);font-weight:650;
  transition:background var(--dur) var(--ease);}
.crosslink:hover{background:var(--fill-hover);color:var(--ink);}

.helper{background:var(--card);border:1px solid var(--line);border-inline-start:4px solid var(--primary);
  border-radius:var(--r-sm);padding:.875rem 1.125rem;font-size:var(--t-md);
  color:var(--ink-2);margin-bottom:var(--s3);}
.helper b{color:var(--ink);}
.helper.staff{border-inline-start-color:var(--ink);}

/* ---- offline banner ---- */
.offline{background:var(--warn-soft);border:1px solid var(--warn-line);border-inline-start:4px solid var(--warn);
  border-radius:var(--r-sm);padding:.875rem 1.125rem;font-size:var(--t-md);
  color:var(--ink-2);margin-bottom:var(--s3);}
.offline b{color:var(--ink);}

/* ---- stepper ---- */
.steps{list-style:none;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;
  margin:0 0 var(--s3);padding:0;font-size:var(--t-sm);}
.steps li{display:flex;align-items:center;gap:.5rem;color:var(--ink-3);}
.steps .num{width:26px;height:26px;border-radius:50%;background:var(--fill-4);color:var(--ink-2);
  display:grid;place-items:center;font-size:var(--t-xs);font-weight:700;flex-shrink:0;
  /* line-height:1 so the digit is centred by the BOX and not by whatever
     ascent the current font happens to have. The Arabic face runs a 1.75
     body leading, which pushed the numeral three pixels off centre even
     once the grid was restored. */
  line-height:1;}
.steps li[data-state=done] .num{background:var(--ok);color:var(--on-fill);}
.steps li[data-state=now] .num{background:var(--primary);color:var(--on-primary);}
.steps li[data-state=now]{color:var(--ink);font-weight:650;}
.steps li[data-state=done]{color:var(--ink-2);}
.steps button{background:none;border:0;padding:.25rem;font:inherit;color:inherit;cursor:pointer;
  display:flex;align-items:center;gap:.5rem;min-height:var(--tap);}
.steps .sep{color:var(--sep);}

/* ---- cards ---- */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:var(--s3);margin-bottom:var(--s2);}
.card > h2{font-size:var(--t-lg);margin-bottom:.375rem;}
.lede{color:var(--ink-2);font-size:var(--t-md);margin:0 0 var(--s3);max-width:48ch;}

/* ---- choices ---- */
.choices{display:grid;gap:.75rem;margin:0;padding:0;border:0;}
.choices.cols-3{grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));}
.choices.cols-1{grid-template-columns:1fr;}
.choice{display:flex;align-items:flex-start;gap:.875rem;width:100%;min-height:var(--tap);
  padding:1.125rem;text-align:start;background:var(--card);border:1.5px solid var(--line);
  border-radius:var(--r-sm);font:inherit;color:inherit;cursor:pointer;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease),transform var(--dur) var(--ease);}
.choice:hover{border-color:var(--primary-line);background:var(--primary-soft);}
.choice:active{transform:scale(.99);}
.choice-title{font-weight:650;display:block;}
.choice-sub{font-size:var(--t-sm);color:var(--ink-3);display:block;margin-top:.125rem;}
.choice-icon{width:38px;height:38px;border-radius:10px;flex-shrink:0;display:grid;place-items:center;
  background:var(--primary-soft);color:var(--primary-ink);}
.avatar{width:44px;height:44px;border-radius:50%;flex-shrink:0;display:grid;place-items:center;
  background:var(--primary-soft);color:var(--primary-ink);font-weight:700;font-size:var(--t-md);
  /* a photograph must not spill past the circle it is cropped to, and a
     portrait uploaded at 3:4 must fill it rather than letterbox inside it */
  overflow:hidden;}
.avatar img{width:100%;height:100%;object-fit:cover;display:block;}
/* A clinic portrait is very often a white coat against a white wall, and on a
   white card that renders as a hole where a face should be. The ring is inset
   so it never changes the circle's footprint, and it is on the IMG rather than
   on .avatar:has(img) — the same result without depending on :has(), which an
   older Android WebView would drop on the floor. The initials fallback keeps
   its own tinted disc and needs no ring. */
.avatar img{box-shadow:inset 0 0 0 1px var(--edge);border-radius:50%;}

/* The photograph slot is square where the logo slot is a landscape banner:
   the two images are cropped to different shapes on screen, and a preview
   that lies about the crop is worse than no preview. */
.logobox.portrait{width:110px;height:110px;border-radius:50%;padding:0;overflow:hidden;}
.logobox.portrait img{width:100%;height:100%;max-width:none;max-height:none;object-fit:cover;}
.vtype{flex-direction:column;gap:.625rem;}
.vtype-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;flex-wrap:wrap;}
.facts{display:flex;gap:1.25rem;flex-wrap:wrap;font-size:var(--t-sm);color:var(--ink-2);}
.facts span{display:flex;align-items:center;gap:.375rem;}

.tag{display:inline-flex;align-items:center;gap:.375rem;font-size:var(--t-xs);font-weight:700;
  letter-spacing:.02em;padding:.25rem .5rem;border-radius:6px;text-transform:uppercase;}
.tag.diagnosis{background:var(--diagnosis-soft);color:var(--diagnosis-ink);}
.tag.counseling{background:var(--counseling-soft);color:var(--counseling-ink);}
.tag.service{background:var(--service-soft);color:var(--service-ink);}
.tag.neutral{background:var(--fill-3);color:var(--ink-2);}
.tag .dot{width:7px;height:7px;border-radius:50%;background:currentColor;flex-shrink:0;}

/* ---- dates ---- */
.datescroll{display:flex;gap:.5rem;overflow-x:auto;padding:.25rem .25rem .75rem;
  margin-bottom:var(--s3);scroll-snap-type:x proximity;scrollbar-width:thin;}
.daybtn{scroll-snap-align:start;flex-shrink:0;min-width:4.5rem;min-height:var(--tap);
  padding:.625rem .5rem;background:var(--card);border:1.5px solid var(--line);
  border-radius:var(--r-sm);font:inherit;color:inherit;cursor:pointer;text-align:center;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);}
.daybtn:hover:not([disabled]){border-color:var(--primary-line);}
.daybtn[aria-checked=true]{background:var(--primary);border-color:var(--primary);color:var(--on-primary);}
.daybtn[disabled]{opacity:.55;cursor:not-allowed;background:var(--fill-1);}
.daybtn .dow{display:block;font-size:var(--t-xs);font-weight:600;text-transform:uppercase;
  letter-spacing:.04em;color:var(--ink-3);}
.daybtn .dnum{display:block;font-size:var(--t-lg);font-weight:700;line-height:1.2;margin:.125rem 0;}
.daybtn .dfree{display:block;font-size:var(--t-xs);font-weight:650;color:var(--ok);}
/* The chosen day is a teal pill now, not a dark blue one, so the two little
   lines inside it flipped from "pale text on dark" to "pale text on light" —
   2.1:1, and the free-slot count on the day you just picked was the least
   readable thing on the page. They take the navy that everything else on a
   teal fill takes, the sub-line one notch softer. */
/* full navy, not a faded one: at .72 alpha over teal it measured 2.95:1, and
   the weekday is information, not decoration. Hierarchy comes from the type
   size, which costs no contrast. */
.daybtn[aria-checked=true] .dow{color:var(--on-primary);}
.daybtn[aria-checked=true] .dfree{color:var(--on-primary);}
.daybtn[disabled] .dfree{color:var(--ink-3);}

/* ---- slots ---- */
.slotgroup{margin-bottom:var(--s3);}
.slotgroup:last-child{margin-bottom:0;}
.slotgroup h3{font-size:var(--t-sm);text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-3);font-weight:700;margin-bottom:.75rem;display:flex;align-items:center;gap:.5rem;}
.slotlist{display:grid;grid-template-columns:repeat(auto-fill,minmax(7rem,1fr));gap:.5rem;}
.slot{min-height:var(--tap);padding:.625rem .5rem;background:var(--card);
  border:1.5px solid var(--line);border-radius:var(--r-sm);
  font:650 var(--t-bd)/1.2 inherit;font-family:inherit;color:var(--ink);cursor:pointer;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);}
.slot:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary-ink);}
.slot:active{transform:scale(.98);}
.slot .to{display:block;font-size:var(--t-xs);font-weight:500;color:var(--ink-3);margin-top:.125rem;}
.slot:hover .to{color:var(--primary-ink);}
.slot.flush{border-color:var(--ok-line);}
.slot.flush .to{color:var(--ok);}

.notice{padding:1.25rem;border-radius:var(--r-sm);font-size:var(--t-md);background:var(--fill-1);
  border:1px dashed var(--line);color:var(--ink-2);text-align:center;}

/* ---- confirm ---- */
.confirm-top{display:flex;align-items:center;gap:.875rem;margin-bottom:var(--s3);}
.tick{width:46px;height:46px;border-radius:50%;background:var(--ok-soft);color:var(--ok);
  display:grid;place-items:center;flex-shrink:0;}
.summary{border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden;margin:0;}
.summary > div{display:flex;justify-content:space-between;gap:1rem;padding:.875rem 1.125rem;
  border-bottom:1px solid var(--line-soft);font-size:var(--t-md);}
.summary > div:last-child{border-bottom:0;}
.summary dt{color:var(--ink-3);}
.summary dd{margin:0;font-weight:650;text-align:end;}

.btnrow{display:flex;gap:.625rem;flex-wrap:wrap;margin-top:var(--s3);}
.btn{min-height:var(--tap);padding:0 1.25rem;border:0;border-radius:var(--r-sm);
  font:650 var(--t-md)/1 inherit;font-family:inherit;cursor:pointer;
  display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;
  transition:background var(--dur) var(--ease);}
.btn-primary{background:var(--primary);color:var(--on-primary);}
.btn-primary:hover{background:var(--primary-hover);}
.btn-quiet{background:var(--fill-3);color:var(--ink-2);}
.btn-quiet:hover{background:var(--fill-hover);}

/* ---- forms / console ---- */
.field{margin-bottom:var(--s2);}
.field label{display:block;font-size:var(--t-sm);font-weight:650;color:var(--ink-2);margin-bottom:.375rem;}
.field .hint{font-weight:400;color:var(--ink-3);display:block;margin-top:.125rem;}
/* Every text-entry type, not a hand-picked few: a type left off this list
   renders at browser-default height, which is 21px — half the 44px touch
   minimum. That is how the patient-search box shipped at 21px. */
/* `input:not([type])` is on this list because the type attribute is optional
   in HTML and defaults to text — so an author who writes <input id=x> gets a
   perfectly valid text box that this rule, listing types explicitly, would
   miss entirely. It renders at 21px next to its 44px neighbours, which reads
   as a broken layout rather than a missing attribute. It happened twice. */
select,textarea,
input:not([type]),
input[type=time],input[type=number],input[type=text],
input[type=search],input[type=email],input[type=tel],input[type=date],
input[type=password],input[type=url]{
  width:100%;min-height:var(--tap);padding:.5rem .75rem;font:var(--t-md)/1.4 inherit;
  font-family:inherit;color:var(--ink);background:var(--card);
  border:1.5px solid var(--line);border-radius:var(--r-sm);}
textarea{line-height:1.6;resize:vertical;}
.narrow{max-width:22rem;}
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));gap:.75rem;}
.tile{border:1px solid var(--line);border-radius:var(--r-sm);padding:1rem;}
.tile-head{margin-bottom:.75rem;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;}
/* what the visit bills as, and whether stock may be given on it — the facts
   that used to be implied by the word "Service" and now have to be stated */
.tile-sub{margin:-.375rem 0 .75rem;font-size:var(--t-sm);color:var(--ink-3);}
.tile-acts{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.875rem;}
/* Retired: still on his screen, visibly not on the menu. Dimmed rather than
   hidden — a visit he cannot see is a visit he cannot offer again. */
.tile-off{background:var(--line-soft,var(--fill-1));border-style:dashed;}
.tile-off .tile-head .tag:first-child{opacity:.65;}
/* "waiting on the owner's fee" — not an error, not finished either */
.tag.warn{background:var(--warn-soft);color:var(--warn);}
/* State tags on the floor board. The WORD inside each one already says the
   state — "With a patient", "Free" — so the colour is the second signal and
   never the only one. That is the same rule the rota's hatched blocks follow. */
.tag.good{background:var(--ok-soft);color:var(--ok);}
.tag.bad{background:var(--danger-soft);color:var(--danger);}
.card.sub{margin-top:var(--s3);border:1px solid var(--primary-line);box-shadow:none;}
.btn.danger{color:var(--danger);}
.btn.danger:hover{background:var(--danger-soft);}
.pair{display:flex;gap:.625rem;}
.pair > div{flex:1;}
.pair label{font-size:var(--t-xs);color:var(--ink-3);font-weight:600;}
.winrow{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:1rem 1.125rem;
  border:1px solid var(--line);border-radius:var(--r-sm);margin-bottom:.5rem;background:var(--card);}
.winrow-body{flex:1;min-width:14rem;}
.winrow-time{font-weight:650;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;}
.winrow-calc{font-size:var(--t-sm);color:var(--ink-2);margin-top:.25rem;}
.iconbtn{width:var(--tap);height:var(--tap);border:0;border-radius:var(--r-sm);background:transparent;
  color:var(--ink-3);cursor:pointer;display:grid;place-items:center;flex-shrink:0;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease);}
.iconbtn:hover{background:var(--danger-soft);color:var(--danger);}
.daygroup{font-size:var(--t-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-3);margin:var(--s3) 0 .5rem;}
.daygroup:first-child{margin-top:0;}
.addgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));gap:.75rem;align-items:end;}
.err{color:var(--danger);font-size:var(--t-sm);margin:.75rem 0 0;}

.stats{display:flex;gap:2rem;flex-wrap:wrap;padding:1.125rem 1.25rem;
  background:var(--primary-soft);border-radius:var(--r-sm);margin-top:var(--s3);}
.stat{font-size:var(--t-sm);color:var(--ink-2);}
.stat b{display:block;font-size:1.375rem;font-weight:700;line-height:1.25;color:var(--primary-ink);}
.stat.warn b{color:var(--warn);}
.stat.good b{color:var(--ok);}

/* ---- timeline: never colour alone ---- */
.tlblock{margin-bottom:var(--s3);}
.tlhead{font-size:var(--t-sm);font-weight:650;color:var(--ink-2);margin-bottom:.5rem;}
.tl{display:flex;height:52px;border-radius:var(--r-sm);overflow:hidden;border:1px solid var(--line);}
.tlseg{display:grid;place-items:center;font-size:var(--t-xs);font-weight:700;
  overflow:hidden;white-space:nowrap;min-width:0;color:var(--on-fill);}
.tlseg.diagnosis{background:var(--diagnosis);}
.tlseg.counseling{background:var(--counseling);}
.tlseg.service{background:var(--service);}
.tlseg.free{background:repeating-linear-gradient(45deg,var(--hatch-free-a),var(--hatch-free-a) 6px,var(--hatch-free-b) 6px,var(--hatch-free-b) 12px);color:var(--ink-2);}
.tlseg.turn{background:repeating-linear-gradient(90deg,var(--hatch-turn-a),var(--hatch-turn-a) 3px,var(--hatch-turn-b) 3px,var(--hatch-turn-b) 6px);color:var(--ink-2);}
.tlseg.dead{background:repeating-linear-gradient(-45deg,var(--hatch-dead-a),var(--hatch-dead-a) 5px,var(--hatch-dead-b) 5px,var(--hatch-dead-b) 10px);color:var(--warn);}
.tlaxis{display:flex;justify-content:space-between;font-size:var(--t-xs);color:var(--ink-3);margin-top:.375rem;}
.tllist{list-style:none;margin:.875rem 0 0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:.25rem .875rem;font-size:var(--t-sm);}
.tllist li{display:flex;align-items:center;gap:.5rem;color:var(--ink-2);padding:.125rem 0;}
.tllist .sw{width:12px;height:12px;border-radius:3px;flex-shrink:0;border:1px solid var(--edge);}
.tllist time{font-variant-numeric:tabular-nums;color:var(--ink);font-weight:600;}

/* ---- the emergency walk-in, folded under the booking form ----
   Separated by a rule rather than a box: it belongs to the booking card, it is
   just not the ordinary path. border-top, not margin alone, so the division is
   visible at any zoom. */
.emwrap{border-top:1px solid var(--line);margin-top:var(--s3);padding-top:var(--s3);}
.emwrap .bookform{margin-top:.75rem;}

/* ---- a visit outside the rota ----
   Set apart rather than recoloured: it is not a different KIND of visit, it is
   the same visit sitting outside the clinic's hours. The visit block keeps its
   own class colour so the eye still reads "diagnosis"; the frame around it is
   what says "this is not rota time". Tokens only, so it follows both palettes,
   and border-inline-start so the rule lands on the correct side in Arabic. */
.tlblock.offhours{background:var(--warn-soft);border:1px solid var(--warn-line);
  border-inline-start:4px solid var(--warn);border-radius:var(--r-sm);
  padding:.875rem 1rem;margin-bottom:var(--s3);}
.tlblock.offhours .tlhead{color:var(--warn);display:flex;align-items:center;gap:.375rem;}
.tlblock.offhours .tl{height:40px;}
.tlblock.offhours .tl + .tl{margin-top:.5rem;}
.tlblock.offhours .tllist{margin-top:.625rem;}

.callout{padding:1rem 1.125rem;border-radius:var(--r-sm);font-size:var(--t-md);
  background:var(--warn-soft);border-inline-start:4px solid var(--warn);color:var(--ink-2);margin-top:var(--s2);}
.callout b{color:var(--ink);}
.callout.good{background:var(--ok-soft);border-inline-start-color:var(--ok);}
.callout.warn{background:var(--warn-soft);border-inline-start-color:var(--warn);}
.switch{display:flex;align-items:flex-start;gap:.75rem;}
.switch input{width:22px;height:22px;min-height:0;flex-shrink:0;margin:.6rem 0 0;accent-color:var(--primary);}
.switch label{font-weight:500;font-size:var(--t-md);color:var(--ink-2);margin:0;}
.switch label b{color:var(--ink);font-weight:650;display:block;}

.loading{padding:var(--s4);text-align:center;color:var(--ink-3);font-size:var(--t-md);}

@media (max-width:32rem){
  .wrap{padding:var(--s2) .75rem var(--s5);}
  .card{padding:var(--s2);}
  .stats{gap:1.25rem;}
}

/* ============================================================
   DASHBOARD — tabs, stat tiles, charts
   Chart marks follow the dataviz spec: thin marks, 4px rounded
   data-ends, 2px lines, >=8px markers, 2px surface gaps, recessive
   axes, text in text tokens (never the series colour).
   ============================================================ */
/* The strip scrolls, and on a phone it must SAY so. Five tabs need 584px; a
   360px screen showed three complete words ending in a tidy rounded corner, so
   a doctor working from a phone had no reason to swipe and Schedule, Settings
   and the audit trail simply were not part of the product for him. Android
   draws no scrollbar until you already scroll, so the affordance has to be in
   the paint. Two devices, no JS: a shadow at each end that is covered up by a
   same-coloured `background-attachment:local` layer once that end is reached —
   so the shadow appears only on the side that still has tabs — and a narrower
   min-width below 32rem so a sliver of the next tab always peeks. */
.tabbar{display:flex;gap:.25rem;background-color:var(--fill-4);padding:.25rem;border-radius:12px;
  margin-bottom:var(--s3);overflow-x:auto;scrollbar-width:thin;
  background-image:
    linear-gradient(to right,var(--fill-4) 40%,var(--fade-0)),
    linear-gradient(to left, var(--fill-4) 40%,var(--fade-0)),
    linear-gradient(to right,var(--scrim),var(--scrim-0)),
    linear-gradient(to left, var(--scrim),var(--scrim-0));
  background-position:left center,right center,left center,right center;
  background-repeat:no-repeat;
  background-size:30px 100%,30px 100%,13px 100%,13px 100%;
  background-attachment:local,local,scroll,scroll;}
/* On a narrow strip the icon is the first thing to go: it decorates a word
   that is already there, and 5rem of button is not enough for both. */
@media (max-width:32rem){ .tabbar button{min-width:5rem;} .tabbar button svg{display:none;} }
.tabbar button{flex:1;min-width:7rem;min-height:var(--tap);padding:0 1rem;border:0;
  border-radius:9px;background:transparent;color:var(--ink-2);
  font:650 var(--t-md)/1 inherit;font-family:inherit;cursor:pointer;white-space:nowrap;
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  transition:background var(--dur) var(--ease);}
/* The selected tab is a TOGGLE IN ITS ACTIVE STATE, which the brand assigns
   to Data Teal. It was a white pill, which said "raised" but not "chosen" —
   on a white page a white pill is the same colour as everywhere else, and
   the only thing marking the current tab was a shadow. Teal fill, navy
   label: 5.87:1, and unmistakable at a glance across the room. */
.tabbar button[aria-selected=true]{background:var(--primary);color:var(--on-primary);
  box-shadow:0 1px 4px var(--shadow-2);}
/* The icon beside a section's name. It is decoration — the word is the label,
   in both languages — so it never appears alone and never carries meaning the
   word does not already carry. */
.tabbar button svg{flex-shrink:0;opacity:.85;}
.tabbar button[aria-selected=true] svg{opacity:1;}

/* ============================================================
   THE CONSOLE SHELL — a rail on a desk, a strip on a phone

   Same buttons, same tablist, same keyboard model: what changes is where they
   are drawn. Rendering a second navigation for wide screens would mean two
   sets of controls for one set of panels — two things to keep in step, and an
   `aria-controls` pointing at a panel from two places. So the tab strip simply
   becomes a column, and every line of the JS behind it is untouched.

   60rem is where the rail earns its width: below it, a fixed 15rem column is a
   quarter of the screen spent on navigation the doctor already knows.
   ============================================================ */
@media (min-width:60rem){
  .wrap.shell{
    /* full width, because the rail runs to the edge of the glass the way it
       does on every console that has one; the CONTENT is what gets a readable
       measure, not the page */
    max-width:none;margin:0;padding:0;
    display:grid;
    grid-template-columns:15.5rem minmax(0,1fr);
    grid-template-areas:"railhead head"
                        "rail     main";
    grid-template-rows:auto 1fr;
    align-items:start;
    min-height:100dvh;
  }
  .wrap.shell > .railhead{grid-area:railhead;}
  .wrap.shell > .masthead{grid-area:head;margin:0;padding:var(--s3) var(--s3) var(--s2);}
  .wrap.shell > main     {grid-area:main;padding:0 var(--s3) var(--s6);max-width:74rem;}

  /* the rail */
  .wrap.shell > .railhead,
  .wrap.shell > .tabbar{
    background:var(--rail);background-image:none;   /* drop the scroll shadows */
    border-inline-end:1px solid var(--rail-line);
    border-radius:0;margin:0;}
  .wrap.shell > .railhead{
    display:flex;align-items:center;gap:.75rem;
    padding:var(--s3) 1rem var(--s2);align-self:stretch;}
  .wrap.shell > .tabbar{
    /* stretched, not sticky: the row is 1fr of a container at least one
       viewport tall, so the dark column runs the full height of whatever the
       page turns out to be. A sticky rail would have to be shorter than its
       own row to have anywhere to travel. */
    align-self:stretch;
    display:flex;flex-direction:column;gap:.2rem;
    padding:.5rem .625rem var(--s3);
    overflow-x:visible;overflow-y:visible;}
  .wrap.shell > .tabbar button{
    flex:0 0 auto;width:100%;min-width:0;
    justify-content:flex-start;text-align:start;
    min-height:2.75rem;padding:0 .875rem;border-radius:10px;
    color:var(--rail-ink);font-weight:600;}
  .wrap.shell > .tabbar button:hover{background:var(--rail-hover);color:#fff;}
  /* Same rule as the strip: the open section is a toggle in its active state,
     which the brand assigns to Data Teal. Navy label on teal, 5.87:1. */
  .wrap.shell > .tabbar button[aria-selected=true]{
    background:var(--primary);color:var(--on-primary);box-shadow:none;}
  .wrap.shell > .tabbar button:focus-visible{outline-offset:-3px;}

  /* one MedBase mark per screen — see the note beside the class in the page */
  .wrap.shell > .masthead .appmark.fallback{display:none;}
}
/* The product's own identity lives here, in the corner of the rail, and
   nowhere else on a staff screen. The masthead stays the CLINIC's — its name
   is what the people using this all day work for, and the one place a vendor
   mark must never appear at all is the page patients see. */
/* ---- narrowed ----
   Icons only, at the width of a tap target. The words are not deleted, they
   are un-shown: each button keeps its label for a screen reader, and gains a
   `title` so a pointer can still ask what it is. Below the rail's breakpoint
   the class means nothing — there is no rail to narrow. */
/* The state lives on the ROOT, not on the shell, so the page can be told about
   it in <head> — before the first paint and before .wrap exists. A rail that
   was narrowed yesterday must not swing open and shut on every navigation. */
@media (min-width:60rem){
  .rail-narrow .wrap.shell{grid-template-columns:4.5rem minmax(0,1fr);}
  .rail-narrow .wrap.shell > .railhead{justify-content:center;padding-inline:.5rem;}
  .rail-narrow .wrap.shell > .railhead .rail-id{display:none;}
  .rail-narrow .wrap.shell > .tabbar button{justify-content:center;padding:0;}
  /* The label is HIDDEN, not removed. `display:none` would take it out of the
     accessibility tree too and leave six buttons with no name between them —
     an icon-only rail that a screen reader reads as six blanks. Clipped, it
     is still the button's accessible name; the `title` set alongside gives a
     pointer the same word. */
  .rail-narrow .wrap.shell > .tabbar button > span{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}
  /* The width animates; the words do not fade with it, because a label caught
     half-way through a fade is a label you cannot read. */
  .wrap.shell{transition:grid-template-columns var(--dur-slow,.22s) var(--ease);}
}
@media (prefers-reduced-motion:reduce){ .wrap.shell{transition:none;} }

/* The control that does it. Not part of the tablist — it opens and closes the
   rail, it does not choose a section — so it sits in the rail's head, where it
   is the first thing Tab reaches on the way into the navigation. */
.railtoggle{display:none;}
@media (min-width:60rem){
  .railtoggle{display:grid;place-items:center;flex-shrink:0;
    width:34px;height:34px;margin-inline-start:auto;
    border:0;border-radius:9px;background:transparent;color:var(--rail-ink);
    cursor:pointer;padding:0;
    transition:background var(--dur) var(--ease),color var(--dur) var(--ease);}
  .railtoggle:hover{background:var(--rail-hover);color:#fff;}
  .railtoggle:focus-visible{outline:3px solid var(--primary);outline-offset:2px;}
  /* The chevron points the way the rail will move: toward the page edge to
     close, away from it to open. `scaleX(-1)` rather than a second icon, and
     RTL flips it again — in Arabic the rail's edge is the other one. */
  .railtoggle svg{transition:transform var(--dur) var(--ease);}
  [dir=rtl] .railtoggle svg,
  .rail-narrow .railtoggle svg{transform:scaleX(-1);}
  [dir=rtl] .rail-narrow .railtoggle svg{transform:none;}
  .rail-narrow .wrap.shell > .railhead .railtoggle{margin-inline:auto;}
  /* narrowed, the mark yields its place to the control that brings it back */
  .rail-narrow .wrap.shell > .railhead .rail-brand{display:none;}
}

.railhead .rail-mark{width:34px;height:34px;flex-shrink:0;display:grid;place-items:center;}
.railhead .rail-mark svg{width:34px;height:34px;display:block;}
.railhead .rail-id{min-width:0;}
/* Small, quiet, and ABOVE the name it qualifies. Not uppercased: the app turns
   letter-spacing and capitals off in Arabic, where both are meaningless, and a
   kicker that changes shape between languages is one more thing to keep true. */
.railhead .rail-kicker{display:block;font-size:var(--t-xs);color:var(--rail-ink-2);
  line-height:1.2;margin-bottom:.1rem;}
.railhead .wordmark{display:block;line-height:1.05;}
/* Below the breakpoint the rail's furniture has no place to be: the strip is
   the navigation again, and the clinic's name is back in the masthead. */
@media (max-width:59.999rem){
  .railhead{display:none;}
}

/* --- KPI row: stat tiles, not a chart --- */
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.75rem;
  margin-bottom:var(--s2);}
.kpi{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:1.125rem 1.25rem;}
.kpi .label{font-size:var(--t-sm);color:var(--ink-3);font-weight:600;margin-bottom:.375rem;
  display:flex;align-items:center;gap:.375rem;}
.kpi .value{font-size:1.75rem;font-weight:700;line-height:1.15;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;}
.kpi .value .unit{font-size:var(--t-md);font-weight:600;color:var(--ink-3);margin-inline-start:.15rem;}
.kpi .sub{font-size:var(--t-sm);color:var(--ink-3);margin-top:.25rem;}
.delta{display:inline-flex;align-items:center;gap:.2rem;font-size:var(--t-sm);font-weight:700;
  padding:.1rem .4rem;border-radius:5px;}
.delta.up{background:var(--ok-soft);color:var(--ok);}
.delta.down{background:var(--warn-soft);color:var(--warn);}
.delta.flat{background:var(--fill-2);color:var(--ink-3);}

/* --- chart figure --- */
/* margin-inline:0 is not cosmetic. The UA sheet gives <figure> `margin:1em 40px`,
   and nothing here ever reset it — so every chart card was inset 80px in total.
   On a 360px phone that left the card 256px wide, which is narrower than the
   fallback table's min-content width, which is what pushed the table off the
   screen. One unreset UA default was the root of the amputated tables. */
.figure{background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:var(--s3);margin:0 0 var(--s2);}
.figure > figcaption{margin-bottom:.25rem;}
.figure h3{font-size:var(--t-bd);margin:0;}
.figure .note{font-size:var(--t-sm);color:var(--ink-3);margin:.15rem 0 var(--s3);}
/* min(20rem,100%) rather than 20rem: a bare 20rem track cannot shrink below
   320px, so on a 280px phone the grid was 40px wider than the screen and — with
   body clipping — those 40px were simply gone. */
.chartgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr));gap:var(--s2);}
.chartgrid .figure{margin-bottom:0;}
.legend{display:flex;gap:1rem;flex-wrap:wrap;font-size:var(--t-sm);color:var(--ink-2);margin-top:.875rem;}

/* ---- the donut ----
   Ring and key side by side on a desktop, stacked on a phone. The key is the
   direct labelling: the ring says "roughly half", the key says which half and
   exactly how much, which is what stops a pie being a decoration. */
.donutwrap{display:flex;align-items:center;gap:var(--s4);flex-wrap:wrap;}
.donut{width:172px;height:172px;flex-shrink:0;}
/* The hole is not empty — it carries the number the ring is a breakdown OF,
   which is the one figure a reader would otherwise have to add up themselves. */
.donut-total{fill:var(--ink);font:700 22px/1 inherit;font-family:inherit;
  text-anchor:middle;font-variant-numeric:tabular-nums;}
/* The hole is ~100px across. Anything longer than a currency code spills onto
   the ring — "CLINIC KEEPS" did exactly that — so what goes here is the unit,
   and the panel's own title says what the number is OF. */
.donut-cap{fill:var(--ink-3);font:600 11px/1 inherit;font-family:inherit;
  text-anchor:middle;letter-spacing:.06em;text-transform:uppercase;}
.donut circle{transition:opacity 120ms var(--ease);}
.donut:hover circle[data-tip]{opacity:.55;}
.donut circle[data-tip]:hover,.donut circle[data-tip]:focus-visible{opacity:1;}
.donut circle[data-tip]:focus-visible{outline:none;stroke-width:30;}

/* Capped, not fluid. Left to grow it stretched across a 1180px panel and
   parked every percentage a hand's width from the name it belonged to, which
   is the one job direct labelling exists to do. */
.donutkey{list-style:none;margin:0;padding:0;flex:1 1 14rem;min-width:0;
  max-width:24rem;display:flex;flex-direction:column;gap:.75rem;}
.donutkey li{display:grid;grid-template-columns:auto 1fr auto;gap:.25rem .625rem;
  align-items:baseline;}
.donutkey i{width:11px;height:11px;border-radius:3px;flex-shrink:0;
  align-self:center;grid-row:1;}
.dk-name{font-weight:650;color:var(--ink);min-width:0;}
.dk-pct{font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;}
/* The supporting figures sit under the name, spanning the two label columns,
   so a long specialty name never pushes the percentage off a phone. */
.dk-sub{grid-column:2/-1;font-size:var(--t-sm);color:var(--ink-2);
  font-variant-numeric:tabular-nums;}

/* ---- one composition bar ----
   A 2px surface gap between segments, per the mark spec: touching fills read
   as one shape and the eye stops finding the boundary. */
.sharebar{display:flex;gap:2px;height:16px;border-radius:8px;overflow:hidden;
  background:var(--fill-2);margin-bottom:var(--s3);}
.sharebar i{display:block;min-width:3px;transition:opacity 120ms var(--ease);}
.sharebar:hover i{opacity:.55;}
.sharebar i:hover,.sharebar i:focus-visible{opacity:1;outline:none;}
.sharebar i:first-child{border-radius:8px 0 0 8px;}
.sharebar i:last-child{border-radius:0 8px 8px 0;}
[dir=rtl] .sharebar i:first-child{border-radius:0 8px 8px 0;}
[dir=rtl] .sharebar i:last-child{border-radius:8px 0 0 8px;}

@media (max-width:520px){
  .donutwrap{gap:var(--s3);}
  .donut{width:140px;height:140px;margin:0 auto;}
}
@media print{ .donut circle{transition:none;} }
.legend span{display:inline-flex;align-items:center;gap:.4rem;}
.legend i{width:11px;height:11px;border-radius:3px;flex-shrink:0;}
details.tableview{margin-top:.875rem;}
details.tableview summary{font-size:var(--t-sm);color:var(--ink-2);cursor:pointer;
  min-height:var(--tap);display:flex;align-items:center;font-weight:600;}
/* A table is the one element that genuinely cannot be made narrower — six
   columns of money have a min-content width and that is that. So it gets its
   own scroller. Without one, `body{overflow-x:hidden}` does not push the table
   off the side of the page, it CUTS IT OFF: at 360px the owner's "Clinic keeps"
   column sat entirely past the right edge with no gesture that could reach it,
   and in Arabic the mirrored overflow truncated 23,940 into 940 — a wrong
   number that looks like a right one. tabindex+role make it keyboard-scrollable
   too, which is required once a region scrolls. */
.tablescroll{overflow-x:auto;-webkit-overflow-scrolling:touch;margin-top:.5rem;}
.tablescroll:focus-visible{outline:3px solid var(--primary);outline-offset:2px;border-radius:var(--r-sm);}
details.tableview table{width:100%;min-width:max-content;border-collapse:collapse;
  font-size:var(--t-sm);margin-top:0;}
details.tableview th,details.tableview td{text-align:start;padding:.375rem .5rem;
  border-bottom:1px solid var(--line-soft);color:var(--ink-2);}
details.tableview th{color:var(--ink-3);font-weight:650;}
details.tableview td:last-child,details.tableview th:last-child{text-align:end;
  font-variant-numeric:tabular-nums;}

svg.chart{display:block;width:100%;height:auto;overflow:visible;}
svg.chart .grid{stroke:var(--line-soft);stroke-width:1;}
svg.chart .axis{fill:var(--ink-3);font-size:11px;font-family:inherit;}
svg.chart .axis-strong{fill:var(--ink-2);font-size:11px;font-weight:600;font-family:inherit;}
svg.chart .val{fill:var(--ink);font-size:11px;font-weight:700;font-family:inherit;
  font-variant-numeric:tabular-nums;}
svg.chart .line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
svg.chart .dot{r:4.5;}
svg.chart .hit{fill:transparent;cursor:pointer;}
svg.chart .crosshair{stroke:var(--ink-3);stroke-width:1;stroke-dasharray:3 3;opacity:0;}

/* --- heatmap --- */
.heat{display:grid;gap:2px;}                 /* 2px surface gap between cells */
.heatcell{aspect-ratio:1.7;border-radius:5px;display:grid;place-items:center;
  font-size:var(--t-xs);font-weight:700;font-variant-numeric:tabular-nums;cursor:default;}
.heatlabel{font-size:var(--t-xs);color:var(--ink-3);font-weight:600;display:grid;
  place-items:center;text-align:center;}
.heatscale{display:flex;align-items:center;gap:.5rem;font-size:var(--t-xs);
  color:var(--ink-3);margin-top:.75rem;}
.heatscale .steps{display:flex;gap:2px;}
.heatscale .steps i{width:20px;height:11px;border-radius:2px;}

/* --- meters (stock vs threshold) --- */
.meters{display:flex;flex-direction:column;gap:.875rem;}
.meter-head{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;
  font-size:var(--t-md);margin-bottom:.375rem;flex-wrap:wrap;}
.meter-head b{font-weight:650;}
.meter-qty{font-variant-numeric:tabular-nums;color:var(--ink-2);font-size:var(--t-sm);}
.meter-track{position:relative;height:12px;background:var(--fill-2);border-radius:6px;overflow:visible;}
.meter-fill{height:100%;border-radius:6px;}
.meter-fill.good{background:var(--ok);}
.meter-fill.warning{background:var(--warn);}
.meter-fill.critical{background:var(--danger);}
.meter-threshold{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--ink-2);border-radius:1px;}
.meter-foot{display:flex;justify-content:space-between;gap:1rem;font-size:var(--t-xs);
  color:var(--ink-3);margin-top:.3rem;flex-wrap:wrap;}
.statuspill{display:inline-flex;align-items:center;gap:.3rem;font-size:var(--t-xs);
  font-weight:700;padding:.15rem .45rem;border-radius:5px;text-transform:uppercase;letter-spacing:.02em;}
.statuspill.good{background:var(--ok-soft);color:var(--ok);}
.statuspill.warning{background:var(--warn-soft);color:var(--warn);}
.statuspill.critical{background:var(--danger-soft);color:var(--danger);}
.stockinput{width:5.5rem;min-height:36px;padding:.25rem .5rem;font-size:var(--t-sm);}

/* --- phone booking --- */
.bookform{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.875rem;align-items:end;}
.slotpick{display:grid;grid-template-columns:repeat(auto-fill,minmax(6.5rem,1fr));gap:.5rem;margin-top:.875rem;}
.slotpick button{min-height:var(--tap);border:1.5px solid var(--line);border-radius:var(--r-sm);
  background:var(--card);font:650 var(--t-md)/1 inherit;font-family:inherit;color:var(--ink);cursor:pointer;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);}
.slotpick button:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary-ink);}
.slotpick button[aria-pressed=true]{background:var(--primary);border-color:var(--primary);color:var(--on-primary);}

.tooltip{position:fixed;z-index:50;pointer-events:none;background:var(--ink);color:var(--tip-ink);
  padding:.5rem .7rem;border-radius:8px;font-size:var(--t-sm);line-height:1.45;
  box-shadow:0 4px 14px var(--shadow-3);opacity:0;transition:opacity 120ms var(--ease);
  max-width:15rem;}
.tooltip.on{opacity:1;}
.tooltip b{color:var(--tip-ink);}
.tooltip .muted{color:var(--tip-muted);}
.heatcell.closed{background:repeating-linear-gradient(45deg,var(--hatch-shut-a),var(--hatch-shut-a) 4px,var(--hatch-shut-b) 4px,var(--hatch-shut-b) 8px);
  color:var(--ink-3);font-weight:400;}
.heatscale i.closedkey{width:20px;height:11px;border-radius:2px;display:inline-block;
  background:repeating-linear-gradient(45deg,var(--hatch-shut-a),var(--hatch-shut-a) 4px,var(--hatch-shut-b) 4px,var(--hatch-shut-b) 8px);
  vertical-align:middle;}
/* a slot mid-request: visible acknowledgement that the click landed */
.slot.picking{background:var(--primary);border-color:var(--primary);color:var(--on-primary);}
.slot.picking .to{color:var(--seq-1);}
.slot[disabled]{cursor:default;}
.slot[disabled]:not(.picking){opacity:.5;}

/* ---- patient contact details form ---- */
.formgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;}
.formgrid .field.full{grid-column:1/-1;}
.formgrid .field{margin:0;}
textarea{width:100%;padding:.55rem .75rem;font:var(--t-md)/1.5 inherit;font-family:inherit;
  color:var(--ink);background:var(--card);border:1.5px solid var(--line);
  border-radius:var(--r-sm);resize:vertical;min-height:5rem;}
textarea:focus{outline:2px solid var(--primary-soft);border-color:var(--primary);}
[aria-invalid=true]{border-color:var(--danger);}
label .req{font-size:var(--t-xs);font-weight:700;color:var(--danger);text-transform:uppercase;
  letter-spacing:.03em;margin-inline-start:.3rem;}
label .opt{font-size:var(--t-xs);font-weight:600;color:var(--ink-3);text-transform:uppercase;
  letter-spacing:.03em;margin-inline-start:.3rem;}
.holdnote{font-size:var(--t-sm);font-weight:600;color:var(--ok);background:var(--ok-soft);
  border-radius:var(--r-sm);padding:.6rem .875rem;margin:0 0 var(--s3);
  font-variant-numeric:tabular-nums;}
.holdnote.urgent{color:var(--warn);background:var(--warn-soft);}
/* a slot being filled in by someone else, on the staff timeline */
.tlseg.held{background:repeating-linear-gradient(45deg,var(--hatch-held-a),var(--hatch-held-a) 5px,var(--hatch-held-b) 5px,var(--hatch-held-b) 10px);
  color:var(--ink-2);}

/* ============================================================
   TODAY — the upcoming patient
   ============================================================ */
.upnext{background:var(--card);border:1px solid var(--primary-line);border-radius:var(--r);
  padding:var(--s3);margin-bottom:var(--s2);box-shadow:0 2px 10px var(--shadow-card);}
.upnext-head{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;
  flex-wrap:wrap;margin-bottom:.875rem;}
.upnext-label{font-size:var(--t-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  color:var(--primary-ink);background:var(--primary-soft);padding:.25rem .6rem;border-radius:6px;}
.upnext-when{font-size:var(--t-md);font-weight:650;color:var(--ink-2);font-variant-numeric:tabular-nums;}
.upnext-body{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;flex-wrap:wrap;}
.upnext-who{display:flex;gap:.875rem;align-items:center;}
.upnext-name{font-size:1.375rem;font-weight:700;letter-spacing:-.015em;line-height:1.2;}
.histrow{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.3rem;}
.hist{font-size:var(--t-xs);font-weight:650;color:var(--ink-2);background:var(--fill-2);
  padding:.15rem .45rem;border-radius:5px;}
.hist.new{background:var(--ok-soft);color:var(--ok);}
.hist.warn{background:var(--warn-soft);color:var(--warn);}
.reason{margin:.875rem 0 0;padding:.75rem 1rem;background:var(--fill-1);border-inline-start:3px solid var(--primary-line);
  border-radius:0 var(--r-sm) var(--r-sm) 0;font-size:var(--t-md);color:var(--ink);}
.reason-label{display:block;font-size:var(--t-xs);font-weight:700;text-transform:uppercase;
  letter-spacing:.05em;color:var(--ink-3);margin-bottom:.2rem;}
.reason.none{color:var(--ink-3);font-style:italic;border-inline-start-color:var(--line);background:var(--fill-0);}
.upnext-foot{display:flex;gap:1rem;flex-wrap:wrap;align-items:center;margin-top:.875rem;
  font-size:var(--t-sm);}
.contact{display:inline-flex;align-items:center;gap:.35rem;min-height:38px;color:var(--primary-ink);
  font-weight:650;text-decoration:none;}
.contact:hover{text-decoration:underline;}
.contact.quiet{color:var(--ink-3);font-weight:500;}
.pasthistory{margin-top:.875rem;}
.pasthistory summary{font-size:var(--t-sm);font-weight:650;color:var(--ink-2);cursor:pointer;
  min-height:var(--tap);display:flex;align-items:center;}
.pasthistory ul,.pastlist{list-style:none;margin:.4rem 0 0;padding:0;font-size:var(--t-sm);}
.pasthistory li,.pastlist li{display:flex;gap:.75rem;align-items:center;padding:.35rem 0;
  border-bottom:1px solid var(--line-soft);color:var(--ink-2);flex-wrap:wrap;}
.pasthistory li:last-child,.pastlist li:last-child{border-bottom:0;}
.pasthistory time,.pastlist time{font-variant-numeric:tabular-nums;color:var(--ink-3);white-space:nowrap;}
.muted{color:var(--ink-3);}

/* running order */
.runlist{list-style:none;margin:0;padding:0;}
.runlist li{border-bottom:1px solid var(--line-soft);}
.runlist li:last-child{border-bottom:0;}
.runlist li.isnext{background:var(--primary-soft);border-radius:var(--r-sm);}
.runlist li.past .runrow{opacity:.55;}
.runrow{display:flex;align-items:center;gap:.875rem;width:100%;min-height:var(--tap);
  padding:.75rem .625rem;background:none;border:0;font:inherit;color:inherit;cursor:pointer;
  text-align:start;flex-wrap:wrap;}
.runrow:hover{background:var(--fill-1);border-radius:var(--r-sm);}
.runrow time{font-variant-numeric:tabular-nums;font-weight:700;min-width:3.4rem;}
.runname{flex:1;min-width:9rem;font-weight:650;display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;}
.runrow .chev{color:var(--ink-3);}
.rundetail{padding:0 .625rem 1rem 4.3rem;}
@media (max-width:32rem){ .rundetail{padding-inline-start:.625rem;} }

/* ============================================================
   RUNNING THE DAY — pace, consultation controls, recorded actuals
   The doctor uses these standing up, between patients, so every control
   is a full 44px target and the primary action is unmistakable.
   ============================================================ */
.pace{display:flex;flex-wrap:wrap;gap:.35rem;padding:.875rem 1.125rem;border-radius:var(--r-sm);
  font-size:var(--t-md);color:var(--ink-2);margin-bottom:var(--s2);border-inline-start:4px solid;}
.pace b{color:var(--ink);}
.pace.ahead {background:var(--ok-soft);   border-inline-start-color:var(--ok);}
.pace.behind{background:var(--warn-soft); border-inline-start-color:var(--warn);}
.pace.on    {background:var(--primary-soft);border-inline-start-color:var(--primary);}

.notice.bad{background:var(--danger-soft);border:1px solid var(--danger-line);
  border-inline-start:4px solid var(--danger);color:var(--ink-2);
  text-align:start;margin-bottom:var(--s2);padding:.875rem 1.125rem;}

/* Something to do soon (a backup that is due) and something done (the backup
   saved). Same shape as .notice.bad; the colour is the only difference, and
   the words always say which it is. */
.notice.warn{background:var(--warn-soft);border:1px solid transparent;border-inline-start:4px solid var(--warn);
  color:var(--ink-2);text-align:start;margin-bottom:var(--s2);padding:.875rem 1.125rem;}
.notice.good{background:var(--ok-soft);border:1px solid transparent;border-inline-start:4px solid var(--ok);
  color:var(--ink-2);text-align:start;margin-bottom:var(--s2);padding:.875rem 1.125rem;}
#backupDue{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.75rem;}
.bkform{max-width:34rem;}
#bkProgress{width:100%;height:.75rem;}
#bkHistory td.bad{color:var(--danger);font-weight:600;}
.bkhead{margin-top:var(--s3, 1.5rem);}

/* A completed code offered for the person to check. Not an error and not an
   answer — it sits between the box and the results and asks to be read. */
.notice.suggest{background:var(--primary-soft);border:1px solid transparent;
  border-inline-start:4px solid var(--primary);color:var(--ink-2);
  text-align:start;margin-bottom:var(--s2);padding:.875rem 1.125rem;}
.notice.suggest .act{margin:.5rem 0 .375rem;font-variant-numeric:tabular-nums;
  letter-spacing:.04em;display:inline-flex;align-items:baseline;gap:.625rem;}
.notice.suggest .sugname{letter-spacing:normal;font-weight:600;color:var(--ink);}
.notice.suggest .sub{display:block;color:var(--ink-3);font-size:var(--t-sm);}

.upnext.live{border-color:var(--ok);box-shadow:0 0 0 3px var(--ok-soft);}
.upnext.live .upnext-label{color:var(--ok);}

.earlyline{margin:.875rem 0 0;padding:.75rem 1rem;border-radius:var(--r-sm);
  background:var(--ok-soft);color:var(--ink-2);font-size:var(--t-sm);}

.acts{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.875rem;}
.act{min-height:var(--tap);padding:0 1.125rem;border-radius:var(--r-sm);cursor:pointer;
  border:1.5px solid var(--line);background:var(--card);color:var(--ink);
  font:650 var(--t-md)/1 inherit;font-family:inherit;
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease);}
.act:hover{background:var(--fill-tint);border-color:var(--sep);}
.act.primary{background:var(--primary);border-color:var(--primary);color:var(--on-primary);}
.act.primary:hover{background:var(--primary-hover);border-color:var(--primary-hover);}
.act.quiet{border-color:transparent;color:var(--ink-3);}
.act.quiet:hover{background:var(--danger-soft);color:var(--danger);border-color:transparent;}
.act:disabled{opacity:.55;cursor:progress;}
.act:focus-visible{outline:3px solid var(--primary);outline-offset:2px;}

/* status is never colour alone — each chip carries its own word */
.stchip{font-size:var(--t-xs);font-weight:700;letter-spacing:.02em;padding:.2rem .5rem;
  border-radius:5px;white-space:nowrap;}
.st-arrived{background:var(--primary-soft);color:var(--primary-ink);}
.st-inroom {background:var(--ok-soft);     color:var(--ok);}
.st-done   {background:var(--fill-3);            color:var(--ink-2);}
.st-noshow {background:var(--danger-soft); color:var(--danger);}

.actualline{margin:.75rem 0 0;font-size:var(--t-sm);color:var(--ink-2);
  font-variant-numeric:tabular-nums;}
.actualline.live{color:var(--ok);font-weight:650;}

/* ---- duration calibration ---- */
.calib{border:1px solid var(--line);border-radius:var(--r-sm);padding:1.125rem;margin-top:var(--s2);}
.calib.flag{border-color:var(--primary-line);background:var(--primary-soft);}
.calib-head{display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap;}
.calib-n{font-size:var(--t-xs);color:var(--ink-3);}
.calib-nums{display:flex;gap:1.75rem;flex-wrap:wrap;margin:.875rem 0 0;}
.calib-nums div{font-size:var(--t-xs);color:var(--ink-3);max-width:9rem;line-height:1.35;}
.calib-nums b{display:block;font-size:1.25rem;font-weight:700;color:var(--ink);
  line-height:1.3;font-variant-numeric:tabular-nums;}
.calib-verdict{margin:.875rem 0 .875rem;font-size:var(--t-md);color:var(--ink-2);}
.calib-verdict b{color:var(--ink);}
.calib-verdict.quiet{color:var(--ink-3);margin-bottom:0;}

/* ============================================================
   ARABIC / RIGHT-TO-LEFT
   Everything above uses logical properties (inline-start/end) rather than
   left/right, so the whole layout mirrors from the <html dir> alone. Only the
   things that must NOT mirror are listed here.
   ============================================================ */
[dir=rtl] body{
  /* the system stack has no Arabic face on most machines; name real ones first */
  font-family:"SF Arabic","Geeza Pro","Segoe UI",Tahoma,"Noto Naskh Arabic",
              "Noto Sans Arabic",-apple-system,BlinkMacSystemFont,Arial,sans-serif;
}
/* Arabic ascenders and descenders are taller than Latin ones; the tight Latin
   leading crowds them into each other. */
[dir=rtl] body{line-height:1.75;}
[dir=rtl] h1,[dir=rtl] h2,[dir=rtl] h3{line-height:1.5;letter-spacing:0;}
/* Letter-spacing and uppercasing are meaningless in Arabic and actively break
   the joined script. */
[dir=rtl] .tag,[dir=rtl] .stchip,[dir=rtl] .upnext-label,[dir=rtl] .rolebadge,
[dir=rtl] .locked,[dir=rtl] .hist{text-transform:none;letter-spacing:0;}

/* Charts stay left-to-right. Time running left→right is how a chart is read in
   Arabic-language publications too, and mirroring an axis mid-sentence is a
   good way to make a doctor misread a trend. Numbers and clock times likewise. */
[dir=rtl] .chartwrap,[dir=rtl] svg,[dir=rtl] .heat,[dir=rtl] .tlblock{direction:ltr;}
/* Latin numerals inside Arabic prose — a clock time, a count — read
   left-to-right and must be isolated so the punctuation around them does not
   migrate. `inline-block` is right for those, because they sit in a line of
   text.

   `.num` used to be in this list and is not any more. It is not a numeral in
   prose: it is the numbered CIRCLE in the booking step strip, whose whole job
   is `display:grid; place-items:center`. Matching it here quietly replaced
   that grid with an inline-block, so on the Arabic page — and only there —
   every step number slid to the left edge of its circle and three pixels up.
   One class name doing two jobs, and the rule written for one of them broke
   the other. */
[dir=rtl] time,[dir=rtl] .clock{direction:ltr;display:inline-block;unicode-bidi:isolate;}
/* the circle keeps the isolation it wants and the grid it needs */
[dir=rtl] .steps .num{direction:ltr;unicode-bidi:isolate;}
/* Phone numbers, drug doses and mg/ml strings are Latin islands inside Arabic
   text and need isolating or the punctuation jumps to the wrong end. */
[dir=rtl] .ltr{direction:ltr;unicode-bidi:isolate;text-align:start;}

/* …but a FIELD is not a phrase. `.ltr` on an input keeps the digits in Latin
   order, which is right — 2026-11-30 must not mirror — and then `text-align:
   start` resolved against that ltr direction pinned the value to the LEFT
   edge of the box, on a page where every other field starts at the right. A
   column of Arabic labels with their numbers hanging off the far side is the
   single thing that made this screen feel foreign, and it is a one-word fix:
   the CONTENT is left-to-right, the FIELD still starts where the page starts. */
[dir=rtl] input.ltr,[dir=rtl] textarea.ltr,
[dir=rtl] input[type=number],[dir=rtl] input[inputmode=numeric],[dir=rtl] input[inputmode=decimal],[dir=rtl] input[type=date],
[dir=rtl] input[type=time],[dir=rtl] input[type=tel]{text-align:right;}

/* Chrome draws the number spinner over the last few pixels of the field, so a
   placeholder that only just fits is chopped by an arrow the user never asked
   for. Quantities here are typed, never nudged one at a time — and hiding the
   spinner returns those pixels to the words. Firefox needs the property name
   it knows; Safari and Chrome need the pseudo-element. */
input[type=number]{appearance:textfield;-moz-appearance:textfield;}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}

/* the language switch itself */
.langswitch{display:flex;gap:.25rem;background:var(--switch-track);padding:.25rem;border-radius:10px;}
/* The theme switch, built to the same shape as the language switch so the two
   read as one control group rather than two unrelated widgets. Icon-only: the
   words are long in both languages and this sits in a masthead that already
   has to hold a clinic name, a face and a role badge on a phone. */
.themeswitch{display:flex;gap:.25rem;background:var(--switch-track);padding:.25rem;
  border-radius:10px;margin-inline-start:.5rem;}
.themeswitch button{min-height:var(--tap);min-width:var(--tap);display:grid;
  place-items:center;border:0;border-radius:7px;background:transparent;
  color:var(--ink-2);cursor:pointer;padding:0;}
.themeswitch button:hover{color:var(--ink);}
/* The shadow is what says "raised", and on a dark page a black shadow says the
   opposite — so the lift is carried by the fill and a hairline, and the shadow
   is spent only where there is light to cast it. */
.themeswitch button[aria-pressed=true]{background:var(--switch-on);color:var(--ink);
  box-shadow:0 1px 3px var(--shadow-switch);}
/* The moon has no interior stroke, so at 17px it can read as a blob until the
   eye finds the crescent. Nudging it up half a pixel aligns its optical centre
   with the sun's, which is what stops the row looking like it wobbles. */
.themeswitch button[data-theme-opt=dark] svg{transform:translateY(-.5px);}
@media print{ .themeswitch{display:none !important;} }
.langswitch button{min-height:var(--tap);padding:0 .875rem;border:0;border-radius:7px;background:transparent;
  color:var(--ink-2);font:650 var(--t-sm)/1 inherit;font-family:inherit;cursor:pointer;}
.langswitch button[aria-pressed=true]{background:var(--switch-on);color:var(--ink);
  box-shadow:0 1px 3px var(--shadow-switch);}

/* ============================================================
   SEGMENT 2 — patients and the clinical record
   ============================================================ */
.ptlist{list-style:none;margin:var(--s2) 0 0;padding:0;}
.ptlist li{border-bottom:1px solid var(--line-soft);}
.ptlist li:last-child{border-bottom:0;}
.ptrow{display:flex;align-items:center;gap:.875rem;width:100%;min-height:var(--tap);
  padding:.75rem .5rem;background:none;border:0;font:inherit;color:inherit;cursor:pointer;
  text-align:start;border-radius:var(--r-sm);}
.ptrow:hover{background:var(--primary-soft);}
.ptrow-main{flex:1;min-width:0;}
.ptrow-name{display:block;font-weight:650;}
.ptrow-sub{display:block;font-size:var(--t-sm);color:var(--ink-3);}

.ptheadline{display:flex;gap:1rem;align-items:flex-start;margin-bottom:var(--s2);}
.ptheadline .avatar{width:56px;height:56px;font-size:var(--t-bd);}
.ptmeta{margin:.25rem 0 0;font-size:var(--t-sm);color:var(--ink-2);
  display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;}
.ptmeta.quiet{color:var(--ink-3);}
.ptmeta b{color:var(--ink);}

/* ---- the patient's code -------------------------------------------------
   A reference number, not prose: monospaced so the digits line up when the
   desk compares two of them, and .ltr + isolate so it reads left-to-right
   with Latin digits even on an Arabic page. Mirroring it would change what
   the person reads down the phone. */
.mrn{display:inline-flex;align-items:center;gap:.25rem;}
.mrn-v{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
  font-size:var(--t-sm);font-weight:650;letter-spacing:.02em;color:var(--ink-2);
  background:var(--line-soft);border:1px solid var(--line);
  border-radius:var(--r-sm);padding:.1rem .4rem;white-space:nowrap;}
.mrn.big .mrn-v{font-size:var(--t-bd);padding:.2rem .55rem;color:var(--ink);}
.mrn-label{font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-3);}
.codecopy{min-width:var(--tap);min-height:var(--tap);display:inline-flex;
  align-items:center;justify-content:center;background:none;border:0;
  color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);padding:0;}
.codecopy:hover{color:var(--primary-ink);background:var(--primary-soft);}
.codecopy:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.codecopy .copied{font-size:var(--t-xs);font-weight:650;color:var(--ok);}

/* The code as the patient first meets it, on the booking confirmation.
   Given its own block rather than a row in the summary list: it is the one
   thing on that screen they are meant to keep. */
.calcard{margin:1rem 0;padding:1rem 1.125rem;border:1px solid var(--line);border-radius:var(--r-sm);}
.calcard h3{margin:0 0 .25rem;font-size:var(--t-md);}
.calcard .btnrow{margin-top:.75rem;flex-wrap:wrap;}
.calcard .btn{flex:1 1 14rem;justify-content:center;text-align:center;line-height:1.3;padding:.625rem 1rem;}
.calcard .btn svg{flex:none;}
.codecard{display:grid;grid-template-columns:1fr auto;align-items:center;
  gap:.35rem .75rem;margin:var(--s2) 0;padding:1rem 1.15rem;
  border:1px solid var(--ok);border-radius:var(--r);background:var(--ok-soft);}
.codecard-label{grid-column:1;font-size:var(--t-xs);text-transform:uppercase;
  letter-spacing:.06em;color:var(--ink-2);}
/* No hard grid-row. The owner's new-doctor card emits TWO of these — the email
   and the one-time password — and pinning both to row 2 painted them through
   each other into an unreadable smear. That is the only time the password is
   ever shown, so an illegible one costs the owner the whole account. Letting
   them auto-place puts each on its own row however many there are.
   overflow-wrap + user-select:all: where the clipboard is unavailable (plain
   http on a clinic LAN) reading and selecting it by hand is the only fallback. */
.codecard-value{grid-column:1;
  font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
  font-size:var(--t-lg);font-weight:700;letter-spacing:.04em;color:var(--ink);
  overflow-wrap:anywhere;user-select:all;-webkit-user-select:all;}
.codecard-copy{grid-column:2;grid-row:1 / -1;align-self:center;min-height:var(--tap);}
.codecard-note{grid-column:1 / -1;margin:.35rem 0 0;font-size:var(--t-sm);
  color:var(--ink-2);}

/* `.wachip` (a delivery state on the day list) and `.optin` (the consent
   block on the booking form) lived here. Both are gone with WhatsApp. The
   .optin pattern is worth remembering if any consent is ever asked for again:
   the whole bordered block was the <label>, so the tap target was the full
   width of the form rather than a 22px square — which is the difference
   between a checkbox a patient can hit on a phone and one they cannot. */

/* "Which of you is this?" — one tap per person on the number. */
.wholist{list-style:none;margin:var(--s2) 0 0;padding:0;display:grid;gap:.6rem;}
.wholine{display:flex;width:100%;align-items:center;justify-content:space-between;
  gap:1rem;min-height:var(--tap);padding:.85rem 1rem;cursor:pointer;
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--card);
  text-align:start;font:inherit;color:var(--ink);}
.wholine:hover:not(:disabled){border-color:var(--primary);background:var(--primary-soft);}
.wholine:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.wholine:disabled{opacity:.55;cursor:default;}
/* A name carries its own direction. Without isolation, "Ahmed H." inside an
   Arabic paragraph renders as ".Ahmed H" — the trailing full stop migrates to
   the wrong end, because the paragraph's direction wins over the run's. */
.wholine-name{font-weight:650;display:block;unicode-bidi:isolate;}
.alias, .ptrow-name{unicode-bidi:isolate;}
.wholine-name small{display:block;font-weight:400;font-size:var(--t-sm);
  color:var(--ink-3);margin-top:.15rem;}
.wholine-act{flex:none;font-size:var(--t-sm);font-weight:650;color:var(--primary-ink);}
.wholine.new{border-style:dashed;}
.note.quiet{color:var(--ink-3);font-size:var(--t-sm);margin-top:var(--s2);}
.whobox{margin:var(--s2) 0;padding:1rem;border:1px solid var(--warn);
  border-radius:var(--r-sm);background:var(--fill-warm);}
.whobox-head{margin:0 0 .5rem;font-size:var(--t-sm);color:var(--ink-2);}
.alias{display:inline-block;padding:.1rem .45rem;border:1px solid var(--line);
  border-radius:var(--r-sm);background:var(--line-soft);font-size:var(--t-sm);
  color:var(--ink-2);}
.tsub{font-size:var(--t-md);color:var(--ink-2);margin:var(--s3) 0 .5rem;}
.tsub:first-child{margin-top:0;}

.medlist{list-style:none;margin:0;padding:0;}
.medlist li{display:flex;gap:1rem;flex-wrap:wrap;align-items:baseline;
  padding:.625rem 0;border-bottom:1px solid var(--line-soft);font-size:var(--t-md);}
.medlist li:last-child{border-bottom:0;}
.visitlist{list-style:none;margin:0;padding:0;}
.visitlist li{border-bottom:1px solid var(--line-soft);}
.visitlist li:last-child{border-bottom:0;}

/* ---- the visit note ---- */
body.noscroll{overflow:hidden;}
#notescreen{position:fixed;inset:0;z-index:60;background:var(--bg);overflow-y:auto;
  -webkit-overflow-scrolling:touch;}
.notewrap{padding-top:var(--s2);}
.notebar{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  flex-wrap:wrap;margin-bottom:var(--s2);position:sticky;top:0;z-index:2;
  background:var(--bg);padding:.5rem 0;border-bottom:1px solid var(--line);}
.notestatus{font-size:var(--t-sm);color:var(--ink-3);font-variant-numeric:tabular-nums;}
.notestatus.bad{color:var(--danger);font-weight:650;}
.notehead{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;
  flex-wrap:wrap;margin-bottom:var(--s2);}
.noteh1{font-size:var(--t-xl);letter-spacing:-.02em;}

.vitals{display:grid;gap:.875rem;grid-template-columns:repeat(auto-fit,minmax(8.5rem,1fr));}
.vital label{display:block;font-size:var(--t-xs);font-weight:650;color:var(--ink-2);
  margin-bottom:.3rem;}
.vinput{display:flex;align-items:center;gap:.375rem;}
.vinput input{width:100%;min-height:var(--tap);padding:0 .625rem;border-radius:var(--r-sm);
  border:1.5px solid var(--line);background:var(--card);font:650 var(--t-bd)/1 inherit;
  font-family:inherit;color:var(--ink);font-variant-numeric:tabular-nums;}
.vinput input:focus{border-color:var(--primary);}
/* out of the usual range — worth the doctor's eye, never a diagnosis, so it is
   an outline and a word in the tooltip rather than a red alarm */
.vinput input.flagged{border-color:var(--warn);background:var(--warn-soft);}
.vinput .unit{font-size:var(--t-xs);color:var(--ink-3);white-space:nowrap;}
.vinput.derived{min-height:var(--tap);display:flex;align-items:center;
  font-weight:700;color:var(--ink-2);font-variant-numeric:tabular-nums;}

.rowedit{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-bottom:.5rem;}
.rowedit input,.rowedit select{flex:1 1 8rem;min-width:6rem;min-height:var(--tap);
  padding:0 .75rem;border-radius:var(--r-sm);border:1.5px solid var(--line);
  background:var(--card);font:var(--t-md)/1 inherit;font-family:inherit;color:var(--ink);}
.rowedit input:focus,.rowedit select:focus{border-color:var(--primary);}
.rowedit input[type=number],.rowedit input[inputmode=numeric]{flex:0 0 6rem;}
.rowedit .iconbtn{flex:0 0 auto;}
.act.big{min-height:52px;font-size:var(--t-bd);padding:0 1.75rem;}
.act:disabled{opacity:.5;cursor:not-allowed;}

/* the trend chart's own legend */
.legend{display:flex;gap:1rem;flex-wrap:wrap;font-size:var(--t-sm);color:var(--ink-2);margin:0 0 .5rem;}
.legend span{display:inline-flex;align-items:center;gap:.375rem;}
.legend i{width:12px;height:3px;border-radius:2px;display:inline-block;}
/* "Duration" was being clipped inside a 6rem number field */
.rowedit input[type=number],.rowedit input[inputmode=numeric]{flex:0 0 8rem;}

/* ---- editing a patient's details ---- */
.editblock{margin-top:var(--s2);}
.chips{display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;}
.chip{display:inline-flex;align-items:center;gap:.375rem;padding:.35rem .5rem .35rem .75rem;
  border-radius:999px;background:var(--primary-soft);color:var(--primary-ink);
  font-size:var(--t-sm);font-weight:650;}
/* an allergy is the one field on this screen that can hurt someone, so it does
   not look like a tag — it looks like a warning, and carries the word too */
.chip.danger{background:var(--danger-soft);color:var(--danger);}
.chipx{width:26px;height:26px;min-height:26px;border:0;border-radius:50%;cursor:pointer;
  background:var(--chip);color:inherit;font:700 15px/1 inherit;font-family:inherit;
  display:grid;place-items:center;padding:0;}
.chipx:hover{background:var(--chip-hover);}
.chipx:focus-visible{outline:3px solid var(--primary);outline-offset:2px;}
.addrow{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.75rem;}
.addrow input{flex:1 1 16rem;min-width:12rem;}
.addrow.compact{margin-top:.5rem;max-width:32rem;}
.detailgrid{display:grid;gap:0 1rem;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));
  margin-bottom:var(--s2);}
.detailgrid .field{margin-bottom:.875rem;}
.detailgrid .narrow{max-width:none;}
.masthead-right{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;}

/* ---- stock batches ---- */
.stockitem + .stockitem{margin-top:var(--s3);}
.exp{font-weight:650;}
.exp.warning{color:var(--warn);}
.exp.critical{color:var(--danger);}
.act.small{min-height:var(--tap);padding:0 .875rem;font-size:var(--t-sm);}
.batchbox{margin-top:.75rem;padding:1rem;border:1px solid var(--line);
  border-radius:var(--r-sm);background:var(--fill-0);}
.batchhead{display:flex;justify-content:space-between;align-items:baseline;
  gap:1rem;flex-wrap:wrap;margin-bottom:.75rem;}
.batchhead h4{margin:0;font-size:var(--t-md);}
.batchlist{list-style:none;margin:0 0 .75rem;padding:0;}
.batchlist li,.batchadd{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;
  padding:.5rem 0;border-bottom:1px solid var(--line-soft);}
.batchadd{border-bottom:0;border-top:1px dashed var(--line);padding-top:.875rem;}
.batchlist li:last-child{border-bottom:0;}
/* an emptied batch is history, not stock — it stays visible but recedes */
.batchlist li.empty{opacity:.55;}
.batchbox input.bq{flex:0 0 5.5rem;}
.batchbox input.bd{flex:0 0 10rem;}
.batchbox input.bl{flex:1 1 8rem;min-width:6rem;}
.batchbox input{min-height:var(--tap);padding:0 .625rem;border-radius:var(--r-sm);
  border:1.5px solid var(--line);background:var(--card);font:var(--t-md)/1 inherit;
  font-family:inherit;color:var(--ink);width:auto;}
.bunit{font-size:var(--t-sm);color:var(--ink-3);white-space:nowrap;}
.bstate{font-size:var(--t-xs);font-weight:650;color:var(--ink-3);white-space:nowrap;}
.bstate.warning{color:var(--warn);}
.bstate.critical{color:var(--danger);}
/* flex-START, not flex-end. These were all one-line fields of equal height, so
   bottom-alignment and top-alignment looked identical; once the price field grew
   a three-line explanation under it, flex-end dropped "Reorder level" and "Unit
   cost" to the bottom of a tall row and the settings read as two separate rows
   that had collided. */
.itemsettings{display:flex;gap:1rem;align-items:flex-start;flex-wrap:wrap;
  margin-top:.875rem;padding-top:.875rem;border-top:1px solid var(--line);}
.itemsettings .field{margin:0;max-width:9rem;}
.itemgrid{display:grid;gap:0 1rem;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));}
.itemgrid .field{margin-bottom:.875rem;}
.newitem{margin-top:var(--s3);}
/* keep the destructive control clear of the number fields beside it */
.itemsettings .act.quiet{margin-inline-start:auto;}
.itemsettings .act.quiet.danger{color:var(--danger);}

/* THE PRICE, and the three facts printed under it. The 9rem cap the other number
   boxes sit under turned that sentence into a six-line column that dragged the
   whole flex row out of alignment, so this one field opts out of the cap and
   sits at the TOP of the row rather than sharing its baseline. */
.itemsettings .field.pricefield{max-width:none;flex:1 1 15rem;}
.itemsettings .field.pricefield .note{margin:.375rem 0 0;}
/* the destructive control keeps its old footing on the input line, which is now
   the top of the row rather than the bottom of it */
.itemsettings .act.quiet.danger{margin-top:1.6rem;}

/* THE OWNER'S SUGGESTION, on the doctor's stock card. It is not a field and must
   not read as one: a tinted block with two buttons, wider than the 9rem cap the
   number boxes sit under, so "the owner thinks this should cost 250" is a
   sentence rather than a cramped label. `max-width:none` overrides the
   .itemsettings .field rule two lines up, which otherwise squeezes it. */
.itemsettings .field.suggestion{max-width:none;flex:1 1 16rem;
  background:var(--primary-soft);border:1px solid var(--primary-line);
  border-radius:var(--r-sm);padding:.625rem .75rem;}
.itemsettings .field.suggestion .note{margin:0 0 .5rem;color:var(--ink-2);}
.itemsettings .field.suggestion .btnrow{margin:0;gap:.5rem;}
/* it carries its own buttons, so the auto-margin that pushes Remove to the far
   end must not apply inside it */
.itemsettings .field.suggestion .act.quiet{margin-inline-start:0;}

/* ============================================================
   THE PRINTED DOCUMENTS

   One element serves both jobs: what the doctor previews on screen is the
   same node the printer receives. There is no second rendering path, so the
   preview cannot drift out of step with the paper.

   Sized in mm, because this leaves the building on A4.
   ============================================================ */
/* ============================================================
   DARK

   Not an inversion. Every value below was measured with the same script that
   measured the light palette — /tmp/palette.py in the build, kept in
   tools/check-contrast.py — against the same floors: 4.5:1 for text, 3:1 for
   anything decorative that still has to be seen, and a ΔE floor for the three
   visit-type hues under normal vision AND under deuteranopia and protanopia.

   Two things flip that an inversion would get wrong:
     - text ON a saturated fill. Those fills get BRIGHTER against a dark page,
       so their label goes near-black (--on-fill), not white.
     - the heatmap ramp. More = brighter here, less = closer to the page.

   Applied on [data-theme=dark] AND on prefers-color-scheme when the reader has
   not chosen — so the device setting is honoured until a person overrides it,
   and their override then wins on every visit.
   ============================================================ */
@media (prefers-color-scheme: dark){
  :root:not([data-theme=light]){
  /* the page and the ink */
  --bg:#0e1622; --card:#16202e; --line:#2b3849; --line-soft:#212d3d; --ink:#e8eef6;
  --ink-2:#b5c3d4; --ink-3:#93a3b8; --sep:#6d7f96;

  /* Data Teal, re-lit. Same hue; on a dark page the ROLES swap — the fill stays
     the brand teal, but what reads as text has to get lighter, not darker */
  --primary:#20b2aa; --primary-hover:#33c2ba; --on-primary:#04222c; --primary-ink:#5fd8d0;
  --primary-soft:#123536; --primary-line:#1e5a58;

  /* status */
  --ok:#5fcf9e; --ok-soft:#123328; --ok-line:#1f6a4c; --warn:#e8b06a;
  --warn-soft:#33260f; --warn-line:#6d5220; --danger:#f2908f; --danger-soft:#3a1a1c;
  --danger-line:#7a3134;

  /* the three visit types, re-measured for separation on a dark ground */
  --diagnosis:#4b93e0; --diagnosis-soft:#22374f; --diagnosis-ink:#8cbdf0;
  --counseling:#e2703a; --counseling-soft:#452a1d; --counseling-ink:#f2a37c;
  --service:#2aa877; --service-soft:#173c31; --service-ink:#6fd7ac;

  /* The rail, on a page that is already dark. It cannot stay the light theme's
     navy — against #0e1622 that navy all but disappears — so it lifts to a
     panel that reads as a surface ABOVE the page, the way the cards do, and
     its labels lift with it. */
  --rail:#1c2a3e; --rail-line:#33415a; --rail-ink:#c2d0e2; --rail-ink-2:#93a5bd;
  --rail-hover:#26374f;

  /* the heatmap ramp runs the other way here: more = brighter, not darker */
  --seq-0:#1a2739; --seq-1:#22384f; --seq-2:#2c4d70; --seq-3:#376492;
  --seq-4:#4a7fb5; --seq-5:#6a9dd0; --seq-6:#9dc2e8;
  --seq-on-0:var(--ink); --seq-on-1:var(--ink); --seq-on-2:var(--ink);
  --seq-on-3:var(--ink);
  --seq-on-4:var(--on-fill); --seq-on-5:var(--on-fill); --seq-on-6:var(--on-fill);

  /* surfaces */
  --fill-0:#1a2432; --fill-1:#1d2836; --fill-2:#222e3e; --fill-3:#27344b;
  --fill-4:#2c3a4c; --fill-hover:#35455a; --fill-tint:#1b2a3e; --fill-warm:#2b2617;
  /* the track recedes toward the page and the chosen pill lifts off it —
     the same relationship as the light theme, not the same values */
  --switch-track:#1d2836; --switch-on:#374961;
  /* no drop shadow on a dark page: black under a pale pill reads as a
     groove, which is the very thing being fixed */
  --shadow-switch:transparent;

  /* text on a saturated fill is now near-black, because those fills got brighter */
  --on-fill:#0b1119;

  /* role badges */
  --role-doc-soft:#2a1f42; --role-doc-ink:#c3a7f0; --role-own-soft:#1c2f1a;
  --role-own-ink:#9ed394;

  /* the tooltip sits on --ink, which just inverted, so its text inverts too */
  --tip-ink:#0e1622; --tip-muted:#44556b;

  /* hatches */
  --hatch-free-a:#1c2635; --hatch-free-b:#222e3f; --hatch-turn-a:#2c3a4c;
  --hatch-turn-b:#243040; --hatch-dead-a:#3a2f16; --hatch-dead-b:#2e2511;
  --hatch-held-a:#2a3547; --hatch-held-b:#222c3c; --hatch-shut-a:#18212e;
  --hatch-shut-b:#1d2734;

  /* depth. Real black and more of it: a navy shadow on a navy surface is not a
     shadow */
  --shadow-1:rgba(0,0,0,.45); --shadow-2:rgba(0,0,0,.55); --shadow-3:rgba(0,0,0,.7);
  --shadow-card:rgba(0,0,0,.35); --edge:rgba(255,255,255,.14); --chip:rgba(255,255,255,.10);
  --chip-hover:rgba(255,255,255,.2); --fade:#2c3a4c; --fade-0:rgba(44,58,76,0);
  --scrim:rgba(0,0,0,.5); --scrim-0:rgba(0,0,0,0); --backdrop:#070c13;
  }
}
:root[data-theme=dark]{
  /* the page and the ink */
  --bg:#0e1622; --card:#16202e; --line:#2b3849; --line-soft:#212d3d; --ink:#e8eef6;
  --ink-2:#b5c3d4; --ink-3:#93a3b8; --sep:#6d7f96;

  /* Data Teal, re-lit. Same hue; on a dark page the ROLES swap — the fill stays
     the brand teal, but what reads as text has to get lighter, not darker */
  --primary:#20b2aa; --primary-hover:#33c2ba; --on-primary:#04222c; --primary-ink:#5fd8d0;
  --primary-soft:#123536; --primary-line:#1e5a58;

  /* status */
  --ok:#5fcf9e; --ok-soft:#123328; --ok-line:#1f6a4c; --warn:#e8b06a;
  --warn-soft:#33260f; --warn-line:#6d5220; --danger:#f2908f; --danger-soft:#3a1a1c;
  --danger-line:#7a3134;

  /* the three visit types, re-measured for separation on a dark ground */
  --diagnosis:#4b93e0; --diagnosis-soft:#22374f; --diagnosis-ink:#8cbdf0;
  --counseling:#e2703a; --counseling-soft:#452a1d; --counseling-ink:#f2a37c;
  --service:#2aa877; --service-soft:#173c31; --service-ink:#6fd7ac;

  /* The rail, on a page that is already dark. It cannot stay the light theme's
     navy — against #0e1622 that navy all but disappears — so it lifts to a
     panel that reads as a surface ABOVE the page, the way the cards do, and
     its labels lift with it. */
  --rail:#1c2a3e; --rail-line:#33415a; --rail-ink:#c2d0e2; --rail-ink-2:#93a5bd;
  --rail-hover:#26374f;

  /* the heatmap ramp runs the other way here: more = brighter, not darker */
  --seq-0:#1a2739; --seq-1:#22384f; --seq-2:#2c4d70; --seq-3:#376492;
  --seq-4:#4a7fb5; --seq-5:#6a9dd0; --seq-6:#9dc2e8;
  --seq-on-0:var(--ink); --seq-on-1:var(--ink); --seq-on-2:var(--ink);
  --seq-on-3:var(--ink);
  --seq-on-4:var(--on-fill); --seq-on-5:var(--on-fill); --seq-on-6:var(--on-fill);

  /* surfaces */
  --fill-0:#1a2432; --fill-1:#1d2836; --fill-2:#222e3e; --fill-3:#27344b;
  --fill-4:#2c3a4c; --fill-hover:#35455a; --fill-tint:#1b2a3e; --fill-warm:#2b2617;
  /* the track recedes toward the page and the chosen pill lifts off it —
     the same relationship as the light theme, not the same values */
  --switch-track:#1d2836; --switch-on:#374961;
  /* no drop shadow on a dark page: black under a pale pill reads as a
     groove, which is the very thing being fixed */
  --shadow-switch:transparent;

  /* text on a saturated fill is now near-black, because those fills got brighter */
  --on-fill:#0b1119;

  /* role badges */
  --role-doc-soft:#2a1f42; --role-doc-ink:#c3a7f0; --role-own-soft:#1c2f1a;
  --role-own-ink:#9ed394;

  /* the tooltip sits on --ink, which just inverted, so its text inverts too */
  --tip-ink:#0e1622; --tip-muted:#44556b;

  /* hatches */
  --hatch-free-a:#1c2635; --hatch-free-b:#222e3f; --hatch-turn-a:#2c3a4c;
  --hatch-turn-b:#243040; --hatch-dead-a:#3a2f16; --hatch-dead-b:#2e2511;
  --hatch-held-a:#2a3547; --hatch-held-b:#222c3c; --hatch-shut-a:#18212e;
  --hatch-shut-b:#1d2734;

  /* depth. Real black and more of it: a navy shadow on a navy surface is not a
     shadow */
  --shadow-1:rgba(0,0,0,.45); --shadow-2:rgba(0,0,0,.55); --shadow-3:rgba(0,0,0,.7);
  --shadow-card:rgba(0,0,0,.35); --edge:rgba(255,255,255,.14); --chip:rgba(255,255,255,.10);
  --chip-hover:rgba(255,255,255,.2); --fade:#2c3a4c; --fade-0:rgba(44,58,76,0);
  --scrim:rgba(0,0,0,.5); --scrim-0:rgba(0,0,0,0); --backdrop:#070c13;
}

/* padding-top clears the now-fixed bar, which no longer occupies flow space. */
#printwrap{position:fixed;inset:0;z-index:80;background:var(--backdrop);
  overflow-y:auto;padding:calc(var(--tap) + 1.5rem) 0 var(--s4);}
/* `position:sticky` pins on the block axis only. The sheet is 794px wide, so on
   a phone the wrapper scrolls sideways to read it — and the bar, whose width is
   the wrapper's CLIENT width rather than its scroll width, slid away with the
   page. Panning right to read the drug column took Back and Print off screen
   together, and nothing said they were still to the left. `fixed` pins it on
   both axes; the wrapper is already `fixed inset:0` so it is the containing
   block and the bar keeps its place. */
.printbar{position:fixed;top:0;inset-inline:0;z-index:2;display:flex;gap:.75rem;
  align-items:center;justify-content:space-between;
  padding:.75rem 1rem;background:var(--card);border-bottom:1px solid var(--line);}
.sheet{width:210mm;min-height:297mm;margin:var(--s3) auto;padding:18mm 16mm;
  background:#fff;color:#111;box-shadow:0 12px 40px rgba(0,0,0,.35);
  position:relative;
  /* serif for the body of a document — it reads as paper, not as an app */
  font-family:Georgia,'Times New Roman',serif;font-size:11.5pt;line-height:1.5;}

/* The Arabic sheet needs faces that are actually installed. The first stack
   here named only 'Noto Naskh Arabic', which ships with no stock macOS or
   Windows — so Georgia was tried next, has no Arabic glyphs at all, and the
   browser silently substituted a system sans. The document printed in the
   wrong voice: a UI typeface on a legal instrument.

   Named in order of what a Mac, then a Windows machine, actually has. The
   direction is set on the ELEMENT, not inherited from the page, because a
   doctor in an English dashboard prints Arabic sheets. */
.sheet[dir=rtl]{
  font-family:'Noto Naskh Arabic','Geeza Pro','Al Bayan','Baghdad','Damascus',
              'Traditional Arabic','Simplified Arabic','Times New Roman',serif;
  /* naskh faces carry more ascender/descender than Georgia; give them room */
  line-height:1.75;}
.sheet[dir=rtl] .sheet-drug,
.sheet[dir=rtl] bdi{font-family:Georgia,'Times New Roman',serif;}

.sheet-head{border-bottom:2px solid #111;padding-bottom:6mm;margin-bottom:6mm;
  display:grid;grid-template-columns:1fr auto;gap:4mm;}
.sheet-name{font-size:15pt;font-weight:700;letter-spacing:-.01em;}
.sheet-dr{font-size:12pt;font-weight:700;}
.sheet-doctor{text-align:end;}
.sheet-addr{font-size:9.5pt;color:#444;}
.sheet-title{grid-column:1/-1;margin:4mm 0 0;font-size:13pt;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;}
.sheet[dir=rtl] .sheet-title{text-transform:none;letter-spacing:0;}

.sheet-pt{display:grid;grid-template-columns:repeat(auto-fit,minmax(38mm,1fr));
  gap:2mm 6mm;margin:0 0 6mm;padding:4mm;background:#f4f6f8;
  border:1px solid #dee3e9;border-radius:2mm;}
.sheet-pt dt{font-size:8.5pt;text-transform:uppercase;letter-spacing:.06em;color:#555;
  margin:0;}
.sheet[dir=rtl] .sheet-pt dt{text-transform:none;letter-spacing:0;}
.sheet-pt dd{margin:0;font-size:11pt;font-weight:700;}

.sheet-allergy{margin:0 0 5mm;padding:2.5mm 3mm;font-size:10pt;
  border-inline-start:3px solid #b3261e;background:#fdf3f2;}
.sheet-allergy.none{border-inline-start-color:#c9d0d8;background:#f8f9fb;color:#555;}

/* The ℞ marks the sheet as a prescription at a glance — a pharmacist finds it
   on a desk of paper by that character before reading a word.

   It sits IN THE FLOW, not absolutely positioned. The first attempt floated it
   behind the list and it landed on top of "Metformin": a decorative glyph
   obscuring a drug name is not a styling slip, it is a dispensing error
   waiting to happen. Nothing decorative goes near the drug column. */
.rx-mark{font-size:30pt;line-height:1;color:#bbc4d0;font-weight:700;
  margin:0 0 1mm;}
.sheet-list{margin:0 0 6mm;padding-inline-start:14mm;}
.sheet-list li{margin-bottom:4mm;break-inside:avoid;}
.sheet-drug{font-size:12pt;font-weight:700;}
.sheet-dir{font-size:10pt;color:#333;}
.sheet-h2{font-size:11pt;font-weight:700;text-transform:uppercase;
  letter-spacing:.06em;margin:6mm 0 2mm;padding-bottom:1mm;
  border-bottom:1px solid #dee3e9;}
.sheet[dir=rtl] .sheet-h2{text-transform:none;letter-spacing:0;}
.sheet-note{margin:0 0 3mm;}

.sheet-foot{margin-top:12mm;padding-top:5mm;border-top:1px solid #c9d0d8;
  display:flex;justify-content:space-between;align-items:flex-end;gap:6mm;}
.sheet-signline{width:55mm;border-bottom:1px solid #111;margin-bottom:2mm;height:12mm;}
.sheet-meta{text-align:end;font-size:9pt;color:#555;}
.sheet-draft-note{color:#b3261e;font-weight:700;}

/* An unsigned sheet must never be able to pass for a final one.

   The word is placed and THEN rotated, rather than stretched across the sheet
   and rotated — because rotating a box the full width of the page throws its
   corners outside the paper, and the browser counts that as document width.
   In English the excess hangs off the right where there is nothing to lose. In
   Arabic the page is anchored to its RIGHT edge, so the same excess shoved the
   whole prescription left and the printer cut the doctor's name, his
   registration number and the draft warning clean off the sheet. The document
   the patient carries out of the clinic, trimmed by a decoration.

   Sized to its own text and centred, the rotated box stays well inside the
   page in both directions. Clipping the sheet instead would have been the
   obvious fix and the wrong one: a long prescription runs to a second page,
   and `overflow:hidden` on a block that fragments can take the rest of the
   drugs with it. */
.sheet.draft::before{content:attr(data-draft);position:absolute;
  inset:auto;top:42%;left:50%;pointer-events:none;white-space:nowrap;
  font-size:64pt;font-weight:700;color:rgba(179,38,30,.10);
  transform:translate(-50%,-50%) rotate(-24deg);letter-spacing:.1em;}

.docrow{display:flex;flex-wrap:wrap;gap:.6rem;}
.docn{display:inline-block;min-width:1.35em;padding:0 .3em;margin-inline-start:.35em;
  border-radius:1em;background:var(--primary-soft);color:var(--primary-ink);
  font-size:var(--t-xs);font-weight:700;text-align:center;}

@media print{
  /* ---- PAPER IS ALWAYS LIGHT ----
     A prescription printed from a dark screen must come out black ink on white
     paper. Everything below the sheet is drawn from the same tokens as the
     app, so without this the dark theme would follow the document to the
     printer: pale text on a background the printer drops, and a letterhead
     nobody can read. Pinning the tokens back — rather than overriding each
     rule — means any sheet style written later is light on paper by default,
     including ones nobody has written yet.
     `:root` here beats the plain `:root` of the light block on order, and the
     `[data-theme=dark]` selector on nothing — so it is repeated at the same
     specificity to be sure it wins in both directions. */
  :root, :root[data-theme=dark], :root[data-theme=light]{
    /* THE ONE THAT IS NOT A TOKEN.
       `color-scheme: dark` is set on the root so the browser paints its OWN
       furniture dark — scrollbars, date pickers, form controls. It also paints
       the CANVAS, and the @page margin is canvas. So a prescription printed
       from a dark screen came out perfect on white paper inside a solid black
       16mm frame: a quarter of the sheet in toner, and every page after it.
       Nothing in the token layer could have caught that, because the margin is
       not an element. `!important` because the theme switch sets the value
       inline, and an inline declaration otherwise wins. */
    color-scheme: light !important;
    --bg:#f8f9fa; --card:#fff; --line:#dde3ea; --line-soft:#eef1f5;
    --ink:#0b2545; --ink-2:#44556b; --ink-3:#586e8a; --sep:#7d8ea6;
    --primary:#20b2aa; --on-primary:#0b2545; --primary-ink:#087d76;
    --primary-soft:#e9f9f8; --primary-line:#b4e4e2;
    --ok:#0c7a54; --ok-soft:#e2f5ec; --warn:#9c5b0a; --warn-soft:#fdf1de;
    --danger:#a3232b; --danger-soft:#fdeaea;
    --diagnosis:#2875d1; --diagnosis-soft:#dae6f5; --diagnosis-ink:#2365b5;
    --counseling:#cd4914; --counseling-soft:#f4d1c3; --counseling-ink:#a43a10;
    --service:#15855d; --service-soft:#8ce4c5; --service-ink:#106647;
    --fill-0:#fbfcfe; --fill-1:#f7f9fb; --fill-2:#eff2f6;
    --fill-3:#eaeef4; --fill-4:#e5eaf1; --fill-hover:#dfe5ed;
    --fill-tint:#f2f6fc; --fill-warm:#fffaf0;
    --switch-track:#dbe2eb; --switch-on:#fff; --shadow-switch:rgba(16,26,44,.12);
    --on-fill:#fff; --on-navy:#fff;
    --shadow-1:rgba(16,26,44,.12); --shadow-2:rgba(11,37,69,.16);
    --shadow-3:rgba(16,26,44,.28); --shadow-card:rgba(21,96,207,.07);
    --edge:rgba(11,37,69,.14);
  }
  /* The page is paper now: white, not the app's tinted background, or the
     margin around a short document prints as a grey slab. */
  html,body{background:#fff !important;color:#0b2545 !important;}
  /* Everything that is not the sheet leaves the page. */
  body > *{display:none !important;}
  body > #printwrap{display:block !important;position:static;background:none;
    padding:0;overflow:visible;}
  .printbar{display:none !important;}
  .sheet{width:auto;min-height:0;margin:0;padding:0;box-shadow:none;
    /* the printer adds its own margin; @page owns that, not the element */
    font-size:11pt;}
  .sheet-pt{background:#f4f6f8 !important;-webkit-print-color-adjust:exact;
    print-color-adjust:exact;}
  .sheet-allergy{-webkit-print-color-adjust:exact;print-color-adjust:exact;}
  .sheet.draft::before{-webkit-print-color-adjust:exact;print-color-adjust:exact;}
  .sheet-list li, .sheet-foot{break-inside:avoid;}
  .sheet-head{break-after:avoid;}
}
@page{size:A4;margin:16mm;}

/* A bdi already isolates; make sure nothing inherited re-aligns it. */
.sheet bdi{unicode-bidi:isolate;}

/* The document's own language switch, in the print bar beside Back and Print. */
.printbar .langswitch{margin:0;}
@media print{ .printbar, .printbar *{display:none !important;} }

/* ---- the clinic's logo ----
   The slot keeps its height whether or not a logo is in it, so the letterhead
   does not shift the day one is uploaded — the address, the rule and the
   patient block sit where they always sat, and a document printed today lines
   up with one printed last month.

   Constrained by HEIGHT, not width: logos arrive at every aspect ratio, and a
   width rule turns a tall crest into a banner. object-fit keeps it honest. */
.sheet-logo{display:block;height:18mm;max-width:70mm;object-fit:contain;
  object-position:left center;margin-bottom:3mm;}
/* No margin trick. A block already sits at its own inline-start edge, which is
   the right in RTL — `margin-inline-start:auto` pushed it to the far LEFT of
   the clinic block, next to the doctor's name, on the Arabic sheet only. Just
   flip where the image sits inside its own box. */
.sheet[dir=rtl] .sheet-logo{object-position:right center;}
.sheet-logo.empty{height:18mm;}          /* the space, reserved, drawn as nothing */

/* Settings — choose and preview it */
.logorow{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;}
.logobox{width:170px;height:80px;display:grid;place-items:center;padding:.5rem;
  border:1px dashed var(--line);border-radius:var(--r-sm);background:var(--line-soft);}
.logobox img{max-width:100%;max-height:100%;object-fit:contain;}
.logobox-empty{font-size:var(--t-sm);color:var(--ink-3);}
.logoacts{display:flex;gap:.6rem;flex-wrap:wrap;}
/* the file input is hidden, so its label has to be the target and carry the
   same hit area as a real button */
label.act{display:inline-flex;align-items:center;gap:.5rem;min-height:var(--tap);
  padding:0 1rem;cursor:pointer;}

@media print{
  /* Browsers drop background images and can drop <img> tints unless told. A
     letterhead without its logo is not the document the clinic approved. */
  .sheet-logo{-webkit-print-color-adjust:exact;print-color-adjust:exact;}
  .sheet-logo.empty{display:none;}   /* no logo: reclaim the space on paper */
}

/* ============================================================
   THE BRAND LOCK-UP
   ============================================================ */
/* The app's own mark in a masthead — no navy tile behind it, because the mark
   already carries the navy. A coloured tile around a coloured logo is how a
   favicon ends up looking like a sticker. */
.appmark{display:inline-flex;flex-shrink:0;line-height:0;align-items:center;}
/* The clinic's own logo in that slot. A clinic uploads whatever it has — a
   square badge, a wide wordmark, a tall crest — so the only fixed dimension is
   the height, which is what makes the masthead the same height on all three
   pages regardless of what was uploaded. Width follows the artwork and is
   capped, because a 4:1 wordmark at 38px tall would otherwise push the clinic
   name off a phone. `contain`, never `cover`: a cropped logo is a wrong logo. */
.clinicmark{height:var(--mark,38px);width:auto;max-width:140px;
  object-fit:contain;object-position:left center;display:block;}
[dir=rtl] .clinicmark{object-position:right center;}
@media (max-width:30rem){ .clinicmark{max-width:96px;} }
/* On a phone the masthead's account and sign-out controls keep their icon and
   drop the word (it stays as the accessible name), so the person's chip and
   both controls share one row instead of three. Each is still a 44px target. */
@media (max-width:30rem){
  .crosslink.iconable{position:relative;min-width:var(--tap);min-height:var(--tap);justify-content:center;padding-inline:.6rem;}
  .crosslink.iconable .lbl{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
}
.lockup{display:inline-flex;align-items:center;gap:.7rem;}
.wordmark{display:inline-flex;flex-direction:column;line-height:1.05;
  font-weight:800;letter-spacing:-.02em;}
.wm-row{display:block;white-space:nowrap;}
.wordmark .wm-med,.wordmark .wm-base{display:inline;}
.wm-sub{display:block;font-size:.4em;font-weight:650;letter-spacing:.06em;
  text-transform:uppercase;margin-top:.28em;}
.wm-sub[lang=ar]{letter-spacing:0;text-transform:none;font-size:.42em;margin-top:.1em;}
/* MED and BASE always share a line — the wordmark broken across two lines
   is a different logo, and a flex column was quietly making one. */

/* ---- the sign-in hero ----
   Two panels: the brand on a navy field, the form on white. On a phone the
   navy panel becomes a band above the form rather than disappearing — the
   product should still introduce itself on the device most staff carry. */
.signin{display:grid;grid-template-columns:1.05fr .95fr;min-height:100vh;min-height:100dvh;}
.hero{background:var(--navy);color:#fff;
  display:flex;flex-direction:column;justify-content:flex-end;
  position:relative;overflow:hidden;isolation:isolate;}
/* the faint data-grid behind the mark: two repeating-linear-gradients, so it
   costs no image and cannot fail to load */
.hero::before{content:"";position:absolute;inset:0;opacity:.13;pointer-events:none;
  background:
    repeating-linear-gradient(90deg,transparent 0 38px,rgba(255,255,255,.5) 38px 39px),
    repeating-linear-gradient(0deg,transparent 0 38px,rgba(255,255,255,.5) 38px 39px);
  mask-image:radial-gradient(120% 90% at 30% 30%,#000 25%,transparent 78%);
  -webkit-mask-image:radial-gradient(120% 90% at 30% 30%,#000 25%,transparent 78%);}
/* THE PANEL IS NOW ONE PICTURE AND NOTHING ELSE.
   The scrim, the tagline, the pills and the footnote are gone: the artwork is
   a portrait photograph that already contains the lock-up, the platform line,
   the Arabic sub-line and SECURE · EFFICIENT · SCALABLE. Type over it would
   have been the same words twice, and the navy gradient existed only to make
   that type legible. No type, no scrim.
   Nothing here is measured for contrast any more, because nothing here is
   text — the accessible name on the <img> carries the brand for a screen
   reader and it is translated. Everything in the panel that a sighted visitor
   reads is inside the JPEG.
   WHAT STILL HAS TO BE TRUE: the lock-up must survive the crop at every
   window shape. The photograph is 1086x1448 — 0.75:1 — and the panel runs
   0.84:1 to 0.93:1 across real windows, so cover trims the TOP AND BOTTOM.
   That is the opposite of the landscape card, which was trimmed at the sides
   and had to be re-cut onto a taller canvas to survive it; a source that is
   already narrower than every panel cannot lose its edges, and 0.75 is also
   narrower than the 0.80 canvas the card was padded to, so this plate is
   strictly harder to side-crop than the one it replaces.
   The vertical trim is 11% at 0.84:1 and 22% at 0.96:1 (1280x700). `center
   35%` takes roughly a third of that off the top: 3.7% and 7.7%. The lock-up
   occupies x 34.9%-92.2%, y 12.6%-33.1% of the source — measured by ink
   detection on the file, not eyeballed off a screenshot — so it clears at
   both ends, and what goes instead is ceiling above and the front edge of the
   desk below, which is what those margins are there for.
   THE ONE SHAPE THAT TRIMS THE SIDES is a window just wide enough to stay in
   two columns: the band layout below takes over at 5:4, so the narrowest
   panel this layout can produce is 0.525 x 1.25 = 0.656:1, which crops 6.25%
   off each side. The lock-up ends at 92.2%, so it survives that with 1.5% to
   spare — about 17px of the source. It is the tightest number on this page.
   Anyone re-cutting the plate should keep the artwork's right-hand margin.
   Do not centre this, and do not hand-crop the source to fix it here — re-cut
   with deploy/make-hero.py, so a clinic bringing its own artwork gets the
   same treatment instead of a one-off. */
.herofilm{position:absolute;inset:0;z-index:0;overflow:hidden;background:#eef3f6;}
.herovid{width:100%;height:100%;object-fit:cover;object-position:center 35%;
  display:block;}
/* .hero-copy, .hero-tag, .hero-pills, .hero-foot and .hero-mark lived here.
   .soundbtn lived here too — a 44px navy disc for the film's soundtrack.
   All five are deleted rather than left unused: a styled class with no markup
   behind it is a trap for the next person, who finds `.hero-tag` in the sheet
   and assumes there is a tagline to change. */
.signin-form{background:var(--card);display:flex;align-items:center;
  justify-content:center;padding:var(--s4) var(--s3);}
.signin-form > div{width:100%;max-width:24rem;}
/* Two breakpoints, because two different things can go wrong.
   The width one is the usual "this is a phone".
   The aspect one exists because the panel is 52.5% of the window: a 900x1000
   desktop window is wide enough for two columns but leaves the panel a
   472x1000 sliver, 0.47:1, and cover-cropping a 0.8:1 film into that throws
   away 41% of its width — straight through the wordmark. Below 5:4 the panel
   becomes a band across the top instead, where the crop lands vertically and
   eats the blurred padding rather than the brand. Measured, not guessed. */
@media (max-width:56rem), (max-aspect-ratio:5/4){
  .signin{grid-template-columns:1fr;min-height:0;}
  /* On one column the panel is a band, not a column, so it needs a height of
     its own or the film collapses to the height of the copy. 62vh was measured
     wrong: it put the FIRST field above the fold and left the password and the
     Sign in button two scrolls down — on every Android phone tested, on a page
     staff open many times a day. 40vh keeps the film present as a band and puts
     the whole form within one screen on a 740px phone. */
  /* 40vh stands. 44 was tried and measured: it put the Sign in button 28px
     below the fold on a 844px phone, which is the exact failure 62vh was
     rejected for. The band does not get taller; the COPY gets shorter. */
  .hero{min-height:40vh;}
  /* THE CROP FLIPS AXIS HERE, so the desktop value is wrong on a phone.
     The band is 390x338 on an 844px phone — 1.15:1, WIDER than it is tall,
     while the picture is 0.75:1. Cover therefore scales to the band's width
     and the picture stands 520px tall inside a 338px window: 65% of it is
     visible and 182px of travel decides which 65%.
     The lock-up sits between 12.6% and 33.1% of the picture, which is 66px to
     172px once scaled. The window has to start at or above 66px to hold it,
     so anything past 36% of the travel cuts the mark. 25% starts the window
     at 46px and ends it at 384px — the whole lock-up with room above it, and
     the doctor and the screen he is working at below.
     `center bottom` was right when the copy block covered the bottom of the
     band and the plate only had a 98px strip to live in. There is no copy
     block now; bottom would show the desk and nothing else. */
  .herovid{object-position:center 25%;}
  .signin-form{padding:var(--s3) var(--s2) var(--s5);}
}
/* A short, wide window (a laptop with the browser at half height) used to need
   its own rule, because the copy block's 4rem of padding ate a panel that had
   none to spare. The copy block is gone, so there is nothing left to tighten:
   the panel is a picture, and a picture in a short panel simply crops a little
   harder — 22% at 1280x700, which `center 35%` still clears the lock-up in.
   The query is deleted rather than left empty. */

/* The fee editor opens inside the roster row it belongs to, indented and on a
   tinted ground, so there is never a question about which doctor the Save
   button is about to re-price. */
.feeedit{margin:.75rem 0 .25rem;padding:.875rem 1rem;background:var(--primary-soft);
  border:1px solid var(--line);border-radius:var(--r-sm);}
.feeedit h3{margin:0 0 .625rem;font-size:var(--t-md);}
.feeedit .formgrid{margin-bottom:.5rem;}
.feeedit input{min-height:44px;}
.feeedit .note{margin:.25rem 0 .75rem;}

/* A dose given is a row of record, not a form field, so it reads as a line in
   a ledger with its lot underneath and its undo at the end. */
.givenrow{display:flex;gap:1rem;align-items:flex-start;justify-content:space-between;
  padding:.75rem 0;border-bottom:1px solid var(--line-soft);}
.givenrow:last-of-type{border-bottom:0;}
.givenrow .ptmeta{margin:.15rem 0 0;}
/* ---- THE BELL ----
   One strip, two audiences. It sits directly under the running-late banner
   because they answer the same question from opposite ends — that one is "the
   clinic is behind", this one is "and here is what to do about the next one".
   Status colours are reserved for state, and these ARE states: ready is the
   action colour, hold is the warning one. */
.callbar{display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;
  background:var(--fill-2);border:1px solid var(--line);border-inline-start:4px solid var(--sep);
  border-radius:var(--r-sm);padding:.75rem 1rem;margin-bottom:var(--s2);}
.callbar.ready{background:var(--primary-soft);border-color:var(--primary-line);
  border-inline-start-color:var(--primary);}
.callbar.hold{background:var(--warn-soft);border-inline-start-color:var(--warn);}
.callmsg{flex:1 1 14rem;color:var(--ink);font-size:var(--t-md);}
.callmsg b{font-weight:700;}
.callacts{display:flex;gap:.5rem;flex-wrap:wrap;margin-inline-start:auto;}
.callacts .act{min-height:44px;}

/* ---- WHAT IS STILL OWED ----
   On the expanded row, under the contact line. The four figures are one
   sentence and must not wrap into a column on a phone that reads as four
   unrelated numbers, so they wrap as a group with their labels attached. */
.moneyline{display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;
  margin:.625rem 0;padding:.625rem .75rem;border-radius:var(--r-sm);
  background:var(--fill-1);border:1px solid var(--line);}
.moneyline.owing{background:var(--warn-soft);border-color:var(--warn);}
.moneyline.settled{background:var(--ok-soft);border-color:var(--ok-soft);}
.moneybits{display:flex;gap:1rem;flex-wrap:wrap;flex:1 1 16rem;
  font-size:var(--t-sm);color:var(--ink-2);}
.moneybits b{color:var(--ink);font-variant-numeric:tabular-nums;}
.moneyline .due b{color:var(--ink);}
.moneyline.owing .due b{color:var(--warn);}
.moneyline .act{min-height:44px;}
.paidtag{color:var(--ok);font-weight:700;font-size:var(--t-sm);}
.paylog{flex-basis:100%;font-size:var(--t-sm);}
.paylog ul{list-style:none;margin:.375rem 0 0;padding:0;}
.paylog li{display:flex;gap:.625rem;align-items:center;padding:.25rem 0;}
.paylog li .quiet{color:var(--ink-3);}
.paylog li .act{margin-inline-start:auto;min-height:auto;}

/* ---- THE PERIOD PICKER, shared by both overview screens ----
   It lived twice, in owner.html and clinic.html, and had already drifted: the
   owner's learned to wrap when it grew to five buttons and the doctor's did not,
   because the doctor's had two. Now that both have five, the second copy would
   have shipped a strip that pushes a phone sideways. One definition. */
.rangepick{display:flex;gap:.25rem;background:var(--fill-4);padding:.25rem;
  border-radius:10px;flex-wrap:wrap;}
.rangepick button{min-height:44px;padding:0 .875rem;border:0;border-radius:7px;
  background:transparent;color:var(--ink-2);font:650 var(--t-sm)/1 inherit;
  font-family:inherit;cursor:pointer;}
/* same rule as the tab bar: an active toggle wears the action colour */
.rangepick button[aria-pressed=true]{background:var(--primary);color:var(--on-primary);
  box-shadow:0 1px 3px var(--shadow-2);}
/* The custom window row. Wraps, and the explanatory line below it takes a full
   basis of its own so it never ends up squeezed beside a date field. */
.customrange{display:flex;gap:.75rem;align-items:flex-end;flex-wrap:wrap;
  background:var(--fill-2);border:1px solid var(--line);border-radius:var(--r-sm);
  padding:.875rem 1rem;margin-bottom:var(--s3);}
.customrange .field label{font-size:var(--t-xs);}

.givenpick{display:flex;gap:.75rem;align-items:flex-end;flex-wrap:wrap;margin-top:var(--s2);}
.givenpick .field{margin:0;flex:1 1 14rem;}
.givenpick .field.narrow{flex:0 0 7rem;}
.givenpick select,.givenpick input{min-height:44px;}
.givenpick .act{min-height:44px;}

/* THE INVENT-ONE-NOW HALF of the procedures box. Set apart from the picker
   above it by a rule and a tint, because the two rows do different things and a
   doctor scanning the screen mid-consultation must not type a new "Ear wash"
   into the row that already has one. Not collapsed behind a disclosure: the
   first time this screen is opened the list above is empty, and a picker with
   no options over a hidden link reads as a feature that is broken rather than
   one that is waiting. */
/* One procedure, on the doctor's settings screen: two names, a fee, and the two
   things that can happen to it. Wraps to a stack on a phone rather than
   squeezing four fields into 390px. */
.procrow{display:flex;gap:.75rem;align-items:flex-end;flex-wrap:wrap;
  padding:.75rem 0;border-bottom:1px solid var(--line-soft);}
.procrow:last-of-type{border-bottom:0;}
.procrow .field{margin:0;flex:1 1 12rem;}
.procrow .field.narrow{flex:0 0 8rem;}
.procrow input{min-height:44px;}
.procacts{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;
  margin-inline-start:auto;padding-bottom:.125rem;}
.procacts .hint{color:var(--ink-3);font-size:var(--t-sm);}
/* switched off: still legible, visibly not on offer. Opacity alone would take
   the text under its contrast floor, so the names go quiet instead. */
.procrow.off input{color:var(--ink-3);}

.newproc{margin-top:var(--s3);padding-top:var(--s3);border-top:1px dashed var(--line);}
.newproc > .note{margin:0 0 .25rem;}
.newproc > .note b{color:var(--ink);}

/* Expired stock reads as a warning, not as another table: a red rule down the
   side, the same treatment the low-stock alert already uses, so the two say
   the same kind of thing in the same voice. */
.expcard{border-left:4px solid var(--danger);}
.expcard h3{display:flex;align-items:center;gap:.5rem;margin:0 0 .25rem;font-size:var(--t-md);}
.expfacts{display:flex;gap:1.25rem;flex-wrap:wrap;margin:.75rem 0;font-size:var(--t-sm);
  color:var(--ink-2);}
.expfacts b{display:block;font-size:var(--t-lg);color:var(--ink);line-height:1.15;}
/* The all-clear. Quiet on purpose — it is the state the shelf should be in,
   and a green banner every day trains people to stop reading banners. */
.okrow{display:flex;gap:.6rem;align-items:center;color:var(--ink-3);
  font-size:var(--t-sm);margin:0 0 var(--s2);}
.okrow svg{color:var(--ok);flex-shrink:0;}

/* The expired banner wears the danger colour, not the amber of "running low".
   Two warnings in the same yellow read as one warning repeated. */
.alertrow.bad{border-left-color:var(--danger);background:var(--danger-soft);}
.alertrow.bad svg{color:var(--danger);}

/* ---- Patient documents (features workstream A) ---------------------------------
   A compact list: what it is, when, who, reviewed or not. Titles and file names
   come from people: they wrap, never overflow, and read in their own direction. */
#patientDocsCard h2{display:flex;align-items:center;gap:.5rem;}
.pdocfilters{display:flex;gap:.5rem;flex-wrap:wrap;margin:.25rem 0 .75rem;}
.pdocfilters select{width:auto;flex:1 1 11rem;min-height:var(--tap);max-width:100%;}
.pdoclist{list-style:none;margin:0;padding:0;}
.pdocrow{display:block;padding:.75rem 0;border-top:1px solid var(--line);}
.pdocrow:first-child{border-top:0;}
.pdocmain{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;}
.pdoctitle{font-weight:600;overflow-wrap:anywhere;min-width:0;flex:0 1 auto;}
.pdocrow .ptmeta{display:block;overflow-wrap:anywhere;margin:.25rem 0 0;}
.pdocactions{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.5rem;}
.act.danger{color:var(--danger);border-color:var(--danger);}
.act.danger:hover{background:var(--danger-soft);}
.pdocupload{border:1px dashed var(--line);border-radius:var(--r-sm);padding:var(--s2);margin:0 0 var(--s2);}
.pdocupload .hint{color:var(--ink-3);font-size:var(--t-sm);margin:0 0 .5rem;}
.pdocupload input[type=file]{max-width:100%;min-height:var(--tap);}
.pdocprogress{display:flex;align-items:center;gap:.75rem;margin-top:.5rem;font-size:var(--t-sm);}
.pdocprogress progress{flex:1;height:.6rem;}
.pvoidform{margin-top:.5rem;padding:.75rem;border-radius:var(--r-sm);background:var(--danger-soft);}
.pvoidform input{width:100%;min-height:var(--tap);}
.paymethod-select{width:auto;min-width:8rem;}

/* ---- Money (features workstream B) ----------------------------------------------
   The drawer as tiles, then plain lists. Amounts line up at the row's end and
   use tabular figures; a voided row stays visible, struck through. */
.finstats .stat.primary b{color:var(--primary-ink);}
.finstats .stat.quiet b{color:var(--ink-3);}
.finlist{list-style:none;margin:0;padding:0;}
.finrow{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .75rem;padding:.6rem 0;border-top:1px solid var(--line);}
.finrow:first-child{border-top:0;}
.finrow.voided .finamt{text-decoration:line-through;color:var(--ink-3);}
.fintime{color:var(--ink-3);font-size:var(--t-sm);font-variant-numeric:tabular-nums;min-width:3.2rem;}
.finwho{flex:1 1 9rem;min-width:0;overflow-wrap:anywhere;}
.finamt{margin-inline-start:auto;font-variant-numeric:tabular-nums;white-space:nowrap;}
.finamt.due{color:var(--danger);}
.findl{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.5rem 1.25rem;margin:.5rem 0;}
.findl dt{font-size:var(--t-sm);color:var(--ink-3);}
.findl dd{margin:0;font-weight:700;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;}
.findl dd.bad{color:var(--danger);} .findl dd.warn{color:var(--warn);} .findl dd.good{color:var(--ok);}
.fintable{width:100%;border-collapse:collapse;margin:.5rem 0;font-variant-numeric:tabular-nums;}
.fintable th,.fintable td{padding:.35rem .5rem;border-bottom:1px solid var(--line);text-align:start;}
.fintable td:not(:first-child),.fintable th:not(:first-child){text-align:end;white-space:nowrap;}
.fintable tfoot td{border-bottom:0;}
.invbox{width:100%;margin-top:.5rem;padding:.75rem;border:1px dashed var(--line);border-radius:var(--r-sm);background:var(--card);overflow-x:auto;}
.invslot{width:100%;}
.pdocfilters input[type=date]{width:auto;flex:1 1 9rem;}
.pdocfilters label{align-self:center;font-size:var(--t-sm);color:var(--ink-3);}
.finsheet .finbig{font-size:16pt;margin:8mm 0 4mm;}
.finsheet .finvoid{font-size:18pt;font-weight:700;color:var(--danger);letter-spacing:.1em;}
.paylog li{flex-wrap:wrap;}
#finSummaryCard .tablewrap{overflow-x:auto;}
#finSummary th[scope=row]{white-space:nowrap;}
#finSummary thead th{white-space:nowrap;}

/* ---- Referrals (features workstream C) ---------------------------------------- */
.st-soon{background:var(--warn-soft);color:var(--warn);}
.reflist{list-style:none;margin:0;padding:0;}
.refitem{border-top:1px solid var(--line);}
.refitem:first-child{border-top:0;}
.reftext{white-space:pre-wrap;font-weight:400 !important;}
.reftimeline{margin:.5rem 0;padding-inline-start:1.25rem;font-size:var(--t-sm);color:var(--ink-2);}
.reffield{border:1px solid var(--line);border-radius:var(--r-sm);padding:.75rem;margin:.75rem 0;}
.reffield legend{font-weight:700;padding:0 .25rem;}
.checkline{display:flex;gap:.5rem;align-items:flex-start;margin-top:.5rem;font-size:var(--t-sm);}
.checkline input{margin-top:.2rem;width:auto;}
.refbook .slotpick{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.5rem;}
#refSummary{width:100%;}
#refSummaryCard .tablewrap{overflow-x:auto;}
#refSummary th,#refSummary thead th{white-space:nowrap;}
.refbook{margin-top:.5rem;padding:.75rem;border-radius:var(--r-sm);background:var(--fill-1);border:1px solid var(--line);}
.refboxes{display:inline-flex;gap:.4rem;flex-wrap:wrap;}

/* ---- choosing a time: a summary beside a month calendar and the day's times ---- */
.sched{display:grid;gap:var(--s3);align-items:start;}
@media (min-width:56rem){.sched{grid-template-columns:minmax(15rem,20rem) 1fr;gap:var(--s4);}}
.sched-side{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:var(--s3);
  box-shadow:var(--shadow);}
@media (min-width:56rem){.sched-side{position:sticky;top:5.5rem;}}
.side-clinic{margin:0 0 .75rem;color:var(--ink-3);font-size:var(--t-sm);font-weight:650;}
.side-head{display:flex;gap:.75rem;align-items:center;margin-bottom:1rem;}
.side-title{margin:0;font-size:var(--t-lg);font-weight:750;line-height:1.3;color:var(--ink);}
.side-sub{margin:.125rem 0 0;color:var(--ink-2);font-size:var(--t-md);}
.side-rows{list-style:none;margin:0;padding:1rem 0 0;border-top:1px solid var(--line);display:grid;gap:.75rem;}
.side-rows li{display:flex;gap:.6rem;align-items:flex-start;color:var(--ink-2);font-weight:600;}
.side-rows svg{flex:none;margin-top:.15em;color:var(--ink-3);}
.side-rows .muted{color:var(--ink-3);font-weight:500;}
.sched-main{min-width:0;container-type:inline-size;}
.sched-grid{display:grid;gap:var(--s3);margin-bottom:var(--s3);}
@container (min-width:40rem){.sched-grid{grid-template-columns:minmax(17rem,22rem) 1fr;align-items:start;}}

.cal{max-width:24rem;width:100%;}
.cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem;}
.cal-title{font-weight:750;font-size:var(--t-md);}
.cal-nav{width:var(--tap);height:var(--tap);border-radius:50%;border:0;background:var(--fill-1);color:var(--primary-ink);
  font-size:1.4rem;line-height:1;cursor:pointer;transition:background-color .15s ease,transform .12s cubic-bezier(.23,1,.32,1);}
.cal-nav:active:not([disabled]){transform:scale(.94);}
.cal-nav[disabled]{opacity:.35;cursor:default;}
.cal-dow,.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:.25rem;justify-items:center;}
.cal-dow{margin-bottom:.25rem;color:var(--ink-3);font-size:var(--t-xs);font-weight:700;}
.cal-day,.cal-pad{width:min(100%,2.9rem);aspect-ratio:1;display:grid;place-items:center;border-radius:50%;}
.cal-day{border:0;font:inherit;font-weight:700;background:var(--fill-1);color:var(--primary-ink);cursor:pointer;
  transition:background-color .15s ease,color .15s ease,transform .12s cubic-bezier(.23,1,.32,1);}
.cal-day.off{background:transparent;color:var(--ink-3);opacity:.55;font-weight:500;cursor:default;}
.cal-day[aria-pressed=true]{background:var(--primary);color:var(--on-primary);}
.cal-day:active:not(.off){transform:scale(.94);}
@media (hover:hover) and (pointer:fine){.cal-day:not(.off):not([aria-pressed=true]):hover{background:var(--primary-soft);}}

.timecol{min-width:0;}
@container (min-width:40rem){.timecol{max-height:26rem;overflow-y:auto;padding-inline-end:.25rem;}}
.timecol-head{margin:0 0 var(--s2);color:var(--ink-2);font-size:var(--t-md);}
.slotgroup .count{font-weight:500;text-transform:none;letter-spacing:0;}
.slotlist{grid-template-columns:repeat(auto-fill,minmax(10.5rem,1fr));}
.slotrow{display:flex;gap:.5rem;}
.slotrow.on{grid-column:1/-1;}
.slotlist{grid-auto-flow:dense;}
.slotrow .slot{flex:1 1 0;min-width:0;}
.slotrow.on .slot{background:var(--fill-2,var(--primary-soft));border-color:var(--ink-3);color:var(--ink);}
.slot-go{flex:1 1 0;min-height:var(--tap);border:0;border-radius:var(--r-sm);background:var(--primary);color:var(--on-primary);
  font:inherit;font-weight:700;cursor:pointer;animation:slotgo .18s cubic-bezier(.23,1,.32,1) both;}
.slot-go:active{transform:scale(.97);}
.slot-go.picking{opacity:.75;}
@keyframes slotgo{from{opacity:0;transform:translateX(6px);}to{opacity:1;transform:none;}}
[dir=rtl] .slot-go{animation-name:slotgo-rtl;}
@keyframes slotgo-rtl{from{opacity:0;transform:translateX(-6px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){.slot-go,[dir=rtl] .slot-go{animation:none;}
  .cal-nav,.cal-day{transition:none;}}

/* ---- the owner's Website tab ---- */
.ws-top{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center;}
.ws-status{display:inline-flex;align-items:center;gap:.5rem;font-weight:650;padding:.375rem .75rem;border-radius:999px;background:var(--fill-3);}
.ws-status .ws-dot{width:.625rem;height:.625rem;border-radius:50%;background:var(--warn);}
.ws-status.live .ws-dot{background:var(--ok);}
.ws-langs{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-inline-start:auto;}
.ws-lang,.ws-viewtoggle button,.ws-devices button{min-height:44px;padding:0 1rem;border-radius:10px;border:1px solid var(--line);background:var(--card);color:var(--ink-2);font:inherit;cursor:pointer;}
.ws-lang.on,.ws-viewtoggle button.on,.ws-devices button.on{background:var(--primary);color:var(--on-primary);border-color:var(--primary);}
.ws-viewtoggle{display:flex;gap:.5rem;margin-bottom:var(--s2);}
.ws-grid{display:grid;gap:var(--s2);align-items:start;}
.ws-grid.ws-show-edit .ws-prev{display:none;}
.ws-grid.ws-show-preview .ws-edit{display:none;}
@media (min-width:72rem){
  .ws-viewtoggle{display:none;}
  .ws-grid{grid-template-columns:minmax(24rem,34rem) minmax(0,1fr);}
  .ws-grid .ws-edit,.ws-grid .ws-prev{display:block!important;}
  .ws-prev{position:sticky;top:1rem;}
}
.ws-sec{padding:0;}
.ws-sec > summary{list-style:none;cursor:pointer;padding:var(--s2) var(--s3);display:grid;gap:.125rem;min-height:44px;}
.ws-sec > summary::-webkit-details-marker{display:none;}
.ws-sumtitle{font-size:var(--t-lg);font-weight:650;}
.ws-sumhint{color:var(--ink-3);font-size:var(--t-sm);}
.ws-sec[open] > summary{border-bottom:1px solid var(--line);}
.ws-secbody{padding:var(--s3);}
.ws-opt{font-weight:400;color:var(--ink-3);}
.ws-lab{display:block;font-size:var(--t-sm);font-weight:650;color:var(--ink-2);margin-bottom:.375rem;}
.ws-lab .hint{font-weight:400;color:var(--ink-3);display:block;}
.ws-row{display:grid;gap:.25rem;margin-bottom:var(--s1);}
.ws-doc{padding:var(--s2);border:1px solid var(--line);border-radius:var(--r-sm);margin-bottom:var(--s2);}
.ws-sub{font-size:var(--t-md);margin:var(--s3) 0 .25rem;}
.ws-hours{padding:var(--s2);border:1px solid var(--line);border-radius:var(--r-sm);margin-bottom:var(--s2);display:grid;gap:var(--s2);}
.ws-days{display:flex;flex-wrap:wrap;gap:.375rem;}
.ws-chip{position:relative;}
.ws-chip input{position:absolute;opacity:0;inset:0;cursor:pointer;}
.ws-chip span{display:inline-flex;align-items:center;min-height:44px;padding:0 .875rem;border-radius:999px;border:1px solid var(--line);background:var(--card);}
.ws-chip.on span{background:var(--primary-soft,var(--fill-3));border-color:var(--primary);font-weight:650;}
.ws-chip input:focus-visible + span{outline:2px solid var(--primary);outline-offset:2px;}
.ws-times{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:end;}
.ws-times label{display:grid;gap:.25rem;font-size:var(--t-sm);font-weight:650;color:var(--ink-2);}
.ws-swatches{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center;}
.ws-swatch{width:44px;height:44px;border-radius:50%;border:3px solid var(--card);background:var(--sw);box-shadow:0 0 0 1px var(--line);cursor:pointer;}
.ws-swatch.on{box-shadow:0 0 0 3px var(--ink);}
.ws-custom{display:inline-flex;align-items:center;gap:.5rem;font-size:var(--t-sm);}
.ws-custom input{width:56px;height:44px;padding:0;}
.ws-pic{display:flex;gap:var(--s2);align-items:center;margin-bottom:var(--s2);}
.ws-picbox{width:96px;height:72px;border-radius:var(--r-sm);background:var(--fill-3);display:grid;place-items:center;overflow:hidden;flex:none;font-size:var(--t-sm);color:var(--ink-3);}
.ws-picbox img{width:100%;height:100%;object-fit:cover;}
.ws-checks{display:grid;gap:.5rem;}
.ws-check{display:flex;gap:.75rem;align-items:center;min-height:44px;}
.ws-check i{color:var(--ink-3);font-style:normal;}
.ws-choice{display:grid;gap:.5rem;margin-bottom:var(--s2);}
.ws-radio{display:flex;align-items:center;gap:.75rem;min-height:44px;padding:0 1rem;border:1px solid var(--line);border-radius:var(--r-sm);cursor:pointer;}
.ws-radio.on{border-color:var(--primary);background:var(--primary-soft,var(--fill-3));font-weight:650;}
.ws-prevhead{display:flex;justify-content:space-between;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:.25rem;}
.ws-devices{display:inline-flex;gap:.375rem;}
.ws-frame{border:1px solid var(--line);border-radius:var(--r);overflow:hidden;background:#fff;height:70vh;min-height:28rem;}
.ws-frame.phone{max-width:390px;margin-inline:auto;}
.ws-frame iframe{width:100%;height:100%;border:0;display:block;}
.ws-savebar{position:sticky;bottom:0;background:var(--bg,var(--card));border-top:1px solid var(--line);padding:var(--s2) 0;margin-top:var(--s2);z-index:5;}
.ws-saverow{display:flex;justify-content:space-between;align-items:center;gap:var(--s2);flex-wrap:wrap;}
.ws-savemsg{color:var(--ink-2);}
