/* ══════════════════════════════════════════════════════════════════════════
   Hospital AI Knowledge Platform — design system
   Enterprise SaaS: white ground, soft blue/purple gradients, glass cards,
   24px radii, generous spacing. Light and dark.
   ══════════════════════════════════════════════════════════════════════════ */

/* Self-hosted so an on-prem hospital box with no internet still renders the
   intended typeface. 5 weights, latin subset, ~121 KB total.
   swap = text paints immediately in the fallback, then upgrades. */
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/inter-latin-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/inter-latin-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/inter-latin-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/inter-latin-700.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap;
  src: url('fonts/inter-latin-800.woff2') format('woff2');
}

:root {
  /* Brand */
  --primary: #2563eb;
  --primary-600: #1d4ed8;
  --primary-050: #eff6ff;
  --secondary: #4f46e5;   /* indigo */
  --accent: #06b6d4;      /* cyan */
  --accent-purple: #7c3aed;
  --success: #22c55e;
  --warning: #f59e0b;
  --danger: #ef4444;

  /* Surfaces */
  --bg: #f8fafc;
  --bg-elevated: #ffffff;
  --card: #ffffff;
  --card-glass: rgba(255, 255, 255, 0.72);
  --sidebar: rgba(255, 255, 255, 0.86);
  --border: #e2e8f0;
  --border-strong: #cbd5e1;

  /* Text
     --text-faint is one step darker than the palette's #94A3B8. At #94A3B8 the
     contrast on white is 2.56:1, which fails WCAG AA (4.5:1) — and this token
     carries real content: field labels, timestamps, page counts, upload hints.
     #64748b measures 4.76:1 and is visually near-identical. #94A3B8 is kept as
     --muted-decorative for non-text use (rules, inactive dots, placeholder bars),
     where no contrast minimum applies. */
  --text: #0f172a;
  --text-muted: #475569;
  --text-faint: #64748b;
  --muted-decorative: #94a3b8;
  --on-primary: #ffffff;

  /* Status colours come in two forms: the palette hex for fills (where white sits
     on top and passes) and a darker variant for coloured text/icons on a light
     surface, since #22C55E is 2.28:1 and #EF4444 is 3.76:1 as foreground. */
  --success-text: #15803d;
  --danger-text: #dc2626;

  /* Radii */
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-pill: 999px;

  /* Shadows — layered, low-alpha, blue-tinted */
  --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05);
  --shadow-sm: 0 2px 8px rgba(15, 23, 42, 0.05), 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 8px 24px rgba(15, 23, 42, 0.07), 0 2px 6px rgba(15, 23, 42, 0.04);
  --shadow-lg: 0 18px 48px rgba(15, 23, 42, 0.10), 0 4px 12px rgba(15, 23, 42, 0.05);
  --shadow-xl: 0 32px 72px rgba(15, 23, 42, 0.13), 0 8px 20px rgba(15, 23, 42, 0.06);
  --shadow-primary: 0 12px 28px rgba(37, 99, 235, 0.28);

  /* Layout */
  --sidebar-w: 268px;
  --navbar-h: 72px;
  --maxw: 1320px;

  /* The four named gradients of the design system */
  --grad-primary: linear-gradient(135deg, #2563eb, #4f46e5);
  --grad-hero:    linear-gradient(135deg, #2563eb, #06b6d4);
  --grad-purple:  linear-gradient(135deg, #7c3aed, #2563eb);
  --grad-card-hover: linear-gradient(135deg, rgba(37, 99, 235, .08), rgba(124, 58, 237, .08));

  /* --grad-brand stays as the alias used across components; it is the primary
     gradient with a cyan tail so large fills (orb, icon tiles) do not read flat. */
  --grad-brand: linear-gradient(135deg, #2563eb 0%, #4f46e5 55%, #06b6d4 100%);
  --grad-text: linear-gradient(100deg, #2563eb 0%, #4f46e5 52%, #06b6d4 100%);
  --grad-soft: linear-gradient(180deg, rgba(37, 99, 235, 0.06), rgba(79, 70, 229, 0.02));

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* Design-system motion: 300ms for interaction feedback, 600ms for entrances */
  --t-fast: 180ms;
  --t-base: 300ms;
  --t-slow: 600ms;
}

:root[data-theme='dark'] {
  --bg: #070b16;
  --bg-elevated: #0d1424;
  --card: #101a2e;
  --card-glass: rgba(18, 27, 46, 0.72);
  --sidebar: rgba(12, 19, 34, 0.9);
  --border: #1e2b45;
  --border-strong: #2b3c5c;

  --text: #eef3fb;
  --text-muted: #9dabc4;
  --text-faint: #6d7c96;
  --primary-050: rgba(37, 99, 235, 0.14);

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 18px 48px rgba(0, 0, 0, 0.5);
  --shadow-xl: 0 32px 72px rgba(0, 0, 0, 0.6);
  --grad-soft: linear-gradient(180deg, rgba(37, 99, 235, 0.14), rgba(79, 70, 229, 0.03));
  /* Dark surfaces swallow an 8% tint, so the card-hover wash is roughly doubled
     to stay perceptible at the same apparent strength as in light mode. */
  --grad-card-hover: linear-gradient(135deg, rgba(59, 130, 246, .16), rgba(139, 92, 246, .14));
  /* On dark surfaces the light-mode text variants invert: the bright palette hues
     already exceed 4.5:1 against #101a2e, while the darkened ones would not. */
  --success-text: #4ade80;
  --danger-text: #f87171;
  --muted-decorative: #55647f;
}

* { box-sizing: border-box; }

/* The `hidden` attribute must actually hide. The browser implements it as
   `display: none` in its own stylesheet, which any author rule setting `display`
   silently beats — so `<button class="nav-item" hidden>` stayed visible because
   `.nav-item` sets `display: flex`, and the same was true of `.nav-item__badge`
   (`display: grid`) on the patient counter. One line, so the attribute means what it
   says everywhere and no future `hidden` fails the same quiet way. */
[hidden] { display: none !important; }

/* ── The app shell scrolls its content, not the page ─────────────────────────
   The navbar and sidebar are fixed, so before this the *document* was the scroller
   underneath them, and every navigation had to fight it: switching to a shorter view
   left the window scrolled past the end of the new one, and because `scroll-behavior:
   smooth` lived here, showView's reset-to-top was animated — so a click on Analytics
   while scrolled down started a smooth scroll through a document whose height had
   just collapsed, and the browser clamped or cancelled it mid-flight. The view had
   changed; the reader was still looking at empty space below it.

   With the page itself unscrollable and .main owning `overflow-y: auto`, a view switch
   is one instant assignment to one container's scrollTop, and the sidebar cannot be
   moved by content scrolling at all. */
html, body { height: 100%; overflow: hidden; }

body {
  margin: 0;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
               'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  line-height: 1.7;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Inter stylistic sets: cv02/03/04 give the single-storey shapes and ss01 the
     disambiguated 1/l/I that keep UHIDs and dosages readable at small sizes. */
  font-feature-settings: 'cv02', 'cv03', 'cv04', 'ss01';
  font-variant-numeric: lining-nums;
}

/* Display type is tighter than body: at 42px+ the default tracking reads loose. */
h1, h2, h3, h4 { margin: 0; line-height: 1.12; letter-spacing: -0.03em; font-weight: 700; }
h3, h4 { letter-spacing: -0.02em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, select, textarea { font: inherit; color: inherit; }
img { max-width: 100%; display: block; }
ul { margin: 0; padding: 0; list-style: none; }

::selection { background: rgba(37, 99, 235, 0.2); }

/* Scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 99px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }

.gradient-text {
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Navbar ─────────────────────────────────────────────────────────────── */

/* Sticky glass header. The hairline is a gradient rather than a flat border so it
   fades toward the edges instead of stopping dead against the viewport. */
.navbar {
  position: fixed; inset: 0 0 auto 0; height: var(--navbar-h); z-index: 100;
  display: flex; align-items: center; gap: 20px; padding: 0 28px;
  background: var(--card-glass);
  backdrop-filter: saturate(180%) blur(24px);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  border-bottom: 1px solid transparent;
  border-image: linear-gradient(90deg, transparent, var(--border) 18%, var(--border) 82%, transparent) 1;
  transition: box-shadow var(--t-base) var(--ease), background var(--t-base);
  animation: navbar-in var(--t-slow) var(--ease) both;
}
/* Elevation appears only once the page has scrolled, so the header sits flat on
   the hero and lifts off the content below it. */
.navbar.is-scrolled {
  box-shadow: 0 6px 24px rgba(15, 23, 42, 0.07);
  background: rgba(255, 255, 255, 0.86);
}
:root[data-theme='dark'] .navbar.is-scrolled { background: rgba(12, 19, 34, 0.88); }

@keyframes navbar-in { from { opacity: 0; transform: translateY(-100%); } }

/* min-height matches the icon buttons opposite it, so the whole navbar row shares
   one hit-target height and the logo is not a 30px sliver to tap. */
.brand { display: flex; align-items: center; gap: 13px; min-width: 0; min-height: 40px; }

/* Alphanso wordmark. Height-constrained with width:auto so the 663x150 source
   keeps its ratio; the width/height attributes on the tag reserve the right box
   before the image decodes, so the header does not reflow on load. */
.brand__logo {
  height: 30px; width: auto; flex: 0 0 auto; display: block;
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.brand:hover .brand__logo { transform: translateY(-1px); opacity: .88; }

/* One polarity per theme — never both. */
.brand__logo--dark { display: none; }
:root[data-theme='dark'] .brand__logo--light { display: none; }
:root[data-theme='dark'] .brand__logo--dark { display: block; }

/* Hospital name, centred on the header itself — not in the space left over beside
   the brand. The brand and the action cluster are different widths, so a flex
   `margin: 0 auto` would have parked the title off-centre by half that
   difference. Absolute positioning puts it on the header's true midpoint whatever
   sits either side, and pointer-events:none keeps its invisible box from
   swallowing clicks aimed at the controls behind it. */
.navbar__title {
  position: absolute; left: 50%; transform: translateX(-50%);
  /* Because it is centred, the title grows symmetrically — so the cap has to
     reserve room on BOTH sides, not just cap the text, or a hospital with a long
     name runs under the logo. 240px per side: the brand and action clusters
     measure 161px and 186px, and below 1024px the hamburger joins the left one,
     which is what made a narrower reservation collide at ~820px. */
  max-width: min(460px, calc(100vw - 480px));
  font-size: 15px; font-weight: 700; letter-spacing: -0.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-align: center; pointer-events: none;
}

.navbar__actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* ── Views ──────────────────────────────────────────────────────────────── */

/* Only one view is in the document flow at a time. The fade-and-rise is what
   makes a view switch read as a page transition rather than a flicker. */
.view { display: none; }
.view.is-active { display: block; animation: view-in .32s var(--ease) both; }
@keyframes view-in { from { opacity: 0; transform: translateY(10px); } }

/* ── Patients list page ─────────────────────────────────────────────────────
   Deliberately plain: one white card holding one table. No toolbar, no badges,
   no avatars. Rounded via overflow:hidden on the wrapper so the first and last
   rows clip to the corner radius without per-cell radii. */

.ptable-wrap {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  /* hidden clips the first and last rows to the corner radius; overflow-x then
     reopens the horizontal axis. Both are needed: with plain `hidden` any table
     wider than its wrapper was silently cut off with no way to reach the rest —
     between 900px and 1024px that severed the Action column, so the View button
     was on screen but unreachable. Scrolling is always available now, and the
     axis only actually scrolls when the columns genuinely do not fit. */
  overflow: hidden;
  overflow-x: auto;
  /* Keeps the scroll gesture on this box instead of chaining to the page. */
  overscroll-behavior-x: contain;
}

.ptable { width: 100%; border-collapse: collapse; }

.ptable th {
  padding: 15px 20px; text-align: left; white-space: nowrap;
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.ptable td {
  padding: 16px 20px; text-align: left;
  font-size: 14px; color: var(--text-muted);
  border-bottom: 1px solid var(--border);
  /* Rows are read across, so every cell in one starts on the same baseline even
     when a neighbour wraps to four lines. Centring made a one-line UHID float in
     the middle of a wrapped patient name. */
  vertical-align: top;
  /* OCR'd values include unbroken tokens (accession numbers, hyphen-free IDs)
     with no space to wrap at; without this they push the column wider than the
     screen instead of breaking. */
  overflow-wrap: anywhere;
}
.ptable tbody tr:last-child td { border-bottom: 0; }

.ptable tbody tr {
  cursor: pointer;
  transition: background var(--t-base);
}
.ptable tbody tr:hover { background: var(--primary-050); }
.ptable tbody tr:focus-visible {
  outline: none; background: var(--primary-050);
  box-shadow: inset 0 0 0 2px rgba(37, 99, 235, .38);
}

.ptable__name { font-weight: 600; color: var(--text); }
/* No nowrap: a provisional UHID can run to 30+ characters, and forcing it onto
   one line was what pushed the table past its wrapper and cut the Action column
   off. It breaks only when it has to. */
.ptable__uhid { font-weight: 600; color: var(--primary); overflow-wrap: anywhere; }
.ptable__num { text-align: right; font-variant-numeric: tabular-nums; }
.ptable__act { text-align: right; white-space: nowrap; }
/* Dates never wrap mid-value, and tabular figures keep a column of them aligned
   even in a proportional font. */
.ptable__date { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* The Medicines table has no row action — nothing to open, since medicines are
   extracted values rather than documents. Without this it would still offer a
   pointer cursor and a hover highlight, promising a click that does nothing. */
.ptable--plain tbody tr { cursor: default; }
.ptable--plain tbody tr:hover { background: transparent; }

/* Below the scroll breakpoint a table needs a floor width, otherwise the columns
   compress into unreadable slivers instead of scrolling. The floor is per table
   rather than global: the patients list carries seven columns and genuinely needs
   one, while the record tabs carry four or five and fit a phone once their cells
   are allowed to wrap — giving those the same 720px floor made them scroll for
   no reason. */
@media (max-width: 900px) {
  .ptable { min-width: 640px; }
  .ws-panel .ptable { min-width: 0; }
  /* Tighter gutters buy back ~90px across a five-column row, which is the
     difference between fitting a 390px screen and not. */
  .ptable th, .ptable td { padding-left: 14px; padding-right: 14px; }
  .ws-panel .ptable th, .ws-panel .ptable td { padding: 13px 12px; }
}

/* ── Patient workspace ──────────────────────────────────────────────────── */

/* Explicit grid rather than a wrapping flex row. As flex, the badge row carried
   margin-left:auto and justify-content:flex-end, so once it wrapped it sat
   right-aligned on its own line while the name above it was left-aligned — a
   ragged edge that got worse the more badges a patient had, and on a phone left
   each badge stranded against the right margin. The areas below pin identity to
   the left, actions to the right, and badges to a full-width row that starts on
   the same left edge as the name. */
.ws__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "avatar identity actions"
    "badges badges   badges";
  align-items: center;
  column-gap: 16px; row-gap: 14px;
  padding: 24px; margin-bottom: 18px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
}
.ws__avatar {
  grid-area: avatar;
  width: 54px; height: 54px; border-radius: 16px; display: grid; place-items: center;
  background: var(--grad-primary); color: #fff; font-size: 18px; font-weight: 700;
  box-shadow: var(--shadow-primary);
}
.ws__identity { grid-area: identity; min-width: 0; }
.ws__name { font-size: 21px; }
.ws__uhid { font-size: 12.5px; font-weight: 700; color: var(--primary); letter-spacing: .05em; overflow-wrap: anywhere; }
.ws__badges { grid-area: badges; margin-left: 0; justify-content: flex-start; }

/* Export and Delete act on the whole record rather than on one document, so they
   belong to the header and not to any of the four tabs. */
.ws__actions { grid-area: actions; display: flex; gap: 8px; }

/* Tab bar scrolls sideways rather than wrapping: on a narrow screen the tabs
   would otherwise reflow into two rows and shift the panels down. */
.ws__tabs {
  display: flex; gap: 6px; margin-bottom: 20px; padding-bottom: 4px;
  overflow-x: auto; scrollbar-width: none;
}
.ws__tabs::-webkit-scrollbar { display: none; }

.ws-tab {
  display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto;
  height: 40px; padding: 0 16px; border-radius: var(--r-pill);
  font-size: 13.5px; font-weight: 600; color: var(--text-muted);
  border: 1px solid var(--border); background: var(--card);
  transition: color var(--t-base), background var(--t-base), border-color var(--t-base),
              box-shadow var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.ws-tab svg { width: 16px; height: 16px; }
.ws-tab:hover { color: var(--primary); border-color: rgba(37, 99, 235, .35); transform: translateY(-1px); }
.ws-tab.is-active {
  color: #fff; background: var(--grad-primary); border-color: transparent;
  box-shadow: var(--shadow-primary);
}

/* Only the active panel is in flow. The fade-in is what makes switching read as
   a transition rather than a jump. */
.ws-panel { display: none; }
.ws-panel.is-active { display: block; animation: ws-fade .28s var(--ease) both; }
@keyframes ws-fade { from { opacity: 0; transform: translateY(6px); } }

.panel-stack { display: grid; gap: 18px; }

/* Prescriptions */
.rx-sub { font-size: 12.5px; color: var(--text-faint); margin: -4px 0 14px; }
.rx-lines { display: grid; gap: 8px; }
.rx-line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: var(--r-md);
  background: var(--bg); border: 1px solid var(--border);
}
.rx-line__name { font-size: 14px; font-weight: 600; }
.rx-line__meta { display: flex; gap: 10px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); }

/* AI extraction checklist */
.xtract { display: grid; gap: 2px; }
.xtract__row {
  display: grid; grid-template-columns: 22px minmax(0, 150px) minmax(0, 1fr);
  gap: 12px; align-items: center; padding: 9px 2px;
  border-bottom: 1px solid var(--border);
}
.xtract__row:last-child { border-bottom: 0; }
.xtract__icon { width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center; }
.xtract__icon svg { width: 13px; height: 13px; }
.xtract__icon--ok { background: rgba(34, 197, 94, .12); color: var(--success-text); }
.xtract__icon--miss { background: rgba(148, 163, 184, .16); color: var(--text-faint); }
.xtract__label { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.xtract__value { font-size: 13.5px; min-width: 0; overflow-wrap: anywhere; }

/* ── Footer ─────────────────────────────────────────────────────────────── */

.site-footer {
  padding: 30px 40px 34px; text-align: center;
  border-top: 1px solid var(--border);
}
.site-footer p { font-size: 13px; color: var(--text-faint); }

.icon-btn {
  width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center;
  color: var(--text-muted); border: 1px solid transparent; position: relative;
  transition: background .18s, color .18s, border-color .18s, transform .18s;
}
.icon-btn svg { width: 19px; height: 19px; }
.icon-btn:hover { background: var(--primary-050); color: var(--primary); border-color: var(--border); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn__dot {
  position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--danger); border: 2px solid var(--card);
}

.ai-status {
  display: flex; align-items: center; gap: 7px; height: 34px; padding: 0 12px;
  border-radius: var(--r-pill); border: 1px solid var(--border);
  background: var(--bg-elevated); font-size: 12px; font-weight: 600; color: var(--text-muted);
  white-space: nowrap;
}
.ai-status__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); flex: 0 0 auto; }
.ai-status--online .ai-status__dot { background: var(--success); box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18); animation: pulse-dot 2.4s var(--ease) infinite; }
.ai-status--offline .ai-status__dot { background: var(--danger); box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.18); }

@keyframes pulse-dot {
  0%, 100% { box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18); }
  50% { box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.05); }
}

.avatar {
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  background: var(--grad-brand); color: #fff; font-weight: 700; font-size: 13px;
  box-shadow: var(--shadow-sm); cursor: pointer; flex: 0 0 auto;
}

.nav-toggle { display: none; }

/* ── Sidebar ────────────────────────────────────────────────────────────── */

.sidebar {
  position: fixed; top: var(--navbar-h); bottom: 0; left: 0; width: var(--sidebar-w);
  z-index: 90; padding: 22px 16px; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
  background: var(--sidebar);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border-right: 1px solid var(--border);
  transition: transform .3s var(--ease);
}

/* Fills the sidebar's height so the footer's margin-top:auto has room to push
   against; min-height:0 keeps the nav scrollable if the list ever outgrows it. */
.sidebar > nav { display: flex; flex-direction: column; flex: 1; min-height: 0; }

.sidebar__label {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-faint); padding: 0 12px; margin: 22px 0 8px;
}
.sidebar__label:first-child { margin-top: 0; }

.nav-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px; margin-bottom: 2px; border-radius: var(--r-sm);
  font-size: 14px; font-weight: 500; color: var(--text-muted); text-align: left;
  position: relative; transition: background .16s, color .16s;
}
.nav-item svg { width: 18px; height: 18px; flex: 0 0 auto; }
.nav-item:hover { background: var(--primary-050); color: var(--text); }
.nav-item.is-active { background: var(--primary-050); color: var(--primary); font-weight: 600; }
/* The indicator bar exists on every item and is revealed on the active one, so the
   handoff animates instead of jumping: the outgoing bar collapses as the incoming one
   grows. A single shared bar sliding between items would need an extra element in the
   sidebar, which is a change to the markup rather than a fix to the indicator. */
.nav-item::before {
  content: ''; position: absolute; left: -16px; top: 50%;
  width: 3px; height: 22px; border-radius: 0 3px 3px 0; background: var(--primary);
  transform: translateY(-50%) scaleY(0); opacity: 0;
  transition: transform .22s var(--ease), opacity .18s var(--ease);
}
.nav-item.is-active::before {
  transform: translateY(-50%) scaleY(1); opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .nav-item::before { transition: none; }
}

/* ── Which item is lit, decided by the view rather than by a class ────────────
   showView writes the current view onto <html data-view="…">, and these rules light
   the item that owns it. The .is-active class still drives the home view, where the
   answer depends on scroll position and only JS can know it — but for a view that
   owns one fixed item, a declarative rule cannot be left stale by a listener firing
   in the wrong order. Only the record view needs it now — Analytics is a section of
   the home view, so the scroll spy lights it the same way it lights Patients. */
html[data-view="record"] .sidebar .nav-item[data-scroll="#patients"] {
  background: var(--primary-050); color: var(--primary); font-weight: 600;
}
/* Reveals the shared bar defined on .nav-item::before rather than drawing its own:
   redefining transform here would drop the scaleY the animation rides on. */
html[data-view="record"] .sidebar .nav-item[data-scroll="#patients"]::before {
  transform: translateY(-50%) scaleY(1); opacity: 1;
}
/* And the home items stop looking selected while a non-home view is open, even if a
   stale .is-active class is still sitting on one of them. */
html[data-view="record"] .sidebar .nav-item[data-view="home"].is-active:not([data-scroll="#patients"]) {
  background: transparent; color: var(--text-muted); font-weight: 500;
}
html[data-view="record"] .sidebar .nav-item[data-view="home"].is-active:not([data-scroll="#patients"])::before {
  transform: translateY(-50%) scaleY(0); opacity: 0;
}
/* The four analytics tables, nested under the Analytics item they belong to.
   Indented and slightly smaller rather than iconed like their parent, so the list
   reads as "Analytics, and its four tables" rather than five equal destinations. */
.nav-item--sub {
  padding-left: 34px; font-size: 13px; font-weight: 500;
}

.nav-item__badge {
  margin-left: auto; min-width: 22px; height: 20px; padding: 0 6px; border-radius: var(--r-pill);
  background: var(--primary); color: #fff; font-size: 11px; font-weight: 700;
  display: grid; place-items: center;
}
.nav-item--danger:hover { background: rgba(239, 68, 68, 0.08); color: var(--danger); }

/* auto margin pins this to the bottom of the sidebar regardless of how many nav
   items sit above it. */
.sidebar__footer { margin-top: auto; padding-top: 18px; border-top: 1px solid var(--border); }

/* Clear All Data sits in the footer, visually separated from navigation so it is
   never mistaken for a destination. Full width to match the nav items above. */
.sidebar__footer .nav-item { width: 100%; justify-content: flex-start; }
.nav-item--danger { color: var(--danger-text); }
.nav-item--danger:disabled { opacity: .6; cursor: not-allowed; }
.nav-item--danger .spinner { margin-right: 2px; }

/* ── Shell ──────────────────────────────────────────────────────────────── */

/* The content scroll container. Fixed to the gap the navbar and sidebar leave, so it
   needs neither the margin-left nor the padding-top it used to carry to clear them —
   the box now starts exactly where the content did before, and the result is
   pixel-identical apart from the scrollbar sitting inside the content area.

   scroll-padding-top replaces the value that was on <html>: an anchored jump lands
   16px below the top of the container rather than 16px below the navbar, because the
   container already begins below the navbar. Counting it twice pushed every anchored
   section a navbar-height too far down. */
.main {
  position: fixed; top: var(--navbar-h); right: 0; bottom: 0; left: var(--sidebar-w);
  min-width: 0;
  overflow-y: auto; overflow-x: hidden;
  scroll-behavior: smooth; scroll-padding-top: 16px;
  /* Reaching the end of the content must not hand the gesture to the page behind it
     (rubber-banding on a trackpad, pull-to-refresh on a phone). */
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
}

.section { padding: 92px 40px; position: relative; }
.section__inner { max-width: var(--maxw); margin: 0 auto; }
.section--tint { background: var(--grad-soft); }

.section-head { max-width: 720px; margin-bottom: 44px; }
.section-head--center { margin-left: auto; margin-right: auto; text-align: center; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 16px;
  padding: 6px 14px; border-radius: var(--r-pill);
  background: var(--primary-050); border: 1px solid rgba(37, 99, 235, 0.16);
  font-size: 12px; font-weight: 600; color: var(--primary); letter-spacing: -0.005em;
}
.eyebrow svg { width: 14px; height: 14px; }

.section-title { font-size: clamp(30px, 3.6vw, 42px); margin-bottom: 16px; }
.section-sub { font-size: 17px; color: var(--text-muted); line-height: 1.7; }

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  position: relative; isolation: isolate; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  height: 48px; padding: 0 24px; border-radius: var(--r-pill);
  font-size: 15px; font-weight: 600; letter-spacing: -0.006em; white-space: nowrap;
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease),
              background var(--t-base), border-color var(--t-base), opacity var(--t-fast), color var(--t-base);
}
.btn svg { width: 17px; height: 17px; flex: 0 0 auto; }
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.28); }

.btn--primary { background: var(--grad-primary); color: #fff; box-shadow: var(--shadow-primary); }
/* Hover brightens via an overlay rather than a second gradient, so the lift and
   the glow animate together instead of the fill snapping. */
.btn--primary::after {
  content: ''; position: absolute; inset: 0; z-index: -1; opacity: 0;
  background: linear-gradient(135deg, #3b82f6, #6366f1);
  transition: opacity var(--t-base) var(--ease);
}
.btn--primary:hover:not(:disabled) {
  transform: translateY(-3px);
  box-shadow: 0 20px 40px rgba(37, 99, 235, 0.36), 0 0 0 1px rgba(37, 99, 235, 0.1);
}
.btn--primary:hover:not(:disabled)::after { opacity: 1; }

.btn--secondary { background: var(--bg-elevated); color: var(--primary); border: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.btn--secondary:hover:not(:disabled) {
  transform: translateY(-3px); box-shadow: var(--shadow-md);
  border-color: rgba(37, 99, 235, 0.4); background: var(--primary-050);
}

.btn--ghost { color: var(--text-muted); }
.btn--ghost:hover:not(:disabled) { background: var(--primary-050); color: var(--primary); }

.btn--danger { background: rgba(239, 68, 68, 0.1); color: var(--danger-text); border: 1px solid rgba(239, 68, 68, 0.22); }
.btn--danger:hover:not(:disabled) { background: var(--danger); color: #fff; }

.btn--sm { height: 36px; padding: 0 15px; font-size: 13px; border-radius: var(--r-sm); }
.btn--sm svg { width: 15px; height: 15px; }
.btn--block { width: 100%; }

/* ── Cards ──────────────────────────────────────────────────────────────── */

.card {
  position: relative; isolation: isolate;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease),
              border-color var(--t-base);
}
/* The card-hover gradient wash lives on a pseudo-element so it can cross-fade
   under the content without repainting the card's own background. */
.card::before {
  content: ''; position: absolute; inset: 0; z-index: -1; opacity: 0;
  border-radius: inherit; background: var(--grad-card-hover);
  transition: opacity var(--t-base) var(--ease);
}
.card--pad { padding: 28px; }
.card--hover:hover {
  transform: translateY(-6px) scale(1.012);
  box-shadow: var(--shadow-lg); border-color: rgba(37, 99, 235, 0.28);
}
.card--hover:hover::before { opacity: 1; }

.glass {
  background: var(--card-glass);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
:root[data-theme='dark'] .glass { border-color: rgba(255, 255, 255, 0.08); }

/* ── Badges & pills ─────────────────────────────────────────────────────── */

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 12px; border-radius: var(--r-pill);
  background: var(--bg-elevated); border: 1px solid var(--border);
  font-size: 12px; font-weight: 600; color: var(--text-muted); white-space: nowrap;
}
.badge svg { width: 13px; height: 13px; }
.badge--primary { background: var(--primary-050); border-color: rgba(37, 99, 235, 0.2); color: var(--primary); }
.badge--success { background: rgba(34, 197, 94, 0.1); border-color: rgba(34, 197, 94, 0.24); color: #15803d; }
.badge--warning { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.26); color: #b45309; }
.badge--danger  { background: rgba(239, 68, 68, 0.1); border-color: rgba(239, 68, 68, 0.24); color: #b91c1c; }
.badge--accent  { background: rgba(14, 165, 233, 0.1); border-color: rgba(14, 165, 233, 0.24); color: #0369a1; }
:root[data-theme='dark'] .badge--success { color: #4ade80; }
:root[data-theme='dark'] .badge--warning { color: #fbbf24; }
:root[data-theme='dark'] .badge--danger  { color: #f87171; }
:root[data-theme='dark'] .badge--accent  { color: #38bdf8; }

.badge-row { display: flex; flex-wrap: wrap; gap: 8px; }

.chip {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px;
  border-radius: var(--r-pill); background: var(--bg-elevated); border: 1px solid var(--border);
  font-size: 13px; font-weight: 500; color: var(--text-muted);
  transition: all .16s;
}
.chip:hover { border-color: var(--primary); color: var(--primary); background: var(--primary-050); }
.chip svg { width: 13px; height: 13px; color: var(--success-text); }

/* ══ SECTION 1 — HERO ═══════════════════════════════════════════════════ */

.hero {
  position: relative; overflow: hidden;
  padding: 84px 40px 100px;
}

.hero__grid {
  position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(to right, rgba(37, 99, 235, 0.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(37, 99, 235, 0.055) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(ellipse 90% 68% at 50% 0%, #000 38%, transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse 90% 68% at 50% 0%, #000 38%, transparent 100%);
}

.hero__glow {
  position: absolute; border-radius: 50%; pointer-events: none; z-index: 0;
  filter: blur(76px); opacity: .5;
}
/* Blue → indigo → cyan, matching the primary/hero/purple gradient trio. */
.hero__glow--1 { width: 520px; height: 520px; top: -180px; left: -120px; background: radial-gradient(circle, rgba(37, 99, 235, 0.40), transparent 66%); animation: drift 22s var(--ease) infinite; }
.hero__glow--2 { width: 440px; height: 440px; top: 90px; right: -110px; background: radial-gradient(circle, rgba(124, 58, 237, 0.30), transparent 66%); animation: drift 26s var(--ease) infinite reverse; }
.hero__glow--3 { width: 360px; height: 360px; bottom: -140px; left: 42%; background: radial-gradient(circle, rgba(6, 182, 212, 0.28), transparent 66%); animation: drift 30s var(--ease) infinite; }

@keyframes drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(34px, -26px) scale(1.07); }
  66% { transform: translate(-24px, 22px) scale(0.95); }
}

/* ── Floating particles ─────────────────────────────────────────────────────
   Purely decorative motes that drift up through the hero. Kept in CSS (no canvas,
   no rAF loop) so they cost nothing on the main thread — each is one composited
   element animating transform and opacity only. aria-hidden in the markup. */
.particles { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.particles i {
  position: absolute; bottom: -12px;
  width: var(--s, 6px); height: var(--s, 6px); border-radius: 50%;
  background: var(--c, rgba(37, 99, 235, .5));
  animation: particle-rise var(--d, 18s) linear var(--delay, 0s) infinite;
  opacity: 0;
}
@keyframes particle-rise {
  0%   { transform: translateY(0) translateX(0) scale(.6); opacity: 0; }
  12%  { opacity: .75; }
  70%  { opacity: .5; }
  100% { transform: translateY(-88vh) translateX(var(--x, 30px)) scale(1.1); opacity: 0; }
}

.hero__inner {
  position: relative; z-index: 1; max-width: var(--maxw); margin: 0 auto;
  display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, 0.82fr);
  gap: 52px; align-items: center;
}

/* Upload block inside the hero. z-index keeps it above the hero glows/grid, which
   are absolutely positioned siblings and would otherwise swallow pointer events
   on the dropzone. */
.hero__upload {
  position: relative; z-index: 1; max-width: var(--maxw);
  margin: 88px auto 0; padding-top: 56px;
  border-top: 1px solid var(--border);
}

/* Tops out at the 72px display size, but the coefficient is tuned to the *column*
   rather than the viewport: the sidebar takes 268px and the visual takes the rest,
   so 5vw hit 72px at 1440px where the column is only ~600px wide and the title
   broke to six lines. 4.1vw reaches 72px near 1760px, where there is room. */
.hero__title {
  font-size: clamp(38px, 4.1vw, 72px); line-height: 1.05; letter-spacing: -0.04em;
  font-weight: 800; margin-bottom: 24px;
}
.hero__title span { display: block; }

.hero__sub {
  font-size: 18px; line-height: 1.7; color: var(--text-muted);
  max-width: 580px; margin-bottom: 36px;
}

.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 40px; }

/* Staggered entrance for the hero column: each element slides up 0.6s after the
   one before it, which reads as the page assembling rather than snapping in. */
.hero__inner > div > .eyebrow,
.hero__title, .hero__sub, .hero__cta, .hero__formats {
  animation: hero-rise var(--t-slow) var(--ease) both;
}
.hero__title   { animation-delay: .06s; }
.hero__sub     { animation-delay: .12s; }
.hero__cta     { animation-delay: .18s; }

@keyframes hero-rise { from { opacity: 0; transform: translateY(20px); } }

.format-pill {
  display: inline-flex; align-items: center; height: 28px; padding: 0 11px;
  border-radius: 8px; background: var(--bg-elevated); border: 1px solid var(--border);
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.02em; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  transition: all .16s;
}
.format-pill:hover { border-color: var(--primary); color: var(--primary); transform: translateY(-1px); }

/* Hero right — illustration + floating glass cards */

.hero__visual { position: relative; min-height: 560px; }

.hero__orb {
  position: absolute; inset: 8% 10%; border-radius: 50%;
  background: var(--grad-brand); opacity: .1; filter: blur(8px);
  animation: breathe 8s var(--ease) infinite;
}
@keyframes breathe {
  0%, 100% { transform: scale(1); opacity: .1; }
  50% { transform: scale(1.06); opacity: .16; }
}

.hero__ring {
  position: absolute; border-radius: 50%; border: 1.5px dashed rgba(37, 99, 235, 0.2);
  top: 50%; left: 50%; transform: translate(-50%, -50%);
}
.hero__ring--1 { width: 340px; height: 340px; animation: spin 40s linear infinite; }
.hero__ring--2 { width: 460px; height: 460px; border-style: solid; border-color: rgba(99, 102, 241, 0.13); animation: spin 60s linear infinite reverse; }
.hero__ring--3 { width: 580px; height: 580px; border-color: rgba(14, 165, 233, 0.09); animation: spin 90s linear infinite; }
@keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } }

.hero__illus {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 250px; height: 250px; display: grid; place-items: center;
  border-radius: var(--r-xl); background: var(--grad-brand);
  box-shadow: 0 30px 70px rgba(37, 99, 235, 0.36);
  color: #fff; animation: float-y 6s var(--ease) infinite;
}
.hero__illus svg { width: 116px; height: 116px; }

@keyframes float-y {
  0%, 100% { transform: translate(-50%, -50%) translateY(0); }
  50% { transform: translate(-50%, -50%) translateY(-14px); }
}

.float-card {
  position: absolute; z-index: 2; min-width: 168px;
  padding: 15px 17px; border-radius: var(--r-md);
  background: var(--card-glass);
  backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.66);
  box-shadow: var(--shadow-lg);
  animation: float-card 7s var(--ease) infinite;
  transition: transform .24s var(--ease), box-shadow .24s;
}
:root[data-theme='dark'] .float-card { border-color: rgba(255, 255, 255, 0.09); }
.float-card:hover { transform: translateY(-6px) scale(1.03); box-shadow: var(--shadow-xl); z-index: 3; }

@keyframes float-card {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-11px); }
}

.float-card__top { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.float-card__icon { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: #fff; flex: 0 0 auto; }
.float-card__icon svg { width: 15px; height: 15px; }
.float-card__label { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-faint); }
.float-card__value { font-size: 25px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.float-card__meta { font-size: 11.5px; color: var(--text-muted); margin-top: 3px; font-weight: 500; }

/* Positions are laid out on two staggered columns that never overlap vertically.
   Right-hand offsets stay at 0 or positive: .hero has overflow:hidden, so a
   negative right offset clipped the card against the viewport edge. */
.float-card--1 { top: 0%;    left: -7%;  animation-delay: 0s; }
.float-card--2 { top: 11%;   right: 0;   animation-delay: .9s; }
.float-card--3 { top: 39%;   left: -11%; animation-delay: 1.8s; }
.float-card--4 { top: 41%;   right: 5%;  animation-delay: 2.7s; }
.float-card--5 { bottom: 3%; left: -2%;  animation-delay: 3.6s; }
.float-card--6 { bottom: 8%; right: 1%;  animation-delay: 4.5s; }

.spark { display: flex; align-items: flex-end; gap: 3px; height: 26px; margin-top: 8px; }
.spark i { flex: 1; border-radius: 2px 2px 0 0; background: var(--grad-brand); opacity: .82; animation: spark-grow 1.4s var(--ease) backwards; }
@keyframes spark-grow { from { height: 0 !important; opacity: 0; } }

.risk-meter { height: 6px; border-radius: 99px; background: var(--border); overflow: hidden; margin-top: 10px; }
.risk-meter i { display: block; height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--success), var(--warning), var(--danger)); }

/* ══ SECTION 2 — UPLOAD ═════════════════════════════════════════════════ */

.upload-layout { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 28px; align-items: start; }

.dropzone {
  position: relative; isolation: isolate;
  padding: 56px 40px; text-align: center;
  border: 2px dashed var(--border-strong); border-radius: var(--r-lg);
  background: var(--card);
  transition: border-color var(--t-base), background var(--t-base),
              transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease);
  cursor: pointer;
  /* Idle pulse-glow: a slow breathing ring that marks this as the primary action
     without the noise of a looping animation on the box itself. */
  animation: dropzone-pulse 3.6s var(--ease) infinite;
}
.dropzone:hover {
  border-color: var(--primary); background: var(--primary-050);
  transform: translateY(-2px); animation-play-state: paused;
}
.dropzone.is-dragover {
  border-color: var(--primary); background: var(--primary-050);
  transform: scale(1.012); box-shadow: var(--shadow-lg), 0 0 0 6px rgba(37, 99, 235, 0.12);
  animation-play-state: paused;
}
@keyframes dropzone-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.10); }
  50%      { box-shadow: 0 0 0 10px rgba(37, 99, 235, 0.045); }
}

.dropzone__icon {
  width: 88px; height: 88px; margin: 0 auto 22px; border-radius: 26px;
  background: var(--grad-primary); display: grid; place-items: center; color: #fff;
  box-shadow: var(--shadow-primary); animation: float-y-simple 5s var(--ease) infinite;
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease);
}
.dropzone:hover .dropzone__icon {
  transform: scale(1.06); box-shadow: 0 18px 38px rgba(37, 99, 235, 0.38);
}
.dropzone__icon svg { width: 38px; height: 38px; }
@keyframes float-y-simple { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }

.dropzone__title { font-size: 24px; margin-bottom: 10px; }
.dropzone__hint { font-size: 15px; color: var(--text-muted); margin-bottom: 26px; }
.dropzone__actions { display: flex; flex-wrap: wrap; gap: 11px; justify-content: center; }

/* Accepted formats, inside the drop box */
.dropzone__formats { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); }
.dropzone__formats-label {
  display: block; margin-bottom: 12px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--text-faint);
}
.dropzone__formats .badge-row { justify-content: center; }
.dropzone__formats .formats-note { margin: 14px 0 0; text-align: center; }

.validation-title { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 16px; }
.formats-note { font-size: 14px; color: var(--text-muted); }
.formats-note b { color: var(--primary); font-weight: 700; }

/* Upload queue */

.queue { display: grid; gap: 12px; }

/* Panel variant: the queue sits in the right column and scrolls inside its own
   box. Without the cap, uploading a folder pushed the page down far enough that
   the dropzone left the viewport while files were still being added. */
.queue--panel {
  max-height: min(56vh, 520px); overflow-y: auto;
  /* Room for the scrollbar so rows do not sit flush against it. */
  margin-right: -6px; padding-right: 6px;
}
.queue--panel:empty { display: none; }

/* Placeholder disappears the moment a row exists — no JS needed. */
.queue:not(:empty) ~ .queue-empty { display: none; }
.queue-empty { text-align: center; padding: 26px 8px 12px; }
.queue-empty__icon {
  width: 52px; height: 52px; margin: 0 auto 14px; border-radius: 16px;
  display: grid; place-items: center;
  background: var(--primary-050); color: var(--primary);
}
.queue-empty__icon svg { width: 24px; height: 24px; }
.queue-empty__title { font-size: 15px; font-weight: 700; margin-bottom: 6px; }
.queue-empty__text { font-size: 13.5px; color: var(--text-muted); line-height: 1.6; }

/* 24px, not navbar+24px: sticky resolves against .main, which already starts below
   the navbar. */
.queue-panel { align-self: start; position: sticky; top: 24px; }

.queue-item {
  display: grid; grid-template-columns: 42px minmax(0, 1fr) auto; gap: 14px; align-items: center;
  padding: 15px 18px; border-radius: var(--r-md);
  background: var(--card); border: 1px solid var(--border); box-shadow: var(--shadow-xs);
  animation: slide-up .34s var(--ease) backwards;
}
@keyframes slide-up { from { opacity: 0; transform: translateY(12px); } }

.queue-item__icon { width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; background: var(--primary-050); color: var(--primary); flex: 0 0 auto; }
.queue-item__icon svg { width: 19px; height: 19px; }
.queue-item__name { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.queue-item__meta { font-size: 12px; color: var(--text-faint); margin-top: 2px; display: flex; gap: 10px; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
.queue-item__actions { display: flex; gap: 6px; }

/* In the side panel the row is much narrower than it was full-width, so the
   actions drop to a line of their own rather than squeezing the filename down to
   a few characters. DOM order is icon → body → actions, so this needs no markup
   change. */
.queue--panel .queue-item { grid-template-columns: 42px minmax(0, 1fr); }
.queue--panel .queue-item__actions { grid-column: 2 / -1; justify-content: flex-end; flex-wrap: wrap; }
.queue-item--error { border-color: rgba(239, 68, 68, 0.32); background: rgba(239, 68, 68, 0.03); }
.queue-item--error .queue-item__icon { background: rgba(239, 68, 68, 0.1); color: var(--danger-text); }
.queue-item--done .queue-item__icon { background: rgba(34, 197, 94, 0.1); color: var(--success-text); }

.progress { height: 6px; border-radius: 99px; background: var(--border); overflow: hidden; margin-top: 9px; }
.progress__bar {
  height: 100%; border-radius: 99px; background: var(--grad-brand); width: 0;
  transition: width .4s var(--ease); position: relative; overflow: hidden;
}
.progress__bar::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.42), transparent);
  animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

.pipe-step.is-active { background: var(--primary-050); }
.pipe-step.is-active .pipe-step__dot {
  border-color: var(--primary); color: var(--primary);
  box-shadow: 0 0 0 5px rgba(37, 99, 235, 0.12);
}
.pipe-step.is-active .pipe-step__dot::after {
  content: ''; position: absolute; inset: -2px; border-radius: 50%;
  border: 2px solid transparent; border-top-color: var(--primary);
  animation: spin-fast .8s linear infinite;
}
@keyframes spin-fast { to { transform: rotate(360deg); } }
.pipe-step.is-active .pipe-step__label { color: var(--primary); font-weight: 700; }

.doc-card { overflow: hidden; display: flex; flex-direction: column; }
.doc-card__actions { display: flex; gap: 8px; margin-top: auto; }
.search-examples .chip { cursor: pointer; }
.search-examples .chip svg { color: var(--primary); }

.filters { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 26px; }
.field { display: flex; flex-direction: column; gap: 6px; min-width: 168px; }
.field__label { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); padding-left: 2px; }
.field select, .field input {
  height: 42px; padding: 0 13px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--card); font-size: 13.5px;
  transition: border-color .18s, box-shadow .18s;
}
.field select:focus, .field input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); }

.answer-card { margin-top: 32px; padding: 26px; border-left: 3px solid var(--primary); }
.answer-card__head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.answer-card__head svg { width: 18px; height: 18px; color: var(--primary); }
.answer-card__title { font-size: 13px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--primary); }
.answer-card__text { font-size: 15.5px; line-height: 1.75; white-space: pre-wrap; }
.answer-card__foot { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-faint); display: flex; gap: 14px; flex-wrap: wrap; }

.hit-list { margin-top: 22px; display: grid; gap: 12px; }
.hit {
  padding: 18px 20px; border-radius: var(--r-md); background: var(--card);
  border: 1px solid var(--border); box-shadow: var(--shadow-xs); cursor: pointer;
  transition: transform .2s var(--ease), box-shadow .2s, border-color .2s;
}
.hit:hover { transform: translateX(3px); box-shadow: var(--shadow-md); border-color: var(--primary); }
.hit__top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 9px; }
.hit__index { width: 22px; height: 22px; border-radius: 6px; background: var(--primary); color: #fff; font-size: 11px; font-weight: 700; display: grid; place-items: center; flex: 0 0 auto; }
.hit__patient { font-size: 13.5px; font-weight: 700; }
.hit__snippet { font-size: 13.5px; line-height: 1.65; color: var(--text-muted); }
.hit__snippet mark { background: rgba(245, 158, 11, 0.28); color: inherit; border-radius: 3px; padding: 0 2px; }

/* ══ SECTION 6 — UHID LOOKUP + PROFILE ══════════════════════════════════ */

.uhid-card { max-width: 620px; margin: 0 auto; padding: 38px; text-align: center; }
.uhid-card__icon { width: 62px; height: 62px; margin: 0 auto 20px; border-radius: 19px; background: var(--grad-brand); display: grid; place-items: center; color: #fff; box-shadow: var(--shadow-primary); }
.uhid-card__icon svg { width: 28px; height: 28px; }
/* Centred as a pair rather than stretched across the card: a UHID is ~12 characters,
   so an input that filled the full 620px card read as a search bar for prose. The
   field sizes to its content and the button sits beside it, both centred together. */
.uhid-form { display: flex; gap: 11px; margin-top: 24px; justify-content: center; }
.uhid-form input {
  flex: 0 1 auto; width: 260px; min-width: 0; max-width: 100%;
  height: 52px; padding: 0 20px; border-radius: var(--r-md);
  border: 1px solid var(--border-strong); background: var(--bg-elevated);
  font-size: 17px; font-weight: 600; letter-spacing: 0.04em; text-align: center;
  text-transform: uppercase; font-variant-numeric: tabular-nums;
  transition: border-color .18s, box-shadow .18s;
}
/* Invalid state, driven by the required-field check on submit. Only the border and
   ring change, so nothing moves and the layout cannot shift. */
.uhid-form input.is-invalid {
  border-color: var(--danger-text, #b91c1c);
  box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.12);
}
.uhid-form__error {
  margin-top: 10px; font-size: 13.5px; font-weight: 600;
  color: var(--danger-text, #b91c1c); min-height: 1em;
}
.uhid-form input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12); }
.uhid-form .btn { height: 52px; }

.profile { display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 26px; align-items: start; }

.profile-card { padding: 30px; text-align: center; position: sticky; top: 24px; }
.profile-photo {
  width: 96px; height: 96px; margin: 0 auto 18px; border-radius: 50%;
  background: var(--grad-brand); display: grid; place-items: center;
  color: #fff; font-size: 32px; font-weight: 700; letter-spacing: -0.03em;
  box-shadow: var(--shadow-primary);
}
.profile-name { font-size: 22px; margin-bottom: 5px; }
.profile-uhid { font-size: 13px; font-weight: 700; color: var(--primary); letter-spacing: 0.06em; font-variant-numeric: tabular-nums; margin-bottom: 18px; }

.profile-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 22px 0; text-align: left; }
.profile-field { padding: 12px 14px; border-radius: var(--r-sm); background: var(--bg); }
.profile-field__label { font-size: 10px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-faint); }
.profile-field__value { font-size: 14px; font-weight: 600; margin-top: 3px; word-break: break-word; }
.profile-field--wide { grid-column: 1 / -1; }

.profile-actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; margin-top: 22px; }
.profile-actions .btn--block { grid-column: 1 / -1; }

.detail-stack { display: grid; gap: 26px; min-width: 0; }

.panel { padding: 28px; }
.panel__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
.panel__title { font-size: 19px; display: flex; align-items: center; gap: 11px; }
.panel__title svg { width: 20px; height: 20px; color: var(--primary); }
.panel__count { font-size: 12px; font-weight: 700; color: var(--text-faint); }

/* ══ SECTION 7 — TIMELINE ═══════════════════════════════════════════════ */

.timeline { position: relative; padding-left: 34px; }
.timeline::before {
  content: ''; position: absolute; left: 11px; top: 6px; bottom: 6px; width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--primary), var(--secondary) 40%, var(--accent) 70%, var(--success));
}

.tl-item { position: relative; padding-bottom: 26px; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item__dot {
  position: absolute; left: -34px; top: 3px; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; color: #fff; box-shadow: 0 0 0 4px var(--card);
  z-index: 1;
}
.tl-item__dot svg { width: 12px; height: 12px; }
.tl-item__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 5px; }
.tl-item__title { font-size: 15px; font-weight: 700; }
.tl-item__date { font-size: 12px; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.tl-item__detail { font-size: 13.5px; color: var(--text-muted); line-height: 1.65; }
.tl-item__meta { font-size: 11.5px; color: var(--text-faint); margin-top: 6px; display: flex; gap: 12px; flex-wrap: wrap; }

/* Event colours */
.ev-admission    { background: var(--primary); }
.ev-diagnosis    { background: var(--secondary); }
.ev-consultation { background: var(--accent); }
.ev-lab          { background: #14b8a6; }
.ev-radiology    { background: #8b5cf6; }
.ev-operation    { background: var(--danger); }
.ev-prescription { background: var(--warning); }
.ev-discharge    { background: var(--success); }
.ev-other        { background: var(--text-faint); }

/* ══ SECTION 8 — LAB REPORTS ════════════════════════════════════════════ */

.lab-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(292px, 1fr)); gap: 18px; }

.lab-card { padding: 22px; display: flex; flex-direction: column; }
.lab-card__head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.lab-card__icon { width: 42px; height: 42px; border-radius: 13px; display: grid; place-items: center; color: #fff; font-size: 12px; font-weight: 700; flex: 0 0 auto; letter-spacing: -0.01em; }
.lab-card__name { font-size: 15px; font-weight: 700; }
.lab-card__date { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; margin-top: 2px; }

.lab-values { display: grid; gap: 7px; margin-bottom: 16px; }
.lab-value { display: flex; align-items: baseline; gap: 10px; font-size: 13px; padding-bottom: 7px; border-bottom: 1px dashed var(--border); }
.lab-value:last-child { border-bottom: none; padding-bottom: 0; }
.lab-value__name { color: var(--text-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lab-value__num { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.lab-value__ref { font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.flag { font-size: 10px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; padding: 1px 6px; border-radius: 5px; }
.flag--high { background: rgba(239, 68, 68, 0.12); color: #b91c1c; }
.flag--low { background: rgba(245, 158, 11, 0.14); color: #b45309; }
.flag--normal { background: rgba(34, 197, 94, 0.12); color: #15803d; }
.flag--critical { background: var(--danger); color: #fff; }
:root[data-theme='dark'] .flag--high { color: #f87171; }
:root[data-theme='dark'] .flag--low { color: #fbbf24; }
:root[data-theme='dark'] .flag--normal { color: #4ade80; }

.lab-findings { font-size: 12.5px; color: var(--text-muted); line-height: 1.6; margin-bottom: 14px; padding: 12px; border-radius: var(--r-sm); background: var(--bg); }
.lab-card__actions { display: flex; gap: 7px; margin-top: auto; }

/* Panel colours */
.panel-CBC     { background: linear-gradient(135deg, #ef4444, #f87171); }
.panel-LFT     { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
.panel-KFT     { background: linear-gradient(135deg, #0ea5e9, #38bdf8); }
.panel-Urine   { background: linear-gradient(135deg, #eab308, #facc15); }
.panel-Sugar   { background: linear-gradient(135deg, #8b5cf6, #a78bfa); }
.panel-Thyroid { background: linear-gradient(135deg, #14b8a6, #2dd4bf); }
.panel-Vitamin { background: linear-gradient(135deg, #22c55e, #4ade80); }
.panel-Lipid   { background: linear-gradient(135deg, #6366f1, #818cf8); }
.panel-other   { background: linear-gradient(135deg, #64748b, #94a3b8); }

/* ══ SECTION 9 — MEDICINES ══════════════════════════════════════════════ */

.med-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 18px; }

.med-card { padding: 22px; }
.med-card__head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.med-card__pill {
  width: 40px; height: 40px; border-radius: 12px; flex: 0 0 auto;
  background: linear-gradient(135deg, var(--secondary), var(--primary));
  display: grid; place-items: center; color: #fff;
}
.med-card__pill svg { width: 19px; height: 19px; }
.med-card__name { font-size: 15.5px; font-weight: 700; line-height: 1.3; }
.med-card__dose { font-size: 12.5px; color: var(--primary); font-weight: 700; margin-top: 2px; }

.dose-timing { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px; }
.dose-slot {
  padding: 11px 8px; border-radius: var(--r-sm); text-align: center;
  background: var(--bg); border: 1px solid var(--border);
  transition: all .18s;
}
.dose-slot__icon { font-size: 15px; line-height: 1; margin-bottom: 5px; }
.dose-slot__label { font-size: 9.5px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-faint); }
.dose-slot.is-on { background: var(--primary-050); border-color: var(--primary); }
.dose-slot.is-on .dose-slot__label { color: var(--primary); }
.dose-slot.is-off { opacity: .38; }

.med-meta { display: grid; gap: 7px; font-size: 12.5px; }
.med-meta__row { display: flex; gap: 10px; }
.med-meta__key { color: var(--text-faint); min-width: 66px; font-weight: 600; }
.med-meta__val { color: var(--text-muted); min-width: 0; }

.med-warning {
  margin-top: 14px; padding: 11px 13px; border-radius: var(--r-sm);
  background: rgba(245, 158, 11, 0.08); border: 1px solid rgba(245, 158, 11, 0.24);
  font-size: 12.5px; color: #92400e; line-height: 1.55;
  display: flex; gap: 9px;
}
.med-warning svg { width: 15px; height: 15px; flex: 0 0 auto; margin-top: 2px; color: var(--warning); }
:root[data-theme='dark'] .med-warning { color: #fcd34d; }

/* ══ SECTION 10 — AI SUMMARY ════════════════════════════════════════════ */

.summary-hero {
  padding: 34px; border-radius: var(--r-lg); margin-bottom: 26px;
  background: var(--grad-brand); color: #fff; box-shadow: var(--shadow-primary);
  position: relative; overflow: hidden;
}
.summary-hero::after {
  content: ''; position: absolute; top: -60%; right: -14%; width: 380px; height: 380px;
  border-radius: 50%; background: rgba(255, 255, 255, 0.09);
}
.summary-hero__top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; position: relative; }
.summary-hero__badge {
  display: inline-flex; align-items: center; gap: 7px; height: 30px; padding: 0 13px;
  border-radius: var(--r-pill); background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.28); font-size: 12px; font-weight: 700;
}
.summary-hero__badge svg { width: 14px; height: 14px; }
.summary-hero__text { font-size: 16.5px; line-height: 1.75; position: relative; white-space: pre-wrap; }

.confidence { display: flex; align-items: center; gap: 14px; margin-top: 24px; position: relative; flex-wrap: wrap; }
.confidence__track { flex: 1; min-width: 160px; height: 8px; border-radius: 99px; background: rgba(255, 255, 255, 0.24); overflow: hidden; }
.confidence__bar { height: 100%; border-radius: 99px; background: #fff; width: 0; transition: width .9s var(--ease); }
.confidence__value { font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.03em; }
.confidence__label { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; opacity: .8; }

.summary-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(292px, 1fr)); gap: 18px; }

.sum-card { padding: 22px; }
.sum-card__head { display: flex; align-items: center; gap: 11px; margin-bottom: 14px; }
.sum-card__icon { width: 34px; height: 34px; border-radius: 11px; display: grid; place-items: center; color: #fff; flex: 0 0 auto; }
.sum-card__icon svg { width: 16px; height: 16px; }
.sum-card__title { font-size: 14px; font-weight: 700; }
.sum-list { display: grid; gap: 9px; }
.sum-list li { display: flex; gap: 10px; font-size: 13.5px; line-height: 1.6; color: var(--text-muted); }
.sum-list li::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--primary); flex: 0 0 auto; margin-top: 8px; }

.sources { margin-top: 26px; padding: 22px; }
.sources__title { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 13px; }
.sources__list { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── Empty / loading / toast ─────────────────────────────────────────────── */

.empty { padding: 62px 30px; text-align: center; }
.empty__icon {
  width: 68px; height: 68px; margin: 0 auto 20px; border-radius: 21px;
  background: var(--primary-050); display: grid; place-items: center; color: var(--primary);
}
.empty__icon svg { width: 30px; height: 30px; }
.empty__title { font-size: 18px; margin-bottom: 8px; }
.empty__text { font-size: 14px; color: var(--text-muted); max-width: 440px; margin: 0 auto 22px; line-height: 1.65; }

.spinner {
  width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto;
  border: 2.5px solid var(--border); border-top-color: var(--primary);
  animation: spin-fast .7s linear infinite;
}
.loading-row { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 44px; color: var(--text-muted); font-size: 14px; }
@keyframes skeleton { from { background-position: 200% 0; } to { background-position: -200% 0; } }

.toast-wrap { position: fixed; bottom: 26px; right: 26px; z-index: 300; display: grid; gap: 10px; max-width: 400px; }
.toast {
  display: flex; align-items: flex-start; gap: 12px; padding: 15px 18px;
  border-radius: var(--r-md); background: var(--card); border: 1px solid var(--border);
  box-shadow: var(--shadow-xl); font-size: 13.5px; font-weight: 500;
  animation: toast-in .34s var(--ease) backwards;
}
@keyframes toast-in { from { opacity: 0; transform: translateX(28px) scale(0.96); } }
.toast--out { animation: toast-out .26s var(--ease) forwards; }
@keyframes toast-out { to { opacity: 0; transform: translateX(28px) scale(0.96); } }
.toast__icon { width: 20px; height: 20px; flex: 0 0 auto; margin-top: 1px; }
.toast--success .toast__icon { color: var(--success-text); }
.toast--error .toast__icon { color: var(--danger-text); }
.toast--info .toast__icon { color: var(--primary); }
.toast--warning .toast__icon { color: var(--warning); }
.toast__body { min-width: 0; }
.toast__title { font-weight: 700; margin-bottom: 2px; }
.toast__text { color: var(--text-muted); font-size: 12.5px; line-height: 1.5; }

/* ── Modal ──────────────────────────────────────────────────────────────── */

.modal-backdrop {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: 24px;
  background: rgba(15, 23, 42, 0.5);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: fade-in .2s var(--ease);
}
@keyframes fade-in { from { opacity: 0; } }
.modal {
  width: 100%; max-width: 860px; max-height: 86vh; overflow: auto;
  background: var(--card); border-radius: var(--r-lg); box-shadow: var(--shadow-xl);
  animation: modal-in .3s var(--ease) backwards;
}
/* The upload popup reuses .modal as-is (same theme, same chrome) but narrower —
   "small centered modal" per the brief — while still wide enough that
   .upload-layout's two columns (dropzone + queue) don't feel cramped. */
.modal--upload { max-width: 640px; }
/* .hero__upload's own spacing (a large top margin, a divider line) exists to
   separate it from the hero section above it on the home page — nothing to
   separate from inside a modal body, which already has its own padding. Scoped
   here rather than changed on .hero__upload itself, so the home page (were this
   block ever un-hidden there again) still gets its original spacing. */
.modal--upload .hero__upload { margin: 0; padding-top: 0; border-top: 0; max-width: none; }
/* .hero__upload's z-index:1 exists to sit above the hero's decorative background
   glows — there are none here, but the declaration still applies, and once the
   popup scrolls, this later-in-DOM element ties with .modal__head's own z-index:1
   and paints over it, covering the close button. Neutralized only inside this
   modal; .hero__upload itself is untouched for if it's ever shown on the hero
   again. */
.modal--upload .hero__upload { z-index: auto; }

/* ── Compact upload popup ──────────────────────────────────────────────────
   Everything below is scoped to .modal--upload alone: .section-head, .dropzone,
   .queue-panel and .upload-layout are shared with the (currently hidden) hero
   upload block and other pages, and none of their base rules change. This is a
   size variant for the popup only — same markup, same functionality, smaller
   and better balanced. */

/* A hero-scale heading (clamp(30px, 3.6vw, 42px)) has no room to earn its size
   in a small dialog — it was most of the popup's excess height by itself. */
.modal--upload .section-head { margin-bottom: 18px; }
.modal--upload .section-title { font-size: 20px; margin-bottom: 6px; }
.modal--upload .section-sub { font-size: 13.5px; line-height: 1.5; }
.modal--upload .eyebrow { margin-bottom: 4px; }

/* Equal columns rather than 1.35fr/1fr — a dropzone and a queue read as one
   balanced pair now, not a big box and an afterthought beside it. Centered so
   both sit level even though the dropzone's own content (icon, title, hint,
   button, formats) is naturally taller than the queue's empty state. */
.modal--upload .upload-layout { grid-template-columns: 1fr 1fr; gap: 20px; align-items: center; }

/* The dropzone's padding, icon and type scale were sized for a full hero
   section; halved (roughly) for a compact card that still reads clearly at a
   glance. flex+justify-content:center is what centers its content vertically
   when the box ends up shorter than its old content demanded. */
.modal--upload .dropzone {
  padding: 26px 20px; display: flex; flex-direction: column; justify-content: center;
  min-height: 280px; z-index: 2;
}
.modal--upload .dropzone__icon {
  width: 52px; height: 52px; margin-bottom: 14px; border-radius: 16px;
}
.modal--upload .dropzone__icon svg { width: 24px; height: 24px; }
.modal--upload .dropzone__title { font-size: 16px; margin-bottom: 5px; }
.modal--upload .dropzone__hint { font-size: 12.5px; margin-bottom: 16px; }
.modal--upload .dropzone__formats { margin-top: 16px; padding-top: 14px; }
.modal--upload .dropzone__formats-label { margin-bottom: 8px; }

/* The queue panel matches the dropzone's new padding and height so the pair
   looks deliberately sized rather than one shrunk more than the other. Left as
   a normal block, not flexed+centered like the dropzone: "Upload queue" is a
   label that belongs pinned at the top in either state, and real rows need to
   stack from the top and grow downward, not have the whole panel recentre
   itself around a shifting block as files are added. Only the empty state
   (below) centers within the room that leaves. */
.modal--upload .queue-panel { padding: 20px; min-height: 280px; position: static; top: auto; align-self: stretch; }
.modal--upload .queue-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 200px; padding: 0;
}
.modal--upload .queue--panel { max-height: min(50vh, 340px); }
@keyframes modal-in { from { opacity: 0; transform: translateY(18px) scale(0.97); } }
.modal__head {
  /* z-index: 2, not 1 — a modal body can contain content that carries its own
     z-index:1 (e.g. .hero__upload, reused here from the hero it usually sits in),
     which used to tie with this sticky header and, being later in the DOM, paint
     over it once the reader scrolled — covering the close button entirely. This
     always wins that tie, whatever ends up in a modal body next. */
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 16px; padding: 22px 26px;
  border-bottom: 1px solid var(--border); background: var(--card);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.modal__title { font-size: 18px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal__close { margin-left: auto; }
.modal__body { padding: 26px; }

.kv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; margin-bottom: 22px; }

/* ── Analytics ───────────────────────────────────────────────────────────────
   Charts are CSS bars and one inline SVG area. No chart library: this runs on an
   on-prem box with no internet, and every colour is an existing token so the charts
   follow the light/dark theme with no second palette to keep in sync.

   Bars are one hue on purpose. These charts answer "how many" — magnitude — and a
   colour per category would encode identity that nothing here needs, while making a
   9-department ward unreadable. Length carries the value; the number is printed. */

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 22px; }
.stat-tile {
  padding: 18px; border-radius: var(--r-md); background: var(--card);
  border: 1px solid var(--border);
}
.stat-tile__label {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--text-faint);
}
/* Tabular figures so a column of counts lines up digit for digit. */
.stat-tile__value {
  font-size: 30px; font-weight: 800; line-height: 1.1; margin-top: 6px;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.stat-tile__note { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
/* Status tones are reserved for state and never reused as a series colour. */
.stat-tile--critical { border-color: rgba(239, 68, 68, 0.3); }
.stat-tile--critical .stat-tile__value { color: var(--danger-text); }
.stat-tile--warning  { border-color: rgba(245, 158, 11, 0.32); }
.stat-tile--warning  .stat-tile__value { color: #b45309; }
.stat-tile--info     { border-color: rgba(34, 197, 94, 0.28); }
.stat-tile--info     .stat-tile__value { color: var(--success-text); }

.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 18px; margin-bottom: 22px; }
.chart-card { padding: 20px; border-radius: var(--r-md); background: var(--card); border: 1px solid var(--border); }
.chart-card__title { font-size: 13px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px; }
.chart-card__sub { font-size: 12px; color: var(--text-faint); margin-bottom: 16px; }

.bars { display: grid; gap: 10px; }
.bar-row { display: grid; grid-template-columns: minmax(90px, 34%) 1fr auto; gap: 10px; align-items: center; }
.bar-row__label { font-size: 12.5px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The track is the 100% reference, so a short bar still reads as "out of what". */
.bar-row__track { height: 10px; border-radius: 999px; background: var(--bg); overflow: hidden; }
.bar-row__fill {
  height: 100%; background: var(--primary);
  /* Rounded only at the data end, anchored flat to the baseline — the 4px cap marks
     where the value stops without implying the bar starts away from zero. */
  border-radius: 0 4px 4px 0; min-width: 3px;
  transition: width .5s var(--ease);
}
.bar-row__value { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; min-width: 2ch; text-align: right; }

/* One series, so no legend: the card title names it. */
.spark { width: 100%; height: 128px; display: block; overflow: visible; }
.spark__grid { stroke: var(--border); stroke-width: 1; }
.spark__area { fill: var(--primary); opacity: .12; }
.spark__line { fill: none; stroke: var(--primary); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* ≥8px so it is a hit target, with a surface ring where points overlap the line. */
.spark__dot { fill: var(--primary); stroke: var(--card); stroke-width: 2; }
.spark__tick { font-size: 10px; fill: var(--text-faint); }

@media (max-width: 700px) {
  .bar-row { grid-template-columns: minmax(72px, 40%) 1fr auto; }
  .stat-tile__value { font-size: 25px; }
}

/* ── Enterprise data table ───────────────────────────────────────────────────
   Its own class namespace (.dt-*) so nothing here can reach an existing table, and
   every colour is an existing token so the light/dark theme needs no second palette.
   Denser than .ptable on purpose: these tables are read a dozen rows at a time. */

.dt { margin-bottom: 30px; }
.dt__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.dt__title { font-size: 15px; font-weight: 700; }
.dt__count { font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* Filters in one row above the table, per the interaction guidance. */
.dt__toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.dt__search, .dt__filter {
  height: 34px; padding: 0 11px; border-radius: var(--r-sm);
  border: 1px solid var(--border-strong); background: var(--bg-elevated);
  font-size: 13px; color: var(--text); font-family: inherit;
}
.dt__search { flex: 1 1 200px; min-width: 0; }
.dt__search:focus, .dt__filter:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, .1); }
.dt__spacer { flex: 1 1 auto; }
.dt__btn {
  height: 34px; padding: 0 12px; border-radius: var(--r-sm); font-size: 12.5px; font-weight: 600;
  border: 1px solid var(--border-strong); background: var(--bg-elevated); color: var(--text-muted);
}
.dt__btn:hover { border-color: var(--primary); color: var(--primary); }

/* The scroll container is what makes the header sticky. Vertical-only now: with
   headers and cells wrapping instead of forcing a single line, a table never grows
   wider than the container, so there is nothing left for overflow-x to scroll —
   hidden rather than auto so a sub-pixel rounding difference can never reintroduce
   a horizontal scrollbar by accident. */
.dt__scroll {
  max-height: 62vh; overflow-y: auto; overflow-x: hidden; border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--card);
}
/* table-layout: auto (the default, stated explicitly) is what auto-fits columns —
   each is sized from its own content rather than splitting the width evenly, so a
   short "Age" column stays narrow while "Diagnosis" claims the room it needs. */
.dt__table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12.5px; table-layout: auto; }
.dt__table th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg); color: var(--text-muted);
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  text-align: left; padding: 9px 8px;
  border-bottom: 1px solid var(--border);
  cursor: pointer; user-select: none;
  /* A floor under every column, short header or not — without it, a column like
     Status or UHID (a short header, but data that is never actually that short)
     gets squeezed down to its header's width and then breaks ordinary short words
     like "Admitted" or a UHID just to fit, which word-break on td (below) would
     otherwise allow. */
  min-width: 64px;
  /* overflow-wrap, not word-break: word-break shrinks a column's *minimum* content
     size down to a single character in table auto-layout, which is what starved
     "Medicine Name" and "Generic Name" down to a sliver while other columns sat
     wide — every header wrapped one word per line as a result. overflow-wrap keeps
     each header's longest whole word as its floor and only breaks mid-word for the
     rare one that still doesn't fit. */
  white-space: normal; overflow-wrap: break-word; hyphens: auto;
}
.dt__table th:hover { color: var(--primary); }
.dt__table th[aria-sort] { color: var(--primary); }
/* The caret is the sort indicator; direction is also in aria-sort for screen readers. */
.dt__table th[aria-sort="ascending"]::after  { content: ' ▲'; font-size: 9px; }
.dt__table th[aria-sort="descending"]::after { content: ' ▼'; font-size: 9px; }
.dt__table td {
  padding: 8px 8px; border-bottom: 1px solid var(--border);
  color: var(--text); vertical-align: top; line-height: 1.5;
  /* No ellipsis, no clipping — a long medicine name or diagnosis wraps onto a
     second line instead of being cut off or forcing the column wider than its
     content needs. word-break here, unlike the header rule above: a header's
     longest word is what should set a column's floor, but a data value has no
     such job to do — a 20-character diagnosis with no spaces in it ("SPONDOLYLO-
     LISTHESIS...") should be free to break rather than dragging the whole column,
     and every other row in it, out to its width. */
  white-space: normal; word-break: break-word;
}
.dt__table tbody tr:last-child td { border-bottom: 0; }
.dt__table tbody tr:hover td { background: var(--primary-050); }
/* .badge is a pill everywhere else in the app and stays that way — nowrap, fixed
   height. Scoped to this table only: "Needs Verification" and "Discontinued" are
   long enough that leaving them nowrap would set the Status column's width on
   their own, on every table, regardless of anything else in the row. */
/* word-break on td (above) is meant for a pathological single long token, not for
   "Admitted" or "Active" — without resetting it here, a short status word inherits
   the parent's word-break and can fracture into single-letter lines for no reason,
   since the pill itself has no long word to justify it. */
.dt__table td .badge { white-space: normal; word-break: normal; overflow-wrap: break-word; height: auto; padding: 4px 10px; line-height: 1.3; }
/* Same reasoning as the badge reset above, for the same reason: word-break on td is
   for a pathological long free-text value, not a UHID — "UHID287064" is a short,
   recognizable token that should stay whole whenever the column has room for it,
   not fracture into "UHID2870" / "64" just because it's one character past whatever
   the column happened to be. overflow-wrap stays as the last-resort fallback for a
   UHID long enough to need it, without shrinking the column's floor the way
   word-break does. */
.dt__table td.dt__strong { word-break: normal; overflow-wrap: break-word; }
/* UHID is always the first column (see the four table specs in renderTables) — a
   105px floor is enough for the longest UHID on file ("UHID287064", which needs
   102px) with a little room to spare, so it stops being the one column still
   landing on overflow-wrap's last-resort break by a handful of pixels. */
.dt__table th:first-child { min-width: 105px; }
/* Numbers right-aligned and tabular so a column of counts can be compared by eye.
   Written as `.dt__table th.dt__num` rather than a bare `.dt__num`: `.dt__table th`
   sets text-align:left and outranks a single class, so the header of every numeric
   column sat left while its cells sat right — the misalignment down the whole table.
   The cell rule is spelled out at the same specificity so the pair cannot drift. */
.dt__table th.dt__num,
.dt__table td.dt__num { text-align: right; font-variant-numeric: tabular-nums; }
/* Text columns state their alignment too, so a header and its cells always agree. */
.dt__table th:not(.dt__num),
.dt__table td:not(.dt__num) { text-align: left; }
.dt__strong { font-weight: 600; }
.dt__muted { color: var(--text-faint); }

.dt__foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.dt__page { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dt__empty { padding: 26px 12px; text-align: center; color: var(--text-faint); font-size: 13.5px; }

@media (max-width: 700px) {
  .dt__scroll { max-height: none; }
  .dt__search { flex: 1 1 100%; }
}

/* ── Reveal on scroll ───────────────────────────────────────────────────── */

.reveal { opacity: 0; transform: translateY(22px); transition: opacity .6s var(--ease), transform .6s var(--ease); }
.reveal.is-visible { opacity: 1; transform: none; }

/* ── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .hero__inner { grid-template-columns: 1fr; gap: 48px; }
  .hero__visual { min-height: 460px; order: -1; }
  /* Single-column hero: the visual loses ~40% of its height, so six cards no
     longer fit without colliding. Drop to four and pin them to the corners. */
  .float-card--3, .float-card--6 { display: none; }
  .float-card--1 { top: 0;       left: 2%;  right: auto; bottom: auto; }
  .float-card--2 { top: 4%;      right: 2%; left: auto;  bottom: auto; }
  .float-card--4 { bottom: 6%;   right: 2%; left: auto;  top: auto; }
  .float-card--5 { bottom: 1%;   left: 2%;  right: auto; top: auto; }
  .upload-layout, .profile { grid-template-columns: 1fr; }
  /* .modal--upload .upload-layout's own equal-columns rule (two classes) is more
     specific than the plain .upload-layout above, so it would otherwise keep
     winning at this width too and hold the dropzone and queue side by side in a
     640px-wide (or, on a phone, ~100%-wide) modal — cramped rather than compact. */
  .modal--upload .upload-layout { grid-template-columns: 1fr; }
  .profile-card { position: static; }
  /* Single column: sticky would pin the queue over the dropzone, and the internal
     scroll is pointless when the panel is full width. */
  .queue-panel { position: static; }
  .queue--panel { max-height: none; overflow-y: visible; }
  .queue--panel .queue-item { grid-template-columns: 42px minmax(0, 1fr) auto; }
  .queue--panel .queue-item__actions { grid-column: auto; }
}

@media (max-width: 1024px) {
  :root { --sidebar-w: 0px; }
  .nav-toggle { display: grid; }
  .sidebar { width: 268px; transform: translateX(-100%); box-shadow: var(--shadow-xl); }
  .sidebar.is-open { transform: none; }
  /* Off-canvas sidebar: the container takes the full width. */
  .main { left: 0; }
  .navbar__search { display: none; }
}

@media (max-width: 760px) {
  .section, .hero { padding-left: 20px; padding-right: 20px; }
  .section { padding-top: 62px; padding-bottom: 62px; }
  .hero { padding-top: 48px; padding-bottom: 68px; }
  .hero__visual { min-height: 340px; }
  .hero__illus { width: 170px; height: 170px; }
  .hero__illus svg { width: 78px; height: 78px; }
  .hero__ring--3 { display: none; }
  .float-card { min-width: 138px; padding: 12px 14px; }
  .float-card__value { font-size: 20px; }
  /* Phone: three cards is the most that reads as decoration rather than clutter. */
  .float-card--3, .float-card--4, .float-card--6 { display: none; }
  .hero__cta .btn { flex: 1 1 auto; }
  .doc-grid, .lab-grid, .med-grid, .summary-grid { grid-template-columns: 1fr; }
  .profile-grid { grid-template-columns: 1fr; }
  .card--pad, .panel, .pipeline-card, .uhid-card, .ws__head { padding: 20px; }
  .uhid-form { flex-direction: column; }
  /* `flex: 1` resolves against the main axis, so turning the form into a column
     pointed flex-basis:0 at the input's HEIGHT and collapsed the 52px field to a
     22px sliver — an unusable, visibly misaligned input beside a 52px button.
     Width is what should fill here, not height. */
  .uhid-form input { flex: 0 0 auto; width: 100%; }
  .uhid-form .btn { width: 100%; }
  /* Section heads carry desktop-scale breathing room that reads as dead space on
     a phone, where the title is already the first thing on screen. */
  .section-head { margin-bottom: 26px; }
  .empty { padding: 40px 22px; }
  .modal__body { padding: 18px; }
  .modal__head { padding: 18px 20px; }
  .loading-row { padding: 30px; }
  .toast-wrap { left: 16px; right: 16px; bottom: 16px; max-width: none; }
  .ai-status span:not(.ai-status__dot) { display: none; }
  .ai-status { padding: 0; width: 30px; justify-content: center; }
  #uploadDocBtn span { display: none; }
  #uploadDocBtn { padding: 0 11px; }
  .brand__logo { height: 26px; }
  .navbar { padding: 0 14px; gap: 10px; }
}

/* Phones: four tabs need 538px and a 360px screen gives 320, so the strip
   scrolled — which hid Prescriptions entirely and sliced the Medicines label in
   half, with nothing on screen to suggest either existed. Two rows of two shows
   all four at full width instead. */
@media (max-width: 560px) {
  .ws__tabs {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px; overflow: visible; padding-bottom: 0;
  }
  .ws-tab { width: 100%; justify-content: center; padding: 0 10px; }

  /* Stack the record header: avatar beside the name, then badges, then actions,
     each starting on the same left edge. */
  .ws__head {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "avatar identity"
      "badges badges"
      "actions actions";
  }
  .ws__actions { justify-content: flex-start; }
}

/* Below this the centred title has under ~20px of clearance from the logo, so it
   gives way rather than crowd the controls. Measured: the collision starts at
   480px. The logo alone still brands the header, and the hospital name remains in
   the document title. */
@media (max-width: 520px) {
  .navbar__title { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  /* Entrance animations use `both` fill, so collapsing the duration alone would
     freeze them on their opening (invisible) frame. Neutralise them outright. */
  .hero__inner > div > .eyebrow,
  .hero__title, .hero__sub, .hero__cta, .hero__formats,
  .navbar { animation: none !important; opacity: 1; transform: none; }
  /* Ambient motion has no informational value — remove it rather than run it once. */
  .particles { display: none; }
  .dropzone { animation: none !important; }
  /* Hover lifts stay, but without the scale, which is the part that induces
     motion discomfort on large surfaces. */
  .card--hover:hover { transform: translateY(-4px); }
}
