/* =========================================================================
   theme.css — design tokens, the app shell, and every shared primitive.

   This is OLD's design system (truemed-yakap/public/css/theme.css), ported rather than
   reinterpreted: the same coral/teal brand over warm neutrals, the same liquid-glass sidebar,
   the same pill/card/button/table vocabulary, Nunito and JetBrains Mono, the `.i` icon sprite.
   Values are OLD's to the decimal — where this file differs from OLD it is because YAKAP3 has
   no such screen (the home queue, the stat tiles, the teleconsult room's dark token flip), or
   because YAKAP3 has a control OLD never had (`.ctl`, `.sug`). Both kinds of difference are
   recorded in DECISIONS D-091.
   ========================================================================= */

/* ============================ DESIGN TOKENS ============================ */
:root{
  /* brand */
  --coral-50:#FFF2EE; --coral-100:#FFE0D5; --coral-500:#E5603B; --coral-600:#C84D2A; --coral-700:#A03A1E;
  --teal-50:#EDFAFA; --teal-100:#D3F3F0; --teal-500:#5BBFB5; --teal-600:#3A9C93; --teal-700:#2C7A72;
  /* neutrals (warm-tinted) */
  --neutral-0:#FFFFFF; --neutral-50:#F7F8FA; --neutral-100:#EEF0F3; --neutral-200:#DEE2E7;
  --neutral-300:#C4CAD4; --neutral-400:#9EA7B3; --neutral-500:#788191; --neutral-600:#566070;
  --neutral-700:#3D4552; --neutral-800:#282F3C; --neutral-900:#171C26;
  /* semantic */
  --success:#22C55E; --success-dark:#15803D; --success-light:#EDFCF2;
  --philhealth-green:#00843D; --philhealth-green-dark:#006B31; --philhealth-green-light:#E8F6EE;
  --warning:#EAB308; --warning-dark:#A16207; --warning-light:#FEFCE8;
  --error:#EF4444;   --error-dark:#B91C1C;   --error-light:#FEF2F2;
  --info:#3B82F6;    --info-dark:#1D4ED8;    --info-light:#EFF6FF;

  /* app aliases */
  --bg:var(--neutral-50); --surface:var(--neutral-0); --surface-2:var(--neutral-50);
  --ink:var(--neutral-900); --ink-2:var(--neutral-600); --ink-3:#697282;
  --line:var(--neutral-200); --line-2:var(--neutral-100);
  /* `--brand-hi` is the TOP stop of the coral gradient every primary surface is painted with —
     the button, the coral avatar, the chart's active tab. It was written out three times as a
     literal before P17; it is one value with one name now, because a brand that drifts by one
     hex digit between two screens is a brand nobody can hold anyone to (D-108). */
  --brand:var(--coral-500); --brand-hi:#EE7250; --brand-ink:var(--coral-600); --brand-soft:var(--coral-50);
  --teal:var(--teal-500); --teal-ink:var(--teal-700); --teal-soft:var(--teal-100);
  --accent:var(--info); --accent-soft:var(--info-light);
  --warn:var(--warning-dark); --warn-soft:var(--warning-light);
  --danger:var(--error-dark); --danger-soft:var(--error-light);
  --ok:var(--success-dark); --ok-soft:var(--success-light);
  /* The hairline that goes with a semantic tint: the ink at low alpha, so a warning box's border
     is the same colour as its text rather than a neighbouring grey. Written as literal rgba in
     six places across app.css before P17. */
  --warn-line:rgba(161,98,7,.28); --danger-line:rgba(185,28,28,.24);
  --brand-line:rgba(229,96,59,.4); --teal-line:rgba(91,191,181,.45);
  /* The two tones that had ink but no name. `.pill.violet` has carried `#5B45B8` as a literal
     since the design pass, and P24's calendar needs the same violet for a follow-up block and
     the same blue for a teleconsult one — so both get the `soft`/`ink`/`line` triplet every
     other tone in this file has, and `.pill` below now reads them rather than repeating them
     (D-108: one value, one name). */
  --violet-soft:#F1EEFC; --violet-ink:#5B45B8; --violet-line:rgba(91,69,184,.5);
  --info-line:rgba(59,130,246,.5);

  --shadow:0 1px 4px 0 rgba(23,28,38,.08),0 0 1px 0 rgba(23,28,38,.04);
  --shadow-md:0 4px 12px 0 rgba(23,28,38,.10),0 1px 3px 0 rgba(23,28,38,.06);
  --shadow-lg:0 18px 50px 0 rgba(23,28,38,.18),0 4px 14px 0 rgba(23,28,38,.08);
  --shadow-coral:0 4px 20px 0 rgba(229,96,59,.22);

  /* Liquid Glass material */
  --glass-blur:saturate(180%) blur(22px);
  --glass-blur-lg:saturate(180%) blur(30px);
  --glass-tint:rgba(255,255,255,.48);
  --glass-tint-strong:rgba(255,255,255,.62);
  --glass-dark:rgba(26,31,42,.52);
  --glass-border:rgba(255,255,255,.55);
  --glass-border-dark:rgba(255,255,255,.1);
  --glass-hi:inset 0 1px 0 rgba(255,255,255,.7),inset 0 0 0 1px rgba(255,255,255,.14);
  --glass-hi-dark:inset 0 1px 0 rgba(255,255,255,.16),inset 0 0 0 1px rgba(255,255,255,.05);
  --hi-soft:inset 0 1px 0 rgba(255,255,255,.75);

  --r:14px; --r-sm:9px; --r-lg:22px; --pill:9999px;
  --font-sans:'Nunito',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --font-mono:'JetBrains Mono','Fira Code',monospace;
  --side-w:252px;

  /* ---- the type scale (P26) ---------------------------------------------------------
     ONE scale, eleven steps, and every font-size in `theme.css`, `app.css`, `chart.css` and
     `room.css` is one of them. It replaced twenty-six different literals — 9px, 9.5, 10, 10.5,
     11, 11.5, 12, 12.5, 13, 13.5, 14, 15, 17, 18, 19, 20, 21, 23, 24, 26, 28, 29, 30, 34, 38 —
     which is not a scale but a history: the same kind of label was set at four different sizes
     on four different screens because each screen was drawn on a different day. This is D-108's
     rule ("one value, one name") applied to type, and the reason it is worth doing is that the
     next screen inherits a decision instead of making one.

     `--fs-2xs` is the FLOOR and it is 11px, up from the 9px this app was setting on the
     encounter room's measurement tiles. Nothing in the app may go below it: `tests/ui-type.test.mjs`
     fails the build if it does. The exceptions are exactly two, and both are stated there —
     `@media print` (paper has its own reading distance) and `slips.css`, which is Annex-H
     geometry in points and is not part of this scale at all.

     `--fs-input` is NOT a step. 16px is the size below which iOS zooms the page when a field
     takes focus, so it is a platform constant that happens to be a size; naming it is what stops
     somebody "tidying" it to 15px and re-introducing the zoom trap on every form in the clinic. */
  --fs-2xs:11px;   /* the floor: uppercase kickers, chip text, timestamps, counts */
  --fs-xs:12px;    /* secondary meta, helper text under a control */
  --fs-sm:13px;    /* dense values, list sub-lines, a phone's table cell */
  --fs-md:14px;    /* controls, buttons, table cells, a desktop input */
  --fs:15px;       /* body */
  --fs-lg:17px;    /* a prominent value: the room's elapsed clock, a patient's name in the card */
  --fs-xl:20px;    /* dialog titles, a big number in a tile */
  --fs-2xl:24px;   /* a screen's subject: the patient banner's name, an encounter's title */
  --fs-3xl:30px;   /* page h1 */
  --fs-4xl:34px;   /* the dashboard's counters */
  --fs-5xl:38px;   /* the sign-in hero, and nothing else */
  --fs-input:16px; /* the iOS zoom threshold — a platform constant, not a step */

  /* The small-caps labels' tracking. Nine different values (.06em … .17em) said the same thing
     in nine slightly different ways; a 10px uppercase kicker is one component and it now has one
     letterform everywhere it appears. */
  --tracking-caps:.12em;

  /* z-index scale, named rather than guessed. The order is the order things sit in: a typeahead
     over the page, the mobile chrome over that, the chart's group drawer over the chrome (its
     Save button lives exactly where the tab bar floats — P17), a modal over everything a screen
     owns, then the toast and the login wall. `--z-sticky` is for CHROME that sticks to the
     viewport; a header sticking inside a card deliberately stays below `--z-menu` so the
     combobox it contains can open over it. */
  --z-menu:40; --z-sticky:60; --z-chrome:115; --z-tabbar:120; --z-sheet:150;
  --z-modal:200; --z-toast:300; --z-login:400;

  /* THE dimmed page behind a layer. This app raises four of them — the shell's modal, the More
     sheet, the chart's group drawer and the encounter room's documentation — and each one had
     picked its own near-black at its own alpha, so the same gesture dimmed the page four
     slightly different amounts depending on which screen you were on (D-120). One value, named,
     the way the z-scale above is named: a layer chooses its BLUR, never its dim. */
  --scrim:rgba(13,16,23,.42);

  /* ---- motion (P21) ----------------------------------------------------------------
     OLD's timing, named. Every duration and every curve below is lifted from
     `truemed-yakap/public/css/theme.css` rather than chosen here — `--ease-out` is the curve
     OLD's `riseIn`, `seg-thumb` and `mtab-blob` all ride (`cubic-bezier(.22,1,.36,1)`),
     `--ease-pop` is its `modalIn` overshoot, `--ease-press` its tab-bar press. They are tokens
     for the same reason `--brand-hi` is one (D-108): a motion language that drifts by 40ms
     between two screens is a language nobody can hold anyone to.

     The one rule that governs all of them: NOTHING here may gate data. Every animation is an
     entrance or a state change on markup that is already in the document, opacity and transform
     only, and `prefers-reduced-motion: reduce` (bottom of this file) switches the whole set off. */
  --ease-out:cubic-bezier(.22,1,.36,1);
  --ease-pop:cubic-bezier(.32,1.3,.42,1);
  --ease-press:cubic-bezier(.34,1.56,.5,1);
  --t-press:.14s;   /* a button answering a finger */
  --t-state:.16s;   /* a row, a chip, a tab changing what it is */
  --t-scrim:.24s;   /* a layer arriving over the page */
  --t-enter:.5s;    /* OLD's `riseIn` — the view itself */
}

/* ============================ BASE ============================ */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
/* `scroll-behavior:smooth` is OLD's (its theme.css line 60) and it is here for one specific
   affordance: the findings rail jumps to the field a rule refused, and a jump that teleports
   loses the reader's place. The reduced-motion block at the bottom puts it back to `auto`. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;scroll-behavior:smooth}
body{
  background:var(--bg); color:var(--ink); font-size:var(--fs);
  font-family:var(--font-sans); -webkit-font-smoothing:antialiased; line-height:1.55; text-wrap:pretty;
}
/* A dialog is up: the page behind it does not scroll out from under it (OLD's `body.noscroll`). */
body.noscroll{overflow:hidden}
h1,h2,h3,h4{letter-spacing:-.015em;margin:0}

/* Conditionally-revealed controls are toggled with the `hidden` attribute, and several of them
   live inside grids and flex rows whose own `display` would otherwise beat the browser default. */
[hidden]{display:none !important}

input,select,textarea,button{font-family:inherit;font-size:var(--fs-input)}
@media (min-width:681px){ input,select,textarea{font-size:var(--fs-md)} }
input,select,textarea{
  -webkit-appearance:none; appearance:none; touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
  width:100%; padding:11px 13px; border:1px solid var(--line);
  border-radius:var(--r-sm); background:var(--surface); color:var(--ink); outline:none;
  font-weight:600;
}
textarea{min-height:96px;resize:vertical;line-height:1.5}
input[type=checkbox],input[type=radio]{-webkit-appearance:auto;appearance:auto;width:18px;height:18px;padding:0;accent-color:var(--brand);cursor:pointer}
input[type=file]{padding:9px 12px;font-weight:700}
input::placeholder,textarea::placeholder{font-weight:500;color:var(--ink-3)}
input:focus,select:focus,textarea:focus{border-color:var(--teal);box-shadow:0 0 0 3px var(--teal-100)}
input[readonly],input[disabled],select[disabled],textarea[disabled]{background:var(--surface-2);color:var(--ink-2);cursor:not-allowed}
input.err,select.err,textarea.err{border-color:var(--coral-500);background:var(--coral-50);box-shadow:0 0 0 3px rgba(229,96,59,.14)}
select{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23788191' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 12px center; padding-right:34px;
}
/* `.ctl` is YAKAP3's marker class for a generated control. Every element it lands on is an
   input, a select or a textarea, so the rules above already dress it; it exists so the renderer
   can find its own controls, and it carries no styling of its own beyond the read-only tint. */
.ctl:disabled,.ctl[readonly]{background:var(--surface-2);color:var(--ink-2)}

label{display:block;font-size:var(--fs-xs);font-weight:700;color:var(--ink-2);margin:0 0 5px 2px;letter-spacing:.01em}
/* The dictionary's two marks: a required field carries `*` in the error colour, a suggested one
   a `·` in the warning colour — the same distinction the engine draws between a block and a nudge. */
label .req,.req{color:var(--error);font-weight:900;margin-left:4px}
label .sug,.sug{color:var(--warning-dark);font-weight:900;margin-left:4px}
a{color:var(--teal-ink);text-decoration:none;cursor:pointer}
.inline-link{border:0;background:none;color:var(--teal-ink);font:inherit;font-weight:800;padding:0;cursor:pointer}
.inline-link:hover{text-decoration:underline}
.mono{font-family:var(--font-mono)}
/* P26 — a fact that only a `title` carried was a fact a touch screen could not read at all. Any
   element a page marks with `data-hint` answers a TAP with the same sentence, as a toast
   (`app.js`); the `title` stays for the pointer, and the dotted rule is what says there is
   something to press. Everything wearing this is INFORMATIONAL — no hint is the only way to
   reach an action. */
[data-hint]{cursor:help;-webkit-tap-highlight-color:transparent;
  text-decoration:underline dotted var(--ink-3);text-underline-offset:3px}
/* The dotted rule belongs under a PHRASE in running text ("Facility name not yet registered").
   A pill, a mono value and anything inside the dark room are already their own object with their
   own edge, and a dotted line under a chip's label or under the elapsed clock's digits reads as
   damage rather than as an affordance. They keep the tap; they drop the underline. */
.pill[data-hint],.mono[data-hint],.room [data-hint]{text-decoration:none}
[data-hint]:focus-visible{outline:2px solid var(--teal-500);outline-offset:2px}
/* THE WRAPPER A DISABLED CONTROL'S HINT LIVES ON (D-120). Every browser stops a click at a
   disabled element, so a `data-hint` on the button or select itself is a sentence a finger can
   never read — and a disabled control is exactly the one that owes an explanation. The hint goes
   here, on a box that is not disabled; the `title` stays on the control, where a mouse finds it
   whatever its state. It is layout-neutral: an inline-flex box that is the size of its one
   child, wearing no edge, no padding and no colour of its own. `.rm-hw` is the room's own copy
   of this idea, sized for a grid cell (room.css). */
.hintw{display:inline-flex;min-width:0}
/* …and it carries no underline: the control inside it is the object, not the wrapper. */
.hintw[data-hint]{text-decoration:none;cursor:not-allowed}
::selection{background:var(--coral-100);color:var(--coral-700)}
:focus-visible{outline:2px solid var(--teal-500);outline-offset:2px}

/* ---- SVG icon system ---- */
.i{width:1.05em;height:1.05em;display:inline-block;vertical-align:-.16em;flex:none;
  fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.i.big{width:32px;height:32px;stroke-width:1.6}
.btn .i{width:15px;height:15px;margin-right:2px}
.empty .big .i{width:34px;height:34px;color:var(--ink-3)}

/* ============================ LAYOUT / SHELL ============================ */
.app{display:grid;grid-template-columns:var(--side-w) 1fr;min-height:100vh}
.side{
  background:linear-gradient(180deg,rgba(34,40,53,.86),rgba(23,28,38,.9));color:#aeb6c2;padding:22px 16px;
  -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
  border-right:1px solid var(--glass-border-dark);box-shadow:1px 0 24px rgba(23,28,38,.06),var(--glass-hi-dark);
  position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:6px;overflow:auto;
}
.brand{display:flex;flex-direction:column;align-items:center;gap:8px;padding:2px 4px 4px}
.brand-logo{width:152px;max-width:100%;height:auto;display:block;margin:8px auto 2px}
.brand-tag{font-size:var(--fs-2xs);color:var(--teal-500);letter-spacing:var(--tracking-caps);text-transform:uppercase;font-weight:800;
  padding:0 6px 14px;display:flex;justify-content:center;align-items:center;gap:8px;flex-wrap:wrap;text-align:center}

.side-user{display:flex;align-items:center;gap:10px;padding:9px 10px;margin-bottom:6px;border-radius:13px;
  border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);box-shadow:var(--glass-hi-dark)}
.side-user[hidden]{display:none}
.side-user-txt{min-width:0;flex:1}
.side-user-name{font-size:var(--fs-sm);font-weight:800;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}
.side-user-role{font-size:var(--fs-2xs);font-weight:800;letter-spacing:var(--tracking-caps);text-transform:uppercase;color:var(--teal-500);margin-top:2px}
.side-logout{width:30px;height:30px;flex:none;display:grid;place-items:center;border-radius:9px;cursor:pointer;
  border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04);color:#aeb6c2;transition:.16s}
.side-logout:hover{background:rgba(229,96,59,.22);border-color:rgba(229,96,59,.4);color:#fff}
.side-logout .i{width:15px;height:15px}

.nav{display:flex;flex-direction:column;gap:3px;margin-top:6px}
.nav a{display:flex;align-items:center;gap:11px;padding:11px 13px;border-radius:10px;color:#aeb6c2;
  font-weight:700;font-size:var(--fs-md);cursor:pointer;transition:.18s}
.nav a:hover{background:rgba(255,255,255,.06);color:#fff}
.nav a.active{background:linear-gradient(180deg,rgba(229,96,59,.28),rgba(229,96,59,.16));color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.16),inset 0 0 0 1px rgba(255,255,255,.07),0 2px 8px rgba(229,96,59,.18)}
.nav a[hidden]{display:none}
.nav a .ico{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center}
.nav a .ico .i{width:18px;height:18px}

.side .foot{margin-top:auto;font-size:var(--fs-2xs);color:#6c7686;padding:8px;line-height:1.5}
.side .facility{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid rgba(255,255,255,.08)}
.side .facility-k{font-size:var(--fs-2xs);font-weight:900;letter-spacing:var(--tracking-caps);text-transform:uppercase;color:#5c6573}
.side .facility-v{font-size:var(--fs-xs);font-weight:700;color:#98a1ad;margin-top:3px;line-height:1.4}
.ver-chip{display:flex;width:max-content;align-items:center;gap:5px;padding:3px 9px;border-radius:var(--pill);
  border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:#aeb6c2;
  font-size:var(--fs-2xs);font-weight:800;letter-spacing:.03em;font-family:var(--font-sans)}

main{padding:28px 32px 64px;width:100%;min-width:0}
/* The registry and the admin screens are reading width; the encounter room is a three-zone
   workspace and takes what the display gives it. */
.page{max-width:1160px;margin:0 auto}
.topbar{display:flex;align-items:center;gap:16px;margin-bottom:24px;flex-wrap:wrap}
.topbar h1{font-size:var(--fs-3xl);margin:0;letter-spacing:-.02em;font-weight:800}
.topbar p{margin:3px 0 0;color:var(--ink-2);font-size:var(--fs-sm);font-weight:600}
.topbar .spacer{flex:1}
.topbar .row{margin-left:auto}

/* ---- P25 (D-117): while an encounter room is open, the room owns the viewport ----
   The sidebar and the mobile chrome step aside and the room's own top bar — status, date,
   elapsed clock, Leave Encounter — is the only chrome left. `app.js` sets `room-open` from the
   ROUTE, so arriving anywhere else always gives the shell back.

   This is LAYOUT and nothing else. No token is redeclared here: the dark palette stays scoped
   to `.room` exactly as D-114 put it, because the concern behind that decision — a class on the
   document can reach anything, a printed slip included — did not change. `.mtab` and `.mtop`
   live outside `.app`, so they are marked separately rather than reached with a `:has()` this
   app uses nowhere else. */
.app.room-open{grid-template-columns:1fr}
.app.room-open>.side{display:none}
.app.room-open>main{padding:0}
.app.room-open .room{border-radius:0;min-height:100vh;min-height:100dvh}

/* mobile chrome (hidden on desktop) */
.mtop{display:none}
.mtab{display:none}
.mtop.room-hidden,.mtab.room-hidden{display:none!important}

/* ============================ BUTTONS ============================ */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;border:1px solid var(--line);white-space:nowrap;
  border-radius:var(--pill);padding:10px 18px;font-weight:800;font-size:var(--fs-md);cursor:pointer;
  transition:transform .14s,box-shadow .16s,background .16s,border-color .16s;
  background:linear-gradient(180deg,rgba(255,255,255,.95),var(--neutral-50));color:var(--ink);font-family:var(--font-sans);
  box-shadow:var(--hi-soft),0 1px 2px rgba(23,28,38,.07)}
.btn:hover{border-color:var(--neutral-300);box-shadow:var(--hi-soft),0 4px 12px rgba(23,28,38,.12);transform:translateY(-1px)}
.btn:active{transform:translateY(0);box-shadow:var(--hi-soft),0 1px 2px rgba(23,28,38,.08)}
.btn:focus-visible{outline:2px solid var(--teal-500);outline-offset:2px}
/* An anchor wearing the button's clothes — "Join call" (P12), a media download (P13). The
   underline and the visited colour an `<a>` brings are the two things that give it away, and both
   have to go wherever it appears, not only inside the encounter room's action row. */
a.btn{text-decoration:none;color:var(--ink)}
a.btn.primary,a.btn.green{color:#fff}
a.btn.teal{color:var(--teal-ink)}
a.btn.danger{color:var(--danger)}
.btn.primary{background:linear-gradient(180deg,var(--brand-hi),var(--brand));color:#fff;border-color:transparent;
  box-shadow:var(--shadow-coral),inset 0 1px 0 rgba(255,255,255,.34)}
.btn.primary:hover{background:linear-gradient(180deg,#E5603B,var(--brand-ink));border-color:transparent;
  box-shadow:0 8px 26px rgba(229,96,59,.34),inset 0 1px 0 rgba(255,255,255,.34);transform:translateY(-1px)}
.btn.teal{background:linear-gradient(180deg,rgba(91,191,181,.16),rgba(91,191,181,.08));color:var(--teal-ink);border-color:rgba(91,191,181,.55)}
.btn.teal:hover{background:linear-gradient(180deg,rgba(91,191,181,.24),rgba(91,191,181,.12))}
.btn.green{color:#fff;border-color:var(--philhealth-green-dark);
  background:linear-gradient(180deg,var(--philhealth-green),var(--philhealth-green-dark));
  box-shadow:0 5px 14px rgba(0,132,61,.2),var(--hi-soft)}
.btn.green:hover{background:linear-gradient(180deg,#009447,var(--philhealth-green));color:#fff}
.btn.ghost{background:transparent;border-color:transparent;color:var(--ink-2);box-shadow:none}
.btn.ghost:hover{background:rgba(120,129,145,.12);color:var(--ink);box-shadow:none;transform:none}
.btn.danger{color:var(--danger);border-color:#f6cfcf;background:linear-gradient(180deg,#fff,var(--danger-soft))}
.btn.danger:hover{border-color:#e79b9b;background:linear-gradient(180deg,#fff,#fde7e7)}
.btn.sm{padding:7px 14px;font-size:var(--fs-sm)}
.btn.xs{padding:5px 11px;font-size:var(--fs-xs);gap:5px}
.btn.xs .i{width:13px;height:13px}
.btn.block{width:100%}
.btn.icon-only{width:36px;height:36px;padding:0;border-radius:50%}
.btn.icon-only .i{width:17px;height:17px;margin:0}
.btn[disabled],.btn:disabled,.btn.locked{opacity:.6;cursor:not-allowed;box-shadow:none;transform:none}
.btn[disabled]:hover,.btn:disabled:hover,.btn.locked:hover{transform:none;box-shadow:none;border-color:var(--line)}

/* ============================ CARDS + PRIMITIVES ============================ */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--shadow),var(--hi-soft);margin-bottom:18px}
.card.pad,.pad{padding:18px}
.card-h{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:15px 18px;border-bottom:1px solid var(--line-2);
  font-size:var(--fs);font-weight:800}
.card-h h3{margin:0;font-size:var(--fs);font-weight:800}
.card-h .spacer{flex:1}
.card.pad>.card-h{padding:0 0 11px;margin-bottom:13px}
.table-card,.card:has(> table),.tscroll{overflow:auto;-webkit-overflow-scrolling:touch}
.grid{display:grid;gap:16px}
.grid>*{min-width:0}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end}
.row.tight{gap:8px}
.between{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.spacer{flex:1}
.muted{color:var(--ink-2)} .tiny{font-size:var(--fs-xs)} .b{font-weight:800} .b9{font-weight:900}
.mt0{margin-top:0} .mb0{margin-bottom:0}
.empty{text-align:center;color:var(--ink-3);padding:46px 20px;font-weight:600}
.empty .big{font-size:var(--fs-4xl);margin-bottom:8px}
.spinner{width:34px;height:34px;border:3px solid var(--line);border-top-color:var(--brand);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto}
.spinner.sm{width:18px;height:18px;border-width:2px;display:inline-block;margin:0;vertical-align:-3px}
@keyframes spin{to{transform:rotate(360deg)}}
.hr{height:1px;background:var(--line-2);border:0;margin:16px 0}
.kv{display:flex;flex-direction:column;gap:2px}
.kv .k{font-size:var(--fs-2xs);font-weight:900;letter-spacing:var(--tracking-caps);text-transform:uppercase;color:var(--ink-3);line-height:1.2}
.kv .v{font-size:var(--fs-md);font-weight:800;line-height:1.35;color:var(--ink);overflow-wrap:anywhere}

.avatar{width:36px;height:36px;border-radius:50%;background:var(--teal-500);color:#fff;
  display:grid;place-items:center;font-weight:800;font-size:var(--fs-sm);flex:none;overflow:hidden}
.avatar.sm{width:30px;height:30px;font-size:var(--fs-2xs)}
.avatar.lg{width:52px;height:52px;font-size:var(--fs-xl);border-radius:16px;background:linear-gradient(135deg,var(--teal-500),var(--teal-700))}
/* OLD's `xl` tile — the big soft square the patient banner is built around. It is a SQUARE with
   a large radius rather than a circle: at 66px a circle reads as a profile photo that is
   missing, and this is a monogram (P22). */
.avatar.xl{width:66px;height:66px;border-radius:20px;font-size:var(--fs-2xl);
  background:linear-gradient(135deg,var(--teal-500),var(--teal-700))}
.avatar.coral{background:linear-gradient(135deg,var(--brand-hi),var(--coral-600))}

/* ============================ PILLS / TAGS / BADGES ============================ */
.pill{display:inline-flex;align-items:center;gap:6px;padding:3px 11px;border-radius:var(--pill);
  font-size:var(--fs-xs);font-weight:800;white-space:nowrap;background:var(--line-2);color:var(--ink-2)}
.pill .pdot{width:6px;height:6px;border-radius:50%;background:currentColor}
.pill .i{width:13px;height:13px}
.pill.warn{background:var(--warning-light);color:var(--warning-dark)}
.pill.danger{background:var(--error-light);color:var(--error-dark)}
.pill.ok{background:var(--success-light);color:var(--success-dark)}
.pill.brand{background:var(--coral-50);color:var(--coral-600)}
.pill.teal{background:var(--teal-50);color:var(--teal-700)}
.pill.green{background:var(--philhealth-green-light);color:var(--philhealth-green)}
.pill.info{background:var(--info-light);color:var(--info-dark)}
.pill.muted{background:var(--line-2);color:var(--ink-2)}
.pill.violet,.pill.ambiguous{background:var(--violet-soft);color:var(--violet-ink)}
.pill.ambiguous{margin-left:6px}
.pill.lg{font-size:var(--fs-sm);padding:5px 13px}
.pill.outline{background:transparent;border:1px solid currentColor}

.badge{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--pill);
  background:var(--error);color:#fff;font-size:var(--fs-2xs);font-weight:800;line-height:1}
.badge.teal{background:var(--teal-600)} .badge.muted{background:var(--neutral-300);color:var(--neutral-800)}
.badge.green{background:var(--philhealth-green)}

/* ============================ ALERTS / NOTES ============================ */
.alert{display:flex;gap:14px;align-items:flex-start;padding:15px 16px;border:1px solid var(--line);
  border-radius:var(--r);background:var(--surface);margin-bottom:11px;box-shadow:var(--shadow),var(--hi-soft)}
.alert .bar{width:4px;align-self:stretch;border-radius:4px;flex:none;background:var(--neutral-300)}
.alert.warn .bar{background:var(--warning)}
.alert.danger .bar{background:var(--error)}
.alert.info .bar{background:var(--info)}
.alert.ok .bar{background:var(--success)}
.alert .act{margin-left:auto;display:flex;gap:8px;align-items:center;flex:none}
.alert .i{width:17px;height:17px}

/* note banner (guidance / dictionary hints) */
.note{display:flex;gap:11px;align-items:flex-start;padding:12px 14px;border-radius:var(--r);
  border:1px dashed rgba(91,191,181,.55);background:linear-gradient(180deg,var(--teal-50),rgba(255,255,255,.9));
  color:var(--teal-ink);font-size:var(--fs-sm);font-weight:700;line-height:1.45;margin-bottom:12px}
.note .i{width:16px;height:16px;flex:none;margin-top:1px}
.note.warn{border-color:rgba(161,98,7,.4);background:linear-gradient(180deg,var(--warning-light),rgba(255,255,255,.9));color:var(--warning-dark)}
.note.brand{border-color:rgba(229,96,59,.4);background:linear-gradient(180deg,var(--coral-50),rgba(255,255,255,.9));color:var(--coral-700)}
.help,.field-help{font-size:var(--fs-xs);color:var(--ink-3);margin-top:5px;font-weight:600;line-height:1.45}

/* ============================ TABLES ============================ */
table{width:100%;border-collapse:collapse}
.card table,.tscroll table{min-width:680px}
th{text-align:left;font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:var(--tracking-caps);color:var(--ink-3);
  font-weight:800;padding:12px 16px;border-bottom:1px solid var(--line)}
th.tnum,td.tnum{text-align:right;white-space:nowrap}
th.tact,td.tact{text-align:right;white-space:nowrap}
td{padding:13px 16px;border-bottom:1px solid var(--line-2);font-size:var(--fs-md);font-weight:600;vertical-align:middle}
tr:last-child td{border-bottom:none}
tbody tr.clk,tr.clk{cursor:pointer;transition:background-color .16s ease}
tbody tr.clk:hover td,tr.clk:hover td{background:var(--surface-2)}
td .row{gap:8px;flex-wrap:nowrap;justify-content:flex-end}

/* ============================ FORMS ============================ */
.field{margin-bottom:14px}
.field:last-child{margin-bottom:0}
.form-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}
.form-grid .full,.form-grid .wide{grid-column:1/-1}
.form-grid .field{margin-bottom:0}
.check{display:flex;align-items:center;gap:9px;font-size:var(--fs-md);font-weight:700;color:var(--ink);cursor:pointer;line-height:1.3}
.check input{flex:none}
.filters{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
.filters .field{margin-bottom:0;min-width:170px}
.filters .grow{flex:1;min-width:220px}
.chip{display:inline-flex;align-items:center;gap:6px;padding:6px 13px;border-radius:var(--pill);cursor:pointer;
  border:1px solid var(--line);background:var(--surface);font-size:var(--fs-sm);font-weight:800;color:var(--ink-2);transition:.15s}
.chip:hover{border-color:var(--teal-500);color:var(--teal-ink);background:var(--teal-50)}
.chip.on{border-color:rgba(91,191,181,.55);background:var(--teal-50);color:var(--teal-ink)}

/* segmented control */
.seg{position:relative;display:flex;border:1px solid var(--line);border-radius:var(--pill);overflow:hidden;width:fit-content;max-width:100%;
  background:var(--neutral-100);padding:3px;box-shadow:inset 0 1px 2px rgba(23,28,38,.06)}
.seg button{position:relative;z-index:1;border:none;background:transparent;padding:7px 16px;font-weight:800;cursor:pointer;
  color:var(--ink-2);font-family:var(--font-sans);border-radius:var(--pill);transition:color .2s;white-space:nowrap}
.seg button.on{color:var(--teal-ink);background:linear-gradient(180deg,#fff,var(--neutral-50));box-shadow:var(--shadow)}
.seg button:focus-visible{outline:2px solid var(--teal-500);outline-offset:1px}
.seg.wide{width:100%}
.seg.wide button{flex:1}

/* ============================ MODAL ============================ */
.scrim{position:fixed;inset:0;background:var(--scrim);
  -webkit-backdrop-filter:blur(14px) saturate(135%);backdrop-filter:blur(14px) saturate(135%);
  display:none;align-items:flex-start;justify-content:center;padding:40px 16px;z-index:var(--z-modal);overflow:auto}
.scrim.show{display:flex;animation:scrimFade var(--t-scrim) ease}
/* The way OUT, which OLD had and this app did not: the scrim keeps painting for 220ms while the
   dialog drops away under it, so a Cancel does not blink the page. It is `pointer-events:none`
   throughout — the dialog is over the moment it is dismissed, and the animation is only what the
   eye is shown on the way. `app.js` takes the class off (and empties `#modal`) on a timer that is
   deliberately LONGER than the animation, never shorter. */
.scrim.closing{display:flex;pointer-events:none;animation:scrimFadeOut .22s ease forwards}
.modal{background:rgba(255,255,255,.97);
  -webkit-backdrop-filter:var(--glass-blur-lg);backdrop-filter:var(--glass-blur-lg);
  border:1px solid var(--glass-border);border-radius:var(--r-lg);
  box-shadow:var(--shadow-lg),var(--glass-hi);width:100%;max-width:560px;margin:auto 0}
.modal.wide{max-width:1040px}
.modal .mh{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px;border-bottom:1px solid rgba(23,28,38,.08)}
.modal .mh h3{margin:0;font-size:var(--fs-xl);font-weight:800}
.modal .mb{padding:22px}
.modal .mb p{margin:0 0 12px;font-size:var(--fs-md);font-weight:650;color:var(--ink-2);line-height:1.55}
.modal .mf{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:15px 22px;border-top:1px solid rgba(23,28,38,.08);flex-wrap:wrap}
.x{cursor:pointer;color:var(--ink-3);font-size:var(--fs-2xl);line-height:1;border:none;background:none;padding:0 4px}
.x:hover{color:var(--ink)}
.scrim.show .modal{animation:modalIn .4s var(--ease-pop)}
.scrim.closing .modal{animation:modalOut .2s cubic-bezier(.4,0,.6,1) forwards}
@keyframes scrimFade{from{opacity:0}to{opacity:1}}
@keyframes scrimFadeOut{from{opacity:1}to{opacity:0}}
@keyframes modalIn{from{opacity:0;transform:scale(.965) translateY(12px)}to{opacity:1;transform:none}}
@keyframes modalOut{from{opacity:1;transform:none}to{opacity:0;transform:scale(.97) translateY(10px)}}

/* ============================ TOAST ============================ */
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(40px);
  display:inline-flex;align-items:center;gap:9px;max-width:min(560px,calc(100vw - 32px));
  background:var(--glass-dark);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
  color:#fff;padding:11px 20px;border-radius:var(--pill);font-weight:800;font-size:var(--fs-md);
  box-shadow:var(--shadow-lg),var(--glass-hi-dark);opacity:0;transition:.25s;z-index:var(--z-toast);pointer-events:none}
/* A TOAST IS AN ANNOUNCEMENT, NEVER A TARGET (D-121).
   `pointer-events:auto` here made the shown toast eat the tap under it, and it sits at
   `--z-toast` (300) over `--z-modal` (200) at the bottom-centre of the viewport — which is
   exactly where a dialog's footer lands once the sheet is scrolled to its actions on a phone.
   Measured on a 375x812 viewport with the booking sheet scrolled down: the toast's box covered
   `Cancel` completely and `elementFromPoint` at the button's centre returned the TOAST, so the
   button could not be pressed while a toast was up — and the toast a failed booking raises is
   precisely the moment the user reaches for Cancel. Nothing has ever listened for a click on
   `#toast` (`app.js` only sets its class and a 3.2s timer), so taking this off removes no
   behaviour at all; the base rule's `pointer-events:none` simply holds throughout. */
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.toast .i{width:16px;height:16px}
.toast.ok{background:rgba(21,128,61,.86)}
.toast.warn{background:rgba(161,98,7,.88)}
.toast.danger{background:rgba(185,28,28,.88)}

/* ============================ LOGIN ============================ */
.login-overlay{display:none;position:fixed;inset:0;z-index:var(--z-login);overflow:auto;
  background:radial-gradient(1200px 620px at 12% 8%,rgba(91,191,181,.22),transparent 62%),
             radial-gradient(900px 560px at 88% 92%,rgba(229,96,59,.2),transparent 60%),
             linear-gradient(160deg,#1b2130,#12161f 62%,#0e1219)}
.login-overlay.show{display:block}
.login-wrap{min-height:100vh;display:grid;grid-template-columns:1.05fr .95fr;max-width:1120px;margin:0 auto;
  align-items:center;gap:40px;padding:40px 28px}
.login-brand{color:#e8ecf2}
.login-brand .brand-logo{width:210px;margin:0 0 14px}
.login-brand .tag{font-size:var(--fs-2xs);color:var(--teal-500);letter-spacing:var(--tracking-caps);text-transform:uppercase;font-weight:900}
.login-brand h1{font-size:var(--fs-5xl);font-weight:900;letter-spacing:-.03em;margin:18px 0 10px;color:#fff;line-height:1.1;text-wrap:balance}
.login-brand p{font-size:var(--fs);font-weight:600;color:#c3cad5;margin:0 0 24px;max-width:44ch;line-height:1.6}
.login-points{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:11px}
.login-points li{display:flex;align-items:flex-start;gap:11px;font-size:var(--fs-md);font-weight:700;color:#c3cad5}
.login-points .dotic{width:26px;height:26px;flex:none;border-radius:9px;display:grid;place-items:center;
  background:rgba(91,191,181,.16);color:var(--teal-500);border:1px solid rgba(91,191,181,.3)}
.login-points .dotic .i{width:14px;height:14px}
.login-card{background:rgba(255,255,255,.97);border:1px solid var(--glass-border);border-radius:var(--r-lg);
  -webkit-backdrop-filter:var(--glass-blur-lg);backdrop-filter:var(--glass-blur-lg);
  box-shadow:var(--shadow-lg),var(--glass-hi);padding:26px 26px 22px}
.login-card h2{margin:0 0 3px;font-size:var(--fs-xl);font-weight:900}
.login-card .sub{margin:0 0 18px;font-size:var(--fs-sm);font-weight:600;color:var(--ink-2)}
.login-err{display:none;margin-bottom:12px;padding:10px 13px;border-radius:11px;background:var(--error-light);
  border:1px solid rgba(185,28,28,.22);color:var(--error-dark);font-size:var(--fs-sm);font-weight:800}
.login-err.show{display:block}
.login-foot{margin-top:14px;text-align:center;font-size:var(--fs-xs);font-weight:700;color:var(--ink-3)}
/* The sign-in card arrives on OLD's own curve. It is the ONE thing on this screen that moves:
   the wall behind it is painted, not animated, so a session that has just ended does not have to
   wait through a light show to be signed back in. */
.login-overlay.show .login-card{animation:riseIn var(--t-enter) var(--ease-out) both}

/* ============================ VIEW MOTION (P21) ============================
   OLD's page-load animation, restored — and D-091's reason for leaving it out honoured rather
   than overruled. That decision said: "choreographing the load makes every navigation cost 500ms
   of watching." What makes it affordable now is WHERE it is attached.

   OLD put every card directly into `#view`, so `#view.view-enter > *` staggered the whole page.
   YAKAP3 puts exactly ONE wrapper into `#view` (`.page`, `.chart`, `.enc`, `.page.slips`), so the
   same selector would animate one box and stagger nothing. The stagger therefore lands on the
   wrapper's children — which is the identical set of elements OLD was animating — and the three
   wrappers that must NOT be transformed get a plain opacity fade instead, exactly as OLD's own
   `encRoomIn` did for its encounter room:

     · `.chart` has a sticky lens row and hosts a fixed-position drawer scrim;
     · `.enc` has a sticky findings rail and sticky group headers;
     · `.slips` is Annex-H geometry in points and is what a printer is pointed at.

   `both` means an element is invisible until its turn: the LONGEST delay is therefore the number
   that matters, and it is OLD's — 220ms, after which every remaining child starts together. No
   element waits on a network read to begin, because the class is added after the view is already
   populated; the animation decorates a page that is finished, it never stands in for one that
   is not. */
@keyframes riseIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}

/* `:not(.appt-scrim)` is the one exclusion, and it is a correctness fix rather than taste: the
   diary keeps its booking sheet's full-viewport scrim as a child of `.page`, so without this the
   entrance handed a `position:fixed` overlay a transform and a 135ms delay — and then that delay
   was inherited by the scrim's OWN arrival animation, so the sheet's backdrop appeared an eighth
   of a second after the sheet did. Found by reading computed styles on staging, not by looking. */
#view.view-enter>.page:not(.slips)>*:not(.appt-scrim){animation:riseIn var(--t-enter) var(--ease-out) both}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(1){animation-delay:0s}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(2){animation-delay:.045s}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(3){animation-delay:.09s}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(4){animation-delay:.135s}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(5){animation-delay:.18s}
#view.view-enter>.page>*:not(.appt-scrim):nth-child(n+6){animation-delay:.22s}
/* The chart's three panels, named rather than counted: its fourth child is the drawer's
   full-viewport scrim, which must never be given a transform — a transformed ancestor is what a
   `position:fixed` sheet resolves against, and the drawer would stop covering the page. */
#view.view-enter>.chart>.chart-head,
#view.view-enter>.chart>.chart-lens,
#view.view-enter>.chart>.chart-body{animation:riseIn var(--t-enter) var(--ease-out) both}
#view.view-enter>.chart>.chart-lens{animation-delay:.045s}
#view.view-enter>.chart>.chart-body{animation-delay:.09s}
/* The three wrappers that fade in place rather than rising, because each one anchors something
   the viewport measures against — sticky rails, sticky headers, and a printed page. */
#view.view-enter>.chart,#view.view-enter>.enc,#view.view-enter>.page.slips{
  animation:fadeIn .45s var(--ease-out) both}

/* ============================ MOBILE ============================ */
@media (max-width:880px){
  .app{grid-template-columns:1fr}
  .side{display:none}
  /* P26 — `viewport-fit=cover` (index.html) means the page paints under the notch and under the
     home indicator, so both pieces of chrome pay the inset back. On a phone with no inset —
     every Android, every desktop window narrowed to this width — `env()` resolves to 0 and these
     are the same numbers as before. */
  .mtop{display:flex;align-items:center;justify-content:center;gap:10px;position:sticky;top:0;z-index:var(--z-chrome);
    padding:8px 16px;padding-top:calc(8px + env(safe-area-inset-top));background:var(--glass-dark);
    -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
    border-bottom:1px solid var(--glass-border-dark);box-shadow:0 4px 22px rgba(23,28,38,.18),var(--glass-hi-dark)}
  .mtop-logo{height:30px;width:auto;display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.25))}
  .mtop-tag{font-size:var(--fs-2xs);font-weight:900;letter-spacing:var(--tracking-caps);text-transform:uppercase;color:var(--teal-500)}
  /* The floor of the page clears the floating bar AND the home indicator under it. */
  main{padding:16px 14px calc(104px + env(safe-area-inset-bottom))}
  .topbar{align-items:flex-start;gap:10px;margin-bottom:16px}
  .topbar .spacer{display:none}
  .topbar .row{margin-left:0}
  .topbar .btn{min-height:44px}
  .topbar h1{font-size:var(--fs-2xl)}
  .g2,.g3{grid-template-columns:1fr}
  .form-grid{grid-template-columns:1fr}
  .alert{flex-wrap:wrap} .alert .act{margin-left:0;width:100%}
  .filters .field,.filters .grow{min-width:0;flex:1 1 100%}
  .login-wrap{grid-template-columns:1fr;padding:28px 18px;gap:26px}
  .login-brand h1{font-size:var(--fs-3xl)}
  .login-brand p{margin-bottom:16px}
  .login-points{display:none}
  .card table{min-width:0;width:100%;table-layout:fixed}
  .card table td{overflow:hidden;text-overflow:ellipsis}
  .card table th{overflow:hidden;white-space:normal;line-height:1.25;letter-spacing:.01em;font-size:var(--fs-2xs);padding-left:8px;padding-right:8px}
  /* wide data tables keep their natural width and scroll sideways inside their card */
  .tscroll table{min-width:620px;width:100%;table-layout:auto}
  .tscroll table td{overflow:visible}
  .table-card,.card:has(> table),.tscroll{overflow:auto}
  td .row{flex-wrap:wrap}
  .btn.sm{padding:7px 10px;font-size:var(--fs-xs)}
  .hide-m{display:none!important}

  /* glass bottom tab bar — FOUR entries since P26 (Home · Patients · Diary · More) */
  .mtab{display:flex;position:fixed;bottom:calc(14px + env(safe-area-inset-bottom));left:50%;width:calc(100% - 28px);max-width:600px;padding:6px;
    background:linear-gradient(180deg,rgba(255,255,255,.6),rgba(255,255,255,.36));
    -webkit-backdrop-filter:var(--glass-blur-lg);backdrop-filter:var(--glass-blur-lg);
    border:1px solid var(--glass-border);border-radius:var(--pill);z-index:var(--z-tabbar);overflow:hidden;
    box-shadow:0 12px 38px rgba(23,28,38,.22),inset 0 1px 0 rgba(255,255,255,.92),inset 0 0 0 1px rgba(255,255,255,.16);
    transform-origin:bottom center;transform:translateX(-50%) scale(1);
    transition:transform .55s cubic-bezier(.4,0,.2,1),box-shadow .55s cubic-bezier(.4,0,.2,1)}
  .mtab::before{content:"";position:absolute;inset:0 0 auto;height:50%;pointer-events:none;z-index:0;border-radius:26px 26px 40px 40px;
    background:linear-gradient(180deg,rgba(255,255,255,.5),transparent)}
  /* A tab is a link or — for More — a button, so the rule is on the CLASS and the button's own
     chrome is reset here. 46px is the floor: 9 + 21 (icon) + 3 + 13 (label) + 9, measured rather
     than hoped for, and `min-height` keeps it true if a label ever wraps. */
  .mtab .mtab-tab{flex:1;min-height:46px;display:block;text-align:center;padding:9px 2px;
    border:0;background:none;font-family:var(--font-sans);
    color:var(--ink-3);font-size:var(--fs-2xs);font-weight:700;cursor:pointer;
    position:relative;z-index:1;border-radius:18px;-webkit-tap-highlight-color:transparent;
    transition:color .22s,transform .22s var(--ease-press)}
  .mtab .mtab-tab[hidden]{display:none}
  .mtab .mtab-tab:active{transform:scale(.86)}
  .mtab .mtab-tab.active{color:var(--brand)}
  .mtab .mtab-tab:focus-visible{outline:2px solid var(--teal-500);outline-offset:-2px}
  /* P21 — OLD's `mtab-blob`: the active pill is ONE element that slides between destinations
     rather than a background that blinks off one tab and on to another. `app.js` measures the
     marked link and writes width/transform onto it; if that measurement cannot be taken (the bar
     is hidden, nothing is marked) the blob simply stays at `opacity:0` and OLD's own fallback —
     the coral label above — is still what says where you are. */
  .mtab-blob{position:absolute;top:6px;bottom:6px;left:0;width:0;opacity:0;z-index:0;pointer-events:none;border-radius:17px;
    background:linear-gradient(180deg,rgba(255,255,255,.85),rgba(255,255,255,.45));
    border:1px solid rgba(255,255,255,.7);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 -2px 4px rgba(255,255,255,.5),0 4px 12px rgba(229,96,59,.16);
    transition:transform .38s var(--ease-out),width .38s var(--ease-out),opacity .2s}
  /* Scrolling DOWN a long registry pulls the bar out of the way; scrolling back up returns it.
     It never disappears — a navigation control you cannot find is worse than one in the way. */
  .mtab.shrunk{transform:translateX(-50%) scale(.85)}
  .mtab .mtab-tab .ico{display:flex;align-items:center;justify-content:center;margin:0 auto 3px}
  .mtab .mtab-tab .ico .i{width:21px;height:21px}
  /* A sheet is up: the floating tab bar is not a thing to reach past it (P17, chart.css) — and
     since P26 neither is the mobile header. Found by opening the chart's group drawer on a
     375px phone: the drawer's own title sat UNDER the sticky wordmark bar. The cause is a
     stacking context nobody declared — `#view.view-enter>.chart` carries a filled `fadeIn`
     animation, which makes `.chart` a stacking context, which traps the drawer's `--z-sheet`
     inside it and leaves the bar's `--z-chrome` painting over the lot. Raising a z-index would
     not have fixed it (the trap is the CONTEXT, not the number), and the honest rule is the one
     the tab bar already follows: while a full-viewport sheet is open, the chrome steps aside. */
  body.drawer-open .mtab,body.drawer-open .mtop{display:none}

  /* ---- the More sheet (P26) ------------------------------------------------------------
     The house dialog, in a phone's shape. It borrows the shell's own two keyframes for the
     scrim (`scrimFade`/`scrimFadeOut`, above) and adds the one thing a bottom sheet needs that a
     centred dialog does not: it comes up from the edge it is attached to. `app.js` drives the
     same `show` → `closing` → gone sequence the confirmation dialog uses, on the same 240ms
     timer, so there is one dismissal behaviour in this app rather than two.

     `display` lives INSIDE this media query on purpose: above 880px the bar does not exist, so a
     sheet left open by a rotation or a resize is closed by the layout itself. */
  .msheet-scrim.show{display:flex}
  .msheet-scrim.closing{display:flex}
  .msheet{width:min(560px,100%);max-height:min(72vh,560px);overflow:auto;-webkit-overflow-scrolling:touch;
    background:rgba(255,255,255,.97);
    -webkit-backdrop-filter:var(--glass-blur-lg);backdrop-filter:var(--glass-blur-lg);
    border:1px solid var(--glass-border);border-bottom:0;
    border-radius:var(--r-lg) var(--r-lg) 0 0;box-shadow:var(--shadow-lg),var(--glass-hi);
    padding:8px 14px calc(16px + env(safe-area-inset-bottom))}
  .msheet-grab{display:block;width:38px;height:4px;border-radius:var(--pill);margin:2px auto 6px;
    background:var(--neutral-300)}
  .msheet-h{display:flex;align-items:center;gap:12px;padding:2px 4px 10px}
  .msheet-h h2{font-size:var(--fs-lg);font-weight:900;flex:1}
  .msheet-h .x{min-width:44px;min-height:44px}
  .msheet-list{display:flex;flex-direction:column;gap:4px}
  /* Every row is a 56px target with its label and a sentence saying what is behind it — the
     sheet has the room the bar never did, so a destination gets explained rather than abbreviated. */
  .msheet-list a{display:flex;align-items:center;gap:13px;min-height:56px;padding:10px 12px;
    border-radius:var(--r);color:var(--ink);font-weight:800;font-size:var(--fs-md);
    -webkit-tap-highlight-color:transparent;transition:background-color var(--t-state)}
  .msheet-list a[hidden]{display:none}
  .msheet-list a:active{background:var(--teal-50)}
  .msheet-list a.active{background:var(--brand-soft);color:var(--brand-ink)}
  .msheet-list a:focus-visible{outline:2px solid var(--teal-500);outline-offset:-2px}
  .msheet-list .ico{flex:none;width:38px;height:38px;border-radius:12px;display:grid;place-items:center;
    background:var(--line-2);color:var(--ink-2)}
  .msheet-list a.active .ico{background:var(--coral-100);color:var(--brand-ink)}
  .msheet-list .ico .i{width:19px;height:19px}
  .msheet-list .lbl{min-width:0}
  .msheet-list small{display:block;font-size:var(--fs-xs);font-weight:650;color:var(--ink-3);
    margin-top:1px;line-height:1.35}
  .msheet-list a.active small{color:var(--brand-ink);opacity:.8}
}
/* The sheet's own shell, outside the media query so the closed state is the same at every width
   and the panel is never a stray box on a desktop. */
.msheet-scrim{position:fixed;inset:0;z-index:var(--z-sheet);background:var(--scrim);
  -webkit-backdrop-filter:blur(12px) saturate(130%);backdrop-filter:blur(12px) saturate(130%);
  display:none;align-items:flex-end;justify-content:center}
.msheet-scrim.show{animation:scrimFade var(--t-scrim) ease}
.msheet-scrim.closing{pointer-events:none;animation:scrimFadeOut .22s ease forwards}
.msheet-scrim.show .msheet{animation:sheetIn .3s var(--ease-out)}
.msheet-scrim.closing .msheet{animation:sheetOut .2s cubic-bezier(.4,0,.6,1) forwards}
@keyframes sheetIn{from{transform:translateY(100%)}to{transform:none}}
@keyframes sheetOut{from{transform:none}to{transform:translateY(100%)}}
@media (max-width:560px){
  main{padding-left:10px;padding-right:10px}
  td,th{padding:11px 9px;font-size:var(--fs-sm)}
  .seg{width:100%}
  .seg button{flex:1;padding:9px 4px;font-size:var(--fs-sm)}
  .modal .mh{padding:15px 16px;flex-wrap:wrap;gap:8px}
  .modal .mb{padding:16px}
  .modal .mf{padding:13px 16px}
  .login-card{padding:20px 18px 18px}
}
/* ---- P26: 44px, everywhere a finger lands ----------------------------------------------
   This block already existed and already said the right thing about `.btn` — but `.btn.sm` was
   40px and `.btn.xs` 34px, and those two are what every ROW ACTION in this app wears: "Start
   encounter" on the diary, "Health Record" on the registry, "Remove" on the formulary, "Set
   password" on the accounts list. Measured on a 375px phone they came out at 34px, which is the
   size at which a thumb starts hitting the row above.

   44px is Apple's minimum and Android's 48dp rounded down to it; the plan (V3-PLAN §26) states it
   as the standard, so the small sizes keep their PADDING and their type — a dense list still
   reads as a dense list — and gain a floor. `pointer:coarse` means none of this reaches a mouse:
   the desktop tables stay exactly as tight as they were. */
@media (pointer:coarse){
  .btn{min-height:44px;padding:11px 18px}
  .btn.sm{min-height:44px;padding:8px 14px}
  .btn.xs{min-height:44px}
  /* The controls that are not buttons but are pressed like them. Each stylesheet raises the
     controls it OWNS — app.css does the typeahead and the group rail, chart.css the tab pills —
     because a rule here for a class defined in a later file would lose the cascade silently,
     which is exactly how the first attempt at this left the combobox's clear cross at 28px. */
  .seg button,.chip,.hintw{min-height:44px}
  /* THE LAST STEP OF THE DISABLED-HINT FIX (D-120). The wrapper only helps if the tap can
     actually reach it — and a disabled control fills the wrapper, so on a touch screen the
     finger lands on the dead button and the event is never generated at all. Making the disabled
     child transparent to POINTER EVENTS hands the tap to the wrapper behind it. It is scoped to
     a coarse pointer on purpose: `pointer-events:none` also kills hover, and hover is how a
     MOUSE reads the `title` that is still on the control. Touch has no hover to lose. */
  .hintw>[disabled]{pointer-events:none}
  .x{min-width:44px;min-height:44px}
  td{padding:15px 14px}
  input,select,textarea{min-height:44px}
  input[type=checkbox],input[type=radio]{width:22px;height:22px}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-delay:0ms!important;
    animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}

/* ============================ PRINT (base) ============================ */
/* The Annex-H slips own their own geometry, their own @page and their own colour policy in
   slips.css — which scopes `print-color-adjust:exact` to the PhilHealth masthead alone, so that
   nothing else in a slip prints a background. This block therefore does one thing: hide the app
   chrome so a slip prints by itself. It is deliberately the same set of rules the verified build
   shipped, extended only to the chrome the design passes added: `.mtop` and `.mtab`, and P17's
   `.drawer-scrim` — a full-viewport translucent plate that would otherwise print as a grey wash
   over whatever is under it. Nothing here may reach inside the printed area (D-091). */
@media print{
  .side,.mtop,.mtab,.msheet-scrim,.toast,.scrim,.drawer-scrim,.login-overlay,.btn{display:none !important}
  .app{display:block}
  main{padding:0}
  .page{max-width:none}
  .card{box-shadow:none;border:1px solid #999;break-inside:avoid}
  /* P21, rewritten as ONE rule (D-116). A view that is mid-entrance when Cmd+P is pressed would
     otherwise be captured at whatever opacity the animation had reached — a slip printed at 40%
     grey. This used to be four enumerations in three stylesheets, each listing the selectors
     that file happened to animate; every new entrance had to remember to join the list, and one
     that did not would print half-faded with nothing to say it had gone wrong.

     The universal selector is the honest shape of the promise "nothing in this app animates on
     paper", and it is SAFE for the same reason the enumerations were: cancelling the animation
     is the whole fix and the only thing done here. Every entrance in this app is `both`-filled,
     so with the animation gone each element falls back to its own base style — opacity 1, no
     transform — without this block asserting either, and therefore without it being able to
     overrule a slip's own geometry. `slips.css` declares no animation and no transition at all
     (`tests/ui-motion.test.mjs` holds it to that), so this rule cannot change a printed slip;
     it only guarantees that nothing else can either. */
  *,*::before,*::after{animation:none !important}
}
