/* ===========================================================================
   DentaCare admin portal stylesheet
   Palette: blue (#0284c7) / teal (#0d9488) / white, slate neutrals.
   =========================================================================== */

:root {
    --blue-600: #0284c7;
    --blue-700: #0369a1;
    --blue-50:  #f0f9ff;
    --teal-600: #0d9488;
    --teal-50:  #f0fdfa;
    --slate-25:  #fcfdfe;
    --slate-50:  #f8fafc;
    --slate-100: #f1f5f9;
    --slate-200: #e2e8f0;
    --slate-300: #cbd5e1;
    --slate-400: #94a3b8;
    --slate-500: #64748b;
    --slate-600: #475569;
    --slate-700: #334155;
    --slate-800: #1e293b;
    --slate-900: #0f172a;
    --green-600: #059669;
    --green-100: #dcfce7;
    --rose-600: #e11d48;
    --rose-100: #ffe4e6;
    --amber-600: #d97706;
    --amber-100: #fef3c7;
    --violet-600: #7c3aed;
    --violet-100: #ede9fe;

    /* Brand theme tokens (Settings -> Branding). Every value falls back to
       the shipped palette; partials/theme-vars.php overrides only the tokens
       a clinic actually customised, so stock installs (and their dark-mode
       remaps) behave exactly as before. */
    --brand-primary: var(--blue-600);
    --brand-primary-hover: var(--blue-700);
    --brand-sidebar-bg: #0f172a;
    --brand-sidebar-link: #cbd5e1;
    --brand-topbar-bg: var(--surface-glass);
    --brand-heading: var(--slate-900);
    --brand-heading-card: var(--slate-800);
    --brand-danger: var(--rose-600);
    --brand-cancel: var(--slate-500);
    --brand-modal-head-bg: transparent;
    --brand-modal-head-fg: inherit;

    /* Height of the sticky header, so anything else that sticks can sit under
       it rather than behind it. The navbar layout overrides it below - it
       stacks a menu bar on top of the same top bar. */
    --headbar-h: 3.6rem;
    --savebar-h: 3.4rem;

    --radius: 10px;
    --radius-sm: 7px;
    --shadow-sm: 0 1px 2px rgb(15 23 42 / .06);
    --shadow: 0 1px 3px rgb(15 23 42 / .08), 0 4px 14px rgb(15 23 42 / .06);
    --shadow-lg: 0 8px 30px rgb(15 23 42 / .16);
    --sidebar-w: 248px;
    --font: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;

    /* Semantic theme tokens (remapped by [data-theme="dark"]) */
    --surface: #ffffff;
    --surface-glass: rgb(255 255 255 / .92);
    --body-bg: #f1f5f9;
    --auth-glass-bg: rgb(255 255 255 / .85);
    --auth-glass-border: rgb(255 255 255 / .7);

    color-scheme: light;
}

/* ==========================================================================
   Dark theme: remaps the neutral scale and tint tokens. Fixed-dark elements
   (sidebar, toasts, primary buttons) are pinned to literals below and are
   unaffected.
   ========================================================================== */
[data-theme="dark"] {
    --surface: #111a2b;
    --surface-glass: rgb(15 22 37 / .92);
    --body-bg: #0a101d;
    --auth-glass-bg: rgb(17 26 43 / .88);
    --auth-glass-border: rgb(255 255 255 / .08);

    --slate-25:  #162032;
    --slate-50:  #182336;
    --slate-100: #1c2942;
    --slate-200: #273550;
    --slate-300: #354765;
    --slate-400: #7f90a8;
    --slate-500: #97a7bd;
    --slate-600: #b4c1d3;
    --slate-700: #ccd6e4;
    --slate-800: #e5ebf4;
    --slate-900: #f4f7fb;

    --blue-50:   #14293f;
    --blue-700:  #38bdf8;
    --teal-50:   #0f2b29;
    --green-100: #113321;
    --rose-100:  #3a1725;
    --amber-100: #34270f;
    --violet-100: #271c3f;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / .35);
    --shadow: 0 1px 3px rgb(0 0 0 / .4), 0 4px 14px rgb(0 0 0 / .3);
    --shadow-lg: 0 8px 30px rgb(0 0 0 / .55);

    color-scheme: dark;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Everything in this stylesheet is sized in rem from this one value, so
   Settings -> Branding -> Text size scales the whole system by overriding a
   single token. Fixed pixel widths (rails, icon buttons) deliberately do NOT
   scale - which is why the size choices stop at 125% and why anything holding
   a label has to be allowed to wrap. */
html { font-size: calc(15px * var(--font-scale, 1)); }

body {
    font-family: var(--font);
    color: var(--slate-800);
    background: var(--body-bg);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; }
canvas { max-width: 100%; }
a { color: var(--blue-600); text-decoration: none; }
a:hover { color: var(--blue-700); }

/* ------------------------------------------------------------------ layout */

.admin-shell { display: flex; min-height: 100vh; }

.admin-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    margin-left: var(--sidebar-w);
}

/* No width cap. The shell used to stop at 1560px, which on a 24" screen left
   a strip of empty desk and on a 27" left a third of the monitor unused - and
   this is a tables-and-forms application, where the width is the feature: more
   columns visible without scrolling, more of a diary on screen at once.

   Prose is the exception, and it already carries its own measure: the user
   guide sets .guide-article { max-width: 62rem } so a paragraph never runs the
   full width of a 34" ultrawide. Anything else prose-heavy should do the same
   rather than re-capping the whole shell. */
.admin-content { flex: 1; padding: 1.4rem 1.6rem; width: 100%; min-width: 0; }

.admin-footer {
    display: flex; justify-content: space-between;
    padding: .8rem 1.6rem; color: var(--slate-400); font-size: .8rem;
    border-top: 1px solid var(--slate-200);
}

/* ----------------------------------------------------------------- sidebar */

/* The sidebar is fixed-dark in both themes, so every colour inside it is a
   literal - theme-remapped tokens like var(--slate-300) flip dark in dark
   mode and would vanish against this background. */
.sidebar {
    position: fixed; inset: 0 auto 0 0;
    width: var(--sidebar-w);
    background: var(--brand-sidebar-bg);
    color: var(--brand-sidebar-link);
    display: flex; flex-direction: column;
    z-index: 50;
}

.sidebar-brand {
    display: flex; align-items: center; justify-content: space-between;
    padding: 1rem 1.1rem; border-bottom: 1px solid rgb(255 255 255 / .08);
}

.brand-link { display: flex; align-items: center; gap: .6rem; color: #fff; font-weight: 700; font-size: 1.02rem; }
.brand-mark { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 10px; background: var(--brand-primary); }
.brand-mark svg { width: 20px; height: 20px; color: #fff; }
/* The clinic name beside the mark. It took its ink from .brand-link and had no
   rule of its own, so a long name pushed itself out of the fixed-width sidebar
   instead of ending in an ellipsis - and the name is set by the clinic, not by
   us, so there is no length we get to assume. */
.brand-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Uploaded org logo (Settings > clinic_logo). Falls back to .brand-mark above.
   The sidebar is dark and most logos are drawn for white paper, so the logo
   sits on a light chip - a dark-on-transparent logo stays readable without
   asking every clinic to produce a dark-mode variant. */
.brand-logo {
    display: block; height: 38px; width: auto; max-width: 172px; object-fit: contain;
    background: rgb(255 255 255 / .92); border-radius: 8px; padding: .25rem .5rem;
}
.sidebar-close { display: none; background: none; border: 0; color: #cbd5e1; font-size: 1.5rem; cursor: pointer; }

.sidebar-nav { flex: 1; overflow-y: auto; padding: .8rem .7rem; }
/* Both scrollbar syntaxes: modern browsers honour scrollbar-color (and ignore
   the ::-webkit rules once it is set), older WebKit uses the pseudo-elements.
   Either way the bar blends into the dark sidebar instead of standing out. */
.sidebar-nav { scrollbar-width: thin; scrollbar-color: rgb(255 255 255 / .22) transparent; }
.sidebar-nav::-webkit-scrollbar { width: 6px; }
.sidebar-nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-nav::-webkit-scrollbar-thumb { background: rgb(255 255 255 / .15); border-radius: 3px; }

.nav-heading {
    padding: 1rem .6rem .35rem;
    font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
    color: #64748b;
}

.nav-item {
    display: flex; align-items: center; gap: .65rem;
    padding: .52rem .65rem; margin-bottom: 2px;
    border-radius: var(--radius-sm);
    color: var(--brand-sidebar-link); font-size: .92rem;
    transition: background .15s, color .15s;
}
.nav-item:hover { background: rgb(255 255 255 / .07); color: #fff; }
.nav-item.is-active { background: var(--brand-primary); color: #fff; font-weight: 600; }

.nav-icon { display: inline-grid; place-items: center; width: 19px; height: 19px; flex: none; }
.nav-icon svg { width: 100%; height: 100%; }

.sidebar-foot { padding: .8rem 1rem; border-top: 1px solid rgb(255 255 255 / .08); }
.branch-pill { display: flex; align-items: center; gap: .5rem; font-size: .82rem; color: #94a3b8; }

.sidebar-backdrop { position: fixed; inset: 0; background: rgb(15 23 42 / .5); z-index: 45; }

/* Desktop collapse: the topbar hamburger tucks the sidebar away entirely. */
.sidebar { transition: transform .22s ease; }
.admin-main { transition: margin-left .22s ease; }
body.sidebar-collapsed .sidebar { transform: translateX(-100%); }
body.sidebar-collapsed .admin-main { margin-left: 0; }

/* --------------------------------------------------- top navigation layout */

/* Settings -> Menu layout = "navbar". The sidebar is not rendered at all, so
   the main column reclaims the gutter the fixed aside used to occupy. */
.admin-shell-navbar .admin-main { margin-left: 0; }
/* Two stacked bars, not one, so everything else that sticks has further to
   clear. Declared on the shell rather than measured in JS: it is a layout
   choice, known at render time. */
.admin-shell-navbar { --headbar-h: 6.6rem; }

/* The menu and the top bar stick as ONE unit. Two separately-sticky bars both
   pin to top: 0 and the search bar parks on top of the menu the moment the
   page scrolls; offsetting the second one by the first one's height only works
   while that height is a constant, which it is not once the menu wraps. */
.admin-headbar { position: sticky; top: 0; z-index: 50; }
.admin-headbar .topnav, .admin-headbar .topbar { position: static; }

/* The bar carries the sidebar's own colours: choosing the top layout must not
   silently discard the sidebar colours the clinic already picked, and the two
   layouts should be recognisably the same product. */
.topnav {
    display: flex; align-items: center; gap: 1rem;
    padding: 0 1.6rem;
    background: var(--brand-sidebar-bg);
    color: var(--brand-sidebar-link);
}

.topnav-brand { padding: .4rem 0; flex: none; }
.topnav-brand .brand-link { font-size: .95rem; }
.topnav-brand .brand-mark { width: 30px; height: 30px; border-radius: 8px; }
.topnav-brand .brand-logo { height: 30px; }
/* Scoped to .topnav on purpose. `.icon-btn` sets `display: inline-grid` and is
   declared further down this file, so a bare `.topnav-toggle { display: none }`
   ties on specificity and LOSES on source order - which showed the hamburger at
   every width, including the desktop one where there is nothing for it to open. */
.topnav .topnav-toggle { display: none; margin-left: auto; color: var(--brand-sidebar-link); }

/* Wrapping, not scrolling: a horizontally scrollable strip computes its
   overflow-y as auto too, which clips the group dropdowns off at the bar's own
   edge and makes the menu unusable. Grouping the sections behind eight
   dropdowns means a second row is rare in practice, and the sticky wrapper
   above absorbs it when it happens. */
.topnav-menu { display: flex; align-items: stretch; flex-wrap: wrap; gap: .15rem; }

.topnav-item {
    display: flex; align-items: center; gap: .45rem;
    padding: .7rem; border: 0; background: none;
    color: var(--brand-sidebar-link); font: inherit; font-size: .9rem;
    white-space: nowrap; cursor: pointer;
    border-bottom: 3px solid transparent;
    transition: background .15s, color .15s;
}
.topnav-item:hover { background: rgb(255 255 255 / .07); color: #fff; }
.topnav-item.is-active { color: #fff; font-weight: 600; border-bottom-color: var(--brand-primary); }
.topnav-caret { width: 14px; height: 14px; opacity: .7; }
.topnav-group { display: flex; }
/* The group dropdown hangs off the bar itself, not off the button's padding. */
.topnav-dropdown { top: 100%; left: 0; right: auto; min-width: 230px; max-height: 70vh; overflow-y: auto; }

@media (max-width: 860px) {
    /* Collapse to a stack the hamburger reveals. The bar keeps the brand and
       the toggle; the menu becomes a full-width panel below it. */
    .topnav { flex-wrap: wrap; padding: 0 1rem; }
    .topnav .topnav-toggle { display: grid; }
    /* The expanded stack can be taller than the phone, and it lives inside a
       sticky wrapper - so it scrolls itself rather than becoming unreachable.
       Safe here only because the group dropdowns are static at this width. */
    .topnav-menu { display: none; flex-direction: column; width: 100%; padding-bottom: .5rem; max-height: calc(100vh - 7rem); overflow-y: auto; }
    .topnav.is-open .topnav-menu { display: flex; }
    .topnav-item { width: 100%; padding: .6rem .5rem; border-bottom: 0; }
    .topnav-item.is-active { background: var(--brand-primary); border-radius: var(--radius-sm); }
    .topnav-group { display: block; }
    .topnav-group .topnav-item { justify-content: space-between; }
    .topnav-dropdown { position: static; box-shadow: none; border: 0; background: transparent; margin: 0 0 .3rem .6rem; }
    .topnav-dropdown .dropdown-item { color: var(--brand-sidebar-link); }
    .topnav-dropdown .dropdown-item:hover { background: rgb(255 255 255 / .07); color: #fff; }
}

/* ------------------------------------------------------------------ topbar */

.topbar {
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; gap: 1rem;
    padding: .6rem 1.6rem;
    background: var(--brand-topbar-bg);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--slate-200);
    /* The bar owns an ink of its own. Without this, anything not named below
       falls through to the BODY colour - a page-content ink, not an
       on-the-bar one - or, inside a <button>, to the UA's `buttontext`. That
       is what left the account chevron invisible. */
    color: var(--brand-topbar-fg, var(--slate-700));
}

/* Text and icons that sit DIRECTLY on the topbar follow its contrast token
   (emitted for every custom bar, light or dark); controls with their own
   surface (search pill, branch switcher, dropdown panels) keep their look. */
.topbar .icon-btn { color: var(--brand-topbar-fg, var(--slate-500)); }
/* Hover must not discard the bar's ink: the generic .icon-btn:hover repaints
   to --slate-800, which on a dark bar is dark-on-dark. */
.topbar .icon-btn:hover { color: var(--brand-topbar-fg, var(--slate-800)); }
.topbar .dropdown-menu .icon-btn,
.topbar .dropdown-menu .icon-btn:hover { color: var(--slate-500); }
.topbar .user-chip-name { color: var(--brand-topbar-fg, var(--slate-700)); }

.topbar-menu { display: grid; }

.global-search { position: relative; flex: 1; max-width: 460px; }
.global-search .search-icon { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--slate-400); pointer-events: none; }
.global-search input {
    width: 100%; padding: .5rem .8rem .5rem 2.2rem;
    border: 1px solid var(--slate-200); border-radius: 99px;
    background: var(--slate-50); font-size: .88rem; font-family: inherit;
}
.global-search input:focus { outline: 2px solid var(--blue-600); outline-offset: -1px; background: var(--surface); }

.search-results {
    position: absolute; top: calc(100% + 6px); left: 0; right: 0;
    background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); max-height: 380px; overflow-y: auto; z-index: 60;
}
/* The results panel is absolutely positioned, so every host needs to be a
   containing block. The topbar search gets this from .global-search; in-form
   pickers would otherwise resolve against the page and render a full
   viewport below the field - invisible. */
[data-component="patient-picker"] { position: relative; }
/* A caret marks the field as openable; without it the control reads as a
   plain text box and nobody discovers the list. */
[data-component="patient-picker"] [data-role="patient-input"] {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .7rem center;
    background-size: 16px;
    padding-right: 2.2rem;
}
/* Inside a modal the card scrolls, so keep the panel short enough to sit
   within it rather than forcing a scroll to see the first match. */
.modal-body .search-results { max-height: 240px; }
.search-result { display: block; width: 100%; text-align: left; background: none; border: 0; font: inherit; padding: .6rem .9rem; border-bottom: 1px solid var(--slate-100); cursor: pointer; }
.search-result:hover, .search-result.is-focused { background: var(--blue-50); }
.search-result strong { color: var(--slate-800); display: block; }
.search-result small { color: var(--slate-500); }
.search-empty { padding: .9rem; color: var(--slate-500); text-align: center; font-size: .88rem; }
.search-empty.search-error { color: var(--rose-600); background: var(--rose-100); }
.search-heading {
    padding: .45rem .9rem; font-size: .72rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--slate-400);
    background: var(--slate-50); border-bottom: 1px solid var(--slate-100);
}

.topbar-actions { display: flex; align-items: center; gap: .5rem; margin-left: auto; }

.branch-switch {
    display: flex; align-items: center; gap: .45rem;
    padding: .45rem .8rem; border: 1px solid var(--slate-200); border-radius: 99px;
    background: var(--surface); cursor: pointer; font: inherit; font-size: .85rem; color: var(--slate-700);
}
.branch-switch:hover { border-color: var(--blue-600); color: var(--blue-600); }
.branch-switch [data-icon] { width: 15px; height: 15px; }

/* `color: inherit` is load-bearing: a <button> otherwise keeps the UA's
   `buttontext`, and the bare chevron inside draws with currentColor. */
.user-chip { display: flex; align-items: center; gap: .5rem; background: none; border: 0; color: inherit; cursor: pointer; font: inherit; padding: .25rem .4rem; border-radius: 99px; }
.user-chip:hover { background: var(--slate-100); }
.user-chip-name { font-size: .88rem; font-weight: 600; color: var(--slate-700); }

/* ----------------------------------------------------------------- avatars */

.avatar { border-radius: 50%; object-fit: cover; display: inline-grid; place-items: center; }
.avatar-sm { width: 32px; height: 32px; font-size: .72rem; }
.avatar-lg { width: 64px; height: 64px; font-size: 1.3rem; }
.avatar-xl { width: 96px; height: 96px; font-size: 1.8rem; }
.avatar-fallback { background: var(--blue-600); color: #fff; font-weight: 700; }

/* --------------------------------------------------------------- dropdowns */

.dropdown { position: relative; }
.dropdown-menu {
    position: absolute; top: calc(100% + 8px); left: 0;
    min-width: 230px; background: var(--surface);
    border: 1px solid var(--slate-200); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); padding: .35rem; z-index: 70;
}
.dropdown-menu-right { left: auto; right: 0; }
/* Set by initDropdowns() on a menu that would open off the right of the screen.
   Written as a compound selector so it also beats `.topnav-dropdown`, which
   pins the navbar's group menus to their button's left edge. */
.dropdown-menu.is-flipped { left: auto; right: 0; }
/* Never wider than the viewport: the bell sits near the right edge, so an
   unclamped 340px panel walks off screen on small windows. */
/* The floor is clamped as well as the ceiling. `min-width` BEATS `max-width` in
   the cascade, so a bare 340px floor won the moment the viewport dropped below
   about 364px and the notifications panel hung off the edge of the screen on a
   small phone - the one place the max-width was written to prevent. */
.dropdown-menu-wide { min-width: min(340px, calc(100vw - 1.5rem)); max-width: min(380px, calc(100vw - 1.5rem)); }
.dropdown-menu-wide .dropdown-item { white-space: normal; overflow-wrap: anywhere; }
.dropdown-heading { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; padding: .55rem .75rem .35rem; font-size: .8rem; font-weight: 700; color: var(--slate-500); }
.dropdown-item {
    display: flex; align-items: center; gap: .55rem; width: 100%;
    padding: .5rem .75rem; border: 0; background: none; text-align: left;
    border-radius: var(--radius-sm); font: inherit; font-size: .88rem; color: var(--slate-700); cursor: pointer;
}
.dropdown-item:hover { background: var(--slate-100); color: var(--slate-900); }
.dropdown-item.is-active { background: var(--blue-50); color: var(--blue-700); font-weight: 600; }
.dropdown-item-danger { color: var(--rose-600); }
.dropdown-item-danger:hover { background: var(--rose-100); color: var(--rose-600); }
.dropdown-item-center { justify-content: center; color: var(--blue-600); }
.dropdown-divider { height: 1px; background: var(--slate-200); margin: .35rem 0; }
.dropdown-empty { padding: 1rem; text-align: center; color: var(--slate-400); font-size: .85rem; }
.dropdown-item [data-icon] { width: 16px; height: 16px; color: var(--slate-500); }

.badge-dot {
    position: absolute; top: 4px; right: 4px;
    min-width: 16px; height: 16px; padding: 0 4px;
    background: var(--rose-600); color: #fff; font-size: .65rem; font-weight: 700;
    border-radius: 99px; display: grid; place-items: center;
}

.notif-preview { display: flex; gap: .6rem; padding: .55rem .75rem; border-radius: var(--radius-sm); }
.notif-preview:hover { background: var(--slate-100); }
.notif-preview.is-unread { background: var(--blue-50); }
.notif-preview strong { font-size: .85rem; display: block; }
.notif-preview small { color: var(--slate-500); font-size: .76rem; }

/* --------------------------------------------------------------- page head */

.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
.page-title { font-size: 1.35rem; font-weight: 700; color: var(--brand-heading); }
.page-actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
.page-actions form { display: inline; }

/* Three finance screens (numbering register, periods, statements) hand-roll a
   header instead of setting $pageTitle, and spelled it `.page-header` with a
   `.page-subtitle` - neither of which any stylesheet defined, so the heading
   rendered as bare unstyled text with no wrap rules of its own. Aliased rather
   than renamed in the views: the markup carries an explanatory paragraph the
   layout's own header has nowhere to put. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
.page-header .page-title { margin-bottom: .25rem; }
.page-subtitle { color: var(--slate-500); font-size: .88rem; max-width: 62rem; }

/* ------------------------------------------------------------------- cards */

.card { background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius); box-shadow: var(--shadow-sm); margin-bottom: 1.1rem; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .9rem 1.15rem; border-bottom: 1px solid var(--slate-100); flex-wrap: wrap; }
.card-head h2 { font-size: 1rem; font-weight: 700; color: var(--brand-heading-card); }
.card-body { padding: 1.15rem; }
.card-body-flush { padding: 0; }
.card-foot { display: flex; justify-content: flex-end; gap: .6rem; padding: .85rem 1.15rem; border-top: 1px solid var(--slate-100); background: var(--slate-25); border-radius: 0 0 var(--radius) var(--radius); flex-wrap: wrap; }
.card-foot-start { justify-content: flex-start; }
.card-foot-between { justify-content: space-between; align-items: center; }
.card-foot form { display: inline; }

.stack > * { margin-bottom: 1.1rem; }

/* ------------------------------------------------------------------- grids */

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; align-items: start; }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: 1.1rem; align-items: start; }
/* The grid strips its children's bottom margin so the two columns line up -
   which left the grid itself flush against whatever follows it, because a card
   normally supplies that gap itself. Put it back on the grid, so a card AFTER
   a two-column row is spaced exactly like one after another card. */
.grid-2 > .card, .grid-2-1 > .card { margin-bottom: 0; }
.grid-2, .grid-2-1 { margin-bottom: 1.1rem; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .9rem; margin-bottom: 1.1rem; }
.stat-grid-4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.stat-card {
    display: flex; gap: .85rem; align-items: center;
    background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius);
    padding: .95rem 1.05rem; box-shadow: var(--shadow-sm);
    color: inherit; transition: transform .12s, box-shadow .12s;
}
a.stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.stat-icon { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; flex: none; }
.stat-icon [data-icon] { width: 22px; height: 22px; }
.stat-value { font-size: 1.3rem; font-weight: 800; color: var(--slate-900); line-height: 1.15; }
.stat-label { font-size: .8rem; color: var(--slate-500); font-weight: 600; }
.stat-sub { font-size: .74rem; color: var(--slate-400); }

.stat-blue .stat-icon   { background: var(--blue-50);   color: var(--blue-600); }
.stat-teal .stat-icon   { background: var(--teal-50);   color: var(--teal-600); }
.stat-green .stat-icon  { background: var(--green-100); color: var(--green-600); }
.stat-rose .stat-icon   { background: var(--rose-100);  color: var(--rose-600); }
.stat-amber .stat-icon  { background: var(--amber-100); color: var(--amber-600); }
.stat-violet .stat-icon { background: var(--violet-100); color: var(--violet-600); }
.stat-slate .stat-icon  { background: var(--slate-100); color: var(--slate-600); }

/* ----------------------------------------------------------------- buttons */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
    padding: .52rem 1rem; border-radius: var(--radius-sm);
    font: inherit; font-size: .88rem; font-weight: 600; cursor: pointer;
    border: 1px solid transparent; transition: all .14s; white-space: nowrap;
}
.btn:disabled { opacity: .45; cursor: not-allowed; pointer-events: auto;
}
.btn [data-icon] { width: 16px; height: 16px; }
.btn-lg { padding: .68rem 1.5rem; font-size: .95rem; }
.btn-sm { padding: .3rem .7rem; font-size: .8rem; }
.btn-block { width: 100%; }

.btn-primary { background: var(--brand-primary); color: #fff; }
.btn-primary:hover { background: var(--brand-primary-hover); color: #fff; }
.btn-secondary { background: #1e293b; color: #fff; }
.btn-secondary:hover { background: #0f172a; color: #fff; }
.btn-outline { background: var(--surface); border-color: var(--slate-300); color: var(--slate-700); }
.btn-outline:hover { border-color: var(--brand-primary); color: var(--brand-primary); }
.btn-outline.is-active { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }
.btn-ghost { background: transparent; color: var(--brand-cancel); }
.btn-ghost:hover { background: var(--slate-100); color: var(--slate-700); }
.btn-danger-outline { background: var(--surface); border-color: var(--brand-danger); color: var(--brand-danger); }
.btn-danger-outline:hover { background: var(--brand-danger); color: #fff; }
.btn-group { display: inline-flex; }
.btn-group .btn { border-radius: 0; margin-left: -1px; }
.btn-group .btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); margin-left: 0; }
.btn-group .btn:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* --------------------------------------------------- branded action buttons */

/* The four controls a receptionist reaches for all day - Export, View, Edit,
   Book - each carry their own colour from Settings -> Branding.
   Every declaration reads its token through a var() fallback that restates
   what `.btn-outline` already does, so these classes are inert until a colour
   is actually chosen: no colour set, no visual change, and the rules must sit
   after `.btn-outline` because they share its specificity. */
.btn-export, .btn-view, .btn-edit, .btn-book { background: var(--surface); }

.btn-export { border-color: var(--brand-export-edge, var(--slate-300)); color: var(--brand-export, var(--slate-700)); }
.btn-export:hover {
    background: var(--brand-export, transparent);
    border-color: var(--brand-export, var(--brand-primary));
    color: var(--brand-export-on, var(--brand-primary));
}
.btn-view { border-color: var(--brand-view-edge, var(--slate-300)); color: var(--brand-view, var(--slate-700)); }
.btn-view:hover {
    background: var(--brand-view, transparent);
    border-color: var(--brand-view, var(--brand-primary));
    color: var(--brand-view-on, var(--brand-primary));
}
.btn-edit { border-color: var(--brand-edit-edge, var(--slate-300)); color: var(--brand-edit, var(--slate-700)); }
.btn-edit:hover {
    background: var(--brand-edit, transparent);
    border-color: var(--brand-edit, var(--brand-primary));
    color: var(--brand-edit-on, var(--brand-primary));
}
.btn-book { border-color: var(--brand-book-edge, var(--slate-300)); color: var(--brand-book, var(--slate-700)); }
.btn-book:hover {
    background: var(--brand-book, transparent);
    border-color: var(--brand-book, var(--brand-primary));
    color: var(--brand-book-on, var(--brand-primary));
}

/* A filled Edit/Book still has to look filled - `.btn-primary` sets the
   background itself, so the branded colour has to win over it explicitly. */
.btn-primary.btn-edit { background: var(--brand-edit, var(--brand-primary)); border-color: transparent; color: #fff; }
.btn-primary.btn-edit:hover { background: var(--brand-edit-strong, var(--brand-primary-hover)); color: #fff; }
.btn-primary.btn-book { background: var(--brand-book, var(--brand-primary)); border-color: transparent; color: #fff; }
.btn-primary.btn-book:hover { background: var(--brand-book-strong, var(--brand-primary-hover)); color: #fff; }

/* min- rather than fixed, and flex rather than grid: an icon-only button is
   still exactly 32px square, but one that carries a label grows to hold it
   instead of stacking it underneath and spilling out of the box. */
.icon-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
    min-width: 32px; min-height: 32px; padding: 0 .35rem;
    border: 0; background: none; border-radius: var(--radius-sm);
    color: var(--slate-500); cursor: pointer; font-size: 1rem;
}
.icon-btn:hover { background: var(--slate-100); color: var(--slate-800); }
.icon-btn-danger:hover { background: var(--rose-100); color: var(--brand-danger); }
/* Row actions: the same four colours, on the icon-only form used inside tables. */
.icon-btn-view { color: var(--brand-view, var(--slate-500)); }
.icon-btn-view:hover { background: var(--brand-view-soft, var(--slate-100)); color: var(--brand-view-strong, var(--slate-800)); }
.icon-btn-edit { color: var(--brand-edit, var(--slate-500)); }
.icon-btn-edit:hover { background: var(--brand-edit-soft, var(--slate-100)); color: var(--brand-edit-strong, var(--slate-800)); }
.icon-btn-book { color: var(--brand-book, var(--slate-500)); }
.icon-btn-book:hover { background: var(--brand-book-soft, var(--slate-100)); color: var(--brand-book-strong, var(--slate-800)); }
.icon-btn-export { color: var(--brand-export, var(--slate-500)); }
.icon-btn-export:hover { background: var(--brand-export-soft, var(--slate-100)); color: var(--brand-export-strong, var(--slate-800)); }
.icon-btn [data-icon] { width: 17px; height: 17px; }

/* ------------------------------------------- row actions: icon AND label ---
   Inside a table's action column every control reads as a small button: the
   icon says what it does at a glance, the label removes the guessing, and both
   sit on a surface the clinic picks under Settings -> Branding.

   The colour tokens are the ones the four action kinds already used for their
   icon tint (--brand-view / -edit / -book / -export, each with a -soft wash, a
   -strong ink and an -on foreground for the filled state). Nothing new is
   invented here - the button form just uses the wash as a RESTING background
   instead of only on hover, so a themed clinic sees its colours without having
   to hover to find them.

   Every value falls back to a neutral slate. That matters: the shipped look
   emits no --brand-* overrides at all (the white sentinel), and the suite
   asserts the stock table is untouched. A clinic that themes nothing must see
   exactly what it saw before, in button form.                              */
.td-actions .icon-btn,
.td-actions .btn-sm,
.row-actions .icon-btn,
.row-actions .btn-sm {
    display: inline-flex; align-items: center; gap: .35rem;
    width: auto; height: auto; padding: .32rem .6rem;
    border: 1px solid var(--slate-200); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--slate-600);
    font-size: .76rem; font-weight: 600; line-height: 1.45; white-space: nowrap;
}
.td-actions .icon-btn [data-icon],
.td-actions .btn-sm [data-icon],
.row-actions .icon-btn [data-icon],
.row-actions .btn-sm [data-icon] { width: 15px; height: 15px; flex: none; }
.td-actions .icon-btn:hover,
.row-actions .icon-btn:hover { background: var(--slate-100); color: var(--slate-900); border-color: var(--slate-300); }

/* The action column can hold three or four of these; let them wrap rather than
   push the table sideways. */
.td-actions { line-height: 2.1; }
.td-actions form { display: inline-block; }

.row-action-label { white-space: nowrap; }

/* The same treatment for a labelled action that is NOT in a table's action
   column - a timeline entry, a note header. Put it on any ancestor of the
   buttons; the form element itself is usually the closest one. */
.row-actions { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; vertical-align: middle; }

/* Per-kind branding. Resting state: the soft wash, the picked ink.
   Hover: filled with the picked colour, on its derived readable foreground. */
.td-actions .icon-btn-view,
.row-actions .icon-btn-view {
    background: var(--brand-view-soft, var(--surface));
    border-color: var(--brand-view-soft, var(--slate-200));
    color: var(--brand-view, var(--slate-600));
}
.td-actions .icon-btn-view:hover,
.row-actions .icon-btn-view:hover {
    background: var(--brand-view, var(--slate-100));
    border-color: var(--brand-view, var(--slate-300));
    color: var(--brand-view-on, var(--slate-900));
}
.td-actions .icon-btn-edit,
.row-actions .icon-btn-edit {
    background: var(--brand-edit-soft, var(--surface));
    border-color: var(--brand-edit-soft, var(--slate-200));
    color: var(--brand-edit, var(--slate-600));
}
.td-actions .icon-btn-edit:hover,
.row-actions .icon-btn-edit:hover {
    background: var(--brand-edit, var(--slate-100));
    border-color: var(--brand-edit, var(--slate-300));
    color: var(--brand-edit-on, var(--slate-900));
}
.td-actions .icon-btn-book,
.row-actions .icon-btn-book {
    background: var(--brand-book-soft, var(--surface));
    border-color: var(--brand-book-soft, var(--slate-200));
    color: var(--brand-book, var(--slate-600));
}
.td-actions .icon-btn-book:hover,
.row-actions .icon-btn-book:hover {
    background: var(--brand-book, var(--slate-100));
    border-color: var(--brand-book, var(--slate-300));
    color: var(--brand-book-on, var(--slate-900));
}
/* Destructive actions are NOT brandable. A clinic that themed "delete" the
   same green as "view" would have made a real mistake harder to see, so this
   one keeps the danger colour whatever the branding says. */
.td-actions .icon-btn-danger,
.row-actions .icon-btn-danger {
    background: var(--rose-100); border-color: var(--rose-100);
    color: var(--brand-danger);
}
.td-actions .icon-btn-danger:hover,
.row-actions .icon-btn-danger:hover {
    background: var(--brand-danger); border-color: var(--brand-danger); color: #fff;
}

.link { color: var(--brand-primary); font-weight: 500; }
.link:hover { text-decoration: underline; }
.link-btn { background: none; border: 0; color: var(--brand-primary); font: inherit; font-size: .8rem; cursor: pointer; }
.link-btn:hover { text-decoration: underline; }

/* ------------------------------------------------------------------- forms */

.form-field { margin-bottom: .95rem; }
.form-label { display: block; font-size: .82rem; font-weight: 600; color: var(--slate-600); margin-bottom: .3rem; }
.form-label .req { color: var(--rose-600); margin-left: 2px; }

/* ---------------------------------------------------------------------------
   Financial statements. An income statement and a balance sheet are read down
   the right-hand column, so the figures need to line up digit for digit
   (tabular-nums) and the three kinds of row - section heading, subtotal, and
   the one line somebody actually came for - need to be distinguishable at a
   glance rather than by reading them.
   --------------------------------------------------------------------------- */
.table-compact td, .table-compact th { padding: .38rem .7rem; }
.table .num { font-variant-numeric: tabular-nums; white-space: nowrap; }

.table tr.row-section th {
    padding-top: .9rem;
    font-size: .74rem;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--slate-500);
    border-bottom: 1px solid var(--slate-200, #e2e8f0);
}
.table tr.row-total td {
    font-weight: 600;
    border-top: 1px solid var(--slate-200, #e2e8f0);
}
.table tr.row-grand td {
    font-weight: 700;
    border-top: 2px solid var(--slate-400, #94a3b8);
    border-bottom: 3px double var(--slate-400, #94a3b8);
}
.badge-muted { background: var(--slate-100, #f1f5f9); color: var(--slate-500); font-weight: 500; }

@media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }

/* Consequences a label cannot carry. Shown under the field it belongs to, and
   promoted to a warning when the control it describes is currently switched
   off - the state worth being reminded of is the unprotected one. */
.form-hint {
    margin: .35rem 0 0;
    font-size: .8rem;
    line-height: 1.5;
    color: var(--slate-500);
    max-width: 62ch;
}
.form-hint-warning {
    color: var(--amber-600);
    background: var(--amber-100);
    border-left: 3px solid var(--amber-600);
    padding: .5rem .7rem;
    border-radius: 0 3px 3px 0;
}
[data-theme="dark"] .form-hint-warning { background: #3a2a10; }

.form-control {
    width: 100%; padding: .52rem .7rem;
    border: 1px solid var(--slate-300); border-radius: var(--radius-sm);
    font: inherit; font-size: .9rem; color: var(--slate-800); background: var(--surface);
    transition: border-color .14s, box-shadow .14s;
}
.form-control:focus { outline: none; border-color: var(--blue-600); box-shadow: 0 0 0 3px rgb(2 132 199 / .14); }
.form-control:disabled, .form-control[readonly] { background: var(--slate-50); color: var(--slate-500); }
.form-control-sm { padding: .34rem .55rem; font-size: .84rem; }
textarea.form-control { resize: vertical; }
input[type="color"].form-control { padding: .15rem; height: 38px; }

.form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 .9rem; }
/* display:grid outweighs the UA [hidden] rule - restate it, or a toggled
   grid section (e.g. the custom-repeat fields) can never actually hide. */
.form-grid-2[hidden], .form-grid-3[hidden] { display: none; }
.form-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 .9rem; }
.form-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0 .9rem; }
/* Horizontal stack: inline controls that should wrap on narrow screens. */
.stack-h { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
@media (max-width: 900px) { .form-grid-4 { grid-template-columns: repeat(2, 1fr); } }

.has-error .form-control { border-color: var(--rose-600); }
.field-error { color: var(--rose-600); font-size: .78rem; margin-top: .28rem; font-weight: 500; }
.field-help { color: var(--slate-400); font-size: .78rem; margin-top: .28rem; }

.checkbox-label { display: inline-flex; align-items: center; gap: .5rem; font-size: .88rem; color: var(--slate-700); cursor: pointer; }
.checkbox-label input { width: 16px; height: 16px; accent-color: var(--blue-600); }

.static-value { padding: .5rem .2rem; font-size: .92rem; }

.error-summary { margin: .3rem 0 0 1.1rem; font-size: .85rem; }

.section-label { font-size: .82rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--slate-500); margin: 1rem 0 .6rem; }
.divider { border: 0; border-top: 1px solid var(--slate-200); margin: 1rem 0; }

.check-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .55rem; }
.check-grid-3 { grid-template-columns: repeat(3, 1fr); }
.check-tile {
    display: flex; gap: .6rem; align-items: flex-start;
    border: 1px solid var(--slate-200); border-radius: var(--radius-sm);
    padding: .6rem .7rem; cursor: pointer; transition: border-color .14s, background .14s;
}
.check-tile:hover { border-color: var(--blue-600); }
.check-tile:has(input:checked) { border-color: var(--blue-600); background: var(--blue-50); }
.check-tile input { margin-top: 3px; accent-color: var(--blue-600); }
.check-tile strong { display: block; font-size: .86rem; }
.check-tile small { color: var(--slate-500); font-size: .74rem; line-height: 1.35; display: block; }
.check-tile-sm { padding: .45rem .6rem; }

.perm-module { border: 1px solid var(--slate-200); border-radius: var(--radius); padding: .8rem 1rem 1rem; margin-bottom: 1rem; }
.perm-module legend { font-weight: 700; font-size: .9rem; padding: 0 .4rem; color: var(--slate-700); }

/* ------------------------------------------------------------------ tables */

/* EVERY table in the app is wrapped in one of these. A table is the one element
   that cannot be made narrow: thirteen columns of patient record do not reflow,
   so without a scroll container the widest table on the page sets the width of
   the PAGE, and on a phone that drags the sticky header, the menu and the
   footer sideways with it - every screen, not just the one with the table.
   Scrolling the strip keeps the damage inside the card.

   `.table-scroll` is the older spelling and stays live: 58 views already say
   it. Both names, one rule, so the two cannot drift apart.

   Safe against the usual overflow trap - `overflow-x: auto` computes
   `overflow-y` to `auto` as well, which clips anything absolutely positioned
   out of the box. Nothing in the app opens a dropdown from inside a table
   (an e2e assertion holds that line); row actions are plain buttons. */
.table-wrap, .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table { width: 100%; border-collapse: collapse; font-size: .875rem; }
/* Heading colours come from Settings -> Branding. Every token is read through
   a var() fallback so an install that never picks one emits nothing and keeps
   the neutral slate head - including its [data-theme="dark"] remap, which a
   hard-coded hex here would break. */
.table th {
    text-align: left; padding: .65rem .9rem;
    background: var(--brand-table-head-bg, var(--slate-50));
    color: var(--brand-table-head-fg, var(--slate-500));
    font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    border-bottom: 1px solid var(--brand-table-head-edge, var(--slate-200)); white-space: nowrap;
}
.table td { padding: .62rem .9rem; border-bottom: 1px solid var(--slate-100); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table-hover tbody tr:hover td { background: var(--slate-25); }
/* In-table section headings (e.g. Settings -> Modules grouped by edition). */
.table .table-section th {
    background: var(--brand-table-head-alt, var(--slate-100));
    color: var(--brand-table-head-fg, var(--slate-600));
    border-top: 1px solid var(--brand-table-head-edge, var(--slate-200)); white-space: normal;
}
.edition-list { margin-bottom: 1.2rem; }
.edition-list dt { font-weight: 600; font-size: .9rem; }
.edition-list dd { margin: 0 0 .7rem; font-size: .86rem; }
.table-plain th { background: none; }
.table tfoot td { background: var(--slate-25); font-weight: 600; }
.tfoot-grand td { font-size: 1rem; font-weight: 800; border-top: 2px solid var(--slate-800); }
.tfoot-danger td { color: var(--rose-600); }

.th-sortable a { color: inherit; display: inline-flex; gap: .3rem; align-items: center; }
/* The sorted column has to stand out from its own heading, so it follows the
   heading's accent rather than a fixed blue that a themed head can swallow. */
.th-sortable.is-sorted a { color: var(--brand-table-head-accent, var(--blue-600)); }
.sort-caret { font-size: .6rem; }
.th-actions, .td-actions { text-align: right; white-space: nowrap; }
.td-actions form { display: inline-block; }
.num { text-align: right; }
/* `.table th` sets text-align: left and out-specifies a bare utility class, so
   an alignment utility on a heading was silently losing to it - every money
   column rendered a right-aligned figure under a left-aligned heading. These
   restate the utilities at heading specificity so the head and the cell below
   it always agree; keep any new alignment utility listed here too. */
.table th.num,
.table th.text-right,
.table th.th-actions,
.table th.td-actions { text-align: right; }
.table th.text-center,
.table th.th-check { text-align: center; }
/* Figures line up column-wise only if the digits are the same width; the UI
   font's proportional numerals make a right-aligned column ragged one row to
   the next even when its edge is true. */
.table th.num, .table td.num,
.table th.text-right, .table td.text-right { font-variant-numeric: tabular-nums; }
.row-warning td { background: #fffbeb; }
.row-highlight td { background: var(--blue-50); }
.row-detail td { font-size: .8rem; padding-top: 0; }

.user-cell { display: flex; gap: .6rem; align-items: center; }

/* -------------------------------------------------------------- pagination */

.pagination-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .8rem 1.1rem; flex-wrap: wrap; }
.table-summary { color: var(--slate-500); font-size: .82rem; padding: .8rem 1.1rem; }
.pagination-bar .table-summary { padding: 0; }
/* Rows-per-page: rendered in the pagination bar, moved above its table (top
   left) by app.js. The margin applies in the moved position; inside the bar
   the flex gap does the spacing. */
.per-page { display: inline-flex; align-items: center; gap: .45rem; color: var(--slate-500); font-size: .82rem; white-space: nowrap; }
.per-page select { width: auto; min-width: 0; }
:not(.pagination-bar) > .per-page { margin: .55rem 1.1rem .25rem; }
.pagination { display: flex; gap: .25rem; }
.page-link {
    display: grid; place-items: center; min-width: 32px; height: 32px; padding: 0 .5rem;
    border: 1px solid var(--slate-200); border-radius: var(--radius-sm);
    font-size: .84rem; color: var(--slate-600); background: var(--surface);
}
.page-link:hover { border-color: var(--blue-600); color: var(--blue-600); }
.page-link.is-current { background: var(--blue-600); border-color: var(--blue-600); color: #fff; font-weight: 700; }
.page-link.is-disabled { opacity: .4; pointer-events: none; }
.page-gap { align-self: center; color: var(--slate-400); padding: 0 .2rem; }

/* ------------------------------------------------------------------ badges */

.badge {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .18rem .55rem; border-radius: 99px;
    font-size: .72rem; font-weight: 700; white-space: nowrap;
}
.badge-blue   { background: var(--blue-50);   color: var(--blue-700); }
.badge-teal   { background: var(--teal-50);   color: var(--teal-600); }
.badge-green  { background: var(--green-100); color: #166534; }
.badge-rose   { background: var(--rose-100);  color: #9f1239; }
.badge-amber  { background: var(--amber-100); color: #92400e; }
.badge-violet { background: var(--violet-100); color: #5b21b6; }
.badge-slate  { background: var(--slate-100); color: var(--slate-600); }
/* Plain-English aliases, so a view can say what it means without knowing which
   palette name happens to be the rose one. */
.badge-gray   { background: var(--slate-100); color: var(--slate-600); }
.badge-red    { background: var(--rose-100);  color: #9f1239; }

.color-dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: .35rem; vertical-align: baseline; }
.tag-cloud { display: flex; flex-wrap: wrap; gap: .35rem; }
.tag { background: var(--slate-100); color: var(--slate-600); font-size: .72rem; font-family: ui-monospace, monospace; padding: .2rem .5rem; border-radius: 5px; }
.stars { color: #f59e0b; letter-spacing: 1px; }

/* ------------------------------------------------------------------ alerts */

.alert {
    display: flex; align-items: flex-start; gap: .7rem;
    padding: .8rem 1rem; border-radius: var(--radius);
    font-size: .88rem; margin-bottom: 1rem; border: 1px solid;
}
.alert[hidden] { display: none; }
.alert > div { flex: 1; }
.alert [data-icon] { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.alert-success { background: var(--green-100); border-color: #86efac; color: #14532d; }
.alert-error   { background: var(--rose-100);  border-color: #fda4af; color: #881337; }
.alert-warning { background: var(--amber-100); border-color: #fcd34d; color: #78350f; }
.alert-info    { background: var(--blue-50);   border-color: #7dd3fc; color: #0c4a6e; }
.alert-close { margin-left: auto; background: none; border: 0; font-size: 1.15rem; cursor: pointer; color: inherit; opacity: .6; }
.alert-close:hover { opacity: 1; }
.alert .btn { margin-left: auto; }
.flash-stack { position: relative; }

/* ------------------------------------------------------------------ toasts */

/* Top right, under the sticky header: the eye is already up there (search,
   bell, account), where a bottom-corner toast went unnoticed. */
.toast-region { position: fixed; top: calc(var(--headbar-h, 0rem) + .8rem); right: 1.2rem; display: flex; flex-direction: column; gap: .5rem; z-index: 200; max-width: 380px; }
.toast {
    display: flex; align-items: flex-start; gap: .6rem;
    background: #0f172a; color: #fff;
    padding: .75rem 1rem; border-radius: var(--radius); box-shadow: var(--shadow-lg);
    font-size: .88rem; animation: toast-in .25s ease;
}
.toast [data-icon] { display: inline-flex; flex-shrink: 0; margin-top: .1rem; }
.toast-message { flex: 1; min-width: 0; overflow-wrap: break-word; }
.toast-close {
    background: none; border: 0; padding: 0; margin-left: .2rem;
    color: inherit; opacity: .6; cursor: pointer; font-size: 1.1rem; line-height: 1;
}
.toast-close:hover { opacity: 1; }
/* The accent stripe and the icon agree on the severity colour. */
.toast-success { border-left: 4px solid #22c55e; }
.toast-success [data-icon] { color: #22c55e; }
.toast-error { border-left: 4px solid var(--rose-600); }
.toast-error [data-icon] { color: var(--rose-600); }
.toast-warning { border-left: 4px solid #f59e0b; }
.toast-warning [data-icon] { color: #f59e0b; }
.toast-info { border-left: 4px solid var(--blue-600); }
.toast-info [data-icon] { color: var(--blue-600); }
@keyframes toast-in { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }
/* Phones: the fixed 380px column would hug one edge; stretch across instead. */
@media (max-width: 480px) { .toast-region { left: 1.2rem; right: 1.2rem; max-width: none; } }

/* ----------------------------------------------------------------- filters */

.filter-bar {
    display: flex; flex-wrap: wrap; gap: .55rem; align-items: center;
    padding: .9rem 1.1rem; border-bottom: 1px solid var(--slate-100);
}
.filter-bar .form-control { width: auto; min-width: 130px; flex: 0 1 auto; }
.filter-bar input[type="search"] { flex: 1 1 220px; }

/* A date input with its meaning attached. Two bare date boxes side by side are
   indistinguishable - "From" and "To" is the entire difference between them,
   and a title attribute cannot carry it: invisible until hovered, absent
   altogether on a touch screen. See components/date-filter.php. */
/* Placeholders had no colour of their own, so they inherited the input's text
   colour and read as a typed value rather than a hint. That matters most on the
   date fields: flatpickr swaps the native control for a text input, and its
   format hint IS the only thing in the box until somebody picks a date. */
::placeholder { color: var(--slate-400); opacity: 1; }

.filter-date { display: inline-flex; align-items: center; gap: .4rem; margin: 0; }
.filter-date-label {
    font-size: .78rem; font-weight: 600; color: var(--slate-500);
    white-space: nowrap; letter-spacing: .01em;
}
.filter-bar .filter-date .form-control { min-width: 8.6rem; }
@media (max-width: 600px) {
    /* Stacked, so the caption never squeezes the field to nothing. */
    .filter-date { flex-direction: column; align-items: flex-start; gap: .15rem; }
}
.filter-bar-report { background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius); margin-bottom: 1.1rem; box-shadow: var(--shadow-sm); }
.filter-spacer { flex: 1; }

/* -------------------------------------------------------------------- tabs */

.tabs { margin-bottom: 1.1rem; }
.tab-nav { display: flex; gap: .25rem; border-bottom: 2px solid var(--slate-200); margin-bottom: 1.1rem; overflow-x: auto; }
.tab-btn {
    padding: .6rem 1rem; background: none; border: 0; border-bottom: 2px solid transparent;
    margin-bottom: -2px; font: inherit; font-size: .9rem; font-weight: 600;
    color: var(--slate-500); cursor: pointer; white-space: nowrap;
}
.tab-btn:hover { color: var(--slate-800); }
/* Same reason as the rail below: the current tab is a selection state, and a
   selection state that ignores the chosen scheme reads as an unstyled patch. */
.tab-btn.is-active { color: var(--brand-primary, var(--blue-600)); border-bottom-color: var(--brand-primary, var(--blue-600)); }
.tab-pane { display: none; }
.tab-pane.is-active { display: block; }

/* Vertical variant: a guides-style rail of groups beside the panes, for pages
   with too many groups to fit a horizontal strip (Settings). */
.tabs-vertical { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 1.1rem; align-items: start; }
.tabs-vertical .tab-nav {
    flex-direction: column; gap: 1px; overflow-x: visible;
    background: var(--surface); border: 1px solid var(--slate-200); border-bottom: 1px solid var(--slate-200);
    border-radius: var(--radius); box-shadow: var(--shadow-sm);
    padding: .6rem; margin-bottom: 0;
    /* Sticks BELOW the header rather than at a guessed offset: the navbar
       layout stacks a menu bar on top of the bar the sidebar layout has alone,
       so a single hardcoded value hides the first entries under one of them.
       The height it can occupy is what is left of the viewport after that
       header, which is what makes the list scroll instead of running off the
       bottom of the screen with entries nobody can reach. */
    position: sticky; top: calc(var(--headbar-h) + .6rem);
    max-height: calc(100vh - var(--headbar-h) - 1.8rem);
    overflow-y: auto; overscroll-behavior: contain;
}
/* The settings form carries a sticky save bar above the rail, so its menu
   starts below that instead of sliding underneath it. */
.settings-tabs .tab-nav {
    top: calc(var(--headbar-h) + var(--savebar-h) + 1.1rem);
    max-height: calc(100vh - var(--headbar-h) - var(--savebar-h) - 2.3rem);
}
.settings-savebar {
    position: sticky; top: calc(var(--headbar-h) + .6rem); z-index: 30;
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding: .7rem 1.1rem; margin-bottom: 1rem;
    background: var(--surface); border: 1px solid var(--slate-200);
    border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
/* The rail is a fixed 240px column, so its labels MUST wrap: `.tab-btn` sets
   `white-space: nowrap` for the horizontal strip it was written for, and in the
   vertical rail that clipped "Appointments & reminders" and "Inventory &
   maintenance" mid-word. A wider typeface or a larger text size makes it worse,
   and both are now things a clinic can choose - so wrap rather than widen the
   column to fit today's longest label in today's font. */
.tabs-vertical .tab-btn {
    display: flex; align-items: center; gap: .6rem; width: 100%; text-align: left;
    padding: .55rem .7rem; border: 0; border-radius: var(--radius-sm);
    color: var(--slate-600); font-size: .92rem;
    white-space: normal; overflow-wrap: anywhere;
}
.tabs-vertical .tab-btn [data-icon] { color: var(--slate-400); flex: 0 0 auto; width: 17px; height: 17px; }
.tabs-vertical .tab-btn:hover { background: var(--slate-50); color: var(--slate-900); }
/* Selected entries follow the brand, falling back to the shipped blue pair when
   no primary is customised - so a stock install is byte-identical and a themed
   one stops showing a sky-blue chip inside a clay or navy menu. */
.tabs-vertical .tab-btn.is-active {
    background: var(--brand-primary-soft, var(--blue-50));
    color: var(--brand-primary-strong, var(--blue-700));
    font-weight: 600;
}
.tabs-vertical .tab-btn.is-active [data-icon] { color: var(--brand-primary, var(--blue-600)); }
@media (max-width: 900px) {
    /* Narrow screens: fall back to the scrolling strip above the panes. */
    .tabs-vertical { grid-template-columns: 1fr; }
    .tabs-vertical .tab-nav { position: static; max-height: none; flex-direction: row; overflow-x: auto; }
    .tabs-vertical .tab-btn { width: auto; white-space: nowrap; }
}

/* Tabs NESTED INSIDE a vertical rail - the dental chart's entries/history
   switch sits in a pane of the patient record's vertical tabs.
   `.tabs-vertical .tab-nav` and `.tabs-vertical .tab-btn` above are DESCENDANT
   selectors, so without this a nested strip silently inherits the rail: column
   direction, boxed and sticky, with full-width left-aligned buttons. This
   restores the plain horizontal strip. Same specificity as the rules it
   undoes, so it must stay after them - source order is what makes it win. */
.tabs-inline .tab-nav {
    flex-direction: row; gap: .25rem;
    background: none; border: 0; border-bottom: 2px solid var(--slate-200);
    border-radius: 0; box-shadow: none;
    padding: 0; margin-bottom: 1.1rem;
    position: static; max-height: none; overflow-x: auto; overflow-y: visible;
}
.tabs-inline .tab-btn {
    display: inline-block; width: auto; text-align: center;
    padding: .6rem 1rem; background: none; border: 0; border-bottom: 2px solid transparent;
    border-radius: 0; margin-bottom: -2px;
    font-size: .9rem; font-weight: 600; color: var(--slate-500); white-space: nowrap;
}
.tabs-inline .tab-btn:hover { background: none; color: var(--slate-800); }
/* That decision - brand the active-tab indicator, or keep it a fixed blue - has
   now been taken, and taken for all tabs at once as this comment asked: the
   strip, the rail and this nested strip all read the primary and all fall back
   to the same blue when none is set. Leaving this one behind is the outcome
   worth avoiding, because a nested strip sits INSIDE a branded rail. */
.tabs-inline .tab-btn.is-active {
    background: none;
    color: var(--brand-primary, var(--blue-600));
    border-bottom-color: var(--brand-primary, var(--blue-600));
}

/* ---------------------------------------------------------------- patients */

.patient-header { display: flex; justify-content: space-between; gap: 1.4rem; padding: 1.15rem; flex-wrap: wrap; }
.patient-header-main { display: flex; gap: 1rem; align-items: center; }
.patient-header-main h2 { font-size: 1.25rem; color: var(--slate-900); }
.patient-meta { display: flex; flex-wrap: wrap; gap: .35rem .9rem; align-items: center; color: var(--slate-500); font-size: .84rem; margin-top: .3rem; }
.patient-meta [data-icon] { width: 13px; height: 13px; vertical-align: -2px; }
.patient-header-stats { display: flex; gap: 1.6rem; align-items: center; }
.patient-header-stats > div { text-align: center; }
.patient-header-stats strong { display: block; font-size: 1rem; color: var(--slate-800); }
.patient-header-stats small { color: var(--slate-400); font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; }
.patient-header-stats .is-danger strong { color: var(--rose-600); }

.detail-list { display: grid; grid-template-columns: 140px 1fr; gap: .45rem .9rem; font-size: .88rem; }
.detail-list dt { color: var(--slate-400); font-weight: 600; }
.detail-list dd { color: var(--slate-700); }
.detail-list-2col { grid-template-columns: 150px 1fr; }

.timeline { list-style: none; position: relative; padding-left: .4rem; }
.timeline::before { content: ''; position: absolute; left: 17px; top: 6px; bottom: 6px; width: 2px; background: var(--slate-200); }
.timeline-item { display: flex; align-items: flex-start; gap: .9rem; padding: .55rem 0; position: relative; }
.timeline-icon {
    display: grid; place-items: center; width: 30px; height: 30px; flex: none;
    background: var(--surface); border: 2px solid var(--slate-200); border-radius: 50%; z-index: 1;
    color: var(--slate-500);
}
.timeline-icon [data-icon] { width: 14px; height: 14px; }
.timeline-appointment .timeline-icon { border-color: var(--blue-600); color: var(--blue-600); }
.timeline-invoice .timeline-icon { border-color: var(--amber-600); color: var(--amber-600); }
.timeline-payment .timeline-icon { border-color: var(--green-600); color: var(--green-600); }
.timeline-note .timeline-icon { border-color: var(--violet-600); color: var(--violet-600); }
.timeline-body { flex: 1; padding-bottom: .3rem; }
.timeline-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.timeline-head time { margin-left: auto; font-size: .78rem; }
.timeline-body p { font-size: .85rem; color: var(--slate-600); margin-top: .2rem; }
.timeline-compact .timeline-icon { width: 24px; height: 24px; }
.timeline-compact::before { left: 14px; }

.note { border: 1px solid var(--slate-200); border-radius: var(--radius); padding: .8rem 1rem; margin-bottom: .7rem; }
.note-alert { border-color: #fda4af; background: #fff1f2; }
.note-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .35rem; flex-wrap: wrap; }
.note-head time { font-size: .76rem; margin-left: auto; }
.note-head form { margin-left: .25rem; }
.note p { font-size: .88rem; }

/* ------------------------------------------------------------------- feeds */

.feed-list { list-style: none; }
.feed-list li { display: flex; align-items: center; gap: .7rem; padding: .6rem 1.1rem; border-bottom: 1px solid var(--slate-100); }
.feed-list li:last-child { border-bottom: 0; }
.feed-list-compact li { padding: .45rem 1.1rem; font-size: .85rem; }
.feed-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--slate-300); flex: none; }
.feed-dot-green { background: #22c55e; }
.feed-dot-amber { background: #f59e0b; }
.feed-amount { margin-left: auto; color: var(--green-600); font-size: .88rem; }

.empty-state { text-align: center; padding: 2.4rem 1rem; color: var(--slate-400); }
.empty-state .empty-icon { display: inline-grid; width: 42px; height: 42px; margin-bottom: .6rem; color: var(--slate-300); }
.empty-state .empty-icon svg { width: 100%; height: 100%; }
.empty-state p { margin-bottom: .9rem; }

/* ---------------------------------------------------------------- calendar */

.calendar-card { padding: 0; }
.calendar-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .9rem 1.1rem; border-bottom: 1px solid var(--slate-100); flex-wrap: wrap; }
.calendar-nav { display: flex; align-items: center; gap: .4rem; }
.calendar-title { font-size: 1.05rem; font-weight: 700; margin-left: .6rem; color: var(--slate-800); }
.calendar-filters { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.calendar-legend { display: flex; flex-wrap: wrap; gap: .4rem 1rem; padding: .5rem 1.1rem; font-size: .78rem; color: var(--slate-500); border-bottom: 1px solid var(--slate-100); }
.legend-item { display: inline-flex; align-items: center; gap: .35rem; }
.legend-dot { width: 10px; height: 10px; border-radius: 3px; }
.calendar-grid { min-height: 540px; padding: .6rem; overflow-x: auto; }
.calendar-loading { display: grid; place-items: center; min-height: 300px; color: var(--slate-400); }

.cal-month { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; min-width: 760px; }
.cal-dow { text-align: center; font-size: .72rem; font-weight: 700; text-transform: uppercase; color: var(--slate-400); padding: .35rem 0; }
.cal-day { min-height: 96px; border: 1px solid var(--slate-100); border-radius: var(--radius-sm); padding: .3rem; background: var(--surface); }
.cal-day.is-other { background: var(--slate-50); opacity: .65; }
.cal-day.is-today { border-color: var(--blue-600); }
.cal-day-num { font-size: .76rem; font-weight: 700; color: var(--slate-500); display: block; margin-bottom: .2rem; }
.is-today .cal-day-num { color: var(--blue-600); }
.cal-event {
    display: block; width: 100%; text-align: left; border: 0; font: inherit;
    font-size: .72rem; padding: .18rem .35rem; margin-bottom: 2px;
    border-radius: 4px; color: #fff; cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-event.is-cancelled { opacity: .55; text-decoration: line-through; }
.cal-more { font-size: .7rem; color: var(--blue-600); background: none; border: 0; cursor: pointer; }

.cal-week { display: grid; grid-template-columns: 56px repeat(7, 1fr); min-width: 860px; }
.cal-week.is-day { grid-template-columns: 56px 1fr; min-width: 480px; }
.cal-time { font-size: .7rem; color: var(--slate-400); text-align: right; padding-right: .5rem; transform: translateY(-7px); }
.cal-col { border-left: 1px solid var(--slate-100); position: relative; }
.cal-col-head { text-align: center; font-size: .78rem; font-weight: 700; padding: .4rem 0; border-bottom: 1px solid var(--slate-200); color: var(--slate-600); position: sticky; top: 0; background: var(--surface); }
.cal-col-head.is-today { color: var(--blue-600); }
.cal-slot { height: 44px; border-bottom: 1px solid var(--slate-100); }
.cal-slot:nth-child(even) { border-bottom-style: dashed; }
/* Empty slots and month cells are booking targets. */
.cal-col .cal-slot[data-slot] { cursor: pointer; }
.cal-col .cal-slot[data-slot]:hover { background: var(--slate-50); }
.cal-day[data-date] { cursor: pointer; }
.cal-day[data-date]:hover { border-color: var(--blue-600); }
.cal-week-event {
    /* left/width come from the layout pass so overlapping appointments sit
       side by side; these are the single-event defaults. */
    position: absolute; left: 3px; width: calc(100% - 6px);
    border-radius: 5px; color: #fff; font-size: .72rem; padding: .18rem .4rem;
    overflow: hidden; cursor: pointer; border: 0; text-align: left; font-family: inherit;
    line-height: 1.25; z-index: 2;
    /* The hairline keeps touching columns legible against each other. */
    box-shadow: 0 0 0 1px rgb(255 255 255 / .55), var(--shadow-sm);
}
/* Never let a label spill past its box - truncate and keep the full text in
   the tooltip instead. */
.cal-week-event strong,
.cal-week-event span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-week-event strong { font-size: .72rem; font-weight: 700; }
.cal-week-event span { font-size: .68rem; opacity: .92; }
/* Short or narrow blocks show the name only. */
.cal-week-event.is-compact span { display: none; }
.cal-week-event:hover { z-index: 6; box-shadow: 0 0 0 1px #fff, var(--shadow-lg); }
.cal-week-event:focus-visible { z-index: 6; outline: 2px solid #fff; outline-offset: -3px; }
.cal-agenda { min-width: 0; }
.cal-agenda-day { font-weight: 700; padding: .7rem .4rem .3rem; color: var(--slate-700); border-bottom: 1px solid var(--slate-200); }

.cal-popover {
    position: absolute; z-index: 90; width: 270px;
    background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); padding: .85rem;
}
.cal-popover-head { display: flex; justify-content: space-between; align-items: center; gap: .5rem; margin-bottom: .5rem; }
.cal-popover dl { display: grid; grid-template-columns: 84px 1fr; gap: .25rem .5rem; font-size: .82rem; }
.cal-popover dt { color: var(--slate-400); }
.cal-popover-actions { margin-top: .7rem; text-align: right; }

/* --------------------------------------------------------------- slots etc */

.slot-picker { margin: .6rem 0 1rem; }
.slot-grid { display: flex; flex-wrap: wrap; gap: .4rem; }
.slot-chip {
    padding: .35rem .7rem; border: 1px solid var(--slate-300); border-radius: 99px;
    background: var(--surface); font: inherit; font-size: .82rem; cursor: pointer;
}
.slot-chip:hover { border-color: var(--blue-600); color: var(--blue-600); }
.slot-chip.is-selected { background: var(--blue-600); border-color: var(--blue-600); color: #fff; }

.picked-patient {
    display: flex; align-items: center; gap: .6rem;
    background: var(--teal-50); border: 1px solid #99f6e4; border-radius: var(--radius-sm);
    padding: .5rem .7rem; margin-top: .45rem; font-size: .86rem;
}
/* display:flex outweighs the UA [hidden] rule - restate it, or an empty
   chip sits under every patient picker (same trap as .modal below). */
.picked-patient[hidden] { display: none; }
.picked-patient button { margin-left: auto; }

.collapse { border: 1px solid var(--slate-200); border-radius: var(--radius-sm); margin-bottom: .6rem; }
.collapse summary {
    display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
    padding: .65rem .9rem; cursor: pointer; font-size: .9rem; list-style: none;
}
.collapse summary::-webkit-details-marker { display: none; }
.collapse summary::before { content: '▸'; color: var(--slate-400); transition: transform .15s; }
.collapse[open] summary::before { transform: rotate(90deg); }
.collapse-body, .collapse > .form-grid-2, .collapse > .form-grid-3, .collapse > .form-field, .collapse > p { padding: .2rem .9rem .9rem; }
.collapse-body { border-top: 1px solid var(--slate-100); padding-top: .8rem; }

/* --------------------------------------------------------- invoice builder */

/* ----------------------------------------------------------- invoice form */
/*
 * The line-item table now spans the full page rather than the 2fr column of a
 * 2-1 grid. In the old layout eight columns shared roughly 60% of the viewport
 * and every input carried .form-control-sm, so a procedure dropdown holding
 * "Composite restoration (posterior, three surface)" truncated the exact text
 * you needed in order to choose correctly.
 *
 * Inputs are therefore full size here, and the columns are given explicit
 * widths rather than left to the browser's guess. `min-width` on the table plus
 * the .table-scroll wrapper means a narrow screen scrolls the table sideways
 * instead of crushing the cells - the page body itself never scrolls.
 */
.invoice-items-table { min-width: 1040px; }
.invoice-items-table th, .invoice-items-table td { padding: .5rem .45rem; vertical-align: middle; }
.invoice-items-table .form-control { font-size: .9rem; padding: .45rem .6rem; }

/* Digits line up in columns, so they get tabular figures and right alignment. */
.invoice-items-table .num,
.invoice-items-table input.num { text-align: right; font-variant-numeric: tabular-nums; }
.invoice-items-table .line-total { font-weight: 600; white-space: nowrap; }

.invoice-items-table .col-procedure   { width: 24%; min-width: 220px; }
.invoice-items-table .col-description { width: 22%; min-width: 180px; }
.invoice-items-table .col-teeth       { width: 8%;  min-width: 80px;  }
.invoice-items-table .col-qty         { width: 7%;  min-width: 72px;  }
.invoice-items-table .col-money       { width: 12%; min-width: 110px; }
.invoice-items-table .col-tax         { width: 7%;  min-width: 76px;  }
.invoice-items-table .col-remove      { width: 40px; }

/* A campaign and a hand-keyed discount answer the same question, so when a
   campaign is chosen the manual pair is dimmed rather than removed - it still
   shows what is being applied, it just is not yours to edit. */
.is-superseded { opacity: .55; }
.is-superseded .form-control { background: var(--slate-100); cursor: not-allowed; }

/* The summary rail follows you down a long line-item table. */
@media (min-width: 1100px) {
    .invoice-summary-card { position: sticky; top: 1rem; }
}

.invoice-summary-card .summary-row { display: flex; justify-content: space-between; padding: .4rem 0; font-size: .92rem; }
.summary-row-grand { border-top: 2px solid var(--slate-800); font-size: 1.05rem !important; font-weight: 800; margin-top: .3rem; padding-top: .6rem !important; }
.mt-1 { margin-top: .5rem; } .mt-2 { margin-top: 1rem; }
.mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: .5rem; } .mb-2 { margin-bottom: 1rem; }

.tender-row { display: grid; grid-template-columns: 1.2fr 1fr 1.2fr auto; gap: .5rem; margin-bottom: .55rem; align-items: center; }
.allocation-row {
    display: grid; grid-template-columns: auto 1fr auto 110px; gap: .6rem; align-items: center;
    border: 1px solid var(--slate-200); border-radius: var(--radius-sm);
    padding: .55rem .7rem; margin-bottom: .5rem; cursor: pointer; font-size: .86rem;
}
.allocation-row:has(input:checked) { border-color: var(--blue-600); background: var(--blue-50); }
.allocation-invoice small { display: block; color: var(--slate-500); font-size: .74rem; }
.allocation-balance { font-weight: 700; color: var(--rose-600); }

/* ----------------------------------------------------------------- modals */

.modal { position: fixed; inset: 0; background: rgb(15 23 42 / .55); display: grid; place-items: center; z-index: 150; padding: 1rem; }
/* display:grid outweighs the UA [hidden] rule - restate it or modals can never close. */
.modal[hidden] { display: none; }
.modal-card { background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow-lg); width: 100%; max-width: 520px; max-height: 92vh; overflow-y: auto; }
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: .9rem 1.15rem; border-bottom: 1px solid var(--slate-100); background: var(--brand-modal-head-bg); color: var(--brand-modal-head-fg); border-radius: var(--radius) var(--radius) 0 0; }
.modal-head h3 { font-size: 1rem; color: inherit; }
.modal-head .icon-btn { color: inherit; }
.modal-body { padding: 1.15rem; }
.modal-foot { display: flex; justify-content: flex-end; gap: .6rem; padding: .85rem 1.15rem; border-top: 1px solid var(--slate-100); }

/* ----------------------------------------------------------------- reports */

.report-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
.report-grid-sm { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-bottom: 1.1rem; }
.report-card {
    display: block; background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius);
    padding: 1.3rem; color: inherit; box-shadow: var(--shadow-sm);
    transition: transform .12s, box-shadow .12s, border-color .12s;
}
.report-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--blue-600); color: inherit; }
.report-icon { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 11px; background: var(--blue-50); color: var(--blue-600); margin-bottom: .8rem; }
.report-icon svg { width: 22px; height: 22px; }
.report-card h2 { font-size: 1rem; margin-bottom: .3rem; display: flex; gap: .5rem; align-items: center; }
.report-card p { font-size: .84rem; color: var(--slate-500); margin-bottom: .7rem; }
.report-cta { font-size: .82rem; color: var(--blue-600); font-weight: 600; }

.diff-old { background: #fff1f2; color: #9f1239; font-family: ui-monospace, monospace; font-size: .78rem; }
.diff-new { background: #f0fdf4; color: #166534; font-family: ui-monospace, monospace; font-size: .78rem; }

/* ------------------------------------------------------- notifications page */

.notif-list { list-style: none; }
.notif-item { display: flex; gap: .8rem; padding: .85rem 1.15rem; border-bottom: 1px solid var(--slate-100); }
.notif-item.is-unread { background: var(--blue-50); }
.notif-icon { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; background: var(--slate-100); color: var(--slate-500); flex: none; }
.notif-icon [data-icon] { width: 17px; height: 17px; }
.notif-success { background: var(--green-100); color: var(--green-600); }
.notif-warning { background: var(--amber-100); color: var(--amber-600); }
.notif-error { background: var(--rose-100); color: var(--rose-600); }
.notif-body p { font-size: .85rem; color: var(--slate-600); margin: .15rem 0; }

/* --------------------------------------------------------- patient portal */
/*
 * The portal reuses the admin shell wholesale; these are the only differences.
 * Sign-in and code-entry render no sidebar (there is no patient yet), so the
 * main column has to reclaim the space the fixed sidebar would have occupied.
 */
.is-gate .admin-main { margin-left: 0; }
/* Sits directly on the bar, so it follows the bar's ink like the account name. */
.portal-topbar-title { font-size: .92rem; font-weight: 600; color: var(--brand-topbar-fg, var(--slate-500)); }
.portal-gate { max-width: 460px; margin: 3rem auto; }
.portal-gate .card-body { padding: 1.6rem; }
/* The layout already prints "Patient portal" as the h1, so the card's own
   heading has to sit below it - two headings at page-title weight read as two
   pages stacked. */
.portal-gate h2 { font-size: 1.15rem; font-weight: 700; color: var(--brand-heading-card); margin-bottom: .4rem; }
/* The one-time code is read off a phone and typed in a hurry, so the field is
   sized for six characters rather than for the sentence a text input expects. */
.portal-code { letter-spacing: .4em; font-size: 1.3rem; text-align: center; }

/* Booking wizard: each answered step narrows the next one's choices. */
.book-step { border-top: 1px solid var(--slate-200); padding: 1.1rem 0; }
.book-step:first-of-type { border-top: 0; padding-top: 0; }
.book-step-label { display: flex; align-items: center; gap: .55rem; font-weight: 600; font-size: .92rem; margin-bottom: .7rem; }
.book-step-num {
    display: grid; place-items: center; width: 22px; height: 22px; flex: none;
    border-radius: 50%; background: var(--blue-600); color: #fff; font-size: .74rem; font-weight: 700;
}
.book-slots { display: flex; flex-wrap: wrap; gap: .45rem; }
.book-slot { position: relative; }
.book-slot input { position: absolute; opacity: 0; width: 0; height: 0; }
.book-slot span {
    display: block; padding: .45rem .8rem; border: 1px solid var(--slate-200); border-radius: var(--radius);
    font-size: .88rem; font-variant-numeric: tabular-nums; cursor: pointer; background: var(--surface);
}
.book-slot input:checked + span { background: var(--blue-600); border-color: var(--blue-600); color: #fff; font-weight: 600; }
.book-slot input:focus-visible + span { outline: 2px solid var(--blue-600); outline-offset: 2px; }

.notif-bulkbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.notif-bulkbar-actions { display: flex; gap: .45rem; }
/* Aligned to the icon medallion beside it rather than the text baseline. */
.notif-pick { flex: none; align-items: flex-start; padding-top: .55rem; }
.notif-item:hover .notif-pick input { outline: 2px solid var(--blue-200); outline-offset: 2px; }

/* ---------------------------------------------------------- notify toggle */
/*
 * The "also send email" switch. Dimmed rather than hidden when there is no
 * address: staff need to see that the option exists and why it is unavailable,
 * or they will hunt for it.
 */
.notify-toggle {
    border: 1px solid var(--slate-200); border-radius: var(--radius);
    padding: .7rem .85rem; background: var(--slate-50);
}
.notify-toggle .field-help { margin: .3rem 0 0 1.55rem; }
.notify-toggle.is-unreachable { border-color: #fcd34d; background: var(--amber-100); }
.notify-toggle.is-unreachable .checkbox-label { color: var(--slate-400); cursor: not-allowed; }

.notify-warning {
    display: flex; align-items: flex-start; gap: .4rem;
    margin: .45rem 0 0; font-size: .84rem; color: #78350f;
}
.notify-warning[hidden] { display: none; }
.notify-warning [data-icon] { width: 15px; height: 15px; flex: none; margin-top: .12rem; }

/* ---------------------------------------------------------- profile & misc */

.profile-avatar-row { display: flex; align-items: center; gap: 1.1rem; margin-bottom: 1.1rem; }
.avatar-upload .btn { cursor: pointer; }

.strength-meter { margin: -.4rem 0 .9rem; }
.strength-bar { height: 6px; background: var(--slate-200); border-radius: 3px; overflow: hidden; }
.strength-bar span { display: block; height: 100%; width: 0; transition: width .2s, background .2s; border-radius: 3px; }
.strength-meter small { color: var(--slate-500); font-size: .75rem; }

.settings-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0 1.2rem; }

.banner-thumb { width: 64px; height: 36px; object-fit: cover; border-radius: 5px; }
.gallery-admin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .9rem; }
.gallery-admin-item { border: 1px solid var(--slate-200); border-radius: var(--radius); overflow: hidden; }
.gallery-admin-item img { width: 100%; height: 120px; object-fit: cover; display: block; }
.gallery-admin-item figcaption { padding: .55rem .7rem; font-size: .82rem; position: relative; }
.gallery-admin-item form { position: absolute; top: .3rem; right: .3rem; }
.message-quote { border-left: 3px solid var(--slate-300); padding: .5rem .9rem; background: var(--slate-50); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin: .7rem 0; font-size: .9rem; }
.message-actions { display: flex; gap: .5rem; align-items: center; }
.message-actions input { flex: 1; }
.message-item.is-unread summary { font-weight: 600; }

.editor-toolbar { display: flex; gap: .25rem; margin-bottom: .35rem; }
.editor-toolbar button {
    min-width: 32px; padding: .3rem .55rem; border: 1px solid var(--slate-300);
    background: var(--surface); border-radius: var(--radius-sm); font: inherit; font-size: .8rem; cursor: pointer;
}
.editor-toolbar button:hover { border-color: var(--blue-600); color: var(--blue-600); }
.editor-area { font-family: ui-monospace, 'Cascadia Code', monospace; font-size: .84rem; }

/* --------------------------------------------------- searchable comboboxes */

.combo { position: relative; }
/* The native <select> behind a searchable combo, when it is [required].
   Invisible but still focusable: a display:none required control makes the
   browser abort submission with only a console warning, so the user would
   click Save and see nothing happen. Sized and placed to sit under the
   toggle, so the browser's own validation bubble points at the right thing. */
.combo-native {
    position: absolute; left: 0; bottom: 0;
    width: 100%; height: 1px;
    opacity: 0; pointer-events: none;
    border: 0; padding: 0; margin: 0;
}
.combo-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    width: 100%; padding: .52rem .7rem;
    border: 1px solid var(--slate-300); border-radius: var(--radius-sm);
    background: var(--surface); font: inherit; font-size: .9rem; color: var(--slate-800);
    cursor: pointer; text-align: left;
}
.combo-toggle:focus { outline: none; border-color: var(--blue-600); box-shadow: 0 0 0 3px rgb(2 132 199 / .14); }
.combo-toggle .combo-caret { color: var(--slate-400); font-size: .7rem; flex: none; }
.combo-toggle .combo-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo-toggle .combo-label.is-placeholder { color: var(--slate-400); }
.combo.form-control-sm-host .combo-toggle { padding: .34rem .55rem; font-size: .84rem; }

.combo-panel {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0; min-width: 220px;
    background: var(--surface); border: 1px solid var(--slate-200); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); z-index: 80; overflow: hidden;
}
.combo-search {
    width: 100%; border: 0; border-bottom: 1px solid var(--slate-200);
    padding: .55rem .8rem; font: inherit; font-size: .88rem;
}
.combo-search:focus { outline: none; background: var(--blue-50); }
.combo-list { max-height: 240px; overflow-y: auto; list-style: none; }
.combo-option {
    display: block; width: 100%; text-align: left; background: none; border: 0;
    padding: .48rem .8rem; font: inherit; font-size: .88rem; color: var(--slate-700); cursor: pointer;
}
.combo-option:hover, .combo-option.is-focused { background: var(--blue-50); }
.combo-option.is-selected { background: var(--blue-600); color: #fff; font-weight: 600; }
.combo-empty { padding: .7rem .8rem; color: var(--slate-400); font-size: .84rem; }

/* Time comboboxes: the option list a time input opens (see enhanceTimeCombo).
   Rides the combo-panel/-option styles above; the wrapper only anchors the
   panel, and a time list reads fine at the input's own width. */
.time-combo { position: relative; }
.time-combo-panel { min-width: 0; }

.inline-edit-row { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }

.table-loading { opacity: .45; pointer-events: none; transition: opacity .15s; }

/* -------------------------------------------------------------- typography */

.text-muted { color: var(--slate-400); }
.text-danger { color: var(--rose-600); }
.text-success { color: var(--green-600); }
.text-bold { font-weight: 700; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-nowrap { white-space: nowrap; }
.text-mono { font-family: ui-monospace, 'Cascadia Code', monospace; font-size: .84em; }
.text-lg { font-size: 1.15rem; }
.d-block { display: block; }
.d-inline { display: inline; }

[data-icon] { display: inline-grid; place-items: center; width: 18px; height: 18px; vertical-align: -3px; }
[data-icon] svg { width: 100%; height: 100%; }

/* -------------------------------------------------------------------- auth */

.auth-body { background: var(--slate-100); }
.auth-split { display: grid; grid-template-columns: 1fr 1fr; min-height: 100vh; }
.auth-visual {
    position: relative; display: grid; place-items: center;
    background: #0c2a46;
    color: #e0f2fe; padding: 3rem;
    isolation: isolate;
    overflow: hidden;
}

/*
 * Photograph layer.
 *
 * The image sits in its own ::before rather than on .auth-visual, so the
 * navy stays underneath: if the file 404s or is still uploading, the panel
 * degrades to the original solid colour instead of to unreadable white text
 * on nothing.
 *
 * `object-position: center 22%` favours the upper third - a portrait framed
 * for a wide hero crops through the chin at this aspect ratio otherwise.
 */
.auth-visual-photo {
    position: absolute; inset: 0; z-index: -2;
    width: 100%; height: 100%;
    object-fit: cover; object-position: center 22%;
}

/*
 * The scrim is what makes the overlaid copy legible, and it is not optional:
 * white text on an unknown photograph is a contrast accident waiting for the
 * first clinic that uploads a bright one. Heavier on the left, where the text
 * block sits, so the face on the right stays visible.
 */
.auth-visual::after {
    content: ''; position: absolute; inset: 0; z-index: -1;
    background:
        linear-gradient(105deg, rgba(6, 26, 45, .93) 0%, rgba(6, 26, 45, .78) 45%, rgba(6, 26, 45, .45) 100%),
        linear-gradient(to top, rgba(6, 26, 45, .6) 0%, transparent 40%);
}

.auth-visual-inner { position: relative; max-width: 400px; }
.auth-tooth { width: 76px; height: 76px; color: #7dd3fc; margin-bottom: 1.2rem; }
.auth-logo { display: block; max-height: 92px; max-width: 260px; width: auto; margin: 0 auto 1.3rem; object-fit: contain; }
.auth-visual h2 { font-size: 1.9rem; color: #fff; margin-bottom: .4rem; }
.auth-visual p { color: #bae6fd; margin-bottom: 1.6rem; }
.auth-points { list-style: none; display: grid; gap: .75rem; }
.auth-points li { display: flex; align-items: center; gap: .7rem; font-size: .95rem; }
.auth-points [data-icon] { width: 20px; height: 20px; color: #5eead4; }

.auth-panel { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1.2rem; }
.auth-card {
    width: 100%; max-width: 420px;
    background: var(--auth-glass-bg);
    backdrop-filter: blur(14px);
    border: 1px solid var(--auth-glass-border);
    border-radius: 18px; box-shadow: var(--shadow-lg);
    padding: 2.2rem;
}
.auth-title { font-size: 1.5rem; color: var(--slate-900); margin-bottom: .3rem; }
.auth-subtitle { color: var(--slate-500); font-size: .92rem; margin-bottom: 1.5rem; }
.auth-row { display: flex; justify-content: space-between; align-items: center; margin: -.2rem 0 1.2rem; font-size: .86rem; }
.auth-alt { margin-top: 1.2rem; text-align: center; font-size: .88rem; }
.auth-footnote { margin-top: 1.4rem; color: var(--slate-400); font-size: .8rem; }

.password-wrap { position: relative; }
.password-wrap .toggle-eye {
    position: absolute; right: .5rem; top: 50%; transform: translateY(-50%);
    background: none; border: 0; color: var(--slate-400); cursor: pointer; font-size: .78rem; font-weight: 600;
}

/* ------------------------------------------------------------------ errors */

.error-body { display: grid; place-items: center; min-height: 100vh; }
.error-page { text-align: center; padding: 2rem; max-width: 560px; }
.error-code {
    font-size: 6.5rem; font-weight: 900; line-height: 1;
    color: var(--blue-600);
}
.error-page h1 { margin: .4rem 0; color: var(--slate-800); }
.error-page p { color: var(--slate-500); margin-bottom: 1.4rem; }
.error-actions { display: flex; gap: .7rem; justify-content: center; }
.error-trace { text-align: left; margin-top: 1.6rem; background: #0f172a; color: #cbd5e1; padding: 1rem; border-radius: var(--radius); font-size: .74rem; overflow: auto; max-height: 300px; }

/* -------------------------------------------------------------- responsive */

@media (max-width: 1100px) {
    .grid-2, .grid-2-1 { grid-template-columns: 1fr; }
    .form-grid-3 { grid-template-columns: 1fr 1fr; }
    .check-grid-3 { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 860px) {
    .sidebar { transform: translateX(-100%); }
    .sidebar.is-open { transform: none; }
    .sidebar-close { display: block; }
    .admin-main { margin-left: 0; }
    .auth-split { grid-template-columns: 1fr; }
    .auth-visual { display: none; }
    .patient-header-stats { width: 100%; justify-content: space-between; }
    .tender-row { grid-template-columns: 1fr 1fr; }
    .allocation-row { grid-template-columns: auto 1fr; }
    .allocation-row input[data-role="alloc-amount"] { grid-column: 2; }
}

@media (max-width: 600px) {
    .admin-content { padding: 1rem .8rem; }
    .form-grid-2, .form-grid-3 { grid-template-columns: 1fr; }
    /* -4 was collapsing to two columns at 900px and then staying there: four
       fields sharing a 390px phone is two columns of about 90px each, which is
       narrower than the date placeholder they were built to hold. */
    .form-grid-4 { grid-template-columns: 1fr; }
    .check-grid, .check-grid-3 { grid-template-columns: 1fr; }

    /* The bar wraps rather than dropping the search. Hiding it was the old
       answer, and it cost the phone the single most useful control in the
       product: front office looking a patient up while standing at the counter.
       A second row is cheap; the search is not.

       Wrapping makes the bar taller, and --headbar-h is what every sticky panel
       clears - so the new height is declared here for the same reason the
       navbar layout declares its own. Leave it at 3.6rem and the settings save
       bar pins itself underneath the search row it is supposed to sit below.
       Rounded up rather than measured: over-clearing costs a few pixels of gap,
       under-clearing hides a control. */
    .admin-shell { --headbar-h: 7rem; }
    .admin-shell-navbar { --headbar-h: 10rem; }
    .topbar { flex-wrap: wrap; padding: .5rem .8rem; row-gap: .5rem; }
    /* Only the real search earns the second row. The admin bar renders an empty
       .global-search when the user cannot view patients, and the portal reuses
       the same box to hold a title - neither is worth 2.5rem of a phone screen,
       so they stay inline and shrink instead. */
    .global-search[data-component="patient-search"] { order: 3; flex: 1 1 100%; max-width: none; min-width: 0; }
    .global-search:not([data-component]) { flex: 0 1 auto; min-width: 0; overflow: hidden; }
    .portal-topbar-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .user-chip-name { display: none; }
    .branch-switch-label { max-width: 90px; overflow: hidden; text-overflow: ellipsis; }

    .page-actions .btn { font-size: .8rem; padding: .42rem .7rem; }
    .detail-list, .detail-list-2col { grid-template-columns: 1fr; gap: .1rem .5rem; }
    .detail-list dt { margin-top: .45rem; }

    /* These two collapse to a pair at 860px and no further, so on a phone the
       amount box and its label shared a row about 80px wide. */
    .tender-row, .allocation-row { grid-template-columns: 1fr; }
    .allocation-row input[data-role="alloc-amount"] { grid-column: 1; }

    /* Cards give back their gutters - 1.15rem each side of a 390px screen is
       15% of the viewport spent on padding. */
    /* `:not()` is load-bearing, not decoration. `.card-body-flush { padding: 0 }`
       is a single class declared further up the file, so a bare `.card-body`
       here ties on specificity and wins on source order - putting side padding
       back onto exactly the full-bleed table cards this breakpoint exists to
       help, on all 85 of them. */
    .card-head, .card-body:not(.card-body-flush), .card-foot { padding-left: .85rem; padding-right: .85rem; }
    .table th, .table td { padding-left: .6rem; padding-right: .6rem; }

    .stat-grid, .stat-grid-4 { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .6rem; }

    /* Filters keep their 130px floor at every width, so on a phone they packed
       three to a row at the minimum size and the select showing insurer names
       truncated every one of them. Two per row, sharing whatever is there. */
    .filter-bar { padding: .75rem .85rem; }
    .filter-bar .form-control { flex: 1 1 140px; min-width: 0; }
    .filter-bar input[type="search"] { flex: 1 1 100%; }
    .filter-bar .filter-date { flex: 1 1 140px; }
    .filter-bar .filter-date .form-control { min-width: 0; width: 100%; }
    .calendar-toolbar { align-items: flex-start; }
    .calendar-title { margin-left: 0; font-size: .95rem; }

    /* The pager renders up to eleven elements (prev, a nine-entry window, next).
       `.pagination` never wrapped, so at 32px each that is ~392px of links on a
       390px screen - the one control on a list screen you are guaranteed to
       want, running off the edge. It wraps and centres instead. */
    .pagination { flex-wrap: wrap; justify-content: center; }
    .pagination-bar { justify-content: center; gap: .6rem; }
    .pagination-bar .table-summary { width: 100%; text-align: center; }

    /* Footer stacks: the copyright line and the version were being squeezed
       onto one row with `justify-content: space-between` and no room. */
    .admin-footer { flex-direction: column; align-items: flex-start; gap: .2rem; padding: .8rem; }

    /* A centred 520px card with 1rem of grid padding leaves a phone modal
       floating in the middle with content scrolling inside a short box. Below
       this width it becomes a sheet: full width, anchored to the bottom, using
       the height it actually needs. */
    .modal { padding: 0; place-items: end stretch; }
    /* Descendant selectors on purpose. `.modal-card-sm` and `.modal-card-lg`
       are declared FURTHER DOWN this file, so a bare `.modal-card-sm` here ties
       on specificity and loses on source order - the small modal would keep its
       420px cap and sit in a left-aligned column on a 500px screen. Every card
       lives inside `.modal`, so borrowing the parent settles it in both
       directions regardless of where the modifiers are declared. */
    .modal .modal-card, .modal .modal-card-sm, .modal .modal-card-lg, .modal .modal-card-xl {
        max-width: none; max-height: 92vh;
        border-radius: var(--radius) var(--radius) 0 0;
    }
}

/* ------------------------------------------------------------------- touch */

/* Keyed on the POINTER, not on the width. A 1024px tablet in a surgery is a
   touch device with a desktop viewport, and a narrow window on a desktop is a
   mouse at 500px - width answers neither question. `pointer: coarse` asks the
   one that matters: is the primary input a fingertip. */
@media (pointer: coarse) {
    /* iOS Safari zooms the whole page in when a focused input's text is below
       16px, and never zooms back out. Every form field in the app was .9rem =
       14.4px, so tapping any field left the layout magnified and scrolled
       sideways, and staff had to pinch out after every entry. 16px exactly is
       the threshold; this is not a taste decision about type size. */
    /* `input.color-setting-hex` carries an element name for a reason: the class
       is redeclared with `font-size: .82rem` further down this file, so the bare
       class ties on specificity and loses on source order. The element name also
       scopes this correctly - the same class dresses a read-only <code> chip on
       the settings index, and a chip nobody can focus cannot trigger the zoom
       this rule exists to prevent. */
    .form-control, .form-control-sm, input.color-setting-hex,
    input[type="text"], input[type="search"], input[type="email"],
    input[type="password"], input[type="tel"], input[type="number"],
    input[type="date"], input[type="time"], select, textarea {
        font-size: 16px;
    }

    /* Row actions are the app's main verb - view, edit, book, cancel - and they
       sat in 32px boxes about 4px apart. That is under half the 44px target
       that stops a finger opening the wrong patient's record. Height is bought
       with padding rather than a fixed size so an icon-and-label button still
       grows to fit its text. */
    .icon-btn { min-width: 44px; min-height: 44px; }
    .btn { min-height: 44px; }
    .btn-sm { min-height: 38px; padding: .4rem .7rem; }
    .td-actions { line-height: 1; }
    .td-actions .icon-btn, .td-actions .btn-sm { margin: .1rem; }

    /* A tap has no hover to disambiguate a checkbox from its label, so the
       box itself has to be big enough to hit. */
    .table input[type="checkbox"], .checkbox-label input[type="checkbox"] {
        width: 18px; height: 18px;
    }
    .dropdown-item { padding-top: .6rem; padding-bottom: .6rem; }
    .tab-btn { min-height: 44px; }
    /* Page numbers are 32px squares about 4px apart - the densest tap target in
       the app, and one people use repeatedly. They wrap at the phone
       breakpoint, so growing them costs a second row, not an overflow. */
    .page-link { min-width: 44px; height: 44px; }
}

/* ---------------------------------------------------- dark theme overrides */
/* Components whose light styling uses literal colours (tinted alerts, badge
   text, warning rows) get explicit dark counterparts here. */

[data-theme="dark"] .alert-success { background: #0e2a1b; border-color: #1e5c39; color: #a7e3c0; }
[data-theme="dark"] .alert-error   { background: #331520; border-color: #7f2740; color: #f3b8c6; }
[data-theme="dark"] .alert-warning { background: #33260d; border-color: #7c5f1d; color: #ecd9a0; }
[data-theme="dark"] .alert-info    { background: #0f2740; border-color: #1f5f8a; color: #a8d4f0; }

[data-theme="dark"] .badge-green  { color: #86efac; }
[data-theme="dark"] .badge-rose   { color: #fda4af; }
[data-theme="dark"] .badge-amber  { color: #fcd34d; }
[data-theme="dark"] .badge-teal   { color: #5eead4; }
[data-theme="dark"] .badge-violet { color: #c4b5fd; }

[data-theme="dark"] .row-warning td { background: #2b2410; }
[data-theme="dark"] .note-alert { background: #331520; border-color: #7f2740; }
[data-theme="dark"] .diff-old { background: #331520; color: #fda4af; }
[data-theme="dark"] .diff-new { background: #0e2a1b; color: #86efac; }
[data-theme="dark"] .picked-patient { background: #0f2b29; border-color: #1d5b52; }
[data-theme="dark"] .global-search input:focus { background: var(--slate-50); }
[data-theme="dark"] .stat-green .stat-icon  { color: #4ade80; }
[data-theme="dark"] .stat-rose .stat-icon   { color: #fb7185; }
[data-theme="dark"] .stat-amber .stat-icon  { color: #fbbf24; }
[data-theme="dark"] .stat-violet .stat-icon { color: #a78bfa; }

/* ----------------------------------------------------------------- nursing */

/* A form that sits inside a table cell next to its siblings rather than
   stacking - the drug round is a row of small decisions, not a page of them. */
.inline-form { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; margin-top: .25rem; }
.inline-form .form-control { width: auto; min-width: 8rem; }

/* .table-scroll is declared with .table-wrap in the tables section - one rule
   for both spellings, so a change to how tables scroll cannot reach half of
   them and miss the other half. */

.mar-slots { display: flex; flex-wrap: wrap; gap: .6rem; }
.mar-slot { min-width: 7rem; padding: .4rem .5rem; border: 1px solid var(--border); border-radius: var(--radius); }
.mar-slot-time { font-weight: 700; font-size: .78rem; margin-bottom: .25rem; }

/* ------------------------------------------------------------------- print */

@media print {
    .sidebar, .topnav, .topbar, .page-actions, .filter-bar, .pagination-bar, .admin-footer,
    .toast-region, .card-foot, .td-actions, .th-actions { display: none !important; }
    .admin-main { margin-left: 0; }
    /* A branded table head is for the screen. On paper a solid dark heading row
       is a page of wasted toner, so printing falls back to the plain rule. */
    .table th, .table .table-section th { background: none !important; color: #1e293b !important; border-bottom: 1px solid #cbd5e1 !important; }
    .card { box-shadow: none; border-color: #ddd; break-inside: avoid; }
    body { background: var(--surface); }
    /* Paper has no scrollbar. A scroll container prints only the strip that
       was visible on screen, so an invoice printed from a phone would lose its
       right-hand columns - the amounts. Every table wrapper opens up for
       print and lets the table paginate instead. */
    .table-wrap, .table-scroll, .calendar-grid,
    [data-component="odontogram"] { overflow: visible !important; }
}


/* -------------------------------------------------------------- odontogram */

/* The chart is a 744-unit SVG that declares `min-width: 700px` - it is a jaw,
   and a jaw does not reflow. Its host is a plain .card-body, so on any phone
   the chart set the width of the page and dragged the rest of the record
   sideways with it. Same treatment as a wide table: scroll the chart, not
   the screen. */
[data-component="odontogram"] { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.odontogram { --odo-tooth: #f1f5f9; --odo-line: #94a3b8; }
[data-theme="dark"] .odontogram { --odo-tooth: #1e293b; --odo-line: #475569; }
.odo-surface { cursor: pointer; transition: filter .1s; }
.odo-surface:hover { filter: brightness(0.85); }
.odo-num { font-size: 11px; fill: var(--text-muted, #64748b); font-family: inherit; }
.odo-swatch {
    display: inline-block; width: .7rem; height: .7rem; border-radius: 2px;
    margin-right: .3rem; vertical-align: baseline;
}

/* ---------------------------------------------------------------- lightbox */

.lightbox-overlay {
    position: fixed; inset: 0; z-index: 300; display: none;
    background: rgba(10, 14, 22, 0.92); flex-direction: column;
}
.lightbox-overlay.is-open { display: flex; }
.lightbox-bar {
    display: flex; justify-content: space-between; align-items: center;
    padding: .6rem 1rem; color: #e2e8f0; font-size: .9rem; gap: 1rem;
}
.lightbox-btn {
    background: rgba(255,255,255,.12); color: #fff; border: 0; border-radius: 6px;
    padding: .35rem .8rem; cursor: pointer; font-size: .85rem; margin-left: .4rem;
}
.lightbox-btn.is-active { background: #2563eb; }
.lightbox-stage {
    flex: 1; display: flex; align-items: center; justify-content: center;
    gap: 1rem; padding: 0 3.5rem 1.5rem; min-height: 0;
}
.lightbox-img, .lightbox-half img {
    max-width: 100%; max-height: 100%; object-fit: contain;
    border-radius: 6px; background: #000;
}
.lightbox-half {
    flex: 1 1 50%; height: 100%; margin: 0; display: flex; flex-direction: column;
    align-items: center; min-width: 0;
}
.lightbox-half img { flex: 1; min-height: 0; width: auto; }
.lightbox-half figcaption { color: #cbd5e1; font-size: .8rem; padding-top: .4rem; text-align: center; }
.lightbox-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    background: rgba(255,255,255,.12); color: #fff; border: 0; border-radius: 999px;
    width: 2.6rem; height: 2.6rem; font-size: 1.1rem; cursor: pointer; z-index: 2;
}
.lightbox-nav:hover { background: rgba(255,255,255,.25); }
.lightbox-prev { left: .75rem; }
.lightbox-next { right: .75rem; }

/* ------------------------------------------------------------------ guides */

.guide-hero {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem;
}
.guide-hero-title {
    display: flex; align-items: center; gap: .6rem;
    font-size: 1.9rem; font-weight: 700; color: var(--slate-900); margin: 0;
}
/* The icon span is filled with a 30px SVG at runtime; left inline it sits on
   the text baseline and crowds the title. A centred flex box sizes the span
   to its SVG so the flex gap on the title is the actual visible spacing. */
.guide-hero-title [data-icon] { color: var(--blue-600); display: inline-flex; align-items: center; flex-shrink: 0; }
.guide-hero-title svg { width: 30px; height: 30px; display: block; }
.guide-hero-sub { color: var(--slate-500); margin: .25rem 0 0; }

.guide-search {
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
    background: var(--surface); border: 1px solid var(--slate-200);
    border-radius: var(--radius); padding: .55rem .75rem; margin-bottom: 1.25rem;
    box-shadow: var(--shadow-sm);
}
.guide-search [data-icon] { color: var(--slate-400); }
.guide-search input {
    flex: 1; min-width: 12rem; border: 0; outline: none; font: inherit;
    background: transparent; color: var(--slate-800); padding: .2rem 0;
}

.guide-results { list-style: none; margin: 0; padding: 0; }
.guide-results li { padding: .6rem 0; border-bottom: 1px solid var(--slate-200); }
.guide-results li:last-child { border-bottom: 0; }
.guide-results p { margin: .2rem 0 0; font-size: .88rem; }

.guide-grid {
    display: grid; gap: 1rem;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.guide-card {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    gap: .5rem; padding: 1.75rem 1.25rem;
    background: var(--surface); border: 1px solid var(--slate-200);
    border-radius: var(--radius); box-shadow: var(--shadow-sm);
    text-decoration: none; color: inherit;
    transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.guide-card:hover {
    transform: translateY(-2px); box-shadow: var(--shadow);
    border-color: var(--blue-600);
}
.guide-card-icon { color: var(--blue-600); }
.guide-card-icon svg { width: 34px; height: 34px; }
.guide-card h3 { margin: .3rem 0 0; font-size: 1.05rem; color: var(--slate-900); }
.guide-card p { margin: 0; font-size: .88rem; color: var(--slate-500); line-height: 1.45; }

/* -------------------------------------------------------------- the reader */

.guide-layout { display: grid; grid-template-columns: 260px 1fr; gap: 1rem; align-items: start; }
@media (max-width: 900px) { .guide-layout { grid-template-columns: 1fr; } }

.guide-rail {
    background: var(--surface); border: 1px solid var(--slate-200);
    border-radius: var(--radius); box-shadow: var(--shadow-sm);
    padding: .6rem; position: sticky; top: 5rem;
    max-height: calc(100vh - 7rem); overflow-y: auto;
}
@media (max-width: 900px) { .guide-rail { position: static; max-height: none; } }

.guide-rail-back {
    display: flex; align-items: center; gap: .5rem; padding: .55rem .7rem;
    color: var(--slate-600); text-decoration: none; font-weight: 600;
    border-bottom: 1px solid var(--slate-200); margin-bottom: .4rem;
}
.guide-rail-back:hover { color: var(--blue-600); }
.guide-rail-nav { display: flex; flex-direction: column; gap: 1px; }
.guide-rail-item {
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem .7rem; border-radius: var(--radius-sm);
    color: var(--slate-600); text-decoration: none; font-size: .92rem;
}
.guide-rail-item [data-icon] { color: var(--slate-400); flex: 0 0 auto; }
.guide-rail-item:hover { background: var(--slate-50); color: var(--slate-900); }
.guide-rail-item.is-active {
    background: var(--blue-50); color: var(--blue-700); font-weight: 600;
}
.guide-rail-item.is-active [data-icon] { color: var(--blue-600); }

.guide-article { max-width: 62rem; }
.guide-title {
    font-size: 1.75rem; color: var(--blue-700); margin: 0 0 .3rem;
    padding-bottom: .6rem; border-bottom: 2px solid var(--blue-600);
}
.guide-summary { color: var(--slate-500); margin: 0 0 1.25rem; }

.guide-toc {
    background: var(--slate-50); border: 1px solid var(--slate-200);
    border-radius: var(--radius-sm); padding: .7rem .9rem; margin-bottom: 1.5rem;
    display: flex; flex-wrap: wrap; gap: .3rem .9rem; align-items: baseline;
}
.guide-toc-label {
    font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
    color: var(--slate-500); font-weight: 700; width: 100%;
}
.guide-toc a { font-size: .89rem; color: var(--blue-700); text-decoration: none; }
.guide-toc a:hover { text-decoration: underline; }

.guide-section { scroll-margin-top: 5rem; margin-bottom: 1.75rem; }
.guide-section > h3 {
    font-size: 1.3rem; color: var(--slate-900); margin: 0 0 .7rem;
}
.guide-section p { color: var(--slate-700); line-height: 1.65; margin: 0 0 .8rem; }
.guide-h { font-size: 1.02rem; color: var(--slate-900); margin: 1.1rem 0 .4rem; }

.guide-list, .guide-steps { color: var(--slate-700); line-height: 1.65; margin: 0 0 .9rem; padding-left: 1.3rem; }
.guide-list li, .guide-steps li { margin-bottom: .35rem; }
.guide-steps li::marker { font-weight: 700; color: var(--blue-600); }

.guide-table-wrap { overflow-x: auto; margin: 0 0 1rem; }
.guide-table { font-size: .9rem; }
.guide-table th { white-space: nowrap; }
.guide-table td { vertical-align: top; line-height: 1.55; }

.guide-callout {
    display: flex; gap: .7rem; align-items: flex-start;
    border-left: 4px solid var(--slate-400); border-radius: var(--radius-sm);
    background: var(--slate-50); padding: .75rem 1rem; margin: 0 0 1rem;
    color: var(--slate-700); line-height: 1.6; font-size: .93rem;
}
.guide-callout [data-icon] { flex: 0 0 auto; margin-top: .1rem; }
.guide-callout-note { border-left-color: var(--blue-600); background: var(--blue-50); }
.guide-callout-note [data-icon] { color: var(--blue-600); }
.guide-callout-tip { border-left-color: var(--green-600); background: var(--green-100); }
.guide-callout-tip [data-icon] { color: var(--green-600); }
.guide-callout-warn { border-left-color: var(--amber-600); background: var(--amber-100); }
.guide-callout-warn [data-icon] { color: var(--amber-600); }

.guide-pager {
    display: flex; justify-content: space-between; gap: .75rem;
    padding-top: 1.25rem; margin-top: 1.5rem; border-top: 1px solid var(--slate-200);
}

@media print {
    .guide-rail, .guide-hero .btn-row, .guide-toc, .guide-pager, .guide-search { display: none !important; }
    .guide-layout { grid-template-columns: 1fr; }
    .guide-article { max-width: none; }
}

/* ------------------------------------------------------- date/time pickers */
/* flatpickr theme integration: brand the selected day and repaint the popup
   for dark mode (flatpickr's own stylesheet uses literal light colours). */

.flatpickr-calendar { box-shadow: var(--shadow-lg); border: 1px solid var(--slate-200); }
.flatpickr-day.selected, .flatpickr-day.selected:hover,
.flatpickr-day.startRange, .flatpickr-day.endRange {
    background: var(--blue-600); border-color: var(--blue-600);
}
.flatpickr-day.today { border-color: var(--teal-600); }
.flatpickr-day.today:hover, .flatpickr-day.today:focus { background: var(--teal-600); border-color: var(--teal-600); color: #fff; }

[data-theme="dark"] .flatpickr-calendar { background: var(--slate-800); border-color: var(--slate-700); }
[data-theme="dark"] .flatpickr-months .flatpickr-month,
[data-theme="dark"] .flatpickr-current-month .flatpickr-monthDropdown-months,
[data-theme="dark"] .flatpickr-current-month input.cur-year,
[data-theme="dark"] .flatpickr-weekday,
[data-theme="dark"] .flatpickr-day,
[data-theme="dark"] .flatpickr-time input,
[data-theme="dark"] .flatpickr-time .flatpickr-am-pm { color: var(--slate-200); fill: var(--slate-200); }
[data-theme="dark"] .flatpickr-current-month .flatpickr-monthDropdown-months { background: var(--slate-800); }
[data-theme="dark"] .flatpickr-day.prevMonthDay,
[data-theme="dark"] .flatpickr-day.nextMonthDay,
[data-theme="dark"] .flatpickr-day.flatpickr-disabled { color: var(--slate-500); }
[data-theme="dark"] .flatpickr-day:hover, [data-theme="dark"] .flatpickr-day:focus { background: var(--slate-700); border-color: var(--slate-700); }
[data-theme="dark"] .flatpickr-day.selected, [data-theme="dark"] .flatpickr-day.selected:hover { background: var(--blue-600); border-color: var(--blue-600); color: #fff; }
[data-theme="dark"] .flatpickr-months .flatpickr-prev-month svg,
[data-theme="dark"] .flatpickr-months .flatpickr-next-month svg { fill: var(--slate-300); }
[data-theme="dark"] .flatpickr-time { border-top-color: var(--slate-700); }
[data-theme="dark"] .flatpickr-time input:hover, [data-theme="dark"] .flatpickr-time input:focus,
[data-theme="dark"] .flatpickr-time .flatpickr-am-pm:hover, [data-theme="dark"] .flatpickr-time .flatpickr-am-pm:focus { background: var(--slate-700); }
[data-theme="dark"] .flatpickr-calendar.arrowTop::after { border-bottom-color: var(--slate-800); }
[data-theme="dark"] .flatpickr-calendar.arrowBottom::after { border-top-color: var(--slate-800); }
[data-theme="dark"] .numInputWrapper span.arrowUp::after { border-bottom-color: var(--slate-300); }
[data-theme="dark"] .numInputWrapper span.arrowDown::after { border-top-color: var(--slate-300); }

/* ---------------------------------------------------------------- bulk bar */
.bulk-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; padding: .6rem 1.15rem; border-bottom: 1px solid var(--slate-100); background: var(--blue-50); }
.bulk-bar[hidden] { display: none; }
.bulk-count { font-size: .85rem; font-weight: 600; color: var(--slate-600); }
.td-check, .th-check { width: 2rem; text-align: center; }
[data-theme="dark"] .bulk-bar { background: var(--slate-800); border-bottom-color: var(--slate-700); }
[data-theme="dark"] .bulk-count { color: var(--slate-300); }

/* --------------------------------------------------------------- quick add */
.quick-add-wrap { display: flex; align-items: flex-start; gap: .4rem; }
.quick-add-wrap > .form-control, .quick-add-wrap > .combo { flex: 1 1 auto; min-width: 0; }
.quick-add-btn { flex: 0 0 auto; border: 1px solid var(--slate-200); border-radius: var(--radius-sm, 6px); height: 2.45rem; width: 2.45rem; display: inline-flex; align-items: center; justify-content: center; }
.quick-add-btn:hover { background: var(--blue-50); color: var(--blue-600); border-color: var(--blue-200, var(--slate-300)); }
.modal-card-sm { max-width: 420px; }
/* For a modal that carries a whole form rather than a confirmation - the
   check-in form has a patient search, a complaint and two collapsible
   sections, and 520px squeezes all three. */
.modal-card-lg { max-width: 720px; }
/* The check-in modal: a patient picker, the complaint, and two collapsible
   clinical blocks. 720px forces the clerking textareas into a cramped column;
   960 lets them breathe without turning the modal into a page. */
.modal-card-xl { max-width: 960px; }
[data-theme="dark"] .quick-add-btn { border-color: var(--slate-600); }
[data-theme="dark"] .quick-add-btn:hover { background: var(--slate-700); color: var(--blue-300, #93c5fd); }

/* ------------------------------------------ organisation registrations */
/* Settings -> Organisation: one row per regulator - what it is, the number,
   when it lapses. A grid rather than a table because every cell is an input,
   and it collapses to stacked rows on a phone where three columns cannot fit. */

.registration-grid { display: grid; gap: .5rem; margin: .5rem 0 1.25rem; }
.registration-row {
    display: grid; grid-template-columns: minmax(0, 1fr) 13rem 13rem;
    gap: .75rem; align-items: start;
    padding: .6rem .7rem; border: 1px solid var(--slate-200); border-radius: var(--radius-sm);
    background: var(--surface);
}
.registration-row .form-label { margin-bottom: .15rem; }
.registration-row .field-help { margin-top: 0; }
.registration-expiry { display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; }
.registration-expiry .badge { white-space: nowrap; }

@media (max-width: 900px) {
    .registration-row { grid-template-columns: 1fr; }
    .registration-expiry { align-items: stretch; }
}

/* ------------------------------------------------------- settings colours */
.color-setting { display: flex; align-items: center; gap: .6rem; }
.form-control-color { width: 64px; height: 2.45rem; padding: .2rem .3rem; cursor: pointer; }
/* The hex box is typed into, so it must look typed-into - but it sits beside
   a 64px swatch inside a two-column settings grid, and a full-width
   .form-control would push the pair onto two lines. Same tokens as
   .form-control (they invert with the dark palette, so no dark override is
   needed), sized to exactly "#0f3d2e" and no wider. */
.color-setting-hex {
    width: 7.5em; flex: 0 0 auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .82rem; text-transform: lowercase;
    padding: .4rem .5rem;
    border: 1px solid var(--slate-300); border-radius: var(--radius-sm);
    color: var(--slate-800); background: var(--surface);
    transition: border-color .14s, box-shadow .14s;
}
.color-setting-hex:focus { outline: none; border-color: var(--blue-600); box-shadow: 0 0 0 3px rgb(2 132 199 / .14); }
.color-setting-hex:disabled { background: var(--slate-50); color: var(--slate-500); }

/* ------------------------------------------------------------ theme studio */
/* Settings -> Branding: preset gallery and live preview. */

.theme-preset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: .8rem; margin-top: .6rem; }
.theme-preset {
    display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; text-align: left;
    padding: .8rem .9rem; border: 2px solid var(--slate-200); border-radius: var(--radius);
    background: var(--surface); cursor: pointer; font: inherit; transition: border-color .15s, box-shadow .15s;
}
.theme-preset:hover { border-color: var(--slate-400); }
.theme-preset.is-active { border-color: var(--brand-primary); box-shadow: 0 0 0 3px rgb(2 132 199 / .15); }
.theme-preset small { font-size: .76rem; line-height: 1.35; }
.theme-swatches { display: flex; gap: 4px; margin-bottom: .2rem; }
.theme-swatch { width: 22px; height: 22px; border-radius: 6px; border: 1px solid rgb(15 23 42 / .12); }

.theme-preview {
    display: flex; border: 1px solid var(--slate-200); border-radius: var(--radius);
    overflow: hidden; min-height: 240px; box-shadow: var(--shadow-sm);
}
.theme-preview-sidebar {
    width: 108px; flex: none; background: #0f172a; color: #cbd5e1;
    display: flex; flex-direction: column; gap: .45rem; padding: .7rem .55rem; font-size: .74rem;
}
.theme-preview-brand { font-weight: 800; color: #fff; background: var(--blue-600); border-radius: 6px; padding: .25rem .45rem; align-self: flex-start; }
.theme-preview-active { background: var(--blue-600); color: #fff; border-radius: 6px; padding: .22rem .45rem; font-weight: 600; }
.theme-preview-sidebar span { padding: .18rem .45rem; }
.theme-preview-main { flex: 1; background: var(--body-bg); display: flex; flex-direction: column; }
.theme-preview-topbar { background: #fff; border-bottom: 1px solid var(--slate-200); padding: .45rem .7rem; font-size: .74rem; color: var(--slate-500); }
.theme-preview-body { padding: .8rem; display: flex; flex-direction: column; gap: .7rem; font-size: .85rem; }
.theme-preview-modal { border: 1px solid var(--slate-200); border-radius: 8px; overflow: hidden; background: var(--surface); max-width: 240px; box-shadow: var(--shadow-sm); }
.theme-preview-modal-head { padding: .4rem .6rem; border-bottom: 1px solid var(--slate-100); font-weight: 600; font-size: .78rem; }
.theme-preview-modal-body { padding: .5rem .6rem; font-size: .75rem; color: var(--slate-500); }
.theme-preview-buttons { display: flex; gap: .5rem; flex-wrap: wrap; }
.theme-preview-buttons .btn { pointer-events: none; }
.theme-preview-buttons [data-preview="danger"],
.theme-preview-buttons [data-preview="export"],
.theme-preview-buttons [data-preview="view"],
.theme-preview-buttons [data-preview="edit"],
.theme-preview-buttons [data-preview="book"] { border: 1px solid var(--slate-300); }
/* --------------------------------------------------- menu layout chooser */

.layout-choice { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; }
.layout-option {
    display: flex; gap: .9rem; align-items: flex-start;
    padding: .9rem; cursor: pointer;
    border: 1px solid var(--slate-200); border-radius: var(--radius); background: var(--surface);
    transition: border-color .15s, box-shadow .15s;
}
.layout-option:hover { border-color: var(--brand-primary); }
.layout-option.is-active { border-color: var(--brand-primary); box-shadow: 0 0 0 3px rgb(2 132 199 / .15); }
.layout-option input { margin-top: .25rem; flex: none; }
.layout-option-text { display: flex; flex-direction: column; gap: .25rem; }
.layout-option-text small { line-height: 1.4; }

/* A wireframe of each shell: dark block = the menu, pale blocks = the page. */
.layout-sketch {
    display: flex; flex: none; width: 74px; height: 56px; gap: 3px; padding: 3px;
    border: 1px solid var(--slate-200); border-radius: 6px; background: var(--body-bg); overflow: hidden;
}
.layout-sketch-menu { background: var(--brand-sidebar-bg); border-radius: 3px; flex: none; }
.layout-sketch-main { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.layout-sketch-bar { height: 7px; background: var(--slate-300); border-radius: 2px; flex: none; }
.layout-sketch-line { height: 5px; background: var(--slate-200); border-radius: 2px; flex: none; }
.layout-sketch-sidebar { flex-direction: row; }
.layout-sketch-sidebar .layout-sketch-menu { width: 18px; align-self: stretch; }
.layout-sketch-navbar { flex-direction: column; }
.layout-sketch-navbar .layout-sketch-menu { height: 10px; width: 100%; }

/* A miniature of the real table so the heading colour is judged in context. */
.theme-preview-table { width: 100%; border-collapse: collapse; font-size: .72rem; background: var(--surface); border-radius: 6px; overflow: hidden; }
.theme-preview-table th { text-align: left; padding: .3rem .45rem; background: var(--slate-50); color: var(--slate-500); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; font-size: .64rem; }
.theme-preview-table td { padding: .3rem .45rem; border-top: 1px solid var(--slate-100); color: var(--slate-600); }

/* -------------------------------------------------------------- mail tools */
.mail-tools { display: grid; gap: 1.4rem; margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--slate-200); }
.mail-tools h3 { font-size: .95rem; margin-bottom: .3rem; }
.mail-presets { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
.mail-test-row { display: flex; gap: .6rem; margin-top: .6rem; max-width: 480px; }
.mail-test-row .form-control { flex: 1 1 auto; }

/* ---------------------------------------------------------- queue check-in */
/* Check-in lives in a modal now, not in a full-width card above the board.
   The form element wraps .modal-body AND .modal-foot, so it must not be the
   flex row it used to be - that laid the body and the footer out side by side.
   The fields stack, which is the right shape at modal width anyway. */
.queue-checkin-patient { margin: 0 0 .7rem; position: relative; }
.queue-checkin-complaint { margin-bottom: .7rem; }
/* Both collapsibles under the check-in row take the full width beneath it.
   They are separate classes because they are separate ideas: one registers a
   person, the other records a visit, and only the first is about walk-ins. */
.queue-walkin, .queue-visit-detail { flex: 1 1 100%; }

/* ------------------------------------------------------------ system health */
/* Status is carried by an icon, a coloured rail AND a badge word - colour
   alone would leave the screen unreadable to anyone who cannot distinguish
   the greens from the ambers, and this is a screen people act on. */
.health-headline { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.health-headline strong { font-size: 1.05rem; }

.health-worst { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; }
.health-worst li { font-size: .9rem; display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; }

.health-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }

.health-item {
    display: flex; gap: .75rem; padding: .7rem .85rem;
    border: 1px solid var(--slate-200); border-left-width: 3px; border-radius: 6px;
    background: var(--surface, #fff);
}
.health-item .health-icon { width: 18px; height: 18px; flex: 0 0 18px; margin-top: .15rem; }
.health-body { flex: 1 1 auto; min-width: 0; }
.health-title { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.health-detail { color: var(--slate-600); font-size: .88rem; margin-top: .15rem; }
.health-remedy {
    margin-top: .4rem; font-size: .85rem; color: var(--slate-700);
    background: var(--slate-50); border-radius: 4px; padding: .4rem .55rem;
}

.health-ok      { border-left-color: #16a34a; }
.health-warn    { border-left-color: #d97706; }
.health-fail    { border-left-color: #dc2626; }
.health-unknown { border-left-color: #64748b; }

.health-ok      .health-icon { color: #16a34a; }
.health-warn    .health-icon { color: #d97706; }
.health-fail    .health-icon { color: #dc2626; }
.health-unknown .health-icon { color: #64748b; }

[data-theme="dark"] .health-item   { background: #0f172a; border-color: #1e293b; }
[data-theme="dark"] .health-remedy { background: #111c31; color: #cbd5e1; }
[data-theme="dark"] .health-detail { color: #94a3b8; }

/* ------------------------------------------------------------ support desk */
/* A ticket thread reads as a conversation, because that is what it is.
   Three shapes: a bubble for a message, a full-width panel for an internal
   note, and a small centred line for a status change - punctuation, not
   speech. Who said something is carried by SIDE, an avatar and a name, never
   by colour alone. */
.chat { display: grid; gap: .55rem; }

.chat-day {
    display: flex; align-items: center; gap: .6rem;
    margin: .5rem 0 .2rem; color: var(--slate-400); font-size: .76rem;
    text-transform: uppercase; letter-spacing: .04em;
}
.chat-day::before, .chat-day::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--slate-200); }

.chat-event { text-align: center; margin: .35rem 0; }
.chat-event span {
    display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap;
    padding: .3rem .75rem; border-radius: 999px;
    background: var(--slate-100, #f1f5f9); color: var(--slate-600);
    font-size: .81rem; line-height: 1.4;
}
.chat-event [data-icon] { width: 14px; height: 14px; flex: 0 0 auto; }
.chat-event em { font-style: normal; color: var(--slate-400); }

.chat-row { display: flex; gap: .55rem; align-items: flex-end; max-width: 100%; }

.chat-avatar {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
    display: grid; place-items: center; font-size: .72rem; font-weight: 700;
    background: var(--slate-200, #e2e8f0); color: var(--slate-700, #334155);
    user-select: none;
}

.chat-bubble {
    max-width: min(78%, 42rem);
    background: var(--surface, #fff);
    border: 1px solid var(--slate-200);
    border-radius: 12px 12px 12px 3px;
    padding: .55rem .75rem .6rem;
}
.chat-meta { display: flex; gap: .45rem; align-items: baseline; flex-wrap: wrap; font-size: .8rem; margin-bottom: .2rem; }
.chat-meta strong { font-weight: 600; }
.chat-meta time { color: var(--slate-400); font-size: .76rem; }
.chat-text { font-size: .92rem; line-height: 1.55; color: var(--slate-700); word-break: break-word; }

/* Mine: on the right, mirrored tail, tinted. */
.chat-row.is-mine { flex-direction: row-reverse; }
.chat-row.is-mine .chat-bubble {
    border-radius: 12px 12px 3px 12px;
    background: var(--blue-50, #eff6ff); border-color: var(--blue-100, #dbeafe);
}
.chat-row.is-mine .chat-avatar { background: var(--blue-100, #dbeafe); color: var(--blue-700, #1d4ed8); }
.chat-row.is-mine .chat-meta { flex-direction: row-reverse; }

/* The supplier: a different voice, and marked as one. */
.chat-row.chat-vendor .chat-avatar { background: #ede9fe; color: #6d28d9; }
.chat-row.chat-vendor .chat-bubble { border-color: #ddd6fe; }

.chat-tag {
    font-size: .68rem; font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
    padding: .1rem .4rem; border-radius: 4px; white-space: nowrap;
}
.chat-tag-vendor   { background: #ede9fe; color: #6d28d9; }
.chat-tag-internal { background: #fef3c7; color: #92400e; display: inline-flex; align-items: center; gap: .25rem; }
.chat-tag-internal [data-icon] { width: 12px; height: 12px; }

/* An internal note is not part of the conversation the requester is having,
   and should not look like one: full width, no tail, hatched. The guard is in
   the repository - this is so whoever writes one can see that at a glance. */
.chat-row.is-internal { flex-direction: row; }
.chat-row.is-internal .chat-bubble {
    max-width: 100%; flex: 1 1 auto; border-radius: 8px;
    border-color: #fcd34d;
    background: repeating-linear-gradient(135deg,
        rgba(217, 119, 6, .06), rgba(217, 119, 6, .06) 8px,
        transparent 8px, transparent 16px);
}
.chat-row.is-internal .chat-avatar { background: #fef3c7; color: #92400e; }

.chat-files { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .45rem; }

.attachment-chip {
    display: inline-flex; align-items: center; gap: .4rem; max-width: 100%;
    padding: .28rem .6rem; font-size: .8rem; text-decoration: none;
    border: 1px solid var(--slate-200); border-radius: 999px;
    color: var(--slate-700); background: var(--surface, #fff);
}
.attachment-chip:hover { background: var(--slate-50); border-color: var(--slate-300); }
.attachment-chip [data-icon] { width: 14px; height: 14px; flex: 0 0 auto; }
.attachment-chip .attachment-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 16rem; }
.attachment-chip small { color: var(--slate-400); flex: 0 0 auto; }

/* A ticket nobody has caught up with. Weight as well as tint - a colour wash
   alone disappears at low contrast and in dark mode. */
.table tr.is-unread > td { background: var(--blue-50, #eff6ff); }
[data-theme="dark"] .table tr.is-unread > td { background: #0f1e36; }

/* Radio choices with a sentence of explanation each. */
.choice-list { display: grid; gap: .6rem; }
.choice {
    display: flex; gap: .6rem; align-items: flex-start; cursor: pointer;
    border: 1px solid var(--slate-200); border-radius: 6px; padding: .65rem .75rem;
}
.choice:hover { background: var(--slate-50); }
.choice.is-active { border-color: var(--blue-600, #2563eb); background: var(--blue-50, #eff6ff); }
.choice input { margin-top: .2rem; flex: 0 0 auto; }
.choice-text { display: grid; gap: .15rem; font-size: .88rem; }
.choice-text small { line-height: 1.45; }

/* Stacked single-button forms in a sidebar card. */
.stack-form { display: grid; gap: .4rem; margin-bottom: .5rem; }

@media (max-width: 640px) {
    .chat-bubble { max-width: 88%; }
    .attachment-chip .attachment-name { max-width: 10rem; }
}

[data-theme="dark"] .chat-bubble { background: #0f172a; border-color: #1e293b; }
[data-theme="dark"] .chat-text { color: #cbd5e1; }
[data-theme="dark"] .chat-avatar { background: #1e293b; color: #cbd5e1; }
[data-theme="dark"] .chat-row.is-mine .chat-bubble { background: #132743; border-color: #1e3a5f; }
[data-theme="dark"] .chat-row.is-mine .chat-avatar { background: #1e3a5f; color: #bfdbfe; }
[data-theme="dark"] .chat-event span { background: #111c31; color: #94a3b8; }
[data-theme="dark"] .chat-day::before, [data-theme="dark"] .chat-day::after { background: #1e293b; }
[data-theme="dark"] .attachment-chip { background: #0f172a; border-color: #1e293b; color: #cbd5e1; }
[data-theme="dark"] .attachment-chip:hover { background: #111c31; }
[data-theme="dark"] .choice { border-color: #1e293b; }
[data-theme="dark"] .choice:hover { background: #111c31; }

/* --------------------------------------------- signed-out capability pages */
/* The shell for a page reached with a link rather than an account. No nav,
   because there is nowhere else its holder may go. */
.plain-body { background: var(--slate-50, #f8fafc); }
.plain-shell { max-width: 780px; margin: 0 auto; padding: 1.5rem 1rem 3rem; }
.plain-head {
    display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap;
    padding-bottom: .8rem; margin-bottom: 1.1rem; border-bottom: 1px solid var(--slate-200);
}
.plain-main { display: grid; gap: 1.1rem; }
.plain-foot { margin-top: 1.6rem; font-size: .82rem; color: var(--slate-400); text-align: center; }
[data-theme="dark"] .plain-body { background: #0b1220; }
[data-theme="dark"] .plain-head { border-color: #1e293b; }

/* ------------------------------------------------------------- demo ribbon */
/* One strip on every screen while the demo fence is up (App\Core\DemoMode).
   Amber on purpose: informational, not an error, and legible in both themes. */
.demo-ribbon {
    background: #b45309; color: #fff;
    text-align: center; font-size: .84rem; line-height: 1.35;
    padding: .45rem .9rem;
}
.demo-ribbon strong { font-weight: 700; }
[data-theme="dark"] .demo-ribbon { background: #92400e; }
