/* ============================================================================
   clinic-design.css — the agreed ClinicSoft-style design, applied to the app.

   Loaded LAST, after app.all.css, custom-style.css and porcelain-noir.css, so it
   wins on equal specificity. That ordering is deliberate: porcelain-noir is the
   app's own theme and was already last, so anything added before it would be
   silently overridden.

   WHY THIS FILE IS PURELY VARIABLE OVERRIDES
   -------------------------------------------
   porcelain-noir.css defines 148 custom properties and uses them 159 times, so
   the whole theme is token-driven. That means the entire app can be moved from
   its forest-green palette to the design's teal without editing a single
   selector, which is why this file does not try. Rewriting selectors would mean
   re-deriving all 159 call sites and would break any screen not yet visited.

   The design's palette is teal-on-cool-grey; porcelain-noir is green-on-sage.
   They are the same structure in different clothes, which is what makes this
   substitution possible at all.

   FONTS
   -----
   The design uses Atkinson Hyperlegible (UI) and JetBrains Mono, and has no
   serif at all. porcelain-noir maps --pn-font-serif to Fraunces, a display
   serif. Pointing the serif slot at the UI font is what removes it; leaving
   Fraunces there would leave serif headings the design does not have.

   NOT YET DONE, AND NOT PRETENDED OTHERWISE
   ------------------------------------------
   This is the colour, type, radius and shadow layer only. It does not restructure
   the sidebar into the design's grouped nav, does not build the branch switcher,
   the command palette, or the redesigned patient record layout. Those are
   structural work tracked separately. Billing is deliberately absent -- see
   documentation/DESIGN_APPLICATION.md.
   ========================================================================= */

body.porcelain-noir {
    /* ---- surfaces (design tokens: --bg --surface --sunken) ---- */
    --pn-bg: #F4F7F9;
    --pn-surface: #FFFFFF;
    --pn-well: #EEF2F5;

    /* ---- borders ---- */
    --pn-border: #E2E8EE;
    --pn-border-strong: #CBD5DF;

    /* ---- text ---- */
    --pn-text-1: #0F2530;
    --pn-text-2: #475569;
    --pn-text-3: #64748B;

    /* ---- primary: the design's teal (--primary / --primary-hover) ---- */
    --pn-accent: #0E7490;
    --pn-accent-hover: #0C6580;
    --pn-on-accent: #FFFFFF;

    /* porcelain-noir has a brass slot for warm accents. The design has no
       second warm hue, so it maps to the design's violet rather than being left
       as a green-gold that appears nowhere in the mockup. */
    --pn-brass: #6D28D9;

    /* ---- status (design: --s-good / --s-warn / --s-crit / --s-info) ---- */
    --pn-success: #047857;
    --pn-warning: #B45309;
    --pn-danger: #B91C1C;
    --pn-info: #1D4ED8;

    /* ---- the soft backgrounds the design's status TONES are drawn on ----
       --
       --pn-success and friends are TEXT colours. The design's `.tone-green` and
       friends are a soft tinted chip: a pale background with the saturated colour
       as its foreground. Those backgrounds are a separate token in the design
       (--s-good-bg) and there is no way to derive one from a text colour, so they
       are declared here rather than computed. Without them a tone chip has to pick
       some background by hand at each call site, which is how two chips that are
       meant to match end up not matching.
    */
    --pn-success-bg: #D9F3E8;
    --pn-warning-bg: #FDF0D8;
    --pn-danger-bg:  #FDE4E4;
    --pn-accent-bg: #E0F4F8;
    --pn-accent-ink: #0B5468;

    --pn-info-bg:    #E2EAFD;

    /* Violet and neutral were missing entirely. The design uses violet for the
       leads KPI and neutral for the "no status" rows of the invoice breakdown, and
       neither has a near-enough existing token to borrow: --pn-accent is cyan, and
       using it for a violet chip would make two different things look identical. */
    --pn-violet:      #6D28D9;
    --pn-violet-bg:   #EEE7FD;
    --pn-neutral:     #475569;
    --pn-neutral-bg:  #EDF1F5;

    /* The design's chart series. Two colours, used as a pair -- invoiced against
       payments -- so they are named for the pair rather than for a hue, which is
       what stops them being used one at a time and reading as a single series. */
    --pn-series-1: #0891B2;
    --pn-series-2: #6366F1;

    /* ---- the dark sidebar spine (design: --sidebar) ---- */
    --pn-sb-bg: #0B2A33;
    --pn-sb-border: #123A45;
    --pn-sb-text: #B7CCD3;
    --pn-sb-text-hi: #FFFFFF;
    --pn-sb-active: #16505F;
    --pn-sb-on: #22D3EE;

    /* ---- type: the design has no serif ---- */
    --pn-font-serif: "Atkinson Hyperlegible", system-ui, -apple-system, "Segoe UI", sans-serif;
    --pn-font-ui: "Atkinson Hyperlegible", system-ui, -apple-system, "Segoe UI", sans-serif;
    --pn-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    /* ---- shape: the design is squarer than porcelain-noir's 16px cards ---- */
    --pn-radius-card: 12px;
    --pn-radius-control: 8px;
    --pn-radius-pill: 999px;

    /* ---- elevation: the design's --shadow-sm / --shadow-md ---- */
    --pn-shadow-card: 0 1px 2px rgba(15, 37, 48, .06);
    --pn-motion: 150ms ease-out;
}

/* ---------------------------------------------------------------------------
   Dark mode.

   The design specifies a full dark palette, and it is not a mechanical inversion
   of the light one -- the primary lightens to #22B8D6 and the "on primary" text
   inverts to near-black, which is what keeps contrast on a bright cyan button.

   This block is only active when the OS asks for dark AND the user has not
   forced light with data-theme="light", mirroring the design's own
   :root:not([data-theme="light"]) guard. Forcing dark works too.
   ------------------------------------------------------------------------ */
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) {
        --pn-bg: #0B1418;
        --pn-surface: #111D22;
        --pn-well: #0E181D;
        --pn-border: #20323A;
        --pn-border-strong: #2C434C;
        --pn-text-1: #E6F0F3;
        --pn-text-2: #A9BCC3;
        --pn-text-3: #8AA0A8;
        --pn-accent: #22B8D6;
        --pn-accent-hover: #4CC7E0;
        --pn-on-accent: #04222A;
        --pn-brass: #C4B5FD;
        --pn-success: #4ADE80;
        --pn-warning: #FBBF24;
        --pn-danger: #F87171;
        --pn-info: #93B4FF;
        --pn-success-bg: #11301F;
        --pn-warning-bg: #35270B;
        --pn-danger-bg:  #3A1616;
        --pn-accent-bg: #0F3440;
        --pn-accent-ink: #7FD9EA;

        --pn-info-bg:    #172448;
        --pn-violet:      #C4B5FD;
        --pn-violet-bg:   #2A1F4A;
        --pn-neutral:     #A9BCC3;
        --pn-neutral-bg:  #1D2B32;
        --pn-series-1: #22B8D6;
        --pn-series-2: #818CF8;
        --pn-sb-bg: #081013;
        --pn-sb-border: #10222A;
        --pn-sb-active: #143440;
        --pn-sb-on: #38BDF8;
        --pn-shadow-card: 0 1px 2px rgba(0, 0, 0, .4);
    }
}

body.porcelain-noir[data-theme="dark"] {
    --pn-bg: #0B1418;
    --pn-surface: #111D22;
    --pn-well: #0E181D;
    --pn-border: #20323A;
    --pn-border-strong: #2C434C;
    --pn-text-1: #E6F0F3;
    --pn-text-2: #A9BCC3;
    --pn-text-3: #8AA0A8;
    --pn-accent: #22B8D6;
    --pn-accent-hover: #4CC7E0;
    --pn-on-accent: #04222A;
    --pn-brass: #C4B5FD;
    --pn-success: #4ADE80;
    --pn-warning: #FBBF24;
    --pn-danger: #F87171;
    --pn-info: #93B4FF;

    /* Dark tints, not the light ones reused.
     *
     * A light-theme background dropped into dark mode is a pale patch on a dark
     * page -- #D9F3E8 on #111D22 reads as a hole, not as a chip. The design
     * specifies a separate dark value for every one of these, and the values
     * below are those. */
    --pn-success-bg: #11301F;
    --pn-warning-bg: #35270B;
    --pn-danger-bg:  #3A1616;
    --pn-accent-bg: #0F3440;
    --pn-accent-ink: #7FD9EA;

    --pn-info-bg:    #172448;
    --pn-violet:      #C4B5FD;
    --pn-violet-bg:   #2A1F4A;
    --pn-neutral:     #A9BCC3;
    --pn-neutral-bg:  #1D2B32;
    --pn-series-1: #22B8D6;
    --pn-series-2: #818CF8;

    --pn-sb-bg: #081013;
    --pn-sb-border: #10222A;
    --pn-sb-active: #143440;
    --pn-sb-on: #38BDF8;
    --pn-shadow-card: 0 1px 2px rgba(0, 0, 0, .4);
}

/* ---------------------------------------------------------------------------
   The design's own component classes.

   Scoped to .pn so they cannot leak into app screens that happen to share a
   generic name like .badge or .chip. Anything below is additive: a screen that
   does not use these classes is completely unaffected.
   ------------------------------------------------------------------------ */
.pn-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 700;
    line-height: 1.4;
    background: var(--pn-well);
    color: var(--pn-text-2);
}
.pn-chip.tone-cyan { background: #E0F4F8; color: #0B5468; }
.pn-chip.tone-red  { background: #FDE4E4; color: #B91C1C; }
.pn-chip.tone-amber{ background: #FDF0D8; color: #B45309; }
.pn-chip.tone-green{ background: #D9F3E8; color: #047857; }

@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-chip.tone-cyan  { background: #0F3440; color: #7FD9EA; }
    body.porcelain-noir:not([data-theme="light"]) .pn-chip.tone-red   { background: #3A1616; color: #F87171; }
    body.porcelain-noir:not([data-theme="light"]) .pn-chip.tone-amber { background: #35270B; color: #FBBF24; }
    body.porcelain-noir:not([data-theme="light"]) .pn-chip.tone-green { background: #11301F; color: #4ADE80; }
}
body.porcelain-noir[data-theme="dark"] .pn-chip.tone-cyan  { background: #0F3440; color: #7FD9EA; }
body.porcelain-noir[data-theme="dark"] .pn-chip.tone-red   { background: #3A1616; color: #F87171; }
body.porcelain-noir[data-theme="dark"] .pn-chip.tone-amber { background: #35270B; color: #FBBF24; }
body.porcelain-noir[data-theme="dark"] .pn-chip.tone-green { background: #11301F; color: #4ADE80; }

/* The design's vitals band. Renders an out-of-range value in the critical colour
   and a missing one as muted text -- never a permanent red strip, because an
   alert that is always on is an alert nobody reads. */
.pn-vitals { display: flex; flex-wrap: wrap; gap: 10px; }
.pn-vital {
    flex: 1 1 130px;
    padding: 10px 12px;
    border: 1px solid var(--pn-border);
    border-radius: 10px;
    background: var(--pn-surface);
}
.pn-vital small { display: block; color: var(--pn-text-3); font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
.pn-vital b { font-size: 19px; font-variant-numeric: tabular-nums; }
.pn-vital b span { font-size: 12px; color: var(--pn-text-3); font-weight: 400; margin-left: 3px; }
.pn-vital.is-abnormal { border-color: #B91C1C; background: #FDE4E4; }
.pn-vital.is-abnormal b { color: #B91C1C; }
.pn-vital.is-missing b { color: var(--pn-text-3); font-size: 15px; font-weight: 400; }
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-vital.is-abnormal { background: #3A1616; border-color: #F87171; }
    body.porcelain-noir:not([data-theme="light"]) .pn-vital.is-abnormal b { color: #F87171; }
}
body.porcelain-noir[data-theme="dark"] .pn-vital.is-abnormal { background: #3A1616; border-color: #F87171; }
body.porcelain-noir[data-theme="dark"] .pn-vital.is-abnormal b { color: #F87171; }

/* ---------------------------------------------------------------------------
   Vitals band: the design's four states.

   Recorded and in range is PLAIN -- no colour at all. That is the point. Red is
   reserved for genuinely out-of-range values, because a permanent red strip is an
   alert nobody reads, and an alert that is always on cannot be distinguished from
   one that just fired.

   A missing value is muted rather than red: absent is not the same as abnormal, and
   colouring them alike would make "we never measured this" look like "this is wrong".
   A missing value that BLOCKS a derived calculation additionally gets an amber flag
   naming what it blocks, which is the design's "Needed for BMI".
--------------------------------------------------------------------------- */
.pn-vitals {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 4px 0 14px;
}
.pn-vital {
    flex: 1 1 132px;
    min-width: 0;
    padding: 10px 12px;
    border: 1px solid var(--pn-border);
    border-radius: 10px;
    background: var(--pn-surface);
}
.pn-vital > small {
    display: block;
    color: var(--pn-text-3);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .05em;
    margin-bottom: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pn-vital > b {
    font-size: 19px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}
.pn-vital > b > span {
    font-size: 11.5px;
    color: var(--pn-text-3);
    font-weight: 400;
    margin-left: 3px;
}
.pn-vital > b.num { font-variant-numeric: tabular-nums; }

/* missing: muted, never red */
.pn-vital.is-missing > b {
    font-size: 14px;
    font-weight: 400;
    color: var(--pn-text-3);
}
/* abnormal: the only red in the band */
.pn-vital.is-abnormal {
    border-color: #B91C1C;
    background: #FDE4E4;
}
.pn-vital.is-abnormal > b { color: #B91C1C; }

/* the flag, used for both an abnormal value and a blocking absence */
.pn-vital-flag {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 4px;
    font-size: 11px;
    line-height: 1.3;
    color: #B45309;
}
.pn-vital.is-abnormal .pn-vital-flag { color: #B91C1C; }
.pn-vital-flag svg { width: 13px; height: 13px; flex-shrink: 0; }

@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-vital.is-abnormal { background: #3A1616; border-color: #F87171; }
    body.porcelain-noir:not([data-theme="light"]) .pn-vital.is-abnormal > b { color: #F87171; }
    body.porcelain-noir:not([data-theme="light"]) .pn-vital.is-abnormal .pn-vital-flag { color: #F87171; }
    body.porcelain-noir:not([data-theme="light"]) .pn-vital-flag { color: #FBBF24; }
}
body.porcelain-noir[data-theme="dark"] .pn-vital.is-abnormal { background: #3A1616; border-color: #F87171; }
body.porcelain-noir[data-theme="dark"] .pn-vital.is-abnormal > b { color: #F87171; }
body.porcelain-noir[data-theme="dark"] .pn-vital.is-abnormal .pn-vital-flag { color: #F87171; }
body.porcelain-noir[data-theme="dark"] .pn-vital-flag { color: #FBBF24; }

/* ---------------------------------------------------------------------------
   PATIENT RECORD HEADER
   The identity band, chips, action row and the two-column aside.
   Scoped .pn-* so nothing here can collide with the app's existing classes.
--------------------------------------------------------------------------- */
.pn-hero {
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    border-radius: var(--pn-radius-card, 12px);
    padding: 18px 20px 14px;
    margin-bottom: 16px;
    box-shadow: var(--pn-shadow-card, 0 1px 2px rgba(15,37,48,.06));
}
.pn-hero-top {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
}
.pn-avatar {
    width: 60px; height: 60px;
    border-radius: 16px;
    font-size: 21px;
    background: #EEE7FD; color: #6D28D9;
    flex-shrink: 0;
}
.pn-hero-id { flex: 1 1 320px; min-width: 0; }
.pn-hero-id h1 {
    margin: 0 0 6px;
    font-size: 23px;
    line-height: 1.2;
    letter-spacing: -.01em;
    font-weight: 700;
}
.pn-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    color: var(--pn-text-2);
    font-size: 13.5px;
    margin-bottom: 9px;
}
.pn-meta span { display: inline-flex; align-items: center; gap: 6px; }
.pn-meta svg { width: 14px; height: 14px; opacity: .65; flex-shrink: 0; }
.pn-meta .text-off { color: var(--pn-text-3); }

.pn-chips { display: flex; flex-wrap: wrap; gap: 7px; }

.pn-hero-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
.pn-hero-actions .btn {
    display: inline-flex; align-items: center; gap: 7px;
    min-height: 38px; padding: 0 14px;
    border: 1px solid var(--pn-border);
    border-radius: 10px;
    background: var(--pn-surface);
    color: var(--pn-text-1);
    font-weight: 600; font-size: 14px;
    text-decoration: none;
    transition: background .15s, border-color .15s;
}
.pn-hero-actions .btn:hover { background: var(--pn-surface-2, #F8FAFC); border-color: var(--pn-border-strong); }
.pn-hero-actions .btn svg { width: 16px; height: 16px; }

/* ---- the two-column aside ---- */
.pn-layout { display: block; margin-bottom: 16px; }
@media (min-width: 992px) {
    .pn-layout-aside { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
    .pn-layout-aside .pn-aside { position: sticky; top: 76px; }
}
.pn-aside { display: flex; flex-direction: column; gap: 14px; }

.pn-card {
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    border-radius: 12px;
    overflow: hidden;
}
.pn-card-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 15px;
    border-bottom: 1px solid var(--pn-border);
}
.pn-card-head h3 { margin: 0; font-size: 14px; font-weight: 700; color: var(--pn-text-1); }
.pn-card-body { padding: 14px 15px; }

.pn-care { display: flex; align-items: center; gap: 11px; }
.pn-care .avatar {
    width: 36px; height: 36px; border-radius: 50%;
    display: grid; place-items: center;
    font-weight: 700; font-size: 13px; flex-shrink: 0;
    background: var(--pn-well, #EEF2F5); color: var(--pn-text-2);
}
.pn-care b { display: block; font-size: 14px; line-height: 1.25; }
.pn-care small { display: block; color: var(--pn-text-3); font-size: 12px; line-height: 1.35; }

.pn-dl { margin: 0; display: grid; grid-template-columns: minmax(96px, 40%) 1fr; gap: 0; }
.pn-dl dt {
    padding: 7px 0;
    color: var(--pn-text-3); font-size: 12.5px; font-weight: 600;
    border-bottom: 1px solid var(--pn-border);
}
.pn-dl dd {
    margin: 0; padding: 7px 0;
    font-size: 13.5px; color: var(--pn-text-1);
    border-bottom: 1px solid var(--pn-border);
    overflow-wrap: anywhere;
}
.pn-dl dt:last-of-type, .pn-dl dd:last-of-type { border-bottom: 0; }
.pn-dl dd.text-off { font-weight: 400; }

@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-avatar { background: #2A1F4A; color: #C4B5FD; }
}
body.porcelain-noir[data-theme="dark"] .pn-avatar { background: #2A1F4A; color: #C4B5FD; }

/* ---------------------------------------------------------------------------
   CLINICAL FORMS  (diagnosis / case sheet / prescription / test result)
--------------------------------------------------------------------------- */
.pn-form {
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    border-radius: 12px;
    padding: 16px 18px;
    margin-bottom: 16px;
}
.pn-form-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 12px; }
@media (max-width: 767px) {
    .pn-form-grid { grid-template-columns: 1fr; }
    .pn-form-grid .fgroup { grid-column: 1 / -1 !important; }
}
.pn-form .fgroup { grid-column: span 12; min-width: 0; }
.pn-form .span-2 { grid-column: span 2; }
.pn-form .span-3 { grid-column: span 3; }
.pn-form .span-4 { grid-column: span 4; }
.pn-form .span-6 { grid-column: span 6; }
.pn-form .span-8 { grid-column: span 8; }
.pn-form .span-12 { grid-column: span 12; }

.pn-form .fgroup > label {
    display: block;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--pn-text-3);
    margin-bottom: 5px;
}
.pn-form .input,
.pn-form input[type=text], .pn-form input[type=number],
.pn-form input:not([type]), .pn-form select, .pn-form textarea {
    width: 100%;
    min-height: 38px;
    padding: 7px 11px;
    border: 1px solid var(--pn-border);
    border-radius: 9px;
    background: var(--pn-surface);
    color: var(--pn-text-1);
    font: inherit;
    font-size: 14px;
}
.pn-form textarea.input { min-height: 64px; resize: vertical; }
.pn-form .input:focus {
    outline: none;
    border-color: var(--pn-accent);
    box-shadow: 0 0 0 3px rgba(14,116,144,.14);
}

.pn-input-icon { position: relative; display: flex; align-items: center; }
.pn-input-icon > svg {
    position: absolute; left: 11px; width: 15px; height: 15px;
    color: var(--pn-text-3); pointer-events: none;
}
.pn-input-icon > input { padding-left: 33px; }

.pn-inline-unit { display: flex; align-items: stretch; }
.pn-inline-unit > input { border-radius: 9px 0 0 9px; }
.pn-inline-unit > span {
    display: flex; align-items: center;
    border: 1px solid var(--pn-border); border-left: 0;
    border-radius: 0 9px 9px 0; background: var(--pn-well);
    padding: 0 9px; font-size: 12.5px; color: var(--pn-text-2); white-space: nowrap;
}
.pn-inline-unit > span > select { border: 0; background: none; outline: none; font: inherit; font-size: 12.5px; color: inherit; }

.pn-hint { font-size: 11.5px; color: var(--pn-text-3); }
.pn-form .form-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; flex-wrap: wrap; margin-top: 14px; padding-top: 12px;
    border-top: 1px solid var(--pn-border);
}
.pn-form .form-foot .actions { display: flex; gap: 8px; }

/* segmented rx type */
.pn-segmented {
    display: inline-flex; flex-wrap: wrap; gap: 2px;
    padding: 3px; margin-bottom: 14px;
    background: var(--pn-well); border-radius: 10px;
}
.pn-segmented button {
    border: 0; background: none; cursor: pointer;
    padding: 6px 13px; border-radius: 8px;
    font-size: 13.5px; font-weight: 600; color: var(--pn-text-2);
}
.pn-segmented button[aria-pressed="true"] {
    background: var(--pn-surface); color: var(--pn-text-1);
    box-shadow: 0 1px 2px rgba(15,37,48,.10);
}

/* medicine typeahead */
.pn-typeahead {
    list-style: none; margin: 6px 0 0; padding: 4px;
    border: 1px solid var(--pn-border); border-radius: 10px;
    background: var(--pn-surface);
    max-height: 250px; overflow-y: auto;
}
.pn-typeahead li {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 7px 9px; border-radius: 7px; cursor: pointer; font-size: 13.5px;
}
.pn-typeahead li:hover { background: var(--pn-well); }
.pn-typeahead .pn-ta-name { font-weight: 600; color: var(--pn-text-1); }
.pn-typeahead small { color: var(--pn-text-3); font-size: 12px; }

/* chosen medicine lines */
.pn-rx-lines { margin-top: 14px; }
.pn-rx-lines-head {
    display: flex; justify-content: space-between; align-items: center;
    font-size: 11.5px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .04em; color: var(--pn-text-3);
    padding-bottom: 6px; border-bottom: 1px solid var(--pn-border);
}
.pn-rx-lines ul { list-style: none; margin: 0; padding: 0; }
.pn-rx-lines li {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 8px 2px; border-bottom: 1px solid var(--pn-border);
}
.pn-line-remove {
    margin-left: auto; border: 0; background: none; cursor: pointer;
    color: var(--pn-text-3); padding: 3px; border-radius: 6px; line-height: 0;
}
.pn-line-remove:hover { background: #FDE4E4; color: #B91C1C; }
.pn-line-remove svg { width: 15px; height: 15px; }

/* save feedback -- never a silent success on a clinical write */
.pn-form-msg {
    margin: 0 0 10px; padding: 8px 11px; border-radius: 8px;
    font-size: 13px; font-weight: 600;
}
.pn-form-msg.ok  { background: #D9F3E8; color: #047857; }
.pn-form-msg.bad { background: #FDE4E4; color: #B91C1C; }
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-form-msg.ok  { background: #11301F; color: #4ADE80; }
    body.porcelain-noir:not([data-theme="light"]) .pn-form-msg.bad { background: #3A1616; color: #F87171; }
}
body.porcelain-noir[data-theme="dark"] .pn-form-msg.ok  { background: #11301F; color: #4ADE80; }
body.porcelain-noir[data-theme="dark"] .pn-form-msg.bad { background: #3A1616; color: #F87171; }

/* buttons inside the forms */
.pn-form .btn {
    display: inline-flex; align-items: center; gap: 7px;
    min-height: 38px; padding: 0 15px; border-radius: 9px;
    border: 1px solid var(--pn-border); background: var(--pn-surface);
    color: var(--pn-text-1); font: inherit; font-size: 14px; font-weight: 600;
    cursor: pointer;
}
.pn-form .btn.primary { background: var(--pn-accent); border-color: var(--pn-accent); color: var(--pn-on-accent); }
.pn-form .btn.primary:hover { background: var(--pn-accent-hover); border-color: var(--pn-accent-hover); }
.pn-form .btn.ghost { background: none; }
.pn-form .btn[disabled] { opacity: .5; cursor: not-allowed; }
.pn-form .btn svg { width: 15px; height: 15px; }

/* ---------------------------------------------------------------------------
   CLINICAL HISTORY: the rows a clinician reads back
   Rendered from the same rows the save endpoints write, so "no diagnosis
   recorded" can only appear when the table is genuinely empty.
--------------------------------------------------------------------------- */
.pn-table { margin-bottom: 0; }
.pn-table thead th {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--pn-text-3);
    border-bottom: 1px solid var(--pn-border);
    white-space: nowrap;
}
.pn-table tbody td {
    border-bottom: 1px solid var(--pn-border);
    vertical-align: top;
    padding-top: 9px;
    padding-bottom: 9px;
}
.pn-table tbody tr:last-child td { border-bottom: 0; }

/* A filed case sheet. The four SOAP fields are labelled because a wall of prose
   with no headings is not a SOAP note. */
.pn-note {
    border: 1px solid var(--pn-border);
    border-left: 3px solid var(--pn-accent);
    border-radius: 0 10px 10px 0;
    padding: 10px 14px;
    margin-bottom: 10px;
    background: var(--pn-well);
}
.pn-note-head {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--pn-text-3);
    margin-bottom: 6px;
}
.pn-note-row { display: flex; gap: 10px; font-size: 13.5px; margin-bottom: 4px; }
.pn-note-row:last-child { margin-bottom: 0; }
.pn-note-label {
    flex: 0 0 88px;
    font-weight: 700;
    color: var(--pn-text-2);
}

/* Shown where a required input does not exist yet -- e.g. an insurance
   prescription on a patient with no diagnosis. Never a silent absence. */
.pn-callout {
    padding: 9px 12px;
    border-radius: 9px;
    background: #FEF3C7;
    color: #92400E;
    font-size: 12.5px;
    font-weight: 600;
}
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-callout { background: #3A2E0B; color: #FCD34D; }
}
body.porcelain-noir[data-theme="dark"] .pn-callout { background: #3A2E0B; color: #FCD34D; }

/* ---------------------------------------------------------------------------
   CERTIFICATES: a destructive action that is not the primary action

   Cancelling an issued certificate is routine -- a typo in a date, a patient who
   no longer needs the letter. Giving it the same visual weight as "Print" made
   the row read as an error state, and a screen where the loudest thing is the
   thing you should be careful about trains people to click through the table
   without reading it.

   So it is a quiet text button that turns red on hover and on focus. Keyboard
   focus is styled too: a control that only signals on :hover is invisible to
   anyone navigating with a keyboard.
--------------------------------------------------------------------------- */
.pn-form .btn-quiet-danger {
    border: 1px solid transparent;
    background: none;
    color: var(--pn-text-3);
    font-size: 12px;
    font-weight: 600;
    padding: 2px 7px;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
}
.pn-form .btn-quiet-danger:hover {
    color: var(--pn-danger);
    border-color: var(--pn-danger);
    background: color-mix(in srgb, var(--pn-danger) 8%, transparent);
}
.pn-form .btn-quiet-danger:focus-visible {
    outline: 2px solid var(--pn-accent);
    outline-offset: 2px;
}

/* ===========================================================================
   THE SIDEBAR'S GROUPS

   The design's sidebar is a shape, not a list: quiet uppercase section headers
   with their items underneath, and two collapsible groups (Billing, Settings)
   that live inside a header and above the footer respectively.

   --pn-sb-muted is added here rather than to the token block, because it only
   means anything to a group header -- nothing else on the sidebar is meant to be
   quieter than its label.
   =========================================================================== */
:root {
    --pn-sb-muted: #6F8F99;
}
[data-theme="dark"], .dark {
    --pn-sb-muted: #6F8F99;
}

/* ---- section header: "Care management", "Engage", "Finance" ---------------- */
#sidebar-menu .pn-menu-group-label,
#sidebar-menu li.pn-menu-group > .pn-menu-group-label {
    display: block;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--pn-sb-muted);
    padding: 16px 10px 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The first group header sits directly under the brand block, where the design
   gives it 16px of top padding. Repeating that under the brand leaves a visible
   gap that reads as a rendering fault rather than as spacing. */
#sidebar-menu .pn-menu-group:first-child > .pn-menu-group-label {
    padding-top: 4px;
}

/* ---- the items under a header --------------------------------------------- */
#sidebar-menu .pn-menu-group-items {
    margin: 0;
    padding: 0;
    list-style: none;
}

#sidebar-menu .pn-menu-group-items > li > a {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 40px;
    padding: 8px 10px;
    border-radius: 9px;
    color: var(--pn-sb-text);
    font-size: 14.5px;
    text-decoration: none;
    white-space: nowrap;
    transition: background var(--pn-motion), color var(--pn-motion);
}

#sidebar-menu .pn-menu-group-items > li > a:hover {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}

/* The active row carries a 3px bar down its left edge, which is the design's
   only ornament and the thing that makes the current page findable in a list
   this long. It is drawn on the `a` rather than the `li` so the bar's offset is
   relative to the item, not to the group. */
#sidebar-menu .pn-menu-group-items > li.active > a {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}
#sidebar-menu .pn-menu-group-items > li.active > a::before {
    content: "";
    position: absolute;
    left: -4px;
    top: 9px;
    bottom: 9px;
    width: 3px;
    border-radius: 3px;
    background: var(--pn-sb-on);
}

/* `position: relative` is needed for the bar above. Set on the anchor, not the
   list item, so a nested accordion's own children do not inherit it. */
#sidebar-menu .pn-menu-group-items > li > a {
    position: relative;
}

/* ---- collapsible group: "Billing", "Settings" ------------------------------ */
#sidebar-menu .pn-accordion > .pn-accordion-toggle {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 40px;
    padding: 8px 10px;
    border: 0;
    background: none;
    border-radius: 9px;
    color: var(--pn-sb-text);
    font-size: 14.5px;
    font-weight: 500;
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--pn-motion), color var(--pn-motion);
}

#sidebar-menu .pn-accordion > .pn-accordion-toggle:hover {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}

#sidebar-menu .pn-accordion > .pn-accordion-toggle:focus-visible {
    outline: 2px solid var(--pn-sb-on);
    outline-offset: -2px;
}

/* The disclosure triangle, drawn here rather than taken from the icon set.
 *
 * The design uses a chevron icon. This bundle has no chevron -- not chevron-down,
 * not chevron-right, none -- and feather.replace() THROWS on a name it does not
 * have, which aborts the pass and leaves every icon after it in the document
 * unrendered. One icon in a group header, and the whole sidebar loses its glyphs.
 *
 * The app's own expander already draws this as a CSS ::before triangle
 * (`#sidebar-menu li.expand > a::before`), so a border triangle is both the safe
 * choice and the app's existing idiom. It also survives a bundle that gains no
 * new icons. */
#sidebar-menu .pn-accordion > .pn-accordion-toggle::after {
    content: "";
    margin-left: auto;
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border: solid currentColor;
    border-width: 0 2px 2px 0;
    padding: 3px;
    display: inline-block;
    transform: rotate(45deg);
    transform-origin: center;
    transition: transform .2s;
    opacity: .65;
}

#sidebar-menu .pn-accordion > .pn-accordion-toggle[aria-expanded="true"]::after {
    transform: rotate(225deg);
    opacity: .9;
}

/* The child list. Padding-left 40px matches the design's `.subnav`, so a child
   lines up under its parent's icon rather than under its label -- which is what
   makes the nesting legible without a vertical rule. */
#sidebar-menu .pn-accordion > ul {
    margin: 0;
    padding: 2px 0 6px 40px;
    list-style: none;
}

#sidebar-menu .pn-accordion > ul > li > a {
    display: block;
    padding: 7px 10px;
    border-radius: 8px;
    color: var(--pn-sb-text);
    font-size: 14px;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: background var(--pn-motion), color var(--pn-motion);
}

#sidebar-menu .pn-accordion > ul > li > a:hover {
    color: var(--pn-sb-text-hi);
    background: var(--pn-sb-active);
}

#sidebar-menu .pn-accordion > ul > li.active > a {
    color: var(--pn-sb-text-hi);
    font-weight: 600;
}

/* A closed accordion shows only its header.
 *
 * Driven by `max-height`, not `display`, and that is not a stylistic preference.
 * `.sidebar-menu li ul { max-height: 0; overflow: hidden }` in app.all.css
 * collapses EVERY nested list in this sidebar and only
 * `.sidebar-menu .expand.open ul { max-height: 1000px }` re-opens it. The group
 * lists below therefore have to be un-collapsed deliberately -- an earlier
 * version of this block used `display: none` and every item row rendered at zero
 * height, which looked like a permissions problem and was a cascade one.
 *
 * Reusing the max-height mechanism also inherits the 300ms transition the app
 * already declares, so the group opens the way every other sidebar group does. */
#sidebar-menu .pn-menu-group > .pn-menu-group-items {
    max-height: none;
    overflow: visible;
}

#sidebar-menu .pn-accordion > ul {
    max-height: 0;
    /* Padding is zeroed while closed, not just clipped.
     *
     * `overflow: hidden` with `max-height: 0` still honours padding, so the closed
     * group kept 8px of vertical padding -- an invisible but real gap between
     * "Billing" and the next section header, which is where a stray margin would
     * usually be blamed. Zeroed here and restored below. */
    padding-top: 0;
    padding-bottom: 0;
    overflow: hidden;
}
#sidebar-menu .pn-accordion.open > ul {
    max-height: 1000px;
    padding-top: 2px;
    padding-bottom: 6px;
}

/* ---- THE SIDEBAR IS A FLEX COLUMN OF THE WINDOW'S HEIGHT ------------------
   The app's stylesheet gives .sidebar-scroll `margin-top: 65px` and a fixed
   height -- right when the sidebar held only a 65px brand header. It now also
   holds the 81px branch switcher, so the scroller started at 146px, ended 81px
   past the window, and the Settings footer was unreachable. As the design has
   it: a 100vh flex column, with the scroller as the flexible child. */
body.porcelain-noir .sidebar {
    height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
body.porcelain-noir .sidebar .sidebar-scroll {
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 0;
    height: auto !important;
}
body.porcelain-noir .sidebar > :not(.sidebar-scroll) {
    flex: 0 0 auto;
}

/* ---- the footer: where the design puts Settings --------------------------- */
#sidebar-menu .pn-menu-group.is_footer {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: var(--pn-sb-bg);
    margin-top: 8px;
    padding-top: 10px;
    padding-bottom: 6px;
    border-top: 1px solid var(--pn-sb-border);
}
#sidebar-menu .pn-menu-group.is_footer > .pn-menu-group-label {
    /* The design has no header over its footer group -- Settings is the first
       control, with the Collapse button beside it. The rule is kept so that a
       header is never rendered there even if one is added by mistake. */
    display: none;
}
#sidebar-menu .pn-menu-group.is_footer .pn-menu-group-label {
    display: none;
}

/* ---- collapsed rail -------------------------------------------------------
   The design hides group labels and child lists when the sidebar is collapsed
   and leaves a 12px rule in their place, so the sections stay countable. A
   header that vanished entirely would leave the icons unlabelled and the groups
   unreadable. */
.app.collapsed #sidebar-menu .pn-menu-group-label {
    display: block !important;
    height: 12px;
    padding: 6px 0;
    font-size: 0;
}
.app.collapsed #sidebar-menu .pn-menu-group-items > li > a,
.app.collapsed #sidebar-menu .pn-accordion > ul {
    display: none;
    max-height: 0;
}

/* ===========================================================================
   THE COMMAND PALETTE

   The design's ⌘K overlay, on the app's tokens.

   The scrim is shown and hidden by the `hidden` ATTRIBUTE, not by a display
   class. That is deliberate: if this stylesheet fails to load, a class-only
   implementation leaves the overlay covering the page with no way to dismiss it,
   whereas the attribute alone keeps it out of the layout and out of the tab
   order. The `is-open` class then only has to handle the animation.

   `hidden` is removed on open and re-applied one frame after close, so the
   closing transition is allowed to start before the element leaves the layout.
   =========================================================================== */

#pn-palette[hidden] {
    display: none !important;
}

.pn-palette-scrim {
    position: fixed;
    inset: 0;
    background: rgba(8, 20, 26, .45);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    z-index: 1050; /* above Bootstrap's modal (1055 is the backdrop; 1050 sits under it) */
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 12vh 16px 16px;
    opacity: 0;
    transition: opacity var(--pn-motion);
}

.pn-palette-scrim.is-open {
    opacity: 1;
}

/* While the palette is open the page behind it must not scroll under the
   scrim, which is what a fixed overlay with no scroll lock does. */
html.pn-palette-open,
html.pn-palette-open body {
    overflow: hidden;
}

.pn-palette {
    width: 100%;
    max-width: 600px;
    background: var(--pn-surface);
    color: var(--pn-text-1);
    border: 1px solid var(--pn-border);
    border-radius: 16px;
    box-shadow: var(--pn-shadow-card);
    overflow: hidden;
    transform: scale(.97);
    transition: transform var(--pn-motion);
}

.pn-palette-scrim.is-open .pn-palette {
    transform: none;
}

/* ---- the input row ------------------------------------------------------ */
.pn-palette-input {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 16px;
    border-bottom: 1px solid var(--pn-border);
}

.pn-palette-input .icon {
    width: 18px;
    height: 18px;
    color: var(--pn-text-3);
    flex: 0 0 auto;
}

.pn-palette-input input {
    flex: 1;
    height: 56px;
    border: 0;
    outline: none;
    background: none;
    color: var(--pn-text-1);
    font: inherit;
    font-size: 16px;
    min-width: 0;
}

/* The app's reset gives inputs a border and a focus ring. Both are wrong here:
   the row already has a rule under it, and a ring inside a rounded card reads as
   a second, smaller card. The ring is moved to the card instead, below. */
.pn-palette-input input:focus {
    box-shadow: none;
    border: 0;
}

.pn-palette:focus-within {
    border-color: var(--pn-accent);
}

.pn-palette-input kbd,
.pn-search-kbd {
    font-family: var(--pn-font-mono);
    font-size: 11px;
    padding: 2px 6px;
    border-radius: 6px;
    border: 1px solid var(--pn-border);
    background: var(--pn-bg);
    color: var(--pn-text-3);
    white-space: nowrap;
}

/* ---- the result list ---------------------------------------------------- */
.pn-palette-list {
    list-style: none;
    margin: 0;
    padding: 8px;
    max-height: 360px;
    overflow: auto;
}

.pn-palette-group {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--pn-text-3);
    padding: 10px 12px 4px;
}

.pn-palette-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border: 0;
    background: none;
    border-radius: 10px;
    color: var(--pn-text-1);
    text-align: left;
    font: inherit;
    font-size: 14.5px;
    cursor: pointer;
}

.pn-palette-item .icon {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    color: var(--pn-text-3);
}

/* The selected row is marked by class AND by aria-selected, and the two are kept
   in step by the script. Styling the class alone would leave a screen reader
   announcing a static list while the highlight moves. */
.pn-palette-item.is-selected,
.pn-palette-item:hover {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}

.pn-palette-item.is-selected .icon,
.pn-palette-item:hover .icon {
    color: var(--pn-sb-on);
}

.pn-palette-item-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pn-palette-empty {
    margin: 0;
    padding: 22px 16px 26px;
    text-align: center;
    color: var(--pn-text-3);
    font-size: 13.5px;
}

/* Visually hidden, still announced. The result count is the only way a screen
   reader user learns that a search returned something, or that the list is being
   rebuilt as they type. */
.pn-palette-status {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ---- the topbar trigger -------------------------------------------------- */
.pn-search-li {
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    min-width: 0;
    margin-right: 12px;
}

.pn-search {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    max-width: 520px;
    height: 40px;
    padding: 0 12px;
    border-radius: 10px;
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    color: var(--pn-text-3);
    text-align: left;
    font: inherit;
    font-size: 14.5px;
    cursor: pointer;
    transition: border-color var(--pn-motion), box-shadow var(--pn-motion);
}

.pn-search:hover {
    border-color: var(--pn-border-strong);
    color: var(--pn-text-2);
}

.pn-search:focus-visible {
    outline: 2px solid var(--pn-accent);
    outline-offset: 2px;
}

.pn-search .icon {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
}

.pn-search-text {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Narrow viewports, and the mobile drawer the app moves this node into: the bar
   becomes an icon-only square. A full-width search field inside a 250px drawer
   is unusable and was never what the design draws there. */
@media (max-width: 900px) {
    .pn-search {
        max-width: none;
    }
    .pn-search-text,
    .pn-search-kbd {
        display: none;
    }
    .pn-search {
        width: 40px;
        flex: 0 0 40px;
        justify-content: center;
        padding: 0;
    }
}

/* ===========================================================================
   THE BRANCH SWITCHER

   The design's control, between the brand and the nav: a full-width button
   showing the current branch, with a chevron-down-up that signals there is a
   list behind it.

   The menu is hidden by the `hidden` ATTRIBUTE, not by a class -- the same
   reasoning as the palette. If this stylesheet fails to load, a class-only menu
   would sit over the sidebar with no way to dismiss it. The class only has to
   handle the animation.
   =========================================================================== */
.pn-branch {
    position: relative;
    margin: 0 0 12px;
}

.pn-branch-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 10px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: rgba(255, 255, 255, .05);
    color: var(--pn-sb-text);
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    cursor: pointer;
    transition: background var(--pn-motion), color var(--pn-motion);
}

.pn-branch-toggle:hover {
    background: rgba(255, 255, 255, .09);
    color: var(--pn-sb-text-hi);
}

.pn-branch-toggle:focus-visible {
    outline: 2px solid var(--pn-sb-on);
    outline-offset: 2px;
}

.pn-branch-toggle .icon {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    color: var(--pn-sb-muted);
}

.pn-branch-name {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The "no branch chosen" prompt, in the muted colour so it reads as a prompt
   rather than as the name of a branch. */
.pn-branch-unset {
    color: var(--pn-sb-muted);
    font-style: italic;
}

.pn-branch-chevron {
    color: var(--pn-sb-muted) !important;
}

/* ---- the menu ----------------------------------------------------------- */
.pn-branch-menu {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + 6px);
    z-index: 40;
    margin: 0;
    padding: 6px;
    list-style: none;
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    border-radius: 12px;
    box-shadow: var(--pn-shadow-card);
}

.pn-branch-menu[hidden] {
    display: none !important;
}

.pn-branch-option {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 9px;
    border: 0;
    background: none;
    border-radius: 8px;
    color: var(--pn-text-1);
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    cursor: pointer;
}

.pn-branch-option:hover,
.pn-branch-option:focus-visible {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}

.pn-branch-option small {
    margin-left: auto;
    color: var(--pn-text-3);
    font-size: 11.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 45%;
}

.pn-branch-option:hover small,
.pn-branch-option:focus-visible small {
    color: var(--pn-sb-text);
}

/* The tick marks the current branch. Hidden rather than merely transparent on
   the others, so it is not a ghost glyph sitting in every row. */
.pn-branch-tick {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    visibility: hidden;
    color: var(--pn-accent);
}

.pn-branch-option.is-active {
    font-weight: 600;
}

.pn-branch-option.is-active .pn-branch-tick {
    visibility: visible;
}

/* Collapsed rail: the control becomes an icon, the way every other sidebar row
   does, and the menu still opens. */
.app.collapsed .pn-branch-toggle {
    justify-content: center;
    padding: 9px 0;
}

.app.collapsed .pn-branch-name {
    display: none;
}

.app.collapsed .pn-branch-menu {
    left: 0;
    right: auto;
    min-width: 220px;
}

/* ===========================================================================
   THE KPI STRIP

   The design's shape: a label and a tinted icon chip on one line, the figure
   under it, and a small line under that.

   ONE CLASS, FIVE CARDS
   ---------------------
   All five cards are one partial (app/Views/dashboards/partials/kpi_card.php)
   emitting `.pn-kpi`, so the strip cannot drift out of alignment -- the previous
   markup was four separate copies of Bootstrap's icon-widget, which is how a strip
   ends up with one card a pixel taller than its neighbours.

   THE STRIP IS A CSS GRID, NOT A BOOTSTRAP ROW
   ---------------------------------------------
   The design wants five equal cards. There is no legal Bootstrap class for a fifth
   of twelve, and the app's row builder hands out col-md-3 per column, so five of
   those wrap. Rather than fight the row, the cards themselves lay out as a grid
   that spans whatever width their Bootstrap column gives them -- so the strip is
   correct at any column count, and the app's own column arithmetic is left alone.

   Scoped to `.dashboard-view` so no other screen grows a grid it did not ask for.
   =========================================================================== */
/* A ONE-column grid, three rows: label+chip, figure, foot.
 *
 * The first version put `repeat(auto-fit, minmax(0, 1fr))` here, which is the
 * correct declaration for a ROW of cards applied to a single card. Each card
 * therefore laid its own three children out across however many columns fitted,
 * and the strip came out as cards of 107px and 364px with heights of 272 and 69 --
 * a grid of cards that is actually a grid inside each card. */
.pn-kpi {
    display: grid;
    grid-template-rows: auto auto 1fr;
    align-content: start;
    gap: 0;
    margin: 0;
    height: 100%;
    padding: 16px;
    border: 1px solid var(--pn-border);
    border-radius: var(--pn-radius-card);
    background: var(--pn-surface);
    color: var(--pn-text-1);
    text-decoration: none;
    transition: border-color var(--pn-motion), box-shadow var(--pn-motion);
}

/* ---- the ROW of cards --------------------------------------------------- */

/* The app's dashboard lays every row out as Bootstrap columns inside a `.row`, and
   a KPI column is col-md-3 -- a quarter of the width. Five of those wrap onto a
   second line, which is a strip that wraps rather than the design's five across.
 *
 * So the KPI row is marked by the controller (Dashboard::_get_first_row... sets
   is_kpi_row, and make_dashboard() emits it as a class) and laid out here as a
 * grid, which needs no per-column width arithmetic at all: the cards are the grid
 * items, so they are equal by construction and the col-md-3 on each column is
   simply not used for this row.
 *
 * `.pn-kpi-row > .widget-container` keeps `min-width: 0` because a grid item's
 * default `min-width: auto` refuses to shrink below its content, and a currency
 * figure is exactly the content that would force a column wider than its share. */
.dashboard-view .dashboards-row.pn-kpi-row {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 15px;
    margin: 0;
}

.dashboard-view .dashboards-row.pn-kpi-row > .widget-container {
    min-width: 0;
    width: auto;
    padding: 0;
    float: none;
}

/* The row also carries Bootstrap's .clearfix, whose ::before/::after are
   `display: table` boxes -- and in a grid a pseudo-element with a box is a grid
   ITEM. ::before took the first cell, so every card sat one slot to the right and
   the strip opened with an empty square. */
.dashboard-view .dashboards-row.pn-kpi-row::before,
.dashboard-view .dashboards-row.pn-kpi-row::after {
    display: none;
}

/* The design steps down to three, then two, then one. The first step was at a
   1280px WINDOW, but the sidebar takes 250px of that, and five cards fit the
   ~1030px that is left -- so a normal laptop saw two rows instead of the design's
   five across. */
@media (max-width: 1100px) {
    .dashboard-view .dashboards-row.pn-kpi-row {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 900px) {
    .dashboard-view .dashboards-row.pn-kpi-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 520px) {
    .dashboard-view .dashboards-row.pn-kpi-row {
        grid-template-columns: minmax(0, 1fr);
    }
}

.pn-kpi-link:hover {
    border-color: var(--pn-border-strong);
    box-shadow: var(--pn-shadow-card);
    color: var(--pn-text-1);
}

/* The whole card is the target, not just the figure. */
/* The card's own link treatment, which follows the CARD and not the screen.
 *
 * It was scoped to the dashboard with the rest, so on the patients page a KPI card
 * that is a link looked clickable to nobody: no hover, no affordance, and a
 * `cursor: pointer` that came from the browser's own `a` default rather than from
 * the design. The two `.dashboards-row` rules that remain scoped are the dashboard's
 * ROW LAYOUT -- how many cards sit side by side in a dashboard row -- which is
 * genuinely a property of that screen and not of the card. */
a.pn-kpi-link {
    display: grid;
}

.pn-kpi-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    color: var(--pn-text-2);
    font-size: 13.5px;
    font-weight: 700;
    min-width: 0;
}

.pn-kpi-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The tinted chip. `currentColor` is deliberately not used for the background: the
   tone classes set a background AND a foreground, and inheriting the text colour
   for one of them would put a card's own text inside its own icon chip. */
.pn-kpi-ico {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
}

.pn-kpi-ico .icon {
    width: 18px;
    height: 18px;
}

.pn-kpi-value {
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -.02em;
    line-height: 1.1;
    margin-top: 10px;
    /* A long currency figure must not push the card's label out of line with its
       neighbours, so the figure is allowed to shrink rather than overflow. */
    overflow-wrap: anywhere;
}

.pn-kpi-foot {
    font-size: 13px;
    color: var(--pn-text-3);
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
    min-height: 1.2em;
}

/* ---- the tone chips ------------------------------------------------------ */
.pn-kpi.tone-cyan    .pn-kpi-ico { background: var(--pn-accent-bg);  color: var(--pn-accent-ink); }
.pn-kpi.tone-green   .pn-kpi-ico { background: var(--pn-success-bg); color: var(--pn-success); }
.pn-kpi.tone-violet  .pn-kpi-ico { background: var(--pn-violet-bg);  color: var(--pn-violet); }
.pn-kpi.tone-amber   .pn-kpi-ico { background: var(--pn-warning-bg); color: var(--pn-warning); }
.pn-kpi.tone-blue    .pn-kpi-ico { background: var(--pn-info-bg);    color: var(--pn-info); }
.pn-kpi.tone-red     .pn-kpi-ico { background: var(--pn-danger-bg);  color: var(--pn-danger); }
.pn-kpi.tone-neutral .pn-kpi-ico { background: var(--pn-neutral-bg); color: var(--pn-neutral); }

/* Cyan and blue ARE the design's two blue-family tones, and they are NOT the same
   colour -- which is the correction.
 *
 * The first version pointed BOTH at `--pn-info`, so a strip of four cards had two
 * identical badges: on the patients Overview tab, "Total patients" and "Partially
 * paid" measured rgb(226,234,253) on rgb(29,78,216), the same pair twice. The
 * comment that stood here said the collision was "faithful to the design's own
 * palette" -- having compared the two tone NAMES and not the two RULES.
 *
 * The design's rules do not collide. It gives tone-blue the INDIGO pair
 * (`--s-info-bg` #E2EAFD / `--s-info` #1D4ED8) and tone-cyan the BRAND pair
 * (`--primary-soft` #E0F4F8 / `--primary-ink` #0B5468) -- in this layer
 * `--pn-accent-bg` and `--pn-accent-ink`. Which is also the honest reason a
 * "cyan" tone is a teal here: it is the design's brand family, and the design's
 * cyan is teal too.
 *
 * So blue stays indigo, cyan is the brand teal, and the two can be told apart at
 * the size of a 32px badge. */

/* ---- the comparison line ------------------------------------------------- */
.pn-trend-up {
    color: var(--pn-success);
    font-weight: 700;
}

.pn-trend-down {
    color: var(--pn-danger);
    font-weight: 700;
}

/* ---- narrow viewports ---------------------------------------------------- */
@media (max-width: 900px) {
    .dashboard-view .pn-kpi-value {
        font-size: 23px;
    }
}

@media (max-width: 520px) {
    .dashboard-view .pn-kpi {
        padding: 12px;
    }
    .dashboard-view .pn-kpi-top {
        font-size: 12.5px;
    }
    .dashboard-view .pn-kpi-ico {
        width: 26px;
        height: 26px;
    }
    .dashboard-view .pn-kpi-ico .icon {
        width: 14px;
        height: 14px;
    }
}

/* ===========================================================================
   DASHBOARD CARDS: the shared head/body, the queue, and the segmented control

   THE CARD
   -------
   The design's card is a header row (an h2, a sub-line, and optionally a control)
   over a body. The app's cards are Bootstrap `.card` with `.card-header` and
   `.card-body`, and the design's header is not a Bootstrap card-header -- it is a
   flex row with a heading and a control sharing it.

   So the arrangement is expressed here on the design's own class names, and
   `.pn-card` is what a view opts into. A card that does not opt in is untouched.

   THE QUEUE
   --------
   A grid row of time / who / status / open, exactly as the design draws it. The
   first row carries no top border, because a card that already has a header does
   not also need a rule under it -- the header is the rule.

   Grid columns, not widths: `64px 1fr auto auto` means the time column is exactly
   as wide as a time needs, the name takes what is left, and the chip and the
   chevron take only what they need. A row with a very long patient name therefore
   truncates the name rather than pushing the chip off the row.

   THE SEGMENTED CONTROL
   --------------------
   The design's is a set of <button>s with aria-pressed. This one's are LINKS,
   because a queue filter is server-side state on a URL: reloadable, shareable, and
   the back button works. So the styling is shared with the design's but the
   selected state is aria-current, and the selector keys on `.is-on` rather than
   `[aria-pressed=true]`.
   =========================================================================== */

/* ---- the card ----------------------------------------------------------- */
.pn-card {
    display: flex;
    flex-direction: column;
    background: var(--pn-surface);
    border: 1px solid var(--pn-border);
    border-radius: var(--pn-radius-card);
    overflow: hidden;
}

/* A SIZE CONTAINER, so the queue can respond to how wide IT is.
 *
 * The design steps the queue's columns down at a viewport breakpoint, and that is
 * the wrong axis here: this card sat in a col-md-4 at 219px on a 1000px-wide
 * viewport, where every viewport rule said "plenty of room" and the four-column
 * row overflowed its card. The name column resolved to 0px and the queue was
 * unusable while the CSS believed it had space.
 *
 * A card's width is set by which column it lands in, not by the window, so the
 * query has to be on the card. The viewport media query further down is kept as a
 * fallback for a browser without container-query support. */
.pn-queue {
    container-type: inline-size;
    container-name: pnqueue;
}

.pn-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 16px 20px 0;
}

.pn-card-head h2 {
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--pn-text-1);
}

.pn-card-sub {
    color: var(--pn-text-3);
    font-size: 13px;
    margin-top: 2px;
}

.pn-card-sub strong {
    color: var(--pn-text-2);
    font-weight: 600;
}

.pn-card-body {
    padding: 16px 20px 20px;
    flex: 1;
    min-width: 0;
}

/* ---- the segmented control ---------------------------------------------- */
.pn-segmented {
    display: inline-flex;
    padding: 3px;
    border-radius: 10px;
    background: var(--pn-well);
    border: 1px solid var(--pn-border);
    gap: 2px;
}

.pn-seg {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 0;
    background: none;
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 13.5px;
    font-weight: 700;
    color: var(--pn-text-2);
    min-height: 30px;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--pn-motion), color var(--pn-motion);
}

.pn-seg:hover {
    color: var(--pn-text-1);
}

.pn-seg.is-on {
    background: var(--pn-surface);
    color: var(--pn-text-1);
    box-shadow: var(--pn-shadow-card);
}

.pn-seg:focus-visible {
    outline: 2px solid var(--pn-accent);
    outline-offset: 2px;
}

/* The count beside each option, so a filter says what is behind it. A filter
   whose options are all "0" is a filter nobody uses, and one that silently narrows
   to nothing is worse. */
.pn-seg-count {
    font-size: 11.5px;
    font-weight: 700;
    padding: 1px 6px;
    border-radius: var(--pn-radius-pill);
    background: var(--pn-neutral-bg);
    color: var(--pn-text-3);
}

.pn-seg.is-on .pn-seg-count {
    background: var(--pn-info-bg);
    color: var(--pn-info);
}

/* ---- the queue ---------------------------------------------------------- */
.pn-queue-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* The name track has a FLOOR, not just a ceiling.
 *
 * `minmax(0, 1fr)` is the usual advice for a truncating grid track, and it is
 * wrong when the grid can end up narrower than its fixed tracks: the fixed columns
 * and the gaps take everything, the 1fr track is left with nothing, and the patient
 * name -- the one column that has to be readable -- disappears while the row
 * overflows the card.
 *
 * With a floor, an over-narrow card overflows visibly instead of silently losing
 * the name, and the container queries below remove the columns that do not fit so
 * the floor is never actually reached in practice. */
.pn-queue-row {
    display: grid;
    grid-template-columns: 64px minmax(7rem, 1fr) auto auto;
    align-items: center;
    gap: 14px;
    padding: 12px 4px;
    border-top: 1px solid var(--pn-border);
}

/* ---- stepped down on the CARD's width, not the window's ----------------- */

/* Under 480px the status chip is the first thing to go: the time and the name are
   what a receptionist scans for, and the chip repeats the row's data-status. */
@container pnqueue (max-width: 480px) {
    .pn-queue-row {
        grid-template-columns: 56px minmax(5rem, 1fr) auto;
        gap: 10px;
    }
    .pn-queue-status {
        display: none;
    }
}

/* Under 360px only the name and the chevron survive, and the time moves under the
   name rather than going -- an appointment with no time on it is not a row. */
@container pnqueue (max-width: 360px) {
    .pn-queue-row {
        grid-template-columns: minmax(5rem, 1fr) auto;
    }
    .pn-queue-time {
        grid-column: 1 / -1;
        margin-top: -4px;
    }
}

/* The first row carries no rule: the card header is already the line above it,
   and a second one puts two rules a few pixels apart. */
.pn-queue-row:first-child {
    border-top: 0;
}

.pn-queue-time {
    font-weight: 700;
    font-size: 14.5px;
    color: var(--pn-text-1);
}

.pn-queue-time small {
    display: block;
    color: var(--pn-text-3);
    font-weight: 400;
    font-size: 12px;
}

.pn-queue-who {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.pn-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 13px;
    flex: 0 0 auto;
    background: var(--pn-info-bg);
    color: var(--pn-info);
}

.pn-queue-id {
    display: block;
    min-width: 0;
}

.pn-queue-id b {
    display: block;
    font-size: 14.5px;
    font-weight: 600;
    color: var(--pn-text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pn-queue-id small {
    display: block;
    color: var(--pn-text-3);
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pn-queue-open {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    color: var(--pn-text-3);
    text-decoration: none;
    transition: background var(--pn-motion), color var(--pn-motion);
}

.pn-queue-open:hover {
    background: var(--pn-sb-active);
    color: var(--pn-sb-text-hi);
}

.pn-queue-open .icon {
    width: 16px;
    height: 16px;
}

/* ---- status chips --------------------------------------------------------
   The six statuses this app records, in the design's own tones: the two that
   need attention (waiting, no-show) are the warnings, the finished end of the
   list is good, and the two that are simply over are muted.

   `arrived` is the design's warning tone and `booked` its info tone, which is the
   order the design states them in and reads correctly: booked is expected, arrived
   means somebody is here now. */
.pn-status {
    display: inline-flex;
    align-items: center;
    height: 26px;
    padding: 0 10px;
    border-radius: var(--pn-radius-pill);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
}

.pn-status-info  { background: var(--pn-info-bg);    color: var(--pn-info); }
/* NO `cyan` TONE, and the reason is worth keeping.
 *
 * There was one, for `arrived`, and it needed a token -- so `--pn-cyan` was created
 * and set to the same value as the existing `--pn-accent`, which made `arrived` and
 * `invoiced` render identically. An invented token whose value duplicates an
 * existing one is a distinction that does not exist.
 *
 * The design already pairs `arrived` with the warning tone, which is the better
 * answer anyway: a person is here and waiting is the state that wants attention, and
 * amber is what attention looks like. So `arrived` took the warning tone and no token
 * was invented at all.
 *
 * `accent` and `danger` both exist because `invoiced` is a different kind of
 * finished from `done`, and a missed appointment is a different fact from a called-off
 * one. Six states, six colours, none of them new. */
.pn-status-accent { background: var(--pn-accent-bg);  color: var(--pn-accent); }
.pn-status-danger { background: var(--pn-danger-bg);  color: var(--pn-danger); }
.pn-status-good  { background: var(--pn-success-bg); color: var(--pn-success); }
.pn-status-warn  { background: var(--pn-warning-bg); color: var(--pn-warning); }
.pn-status-muted { background: var(--pn-neutral-bg); color: var(--pn-neutral); }

/* ---- empty state --------------------------------------------------------- */
.pn-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    padding: 28px 16px;
    color: var(--pn-text-3);
}

.pn-empty-ico {
    width: 48px;
    height: 48px;
    border-radius: 14px;
    display: grid;
    place-items: center;
    background: var(--pn-well);
    color: var(--pn-text-3);
}

.pn-empty-ico .icon {
    width: 22px;
    height: 22px;
}

.pn-empty b {
    color: var(--pn-text-1);
    font-size: 15px;
    font-weight: 600;
}

.pn-empty p {
    margin: 0;
    font-size: 13.5px;
    max-width: 280px;
}

/* ---- narrow viewports ---------------------------------------------------- */
@media (max-width: 700px) {
    .pn-queue-row {
        grid-template-columns: 52px minmax(0, 1fr) auto;
        gap: 10px;
    }
    /* The status chip is the first thing to go: the time and the name are what a
       receptionist is scanning for, and the chip is the same information as the
       row's data-status attribute. */
    .pn-queue-status {
        display: none;
    }
    .pn-card-head,
    .pn-card-body {
        padding-left: 14px;
        padding-right: 14px;
    }
}

/* ===========================================================================
   THE DASHBOARD'S TWO-COLUMN BODY: dash-grid + stacks

   The design's body is a two-column grid, 1.55fr and 1fr, each column a vertical
   flex stack with 16px between the cards.

   WHY THIS OVERRIDES BOOTSTRAP'S COLUMNS
   ---------------------------------------
   The row is emitted by the app's own row builder, which wraps every card in a
   `widget-container col-md-*` and computes that width from a ratio string. Inside
   this row those Bootstrap floats must not apply: `float: left` on a grid item
   takes it out of the grid entirely, so the two stacks would not sit side by side
   at all -- they would stack, silently, and the row would look almost right.

   So the children are floated out and the grid's own columns take over. `width:
   auto` and `float: none` are the minimum needed to hand each stack back to the
   grid; the padding gutter is removed so the cards align with the rest of the
   page rather than being inset inside their own column.

   1.55fr / 1fr IS THE DESIGN'S PROPORTION, and it is not arbitrary: the left
   stack holds the day's queue, whose rows are a time, a person, a status and a
   link, and an equal split leaves that row truncated on half the widths a
   receptionist uses.
   =========================================================================== */

/* Same trap as the KPI strip: this row is also a Bootstrap .row.clearfix, and its
   ::before became the first grid item -- taking the WIDE column, pushing Today's
   queue into the narrow one and the invoice card onto a row of its own. */
.pn-dash-grid::before,
.pn-dash-grid::after {
    display: none;
}

.pn-dash-grid {
    display: grid;
    gap: 16px;
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    margin-top: 16px;
    align-items: start;
}

/* The Bootstrap float has to go before the grid can own the item. */
.pn-dash-grid > .widget-container {
    float: none;
    width: auto;
    margin: 0;
    padding-left: 0;
    padding-right: 0;
}

/* Each column: a vertical stack. The widgets in a column are consecutive
   `widget-container` divs, so the column is not a separate element -- the
   containers themselves become the stack's items. */
.pn-dash-grid > .widget-container {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

.pn-dash-grid > .widget-container > * {
    min-width: 0;
}

/* THE MARGIN AND THE GAP WERE ADDING UP, AND BOTH SPACED THE CARDS.
 *
 * Measured: a stack's own `gap: 16px` plus the app's `.card { margin-bottom: 20px }`
 * -- 36px between two cards where the design has 16. The card's bottom margin is
 * the page's existing vertical rhythm between rows, and it is correct there; inside
 * a flex stack it is the SECOND mechanism spacing the same gap, so the two add.
 *
 * So the margin comes off every child and its 20px moves to the stack's foot as
 * padding. Putting it back on the LAST CARD was the first attempt and it is wrong
 * for a reason this page makes concrete: the stacks do not end with a card, they
 * end with the `<script>` tag the last widget emitted, so `:last-child` selects
 * the script and the last real card loses its margin and sits flush against
 * whatever is below the row. Sizing the space on the stack cannot misfire that way.
 *
 * A `display: none` element is not a flex item and takes no gap, so the `<script>`
 * and `<style>` tags the widgets emit between their cards cost nothing. Measured
 * to confirm that rather than assumed: with them left in, two cards still sit
 * exactly one gap apart. */
.pn-dash-grid > .widget-container > * {
    margin-bottom: 0;
}

.pn-dash-grid > .widget-container {
    padding-bottom: 20px;
}

/* A widget that renders to nothing must not leave a gap behind it. An empty flex
   item is still an item, so it still takes the gap. */
.pn-dash-grid > .widget-container:empty {
    display: none;
}

/* Below 900px the two stacks become one. Not a viewport convenience: at that width
   a 1.55fr/1fr split gives the right stack about 300px, which is the width that
   made the queue's name column collapse to zero. The queue's own `@container` rules
   then key off the narrowed column, which is the whole reason it is a size
   container rather than a viewport one. */
@media (max-width: 900px) {
    .pn-dash-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ===========================================================================
   THE REVENUE CARD: invoices vs. payments

   WHAT THIS CARD IS FOR
   --------------------
   It plots two series that are never equal -- what was invoiced against what was
   paid -- and the gap between them is the arrears. Everything here serves that:
   the axis is scaled over BOTH series so the payments bars are not squeezed into
   the bottom third, the outstanding figure and the collection rate are stated as
   text above the chart, and the table view is rendered in the HTML rather than
   built by script, so the numbers are readable without JavaScript and by a screen
   reader.
   =========================================================================== */

.pn-revenue {
    /* The card is the one place on the dashboard whose height must not be driven
       by its chart, because the table view below is shorter and swapping between
       them would otherwise make the whole left stack jump. */
    min-height: 0;
}

.pn-rev-controls {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* The legend. TEXT, not colour alone: two swatches with no labels is a chart
   nobody can read without the ability to tell the series apart, which is exactly
   the ability a colour legend exists to serve. */
.pn-legend {
    display: flex;
    gap: 16px;
    font-size: 13px;
    color: var(--pn-text-2);
}

.pn-legend span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.pn-legend i {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    display: inline-block;
    flex: 0 0 auto;
}

/* ---- the three figures above the chart --------------------------------- */
.pn-rev-totals {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}

.pn-rev-totals > div {
    min-width: 0;
}

.pn-rev-totals b {
    display: block;
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--pn-text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pn-rev-totals small {
    display: block;
    color: var(--pn-text-3);
    font-size: 12.5px;
    margin-top: 1px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- the note about what the branch filter excluded --------------------- */
.pn-rev-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0 0 14px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--pn-warning-bg);
    color: var(--pn-warning);
    font-size: 12.5px;
    line-height: 1.45;
}

.pn-rev-note .icon {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    margin-top: 1px;
}

/* ---- the chart --------------------------------------------------------- */
.pn-chart-wrap {
    position: relative;
    display: block;
    width: 100%;
    /* A definite height, because a canvas with no height collapses to 150px of
       squashed chart and Chart.js will not correct it afterwards. */
    height: 220px;
}

.pn-chart-wrap canvas {
    display: block;
    width: 100% !important;
    height: 100% !important;
}

/* ---- the table view ----------------------------------------------------- */
.pn-rev-table table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
}

.pn-rev-table th,
.pn-rev-table td {
    padding: 8px;
    border-bottom: 1px solid var(--pn-border);
    text-align: left;
}

.pn-rev-table .right {
    text-align: right;
}

.pn-rev-table thead th {
    color: var(--pn-text-3);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
}

.pn-rev-table tbody th {
    font-weight: 600;
    color: var(--pn-text-1);
    white-space: nowrap;
}

.pn-rev-table tfoot th,
.pn-rev-table tfoot td {
    border-bottom: 0;
    border-top: 1px solid var(--pn-border);
    font-weight: 700;
    color: var(--pn-text-1);
}

/* Money still owed, in the only column where it is meaningful. The tint is the
   design's warning tone at its soft value, not a full-strength red: this column
   is normally non-zero on most rows, and a strong colour on the default state of
   the card would make the card look like an error. */
.pn-rev-table .is-overdue {
    color: var(--pn-warning);
}

/* Narrow: the three figures stack, rather than each becoming an ellipsis. */
@media (max-width: 520px) {
    .pn-rev-totals {
        grid-template-columns: minmax(0, 1fr);
        gap: 8px;
    }
}

/* ===========================================================================
   VISUALLY HIDDEN, BUT READ OUT
   =========================================================================== */

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

/* ===========================================================================
   STATUS FILTER CHIPS

   The design's chip row: one chip per state, each carrying how many it would show.

   WHY THE COUNT IS THE POINT
   -------------------------
   A row of named filters tells you which states exist. A row of COUNTED ones tells
   you which state needs attention, which is the question somebody opens an
   appointments list to answer. The design asks for the count for that reason and
   the chips keep it.

   THE SELECTED CHIP IS A LINK, NOT A BUTTON
   -----------------------------------------
   These navigate -- they are real URLs, carrying every other filter -- so the
   selected state is `aria-current`, not `aria-pressed`. A pressed state on a link
   tells a screen reader the control toggles something in place, and this one does
   not: it goes somewhere, and the somewhere can be reloaded, bookmarked and
   reached with the back button.

   A CHIP WITH A ZERO IS STILL SHOWN. A row that grows and shrinks with the data is
   a filter nobody can learn: yesterday "No-show 2" was there and today it is not,
   and a user concludes the state was removed rather than that nobody missed an
   appointment. The vocabulary is fixed; only the numbers move.
   =========================================================================== */

.pn-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.pn-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 10px;
    border: 1px solid var(--pn-border);
    border-radius: var(--pn-radius-pill);
    background: var(--pn-surface);
    color: var(--pn-text-2);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    transition: background var(--pn-motion), color var(--pn-motion), border-color var(--pn-motion);
}

.pn-chip:hover {
    background: var(--pn-well);
    color: var(--pn-text-1);
}

.pn-chip.is-on {
    background: var(--pn-info-bg);
    border-color: var(--pn-info);
    color: var(--pn-info);
}

.pn-chip:focus-visible {
    outline: 2px solid var(--pn-accent);
    outline-offset: 2px;
}

/* The count. Muted when the chip is not selected and the chip's own tone when it
   is, so the number you are looking at is the one that is lit up. */
.pn-chip-n {
    font-size: 11.5px;
    font-weight: 700;
    padding: 1px 6px;
    border-radius: var(--pn-radius-pill);
    background: var(--pn-neutral-bg);
    color: var(--pn-text-3);
    min-width: 20px;
    text-align: center;
}

.pn-chip.is-on .pn-chip-n {
    background: var(--pn-surface);
    color: var(--pn-info);
}

/* A chip with nothing behind it is dimmed rather than hidden -- the state exists,
   there is simply none of it. Hidden would change the row's shape; dimmed keeps
   the vocabulary in place and says "none", which is the useful reading. */
.pn-chip[data-empty="1"] {
    opacity: .55;
}

/* ===========================================================================
   THE SCHEDULE: a day view with one lane per doctor (or per room)

   WHAT THE DESIGN IS
   ------------------
   A lane grid: a time gutter, then one column per clinician, each headed by an
   avatar, a name and a specialty, with the day's appointments as blocks in the
   lanes and a status-coloured border on each.

   WHY THE BORDER CARRIES THE STATUS
   ---------------------------------
   A lane is a COLUMN of a grid, and a receptionist finds who is waiting by
   glancing DOWN a column. That only works if a status is distinguishable at the
   size of a block -- which means the border, not the text, and not the fill: a
   filled block per status turns the lane into a stack of competing colours and
   the patient's name stops being the thing you read first.

   So the event body is one quiet surface and a 4px left border carries the state.
   That is the design's own arrangement, and it is the right one.
   =========================================================================== */

.pn-sched-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* ---- the lane header ---------------------------------------------------- */
.pn-lane {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    padding: 2px 0;
}

.pn-lane-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .01em;
    background: var(--pn-neutral-bg);
    color: var(--pn-text-2);
}

.pn-lane-text {
    min-width: 0;
}

/* The name and the specialty are SEPARATE LINES, not one joined string.
   A lane header is a column heading, and column headings are scanned rather than
   read -- the name on its own line is findable, and the specialty under it is
   something you look up once you have found the person. */
.pn-lane-text b {
    display: block;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--pn-text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.25;
}

.pn-lane-text small {
    display: block;
    font-size: 11.5px;
    color: var(--pn-text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.3;
}

/* ---- the appointment block ---------------------------------------------- */
.pn-evt {
    border: 0;
    border-left: 4px solid var(--pn-border);
    border-radius: 6px;
    /* Quiet by default. The lane's own colour tints the background through
       FullCalendar's eventColor; the STATUS is the left border and nothing else,
       so a block never competes with the patient's name for attention. */
    box-shadow: none;
    transition: box-shadow var(--pn-motion), transform var(--pn-motion);
}

.pn-evt:hover {
    box-shadow: var(--pn-shadow-card);
    transform: translateY(-1px);
    z-index: 3;
}

.pn-evt .fc-event-main {
    /* The name is what the reader came for, so it gets the weight. */
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
}

/* ---- the six statuses, in the design's tones ---------------------------- */
/* ONE rule per status, and the statuses come from pn_status_vocabulary(), so this
   list and the chips cannot disagree about which states exist. A status with no
   rule here is a status with no border, which is the honest rendering of a state
   this build has not been taught -- better than wearing a neighbour's colour. */
/* !important, AND THAT IS THE POINT RATHER THAN A SHORTCUT.
 *
 * FullCalendar writes the resource's eventColor onto the event as an INLINE style,
 * and an inline style beats any class rule in this file. Without the override the
 * measured border colour on every block was FullCalendar's default blue, whatever
 * the status -- which looked fine and said nothing, because the legend underneath
 * was keying colours no block used.
 *
 * The alternative -- dropping the event's colour so the class can win -- is worse:
 * the lane's own tint is genuinely useful, and removing it to get a border is giving
 * up something to get something else.
 *
 * So the override is scoped to the ONE property and to the class that already says
 * which status this is. It cannot leak onto a lane, a time cell or anything else. */
/* KEYED ON THE TONE, not on the status.
 *
 * Six rules on six statuses is a second place where "which colour is `arrived`" is
 * answered, and the first version of this file had exactly that -- and got three of
 * the six wrong against the chips beside them. The tone is the one answer: the chip
 * renders from it and these rules read it, so the legend under the grid is a real
 * key rather than a decoration that happens to be next to coloured blocks.
 *
 * One rule per TONE. A status with no tone class -- an enum value this build has not
 * been taught -- gets no border, which is the honest rendering. */
.pn-evt.tone-info    { border-left-color: var(--pn-info) !important; }
.pn-evt.tone-warn    { border-left-color: var(--pn-warning) !important; }
.pn-evt.tone-good    { border-left-color: var(--pn-success) !important; }
.pn-evt.tone-accent  { border-left-color: var(--pn-accent) !important; }
.pn-evt.tone-danger  { border-left-color: var(--pn-danger) !important; }
.pn-evt.tone-muted   { border-left-color: var(--pn-neutral) !important; }

/* The lanes' own tint, kept on the background where it belongs. */
.pn-evt {
    border-left-width: 4px !important;
}

/* A cancelled or missed appointment is dimmed as well as coloured, so the state
   is legible without relying on hue -- which is the same reason the status chips
   carry an icon as well as a colour. */
/* Called off and missed are the two states that need a second signal as well as a
 * colour: a hue alone is not legible to a colour-blind reader, and these are the
 * two states where being wrong matters most.
 *
 * Keyed on the TONE, and on `danger` rather than `warn` -- the first version dimmed
 * `warn`, which was `arrived` at the time, so the state that most needs attention
 * (somebody is here and waiting) was the one rendered faintest. It is the opposite
 * of what the dimming is for.
 *
 * The dimming reads `muted` and `danger`, which is "called off" and "missed", and
 * that pair comes from the vocabulary rather than from a list of names restated
 * here. */
.pn-evt.tone-muted,
.pn-evt.tone-danger {
    opacity: .62;
}

.pn-evt.tone-muted .fc-event-main {
    text-decoration: line-through;
}

/* ---- the status legend -------------------------------------------------- */
/* The design puts a chip row under the grid. It is the key to the border colours,
   and without it a coloured edge is decoration rather than information. */
.pn-sched-legend {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: center;
    padding: 12px 0 0;
    margin-top: 14px;
    border-top: 1px solid var(--pn-border);
}

.pn-sched-legend-label {
    font-size: 12.5px;
    color: var(--pn-text-3);
    margin-right: 2px;
}

/* ---- the time gutter and the now line ---------------------------------- *
 * FullCalendar draws the now-indicator itself and it is red, which is the design's
 * own choice and the one colour on this page that is not a status. It is left
 * alone rather than restyled: overriding a library's now-indicator means fighting
   its z-index and its positioning in every view, and "where am I now" is not a
   thing the design's palette has an opinion about beyond being visible. */

/* ===========================================================================
   THE PATIENTS LIST: the two columns the design adds

   MRN is an IDENTIFIER and a policy number is an IDENTIFIER, so both take the
   design's mono treatment (`.mono` in the design: the mono face at .86em with a
   hair of negative tracking). Proportional digits have no rhythm, and both of
   these are read by comparing them character against a card in someone's hand --
   a hyphen that looks like a hyphen and a 1 that looks like a 1.
   =========================================================================== */

/* The design's own values, on the layer's own tokens: --mono resolves to
   --pn-font-mono and --text-3 to --pn-text-3, and both were already the design's
   values, so this is the design's rule rather than an approximation of it. */
.pn-mono {
    font-family: var(--pn-font-mono);
    font-size: .86em;
    letter-spacing: -.01em;
    white-space: nowrap;
}

/* The policy number under the provider name.
 *
 * Muted to the design's third text tone, because it is the second thing read and
 * not the first: the reader has already found the provider, and the number is
 * there to be copied. At full strength the two lines compete, and the provider --
 * the answer to "who insures this patient" -- stops being findable. */
.pn-policy {
    color: var(--pn-text-3);
    font-size: .86em;
    line-height: 1.35;
    margin-top: 1px;
    white-space: nowrap;
}


/* ---- color pickers --------------------------------------------------------
   app.all.css sizes every input[type=color] at 200 x 22px. Inside a form grid
   column (Rooms, Doctors) that overflowed the column and sat on top of the
   Active checkbox beside it. As a .form-control it takes the column's width and
   the design's control height instead. */
input[type=color].form-control {
    width: 100%;
    min-width: 44px;
    height: 38px;
    padding: 4px;
    border: 1px solid var(--pn-border);
    border-radius: var(--pn-radius-sm, 6px);
    background: var(--pn-surface, #fff);
    cursor: pointer;
}


/* ---- top bar: the design's "New" button and the theme toggle ----------------
   "New" is the one primary action in the bar, so it is the one filled control;
   everything else stays an icon button. */
#default-navbar .pn-new-li .pn-new-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 36px;
    margin: 0 6px;
    padding: 0 14px !important;
    border-radius: var(--pn-radius-sm, 8px);
    background: var(--pn-accent);
    color: var(--pn-on-accent, #fff) !important;
    font-weight: 600;
    font-size: 13.5px;
    line-height: 1;
}
#default-navbar .pn-new-li .pn-new-btn:hover,
#default-navbar .pn-new-li .pn-new-btn:focus-visible {
    filter: brightness(1.08);
}
#default-navbar .pn-new-li .pn-new-btn::after { display: none; } /* no caret */
#default-navbar .pn-new-li .pn-new-btn svg { stroke: currentColor; }
.pn-new-menu { min-width: 220px; }
.pn-new-menu .dropdown-item { display: flex; align-items: center; padding: 8px 14px; }

#default-navbar .pn-theme-btn {
    background: none;
    border: 0;
    display: inline-flex;
    align-items: center;
    cursor: pointer;
}
.pn-theme-btn .pn-theme-light { display: none; }
body.porcelain-noir[data-theme="dark"] .pn-theme-btn .pn-theme-light { display: inline-block; }
body.porcelain-noir[data-theme="dark"] .pn-theme-btn .pn-theme-dark { display: none; }
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-theme-btn .pn-theme-light { display: inline-block; }
    body.porcelain-noir:not([data-theme="light"]) .pn-theme-btn .pn-theme-dark { display: none; }
}
@media (max-width: 575px) {
    #default-navbar .pn-new-li .pn-new-label { display: none; }
    #default-navbar .pn-new-li .pn-new-btn { padding: 0 10px !important; }
}


/* ---- fields custom-style.css hides ---------------------------------------
   custom-style.css hides a list of vendor fields (address, city, VAT, currency,
   owner, ...) by hiding the LABEL and the INPUT. Each .form-group around them
   stayed in the layout with its 15px margin, so the patient form had ~165px of
   blank space between Gender and Phone. Hide the whole group, for exactly the same
   list of fields. */
.form-group:has(> .row > label[for="address"]),
.form-group:has(> .row > label[for="city"]),
.form-group:has(> .row > label[for="country"]),
.form-group:has(> .row > label[for="currency"]),
.form-group:has(> .row > label[for="currency_symbol"]),
.form-group:has(> .row > label[for="gst_number"]),
.form-group:has(> .row > label[for="managers"]),
.form-group:has(> .row > label[for="owner_id"]),
.form-group:has(> .row > label[for="s2id_managers"]),
.form-group:has(> .row > label[for="s2id_owner_id"]),
.form-group:has(> .row > label[for="state"]),
.form-group:has(> .row > label[for="vat_number"]),
.form-group:has(> .row > label[for="website"]),
.form-group:has(> .row > label[for="zip"]) {
    display: none;
}

/* ...except on the patient and lead forms, where address, city, country and the
   owner (the call-centre agent who works the lead) are clinic data, not vendor
   fields. Hidden there, the patient record read as "read-only": its details
   could not be completed (reported 2026-09-30). */
:is(#client-form, #company-form, #lead-form) .form-group:has(> .row > label[for="address"]),
:is(#client-form, #company-form, #lead-form) .form-group:has(> .row > label[for="city"]),
:is(#client-form, #company-form, #lead-form) .form-group:has(> .row > label[for="country"]),
:is(#client-form, #company-form, #lead-form) .form-group:has(> .row > label[for="owner_id"]),
:is(#client-form, #company-form, #lead-form) .form-group:has(> .row > label[for="s2id_owner_id"]) {
    display: block;
}
:is(#client-form, #company-form, #lead-form) :is(label[for="address"], label[for="city"], label[for="country"], label[for="owner_id"], label[for="s2id_owner_id"]) {
    display: block;
}
:is(#client-form, #company-form, #lead-form) :is(#address, #city, #country, #s2id_owner_id) {
    display: block;
}


/* ---- phone bottom navigation (the design's .bottom-nav) ------------------- */
.pn-bottom-nav { display: none; }
@media (max-width: 767px) {
    .pn-bottom-nav {
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        position: fixed;
        left: 0; right: 0; bottom: 0;
        top: auto; /* a vendor nav rule sets top: 0, which pinned the bar to the TOP */
        z-index: 1030;
        height: 60px;
        padding-bottom: env(safe-area-inset-bottom, 0);
        background: var(--pn-surface);
        border-top: 1px solid var(--pn-border);
        box-shadow: 0 -2px 10px rgba(15, 37, 48, .06);
    }
    .pn-bottom-nav a,
    .pn-bottom-nav button {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        border: 0;
        background: none;
        color: var(--pn-text-2);
        font-size: 11px;
        font-weight: 600;
        text-decoration: none;
    }
    .pn-bottom-nav svg { width: 20px; height: 20px; }
    .pn-bottom-nav .is-on { color: var(--pn-accent); }
    /* keep the last rows of every page above the bar */
    body.porcelain-noir .page-container,
    body.porcelain-noir #page-content { padding-bottom: 76px; }
}

@media (max-width: 575px) {
    /* the old bottom bar's slots, now in the top bar */
    #default-navbar .pn-mobile-slot { display: flex; align-items: center; }
    #default-navbar .pn-mobile-slot .dropdown-toggle::after { display: none; }
    #default-navbar #mobile-quick-add-button > a {
        display: inline-flex; align-items: center; justify-content: center;
        width: 36px; height: 36px; border-radius: 8px;
        background: var(--pn-accent); color: var(--pn-on-accent, #fff);
    }
    #default-navbar #mobile-quick-add-button .pn-new-label { display: none; }
}
@media (max-width: 575px) {
    /* With no page actions the vendor script puts a Home link in this slot; the
       bottom bar already has Home. */
    #default-navbar #mobile-function-button .home-btn { display: none; }
    /* It was built to open UPWARD from a bottom bar; from the top bar it opens down. */
    #default-navbar .mobile-function-button-dropdown {
        top: 100% !important;
        bottom: auto !important;
        transform: none !important;
        right: 0; left: auto !important;
    }
}
@media (max-width: 575px) {
    #default-navbar #mobile-quick-add-button > a svg { stroke: var(--pn-on-accent, #fff); color: var(--pn-on-accent, #fff); }
}
@media (max-width: 575px) {
    #default-navbar #mobile-quick-add-button > a svg { width: 20px; height: 20px; }
}
@media (max-width: 575px) {
    /* The vendor styled this menu only inside .mobile-bottom-menu; in a navbar
       Bootstrap renders a dropdown menu static (in flow), below the screen. */
    #default-navbar #mobile-function-button .dropdown { position: relative; }
    #default-navbar #mobile-function-button .dropdown-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 36px; height: 36px; cursor: pointer;
    }
    #default-navbar #mobile-function-button .dropdown-toggle::after { display: none; }
    #default-navbar .mobile-function-button-dropdown {
        position: absolute !important;
        min-width: 220px;
        z-index: 1040;
    }
}
@media (max-width: 575px) {
    #default-navbar .mobile-function-button-dropdown {
        inset: 100% auto auto 0 !important;   /* open rightwards from the icon */
    }
    /* the desktop New button's 14px side padding left ~8px for the icon here */
    #default-navbar #mobile-quick-add-button > a.pn-new-btn { padding: 0 !important; margin: 0 4px; }
    #default-navbar #mobile-quick-add-button .dropdown-menu { right: 0; left: auto; }
}
@media (max-width: 767px) {
    /* Above the slide-out menu (z 1050), and the menu stops above the bar, so
       "More" is still there to close what it opened. */
    .pn-bottom-nav { z-index: 1060; }
    body.porcelain-noir.sidebar-toggled .sidebar { bottom: 60px !important; height: auto !important; }
    body.porcelain-noir.sidebar-toggled #pn-bottom-more { color: var(--pn-accent); }
}


/* ---- sign-in page ---------------------------------------------------------- */
body.porcelain-noir.signin-page { background: var(--pn-bg); }
body.porcelain-noir.signin-page .form-signin { max-width: 400px; margin: 8vh auto 0; }
body.porcelain-noir.signin-page .card {
    border: 1px solid var(--pn-border);
    border-radius: 14px;
    box-shadow: var(--pn-shadow-card, 0 1px 3px rgba(15,37,48,.08));
    background: var(--pn-surface);
}
body.porcelain-noir.signin-page .card-header {
    background: none;
    border: 0;
    padding: 28px 30px 0;
    text-align: left !important;
}
body.porcelain-noir.signin-page .card-header h2 {
    font-size: 22px; font-weight: 700; color: var(--pn-text-1); margin: 6px 0 4px;
}
.pn-signin-brand {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 13px; font-weight: 700; letter-spacing: .02em; color: var(--pn-accent);
}
.pn-signin-brand::before {
    content: ""; width: 10px; height: 10px; border-radius: 3px; background: var(--pn-accent);
}
.pn-signin-sub { color: var(--pn-text-2); font-size: 13.5px; margin: 0; }
body.porcelain-noir.signin-page .card-body { padding: 22px 30px 28px !important; }
body.porcelain-noir.signin-page .form-control {
    height: 42px; border-radius: 8px; border: 1px solid var(--pn-border);
    background: var(--pn-surface); color: var(--pn-text-1);
}
body.porcelain-noir.signin-page .btn-primary {
    height: 42px; border-radius: 8px; font-weight: 600;
    background: var(--pn-accent); border-color: var(--pn-accent); color: var(--pn-on-accent, #fff);
}
body.porcelain-noir.signin-page a { color: var(--pn-accent); }

/* ---- EMR plugin: NABIDH registration -------------------------------------- */
.pn-emr-banner {
    display: flex; align-items: center; gap: 12px;
    margin: 0 0 16px; padding: 12px 16px;
    border: 1px solid #F5D08A; border-radius: 10px;
    background: #FFF7E6; color: var(--pn-text-1);
}
.pn-emr-banner > div { flex: 1; font-size: 13.5px; }
.pn-emr-banner svg { color: #B45309; flex: none; }
body.porcelain-noir[data-theme="dark"] .pn-emr-banner { background: #2A2210; border-color: #6B4E12; }
@media (prefers-color-scheme: dark) {
    body.porcelain-noir:not([data-theme="light"]) .pn-emr-banner { background: #2A2210; border-color: #6B4E12; }
}
.pn-emr-h {
    font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--pn-text-2); margin: 18px 0 10px; padding-top: 14px; border-top: 1px solid var(--pn-border);
}
.pn-emr-form .pn-emr-h:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; }
.pn-emr-meter { height: 8px; border-radius: 99px; background: var(--pn-surface-2, #EEF2F5); overflow: hidden; }
.pn-emr-meter span { display: block; height: 100%; background: var(--pn-accent); border-radius: 99px; }
.pn-emr-score { font-size: 22px; font-weight: 700; margin: 8px 0 10px; font-variant-numeric: tabular-nums; }
.pn-emr-ok { color: #15803D; font-weight: 600; display: flex; align-items: center; gap: 6px; }
.pn-emr-missing { margin: 0; padding-left: 18px; font-size: 13px; }
.pn-emr-missing li { margin: 3px 0; }
.pn-emr-need { background: #FDE4E4; color: #B91C1C; font-weight: 600; margin: 0 4px 4px 0; }
.pn-emr-ready { position: sticky; top: 80px; }

/* ---- appointment confirmation badge (beside the visit status) ----------------- */
.pn-cf {
    display: inline-flex; align-items: center; gap: 4px;
    margin-inline-start: 6px; padding: 2px 8px; border-radius: 99px;
    border: 1px solid transparent; font-size: 11.5px; font-weight: 600; line-height: 1.5;
    background: var(--pn-surface-2, #F1F5F9); color: var(--pn-text-2); cursor: pointer;
}
.pn-cf::after { display: none; }
.pn-cf svg { width: 13px; height: 13px; }
.pn-cf-sent { background: #E0F2FE; color: #0369A1; }
.pn-cf-ok   { background: #DCFCE7; color: #15803D; }
.pn-cf-warn { background: #FEF3C7; color: #B45309; }
.pn-cf-bad  { background: #FDE4E4; color: #B91C1C; }
body.porcelain-noir[data-theme="dark"] .pn-cf-sent { background: #0C2F43; color: #7DD3FC; }
body.porcelain-noir[data-theme="dark"] .pn-cf-ok   { background: #0F2E1C; color: #86EFAC; }
body.porcelain-noir[data-theme="dark"] .pn-cf-warn { background: #3A2A08; color: #FCD34D; }
body.porcelain-noir[data-theme="dark"] .pn-cf-bad  { background: #3B1212; color: #FCA5A5; }

/* ---- Marketing ROI: ad set funnel -------------------------------------------- */
.pn-roi-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
@media (max-width: 900px) { .pn-roi-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pn-roi-kpi { border: 1px solid var(--pn-border); border-radius: 10px; padding: 12px 14px; background: var(--pn-surface); }
.pn-roi-kpi span { display: block; font-size: 12px; color: var(--pn-text-2); }
.pn-roi-kpi b { display: block; font-size: 20px; margin: 2px 0; font-variant-numeric: tabular-nums; }
.pn-roi-kpi small { color: var(--pn-text-2); }
.pn-roi-table td.num, .pn-roi-table th { white-space: nowrap; font-variant-numeric: tabular-nums; }
.pn-roi-best { color: #15803D; font-weight: 700; }

/* ---- EMR: Emirates ID scan -------------------------------------------------- */
.pn-eid-panel { border: 1px dashed var(--pn-border-strong, #CBD5E1); border-radius: 10px; padding: 12px; margin: 10px 0 6px; }
.pn-eid-progress { height: 6px; border-radius: 99px; background: var(--pn-surface-2, #EEF2F5); overflow: hidden; margin-bottom: 8px; }
.pn-eid-progress span { display: block; height: 100%; width: 0; background: var(--pn-accent); transition: width .2s; }
.pn-eid-status { font-size: 12.5px; margin-bottom: 6px; }
.pn-eid-result th { width: 34%; font-weight: 600; color: var(--pn-text-2); }
