:root {
  --bg: #f6f7fb;
  --fg: #1f2330;
  --muted: #6b7280;
  --line: #e5e7eb;
  --card: #ffffff;
  --primary: #0078d4;
  --primary-fg: #ffffff;
  --danger: #c62828;
  --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04);
}
* { box-sizing: border-box; }
/* Force the HTML `hidden` attribute to win over per-element `display` rules
   (e.g. .tile { display: block } would otherwise render tiles the dashboard
   search script marks with element.hidden = true). */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
body { font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: var(--fg); background: var(--bg); }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.error { color: var(--danger); }

.topbar { display: flex; align-items: center; gap: 1.25rem; padding: .75rem 1.25rem; background: var(--card); border-bottom: 1px solid var(--line); }
.topbar .brand a { color: var(--fg); font-weight: 700; }
.topbar .nav { display: flex; gap: 1rem; flex: 1; }
.topbar .nav a { color: var(--muted); padding: .25rem .25rem; border-bottom: 2px solid transparent; }
.topbar .nav a.active, .topbar .nav a:hover { color: var(--fg); border-bottom-color: var(--primary); text-decoration: none; }
.topbar .user { display: flex; align-items: center; gap: .75rem; font-size: 13px; color: var(--muted); }
.topbar .user em { font-style: normal; opacity: .8; }

.container { max-width: 1100px; margin: 1.5rem auto; padding: 0 1.25rem; }
.foot { text-align: center; color: var(--muted); padding: 2rem 0; }

.card { background: var(--card); padding: 1.5rem; border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); }
.centered { max-width: 420px; margin: 4rem auto; text-align: center; }

.btn { display: inline-block; padding: .5rem .9rem; border: 1px solid var(--line); border-radius: 6px; background: #fff; color: var(--fg); cursor: pointer; font-size: 14px; }
.btn:hover { border-color: #c8ccd4; text-decoration: none; }
.btn.primary { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
.btn.big { padding: .75rem 1.25rem; font-size: 16px; }
.btn.small { padding: .25rem .5rem; font-size: 12px; }
.btn.danger { color: var(--danger); border-color: #f0c8c8; }
.btn.link { background: transparent; border: 0; color: var(--primary); padding: 0; cursor: pointer; }
.inline { display: inline; }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
.tile { display: block; padding: 1rem; border-radius: 10px; background: var(--card); border: 1px solid var(--line); border-top: 4px solid var(--accent, var(--primary)); color: var(--fg); box-shadow: var(--shadow); transition: transform .08s ease, box-shadow .08s ease; }
.tile:hover { text-decoration: none; transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0,0,0,.07); }
.tile-name { font-weight: 600; }
.tile-desc { color: var(--muted); margin-top: .25rem; font-size: 13px; }
.tile-mode { color: var(--muted); margin-top: .5rem; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }

.viewer-bar { display: flex; align-items: center; gap: 1rem; padding: .5rem 0; }
.viewer-bar .viewer-title { flex: 1; font-weight: 600; }
.iframe-wrap { position: relative; height: calc(100vh - 220px); min-height: 480px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: #fff; }
.iframe-wrap iframe { width: 100%; height: 100%; border: 0; }

.page-head { display: flex; align-items: center; justify-content: space-between; }
.data { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.data th, .data td { padding: .55rem .75rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.data tr:last-child td { border-bottom: 0; }
.data th { background: #fafbfd; color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.row-actions { white-space: nowrap; }
.url { word-break: break-all; }

.form { display: grid; gap: .75rem; max-width: 560px; }
.form label { display: grid; gap: .25rem; font-weight: 500; }
.form input, .form select, .form textarea { padding: .5rem; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
.form fieldset { border: 1px solid var(--line); border-radius: 6px; padding: .75rem; }
.form fieldset legend { padding: 0 .25rem; color: var(--muted); font-size: 12px; }
.form .check { display: inline-flex; gap: .35rem; margin-right: 1rem; font-weight: 400; }
.form-actions { display: flex; gap: .5rem; margin-top: .25rem; }

.badge { display: inline-block; padding: .1rem .5rem; border-radius: 999px; background: #eef2f7; color: #334; font-size: 12px; }
.badge.role-SUPERADMIN { background: #fde8e8; color: #b00020; }
.badge.role-PENDING { background: #fff4d6; color: #8a6d00; }

.filters { display: flex; gap: .75rem; align-items: end; margin: .75rem 0; flex-wrap: wrap; }
.filters label { display: grid; gap: .25rem; font-size: 12px; color: var(--muted); }
.filters input, .filters select { padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 6px; }

.pager { display: flex; align-items: center; gap: 1rem; margin-top: 1rem; color: var(--muted); }

/* Dashboard: folder grouping + client-side filter/sort UI */
.dashboard-filters { align-items: end; }
.dashboard-filters #tile-search { width: 100%; }
.folder-chips { display: flex; flex-wrap: wrap; gap: .4rem; margin: .25rem 0 1rem; }
.folder-chip { padding: .3rem .8rem; border: 1px solid var(--line); border-radius: 999px; background: var(--card); color: var(--muted); font-size: 13px; cursor: pointer; }
.folder-chip:hover { color: var(--fg); border-color: #c8ccd4; }
.folder-chip.active { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); }
.folder-section { margin-bottom: 1.5rem; }
.folder-section:last-of-type { margin-bottom: 0; }
.folder-head { margin: 1rem 0 .5rem; font-size: 16px; font-weight: 600; color: var(--fg); border-bottom: 1px solid var(--line); padding-bottom: .35rem; }
.folder-empty { margin: .5rem 0 0; }

/* Users admin: search bar, compact grants popover, save-in-place indicators */
.users-filters { align-items: end; }
.users-filters #user-search { width: 100%; }
.users-table td.grants-cell { position: relative; }
.grants-wrap { position: relative; display: inline-flex; align-items: center; gap: .35rem; }
.grants-toggle { display: inline-flex; align-items: center; gap: .35rem; }
.grants-toggle-caret { font-size: 10px; color: var(--muted); }
.grants-popover {
  position: absolute; top: calc(100% + .35rem); right: 0;
  width: 300px; max-width: 90vw;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.10);
  z-index: 20;
}
.grants-popover-head { padding: .5rem .6rem; border-bottom: 1px solid var(--line); }
.grants-popover-head .grants-search { width: 100%; padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
.grants-list { max-height: 260px; overflow-y: auto; padding: .35rem .6rem; }
.grants-list .grants-check { display: flex; align-items: center; gap: .4rem; padding: .15rem 0; font-weight: 400; margin: 0; }
.grants-popover-foot { display: flex; gap: .5rem; padding: .5rem .6rem; border-top: 1px solid var(--line); }
.save-status { font-size: 12px; margin-left: .5rem; }
.save-status.ok  { color: #157347; }
.save-status.err { color: var(--danger); }

/* Office-scope popover — reuses .grants-popover geometry; only adds section
   headings and a bit more width because office names are longer. */
.scope-popover { width: 340px; }
.scope-section-head { padding: .35rem 0 .15rem; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); border-bottom: 1px solid var(--line); margin-top: .35rem; }
.scope-section-head:first-child { margin-top: 0; }

/* Appointment-count matrix (/appointment-count).
   Separate class from .data rather than an extension of it: sticky cells need
   border-collapse: separate (collapsed borders don't paint reliably under
   position: sticky — they belong to no single cell and drop out when the cell
   detaches), and flipping that on .data would restyle every existing table.
   The wrapper scrolls in BOTH axes so the sticky header has a vertical
   scrollport to stick to — with overflow-x alone the y axis computes to
   `auto` anyway and `top: 0` would resolve against the page instead. */
.matrix-wrap { max-height: 70vh; overflow: auto; border: 1px solid var(--line); border-radius: 8px; background: var(--card); }
.matrix { border-collapse: separate; border-spacing: 0; width: auto; min-width: 100%; font-size: 13px; }
.matrix th, .matrix td { padding: .3rem .55rem; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); text-align: right; white-space: nowrap; }
.matrix tr > *:last-child { border-right: 0; }
.matrix tbody tr:last-child > * { border-bottom: 0; }
/* Three stacked header rows (totals / weekday / date), all pinned. Each row
   needs its own `top`; these are static fallbacks sized to --mx-hrow, and the
   page script overwrites them with real measured offsets after every render so
   a font or zoom difference can't make the rows overlap. */
.matrix { --mx-hrow: 24px; }
.matrix thead th { position: sticky; z-index: 2; height: var(--mx-hrow); box-sizing: border-box; background: #fafbfd; color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.matrix thead tr.row-totals  th { top: 0; }
.matrix thead tr.row-weekday th { top: var(--mx-hrow); }
.matrix thead tr.row-date    th { top: calc(var(--mx-hrow) * 2); }
.matrix .office-col { position: sticky; left: 0; z-index: 1; background: var(--card); text-align: left; font-weight: 500; }
/* Corner cells are sticky on both axes, so they have to outrank both. */
.matrix thead th.office-col { z-index: 3; background: #fafbfd; }
/* Totals read as data, not as a label row — give them the foreground colour and
   normal digits rather than the uppercase muted treatment of the other two. */
.matrix thead tr.row-totals th { color: var(--fg); font-size: 12px; text-transform: none; letter-spacing: 0; }
.matrix thead tr.row-weekday th { text-transform: none; letter-spacing: 0; font-weight: 500; }
/* Stronger rule under the stack so the totals/weekday rows don't read as
   offices. 2px inset shadow rather than border-bottom: a border on a sticky
   cell scrolls away with the cell's own box in some engines. */
.matrix thead tr.row-date th { box-shadow: inset 0 -2px 0 var(--fg); }
.matrix td.empty { color: var(--muted); }
/* Filter bar: reuses .filters geometry. The count is pushed to the far end on
   a wide viewport but wraps onto its own line in a narrow iframe tile rather
   than squeezing the inputs. */
.showing-count { margin-left: auto; padding-bottom: .4rem; white-space: nowrap; }
/* Today strip — Completed lives here rather than in the measure toggle because
   it is only meaningful for the current date. Reads as a summary band, not as
   another table row. */
.today-strip { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; padding: .6rem .85rem; margin: .25rem 0 .75rem; background: var(--card); border: 1px solid var(--line); border-radius: 8px; }
.today-title { font-weight: 600; font-size: 13px; }
.today-title .muted { font-weight: 400; margin-left: .35rem; }
.today-metrics { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.today-metric { display: flex; align-items: baseline; gap: .4rem; }
.today-label { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.today-value { font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.today-none { font-size: 13px; }
.measure-caption { margin: 0 0 .35rem; }
#appt-filters input[type="search"] { min-width: 12rem; }
@media (max-width: 620px) { .showing-count { margin-left: 0; } #appt-filters input[type="search"] { min-width: 0; } }
.matrix tbody tr:hover td { background: #f4f7fb; }
.matrix tbody tr:hover td.office-col { background: #eef3fa; }

/* ---------------------------------------------------------------------------
   /appointment-analytics — chart cards and hand-rolled SVG charts.
   No chart library is vendored: everything is inline SVG built in the page
   script, which keeps script-src 'self' 'unsafe-inline' untouched and avoids
   versioning a static asset behind the 1-hour /static cache.
   --------------------------------------------------------------------------- */
.chart-card { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: .85rem 1rem 1rem; margin: 0 0 1rem; }
.chart-head h2 { font-size: 15px; margin: 0 0 .2rem; }
.chart-head p { margin: .2rem 0 .5rem; }
.chart-controls { display: flex; gap: .75rem; align-items: end; flex-wrap: wrap; margin: .35rem 0 .1rem; }
.chart-controls label { display: grid; gap: .25rem; font-size: 12px; color: var(--muted); }
.chart-controls label.check { display: flex; align-items: center; gap: .35rem; }
.chart-controls select { padding: .3rem .45rem; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
.chart-host { width: 100%; overflow: hidden; }
.chart-host svg { display: block; width: 100%; }
.chart-host text.ax { font-size: 10px; fill: var(--muted); }
.chart-host text.ax.val { fill: var(--fg); font-variant-numeric: tabular-nums; }
.chart-host text.ax-title { font-size: 10px; fill: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

/* Legend carries the same distinction the chart does — solid vs dashed vs
   hatched — so the series stay separable without relying on colour. */
.chart-legend { display: flex; gap: 1rem; flex-wrap: wrap; margin: .1rem 0 .4rem; font-size: 12px; color: var(--muted); }
.legend-item { display: inline-flex; align-items: center; gap: .35rem; }
.legend-swatch { width: 18px; height: 3px; border-radius: 2px; display: inline-block; }
.legend-swatch.dashed { background-image: linear-gradient(90deg, currentColor 60%, transparent 0); }
.legend-swatch.hatched { height: 10px; border: 1px solid var(--muted); background: repeating-linear-gradient(45deg, var(--muted) 0 2px, var(--card) 2px 4px) !important; }

.footnote { margin-top: 1rem; padding-top: .6rem; border-top: 1px solid var(--line); }
.warn { color: #8a5a00; background: #fff8e6; border: 1px solid #f0dca8; border-radius: 6px; padding: .5rem .65rem; }

/* Tooltip follows the pointer; fixed so it escapes the chart's overflow box. */
.chart-tip { position: fixed; z-index: 50; pointer-events: none; background: var(--fg); color: #fff; font-size: 12px; padding: .25rem .45rem; border-radius: 4px; max-width: 260px; }

/* ---------------------------------------------------------------------------
   /admin/role-sync — dry-run plan table. Colour is a hint only; every row also
   carries a text pill, because "why was this person skipped" must survive a
   monochrome print or a colour-blind reader.
   --------------------------------------------------------------------------- */
.sync-counts { display: flex; gap: 1.25rem; flex-wrap: wrap; margin: .5rem 0 .75rem; padding: .6rem .85rem; background: var(--card); border: 1px solid var(--line); border-radius: 8px; }
.sync-count { font-size: 12px; color: var(--muted); display: inline-flex; align-items: baseline; gap: .35rem; }
.sync-num { font-size: 20px; font-weight: 700; color: var(--fg); font-variant-numeric: tabular-nums; }
.sync-h2 { font-size: 15px; margin: 1.25rem 0 .35rem; }
.pill { display: inline-block; padding: .1rem .5rem; border-radius: 999px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }
.pill-promote { background: #e6f4ea; color: #14652b; }
.pill-skip    { background: #fff4d6; color: #8a6d00; }
.pill-none    { background: #eef2f7; color: #4a5568; }
tr.act-promote td { background: #fbfefc; }
tr.act-skip td    { background: #fffdf6; }
.branch-list { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: var(--muted); word-break: break-word; }

/* Doctor coverage range bar. Additive only — the preset buttons reuse .btn.small
   and need an "active" affordance, and the preset group needs to sit on the same
   baseline as the .filters labels beside it. */
.btn.active { border-color: var(--fg); font-weight: 600; }
.range-presets { display: inline-flex; gap: .35rem; padding-bottom: .4rem; }

/* Doctor coverage: per-doctor threshold bands and the draft marker.
   Tints are defined as variables so a future dark theme overrides them in ONE
   place. The app currently ships a single light theme (:root only, no
   prefers-color-scheme block anywhere in this file), so no dark override is
   declared here — a speculative dark tint would render dark green on a white
   card and fail contrast today rather than in some hypothetical future.
   Colour is never the only signal: the numeric value is always rendered. */
:root {
  --cov-green-bg:  #e6f6ec;  --cov-green-fg:  #10633a;
  --cov-yellow-bg: #fdf4d9;  --cov-yellow-fg: #7a5a06;
  --cov-red-bg:    #fdeaea;  --cov-red-fg:    #8c2020;
}
.matrix td.cov-green,  .matrix th.cov-green  { background: var(--cov-green-bg);  color: var(--cov-green-fg); }
.matrix td.cov-yellow, .matrix th.cov-yellow { background: var(--cov-yellow-bg); color: var(--cov-yellow-fg); }
.matrix td.cov-red,    .matrix th.cov-red    { background: var(--cov-red-bg);    color: var(--cov-red-fg); }
/* Draft marker is weight/opacity only — never a colour, so it cannot be
   confused with a threshold band when both apply to the same cell. */
.matrix td.cov-draft { font-style: italic; opacity: .72; }
.cov-swatch { display: inline-block; width: .7rem; height: .7rem; border-radius: 2px; vertical-align: middle; border: 1px solid var(--line); }
.cov-swatch.cov-green  { background: var(--cov-green-bg); }
.cov-swatch.cov-yellow { background: var(--cov-yellow-bg); }
.cov-swatch.cov-red    { background: var(--cov-red-bg); }

/* ---- Dashboard background (Appearance admin) --------------------------------
   Two selectors carry this feature, and BOTH are emitted only by
   views/dashboard.ejs and only when a background is set: .dash-bg-layer and
   .dash-surface. Nothing here targets body/html directly, so no other page can
   pick it up — see the `body:has(.dash-bg-layer)` note further down. */

/* The full-viewport backdrop: photo + scrim, one fixed layer behind everything.

   position:fixed rather than background-attachment:fixed on purpose. Fixed
   attachment is the janky option — iOS Safari repaints it badly on scroll and
   on rubber-band, which is why the previous version needed a
   `background-attachment: scroll` media query. A fixed LAYER gets the same
   parallax-free effect with none of that, on desktop and mobile alike.

   z-index:-1 puts it under all page content while still being visible: `body`
   has a background and `html` does not, so per CSS Backgrounds §2.11.2 the body
   background propagates to the canvas and paints below negative-z-index boxes
   rather than over this layer. Do not add a background to `html` — that would
   break the propagation and hide this layer entirely. */
.dash-bg-layer {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Scrim first so it stacks ON TOP of the photo. Both live on this one layer,
     so the scrim is full-viewport too and contrast holds edge to edge. */
  background-image:
    linear-gradient(180deg, rgba(16,20,30,.82) 0%, rgba(16,20,30,.68) 45%, rgba(16,20,30,.80) 100%),
    var(--dash-bg-image, none);
  background-position: center center, center center;
  background-repeat: no-repeat, no-repeat;
  background-size: cover, cover;
  /* Fallback behind a slow-loading or 404'd image, so light-on-dark text is
     never white-on-white. */
  background-color: #1f2330;
}

/* The scrim alphas above are not arbitrary. Worst case is a PURE WHITE photo at
   the gradient's thinnest point (.68), which composites to rgb(94,97,104);
   against that, #fff headings measure 6.37:1 and .88-alpha muted text 5.38:1 —
   both clear WCAG AA (4.5:1) for small text. Thinning the middle stop below
   ~.66 drops muted text under AA, so don't. test/background.js recomputes this
   from this stylesheet and fails if it regresses. */

/* Content surface. The layer is a sibling of the content, not an ancestor, so
   this wrapper is what the text rules below hang off. It also carries the
   translucency that lifts the tile sections off the photo — a touch more
   darkening on top of the scrim, which only ever helps the contrast figures. */
.dash-surface {
  position: relative;
  margin: -.5rem -1.25rem 0;
  padding: 1rem 1.25rem 1.5rem;
  border-radius: 12px;
  background: rgba(16,20,30,.24);
}

.dash-surface h1, .dash-surface .folder-head { color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.55); }
.dash-surface .folder-head { border-bottom-color: rgba(255,255,255,.28); }
.dash-surface .muted { color: rgba(255,255,255,.88); }
.dash-surface .filters label { color: rgba(255,255,255,.88); }

/* Tiles deliberately keep their solid --card background: tile text is read
   against the card, never against the photo. Only the shadow is deepened so
   they separate from the darker backdrop. */
.dash-surface .tile { box-shadow: 0 4px 14px rgba(0,0,0,.28); }
.dash-surface .tile:hover { box-shadow: 0 8px 22px rgba(0,0,0,.34); }

/* The footer sits OUTSIDE <main>, so going full-viewport put it over the scrim
   where its --muted grey measured ~1.2:1 — unreadable. It cannot be reached
   from .dash-surface (it is a later sibling of an ancestor), hence :has() on
   the body, which still matches only when the dashboard emitted the layer.
   Cosmetic-only if :has() is unsupported: the text stays legible-ish rather
   than the page breaking. The topbar needs no such rule — it keeps its own
   solid --card background, so its nav and Sign out are unchanged. */
body:has(.dash-bg-layer) .foot { color: rgba(255,255,255,.88); text-shadow: 0 1px 3px rgba(0,0,0,.5); }

/* ---- Appearance admin page -------------------------------------------------- */
.appearance-card { margin-top: 1rem; }
.appearance-h2 { margin: 0 0 .75rem; font-size: 15px; font-weight: 600; }
.appearance-notice { color: #1b5e20; }
.appearance-current { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
.appearance-thumb {
  width: 260px; max-width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border: 1px solid var(--line); border-radius: 8px; background: #1f2330;
}
.appearance-meta { display: grid; gap: .35rem; }
.appearance-remove { margin-top: .5rem; }
.appearance-form { display: flex; gap: .75rem; align-items: end; flex-wrap: wrap; }
.appearance-file { display: grid; gap: .25rem; }
.appearance-rules { margin-top: .75rem; max-width: 60ch; }
