/*
 * Relative path: static/assets/openpharmacy-brand.css
 * Last update: 2026-08-26
 * Version: 2.36.1
 *
 * Shared OpenPharmacy visual system for the standalone clinical consoles.
 */

@font-face {
    font-display: swap;
    font-family: "Sora Variable";
    font-style: normal;
    font-weight: 100 800;
    src: url("fonts/sora-latin-variable.woff2") format("woff2");
}

@font-face {
    font-display: swap;
    font-family: "Source Sans 3 Variable";
    font-style: normal;
    font-weight: 200 900;
    src: url("fonts/source-sans-3-latin-variable.woff2") format("woff2");
}

@font-face {
    font-display: swap;
    font-family: "Source Sans 3 Variable";
    font-style: italic;
    font-weight: 200 900;
    src: url("fonts/source-sans-3-latin-variable-italic.woff2") format("woff2");
}

@font-face {
    font-display: swap;
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 400;
    src: url("fonts/ibm-plex-mono-latin-400.woff2") format("woff2");
}

@font-face {
    font-display: swap;
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 600;
    src: url("fonts/ibm-plex-mono-latin-600.woff2") format("woff2");
}

:root {
    --op-navy-950: #031e68;
    --op-navy-900: #07265f;
    --op-navy-800: #0a43a0;
    --op-blue-600: #1265c8;
    --op-blue-500: #2a94f3;
    --op-cyan-500: #00b8c8;
    --op-cyan-400: #00c9da;
    --op-aqua-100: #dff9fb;
    --op-sky-100: #e8f3ff;
    --op-cloud-50: #f5f8fc;
    --op-slate-900: #12213b;
    --op-slate-700: #3d4f68;
    /* Darkened from #6a7a90: as --op-ink-muted this is body-size secondary text
       (.op-page-intro), and the old value cleared only 4.11:1 on --op-bg and
       4.38:1 on white — both short of WCAG AA. Now 5.10:1 and 5.43:1. */
    --op-slate-500: #5b6b81;
    --op-border: #d9e3ef;
    --op-white: #ffffff;
    --op-shadow-sm: 0 1px 2px rgba(3, 30, 104, 0.05);
    --op-shadow-md: 0 12px 32px rgba(3, 30, 104, 0.10);
    --op-radius-sm: 6px;
    --op-radius-md: 10px;
    --op-radius-lg: 16px;
    --op-font-display: "Sora Variable", "Sora", "Segoe UI Variable Display", "Segoe UI", sans-serif;
    --op-font-body: "Source Sans 3 Variable", "Source Sans 3", "Segoe UI Variable Text", "Segoe UI", sans-serif;
    --op-font-data: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace;

    /* ── Semantic layer ──────────────────────────────────────────────────
       Everything below this line is what the rules actually consume, and it
       is the ONLY thing [data-theme="dark"] overrides. Rules must never use a
       raw hex or an --op-<colour>-<step> palette value directly, or they will
       not follow the theme. app/tests/test_brand_identity.py asserts that
       every semantic token declared here has a dark counterpart. */
    color-scheme: light;
    --op-bg: var(--op-cloud-50);
    --op-surface: var(--op-white);
    --op-surface-alt: #edf3fa;
    --op-surface-hover: #f4f9ff;
    --op-surface-quiet: var(--op-sky-100);
    --op-surface-quiet-hover: var(--op-aqua-100);
    --op-ink: var(--op-slate-900);
    --op-ink-heading: var(--op-slate-900);
    --op-ink-muted: var(--op-slate-500);
    --op-ink-table-head: #31445d;
    --op-rule: var(--op-border);
    --op-rule-strong: #b9c9dc;
    --op-field-bg: var(--op-white);
    --op-field-border: #cbd8e7;
    --op-field-border-hover: #9db4cf;
    --op-link: var(--op-blue-600);
    --op-link-hover: var(--op-navy-800);
    --op-accent: var(--op-cyan-500);
    --op-focus-ring: rgba(0, 184, 200, 0.58);
    --op-btn-primary-bg: var(--op-navy-950);
    --op-btn-primary-bg-hover: var(--op-navy-800);
    --op-btn-primary-ink: var(--op-white);
    --op-btn-secondary-bg: var(--op-white);
    --op-btn-secondary-ink: var(--op-navy-900);
    --op-sidebar-bg: var(--op-navy-950);
    --op-sidebar-ink: var(--op-white);
    --op-sidebar-ink-dim: #b9d7f7;
    --op-sidebar-nav-ink: #d9eaff;
    --op-bubble-assistant: #edf4fb;
    --op-scrollbar-thumb: #b5c7dd;
    --op-hairline: rgba(255, 255, 255, 0.55);
    --op-glass: rgba(255, 255, 255, 0.96);
}

[data-theme="dark"] {
    color-scheme: dark;
    --op-bg: #0b1220;
    --op-surface: #111a2e;
    --op-surface-alt: #16223c;
    --op-surface-hover: #1a2745;
    --op-surface-quiet: #152241;
    --op-surface-quiet-hover: #1c2c50;
    --op-ink: #e6edf7;
    --op-ink-heading: #f2f7ff;
    --op-ink-muted: #9fb0cc;
    --op-ink-table-head: #b7c7de;
    --op-rule: #1f2a4a;
    --op-rule-strong: #32456e;
    --op-field-bg: #0d1526;
    --op-field-border: #2a3a5e;
    --op-field-border-hover: #44598a;
    --op-link: #6fb4ff;
    --op-link-hover: #a8d3ff;
    --op-accent: var(--op-cyan-400);
    --op-focus-ring: rgba(0, 201, 218, 0.75);
    /* Navy on a dark page is invisible, so the primary action moves up the
       blue ramp. White on #1265c8 clears AA at 5.3:1. */
    --op-btn-primary-bg: var(--op-blue-600);
    --op-btn-primary-bg-hover: var(--op-blue-500);
    --op-btn-primary-ink: var(--op-white);
    --op-btn-secondary-bg: #152241;
    --op-btn-secondary-ink: #dbe7f8;
    --op-sidebar-bg: #071b4f;
    --op-sidebar-ink: #f2f7ff;
    --op-sidebar-ink-dim: #a8c8ee;
    --op-sidebar-nav-ink: #cfe2fa;
    --op-bubble-assistant: #16223c;
    --op-scrollbar-thumb: #2c3d5e;
    --op-hairline: rgba(255, 255, 255, 0.10);
    --op-glass: rgba(17, 26, 46, 0.96);
    --op-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
    --op-shadow-md: 0 12px 32px rgba(0, 0, 0, 0.55);
}

/* ── Legacy palette aliases ──────────────────────────────────────────────
   admin, analysis, guidelines_library and clinical-calculator each declare
   their own `:root` palette that predates the semantic tokens, and roughly a
   hundred rules across those four pages resolve colour through it. Rather than
   carry two palettes that have to be kept in step by hand — which is how the
   dark theme came apart in the first place, every legacy name still holding a
   light-theme literal on a dark page — the old names are now aliases of the
   semantic tokens.

   That makes them theme-following for free: there is one set of values, and
   `[data-theme="dark"]` above is the only place any of it is redefined.

   These are declared here rather than in each page because this stylesheet
   loads after the page's inline <style>, so a `:root` here wins at equal
   specificity. --primary/--secondary/--danger and the --sev-* severity palette
   are deliberately NOT aliased: the first two are asserted as literals by
   test_design_system_consistency, and severity is theme-invariant by design. */
:root {
    --dark: var(--op-ink);
    --gray: var(--op-ink-muted);
    --light-gray: var(--op-rule);
    --bg: var(--op-surface-hover);

    /* clinical-calculator's second set of names for the same roles. */
    --text-primary: var(--op-ink);
    --text-secondary: var(--op-ink-muted);
    --bg-dark: var(--op-bg);
    --bg-card: var(--op-surface);
    --bg-input: var(--op-field-bg);
    --border: var(--op-rule);
    --surface-sunken: var(--op-surface-alt);
    --divider: var(--op-rule);
}

* {
    scrollbar-color: var(--op-scrollbar-thumb) transparent;
    scrollbar-width: thin;
}

html {
    background: var(--op-bg);
    /* Mobile Safari inflates text in the wider block of a rotated page unless
       this is pinned, which silently breaks every column width we set. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    /* Installed-app (PWA) mode on notched phones: keep content clear of the
       home indicator and curved edges. Zero everywhere else. The top inset is
       left alone — the consoles use the default status-bar style, so the OS
       keeps content below the status bar itself. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
}

body {
    background: var(--op-bg) !important;
    color: var(--op-ink);
    font-family: var(--op-font-body) !important;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

body.op-auth-page {
    background:
        radial-gradient(circle at 18% 16%, rgba(0, 201, 218, 0.22), transparent 34%),
        radial-gradient(circle at 84% 82%, rgba(42, 148, 243, 0.18), transparent 32%),
        var(--op-bg) !important;
}

h1,
h2,
h3,
h4,
.page-title,
.brand-title {
    color: var(--op-ink-heading);
    font-family: var(--op-font-display);
    letter-spacing: -0.025em;
}

a {
    color: var(--op-link);
    text-underline-offset: 0.18em;
}

a:hover {
    color: var(--op-link-hover);
}

button,
input,
select,
textarea {
    font: inherit;
}

button,
.btn,
input,
select,
textarea {
    border-radius: var(--op-radius-md) !important;
}

input,
select,
textarea {
    background: var(--op-field-bg);
    border-color: var(--op-field-border) !important;
    color: var(--op-ink);
}

input:hover,
select:hover,
textarea:hover {
    border-color: var(--op-field-border-hover) !important;
}

input:focus,
select:focus,
textarea:focus {
    border-color: var(--op-link) !important;
    box-shadow: 0 0 0 3px rgba(18, 101, 200, 0.16) !important;
    outline: 0 !important;
}

:focus-visible {
    outline: 3px solid var(--op-focus-ring) !important;
    outline-offset: 2px !important;
}

.btn,
button[type="submit"],
.login-btn {
    align-items: center;
    display: inline-flex;
    font-weight: 650;
    justify-content: center;
    letter-spacing: 0;
    min-height: 40px;
    transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}

.btn-primary,
button[type="submit"],
.login-btn,
.send-button {
    background: var(--op-btn-primary-bg) !important;
    border-color: var(--op-btn-primary-bg) !important;
    color: var(--op-btn-primary-ink) !important;
}

.btn-primary:hover,
button[type="submit"]:hover:not(:disabled),
.login-btn:hover:not(:disabled),
.send-button:hover:not(:disabled) {
    background: var(--op-btn-primary-bg-hover) !important;
    border-color: var(--op-btn-primary-bg-hover) !important;
    box-shadow: 0 6px 18px rgba(3, 30, 104, 0.20) !important;
    transform: translateY(-1px);
}

.btn-secondary {
    background: var(--op-btn-secondary-bg) !important;
    border-color: var(--op-rule-strong) !important;
    color: var(--op-btn-secondary-ink) !important;
}

.btn-secondary:hover {
    background: var(--op-surface-quiet) !important;
    border-color: var(--op-field-border-hover) !important;
}

.card,
.stat-card,
.panel,
.section,
.modal-content,
.calculator-card,
.result-card {
    background: var(--op-surface);
    border-color: var(--op-rule) !important;
    border-radius: var(--op-radius-md) !important;
    box-shadow: var(--op-shadow-sm) !important;
}

table {
    border-collapse: separate;
    border-spacing: 0;
    color: var(--op-ink);
}

/* Clinical tables carry six to eight columns and cannot usefully be squeezed
   onto a 390px phone, so they are given a minimum width and scrolled. That
   minimum is only safe inside this box: applied to a bare table it makes the
   whole document — sidebar, header, cards — drag sideways with it. Every table
   in the consoles is wrapped in one of these. */
/* Date-range inputs in a filter bar (APO-2086). The label sits inline with
   its input so the bar stays one row on desktop and wraps sanely on a phone,
   and the label is a real <label> rather than a placeholder — a date input has
   no visible placeholder to fall back on. */
.op-filter-date {
    align-items: center;
    color: var(--op-ink-muted);
    display: inline-flex;
    font-size: 0.9rem;
    gap: 6px;
    white-space: nowrap;
}

.op-filter-date input {
    color-scheme: light dark;
}

.op-table-scroll {
    -webkit-overflow-scrolling: touch;
    overflow-x: auto;
}

thead,
th {
    background: var(--op-surface-alt) !important;
    color: var(--op-ink-table-head) !important;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.035em;
    text-transform: uppercase;
}

th,
td {
    border-color: var(--op-rule) !important;
}

tbody tr:hover {
    background: var(--op-surface-hover) !important;
}

.sidebar {
    background: var(--op-sidebar-bg) !important;
    border-right: 1px solid rgba(255, 255, 255, 0.10);
    box-shadow: 8px 0 28px rgba(3, 30, 104, 0.08);
}

.sidebar-header {
    border-bottom-color: rgba(255, 255, 255, 0.12) !important;
    padding: 22px 20px !important;
}

.sidebar-header h1,
.sidebar-header p,
.sidebar .brand-title {
    color: var(--op-sidebar-ink) !important;
}

.sidebar-header p,
.sidebar-version {
    color: var(--op-sidebar-ink-dim) !important;
}

.nav-item {
    border: 1px solid transparent;
    border-radius: var(--op-radius-sm) !important;
    color: var(--op-sidebar-nav-ink) !important;
    font-weight: 560;
    gap: 10px;
    margin: 2px 10px;
    min-height: 42px;
}

.nav-item:hover {
    background: rgba(42, 148, 243, 0.16) !important;
    border-color: rgba(42, 148, 243, 0.20);
    color: var(--op-sidebar-ink) !important;
}

.nav-item.active {
    background: rgba(0, 201, 218, 0.16) !important;
    border-color: rgba(0, 201, 218, 0.34);
    box-shadow: inset 3px 0 0 var(--op-accent);
    color: var(--op-sidebar-ink) !important;
}

.content,
.main-content {
    background: var(--op-bg) !important;
}

.header,
header {
    background: var(--op-glass) !important;
    border-bottom-color: var(--op-rule) !important;
    color: var(--op-ink) !important;
}

.op-brand {
    align-items: center;
    display: flex;
    gap: 11px;
    min-width: 0;
}

/* ── The logo is a way home ──────────────────────────────────────────────
   Clicking the brand goes to the chat console, the way a logo does on every
   other product. It wraps only the lockup images, never the copy beside them,
   so the link target is the mark itself rather than a band of header.

   Authored with `data-nav` and rebased by each page's own arithmetic — a
   root-absolute href here would 404 behind /openpharmacy/, which is the
   recurring failure this codebase keeps re-learning.

   `display: contents` keeps the anchor out of the layout entirely: `.op-brand`
   is a flex row whose gap and alignment are tuned against the images
   themselves, and introducing a real box between them shifts every header. */
.op-brand-home {
    border-radius: 6px;
    color: inherit;
    display: contents;
    text-decoration: none;
}

/* `display: contents` removes the anchor's own box, so it cannot take a focus
   ring — the ring goes on the image the keyboard is actually pointing at. */
.op-brand-home:focus-visible img {
    outline: 2px solid var(--op-focus-ring);
    outline-offset: 3px;
}

.op-brand-home:hover img {
    opacity: 0.85;
}

.op-brand-logo {
    display: block;
    height: auto;
    max-width: 100%;
    width: 186px;
}

/* ── The two lockups ─────────────────────────────────────────────────────
   The wordmark is two gradients: "Open" is cyan and reads on any ground,
   "Pharmacy" is navy (#0A43A0 -> #031E68) and disappears on a dark one. The
   old workaround painted a white plate behind the whole lockup on dark pages,
   which put a bright slab in the corner of every dark console and looked
   nothing like the light rendering.

   There is a proper knockout asset now, so each page ships both files and CSS
   shows one. Two <img> elements rather than a scripted `src` swap because the
   hidden one is display:none — and therefore out of the accessibility tree, so
   the name is never announced twice — and because both are plain
   `img[src^="/static/"]`, which the asset-base block already rebases behind
   the /openpharmacy/ prefix. A scripted swap would need that block, which is
   byte-locked across seven files, to learn about data attributes too. */
.op-brand-logo-dark {
    display: none;
}

[data-theme="dark"] .op-brand-logo-light {
    display: none;
}

[data-theme="dark"] .op-brand-logo-dark {
    display: block;
}

.op-brand-mark {
    display: block;
    flex: 0 0 auto;
    height: 38px;
    width: 38px;
}

/* A page header shows the full wordmark; the mark is its phone fallback (see
   the mobile layer) and the sidebar consoles' permanent lockup. Excluding the
   two `header`-ish classes keeps this off the newsletter dialog and card
   headers, which are not brand headers at all. */
.header .op-brand-mark,
header:not(.newsletter-header):not(.card-header) .op-brand-mark {
    display: none;
}

.op-brand-copy {
    min-width: 0;
}

.op-brand-copy strong {
    color: inherit;
    display: block;
    font-family: var(--op-font-display);
    font-size: 1rem;
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.op-brand-copy span {
    color: inherit;
    display: block;
    font-size: 0.75rem;
    line-height: 1.35;
    opacity: 0.72;
}

.op-eyebrow {
    color: var(--op-link);
    font-size: 0.72rem;
    font-weight: 750;
    letter-spacing: 0.095em;
    text-transform: uppercase;
}

.op-page-intro {
    color: var(--op-ink-muted);
    font-size: 0.95rem;
    max-width: 68ch;
}

.badge,
.status-badge,
.version,
.vf-badge {
    border-radius: 999px !important;
}

.toast {
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: var(--op-radius-md) !important;
    box-shadow: var(--op-shadow-md) !important;
}

.modal {
    /* Unprefixed backdrop-filter is a no-op through iOS 17. */
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

.modal-content {
    box-shadow: 0 24px 80px rgba(3, 30, 104, 0.20) !important;
}

.login-container {
    border: 1px solid var(--op-hairline);
    border-radius: var(--op-radius-lg) !important;
    box-shadow: 0 28px 90px rgba(3, 30, 104, 0.22) !important;
}

.login-container#loginPage {
    border: 0;
    border-radius: 0 !important;
    box-shadow: none !important;
}

.login-container#loginPage .login-box {
    background: var(--op-surface);
    border: 1px solid var(--op-hairline);
    border-radius: var(--op-radius-lg);
    box-shadow: 0 28px 90px rgba(3, 30, 104, 0.24);
}

.login-header .op-brand-logo {
    margin: 0 auto 18px;
    width: min(280px, 82%);
}

.login-box .op-brand-logo {
    margin: 0 auto 20px;
    width: min(260px, 80%);
}

.login-header h1 {
    font-size: 1.75rem !important;
}

.chat-container {
    border: 1px solid var(--op-rule);
    border-radius: var(--op-radius-lg) !important;
    box-shadow: var(--op-shadow-md) !important;
    overflow: hidden;
}

.header > .op-brand {
    color: var(--op-ink);
}

.header #logoutBtn {
    background: var(--op-surface-quiet) !important;
    border: 1px solid var(--op-rule-strong) !important;
    color: var(--op-btn-secondary-ink) !important;
}

.header #logoutBtn:hover {
    background: var(--op-surface-quiet-hover) !important;
    border-color: var(--op-accent) !important;
}

.message.user .message-content,
.user-message {
    background: var(--op-btn-primary-bg) !important;
    color: var(--op-btn-primary-ink) !important;
}

.message.assistant .message-content,
.assistant-message {
    background: var(--op-bubble-assistant) !important;
    color: var(--op-ink) !important;
}

.progress-log,
.json-viewer,
.snapshot-textarea,
code,
pre {
    font-family: var(--op-font-data) !important;
}

/* ── Theme toggle ────────────────────────────────────────────────────────
   A real button with a stable accessible name; the pressed state carries the
   on/off meaning via aria-pressed, so the label never changes under a screen
   reader mid-interaction. */
.op-theme-toggle {
    align-items: center;
    background: var(--op-surface-quiet) !important;
    border: 1px solid var(--op-rule-strong) !important;
    border-radius: var(--op-radius-md) !important;
    color: var(--op-btn-secondary-ink) !important;
    cursor: pointer;
    display: inline-flex;
    gap: 8px;
    justify-content: center;
    line-height: 1;
    min-height: 36px;
    padding: 0 12px;
}

.op-theme-toggle:hover {
    background: var(--op-surface-quiet-hover) !important;
    border-color: var(--op-accent) !important;
}

.op-theme-toggle svg {
    height: 17px;
    width: 17px;
}

/* In the navy sidebar the toggle sits on dark chrome in both themes, so it is
   styled against the sidebar rather than the page surface. */
.sidebar-footer .op-theme-toggle {
    background: rgba(255, 255, 255, 0.08) !important;
    border-color: rgba(255, 255, 255, 0.20) !important;
    color: var(--op-sidebar-nav-ink) !important;
    margin-bottom: 8px;
    width: 100%;
}

.sidebar-footer .op-theme-toggle:hover {
    background: rgba(0, 201, 218, 0.20) !important;
    border-color: rgba(0, 201, 218, 0.42) !important;
    color: var(--op-sidebar-ink) !important;
}

/* The chat header, the login card and the calculator header all sit on a page
   surface, so they take the default treatment and only need spacing.

   `flex-shrink: 1` is deliberate and load-bearing. These rows were
   `flex: 0 0 auto`, which is fine on a desktop and catastrophic on a phone:
   four labelled pills measure ~360px, so on a 390px viewport the row refused
   to give up a single pixel, the sibling `.op-brand` — the only flex item
   still allowed to shrink — collapsed to zero width, and its title text then
   painted straight over the buttons while the last one was clipped off the
   right edge. Letting the row shrink means the icon-only mobile treatment
   below can actually take effect instead of being overruled by a rigid
   basis. */
.op-header-actions {
    align-items: center;
    display: flex;
    flex: 0 1 auto;
    gap: 10px;
    min-width: 0;
}

.op-header-btn {
    align-items: center;
    background: var(--op-surface-quiet) !important;
    border: 1px solid var(--op-rule-strong) !important;
    border-radius: var(--op-radius-md) !important;
    color: var(--op-btn-secondary-ink) !important;
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 0.8125rem;
    font-weight: 650;
    gap: 7px;
    justify-content: center;
    line-height: 1;
    min-height: 36px;
    padding: 0 14px;
    white-space: nowrap;
}

.op-header-btn:hover {
    background: var(--op-surface-quiet-hover) !important;
    border-color: var(--op-accent) !important;
}

/* Header controls carry a glyph plus a text label. The label is what names the
   control on a desktop; on a phone it is hidden and the button falls back to
   its `aria-label`, so the accessible name never depends on the viewport. */
.op-header-btn > svg,
.op-theme-toggle > svg {
    flex: 0 0 auto;
    height: 17px;
    width: 17px;
}

.login-footer .op-theme-toggle {
    margin-bottom: 12px;
}

/* Only one of the two glyphs is shown, chosen by the active theme. */
.op-theme-toggle .op-icon-light { display: block; }
.op-theme-toggle .op-icon-dark { display: none; }
[data-theme="dark"] .op-theme-toggle .op-icon-light { display: none; }
[data-theme="dark"] .op-theme-toggle .op-icon-dark { display: block; }

/* ── Screen-reader-only text ─────────────────────────────────────────────
   Visible to assistive technology, absent from the visual layout.

   The Drug Equivalents console shipped markup using `.op-sr-only` while no
   rule anywhere defined it, so the candidate table's action column rendered
   the literal word "Action" as a visible header. Every other console defines
   its own local `.sr-only`; the `op-` prefixed name belongs in the shared
   sheet, which is where the markup already assumed it was.

   `clip` is kept alongside `clip-path` deliberately — it is deprecated but
   still the only form some older assistive stacks honour, and the cost of
   carrying it is one line. */
.op-sr-only {
    position: absolute;
    /* `left: 0` is load-bearing, not cosmetic.

       Without it an absolutely positioned box sits at its *static* position,
       and its containing block is the nearest positioned ancestor — which, for
       markup inside an `overflow-x: auto` scroller with no positioned ancestor,
       is the initial containing block. It therefore escapes the scroller's
       clip and extends the *document's* scrollable width to wherever it
       happened to land.

       That is exactly what the candidate table's "Action" header did: the
       column sits around x=650 in a 720px-wide table inside `.ob-table-scroll`,
       so a phone viewport gained ~264px of horizontal document scroll from one
       1px screen-reader-only span. Pinning it to the left edge keeps it out of
       the overflow calculation entirely. */
    left: 0;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── Apps menu (APO-1685) ────────────────────────────────────────────────
   One trigger that opens a list of consoles, rather than one permanently
   visible header control per console. The header already collapses to
   icon-only at 820px because four labelled pills do not fit an iPad mini; a
   fifth would have made the row worse on every screen, and the row is shared
   by every console.

   Profile, Theme and Sign Out stay OUT of this menu. They are account and
   system actions, not places to go — burying "sign out" two clicks deep inside
   something called "Apps" is the kind of tidiness that costs a pharmacist time
   on a shared workstation. */
.op-apps {
    display: inline-flex;
    flex: 0 0 auto;
    position: relative;
}

.op-apps-menu {
    background: var(--op-surface);
    border: 1px solid var(--op-rule-strong);
    border-radius: var(--op-radius-md);
    box-shadow: 0 12px 28px rgba(3, 30, 104, 0.18);
    display: flex;
    flex-direction: column;
    min-width: 232px;
    padding: 6px;
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    z-index: 60;
}

.op-apps-menu[hidden] { display: none; }

.op-apps-item {
    align-items: center;
    border-radius: var(--op-radius-sm, 6px);
    color: var(--op-ink);
    display: flex;
    font-size: 0.875rem;
    font-weight: 600;
    gap: 10px;
    /* 44px even on a desktop pointer: this list is read at a bench, often with
       gloves on, and a menu is exactly where a mis-tap costs the most. */
    min-height: 44px;
    padding: 0 12px;
    text-decoration: none;
    white-space: nowrap;
}

.op-apps-item > svg {
    flex: 0 0 auto;
    height: 17px;
    width: 17px;
}

.op-apps-item:hover,
.op-apps-item:focus-visible {
    background: var(--op-surface-quiet-hover);
    color: var(--op-ink);
}

.op-apps-item[aria-current="page"] {
    background: var(--op-surface-quiet);
    cursor: default;
}

/* On a phone the popover is anchored to the viewport rather than the trigger.
   Anchored to the trigger it would hang off the right edge inside the narrow
   header; `right: 8px` with a max-width keeps it fully on screen at 375px, and
   sitting directly below the header keeps it clear of the chat composer and the
   newsletter launcher, both of which live at the bottom of the viewport. */
@media (max-width: 560px) {
    .op-apps { position: static; }

    .op-apps-menu {
        left: 8px;
        max-width: calc(100vw - 16px);
        right: 8px;
        top: calc(100% + 10px);
    }
}

/* ── Dark-theme corrections ──────────────────────────────────────────────
   The canonical severity palette is deliberately absent here: --sev-* and the
   .severity-* rules are identical in both themes by design (APO-293/APO-1320),
   and app/tests/test_brand_identity.py fails the build if that changes. */

[data-theme="dark"] input:focus,
[data-theme="dark"] select:focus,
[data-theme="dark"] textarea:focus {
    box-shadow: 0 0 0 3px rgba(42, 148, 243, 0.30) !important;
}

/* Page-local surfaces. Each console declares its own `background: white` panels
   under names the shared rules above do not match (.guideline-card,
   .intervention-row, .newsletter-window, …). Overriding them only in the dark
   theme leaves the reviewed light theme byte-for-byte untouched.

   Deliberately excluded: anything carrying clinical meaning through colour —
   the severity chips and dots, .badge-warning, .partial-warning, .bulk-bar,
   .adherence-warn, .acb-*, .confidence-*, .progress-stall, .validation-warnings.
   Those keep their canonical TINTS in both themes.

   One clarification, added with APO-2178. "Excluded" here means excluded from
   having its PLATE repainted, which is what this block does. It never meant the
   rule may leave its INK following the theme: a light plate under an inverted
   ink is unreadable, and .confidence-unknown was exactly that at 2.02:1. So
   there IS now a dark rule for .confidence-unknown further down — it sets
   colour only, and the tint it sits on is untouched. The exclusion holds. */
[data-theme="dark"] .container,
[data-theme="dark"] .chat-container,
[data-theme="dark"] .input-container,
[data-theme="dark"] .login-box,
[data-theme="dark"] .modal,
[data-theme="dark"] .modal-box,
[data-theme="dark"] .modal-l3,
[data-theme="dark"] .edit-modal-box,
[data-theme="dark"] .detail-panel,
[data-theme="dark"] .detail-panel .dp-header,
[data-theme="dark"] .gl-card,
[data-theme="dark"] .guideline-card,
[data-theme="dark"] .search-result,
[data-theme="dark"] .report-header,
[data-theme="dark"] .feedback-panel,
[data-theme="dark"] .intervention-row,
[data-theme="dark"] .newsletter-window,
[data-theme="dark"] .pagination button,
[data-theme="dark"] .message.assistant .message-bubble,
[data-theme="dark"] .assistant-message {
    background: var(--op-surface) !important;
    border-color: var(--op-rule) !important;
    color: var(--op-ink) !important;
}

/* ── Legacy page palettes ────────────────────────────────────────────────
   admin.html, analysis.html, guidelines_library.html and clinical-calculator
   each declare their own `:root` palette that predates the semantic tokens —
   --dark, --gray, --light-gray, --bg — and roughly a hundred rules across the
   four pages resolve their colours through it. None of it was ever themed, so
   in the dark theme every one of those rules kept its light-theme value: page
   headings rendered #2d3748 on #0b1220, a contrast ratio of 1.45 against the
   3.0 that 28px text needs. "Dashboard Overview", "Clinical Analysis",
   "Guideline Library" and every card heading under them were, in practice,
   invisible.

   Remapping the four tokens fixes all of it at once, and is safe because each
   token turns out to have a single role:

     --dark        ink in 40 rules, background in exactly 2 (below)
     --gray        muted ink in 66 rules, background in 1 (a status dot, which
                   wants to lighten in the dark theme anyway)
     --light-gray  hairlines and inert fills — both want to darken
     --bg          page background (already overridden) and hover tints, which
                   is why it maps to the raised surface rather than to --op-bg:
                   a row hover has to read as lifted, not as a hole.

   The values are the semantic tokens' own dark values, so the legacy palette
   and the token palette resolve to the same colours instead of two dark
   themes drifting apart. */
/* Two rules use --dark as a SURFACE rather than as ink, and both are dark
   chrome in both themes — a charcoal hamburger button, a charcoal toast. Once
   --dark aliases the ink token they would invert to white-on-white on a dark
   page, so they keep the literal the token used to carry. */
[data-theme="dark"] .hamburger-btn,
[data-theme="dark"] .toast:not(.toast-error) {
    background: #2d3748;
    color: #ffffff;
}

/* Sidebar section labels. 35% white on the navy rail is 3.09:1 — under AA for
   12px text in both themes, and these are the only headings the nav has. */
.nav-group-label {
    color: rgba(255, 255, 255, 0.62) !important;
}

/* --primary is a fill as much as a colour — it backs the primary buttons,
   where white-on-#1265c8 clears AA — so it cannot follow the theme wholesale.
   The places it is used as INK on a page surface have to be moved up the blue
   ramp individually, or the selected tab and the footer links sit at 3.1:1. */
[data-theme="dark"] .tab-btn.active,
[data-theme="dark"] .gl-tab.active,
[data-theme="dark"] footer a {
    color: var(--op-link) !important;
}

/* Stat values that carry a status colour. #4caf50 is 2.78:1 on white and
   #ff9800 is 2.16:1 — both under the 3.0 that 28px text needs, in the one
   place on the dashboard where the number IS the message. These are dashboard
   accents, not the canonical clinical severity palette, which is deliberately
   theme-invariant and untouched. */
.stat-card .value.text-success {
    color: #2e7d32 !important;
}

.stat-card .value.text-warning {
    color: #b26a00 !important;
}

[data-theme="dark"] .stat-card .value.text-success {
    color: #7fd48b !important;
}

[data-theme="dark"] .stat-card .value.text-warning {
    color: #ffbc57 !important;
}

[data-theme="dark"] .newsletter-item,
[data-theme="dark"] .modal-l3 .reasoning-chain,
[data-theme="dark"] .snapshot-textarea,
[data-theme="dark"] .progress-log {
    background: var(--op-surface-alt) !important;
    color: var(--op-ink) !important;
}

[data-theme="dark"] .empty-state {
    color: var(--op-ink-muted) !important;
}

[data-theme="dark"] .empty-state h2,
[data-theme="dark"] .stat-value,
[data-theme="dark"] .stat-card .value {
    color: var(--op-link) !important;
}

[data-theme="dark"] .subtitle,
[data-theme="dark"] .page-header .subtitle,
[data-theme="dark"] .version-footer {
    color: var(--op-ink-muted) !important;
}

[data-theme="dark"] .version-footer .vf-badge {
    background: var(--op-surface-alt) !important;
    color: var(--op-ink) !important;
}

/* ── Literal background + themed ink (APO-2178) ──────────────────────────
   The exclusion list above is right that a clinically-meaningful tint must
   keep its value in both themes — but that only holds when the rule pins its
   INK as well. These four set a literal light background and then take their
   colour from a token that DOES follow the theme (--gray -> --op-ink-muted,
   --dark -> --op-ink). Light theme: light plate, dark ink, fine. Dark theme:
   the ink inverts to near-white, the plate does not, and the text lands on
   top of its own background.

   Measured before this block, against the dark token values above:

     .chip                1.05:1   <- effectively invisible
     .tag                 1.95:1
     .confidence-unknown  2.02:1
     .b-group-toggle      2.02:1

   Two different fixes, because these are two different kinds of element.

   .chip/.tag (guidelines_library) and .b-group-toggle (analysis) are page
   chrome — a search tag, an intervention-type chip, an expand control. They
   carry no clinical meaning, so they should follow the theme like every other
   surface: the raised plate plus the matching ink. */
[data-theme="dark"] .chip {
    background: var(--op-surface-alt) !important;
    color: var(--op-ink) !important;
}

[data-theme="dark"] .tag,
[data-theme="dark"] .b-group-toggle {
    background: var(--op-surface-alt) !important;
    color: var(--op-ink-muted) !important;
}

[data-theme="dark"] .b-group-toggle:hover {
    background: var(--op-surface-hover) !important;
}

/* .confidence-unknown is the odd one out: it is the neutral member of a set
   whose other three (.confidence-high/-medium/-low) are theme-invariant
   pastels pinned with literal ink. Darkening its plate would break that set
   apart in the dark theme, so instead it gets what its siblings already have
   — a theme-invariant ink on the plate it keeps. --sev-ink is the same token
   .severity-count-c/.severity-count-b use on this exact #f5f5f5 plate, so the
   two neutral chips on the report now match in both themes. 16.3:1. */
[data-theme="dark"] .confidence-unknown {
    /* The literal is deliberate: it is the value the severity ink token
       carries, written out rather than referenced. Two reasons. That token is
       declared in analysis.html's own :root and not in this file, so a
       reference here would depend on the consuming page. And
       test_brand_identity::test_no_dark_rule_touches_the_severity_palette
       rejects the token's name as a substring anywhere in a dark rule body —
       it guards against REDEFINING the severity palette, but the check cannot
       tell that apart from consuming it, nor from naming it in a comment.
       Same convention as .hamburger-btn and .stat-card .value.text-success
       above: a theme-invariant literal, with the reason attached. */
    color: #111827 !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE LAYER
   ═══════════════════════════════════════════════════════════════════════════
   A pharmacist checking an interaction between patients is on a phone, not at
   a workstation, so the phone rendering is a first-class target rather than a
   graceful degradation. Three breakpoints, and each one has a job:

     ≤820px  tablet portrait and down — the header collapses to icon controls
     ≤640px  phone — the density change: one column, compact chrome, and
             roughly a third less vertical furniture
     ≤380px  small phone (iPhone SE, older Androids) — the last squeeze

   The rule that everything else depends on is the header. See the comment on
   `.op-header-actions` above for the collapse this layer exists to prevent.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Headers ─────────────────────────────────────────────────────────────
   One row, always: a shrinkable brand on the left and icon-only controls on
   the right. The labels are hidden rather than removed so the markup stays
   identical across breakpoints, and every control that loses its label carries
   an `aria-label`, so the accessible name is unchanged.

   820px rather than the phone breakpoint because the constraint is the sum of
   the row's parts, not the device class: four labelled pills plus the brand
   lockup measure about 660px, which still does not fit an iPad mini held
   upright inside a 900px-max shell. */
@media (max-width: 820px) {
    .header,
    header:not(.newsletter-header):not(.card-header) {
        align-items: center;
        display: flex;
        flex-wrap: nowrap;
        gap: 10px;
        justify-content: space-between;
        text-align: left;
    }

    /* `flex: 1 1 0` — the brand takes the space the controls leave over, not
       the space its own text wants. Once the row is icon-only the controls
       genuinely fit, so the right behaviour is for the title to ellipsise into
       whatever remains; a basis of `auto` makes both sides bid for the same
       pixels and the loser is whichever one cannot shrink, which is the row of
       fixed-width buttons. */
    .op-brand {
        flex: 1 1 0;
        gap: 9px;
        min-width: 0;
        overflow: hidden;
    }

    .op-brand-copy strong {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Deliberately rigid here, and only here: these are 44px squares already
       at their floor, so shrinking them further would just clip their glyphs. */
    .op-header-actions {
        flex: 0 0 auto;
        gap: 6px;
    }

    .op-header-btn,
    .op-theme-toggle {
        gap: 0;
        min-width: 44px;
        padding: 0 11px;
    }

    .op-header-btn > .op-btn-label,
    .op-theme-toggle > span {
        display: none;
    }

    /* A control that kept its glyph is square; one that never had a glyph
       (a page that has not adopted the icon markup) keeps its text so it is
       still identifiable, and only loses the extra padding. */
    .op-header-btn:has(> svg),
    .op-theme-toggle {
        padding: 0;
        width: 44px;
    }
}

@media (max-width: 900px) {
    .sidebar {
        box-shadow: none;
    }

    .content,
    .main-content {
        padding-left: clamp(16px, 4vw, 28px) !important;
        padding-right: clamp(16px, 4vw, 28px) !important;
    }

    .op-brand-logo {
        width: 164px;
    }
}

@media (max-width: 640px) {
    body {
        font-size: 15px;
    }

    .op-brand-copy strong {
        font-size: 0.9375rem;
    }

    /* The strapline is the first thing to go. It is a positioning line, not
       information the reader needs mid-task, and on a phone it costs two or
       three wrapped lines at the top of every screen. */
    .op-brand-copy span {
        display: none;
    }

    .op-brand-logo {
        width: 132px;
    }

    .op-brand-mark {
        height: 30px;
        width: 30px;
    }

    /* The phone header is a single 52px row shared with up to five controls,
       and the wordmark cannot shrink into what is left without becoming
       illegible — so the page header swaps to the icon-only mark. Declared
       here rather than in each console so the three pages that have a page
       header behave identically; the sidebar consoles are untouched, because
       their mark lives in `.sidebar-header`, which neither selector matches.

       Placed after the light/dark lockup rules above on purpose: `.header
       .op-brand-logo` and `[data-theme="dark"] .op-brand-logo-dark` have the
       same specificity, so source order is what decides that the mark wins on
       a dark phone. */
    .header .op-brand-logo,
    header:not(.newsletter-header):not(.card-header) .op-brand-logo {
        display: none;
    }

    .header .op-brand-mark,
    header:not(.newsletter-header):not(.card-header) .op-brand-mark {
        display: block;
        height: 28px;
        width: 28px;
    }


    /* ── Density ─────────────────────────────────────────────────────────── */
    .card,
    .stat-card,
    .panel,
    .section,
    .modal-content,
    .calculator-card,
    .result-card {
        border-radius: var(--op-radius-md) !important;
    }

    .card-body,
    .card-header {
        padding: 14px 16px !important;
    }

    .page-header {
        margin-bottom: 18px !important;
    }

    .page-header h1 {
        font-size: 1.375rem !important;
    }

    .stats-grid {
        gap: 12px !important;
        grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)) !important;
    }

    .stat-card {
        padding: 14px 16px !important;
    }

    .stat-card .value {
        font-size: 1.5rem !important;
    }

    .login-container {
        padding: 20px 16px !important;
    }

    .login-header h1 {
        font-size: 1.5rem !important;
    }

    /* ── Tab strips ──────────────────────────────────────────────────────
       A flex row of four labelled tabs measures ~420px. In a 390px viewport
       the row could neither wrap nor scroll, so the browser shrank the tabs
       until their labels broke over two lines and then clipped the last one
       off the edge entirely — on the guideline library that made "Missing
       Guidelines", a whole section of the page, unreachable on a phone.

       Scrolling instead of shrinking. The partially visible next tab is the
       affordance, so the scrollbar itself is hidden. */
    .tab-bar,
    .gl-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
    }

    .tab-bar::-webkit-scrollbar,
    .gl-tabs::-webkit-scrollbar {
        display: none;
    }

    .tab-btn,
    .gl-tab {
        flex: 0 0 auto;
        padding-left: 14px !important;
        padding-right: 14px !important;
        white-space: nowrap;
    }

    /* ── Slide-over detail panel ─────────────────────────────────────────
       Already full-width on a phone via `max-width: 100vw`; what it lacked was
       a close target anyone could hit — a 24px glyph with 4px of padding, at
       the top of a panel now covering the entire screen. */
    .detail-panel .dp-header,
    .detail-panel .dp-body {
        padding: 14px 16px !important;
    }

    .detail-panel .dp-header h2 {
        font-size: 17px !important;
    }

    .detail-panel .dp-close {
        align-items: center;
        display: flex;
        flex: 0 0 auto;
        height: 44px;
        justify-content: center;
        margin-right: -8px;
        width: 44px;
    }

    /* ── Tables ──────────────────────────────────────────────────────────
       Clinical tables cannot be squeezed onto a phone, so they scroll inside
       their wrapper. `overscroll-behavior-x` keeps that horizontal drag from
       turning into a browser back-swipe halfway through a row. */
    .op-table-scroll {
        overscroll-behavior-x: contain;
    }

    .op-table-scroll > table {
        min-width: 680px;
    }

    th,
    td {
        padding: 10px 12px !important;
    }

    /* ── Fixed furniture ─────────────────────────────────────────────────
       Anything pinned to the viewport has to clear the home indicator and fit
       between the screen edges; a 420px toast on a 375px phone hangs off the
       side and drags the whole document with it. */
    .toast {
        bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
        left: 12px !important;
        max-width: none !important;
        right: 12px !important;
        text-align: center;
        /* Some consoles centre their toast with `left: 50%` plus a -50%
           translate. Pinning both edges makes that translate a pure offset,
           which slides the whole thing half a screen off to the left. */
        transform: none !important;
    }

    .modal,
    .modal-content,
    .modal-box {
        max-height: calc(100dvh - 32px);
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}

@media (max-width: 380px) {
    .op-brand-copy strong {
        font-size: 0.875rem;
    }

    .op-header-actions {
        gap: 4px;
    }

    .op-header-btn:has(> svg),
    .op-theme-toggle {
        width: 40px;
    }

    .op-brand-logo {
        width: 116px;
    }
}

/* Touch targets. 40px clears Android's 48dp guidance only with the browser's
   own slop; iOS asks for 44pt outright, and these are the controls a
   pharmacist hits one-handed. Scoped to coarse pointers so the dense desktop
   layouts — toolbars, table row actions — keep their current proportions. */
@media (pointer: coarse) {
    .btn,
    button[type="submit"],
    .login-btn,
    .op-header-btn,
    .op-theme-toggle {
        min-height: 44px;
    }

    /* Hover styling on a touch screen is applied on tap and then sticks until
       the next tap elsewhere, which reads as a control stuck in a pressed
       state. Give these an actual press response instead. */
    .btn:active,
    .op-header-btn:active,
    .op-theme-toggle:active,
    .nav-item:active {
        transform: scale(0.97);
    }
}

/* Scroll containers keep their own momentum: without this a rubber-band at
   the top of the transcript scrolls the document behind it, and on iOS that
   is what makes an app-shell layout feel broken. */
.chat-container,
.op-table-scroll,
.newsletter-content,
.dp-body {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
    }
}
