/* ============================================================================
   PostPilot — base.css · shared foundation ("Atlas")
   ----------------------------------------------------------------------------
   Loaded by all three layouts, between theme.css and the surface-specific
   sheets (shell.css + app.css for the app, auth.css for sign-in, marketing.css for the public site).

   Owns: reset and root sizing, typography, focus, the shared keyframes, and
   every primitive component — buttons, inputs, badges, empty states, loading
   states, tabs, meters, detail lists, chips, tooltips. Everything downstream
   inherits from here, so a change in this file is a change everywhere.

   The ambient background layers (aurora, mesh, grain) that used to live here
   are GONE. The canvas is flat. Depth comes from hairline borders and very
   soft shadows; there is no glass, no blur, no gradient wash.
   ============================================================================ */

/* ===========================================================================
   1. Root & document
   ===========================================================================
   16px root. Bootstrap's vendored dist is built for a 16px root, so the older
   15px value silently rendered all of its spacing and type ~6% small.
   No `scroll-behavior: smooth` — Lenis drives smooth scrolling on the
   marketing layout and the two fight each other. */
html {
    font-size: 16px;
    position: relative;
    min-height: 100%;
    -webkit-text-size-adjust: 100%;
}

body {
    background: var(--pp-canvas);
    color: var(--pp-ink);
    font-family: var(--pp-font-body);
    font-feature-settings: var(--pp-font-features);
    font-size: var(--fs-base);
    line-height: var(--lh-base);
    min-height: 100vh;
    min-height: 100svh;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* ===========================================================================
   2. Typography
   =========================================================================== */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: var(--pp-font-display);
    color: var(--pp-heading);
    letter-spacing: var(--ls-snug);
    line-height: var(--lh-snug);
    font-weight: 700;
}

/* allupnext.com: h1 at 800, the other display headings at 700, and the two smallest set in the body face. */
h1, .h1 { font-size: var(--fs-2xl); letter-spacing: var(--ls-tight); line-height: var(--lh-tight); font-weight: var(--pp-display-weight); }
h2, .h2 { font-size: var(--fs-xl); letter-spacing: var(--ls-tight); line-height: var(--lh-tight); font-weight: 700; }
h3, .h3 { font-size: var(--fs-lg); }
h4, .h4 { font-size: var(--fs-md); font-weight: 600; }
h5, .h5 { font-size: var(--fs-base); font-family: var(--pp-font-body); font-weight: 700; letter-spacing: 0; }
h6, .h6 { font-size: var(--fs-sm); font-family: var(--pp-font-body); font-weight: 600; letter-spacing: 0; }

p { line-height: var(--lh-base); }
.lead { font-size: var(--fs-md); line-height: var(--lh-relaxed); color: var(--pp-ink-muted); }

/* Bootstrap ships code in #d63384 — a pink that belongs to no part of this
   palette, and which measured 3.99:1 inside the .alert-info on Billing. An
   inline chip instead: ink on the sunken surface, which also makes a config
   key read as a discrete token rather than as coloured prose. */
code {
    color: var(--pp-ink);
    background: var(--pp-sunken);
    border: 1px solid var(--pp-hairline);
    border-radius: var(--r-xs);
    padding: 0.1em 0.35em;
    font-size: 0.9em;
    overflow-wrap: anywhere;
}
small, .small { font-size: var(--fs-sm); }

a {
    color: var(--pp-link);
    text-decoration: none;
    transition: color var(--pp-dur-1) var(--pp-ease-soft);
}

a:hover { color: var(--pp-accent-deep); }

::selection { background: var(--pp-accent-wash-strong); color: var(--pp-ink); }
hr { border-color: var(--pp-hairline); opacity: 1; }

.text-muted { color: var(--pp-ink-muted) !important; }
.text-primary { color: var(--pp-accent-ink) !important; }
.link-primary { color: var(--pp-accent-ink) !important; }
.bg-primary { background-color: var(--pp-accent-fill) !important; }

/* An eyebrow: small, uppercase, tracked. Used above section and page titles. */
.pp-eyebrow {
    display: inline-block;
    font-size: var(--fs-eyebrow);
    font-weight: 700;
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--pp-ink-subtle);
}

/* The reference's eyebrow: a short coral rule, then the label in coral. Every
   section heading on allupnext.com opens with one, so a page header that does
   the same reads as the same product. The label is --pp-accent-ink, not the
   brand coral — coral text on paper is 2.89:1. */
.pp-eyebrow--dash {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--pp-accent-ink);
    font-weight: 700;
}

.pp-eyebrow--dash::before {
    content: "";
    width: 26px;
    height: 2px;
    border-radius: 2px;
    background: var(--pp-accent);
    flex: 0 0 auto;
}

/* Figures line up in stat cards, tables, pricing and the calendar. */
.tnum, .stat-value, .table td, .table th, .amount, [data-counter] {
    font-feature-settings: var(--pp-font-numeric);
    font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   3. Focus & scrollbars
   =========================================================================== */
* { scrollbar-width: thin; scrollbar-color: var(--pp-hairline-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }

*::-webkit-scrollbar-thumb {
    background: var(--pp-hairline-strong);
    border-radius: var(--r-pill);
    border: 2px solid transparent;
    background-clip: content-box;
}

*::-webkit-scrollbar-thumb:hover { background: var(--pp-ink-subtle); background-clip: content-box; }

.btn:focus-visible,
.form-control:focus,
.form-check-input:focus,
.form-select:focus,
.accordion-button:focus,
.pp-chip:focus-visible,
.select2-container--focus .select2-selection {
    box-shadow: var(--pp-ring) !important;
    border-color: var(--pp-focus) !important;
    outline: none;
}

a:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--pp-focus);
    outline-offset: 3px;
}

/* A bare text link gets softly rounded corners on its ring. Anything with a shape of its own keeps it: a link-button stays a pill
   on focus, and a card or <main> that a script or the skip link focuses is not squared off. */
a:not([class]):focus-visible { border-radius: var(--r-xs); }

/* Windows High Contrast drops box-shadow and forces border colours, so the ring above (a shadow) would vanish: a real outline survives. */
@media (forced-colors: active) {
    .btn:focus-visible,
    .form-control:focus,
    .form-check-input:focus,
    .form-select:focus,
    .accordion-button:focus,
    .pp-chip:focus-visible,
    .select2-container--focus .select2-selection {
        outline: 2px solid Highlight;
        outline-offset: 2px;
    }
}

/* Skip link — hidden until keyboard focus, then floats top-left. */
.pp-skip-link {
    position: absolute;
    top: var(--sp-3);
    left: var(--sp-3);
    z-index: 2100;
    padding: 0.6rem 1.1rem;
    border-radius: var(--r-sm);
    background: var(--pp-surface);
    color: var(--pp-ink);
    border: 1px solid var(--pp-hairline-strong);
    box-shadow: var(--e-2);
    font-weight: 600;
    font-size: var(--fs-sm);
    transform: translateY(calc(-100% - 1.5rem));
    opacity: 0;
    pointer-events: none;
    transition: transform var(--pp-dur-2) var(--pp-ease-soft), opacity var(--pp-dur-2) var(--pp-ease-soft);
}

.pp-skip-link:focus {
    transform: none;
    opacity: 1;
    pointer-events: auto;
    outline: none;
    box-shadow: var(--pp-ring), var(--e-2);
}

#main-content:focus { outline: none; }

/* ===========================================================================
   4. Shared keyframes
   =========================================================================== */
@keyframes pp-fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes pp-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes pp-scale-in { from { opacity: 0; transform: scale(0.985) translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes pp-skel { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@keyframes pp-spin { to { transform: rotate(360deg); } }

/* A "this is live" dot. Opacity only — the glow keyframe this replaced pulsed a
   box-shadow every frame, which is a repaint the effect does not need. */
@keyframes pp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* ===========================================================================
   5. Brand mark
   =========================================================================== */
/* The mark is a drawn SVG (_BrandMark.cshtml): a coral disc with a calendar and
   a paper plane on it, no letters. It carries its own ground and picture, so
   this only sizes it — --brand-size lets a caller scale the whole lockup from
   one number. */
.brand-mark {
    width: var(--brand-size, 32px);
    height: var(--brand-size, 32px);
    flex: 0 0 auto;
    display: block;
    border-radius: 50%;
    /* A mark this small reads better with a hairline of separation from a
       white card than floating on it unaided. */
    box-shadow: 0 1px 2px rgba(var(--c-ink-rgb), 0.12);
}

/* The wordmark beside it (marketing nav). Display face, tight tracking and a slightly darker ink than body copy.
   The app sidebar's own wordmark is .pp-brand__text in shell.css. */
.navbar-brand {
    font-family: var(--pp-font-display);
    font-weight: 700;
    letter-spacing: var(--ls-tight);
    color: var(--pp-ink);
}

/* ===========================================================================
   6. Buttons
   ---------------------------------------------------------------------------
   One solid accent for the primary action; everything else is a bordered or
   bare button. No gradients, no shimmer sweep, no perpetual animation — a
   button that moves on its own is the definition of "generic SaaS".
   =========================================================================== */
/* Pill buttons, as on allupnext.com: every CTA there is fully round and set in
   the display-adjacent 600 weight. The app's buttons were the last square-ish
   thing on a screen otherwise re-skinned to the brand. min-height gives one
   control height across .btn, .form-control and the toolbar search. */
.btn {
    --bs-btn-focus-box-shadow: var(--pp-ring);
    border-radius: var(--r-pill);
    font-family: var(--pp-font-body);
    font-weight: 600;
    font-size: var(--fs-sm);
    padding: 0.5rem 1.15rem;
    min-height: 2.5rem;
    line-height: 1.4;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    transition:
        background-color var(--pp-dur-1) var(--pp-ease-soft),
        border-color var(--pp-dur-1) var(--pp-ease-soft),
        color var(--pp-dur-1) var(--pp-ease-soft),
        box-shadow var(--pp-dur-2) var(--pp-ease-soft),
        transform var(--pp-dur-1) var(--pp-ease-soft);
}

/* The icon-then-label spacing used to come from a `me-1` on every <i>. With the
   flex gap above, that margin would double it, so it is neutralised here. */
.btn > .bi { margin-inline-end: 0 !important; }

.btn-sm { border-radius: var(--r-pill); padding: 0.3rem 0.8rem; min-height: 2rem; font-size: var(--fs-xs); }
.btn-lg { border-radius: var(--r-pill); padding: 0.75rem 1.6rem; min-height: 3rem; font-size: var(--fs-base); }

/* A 1px press, not a bounce. */
.btn:active, .btn.active { transform: translateY(1px); }

.btn-primary {
    /* The site's coral, with the site's WHITE label (--pp-on-accent): 3.15:1 — the owner's choice, and an accepted exception (theme.css). */
    --bs-btn-bg: var(--pp-accent-fill);
    --bs-btn-border-color: var(--pp-accent-fill);
    --bs-btn-hover-bg: var(--pp-accent-fill-hover);
    --bs-btn-hover-border-color: var(--pp-accent-fill-hover);
    --bs-btn-active-bg: var(--pp-accent-fill-active);
    --bs-btn-active-border-color: var(--pp-accent-fill-active);
    --bs-btn-disabled-bg: var(--pp-accent-fill);
    --bs-btn-disabled-border-color: var(--pp-accent-fill);
    --bs-btn-color: var(--pp-on-accent);
    --bs-btn-hover-color: var(--pp-on-accent);
    --bs-btn-active-color: var(--pp-on-accent);
    box-shadow: var(--e-1);
}

/* The coral glow and the 2px lift arrive on hover (allupnext.com's button does the same lift), not at rest:
   a resting glow on every primary button turns a toolbar into a row of lamps. */
.btn-primary:hover,
.btn-primary:focus-visible { box-shadow: var(--pp-glow-coral); }
.btn-primary:hover { transform: translateY(var(--pp-lift-btn)); }
.btn-primary:active { transform: translateY(1px); }

/* Tonal accent — a coral wash with coral-ink text. For an action that matters
   but is not THE action on the screen (Reconnect an account, Resend an invite).
   This replaces Bootstrap's yellow .btn-warning, which appeared nowhere else in
   the palette and read as an error. */
.btn-warning,
.btn-tonal {
    /* The deep coral, not --pp-accent-ink: on this stronger wash the ink is 4.28:1, the deep one 5.3:1. */
    --bs-btn-color: var(--pp-accent-deep);
    --bs-btn-bg: var(--pp-accent-wash-strong);
    --bs-btn-border-color: rgba(var(--pp-accent-rgb), 0.35);
    --bs-btn-hover-color: var(--pp-on-accent);
    --bs-btn-hover-bg: var(--pp-accent-fill);
    --bs-btn-hover-border-color: var(--pp-accent-fill);
    --bs-btn-active-color: var(--pp-on-accent);
    --bs-btn-active-bg: var(--pp-accent-fill-active);
    --bs-btn-active-border-color: var(--pp-accent-fill-active);
}

/* Secondary: hairline border, ink label. The workhorse. */
.btn-outline-primary,
.btn-ghost,
.btn-light,
.btn-outline-secondary {
    --bs-btn-color: var(--pp-ink);
    --bs-btn-bg: var(--pp-surface);
    --bs-btn-border-color: var(--pp-hairline-strong);
    --bs-btn-hover-bg: var(--pp-sunken);
    --bs-btn-hover-border-color: var(--pp-ink-subtle);
    --bs-btn-hover-color: var(--pp-ink);
    --bs-btn-active-bg: var(--pp-sunken);
    --bs-btn-active-border-color: var(--pp-ink-subtle);
    --bs-btn-active-color: var(--pp-ink);
    color: var(--pp-ink);
    background: var(--pp-surface);
    border: 1px solid var(--pp-hairline-strong);
    box-shadow: var(--e-1);
}

.btn-outline-primary:hover,
.btn-ghost:hover,
.btn-light:hover,
.btn-outline-secondary:hover {
    background: var(--pp-sunken);
    border-color: var(--pp-ink-subtle);
    color: var(--pp-ink);
}

/* ---------------------------------------------------------------------------
   Ink surfaces.

   A navy band re-aliases the token set LOCALLY, so every component inside it
   adapts without knowing it is on a dark ground — a card, a button, a badge or
   a divider each keep reading the same variable names they always did. That is
   why the hero needs no bespoke per-component overrides.

   Ratios are measured against --pp-ink-deep (#0B1A26).
   --------------------------------------------------------------------------- */
.pp-on-ink {
    --pp-ink: #f2f5f7;                              /* 16.1:1 */
    --pp-ink-muted: #9fb0bc;                        /*  7.9:1 */
    --pp-ink-subtle: #8ba0ae;                       /*  6.5:1 */
    --pp-heading: #f2f5f7;
    /* Coral TEXT flips to the soft coral: --pp-accent-ink is 2.1:1 on navy.
       --pp-accent-fill is deliberately NOT re-aliased: a primary button on the navy band is the same coral
       with the same white label (coral on navy is 5.60:1, so the button edge is legible too). */
    --pp-accent-ink: var(--pp-accent-on-ink);       /*  9.1:1 */
    --pp-accent-deep: var(--pp-accent-on-ink);
    --pp-link: var(--pp-link-on-ink);               /*  5.0:1 */
    --pp-hairline: rgba(255, 255, 255, 0.12);
    --pp-hairline-strong: rgba(255, 255, 255, 0.22);
    --pp-control-border: rgba(255, 255, 255, 0.38);
    --pp-surface: rgba(255, 255, 255, 0.06);
    --pp-sunken: rgba(255, 255, 255, 0.09);
    --pp-focus: #f4a78e;
    --pp-focus-rgb: 244, 167, 142;
    --c-ink-rgb: 0, 0, 0;
    color: var(--pp-ink);
}

.pp-on-ink .text-muted { color: var(--pp-ink-muted) !important; }

/* Pill CTAs — the AllUpNext button shape. Opt-in, so app buttons keep the
   calmer 8px radius and only marketing surfaces go fully round. */
.btn-pill { border-radius: var(--r-pill) !important; }

/* Tertiary: no chrome at all until hovered. */
.btn-plain {
    background: transparent;
    border: 1px solid transparent;
    color: var(--pp-ink-muted);
    box-shadow: none;
}

.btn-plain:hover { background: var(--pp-sunken); color: var(--pp-ink); }

.btn-danger {
    --bs-btn-bg: var(--pp-danger);
    --bs-btn-border-color: var(--pp-danger);
    --bs-btn-hover-bg: var(--pp-danger-ink);
    --bs-btn-hover-border-color: var(--pp-danger-ink);
    --bs-btn-color: #fff;
}

/* In the dark theme --pp-danger is the light red #F87171, and a white label on it is 2.8:1. The navy one is 6.1:1 —
   the same reasoning as the coral primary. The hover is a lighter red (7.9:1), so the button still brightens. */
[data-bs-theme="dark"] .btn-danger {
    --bs-btn-color: #0b1a26;
    --bs-btn-hover-color: #0b1a26;
    --bs-btn-active-color: #0b1a26;
    --bs-btn-hover-bg: #fca5a5;
    --bs-btn-hover-border-color: #fca5a5;
    --bs-btn-active-bg: #f87171;
    --bs-btn-active-border-color: #f87171;
}

[data-bs-theme="dark"] .btn-danger.is-busy::after { color: #0b1a26; }

.btn-outline-danger { --bs-btn-color: var(--pp-danger); --bs-btn-border-color: var(--pp-danger); --bs-btn-hover-bg: var(--pp-danger); --bs-btn-hover-color: #fff; }
.btn-success { --bs-btn-bg: var(--pp-success); --bs-btn-border-color: var(--pp-success); --bs-btn-color: #fff; }

/* ---- Busy state ----------------------------------------------------------
   ajaxSubmit used to only set `disabled`, which gives no feedback at all: on a
   slow connection a submit button simply greyed out and the user clicked
   again. .is-busy keeps the label (so the button doesn't resize and the
   context isn't lost) and swaps in a spinner. Affects 8 modal forms and 5
   auth forms. */
.btn.is-busy {
    position: relative;
    pointer-events: none;
    color: transparent !important;
}

.btn.is-busy::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 1em;
    height: 1em;
    border-radius: 50%;
    border: 2px solid currentcolor;
    border-top-color: transparent;
    color: var(--pp-on-accent);
    animation: pp-spin 0.6s linear infinite;
}

/* Solid status buttons keep a white label, so their spinner is white too. */
.btn-danger.is-busy::after,
.btn-success.is-busy::after { color: #fff; }

/* A bordered button spins in ink, not white. */
.btn-outline-primary.is-busy::after,
.btn-ghost.is-busy::after,
.btn-light.is-busy::after,
.btn-plain.is-busy::after { color: var(--pp-ink); }

/* ===========================================================================
   7. Forms
   ---------------------------------------------------------------------------
   Control borders use --pp-control-border, NOT the decorative hairline. WCAG
   1.4.11 requires 3:1 for the boundary of an interactive component and the
   hairline measures 1.2:1 — using it here would be a real failure, not a
   stylistic choice.
   =========================================================================== */
.form-label {
    font-weight: 500;
    font-size: var(--fs-sm);
    color: var(--pp-ink);
    margin-bottom: 0.4rem;
}

.form-control, .form-select, .input-group-text {
    border-radius: var(--r-sm);
    border: 1px solid var(--pp-control-border);
    background: var(--pp-surface);
    color: var(--pp-ink);
    padding: 0.55rem 0.8rem;
    font-size: var(--fs-base);
    box-shadow: none;
    transition:
        border-color var(--pp-dur-1) var(--pp-ease-soft),
        box-shadow var(--pp-dur-1) var(--pp-ease-soft);
}

.form-control::placeholder { color: var(--pp-ink-subtle); opacity: 1; }
.form-control:hover, .form-select:hover { border-color: var(--pp-ink-subtle); }

/* Compact controls (toolbar filters) match the pill buttons beside them, and
   share their 40px height so a filter row lines up instead of stair-stepping. */
.form-control-sm,
.form-select-sm {
    min-height: 2.5rem;
    padding: 0.4rem 0.95rem;
    font-size: var(--fs-sm);
    border-radius: var(--r-pill);
}

.form-select-sm { padding-right: 2.25rem; }
.form-control:disabled, .form-select:disabled, .form-control[readonly] { background: var(--pp-sunken); color: var(--pp-ink-muted); }
.input-group-text { background: var(--pp-sunken); color: var(--pp-ink-muted); }
.form-check-input { background-color: var(--pp-surface); border-color: var(--pp-control-border); }
.form-check-input:checked { background-color: var(--pp-accent-fill); border-color: var(--pp-accent-fill); }
/* The tick is white, like every other label on the coral fill (--pp-on-accent). */
.form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
.form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e");
}
/* The switch is 2.25em wide; Bootstrap's label offset (2.5em) assumed 2em, which left a 4px gap before the label. */
.form-switch { padding-left: 2.85em; }
.form-switch .form-check-input { width: 2.25em; margin-left: -2.85em; }
/* Bootstrap turns the off knob a focus-blue. The brand ring already says "focused", so the knob stays neutral. */
.form-switch .form-check-input:focus:not(:checked) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23647884'/%3e%3c/svg%3e");
}
.form-text { color: var(--pp-ink-muted); font-size: var(--fs-sm); }

/* Validation stays loud — it is the one place a colour must interrupt. */
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--pp-danger) !important; }
.form-control.is-invalid:focus, .form-select.is-invalid:focus { box-shadow: 0 0 0 3px rgba(var(--pp-danger-rgb), 0.18) !important; }
.invalid-feedback, .field-validation-error { color: var(--pp-danger) !important; font-size: var(--fs-sm); }
/* Bootstrap only shows .invalid-feedback when the invalid control is its PRECEDING SIBLING. Inputs wrapped in
   .auth-field (or any icon/addon wrapper) broke that, so a server-side field error painted a red border and no
   message at all. A filled [data-valmsg-for] slot is shown regardless of where it sits. */
.invalid-feedback[data-valmsg-for]:not(:empty) { display: block; }
.valid-feedback { color: var(--pp-success) !important; font-size: var(--fs-sm); }

/* ===========================================================================
   8. Status pill — ONE system
   ---------------------------------------------------------------------------
   Three competing systems existed: .pp-pill (JS-rendered), Bootstrap
   `.badge bg-*-subtle text-*-emphasis` (9 Razor views), and bare
   `text-success` + icon (5 views). All of them collapse to this: a small dot,
   a label, a muted wash. Never a filled block.
   =========================================================================== */
.pp-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.45em;
    padding: 0.26em 0.75em 0.26em 0.62em;
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    font-weight: 600;
    line-height: 1.45;
    white-space: nowrap;
    max-width: 100%;
    background: var(--pill-wash, var(--pp-sunken));
    color: var(--pill-ink, var(--pp-ink-muted));
    border: 1px solid var(--pill-edge, var(--pp-hairline));
}

.pp-pill em { width: 0.5em; height: 0.5em; border-radius: 50%; background: var(--pill-dot, currentcolor); flex: 0 0 auto; }

/* The status glyph (the shared renderer, _StatusPill and PostPilot.status.badge, draws one instead of the dot): the
   meaning is in the shape and the words as well as the colour. */
.pp-pill > .bi { flex: 0 0 auto; font-size: 0.95em; line-height: 1; margin-inline-end: -0.05em; }

/* The text is the -ink tier, not the base colour: a status colour on a wash of itself measures under 4.5:1 at this size
   (success and warn were 4.37:1), and the darkened inks clear 7:1. The dot and the edge keep the vivid base. */
.pp-pill--success { --pill-wash: rgba(var(--pp-success-rgb), 0.08); --pill-ink: var(--pp-success-ink); --pill-dot: var(--pp-success); --pill-edge: rgba(var(--pp-success-rgb), 0.2); }
.pp-pill--warn { --pill-wash: rgba(var(--pp-warn-rgb), 0.08); --pill-ink: var(--pp-warn-ink); --pill-dot: var(--pp-warn); --pill-edge: rgba(var(--pp-warn-rgb), 0.2); }
.pp-pill--danger { --pill-wash: rgba(var(--pp-danger-rgb), 0.08); --pill-ink: var(--pp-danger-ink); --pill-dot: var(--pp-danger); --pill-edge: rgba(var(--pp-danger-rgb), 0.2); }
.pp-pill--brand { --pill-wash: var(--pp-accent-wash); --pill-ink: var(--pp-accent-ink); --pill-dot: var(--pp-accent); --pill-edge: rgba(var(--pp-accent-rgb), 0.2); }
.pp-pill--neutral { --pill-wash: var(--pp-sunken); --pill-ink: var(--pp-ink-muted); --pill-dot: var(--pp-neutral); --pill-edge: var(--pp-hairline); }

/* A state that is happening right now (Publishing): its glyph breathes. Stopped by the reduced-motion list below. */
.pp-pill--pulse > .bi { animation: pp-pulse 1.6s var(--pp-ease-in-out) infinite; }

/* Teal — "info" — for a state that is informational rather than good or bad (Approved, Prospect, Trial), and for tiers
   (a plan name). It used to be an alias of the coral brand pill, which made Approved and Scheduled the same colour.
   Teal ink is the -ink tier (7.6:1 on its wash); the navy pill carries white. */
.pp-pill--info,
.pp-pill--teal { --pill-wash: rgba(var(--pp-info-rgb), 0.1); --pill-ink: var(--pp-info-ink); --pill-dot: var(--pp-info); --pill-edge: rgba(var(--pp-info-rgb), 0.25); }
.pp-pill--ink { --pill-wash: #0b1a26; --pill-ink: #ffffff; --pill-dot: #f4a78e; --pill-edge: #0b1a26; }
[data-bs-theme="dark"] .pp-pill--ink { --pill-wash: #e6ecf0; --pill-ink: #0b1a26; --pill-dot: #ea6a4d; --pill-edge: #e6ecf0; }

/* Bootstrap badges used in Razor views are remapped onto the same language so
   the two rendering paths cannot drift apart again. */
.badge { border-radius: var(--r-pill); font-weight: 600; font-size: var(--fs-xs); padding: 0.32em 0.7em; }
.badge.bg-success-subtle, .badge.bg-primary-subtle, .badge.bg-danger-subtle,
.badge.bg-warning-subtle, .badge.bg-info-subtle, .badge.bg-secondary-subtle {
    background: var(--pp-sunken) !important;
    color: var(--pp-ink-muted) !important;
    border: 1px solid var(--pp-hairline);
}

/* The "-ink" tier, not the base colour. A status colour sitting on an 8% wash
   of itself is a low-contrast pairing: success and warn both measured 4.37:1
   against their own wash, under the 4.5 needed at this size. The darkened inks
   clear 7:1. */
.badge.bg-success-subtle { color: var(--pp-success-ink) !important; background: rgba(var(--pp-success-rgb), 0.08) !important; }
.badge.bg-danger-subtle { color: var(--pp-danger-ink) !important; background: rgba(var(--pp-danger-rgb), 0.08) !important; }
.badge.bg-warning-subtle { color: var(--pp-warn-ink) !important; background: rgba(var(--pp-warn-rgb), 0.08) !important; }
.badge.bg-primary-subtle, .badge.bg-info-subtle { color: var(--pp-accent-ink) !important; background: var(--pp-accent-wash) !important; }

/* Solid badges — Bootstrap's own palette leaks through these. The unread
   notification count was rendering on #dc3545, a red that appears nowhere else
   in this product. Remapped onto the tokens, which also lifts white-on-red from
   4.53:1 to 6.47:1. */
.badge.bg-danger { background: var(--pp-danger) !important; color: #fff !important; }
.badge.bg-success { background: var(--pp-success) !important; color: #fff !important; }
.badge.bg-warning { background: var(--pp-warn) !important; color: #fff !important; }
.badge.bg-primary, .badge.bg-info { background: var(--pp-accent-fill) !important; color: var(--pp-on-accent) !important; }
.badge.bg-secondary, .badge.bg-dark { background: var(--pp-ink) !important; color: #fff !important; }

/* Alerts, same restraint. */
.alert { border-radius: var(--r-sm); border: 1px solid var(--pp-hairline); background: var(--pp-sunken); color: var(--pp-ink); padding: 0.85rem 1rem; font-size: var(--fs-sm); }
/* Status text on a wash of its own colour is precisely what the "-ink" tier is
   for. .alert-info was the brand coral on a coral wash -- 2.89:1 -- because the
   base colour is a FILL colour, not a text colour. It is also genuinely "info"
   now rather than accent-coloured: --pp-info is teal. */
.alert-success { background: rgba(var(--pp-success-rgb), 0.06); border-color: rgba(var(--pp-success-rgb), 0.22); color: var(--pp-success-ink); }
.alert-warning { background: rgba(var(--pp-warn-rgb), 0.06); border-color: rgba(var(--pp-warn-rgb), 0.22); color: var(--pp-warn-ink); }
.alert-danger { background: rgba(var(--pp-danger-rgb), 0.06); border-color: rgba(var(--pp-danger-rgb), 0.22); color: var(--pp-danger-ink); }
.alert-info { background: rgba(var(--pp-info-rgb), 0.07); border-color: rgba(var(--pp-info-rgb), 0.22); color: var(--pp-info-ink); }

/* ===========================================================================
   8b. Icon tiles and tinted avatars
   ---------------------------------------------------------------------------
   The two devices that make a screen read as AllUpNext rather than as stock
   Bootstrap. A tile is a small FILLED gradient square with a white glyph —
   coral, teal, or ink — exactly as the site draws its feature icons. An
   avatar tint hashes a name to one of six wash/ink pairs so a list of people
   is not a column of identical grey squares (see --pp-tint-* in theme.css).
   =========================================================================== */
.pp-tile {
    --tile-size: 2.75rem;
    width: var(--tile-size);
    height: var(--tile-size);
    border-radius: calc(var(--tile-size) * 0.3);
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    color: #fff;
    font-size: calc(var(--tile-size) * 0.46);
    line-height: 1;
    background: var(--pp-tile-coral);
    box-shadow: 0 6px 14px -8px rgba(var(--c-ink-rgb), 0.5);
}

.pp-tile--teal { background: var(--pp-tile-teal); }
.pp-tile--sm { --tile-size: 2.25rem; }

/* A washed tile for places a filled one would shout (an empty state, a row). */
.pp-tile--wash {
    background: var(--pp-accent-wash-strong);
    color: var(--pp-accent-ink);
    box-shadow: none;
}

.pp-tint-1 { --tint-bg: var(--pp-tint-1-bg); --tint-fg: var(--pp-tint-1-fg); }
.pp-tint-2 { --tint-bg: var(--pp-tint-2-bg); --tint-fg: var(--pp-tint-2-fg); }
.pp-tint-3 { --tint-bg: var(--pp-tint-3-bg); --tint-fg: var(--pp-tint-3-fg); }
.pp-tint-4 { --tint-bg: var(--pp-tint-4-bg); --tint-fg: var(--pp-tint-4-fg); }
.pp-tint-5 { --tint-bg: var(--pp-tint-5-bg); --tint-fg: var(--pp-tint-5-fg); }
.pp-tint-6 { --tint-bg: var(--pp-tint-6-bg); --tint-fg: var(--pp-tint-6-fg); }

/* Platform marks — the one sanctioned exception to the single-accent rule. */
.platform-facebook { color: var(--brand-facebook) !important; }
.platform-instagram { color: var(--brand-instagram) !important; }
.platform-linkedin { color: var(--brand-linkedin) !important; }
.platform-threads { color: var(--brand-threads) !important; }
.platform-google { color: var(--brand-google) !important; }

/* Drawn as a glyph on a dark surface, Threads' black (1.3:1) would vanish and LinkedIn's blue (2.8:1) all but: these keep the hue and
   clear 5:1. The avatar badge is left alone — it uses the same tokens as a FILL behind a white glyph, which is fine as it is. */
[data-bs-theme="dark"] .platform-threads:not(.pp-avatar-badge) { color: #f2f5f7 !important; }
[data-bs-theme="dark"] .platform-linkedin:not(.pp-avatar-badge) { color: #58a8f5 !important; }

/* ===========================================================================
   9. Empty state
   ---------------------------------------------------------------------------
   Twelve different ad-hoc spellings existed across the product — padding
   varied py-4/py-5, four had an icon, one had a CTA, and the notification
   empty state was authored twice (once in Razor, once in JS). One component.
   =========================================================================== */
.pp-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--sp-7) var(--sp-5);
    color: var(--pp-ink-muted);
}

/* A coral-washed tile rather than a grey well: an empty screen is the one place
   a first-time user meets the brand with nothing else on it, so it should not
   look like a disabled control. The glyph is --pp-accent-ink (5.0:1) on the
   wash, not the brand coral. */
.pp-empty-icon {
    width: 56px;
    height: 56px;
    border-radius: var(--r-md);
    display: grid;
    place-items: center;
    background: var(--pp-accent-wash-strong);
    border: 1px solid rgba(var(--pp-accent-rgb), 0.24);
    color: var(--pp-accent-ink);
    font-size: 1.4rem;
    margin-bottom: var(--sp-4);
}

.pp-empty-title { font-family: var(--pp-font-display); font-size: var(--fs-base); font-weight: 600; color: var(--pp-ink); margin: 0 0 0.35rem; }
.pp-empty-body { font-size: var(--fs-sm); margin: 0; max-width: 42ch; }
.pp-empty .btn { margin-top: var(--sp-4); }

/* Compact variant for dropdown panels and table cells. */
.pp-empty--sm { padding: var(--sp-5) var(--sp-4); }
.pp-empty--sm .pp-empty-icon { width: 40px; height: 40px; border-radius: var(--r-sm); font-size: 1.05rem; margin-bottom: var(--sp-3); }

/* ===========================================================================
   10. Loading — one spinner, one skeleton
   =========================================================================== */
.pp-spinner {
    display: inline-block;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    border: 2px solid var(--pp-hairline-strong);
    border-top-color: var(--pp-accent);
    animation: pp-spin 0.6s linear infinite;
    vertical-align: -0.2em;
}

.pp-spinner--lg { width: 1.75rem; height: 1.75rem; border-width: 2.5px; }

/* Bootstrap's spinner is remapped so the two cannot look different. */
.spinner-border {
    border-width: 2px;
    border-color: var(--pp-hairline-strong);
    border-right-color: var(--pp-accent);
    color: var(--pp-accent-ink);
}


.pp-skeleton { display: grid; gap: 0.75rem; padding: var(--sp-5); }

.pp-skel-line {
    height: 12px;
    border-radius: var(--r-xs);
    background: linear-gradient(90deg, var(--pp-sunken) 25%, var(--pp-hairline) 37%, var(--pp-sunken) 63%);
    background-size: 400% 100%;
    animation: pp-skel 1.4s ease infinite;
}

.pp-skel-line.lg { height: 34px; }
.pp-skel-line.sm { height: 9px; width: 55%; }

/* ===========================================================================
   11. Tabs
   ---------------------------------------------------------------------------
   One usage in the product and zero CSS — it rendered as raw Bootstrap and was
   the most off-brand element on screen. Underline tabs, accent on active,
   horizontally scrollable on narrow screens.
   =========================================================================== */
.nav-tabs {
    border-bottom: 1px solid var(--pp-hairline);
    gap: var(--sp-4);
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}

.nav-tabs::-webkit-scrollbar { display: none; }

.nav-tabs .nav-link {
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: 0.65rem 0.1rem;
    margin-bottom: -1px;
    color: var(--pp-ink-muted);
    font-size: var(--fs-sm);
    font-weight: 500;
    white-space: nowrap;
    background: transparent;
    transition: color var(--pp-dur-1) var(--pp-ease-soft), border-color var(--pp-dur-1) var(--pp-ease-soft);
}

.nav-tabs .nav-link:hover { color: var(--pp-ink); border-bottom-color: var(--pp-hairline-strong); }

.nav-tabs .nav-link.active {
    color: var(--pp-accent-ink);
    border-bottom-color: var(--pp-accent-ink);
    background: transparent;
}

/* ===========================================================================
   12. Meter (progress)
   ---------------------------------------------------------------------------
   Six usages, every one with an inline style="height:8px". A label row, a
   track, and a threshold state for "nearly at your limit".
   =========================================================================== */
.pp-meter { display: block; }

.pp-meter-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: 0.4rem;
    font-size: var(--fs-sm);
}

.pp-meter-label { color: var(--pp-ink-muted); }
.pp-meter-value { color: var(--pp-ink); font-weight: 500; font-variant-numeric: tabular-nums; }

.pp-meter-track,
.progress {
    height: 8px;
    border-radius: var(--r-pill);
    background: var(--pp-sunken);
    box-shadow: inset 0 0 0 1px var(--pp-hairline);
    overflow: hidden;
    border: 0;
}

/* The data coral, not the brand coral: a filled bar is a graphic that must be 3:1 against its track, and #EA6A4D is 2.89:1
   (--pp-data-1 is 3.87:1 on paper). */
.pp-meter-fill,
.progress-bar {
    height: 100%;
    border-radius: var(--r-pill);
    background: var(--pp-data-1);
    transition: width var(--pp-dur-3) var(--pp-ease-soft);
}

.pp-meter.is-warning .pp-meter-fill, .progress-bar.bg-warning { background: var(--pp-warn); }
.pp-meter.is-danger .pp-meter-fill, .progress-bar.bg-danger { background: var(--pp-danger); }
.progress-bar.bg-info, .progress-bar.bg-success { background: var(--pp-data-1); }

/* ===========================================================================
   13. Detail list (dl/dt/dd)
   ---------------------------------------------------------------------------
   Three detail views use definition lists with no CSS at all — below 576px the
   dt and dd stacked full-width with no separation.
   =========================================================================== */
.pp-detail-list { display: grid; gap: 0; margin: 0; }

.pp-detail-list > div,
.pp-detail-row {
    display: grid;
    grid-template-columns: minmax(8rem, 30%) 1fr;
    gap: var(--sp-4);
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--pp-hairline);
}

.pp-detail-list > div:last-child, .pp-detail-row:last-child { border-bottom: 0; }
.pp-detail-list dt { color: var(--pp-ink-muted); font-weight: 400; font-size: var(--fs-sm); margin: 0; }
.pp-detail-list dd { color: var(--pp-ink); margin: 0; font-size: var(--fs-sm); min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 575.98px) {
    .pp-detail-list > div, .pp-detail-row { grid-template-columns: 1fr; gap: 0.15rem; }
}

/* ===========================================================================
   14. Chip (filter toggle)
   ---------------------------------------------------------------------------
   Filter chips were `btn-primary` / `btn-light border` swapped in JS, with no
   aria-pressed — a button pretending to be a toggle.
   =========================================================================== */




/* ===========================================================================
   15. Tooltip
   ---------------------------------------------------------------------------
   Twelve title= attributes relied on the browser's native tooltip — slow to
   appear, unstyleable, and invisible to touch users. Bootstrap's tooltip JS is
   already bundled; this skins it.
   =========================================================================== */
.tooltip { --bs-tooltip-bg: var(--pp-ink); --bs-tooltip-color: #fff; --bs-tooltip-border-radius: var(--r-xs); --bs-tooltip-opacity: 1; font-size: var(--fs-xs); }
.tooltip-inner { padding: 0.4rem 0.6rem; font-weight: 500; box-shadow: var(--e-2); max-width: 22rem; }

/* ===========================================================================
   16. List group
   =========================================================================== */
.list-group { border-radius: var(--r-md); }

.list-group-item {
    background: var(--pp-surface);
    border-color: var(--pp-hairline);
    color: var(--pp-ink);
    padding: 0.85rem 1rem;
    font-size: var(--fs-sm);
}

.list-group-item + .list-group-item { border-top-width: 1px; }
.list-group-item-action:hover { background: var(--pp-sunken); color: var(--pp-ink); }

/* ===========================================================================
   17. Touch targets
   ---------------------------------------------------------------------------
   On a coarse pointer every interactive control clears the 44px minimum.
   (Preserved verbatim from the previous system — measured and passing.)
   =========================================================================== */
@media (pointer: coarse) {
    .btn,
    .icon-btn,
    .page-link,
    .dropdown-item,
    .nav-link,
    .navbar-brand,
    .form-control,
    .form-select {
        min-height: var(--pp-tap-min, 44px);
    }

    .btn,
    .page-link {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .btn-sm { min-height: var(--pp-tap-min, 44px); padding-inline: 0.85rem; }
    .icon-btn { min-width: var(--pp-tap-min, 44px); }
    
    /* Bootstrap sizes .btn-close from its background image, landing at ~31px.
       On a phone the modal dismiss is a primary control and needs the floor. */
    .btn-close { min-width: var(--pp-tap-min, 44px); min-height: var(--pp-tap-min, 44px); }

    /* FullCalendar renders its own toolbar buttons at 37px, and they are the
       primary month/week/day navigation on a phone. */
    .fc .fc-button { min-height: var(--pp-tap-min, 44px); }

    /* A checkbox stays visually small but gets a full-size hit area via a
       pseudo-element, rather than being blown up to 44px on screen. */
    .form-check-input { width: 1.35em; height: 1.35em; margin-top: 0.1em; position: relative; }
    .form-check-input::after { content: ""; position: absolute; inset: -12px; }
    .form-check-label { min-height: var(--pp-tap-min, 44px); display: inline-flex; align-items: center; }

    /* Standalone inline links ("Forgot password?", "Create one") have room
       around them, so a hit pad is fine and keeps the text where it is.
       A link-button (.btn) already has its own height and centring: it is left alone. */
    .auth-card a:not(.btn) { position: relative; display: inline-block; }
    .auth-card a:not(.btn)::after { content: ""; position: absolute; inset: -13px -8px; }

    .pp-identity-avatar { position: relative; }
    .pp-identity-avatar::after { content: ""; position: absolute; inset: -3px; }
    .pp-identity-name { position: relative; padding-block: 0.35rem; }
}

/* iOS Safari zooms the viewport whenever a focused field is under 16px. The
   fluid --fs-base bottoms out below 16px on phones, so pin the literal value
   where it matters — the 9 auth screens, the composer and every modal form. */
@media (max-width: 575.98px) {
    .form-control,
    .form-select,
    textarea,
    input[type="text"],
    input[type="email"],
    input[type="password"],
    input[type="number"],
    input[type="search"],
    input[type="url"],
    input[type="tel"],
    input[type="date"],
    input[type="datetime-local"],
    .select2-container--bootstrap-5 .select2-search__field {
        font-size: 16px;
    }
}

/* ===========================================================================
   18. Performance tier
   ---------------------------------------------------------------------------
   Much smaller than it used to be: with no glass, blur, aurora or grain there
   is almost nothing expensive left to switch off. What remains is stopping the
   decorative loops on low-power devices.
   =========================================================================== */
[data-fx-tier="lite"] .pp-skel-line { animation-duration: 2.4s; }
[data-fx-tier="lite"] [data-tilt] { transform: none !important; }

/* ===========================================================================
   19. Print
   ---------------------------------------------------------------------------
   Browsers default to `print-color-adjust: economy`, which drops backgrounds
   but keeps `color`. Un-fill anything coloured and drop the chrome.
   =========================================================================== */
@media print {
    body { background: #fff !important; color: #000 !important; }
    .app-sidebar, .app-topbar, .pp-bottomnav, .pp-sheet, .pp-skip-link, .pp-row-actions, .mk-nav { display: none !important; }
    .app-main { margin-left: 0 !important; }

    .card, .stat-card {
        background: #fff !important;
        color: #000 !important;
        border: 1px solid #999 !important;
        box-shadow: none !important;
        break-inside: avoid;
    }

    .pp-pill { border: 1px solid #666 !important; color: #000 !important; background: none !important; }
    .btn { display: none !important; }
    a[href]::after { content: ""; }

    /* The navy is not printed (lit.css), so the light ink made for it goes too — or its text comes out white on white. */
    .pp-on-ink {
        color: #000;
        --pp-ink: #000;
        --pp-ink-muted: #333;
        --pp-ink-subtle: #444;
        --pp-heading: #000;
        --pp-accent-ink: #a93a20;
        --pp-accent-deep: #a93a20;
        --pp-accent-on-ink: #a93a20;          /* the greeting's name (.pp-accent-name) */
        --pp-link: #13526a;
        --pp-hairline: #999;
        --pp-hairline-strong: #999;
    }
}

/* ===========================================================================
   20. Reduced motion — the single source of truth
   ---------------------------------------------------------------------------
   A duration-token flip rather than a `*{ animation-duration:.001ms }` sledge-
   hammer. Because every transition in this codebase is written as
   `var(--pp-dur-N) var(--pp-ease-*)`, zeroing the tokens disables travel-based
   motion by construction — with no wildcard selector and no !important — while
   leaving affordances like the focus ring intact. Looping animations are then
   stopped by an explicit list, never a wildcard.

   data-pp-motion is a user-settable override persisted like pp-theme: the OS
   preference is the default, not the ceiling.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
    :root { --pp-dur-1: 0.001s; --pp-dur-2: 0.001s; --pp-dur-3: 0.001s; --pp-dur-4: 0.001s; }
    html { scroll-behavior: auto; }
}

html[data-pp-motion="reduced"] {
    --pp-dur-1: 0.001s;
    --pp-dur-2: 0.001s;
    --pp-dur-3: 0.001s;
    --pp-dur-4: 0.001s;
    scroll-behavior: auto;
}

@media (prefers-reduced-motion: reduce) {
    .pp-skel-line, .pp-spinner, .spinner-border, .btn.is-busy::after, .mk-chip em.ok, .pp-pill--pulse > .bi { animation: none !important; }
    /* A busy button still needs to read as busy without spinning. */
    .btn.is-busy::after { border-top-color: currentcolor; opacity: 0.55; }
}

html[data-pp-motion="reduced"] .pp-skel-line,
html[data-pp-motion="reduced"] .pp-spinner,
html[data-pp-motion="reduced"] .spinner-border,
html[data-pp-motion="reduced"] .btn.is-busy::after,
html[data-pp-motion="reduced"] .mk-chip em.ok,
html[data-pp-motion="reduced"] .pp-pill--pulse > .bi { animation: none !important; }

/* Long-form copy — Privacy, Terms, Data deletion, the landing FAQ. Lives here
   rather than in the app sheets because those pages render on the marketing layout,
   which loads base + marketing and never the app sheets: a rule placed there simply had
   no effect and the text ran the full 1140px of the container. */

/* Dark theme deltas for the form primitives. They lived in the app stylesheet, which the sign-in
   pages do not load, so dark inputs there fell back to the light-theme fill. */
[data-bs-theme="dark"] .btn-light { background: rgba(255, 255, 255, 0.07); color: var(--pp-ink); }
[data-bs-theme="dark"] .form-control, [data-bs-theme="dark"] .form-select { background: rgba(255, 255, 255, 0.05); }
[data-bs-theme="dark"] .form-control:focus, [data-bs-theme="dark"] .form-select:focus { background: var(--pp-surface); }
