/* ============================================================================
   Presque design system — the app-side voice of the presque.io brand.
   Serif display (Newsreader) + humanist sans (Hanken Grotesk), warm sage
   surfaces, pill buttons, soft 18px cards. Role accents: green (patients &
   the platform's default), slate blue (clinicians / practice ops), ink
   (platform admin). Slate blue carries two filled weights because the doors
   sit side by side: -deep is the clinician, -darkest the practice (review
   2026-09-15 — the practice door had no colour of its own). Loaded AFTER
   Bootstrap so these tokens win.
   ========================================================================== */

/* THE LIGHT PALETTE, on the root and on paper. `.pq-invoice` is the document card every letter, release, receipt and
   subscription invoice renders inside (LetterDocument, ReleaseDocument, AppointmentInvoice, SubscriptionInvoiceDocument):
   declaring the palette on it too means a document reads light on screen whatever the person chose — the sheet a
   patient sees is the sheet they print, and a QR code stays dark on white — and `color` is restated there because an
   inherited colour arrives computed, not as the token it came from. The DARK palette follows this block (screen only, so
   paper never goes dark: docs/07 dark theme); DarkThemeTests holds the two to the same set of tokens and to AA. */
:root, .pq-invoice {
    color-scheme: light;

    /* surfaces & ink */
    --pq-bg: #F4F6F1;
    --pq-surface: #FFFFFF;
    --pq-ink: #25322B;
    --pq-text: #515E57;
    /* The quiet tiers carry real meaning on the patient side — field hints, "(optional)", the sentence on
       PayReturn that says whether the money went through — so both clear WCAG AA (4.5:1) on every surface they
       land on, and the ink > text > soft > muted ladder still reads in that order (2026-09-06 patient UX round
       two: the old #6A766F/#8A968C pair measured 4.74:1 and 3.08:1 on white, and 2.83:1 on the page ground). */
    --pq-text-soft: #5F6B64;   /* 5.56:1 on #FFFFFF · 5.12:1 on #F4F6F1 */
    --pq-text-muted: #66726A;  /* 5.02:1 on #FFFFFF · 4.62:1 on #F4F6F1 */
    --pq-border: #E4E9DF;
    --pq-border-soft: #EAEEE4;
    /* The boundary of a CONTROL (text field, outline button) is the only cue that says "type here" / "this is a
       button", so it clears the 3:1 non-text minimum (WCAG 1.4.11). --pq-border stays decorative-weight. */
    --pq-border-control: #849089;  /* 3.32:1 on #FFFFFF */
    /* The edge a card takes when hovered, a spent slot's edge, a cancelled appointment's — one notch past --pq-border. */
    --pq-border-strong: #D8E2D4;
    /* The surface ladder past the page and the card: -2 is the near-white a row takes on hover and a seal sits on, -3
       the sage-tinted hover of a crumb or a nav row (and the neutral badge), -4 the deeper plate under a meter track, a
       chip row, a skeleton and the ink portal icon. Four steps where the file once held eleven near-identical hexes. */
    --pq-surface-2: #FAFBF8;
    --pq-surface-3: #F1F4EE;
    --pq-surface-4: #E8EDE6;

    /* brand accents */
    --pq-green: #5E8E72;
    --pq-green-deep: #436E56;
    --pq-green-soft: #E4EEE7;
    /* the dashed edge of a free calendar slot: a green line, lighter than the sage, darker than its tint */
    --pq-green-line: #BFD4C6;
    --pq-blue: #5C7C9C;
    --pq-blue-deep: #43617C;
    /* The practice door, one step down the same slate — a fourth hue would have to follow practice ops
       through every staff surface, and the ink is already spoken for by platform admin. */
    --pq-blue-darkest: #35506A;
    --pq-blue-soft: #E1E9F0;
    /* TEXT ON A FILL: the white every filled pill, badge and pin paints its label in. A token because the dark palette
       lightens every fill (a mint primary, a near-white ink) and paints the label dark on it; nothing in the file says
       #fff for a label any more. */
    --pq-on-accent: #FFFFFF;
    /* the ink button's hover: a touch lighter than the ink, so the press still has somewhere to go */
    --pq-ink-soft: #36443B;

    /* status (state, never series): tint background + dark readable text + the border a notice draws in */
    --pq-status-good-bg: #E4EEE7;    --pq-status-good-ink: #2E5B42;    --pq-status-good-border: #CBDFD2;
    --pq-status-info-bg: #E1E9F0;    --pq-status-info-ink: #395A76;    --pq-status-info-border: #C9D8E4;
    --pq-status-warn-bg: #F5E9D4;    --pq-status-warn-ink: #7A5A1C;    --pq-status-warn-border: #E8D5AE;
    --pq-status-bad-bg:  #F0E2DE;    --pq-status-bad-ink:  #7E3B2A;    --pq-status-bad-border:  #E3C7BF;
    /* The live warning colours: an invalid field's edge, a meter past its budget, a warn dot — brighter than the
       status inks, which are text. --pq-red is also what image-upload.js and profile-ux.js colour an error line in. */
    --pq-red: #B4534B;
    --pq-amber: #C99F3D;
    /* "no check ran", "unknown": the one grey that means nothing is known, never a healthy or a failing hour */
    --pq-neutral: #B9BEB4;
    --pq-selection: #D6E5DA;

    /* chart series (validated: lightness band, chroma floor, CVD, contrast) */
    --pq-series-1: #3E8E60;   /* registrations / growth        */
    --pq-series-2: #3F6FA8;   /* cancellations / second series */

    /* shape & type */
    --pq-radius: 18px;
    --pq-radius-sm: 12px;
    --pq-serif: 'Newsreader', Georgia, 'Times New Roman', serif;
    --pq-sans: 'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --pq-shadow: 0 18px 44px -22px rgba(37, 50, 43, 0.28);
    --pq-shadow-soft: 0 1px 2px rgba(37, 50, 43, 0.08);
    /* the rest of the shadows, each named for the thing it sits under: a filled button and its hover, a modal, a
       floating control (the map pin, the report-a-problem button), the crash banner's upward edge, the dimmed page
       behind a dialog, the faint shade on a tab's count, and the skeleton's shimmer */
    --pq-shadow-btn: 0 2px 8px rgba(37, 50, 43, 0.13);
    --pq-shadow-btn-hover: 0 6px 16px -6px rgba(37, 50, 43, 0.35);
    --pq-shadow-modal: 0 18px 48px rgba(37, 50, 43, 0.28);
    --pq-shadow-float: 0 6px 16px -6px rgba(37, 50, 43, 0.5);
    --pq-shadow-up: 0 -1px 2px rgba(37, 50, 43, 0.2);
    --pq-backdrop: rgba(37, 50, 43, 0.45);
    --pq-shade: rgba(37, 50, 43, 0.06);
    --pq-shimmer: rgba(255, 255, 255, 0.6);
    --pq-focus-ring-blue: rgba(92, 124, 156, 0.35);
    /* The plate under a clinic's logo in the menu and the directory: nothing on the light palette, white on the dark
       one, so a mark drawn for a white page is never a dark smudge on a dark one. */
    --pq-logo-plate: transparent;
    /* Which face the menu's sun-and-moon button wears (AppearanceToggle): a moon while the light palette is drawn, a sun
       while the dark one is. Each face's `display` reads one of these, so the face follows the palette by the very rules
       that draw the palette (the device's setting, the pinned choice, a choice made in another tab), right in the first
       frame and with no script between the two. */
    --pq-face-moon: inline-flex;
    --pq-face-sun: none;

    /* motion (every animated rule is prefers-reduced-motion-guarded) */
    --pq-ease: cubic-bezier(0.22, 0.72, 0.28, 1);
    --pq-dur: 0.16s;
    --pq-shadow-lift: 0 10px 30px -12px rgba(37, 50, 43, 0.22);

    /* aliases: names pages reached for before they existed — resolve them to the
       real palette so the per-usage hex fallbacks stop drifting off-brand */
    --pq-line: var(--pq-border);
    --pq-green-pale: var(--pq-green-soft);
    --pq-muted: var(--pq-text-muted);
    --pq-good: var(--pq-green);
    --pq-danger: var(--pq-status-bad-ink);
    --pq-warn: var(--pq-status-warn-ink);
    --pq-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

    /* THE VIDEO CALL'S STAGE is dark on both palettes — a face on video sits in a dark room whatever the page around it
       is set to, as every video app's does — so these are declared once and the dark palette leaves them alone. The
       scrims sit over video, the lines and edges over the scrims, the danger pair is the muted chip and the recording
       light, and the chat drawer's own bubble keeps the sage it always had. Paper is white on both palettes too. */
    --pq-call-bg: #0F1512;
    --pq-call-stage: #000000;
    --pq-call-ink: #EDF2EE;
    --pq-call-ink-soft: #DCE4DD;
    --pq-call-ink-muted: #B9C6BC;
    --pq-call-scrim: rgba(37, 50, 43, 0.72);
    --pq-call-scrim-strong: rgba(37, 50, 43, 0.78);
    --pq-call-glass: rgba(0, 0, 0, 0.4);
    --pq-call-glass-strong: rgba(0, 0, 0, 0.55);
    --pq-call-line: rgba(255, 255, 255, 0.12);
    --pq-call-edge: rgba(255, 255, 255, 0.35);
    --pq-call-edge-strong: rgba(255, 255, 255, 0.4);
    --pq-call-danger-bg: rgba(126, 59, 42, 0.85);
    --pq-call-danger-ink: #FBECE8;
    --pq-call-danger-edge: rgba(251, 236, 232, 0.35);
    --pq-call-chat-bg: rgba(24, 33, 28, 0.96);
    --pq-call-mine: #5E8E72;
    --pq-paper: #FFFFFF;
}

/* Where the clinic's accent lands on a page (docs/17 C5, docs/07 dark theme): an edge and a line of eyebrow text, on the
   call page and (the edge, 3px) on the clinic host's menu brand. The custom properties ride the element's style attribute,
   written by BrandAccent: the accent the clinic chose, which tenancy checked against WHITE text and nothing else; the
   shade for a dark edge, which is that accent when it clears 3:1 on the dark surface; and the shade for dark text, 4.5:1
   — each the house green instead where the accent fails, because a shade picked for white text is usually too dark to
   read on a dark page. The dark palette's blocks below repoint the two rules; no element carries a colour inline. */
.pq-brand-edge { border-left: 4px solid var(--pq-brand-accent, var(--pq-ink)); padding-left: 16px; }
.pq-brand-ink { color: var(--pq-brand-accent, var(--pq-green-deep)); }

/* ============================================================================
   THE DARK PALETTE (docs/07 dark theme). The same tokens, redefined twice over: once for a device set to dark while the
   person has made no choice (the media query), once for a choice of Dark on any device (App.razor's head script pins
   data-theme on <html> from the browser's stored choice before the body is parsed; "light" pins the palette above).
   Both under `screen`, so paper — a letter, a receipt, an invoice printed — is always the light palette, and so is the
   .pq-invoice card on screen. The two blocks are kept identical by DarkThemeTests, which also holds every pairing the
   pages make to AA: light text on the page and the cards, the links, the status inks on their tints and on a card, a
   label on a filled pill, a field's edge. Every fill lightens (a mint primary, a near-white ink) and its label goes dark
   (--pq-on-accent); the tints go deep; the greys keep their sage. Bootstrap's own variables are repointed too, for the
   few controls still drawn from its sheet (a form-check, a table), so nothing of its white shows through.
   ========================================================================== */

@media screen and (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;
        --pq-bg: #121816;
        --pq-surface: #1A211D;
        --pq-ink: #EEF2EC;
        --pq-text: #C9D1CA;
        --pq-text-soft: #B9C2BB;
        --pq-text-muted: #AAB4AC;
        --pq-border: #2F3B34;
        --pq-border-soft: #29342E;
        --pq-border-control: #7E8A81;
        --pq-border-strong: #3C4A42;
        --pq-surface-2: #1F2823;
        --pq-surface-3: #25302A;
        --pq-surface-4: #2D3A33;
        --pq-green: #7DB391;
        --pq-green-deep: #9ED1B3;
        --pq-green-soft: #1F3328;
        --pq-green-line: #3A5A48;
        --pq-blue: #86A6C4;
        --pq-blue-deep: #AAC5DE;
        --pq-blue-darkest: #C4D7E9;
        --pq-blue-soft: #1F2C39;
        --pq-on-accent: #101613;
        --pq-ink-soft: #D6DDD6;
        --pq-status-good-bg: #1E3428;    --pq-status-good-ink: #A9D9BC;    --pq-status-good-border: #2E4A3A;
        --pq-status-info-bg: #1E2B3A;    --pq-status-info-ink: #ABC8E4;    --pq-status-info-border: #2E425A;
        --pq-status-warn-bg: #3A2F19;    --pq-status-warn-ink: #E9C87F;    --pq-status-warn-border: #5A4A26;
        --pq-status-bad-bg:  #3C2420;    --pq-status-bad-ink:  #F0A999;    --pq-status-bad-border:  #5C3A33;
        --pq-red: #EA978B;
        --pq-amber: #E1B762;
        --pq-neutral: #5E6A62;
        --pq-selection: #2F5A42;
        --pq-series-1: #6CC08C;
        --pq-series-2: #82ABDB;
        --pq-shadow: 0 18px 44px -22px rgba(0, 0, 0, 0.65);
        --pq-shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.35);
        --pq-shadow-btn: 0 2px 8px rgba(0, 0, 0, 0.35);
        --pq-shadow-btn-hover: 0 6px 16px -6px rgba(0, 0, 0, 0.6);
        --pq-shadow-modal: 0 18px 48px rgba(0, 0, 0, 0.6);
        --pq-shadow-float: 0 6px 16px -6px rgba(0, 0, 0, 0.6);
        --pq-shadow-up: 0 -1px 2px rgba(0, 0, 0, 0.5);
        --pq-backdrop: rgba(0, 0, 0, 0.6);
        --pq-shade: rgba(255, 255, 255, 0.06);
        --pq-shimmer: rgba(255, 255, 255, 0.08);
        --pq-focus-ring-blue: rgba(134, 166, 196, 0.4);
        --pq-logo-plate: #FFFFFF;
        --pq-face-moon: none;
        --pq-face-sun: inline-flex;
        --pq-shadow-lift: 0 10px 30px -12px rgba(0, 0, 0, 0.55);
        --bs-body-bg: var(--pq-surface);
        --bs-body-color: var(--pq-text);
        --bs-emphasis-color: var(--pq-ink);
        --bs-heading-color: var(--pq-ink);
        --bs-secondary-color: var(--pq-text-muted);
        --bs-secondary-bg: var(--pq-surface-4);
        --bs-tertiary-bg: var(--pq-surface-3);
        --bs-border-color: var(--pq-border);
        --bs-link-color: var(--pq-green-deep);
        --bs-link-hover-color: var(--pq-ink);
    }
    :root:not([data-theme="light"]) .pq-brand-edge { border-left-color: var(--pq-brand-accent-dark, var(--pq-ink)); }
    :root:not([data-theme="light"]) .pq-brand-ink { color: var(--pq-brand-accent-dark-ink, var(--pq-green-deep)); }
}

@media screen {
    :root[data-theme="dark"] {
        color-scheme: dark;
        --pq-bg: #121816;
        --pq-surface: #1A211D;
        --pq-ink: #EEF2EC;
        --pq-text: #C9D1CA;
        --pq-text-soft: #B9C2BB;
        --pq-text-muted: #AAB4AC;
        --pq-border: #2F3B34;
        --pq-border-soft: #29342E;
        --pq-border-control: #7E8A81;
        --pq-border-strong: #3C4A42;
        --pq-surface-2: #1F2823;
        --pq-surface-3: #25302A;
        --pq-surface-4: #2D3A33;
        --pq-green: #7DB391;
        --pq-green-deep: #9ED1B3;
        --pq-green-soft: #1F3328;
        --pq-green-line: #3A5A48;
        --pq-blue: #86A6C4;
        --pq-blue-deep: #AAC5DE;
        --pq-blue-darkest: #C4D7E9;
        --pq-blue-soft: #1F2C39;
        --pq-on-accent: #101613;
        --pq-ink-soft: #D6DDD6;
        --pq-status-good-bg: #1E3428;    --pq-status-good-ink: #A9D9BC;    --pq-status-good-border: #2E4A3A;
        --pq-status-info-bg: #1E2B3A;    --pq-status-info-ink: #ABC8E4;    --pq-status-info-border: #2E425A;
        --pq-status-warn-bg: #3A2F19;    --pq-status-warn-ink: #E9C87F;    --pq-status-warn-border: #5A4A26;
        --pq-status-bad-bg:  #3C2420;    --pq-status-bad-ink:  #F0A999;    --pq-status-bad-border:  #5C3A33;
        --pq-red: #EA978B;
        --pq-amber: #E1B762;
        --pq-neutral: #5E6A62;
        --pq-selection: #2F5A42;
        --pq-series-1: #6CC08C;
        --pq-series-2: #82ABDB;
        --pq-shadow: 0 18px 44px -22px rgba(0, 0, 0, 0.65);
        --pq-shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.35);
        --pq-shadow-btn: 0 2px 8px rgba(0, 0, 0, 0.35);
        --pq-shadow-btn-hover: 0 6px 16px -6px rgba(0, 0, 0, 0.6);
        --pq-shadow-modal: 0 18px 48px rgba(0, 0, 0, 0.6);
        --pq-shadow-float: 0 6px 16px -6px rgba(0, 0, 0, 0.6);
        --pq-shadow-up: 0 -1px 2px rgba(0, 0, 0, 0.5);
        --pq-backdrop: rgba(0, 0, 0, 0.6);
        --pq-shade: rgba(255, 255, 255, 0.06);
        --pq-shimmer: rgba(255, 255, 255, 0.08);
        --pq-focus-ring-blue: rgba(134, 166, 196, 0.4);
        --pq-logo-plate: #FFFFFF;
        --pq-face-moon: none;
        --pq-face-sun: inline-flex;
        --pq-shadow-lift: 0 10px 30px -12px rgba(0, 0, 0, 0.55);
        --bs-body-bg: var(--pq-surface);
        --bs-body-color: var(--pq-text);
        --bs-emphasis-color: var(--pq-ink);
        --bs-heading-color: var(--pq-ink);
        --bs-secondary-color: var(--pq-text-muted);
        --bs-secondary-bg: var(--pq-surface-4);
        --bs-tertiary-bg: var(--pq-surface-3);
        --bs-border-color: var(--pq-border);
        --bs-link-color: var(--pq-green-deep);
        --bs-link-hover-color: var(--pq-ink);
    }
    :root[data-theme="dark"] .pq-brand-edge { border-left-color: var(--pq-brand-accent-dark, var(--pq-ink)); }
    :root[data-theme="dark"] .pq-brand-ink { color: var(--pq-brand-accent-dark-ink, var(--pq-green-deep)); }
}

/* Paper keeps its own colour however the palette above it was set: the card's text is restated here because `color`
   inherits as a computed value, so the body's dark-palette grey would otherwise arrive on the white sheet as-is. */
.pq-invoice { color: var(--pq-text); }

/* ---- base ---- */

/* TEXT SIZE (WCAG 1.4.4). The root's size is a share of the browser's own default, never a length, so a person who
   sets a larger default text size in their browser gets larger text here. Until 2026-11-05 the root was 15.5px and
   every size after it a px, and the only thing that made anything bigger was zooming the whole page. 96.875% of the
   usual 16px is that same 15.5px: at the default setting nothing has moved, Bootstrap's rem spacing included.

   Every font size in this sheet, in the scoped sheets and in the pages' inline styles is written against the root as
   calc(Nrem / 15.5), where N is the size in px at the default setting: calc(13.5rem / 15.5) is 13.5px there and
   16.875px for a reader whose default is 20px. The body's own size is 1rem. Line heights are unitless, so lines open
   up with their text. TextResizeTests fails on a font size written in px and names the line; a line that has to carry
   a px length says why in a trailing "pq-px:" comment, which lets it through.

   A phone has no such default in its browser: its owner sets one text size for the whole device, and Chrome on Android
   applies it only to a page that says it is built for scaled text, which the text-scale line in App.razor's head does
   (the offline page and the passkey popup carry their own). The size the browser starts from is then 16px times the
   phone's setting, and this share of it follows: 31px on a phone set to 200%. That is the second reason the root stays
   a share. A length here would switch the phone's setting off with the browser's, and the setting must not be applied
   again in a size (env(preferred-text-scale) is the same number), or a phone at 200% would be drawn at 400%. */
html { font-size: 96.875%; }

/* What a reader's setting adds to the body's size, in px: nothing at the default, 3.875px at a 20px default, less than
   nothing at a default under 16px. For the few rules that must stand exactly where they were at the default setting
   and give way only as the text grows (a list row's text column, below). */
:root { --pq-text-added: calc(1rem - 15.5px); }

/* Paper keeps the size it was drawn at. A letter, a receipt and an invoice are set out to fit their sheet (a letter's
   seal must not be pushed onto a second page), and the print dialog has its own scale for a reader who wants more. */
@media print {
    html { font-size: 15.5px; } /* pq-px: paper is measured in the sheet's units, not by the screen's text setting */
}

html, body {
    font-family: var(--pq-sans);
    background: var(--pq-bg);
    color: var(--pq-text);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

body { font-size: 1rem; }

::selection { background: var(--pq-selection); }

h1, h2, h3, .pq-serif {
    font-family: var(--pq-serif);
    font-weight: 400;
    color: var(--pq-ink);
    letter-spacing: -0.018em;
}

/* The page heading is fluid: 28px on a phone, 38px on a wide screen, 3.4vw between. Its two bounds grow with the
   reader's setting like every other size. The middle would not, a share of the viewport being the same length whatever
   the setting, so it also carries the difference between 33px that grow and 33px that do not (33 is the middle of the
   range). At the default setting that difference is nothing and the heading is the 3.4vw it always was; at a larger
   one it is what the setting added. `.pq-prose h1` and the landing page's headline are written the same way.

   And it stops at 13vw. A heading is a word or two, and a word cannot wrap: on a 360px phone set to 200% (the end of
   Android's Font size slider) "Appointments" was 56px tall and wider than the screen, cut off at its edge (measured).
   13vw is the size at which a twelve-letter word, the longest a page heading holds, still fits a phone's column: the
   heading grows with the setting until it would no longer fit and then stays whole, while the text under it goes on
   growing. On any screen wider than 215px the heading at the default setting is under 13vw, so there nothing has
   moved; nor on a tablet or a desktop at any setting, where 13vw is far above it. */
h1 { font-size: min(clamp(28rem / 15.5, 3.4vw + 33rem / 15.5 - 33px, 38rem / 15.5), 13vw); line-height: 1.1; } /* pq-px: the fixed half of the fluid term, see above */
h1:focus { outline: none; }
/* A word longer still than its line (a long first name in a greeting, a form's own title) goes onto the next line part
   way through instead of running off the screen. It changes nothing where every word fits, which is everywhere at the
   default setting. */
h1, h2, h3 { overflow-wrap: break-word; }
/* A card's heading stops for the page heading's reason, at 11vw: it stands inside a card's padding, so its column is
   narrower than the page's, and at 200% on a 360px phone the Home card's "Appointments" went onto a second line after
   its eleventh letter (measured; that card sizes its own heading, .pq-portal-card, and stops at the same share).
   Under 11vw at the default setting on any screen wider than 218px. */
h2 { font-size: min(calc(24rem / 15.5), 11vw); }
h3 { font-size: calc(19rem / 15.5); }

a, .btn-link {
    color: var(--pq-green-deep);
    text-decoration: none;
}
a:hover, .btn-link:hover { color: var(--pq-ink); }

/* A link in running text is told apart from the words around it by more than its colour: deep green on the body's grey
   measures under 3:1 against the text beside it, and WCAG 1.4.1 (axe's link-in-text-block, serious, on nine patient
   routes in the 2026-11-04 pass) refuses colour as the only cue. Underlined, in prose alone — a paragraph, a list
   item, a hint, the fine print, a notice, a label, a description — and only the bare links there: the nav, the pills,
   the crumbs and the card titles are links by placement and shape, carry a class, and stay as they were. */
p a:not([class]), li a:not([class]), dd a:not([class]), dt a:not([class]), label a:not([class]), legend a:not([class]),
summary a:not([class]), figcaption a:not([class]), blockquote a:not([class]), small a:not([class]),
.pq-hint a:not([class]), .pq-fineprint a:not([class]), .pq-muted a:not([class]), .pq-notice a:not([class]),
.pq-empty-hint a:not([class]) {
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

code { color: var(--pq-blue-deep); overflow-wrap: anywhere; }

/* ---- skip link: the keyboard's way past the menu ---- */

/* Off-screen until focused — then a clear pill above everything, first in the Tab order (MainLayout renders it
   before the sidebar). Focus-only, so pointer users never see it and keyboard users always can. */
.pq-skip-link {
    position: absolute;
    left: -9999px;
    top: 12px;
    z-index: 1100;
    background: var(--pq-ink);
    color: var(--pq-on-accent);
    padding: 10px 18px;
    border-radius: 999px;
    font-weight: 600;
    box-shadow: var(--pq-shadow);
}
.pq-skip-link:focus {
    left: 12px;
    color: var(--pq-on-accent);
    outline: 2px solid var(--pq-green);
    outline-offset: 2px;
}
#main-content:focus { outline: none; } /* the skip target itself needs no ring — focus moved BY choice */

/* ---- eyebrow + page headers ---- */

.pq-eyebrow {
    font-size: calc(12rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    /* the deep green, not the sage: 12px bold sage on white is 3.76:1 (below AA), and .blue already
       reaches for its own deep token one line down */
    color: var(--pq-green-deep);
    margin-bottom: 8px;
}
.pq-eyebrow.blue { color: var(--pq-blue-deep); }
.pq-eyebrow.ink { color: var(--pq-text-soft); }

.pq-page-head { margin-top: 24px; margin-bottom: 26px; }
.pq-page-head p { color: var(--pq-text-soft); max-width: 56em; margin: 8px 0 0; }

/* ---- buttons: brand pills ---- */

.pq-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    border-radius: 999px;
    border: 1px solid transparent;
    padding: 11px 22px;
    font-family: var(--pq-sans);
    font-size: calc(15rem / 15.5);
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease,
                transform var(--pq-dur) var(--pq-ease), box-shadow var(--pq-dur) var(--pq-ease);
}
.pq-btn:disabled { opacity: 0.55; cursor: default; }
.pq-btn:focus-visible { outline: 2px solid var(--pq-green-deep); outline-offset: 2px; }
/* The explicit display above beats the UA sheet's [hidden] rule (the pq-scale-ends note far below), so every
   button a driver hides by platform or entitlement — screen share and fullscreen on phones, the camera flip
   until a second camera exists, the paywalled dictation/AI buttons until the server confirms them — was
   rendering as a dead pill. Restate it: a hidden button is not shown. */
.pq-btn[hidden] { display: none; }

@media (prefers-reduced-motion: no-preference) {
    .pq-btn:hover:not(:disabled) { transform: translateY(-1px); }
    .pq-btn:active:not(:disabled) { transform: translateY(0); transition-duration: 0.06s; }
}

/* FILLED buttons take the DEEP tokens (2026-09-06 patient UX round two). White on the sage #5E8E72 is 3.76:1
   and on the slate #5C7C9C is 4.36:1 — both below AA for 15px/600, which is not "large text". The one control a
   patient is meant to press on each page should not be the lowest-contrast text on it. The sage keeps every
   non-text job it had: rules, dots, the active nav reed, accent-color, focus rings. Hover darkens to ink so the
   press still has somewhere to go. */
.pq-btn-primary { background: var(--pq-green-deep); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn); }  /* 5.83:1 */
.pq-btn-primary:hover:not(:disabled) { background: var(--pq-ink); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn-hover); }

.pq-btn-blue { background: var(--pq-blue-deep); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn); }  /* 6.48:1 */
.pq-btn-blue:hover:not(:disabled) { background: var(--pq-ink); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn-hover); }

.pq-btn-blue-dark { background: var(--pq-blue-darkest); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn); }  /* 8.38:1 */
.pq-btn-blue-dark:hover:not(:disabled) { background: var(--pq-ink); color: var(--pq-on-accent); box-shadow: var(--pq-shadow-btn-hover); }

.pq-btn-ink { background: var(--pq-ink); color: var(--pq-on-accent); }
.pq-btn-ink:hover:not(:disabled) { background: var(--pq-ink-soft); color: var(--pq-on-accent); }

.pq-btn-outline {
    background: var(--pq-surface);
    color: var(--pq-ink);
    /* the edge IS the button here — 3:1 minimum, not the decorative border tint */
    border-color: var(--pq-border-control);
}
.pq-btn-outline:hover:not(:disabled) { border-color: var(--pq-green); color: var(--pq-green-deep); }

.pq-btn-danger-outline {
    background: var(--pq-surface);
    color: var(--pq-status-bad-ink);
    border-color: var(--pq-status-bad-border);
}
.pq-btn-danger-outline:hover:not(:disabled) { background: var(--pq-status-bad-bg); }

/* Filled destructive action — reserved for the FINAL step of an explicit confirmation flow (e.g. account
   closure), so the strongest button style is never a first click. */
.pq-btn-danger { background: var(--pq-status-bad-ink); color: var(--pq-on-accent); }
.pq-btn-danger:hover:not(:disabled) { background: var(--pq-red); color: var(--pq-on-accent); }

.pq-btn-sm { padding: 6px 14px; font-size: calc(13.5rem / 15.5); }

/* The offered-time mark (docs/25 book-slot-grid-doesnt-say-which): a slot that books straight in rather than
   going to the practice to confirm. Rendered ONLY when the offered times differ — a mark that every time
   carries says nothing. Decoration by construction: it is aria-hidden, every marked time also states the
   answer in its accessible name, and a legend above the grid explains the glyph. `currentColor` so it stays
   legible on the selected time too, where the pill is filled green rather than outlined. */
.pq-slot-mark {
    margin-left: 5px;
    font-size: calc(9rem / 15.5);
    line-height: 1;
    vertical-align: middle;
    color: currentColor;
}

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

.pq-card {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 22px;
    box-shadow: var(--pq-shadow-soft);
}

/* A card INSIDE a card — material a clinician attached to a task, sitting above the form it belongs to. Flatter and
   tighter than the outer card so it reads as part of it rather than as a second, competing panel. */
.pq-card-inset {
    padding: 12px 14px;
    margin: 12px 0;
    box-shadow: none;
    background: var(--pq-bg);
}

.pq-card-title {
    font-size: calc(12rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--pq-text-soft);
    margin: 0 0 14px;
}

/* ---- badges (status: label + tint, never color alone) ---- */

.pq-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border-radius: 999px;
    padding: 4px 11px;
    font-size: calc(12.5rem / 15.5);
    font-weight: 600;
    white-space: nowrap;
}
.pq-badge-good { background: var(--pq-status-good-bg); color: var(--pq-status-good-ink); }
.pq-badge-info { background: var(--pq-status-info-bg); color: var(--pq-status-info-ink); }
.pq-badge-warn { background: var(--pq-status-warn-bg); color: var(--pq-status-warn-ink); }
.pq-badge-bad  { background: var(--pq-status-bad-bg);  color: var(--pq-status-bad-ink); }
.pq-badge-neutral { background: var(--pq-surface-3); color: var(--pq-text-soft); }

/* The scheduled checks' seven-day history (/admin/health, docs/26 MON-7): one SVG cell an hour, tinted with the badges'
   inks. "No check ran" is the palest, so a gap in the sentinel never reads as a healthy hour. */
.pq-ops-strip { display: block; width: 100%; height: 14px; min-width: 160px; }
.pq-ops-strip .ok, .pq-ops-key.ok { fill: var(--pq-status-good-ink); background: var(--pq-status-good-ink); opacity: .55; }
.pq-ops-strip .warn, .pq-ops-key.warn { fill: var(--pq-status-warn-ink); background: var(--pq-status-warn-ink); }
.pq-ops-strip .bad, .pq-ops-key.bad { fill: var(--pq-status-bad-ink); background: var(--pq-status-bad-ink); }
.pq-ops-strip .unknown, .pq-ops-key.unknown { fill: var(--pq-neutral); background: var(--pq-neutral); }
.pq-ops-strip .none, .pq-ops-key.none { fill: var(--pq-surface-4); background: var(--pq-surface-4); }
.pq-ops-key { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin: 0 3px 0 10px; vertical-align: -1px; }
.pq-ops-key:first-child { margin-left: 0; }

/* The console's status strip on /admin (docs/26 §8): a dot in the state's ink, the headline, and one line per thing to
   do. The dot never stands alone — the headline says the same in words. */
.pq-console-status { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 16px; }
.pq-console-status-dot { flex: none; width: 12px; height: 12px; border-radius: 50%; margin-top: 5px; background: var(--pq-neutral); }
.pq-console-status[data-console-status="clear"] .pq-console-status-dot { background: var(--pq-status-good-ink); }
.pq-console-status[data-console-status="attention"] .pq-console-status-dot { background: var(--pq-status-warn-ink); }
.pq-console-status[data-console-status="failing"] { border-color: var(--pq-status-bad-border); }
.pq-console-status[data-console-status="failing"] .pq-console-status-dot { background: var(--pq-status-bad-ink); }
.pq-console-status-body { flex: 1; min-width: 0; }
.pq-console-status-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px; }
.pq-console-status-head strong { font-size: calc(16rem / 15.5); color: var(--pq-ink); }
.pq-console-status-head .pq-btn { margin-left: auto; }

/* A build's commit (/admin/health's Deployed build, docs/26 MON-19): git's seven characters, monospaced. */
.pq-build-id { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .92em; }
.pq-stat-value .pq-build-id { font-size: calc(24rem / 15.5); }

/* "On this page": a long console page's jump links, one pill each (InPageLink — a jump, never a reload). */
.pq-jump { display: flex; flex-wrap: wrap; gap: 6px 8px; margin-top: 14px; }
.pq-jump a {
    display: inline-flex; align-items: center; padding: 4px 12px; border: 1px solid var(--pq-border);
    border-radius: 999px; background: var(--pq-surface); color: var(--pq-text); font-size: calc(13rem / 15.5); text-decoration: none;
}
.pq-jump a:hover, .pq-jump a:focus-visible { border-color: var(--pq-border-control); color: var(--pq-ink); }

/* The public status page (/status, docs/28): one row per component, its name and its badge. */
.pq-status-list { list-style: none; margin: 0; padding: 0; }
.pq-status-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-top: 1px solid var(--pq-line);
}
.pq-status-list li:first-child { border-top: 0; padding-top: 0; }

/* STAT-2 (docs/28 §4.6): the 90-day bars under each component, one per day, worst state as colour; the legend
   reuses the same swatches. The older 60 bars carry .pq-status-day-far and leave at phone width, so a phone shows
   the last 30 without the bars shrinking to slivers. No data is grey, never green. */
.pq-status-list li { flex-wrap: wrap; }
.pq-status-days { display: flex; gap: 2px; width: 100%; margin-top: 8px; }
.pq-status-day { flex: 1 1 0; min-width: 0; height: 18px; border-radius: 2px; background: var(--pq-border); }
.pq-status-day-o { background: var(--pq-green); }
.pq-status-day-d { background: var(--pq-status-warn-ink); }
.pq-status-day-x { background: var(--pq-status-bad-ink); }
.pq-status-day-m { background: var(--pq-blue); }
.pq-status-day-n { background: var(--pq-border); }
@media (max-width: 640px) { .pq-status-day-far { display: none; } }
.pq-status-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px; font-size: calc(12rem / 15.5); }
.pq-status-legend > span { display: inline-flex; align-items: center; gap: 5px; }
.pq-status-legend .pq-status-day { flex: 0 0 12px; width: 12px; height: 12px; }

/* STAT-4 (docs/28 §4.4): the in-app status banner — one line above the page, its links inline, a small Hide. */
.pq-status-banner { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 14px; }
.pq-status-banner > span { flex: 1 1 320px; }
.pq-status-banner a { font-weight: 600; white-space: nowrap; }
.pq-status-banner-dismiss { background: none; border: 1px solid currentColor; border-radius: 6px; color: inherit; padding: 2px 10px; font-size: calc(13rem / 15.5); cursor: pointer; }

/* A removable search-reading chip: the WHOLE chip is the link, so the target is the chip rather than the ✕
   glyph inside it (docs/22 reading-chip-x-touch-target). The directory is a one-handed-on-a-phone page, and the
   old target was a few millimetres of inline text. Padding grows to the house 44px minimum on touch pointers
   only, so the desktop chip row keeps its density. */
.pq-chip-remove { text-decoration: none; cursor: pointer; }
.pq-chip-remove:hover { background: var(--pq-surface-4); }

/* A type=search box is still a .pq-input: Safari and Chrome otherwise impose their own rounding, inner
   padding and a clear affordance that fights the house field. */
.pq-input[type="search"] { -webkit-appearance: none; appearance: none; }
.pq-input[type="search"]::-webkit-search-decoration,
.pq-input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }

/* ---- tables (also rebrands Bootstrap .table used by existing pages) ---- */

.table, .pq-table {
    width: 100%;
    border-collapse: collapse;
    background: transparent;
    color: var(--pq-text);
}
.table thead th, .pq-table thead th {
    font-size: calc(12rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--pq-text-muted);
    border-bottom: 1px solid var(--pq-border);
    padding: 10px 12px;
    text-align: left;
    background: transparent;
}
.table tbody td, .pq-table tbody td {
    padding: 12px;
    border-bottom: 1px solid var(--pq-border-soft);
    vertical-align: middle;
    background: transparent;
}
.pq-table .pq-num, .pq-table td.pq-num { font-variant-numeric: tabular-nums; text-align: right; }
.pq-table tbody tr:hover td { background: var(--pq-surface-2); }

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

.pq-label {
    display: block;
    font-size: calc(13.5rem / 15.5);
    font-weight: 600;
    color: var(--pq-ink);
    margin-bottom: 6px;
}
/* A hint is not decoration: it is where a field says what it wants, and where the notification page explains
   what WhatsApp will and will not carry. 13.5px on the soft tier, matching .pq-label and .pq-fineprint. */
.pq-hint { font-size: calc(13.5rem / 15.5); line-height: 1.45; color: var(--pq-text-soft); margin-top: 4px; }

.pq-input, .pq-select, .pq-textarea {
    width: 100%;
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-control);
    border-radius: var(--pq-radius-sm);
    padding: 10px 14px;
    font-family: var(--pq-sans);
    font-size: calc(15rem / 15.5);
    color: var(--pq-ink);
}
.pq-input:focus, .pq-select:focus, .pq-textarea:focus {
    outline: none;
    border-color: var(--pq-green);
    box-shadow: 0 0 0 3px var(--pq-green-soft);
}
/* Compact inline variants for dense tables (e.g. the DSAR register's per-row status/assignee controls). */
.pq-input-sm, .pq-select-sm { padding: 5px 9px; font-size: calc(13rem / 15.5); }
.pq-field { margin-bottom: 16px; }

/* A mobile number's two fields: the country, then the number as it is written at home, side by side (My details,
   onboarding, a child's registration and record). A class since 2026-11-05, the inline styles it had been, so that the
   pair can give way on a screen narrow for its text (the last block in this sheet). */
.pq-phone-row { display: flex; gap: 8px; align-items: flex-start; }
.pq-phone-row > .pq-select { flex: 0 0 auto; max-width: 46%; }
.pq-phone-row > .pq-input { flex: 1 1 auto; }

/* A field the server refused says so AT the field: aria-invalid="true" on the control (the red edge is this rule, the
   state is the attribute) and the inline line below it, which the control's aria-describedby names, so a screen reader
   hears what was wrong where the answer is typed, not only in the notice at the top of the page. The status-bad ink,
   bold, on the hint's size (2026-11-04, the patient pages' accessibility audit: every server-validated patient form
   put its error in a role="alert" notice tied to no field). */
.pq-input[aria-invalid="true"], .pq-select[aria-invalid="true"], .pq-textarea[aria-invalid="true"] {
    border-color: var(--pq-status-bad-ink);
}
.pq-field-error { font-size: calc(13.5rem / 15.5); line-height: 1.45; font-weight: 600; color: var(--pq-status-bad-ink); margin-top: 4px; }

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

.pq-notice {
    border-radius: var(--pq-radius-sm);
    padding: 12px 16px;
    font-size: calc(14.5rem / 15.5);
    border: 1px solid transparent;
    margin-bottom: 18px;
}
.pq-notice-good { background: var(--pq-status-good-bg); color: var(--pq-status-good-ink); border-color: var(--pq-status-good-border); }
.pq-notice-warn { background: var(--pq-status-warn-bg); color: var(--pq-status-warn-ink); border-color: var(--pq-status-warn-border); }
.pq-notice-bad  { background: var(--pq-status-bad-bg);  color: var(--pq-status-bad-ink);  border-color: var(--pq-status-bad-border); }

.pq-notice-info { background: var(--pq-status-info-bg); color: var(--pq-status-info-ink); border-color: var(--pq-status-info-border); }

/* A section a lapsed or paused practice cannot change (WriteLock, docs/11 §9): the browser disables every control in
   the fieldset, and the fieldset itself draws no box (display: contents — disabling is the attribute's, not the
   layout's), so what it wraps lays out exactly as before, flex items included, with its controls greyed. The resets
   are the fallback for a browser that renders the fieldset anyway. */
.pq-write-lock { display: contents; border: 0; margin: 0; padding: 0; min-width: 0; }
.pq-write-lock :is(input, select, textarea):disabled { opacity: 0.65; cursor: not-allowed; }
.pq-write-lock .pq-btn:disabled { cursor: not-allowed; }
.pq-write-lock-note { font-size: calc(13.5rem / 15.5); color: var(--pq-text-muted); margin: 0 0 10px; }

.alert-warning { /* rebrand the Bootstrap alerts existing pages use */
    background: var(--pq-status-warn-bg);
    color: var(--pq-status-warn-ink);
    border-color: var(--pq-status-warn-border);
    border-radius: var(--pq-radius-sm);
}
.alert-success {
    background: var(--pq-status-good-bg);
    color: var(--pq-status-good-ink);
    border-color: var(--pq-status-good-border);
    border-radius: var(--pq-radius-sm);
}
.alert-danger {
    background: var(--pq-status-bad-bg);
    color: var(--pq-status-bad-ink);
    border-color: var(--pq-status-bad-border);
    border-radius: var(--pq-radius-sm);
}
.alert-info {
    background: var(--pq-status-info-bg);
    color: var(--pq-status-info-ink);
    border-color: var(--pq-status-info-border);
    border-radius: var(--pq-radius-sm);
}

/* rebrand the Bootstrap badges existing pages use (label + tint, like pq-badge) */
.badge.bg-secondary, .badge.bg-success, .badge.bg-info, .badge.bg-warning, .badge.bg-danger {
    border-radius: 999px;
    font-weight: 600;
    padding: 4px 11px;
}
.badge.bg-secondary { background-color: var(--pq-surface-3) !important; color: var(--pq-text-soft) !important; }
.badge.bg-success   { background-color: var(--pq-status-good-bg) !important; color: var(--pq-status-good-ink) !important; }
.badge.bg-info      { background-color: var(--pq-status-info-bg) !important; color: var(--pq-status-info-ink) !important; }
.badge.bg-warning   { background-color: var(--pq-status-warn-bg) !important; color: var(--pq-status-warn-ink) !important; }
.badge.bg-danger    { background-color: var(--pq-status-bad-bg) !important; color: var(--pq-status-bad-ink) !important; }

/* ---- portal chooser (sign-in doors) ---- */

.pq-portal-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 16px;
    margin-top: 26px;
}

.pq-portal-card {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 24px;
    box-shadow: var(--pq-shadow-soft);
    display: flex;
    flex-direction: column;
    gap: 10px;
    transition: border-color var(--pq-dur) var(--pq-ease), box-shadow var(--pq-dur) var(--pq-ease),
                transform var(--pq-dur) var(--pq-ease);
}
/* The Settings hub's grid (one theme per tab, so a tab may hold a single card): auto-FILL keeps a lone card at a
   card's width where auto-fit would stretch it across the page, and the tab's lede already supplies the gap above. */
.pq-settings-grid { grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); margin-top: 0; }
.pq-settings-lede { margin: 0 0 18px; max-width: 640px; color: var(--pq-text-soft); }

.pq-portal-card:hover { border-color: var(--pq-border-strong); box-shadow: var(--pq-shadow-lift); }
@media (prefers-reduced-motion: no-preference) {
    .pq-portal-card:hover { transform: translateY(-2px); }
}
/* h2 AND h3: the heading-level sweep (docs/22 heading-level-skips) moved these to <h2> so they no longer
   skip a level under the page's <h1>, and the size has to stay exactly where it was. Both selectors, rather
   than a rename, because portal cards appear on staff pages too and a card nested under a real h2 is right
   to stay an h3. */
/* The card's heading stops at 11vw as every card heading does (h2, under "base"): "Appointments" is one of them. */
.pq-portal-card h2, .pq-portal-card h3 { margin: 0; font-size: min(calc(21rem / 15.5), 11vw); }
.pq-portal-card p { margin: 0 0 8px; color: var(--pq-text-soft); font-size: calc(14.5rem / 15.5); flex-grow: 1; }
.pq-portal-icon {
    width: 42px; height: 42px;
    border-radius: 12px;
    display: flex; align-items: center; justify-content: center;
    margin-bottom: 4px;
}
.pq-portal-icon.green { background: var(--pq-green-soft); color: var(--pq-green-deep); }
.pq-portal-icon.blue { background: var(--pq-blue-soft); color: var(--pq-blue-deep); }
.pq-portal-icon.ink { background: var(--pq-surface-4); color: var(--pq-ink); }

/* ---- plan picker (registration wizard) ---- */

.pq-plan-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
    gap: 16px;
    margin: 18px 0 8px;
}

.pq-plan-card { position: relative; }
.pq-plan-card input[type="radio"] {
    position: absolute;
    opacity: 0;
    inset: 0;
}
.pq-plan-card label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    height: 100%;
    background: var(--pq-surface);
    border: 1.5px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 20px;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.pq-plan-card input[type="radio"]:checked + label {
    border-color: var(--pq-blue);
    box-shadow: 0 0 0 3px var(--pq-blue-soft);
}
.pq-plan-card input[type="radio"]:focus-visible + label { outline: 2px solid var(--pq-blue-deep); outline-offset: 2px; }
.pq-plan-name { font-family: var(--pq-serif); font-size: calc(21rem / 15.5); color: var(--pq-ink); }
.pq-plan-tagline { font-size: calc(13.5rem / 15.5); color: var(--pq-text-soft); min-height: 2.6em; }
.pq-plan-price { font-size: calc(26rem / 15.5); font-weight: 700; color: var(--pq-ink); }
.pq-plan-price small { font-size: calc(13rem / 15.5); font-weight: 500; color: var(--pq-text-muted); }
.pq-plan-features { list-style: none; margin: 8px 0 0; padding: 0; font-size: calc(13.5rem / 15.5); color: var(--pq-text-soft); }
.pq-plan-features li { padding: 3px 0 3px 22px; position: relative; }
.pq-plan-features li::before {
    content: "✓";
    position: absolute; left: 0;
    color: var(--pq-green-deep);
    font-weight: 700;
}
.pq-plan-flag {
    position: absolute;
    top: -9px; left: 18px;
    background: var(--pq-blue);
    color: var(--pq-on-accent);
    font-size: calc(10.5rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.1em;
    padding: 3px 10px;
    border-radius: 999px;
}

/* ---- patient pay-how: the tick reveals its own instructions ----------------------------------------
   Ticking a payment method used to change nothing on screen until "Save choice" was pressed, because the
   instructions keyed off the SAVED value — so someone who chose Bank transfer was still being told to pay
   online (docs/22 pay-by-two-step-save). Every offered method is rendered; this decides which one is visible.

   The base rules are the FALLBACK: with no :has() support the saved method's block shows and nothing else,
   which is exactly the old behaviour. Where :has() is supported the checked radio wins instead — no JS, no
   round-trip, and "Save choice" goes back to meaning what it says: tell the practice. */
.pq-pay-how-for { display: none; }
.pq-pay-how-for.is-saved { display: block; }

@supports selector(:has(*)) {
    .pq-pay-how:has(input[name="method"]:checked) .pq-pay-how-for { display: none; }
    .pq-pay-how:has(input[name="method"][value="Online"]:checked) .pq-pay-how-for[data-method="Online"],
    .pq-pay-how:has(input[name="method"][value="Cash"]:checked) .pq-pay-how-for[data-method="Cash"],
    .pq-pay-how:has(input[name="method"][value="BankTransfer"]:checked) .pq-pay-how-for[data-method="BankTransfer"],
    .pq-pay-how:has(input[name="method"][value="Revolut"]:checked) .pq-pay-how-for[data-method="Revolut"] {
        display: block;
    }
}

/* billing interval toggle (radio pills) */
.pq-toggle {
    display: inline-flex;
    gap: 4px;
    background: var(--pq-surface-4);
    border: 1px solid var(--pq-border);
    border-radius: 999px;
    padding: 4px;
}
.pq-toggle input[type="radio"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
.pq-toggle label {
    border-radius: 999px;
    padding: 7px 16px;
    font-size: calc(14rem / 15.5);
    font-weight: 600;
    color: var(--pq-text-soft);
    cursor: pointer;
}
.pq-toggle input[type="radio"]:checked + label { background: var(--pq-surface); color: var(--pq-ink); box-shadow: var(--pq-shadow-soft); }
.pq-toggle input[type="radio"]:focus-visible + label { outline: 2px solid var(--pq-blue-deep); outline-offset: 2px; }

/* The colleague's-Clinician-ID field on the register wizard (founder, 2026-08-22): revealed only when "a
   colleague referred me" is the picked source — pure CSS, since the wizard is a static form post with no
   circuit (the release-composer's :has() pattern). Guarded by @supports so a browser without :has() simply
   always shows the optional field; hiding it there would put the referral offer out of reach. */
@supports selector(:has(*)) {
    form:has(#referralSource) .pq-referrer-field { display: none; }
    form:has(#referralSource option[value="professional_referral"]:checked) .pq-referrer-field { display: block; }
}

/* ---- stat tiles (KPI row) ---- */

.pq-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
    gap: 14px;
    margin-bottom: 22px;
}
.pq-stat {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 16px 18px;
    box-shadow: var(--pq-shadow-soft);
}
.pq-stat-label { font-size: calc(13rem / 15.5); color: var(--pq-text-soft); margin-bottom: 6px; }
.pq-stat-value { font-size: calc(27rem / 15.5); font-weight: 600; color: var(--pq-ink); line-height: 1.1; }
.pq-stat-sub { font-size: calc(12.5rem / 15.5); color: var(--pq-text-muted); margin-top: 5px; }

/* ---- simple column chart (monthly trend) ---- */

.pq-chart {
    display: flex;
    align-items: flex-end;
    gap: 18px;
    height: 150px;
    padding-top: 8px;
}
.pq-chart-month {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    height: 100%;
}
.pq-chart-bars {
    flex-grow: 1;
    display: flex;
    align-items: flex-end;
    gap: 2px; /* the 2px surface gap between adjacent marks */
    width: 100%;
    justify-content: center;
}
.pq-chart-bar {
    width: 18px;
    max-width: 40%;
    border-radius: 4px 4px 0 0; /* rounded data end, square baseline */
    /* no min-height: a zero value draws NO mark (the page floors tiny nonzero values instead) */
}
.pq-chart-bar.s1 { background: var(--pq-series-1); }
.pq-chart-bar.s2 { background: var(--pq-series-2); }
.pq-chart-label { font-size: calc(12rem / 15.5); color: var(--pq-text-muted); }
.pq-legend { display: flex; gap: 18px; font-size: calc(13rem / 15.5); color: var(--pq-text-soft); margin-top: 10px; }
.pq-legend-key { display: inline-flex; align-items: center; gap: 7px; }
.pq-legend-swatch { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.pq-legend-swatch.s1 { background: var(--pq-series-1); }
.pq-legend-swatch.s2 { background: var(--pq-series-2); }

/* ---- video call (/call/{reference}) ---- */

.pq-call { max-width: 980px; }
.pq-call-stage {
    position: relative;
    background: var(--pq-call-bg);
    border-radius: var(--pq-radius);
    overflow: hidden;
    aspect-ratio: 16 / 9;
    box-shadow: var(--pq-shadow);
}
/* The other side of the call is a GRID of tiles, one per person (round 8, 2026-09-03 — joint sessions put three
   people in a room). The driver builds the tiles and stamps the count; one tile fills the stage, two sit side by
   side (stacked on a portrait phone), three or four make a 2×2. */
.pq-call-remote { position: absolute; inset: 0; display: grid; grid-template-columns: 1fr; grid-auto-rows: 1fr; gap: 6px; }
.pq-call-remote[data-tiles="2"] { grid-template-columns: 1fr 1fr; }
.pq-call-remote[data-tiles="3"], .pq-call-remote[data-tiles="4"] { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.pq-call-remote .pq-call-media { width: 100%; height: 100%; object-fit: cover; }
.pq-call-tile { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: var(--pq-call-bg); border-radius: 10px; }
.pq-call-remote[data-tiles="1"] .pq-call-tile { border-radius: 0; }
.pq-call-tile .pq-call-media { position: absolute; inset: 0; }
/* who this is — the role label, numbered where two people share one — with the muted badge beside it */
.pq-call-tile-label {
    position: absolute; left: 8px; bottom: 8px; z-index: 2;
    display: flex; align-items: center; gap: 6px;
    background: var(--pq-call-scrim); color: var(--pq-call-ink);
    border-radius: 999px; padding: 3px 10px; font-size: calc(12rem / 15.5); font-weight: 600;
    pointer-events: none;
}
.pq-call-tile-label .pq-call-tile-badge { position: static; padding: 1px 7px; font-size: calc(10.5rem / 15.5); }
/* this person's camera is off / never on (round 7, per tile since round 8): centred in THEIR tile, over the
   hidden frozen frame; transparent to clicks */
.pq-call-tile-off {
    position: absolute; inset: 0; z-index: 1;
    display: flex; align-items: center; justify-content: center;
    padding: 16px; text-align: center; color: var(--pq-call-ink-soft); font-size: calc(14rem / 15.5);
    pointer-events: none;
}
.pq-call-tile-off[hidden] { display: none; }
.pq-call-tile-off span { background: var(--pq-call-scrim-strong); border-radius: 12px; padding: 8px 14px; }
/* A shared screen is READ, not watched: contained, never cover-cropped (cover cuts the text off). Its tile takes
   the stage, and the camera tiles shrink to corner thumbnails — the self-view grammar. */
.pq-call-remote .pq-call-media.pq-call-screen-media { object-fit: contain; background: var(--pq-call-stage); }
.pq-call-tile-screen { grid-column: 1 / -1; grid-row: 1 / -1; border-radius: 0; }
.pq-call-remote:has(.pq-call-tile-screen) .pq-call-tile:not(.pq-call-tile-screen) {
    position: absolute;
    left: 14px;
    bottom: 14px;
    width: clamp(96px, 22%, 200px);
    height: auto;
    aspect-ratio: 4 / 3;
    border-radius: 10px;
    border: 2px solid var(--pq-call-edge);
    background: var(--pq-call-glass);
    z-index: 1;
}
.pq-call-remote:has(.pq-call-tile-screen) .pq-call-tile:not(.pq-call-tile-screen) ~ .pq-call-tile:not(.pq-call-tile-screen) {
    left: calc(14px + clamp(96px, 22%, 200px) + 8px);
}
/* your own camera mirrors (every camera app's convention); the driver drops the class on the back lens */
.pq-call-media.pq-mirror { transform: scaleX(-1); }
/* The stage's STATUS LINE ("Connecting you…", reconnection, the wrap-up prompts) is an OVERLAY, never a flex
   sibling of the video: the moment a remote track filled the stage, a sibling <p> was squeezed into a one-word-
   per-line column down the left edge — "Time is up — please wrap up the session" unreadable (founder, live
   2026-09-03). Centred on an empty stage; a subtitle at the bottom once there is a face, so it never covers it. */
.pq-call-waiting {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    max-width: min(80%, 560px);
    margin: 0;
    padding: 10px 18px;
    border-radius: 12px;
    background: var(--pq-call-scrim-strong);
    color: var(--pq-call-ink);
    font-size: calc(15rem / 15.5);
    line-height: 1.45;
    text-align: center;
    pointer-events: none;
}
.pq-call-remote:has(video.pq-call-media) .pq-call-waiting { top: auto; bottom: 16px; transform: translateX(-50%); }
.pq-call-local {
    position: absolute;
    right: 14px;
    bottom: 14px;
    width: clamp(120px, 22%, 220px);
    aspect-ratio: 4 / 3;
    border-radius: 12px;
    overflow: hidden;
    border: 2px solid var(--pq-call-edge);
    background: var(--pq-call-glass);
}
.pq-call-local .pq-call-media { width: 100%; height: 100%; object-fit: cover; }
.pq-call-local:empty { display: none; }
/* The self-view window is YOURS (request 2026-08-27): drag it (pointer events — touch-action:none keeps a
   phone drag from panning the page), hide it (the control under the stage), mirror it or not (its corner
   button). The driver switches right/bottom for left/top on the first drag and clamps to the stage. */
.pq-call-local { touch-action: none; cursor: grab; }
.pq-call-local:active { cursor: grabbing; }
.pq-call-tile-btn {
    position: absolute; top: 6px; right: 6px; z-index: 2;
    padding: 2px 9px; font-size: calc(11rem / 15.5); line-height: 1.6; min-height: 0;
    background: var(--pq-call-glass-strong); color: var(--pq-call-ink);
    border: 1px solid var(--pq-call-edge-strong); border-radius: 999px;
    opacity: 0.65;
}
.pq-call-local:hover .pq-call-tile-btn, .pq-call-tile-btn:focus-visible { opacity: 1; }
/* the self-view tile says "Muted" too (2026-09-03), in the corner opposite its mirror button */
.pq-call-tile-badge {
    position: absolute; top: 6px; left: 6px; z-index: 2;
    padding: 2px 9px; font-size: calc(11rem / 15.5); line-height: 1.6; font-weight: 600;
    background: var(--pq-call-danger-bg); color: var(--pq-call-danger-ink); border-radius: 999px;
}
.pq-call-tile-badge[hidden] { display: none; }
.pq-call-admit-line { margin-top: 6px; }
.pq-call-controls { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.pq-call-controls .pq-btn.active { background: var(--pq-green-soft); border-color: var(--pq-green); color: var(--pq-green-deep); }

/* FULL SCREEN: the whole call area goes fullscreen (stage + controls + the clinician's side column — the desk
   and the notes, round 8), so the record and the notes stay reachable mid-session. With a side column present
   (the clinician), it sits as a scrollable column beside the stage; without one (the patient), the stage takes
   the full width. */
.pq-call:fullscreen {
    max-width: none;
    background: var(--pq-bg);
    overflow: auto;
    padding: 18px;
}
.pq-call:fullscreen .pq-call-stage { aspect-ratio: auto; height: calc(100vh - 110px); }
.pq-call:fullscreen > .pq-fineprint { display: none; }
.pq-call:fullscreen:has(.pq-call-side) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
    gap: 18px;
    align-items: start;
}
/* on a phone/small tablet there is no room for a side column — it stacks under the stage */
@media (max-width: 899.98px) {
    .pq-call:fullscreen:has(.pq-call-side) { display: block; }
    .pq-call:fullscreen .pq-call-stage { height: min(60vh, 60dvh); }
    .pq-call:fullscreen .pq-call-side { max-height: none; margin-top: 14px; }
}
.pq-call:fullscreen:has(.pq-call-side) [data-call-live] { grid-column: 1; grid-row: 1; }
.pq-call:fullscreen:has(.pq-call-side) .pq-call-lobby { grid-column: 1 / -1; }
.pq-call:fullscreen .pq-call-side {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    max-height: calc(100vh - 36px);
    overflow: auto;
}
.pq-call:fullscreen .pq-call-side .pq-call-desk { margin-top: 0; }

/* the GREEN ROOM — device + service checks before anything transmits */
.pq-call-lobby-grid {
    display: grid;
    grid-template-columns: minmax(220px, 380px) 1fr;
    gap: 20px;
    align-items: start;
}
@media (max-width: 700px) {
    .pq-call-lobby-grid { grid-template-columns: 1fr; }
}
.pq-call-preview {
    aspect-ratio: 4 / 3;
    background: var(--pq-call-bg);
    border-radius: var(--pq-radius-sm);
    overflow: hidden;
}
.pq-call-preview .pq-call-media { width: 100%; height: 100%; object-fit: cover; }
/* the audio-only lobby: the camera-less preview box says so instead of sitting silently black */
.pq-call-preview-note {
    color: var(--pq-call-ink-soft);
    font-size: calc(14rem / 15.5);
    margin: 0;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* the green-room device pickers — one row per real choice, revealed by the driver */
.pq-call-devices { display: grid; gap: 8px; margin: 10px 0; }
.pq-call-devices label { display: flex; align-items: center; gap: 8px; }
.pq-call-devices .pq-select { flex: 1; min-width: 0; }
.pq-call-devices [data-pick-speaker-test] { justify-self: start; }
.pq-mic-meter {
    height: 8px;
    background: var(--pq-surface-4);
    border-radius: 999px;
    overflow: hidden;
    margin: 6px 0 4px;
    max-width: 320px;
}
.pq-mic-meter-fill {
    height: 100%;
    width: 0%;
    border-radius: 999px;
    background: var(--pq-green);
    transition: width 0.08s linear;
}
[data-lobby-service].ok { color: var(--pq-status-good-ink); font-weight: 600; }
/* "out of video minutes" — not a fault, an account state the practice can fix, so it reads warn, not bad */
[data-lobby-service].warn { color: var(--pq-status-warn-ink); font-weight: 600; }
[data-lobby-network].warn { color: var(--pq-status-warn-ink); font-weight: 600; }

/* The green-room recording notice — told before joining, while declining is still a choice. NOT fine print: it
   was 12.5px, which is the smallest text on the page for the heaviest thing said on it, and the reader has
   seconds to take it in (docs/22 recording-disclosed-only-at-the-door). */
.pq-call-recording {
    background: var(--pq-status-warn-bg);
    color: var(--pq-status-warn-ink);
    border-radius: 8px;
    padding: 10px 12px;
    font-size: calc(14.5rem / 15.5);
    line-height: 1.45;
}

.pq-call-recording a { color: inherit; font-weight: 600; }

/* the session countdown chip on the stage — wrap-up tints at 10 and 3 minutes */
.pq-call-timer {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 1;
    background: var(--pq-call-scrim);
    color: var(--pq-call-ink);
    border-radius: 999px;
    padding: 5px 13px;
    font-size: calc(13rem / 15.5);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.pq-call-timer:empty { display: none; }

/* The stage's top-left CHIP STACK (2026-09-03): the countdown, then who is muted — one column, so the chips never
   overlap however many are up, and transparent to clicks. Inside the stack the timer flows instead of pinning. */
.pq-call-chips {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    pointer-events: none;
}
.pq-call-chips .pq-call-timer { position: static; }
.pq-call-mute-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--pq-call-scrim-strong);
    color: var(--pq-call-danger-ink);
    border: 1px solid var(--pq-call-danger-edge);
    border-radius: 999px;
    padding: 5px 13px;
    font-size: calc(13rem / 15.5);
    font-weight: 600;
}
.pq-call-mute-chip[hidden] { display: none; }
/* the weak-connection chip (round 7): warn tint, up only while the link is weak */
.pq-call-quality-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--pq-status-warn-bg);
    color: var(--pq-status-warn-ink);
    border-radius: 999px;
    padding: 5px 13px;
    font-size: calc(13rem / 15.5);
    font-weight: 600;
}
.pq-call-quality-chip[hidden] { display: none; }

/* the in-call recording indicator — mirrors the timer chip, opposite corner, up for the whole session */
.pq-call-recording-chip {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 1;
    background: var(--pq-call-danger-bg);
    color: var(--pq-call-danger-ink);
    border-radius: 999px;
    padding: 5px 13px;
    font-size: calc(13rem / 15.5);
    font-weight: 600;
}
.pq-call-timer.closing { background: var(--pq-status-warn-bg); color: var(--pq-status-warn-ink); }
.pq-call-timer.ending { background: var(--pq-status-bad-bg); color: var(--pq-status-bad-ink); }

.pq-call-notes { max-width: 980px; }

/* the end-of-call card (round 7): takes the stage's place, and the stage's grid cell in full screen */
.pq-call-ended[hidden] { display: none; }
.pq-call-ended:focus { outline: none; }
.pq-call:fullscreen:has(.pq-call-side) .pq-call-ended { grid-column: 1; grid-row: 1; }

/* THE DESK (round 8): the record beside the video for the clinician — the brief and the plan, read-only. A side
   column next to the stage while the call is up on a wide screen (the driver stamps pq-desk-open), stacked under
   it otherwise; always in flow under the green room before the call. */
.pq-call-side { min-width: 0; }
.pq-call-desk { margin-top: 22px; }
.pq-call-desk[hidden] { display: none; }
.pq-call-desk .pq-card { padding: 16px 18px; }
.pq-call-desk-goals { list-style: none; margin: 4px 0 0; padding: 0; display: grid; gap: 4px; }
.pq-call-desk-goals li { display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: calc(13.5rem / 15.5); }
.pq-call-desk-goals li > span:first-child { min-width: 0; }
@media (min-width: 1100px) {
    .pq-call.pq-desk-open:has(.pq-call-side) {
        max-width: 1380px;
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
        gap: 18px;
        align-items: start;
    }
    .pq-call.pq-desk-open:has(.pq-call-side) [data-call-live],
    .pq-call.pq-desk-open:has(.pq-call-side) .pq-call-ended { grid-column: 1; grid-row: 1; }
    .pq-call.pq-desk-open:has(.pq-call-side) > .pq-fineprint { grid-column: 1; grid-row: 2; }
    .pq-call.pq-desk-open:has(.pq-call-side) .pq-call-side { grid-column: 2; grid-row: 1 / span 2; }
    .pq-call.pq-desk-open .pq-call-desk { margin-top: 0; }
}

/* ---- the in-call CHAT (2026-09-03): a drawer over the stage's right edge, so it rides into full screen ----
   Messages live in the two browsers for the length of the call and nowhere else (video-call.js sends them over the
   room's DataTrack — no Presque endpoint, no store); the drawer's footnote says so. With the drawer open the video
   shrinks beside it rather than hiding behind it; on a phone the drawer takes the whole stage. */
.pq-call-chat {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 3;
    width: min(340px, 100%);
    display: flex;
    flex-direction: column;
    background: var(--pq-call-chat-bg);
    color: var(--pq-call-ink);
    border-left: 1px solid var(--pq-call-line);
}
.pq-call-chat[hidden] { display: none; }
.pq-call-stage:has(.pq-call-chat:not([hidden])) .pq-call-remote { right: min(340px, 100%); }
.pq-call-chat-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--pq-call-line);
    font-weight: 600;
}
.pq-call-chat-head .pq-modal-close { color: var(--pq-call-ink-soft); }
.pq-call-chat-head .pq-modal-close:hover { color: var(--pq-call-ink); background: var(--pq-call-line); }
.pq-call-chat-log {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.pq-call-chat-msg {
    max-width: 90%;
    align-self: flex-start;
    padding: 7px 11px;
    border-radius: 12px;
    background: var(--pq-call-line);
    font-size: calc(14rem / 15.5);
    line-height: 1.4;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.pq-call-chat-msg.mine { align-self: flex-end; background: var(--pq-call-mine); color: var(--pq-call-ink); }
.pq-call-chat-msg.notice { align-self: center; background: none; color: var(--pq-call-ink-muted); font-size: calc(12.5rem / 15.5); text-align: center; }
.pq-call-chat-meta { font-size: calc(11rem / 15.5); opacity: 0.75; margin-bottom: 2px; }
.pq-call-chat-form { display: flex; gap: 8px; padding: 10px 14px 6px; border-top: 1px solid var(--pq-call-line); }
.pq-call-chat-form .pq-input { flex: 1; min-width: 0; }
.pq-call-chat-note { font-size: calc(11.5rem / 15.5); color: var(--pq-call-ink-muted); margin: 0; padding: 0 14px 10px; }
/* the unread count on the Chat control while the drawer is closed */
.pq-call-unread {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--pq-status-bad-ink);
    color: var(--pq-on-accent);
    font-size: calc(12rem / 15.5);
    font-weight: 700;
}
.pq-call-unread[hidden] { display: none; }
@media (max-width: 700px) {
    .pq-call-chat { width: 100%; border-left: 0; }
    .pq-call-stage:has(.pq-call-chat:not([hidden])) .pq-call-remote { right: 0; }
}

/* the in-call DEVICE pickers (2026-09-03) — the green room's twins, under the controls, behind the Devices pill */
.pq-call-devices-panel { margin-top: 12px; padding: 14px 16px; }
.pq-call-devices-panel .pq-call-devices { margin: 0; }
.pq-call-devices-panel [data-live-devices-status] { margin: 8px 0 0; }
.pq-call-devices-panel [data-live-devices-status]:empty { display: none; }

/* the notes saved this session — pick one back up to edit it (2026-09-03) */
.pq-call-notes-list { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 6px; }
.pq-call-notes-list[hidden] { display: none; }
.pq-call-notes-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 12px;
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius-sm);
    font-size: calc(13.5rem / 15.5);
}
.pq-call-notes-list li.editing { border-color: var(--pq-green); background: var(--pq-green-soft); }
.pq-call-notes-list .pq-call-note-preview {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--pq-text-soft);
}

/* ---- misc layout helpers ---- */

/* min(…, 100%) lets the track shrink inside a phone-width card instead of
   dragging the page into horizontal scroll — desktop behaviour is unchanged */
.pq-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 16px; }
.pq-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pq-spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* A TIGHTER row — badges, meta and small controls that sit closer than a row of buttons does. It exists
   because the patient pages had already invented it 41 times inline, as `gap: 10px` (29) and `gap: 8px` (12),
   on elements that were ALSO re-declaring the display/align/wrap the class beside it already sets. One value,
   named, beats two arbitrary ones repeated per element (docs/22 `inline-style-density`). Must stay AFTER
   .pq-row and .pq-spread: same specificity, so the later rule is what wins. */
.pq-row-tight { gap: 10px; }
.pq-mt { margin-top: 22px; }
.pq-muted { color: var(--pq-text-muted); }

/* A sub-heading whose LEVEL is semantics and whose SIZE is this class. Several pages had reached for <h3>
   simply to get 19px type directly under the page's <h1>, which a screen reader reads as a missing level
   (docs/22 heading-level-skips). The tag moved to <h2>; this keeps the type where it was. */
.pq-subhead { font-size: calc(19rem / 15.5); }

/* The submit guard parks focus on the form it just disabled the buttons of (ux-enhance.js). It is a
   programmatic target with tabindex="-1", never tabbed to, so a ring around the whole form would be noise. */
[data-pq-guard]:focus { outline: none; }

/* Text for screen readers only. Bootstrap ships this class and, while Bootstrap is loaded, its !important
   copy is what actually wins — but roughly a dozen components already depend on it for text that MUST NOT
   become visible (ScaleInput renders "Mood: 3 out of 5" next to every radio), so the dependency is worth
   owning here rather than inheriting. If Bootstrap is ever dropped this rule is what keeps that text hidden,
   and dropping it would otherwise print all of it on screen. Same clip-path technique as .pq-drop-input. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.pq-money { font-variant-numeric: tabular-nums; }

/* reading-column widths — replace the per-element inline max-widths */
.pq-narrow { max-width: 640px; }
.pq-mid    { max-width: 760px; }
.pq-wide   { max-width: 980px; }
.pq-stack  { display: flex; flex-direction: column; gap: 16px; }
.pq-form   { max-width: 640px; }
.pq-btn-block { width: 100%; }

/* quiet secondary link — AA contrast, unlike a pq-muted anchor */
.pq-link-quiet { color: var(--pq-text-soft); font-size: calc(13.5rem / 15.5); text-decoration: none; }
.pq-link-quiet:hover { color: var(--pq-ink); text-decoration: underline; }

.pq-fineprint { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); }

/* The operator's IDENTIFIER stack under a name (the payments ledger's practice / clinic / clinician ids). A
   two-column grid so the labels line up down the column and the values start at one edge; monospaced because
   these are read character by character against another system, and `user-select: all` so ONE click takes a
   whole 36-character guid — an id that has to be dragged over is an id that gets copied half-right. */
.pq-ids {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0 8px;
    margin: 3px 0 0;
    align-items: baseline;
}
.pq-ids dt {
    font-size: calc(10.5rem / 15.5);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--pq-text-muted);
    white-space: nowrap;
}
.pq-ids dd { margin: 0; font-size: calc(11.5rem / 15.5); line-height: 1.5; min-width: 0; }
.pq-ids code {
    font-family: var(--pq-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: calc(11.5rem / 15.5);
    color: var(--pq-text-soft);
    user-select: all;
    overflow-wrap: anywhere;
}
.pq-ids-note { margin-left: 6px; color: var(--pq-text-muted); }
.pq-optional { font-weight: 400; font-size: calc(13rem / 15.5); color: var(--pq-text-muted); }
.pq-required { font-weight: 400; font-size: calc(13rem / 15.5); color: var(--pq-status-bad-ink); }
.pq-card > h2:first-child, .pq-card > h3:first-child { margin-top: 0; }

/* native inputs: brand accent + gentle invalid tint (only after interaction) */
input[type="checkbox"], input[type="radio"] { accent-color: var(--pq-green); }
.pq-input:user-invalid, .pq-select:user-invalid, .pq-textarea:user-invalid { border-color: var(--pq-red); }

/* checkbox/radio row (invites, notification prefs, payment methods, questionnaires) */
.pq-check { display: flex; align-items: flex-start; gap: 10px; margin: 10px 0; font-size: calc(14.5rem / 15.5); color: var(--pq-text); }
.pq-check input[type="checkbox"], .pq-check input[type="radio"] { width: 17px; height: 17px; margin-top: 2px; flex: none; }

/* weekly-hours day rows (opening hours, room hours): a day checkbox-label and its
   opens/closes time pair. On a phone the label takes the whole line and the two
   times share the next one, so every day reads as a tidy block. */
.pq-day-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
    border-top: 1px solid var(--pq-border);
    flex-wrap: wrap;
}
.pq-day-label {
    width: 150px;
    margin: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 500;
}
.pq-time-input { width: 130px; }
@media (max-width: 640.98px) {
    .pq-day-label { width: 100%; }
    .pq-time-input { flex: 1 1 110px; width: auto; min-width: 0; }
    .pq-day-row .pq-muted { flex: none; }
}

/* a control that BELONGS to the checkbox above it (how far ahead an appointment reminder goes out). Indented to the
   checkbox's own text, and narrow, so it reads as a detail of that choice rather than a fifth question. */
.pq-subfield { margin: 10px 0 0 27px; }
.pq-subfield .pq-select { max-width: 260px; }
.pq-subfield .pq-fineprint { margin-top: 4px; }

/* item rows (trusted devices, passkeys, member lists) */
.pq-item-list { list-style: none; margin: 0; padding: 0; }
.pq-item-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-top: 1px solid var(--pq-border-soft);
    flex-wrap: wrap;
}
.pq-item-row:first-child { border-top: none; }
/* The text column takes what the row's buttons leave it, and at the default text size that is all it asks for, as it
   always was. A larger setting widens the buttons (a label does not wrap), and on a phone that used to squeeze the
   column to a word's width with its text running out of it. So the column asks for room of its own in step with the
   setting, 40px for each px the setting adds to the body's size, and when the row cannot give it the buttons drop
   under the text (the row wraps). */
.pq-item-main { min-width: 0; flex: 1 1 max(0px, var(--pq-text-added) * 40); }
.pq-item-name { font-weight: 600; color: var(--pq-ink); font-size: calc(14.5rem / 15.5); }
.pq-item-meta { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); }

/* meters (funnel, budget, plan-mix bars) */
.pq-meter { height: 8px; background: var(--pq-surface-4); border-radius: 999px; overflow: hidden; }
.pq-meter-fill { height: 100%; border-radius: 999px; background: var(--pq-series-1); }
.pq-meter-fill.s2 { background: var(--pq-series-2); }
.pq-meter-fill.warn { background: var(--pq-amber); }
.pq-meter-fill.bad { background: var(--pq-red); }

/* NEW since you last looked — the unread marker (docs/13 §F-home). A dot, and never only a dot: it always sits
   beside the word "New" or a sentence saying how many, so the state survives colour-blindness, a greyscale
   screen and a screen reader. The row tint is deliberately faint — this marks what to read FIRST, not an alert;
   the returns it marks have already been dealt with by the patient. */
.pq-badge-new { background: var(--pq-status-info-bg); color: var(--pq-status-info-ink); }
.pq-new-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-radius: 50%;
    background: var(--pq-green);
    flex: none;
}
.pq-item-row.pq-item-new { background: var(--pq-green-soft); border-radius: 10px; padding-inline: 10px; }
/* …and the same "this one is new" for a page whose items are CARDS rather than rows (shared notes). The rule
   above is scoped to .pq-item-row and would never have fired there. */
.pq-card.pq-card-new { border-color: var(--pq-green); background: var(--pq-green-soft); }

/* status dots — always pair with a text label; never color alone */
.pq-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--pq-text-muted); }
.pq-dot.good { background: var(--pq-green); }
.pq-dot.warn { background: var(--pq-amber); }
.pq-dot.bad  { background: var(--pq-red); }

/* the directory's fine-tune fold — the sentence box stays; the dropdowns wait behind one calm line, and the
   summary names how many are applied whenever the fold has something to hide */
details.pq-filter-fold > summary {
    cursor: pointer;
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    color: var(--pq-green-deep);
    font-weight: 600;
    padding: 4px 0;
}
details.pq-filter-fold > summary:hover { color: var(--pq-ink); }
details.pq-filter-fold > summary .pq-fineprint { font-weight: 400; }
details.pq-filter-fold[open] > summary { margin-bottom: 10px; }

/* the progress path's "earlier" fold — the recent stretch reads at a glance, the rest waits behind one line */
details.pq-path-fold > summary {
    cursor: pointer;
    color: var(--pq-green-deep);
    font-weight: 600;
    padding: 6px 0;
}
details.pq-path-fold > summary:hover { color: var(--pq-ink); }
details.pq-path-fold[open] > summary { margin-bottom: 6px; }

/* onboarding's optional sections — Address and Emergency contact used to carry the same weight as the name and
   date of birth that actually gate getting in, so the first thing a new patient met was three phone screens of
   fields (docs/22 onboarding-wall-of-fields). The summary keeps the default list-item display: the disclosure
   triangle IS the affordance here, and the padding gives it a thumb-sized target. */
details.pq-form-fold { margin-top: 22px; }
details.pq-form-fold > summary {
    cursor: pointer;
    color: var(--pq-green-deep);
    font-weight: 600;
    font-size: calc(15rem / 15.5);
    padding: 10px 0;
}
details.pq-form-fold > summary:hover { color: var(--pq-ink); }
details.pq-form-fold[open] > summary { margin-bottom: 10px; }

/* ---- anchored sections clear the sticky mobile top bar -----------------------------------------------
   Every pick on the booking page is a navigation, and each one now lands on the question it OPENS rather than
   at the top of the page (docs/22 book-scroll-to-top-each-pick). On a phone the sidebar becomes a sticky top
   bar (MainLayout.razor.css), so without a scroll margin the anchored heading would sit underneath it — the
   patient would arrive at the right section and see the one above. The inset keeps it right in an installed
   home-screen app, where that bar also covers the notch.

   On a phone the whole page clears the bar instead (scroll-padding-top below), so that every section a link or a
   jump brings to the top does, these three and InPageLink's alike (the skip link, the profile's chips, the check-in
   flash's history link, the empty-state calls to action, which also give their section focus: under the bar its
   focus ring would be hidden, WCAG 2.4.11). 73px is the bar's 72px row and its border. These three keep their margin
   on a wider screen, where there is no bar, as breathing room above the question. */
@media (max-width: 640.98px) {
    html {
        scroll-padding-top: calc(73px + env(safe-area-inset-top));
    }
}

@media (min-width: 641px) {
    [id="kind"], [id="when"], [id="confirm"] {
        scroll-margin-top: calc(64px + env(safe-area-inset-top));
    }
}

/* ---- patient chat: two panes on a desktop, ONE at a time on a phone ---------------------------------
   Both columns used to wrap to full width with the conversation list first, so tapping a thread reloaded to a
   page where the newest message sat below the page header and the entire list — the thing the patient tapped
   for was off-screen (docs/22 messages-thread-buried-under-list-on-phone). The flex values are the ones the
   inline styles carried, so nothing changes above 640px. */
.pq-chat { display: flex; gap: 18px; flex-wrap: wrap; align-items: flex-start; }
.pq-chat-list { flex: 1 1 240px; min-width: min(220px, 100%); }
.pq-chat-thread { flex: 2 1 320px; min-width: min(280px, 100%); }

@media (max-width: 640.98px) {
    /* A thread open ⇒ the thread, full width; nothing open ⇒ the list. Never both stacked. The page's back
       crumb is the way between them, so no route is lost. */
    .pq-chat-open .pq-chat-list { display: none; }
    .pq-chat:not(.pq-chat-open) .pq-chat-thread { display: none; }
}

/* chat-thread day marker — a quiet centred chip ("Today", "Yesterday", a date) between bubbles, so a long
   conversation can be walked by day rather than squinted at through 11px timestamps */
.pq-chat-day {
    align-self: center;
    font-size: calc(11.5rem / 15.5);
    font-weight: 600;
    color: var(--pq-text-muted);
    background: var(--pq-surface-2);
    border: 1px solid var(--pq-border-soft);
    border-radius: 999px;
    padding: 2px 10px;
    margin: 4px 0;
}

/* the "presque" open-circle mark, drawn inline as SVG wherever it appears */
.pq-mark { display: inline-block; vertical-align: middle; }

.content { padding-top: 1.1rem; }

/* ============================================================================
   Engagement layer — motion, skeletons, empty states, calendar chips, avatars.
   Everything animated is guarded by prefers-reduced-motion; everything colored
   pairs color with a label or shape (state is never color alone).
   ========================================================================== */

/* ---- page entry: a gentle rise on first paint ---- */

@media (prefers-reduced-motion: no-preference) {
    article.content { animation: pq-rise 0.32s var(--pq-ease) both; }
    @keyframes pq-rise {
        from { opacity: 0; transform: translateY(7px); }
        to   { opacity: 1; transform: none; }
    }
}

/* ---- keyboard focus: one visible ring everywhere ---- */

/* Bootstrap's own :focus-visible rules — .nav-link, .btn, .btn-close, and .form-control/.form-select/.form-check-input's
   :focus — are a class deep, which beats a:focus-visible and button:focus-visible, so every menu link showed Bootstrap's
   faint blue halo (box-shadow, outline: none) in place of this ring (measured 2026-11-04 on .nav-link). Named here at
   their own specificity and loaded later, the house ring wins everywhere, and the halo goes with it. */
a:focus-visible,
.pq-input:focus-visible, .pq-select:focus-visible, .pq-textarea:focus-visible,
button:focus-visible,
.nav-link:focus-visible, .btn:focus-visible, .btn-close:focus-visible,
.form-control:focus-visible, .form-select:focus-visible, .form-check-input:focus-visible {
    outline: 2px solid var(--pq-green-deep);
    outline-offset: 2px;
    border-radius: 6px;
}
.nav-link:focus-visible, .btn:focus-visible, .btn-close:focus-visible,
.form-control:focus-visible, .form-select:focus-visible, .form-check-input:focus-visible { box-shadow: none; }
.pq-input:focus-visible, .pq-select:focus-visible, .pq-textarea:focus-visible { border-radius: var(--pq-radius-sm); }

/* ---- Windows High Contrast and every other forced-colors mode ---- */

/* Forced colors keep borders, outlines and text and throw away backgrounds and box-shadows, so every state drawn with
   one of those was invisible there: a focused field (its green halo), the chosen face and chip of a scale (an inset
   shadow, a filled chip), the booking wizard's chosen time (a filled pill), the current step (a filled dot) and the
   active tab (a lifted pill). Each gets a border, an outline or an underline that survives, in the system Highlight
   colour where the state is a choice; widths and padding trade so nothing moves. The box-sizing is Bootstrap's
   border-box, so a thicker border on a sized box changes nothing outside it. (2026-11-04 audit.) */
@media (forced-colors: active) {
    .pq-input:focus, .pq-select:focus, .pq-textarea:focus { outline: 2px solid Highlight; outline-offset: 1px; }
    .pq-scale-radio:checked + .pq-scale-label { border-width: 3px; border-color: Highlight; padding: 4.5px 2.5px; }
    .pq-scale input[type="radio"]:checked + label { border-width: 3px; border-color: Highlight; }
    .pq-slot.selected { border: 3px solid Highlight; padding: 3.5px 7.5px; }
    .pq-step.current { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }
    .pq-step.current .pq-step-dot { outline: 3px solid Highlight; outline-offset: 1px; }
    .pq-seg-btn.active { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px; }
}

/* ---- icons: inline SVG helpers (stroke inherits currentColor) ---- */

.pq-icon {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    color: inherit;
    vertical-align: -3px;
}

/* ---- skeletons: the page's real shape, shimmering while it loads ---- */

.pq-skeleton {
    position: relative;
    overflow: hidden;
    background: var(--pq-surface-4);
    border-radius: 8px;
    min-height: 12px;
}
@media (prefers-reduced-motion: no-preference) {
    .pq-skeleton::after {
        content: "";
        position: absolute;
        inset: 0;
        transform: translateX(-100%);
        background: linear-gradient(90deg, transparent, var(--pq-shimmer), transparent);
        animation: pq-shimmer 1.5s var(--pq-ease) infinite;
    }
    @keyframes pq-shimmer { to { transform: translateX(100%); } }
}
.pq-skeleton-title { height: 26px; width: 38%; margin-bottom: 16px; }
.pq-skeleton-text  { height: 13px; margin-bottom: 10px; }
.pq-skeleton-row   { height: 44px; border-radius: 10px; margin-bottom: 8px; }
.pq-skeleton-stat  { height: 92px; border-radius: var(--pq-radius); }
.pq-skeleton-card  { height: 140px; border-radius: var(--pq-radius); }
.pq-w-25 { width: 25%; } .pq-w-40 { width: 40%; } .pq-w-60 { width: 60%; } .pq-w-80 { width: 80%; }

/* ---- empty states: warm, helpful, with a next step ---- */

.pq-empty {
    text-align: center;
    padding: 40px 24px;
    border: 1.5px dashed var(--pq-border);
    border-radius: var(--pq-radius);
    background: var(--pq-surface-2);
}
.pq-empty-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--pq-green-soft);
    color: var(--pq-green-deep);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 12px;
}
.pq-empty-icon.blue { background: var(--pq-blue-soft); color: var(--pq-blue-deep); }
.pq-empty-icon.ink  { background: var(--pq-surface-4); color: var(--pq-ink); }
.pq-empty-title {
    font-family: var(--pq-serif);
    font-size: calc(19rem / 15.5);
    color: var(--pq-ink);
    margin: 0 0 6px;
}
.pq-empty-hint {
    color: var(--pq-text-soft);
    font-size: calc(14.5rem / 15.5);
    max-width: 38em;
    margin: 0 auto 16px;
}
.pq-empty .pq-empty-hint:last-child { margin-bottom: 0; }

/* ---- segmented control (anchor or button pills — view switchers) ---- */

.pq-seg {
    display: inline-flex;
    gap: 4px;
    background: var(--pq-surface-4);
    border: 1px solid var(--pq-border);
    border-radius: 999px;
    padding: 4px;
}
.pq-seg-btn {
    border: none;
    background: transparent;
    border-radius: 999px;
    padding: 6px 14px;
    font-size: calc(13.5rem / 15.5);
    font-weight: 600;
    color: var(--pq-text-soft);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--pq-dur) var(--pq-ease), color var(--pq-dur) var(--pq-ease);
}
.pq-seg-btn:hover { color: var(--pq-ink); }
.pq-seg-btn.active { background: var(--pq-surface); color: var(--pq-ink); box-shadow: var(--pq-shadow-soft); }

/* List tabs: the same segmented control, used as the page's primary filter (Upcoming / Past / Cancelled / No shows)
   rather than as a compact view switcher — so it breathes, and it WRAPS instead of overflowing on a phone. */
.pq-tabs { display: flex; flex-wrap: wrap; margin-bottom: 18px; max-width: 100%; }
.pq-tabs .pq-seg-btn { display: inline-flex; align-items: center; gap: 7px; }

/* A tab strip that STAYS REACHABLE while its tab is being read (the patient record — docs/13 §F-record-tabs).
   Tabs only shorten a page if switching stays one movement; a clinician half-way down a record who has to scroll
   back to the header first has the long-page problem again, in a new shape. The dock, not the pill row itself, is
   what sticks: it carries the page background, so nothing shows through the pill's rounded corners on the way
   past. Desktop only — on a phone the sidebar is already a sticky top bar, and two stacked sticky strips would
   eat the screen the record needs. */
@media (min-width: 641px) {
    .pq-tabs-dock {
        position: sticky;
        top: 0;
        z-index: 3;
        background: var(--pq-bg);
        padding: 10px 0 4px;
    }
    .pq-tabs-dock .pq-tabs { margin-bottom: 6px; }
}

/* The per-tab row count. Muted and secondary — it annotates the label, it is not a notification badge, so it never
   competes with the tab name or reads as something needing attention. */
.pq-seg-count {
    font-size: calc(12rem / 15.5);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    /* The body text tier, not the muted one: muted on this translucent chip measured 4.36:1 on Appointments and
       Payments (2026-11-04), under the 4.5:1 a 12px label needs; --pq-text clears 5.5:1 on both grounds the strip sits
       on (DesignTokenContrastTests computes the blend). */
    color: var(--pq-text);
    background: var(--pq-shade);
    border-radius: 999px;
    padding: 1px 7px;
    min-width: 20px;
    text-align: center;
}
.pq-seg-btn.active .pq-seg-count { background: var(--pq-green-soft); color: var(--pq-green-deep); }

/* ---- tables: horizontal scroll on narrow screens instead of overflow ---- */

.pq-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---- flash notices: pq-notice that slides in after a POST-redirect ---- */

/* The class is on 36 notices; the data attribute — which ux-enhance.js uses to find and focus the flash — is on
   72. So the same "Saved" slid in on half the pages and simply appeared on the rest, for no reason a reader could
   see (docs/22 flash-animation-half-pages). Trigger on the attribute every post-redirect notice already carries.
   Still pure CSS: the no-JS path animates too, and reduced-motion still switches it off. */
@media (prefers-reduced-motion: no-preference) {
    .pq-flash, [data-pq-flash] { animation: pq-flash-in 0.34s var(--pq-ease) both; }
    @keyframes pq-flash-in {
        from { opacity: 0; transform: translateY(-6px); }
        to   { opacity: 1; transform: none; }
    }
}

/* ---- wayfinding: back-crumbs, guided walkthroughs, next-step nudges ---- */

/* "← Back to X" above a subpage's head, so nobody wonders where they are */
.pq-crumbs { margin: 20px 0 -14px; font-size: calc(13.5rem / 15.5); }
.pq-crumbs a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--pq-text-soft);
    text-decoration: none;
    border-radius: 999px;
    padding: 4px 10px 4px 6px;
    transition: background var(--pq-dur) var(--pq-ease), color var(--pq-dur) var(--pq-ease);
}
.pq-crumbs a:hover { background: var(--pq-surface-3); color: var(--pq-ink); }
.pq-crumbs a::before { content: "←"; font-size: calc(14rem / 15.5); line-height: 1; }

/* The crisis line as a DOOR (founder question 3). A <details>, so it opens with no JavaScript and survives
   enhanced navigation. The summary carries the disclaimer — it is readable without any interaction, which is
   what keeps this safe on a surface whose UX is a [LEGAL SIGN-OFF REQUIRED] item (docs/02 §15). Only the two
   actions sit behind the disclosure, and they arrive with room to tap. */
details.pq-crisis > summary {
    list-style: none;
    cursor: pointer;
    color: var(--pq-text-muted);
}
details.pq-crisis > summary::-webkit-details-marker { display: none; }
details.pq-crisis > summary:hover { color: var(--pq-ink); }

/* The affordance. A chip rather than a bare link: at the foot of a menu the thing a person in difficulty must
   find should not look like the rest of the fine print. */
.pq-crisis-more {
    display: inline-block;
    margin-left: 2px;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--pq-surface-3);
    color: var(--pq-ink);
    font-weight: 600;
    white-space: nowrap;
}
.pq-crisis-more::after { content: " →"; font-weight: 400; }
details.pq-crisis[open] .pq-crisis-more::after { content: " ×"; }
details.pq-crisis > summary:hover .pq-crisis-more { background: var(--pq-green); color: var(--pq-on-accent); }

.pq-crisis-help { margin-top: 10px; }
.pq-crisis-help p { margin: 0 0 8px; }

/* The compact form the sidebar footer uses — sized like the fine print it replaced, but with a real button. */
.pq-crisis-compact { margin: 12px 0 0; font-size: calc(12rem / 15.5); line-height: 1.45; }

/* collapsible guided walkthrough (a <details> — CSS-only, static-SSR safe) */
.pq-walkthrough {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    box-shadow: var(--pq-shadow-soft);
    margin-bottom: 22px;
    overflow: hidden;
}
.pq-walkthrough > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 22px;
    font-weight: 600;
    color: var(--pq-ink);
    font-size: calc(15rem / 15.5);
}
.pq-walkthrough > summary::-webkit-details-marker { display: none; }
.pq-walkthrough > summary::after {
    content: "+";
    margin-left: auto;
    font-size: calc(20rem / 15.5);
    font-weight: 400;
    color: var(--pq-text-muted);
    line-height: 1;
    transition: transform var(--pq-dur) var(--pq-ease);
}
.pq-walkthrough[open] > summary::after { transform: rotate(45deg); }
.pq-walkthrough > summary:hover { background: var(--pq-surface-2); }
.pq-walkthrough > summary .pq-portal-icon { margin-bottom: 0; flex: none; width: 36px; height: 36px; }
.pq-walkthrough-sub { font-weight: 400; color: var(--pq-text-soft); font-size: calc(13.5rem / 15.5); display: block; }
.pq-walkthrough-body { padding: 4px 22px 18px; }

/* the ordered checklist inside a walkthrough (or a hub page) */
.pq-checklist { list-style: none; margin: 0; padding: 0; counter-reset: pq-step; }
.pq-checklist > li {
    counter-increment: pq-step;
    position: relative;
    padding: 10px 0 10px 44px;
    border-top: 1px solid var(--pq-border-soft);
}
.pq-checklist > li:first-child { border-top: none; }
.pq-checklist > li::before {
    content: counter(pq-step);
    position: absolute;
    left: 0;
    top: 9px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--pq-green-soft);
    color: var(--pq-green-deep);
    font-size: calc(13.5rem / 15.5);
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* The same list as a MAP rather than a sequence (Home's "how Presque works"): places, not numbered steps. */
.pq-checklist-plain > li { padding-left: 22px; }
.pq-checklist-plain > li::before { content: ""; top: 18px; width: 8px; height: 8px; }
.pq-checklist a { font-weight: 600; }
.pq-checklist-hint { display: block; font-size: calc(13rem / 15.5); color: var(--pq-text-soft); margin-top: 1px; }

/* The patient's "Getting started" checklist on Home (PatientGettingStarted.razor): steps ticked from what the
   dashboard knows, a count, and the one step that is theirs to take next picked out. No script — every state is
   a class the server wrote. */
.pq-start {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    box-shadow: var(--pq-shadow-soft);
    margin-bottom: 22px;
    padding: 18px 22px;
}
/* Below Home's cards the checklist follows the grid directly, which carries no bottom margin of its own. */
.pq-portal-grid + .pq-start { margin-top: 22px; }
.pq-start-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pq-start-head .pq-portal-icon { margin-bottom: 0; flex: none; width: 36px; height: 36px; }
.pq-start-heading { flex: 1 1 220px; min-width: 0; }
.pq-start-heading h2 { margin: 0; font-size: min(calc(19rem / 15.5), 11vw); }
.pq-start-sub { margin: 2px 0 0; color: var(--pq-text-soft); font-size: calc(13.5rem / 15.5); }
.pq-start-count { font-weight: 600; color: var(--pq-green-deep); font-size: calc(13.5rem / 15.5); white-space: nowrap; }
.pq-start-bar { height: 6px; border-radius: 3px; background: var(--pq-surface-3); margin: 14px 0 6px; overflow: hidden; }
.pq-start-bar > span { display: block; height: 100%; border-radius: 3px; background: var(--pq-green); }
.pq-start-steps { list-style: none; margin: 0; padding: 0; }
.pq-start-steps > li {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) auto;
    align-items: start;
    gap: 8px 12px;
    padding: 12px 0;
    border-top: 1px solid var(--pq-border-soft);
}
.pq-start-steps > li > .pq-btn { align-self: center; }
/* On a phone the step's button drops under its text, in line with it, rather than squeezing the sentence. */
@media (max-width: 640.98px) {
    .pq-start-steps > li > .pq-btn { grid-column: 2; justify-self: start; }
}
.pq-start-steps > li:first-child { border-top: none; }
.pq-start-mark {
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid var(--pq-border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: calc(14rem / 15.5);
    font-weight: 700;
    line-height: 1;
}
.pq-start-steps > li.done .pq-start-mark { background: var(--pq-green-soft); border-color: var(--pq-green-soft); color: var(--pq-green-deep); }
.pq-start-steps > li.next .pq-start-mark { border-color: var(--pq-green-deep); }
.pq-start-text { min-width: 0; }
.pq-start-title { display: block; font-weight: 600; color: var(--pq-ink); }
.pq-start-title .pq-badge { margin-left: 6px; vertical-align: middle; }
.pq-start-steps > li.done .pq-start-title { color: var(--pq-text-soft); font-weight: 500; }
.pq-start-hint { display: block; font-size: calc(13rem / 15.5); color: var(--pq-text-soft); margin-top: 1px; }
.pq-start-places { border-top: 1px solid var(--pq-border-soft); padding-top: 12px; font-size: calc(13.5rem / 15.5); }
.pq-start-places-label { color: var(--pq-text-soft); }
.pq-start-places ul { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 4px; }
.pq-start-places a { font-weight: 600; }
.pq-start-places li span { color: var(--pq-text-soft); }
.pq-start-places li span::before { content: "— "; }
.pq-start-dismiss { margin: 12px 0 0; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.pq-start-dismiss .pq-linklike { margin: 0; color: var(--pq-text-muted); font-size: calc(13rem / 15.5); }

/* "Next: …" nudge appended to a saved notice — carries the person to the next step */
.pq-next {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    white-space: nowrap;
}
.pq-next::after { content: "→"; }

/* ---- reference codes (Presque ID / Clinician ID) ---- */

.pq-ref-code {
    font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: calc(18rem / 15.5);
    letter-spacing: 1px;
    color: var(--pq-green-deep);
    margin: 0;
}

/* ---- anonymous landing hero ---- */

.pq-hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--pq-surface);
    border: 1px solid var(--pq-border);
    border-radius: 999px;
    padding: 6px 14px;
    font-size: calc(13rem / 15.5);
    font-weight: 600;
    color: var(--pq-text-soft);
    margin-bottom: 16px;
}
.pq-hero-features {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: 18px;
    margin-top: 38px;
    max-width: 900px;
}
.pq-hero-feature { display: flex; gap: 12px; align-items: flex-start; }
.pq-hero-feature .pq-portal-icon { margin-bottom: 0; flex: none; }
.pq-hero-feature h2, .pq-hero-feature h3 {
    font-family: var(--pq-sans);
    font-size: calc(16rem / 15.5);
    font-weight: 600;
    margin: 2px 0 3px;
    letter-spacing: 0;
}
.pq-hero-feature p { margin: 0; font-size: calc(13.5rem / 15.5); color: var(--pq-text-soft); }

/* the open-circle mark draws itself in on the landing page */
@media (prefers-reduced-motion: no-preference) {
    .pq-mark-draw circle {
        stroke-dashoffset: 88;
        animation: pq-draw 1.1s var(--pq-ease) 0.15s forwards;
    }
    @keyframes pq-draw { to { stroke-dashoffset: 0; } }
}

/* ---- avatars: initials in a tinted circle ---- */

.pq-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--pq-green-soft);
    color: var(--pq-green-deep);
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: calc(12.5rem / 15.5);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.pq-avatar.blue { background: var(--pq-blue-soft); color: var(--pq-blue-deep); }
.pq-avatar.ink  { background: var(--pq-surface-4); color: var(--pq-ink); }
.pq-avatar-lg   { width: 44px; height: 44px; font-size: calc(15rem / 15.5); }

/* ---- wizard steps (registration, onboarding) ---- */

.pq-steps {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 4px 0 24px;
}
.pq-step {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: calc(13.5rem / 15.5);
    font-weight: 600;
    color: var(--pq-text-muted);
}
.pq-step-dot {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--pq-surface-4);
    color: var(--pq-text-soft);
    font-size: calc(13rem / 15.5);
    font-weight: 700;
}
.pq-step.current { color: var(--pq-ink); }
.pq-step.current .pq-step-dot { background: var(--pq-green); color: var(--pq-on-accent); }
.pq-step.done .pq-step-dot { background: var(--pq-green-soft); color: var(--pq-green-deep); }
.pq-step-line { flex: none; width: 26px; height: 1.5px; background: var(--pq-border); }

/* ---- calendar: day cards, appointment chips, open slots, month grid ----
   Status is chip label text + left-edge accent together, never color alone. */

.pq-cal-grid { display: grid; gap: 10px; grid-template-columns: 1fr; }
@media (min-width: 900px) {
    .pq-cal-grid.week { grid-template-columns: repeat(7, 1fr); }
}

.pq-cal-day {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius-sm);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
}
.pq-cal-day.today { border-color: var(--pq-green); box-shadow: 0 0 0 1px var(--pq-green) inset; }

.pq-cal-day-head {
    padding: 8px 10px 7px;
    text-align: center;
    border-bottom: 1px solid var(--pq-border-soft);
    background: var(--pq-surface-2);
    line-height: 1.25;
}
.pq-cal-day.today .pq-cal-day-head { background: var(--pq-green-soft); }
.pq-cal-dow {
    display: block;
    font-size: calc(11.5rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--pq-text-muted);
}
.pq-cal-day.today .pq-cal-dow { color: var(--pq-green-deep); }
.pq-cal-date { display: block; font-size: calc(13rem / 15.5); color: var(--pq-text-soft); }

.pq-cal-day-body {
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1;
}
.pq-cal-quiet {
    color: var(--pq-text-muted);
    font-size: calc(12.5rem / 15.5);
    text-align: center;
    padding: 12px 0;
    margin: 0;
}

.pq-appt {
    border: 1px solid var(--pq-border-soft);
    border-left: 3px solid var(--pq-text-muted);
    border-radius: 10px;
    padding: 7px 9px;
    font-size: calc(12.5rem / 15.5);
    background: var(--pq-surface-2);
    min-width: 0;
}
.pq-appt.requested { border-left-color: var(--pq-amber); }
.pq-appt.confirmed { border-left-color: var(--pq-green); }
.pq-appt.completed { border-left-color: var(--pq-blue); }
.pq-appt.cancelled, .pq-appt.noshow { border-left-color: var(--pq-border-strong); opacity: 0.72; }
.pq-appt-time {
    font-weight: 700;
    color: var(--pq-ink);
    font-variant-numeric: tabular-nums;
}
.pq-appt-meta { color: var(--pq-text-soft); overflow-wrap: anywhere; }
.pq-appt-ref {
    color: var(--pq-text-muted);
    margin-top: 3px;
    font-variant-numeric: tabular-nums;
    font-size: calc(11rem / 15.5);
    letter-spacing: 0.02em;
}
.pq-appt-who {
    font-weight: 600;
    color: var(--pq-ink);
    overflow-wrap: anywhere;
}

/* The chip's Details opener — reads like a link, sized for the chip. */
.pq-linklike {
    border: none;
    background: none;
    padding: 0;
    margin-top: 6px;
    cursor: pointer;
    color: var(--pq-green-deep);
    font-weight: 600;
    font-size: calc(12rem / 15.5);
    display: inline-block;
}
.pq-linklike:hover { text-decoration: underline; }

/* Modal pop-over (native <dialog>, opened by ux-enhance.js): a focused card over a dimmed calendar. */
.pq-modal {
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 0;
    max-width: 500px;
    width: calc(100vw - 32px);
    /* on a short phone screen the card scrolls inside itself, never off-screen */
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    box-shadow: var(--pq-shadow-modal);
    text-align: left;
}
.pq-modal::backdrop { background: var(--pq-backdrop); }
.pq-modal-body { padding: 20px 22px; font-size: calc(14rem / 15.5); }
.pq-modal-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 12px; }
.pq-modal-title { font-family: var(--pq-serif); font-size: calc(20rem / 15.5); color: var(--pq-ink); margin: 0; }
.pq-modal-close {
    border: none;
    background: none;
    cursor: pointer;
    font-size: calc(22rem / 15.5);
    line-height: 1;
    color: var(--pq-text-muted);
    padding: 2px 6px;
    border-radius: 6px;
}
.pq-modal-close:hover { color: var(--pq-ink); background: var(--pq-green-soft); }
.pq-appt-dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 3px 10px;
    margin: 0 0 6px;
    font-size: calc(12rem / 15.5);
}
.pq-appt-dl dt { color: var(--pq-text-muted); font-weight: 600; }
.pq-appt-dl dd { margin: 0; color: var(--pq-ink); overflow-wrap: anywhere; min-width: 0; }
.pq-appt-join { display: flex; flex-wrap: wrap; gap: 6px; }
.pq-appt-join .pq-btn { font-size: calc(12rem / 15.5); padding: 5px 12px; }

/* Section headings for the appointments / payments list pages (Today · Coming up · …). */
.pq-section-title {
    font-family: var(--pq-serif);
    font-size: calc(21rem / 15.5);
    color: var(--pq-ink);
    margin: 26px 0 10px;
}

/* The first heading INSIDE a card already sits below the card's own padding — the standard top margin would
   double it up and leave the group looking detached from the card it belongs to. */
.pq-section-title-first { margin-top: 0; }

/* ---- invoice / receipt (/appointments/invoice/{reference}) — printable ---- */
.pq-invoice { max-width: 640px; }
.pq-invoice-logo { max-height: 52px; max-width: 200px; display: block; margin-bottom: 8px; border-radius: 6px; }
.pq-invoice-lines {
    width: 100%;
    border-collapse: collapse;
    margin: 14px 0;
    font-size: calc(14rem / 15.5);
}
.pq-invoice-lines th, .pq-invoice-lines td {
    text-align: left;
    padding: 8px 2px;
    border-bottom: 1px solid var(--pq-border-soft);
}
.pq-invoice-lines tfoot th { border-bottom: none; border-top: 2px solid var(--pq-ink); font-size: calc(15rem / 15.5); }

@media print {
    /* Only the document: the app chrome (sidebar, crumbs, buttons) disappears from the paper. The floating
       report-a-problem control and its toast are named explicitly — they are position:fixed, so without this
       they print into the corner of a therapy letter or an insurer's receipt. */
    .sidebar, .pq-no-print, .rp-root, .rp-toast { display: none !important; }
    main { padding: 0 !important; }
    .content { padding: 0 !important; }
    .pq-invoice { max-width: none; border: none; box-shadow: none; }
    /* …and the reverse: the seal must be ON the paper, because paper may be the only copy the recipient holds.
       The system was purely subtractive until now (.pq-no-print); this is its one additive rule. */
    .pq-print-only { display: block !important; }
    /* A seal broken over a page turn is a QR code that cannot be scanned and a JWS in two pieces on two sheets. */
    .pq-seal { break-inside: avoid; page-break-inside: avoid; }
}

/* The cryptographic seal on a letter (docs/13 §F-letters-seal). Quiet on screen — it is evidence, not chrome —
   and small enough on paper that a ~450-character JWS fits without pushing the letter onto a second page. */
.pq-seal {
    margin-top: 18px;
    padding: 10px 12px;
    border: 1px solid var(--pq-border);
    border-radius: 8px;
    background: var(--pq-surface-2);
}

.pq-seal-head {
    font-size: calc(11rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--pq-text-muted);
    margin-bottom: 4px;
}

/* THE LETTER'S QR CODE (docs/13 §F-letters-seal). Sized in MILLIMETRES, deliberately: a QR is only scannable if
   its modules are physically big enough, and a v2 seal needs ~117 of them a side. 46mm gives ~0.39mm per module,
   which a phone camera reads comfortably off paper — and is the size the Swiss QR-bill standard settled on for
   exactly this job, on exactly this kind of document. THIS is the number to change if the code looks too heavy on
   the page; below about 38mm it stops scanning reliably, which is worse than not printing one. */
.pq-seal-qr {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin: 10px 0;
}
.pq-qr {
    width: 46mm;
    height: 46mm;
    flex: none;
    /* Crisp module edges: a QR is squares, and smoothing them is what makes a scan fail. */
    shape-rendering: crispEdges;
    background: var(--pq-paper);
}
.pq-qr-caption { margin: 0; max-width: 26em; }

/* The camera preview on /verify while scanning a letter. Capped rather than full-width: the reader is aiming a
   page at a lens, and a viewfinder the size of the window makes that harder, not easier. */
.pq-scan-view {
    display: block;
    width: 100%;
    max-width: 320px;
    margin-top: 10px;
    border-radius: var(--pq-radius-sm);
    background: var(--pq-call-bg);
}

@media (max-width: 640.98px) {
    /* On a phone the code is being LOOKED at, not scanned off the screen by another phone — so it can shrink to
       leave room for the caption beside it. */
    .pq-qr { width: 38mm; height: 38mm; }
}

/* The text a v2 seal carries, shown inside a verdict so the reader can compare it with the page in their hand
   (docs/13 §F-letters-seal). Scrolls rather than pushing the verdict off screen — the sentence explaining what to
   do with it has to stay visible above a letter's worth of prose. */
.pq-seal-text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 22em;
    overflow-y: auto;
    margin: 8px 0;
    padding: 10px 12px;
    border-radius: var(--pq-radius-sm);
    background: var(--pq-surface);
    font-family: inherit;
    font-size: calc(13.5rem / 15.5);
    line-height: 1.55;
    color: var(--pq-ink);
}

/* The receipt a seal carries, shown inside a verdict on /verify (docs/13 §F-pay-seal) so the reader can compare it
   field by field with the document in their hand. A table rather than prose because that IS how a receipt is read —
   an insurer checks the amount against the amount, not a paragraph against a paragraph. Scrolls sideways rather
   than forcing the verdict box wide: a long practice name must not push the figures off a phone. */
.pq-receipt-check {
    width: 100%;
    display: block;
    overflow-x: auto;
    border-collapse: collapse;
    margin: 10px 0;
    font-size: calc(13.5rem / 15.5);
    line-height: 1.5;
}
.pq-receipt-check th,
.pq-receipt-check td {
    text-align: left;
    vertical-align: top;
    padding: 5px 10px 5px 0;
    border-bottom: 1px solid var(--pq-line);
}
.pq-receipt-check th {
    font-weight: 500;
    color: var(--pq-text-soft);
    white-space: nowrap;
    width: 1%;
}
.pq-receipt-check td {
    color: var(--pq-ink);
    /* Figures line up under each other, which is the whole point of reading a receipt in a column. */
    font-variant-numeric: tabular-nums;
}
.pq-receipt-check tr:last-child th,
.pq-receipt-check tr:last-child td { border-bottom: 0; }

/* The compare callout inside a receipt verdict (docs/13 §F-pay-seal). Deliberately the loudest thing in the box
   after the verdict line itself: a receipt's seal rides inside its QR code, so editing the printed page leaves the
   signature intact and the check still passes — the mismatch is caught by a human reading these figures against
   the paper, and nothing else. A sentence people skim would lose the one fraud this feature exists to catch. */
.pq-receipt-compare {
    margin: 12px 0 4px;
    padding: 10px 12px;
    border-left: 3px solid currentColor;
    border-radius: var(--pq-radius-sm);
    background: var(--pq-shade);
    font-size: calc(14rem / 15.5);
    line-height: 1.55;
}

.pq-seal-code {
    font-family: var(--pq-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: calc(8.5rem / 15.5);
    line-height: 1.35;
    margin: 6px 0 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;   /* an unbroken base64url run must wrap, not overflow the page */
    color: var(--pq-text-soft);
}

/* Hidden on screen, revealed by the @media print rule above. */
.pq-print-only { display: none; }

.pq-slot {
    display: block;
    border: 1.5px dashed var(--pq-green-line);
    border-radius: 10px;
    padding: 5px 9px;
    font-size: calc(12.5rem / 15.5);
    color: var(--pq-green-deep);
    text-decoration: none;
    font-variant-numeric: tabular-nums;
    transition: background var(--pq-dur) var(--pq-ease), border-color var(--pq-dur) var(--pq-ease);
}
.pq-slot:hover { background: var(--pq-green-soft); border-color: var(--pq-green); color: var(--pq-green-deep); }

/* a slot whose time has passed — still clickable (recording a past session) but visually spent */
.pq-slot.past { border-color: var(--pq-border); color: var(--pq-text-muted); }
.pq-slot.past:hover { background: var(--pq-surface-3); border-color: var(--pq-border-strong); color: var(--pq-text-soft); }
/* The front-desk booking wizard's chosen slot (docs/13 §F-frontdesk) — the one time the walk is committed to. */
.pq-slot.selected { background: var(--pq-green); border-color: var(--pq-green-deep); color: var(--pq-on-accent); }
.pq-slot.selected:hover { background: var(--pq-green-deep); color: var(--pq-on-accent); }

/* min-width keeps seven day columns legible; on a phone the wrapping
   .pq-table-wrap pans sideways instead of crushing the cells */
.pq-cal-month { width: 100%; min-width: 560px; border-collapse: separate; border-spacing: 5px; table-layout: fixed; }
.pq-cal-month th {
    font-size: calc(11rem / 15.5);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--pq-text-muted);
    text-align: center;
    padding-bottom: 2px;
}
.pq-cal-month td {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: 10px;
    vertical-align: top;
    padding: 6px 8px;
    height: 6.5rem;
}
.pq-cal-month td.out { background: transparent; border-style: dashed; }
.pq-cal-month td.out .pq-cal-daynum { color: var(--pq-text-muted); }
.pq-cal-month td.today { border-color: var(--pq-green); box-shadow: 0 0 0 1px var(--pq-green) inset; }
.pq-cal-daynum { font-size: calc(12.5rem / 15.5); font-weight: 700; color: var(--pq-ink); margin-bottom: 2px; }
.pq-cal-mini {
    font-size: calc(11.5rem / 15.5);
    color: var(--pq-text-soft);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}
.pq-cal-mini .status { color: var(--pq-text-muted); }

/* ---- validation & framework chrome (kept from the template) ---- */

.valid.modified:not([type=checkbox]) { outline: 1px solid var(--pq-green); }
.invalid { outline: 1px solid var(--pq-red); }
.validation-message { color: var(--pq-danger); }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--pq-status-bad-ink);
    padding: 1rem 1rem 1rem 3.7rem;
    color: var(--pq-on-accent);
    border-radius: var(--pq-radius-sm);
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input { border-color: var(--pq-border-control); }

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ── Dictation: the recording state ───────────────────────────────────────────
   An open microphone in a clinical setting is not an ordinary UI state. The
   clinician's voice — naming their patient — is leaving the building while this
   is on (docs/02 §12, Path B), so "am I recording?" must be answerable from
   across the room, not by reading a sentence. The button carries the colour and
   the pulse; the status line carries the countdown and turns live.

   Colour is NOT the only signal: the button's label also flips to "Stop
   dictating" and aria-pressed tracks it, so this reads the same to someone who
   cannot distinguish the red. The pulse is motion-guarded — a clinician who has
   asked for reduced motion still gets the colour and the dot. */
.pq-btn.pq-recording {
    border-color: var(--pq-danger);
    color: var(--pq-danger);
    background: color-mix(in srgb, var(--pq-danger) 8%, transparent);
}
.pq-btn.pq-recording:hover:not(:disabled) {
    background: color-mix(in srgb, var(--pq-danger) 14%, transparent);
    color: var(--pq-danger);
}
.pq-recording-status {
    color: var(--pq-danger);
    font-variant-numeric: tabular-nums; /* the countdown must not jitter as digits change */
}

@media (prefers-reduced-motion: no-preference) {
    .pq-btn.pq-recording { animation: pq-recording-pulse 1.6s ease-in-out infinite; }
    @keyframes pq-recording-pulse {
        0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pq-danger) 40%, transparent); }
        50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--pq-danger) 0%, transparent); }
    }
}

/* ── Photographed handwritten notes (docs/08 §D, "photograph your notes") ─────
   The thumbnails of the pages a clinician has photographed onto a note. Each is
   a page of THEIR notepad, so it is drawn as a page: portrait, the number in
   its corner (the order a converted transcript reads in), and a remove control
   that is a real button. Colour carries nothing here — the number and the
   control's label say everything — so the strip reads the same in forced
   colours and to a screen reader (each image has alt text naming its page).
   Wraps rather than scrolls; tokens only, so the dark theme needs nothing. */
.pq-photo-strip {
    list-style: none;
    margin: 10px 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}
.pq-photo-strip:empty { display: none; }
.pq-photo {
    position: relative;
    margin: 0;
    border: 1px solid var(--pq-border-control);
    border-radius: var(--pq-radius-sm);
    background: var(--pq-surface-2);
    overflow: hidden;
    line-height: 0; /* an <img> in a block would otherwise leave a descender gap under the page */
}
.pq-photo img { display: block; width: 96px; height: 128px; object-fit: cover; }
.pq-photo a { display: block; }
.pq-photo-working {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 128px;
    padding: 8px;
    text-align: center;
    line-height: 1.3;
}
.pq-photo-n {
    position: absolute;
    left: 4px;
    bottom: 4px;
    min-width: 22px;
    padding: 2px 6px;
    border-radius: 999px;
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-control);
    color: var(--pq-ink);
    font-size: calc(12rem / 15.5);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    text-align: center;
}
.pq-photo-remove {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--pq-border-control);
    border-radius: 999px;
    background: var(--pq-surface);
    color: var(--pq-ink);
    font-size: calc(16rem / 15.5);
    line-height: 1;
    cursor: pointer;
}
.pq-photo-remove:hover:not(:disabled) { color: var(--pq-danger); border-color: var(--pq-danger); }
.pq-photo-remove:disabled { opacity: 0.5; cursor: default; }
/* What the model read, kept beside what it filed (the review's collapsed "what was read from the photo"). Preserves the
   clinician's own line breaks; never a place text is edited, so it is not a textarea. */
.pq-photo-transcript {
    margin: 8px 0 0;
    padding: 10px 12px;
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: calc(14rem / 15.5);
    line-height: 1.5;
    color: var(--pq-text);
    background: var(--pq-surface-2);
    border: 1px solid var(--pq-border);
    border-radius: var(--pq-radius-sm);
}

/* ── Patient record: the at-a-glance strip ────────────────────────────────────
   Four facts a clinician needs before any control: when they last saw this
   person, when they see them next, whether the last session is written up, and
   what is outstanding. Wraps rather than scrolls — on a narrow screen these
   stack, because a clinician between sessions is as likely to be on a tablet. */
.pq-glance {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 28px;
}
.pq-glance-item { display: flex; flex-direction: column; min-width: 150px; }
.pq-glance-label {
    font-size: calc(12rem / 15.5);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--pq-text-soft);
}
.pq-glance-value { font-size: calc(17rem / 15.5); font-weight: 600; margin-top: 2px; }
.pq-glance-note { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); margin-top: 1px; }
/* "Not written up yet" is the one figure here a clinician may need to act on,
   so it carries weight — but never colour ALONE: the words say it too. */
.pq-glance-note.pq-glance-warn { color: var(--pq-status-bad-ink); font-weight: 600; }

/* ── Rating scales ────────────────────────────────────────────────────────────
   A row of faces instead of a number box. The radio itself is hidden but still
   FOCUSABLE (clip, not display:none), so keyboard and screen-reader users get
   the native radio-group behaviour — arrow keys, one selection — while the
   label does the drawing.

   Colour is never the only signal: each option carries its face and its number
   too, and the selected one takes a ring as well as a fill. */
.pq-scale-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.pq-scale-option { flex: 0 0 auto; }
.pq-scale-radio {
    position: absolute;
    width: 1px; height: 1px;
    clip-path: inset(50%);
    overflow: hidden;
}
.pq-scale-label {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 46px;
    padding: 6px 4px;
    border: 1.5px solid var(--pq-border);
    border-radius: 10px;
    cursor: pointer;
    background: var(--pq-surface);
    line-height: 1.1;
}
.pq-scale-face { font-size: calc(19rem / 15.5); }
.pq-scale-value { font-size: calc(12rem / 15.5); color: var(--pq-text-soft); font-variant-numeric: tabular-nums; }
.pq-scale-label:hover { border-color: var(--pq-scale-tone); }
.pq-scale-radio:checked + .pq-scale-label {
    border-color: var(--pq-scale-tone);
    box-shadow: inset 0 0 0 2px var(--pq-scale-tone);
}
.pq-scale-radio:checked + .pq-scale-label .pq-scale-value { color: var(--pq-scale-tone); font-weight: 700; }
/* Focus must be visible on the LABEL, since the input it belongs to is clipped. */
.pq-scale-radio:focus-visible + .pq-scale-label {
    outline: 2px solid var(--pq-green-deep);
    outline-offset: 2px;
}
/* Scoped to the FACE row it belongs to (`.pq-scale-row` above it): the 340px cap matches that row's own width.
   Unscoped, it also caught the CHIP scale's ends further down — whose row is full-width — so the high-end caption
   landed a third of the way across, under the wrong chip (founder 2026-08-09: "out of the line"). The cap is all
   this rule ever contributed: every other declaration it once carried was already being overridden by the
   same-specificity `.pq-scale-ends` block further down the file, so restating them here would CHANGE the face
   row rather than preserve it. */
.pq-scale-row + .pq-scale-ends { max-width: 340px; }

@media (prefers-reduced-motion: no-preference) {
    .pq-scale-label { transition: border-color 0.12s ease, box-shadow 0.12s ease; }
}

/* ── The progress path ────────────────────────────────────────────────────────
   A single chronological stream: sessions, check-ins, task entries and shared
   notes on one line, because the answer to "am I getting anywhere" lives in how
   they sit next to each other, not in four separate pages.

   An ordered list, so a screen reader announces it as the sequence it is. The
   connecting line is decorative and drawn on the item, not as content. */
.pq-path { list-style: none; margin: 12px 0 0; padding: 0; }
.pq-path-item {
    position: relative;
    display: flex;
    gap: 12px;
    padding: 0 0 18px 0;
}
.pq-path-item:last-child { padding-bottom: 0; }
/* The thread between the dots — stops at the last item so the path does not
   trail off into nothing. */
.pq-path-item:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 13px;
    top: 28px;
    bottom: 2px;
    width: 2px;
    background: var(--pq-border);
}
.pq-path-dot {
    flex: 0 0 auto;
    width: 28px; height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--pq-surface);
    border: 1.5px solid var(--pq-border);
    font-size: calc(14rem / 15.5);
    line-height: 1;
    z-index: 1;
}

/* ── Submitted task entries ───────────────────────────────────────────────────
   A patient's own words, given back. Each submission is a card with the
   question above the answer, because an answer without its question is just a
   sentence — and for a thought record the pairing IS the clinical material. */
.pq-entries { list-style: none; margin: 10px 0 0; padding: 0; }
.pq-entry {
    border-left: 3px solid var(--pq-border);
    padding: 0 0 0 12px;
    margin-bottom: 14px;
}
.pq-entry:last-child { margin-bottom: 0; }
.pq-entry-when { margin-bottom: 4px; }
.pq-entry-fields { margin: 0; }
.pq-entry-fields dt { margin-top: 6px; }
.pq-entry-value {
    margin: 0;
    white-space: pre-wrap; /* a patient's paragraph breaks are part of what they wrote */
    font-size: calc(14rem / 15.5);
}

/* ---- intake-form designer question cards (docs/05 P2): one quiet panel per question, with the number chip and
   the (JS-revealed) move/remove tools in its header — flatter than pq-card so thirteen of them read as one form,
   not thirteen competing panels. ---- */
.pq-q-card {
    border: 1px solid var(--pq-border-soft);
    border-left: 3px solid var(--pq-status-good-border);
    border-radius: 12px;
    background: var(--pq-surface-2);
    padding: 14px 16px 4px;
    margin-bottom: 12px;
}

.pq-q-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }

.pq-q-num {
    background: var(--pq-green-soft);
    color: var(--pq-status-good-ink);
    border-radius: 999px;
    font-size: calc(12rem / 15.5);
    font-weight: 700;
    line-height: 1;
    padding: 5px 9px;
    min-width: 24px;
    text-align: center;
}

.pq-q-tools { margin-left: auto; display: flex; gap: 6px; }

.pq-q-tool {
    border: 1px solid var(--pq-border);
    background: var(--pq-surface);
    color: var(--pq-text-soft);
    border-radius: 8px;
    width: 30px;
    height: 30px;
    font-size: calc(14rem / 15.5);
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.14s ease, color 0.14s ease;
}

.pq-q-tool:hover { background: var(--pq-surface-3); color: var(--pq-ink); }

.pq-q-tool-danger:hover { background: var(--pq-status-bad-bg); color: var(--pq-status-bad-ink); border-color: var(--pq-status-bad-border); }

/* ============================================================================
   Small screens — phone-fit adjustments shared by all three portals.
   The chrome collapses at 641px (MainLayout/NavMenu); these rules keep the
   CONTENT comfortable on a ~360–430px phone: 16px inputs (iOS zooms anything
   smaller on focus), tighter card/table density so the width goes to content,
   and 44px-class touch targets on the compact button variants.
   ========================================================================== */

@media (max-width: 640.98px) {
    /* iOS Safari zooms into any focused control set below 16px — meet it. The floor is a length on purpose: the size
       grows with the reader's setting, and a setting below the default must not take it under the 16px Safari tests. */
    .pq-input, .pq-select, .pq-textarea { font-size: max(16px, 16rem / 15.5); } /* pq-px: the iOS focus-zoom floor */

    /* chrome tightens so content, not padding, gets the width */
    .pq-card { padding: 16px; }
    .pq-card-inset { padding: 10px 12px; }
    .pq-page-head { margin-top: 16px; margin-bottom: 20px; }
    .pq-empty { padding: 28px 16px; }
    .table thead th, .pq-table thead th { padding: 8px 10px; }
    .table tbody td, .pq-table tbody td { padding: 10px; }

    /* The 44px fingertip targets live in their own block below: keyed on touch as well as on width, so a tablet wider
       than this gets them too (2026-11-04 audit: the Check-ins page's Start links were 59×30 at 768px). */

    /* Radios, Save and the "Currently:" note shared one wrapping row that broke into uneven lines on a phone
       (docs/22 payments-pay-by-row-wraps-awkwardly). One choice per line, and Save gets the full width. */
    .pq-payby { flex-direction: column; align-items: stretch; }
    .pq-payby .pq-btn { width: 100%; }
    .view-switch-btn { padding: 9px 8px; }

    /* the monthly columns chart fits by slimming its gaps, not clipping */
    .pq-chart { gap: 6px; }
    .pq-chart-bars { gap: 1px; }
    .pq-chart-bar { width: 12px; }

    /* month-calendar cells relax their height once sideways panning takes over */
    .pq-cal-month td { height: 5.2rem; }

    /* wizard steps: drop the connector lines, let the steps wrap tight */
    .pq-step-line { display: none; }

    /* modals use nearly the full width, with breathing room kept */
    .pq-modal { width: calc(100vw - 20px); }
    .pq-modal-body { padding: 16px 18px; }
    .pq-modal-close { padding: 8px 12px; margin: -6px -8px 0 0; }

    /* in-call controls: tidy rows of even pills at the 44px finger-target floor,
       and a self-view that doesn't cover half the stage */
    .pq-call-controls .pq-btn { flex: 1 1 auto; padding: 10px 12px; font-size: calc(14rem / 15.5); min-height: 44px; }
    .pq-call-local { width: clamp(84px, 24%, 140px); right: 10px; bottom: 10px; }

    /* dense two-column fact grids stack (appointment details in chips/modals) */
    .pq-glance { gap: 10px 18px; }
}

/* ============================================================================
   Fingertip targets: 44px on a phone's width AND on any touch screen.
   A finger is the same size on a 768px tablet as on a 320px phone, and the
   width block above used to be the only thing growing these, so a touch
   tablet got the desktop's 30px pills (2026-11-04 audit: the Check-ins
   page's Start links measured 59×30 at 768px). Width OR coarse pointer:
   a touchscreen laptop at 1400px gets them too, which is right — the
   pointer it reports as primary is the one being used.
   ========================================================================== */

@media (max-width: 640.98px), (pointer: coarse) {
    /* compact buttons grow into honest fingertip targets — min-height, not padding alone, because the phone
       block's comment has promised 44px since it was written and 8px+13.5px×1.2 is ~32px. The booking-time
       chips are .pq-btn-sm in a 6px-gap wrap, which is exactly where a thumb hits 10:30 meaning 10:00
       (2026-09-06 patient UX round two). */
    .pq-btn-sm { min-height: 44px; padding: 10px 16px; }
    .pq-appt-join .pq-btn { min-height: 44px; padding: 10px 14px; }
    /* 4px of padding on 13px type is ~24px, and one of these is "Don't show this again" sitting beside a
       full-size pill — the small one being the destructive-ish choice (docs/22 dont-show-again-tiny-target,
       tiny-inline-buttons-copy-iban-dismiss). inline-flex so min-height is honoured on a button that is
       otherwise inline. */
    .pq-linklike { padding: 4px 0; min-height: 44px; display: inline-flex; align-items: center; }

    /* The back crumb is on nearly every page and was a ~24px pill (docs/22 nav-signout-and-crumb-targets-small). */
    .pq-crumbs a { min-height: 44px; padding: 10px 14px 10px 10px; }

    .pq-q-tool { width: 44px; height: 44px; }
    .pq-seg-btn { min-height: 44px; padding: 9px 14px; }

    /* A choice row — PHQ-9's and GAD-7's "Not at all … Nearly every day", a consent's tick — was a 17px radio beside a
       line of 14.5px text: a 22px-tall target on the page a patient answers nine times over (2026-11-04 audit, 256×22
       at 320px). Padding where the margin was, so the rows keep nearly their pitch and the whole row is the target. */
    .pq-check { min-height: 44px; padding: 11px 0; margin: 0; }

    /* The "On this page" jump pills (13px in 4px of padding) grow the same way. */
    .pq-jump a { min-height: 44px; }
}

/* a video session held upright on a phone: the 16:9 stage would be a thin
   letterboxed strip, so the stage goes portrait like a native video call */
/* Shown only where there is no mouse. Default hidden, revealed in the coarse-pointer block below. */
.pq-touch-only { display: none; }

/* TOUCH, not width — a phone has no hover, and both of these controls were built assuming one. Kept separate
   from the width block on purpose: a touchscreen laptop has the same problem at 1400px. */
@media (pointer: coarse) {
    /* The self-view's mirror button reveals itself on :hover, so on a touch screen it sat at 0.65 opacity
       permanently, and 11px type in 2px of padding is a ~22px target (docs/22
       video-selfview-button-hover-only-and-tiny). Full opacity, and the TOUCH area grows past the visual with
       a pseudo-element rather than the button growing — it sits on the smallest tile on the page and must not
       swallow the picture it belongs to. */
    .pq-touch-only { display: block; }

    /* A removable reading chip grows to a real target on touch (docs/22 reading-chip-x-touch-target). The whole
       chip is the link, so unlike the two cases below the visual CAN grow — chips sit on their own wrapped row,
       so nothing is swallowed. */
    .pq-chip-remove { min-height: 44px; padding-left: 14px; padding-right: 14px; }

    .pq-call-tile-btn { opacity: 1; }
    .pq-call-tile-btn::after { content: ""; position: absolute; inset: -11px; }

    /* Map pins are 30px and cluster: on a static base image several can sit within a thumb's width of each
       other (docs/22 map-pins-30px-static-image-on-phone). Same trick, same reason — growing the pin would
       make the pile-up worse, so only the target grows. */
    .pq-map-pin > summary { position: relative; }
    .pq-map-pin > summary::after { content: ""; position: absolute; inset: -7px; }
    /* …and the open one wins the overlap, so its popup is never opened by a neighbour's halo. */
    .pq-map-pin[open] > summary::after { inset: 0; }

    /* A photographed page's remove control sits on a 96px thumbnail: a thumb, not a cursor, so it grows to the 44px the
       audit set rather than being a 28px dot a clinician hits instead of the page beside it. (One coarse-pointer block,
       not a second above this one — PatientMobileSweepTests reads the FIRST.) */
    .pq-photo-remove { width: 44px; height: 44px; top: 0; right: 0; }
}

@media (max-width: 640.98px) and (orientation: portrait) {
    .pq-call-stage { aspect-ratio: 3 / 4; }
}

/* ---- Bounded-scale answers (clinician-built tools, docs/13 F10 slice 3): one tappable chip per point instead
   of a tower of radio rows. The radios stay real (plain-form POST, keyboard arrows work); only their look is
   replaced — the pq-plan-card/pq-toggle hidden-radio convention. ---- */
.pq-scale { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.pq-scale input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.pq-scale label, .pq-scale .pq-scale-chip {
    flex: 1 1 0; min-width: 42px; min-height: 44px; display: grid; place-items: center;
    border: 1.5px solid var(--pq-border); border-radius: 10px; background: var(--pq-surface);
    font-weight: 600; font-variant-numeric: tabular-nums; color: var(--pq-text);
    cursor: pointer; transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.pq-scale .pq-scale-chip { cursor: default; }
.pq-scale label:hover { border-color: var(--pq-green); color: var(--pq-ink); }
.pq-scale input[type="radio"]:checked + label {
    /* the CHOSEN answer must not be the hardest chip on the page to read: deep green, 5.83:1 */
    background: var(--pq-green-deep); border-color: var(--pq-ink); color: var(--pq-on-accent);
}
.pq-scale input[type="radio"]:focus-visible + label { outline: 2px solid var(--pq-green-deep); outline-offset: 2px; }
.pq-scale-ends {
    display: flex; justify-content: space-between; gap: 12px;
    margin-top: 6px; font-size: calc(13rem / 15.5); color: var(--pq-text-soft);
}
/* An explicit `display` beats the UA sheet's [hidden] rule, so say it here: the tool designer's preview hides its
   caption row (rather than dropping it) to keep the slots the preview script writes into. */
.pq-scale-ends[hidden] { display: none; }

/* The tool designer's live preview (docs/13 F10 slice 3 UX round): the questions + chip row framed as the page
   the patient will meet — dashed, so a glance says "rendering, not another form field". The [hidden] restatements
   guard the same UA-sheet fight as .pq-scale-ends above: the question list and the empty-state hint keep their
   slots for the preview script and must genuinely disappear when hidden. */
.pq-tool-preview {
    border: 1.5px dashed var(--pq-border); border-radius: 12px;
    padding: 14px 16px; background: var(--pq-surface);
}
.pq-tool-preview-questions { margin: 0; padding-left: 22px; }
.pq-tool-preview-questions li { margin: 3px 0; }
.pq-tool-preview-questions[hidden] { display: none; }
.pq-tool-preview [data-preview-none][hidden] { display: none; }
/* an 11-chip row on a narrow phone: two tidy rows beat one cramped one. The height stays at 44px: the chips are the
   answer to every question on the page, and 42px was the one place the floor dipped (2026-11-04 audit, 38×42 at 320px). */
@media (max-width: 480.98px) {
    .pq-scale label, .pq-scale .pq-scale-chip { min-width: 38px; min-height: 44px; }
}

/* ---- Clinician dashboard: today's sessions and the "needs you" strip (docs/13 §F-home) ----
   The day is a LIST OF LINKS, not a grid of cards: the whole row is the target, because what a clinician wants
   from their own dashboard is one click from "who's at eleven" to that person's record. */
.pq-day-list { list-style: none; margin: 0; padding: 0; }
.pq-day-item { border-top: 1px solid var(--pq-border-soft); }
.pq-day-item:first-child { border-top: 0; }
.pq-day-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px 14px;
    padding: 12px 10px;
    margin: 2px 0;
    border-radius: 10px;
    text-decoration: none;
    color: inherit;
    transition: background var(--pq-dur) var(--pq-ease);
}
.pq-day-row:hover, .pq-day-row:focus-visible { background: var(--pq-green-soft); text-decoration: none; }
.pq-day-time { flex: 0 0 auto; min-width: 104px; font-variant-numeric: tabular-nums; font-weight: 600; }
.pq-day-main { flex: 1 1 200px; min-width: 0; }
.pq-day-who { display: block; font-weight: 600; }
.pq-day-what { display: block; color: var(--pq-text-soft); font-size: calc(13.5rem / 15.5); }
/* The session happening RIGHT NOW keeps its own mark — on a full day it is the one row worth finding at a glance. */
.pq-day-item.is-now .pq-day-row { background: var(--pq-blue-soft); box-shadow: inset 3px 0 0 var(--pq-blue-deep); }
/* …and the NEXT one to start, when nothing is running: the row the summary line above the list just named. */
.pq-day-item.is-next .pq-day-row { box-shadow: inset 3px 0 0 var(--pq-green-deep); }
.pq-day-tag {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--pq-green-soft);
    color: var(--pq-green-deep);
    font-size: calc(11.5rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 1px;
}
.pq-day-tag.is-now { background: var(--pq-surface); color: var(--pq-blue-deep); }
/* The day in one line, between the heading and the list: date · how full · what's next. */
.pq-day-summary { margin: -4px 0 10px; color: var(--pq-text-soft); }
/* Coming up: the day leads the time, in the same column. */
.pq-day-date { color: var(--pq-text-soft); font-weight: 500; margin-right: 4px; }
/* Quick actions: one wrapping row of buttons above the day. */
.pq-quick { display: flex; flex-wrap: wrap; gap: 10px; margin: 18px 0 4px; }
/* The Clinician ID as one compact strip rather than a card of its own height. */
.pq-id-strip { margin-top: 26px; display: flex; flex-direction: column; gap: 8px; }
.pq-id-strip-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.pq-id-strip-label {
    margin: 0;
    font-family: inherit;
    font-size: calc(13rem / 15.5);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--pq-text-soft);
}
.pq-day-aside { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 10px 10px; }
.pq-day-aside .pq-btn { font-size: calc(12.5rem / 15.5); padding: 5px 12px; }

@media (max-width: 560.98px) {
    /* the time leads its own line rather than squeezing the name into a sliver */
    .pq-day-time { min-width: 100%; }
}

/* ---- Release-of-information composer (docs/13 §F-release). The recipient choice is two selectable cards, and
   the form shows only the chosen recipient's fields — pure CSS (`:has`), so with no styles (or an old browser)
   both sections simply render and the POST contract is untouched. ---- */
.pq-release-form { margin-top: 6px; }
.pq-release-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 6px; }
.pq-release-kind {
    display: block;
    border: 1px solid var(--pq-border);
    border-radius: 12px;
    padding: 12px 14px;
    cursor: pointer;
    transition: border-color var(--pq-dur) var(--pq-ease), background var(--pq-dur) var(--pq-ease);
}
.pq-release-kind:hover { border-color: var(--pq-green); }
.pq-release-kind:has(input:checked) {
    border-color: var(--pq-green-deep);
    background: var(--pq-green-soft);
    box-shadow: inset 0 0 0 1px var(--pq-green-deep);
}
.pq-release-kind input { margin-right: 6px; accent-color: var(--pq-green-deep); }
.pq-release-kind-title { font-weight: 600; }
.pq-release-kind-hint { display: block; margin-top: 3px; font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); }
.pq-release-section {
    border: 1px solid var(--pq-border-soft);
    border-radius: 12px;
    padding: 12px 14px 14px;
    margin: 14px 0 0;
}
.pq-release-section legend { font-weight: 600; font-size: calc(13.5rem / 15.5); padding: 0 6px; }
/* Only the chosen recipient's fields stay on screen. */
.pq-release-form:has(input[name="recipientKind"][value="external"]:checked) .pq-release-network { display: none; }
.pq-release-form:has(input[name="recipientKind"][value="presque-clinician"]:checked) .pq-release-external { display: none; }
.pq-release-scopes { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 4px 18px; }
.pq-release-scope { display: block; padding: 3px 0; }
.pq-release-scope input { margin-right: 6px; accent-color: var(--pq-green-deep); }
/* The live lookup's answer line (release-compose.js): a green check with the resolved clinician, or an honest miss. */
.pq-release-check { margin: 6px 0 0; font-size: calc(13rem / 15.5); }
.pq-release-check.is-good { color: var(--pq-green-deep); font-weight: 600; }
.pq-release-check.is-bad { color: var(--pq-text-soft); }
@media (max-width: 640.98px) {
    .pq-release-kinds { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
   Library: the add-material swap, the drop zone, and the programme timeline
   (roadmap E3). The page used to stack two always-open forms above everything
   a clinician already owned, so every visit began by scrolling past the form
   they did not want.
   ------------------------------------------------------------------------- */

/* The Link/File chooser. Pure CSS (:has on a checked radio, the release
   composer's precedent) so the swap works with JavaScript off — the script
   only adds the conveniences (a title guessed from the URL, drag-and-drop). */
.pq-swap-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.pq-swap-kind {
    border: 1px solid var(--pq-border);
    border-radius: var(--pq-radius-sm);
    padding: 10px 12px;
    cursor: pointer;
    display: block;
    font-size: calc(13.5rem / 15.5);
    transition: border-color var(--pq-dur) var(--pq-ease), background var(--pq-dur) var(--pq-ease);
}
.pq-swap-kind:hover { border-color: var(--pq-green); }
.pq-swap-kind:has(input:checked) {
    border-color: var(--pq-green-deep);
    background: var(--pq-green-soft);
    box-shadow: inset 0 0 0 1px var(--pq-green-deep);
}
.pq-swap-kind:has(input:focus-visible) { outline: 2px solid var(--pq-green-deep); outline-offset: 2px; }
.pq-swap-kind input { margin-right: 6px; accent-color: var(--pq-green-deep); }
.pq-swap-kind-title { font-weight: 600; }
.pq-swap-kind-hint { display: block; margin-top: 3px; font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); }
/* Only the chosen pane stays on screen. */
.pq-swap:has(input[name="pq-add-kind"][value="link"]:checked) .pq-swap-file { display: none; }
.pq-swap:has(input[name="pq-add-kind"][value="file"]:checked) .pq-swap-link { display: none; }

/* The drop zone is a LABEL for the real file input, so the keyboard, the file
   picker and form validation all behave exactly as they did; drag-and-drop is
   a bonus rather than the only way in. */
.pq-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 4px;
    padding: 20px 16px;
    border: 1.5px dashed var(--pq-border);
    border-radius: var(--pq-radius-sm);
    background: var(--pq-surface-2);
    cursor: pointer;
    transition: border-color var(--pq-dur) var(--pq-ease), background var(--pq-dur) var(--pq-ease);
}
.pq-drop:hover { border-color: var(--pq-green); background: var(--pq-green-soft); }
.pq-drop.is-dragging { border-color: var(--pq-green-deep); background: var(--pq-green-soft); }
.pq-drop-icon { color: var(--pq-green-deep); }
.pq-drop-text { font-weight: 600; color: var(--pq-ink); font-size: calc(14rem / 15.5); overflow-wrap: anywhere; }
.pq-drop-hint { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); max-width: 42ch; }
.pq-drop-error { color: var(--pq-status-bad-ink); font-weight: 600; }
/* The input stays reachable (never display:none, which would take it out of the
   tab order and out of validation) — it is simply visually replaced by the zone. */
.pq-drop-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* A tidier <details> than the inline styles each disclosure used to carry. */
.pq-disclose { margin-top: 6px; }
.pq-disclose > summary {
    cursor: pointer;
    font-size: calc(12.5rem / 15.5);
    color: var(--pq-text-soft);
    font-weight: 600;
    padding: 2px 0;
}
.pq-disclose > summary:hover { color: var(--pq-ink); }
.pq-disclose > summary:focus-visible { outline: 2px solid var(--pq-green-deep); outline-offset: 2px; border-radius: 4px; }

/* The programme timeline: what happens when, as a shape rather than a table.
   A step list you have to read row by row is a filing cabinet; this is what a
   clinician is actually choosing between. */
.pq-timeline { list-style: none; margin: 10px 0 0; padding: 0 0 0 2px; }
.pq-timeline-item {
    position: relative;
    display: flex;
    gap: 12px;
    align-items: baseline;
    flex-wrap: wrap;
    padding: 5px 0 5px 20px;
    font-size: calc(13.5rem / 15.5);
    color: var(--pq-text);
}
/* The line and the dot: drawn, not imaged, so they follow the text size. */
.pq-timeline-item::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 12px;
    bottom: -5px;
    width: 1.5px;
    background: var(--pq-border);
}
.pq-timeline-item:last-child::before { display: none; }
.pq-timeline-item::after {
    content: "";
    position: absolute;
    left: 0;
    top: 9px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--pq-surface);
    box-shadow: inset 0 0 0 1.5px var(--pq-text-muted);
}
.pq-timeline-item.is-sent::after { background: var(--pq-green); box-shadow: inset 0 0 0 1.5px var(--pq-green-deep); }
.pq-timeline-item.is-waiting::after { box-shadow: inset 0 0 0 1.5px var(--pq-blue); }
.pq-timeline-item.is-off { color: var(--pq-text-muted); }
.pq-timeline-item.is-off .pq-timeline-what { text-decoration: line-through; }
.pq-timeline-when {
    flex: 0 0 auto;
    min-width: 92px;
    font-weight: 600;
    color: var(--pq-text-soft);
    font-variant-numeric: tabular-nums;
    font-size: calc(12.5rem / 15.5);
}
.pq-timeline-what { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
.pq-timeline-what .pq-badge { margin-left: 6px; }
.pq-timeline-compact .pq-timeline-item { padding-top: 3px; padding-bottom: 3px; }

/* The builder's "then 2 days later" reading of an absolute day offset — the way
   a clinician thinks about a sequence, kept beside the number that is stored. */
.pq-step-gap { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); }

@media (max-width: 640.98px) {
    .pq-swap-kinds { grid-template-columns: 1fr; }
    .pq-timeline-when { min-width: 0; flex-basis: 100%; }
}

/* ============================================================================
   The patient's therapist directory (docs/13 F9) — /find-a-therapist.
   This is the one page where a person is CHOOSING a human being, so it is
   allowed more warmth than a working screen: faces where a clinician gave one,
   the practice's own mark beside its name, the address they'd travel to, and a
   map they can open. Everything below degrades to a plain, readable list with
   no JavaScript: the pins are links, the popups are <details>, and only the
   "you are here" dot needs a browser that can locate itself.
   ========================================================================== */

.pq-dir-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    gap: 16px;
}

.pq-dir-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 20px;
    box-shadow: var(--pq-shadow-soft);
    transition: border-color var(--pq-dur) var(--pq-ease), box-shadow var(--pq-dur) var(--pq-ease),
                transform var(--pq-dur) var(--pq-ease);
}
.pq-dir-card:hover { border-color: var(--pq-border-strong); box-shadow: var(--pq-shadow-lift); }
@media (prefers-reduced-motion: no-preference) {
    .pq-dir-card:hover { transform: translateY(-2px); }
}

/* Already theirs. The green edge is the quiet half of the signal — the badge in the
   card says it in words, because colour alone is never the message. */
.pq-dir-card.is-connected {
    border-color: var(--pq-green);
    box-shadow: var(--pq-shadow-soft), inset 3px 0 0 0 var(--pq-green);
}

/* Landing here from a map pin: hold the eye on the card the pin meant. */
.pq-dir-card:target {
    border-color: var(--pq-green-deep);
    box-shadow: 0 0 0 3px var(--pq-green-soft), var(--pq-shadow-lift);
}

.pq-dir-head { display: flex; align-items: flex-start; gap: 14px; }
.pq-dir-head-main { min-width: 0; flex: 1 1 auto; }
.pq-dir-name { margin: 0; font-family: var(--pq-serif); font-size: calc(21rem / 15.5); color: var(--pq-ink); line-height: 1.2; }
.pq-dir-name a { color: inherit; }
.pq-dir-name a:hover { color: var(--pq-green-deep); }
.pq-dir-title { margin: 2px 0 0; font-size: calc(13.5rem / 15.5); color: var(--pq-text-soft); }

.pq-dir-photo {
    width: 64px; height: 64px;
    flex: 0 0 auto;
    border-radius: 50%;
    object-fit: cover;
    background: var(--pq-green-soft);
    box-shadow: 0 0 0 3px var(--pq-surface), 0 0 0 4px var(--pq-border-soft);
}
.pq-dir-avatar {
    width: 64px; height: 64px;
    flex: 0 0 auto;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--pq-green-soft);
    color: var(--pq-green-deep);
    font-family: var(--pq-serif);
    font-size: calc(22rem / 15.5);
    box-shadow: 0 0 0 3px var(--pq-surface), 0 0 0 4px var(--pq-border-soft);
}

/* The practice line: its mark, then its name. The logo is optional and small — it
   identifies the practice, it does not advertise it. */
.pq-dir-practice { display: flex; align-items: center; gap: 9px; min-width: 0; }
.pq-dir-logo {
    height: 28px; max-width: 90px;
    flex: 0 0 auto;
    object-fit: contain;
    border-radius: 6px;
    background: var(--pq-logo-plate);
}
.pq-dir-practice-name { font-size: calc(13.5rem / 15.5); font-weight: 600; color: var(--pq-text); overflow-wrap: anywhere; }

.pq-dir-where {
    display: flex; gap: 8px;
    font-size: calc(13.5rem / 15.5);
    color: var(--pq-text-soft);
    line-height: 1.45;
}
.pq-dir-where address { margin: 0; font-style: normal; overflow-wrap: anywhere; }
.pq-dir-distance { font-weight: 600; color: var(--pq-green-deep); font-size: calc(13.5rem / 15.5); }

.pq-dir-snippet { margin: 0; font-size: calc(14rem / 15.5); color: var(--pq-text); }
.pq-dir-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.pq-dir-actions { margin-top: auto; padding-top: 4px; display: flex; flex-wrap: wrap; gap: 8px; }

/* ---- the map view ---- */

.pq-map-panel {
    background: var(--pq-surface);
    border: 1px solid var(--pq-border-soft);
    border-radius: var(--pq-radius);
    padding: 14px;
    box-shadow: var(--pq-shadow-soft);
    margin-bottom: 18px;
}

/* The frame is the projection: the image fills it exactly, so a marker placed at a
   percentage of the frame sits on the right piece of ground however the browser
   scales the picture. Overflow stays VISIBLE so a popup can leave the map. */
.pq-map-frame {
    position: relative;
    border-radius: 12px;
    background: var(--pq-surface-4);
}
.pq-map-frame > img {
    display: block;
    width: 100%; height: auto;
    border-radius: 12px;
}

.pq-map-pin { position: absolute; transform: translate(-50%, -100%); z-index: 1; }
.pq-map-pin[open] { z-index: 5; }
.pq-map-pin > summary {
    list-style: none;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    width: 30px; height: 30px;
    border-radius: 50% 50% 50% 4px;
    transform: rotate(-45deg);
    background: var(--pq-green-deep);
    color: var(--pq-on-accent);
    font-size: calc(12.5rem / 15.5); font-weight: 700;
    border: 2px solid var(--pq-surface);
    box-shadow: var(--pq-shadow-float);
    transition: background var(--pq-dur) var(--pq-ease), transform var(--pq-dur) var(--pq-ease);
}
.pq-map-pin > summary::-webkit-details-marker { display: none; }
.pq-map-pin > summary > span { transform: rotate(45deg); }
.pq-map-pin > summary:hover { background: var(--pq-green); }
.pq-map-pin[open] > summary { background: var(--pq-ink); }
.pq-map-pin > summary:focus-visible { outline: 3px solid var(--pq-green); outline-offset: 2px; }
/* A pin whose practice the patient is already with wears the connected green. */
.pq-map-pin.is-connected > summary { background: var(--pq-green); box-shadow: 0 0 0 3px var(--pq-green-soft); }

.pq-map-pop {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 10px);
    transform: translateX(-50%);
    width: 244px;
    max-width: 70vw;
    max-height: 230px;
    overflow-y: auto;
    background: var(--pq-surface);
    border: 1px solid var(--pq-border);
    border-radius: var(--pq-radius-sm);
    box-shadow: var(--pq-shadow);
    padding: 12px;
    text-align: left;
}
/* Which way the popup opens is decided on the SERVER from the pin's own position in
   the frame, so a pin near an edge never opens off the picture. */
.pq-map-pin.is-below .pq-map-pop { bottom: auto; top: calc(100% + 10px); }
.pq-map-pin.is-left  .pq-map-pop { left: 0; transform: none; }
.pq-map-pin.is-right .pq-map-pop { left: auto; right: 0; transform: none; }

.pq-map-pop-practice { font-size: calc(13rem / 15.5); font-weight: 700; color: var(--pq-ink); }
.pq-map-pop-address { font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft); font-style: normal; margin: 2px 0 0; }
.pq-map-pop-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.pq-map-pop-list li { display: flex; align-items: center; gap: 9px; }
.pq-map-pop-photo { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.pq-map-pop-avatar {
    width: 34px; height: 34px; flex: 0 0 auto;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--pq-green-soft); color: var(--pq-green-deep);
    font-family: var(--pq-serif); font-size: calc(13rem / 15.5);
}
.pq-map-pop-who { min-width: 0; }
.pq-map-pop-name { display: block; font-size: calc(13.5rem / 15.5); font-weight: 600; }
.pq-map-pop-role { display: block; font-size: calc(12rem / 15.5); color: var(--pq-text-soft); }

/* "You are here" — placed by the browser from a position that never leaves it. */
.pq-map-me {
    position: absolute;
    transform: translate(-50%, -50%);
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--pq-blue);
    border: 3px solid var(--pq-surface);
    box-shadow: var(--pq-focus-ring-blue);
    z-index: 4;
}

.pq-map-legend {
    display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
    margin-top: 12px;
    font-size: calc(12.5rem / 15.5); color: var(--pq-text-soft);
}
.pq-map-legend span { display: inline-flex; align-items: center; gap: 6px; }
.pq-map-key { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }
.pq-map-key.pin { background: var(--pq-green-deep); }
.pq-map-key.mine { background: var(--pq-blue); }

@media (max-width: 640.98px) {
    .pq-dir-card { padding: 16px; }
    .pq-dir-photo, .pq-dir-avatar { width: 52px; height: 52px; font-size: calc(18rem / 15.5); }
    .pq-map-pop { width: 200px; }
}

/* ---- practice analytics (docs/11 §2, 2026-09-05): key/value lists on the activity cards, the needs-attention list,
   and the inline figure row under a card ---- */
.pq-kv { margin: 0; display: grid; gap: 6px; }
.pq-kv > div { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; font-size: calc(14rem / 15.5); }
.pq-kv dt { margin: 0; color: var(--pq-text-soft); }
.pq-kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.pq-kv-row { display: flex; gap: 14px; flex-wrap: wrap; font-size: calc(14rem / 15.5); }
.pq-attention-list { margin: 0; padding-left: 20px; display: grid; gap: 8px; font-size: calc(14rem / 15.5); line-height: 1.45; }
.pq-attention-list a { margin-left: 4px; white-space: nowrap; }

/* ---- .pq-prose: a long document rendered from markdown (docs/22 legal-readability) ----

   /legal has worn this class since the page was written and NOTHING in this file defined it: three thousand
   words of Markdig output inheriting card defaults, with no measure, no rhythm between a heading and the text
   under it, and list items set solid against each other. These are the documents the app asks people to ACCEPT
   before they can use it, which makes "too dense to read" a consent problem rather than a styling one.

   The rules are written for DESCENDANTS, because everything inside arrives from markdown and none of it carries
   a class of its own. Nothing here is specific to the legal pages — any rendered document can wear it. */
.pq-prose {
    /* The measure. Continuous prose stops being readable somewhere past 75 characters a line — the eye loses
       the start of the next one — and a legal document is the longest continuous prose the app has. */
    max-width: 68ch;
    line-height: 1.65;
    color: var(--pq-text);
}
.pq-prose > :first-child { margin-top: 0; }
.pq-prose > :last-child { margin-bottom: 0; }

.pq-prose p { margin: 0 0 1em; }

/* Headings bind DOWN to what they introduce: a generous space above and a small one below is the whole of what
   makes a document read as sections rather than one undifferentiated column. */
.pq-prose h1 { font-size: min(clamp(26rem / 15.5, 3vw + 29rem / 15.5 - 29px, 32rem / 15.5), 13vw); line-height: 1.15; margin: 0 0 14px; } /* pq-px: the fixed half of the fluid term, as on h1 */
.pq-prose h2 { font-size: calc(22rem / 15.5); line-height: 1.25; margin: 34px 0 10px; }
.pq-prose h3 { font-size: calc(17rem / 15.5); line-height: 1.3; margin: 26px 0 8px; }
/* h4 and below are sub-clauses, not display type — sans, so the serif scale doesn't have to keep shrinking. */
.pq-prose h4, .pq-prose h5, .pq-prose h6 {
    font-family: var(--pq-sans);
    font-size: 1rem;
    font-weight: 700;
    color: var(--pq-ink);
    margin: 22px 0 6px;
}

.pq-prose ul, .pq-prose ol { margin: 0 0 1em; padding-left: 22px; }
.pq-prose li { margin-bottom: 6px; }
.pq-prose li > ul, .pq-prose li > ol { margin: 6px 0 0; }

/* Underlined, because a link inside a wall of text has no other shape to be found by. */
.pq-prose a { text-decoration: underline; text-underline-offset: 2px; }
.pq-prose strong { color: var(--pq-ink); }
.pq-prose em { font-style: italic; }
.pq-prose code { font-size: 0.92em; }

.pq-prose blockquote {
    margin: 1em 0;
    padding: 2px 0 2px 14px;
    border-left: 3px solid var(--pq-border);
    color: var(--pq-text-soft);
}
.pq-prose hr { border: 0; border-top: 1px solid var(--pq-border); margin: 30px 0; }

/* A table in one of these documents is the retention schedule or the sub-processor list — usually the part
   someone came to read, so it gets real borders rather than the bare rows a browser draws. */
.pq-prose table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: calc(14.5rem / 15.5); }
.pq-prose th, .pq-prose td { border: 1px solid var(--pq-border); padding: 8px 10px; text-align: left; vertical-align: top; }
.pq-prose th { background: var(--pq-bg); color: var(--pq-ink); font-weight: 600; }

/* The one line inside a hint that is an AGREEMENT rather than an explanation (the WhatsApp opt-in, docs/22
   prefs-whatsapp-optin-not-discrete). Saving that tick writes a communication-consent record with a date, and a
   sentence of that weight set in the same 12.5px grey as "choose which browsers below" is how somebody agrees to
   something without noticing. Set off by a rule and given its own line, so it reads as terms — not boxed like a
   notice, which would claim something has gone wrong. */
.pq-hint-agreement {
    display: block;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid var(--pq-border);
    color: var(--pq-text);
}

/* ---- a screen narrow for its text ---- */

/* An em in a media query is the size the browser starts every page from: 16px times whatever the reader has set, in
   the browser or, on a phone, for the whole device (TEXT SIZE, under "base"). So (max-width: 15em) is a screen under
   fifteen of the reader's own ems across. No screen is that at the default setting (it would be 240px wide); a 360px
   phone is from 150%, and every phone is at 200%, the end of Android's Font size slider. There, two things side by
   side no longer have the room they were laid out for, and each of these gives way instead of breaking a word in two,
   leaving its card or hiding what was typed (each measured on a 360px phone at 200%, 2026-11-05). Last in the sheet:
   it loosens rules of its own weight by coming after them. */
@media (max-width: 15em) {
    /* An appointment's facts: each label over its value, not beside it. Beside, "Reference" left the values a column
       narrower than "Counselling", which went onto a second line after its tenth letter. */
    .pq-appt-dl { grid-template-columns: 1fr; }
    .pq-appt-dl dt:not(:first-child) { margin-top: 6px; }

    /* A badge may run onto a second line: in one, "Presque ID PT-2026-0001" was wider than the card it sits in. */
    .pq-badge { white-space: normal; }

    /* A mobile number: the country over the number, each the row's whole width. Side by side the number's field
       showed six of its ten digits. */
    .pq-phone-row { flex-wrap: wrap; }
    .pq-phone-row > .pq-select { flex: 1 1 100%; max-width: 100%; }
}
