// =============================================================================
// Clinic Management System — application stylesheet
//
// Design intent: clinical software is used for eight hours a day by staff who
// are not looking at it — they are looking at a patient. So: high information
// density, strong typographic hierarchy, no decorative motion, and status
// conveyed by shape and text as well as by colour.
//
// Bootstrap 5 provides the grid, components and RTL handling. This file adds a
// thin theme layer of custom properties on top; a dark theme or a per-clinic
// accent colour is then a variable swap, not a stylesheet fork.
// =============================================================================

@import "bootstrap/scss/bootstrap";
@import "bootstrap-icons/font/bootstrap-icons.css";

:root {
    --clinic-sidebar-width: 17rem;
    --clinic-topbar-height: 4rem;

    --clinic-surface: #ffffff;
    --clinic-surface-alt: #f5f7fc;
    --clinic-border: #e5e7eb;
    --clinic-text: #111827;
    --clinic-text-muted: #6b7280;

    --clinic-sidebar-bg: #0f172a;
    --clinic-sidebar-hover: #1e293b;
    --clinic-sidebar-text: #cbd5e1;
    --clinic-sidebar-muted: #94a3b8;

    --clinic-primary: #4f46e5;
    --clinic-primary-2: #7c3aed;

    --clinic-radius: 0.75rem;
    --clinic-radius-sm: 0.5rem;
    --clinic-shadow: 0 1px 2px rgb(15 23 42 / 6%);
}

/*
 | Bridge the design tokens onto Bootstrap's own variables so cards, tables,
 | dropdowns, modals and inputs in every module screen inherit the palette
 | instead of Bootstrap's stock greys. Bootstrap reads --bs-body-bg for
 | component surfaces; the page itself is painted from --clinic-surface-alt
 | on body, which is what keeps a card visibly lifted off the background.
 */
:root {
    --bs-body-bg: var(--clinic-surface);
    --bs-body-color: var(--clinic-text);
    --bs-border-color: var(--clinic-border);
    --bs-secondary-color: var(--clinic-text-muted);
    --bs-link-color-rgb: 79, 70, 229;
    --bs-link-hover-color-rgb: 67, 56, 202;
    --bs-border-radius: var(--clinic-radius-sm);
}

/*
 | Dark mode rides on Bootstrap 5.3's own `data-bs-theme`, set server-side in
 | the layout from the `clinic_theme` cookie. Using the native hook means every
 | Bootstrap component in every module screen darkens for free — only the
 | custom --clinic-* tokens need restating here.
 */
[data-bs-theme="dark"] {
    --clinic-surface: #0f172a;
    --clinic-surface-alt: #020617;
    --clinic-border: #1e293b;
    --clinic-text: #f1f5f9;
    --clinic-text-muted: #94a3b8;
    --clinic-shadow: 0 1px 2px rgb(0 0 0 / 40%);

    /*
     | The sidebar keeps its light-mode colour on purpose. It is already the
     | darkest surface in the light theme, and darkening it further here would
     | make it merge into the page background instead of reading as a distinct
     | panel — which is also exactly what the dashboard does.
     */

    --bs-secondary-bg: #1e293b;
    --bs-tertiary-bg: #1e293b;
    --bs-emphasis-color: #ffffff;
    --bs-heading-color: #f1f5f9;
    --bs-link-color-rgb: 129, 140, 248;
    --bs-link-hover-color-rgb: 165, 180, 252;
}

body.clinic-app {
    background: var(--clinic-surface-alt);
    color: var(--clinic-text);
    font-size: 0.9375rem;
    -webkit-font-smoothing: antialiased;
}

.skip-link {
    position: absolute;
    top: 0.5rem;
    inset-inline-start: 0.5rem;
    z-index: 1080;
    padding: 0.5rem 0.75rem;
    background: var(--clinic-surface);
    border-radius: var(--clinic-radius);
}

// -----------------------------------------------------------------------------
// Shell
// -----------------------------------------------------------------------------

/*
 | The sidebar is fixed at every width — it is the product's primary
 | navigation and staff jump between screens constantly, so it must not
 | scroll away. The main column is inset by its width rather than sitting
 | beside it in a flex row, which is what keeps it pinned.
 */
.clinic-shell {
    min-height: 100vh;
}

.clinic-main {
    min-width: 0; // lets wide tables scroll instead of stretching the page
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    padding-inline-start: var(--clinic-sidebar-width);

    @media (max-width: 991.98px) {
        padding-inline-start: 0;
    }
}

.clinic-content {
    padding: 1.5rem;
    flex: 1 1 auto;

    @media (max-width: 575.98px) {
        padding: 1rem;
    }

    &:focus {
        outline: none;
    }
}

/*
 | Anything jumped to by a link keeps clear of the topbar.
 |
 | The bar is sticky, so the browser scrolling a target flush with the top of
 | the viewport parks it *underneath* — "skip to content" landed on a heading
 | hidden behind the bar, which is the opposite of what that link is for. The
 | same applies to any in-page anchor, so it is set on the target rather than
 | on one element.
 */
:target,
.clinic-content:focus {
    scroll-margin-top: calc(var(--clinic-topbar-height) + 1rem);
}

// Dims the page behind the off-canvas sidebar on small screens.
.clinic-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgb(15 23 42 / 50%);
    backdrop-filter: blur(2px);
    display: none;

    .sidebar-open & {
        @media (max-width: 991.98px) {
            display: block;
        }
    }
}

// -----------------------------------------------------------------------------
// Sidebar
// -----------------------------------------------------------------------------

.clinic-sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: 1045;
    width: var(--clinic-sidebar-width);
    background: var(--clinic-sidebar-bg);
    color: var(--clinic-sidebar-text);
    display: flex;
    flex-direction: column;

    svg {
        width: 1.25rem;
        height: 1.25rem;
        flex: none;
    }

    &__brand {
        height: var(--clinic-topbar-height);
        display: flex;
        align-items: center;
        gap: 0.75rem;
        padding-inline: 1.25rem;
        flex: none;
    }

    &__logo {
        width: 2.25rem;
        height: 2.25rem;
        border-radius: var(--px-radius-sm);
        background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
        color: var(--px-on-primary);
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: 0 8px 18px -6px rgb(109 76 255 / 70%);
    }

    &__title {
        min-width: 0;
        line-height: 1.2;

        strong {
            display: block;
            font-size: 0.9375rem;
            font-weight: 600;
            color: #fff;
        }

        small {
            display: block;
            font-size: 0.6875rem;
            color: var(--clinic-sidebar-muted);
        }
    }

    &__close {
        margin-inline-start: auto;
        border: 0;
        background: none;
        color: var(--clinic-sidebar-muted);
        border-radius: var(--clinic-radius-sm);
        padding: 0.25rem;
        display: none;

        @media (max-width: 991.98px) {
            display: inline-flex;
        }
    }

    &__nav {
        flex: 1 1 auto;
        overflow-y: auto;
        padding: 0.5rem 0.75rem 1rem;
        scrollbar-width: thin;
        scrollbar-color: #334155 transparent;
    }

    .nav-link {
        position: relative;
        display: flex;
        align-items: center;
        gap: 0.75rem;
        color: var(--clinic-sidebar-text);
        padding: 0.625rem 0.75rem;
        border-radius: var(--px-radius-sm);
        width: 100%;
        border: 0;
        background: none;
        text-align: start;
        font-size: 0.875rem;
        font-weight: 500;
        transition: background-color var(--px-ease), color var(--px-ease);

        svg {
            color: var(--clinic-sidebar-muted);
            transition: color var(--px-ease);
        }

        &:hover,
        &:focus-visible {
            background: rgb(255 255 255 / 6%);
            color: #fff;

            svg {
                color: inherit;
            }
        }

        // The ring has to be light here — the tier's violet outline is close to
        // invisible against the sidebar's near-black.
        &:focus-visible {
            outline: 2px solid rgb(255 255 255 / 70%);
            outline-offset: -2px;
        }

        &.active {
            background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
            color: var(--px-on-primary);
            font-weight: 600;
            box-shadow: 0 6px 16px -6px rgb(109 76 255 / 65%);

            svg {
                color: inherit;
            }
        }
    }

    &__submenu {
        position: relative;
        margin-top: 0.125rem;
        padding-inline-start: 1.5rem;

        // A hairline rail tying the children to their group, so a long
        // submenu still reads as belonging to the heading above it.
        &::before {
            content: "";
            position: absolute;
            inset-block: 0.25rem;
            inset-inline-start: 1.0625rem;
            width: 1px;
            background: rgb(255 255 255 / 10%);
        }

        .nav-link {
            font-size: 0.8125rem;
            font-weight: 450;
            padding-block: 0.4375rem;
            color: var(--clinic-sidebar-muted);

            svg {
                width: 1rem;
                height: 1rem;
            }

            &:hover,
            &:focus-visible,
            &.active {
                color: #fff;
            }

            // A child is marked by a filled rail segment rather than a second
            // gradient pill — two nested pills read as two separate selections.
            &.active {
                background: rgb(255 255 255 / 8%);
                box-shadow: none;
                font-weight: 600;

                &::before {
                    content: "";
                    position: absolute;
                    inset-block: 0.375rem;
                    inset-inline-start: -0.4375rem;
                    width: 2px;
                    border-radius: 2px;
                    background: var(--px-primary);
                }
            }
        }
    }

    &__chevron {
        margin-inline-start: auto;
        width: 1rem !important;
        height: 1rem !important;
        transition: transform 0.2s ease;

        // Bootstrap's collapse toggle carries aria-expanded; drive the arrow
        // from that rather than a second class the markup has to keep in sync.
        [aria-expanded="true"] & {
            transform: rotate(180deg);
        }
    }

    &__footer {
        flex: none;
        padding: 0.75rem;
        border-top: 1px solid rgb(255 255 255 / 6%);
    }

    &__profile {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        width: 100%;
        padding: 0.5rem;
        border: 1px solid rgb(255 255 255 / 8%);
        background: rgb(255 255 255 / 4%);
        border-radius: var(--px-radius-sm);
        text-align: start;
        color: inherit;
        transition: all var(--px-ease);

        &:hover {
            background: rgb(255 255 255 / 9%);
            border-color: rgb(255 255 255 / 14%);
        }

        &:focus-visible {
            outline: 2px solid rgb(255 255 255 / 70%);
            outline-offset: -2px;
        }

        strong {
            display: block;
            font-size: 0.8125rem;
            font-weight: 500;
            color: #fff;
        }

        small {
            display: block;
            font-size: 0.6875rem;
            color: var(--clinic-sidebar-muted);
        }
    }

    &__version {
        display: block;
        text-align: center;
        font-size: 0.625rem;

        // Was #475569, which is 2.36:1 against the sidebar. De-emphasised is
        // not the same as unreadable, and this is the string every support
        // conversation starts by asking for.
        color: var(--clinic-sidebar-muted);
        margin-top: 0.5rem;
    }
}

// Initials avatar with an online dot, shared by the sidebar and topbar.
.clinic-avatar {
    position: relative;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
    color: var(--px-on-primary);
    font-size: 0.75rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    user-select: none;

    &--sm {
        width: 2rem;
        height: 2rem;
        font-size: 0.6875rem;
    }

    &--online::after {
        content: "";
        position: absolute;
        inset-block-end: -0.125rem;
        inset-inline-end: -0.125rem;
        width: 0.75rem;
        height: 0.75rem;
        border-radius: 50%;
        background: #10b981;
        border: 2px solid var(--clinic-sidebar-bg);
    }
}

@media (max-width: 991.98px) {
    .clinic-sidebar {
        transform: translateX(-100%);
        transition: transform 0.25s ease;

        [dir="rtl"] & {
            transform: translateX(100%);
        }

        &.is-open {
            transform: none;
        }
    }
}

// -----------------------------------------------------------------------------
// Topbar
// -----------------------------------------------------------------------------

.clinic-topbar {
    height: var(--clinic-topbar-height);
    // Semi-transparent so content scrolling beneath reads as depth rather
    // than disappearing under an opaque bar.
    background: color-mix(in srgb, var(--clinic-surface) 78%, transparent);
    backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--px-border);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding-inline: 1.5rem;
    position: sticky;
    top: 0;
    z-index: 1030;

    @media (max-width: 575.98px) {
        padding-inline: 1rem;
    }

    svg {
        width: 1.25rem;
        height: 1.25rem;
        flex: none;
    }

    &__icon-btn {
        border: 1px solid transparent;
        background: none;
        color: var(--px-muted);
        border-radius: var(--px-radius-sm);
        padding: 0.5rem;
        display: inline-flex;
        transition: all var(--px-ease);

        &:hover {
            background: var(--clinic-surface-alt);
            border-color: var(--px-border);
            color: var(--px-text);
        }

        &:focus-visible {
            outline: 2px solid var(--px-primary);
            outline-offset: 2px;
            color: var(--px-text);
        }
    }

    &__search {
        position: relative;
        flex: 1 1 26rem;
        max-width: 28rem;

        .form-control {
            height: 2.625rem;
            border-radius: var(--px-radius-sm);

            // Recessed rather than raised. It is the one control that is
            // present on every screen, and a white box floating on a
            // translucent bar reads as a card rather than as a field.
            background: var(--clinic-surface-alt);
            border-color: var(--px-border);
            padding-inline-start: 2.5rem;
            padding-inline-end: 2.5rem;
            font-size: 0.875rem;
            transition: all var(--px-ease);

            &:focus {
                background: var(--clinic-surface);
                border-color: var(--px-primary);
                box-shadow: 0 0 0 4px rgb(109 76 255 / 12%);
            }
        }

        > svg {
            position: absolute;
            inset-inline-start: 0.75rem;
            top: 50%;
            transform: translateY(-50%);
            width: 1rem;
            height: 1rem;
            color: var(--clinic-text-muted);
            pointer-events: none;
        }

        kbd {
            position: absolute;
            inset-inline-end: 0.625rem;
            top: 50%;
            transform: translateY(-50%);
            font-size: 0.625rem;
            background: var(--clinic-surface-alt);
            color: var(--clinic-text-muted);
            border: 1px solid var(--clinic-border);
            border-radius: 0.375rem;
            padding: 0.0625rem 0.375rem;
        }
    }

    &__results {
        position: absolute;
        top: calc(100% + 0.375rem);
        inset-inline: 0;
        z-index: 1040;
        max-height: 60vh;
        overflow-y: auto;
        border-radius: var(--clinic-radius);
        box-shadow: 0 0.75rem 2rem rgb(15 23 42 / 15%);
    }

    &__actions {
        margin-inline-start: auto;
        display: flex;
        align-items: center;
        gap: 0.375rem;
    }

    &__branch {
        display: inline-flex;
        align-items: center;
        gap: 0.375rem;
        font-size: 0.75rem;
        font-weight: 600;
        color: var(--px-muted-strong);
        background: var(--clinic-surface-alt);
        border: 1px solid var(--px-border);
        border-radius: 999px;
        padding: 0.375rem 0.75rem;

        svg {
            width: 0.875rem;
            height: 0.875rem;
        }

        @media (max-width: 767.98px) {
            display: none;
        }
    }

    &__profile {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        border: 1px solid transparent;
        background: none;
        border-radius: 999px;
        padding: 0.25rem 0.75rem 0.25rem 0.25rem;
        color: var(--px-text);
        font-size: 0.8125rem;
        font-weight: 600;
        transition: all var(--px-ease);

        &::after {
            display: none; // Bootstrap's caret; the chevron icon replaces it
        }

        &:hover {
            background: var(--clinic-surface-alt);
            border-color: var(--px-border);
        }

        &:focus-visible {
            outline: 2px solid var(--px-primary);
            outline-offset: 2px;
        }

        span {
            @media (max-width: 767.98px) {
                display: none;
            }
        }
    }

    &__toggle {
        display: none;

        @media (max-width: 991.98px) {
            display: inline-flex;
        }
    }

    // The theme toggle shows whichever icon represents the *other* mode.
    &__theme-dark { display: none; }

    [data-bs-theme="dark"] & {
        .clinic-topbar__theme-light { display: none; }
        .clinic-topbar__theme-dark { display: inline-flex; }
    }
}

// -----------------------------------------------------------------------------
// Building blocks
// -----------------------------------------------------------------------------

.clinic-page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;

    h1 {
        letter-spacing: -0.01em;
        font-weight: 700;
    }
}

// Matches the dashboard's panel: generous radius, hairline border, soft
.clinic-stat {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 1rem;
    background: var(--clinic-surface);
    border: 1px solid var(--clinic-border);
    border-radius: var(--clinic-radius);

    &__icon {
        font-size: 1.5rem;
        color: var(--clinic-text-muted);
    }

    &__label {
        font-size: 0.75rem;
        text-transform: uppercase;
        letter-spacing: 0.03em;
        color: var(--clinic-text-muted);
    }

    &__value {
        font-size: 1.5rem;
        font-weight: 600;
        line-height: 1.2;
        font-variant-numeric: tabular-nums;
    }

    &__trend {
        font-size: 0.75rem;
        color: var(--clinic-text-muted);
    }

    &--positive .clinic-stat__icon { color: $success; }
    &--warning  .clinic-stat__icon { color: $warning; }
    &--negative .clinic-stat__icon { color: $danger; }
}

/*
 * Clinical notes are written with line breaks that carry meaning — a list of
 * findings, a numbered plan. Collapsing them into a paragraph, as HTML does by
 * default, changes how the note reads.
 */
.clinic-note-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

// -----------------------------------------------------------------------------
// Premium listing tier
// -----------------------------------------------------------------------------
//
// A richer surface for the index screens: metric cards, a filter bar, a data
// table with avatars and status pills. Written as reusable `.px-*` components
// rather than page-specific rules, because the same treatment is wanted on
// Appointments, Invoices and Laboratory next — and eleven copies of a table
// style is eleven places to fix a contrast bug.
//
// The palette extends the existing --clinic-* tokens rather than replacing
// them, so dark mode and the per-clinic accent keep working untouched.

:root {
    --px-primary: #6d4cff;
    --px-primary-2: #8b5cf6;

    // The ink that goes on top of the accent. White while the accent is the
    // violet we ship; recomputed by BrandingPalette when a clinic chooses its
    // own, because white on a bright colour cannot be read.
    --px-on-primary: #fff;
    --px-radius: 1.25rem;      // 20px
    --px-radius-sm: 0.75rem;

    // Three depths. Anything more and the page reads as cluttered rather than
    // layered — the reference gets its calm from restraint, not from shadows.
    --px-shadow: 0 1px 2px rgb(15 23 42 / 4%), 0 1px 3px rgb(15 23 42 / 3%);
    --px-shadow-md: 0 4px 6px -1px rgb(15 23 42 / 6%), 0 2px 4px -2px rgb(15 23 42 / 4%);
    --px-shadow-lg: 0 12px 20px -6px rgb(15 23 42 / 10%), 0 4px 8px -4px rgb(15 23 42 / 6%);

    --px-ease: 250ms cubic-bezier(0.4, 0, 0.2, 1);

    // Slightly cooler and darker than the global --clinic-* equivalents. Kept
    // as separate tokens rather than retuning the globals: every other screen
    // in the app draws from those, and this tier is opt-in per page.
    --px-border: #e8ecf4;
    --px-text: #0f172a;
    // Both clear 4.5:1 against the page background as well as against white —
    // muted text sits on both, and #64748b lands at 4.44 on the page.
    --px-muted: #5d6b80;
    --px-muted-strong: #55637a;

    // The spec's card shadow. Large and very low opacity: it reads as depth
    // rather than as a drawn edge, which is what keeps a page of white cards
    // on a near-white background from looking flat.
    --px-shadow-card: 0 12px 40px rgb(20 20 43 / 8%);

    /*
     | The card surface these components paint on.
     |
     | It was referenced by .px-hero and .px-vital from the day they were
     | written and never actually defined, so both declarations were invalid and
     | both elements have been transparent ever since — reading as cards only
     | because of their border and shadow, with the page showing through.
     */
    --px-surface: var(--clinic-surface);

    /*
     | The monospace face, restacked.
     |
     | Bootstrap ships three macOS faces before the first one Windows has, so a
     | clinic on Windows walked the list to whatever it found — frequently
     | Courier New, which is why every reference and licence key on the site was
     | set in a typewriter face. Cascadia and Consolas ship with Windows and are
     | tried first now, with the macOS faces kept after them.
     |
     | This is for things somebody copies character by character. Money is not
     | one of them and no longer uses it — see .px-money.
     */
    --bs-font-monospace: Arial, Helvetica, sans-serif;

    /*
     | Arial throughout, at the clinic's request.
     |
     | Both variables are set, and to the same thing, so nothing quietly keeps
     | a second face: Bootstrap resolves body text through --bs-font-sans-serif
     | and anything marked .font-monospace through the variable above, and
     | leaving either at its default would have produced a screen that was
     | mostly-but-not-entirely Arial.
     |
     | What this gives up, recorded because it was deliberate rather than
     | overlooked: MRNs, phone numbers and licence keys no longer sit in a
     | fixed-width face. In Arial the digit 1 and lowercase l are near
     | identical, as are 0 and capital O, and columns of numbers no longer line
     | up. Restoring it is one line — put the old stack back on
     | --bs-font-monospace and identifiers return to Cascadia Mono.
     */
    --bs-font-sans-serif: Arial, Helvetica, sans-serif;
    --bs-body-font-family: Arial, Helvetica, sans-serif;
}

[data-bs-theme="dark"] {
    --px-shadow: 0 1px 2px rgb(0 0 0 / 40%);
    --px-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 45%);
    --px-shadow-lg: 0 12px 20px -6px rgb(0 0 0 / 55%);

    // The light palette above is tuned for a white page. In dark mode defer to
    // the theme's own tokens, which are already contrast-checked.
    --px-border: var(--clinic-border);
    --px-text: var(--clinic-text);
    --px-muted: var(--clinic-text-muted);
    --px-muted-strong: var(--clinic-text-muted);
    --px-shadow-card: 0 12px 40px rgb(0 0 0 / 45%);
}

// ---- Page head --------------------------------------------------------------
//
// The breadcrumb that lived here was removed from every screen on 2026-08-12,
// at the clinic's request, and its styles went with it — a rule nothing selects
// is a rule the next person has to prove is dead before touching anything near
// it.

// ---- Tabs -------------------------------------------------------------------
//
// Bootstrap's .nav-tabs draws a folder-tab outline — a box round the active
// item and a rule under the rest — which fights every other surface here, all
// of which are flat panels separated by a single hairline. This keeps the
// hairline and marks the active tab with a weight change and a rule directly
// beneath it, so the tab row reads as part of the page rather than a widget
// sitting on it.
//
// Built on .nav so Bootstrap's keyboard and ARIA behaviour still applies; only
// the skin is replaced.

.px-tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--px-border);
    margin-bottom: 1.5rem;

    // Scrolls rather than wraps: a wrapped second row of tabs reads as a
    // separate group, and on a phone these are links to other pages.
    overflow-x: auto;
    scrollbar-width: none;
    flex-wrap: nowrap;

    &::-webkit-scrollbar { display: none; }

    .px-tabs__link {
        position: relative;
        white-space: nowrap;
        padding: 0.625rem 0.875rem;
        font-size: 0.9375rem;
        font-weight: 500;
        color: var(--px-muted);
        text-decoration: none;
        border: 0;
        background: none;
        transition: color var(--px-ease);

        &:hover { color: var(--px-text); }

        // Sits on the container's hairline rather than below it, so the active
        // marker and the rule are the same line.
        &::after {
            content: '';
            position: absolute;
            inset-inline: 0.5rem;
            bottom: -1px;
            height: 2px;
            border-radius: 2px;
            background: transparent;
            transition: background-color var(--px-ease);
        }
    }

    .px-tabs__link--active {
        color: var(--px-text);
        font-weight: 650;

        &::after { background: var(--px-primary); }
    }
}

.px-title {
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.2;
    margin: 0;
}

.px-subtitle {
    color: var(--px-muted);
    font-size: 0.9375rem;
    margin: 0.375rem 0 0;
}

/*
 | A column of money: label on the left, figure on the right.
 |
 | Replaces the `dl.row` + `col-7`/`col-5` pairs the billing screens used, which
 | put the label and its amount in separate grid cells and left them to line up
 | by luck at narrow widths. Here each row is its own flex line, so a long method
 | name pushes its figure rather than wrapping underneath the wrong one.
 */
.px-totals {
    margin: 0;

    &__row {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 1rem;
        padding: 0.4375rem 0;
        font-size: 0.875rem;

        dt {
            color: var(--px-muted);
            font-weight: 500;
            min-width: 0;
        }

        dd {
            margin: 0;
            text-align: end;
            white-space: nowrap;
        }

        // A figure the rest of the column adds up to: ruled off above, and set
        // in the body colour at full weight so the eye stops on it.
        &--sum {
            margin-top: 0.25rem;
            padding-top: 0.6875rem;
            border-top: 1px solid var(--px-border);
            font-size: 1rem;

            dt { color: var(--px-text); font-weight: 600; }
            dd { font-weight: 700; }
        }

        // Money still owed, or a drawer that did not reconcile.
        &--owing dd { color: #b91c1c; }
    }
}

// The same red as above, for a balance sitting in a table cell rather than a
// totals column. Bootstrap's .text-danger is a different red and is used for
// form validation, which this is not.
.px-owing {
    color: #b91c1c;
}

/*
 | A yes/no asked in the place the button was.
 |
 | Replaces window.confirm() where there is one button per row: the browser's
 | dialog opens against the top of the window, names an amount and gives no
 | clue which of thirty rows raised it.
 |
 | Laid out to keep the row's height: the question and its two answers occupy
 | roughly the width the button did, so the table does not jump when one opens.
 */
.px-confirm {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;

    &__ask {
        display: inline-flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0.25rem 0.25rem 0.25rem 0.75rem;
        border: 1px solid var(--px-border);
        border-radius: var(--px-radius-sm);
        background: var(--clinic-surface-alt);

        &[hidden] { display: none; }
    }

    &__text {
        font-size: 0.8125rem;
        color: var(--px-text);
        white-space: nowrap;
    }
}

/*
 | A row that is still on the record but no longer on offer.
 |
 | Dimmed rather than hidden: a withdrawn price-list entry is what an old
 | invoice points at, so it has to stay findable — but it must not read as
 | something that can be charged today. Kept off `.text-muted`, which also
 | greys the pill that says *why* the row is dim.
 */
.px-row--muted > td {
    color: var(--px-muted);
    background: var(--clinic-surface-alt);
}

/*
 | The editor that opens underneath the row it edits.
 |
 | Inset and tinted so it reads as belonging to the row above rather than as
 | another row of the table — which matters when the thing being changed is a
 | price and the row above is the only copy of the old one still on screen.
 */
.px-editor {
    padding: 1rem 1.25rem 1.25rem;
    background: var(--clinic-surface-alt);
    box-shadow: inset 3px 0 0 var(--px-primary);
}

/*
 | The box the bookable times are painted into.
 |
 | Given a minimum height on purpose: it is filled by a fetch after the doctor
 | and date are chosen, and without one the whole form jumps upward the moment
 | the slots arrive — with the mouse already travelling towards where a button
 | used to be.
 */
.px-slotbox {
    min-height: 5.25rem;
    padding: 0.75rem;
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface-alt);
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 0.5rem;
}

/*
 | One bookable time. Rendered by Clinic.booking rather than by Blade, so the
 | class names here are the contract with app.js.
 |
 | The chosen one is filled, not merely outlined: this is a radio group wearing
 | buttons, and at a glance you must be able to see which time you picked from
 | across a row of twenty near-identical ones.
 */
.px-slot {
    padding: 0.4375rem 0.75rem;
    border: 1px solid var(--px-border);
    border-radius: 0.625rem;
    background: var(--clinic-surface);
    color: var(--px-text);
    font-size: 0.8125rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    transition: border-color var(--px-ease), background-color var(--px-ease), color var(--px-ease);

    &:hover,
    &:focus-visible {
        border-color: var(--px-primary);
        color: var(--px-primary);
    }

    &.active {
        background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
        border-color: transparent;
        color: var(--px-on-primary);
        box-shadow: 0 4px 12px -2px rgb(109 76 255 / 40%);
    }

    // How many more patients this slot still takes.
    &__count {
        margin-inline-start: 0.375rem;
        padding: 0.0625rem 0.375rem;
        border-radius: 999px;
        background: rgb(0 0 0 / 8%);
        font-size: 0.6875rem;
    }

    &.active &__count {
        background: rgb(255 255 255 / 22%);
    }
}

// ---- Buttons ----------------------------------------------------------------

.px-btn-primary {
    --bs-btn-color: var(--px-on-primary);
    --bs-btn-hover-color: var(--px-on-primary);
    --bs-btn-active-color: var(--px-on-primary);

    background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
    border: 0;
    color: var(--px-on-primary);
    font-weight: 600;
    border-radius: var(--px-radius-sm);
    padding: 0.625rem 1.125rem;
    box-shadow: 0 4px 12px -2px rgb(109 76 255 / 40%);
    transition: transform var(--px-ease), box-shadow var(--px-ease);

    &:hover,
    &:focus-visible {
        color: #fff;
        transform: translateY(-1px);
        box-shadow: 0 8px 18px -4px rgb(109 76 255 / 50%);
    }

    &:active { transform: translateY(0); }
}

/*
 | Solid red, and rare on purpose.
 |
 | The ghost variant is what a destructive action normally gets — cancelling an
 | order, removing an isolate — because those are recoverable and should not
 | shout. This one is for the opposite case: telling the system that a critical
 | result has been phoned through to a human being. Nothing else on a laboratory
 | screen carries that weight, so nothing else should be this colour.
 */
.px-btn-danger {
    --bs-btn-color: var(--px-on-primary);
    --bs-btn-hover-color: var(--px-on-primary);
    --bs-btn-active-color: var(--px-on-primary);

    background: linear-gradient(135deg, #e11d48, #be123c);
    border: 0;
    color: #fff;
    font-weight: 600;
    border-radius: var(--px-radius-sm);
    padding: 0.625rem 1.125rem;
    box-shadow: 0 4px 12px -2px rgb(225 29 72 / 35%);
    transition: transform var(--px-ease), box-shadow var(--px-ease);

    &:hover,
    &:focus-visible {
        color: #fff;
        transform: translateY(-1px);
        box-shadow: 0 8px 18px -4px rgb(225 29 72 / 45%);
    }

    &:active { transform: translateY(0); }
}

.px-btn-ghost {
    background: var(--clinic-surface);
    border: 1px solid var(--px-border);
    color: var(--px-text);
    font-weight: 500;
    border-radius: var(--px-radius-sm);
    padding: 0.625rem 1rem;
    transition: border-color var(--px-ease), box-shadow var(--px-ease), transform var(--px-ease);

    &:hover,
    &:focus-visible {
        color: var(--px-text);
        border-color: color-mix(in srgb, var(--px-primary) 40%, var(--px-border));
        box-shadow: var(--px-shadow-md);
    }
}

// ---- Metric cards -----------------------------------------------------------

.px-metrics {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    margin-bottom: 1.25rem;

    /*
     | Four cards, so wrap two-and-two rather than letting auto-fit strand a
     | lone card on a second row. Applied when the grid shares its row with the
     | sticky aside, which is the width where auto-fit lands on three.
     */
    .px-layout & {
        @media (min-width: 1200px) and (max-width: 1599.98px) {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
    }
}

.px-metric {
    display: flex;
    align-items: flex-start;
    gap: 0.875rem;
    padding: 1.25rem;
    background: var(--clinic-surface);
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius);
    box-shadow: var(--px-shadow-card);
    transition: transform var(--px-ease), box-shadow var(--px-ease);

    &:hover {
        transform: translateY(-2px);
        box-shadow: var(--px-shadow-lg);
    }

    &__icon {
        flex: 0 0 auto;
        width: 2.75rem;
        height: 2.75rem;
        display: grid;
        place-items: center;
        border-radius: 0.875rem;
        color: #fff;
        font-size: 1.125rem;
    }

    // Written out per tone: a gradient built from an interpolated variable
    // cannot be read by anything that scans this file, and these are the
    // colours the four cards are specified in.
    &--violet .px-metric__icon { background: linear-gradient(135deg, #6d4cff, #8b5cf6); }
    &--blue   .px-metric__icon { background: linear-gradient(135deg, #3b82f6, #06b6d4); }
    &--green  .px-metric__icon { background: linear-gradient(135deg, #10b981, #14b8a6); }
    &--pink   .px-metric__icon { background: linear-gradient(135deg, #ec4899, #f43f5e); }
    &--amber  .px-metric__icon { background: linear-gradient(135deg, #f59e0b, #f97316); }

    &__label {
        font-size: 0.8125rem;
        font-weight: 600;
        color: var(--px-muted);
        margin: 0;
    }

    &__value {
        font-size: 1.75rem;
        font-weight: 700;
        line-height: 1.15;
        letter-spacing: -0.02em;
        font-variant-numeric: tabular-nums;
        margin: 0.125rem 0 0;
    }

    &__trend {
        font-size: 0.75rem;
        font-weight: 500;
        color: var(--px-muted);
        margin: 0.25rem 0 0;
        display: flex;
        align-items: center;
        gap: 0.25rem;

        // Darker than the fill greens and reds used for icons: this is 12px
        // text, where the mid-tone versions land near 3.8:1 on white.
        &--up { color: #047857; }
        &--down { color: #b91c1c; }
    }
}

// Those two are tuned for a white card; on a dark one they are the ones that
// disappear.
[data-bs-theme="dark"] .px-metric__trend {
    &--up { color: #34d399; }
    &--down { color: #fca5a5; }
}

// ---- Filter bar -------------------------------------------------------------

.px-panel {
    background: var(--clinic-surface);
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius);
    box-shadow: var(--px-shadow-card);
}

/*
 | The label above a field.
 |
 | Was 12px in the muted grey, against a 15px placeholder inside the control —
 | so on every filter bar in the product the hint was louder than the heading
 | that named it, and the eye read the example before the question.
 |
 | Now 14px in the stronger muted tone: still quieter than the value somebody
 | types, still clearly the label rather than content, but no longer beaten by
 | the placeholder underneath it. Left at 14 rather than matched to 15 so the
 | field's own text stays the loudest thing in the field.
 */
.px-field-label {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--px-muted-strong);
    margin-bottom: 0.375rem;
    display: block;
}

// The sentence under a field explaining what the clinic should type in it.
.px-hint {
    display: block;
    margin-top: 0.375rem;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--px-muted-strong);
}

.px-input,
.px-select {
    border-radius: var(--px-radius-sm);
    border-color: var(--px-border);
    padding: 0.625rem 0.875rem;
    font-size: 0.9375rem;
    transition: border-color var(--px-ease), box-shadow var(--px-ease);

    /*
     | The native list, for the moment before the skin is on it.
     |
     | .px-picker replaces this control with markup we can style, but that
     | happens when the script runs — and until it does, or if it never does,
     | the operating system draws the list. Chrome takes the font and the colours
     | from the option elements themselves, so stating them here is the
     | difference between a serif list in a sans interface and one nobody
     | notices.
     */
    option {
        font-family: inherit;
        font-size: 0.9375rem;
        color: var(--px-text);
        background: var(--clinic-surface);
    }

    /*
     | The example, set back from the answer.
     |
     | A placeholder is a hint, not content, and at full contrast an empty field
     | reads as a filled one — which on a filter bar makes it look as though a
     | search is already applied.
     */
    &::placeholder {
        color: var(--px-muted);
        opacity: 1;
    }

    &:focus {
        border-color: var(--px-primary);
        box-shadow: 0 0 0 4px rgb(109 76 255 / 12%);
    }
}

// ---- Dropdown ---------------------------------------------------------------
//
// The list a native <select> opens is drawn by the operating system, and no CSS
// reaches inside it — which is why it arrives as a white box with a system-blue
// highlight in the middle of a purple interface.
//
// So the list here is ordinary markup, driven by the real <select> that stays in
// the form underneath it. The select is what submits, what validates and what
// other scripts read; this is a skin over it. With JavaScript off, or before it
// runs, the native control is what you get and everything still works.
.px-picker {
    position: relative;
}

// Deliberately not `display: none`: a hidden-but-present control keeps its value
// in the form and stays reachable to anything that queries the DOM for it.
.px-picker__native {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

// Matches .px-input exactly, so a row of mixed fields lines up to the pixel.
.px-picker__button {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.625rem 0.875rem;
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);
    color: var(--px-text);
    font-size: 0.9375rem;
    line-height: 1.5;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--px-ease), box-shadow var(--px-ease);

    &:hover {
        border-color: var(--px-muted);
    }

    &:focus-visible,
    &[aria-expanded="true"] {
        outline: none;
        border-color: var(--px-primary);
        box-shadow: 0 0 0 4px rgb(109 76 255 / 12%);
    }
}

.px-picker__value {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

// A placeholder-ish value — the empty "All" option — reads as unset rather than
// as a choice somebody made.
.px-picker__value--empty {
    color: var(--px-muted);
}

.px-picker__chevron {
    flex: none;
    width: 0.875rem;
    height: 0.875rem;
    background-color: var(--px-muted);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E") center / contain no-repeat;
    transition: transform var(--px-ease);
}

.px-picker__button[aria-expanded="true"] .px-picker__chevron {
    transform: rotate(180deg);
}

.px-picker__list {
    position: absolute;
    z-index: 1050;
    inset-inline: 0;
    top: calc(100% + 0.25rem);
    margin: 0;
    padding: 0.25rem;
    list-style: none;

    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);
    box-shadow: 0 12px 32px rgb(15 23 42 / 12%);

    /*
     | Long lists scroll rather than running off the screen — the clinician list
     | is the one that needs it.
     |
     | The cap was a flat 16rem, which put a scrollbar on a list of ten. A
     | dropdown that scrolls when it did not need to reads as a much longer list
     | than it is, and hides the last options behind a gesture. It may pass
     | outside the card it sits in; a floating list is meant to.
     |
     | Measured against the window as well, so on a short screen the list still
     | ends inside it rather than running off the bottom.
     */
    max-height: min(26rem, 60vh);
    overflow-y: auto;

    &[hidden] {
        display: none;
    }
}

// Opened upward when there is no room below — set by the script, which is the
// only thing that can measure it.
.px-picker--up .px-picker__list {
    top: auto;
    bottom: calc(100% + 0.25rem);
}

.px-picker__option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.625rem;
    border-radius: 0.5rem;
    cursor: pointer;

    /*
     | Stated rather than inherited.
     |
     | The list is appended as a sibling of the control, and a bare <ul> in a
     | page whose font is set further up arrives in the browser's default —
     | which on Windows is a serif face, in the middle of an interface that has
     | none. It is the sort of thing that reads as "half-built" long before
     | anybody works out why.
     */
    font-family: inherit;
    font-size: 0.9375rem;
    color: var(--px-text);

    // Hover and keyboard focus look the same on purpose: there is one "current"
    // row, however the person is driving it.
    &:hover,
    &[data-active] {
        background: var(--clinic-surface-alt);
    }

    // The chosen row, in the product's own colour. It carries a tick as well,
    // so the selection is not conveyed by colour alone.
    &[aria-selected="true"] {
        background: var(--px-primary);
        color: #fff;

        &::after {
            content: "";
            margin-inline-start: auto;
            width: 0.875rem;
            height: 0.875rem;
            flex: none;
            background-color: currentcolor;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.5 3.5L13 5'/%3E%3C/svg%3E") center / contain no-repeat;
        }
    }
}

.px-picker__option--disabled {
    opacity: 0.5;
    cursor: default;
}

// Search field with the icon inside rather than in an input-group addon, so
// the control keeps one continuous border and one focus ring.
.px-search {
    position: relative;

    .px-input { padding-inline-start: 2.5rem; }

    .bi {
        position: absolute;
        inset-inline-start: 0.875rem;
        top: 50%;
        transform: translateY(-50%);
        color: var(--px-muted);
        pointer-events: none;
        font-size: 0.9375rem;
    }
}

// ---- Data table -------------------------------------------------------------

.px-table {
    margin: 0;
    font-variant-numeric: tabular-nums;

    > :not(caption) > * > * { padding: 0.875rem 1rem; }

    thead th {
        background: var(--clinic-surface-alt);
        font-size: 0.6875rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.05em;

        // A shade darker than --px-muted. These sit on the tinted header strip
        // rather than on white, and the muted grey lands just under 4.5:1
        // against it — small uppercase text is the last place to accept that.
        color: var(--px-muted-strong);
        border-bottom: 1px solid var(--px-border);
        white-space: nowrap;
    }

    tbody tr {
        transition: background-color var(--px-ease);

        &:hover { background: var(--clinic-surface-alt); }
    }

    tbody td { border-bottom: 1px solid var(--px-border); vertical-align: middle; }
    tbody tr:last-child td { border-bottom: 0; }
}

// The coloured initials that used to sit beside every patient are gone, at the
// clinic's request: they repeated the name they sat next to, and on a listing
// they competed with the allergy marker, which is the one mark on those screens
// that genuinely has to catch the eye. Removed product-wide rather than hidden,
// so nothing renders an empty circle.

.px-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1875rem 0.5rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;

    // Every pill carries a word as well as a colour. Roughly one man in twelve
    // has some form of colour vision deficiency, and this is a clinical screen.
    &--blue   { background: #eff6ff; color: #1d4ed8; }
    &--pink   { background: #fdf2f8; color: #be185d; }
    &--grey   { background: #f1f5f9; color: #475569; }
    &--green  { background: #ecfdf5; color: #047857; }
    &--amber  { background: #fffbeb; color: #b45309; }
    &--red    { background: #fef2f2; color: #b91c1c; }
    &--violet { background: #f5f3ff; color: #6d28d9; }
}

[data-bs-theme="dark"] .px-pill {
    &--blue   { background: rgb(59 130 246 / 15%);  color: #93c5fd; }
    &--pink   { background: rgb(236 72 153 / 15%);  color: #f9a8d4; }
    &--grey   { background: rgb(148 163 184 / 15%); color: #cbd5e1; }
    &--green  { background: rgb(16 185 129 / 15%);  color: #6ee7b7; }
    &--amber  { background: rgb(245 158 11 / 15%);  color: #fcd34d; }
    &--red    { background: rgb(239 68 68 / 15%);   color: #fca5a5; }
    &--violet { background: rgb(139 92 246 / 15%);  color: #c4b5fd; }
}

.px-dot {
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    background: currentcolor;
}

// ---- Patient hero -------------------------------------------------------------
//
// The identity band at the top of a patient record. Everything a clinician
// checks before opening anything else — who this is, how old, blood group, and
// whether the record is still live — on one line they can read without moving
// their eyes off the top of the screen.

.px-hero {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem;
    padding: 1.25rem 1.5rem;

    /*
     | Not another white card.
     |
     | This is the header of the record — who the patient is, what the order is
     | — and every panel below it is a white card too. Painted the same, it read
     | as the first item in the list rather than as the thing the list belongs
     | to.
     |
     | A wash of the accent from the top-left, where the avatar and the name
     | sit, fading out before it reaches the counts. Mixed into the card surface
     | rather than stated outright, so dark mode gets the same gesture at the
     | same strength without a second rule.
     |
     | Pitched against the page rather than against white: the page itself is a
     | cool near-white (--clinic-surface-alt), so a wash light enough to look
     | subtle on white disappears entirely on it.
     */
    background:
        linear-gradient(
            135deg,
            color-mix(in srgb, var(--px-primary) 13%, var(--px-surface)) 0%,
            color-mix(in srgb, var(--px-primary) 5%, var(--px-surface)) 45%,
            var(--px-surface) 100%
        );
    border: 1px solid color-mix(in srgb, var(--px-primary) 16%, var(--px-border));
    border-radius: var(--px-radius);
    box-shadow: var(--px-shadow-card);

    &__avatar {
        width: 4rem;
        height: 4rem;
        flex: none;
        border-radius: 1.125rem;
        display: grid;
        place-items: center;
        color: #fff;
        font-size: 1.375rem;
        font-weight: 700;
        letter-spacing: 0.02em;
    }

    &__name {
        margin: 0;
        font-size: 1.375rem;
        font-weight: 700;
        letter-spacing: -0.02em;
        line-height: 1.2;
    }

    // Name and state on one line: the status is the first thing looked for and
    // was previously two rows below the name, under everything else.
    &__head {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem 0.75rem;
    }

    &__state {
        display: flex;
        flex-wrap: wrap;
        gap: 0.375rem;
    }

    // The facts, separated by dots rather than boxes — a row of pills here
    // competes with the clinical badges beside the name, which are the ones
    // that should catch the eye.
    &__facts {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem 0.75rem;
        margin-top: 0.375rem;
        font-size: 0.8125rem;
        color: var(--px-muted-strong);

        > *:not(:last-child)::after {
            content: "·";
            margin-inline-start: 0.75rem;
            color: var(--px-border);
        }
    }

    /*
     | The accession number.
     |
     | Monospace and a shade darker than the facts around it, but not the black
     | bold it used to be — set that way it read as a second heading and fought
     | the patient's name directly above it. Monospace alone is enough to mark
     | it as something transcribed rather than read.
     */
    &__ref {
        font-family: var(--bs-font-monospace);
        font-weight: 600;
        letter-spacing: -0.01em;
        color: var(--px-text);
    }

    /*
     | Provenance: who asked and when. Set apart by size and position rather
     | than by colour — the two muted tokens are all but identical in light
     | mode, and going lighter still would not survive the contrast floor the
     | palette above is tuned to.
     */
    &__meta {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.25rem 0.75rem;
        margin-top: 0.25rem;
        font-size: 0.75rem;
        color: var(--px-muted);

        > *:not(:last-child)::after {
            content: "·";
            margin-inline-start: 0.75rem;
            color: var(--px-border);
        }
    }

    &__actions {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem;
        margin-inline-start: auto;
    }

    /*
     | Three regions across the card: who, how much, what next.
     |
     | Identity on the left because it is what the eye lands on, the tallies in
     | the middle because they are read at a glance and never clicked in a
     | hurry, and the actions in a column at the end where a mouse can run down
     | them without hunting. Below a wide screen they stack in that same order.
     */
    /*
     | Three regions, all centred against each other.
     |
     | Top-aligned, the column of actions was the tallest thing in the card and
     | left a band of dead space under the identity and the counts. Centring
     | distributes what is left over evenly instead of pooling it at the bottom.
     */
    &--split {
        align-items: center;
        gap: 1.5rem;

        .px-hero__identity {
            flex: 1 1 18rem;
            min-width: 0;
            display: flex;
            align-items: flex-start;
            gap: 1rem;
        }

        .px-hero__tallies {
            flex: 2 1 20rem;
            min-width: 0;
            display: grid;

            /*
             | Two columns, so four counts read as a block two rows deep rather
             | than a single thin line. That is also what makes the middle of
             | the card roughly as tall as the stack of buttons beside it, which
             | is where the empty space was coming from.
             */
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 0.75rem 1rem;
            align-content: center;

            // Rules rather than boxes: bordered tiles inside a bordered card is
            // three frames deep, and the numbers stop standing out.
            padding-inline: 1.5rem;
            border-inline: 1px solid var(--px-border);
        }

        .px-hero__actions {
            flex: 0 0 12rem;
            flex-direction: column;
            align-items: stretch;
            margin-inline-start: 0;

            /*
             | Everything in the stack is the same width, including the delete
             | control — which renders its own form, so without this it sat
             | half-width and out of line with the buttons above it.
             */
            > *,
            > form,
            > form .btn {
                width: 100%;
            }

            .btn {
                justify-content: center;
            }
        }
    }

    @media (max-width: 991.98px) {
        &--split {
            align-items: stretch;
        }

        &--split .px-hero__tallies {
            padding-inline: 0;
            border-inline: 0;
            padding-block: 1rem;
            border-block: 1px solid var(--px-border);
            grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
        }

        &--split .px-hero__actions {
            flex: 1 1 100%;
            flex-direction: row;
            flex-wrap: wrap;
        }
    }
}

// A count inside the patient header. Small tinted glyph, the number, the word —
// in that order, because the glyph is what the eye uses to find the right one.
.px-tally {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    color: inherit;
    text-decoration: none;
    border-radius: var(--px-radius-sm);
    padding: 0.25rem;
    transition: background var(--px-ease);

    &:hover {
        background: var(--clinic-surface-alt);
    }

    &__icon {
        display: grid;
        place-items: center;
        width: 2rem;
        height: 2rem;
        flex: none;
        border-radius: 0.625rem;
        font-size: 0.875rem;
    }

    &__value {
        display: block;
        font-size: 1.125rem;
        font-weight: 700;
        line-height: 1.1;
        font-variant-numeric: tabular-nums;
    }

    &__label {
        display: block;
        font-size: 0.75rem;
        color: var(--px-muted);
    }

    &--violet .px-tally__icon { background: rgb(109 76 255 / 12%); color: #6d4cff; }
    &--amber  .px-tally__icon { background: rgb(245 158 11 / 14%); color: #b45309; }
    &--green  .px-tally__icon { background: rgb(16 185 129 / 14%); color: #047857; }
    &--blue   .px-tally__icon { background: rgb(37 99 235 / 12%);  color: #2563eb; }
    &--pink   .px-tally__icon { background: rgb(236 72 153 / 14%); color: #be185d; }
    &--red    .px-tally__icon { background: rgb(220 38 38 / 12%);  color: #b91c1c; }

    /*
     | The boxed variant: the same tally, given a tinted card of its own.
     |
     | Added rather than changed, because the bare version is what sits inside
     | the panels on the patient record and the lab order — a tinted box around
     | each of those would turn a quiet summary line into five competing cards.
     | Here the tallies are the summary, above a list, and need to read as
     | pressable things.
     */
    &--boxed {
        padding: 0.875rem 1rem;
        border: 1px solid var(--px-border);
        border-radius: var(--px-radius-sm);
        background: var(--clinic-surface);

        .px-tally__value { font-size: 1.5rem; }

        // The current filter, so the cards say which one is showing.
        &[aria-current="true"] {
            border-color: color-mix(in srgb, var(--px-primary) 45%, var(--px-border));
            box-shadow: 0 0 0 1px color-mix(in srgb, var(--px-primary) 25%, transparent);
        }
    }

    &--boxed.px-tally--violet { background: color-mix(in srgb, #6d4cff 5%, var(--clinic-surface)); }
    &--boxed.px-tally--green  { background: color-mix(in srgb, #10b981 6%, var(--clinic-surface)); }
    &--boxed.px-tally--red    { background: color-mix(in srgb, #dc2626 5%, var(--clinic-surface)); }
    &--boxed.px-tally--blue   { background: color-mix(in srgb, #2563eb 5%, var(--clinic-surface)); }
}

/*
 | The upload review list.
 |
 | A grid rather than a table. The four columns are not four comparable values
 | — they are a person, a file, a state, and a set of decisions — and the last
 | one holds a stack of controls that a table cell squeezes into a column width
 | decided by the longest filename above it.
 |
 | Below the large breakpoint each row becomes its own card and the header row
 | goes away: on a phone the content names itself, and repeating "PATIENT /
 | FILE DETAILS / STATUS" above every row is four extra lines per upload.
 */
.px-uploads {
    &__head,
    &__row {
        display: grid;
        gap: 1rem;
        // The actions column carries four controls side by side, so it is given
        // a floor in rem and the largest share of what is left over. The three
        // reading columns had spare width to give: a name, a filename and a
        // pill do not need a third of the screen each.
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr) minmax(0, 0.55fr) minmax(25rem, 1.5fr);
        align-items: center;
    }

    &__head {
        padding: 0.875rem 1.25rem;
        border-bottom: 1px solid var(--px-border);
        background: var(--clinic-surface-alt);
        font-size: 0.6875rem;
        font-weight: 700;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--px-muted);
    }

    &__row {
        padding: 1.25rem;
        border-bottom: 1px solid var(--px-border);
        transition: background var(--px-ease);

        &:last-child { border-bottom: 0; }
        &:hover { background: var(--clinic-surface-alt); }
    }

    /*
     | One line: open it, name it, file it, or decline it.
     |
     | Read left to right in the order the decision is actually made. The title
     | field is the only elastic part, so a long filename shortens the box
     | rather than pushing "Add to record" off the row.
     */
    &__actions {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem;

        // The accept form only; the reject form nested inside <details> keeps
        // its own stacked layout.
        > form {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            flex: 1 1 12rem;
            min-width: 0;
        }

        .input-group {
            flex: 1 1 8rem;
            min-width: 0;
            width: auto;
        }

        /*
         | One height for everything on the line.
         |
         | A small button, a small input and a text link each work out a
         | different height from their own padding, and four controls abreast at
         | four heights reads as four unrelated things. Set once here rather
         | than trimmed padding by padding.
         */
        .btn,
        .form-control,
        .input-group-text,
        .px-uploads__decline {
            min-height: 2.375rem;
        }

        // Not the input: a flex display on a replaced element is ignored at
        // best, and its own line-height already centres the text.
        .btn,
        .input-group-text,
        .px-uploads__decline {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            white-space: nowrap;
        }

        // Sits with the other three; open, it takes a line of its own, because
        // a reason typed into a 6rem box is a reason nobody reads.
        > details {
            flex: none;
        }

        > details[open] {
            flex: 1 1 100%;
        }
    }

    /*
     | Declining: a button like the others, but an outlined one.
     |
     | It has to look pressable — it is one of the two decisions this screen
     | exists for — while still losing to Approved at a glance, which the filled
     | purple against a red outline does. The marker goes because the triangle
     | reads as a tree control rather than as a control you press.
     */
    &__decline {
        display: inline-flex;
        align-items: center;
        gap: 0.375rem;
        cursor: pointer;
        padding: 0.375rem 0.875rem;
        border: 1px solid color-mix(in srgb, #dc2626 35%, var(--px-border));
        border-radius: var(--px-radius-sm);
        background: var(--clinic-surface);
        font-size: 0.8125rem;
        font-weight: 500;
        color: #b91c1c;
        list-style: none;
        transition: background var(--px-ease), border-color var(--px-ease);

        &::-webkit-details-marker { display: none; }

        &:hover {
            background: color-mix(in srgb, #dc2626 7%, var(--clinic-surface));
            border-color: color-mix(in srgb, #dc2626 55%, var(--px-border));
        }

        &:focus-visible {
            outline: 2px solid var(--px-primary);
            outline-offset: 2px;
        }
    }

    // The file's type as a tile, so the eye sorts scans from letters before
    // reading a single filename.
    &__tile {
        width: 2.75rem;
        height: 2.75rem;
        flex: none;
        display: grid;
        place-items: center;
        border-radius: 0.75rem;
        font-size: 1.125rem;
        background: var(--clinic-surface-alt);
        color: var(--px-muted-strong);

        &--pdf   { background: rgb(220 38 38 / 10%);  color: #b91c1c; }
        &--image { background: rgb(37 99 235 / 10%);  color: #2563eb; }
    }

    @media (max-width: 991.98px) {
        &__head { display: none; }

        &__row {
            grid-template-columns: minmax(0, 1fr);
            gap: 0.875rem;
        }

        // Full-width controls, stacked: at this size the action list is the
        // reason the row was opened, not a trailing column, and four things
        // abreast on a phone is four things too narrow to press.
        &__actions {
            flex-direction: column;
            align-items: stretch;

            > form { flex-direction: column; align-items: stretch; }
            > details, > details[open] { margin-inline-start: 0; }
            .btn { width: 100%; }
        }
    }
}

/*
 | Panels laid out side by side, as many as fit.
 |
 | Used for the record's cards — and for the regions other modules contribute,
 | which is the point: this screen does not know how many panels Laboratory or
 | Messaging will add, so it cannot place them in named columns. They flow.
 |
 | Columns rather than a grid. A grid puts panels in rows, and a row is as tall
 | as the tallest thing in it — so one long problem list left a hand's width of
 | white space under every short card beside it. Columns pack each panel under
 | the one above it and the page closes up.
 |
 | The trade is reading order: down each column rather than across. For a set of
 | independent panels nobody reads in sequence, that is the better half of the
 | bargain.
 */
/*
 | The panels on the patient record, flowing across before they flow down.
 |
 | This was CSS multi-column, which fills the first column to the bottom before
 | starting the second. With one very tall unbreakable panel — result trends,
 | which draws a chart per analyte — the browser could not balance the columns
 | and left the third one empty, so a third of the screen was blank while the
 | left column ran for pages.
 |
 | A row-based grid instead: panels are laid out left to right, as many per row
 | as fit, and the row count follows from how many panels the installed modules
 | contribute. This screen still never learns their names.
 */
.px-cardgrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 1rem;

    /*
     | Panels fill their row rather than stopping at their own height.
     |
     | A grid row is as tall as its tallest member either way — that is not
     | optional. What is optional is whether the shorter panels stop short and
     | leave grey page showing beneath them, or stretch and carry the space
     | inside their own surface. Demographics lists eight fields and sets a tall
     | first row; left to their natural heights the three beside it each ended in
     | a band of background, which reads as a broken layout rather than as a
     | short panel.
     |
     | The space does not go away. It moves from between the cards to inside
     | them, where it reads as a card with room in it.
     */
    align-items: stretch;

    /*
     | Source order is display order — no `dense`.
     |
     | Dense packing lets a later panel jump into an earlier hole, which is a
     | good trade when the panels are interchangeable and a bad one now that
     | their sequence is deliberate: allergies beside the identity, contacts
     | last. A gap under a short panel is the price of the order holding.
     */

    > * {
        min-width: 0;

        // The panels carry their own bottom margin for the layout that preceded
        // this one. The gap does that job now, and both together double it.
        margin-bottom: 0 !important;
    }

    // A panel that holds a grid of its own and therefore earns the whole row.
    // Only result trends claims this; a panel of six fields would gain nothing
    // from the width but a long line to read across.
    &__full {
        grid-column: 1 / -1;
    }
}

// A panel that titles itself. The header is divided from the body by a rule
// rather than by space alone, so a list of entries beneath it reads as belonging
// to the heading rather than as the first of them.
.px-panel--titled {
    overflow: hidden;
}

/*
 | …except while a dropdown inside it is open.
 |
 | The clip above exists so the header's tint stops at the rounded corner. It
 | also clipped anything that tried to leave the card — and a dropdown is
 | supposed to leave the card. The specimen container list came out at its full
 | height, was cut off at the panel's edge, and grew a scrollbar over four
 | visible options while six sat outside the cut.
 |
 | Scoped to the moment it matters: the panel clips as before, and only while a
 | list is actually open does it let that list through. Anything else that would
 | overflow is still contained.
 |
 | `:has()` rather than a class toggled by script — the CSS knows the state from
 | the ARIA attribute the picker already sets, so there is nothing to keep in
 | step and nothing to leave behind if the script fails midway.
 */
.px-panel:has(.px-picker__button[aria-expanded='true']),
.px-panel--titled:has(.px-picker__button[aria-expanded='true']) {
    overflow: visible;
}

.px-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    border-bottom: 1px solid var(--px-border);
}

/*
 | A panel heading set as a heading.
 |
 | The aside label — small, uppercase, muted — is right for a column of eight
 | short panels on a record, and wrong for a card that is the main subject of
 | its screen: at 12px in grey capitals, "Medicines" reads as a field label
 | rather than the title of the thing being read.
 */
.px-panel__title {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--px-text);
}

// The optional tile before a panel's title. Tinted rather than filled: it marks
// the section without competing with the one filled control on the card.
.px-panel__icon {
    display: grid;
    place-items: center;
    flex: none;
    width: 2rem;
    height: 2rem;
    border-radius: 0.625rem;
    font-size: 0.875rem;
    background: color-mix(in srgb, var(--px-primary) 10%, transparent);
    color: var(--px-primary);
}

.px-panel__body {
    padding: 0.25rem 1.25rem 1.25rem;
}

// A quiet destructive action: the same weight as the ghost button beside it,
// carrying its meaning in the word and the colour rather than in a filled red
// block. These sit next to ordinary actions in a list, and a solid button would
// make retracting an entry look like the expected thing to do.
.px-btn-danger-ghost {
    background: var(--clinic-surface);
    border: 1px solid var(--px-border);
    color: #b91c1c;
    font-weight: 500;
    border-radius: var(--px-radius-sm);
    padding: 0.625rem 1rem;
    transition: border-color var(--px-ease), box-shadow var(--px-ease), background-color var(--px-ease);

    &:hover,
    &:focus-visible {
        color: #b91c1c;
        border-color: #b91c1c;
        background: rgb(239 68 68 / 6%);
    }
}

[data-bs-theme="dark"] .px-btn-danger-ghost {
    color: #fca5a5;

    &:hover,
    &:focus-visible {
        color: #fca5a5;
        border-color: #fca5a5;
    }
}

// -----------------------------------------------------------------------------
// A form inside a panel
//
// These forms used Bootstrap's grid — `col-md-2`, `col-md-3` — which is sized
// against the *viewport*. Inside a 20rem panel on a wide screen the breakpoint
// is satisfied and the column is not: `col-md-2` became a 3rem box, so the dose
// field was too narrow for "500mg" and the category select showed "Iden".
//
// This sizes against the container instead. Every field asks for 11rem and the
// panel fits as many as it can — four across a wide panel, one down a narrow
// one — without either the form or the panel knowing the other's width.
// -----------------------------------------------------------------------------

.px-formgrid {
    display: grid;
    gap: 0 0.75rem;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    align-items: start;

    // Buttons, checkbox rows and the file input: things that read as one line
    // rather than as a field beside another.
    &__wide {
        grid-column: 1 / -1;
    }

    // A date input carries its own picker button and will not shrink below it.
    // Left to itself it pushes the column wider than its share and the row
    // stops being even.
    input[type="date"] {
        min-width: 0;
    }
}

// A number and its unit, side by side: "3" and D / W / M. The number is given a
// fixed share rather than left to flex, so the three unit boxes keep their size
// and the pair reads as one control at any panel width.
.px-ago {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;

    > .form-control {
        flex: 0 1 5rem;
        min-width: 4rem;
        text-align: center;
        font-variant-numeric: tabular-nums;
    }
}

.px-ago__units {
    gap: 0.25rem;
    flex-wrap: nowrap;

    .px-segmented__option {
        // Square-ish, so four single letters read as a set of four buttons
        // rather than four words of wildly different widths.
        min-width: 2.25rem;
        justify-content: center;
        padding-inline: 0.5rem;
    }

    // The letters carry a title attribute naming the unit in full; the dotted
    // underline browsers add to <abbr> makes the control look misspelt.
    abbr {
        text-decoration: none;
        cursor: inherit;
    }
}

// -----------------------------------------------------------------------------
// A row in one of the patient record's lists
//
// What was recorded on one side, what can be done about it on the other:
// allergies, problems, medications, documents and contacts all take this shape.
//
// These panels sit in a column that is about 21rem wide, and the two action
// buttons were taking their space out of the text — "Mark resolved" broke across
// two lines inside its own button, and "Recorded by: Clinic Administrator" wrapped
// mid-name. The row now gives the text a floor and lets the buttons drop to a
// line of their own when there is not room for both.
// -----------------------------------------------------------------------------

.px-entry {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;

    // The recorded detail. `min-width: 0` is what lets long values wrap inside
    // the column instead of forcing it wider than the panel.
    > :first-child {
        flex: 1 1 12rem;
        min-width: 0;
    }

    /*
     | The controls. Never squeezed — they shrink to nothing useful long before
     | they run out of room, which is how a button ends up two lines tall — so
     | they keep their width and wrap below the text instead, staying aligned to
     | the end of the row where the eye already expects them.
     |
     | `:not(:first-child)` because a row whose actions are hidden by permission
     | has one child, and it must not be pushed to the right.
     */
    > :last-child:not(:first-child) {
        flex: 0 0 auto;
        margin-inline-start: auto;
    }

    // A label that breaks mid-phrase reads as two controls rather than one.
    .btn {
        white-space: nowrap;
    }
}

// Quick links as a grid of glyphs rather than a list of rows: six destinations
// read faster as icons in three columns than as six lines to scan.
/*
 | The "go to" tiles on the patient record.
 |
 | Used on that screen and nowhere else, which is what makes it safe to give
 | them room: at three to a row in a narrow aside they were 11px labels under a
 | bare icon, small enough to look like decoration rather than the way through
 | to a patient's consultations and bills.
 |
 | Sized by content rather than a fixed three columns, so a fourth tile wraps
 | instead of squeezing the other three.
 */
.px-quick--grid {
    grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
    gap: 0.5rem;

    // One tile posts rather than navigates. Its form is the grid cell; the
    // button fills it, so it lines up with the tiles either side.
    form { display: block; }

    button.px-quick__item {
        width: 100%;
        height: 100%;
        font-family: inherit;
        cursor: pointer;
    }

    .px-quick__item {
        flex-direction: column;
        align-items: center;
        justify-content: flex-start;
        gap: 0.5rem;
        padding: 0.875rem 0.5rem;
        border: 1px solid var(--px-border);
        border-radius: var(--px-radius-sm);
        background: var(--clinic-surface);
        text-align: center;
        font-size: 0.8125rem;
        font-weight: 600;
        line-height: 1.3;
        transition: border-color var(--px-ease), box-shadow var(--px-ease), transform var(--px-ease);

        // The icon in a tile of its own, so the tile reads as a destination
        // rather than a line of text that happens to start with a glyph.
        i {
            display: grid;
            place-items: center;
            width: 2.25rem;
            height: 2.25rem;
            border-radius: 0.75rem;
            font-size: 1rem;
            background: color-mix(in srgb, var(--px-primary) 10%, transparent);
            color: var(--px-primary);
        }

        &:hover {
            border-color: color-mix(in srgb, var(--px-primary) 45%, var(--px-border));
            box-shadow: var(--px-shadow-md);
            transform: translateY(-1px);
        }
    }
}

// A single clinical fact with its count, sitting under the hero.
.px-vitals {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.75rem;
}

.px-vital {
    padding: 0.75rem 0.875rem;
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--px-surface);

    &__label {
        display: block;
        font-size: 0.6875rem;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--px-muted);
    }

    &__value {
        display: block;
        margin-top: 0.25rem;
        font-size: 1.125rem;
        font-weight: 700;
        letter-spacing: -0.01em;
    }

    // Colour carries urgency, and the word beside it carries the meaning —
    // roughly one man in twelve has some colour vision deficiency and this is
    // a clinical screen.
    &--alert  { border-color: #fecaca; background: #fffafb; }
    &--watch  { border-color: #fde68a; background: #fffdf5; }
}

[data-bs-theme="dark"] .px-vital {
    &--alert { border-color: rgb(244 63 94 / 35%); background: rgb(244 63 94 / 8%); }
    &--watch { border-color: rgb(245 158 11 / 35%); background: rgb(245 158 11 / 8%); }
}

/*
 | One clinical note, inside the notes panel.
 |
 | A card within a card, which is why it is drawn quietly: a border and a tint
 | rather than a second shadow. The panel is the record; each of these is an
 | entry in it, and a stack of six should read as a list rather than as six
 | competing boxes.
 */
.px-note {
    padding: 0.875rem 1rem;
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);

    & + & {
        margin-top: 0.75rem;
    }

    // Retracted, and still on the record: struck through elsewhere, faded here.
    // A note that was entered in error is never removed — "was this on the
    // record at the time?" has to stay answerable.
    &--void {
        opacity: 0.55;
        background: var(--clinic-surface-alt);
    }

    &__history {
        font-size: 0.75rem;
        color: var(--px-muted);
        cursor: pointer;

        &:hover { color: var(--px-text); }
    }

    &__version {
        margin-top: 0.5rem;
        padding-left: 0.75rem;
        border-left: 2px solid var(--px-border);
        font-size: 0.8125rem;
    }
}

// ---- Advice card --------------------------------------------------------------
//
// One suggestion from decision support.
//
// Severity is carried by a left accent bar and a faint tint rather than by a
// saturated fill: the body text stays on the panel's own background, which is
// where its contrast was measured, and does not have to be re-checked against
// three coloured surfaces. The bar is 3px because it has to survive being the
// only difference between an advisory and a contraindication.
//
// Deliberately unanimated. `.px-flag` pulses because a triage red flag is a
// single interruption demanding attention now; a panel of four advice cards
// pulsing in unison is how a prescriber learns to stop reading them.

.px-advice {
    position: relative;
    padding: 0.75rem 0.875rem 0.75rem 1rem;
    border: 1px solid var(--px-border);
    border-radius: 0.75rem;
    background: var(--px-advice-tint, transparent);

    &::before {
        content: '';
        position: absolute;
        inset-block: 0.5rem;
        inset-inline-start: 0;
        width: 3px;
        border-radius: 999px;
        background: var(--px-advice-accent, var(--px-border));
    }

    // Named as well as coloured, in the markup — this is a clinical screen and
    // roughly one man in twelve has some form of colour vision deficiency.
    &--info     { --px-advice-accent: #2563eb; --px-advice-tint: #f8fbff; }
    &--caution  { --px-advice-accent: #b45309; --px-advice-tint: #fffdf5; }
    &--critical { --px-advice-accent: #be123c; --px-advice-tint: #fffafb; }

    // Settled rather than alarming: this is the tone for "done, and now fixed",
    // which is a different thing from a warning and reads wrongly in amber.
    &--success  { --px-advice-accent: #047857; --px-advice-tint: #f5fdfa; color: #065f46; }

    // The icon takes the accent, so the banner reads as one colour rather than
    // a coloured bar with a black mark sitting next to it.
    &__icon {
        color: var(--px-advice-accent);
        flex: none;
    }
}

[data-bs-theme="dark"] .px-advice {
    &--info     { --px-advice-accent: #60a5fa; --px-advice-tint: rgb(59 130 246 / 8%); }
    &--caution  { --px-advice-accent: #fbbf24; --px-advice-tint: rgb(245 158 11 / 8%); }
    &--critical { --px-advice-accent: #fb7185; --px-advice-tint: rgb(244 63 94 / 8%); }
    &--success  { --px-advice-accent: #34d399; --px-advice-tint: rgb(16 185 129 / 8%); }
}

// ---- Row actions ------------------------------------------------------------

.px-action {
    width: 2rem;
    height: 2rem;
    display: inline-grid;
    place-items: center;
    border-radius: 0.5rem;
    border: 1px solid transparent;
    background: transparent;
    color: var(--px-muted);
    transition: all var(--px-ease);

    &:hover,
    &:focus-visible {
        background: var(--clinic-surface-alt);
        border-color: var(--px-border);
        color: var(--px-text);
    }

    &--danger:hover,
    &--danger:focus-visible { color: #dc2626; border-color: #fecaca; background: #fef2f2; }

    // 32px is comfortable with a mouse but tight under a fingertip, and these
    // sit two-abreast at the end of a row. Widened on touch only, so the
    // desktop table keeps its density.
    @media (pointer: coarse) {
        width: 2.5rem;
        height: 2.5rem;
    }
}

[data-bs-theme="dark"] .px-action--danger:hover {
    background: rgb(239 68 68 / 12%);
    border-color: rgb(239 68 68 / 30%);
    color: #fca5a5;
}

// ---- Pagination -------------------------------------------------------------

.px-pagination {
    .pagination { margin: 0; gap: 0.25rem; }

    .page-link {
        border-radius: 0.625rem;
        border-color: var(--px-border);
        color: var(--px-text);
        min-width: 2.25rem;
        text-align: center;
        transition: all var(--px-ease);

        &:hover { background: var(--clinic-surface-alt); border-color: var(--px-primary); }
    }

    .page-item.active .page-link {
        background: linear-gradient(135deg, var(--px-primary), var(--px-primary-2));
        border-color: transparent;
        color: var(--px-on-primary);
        box-shadow: 0 4px 10px -2px rgb(109 76 255 / 45%);
    }

    .page-item.disabled .page-link { opacity: 0.45; }
}

// ---- Two-column working layout ----------------------------------------------
//
// The diary is a working screen: the list on the left, the day's context on the
// right. The aside sticks because reception refers back to it constantly while
// scrolling a long day, and collapses under the list below 1200px rather than
// squeezing the table — a diary that cannot show the time column is useless.

.px-layout {
    display: grid;
    gap: 1.5rem;
    align-items: start;
    grid-template-columns: minmax(0, 1fr);

    @media (min-width: 1200px) {
        grid-template-columns: minmax(0, 1fr) 20rem;
    }
}

.px-aside {
    display: grid;
    gap: 1rem;
    min-width: 0;

    @media (min-width: 1200px) {
        position: sticky;

        // Clears the fixed topbar, so a sticky card never hides under it.
        top: calc(var(--clinic-topbar-height) + 1.5rem);
    }
}

.px-aside__card {
    background: var(--clinic-surface);
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius);
    box-shadow: var(--px-shadow-card);
    padding: 1.25rem;
}

.px-aside__title {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--px-muted);
    margin-bottom: 0.875rem;
}

// A label/value row — the shape most of the aside is made of.
.px-stat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.4375rem 0;
    font-size: 0.875rem;

    & + & { border-top: 1px solid var(--px-border); }

    &__label { color: var(--px-muted); min-width: 0; }
    &__value { font-weight: 600; font-variant-numeric: tabular-nums; }
}

// ---- Quick actions ----------------------------------------------------------

.px-quick {
    display: grid;
    gap: 0.5rem;
}

.px-quick__item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.625rem 0.75rem;
    border-radius: var(--px-radius-sm);
    border: 1px solid transparent;
    color: var(--px-text);
    text-decoration: none;
    font-size: 0.875rem;
    font-weight: 500;
    transition: all var(--px-ease);

    i {
        display: grid;
        place-items: center;
        width: 1.75rem;
        height: 1.75rem;
        flex: none;
        border-radius: 0.5rem;
        background: color-mix(in srgb, var(--px-primary) 10%, transparent);
        color: var(--px-primary);
        font-size: 0.875rem;
    }

    &:hover,
    &:focus-visible {
        background: var(--clinic-surface-alt);
        border-color: var(--px-border);
        color: var(--px-text);
        transform: translateX(2px);
    }
}

// ---- Empty state ------------------------------------------------------------

.px-empty {
    display: grid;
    justify-items: center;
    text-align: center;
    padding: 3.5rem 1.5rem;
}

.px-empty__art {
    position: relative;
    display: grid;
    place-items: center;
    width: 7.5rem;
    height: 7.5rem;
    margin-bottom: 1.5rem;

    // The floating circles. Purely decorative, hence aria-hidden on the markup.
    &::before,
    &::after {
        content: "";
        position: absolute;
        border-radius: 50%;
    }

    &::before {
        inset: 0;
        background: linear-gradient(135deg,
            color-mix(in srgb, var(--px-primary) 14%, transparent),
            color-mix(in srgb, var(--px-primary-2) 8%, transparent));
    }

    &::after {
        width: 0.875rem;
        height: 0.875rem;
        top: 0.5rem;
        inset-inline-end: 0.25rem;
        background: color-mix(in srgb, var(--px-primary) 30%, transparent);
    }

    svg {
        position: relative;
        width: 3.25rem;
        height: 3.25rem;
        color: var(--px-primary);
        filter: drop-shadow(0 6px 12px rgb(109 76 255 / 25%));
    }
}

.px-empty__title {
    font-size: 1.125rem;
    font-weight: 650;
    color: var(--px-text);
    margin-bottom: 0.375rem;
}

.px-empty__text {
    font-size: 0.875rem;
    color: var(--px-muted);
    max-width: 26rem;
    margin-bottom: 1.5rem;
}

// ---- Sticky table header ----------------------------------------------------
//
// Scoped to a modifier rather than applied to every .px-table: a sticky header
// inside a short table just adds a shadow that never moves.

.px-table--sticky thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--clinic-surface-alt);
}

// ---- Activity timeline ------------------------------------------------------

.px-timeline {
    position: relative;
    margin: 0;
    padding: 0 0 0 1.125rem;
    list-style: none;

    // The rail. Inset so it runs through the middle of the dots rather than
    // beside them.
    &::before {
        content: "";
        position: absolute;
        inset-block: 0.35rem;
        inset-inline-start: 0.25rem;
        width: 1px;
        background: var(--px-border);
    }

    li {
        position: relative;
        padding-bottom: 0.875rem;
        font-size: 0.8125rem;

        &:last-child { padding-bottom: 0; }

        &::before {
            content: "";
            position: absolute;
            inset-inline-start: -1.0625rem;
            top: 0.35rem;
            width: 0.4375rem;
            height: 0.4375rem;
            border-radius: 50%;
            background: var(--px-primary);
            box-shadow: 0 0 0 3px var(--clinic-surface);
        }
    }

    &__when {
        display: block;
        color: var(--px-muted);
        font-size: 0.75rem;
    }

    /*
     | A dot per kind of event.
     |
     | Set on the item rather than by nth-child, because the order changes with
     | the record: colour has to mean "this is a document" wherever it lands,
     | not "this is the fourth thing that happened".
     */
    li.px-timeline--violet::before { background: var(--px-primary); }
    li.px-timeline--red::before    { background: #dc2626; }
    li.px-timeline--amber::before  { background: #d97706; }
    li.px-timeline--blue::before   { background: #2563eb; }
    li.px-timeline--green::before  { background: #059669; }

    // An appointment whose time has passed. Greyed rather than removed: the
    // question at a desk mid-morning is as often "who have we already seen?"
    // as "who is next?", and the marker is what separates the two halves of
    // the list at a glance.
    li.is-past::before {
        background: var(--px-border);
    }

    li.is-past {
        color: var(--px-muted);
    }
}

// ---- Duration chip ----------------------------------------------------------

// The square glyph beside a payment method's name. Sized to the row rather
// than to the icon so a table of them lines up whichever glyph each carries,
// and tinted rather than filled — a column of saturated squares competes with
// the status pills, which are the thing worth noticing.
.px-method-icon {
    width: 1.75rem;
    height: 1.75rem;
    flex: none;
    border-radius: 0.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--px-primary-tint, rgb(109 76 255 / 10%));
    color: var(--px-primary);
    font-size: 0.8125rem;
}

// ---- Step indicator ---------------------------------------------------------
//
// Where you are in a multi-step task. Numbered rather than ticked, because the
// count is the reassurance — somebody at a busy desk wants to know how much is
// left, not that step two went well.
//
// A completed step is a link back; the current one is not. State is carried by
// weight and fill as well as colour, so the position is still readable in
// greyscale and to anyone who cannot separate the two tints.
.px-steps {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1.25rem;
    padding: 0;
    list-style: none;
}

.px-steps__item {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--px-muted);
    font-size: 0.8125rem;
    text-decoration: none;
}

.px-steps > li {
    display: inline-flex;
    align-items: center;
}

// The rule between steps. Drawn as a pseudo-element on the list item rather
// than as a separate node, so the markup stays an ordered list of four things.
.px-steps > li + li::before {
    content: "";
    width: 1.25rem;
    height: 1px;
    background: var(--px-border);
    margin-inline-end: 0.5rem;
}

.px-steps__num {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--px-border);
    background: var(--clinic-surface-alt);
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.px-steps__item--done {
    color: var(--px-muted-strong);

    .px-steps__num {
        border-color: var(--px-primary);
        color: var(--px-primary);
    }

    &:hover .px-steps__label {
        text-decoration: underline;
    }
}

.px-steps__item--current {
    color: var(--px-text);
    font-weight: 600;

    .px-steps__num {
        border-color: var(--px-primary);
        background: var(--px-primary);
        color: #fff;
    }
}

// The label is the part that goes when there is no room for it. The numbers
// stay: a reception screen at 380px still has to show four of something.
@media (max-width: 575.98px) {
    .px-steps__label {
        display: none;
    }
}

// ---- Navigation progress ----------------------------------------------------
//
// A swap is usually faster than the eye, but a click that produces nothing
// visible for half a second on a slow connection reads as a click that missed.
// A bar at the very top of the viewport says "working" without moving anything
// on the page, which a spinner over the content would.
//
// Delayed by 150ms: below that the bar would flash on every navigation and be
// pure noise, since most swaps finish before it appears.
.is-navigating::before {
    content: "";
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    height: 3px;
    z-index: 2000;
    background: linear-gradient(90deg, var(--px-primary), #06b6d4);
    transform-origin: 0 50%;
    animation: clinic-progress 1.2s ease-out 150ms both;
}

@keyframes clinic-progress {
    0%   { transform: scaleX(0); opacity: 0; }
    10%  { opacity: 1; }
    100% { transform: scaleX(0.92); opacity: 1; }
}

// Somebody who has asked their system for less motion gets a static bar rather
// than none: the information is "still working", and that still has to arrive.
@media (prefers-reduced-motion: reduce) {
    .is-navigating::before {
        animation: none;
        transform: scaleX(1);
        opacity: 1;
    }
}

// ---- Date and time picker ---------------------------------------------------
//
// Flatpickr, dressed as part of this application.
//
// The library ships its own light theme, which is perfectly reasonable and
// looks nothing like the rest of these screens. Everything below re-points it
// at the same tokens the inputs, panels and pills use, so somebody using the
// clinic never has the sense that one control came from somewhere else.
//
// Imported after the library's stylesheet — see app.scss's import order — so
// these win without needing !important on every line.
@import 'flatpickr/dist/flatpickr.css';

.flatpickr-calendar {
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);
    box-shadow: 0 12px 32px rgb(15 23 42 / 12%);
    font-family: inherit;
    font-size: 0.875rem;
    padding: 0.25rem;

    /*
     | Room for that padding.
     |
     | The library pins the calendar, the day area and the day grid all to
     | 307.875px, and sets box-sizing: border-box. Padding therefore came out of
     | the inside: the grid kept its own min-width and overflowed by the 8px it
     | had been given, so Saturday and the next-month arrow fell outside the
     | rounded box and were clipped. The padding is worth keeping — without it
     | the dates sit flush against the border — so the box grows instead.
     */
    width: calc(307.875px + 0.5rem);

    // The library draws a small arrow pointing at the field. It reads as a
    // tooltip rather than a menu, and nothing else in this interface has one.
    &::before,
    &::after { display: none; }
}

.flatpickr-months {
    padding-block: 0.375rem 0.25rem;

    .flatpickr-month,
    .flatpickr-current-month .flatpickr-monthDropdown-months,
    .flatpickr-current-month input.cur-year {
        color: var(--px-text);
        fill: var(--px-text);
        font-weight: 600;
    }

    .flatpickr-prev-month,
    .flatpickr-next-month {
        border-radius: 0.5rem;
        padding: 0.375rem;
        fill: var(--px-muted);

        &:hover {
            background: var(--clinic-surface-alt);
            svg { fill: var(--px-primary); }
        }
    }
}

.flatpickr-weekday {
    color: var(--px-muted);
    font-weight: 600;
    font-size: 0.75rem;
}

.flatpickr-day {
    border-radius: 0.5rem;
    color: var(--px-text);
    border-color: transparent;

    &:hover { background: var(--clinic-surface-alt); border-color: transparent; }

    // Today is outlined, the selection is filled: a clinic looking at a month
    // needs to tell "where we are" from "what I picked" at a glance.
    &.today {
        border-color: var(--px-primary);

        &:hover { background: var(--clinic-surface-alt); color: var(--px-text); }
    }

    &.selected,
    &.selected:hover,
    &.startRange,
    &.endRange {
        background: var(--px-primary);
        border-color: var(--px-primary);
        color: #fff;
    }

    &.flatpickr-disabled,
    &.prevMonthDay,
    &.nextMonthDay {
        color: var(--px-muted);
        opacity: 0.55;
    }
}

.flatpickr-time {
    border-top: 1px solid var(--px-border);

    input,
    .flatpickr-time-separator,
    .flatpickr-am-pm {
        color: var(--px-text);
        font-variant-numeric: tabular-nums;
    }

    input:hover,
    input:focus,
    .flatpickr-am-pm:hover {
        background: var(--clinic-surface-alt);
    }
}

// The visible field flatpickr puts in front of the real one. It inherits the
// original's classes, so it already matches — this only stops a text cursor
// appearing over what behaves like a button.
.flatpickr-alt {
    cursor: pointer;
}

[data-bs-theme="dark"] {
    .flatpickr-calendar,
    .flatpickr-months .flatpickr-month,
    .flatpickr-time input {
        background: var(--clinic-surface);
        color: var(--px-text);
    }

    .flatpickr-day.prevMonthDay,
    .flatpickr-day.nextMonthDay {
        color: var(--px-muted);
    }
}

// ---- Disclosure -------------------------------------------------------------
//
// A card that stays shut until somebody wants what is inside it.
//
// For the settings-shaped panels that share a screen with the work: set up once,
// then rarely touched, and left open they push the thing people actually came
// for below the fold.
//
// Built on <details>, like every other disclosure here — keyboard, focus and
// find-in-page all work without a line of script, which the content security
// policy would refuse anyway.
.px-disclosure {
    &__summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.75rem;
        cursor: pointer;
        list-style: none;

        &::-webkit-details-marker { display: none; }

        // Drawn rather than inherited: the native marker differs between
        // browsers and sits at the wrong end of the row in two of them.
        &::after {
            content: '';
            flex: none;
            width: 0.5rem;
            height: 0.5rem;
            border-right: 2px solid var(--px-muted);
            border-bottom: 2px solid var(--px-muted);
            transform: rotate(45deg);
            transition: transform var(--px-ease);
        }
    }

    &[open] > &__summary::after {
        transform: rotate(-135deg);
    }

    &__body {
        margin-top: 0.75rem;
    }
}

// ---- Test group -------------------------------------------------------------
//
// The panel a set of analytes was ordered under.
//
// A full blood count is one thing a clinician asked for. Rendered flat it reads
// as thirteen separate requests, and the analyte somebody is hunting for sits
// among twelve they never chose individually. Grouping puts the order back the
// way it was placed.
//
// <details> again, for the same reasons as the card inside it: keyboard, focus
// and find-in-page all work without a line of script — which the content
// security policy would refuse anyway.
.px-testgroup {
    margin-bottom: 1.25rem;
    border: 1px solid color-mix(in srgb, var(--px-primary) 28%, var(--px-border));
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface-alt);
    overflow: hidden;

    // The panel is the parent of everything below it, and until this it was
    // drawn at the same weight as the analytes inside — so "CBC(Dengue)" read
    // as one more test in the list rather than as the heading over thirteen.
    &__summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.75rem;
        padding: 0.875rem 1rem;
        cursor: pointer;
        list-style: none;
        font-weight: 600;
        background: color-mix(in srgb, var(--px-primary) 8%, var(--clinic-surface));
        border-bottom: 1px solid color-mix(in srgb, var(--px-primary) 22%, var(--px-border));
        box-shadow: inset 3px 0 0 var(--px-primary);

        &:hover {
            background: color-mix(in srgb, var(--px-primary) 13%, var(--clinic-surface));
        }

        &::-webkit-details-marker { display: none; }

        // The twisty, drawn rather than relied upon: the native marker differs
        // between browsers and is invisible against this surface in one of them.
        &::after {
            content: '';
            flex: none;
            width: 0.5rem;
            height: 0.5rem;
            border-right: 2px solid var(--px-primary);
            border-bottom: 2px solid var(--px-primary);
            transform: rotate(45deg);
            transition: transform var(--px-ease);
        }
    }

    &[open] > &__summary::after {
        transform: rotate(-135deg);
    }

    &__name {
        display: block;
        font-size: 1.0625rem;
        font-weight: 700;
        letter-spacing: -0.01em;
        color: var(--px-primary);
    }

    &__body {
        padding: 0.75rem;

        // The cards inside sit on the page colour, so the group reads as a
        // container rather than as another card of the same weight.
        .px-testcard {
            background: var(--clinic-surface);
        }
    }
}

// ---- System info section ----------------------------------------------------
//
// One contributed block on Settings > System info — the licence, the updater.
// A heading and a rule rather than another card: what each contributes is
// already a set of cards, and a card around cards is three borders deep.
.px-syspanel {
    & + & {
        margin-top: 2rem;
        padding-top: 2rem;
        border-top: 1px solid var(--px-border);
    }

    &__head {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 0.5rem 1rem;
        margin-bottom: 0.75rem;
    }

    &__title {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        margin: 0;
        font-size: 1rem;
        font-weight: 700;
        letter-spacing: -0.01em;
        color: var(--px-primary);
    }
}

// ---- Filter bar --------------------------------------------------------------
//
// A row of filters that shares out the width it is given.
//
// The twelve-column grid cannot do this: seven controls plus a pair of buttons
// have to be assigned integer widths that add to twelve, and the moment they do
// not, the bar wraps and takes more vertical space than the list it filters.
// Flexed, each control takes an equal share, shrinks with the window, and drops
// to a second row only when there is genuinely no room left.
.px-filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.75rem;

    &__field {
        // Equal shares, but never narrower than a legible select.
        flex: 1 1 8.5rem;
        min-width: 0;
    }

    // The patient box takes a name, not a word from a list.
    &__field--wide {
        flex: 2 1 13rem;
    }

    &__actions {
        display: flex;
        gap: 0.5rem;
        flex: 0 0 auto;
        margin-inline-start: auto;
    }

    // Labels are the row's height, and there are seven of them.
    .px-field-label {
        margin-bottom: 0.25rem;
    }

    @media (max-width: 575.98px) {
        &__actions {
            flex: 1 1 100%;
            margin-inline-start: 0;

            .btn {
                flex: 1 1 0;
            }
        }
    }
}

// ---- Money -------------------------------------------------------------------
//
// An amount is read, not transcribed.
//
// Money was set in the monospace face, which is the right instinct for the
// wrong reason: what a column of figures needs is digits of equal width, and
// that is what tabular numerals give — in the page's own typeface, without the
// typewriter look. Monospace stays where it belongs, on the things somebody
// copies character by character: accession numbers, licence keys, permissions.
.px-money {
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    letter-spacing: 0;

    // "PKR 800.00" is one thing and must not break across the space in it.
    white-space: nowrap;
}

// ---- Modal ------------------------------------------------------------------
//
// Bootstrap's own chrome, repainted in px tokens. Left alone it arrives with
// square corners, a hairline grey border and a white body that stays white in
// dark mode — a dialogue box from a different application.
.modal-content {
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius);
    background: var(--clinic-surface);
    box-shadow: var(--px-shadow-lg);
}

.modal-header,
.modal-footer {
    border-color: var(--px-border);
}

.modal-footer {
    gap: 0.5rem;

    // Bootstrap margins every footer child; the gap above does that job, and
    // the two together leave the buttons drifting apart.
    > * {
        margin: 0;
    }
}

// ---- Prescription review ----------------------------------------------------
//
// What is about to be issued, read once before it becomes a thing a patient
// collects from a pharmacy.
.px-review__for {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    margin-bottom: 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--px-border);
}

.px-review__item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.75rem 0;

    & + & {
        border-top: 1px solid var(--px-border);
    }
}

// Numbered, because "the third one" is how a prescription is talked about.
.px-review__no {
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--px-primary);
    background: color-mix(in srgb, var(--px-primary) 12%, var(--clinic-surface));
}

// ---- Release the whole order ------------------------------------------------
//
// Sits under the last test, separated by a rule: it acts on everything above it,
// not on the card it happens to follow.
.px-releaseall {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem 1rem;
    margin-top: 1.25rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--px-border);
}

// ---- Test card --------------------------------------------------------------
//
// One analyte on a laboratory worklist: what it is, what came back, and the form
// to enter it — folded away until wanted.
//
// Built on <details>, not on a script. A technician works down a list of twenty
// with the keyboard; native disclosure gives them space and enter, focus that
// lands where it should, and a find-in-page that opens the section it matched.
// None of that has to be written, and none of it can break.
.px-testcard {
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);
    transition: border-color var(--px-ease), box-shadow var(--px-ease);

    & + & {
        margin-top: 0.75rem;
    }

    &:hover {
        border-color: var(--px-muted);
    }

    &[open] {
        border-color: var(--px-primary);
        box-shadow: 0 0 0 3px rgb(109 76 255 / 8%);
    }

    /*
     | A result outside its range.
     |
     | The critical bar is deliberately heavy. That is the failure the whole
     | flagging mechanism exists to prevent — a potassium of 7.2 sitting on a
     | screen nobody opened — so it is visible from across a bench, and the row
     | also carries the word for anyone who cannot see the colour.
     */
    &--critical { border-inline-start: 4px solid #dc2626; }
    &--abnormal { border-inline-start: 4px solid #d97706; }

    &__summary {
        display: flex;
        align-items: center;
        gap: 1rem;
        padding: 0.875rem 1rem;
        cursor: pointer;
        list-style: none;

        &::-webkit-details-marker { display: none; }

        &:focus-visible {
            outline: 2px solid var(--px-primary);
            outline-offset: -2px;
            border-radius: var(--px-radius-sm);
        }
    }

    &__name {
        font-weight: 600;
        line-height: 1.3;
    }

    &__code {
        display: block;
        font-size: 0.75rem;
        color: var(--px-muted);
        font-family: var(--bs-font-monospace, monospace);
    }

    &__value {
        font-variant-numeric: tabular-nums;
        font-weight: 700;
        font-size: 1.0625rem;
        white-space: nowrap;
    }

    &__chevron {
        flex: none;
        margin-inline-start: auto;
        transition: transform var(--px-ease);
        color: var(--px-muted);
    }

    &[open] &__chevron { transform: rotate(180deg); }

    &__body {
        padding: 1rem;
        border-top: 1px solid var(--px-border);
    }

    /*
     | The row a result is typed into.
     |
     | It was a twelve-column grid, and the number of fields on it depends on
     | the kind of result: a plain number has one, a cut-off assay has two, a
     | culture has a field and a button. The widths were fixed per field, so
     | the commonest case — a single number — used nine columns of twelve and
     | left the value box, the one thing being typed into, the narrowest on the
     | row with a quarter of the width empty beside it.
     |
     | Flex instead, so the fields share whatever the card is wide: each has a
     | floor it will not go below, the comment takes the slack because it is
     | the only one holding a sentence, and nothing has to be counted again
     | when a result type is added.
     */
    &__entry {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem;

        &-field {
            flex: 1 1 11rem;
            min-width: 11rem;
        }

        // A sentence, next to fields holding a number and a machine name.
        &-field--wide {
            flex: 2 1 16rem;
        }

        // A checkbox and a button are as wide as their own content; stretching
        // them only steals room from the fields being typed into.
        &-fixed {
            flex: 0 0 auto;
        }
    }
}

/*
 | Where a value sits inside its reference range.
 |
 | A number beside "0.60 – 1.20" still has to be compared by the reader. The
 | marker does that comparison once, on the server, from the range copied onto
 | the result — so it reflects the range this result was actually read against,
 | not whatever the catalogue says today.
 |
 | Never the only signal: the flag beside it says Low or High in words.
 */
.px-gauge {
    position: relative;
    height: 0.375rem;
    border-radius: 999px;
    background: linear-gradient(90deg, #fed7aa 0 18%, #bbf7d0 18% 82%, #fecaca 82% 100%);
    margin-block: 0.5rem 0.25rem;

    &__mark {
        position: absolute;
        top: 50%;
        width: 0.625rem;
        height: 0.625rem;
        border-radius: 50%;
        background: var(--px-text);
        border: 2px solid var(--clinic-surface);
        transform: translate(-50%, -50%);
    }
}

.px-gauge__scale {
    display: flex;
    justify-content: space-between;
    font-size: 0.6875rem;
    color: var(--px-muted);
    font-variant-numeric: tabular-nums;
}

// ---- Filter bar -------------------------------------------------------------
//
// A row of filters that share one line.
//
// Flex rather than the twelve-column grid: five controls and two buttons do not
// divide into twelve, and forcing them to leaves the buttons a column wide with
// "Apply filters" wrapping onto two lines. Here each control asks for a sensible
// width and gives up space evenly, while the buttons take exactly what their
// text needs and never shrink.
.px-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1rem;
}

.px-filters__field {
    flex: 1 1 9rem;

    // Without this a long clinician name stretches its own control and squeezes
    // every other one on the row.
    min-width: 0;
}

// The search box earns more of the line: it is the one control somebody types
// into rather than picks from, and a truncated term is unreadable.
.px-filters__field--wide {
    flex: 2 1 14rem;
}

// A member that takes a line of its own: a group of related controls that would
// be unreadable squeezed between two single fields.
.px-filters__full {
    flex: 1 1 100%;
}

/*
 | The search-by pills, sitting on the box they aim.
 |
 | Full width like any other, but pulled up against what follows: the pills say
 | what to type into the field below them, and a full filter-bar gap between the
 | two read as two unrelated settings rather than one control. The negative
 | margin cancels most of the flex gap; four pixels are left so they are close
 | without touching.
 */
.px-filters__scope {
    flex: 1 1 100%;
    margin-bottom: -0.75rem;
}

/*
 | Repeated entry rows in the record's panels.
 |
 | Allergies, problems and medications are all taken down several at a time, so
 | each panel's form holds a set of rows and one Save. The rows need to read as
 | separate entries without turning a side panel into a stack of cards, which is
 | what a border and a little breathing room do more quietly than a box each.
 */
.px-repeat__row {
    position: relative;

    // Separators between entries, not around them: a line above the first row
    // would fence the form off from the panel heading it belongs to.
    & + & {
        margin-top: 0.75rem;
        padding-top: 0.75rem;
        border-top: 1px dashed var(--px-border);
    }

    // Only worth offering once there is more than one — with a single row it
    // is a button that empties the form somebody is filling in.
    .px-repeat__remove {
        display: none;
    }
}

[data-clinic-repeat] .px-repeat__row:not(:only-of-type) .px-repeat__remove {
    display: inline-flex;
}

.px-repeat__remove {
    align-items: center;
    gap: 0.25rem;
    padding: 0;
    border: 0;
    background: none;
    font-size: 0.75rem;
    color: var(--px-muted);
    cursor: pointer;

    &:hover {
        color: #b91c1c;
    }

    &:focus-visible {
        outline: 2px solid var(--px-primary);
        outline-offset: 2px;
    }
}

.px-repeat__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: 0.875rem;
}

.px-filters__actions {
    flex: 0 0 auto;
    display: flex;
    gap: 0.5rem;
    margin-inline-start: auto;

    .btn {
        white-space: nowrap;
    }
}

// On a phone the buttons stop hugging the end and take the full width, which is
// where a thumb expects them.
@media (max-width: 575.98px) {
    .px-filters__actions {
        width: 100%;
        margin-inline-start: 0;

        .btn {
            flex: 1 1 0;
        }
    }
}

// ---- Segmented picker -------------------------------------------------------
//
// A short, closed set of choices shown all at once — which field a search is
// aimed at, say. A dropdown hides the options behind a click and gives no clue
// how many there are; laid out flat, the clerk sees every way they could look
// somebody up without opening anything.
//
// Radio inputs underneath, not buttons. Arrow keys move between them, the group
// takes one tab stop rather than five, and a screen reader announces "2 of 4" —
// all of it native, none of it reimplemented in JavaScript.
.px-segmented {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem;
}

.px-segmented__label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--px-muted);
    margin-inline-end: 0.25rem;
}

// Each option is its own box — fill and radius of its own — rather than segments
// sharing one long divided bar. Discrete boxes survive wrapping onto a second
// line, which a joined bar does not: the rounded ends land mid-row and the
// control stops reading as one thing.
.px-segmented__option {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 0.4375rem 0.875rem;
    border-radius: 0.625rem;

    /*
     | Transparent rather than absent.
     |
     | The unselected pill carries no visible edge, but it still reserves the
     | two pixels the selected one would need — without that the whole row
     | shifts sideways every time somebody changes field.
     */
    border: 1px solid transparent;
    background: var(--clinic-surface-alt);
    color: var(--px-muted-strong);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: background var(--px-ease), color var(--px-ease), box-shadow var(--px-ease);

    input {
        position: absolute;
        opacity: 0;
        pointer-events: none;
    }

    &:hover {
        background: color-mix(in srgb, var(--px-primary) 8%, var(--clinic-surface-alt));
        color: var(--px-text);
    }

    /*
     | Filled, and lit rather than flat.
     |
     | This is the one control on the screen whose state changes what typing
     | into the next one will do, so it has to be readable at a glance from
     | across a desk. The gradient is the design system's violet — the same one
     | the metric icons use — so the selected pill belongs to this product
     | rather than looking borrowed.
     */
    &:has(input:checked) {
        background: linear-gradient(135deg, #6d4cff, #8b5cf6);
        border-color: transparent;
        color: #fff;
        box-shadow: 0 1px 3px rgb(109 76 255 / 35%);
    }

    &:has(input:focus-visible) {
        outline: 2px solid var(--px-primary);
        outline-offset: 2px;
    }
}

// ---- Selectable option card -------------------------------------------------
//
// A whole card that behaves like a radio. The input stays in the markup and
// keeps the keyboard and screen-reader behaviour; the card is its label, so the
// hit area is the card rather than a 16px circle.
.px-choice {
    display: block;
    position: relative;
    border: 1px solid var(--px-border);
    border-radius: 0.75rem;
    background: var(--clinic-surface);
    padding: 1rem;
    cursor: pointer;
    height: 100%;
    transition: border-color 0.15s ease, background 0.15s ease;

    &:hover {
        border-color: var(--px-primary);
    }

    input {
        position: absolute;
        opacity: 0;
        pointer-events: none;
    }

    input:checked ~ .px-choice__body {
        color: var(--px-text);
    }

    &:has(input:checked) {
        border-color: var(--px-primary);
        background: var(--px-primary-tint, rgb(109 76 255 / 6%));
    }

    &:has(input:focus-visible) {
        outline: 2px solid var(--px-primary);
        outline-offset: 2px;
    }
}

.px-choice__title {
    font-weight: 600;
    font-size: 0.9375rem;
    margin-bottom: 0.25rem;
}

// The price on a selectable card. Set apart from the description rather than
// appended to it: the cost is a different kind of fact from the specimen type,
// and on the lab wizard it is half of what the choice is being made on.
.px-choice__price {
    display: block;
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--px-border);
    font-size: 0.8125rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--px-text);
}

// The running total under the basket. Divided off with a rule and set in
// tabular figures, so a column of them lines up as it grows.
.px-lab-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.875rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--px-border);
    font-size: 0.875rem;
    color: var(--px-muted);

    strong {
        font-size: 1.0625rem;
        font-variant-numeric: tabular-nums;
        color: var(--px-text);
    }
}

.px-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.125rem 0.4375rem;
    border-radius: 0.5rem;
    border: 1px solid var(--px-border);
    background: var(--clinic-surface-alt);
    color: var(--px-muted-strong);
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

// ---- Trend chart ------------------------------------------------------------
//
// One analyte over time. Position carries in-or-out-of-range — the band is the
// reference range, so a point outside it is the finding — which leaves colour
// free for the one state that needs interrupting: a critical value.
//
// Both mark colours were validated against the panel surface in each mode:
// lightness band, chroma floor, 3:1 contrast, and separation under simulated
// protanopia, deuteranopia and tritanopia. The dark steps are chosen for the
// dark surface, not flipped from the light ones.

:root {
    --px-trend-line: #6d4cff;
    --px-trend-critical: #be123c;
    --px-trend-band: rgb(109 76 255 / 7%);
    --px-trend-grid: var(--px-border);
}

[data-bs-theme="dark"] {
    --px-trend-line: #8b5cf6;
    --px-trend-critical: #e05a5a;
    --px-trend-band: rgb(139 92 246 / 12%);
}

.px-trend {
    margin: 0;
    padding: 1rem;
    border: 1px solid var(--px-border);
    border-radius: var(--px-radius-sm);
    background: var(--clinic-surface);

    &__head {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 1rem;
        margin-bottom: 0.5rem;
    }

    &__title {
        margin: 0;
        font-size: 0.875rem;
        font-weight: 650;
        color: var(--px-text);
    }

    &__unit {
        font-weight: 500;
        color: var(--px-muted);
    }

    &__range,
    &__axis-labels {
        margin: 0;
        font-size: 0.75rem;
        color: var(--px-muted);
    }

    &__latest {
        margin: 0;
        text-align: end;
        white-space: nowrap;
    }

    // Proportional figures: this is a standalone value, not a column, and
    // tabular digits make it look loose at this size.
    &__value {
        display: block;
        font-size: 1.25rem;
        font-weight: 650;
        color: var(--px-text);
    }

    // Height follows width so the scaling stays uniform. A fixed height here
    // is what forces the non-uniform stretch that ovalises the markers.
    &__plot {
        display: block;
        width: 100%;
        height: auto;
        overflow: visible;
    }

    &__axis-labels {
        display: flex;
        justify-content: space-between;
        font-variant-numeric: tabular-nums;
    }

    &__band { fill: var(--px-trend-band); }

    &__axis {
        stroke: var(--px-trend-grid);
        stroke-width: 1;
    }

    &__line {
        fill: none;
        stroke: var(--px-trend-line);
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
    }

    // A 9px marker inside a 13px surface ring — 2px of surface on each side.
    // Both are strokes on zero-length lines, so the sizes are in screen pixels
    // and hold however far the chart is scaled down.
    &__point {
        stroke-linecap: round;
    }

    &__ring {
        stroke: var(--clinic-surface);
        stroke-width: 13;
    }

    &__dot {
        stroke: var(--px-trend-line);
        stroke-width: 9;
    }

    &__point--critical .px-trend__dot {
        stroke: var(--px-trend-critical);
        stroke-width: 12;
    }

    &__point--critical .px-trend__ring {
        stroke-width: 16;
    }

    &__endlabel {
        fill: var(--px-muted-strong);
        font-size: 11px;
        font-weight: 600;
    }

    // The reference range printed under the latest value — a caption, not a
    // reading. It sat unstyled at body size and outweighed the number above it.
    &__range {
        margin: 0.125rem 0 0;
        font-size: 0.75rem;
        color: var(--px-muted-strong);
    }

    &__table {
        margin-top: 0.75rem;

        summary {
            font-size: 0.75rem;
            color: var(--px-muted-strong);
            cursor: pointer;
        }

        summary:focus-visible {
            outline: 2px solid var(--px-primary);
            outline-offset: 2px;
        }

        .px-table { margin-top: 0.5rem; }
    }
}

// Four analytes to a row on a wide screen, falling to three, two and one as the
// window narrows. `auto-fill` rather than `auto-fit`: with two results on file
// auto-fit would stretch them across the full width, giving a clinic with one
// test a pair of enormous cards.
.px-trends {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    align-items: start;
}

@media (min-width: 1400px) {
    .px-trends {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

// The disclosure that reveals the chart and the values beneath a result.
.px-trend__more {
    margin-top: 0.75rem;
    border-top: 1px solid var(--px-border);
    padding-top: 0.625rem;
}

.px-trend__summary {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--px-primary);
    cursor: pointer;
    list-style: none;

    // The default marker is a black triangle that ignores the text colour.
    &::-webkit-details-marker { display: none; }

    &::before {
        content: "";
        width: 0.4rem;
        height: 0.4rem;
        border-right: 1.5px solid currentcolor;
        border-bottom: 1.5px solid currentcolor;
        transform: rotate(-45deg);
        transition: transform 150ms ease;
    }

    &:hover { text-decoration: underline; }
}

.px-trend__more[open] > .px-trend__summary::before {
    transform: rotate(45deg);
}

/*
 | An opened result takes the whole row.
 |
 | Four to a row is right for the collapsed cards, and wrong the moment one is
 | opened: a chart and a four-column table in a 15rem cell wraps every date onto
 | three lines, and the three cards beside it are left standing next to a foot of
 | empty white. Spanning the row gives the detail the width it needs and puts the
 | others on the line below, where they stay readable.
 */
.px-trend:has(.px-trend__more[open]) {
    grid-column: 1 / -1;
}

// With the row to work in, the chart and the values sit side by side rather
// than the table being pushed a screen below the thing it belongs to.
@media (min-width: 992px) {
    .px-trend__more[open] {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        column-gap: 1.5rem;
        align-items: start;
    }

    .px-trend__more[open] > .px-trend__summary {
        grid-column: 1 / -1;
    }

    // The table's first column is a date and should never be broken across
    // lines; the rest can wrap if they must.
    .px-trend__table td:first-child {
        white-space: nowrap;
    }
}

// The values table is no longer its own disclosure, so its caption is a heading
// rather than a summary.
.px-trend__tablehead {
    margin: 0.75rem 0 0.25rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--px-muted);
}

@media (prefers-reduced-motion: reduce) {
    .px-trend__summary::before { transition: none; }
}

// ---- Red flag ---------------------------------------------------------------
//
// The one thing on a listing allowed to move. A hypertensive crisis sitting in
// a waiting room is the case this whole feature exists for, and a static pill
// among forty other pills is not seen.
//
// A slow halo rather than a flash: it draws the eye on a glance across the
// screen without making the row hard to read, and it stops entirely for anyone
// who has asked for reduced motion — for whom a pulsing alert is worse than no
// alert at all.

.px-flag {
    animation: px-flag-pulse 2s ease-in-out infinite;
}

@keyframes px-flag-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgb(220 38 38 / 40%); }
    50% { box-shadow: 0 0 0 5px rgb(220 38 38 / 0%); }
}

@media (prefers-reduced-motion: reduce) {
    .px-flag {
        animation: none;

        // Kept distinguishable without the motion.
        outline: 2px solid #dc2626;
        outline-offset: 1px;
    }
}

// ---- Progress -----------------------------------------------------------------
//
// Lines resulted out of lines ordered. Deliberately thin and unlabelled inside
// the bar — the exact figure is printed beside it, and a percentage on a
// four-test order ("75%") is less use to a technician than "3/4".

.px-progress {
    height: 0.25rem;
    border-radius: 999px;
    background: var(--px-border);
    overflow: hidden;
    max-width: 9rem;
}

.px-progress__bar {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--px-primary), var(--px-primary-2));
    transition: width var(--px-ease);
}

// ---- Responsive table -------------------------------------------------------
//
// Below the breakpoint the ten-column table stops being a table and each row
// becomes its own card, with the column name carried in a data attribute. A
// diary that can only be read by scrolling sideways on a phone is a diary
// nobody reads on a phone.

@media (max-width: 991.98px) {
    .px-table--cards {
        thead { display: none; }

        tbody,
        tr,
        td { display: block; width: 100%; }

        tr {
            border: 1px solid var(--px-border);
            border-radius: var(--px-radius-sm);
            padding: 0.75rem;
            margin-bottom: 0.75rem;
            background: var(--clinic-surface);
        }

        td {
            border: 0 !important;
            padding: 0.3125rem 0 !important;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 1rem;
            text-align: start !important;

            &::before {
                content: attr(data-label);
                flex: none;
                font-size: 0.6875rem;
                font-weight: 700;
                text-transform: uppercase;
                letter-spacing: 0.05em;
                color: var(--px-muted-strong);
            }

            // The patient cell leads the card, so it keeps its own layout and
            // drops the label.
            &[data-label=""]::before { content: none; }
        }
    }
}

// ---- Focus -----------------------------------------------------------------
//
// One explicit ring for the whole tier. Several of these components already
// shared their hover treatment with :focus-visible, but a hover tint is a weak
// indicator on its own — on the row actions it is a barely-there background —
// and the links inside the table were falling back to the browser default.
// A keyboard user should never have to hunt for where they are.
//
// Offset rather than inset so it reads on the pills and circular avatarish
// controls too, and it is the same ring everywhere so the eye learns it once.

.px-btn-primary:focus-visible,
.px-btn-ghost:focus-visible,
.px-action:focus-visible,
.px-quick__item:focus-visible,
.px-tabs__link:focus-visible,
.px-table tbody a:focus-visible,
.px-pagination .page-link:focus-visible {
    outline: 2px solid var(--px-primary);
    outline-offset: 2px;
}

// Motion is decoration here; the information is in the layout.
@media (prefers-reduced-motion: reduce) {
    .px-metric,
    .px-btn-primary,
    .px-btn-ghost,
    .px-action,
    .px-quick__item,
    .px-table tbody tr,

    // The shell draws from the same tier, so it honours the same preference.
    .clinic-sidebar .nav-link,
    .clinic-sidebar__profile,
    .clinic-topbar__icon-btn,
    .clinic-topbar__profile,
    .clinic-topbar__search .form-control {
        transition: none;
    }

    .px-metric:hover,
    .px-btn-primary:hover,
    .px-quick__item:hover { transform: none; }
}

// -----------------------------------------------------------------------------
// Print
// -----------------------------------------------------------------------------
//
// Prescriptions, invoices and lab requests are printed every day. Treating
// print as a first-class layout is not polish — it is the deliverable the
// patient walks out with.

@media print {
    .clinic-sidebar,
    .clinic-topbar,
    .btn,
    .alert-dismissible .btn-close {
        display: none !important;
    }

    .clinic-content {
        padding: 0;
    }

    body.clinic-app {
        background: #fff;
        font-size: 11pt;
    }
}

/*
 | A listing that is being re-filtered.
 |
 | Dimmed rather than emptied or replaced by a spinner: the previous results
 | stay readable while the new ones are fetched, so the screen does not jump
 | and a slow connection does not flash a blank table at someone mid-sentence
 | with a patient. `pointer-events` stops a click landing on a row that is
 | about to be replaced by a different one.
 */
.clinic-results {
    transition: opacity 120ms ease-in-out;
}

.clinic-results--loading {
    opacity: 0.55;
    pointer-events: none;
}

/* Honour a reduced-motion preference — the fade is decorative. */
@media (prefers-reduced-motion: reduce) {
    .clinic-results {
        transition: none;
    }
}

// Screen-specific layers. Kept in partials once a screen needs more than a
// handful of rules, so this file stays the shared theme rather than a place
// every page appends to.
@import "patient-register";
@import "appointment-calendar";
