/* GENERATED by build-css.php from style.css. Do not edit by hand.
   Rules removed: only those whose every selector class is used by an admin
   template and by nothing on the storefront. Anything shared or unclassifiable
   is kept, so this can be larger than necessary but never missing a rule.
   Regenerate after every style.css change: php build-css.php apply */
/* =========================================================================
   Prime Core Dynamics — Phase 1
   Strict monochrome. No accent color. No rounded corners. Hairline borders.
   Inter (UI/body) + JetBrains Mono (data).
   ========================================================================= */

:root {
    --black:      #0a0a0a;
    --surface-dark: #1c1c1c;   /* matte black for large dark panels (nav, login) */
    --border-dark:  #3a3a3a;   /* hairlines/borders drawn on --surface-dark */
    --footer-bg:    #1B4DB1;   /* brand blue (placeholder — swap for your logo hex) */
    --accent-orange: #E8590C;  /* deep premium orange — Order pill (empty cart) */
    --accent-warm:   #F5B43C;  /* warm amber — SEO card icon blocks (reusable) */
    --accent-green:  #1b8a4e;  /* Order pill when the cart has items */
    --accent-blue:   #2E5BE0;  /* login tabs/button/glows/seam accent */
    --hero-glow:     46, 91, 224;  /* brand-blue RGB triplet for the hero glow blobs (adjust hex here) */
    --off-white:  #fafafa;
    --white:      #ffffff;
    --border:     #e4e4e4;
    --border-2:   #d4d4d4;
    --muted:      #6b6b6b;
    --muted-2:    #9a9a9a;
    --muted-3:    #b8b8b8;
    --ink:        #141414;

    --font-ui:   'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

    --maxw: 1240px;
    --step: 8px;

    /* THE storefront gutter. The nav bar, the hero content and the catalog
       (filters, count, product grid, SEO + FAQ blocks) all read this, so their
       left and right edges are the same number by construction and cannot drift
       apart again. The values are the catalog's existing ones — the grid and the
       card widths are unchanged; the nav and hero moved to meet them. */
    --gutter: 60px;

    /* The other half of the alignment contract. The product grid is capped at this
       and centred, so above ~1560px viewport it stops at 1440 while a container
       with only --gutter keeps growing — that is how the nav, hero and footer
       drifted away from the grid. Anything that must line up with the grid uses
       BOTH: max-width calc(--content-max + 2 * --gutter) plus margin auto. */
    --content-max: 1440px;
}
@media (max-width: 900px) { :root { --gutter: 32px; } }

@media (max-width: 640px) { :root { --gutter: 18px; } }

@media (max-width: 400px) { :root { --gutter: 12px; } }


/* ---- Reset ------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute must always win over author display rules. Without this a
   rule like `.iname-chip { display: inline-block }` re-shows a hidden element (the
   chip + dashed add button were rendering stacked because of exactly that). */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }  /* reduced-motion resets this to auto */

body {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 15px;
    line-height: 1.55;
    color: var(--ink);
    background: var(--off-white);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    /* Sticky footer: full-height flex column so the footer is pushed to the
       viewport bottom on short pages and flows below content on long ones. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}
/* The primary content region grows to fill the space above the footer, site-wide
   (storefront <main>, admin/login shells already set their own min-height:100vh). */
body > main,
body > .admin-shell {
    flex: 1 0 auto;
}

img { max-width: 100%; display: block; }

a { color: var(--black); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.01em; line-height: 1.1; }

button { font-family: inherit; cursor: pointer; }

input, textarea, select, button { font-size: 15px; }

.mono { font-family: var(--font-mono); font-variant-ligatures: none; }

/* ---- Shared bits ------------------------------------------------------- */

.eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0 0 12px;
}
.eyebrow-invert { color: var(--muted-3); }

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: var(--black);
    color: var(--white);
    border: 1px solid var(--black);
    padding: 13px 22px;
    font-weight: 500;
    letter-spacing: 0.01em;
    line-height: 1;
    transition: background .15s ease, color .15s ease;
}
.btn:hover { background: #262626; text-decoration: none; }
.btn-block { width: 100%; }
.btn-outline {
    background: transparent;
    color: var(--black);
}
.btn-outline:hover { background: var(--black); color: var(--white); }

.alert {
    padding: 12px 16px;
    border: 1px solid var(--border-2);
    margin: 0 0 20px;
    font-size: 14px;
}
.alert-error { border-color: var(--black); background: var(--white); }
.alert-success { border-color: var(--black); background: var(--white); }

.hint { color: var(--muted-2); font-weight: 400; }

/* =========================================================================
   FORMS
   ========================================================================= */

.field { margin-bottom: 18px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

.field label {
    display: block;
    font-size: 13px;
    font-weight: 500;
    margin-bottom: 7px;
    color: var(--ink);
}

.field input,
.field textarea,
.contact-form input,
.contact-form textarea {
    width: 100%;
    background: var(--white);
    border: 1px solid var(--border-2);
    padding: 12px 14px;
    color: var(--ink);
    transition: border-color .15s ease;
}
.field input:focus,
.field textarea:focus {
    outline: none;
    border-color: var(--black);
}
textarea { resize: vertical; font-family: inherit; }

/* Error text reads as an error. The dark login panel overrides this further down. */
.field-error { color: #c0392b; font-size: 12.5px; margin: 6px 0 0; font-weight: 500; }
.field-error::before { content: '— '; }

/* ---- Validation feedback ------------------------------------------------
   Applied by form-validate.js on a blocked submit AND server-side on a
   rejected post, so both look identical to the customer. */
.field input.is-invalid,
.field select.is-invalid,
.field textarea.is-invalid,
input.is-invalid,
select.is-invalid,
textarea.is-invalid {
    border: 1px solid #c0392b;
    background: rgba(192, 57, 43, .04);
}
.field input.is-invalid:focus,
input.is-invalid:focus,
select.is-invalid:focus,
textarea.is-invalid:focus { outline: none; border-color: #c0392b; }

/* Group highlight, for a control the customer sees as one block rather than as an
   input: the shipping-method radios. The banner must always have something to
   point at, and a bare radio has no border to colour. */
.is-invalid-group {
    border: 1px solid #c0392b;
    background: rgba(192, 57, 43, .04);
    border-radius: 8px;
    padding: 8px;
}

.form-error-summary {
    border: 1px solid #c0392b;
    background: rgba(192, 57, 43, .06);
    color: #c0392b;
    border-radius: 8px;
    padding: 10px 14px;
    margin: 0 0 16px;
    font-size: 13.5px;
    font-weight: 500;
}

/* Inline "required" marker inside a label, so the requirement is visible before a
   failed submit rather than only in the error underneath. */
.field-required {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
    margin-left: 6px;
}

/* "Did you mean …?" under an email field. A warning, never a blocker, so it is
   deliberately quieter than .field-error and carries no error colour. */
.email-hint { font-size: 12.5px; margin: 6px 0 0; color: #b8860b; line-height: 1.45; }
.email-hint[hidden] { display: none; }
.email-hint-fix { color: #b8860b; font-weight: 600; text-decoration: underline; cursor: pointer; }
.email-hint-fix:hover { color: #8a6508; }

.fineprint { font-size: 12.5px; color: var(--muted); margin: 14px 0 0; }

/* Spam-folder notice. Shared by the confirmation page and the pay page, both of
   which render it through spam_note_html() so the address inside is emphasised.

   Informational blue, not warning yellow and not error red: nothing has gone wrong
   yet, and colouring it like a problem would make every successful checkout look
   like one. Solid background, no transparency, per the house rule. Type is 12.5px
   to sit on the same step as .small and .fineprint around it. */
.spam-note {
    font-size: 12.5px;
    line-height: 1.6;
    color: #20408f;
    background: #f4f7ff;
    border: 1px solid #ccd9fb;
    border-radius: 10px;
    padding: 11px 14px;
    margin: 10px 0 0;
}
/* The address itself. Wraps rather than overflowing: a long address on a 360px
   screen must not push the panel wider than the column. */
.spam-note .mono {
    font-weight: 650;
    word-break: break-word;
    overflow-wrap: anywhere;
}

/* Disclaimer checkboxes */
.disclaimers {
    border: 1px solid var(--border);
    padding: 16px 16px 6px;
    margin: 8px 0 22px;
}
.disclaimers legend {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 0 6px;
}
.check {
    display: flex;
    gap: 11px;
    align-items: flex-start;
    font-size: 13px;
    line-height: 1.45;
    margin-bottom: 14px;
    color: var(--ink);
    cursor: pointer;
}
.check input {
    margin: 2px 0 0;
    width: 16px;
    height: 16px;
    accent-color: var(--black);
    flex: 0 0 auto;
}
.req {
    display: inline-block;
    font-family: var(--font-mono);
    font-style: normal;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted-2);
    margin-left: 6px;
}

/* =========================================================================
   LOGIN — split screen (image visual + dark auth card), #0d0d0f
   ========================================================================= */

.login-body { background: #0d0d0f; }

.login-split {
    position: relative;
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    min-height: 100vh;
}

/* Soft blue seam blending the two panels (above panels, below content). */
.login-seam {
    position: absolute;
    top: 0; bottom: 0;
    left: 52.5%;
    width: 360px;
    transform: translateX(-50%);
    background: linear-gradient(90deg,
        rgba(46, 91, 224, 0) 0%,
        rgba(46, 91, 224, 0.11) 45%,
        rgba(46, 91, 224, 0.06) 62%,
        rgba(0, 0, 0, 0) 100%);
    filter: blur(34px);
    pointer-events: none;
    z-index: 2;
}

/* ---- LEFT: full-bleed visual ---------------------------------------------- */
.login-visual {
    position: relative;
    background-color: #0d0d0f;   /* fallback when no image is set */
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    color: #fff;
    display: flex;
    overflow: hidden;
    padding: 54px 60px 48px;
}
.login-visual-overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(180deg,
        rgba(13, 13, 15, 0.55) 0%,
        rgba(13, 13, 15, 0.40) 42%,
        rgba(13, 13, 15, 0.82) 100%);
}

/* Drifting blue glows (no grid) */
.login-glow {
    position: absolute;
    z-index: 1;
    border-radius: 50%;
    pointer-events: none;
    filter: blur(90px);
}
.login-glow-1 {
    width: 620px; height: 620px;
    top: -190px; left: -110px;
    background: radial-gradient(circle, rgba(46, 91, 224, 0.50), transparent 68%);
    animation: login-glow-a 18s ease-in-out infinite alternate;
}
.login-glow-2 {
    width: 520px; height: 520px;
    bottom: -180px; right: -90px;
    background: radial-gradient(circle, rgba(46, 91, 224, 0.34), transparent 70%);
    animation: login-glow-b 24s ease-in-out infinite alternate;
}
.login-visual.has-bg .login-glow { opacity: 0.5; }   /* don't muddy the image */
@keyframes login-glow-a {
    from { transform: translate(0, 0); }
    to   { transform: translate(80px, 55px); }
}
@keyframes login-glow-b {
    from { transform: translate(0, 0); }
    to   { transform: translate(-85px, -40px); }
}

.login-visual-inner {
    position: relative;
    z-index: 3;
    width: 100%;
    display: flex;
    flex-direction: column;
}
.login-logo { align-self: center; display: inline-block; }
.login-visual .login-logo-img { height: 120px; width: auto; }
.login-visual .logo-wordmark { color: #fff; font-size: 30px; }

/* Three equal 80%-width glass boxes, centered between logo and bottom block */
.login-features {
    width: 80%;
    align-self: center;
    margin: auto 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}
/* Apple "liquid glass": translucent gradient fill (light catches the top edge),
   a glowing brand-blue border, and a faint top inner highlight for the glossy edge. */
.login-feature {
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.03));
    border: 1px solid rgba(46, 91, 224, 0.50);
    border-radius: 18px;
    padding: 15px 22px;
    -webkit-backdrop-filter: blur(20px) saturate(160%);
    backdrop-filter: blur(20px) saturate(160%);
    box-shadow:
        0 0 20px rgba(46, 91, 224, 0.25),
        0 0 0 1px rgba(46, 91, 224, 0.40),
        inset 0 1px 0 rgba(255, 255, 255, 0.15);
    transition: box-shadow .2s ease, border-color .2s ease;
}
/* Subtle hover: intensify the glow. */
.login-feature:hover {
    border-color: rgba(46, 91, 224, 0.65);
    box-shadow:
        0 0 28px rgba(46, 91, 224, 0.38),
        0 0 0 1px rgba(46, 91, 224, 0.55),
        inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
/* Fallback where backdrop-filter is unsupported: a semi-opaque dark fill so the
   boxes never turn into unreadable light gradients on the dark panel. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .login-feature { background: rgba(16, 20, 38, 0.88); }
}
.login-feature-title { margin: 0; font-size: 19px; font-weight: 600; color: #fff; }
.login-feature-text { margin: 5px 0 0; font-size: 15px; line-height: 1.45; color: rgba(255, 255, 255, 0.68); }

/* Bottom block: same 80% width + centering, anchored to bottom */
.login-visual-foot { width: 80%; align-self: center; margin-top: auto; }
.login-bottom-label {
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.42);
    margin: 0;
}
.login-chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0 18px; }
.login-chip {
    font-size: 14px;
    color: rgba(255, 255, 255, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 999px;
    padding: 7px 17px;
}
.login-social { margin: 0; font-size: 15.5px; color: rgba(255, 255, 255, 0.55); }
.login-social-stat { color: #fff; font-weight: 700; }

/* ---- RIGHT: auth card ----------------------------------------------------- */
.login-auth {
    position: relative;
    background: #0d0d0f;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 54px 60px;
}
.login-auth::before {   /* subtle wash toward the seam */
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(255, 255, 255, 0.022), rgba(255, 255, 255, 0) 60%);
}
.auth-card { position: relative; z-index: 3; width: 100%; max-width: 470px; }

.tabs {
    display: flex;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.09);
    border-radius: 13px;
    padding: 5px;
    margin-bottom: 28px;
}
.tab {
    flex: 1;
    background: none;
    border: none;
    border-radius: 9px;
    padding: 12px 0;
    font-size: 16px;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.52);
    transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.tab.is-active {
    background: var(--accent-blue);
    color: #fff;
    box-shadow: 0 5px 18px rgba(46, 91, 224, 0.32);
}
.tab:not(.is-active):hover { color: #fff; }
.auth-form.is-hidden { display: none; }

/* Fields on the dark card */
.login-auth .field label { font-size: 14.5px; font-weight: 500; color: rgba(255, 255, 255, 0.80); margin-bottom: 9px; }
.login-auth .field label .hint { color: rgba(255, 255, 255, 0.40); font-weight: 400; }
.input-icon { position: relative; }
.input-icon-svg {
    position: absolute;
    left: 16px; top: 50%;
    transform: translateY(-50%);
    width: 18px; height: 18px;
    color: rgba(255, 255, 255, 0.38);
    pointer-events: none;
}
.login-auth .input-icon input {
    width: 100%;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.11);
    border-radius: 12px;
    padding: 14px 46px 14px 46px;
    color: #fff;
    transition: border-color .15s ease, background .15s ease;
}
.login-auth .input-icon input::placeholder { color: rgba(255, 255, 255, 0.30); }
.login-auth .input-icon input:focus {
    outline: none;
    border-color: rgba(46, 91, 224, 0.70);
    background: rgba(255, 255, 255, 0.06);
}
.pw-toggle {
    position: absolute;
    right: 10px; top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 6px;
    color: rgba(255, 255, 255, 0.38);
    display: inline-flex;
}
.pw-toggle:hover { color: rgba(255, 255, 255, 0.85); }
.pw-toggle svg { width: 18px; height: 18px; }
.login-auth .field-error { color: #f0a58f; }

/* Primary button — blue, white text (no cream) */
.btn-auth {
    display: block;
    width: 100%;
    background: var(--accent-blue);
    color: #fff;
    border: none;
    border-radius: 12px;
    padding: 16px;
    font-size: 17px;
    font-weight: 600;
    cursor: pointer;
    box-shadow: 0 7px 24px rgba(46, 91, 224, 0.30);
    transition: background .15s ease, transform .05s ease;
}
.btn-auth:hover { background: #264fc4; }
.btn-auth:active { transform: translateY(1px); }

/* Agreements box */
.login-auth .disclaimers {
    border: 1px solid rgba(255, 255, 255, 0.09);
    border-radius: 13px;
    padding: 18px 18px 16px;
    margin: 6px 0 22px;
    background: rgba(255, 255, 255, 0.018);
}
.login-auth .disclaimers legend {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.40);
    margin-bottom: 4px;
    padding: 0;
}
.login-auth .check {
    gap: 11px;
    font-size: 14px;
    color: rgba(255, 255, 255, 0.68);
    line-height: 1.45;
    margin-bottom: 12px;
}
.login-auth .check:last-of-type { margin-bottom: 0; }
.login-auth .check input {
    width: 18px; height: 18px;
    border-radius: 5px;
    border: 1px solid rgba(255, 255, 255, 0.28);
    accent-color: var(--accent-blue);
}
.login-auth .req {
    font-family: var(--font-mono);
    font-size: 10px;
    color: rgba(255, 255, 255, 0.34);
    text-transform: uppercase;
}

.auth-fineprint {
    margin: 16px 0 0;
    font-size: 13.5px;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.45);
    text-align: center;
}
.auth-fineprint a { color: rgba(255, 255, 255, 0.75); text-decoration: underline; text-underline-offset: 2px; }
.auth-fineprint a:hover { color: #fff; }

.auth-divider { height: 1px; background: rgba(255, 255, 255, 0.08); margin: 24px 0 18px; }

.trust-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; }
.trust { display: flex; align-items: center; gap: 8px; font-size: 13px; color: rgba(255, 255, 255, 0.50); }
.trust svg { width: 16px; height: 16px; color: rgba(255, 255, 255, 0.45); flex: 0 0 auto; }

.auth-closing { text-align: center; font-size: 13.5px; color: rgba(255, 255, 255, 0.34); margin: 17px 0 0; }

/* Alerts on the dark card */
.login-auth .alert { border-color: rgba(255, 255, 255, 0.22); background: rgba(255, 255, 255, 0.05); color: #fff; }

/* Freeze glows for reduced motion (static glow still shows) */
@media (prefers-reduced-motion: reduce) {
    .login-glow { animation: none; }
}


/* =========================================================================
   TOP BAR (gated)
   ========================================================================= */

/* The <header> band. It stays a dark fill on the light storefront pages (contact,
   cart, checkout, account, FAQ, COA library) so the white-on-glass bar inside it
   stays readable; on the dark catalog it goes transparent and the bar genuinely
   floats over the page background. No backdrop-filter anywhere in here. */
.topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--surface-dark);
    color: var(--white);
}
.topbar-inner {
    /* The bar sits INSIDE the page padding rather than edge to edge. --gutter alone
       is not enough: the grid is also capped at --content-max, so the bar has to be
       capped identically or it runs wider on a display over ~1560px. */
    max-width: calc(var(--content-max) + 2 * var(--gutter));
    margin: 0 auto;
    padding: 14px var(--gutter);
}

/* ---- Floating glass bar ------------------------------------------------ */
/* Two states. At rest the bar is barely there; once the page has scrolled it
   fills in so content passing underneath cannot read through the logo and links.
   Only background-color and border-color transition — the blur radius is a fixed
   value that switches with the class, never animated, because animating a filter
   forces a re-rasterize on every frame (the exact cost that got the old overlay
   blurs deleted). The 0.72 alpha fill carries the bar on its own if a device
   drops the filter. */
.navbar {
    position: relative;
    display: flex;
    align-items: center;
    gap: 22px;
    height: 58px;
    padding: 0 14px;
    background-color: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 14px;
    transition: background-color 0.25s ease, border-color 0.25s ease;
}
.topbar.is-scrolled .navbar {
    background-color: rgba(16, 18, 22, 0.72);
    border-color: rgba(255, 255, 255, 0.12);
    -webkit-backdrop-filter: blur(16px) saturate(1.3);
    backdrop-filter: blur(16px) saturate(1.3);
}

/* ---- Light storefront pages -------------------------------------------
   Only the dark catalog gets the dark bar. Everywhere else (product, contact,
   cart, checkout, account, FAQ, COA library) the page is near-white, so a dark
   bar reads as a black slab dropped on the page. Same geometry, same 12px
   trigger, same blur values and the same 0.72 fill alpha — mirrored to a light
   surface with dark text, and the same fallback logic: if a device drops the
   filter, the 0.72 fill still carries the bar on its own. */
body:not(.catalog-dark) .topbar { background: transparent; }
body:not(.catalog-dark) .navbar { border-color: rgba(0, 0, 0, 0.07); }
body:not(.catalog-dark) .topbar.is-scrolled .navbar {
    background-color: rgba(252, 252, 253, 0.72);
    border-color: rgba(0, 0, 0, 0.1);
}

/* Text + icons invert. Resting sits at 5.3:1 on the light fill, the hover/active
   states at 14:1 — both clear AA. */
body:not(.catalog-dark) .nav-item,
body:not(.catalog-dark) .nav-icon-btn,
body:not(.catalog-dark) .nav-avatar { color: rgba(17, 17, 17, 0.62); }
body:not(.catalog-dark) .nav-avatar { background: rgba(0, 0, 0, 0.05); }
body:not(.catalog-dark) .nav-item:hover,
body:not(.catalog-dark) .nav-item:focus-visible,
body:not(.catalog-dark) .nav-icon-btn:hover,
body:not(.catalog-dark) .nav-icon-btn:focus-visible,
body:not(.catalog-dark) .nav-avatar:hover,
body:not(.catalog-dark) .nav-avatar:focus-visible,
body:not(.catalog-dark) .nav-avatar[aria-expanded="true"] {
    color: #111;
    background: rgba(46, 91, 224, 0.1);
    border-color: rgba(46, 91, 224, 0.35);
    box-shadow: 0 0 12px rgba(46, 91, 224, 0.18);
}
body:not(.catalog-dark) .nav-item.is-active,
body:not(.catalog-dark) .nav-item.is-active:hover {
    color: #111;
    background: rgba(46, 91, 224, 0.14);
    border-color: rgba(46, 91, 224, 0.5);
}
/* The cart pill is brand blue on both surfaces — never inverted. */

/* Focus rings on a light bar have to be dark; the global .topbar rule makes them
   white, which is invisible here. */
body:not(.catalog-dark) .topbar :focus-visible { outline-color: var(--black); }

/* The two panels that hang off the bar follow it, or a dark popover ends up
   attached to a light bar. */
body:not(.catalog-dark) .nav-menu,
body:not(.catalog-dark) .nav-search {
    background: #ffffff;
    border-color: rgba(0, 0, 0, 0.1);
    box-shadow: 0 18px 40px rgba(10, 10, 10, 0.16);
}
body:not(.catalog-dark) .nav-menu-item { color: rgba(17, 17, 17, 0.78); }
body:not(.catalog-dark) .nav-menu-item:hover,
body:not(.catalog-dark) .nav-menu-item:focus-visible { background: rgba(46, 91, 224, 0.1); color: #111; }
body:not(.catalog-dark) .nav-menu-item .mnav-ico { color: rgba(17, 17, 17, 0.4); }
body:not(.catalog-dark) .nav-menu-item:hover .mnav-ico { color: var(--accent-blue); }
body:not(.catalog-dark) .nav-menu-sep { background: rgba(0, 0, 0, 0.09); }
body:not(.catalog-dark) .nav-search-ico { color: rgba(17, 17, 17, 0.4); }
body:not(.catalog-dark) .nav-search input { color: #111; }
body:not(.catalog-dark) .nav-search input::placeholder { color: rgba(17, 17, 17, 0.4); }
body:not(.catalog-dark) .nav-search-close { color: rgba(17, 17, 17, 0.5); }
body:not(.catalog-dark) .nav-search-close:hover { background: rgba(0, 0, 0, 0.05); color: #111; }

.nav-brand { display: flex; align-items: center; flex: 0 0 auto; }
.nav-brand:hover { text-decoration: none; }
.nav-logo { height: 30px; width: auto; display: block; }
.nav-brand .logo-wordmark { color: #fff; font-size: 16px; font-weight: 700; }

.nav-links { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }

/* Shared pill treatment: nav items, the search icon and the avatar all resting
   transparent and lighting up blue on hover/focus. */
.nav-item,
.nav-icon-btn,
.nav-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    border: 1px solid transparent;
    background: transparent;
    color: rgba(255, 255, 255, 0.55);
    cursor: pointer;
    transition: all 0.18s ease;
}
.nav-item {
    padding: 0 13px;
    border-radius: 9px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
}
.nav-item:hover,
.nav-item:focus-visible,
.nav-icon-btn:hover,
.nav-icon-btn:focus-visible,
.nav-avatar:hover,
.nav-avatar:focus-visible,
.nav-avatar[aria-expanded="true"] {
    color: #ffffff;
    background: rgba(46, 91, 224, 0.14);
    border-color: rgba(46, 91, 224, 0.35);
    box-shadow: 0 0 12px rgba(46, 91, 224, 0.28);
    text-decoration: none;
}
.nav-item.is-active,
.nav-item.is-active:hover {
    color: #ffffff;
    background: rgba(46, 91, 224, 0.2);
    border-color: rgba(46, 91, 224, 0.5);
}

/* ---- Shop dropdown, the category tier -----------------------------------
   HIDDEN WITH CSS, NEVER BUILT BY SCRIPT. Every item is a real <a href> that
   ships in the server HTML; this only decides when it is visible. A menu
   injected on hover is a menu a crawler never sees, and linking the ten
   category pages from every page is the entire reason this exists.

   Opens on hover AND on focus-within, so it works from the keyboard with no
   JavaScript at all. visibility rather than display so the transition runs and
   so the items stay focusable in source order. */
.nav-drop { position: relative; }
.nav-drop-top { display: inline-flex; align-items: center; gap: 5px; }
.nav-drop-chev {
    width: 0; height: 0; margin-top: 1px;
    border-left: 4px solid transparent; border-right: 4px solid transparent;
    border-top: 4px solid currentColor; opacity: .7;
}
/* Colours, radii and type sizes below are the approved shop mockup's, so the
   dropdown and the shop page's class panel read as one component in two places.
   overflow:hidden is what lets the footer row sit flush in the rounded corner
   without a second radius on it. */
.nav-drop-menu {
    position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
    /* 620px. At about 460 the two columns and the certificates block read
       cramped, and the block's body wrapped to three lines. */
    width: 620px; max-width: calc(100vw - 32px);
    background: #12151c; border: 0.5px solid rgba(255, 255, 255, 0.09);
    border-radius: 12px; overflow: hidden;
    box-shadow: 0 22px 48px rgba(0, 0, 0, 0.45);
    opacity: 0; visibility: hidden; transform: translateY(-4px);
    transition: opacity .14s ease, transform .14s ease, visibility .14s;
}
.nav-drop:hover .nav-drop-menu,
.nav-drop:focus-within .nav-drop-menu {
    opacity: 1; visibility: visible; transform: translateY(0);
}
.nav-drop-cols {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    position: relative; padding: 24px 28px 22px;
}
/* Each column is inset 28px from the centre line, so the gutter is 56px with the
   divider down the middle of it. */
.nav-drop-col { min-width: 0; }
.nav-drop-col:first-child { padding-right: 28px; }
.nav-drop-col + .nav-drop-col { padding-left: 28px; }
/* The rule between the columns is an INSET POSITIONED HAIRLINE on the container,
   not a border on either column: a border paints one side of a box, which the
   house rules forbid. left:50% centres it in the gutter; top/bottom inset so it
   stops short of the panel padding rather than running into the rounded corners. */
.nav-drop-cols::before {
    content: "";
    position: absolute; left: 50%; top: 26px; bottom: 24px;
    width: 1px; background: rgba(255, 255, 255, 0.08);
}

/* The certificates block. Fills the space under Supplies in the right column. */
/* Icon and heading on one row, body underneath at full width. */
.nav-drop-coa {
    display: block;
    margin-top: 24px; padding: 16px 17px;
    border-radius: 10px; text-decoration: none;
    background: #171b24;
}
.nav-drop-coa:hover, .nav-drop-coa:focus-visible { background: #1c212c; }
.nav-drop-coa-h { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.nav-drop-coa-h svg { width: 17px; height: 17px; color: #2E5BE0; flex: 0 0 auto; }
.nav-drop-coa-t { font-size: 13px; font-weight: 600; color: #fff; }
.nav-drop-coa-b { display: block; font-size: 12px; line-height: 1.55; color: #8b8f9a; }
.nav-drop-head {
    display: block; margin-bottom: 15px;
    font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
    color: #6d717c;
}
/* Second and later group headings in a column need air above them, or Supplies
   reads as the last row of Compounds. */
.nav-drop-head ~ .nav-drop-head { margin-top: 24px; }
.nav-drop-item {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 10px 13px; border-radius: 7px; text-decoration: none;
}
.nav-drop-label {
    font-size: 13px; color: #c9ccd3;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-drop-count { font-size: 12px; color: #6d717c; flex: 0 0 auto; }
.nav-drop-item:hover, .nav-drop-item:focus-visible { background: rgba(255, 255, 255, 0.05); }
.nav-drop-item:hover .nav-drop-label,
.nav-drop-item:focus-visible .nav-drop-label { color: #fff; }
.nav-drop-item.is-active { background: rgba(255, 255, 255, 0.055); }
.nav-drop-item.is-active .nav-drop-label { color: #fff; }
/* The catch-all row, full width on its own darker ground so it reads as the way
   out of the menu rather than as a sibling of Bacteriostatic Water above it. */
.nav-drop-foot {
    position: relative;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 15px 28px; text-decoration: none;
    background: #0f1219;
}
.nav-drop-foot::before {
    content: "";
    position: absolute; left: 0; right: 0; top: 0;
    height: 1px; background: rgba(255, 255, 255, 0.06);
}
.nav-drop-foot .nav-drop-label { font-size: 13px; font-weight: 600; color: #2E5BE0; }
.nav-drop-foot:hover { background: #131720; }
/* The desktop nav starts at 768 (below that the hamburger takes over), so 768 is
   the tightest width this panel ever renders at. 620px starting from the Shop
   link's x position runs past the right edge there and clips the footer count, so
   it steps down until there is room for the full width. Later in the file than
   the base .nav-drop-menu rule, which is what makes it win: a media query adds no
   specificity of its own. */
@media (max-width: 1023px) {
    .nav-drop-menu { width: 552px; }
    .nav-drop-cols { padding: 20px 22px 18px; }
    .nav-drop-col:first-child { padding-right: 20px; }
    .nav-drop-col + .nav-drop-col { padding-left: 20px; }
    .nav-drop-foot { padding: 14px 22px; }
}

@media (prefers-reduced-motion: reduce) {
    .nav-drop-menu { transition: none; }
}

.nav-actions { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; margin-left: auto; }
.nav-icon-btn { width: 34px; padding: 0; border-radius: 9px; }
.nav-icon-btn svg { width: 18px; height: 18px; display: block; }

/* ---- Account avatar + dropdown ----------------------------------------- */
.nav-account { position: relative; flex: 0 0 auto; }
.nav-avatar {
    width: 34px;
    padding: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.07);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    line-height: 1;
}
.nav-avatar-ico { width: 18px; height: 18px; display: block; }

.nav-menu {
    position: absolute;
    top: calc(100% + 11px);
    right: 0;
    z-index: 60;
    min-width: 210px;
    padding: 6px;
    background: #11141c;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
}
.nav-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 40px;
    padding: 0 10px;
    border-radius: 8px;
    font-size: 13px;
    color: rgba(255, 255, 255, 0.72);
    text-decoration: none;
    transition: all 0.18s ease;
}
.nav-menu-item:hover,
.nav-menu-item:focus-visible { background: rgba(46, 91, 224, 0.14); color: #fff; text-decoration: none; }
.nav-menu-item .mnav-ico { width: 17px; height: 17px; flex: 0 0 auto; color: rgba(255, 255, 255, 0.45); }
.nav-menu-item:hover .mnav-ico { color: #8fadf5; }
.nav-menu-sep { display: block; height: 1px; margin: 6px 4px; background: rgba(255, 255, 255, 0.09); }

/* ---- Search panel (revealed by the icon) ------------------------------- */
.nav-search {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    right: 0;
    z-index: 55;
    display: flex;
    align-items: center;
    gap: 10px;
    height: 52px;
    padding: 0 8px 0 14px;
    background: #11141c;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
}
.nav-search-ico { display: inline-flex; flex: 0 0 auto; color: rgba(255, 255, 255, 0.45); }
.nav-search-ico svg { width: 18px; height: 18px; display: block; }
.nav-search input {
    flex: 1 1 auto;
    min-width: 0;
    background: transparent;
    border: none;
    color: #fff;
    font-family: var(--font-ui);
    font-size: 14.5px;
}
.nav-search input::placeholder { color: rgba(255, 255, 255, 0.38); }
.nav-search input:focus { outline: none; }
.nav-search-close {
    flex: 0 0 auto;
    width: 36px; height: 36px;
    padding: 0;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: rgba(255, 255, 255, 0.5);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer;
    transition: all 0.18s ease;
}
.nav-search-close:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }
.nav-search-close svg { width: 17px; height: 17px; display: block; }

/* Solid blue pill: cart icon + inset white count badge. Matches the mobile
   .tm-cart colour (#2E5BE0) so both cart pills stay in sync. */
.order-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 36px;
    background: var(--accent-blue);
    color: #ffffff;
    border: none;
    border-radius: 24px;
    padding: 0 11px 0 13px;
    font-weight: 500;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.22);
    transition: background .2s ease;
}
.order-pill:hover { text-decoration: none; background: #2450c4; }  /* slightly darker on hover */
.order-pill-ico { display: inline-flex; flex: 0 0 auto; color: #fff; }
.order-pill-ico svg { width: 17px; height: 17px; display: block; }
.order-pill-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    min-width: 21px;
    height: 21px;
    padding: 0 6px;
    background: #ffffff;
    color: var(--accent-blue);
    border-radius: 20px;
}

/* =========================================================================
   HERO — dark, two columns, drifting blue glows (no grid)
   ========================================================================= */

/* The section itself is full-bleed: it is a direct flex child of <body>, so its
   width is the viewport with no 100vw (and therefore no scrollbar-width) trick.
   That is what lets the background layers reach both edges at every width. The
   cap and gutter moved to .hero-inner, so the CONTENT still lines up with the
   nav, filter pills and product grid exactly as before. */
.hero {
    position: relative;
    overflow: hidden;              /* contains the layers; no horizontal scroll */
    background: #0d0d0f;
    /* svh second so iOS Safari does not resize the section when the address bar
       collapses; the vh line is the fallback for engines without svh. Never a
       bare 100vh. The ≤900px rule still relaxes this to auto, where the stacked
       content is taller than the viewport anyway. */
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;       /* keeps the content optically centred as the grid did */
}
/* Same two-part container as the nav bar and the product grid: gutter AND cap. */
.hero-inner {
    position: relative;
    z-index: 2;
    width: 100%;
    display: grid;
    grid-template-columns: 1.02fr 0.98fr;
    align-items: center;
    gap: 48px;
    max-width: calc(var(--content-max) + 2 * var(--gutter));
    margin: 0 auto;
    padding: 0 var(--gutter);
}
.hero.no-media .hero-inner { grid-template-columns: 1fr; }   /* no image -> left column takes the space */

/* Hero "Drift" background. Replaces the two blur(95px) glow blobs: the softness
   here comes from the gradient's own falloff, so there is no filter in the
   section at all and the only animated property is transform. The blooms are
   composited layers being translated and scaled — no per-frame blur pass, which
   is what made the old treatment (and the page glows before it) expensive.

   --hero-bloom is the glow multiplier and --hero-drift-speed divides every
   duration, both per the handoff's tunables. */
.hero {
    /* Colour comes from the existing :root --hero-glow triplet (brand blue
       #2E5BE0); only the two handoff tunables are local. */
    --hero-bloom: 0.45;           /* bloom opacity multiplier, 0-1 */
    --hero-drift-speed: 1;        /* divides every drift duration */
}
.hero-drift {
    position: absolute;
    z-index: 0;
    pointer-events: none;
}
.hero-bloom-a,
.hero-bloom-b {
    border-radius: 50%;
    will-change: transform;
}
/* The gradient is already transparent well inside each element's own box, so the
   negative insets only position the light — nothing is clipped at the section
   edge even where .hero stops short of a very wide viewport. */
.hero-bloom-a {
    left: -12%; top: -22%;
    width: 68%; height: 105%;
    background: radial-gradient(closest-side, rgba(var(--hero-glow), 0.34), transparent 72%);
    opacity: calc(var(--hero-bloom) * 0.9);
    animation: hero-drift-a calc(20s / var(--hero-drift-speed)) ease-in-out infinite;
}
.hero-bloom-b {
    right: -16%; bottom: -30%;
    width: 62%; height: 95%;
    background: radial-gradient(closest-side, rgba(var(--hero-glow), 0.22), transparent 70%);
    opacity: calc(var(--hero-bloom) * 0.7);
    animation: hero-drift-b calc(27s / var(--hero-drift-speed)) ease-in-out infinite;
}
/* Fine dot field, masked so it only reads behind the headline. Oversized by one
   grid cell in each axis and drifting exactly one cell, so the loop is seamless:
   at 100% the pattern has shifted onto where its neighbour started. Transform is
   the only thing animated — the layer never fades and carries no filter. */
.hero-dots {
    top: 0;
    left: 0;
    width: calc(100% + 30px);
    height: calc(100% + 30px);
    background-image: radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.13) 1px, transparent 0);
    background-size: 30px 30px;
    -webkit-mask-image: radial-gradient(80% 70% at 22% 42%, #000 0%, transparent 78%);
    mask-image: radial-gradient(80% 70% at 22% 42%, #000 0%, transparent 78%);
    animation: hero-dot-drift calc(18s / var(--hero-drift-speed)) linear infinite;
    will-change: transform;
}
@keyframes hero-dot-drift {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(30px, 30px, 0); }
}
.hero-vignette {
    inset: 0;
    background: radial-gradient(130% 110% at 50% 50%, transparent 55%, rgba(6, 7, 12, 0.7) 100%);
}
@keyframes hero-drift-a {
    0%, 100% { transform: translate3d(-9%, -6%, 0) scale(0.94); }
    50%      { transform: translate3d(13%, 9%, 0) scale(1.26); }
}
@keyframes hero-drift-b {
    0%, 100% { transform: translate3d(11%, 7%, 0) scale(1.18); }
    50%      { transform: translate3d(-12%, -8%, 0) scale(0.86); }
}

/* Bottom padding is much smaller than the top on purpose. The stat row is the last
   thing in the hero, and 72px under it plus the catalog's own 48px left a ~120px
   band of nothing between the stats and the filter pills. */
/* Top padding is the nav-to-badge gap. Clamped rather than restated per
   breakpoint so it scales continuously and the hero adds no media queries of its
   own: 40px at 1440 down to a 20px floor on the smallest phones. */
.hero-col {
    position: relative; z-index: 2;
    padding: clamp(20px, 1.95vw + 12px, 40px) 0 16px;
}
.hero-media { position: relative; z-index: 2; display: flex; align-items: center; justify-content: center; }
/* Faint blue pool of light so the vials emerge from the dark instead of sitting on flat
   black. Sits behind the image (z-index 0); the product paints on top (z-index 1). */
.hero-media::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 88%;
    height: 78%;
    background: radial-gradient(ellipse at center, rgba(46, 91, 224, 0.16), transparent 68%);
    filter: blur(26px);
    z-index: 0;
    pointer-events: none;
}
/* Grounded soft shadow + subtle blue ambient glow so the transparent-PNG cutout edges
   read as lit and settled, not hard-pasted. Product itself stays sharp (drop-shadow only
   affects the alpha silhouette, not the pixels). */
.hero-media img {
    position: relative;
    z-index: 1;
    max-width: 100%;
    height: auto;
    object-fit: contain;
    /* The vials are scaled up 25% with a transform, not by growing the element.
       The source PNG carries 13.6%/12.3% transparent margin left/right and
       25.5%/11.3% top/bottom, so 1.25x is still inside the 1.35x at which the
       artwork would touch the box edge — the vials get bigger, nothing spills.
       A transform also leaves the layout box at its old size, so the media column
       cannot widen and the hero cannot grow taller at any width.

       The max-height caps the layout box on short viewports (a 1440x720 laptop
       window), where 1.25x of a full-width box would be taller than the hero and
       the vials would be clipped top and bottom by .hero's overflow: hidden.
       svh second, vh first, same pairing .hero uses. */
    max-height: 76vh;
    max-height: 76svh;
    transform: scale(1.25);
    /* The scaled transform box overhangs the column by ~78px per side and lands
       over the right edge of the text column. The image is decorative and is not
       wrapped in a link, so it must not intercept clicks in that strip. */
    pointer-events: none;
    filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.55)) drop-shadow(0 0 60px rgba(46, 91, 224, 0.15));
}

.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: rgba(46, 91, 224, 0.12);
    border: 1px solid rgba(46, 91, 224, 0.30);
    border-radius: 999px;
    padding: 9px 20px;
    font-size: 15px;
    font-weight: 500;
    color: #7EA0F5;
}
.hero-h1 {
    margin: 30px 0 0;
    font-size: 76px;
    line-height: 1.02;
    font-weight: 800;
    letter-spacing: -0.035em;
    max-width: 700px;
    color: #fff;
}
.hero-h1-line2 { display: block; color: var(--accent-blue); }
.hero-subhead {
    margin: 28px 0 0;
    font-size: 19px;
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.58);
    max-width: 560px;
}
.hero-buttons { margin-top: 40px; display: flex; gap: 16px; flex-wrap: wrap; }
.hero-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 18px 36px;
    border-radius: 12px;
    font-size: 17px;
    font-weight: 600;
    border: none;
    transition: background .15s ease, border-color .15s ease;
}
.hero-btn svg { width: 18px; height: 18px; }
.hero-btn:hover { text-decoration: none; }
.hero-btn-primary { background: var(--accent-blue); color: #fff; box-shadow: 0 8px 26px rgba(46, 91, 224, 0.35); }
.hero-btn-primary:hover { background: #264fc4; }
.hero-btn-secondary {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.13);
    color: rgba(255, 255, 255, 0.88);
}
.hero-btn-secondary:hover { background: rgba(255, 255, 255, 0.10); }
/* Desktop shows the full label; the phone block below swaps them. */
.hero-btn-short { display: none; }

/* Stat panel, same treatment as the phone one further down. inline-flex so it
   hugs its three stats instead of stretching across the hero column; it is a
   normal left-aligned box, so its left edge lands on the page gutter with the
   heading above it. vertical-align keeps the inline-level box off the parent's
   text baseline, which would otherwise leave a strip of dead space beneath it. */
.hero-stats {
    margin-top: 64px;
    display: inline-flex;
    vertical-align: top;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 12px;
    padding: 16px 6px;
}
/* Dividers live BETWEEN the stats now — one rule, no outer edges, and nothing
   left of the old per-stat right border to double up with. */
.hero-stat { padding: 0 30px; text-align: center; }
.hero-stat + .hero-stat { border-left: 1px solid rgba(255, 255, 255, 0.09); }
.hero-stat-value { font-size: 29px; font-weight: 800; color: var(--accent-blue); letter-spacing: -0.02em; line-height: 1; }
.hero-stat-label { margin-top: 4px; font-size: 11.5px; color: rgba(255, 255, 255, 0.45); }
/* Desktop shows the full label; the phone block below swaps them. */
.hero-stat-short { display: none; }

@media (prefers-reduced-motion: reduce) {
    /* The blooms hold their keyframe start pose, which is a composition the
       handoff signs off on — the section reads as designed, just still. The
       transform is restated because animation:none alone would drop it. */
    .hero-bloom-a { animation: none; transform: translate3d(-9%, -6%, 0) scale(0.94); }
    .hero-bloom-b { animation: none; transform: translate3d(11%, 7%, 0) scale(1.18); }
    .hero-dots    { animation: none; transform: translate3d(0, 0, 0); }
}


/* =========================================================================
   CATALOG — light product section (anchor #catalog)
   ========================================================================= */

/* The sticky-bar offset for #catalog comes from html{scroll-padding-top} further
   down — do not add scroll-margin here as well, or it applies twice. */
.catalog { background: #fafafa; padding: 48px var(--gutter); }
/* Keep the catalog content from stretching absurdly wide + centered. */
/* .cbc-bar rather than .catalog-filters: the pill row now sits inside the bar
   alongside the Browse by class trigger, so the cap belongs on the parent. Cap
   the pills too and the trigger gets pushed outside it. */
.cbc-bar, .cbc-panel, .catalog-count, .pgrid { max-width: var(--content-max); margin-left: auto; margin-right: auto; }

.catalog-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; }
.catalog-filter {
    font-size: 14px;
    font-weight: 500;
    color: #555;
    background: #fff;
    border: 1px solid #e4e4e4;
    border-radius: 999px;
    padding: 9px 18px;
    transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.catalog-filter:hover { border-color: #bbb; color: #111; text-decoration: none; }
.catalog-filter.is-active { background: #111; color: #fff; border-color: #111; }
/* margin-top/bottom as LONGHANDS. The `margin: 0 0 20px` shorthand that used to be
   here also reset margin-left/right to 0, silently cancelling the auto centring set
   on the line above — so the count alone escaped --content-max and sat a whole
   gutter wider than the filters and the grid on any display over ~1560px. */
.catalog-count { font-size: 13.5px; color: var(--muted-2); margin-top: 0; margin-bottom: 20px; }

/* Exactly 4 columns on desktop. minmax(0, 1fr) lets tracks shrink so card
   content can never force a 5th column or push past the container edge. */
.pgrid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 24px;
}
/* Reflow 4 -> 3 -> 2 -> 1 (the 2 -> 1 steps live in the 900px / 640px blocks). */
@media (max-width: 1160px) {
    .pgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* Product card */
.pcard {
    background: #fff;
    border: 1px solid #ececec;
    border-radius: 11px;
    overflow: hidden;
    min-width: 0;   /* allow the card to shrink to its grid track (no overflow) */
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.pcard-name { overflow-wrap: break-word; }
.pcard:hover {
    transform: translateY(-5px);
    border-color: #dcdcdc;
    box-shadow: 0 16px 34px rgba(10, 10, 10, 0.09);
}
@media (prefers-reduced-motion: reduce) {
    .pcard:hover { transform: none; box-shadow: none; }
}

.pcard-form { display: flex; flex-direction: column; height: 100%; }

.pcard-media {
    position: relative;
    height: 210px;   /* shorter card; still enough for the vial to breathe */
    background: linear-gradient(170deg, #f7f7f8, #eeeeef);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
/* Absolutely centred inside the media box rather than laid out as a flex item.
   As a flex item the percentage max-height did not resolve (the box's own height
   comes from a column flex parent), so the 800px-tall <img> overflowed and the
   vial was cropped top and bottom. Positioned against .pcard-media the percentage
   always resolves, and inset:0 + margin:auto does the centring. */
.pcard-media img {
    position: absolute;
    inset: 0;
    margin: auto;
    max-width: 82%;
    max-height: 82%;
    object-fit: contain;
}
.pcard-placeholder { color: #cdced3; }
.pcard-placeholder svg { width: 84px; height: 84px; }
.pcard-cart {
    position: absolute;
    top: 12px; right: 12px;
    z-index: 2;                  /* above the now-positioned product image */
    width: 44px; height: 44px;   /* 44px minimum tap target — do not shrink */
    border-radius: 50%;
    background: #fff;
    border: none;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.10);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--accent-blue);
    cursor: pointer;
    transition: transform .12s ease;
}
.pcard-cart:hover { transform: scale(1.06); }
.pcard-cart svg { width: 20px; height: 20px; }

/* SALE badge — top-left, opposite the quick-add cart. Colour is admin-set inline. */
.pcard-sale {
    position: absolute;
    top: 12px; left: 12px;
    z-index: 2;
    font-size: 11px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #fff;
    background: #E23744;
    padding: 6px 11px;
    border-radius: 999px;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.16);
}

/* Out-of-stock badge — centered pill over the image; same badge system as SALE.
   pointer-events:none so it never blocks the click-through to the product page. */
.pcard-media.is-oos .pcard-media-link { opacity: 0.55; }
.pcard-oos-badge {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 3;
    pointer-events: none;
    max-width: calc(100% - 24px);
    font-size: 12px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-align: center;
    white-space: nowrap;
    color: #fff;
    background: #1c1c1c;
    padding: 9px 20px;
    border-radius: 999px;
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.24);
}
@media (max-width: 480px) {
    .pcard-oos-badge { font-size: 10.5px; padding: 7px 15px; letter-spacing: 0.05em; }
}


.pcard-body { padding: 14px 14px 15px; display: flex; flex-direction: column; flex: 1; }
/* Admin-managed category colour palette (tint bg + darker text) */
.cat-green  { background: #e7f6ee; color: #1b8a4e; }
.cat-blue   { background: #e8eefc; color: #2E5BE0; }
.cat-red    { background: #fdeaea; color: #c0392b; }
.cat-orange { background: #fbefe2; color: #c05a12; }
.cat-purple { background: #f0eafb; color: #7d3ec0; }
.cat-gray   { background: #ededed; color: #555555; }

/* Name left, price right, one row. The name takes the free width and wraps; the
   price never wraps and never shrinks. */
.pcard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.pcard-name { margin: 0; flex: 1 1 auto; min-width: 0; font-size: 15.5px; font-weight: 700; color: #111; letter-spacing: -0.015em; line-height: 1.3; }
.pcard-price { margin: 0; flex: 0 0 auto; text-align: right; white-space: nowrap; }
.pcard-amount { display: block; font-family: var(--font-mono); font-size: 15px; font-weight: 700; color: #111; line-height: 1.35; }
.pcard-compare { display: block; font-size: 11.5px; color: #9a9a9a; font-weight: 600; text-decoration: line-through; line-height: 1.2; }

.pcard-buy { margin-top: auto; padding-top: 13px; }
.pcard-size {
    width: 100%;
    height: 44px;                     /* 44px minimum tap target */
    border: 1px solid #dedede;
    border-radius: 9px;
    padding: 0 36px 0 12px;
    font-size: 14px;
    font-weight: 600;
    color: #111;
    background-color: #fff;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 15px;
    -webkit-appearance: none;
    appearance: none;
}
.pcard-size:focus { outline: none; border-color: var(--accent-blue); }
.pcard-add {
    margin-top: 9px;
    width: 100%;
    min-height: 46px;                 /* 44px minimum tap target */
    background: var(--accent-blue);
    color: #fff;
    border: none;
    border-radius: 9px;
    padding: 12px;
    font-size: 14.5px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    box-shadow: 0 6px 18px rgba(46, 91, 224, 0.26);
    cursor: pointer;
    transition: background .15s ease;
}
.pcard-add:hover { background: #264fc4; }
.pcard-add svg { width: 17px; height: 17px; }

/* Empty state */
.empty { text-align: center; padding: 64px 24px; border: 1px solid var(--border); border-radius: 14px; background: var(--white); }
.empty p { color: var(--muted); margin: 0 0 20px; }

/* =========================================================================
   GENERIC PAGE (contact, legal, error)
   ========================================================================= */

.page { background: var(--off-white); }
.page-inner { max-width: var(--maxw); margin: 0 auto; padding: 56px 24px 72px; }
.page-inner.narrow { max-width: 720px; }
.page-h1 { font-size: clamp(26px, 3vw, 38px); margin: 0 0 16px; }
.page-intro { color: var(--muted); font-size: 16px; max-width: 62ch; margin: 0 0 32px; }

.contact-form { background: var(--white); border: 1px solid var(--border); padding: 28px; }

.legal-body-copy { color: var(--ink); font-size: 15.5px; line-height: 1.7; max-width: 68ch; }

/* Slim header for public legal / error pages */
.legal-body { background: var(--off-white); }
.slim-header { background: var(--black); }
.slim-header-inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 0 24px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.slim-brand { color: var(--white); font-weight: 600; letter-spacing: -0.01em; }
.slim-brand:hover { text-decoration: none; }
.slim-back { color: var(--muted-3); font-size: 13px; }
.slim-back:hover { color: var(--white); text-decoration: none; }

/* =========================================================================
   FOOTER
   ========================================================================= */

/* ---- Site footer ------------------------------------------------------- */
/* Horizontal padding lives on each band (24px), not on the footer, so the trust
   strip's dividers and bottom rule can run edge to edge. The inner wrapper only
   bites above 1440px, keeping the footer in step with the catalog grid. */
.site-footer {
    position: relative;
    background: #0E1014;
    /* Top and bottom only. A full border put a 1px inset on everything inside, so
       the footer content could never land on the same gutter as the grid; the left
       and right edges sat flush against the viewport and were invisible anyway. */
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.6);
}
/* Brand accent across the very top edge. Inset inside the border rather than
   pulled over it, so it can never add a pixel of width at 320px. */
.site-footer::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, #2E5BE0, #1E3F9E 40%, transparent);
    pointer-events: none;
}
.site-footer-inner { max-width: calc(var(--content-max) + 2 * var(--gutter)); margin: 0 auto; }

/* Trust strip — three equal columns sitting on the footer's own gutter, so the
   strip's edges line up with the columns below it. No vertical rules, no bottom
   border, no cell backgrounds: those ran the full width of .site-footer-inner
   while everything below sat inside the padding, which is what made the cell
   edges read as boxes overhanging the footer.
   Horizontal padding must stay var(--gutter), never a fixed value — the gutter
   is 60/32/18/12px across the breakpoints and a fixed number would put the
   strip back out of line at every width but one. */
.footer-trust {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 34px;
    padding: 26px var(--gutter);
}
.footer-trust-cell {
    display: flex; align-items: center; gap: 11px;
    min-width: 0;
}
/* Fixed tile — flex: 0 0 auto so it keeps its 38px when the text beside it wraps. */
.footer-trust-tile {
    flex: 0 0 auto;
    width: 38px; height: 38px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 11px;
    background: rgba(46, 91, 224, 0.16);
}
.footer-trust-ico { width: 17px; height: 17px; color: #5B7FE8; }
.footer-trust-text { display: flex; flex-direction: column; min-width: 0; }
.footer-trust-title { font-size: 12.5px; font-weight: 600; line-height: 1.35; color: #fff; }
.footer-trust-sub { margin-top: 3px; font-size: 11px; line-height: 1.35; color: rgba(255, 255, 255, 0.42); }
/* Blanking a trust cell in admin drops it, so the grid has to narrow to what is
   left instead of holding an empty third column open. */
.footer-trust.cells-1 { grid-template-columns: 1fr; }
.footer-trust.cells-2 { grid-template-columns: repeat(2, 1fr); }
/* Keeps the 3px gap between title and sub from becoming a top offset when only
   the sub survives. */
.footer-trust-text > :first-child { margin-top: 0; }

/* Main columns */
.footer-main {
    display: grid;
    grid-template-columns: 1.5fr 1fr 1fr 1fr;
    gap: 26px;
    padding: 30px var(--gutter) 26px;
}
.footer-col { min-width: 0; }

.footer-heading {
    margin: 0 0 14px; font-size: 10px; font-weight: 600; line-height: 1.4;
    text-transform: uppercase; letter-spacing: 0.15em; color: rgba(255, 255, 255, 0.35);
}

.footer-links { list-style: none; margin: 0; padding: 0; }
/* Row rhythm lives on the <li> as real margin, NOT as line-height on an
   inline-block anchor. An inline-block's baseline is the baseline of its LAST
   line box, so the moment a label wraps to two lines the anchor's box hangs
   above its own line and prints over the item above it. Every one of these
   labels is an admin-editable content block, so any of them can grow at any
   time. A block-level anchor with real margins cannot collide, and it gives
   the whole row a tap target instead of just the glyphs. */
.footer-links li + li { margin-top: 11px; }
.footer-links a {
    display: block; font-size: 12.5px; line-height: 1.4; color: #fff;
    text-decoration: none; transition: color .12s ease; overflow-wrap: anywhere;
}
.footer-links a:hover { color: rgba(255, 255, 255, 0.7); }

/* Brand column */
.footer-col-brand { min-width: 0; }
.footer-brand { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.footer-brand:hover { text-decoration: none; }
/* The real lockup, same asset as the nav. Height only — width stays auto so the
   4.32:1 artwork is never distorted. The anchor is inline-flex, so the image's
   left edge lands on the column edge, flush with the blurb underneath. */
.footer-logo { height: 40px; width: auto; display: block; }

.footer-blurb { margin: 0 0 16px; font-size: 12px; line-height: 1.65; color: rgba(255, 255, 255, 0.6); max-width: 38ch; }
.footer-contact { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.footer-contact li { display: flex; align-items: center; gap: 9px; font-size: 12px; color: rgba(255, 255, 255, 0.85); min-width: 0; }
.footer-contact a { color: rgba(255, 255, 255, 0.85); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.footer-contact a:hover { color: #fff; }
.footer-ico { width: 14px; height: 14px; flex: 0 0 auto; color: rgba(255, 255, 255, 0.35); }

/* Payment chips */
.footer-pay { padding: 0 var(--gutter) 22px; }
.footer-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
/* Solid brand chips. Colour only — no provider logo, wordmark or other
   trademarked asset, matching how the pay-page buttons are built. */
.footer-chip {
    font-size: 12px; font-weight: 500; line-height: 1.35;
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 8px 15px;
    color: #fff;
}
/* Mirrors PaymentMethods::ALL. These four are a static "we accept" row rather
   than buttons built from order data, so they cannot read the table at render
   time; if a brand colour changes there, change it here in the same commit.
   Cash App and PayPal carry dark text: white fails contrast on both. */
.footer-chip-venmo   { background: #008CFF; color: #ffffff; }
.footer-chip-cashapp { background: #00D54B; color: #04310f; }
.footer-chip-zelle   { background: #6D1ED4; color: #ffffff; }
.footer-chip-paypal  { background: #FFC439; color: #062b4e; }
/* Providers with no brand colour of their own (e.g. card/Square). Still reads as
   live — the dimmed treatment below is reserved for coming-soon. */
.footer-chip-plain   { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.16); color: #ffffff; }
/* Last, and two classes deep, so it wins over any brand colour above. */
.footer-chip.is-soon {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.35);
}

/* Compliance notice */
.footer-notice { border-top: 1px solid rgba(255, 255, 255, 0.07); padding: 18px var(--gutter); }
.footer-notice .footer-heading { margin-bottom: 8px; }
.footer-notice-text { margin: 0; max-width: 900px; font-size: 11px; line-height: 1.7; color: rgba(255, 255, 255, 0.42); }

/* Bottom bar */
.footer-bottom {
    border-top: 1px solid rgba(255, 255, 255, 0.07); padding: 15px var(--gutter);
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
.footer-copy { margin: 0; font-size: 11px; color: rgba(255, 255, 255, 0.5); }
.footer-powered { margin: 0; font-size: 11px; color: rgba(255, 255, 255, 0.3); }

@media (max-width: 900px) {
    .footer-main { grid-template-columns: 1fr 1fr; gap: 24px 26px; }
}

@media (max-width: 640px) {
    /* One column everywhere; the three trust items stack with a 20px gap. The
       cells-* classes are repeated here because they are more specific than a bare
       .footer-trust and would otherwise re-impose a multi-column grid on a phone. */
    .footer-trust,
    .footer-trust.cells-1,
    .footer-trust.cells-2 { grid-template-columns: 1fr; gap: 20px; }
    .footer-main { grid-template-columns: 1fr; gap: 26px; }
    .footer-bottom { flex-direction: column; align-items: flex-start; }
}


/* =========================================================================
   FOCUS / MOTION
   ========================================================================= */

:focus-visible {
    outline: 2px solid var(--black);
    outline-offset: 2px;
}
.topbar :focus-visible,
.login-visual :focus-visible,
.login-auth :focus-visible,
.slim-header :focus-visible {
    outline-color: var(--white);
}

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}


/* =========================================================================
   RESPONSIVE
   ========================================================================= */

/* Between 768px and ~1040px the logo + three nav items + three actions get
   tight in the floating bar. Trim the item padding rather than dropping items. */
@media (max-width: 1040px) {
    .navbar { gap: 14px; }
    .nav-item { padding: 0 10px; letter-spacing: 0.06em; }
}


@media (max-width: 900px) {
    /* Hero stacks (text, then image); glows still cover. Horizontal padding stays
       on --gutter from the base rule — never restate it per breakpoint. */
    .hero { min-height: auto; }
    .hero-inner, .hero.no-media .hero-inner { grid-template-columns: 1fr; }
    .hero-col { padding-bottom: 8px; }   /* top padding stays on the base clamp */
    .hero-h1 { font-size: 54px; }
    .hero-media { padding-bottom: 28px; }
    .catalog { padding: 40px var(--gutter); }
    .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* Stack the login split: image becomes a shorter banner on top, auth card full-width. */
    .login-split { grid-template-columns: 1fr; }
    .login-seam { display: none; }
    .login-visual { order: -1; padding: 36px 32px; }
    .login-visual .login-logo-img { height: 84px; }
    .login-features { width: 100%; margin: 26px 0; }
    .login-visual-foot { width: 100%; }
    .login-auth { padding: 36px 32px; }
    .login-auth::before { display: none; }

    /* Nav bar steps down on tablets before the mobile reflow (match hero padding). */
    .topbar-inner { padding: 12px var(--gutter); }
    .navbar { gap: 12px; }
    .nav-logo { height: 26px; }
}


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

    .login-visual { padding: 28px 18px; }
    .login-visual .login-logo-img { height: 70px; }
    .login-feature { padding: 13px 18px; }
    .login-feature-title { font-size: 18px; }
    .login-auth { padding: 28px 18px; }
    .trust-row { gap: 10px 16px; }

    .field-row { grid-template-columns: 1fr; gap: 0; }
    .hero-col { padding-bottom: 4px; }   /* top padding stays on the base clamp */
    .hero-h1 { font-size: 40px; }
    .hero-subhead { font-size: 17px; }

    /* Hero CTAs: two equal columns on one row rather than stacked. A grid, not a
       wrapping flex row, so the two cells stay exactly equal however long the
       labels get, and the short labels take over so nothing has to be clipped. */
    .hero-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
    .hero-btn {
        justify-content: center;
        min-width: 0;
        height: 36px;
        padding: 0 10px;
        gap: 4px;
        border-radius: 8px;
        font-size: 10.5px;
        font-weight: 500;
        white-space: nowrap;
    }
    .hero-btn svg { width: 10px; height: 10px; flex: 0 0 auto; }
    .hero-btn-full { display: none; }
    .hero-btn-short { display: inline; }
    .hero-btn-primary { color: #fff; }
    .hero-btn-secondary {
        background: rgba(255, 255, 255, 0.05);
        border: 1px solid rgba(255, 255, 255, 0.12);
        color: #fff;
    }
    /* Three stats on ONE row inside a panel, rather than wrapping to two rows.
       A grid of equal columns; the dividers are the middle cell's side borders, so
       they always land on the exact thirds. */
    .hero-stats {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 0;
        background: rgba(255, 255, 255, 0.035);
        border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 10px;
        padding: 12px 6px;
    }
    .hero-stat {
        min-width: 0;
        padding: 0 4px;
        margin: 0;
        border-right: none;
        text-align: center;
    }
    /* Clear the desktop between-stats divider. Needs to match that rule's
       specificity — a plain .hero-stat cannot override .hero-stat + .hero-stat. */
    .hero-stat + .hero-stat { border-left: none; }
    .hero-stat:nth-child(2) {
        border-left: 1px solid rgba(255, 255, 255, 0.09);
        border-right: 1px solid rgba(255, 255, 255, 0.09);
    }
    .hero-stat-value { font-size: 18px; font-weight: 600; color: #4A78F0; letter-spacing: -0.01em; }
    .hero-stat-label { margin-top: 3px; font-size: 8px; line-height: 1.3; color: rgba(255, 255, 255, 0.42); }
    .hero-stat-full { display: none; }
    .hero-stat-short { display: inline; }
    .catalog { padding: 32px var(--gutter); }
    .pd-related-grid { grid-template-columns: 1fr; }
    .page-inner { padding: 36px 16px 56px; }
    .contact-form { padding: 20px; }
}


/* ---- Phone catalog: TWO cards per row, scaled down proportionally --------
   Same structure as desktop (image, quick-add, name + price row, size, add).
   Nothing here may shrink a tap target below 44px. */
@media (max-width: 640px) {
    .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
    .pcard-media { height: 150px; }
    .pcard-media img { max-width: 86%; max-height: 86%; }
    /* The hit area stays 44px; only the white disc shrinks, so the quick-add
       does not swallow a 143px-wide card. */
    .pcard-cart { top: 6px; right: 6px; background: transparent; box-shadow: none; }
    .pcard-cart::before {
        content: "";
        position: absolute;
        inset: 4px;
        border-radius: 50%;
        background: #fff;
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.14);
    }
    .pcard-cart svg { position: relative; width: 18px; height: 18px; }
    .pcard-sale { top: 8px; left: 8px; font-size: 9.5px; padding: 5px 8px; }
    .pcard-body { padding: 11px 11px 12px; }
    .pcard-head { gap: 8px; }
    .pcard-name { font-size: 13px; line-height: 1.28; }
    .pcard-amount { font-size: 13px; }
    .pcard-compare { font-size: 10.5px; }
    .pcard-restock { font-size: 10.5px; }
    .pcard-buy { padding-top: 10px; }
    .pcard-size { font-size: 13px; padding: 0 30px 0 10px; background-position: right 9px center; background-size: 13px; }
    .pcard-add { font-size: 13px; gap: 6px; padding: 11px 8px; }
    .pcard-add svg { width: 15px; height: 15px; }
}


/* 414 / 375 / 320: tighten the gutters and the gap so two tracks still fit
   without the name or the price being clipped. */
@media (max-width: 400px) {
    .catalog { padding: 28px var(--gutter); }
    .pgrid { gap: 10px; }
    .pcard-media { height: 130px; }
    .pcard-body { padding: 10px 9px 11px; }
    .pcard-head { gap: 6px; }
    .pcard-name { font-size: 12.5px; }
    .pcard-amount { font-size: 12.5px; }
    /* The option text is "10 mg — $45.00"; at 320px it has to fit a ~125px box
       or the native select clips it mid-price. */
    .pcard-size { font-size: 11.5px; padding: 0 24px 0 8px; background-position: right 7px center; background-size: 12px; }
    .pcard-add { font-size: 12px; gap: 5px; padding: 11px 4px; }
    .pcard-oos-badge { font-size: 9.5px; padding: 6px 11px; }
    .pcard-sale { font-size: 9px; padding: 4px 7px; }
}


/* =========================================================================
   PHASE 2 — logo helper + admin center
   ========================================================================= */

/* ---- Logo helper (shared) --------------------------------------------- */
.logo-wordmark { font-weight: 600; font-size: 16px; letter-spacing: -0.01em; }
.nav-brand { color: #fff; }
.nav-brand .logo-wordmark,
.login-logo .logo-wordmark,
.admin-brand .logo-wordmark { color: #fff; }
.login-logo { color: #fff; }
.login-logo-img { height: 72px; width: auto; }
.admin-logo-img { height: 24px; width: auto; }

/* ---- Admin shell ------------------------------------------------------- */

.admin-shell { display: flex; min-height: 100vh; align-items: stretch; }

.admin-sidebar {
    flex: 0 0 240px;
    width: 240px;
    background: var(--black);
    color: #fff;
    display: flex;
    flex-direction: column;
    padding: 26px 0 0;
    position: sticky;
    top: 0;
    height: 100vh;
    align-self: flex-start;
    /* The box is exactly one viewport tall, so its content must be CONTAINED rather
       than allowed to spill. Without this, a nav taller than the viewport pushed the
       footer out past the dark background and onto the white page — which is what
       made "Sign out" unreadable. The nav below scrolls instead. */
    overflow: hidden;
}
.admin-brand { display: block; padding: 0 24px; }
.admin-brand:hover { text-decoration: none; }
.admin-brand-tag {
    color: var(--muted);
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    padding: 0 24px;
    margin: 8px 0 22px;
}
/* Takes the space between the brand and the footer, and scrolls on its own when
   the groups are all expanded. min-height:0 is what lets a flex item shrink below
   its content height; without it the nav refuses to scroll and overflows again. */
.admin-nav {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
.admin-nav-link {
    display: flex;
    align-items: center;
    gap: 11px;
    color: #a9b0bc;
    padding: 8px 16px;
    font-size: 14px;
    /* No border here: the active state adds a 2px one and drops its padding-left to
       14px, so both states put the label at the same 16px offset. */
    border-left: 0;
    transition: background .12s ease, color .12s ease;
}
.admin-nav-link:hover { color: #fff; background: rgba(255, 255, 255, .05); text-decoration: none; }
.admin-nav-link.is-active {
    background: rgba(46, 91, 224, .16);
    border-left: 2px solid #2E5BE0;
    padding-left: 14px;   /* keeps the label aligned with the inactive items */
    font-weight: 500;
}
.admin-nav-link.is-active .admin-nav-text { color: #fff; }
.admin-nav-link.is-active .admin-nav-icon { color: #8fadf5; }
.admin-nav-icon { width: 17px; height: 17px; flex: none; color: #6b7280; }
.admin-nav-link:hover .admin-nav-icon { color: #9ca3af; }
.admin-nav-text { min-width: 0; }
.admin-nav-external { display: flex; align-items: center; }
.admin-nav-ext-icon { width: 13px; height: 13px; flex: none; opacity: .7; margin-left: 2px; }
.admin-nav-external:hover .admin-nav-ext-icon { opacity: 1; }
.admin-nav-ext-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---- Sidebar groups ----------------------------------------------------
   Real <details> elements, rendered open by the server so a no-JS admin can
   still reach every section. JS collapses them and remembers the state. */
.admin-navgroup { border: 0; }
.admin-navgroup-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 9px 16px;
    cursor: pointer;
    font-size: 12px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8b919c;
    font-weight: 500;
    list-style: none;
    user-select: none;
}
.admin-navgroup-head::-webkit-details-marker { display: none; }
.admin-navgroup-head::marker { content: ''; }
.admin-navgroup-head:hover { color: #c3c9d4; }
.admin-navgroup-head:focus-visible { outline: 2px solid #2E5BE0; outline-offset: -2px; }
.admin-navgroup-chevron {
    width: 14px; height: 14px; flex: none;
    color: #5d636e;
    transform: rotate(-90deg);   /* closed: points right */
    transition: transform .15s ease;
}
.admin-navgroup[open] > .admin-navgroup-head .admin-navgroup-chevron { transform: rotate(0deg); }
.admin-navgroup-items { display: flex; flex-direction: column; padding-bottom: 2px; }

/* ---- Sidebar "Jump to" search ------------------------------------------ */
.admin-navsearch { position: relative; margin: 0 16px 10px; }
.admin-navsearch-icon {
    position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    width: 15px; height: 15px; color: #6b7280; pointer-events: none;
}
.admin-navsearch-input {
    width: 100%;
    height: 34px;
    background: rgba(255, 255, 255, .06);
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 0 10px 0 32px;
    color: #fff;
    font-family: inherit;
    font-size: 12.5px;
    -webkit-appearance: none;
    appearance: none;
}
.admin-navsearch-input::placeholder { color: #6b7280; font-size: 12.5px; }
.admin-navsearch-input:focus { outline: none; border-color: #2E5BE0; background: rgba(255, 255, 255, .09); }
.admin-navsearch-input::-webkit-search-cancel-button { -webkit-appearance: none; }
.admin-nav-empty { color: #6b7280; font-size: 12.5px; padding: 6px 16px; margin: 0; }
.admin-nav [hidden] { display: none !important; }

/* Pinned to the bottom of the sidebar and never shrinks: the nav above absorbs
   the spare space, so this stays put while a long nav scrolls behind it. */
.admin-sidebar-foot {
    flex: 0 0 auto;
    margin-top: 0;
    padding: 16px 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    border-top: 1px solid rgba(255, 255, 255, .07);
}
.admin-sidebar-foot a {
    display: flex;
    align-items: center;
    gap: 8px;
    color: #a9b0bc;
    font-size: 14px;
}
.admin-sidebar-foot a:hover { color: #fff; text-decoration: none; }
.admin-sidebar-foot-icon { width: 14px; height: 14px; flex: none; opacity: .75; }
.admin-sidebar-foot a:hover .admin-sidebar-foot-icon { opacity: 1; }

.admin-main { flex: 1 1 auto; min-width: 0; }
.admin-main-inner { max-width: 1600px; margin: 0 auto; padding: 40px; }
.admin-menu-toggle { display: none; }

/* ---- Admin headings / sections ---------------------------------------- */

.muted { color: var(--muted); }
.small { font-size: 12.5px; }
.btn-sm { padding: 9px 16px; font-size: 13px; }

/* ---- Stat grid --------------------------------------------------------- */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    margin-bottom: 32px;
}
.stat { background: #fff; padding: 20px; display: flex; flex-direction: column; gap: 6px; }
a.stat:hover { background: var(--off-white); text-decoration: none; }
.stat-num { font-size: 26px; font-weight: 600; line-height: 1; }
.stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.07em; }

/* ---- Dashboard: site traffic panel ------------------------------------- */
.traffic-grid { margin-bottom: 20px; }
.traffic-lists { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
.traffic-list { min-width: 0; }
.traffic-rows { list-style: none; margin: 0; padding: 0; }
.traffic-row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px;
}
.traffic-row:last-child { border-bottom: none; }
.traffic-row-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.traffic-row-count { flex: 0 0 auto; color: var(--muted); }
@media (max-width: 640px) {
    .traffic-lists { grid-template-columns: 1fr; gap: 16px; }
}


/* ---- Tables ------------------------------------------------------------ */

.truncate { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.thumb {
    display: inline-flex;
    width: 40px; height: 40px;
    border: 1px solid var(--border);
    align-items: center; justify-content: center;
    background: var(--off-white);
    overflow: hidden;
}
.thumb img { width: 100%; height: 100%; object-fit: cover; filter: grayscale(1); }
.thumb-empty { color: var(--muted-3); }

/* ---- Badges ------------------------------------------------------------ */

.badge-pending { color: var(--black); border-color: var(--black); background: var(--off-white); }
.badge-admin { background: var(--black); color: #fff; border-color: var(--black); }

/* ---- Row actions / link buttons --------------------------------------- */
/* Action cells stay real table cells (never display:flex on a <td>, which pulls
   the cell out of the table layout and pushes the links past the right border).
   Lay the links/forms out inline instead, right-aligned within the cell. */

.inline-form { display: inline-block; margin: 0; vertical-align: middle; }
.link-action {
    background: none; border: none; padding: 0;
    font-size: 13px; color: var(--black);
    cursor: pointer; font-family: inherit;
    text-decoration: underline;
}
.link-action:hover { color: var(--muted); }
.link-danger { color: var(--black); }

/* Messages: bulk-delete bar + checkbox column */

.msg-check-col { width: 1%; white-space: nowrap; text-align: center; vertical-align: middle; }
.msg-check-col input[type="checkbox"] { margin: 0; vertical-align: middle; }

/* ---- Admin form controls ---------------------------------------------- */


/* Disclaimer editor rows */

.disc-row .field { margin-bottom: 0; }

/* Product cert checkboxes */

.cert-checks .check { margin-bottom: 0; }

/* Logo manager */

.logo-preview-light { background: #fff; }
.current-image { min-height: auto; padding: 12px; display: inline-flex; }
.current-image img { max-height: 80px; filter: grayscale(1); }

/* Users: accepted disclaimers */
.disc-cell { max-width: 260px; }
.disc-accepted { margin: 0; padding-left: 16px; font-size: 12px; color: var(--muted); }
.disc-accepted li { margin-bottom: 2px; }
.disc-gone { color: var(--muted-3); text-decoration: line-through; }

/* ---- Admin list filters (products, FAQs) -------------------------------
   Client-side only: the rows are already in the DOM, nothing reloads. */

[data-row][hidden] { display: none !important; }
[data-filter-section][hidden] { display: none !important; }

/* Reordering off while a filter is active — a sort order written from a visible
   subset would renumber rows the operator cannot see. */


/* ---- Admin responsive -------------------------------------------------- */
@media (max-width: 900px) {
    /* Filters stack full width; nothing overflows at 320px. */
    

    .admin-shell { flex-direction: column; }
    .admin-menu-toggle {
        display: block;
        position: sticky; top: 0; z-index: 40;
        width: 100%;
        background: var(--black); color: #fff;
        border: none; padding: 14px 20px; text-align: left;
        font-family: var(--font-mono); font-size: 12px;
        letter-spacing: 0.08em; text-transform: uppercase;
    }
    /* Stacked under the Menu button: the sidebar grows with its content, so the
       desktop containment (fixed 100vh + internal nav scrolling) is switched off. */
    .admin-nav { flex: 0 1 auto; min-height: auto; overflow-y: visible; }
    .admin-sidebar {
        display: none;
        overflow: visible;
        position: static; height: auto; width: 100%; flex-basis: auto;
        padding-bottom: 16px;
    }
    .admin-sidebar.is-open { display: flex; }
    .admin-main-inner { padding: 24px 16px; }
}


/* Admin: monospace body textarea (login.body) + inline code in helper notes */
.mono-body { font-family: var(--font-mono); font-size: 13px; line-height: 1.55; }

/* =========================================================================
   Legal page rendered HTML (light background)
   ========================================================================= */
.legal-body-copy > :first-child { margin-top: 0; }
.legal-body-copy h1 { font-size: 26px; margin: 28px 0 12px; }
.legal-body-copy h2 { font-size: 21px; margin: 24px 0 10px; }
.legal-body-copy h3 { font-size: 17px; margin: 20px 0 8px; }
.legal-body-copy p { margin: 0 0 14px; }
.legal-body-copy ul, .legal-body-copy ol { margin: 0 0 16px; padding-left: 22px; }
.legal-body-copy li { margin-bottom: 6px; }
.legal-body-copy strong { font-weight: 600; }
.legal-body-copy a { color: var(--black); text-decoration: underline; }

/* =========================================================================
   WYSIWYG editor (Quill Snow) — monochrome admin overrides
   (style.css loads AFTER quill.snow.css, so these win)
   ========================================================================= */
.rich-field { margin-top: 2px; }
.ql-toolbar.ql-snow,
.ql-container.ql-snow { border-color: var(--border-2); border-radius: 0; }
.ql-toolbar.ql-snow { background: var(--off-white); }
.ql-container.ql-snow { background: #fff; }
.ql-editor {
    min-height: 220px;
    font-family: var(--font-ui);
    font-size: 15px;
    line-height: 1.6;
    color: var(--ink);
}
.ql-editor.ql-blank::before { color: var(--muted-2); font-style: normal; left: 14px; }

/* Icons: muted by default, black on hover/active (kills Quill's blue accent) */
.ql-snow .ql-stroke { stroke: var(--muted); }
.ql-snow .ql-fill { fill: var(--muted); }
.ql-snow .ql-picker { color: var(--muted); }
.ql-snow.ql-toolbar button:hover,
.ql-snow.ql-toolbar button.ql-active,
.ql-snow .ql-picker-label:hover,
.ql-snow .ql-picker-item:hover,
.ql-snow .ql-picker-item.ql-selected { color: var(--black); }
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-picker-label:hover .ql-stroke,
.ql-snow .ql-picker-label.ql-active .ql-stroke,
.ql-snow .ql-picker-item:hover .ql-stroke,
.ql-snow .ql-picker-item.ql-selected .ql-stroke { stroke: var(--black); }
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--black); }
.ql-snow .ql-picker.ql-expanded .ql-picker-label { border-color: var(--border-2); }
.ql-snow .ql-picker-options { border-color: var(--border-2); border-radius: 0; }
.ql-snow a { color: var(--black); }
.ql-snow .ql-tooltip {
    border-color: var(--border-2);
    border-radius: 0;
    box-shadow: 0 6px 18px rgba(10, 10, 10, 0.10);
    color: var(--ink);
}
.ql-snow .ql-tooltip input[type=text] { border-color: var(--border-2); border-radius: 0; }
.ql-snow .ql-tooltip a.ql-action,
.ql-snow .ql-tooltip a.ql-remove { color: var(--black); }

/* Login background-image preview in admin settings (portrait-ish) */


/* Category manager rows: name + colour + order + preview + usage */

.cat-fields .field { margin-bottom: 0; }

/* =========================================================================
   COMMERCE — cart / checkout / confirmation / account / admin orders
   ========================================================================= */

/* Status pills */
.st-pill { display: inline-block; font-size: 11px; font-weight: 600; letter-spacing: .03em; padding: 4px 10px; border-radius: 999px; white-space: nowrap; }
.st-blue   { background: #e8eefc; color: #2E5BE0; }
.st-amber  { background: #fdf3e0; color: #b7791f; }
.st-green  { background: #e7f6ee; color: #1b8a4e; }
.st-purple { background: #f0eafb; color: #7d3ec0; }
.st-gray   { background: #ededed; color: #555; }
/* An order number is a single token and must never break across lines. */
.order-link { color: #111; font-weight: 700; white-space: nowrap; }

/* Orders list: internal payment-note flag + row quick-actions */
/* white-space: nowrap so the label can never break inside its own pill — in the
   118px Payment track it was rendering as a two-line lozenge 35px tall. */
.pay-note-flag { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; color: #7d3ec0; background: #f0eafb; padding: 2px 7px; border-radius: 999px; vertical-align: middle; cursor: help; white-space: nowrap; }
.order-status-form { margin: 0; }
.status-select { font-size: 12.5px; padding: 6px 8px; border: 1px solid var(--border-2); background: #fff; color: var(--ink); border-radius: 7px; max-width: 160px; }
.status-select:focus { outline: none; border-color: var(--black); }
.order-status-form noscript button { margin-left: 8px; }

/* Admin danger zone (destructive actions) */

.order-link:hover { color: var(--accent-blue); }

/* Cart */
.cart { display: grid; grid-template-columns: 1fr 320px; gap: 32px; align-items: start; }
.cart-line { display: grid; grid-template-columns: 76px 1fr auto; gap: 16px; align-items: center; padding: 18px 0; border-bottom: 1px solid var(--border); }
.cart-line-media { width: 76px; height: 76px; background: linear-gradient(170deg, #f7f7f8, #eeeeef); border-radius: 12px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.cart-line-media img { max-width: 80%; max-height: 80%; object-fit: contain; }
.cart-line-ph { color: #c9c9cd; font-size: 11px; }
.cart-line-name { margin: 0; font-weight: 600; font-size: 16px; color: #111418; }
.cart-line-meta { margin: 3px 0 0; font-size: 12px; color: #4a5560; }
.cart-line-unit { margin: 4px 0 0; font-size: 14px; color: #111418; }
.cart-line-unit .muted { color: #4a5560; }
/* Which discount is live on this line — quantity tier or code, never both. */
.cart-line-was { color: #6b7480; font-size: 13px; margin-right: 6px; }
.cart-line-save { margin: 3px 0 0; font-size: 12.5px; color: #12703f; }
.cart-line-save .muted { color: #4a5560; }
.cart-line-actions { display: flex; align-items: center; gap: 16px; margin-top: 8px; flex-wrap: wrap; }
.cart-qty-form { display: flex; align-items: center; gap: 8px; }
.cart-qty-label { font-size: 12px; color: var(--muted); margin: 0; }
.cart-qty { width: 64px; text-align: center; border: 1px solid var(--border-2); border-radius: 8px; padding: 7px; }
.cart-line-total { font-weight: 700; font-size: 16px; color: #111418; }

/* BAC water offer on the cart: under the line items on desktop, above the summary
   when the grid stacks (the mobile rule below resets this placement). */
.cart-bac { grid-column: 1; grid-row: 2; margin-top: 20px; }
.cart-lines { grid-column: 1; grid-row: 1; }
.cart-summary { grid-column: 2; grid-row: 1 / span 2; align-self: start; }
.cart-bac-form { margin: 0; }
.cart-bac-add {
    flex: 0 0 auto; border: none; border-radius: 7px; padding: 9px 16px;
    background: #2E5BE0; color: #fff; font-size: 14px; font-weight: 600;
    font-family: inherit; cursor: pointer; white-space: nowrap;
}
.cart-bac-add:hover { background: #264fc4; }
.cart-bac-added { margin: 20px 0 0; font-size: 13.5px; color: #12703f; }
.cart-summary { border: 1px solid var(--border); border-radius: 14px; padding: 22px; background: #fff; position: sticky; top: 100px; }
.cart-summary-row { display: flex; justify-content: space-between; font-size: 15px; }
.cart-summary-total { font-weight: 700; font-size: 17px; margin-bottom: 6px; }
.cart-summary-note { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted); }
.cart-checkout { margin-top: 16px; }
.cart-continue { display: block; text-align: center; margin-top: 12px; font-size: 14px; color: var(--muted); }

/* =========================================================================
   SHIPPING METHOD PICKER (checkout)
   ========================================================================= */
.ship-block { margin-top: 4px; }
.ship-label { display: block; margin-bottom: 10px; font-size: 11px; text-transform: uppercase; letter-spacing: .09em; color: #5a6470; }
.ship-methods { display: flex; flex-direction: column; gap: 9px; }
.ship-row {
    display: flex; align-items: center; gap: 12px;
    min-height: 44px; padding: 14px; box-sizing: border-box;
    border: 1px solid #d5dae0; border-radius: 11px; background: #fff;
    cursor: pointer; transition: border-color .12s ease, background .12s ease, box-shadow .12s ease;
}
.ship-row:hover { border-color: #9aa2ac; }
.ship-row input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; }
.ship-row:has(input:checked) { border-color: #2E5BE0; box-shadow: inset 0 0 0 1px #2E5BE0; background: #f7f9ff; }
.ship-row:has(input:focus-visible) { outline: 2px solid #2E5BE0; outline-offset: 2px; }
/* Only one method configured: no choice to make, so no radio is drawn. */
.ship-row.is-only { cursor: default; }
.ship-row.is-only:hover { border-color: #d5dae0; }

.ship-dot {
    flex: 0 0 auto; width: 19px; height: 19px; border-radius: 50%;
    border: 2px solid #b6bdc6; background: #fff; position: relative;
}
.ship-row:has(input:checked) .ship-dot { border-color: #2E5BE0; }
.ship-row:has(input:checked) .ship-dot::after {
    content: ""; position: absolute; inset: 3px; border-radius: 50%; background: #2E5BE0;
}

.ship-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ship-name { font-size: 15px; font-weight: 500; color: #111418; }
.ship-detail { font-size: 13px; color: #4a5560; }
.ship-price { flex: 0 0 auto; font-size: 16px; color: #111418; white-space: nowrap; text-align: right; }
.ship-was { display: block; font-size: 12px; color: #6b7480; }
.ship-free { color: #12703f; font-weight: 600; }

@media (max-width: 420px) {
    .ship-row { gap: 10px; padding: 12px; }
    .ship-name { font-size: 14px; }
    .ship-detail { font-size: 12px; }
    .ship-price { font-size: 15px; }
}


/* =========================================================================
   ON-PAGE PAYMENT STEP (confirmation + account order)
   Brand COLOURS only — no provider logo, wordmark, or icon anywhere.
   ========================================================================= */
.orderpay-panel { display: block; }
.orderpay-due-label { margin: 0; font-size: 13px; color: #5a6470; }
.orderpay-due-amount { margin: 2px 0 0; font-family: var(--font-mono); font-size: 28px; font-weight: 500; color: #111418; }
.orderpay-ships-note { margin: 8px 0 0; font-size: 14px; color: #4a5560; }

.orderpay-now-label { display: block; margin: 22px 0 10px; font-size: 11px; text-transform: uppercase; letter-spacing: .09em; color: #5a6470; }
.orderpay-buttons { display: flex; flex-direction: column; gap: 9px; }
.orderpay-btn {
    display: flex; align-items: center; justify-content: center;
    width: 100%; box-sizing: border-box; min-height: 52px; padding: 0 18px;
    border: none; border-radius: 10px; text-decoration: none;
    font-size: 16px; font-weight: 600; font-family: inherit;
    cursor: pointer; transition: filter .12s ease;
}
.orderpay-btn:hover { filter: brightness(1.07); }
.orderpay-btn:focus-visible { outline: 3px solid #111418; outline-offset: 2px; }
/* Configured but not live yet: visible, obviously inert, not clickable. */
.orderpay-btn.is-coming {
    background: #eceef1; color: #6b7480; cursor: not-allowed;
    font-weight: 500; filter: none;
}

/* "Pay now" in the order history list. A real tappable button, not a text link:
   44px minimum on a phone, where it is the full width of the card. */
.pay-now-link {
    display: inline-flex; align-items: center; justify-content: center;
    box-sizing: border-box; min-height: 44px; padding: 0 18px;
    border-radius: 10px; background: #2E5BE0; color: #fff;
    font-size: 15px; font-weight: 600; text-decoration: none;
}
.pay-now-link:hover { text-decoration: none; filter: brightness(1.07); }
.pay-now-link:focus-visible { outline: 3px solid #111418; outline-offset: 2px; }

.orderpay-zelle { margin-top: 14px; border: 1px solid #dcdfe3; border-radius: 10px; padding: 14px 16px; }
.orderpay-zelle.is-coming { color: #6b7480; background: #f5f6f8; font-size: 14px; }
.orderpay-zelle-head { margin: 0 0 6px; font-size: 15px; font-weight: 600; color: #111418; }
.orderpay-zelle-addr { margin: 0 0 8px; font-size: 16px; color: #111418; word-break: break-all; }
.orderpay-zelle-note { margin: 0; font-size: 13px; line-height: 1.55; color: #4a5560; }

/* Zelle carries neither an amount nor a reference, so the ID, the exact amount, and
   the memo are each shown with their own copy control rather than left to be typed
   from memory. Label left, value + Copy right; stacks on a narrow phone. */
.orderpay-zelle-dl { margin: 10px 0 12px; }
.orderpay-zelle-row {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 6px 10px; padding: 9px 0; border-bottom: 1px solid #eceef1;
}
.orderpay-zelle-row:first-child { padding-top: 0; }
.orderpay-zelle-row:last-child { padding-bottom: 0; border-bottom: none; }
.orderpay-zelle-row dt { flex: 0 0 auto; font-size: 13px; color: #5a6470; }
.orderpay-zelle-row dd {
    flex: 1 1 auto; min-width: 0; margin: 0;
    display: flex; align-items: center; justify-content: flex-end; gap: 10px;
}
.orderpay-zelle-val { min-width: 0; font-size: 15px; color: #111418; word-break: break-all; text-align: right; }
.orderpay-copy {
    flex: 0 0 auto; min-height: 36px; padding: 0 12px;
    background: #fff; color: #111418; border: 1px solid #9aa2ac; border-radius: 8px;
    font-size: 13px; font-family: inherit; cursor: pointer;
}
.orderpay-copy:hover { border-color: #111418; }
.orderpay-copy:focus-visible { outline: 3px solid #111418; outline-offset: 2px; }
.orderpay-copy.is-copied { border-color: #12703f; color: #12703f; }
/* The Zelle account is registered to another legal entity, so the name the customer
   is about to see is stated plainly. Informational, not a warning. */
.orderpay-zelle-recipient { margin: 8px 0 0; font-size: 13px; line-height: 1.55; color: #4a5560; }

@media (max-width: 480px) {
    .orderpay-zelle-row { flex-direction: column; align-items: stretch; gap: 4px; }
    .orderpay-zelle-row dd { justify-content: space-between; }
    .orderpay-zelle-val { text-align: left; }
}


.orderpay-reported-btn {
    display: block; width: 100%; min-height: 48px; padding: 0 18px;
    background: #fff; color: #111418; border: 1px solid #9aa2ac; border-radius: 10px;
    font-size: 15px; font-weight: 600; font-family: inherit; cursor: pointer;
}
.orderpay-reported-btn:hover { border-color: #111418; }
/* The "tell us which app" prompt. Replaces the optional <select> that let an order
   be reported with no method. .is-lead is the confirmation page, which is the first
   thing a customer sees after checkout and so states the requirement outright. */
.paystep-report { margin: 18px 0 0; }
.paystep-report-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin: 0 0 4px; }
.paystep-req {
    display: inline-flex; align-items: center; padding: 3px 9px; border-radius: 7px;
    background: #b4441f; color: #fff; font-size: 10.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .06em;
}
.paystep-report-h { font-size: 15px; font-weight: 650; color: #111418; }
.paystep-report-s { margin: 0 0 14px; font-size: 13.5px; line-height: 1.55; color: #4a5560; }
/* Confirmation page: a bordered panel rather than a line of text, so it reads as a
   step that is still outstanding instead of an afterthought under the buttons. */
.paystep-report.is-lead {
    border: 2px solid #b4441f; border-radius: 12px; padding: 16px; background: #fdf6f3;
}
.paystep-report.is-lead .paystep-report-h { font-size: 16px; }
.paystep-report.is-lead .orderpay-reported-btn { background: #111418; color: #fff; border-color: #111418; }
.paystep-report.is-lead .orderpay-reported-btn:hover { background: #000; }
/* A refused report. Solid, no tint games, and it sits above the prompt it belongs to. */
.paystep-err, .opay-err {
    margin: 0 0 14px; padding: 12px 14px; border-radius: 10px;
    background: #b4441f; color: #fff; font-size: 13.5px; line-height: 1.55; font-weight: 500;
}
.orderpay-reported { margin: 18px 0 0; font-size: 14px; font-weight: 500; color: #12703f; }
.orderpay-status-note { margin: 12px 0 0; font-size: 13px; color: #5a6470; }

/* ---- Cash App order-number reminder dialog ---- */
.orderpay-dialog[hidden] { display: none; }
.orderpay-dialog { position: fixed; inset: 0; z-index: 9500; display: flex; align-items: center; justify-content: center; padding: 16px; }
.orderpay-dialog-backdrop { position: absolute; inset: 0; background: rgba(17, 20, 24, .55); }
.orderpay-dialog-panel {
    position: relative; width: 100%; max-width: 380px; box-sizing: border-box;
    background: #fff; border-radius: 14px; padding: 22px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .28);
}
.orderpay-dialog-h { margin: 0 0 10px; font-size: 19px; font-weight: 700; color: #111418; }
.orderpay-dialog-body { margin: 0 0 14px; font-size: 14px; line-height: 1.55; color: #4a5560; }
.orderpay-dialog-order {
    margin: 0 0 18px; padding: 12px; text-align: center;
    border: 1px solid #c8ccd2; border-radius: 10px;
    font-size: 20px; font-weight: 600; color: #111418; word-break: break-all;
}
.orderpay-dialog-actions { display: flex; gap: 9px; }
.orderpay-dialog-back, .orderpay-dialog-go {
    flex: 1 1 0; min-width: 0; min-height: 44px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 10px; font-size: 15px; font-weight: 600; font-family: inherit;
    text-decoration: none; cursor: pointer; box-sizing: border-box;
}
.orderpay-dialog-back { background: #fff; color: #111418; border: 1px solid #9aa2ac; }
/* Fallback only: the real pair is set inline from data-pay-go-bg / -fg, which
   come from payment_buttons() and so from PaymentMethods::ALL. */
.orderpay-dialog-go   { background: #00D54B; color: #04310f; border: none; }
body.orderpay-dialog-lock { overflow: hidden; }

@media (max-width: 400px) {
    .orderpay-dialog-panel { padding: 18px; }
    .orderpay-dialog-order { font-size: 18px; }
    .orderpay-dialog-back, .orderpay-dialog-go { font-size: 14px; }
}


/* Discount code UI (cart + checkout) */
.discount-row { color: #1b8a4e; }
.discount-row span { color: #1b8a4e; }
.discount-form { margin: 14px 0 6px; padding: 14px 0 4px; border-top: 1px solid var(--border); }
.checkout-discount { margin-top: 8px; }
.discount-label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 7px; color: var(--ink); }
.discount-input-row { display: flex; gap: 8px; }
.discount-input { flex: 1; min-width: 0; background: #fff; border: 1px solid var(--border-2); padding: 10px 12px; color: var(--ink); text-transform: uppercase; }
.discount-input:focus { outline: none; border-color: var(--black); }
.discount-apply { flex: none; }
.discount-applied { color: #1b8a4e; margin: 8px 0 0; }
.discount-error { margin: 8px 0 0; }
.discount-remove { margin-top: 6px; }

/* Referral discount. The pill marks an applied code as the one the visitor's link
   carried; the offer line appears only when swapping would genuinely save more.
   Neither ever names the owner. */
.ref-pill {
    display: inline-block; margin-left: 6px; padding: 2px 8px; border-radius: 7px;
    background: #2E5BE0; color: #fff; font-size: 10.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .05em; vertical-align: 1px;
}
.ref-offer {
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    margin: 14px 0 0; padding: 12px 14px; border: 1px solid #cfdcfa; border-radius: 10px;
    background: #f4f8ff;
}
.ref-offer-t { margin: 0; font-size: 13px; line-height: 1.5; color: #1c1c1e; min-width: 0; }
.ref-offer-amt { color: #1b8a4e; font-weight: 650; }
.ref-offer-btn {
    flex: 0 0 auto; min-height: 40px; padding: 0 16px; border: none; border-radius: 9px;
    background: #2E5BE0; color: #fff; font-family: inherit; font-size: 13px; font-weight: 650;
    cursor: pointer;
}
.ref-offer-btn:hover { background: #264fc4; }
.ref-offer-btn:focus-visible { outline: 2px solid #2E5BE0; outline-offset: 2px; }
.ref-offer .inline-form { margin: 0; flex: 0 0 auto; }

@media (max-width: 480px) {
    /* Stacked, with the button full width, so the tap target does not shrink to a
       sliver beside two lines of wrapped text. */
    .ref-offer { flex-direction: column; align-items: stretch; gap: 10px; }
    .ref-offer-btn { width: 100%; }
    .ref-offer .inline-form { width: 100%; }
}


/* Checkout */
.checkout { display: grid; grid-template-columns: 1fr 360px; gap: 32px; align-items: start; }
.checkout-block { margin-bottom: 24px; }
.checkout-h2 { font-size: 16px; margin: 0 0 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.checkout-billing.is-hidden { display: none; }
.checkout-summary { border: 1px solid var(--border); border-radius: 14px; padding: 22px; background: #fff; position: sticky; top: 100px; }
.summary-lines { margin-bottom: 12px; }
.summary-line { display: flex; justify-content: space-between; font-size: 13.5px; padding: 5px 0; color: var(--muted); gap: 12px; }
.summary-line-name { color: var(--ink); }
.summary-row { display: flex; justify-content: space-between; font-size: 14.5px; padding: 6px 0; }
.summary-grand { font-weight: 700; font-size: 17px; border-top: 1px solid var(--border); margin-top: 6px; padding-top: 12px; }
.checkout-place { margin-top: 16px; }

/* Google Places autocomplete dropdown (classic widget). Restyled to match the site:
   the widget attaches .pac-container to <body>, so it is never clipped by container
   overflow; a high z-index keeps it above the sticky summary. The "powered by Google"
   row is required attribution, so it is kept and styled around, not hidden. */
.pac-container {
    z-index: 4000;
    margin-top: 6px;
    background: #fff;
    border: 1px solid #e4e4e4;
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.10);
    font-family: 'Inter', system-ui, sans-serif;
    overflow: hidden;
}
.pac-item {
    padding: 10px 14px;
    font-size: 14px;
    line-height: 1.4;
    color: var(--ink);
    border-top: 1px solid #f0f0f0;
    cursor: pointer;
}
.pac-item:first-child { border-top: none; }
.pac-item:hover,
.pac-item-selected,
.pac-item-selected:hover { background: #eef3ff; }
.pac-item-query { font-size: 14px; color: var(--ink); }
.pac-matched { font-weight: 600; }
.pac-icon { margin-top: 2px; }
/* Attribution row spacing (kept per Google terms). */
.pac-logo:after { margin: 8px 14px; opacity: 0.7; }

/* =========================================================================
   ORDER CONFIRMATION, desktop two column (approved mockup).
   Left: pay, then what was bought. Right: invoice, address, email note.
   The payment block leads because it is the only thing this page is for; it
   used to sit under the invoice preview and the summary, about a screen down.
   ========================================================================= */
.conf-page { max-width: 1200px; }

.conf-head { display: flex; align-items: center; gap: 13px; margin-bottom: 7px; }
.conf-tick { width: 30px; height: 30px; border-radius: 50%; background: #1fa863; color: #fff; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.conf-tick svg { width: 17px; height: 17px; }
.conf-h1 { font-size: 28px; font-weight: 600; margin: 0; letter-spacing: -0.01em; }
.conf-sub { font-size: 14px; color: var(--muted); margin: 0 0 24px; }
.conf-sub b { color: var(--ink); font-weight: 600; }
.conf-sub code { font-size: 13.5px; }

/* 1.25fr / 1fr: the payment block and the summary need the width, the invoice
   preview and the address do not. minmax(0,…) so a long product name cannot
   push its track wider than its share. */
.conf-cols { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 20px; align-items: start; }
.conf-col { min-width: 0; }

/* ---- The payment block --------------------------------------------------- */
.conf-paybox { background: #101319; border-radius: 12px; padding: 22px 24px; margin-bottom: 16px; }
.conf-paytop { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.conf-lbl { font-size: 12px; color: #8b8f9a; letter-spacing: 0.1em; text-transform: uppercase; }
.conf-due { font-size: 40px; font-weight: 600; color: #fff; line-height: 1.12; margin: 0 0 6px; }
.conf-note { font-size: 13px; color: #8b8f9a; margin: 0 0 20px; line-height: 1.6; }

/* The 48 hour clock. Ticked by the shared assets/js/countdown.js, which sets the
   state class on this element; the four colourways below match the pay page. */
.conf-cd { display: inline-flex; align-items: center; gap: 7px; background: rgba(245, 197, 24, 0.15); color: #f5c518; font-size: 12px; padding: 6px 12px; border-radius: 999px; white-space: nowrap; }
.conf-cd svg { width: 13px; height: 13px; flex: 0 0 auto; }
.conf-cd-t { font-variant-numeric: tabular-nums; }
.conf-cd.is-warn { background: rgba(245, 197, 24, 0.15); color: #f5c518; }
.conf-cd.is-urgent { background: rgba(224, 90, 74, 0.18); color: #ff9d8a; }
.conf-cd.is-past { background: rgba(224, 90, 74, 0.22); color: #ff8a74; }

/* Two by two. Only configured methods render, so this is a 3-up or 2-up when a
   provider is switched off rather than a hole where a button should be. */
.conf-paygrid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.conf-pay { font-size: 14px; font-weight: 600; text-align: center; padding: 14px; border-radius: 8px; text-decoration: none; display: block; }
.conf-pay:hover { filter: brightness(1.06); }
/* NO PER-PROVIDER COLOUR RULES HERE ON PURPOSE. Each button's background and
   foreground are set inline from PaymentMethods::ALL, the same table the pay page
   paints from. Duplicating the palette in this stylesheet is what let the two
   pages disagree about Cash App's green in the first place. */
.conf-pay.is-coming { background: #23262f; color: #6d717c; cursor: default; }
.conf-zelle { display: flex; align-items: baseline; gap: 10px; margin: 16px 0 0; color: #fff; font-size: 13px; flex-wrap: wrap; }

/* ---- Panels -------------------------------------------------------------- */
.conf-panel { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 18px 20px; }
.conf-panel + .conf-panel, .conf-paybox + .conf-panel { margin-top: 16px; }
.conf-panel-t { font-size: 14px; font-weight: 600; margin: 0 0 14px; }

/* The rule under a line item is an inset positioned hairline, not a
   border-bottom: a border paints one side of the box, which the house rules
   forbid. left/right/bottom pinned, so it spans the row exactly. */
/* SCOPED TO .conf-page. A plain .conf-item rule already exists further down for
   the account order and admin order pages, and it sits LATER in the file, so at
   equal specificity it would win over this one. The page scope outranks it. */
.conf-page .conf-item { position: relative; display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 0 0 12px; margin-bottom: 12px; font-size: 13px; }
.conf-page .conf-item::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--border); }
.conf-page .conf-item-n span { color: var(--muted); }
.conf-page .conf-item-v { font-size: 13px; }

.conf-line { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--ink-2, #3d4149); padding: 5px 0; }
.conf-line span:last-child { font-family: var(--font-mono); }
.conf-line.is-discount { color: #1fa863; }
/* Same reasoning as .conf-item: an inset hairline, not a border-top. */
.conf-line.is-total { position: relative; font-size: 15px; font-weight: 600; color: var(--ink); padding-top: 12px; margin-top: 8px; }
.conf-line.is-total::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--border); }

.conf-ref { font-size: 13px; color: var(--muted); margin: 0 0 12px; }
.conf-paylist { margin: 0; }
.conf-payrow { position: relative; display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 12px; padding: 9px 0; font-size: 13px; }
.conf-payrow + .conf-payrow::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--border); }
.conf-payrow dt { color: var(--muted); margin: 0; }
.conf-payrow dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }

.conf-invhead { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.conf-invhead .conf-panel-t { margin: 0; }
.conf-dl { background: var(--accent-blue); color: #fff; font-size: 12px; font-weight: 600; padding: 9px 14px; border-radius: 7px; text-decoration: none; white-space: nowrap; }
.conf-dl:hover { background: #244cc0; }
.conf-invprev { display: block; width: 100%; height: 230px; border: 0; border-radius: 8px; background: #f0f1f4; }
.conf-invfallback { padding: 16px; font-size: 13px; color: var(--muted); }

.conf-addr { font-style: normal; font-size: 13px; color: var(--ink); line-height: 1.75; }
.conf-emailnote { background: #f7f8fa; border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; margin: 16px 0 0; font-size: 13px; color: var(--ink); line-height: 1.65; }
.conf-account { display: inline-block; margin-top: 16px; font-size: 13.5px; font-weight: 600; color: var(--accent-blue); }

/* Desktop only, per the brief. Below 900 the two columns become one and the
   blocks stack in document order, which already leads with payment. This block
   sits later in the file than the rules above, which is what lets it win: a
   media query adds no specificity of its own. */
@media (max-width: 900px) {
    .conf-cols { grid-template-columns: 1fr; gap: 16px; }
    .conf-paygrid { grid-template-columns: 1fr; }
    .conf-h1 { font-size: 22px; }
    .conf-due { font-size: 32px; }
    .conf-payrow { grid-template-columns: 1fr; gap: 2px; }
}


/* Confirmation + order detail */
.conf-check { display: inline-flex; align-items: center; justify-content: center; width: 52px; height: 52px; border-radius: 50%; background: #e7f6ee; color: #1b8a4e; font-size: 26px; margin-bottom: 14px; }
.conf-number { font-size: 16px; margin: 0 0 8px; }
.conf-section { margin-top: 28px; }
.conf-h2 { font-size: 16px; margin: 0 0 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.conf-item { display: flex; justify-content: space-between; padding: 7px 0; font-size: 14.5px; gap: 16px; }
.conf-totals { margin-top: 12px; max-width: 360px; }
.conf-address { font-style: normal; font-size: 14.5px; line-height: 1.6; color: var(--ink); }
.pay-list { margin: 14px 0 0; }
.pay-row { display: grid; grid-template-columns: 170px 1fr; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 14.5px; }
.pay-row dt { color: var(--muted); font-weight: 500; margin: 0; }
.pay-row dd { margin: 0; color: var(--ink); }

/* Account order detail: timeline + tracking */
.order-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.order-head-num { font-size: 0.62em; color: var(--muted); }
.timeline { list-style: none; display: flex; margin: 24px 0; padding: 0; }
.timeline-step { flex: 1; text-align: center; position: relative; }
.timeline-step::before { content: ""; position: absolute; top: 8px; left: -50%; width: 100%; height: 2px; background: var(--border); z-index: 0; }
.timeline-step:first-child::before { display: none; }
.timeline-dot { position: relative; z-index: 1; display: block; width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid var(--border-2); margin: 0 auto 8px; }
.timeline-step.is-done .timeline-dot { background: var(--accent-blue); border-color: var(--accent-blue); }
.timeline-step.is-done::before { background: var(--accent-blue); }
.timeline-label { display: block; font-size: 13px; font-weight: 600; color: var(--muted-2); }
.timeline-step.is-done .timeline-label { color: var(--ink); }
.timeline-date { display: block; color: var(--muted-2); }
.tracking-box { border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; margin: 8px 0 20px; display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; background: #fff; }
.tracking-box p { margin: 0; }
/* Keeps the carrier/number/status lines together as one block so the button
   stays on the right of the row rather than being spread by justify-content.
   The 260px basis is what makes the button drop to its own line on a narrow
   phone instead of squeezing the status into a four-line column: the status can
   now be the carrier's own sentence, not just "In transit". */
.tracking-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 260px; }
.tracking-ident { overflow-wrap: anywhere; }
.tracking-status { font-weight: 600; color: var(--ink); line-height: 1.45; overflow-wrap: anywhere; }
/* "No detailed status yet." Deliberately quieter than a real status: it is the
   absence of news, not a state to read as progress. */
.tracking-note { line-height: 1.5; }
.btn-sm-blue { display: inline-flex; align-items: center; gap: 6px; background: var(--accent-blue); color: #fff; border: none; border-radius: 9px; padding: 10px 18px; font-size: 13.5px; font-weight: 600; }
.btn-sm-blue:hover { background: #264fc4; text-decoration: none; }
.order-cols { display: grid; grid-template-columns: 1.4fr 1fr; gap: 32px; align-items: start; }

/* Admin orders */
.order-filters { margin-top: 6px; }
.order-cust-email { display: block; color: var(--muted-2); }
.pager { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 18px; flex-wrap: wrap; }
.btn.btn-blue { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }
.btn.btn-blue:hover { background: #264fc4; }

/* Message reply (admin) */


/* Admin users: tax-exempt cell */
.tax-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tax-check { margin-bottom: 0; font-size: 12px; white-space: nowrap; gap: 7px; }
.tax-cert { width: 120px; padding: 7px 9px; border: 1px solid var(--border-2); border-radius: 8px; font-size: 12px; }

/* Commerce responsive */
@media (max-width: 900px) {
    .cart, .checkout, .order-cols, .admin-order-cols { grid-template-columns: 1fr; }
    .cart-summary, .checkout-summary { position: static; }
    /* Single column: drop the explicit placement so DOM order rules, which puts the
       offer above the summary. */
    .cart-lines, .cart-bac, .cart-summary { grid-column: 1; grid-row: auto; }
}


/* =========================================================================
   PRODUCT DETAIL PAGE
   ========================================================================= */
.pd { max-width: 1360px; margin: 0 auto; padding: 36px 40px 60px; }
.pd-crumb { font-size: 13px; color: var(--muted); margin-bottom: 22px; }
.pd-crumb a { color: var(--muted); }
.pd-crumb a:hover { color: #111; }
.pd-crumb span { color: var(--muted-3); margin: 0 2px; }
.pd-crumb-current { color: #111; }

/* Two equal columns, per the approved mockup. minmax(0,1fr) rather than 1fr so a
   long spec value or a wide certificate row cannot push its track wider than half
   the page: a grid track's default min-width is auto, not zero. */
.pd-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }

/* The two column area holds ONLY the image and the buy box now, so the two
   columns end at roughly the same height and no empty column is left behind.
   Everything that used to stack under the image runs full width below .pd-top. */
.pd-gallery { min-width: 0; }
.pd-top { margin-bottom: 18px; }

/* ---- Full width below the buy box --------------------------------------- */

/* The certificate row, when the selected size has one. */
.pd-coa-row { margin-bottom: 16px; }

/* Trust row. Three cards: purity testing, free shipping, support. Absorbs the
   two standalone cards that used to sit under the image. */
.pd-trustrow { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }
.pd-trustcard {
    display: flex; align-items: center; gap: 12px; min-width: 0;
    background: #fff; border: 1px solid #ececec; border-radius: 10px;
    padding: 15px 17px;
}
.pd-trustcard > svg { width: 20px; height: 20px; color: var(--accent-blue); flex: 0 0 auto; }
.pd-trustcard-t { margin: 0; font-size: 13.5px; font-weight: 600; color: #111; }
.pd-trustcard-s { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); }

/* Tabs and accordions side by side. Half the vertical space of stacking them. */
.pd-info { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; margin-bottom: 16px; align-items: start; }
.pd-info > .pd-tabs-panel, .pd-info > .pd-accordions { min-width: 0; }

/* Gallery */
.pd-gallery-card { background: #fff; border: 1px solid #ececec; border-radius: 20px; padding: 26px; }
.pd-main { height: 470px; background: linear-gradient(170deg, #f8f8f9, #efeff1); border-radius: 14px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.pd-main img { max-width: 88%; max-height: 88%; object-fit: contain; }
.pd-main .pcard-placeholder svg { width: 110px; height: 110px; color: #cdced3; }
/* =========================================================================
   Certificate on the product page. Three surfaces, one certificate:
     .pd-coa-badge   always visible when the selected size has one
     .pd-coa-hov     the hover panel, the grid's treatment at a larger size
     .pd-coa-row     under the trust list, above the size chips

   The ROW is the one that matters. It needs no pointer, it is the same on
   every device, and it carries a real button. The badge announces that a
   certificate exists without any interaction; the hover panel is a desktop
   enhancement on top of both. All three are hidden by JS when the selected
   size has no certificate: no placeholder, no pending state.
   ========================================================================= */
/* An explicit stacking context, so the order inside is decided here rather than
   inherited from wherever .pd-main happens to sit. Without it the product image
   is a static element with z-index auto and the panel is positioned at z-index 2,
   which SHOULD order them, but the image also carries a filter while the panel is
   open, and a filtered element forms its own stacking context. Pinning the parent
   removes the argument: everything inside sorts by z-index against this box. */
.pd-main { position: relative; z-index: 0; }

.pd-coa-badge {
    position: absolute;
    left: 14px;
    top: 14px;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 44px;
    padding: 8px 14px;
    background: #ffffff;
    color: #12805a;
    border: 1px solid #cfe6d9;
    border-radius: 999px;
    box-shadow: 0 2px 8px rgba(20, 23, 29, 0.10);
    font: inherit;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: opacity 250ms ease, transform 250ms cubic-bezier(.2, .8, .25, 1);
}
.pd-coa-badge i { width: 7px; height: 7px; border-radius: 999px; background: #1fa863; display: block; }
.pd-coa-badge:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; }
/* Tucks away as the panel it announces opens, so it is not left sitting on
   top of the document. */
@media (hover: hover) and (pointer: fine) {
    .pd-main.pd-coa-ready:hover .pd-coa-badge { opacity: 0; transform: translateY(-6px); pointer-events: none; }
}

.pd-main.coa-open .pd-coa-badge { opacity: 0; transform: translateY(-6px); pointer-events: none; }

/* The hover panel. Same construction as the grid's, sized for a 470px box. */
.pd-coa-hov {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    grid-template-rows: 1fr auto;
    overflow: hidden;             /* the tilt swings a corner out; clip it */
    background: #0d1220;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 280ms ease, visibility 280ms;
}
@media (hover: hover) and (pointer: fine) {
    .pd-main.pd-coa-ready:hover .pd-coa-hov { opacity: 1; visibility: visible; }
    .pd-main.pd-coa-ready:hover > img { filter: brightness(0.12); }
}

.pd-main.coa-open .pd-coa-hov { opacity: 1; visibility: visible; pointer-events: auto; }
.pd-main.coa-open > img { filter: brightness(0.12); }
/* Explicitly the bottom layer. It was static with z-index auto, which leaves its
   order against the positioned panel above it decided by painting rules rather
   than by a number, and it gains a filter while the panel is open, which makes it
   a stacking context of its own. Stating 0 here and 2 on the panel removes the
   ambiguity: the product image is always under the certificate, never over the
   one control in it. */
.pd-main > img { position: relative; z-index: 0; transition: filter 340ms ease; }

.pd-coa-hdoc { position: relative; display: grid; place-items: center; padding: 26px 26px 0; min-height: 0; }
/* The glow: one solid colour under a blur, no gradient. */
.pd-coa-hdoc::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 46%;
    width: 74%;
    height: 70%;
    border-radius: 50%;
    background: #4c74e8;
    filter: blur(54px);
    opacity: 0;
    transform: translate(-50%, -50%) scale(.85);
    transition: opacity 420ms ease 40ms, transform 500ms cubic-bezier(.2, .8, .25, 1) 40ms;
}
@media (hover: hover) and (pointer: fine) {
    .pd-main.pd-coa-ready:hover .pd-coa-hdoc::before { opacity: .34; transform: translate(-50%, -50%) scale(1); }
}

.pd-main.coa-open .pd-coa-hdoc::before { opacity: .34; transform: translate(-50%, -50%) scale(1); }

/* THE SHEET IS SIZED BY RATIO, NOT BY ITS CONTENT.

   It used to have no width of its own and shrink-wrapped around the image. That
   works only when every link in the chain behaves: the image must have resolved
   its intrinsic size, the grid item must not stretch, and nothing may contribute
   extra inline width. When any one of those failed the box came out wider than
   the page and the panel colour showed down one side.

   aspect-ratio removes the dependency. The height is a share of the box, the
   width is computed FROM that height and the ratio, and the image then fills
   100% of both. The container cannot be wider than the page because the page is
   the container. --coa-ar is set by JS from the stored preview dimensions, and
   re-set from the image's own natural size once it loads, so a certificate that
   is not US Letter still lands exactly. The literal fallback only applies for the
   instant before either of those runs. */
.pd-coa-hsheet {
    position: relative;
    height: 78%;
    width: auto;
    aspect-ratio: var(--coa-ar, 420 / 544);
    max-width: 100%;
    /* Stated rather than inherited from the parent's place-items. A grid item
       that stretches would take the full column width and win over width:auto,
       which is the one remaining way the box could end up wider than the page. */
    justify-self: center;
    align-self: center;
    background: #141b28;          /* the loading panel, never white: see the grid */
    line-height: 0;
    font-size: 0;
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 18px 44px rgba(0, 0, 0, 0.62);
    opacity: 0;
    transform: translateY(16px) scale(.94) rotate(-1.2deg);
    transition: opacity 320ms ease, transform 420ms cubic-bezier(.2, .8, .25, 1);
}
@media (hover: hover) and (pointer: fine) {
    .pd-main.pd-coa-ready:hover .pd-coa-hsheet { opacity: 1; transform: none; }
}

.pd-main.coa-open .pd-coa-hsheet { opacity: 1; transform: none; }
/* Fills the sheet exactly. The box already carries the page's own ratio, so
   width and height at 100% leave nothing of the container visible, and there is
   no letterboxing for object-fit to do. max-* are reset because .pd-main img caps
   every image in the gallery at 88%, which would inset the page inside its sheet. */
.pd-coa-hsheet img {
    position: relative;
    z-index: 1;
    display: block;
    height: 100%;
    width: 100%;
    max-width: none;
    max-height: none;
    object-fit: fill;
    filter: brightness(1.10) contrast(1.14);
    opacity: 0;
    transition: opacity 340ms ease;
}
.pd-coa-hsheet img.is-ready { opacity: 1; }

/* The loading placeholder, scoped to THIS page. The markup reuses the grid's
   .pcard-coa-skel class, but every rule for it is scoped to .catalog-dark .pgrid,
   so on the product page it was an unstyled inline span: measured at 0x0,
   position:static, display:inline. The panel had no placeholder at all and simply
   opened onto the panel colour until the image arrived. */
.pd-coa-hsheet .pcard-coa-skel {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: block;
    background: #141b28;
    overflow: hidden;
    opacity: 1;
    transition: opacity 300ms ease;
}
.pd-coa-hsheet .pcard-coa-skel i {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 42%;
    background: #243049;
    filter: blur(22px);
    opacity: .85;
    animation: pcdCoaSweep 1.15s ease-in-out infinite;
}
.pd-coa-hsheet.is-ready .pcard-coa-skel { opacity: 0; }

/* THE BAR TAKES POINTER EVENTS BACK.
   The panel above is pointer-events:none so it never swallows a click meant for
   the product image, and the hover rule only ever lifted opacity and visibility.
   That left the View COA button unreachable on a pointer device: measured, the
   element at the button's centre was img#pd-main-img, not the button.
   pointer-events is inherited but a descendant may re-enable it, so re-enabling
   it here fixes BOTH paths at once rather than duplicating the state. While the
   panel is closed it inherits visibility:hidden and hit-tests nothing, so this
   cannot make a hidden button clickable. */
.pd-coa-hbar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 20px;
    background: #0d1220;
    border-top: 1px solid #1e2637;
    pointer-events: auto;
    /* Above the sheet and above anything the panel overlays, so the one control
       in here is never sitting under the document it captions. */
    position: relative;
    z-index: 2;
}
.pd-coa-ht { flex: 1; min-width: 0; }
.pd-coa-ht b { display: block; color: #fff; font-size: 14px; font-weight: 700; }
.pd-coa-ht span { display: block; margin-top: 2px; color: #9aa2b4; font-size: 12px; font-family: var(--font-mono); overflow-wrap: anywhere; }
.pd-coa-hbtn {
    background: var(--accent-blue);
    color: #fff;
    font-size: 14px;
    font-weight: 700;
    text-decoration: none;
    padding: 12px 20px;
    border-radius: 9px;
    white-space: nowrap;
    transition: background 200ms ease;
}
.pd-coa-hbtn:hover { background: #1B3FA8; }

/* The row. */
.pd-coa-row {
    margin-top: 18px;
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 14px 16px;
}
.pd-coa-thumb {
    width: 44px;
    height: 57px;
    flex: none;
    border-radius: 4px;
    overflow: hidden;
    background: #fff;
    box-shadow: 0 2px 8px rgba(20, 23, 29, 0.14);
}
.pd-coa-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
.pd-coa-rowtext { flex: 1; min-width: 150px; }
.pd-coa-rowtext b { display: block; font-size: 14.5px; font-weight: 700; color: var(--ink); }
.pd-coa-rowtext span { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted-2); font-family: var(--font-mono); overflow-wrap: anywhere; }
.pd-coa-rowbtn {
    background: var(--ink);
    color: #fff;
    font-size: 14px;
    font-weight: 700;
    text-decoration: none;
    padding: 11px 18px;
    border-radius: 10px;
    white-space: nowrap;
}
.pd-coa-rowbtn:hover { background: #000; }

/* Phones: the button takes the full width rather than being squeezed beside the
   text, which is what the mockup does at 390. */
@media (max-width: 560px) {
    .pd-coa-row { padding: 12px 14px; }
    .pd-coa-thumb { width: 38px; height: 49px; }
    .pd-coa-rowbtn { width: 100%; text-align: center; }
    .pd-coa-badge { left: 10px; top: 10px; padding: 7px 12px; font-size: 11px; }
}


@media (prefers-reduced-motion: reduce) {
    .pd-coa-badge,
    .pd-coa-hov,
    .pd-coa-hdoc::before,
    .pd-coa-hsheet,
    .pd-coa-hsheet img,
    .pd-main > img {
        transition-duration: .01ms;
        transition-delay: 0s;
    }
    .pd-coa-hsheet { transform: none; }
    .pd-coa-hdoc::before { transform: translate(-50%, -50%) scale(1); }
    .pd-coa-hsheet .pcard-coa-skel i { animation: none; }
}


.pd-thumbs { display: flex; gap: 11px; margin-top: 16px; flex-wrap: wrap; }
.pd-thumb { width: 82px; height: 82px; border-radius: 11px; border: 1px solid #e4e4e4; background: #f7f7f8; overflow: hidden; padding: 0; cursor: pointer; }
.pd-thumb img { width: 100%; height: 100%; object-fit: contain; }
.pd-thumb.is-active { border: 2px solid var(--accent-blue); }

/* Buy box */
.pd-buy .cat-badge { margin-bottom: 14px; }
.pd-title { font-size: 41px; font-weight: 800; letter-spacing: -.03em; line-height: 1.05; margin: 0; color: #111; }
.pd-subtitle { font-size: 17px; color: #7a7a7a; margin: 8px 0 0; }
/* align-items: center, not baseline: the stock pill sits on this line now and a
   pill aligned on its text baseline hangs below a 35px price. */
.pd-price-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 20px 0 18px; }
.pd-price-current { font-size: 35px; font-weight: 800; color: var(--accent-blue); }
.pd-price-compare { font-size: 20px; color: #a0a0a0; text-decoration: line-through; }
.pd-save { display: inline-flex; align-items: center; gap: 6px; background: var(--accent-blue); color: #fff; font-size: 12.5px; font-weight: 700; letter-spacing: .03em; padding: 5px 12px; border-radius: 999px; }
.pd-save svg { width: 14px; height: 14px; }
/* No top margin: it lives on the price line now, and .pd-price-row spaces it. */
.pd-stock { display: inline-flex; align-items: center; gap: 8px; background: #111; color: #fff; font-size: 12.5px; font-weight: 600; padding: 7px 14px; border-radius: 999px; margin: 0; }
.pd-stock-dot { width: 8px; height: 8px; border-radius: 50%; background: #3ED67F; }
.pd-stock-dot.pd-stock-low { background: #f0b429; }
.pd-stock-dot.pd-stock-out { background: #e05a4a; }

/* Trust list. A card in the left column now, matching the mockup, which carries no
   rules between the rows. The previous border-top on the list and border-bottom on
   each row are gone: they were single side borders, and the card edge plus the row
   spacing does the same separating job without them. */
.pd-trust { list-style: none; margin: 0; padding: 14px 18px; background: #fff; border: 1px solid #ececec; border-radius: 14px; }
.pd-trust li { display: flex; align-items: center; gap: 11px; padding: 8px 0; font-size: 14.5px; color: var(--ink); }
.pd-trust svg { width: 19px; height: 19px; color: var(--accent-blue); flex: 0 0 auto; }

.pd-size { margin: 4px 0 20px; }
.pd-size-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
.pd-size-label { font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #9a9a9a; }
.pd-size-selected { font-size: 13.5px; color: var(--muted); }
.pd-chips { display: flex; flex-wrap: wrap; gap: 10px; }
/* Compact rounded pill: label + small inline price, single wrapping row. */
.pd-chip { position: relative; display: inline-flex; align-items: center; gap: 7px; padding: 12px 26px; border: 1px solid #dcdcdc; border-radius: 999px; background: #fff; cursor: pointer; transition: border-color .12s ease, background .12s ease; }
.pd-chip:hover { border-color: #999; }
.pd-chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.pd-chip-size { font-size: 15px; font-weight: 600; color: #111; }
.pd-chip-price { font-size: 11px; color: var(--muted); }
.pd-chip:has(input:checked) { background: #111; border-color: #111; }
.pd-chip:has(input:checked) .pd-chip-size { color: #fff; }
.pd-chip:has(input:checked) .pd-chip-price { color: rgba(255,255,255,.7); }
.pd-chip:has(input:focus-visible) { outline: 2px solid var(--accent-blue); outline-offset: 2px; }
.pd-chip.is-out { opacity: .45; cursor: not-allowed; text-decoration: line-through; }

/* Stepper + Add to cart: two matching rounded pills, sized to content, side by
   side, left-aligned within a capped-width row (not stretched full column). */
.pd-buyrow { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; max-width: 440px; }

.pd-stepper { display: inline-flex; align-items: center; gap: 12px; min-height: 56px; padding: 0 18px; border: 1px solid #e4e4e4; border-radius: 999px; background: #fff; flex: 0 0 auto; }
.pd-step { min-width: 20px; background: none; border: none; font-size: 20px; line-height: 1; color: #6b6b6b; cursor: pointer; padding: 0; transition: color .12s ease; }
.pd-step:hover { color: #111; }
.pd-qty { width: 40px; text-align: center; border: none; background: none; font-size: 15px; font-weight: 700; color: #1a1a1a; -moz-appearance: textfield; appearance: textfield; }
.pd-qty::-webkit-outer-spin-button, .pd-qty::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pd-qty:focus { outline: none; }

.pd-add { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; min-height: 56px; padding: 0 44px; background: var(--accent-blue); color: #fff; border: none; border-radius: 999px; font-size: 17px; font-weight: 700; box-shadow: 0 7px 20px rgba(46,91,224,.28); cursor: pointer; transition: background .15s ease; }
.pd-add:hover { background: #264fc4; }

/* ===== Pack selector (product page) =====
   Replaces the stepper entirely when quantity tiers are on: one row of four packs,
   the running total, and the saving. Each pack shows its own per-vial price rather
   than a discount percentage, so the packs compare directly. */
.pd-tiers { margin: 4px 0 16px; max-width: 620px; }
.pd-pack-label {
    display: block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.11em;
    color: #8a8a8a;
    margin-bottom: 14px;
}

/* The top padding is what the badges hang into (they sit at top: -8px). */
.pd-pack-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding-top: 8px; }

.pd-pack {
    position: relative;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 11px 7px;
    border: 1px solid #e3e3e3;
    border-radius: 11px;
    background: #fff;
    cursor: pointer;
    text-align: center;
    transition: border-color .12s ease, background .12s ease;
}
.pd-pack input { position: absolute; opacity: 0; width: 0; height: 0; }
.pd-pack-qty  { font-size: 13px; font-weight: 500; color: #111; line-height: 1.25; }
.pd-pack-unit { margin-top: 2px; font-family: var(--font-mono); font-size: 10px; color: #666; line-height: 1.25; }

/* Flagged packs, unselected. Both keep the plain white fill; only the rim and the
   per-vial price carry the signal. */
.pd-pack.is-popular { border: 1.5px solid #2E5BE0; }
.pd-pack.is-popular .pd-pack-unit { color: #2E5BE0; }
.pd-pack.is-best .pd-pack-unit { color: #0f7a4d; }

.pd-pack:hover { border-color: #999; }
.pd-pack.is-popular:hover { border-color: #2450c4; }

/* Selected. Black card whatever the badge, so the choice reads at a glance. */
.pd-pack:has(input:checked) { background: #111; border: 1px solid #111; }
.pd-pack:has(input:checked) .pd-pack-qty { color: #fff; }
.pd-pack:has(input:checked) .pd-pack-unit { color: rgba(255, 255, 255, 0.5); }
.pd-pack:has(input:focus-visible) { outline: 2px solid var(--accent-blue); outline-offset: 2px; }

/* Badges ride above the card edge and stay put when the card is selected. */
.pd-pack-badge {
    position: absolute;
    top: -8px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    font-size: 8px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.02em;
    white-space: nowrap;
    color: #fff;
    padding: 2px 9px;
    border-radius: 8px;
}
.pd-pack-badge-popular { background: #2E5BE0; }
.pd-pack-badge-best    { background: #0f7a4d; }

/* ----- "Frequently added" BAC water row -----
   One line at every width: the checkbox, thumbnail and price stack are fixed, and
   the name/subtitle column is the only thing that flexes (min-width:0 + ellipsis),
   so nothing wraps and nothing overflows at 320px. */
.pd-bac { margin: 16px 0 0; }
.pd-bac-label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .09em; color: #5a6470; margin-bottom: 8px; }

/* Dark offer panel, shared by the product page and the cart. On the product page the
   row is a <label> wrapping a checkbox; on the cart it holds an Add button instead. */
.pd-bac-row {
    display: flex; align-items: center; gap: 10px;
    background: #1c1c1c; border: none; border-radius: 11px; padding: 12px;
    transition: box-shadow .12s ease;
}
label.pd-bac-row { cursor: pointer; }
.pd-bac-row:has(input:checked) { box-shadow: inset 0 0 0 2px #2E5BE0; }
.pd-bac-row:has(input:focus-visible) { outline: 2px solid var(--accent-blue); outline-offset: 2px; }
.pd-bac-check { flex: 0 0 auto; width: 19px; height: 19px; margin: 0; accent-color: #2E5BE0; cursor: pointer; }
/* Light backing so the vial photography still reads against the dark panel. */
.pd-bac-thumb { flex: 0 0 auto; width: 44px; height: 44px; border-radius: 8px; background: #f0f2f5; object-fit: contain; }
.pd-bac-thumb-empty { display: block; }
.pd-bac-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pd-bac-name { font-size: 15px; font-weight: 500; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pd-bac-sub  { font-size: 12px; color: #b9c0c8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pd-bac-prices { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; line-height: 1.35; }
.pd-bac-was { font-size: 12px; color: #8d959d; }
.pd-bac-now { font-size: 16px; font-weight: 500; color: #4ade80; }

.pd-tier-totals { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-top: 16px; }
.pd-tier-total { font-family: var(--font-mono); font-size: 28px; font-weight: 500; color: #111; }
.pd-tier-each  { font-size: 13px; color: var(--muted); }

/* Height is reserved so the Add to cart button never shifts as the tier changes. */
.pd-tier-saved { margin: 6px 0 0; min-height: 19px; font-size: 13px; line-height: 19px; color: #1a7f4b; }

.pd-add-full { display: flex; width: 100%; max-width: 620px; min-height: 48px; padding: 0 24px; font-size: 16px; }
.pd-buyrow-coa { margin-top: 12px; }

/* Phones: 2x2, same card styling. The row gap is larger than the column gap on
   purpose — the second row's badges hang 8px above their cards and would otherwise
   sit on the bottom edge of the row above. Never one column: four stacked
   full-width cards push Add to cart below the fold. */
@media (max-width: 640px) {
    .pd-pack-row { grid-template-columns: 1fr 1fr; gap: 16px 8px; }
}


@media (max-width: 480px) {
    .pd-tier-total { font-size: 25px; }
    /* The BAC row stays one line: tighten the gaps, keep every box the same size. */
    .pd-bac-row { gap: 8px; padding: 10px; }
    .pd-bac-name { font-size: 14px; }
    .pd-bac-sub  { font-size: 11.5px; }
    .pd-bac-now  { font-size: 15px; }
    .cart-bac-add { padding: 8px 12px; font-size: 13px; }
}


.pd-info-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 22px; }
.pd-info-card { display: flex; gap: 12px; align-items: center; background: #fff; border: 1px solid #ececec; border-radius: 14px; padding: 16px 18px; }
.pd-info-card svg { width: 26px; height: 26px; flex: 0 0 auto; }
.pd-flag { width: 30px; height: 20px; border: 1px solid #ddd; border-radius: 2px; }
.pd-info-title { margin: 0; font-size: 14px; font-weight: 600; color: #111; }
.pd-info-sub { margin: 3px 0 0; font-size: 13px; color: var(--muted); }

.pd-disclaimer { background: var(--accent-blue); color: #fff; border-radius: 16px; padding: 22px 26px; font-size: 15.5px; line-height: 1.6; margin: 32px 0; }
.pd-disclaimer a { color: #fff; text-decoration: underline; }

/* .pd-lower is gone. It was the full width row under the buy box holding the tabs
   and the accordions; both now sit in the left column of .pd-top. */
.pd-tabs-panel { background: #fff; border: 1px solid #ececec; border-radius: 16px; overflow: hidden; }
/* Tab bar. The rule under the bar and the accent bar under the active tab are inset
   positioned hairlines, not border-bottom: a border paints one side of the box and
   is exactly what the house rule forbids. Both are pseudo-elements pinned with
   left/right/bottom, so they span the full width of their box and nothing shifts by
   a pixel when a tab becomes active. */
.pd-tabs { display: flex; position: relative; }
.pd-tabs::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--border); }
.pd-tab { position: relative; background: none; border: none; padding: 16px 22px; font-size: 15px; font-weight: 600; color: var(--muted); }
.pd-tab.is-active { color: #111; }
.pd-tab.is-active::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--accent-blue); z-index: 1; }
.pd-tab-body { padding: 24px 26px; font-size: 15px; line-height: 1.7; color: var(--ink); }
.pd-tab-body.is-hidden { display: none; }
.pd-tab-body p { margin: 0 0 14px; }
.pd-tab-body h1, .pd-tab-body h2, .pd-tab-body h3 { margin: 20px 0 10px; }
.pd-tab-body ul, .pd-tab-body ol { margin: 0 0 14px; padding-left: 22px; }
.pd-specs-h { font-size: 16px; margin: 24px 0 12px; }
.pd-specs { margin: 0; }
.pd-spec-row { display: flex; align-items: baseline; gap: 8px; padding: 8px 0; }
.pd-spec-row dt { color: var(--muted); font-size: 14px; white-space: nowrap; }
.pd-spec-row dd { margin: 0; font-size: 14px; color: #111; text-align: right; flex: 1; }
.pd-spec-row::after { content: ""; }
.pd-spec-row dt { order: 0; }
.pd-spec-row { border-bottom: 1px dotted var(--border-2); }

/* ---- Product specification tables --------------------------------------
   Written into description_general by the chemical-data build script. Distinct
   from .pd-specs / .pd-spec-row above, which are the older admin-entered
   definition list. */
.pd-spec-head,
.pd-spec { width: 100%; border-collapse: collapse; margin: 0 0 14px; }
.pd-spec-head th,
.pd-spec-head td { border-bottom: 1px solid #eef1f4; padding: 8px 0; text-align: left; }
.pd-spec-head th {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: #5a6470;
    width: 130px;
    vertical-align: top;
    font-weight: 600;
}
.pd-spec-head td { font-size: 14px; color: #111418; }

.pd-compound { margin: 0 0 18px; }
.pd-compound h4 { font-size: 15px; font-weight: 600; color: #111418; margin: 0 0 8px; }

.pd-spec th,
.pd-spec td { border-bottom: 1px solid #f2f4f7; padding: 6px 0; text-align: left; }
.pd-spec th { font-size: 12px; color: #5a6470; width: 150px; font-weight: 500; }
.pd-spec td { font-size: 13px; font-family: var(--font-mono); color: #111418; }

/* Shared FDA notice, rendered from the product.fda_notice content block. */
.pd-fda-notice {
    border-top: 1px solid #e4e8ec;
    margin: 20px 0 0;
    padding-top: 14px;
    font-size: 12px;
    color: #5a6470;
    line-height: 1.55;
}
/* ".pd-tab-body p" above sets margin:0 0 14px at the same specificity as a single
   class, so it would win on source order and flatten the 20px gap. Naming the
   container lifts this rule above it without !important. */
.pd-tab-body .pd-fda-notice { margin: 20px 0 0; }

/* Narrow screens: a 130/150px fixed label column plus a long formula would
   otherwise force the page sideways. */
@media (max-width: 560px) {
    .pd-spec-head th, .pd-spec th { width: 108px; }
    .pd-spec td { word-break: break-word; }
}


/* ONE panel with rows inside, not four separate cards with gaps between them.
   It sits beside the tabs panel now and has to read as its twin. */
.pd-accordions { background: #fff; border: 1px solid #ececec; border-radius: 16px; overflow: hidden; }
.pd-acc { position: relative; background: none; border: none; border-radius: 0; }
/* The rule between rows is an inset positioned hairline, not a border-bottom.
   On the row above rather than below, so the last row carries none without
   needing a :last-child exception. */
.pd-acc + .pd-acc::before {
    content: ""; position: absolute; left: 18px; right: 18px; top: 0;
    height: 1px; background: var(--border);
}
.pd-acc-head { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 15px 18px; background: none; border: none; font-size: 14px; font-weight: 600; color: #111; text-align: left; }
.pd-acc-chev { width: 16px; height: 16px; color: var(--muted); transition: transform .2s ease; flex: 0 0 auto; }
.pd-acc-head[aria-expanded="true"] .pd-acc-chev { transform: rotate(180deg); }
.pd-acc-inner { padding: 0 18px 16px; font-size: 13.5px; line-height: 1.65; color: var(--ink); }
.pd-acc-inner p { margin: 0 0 12px; }
.pd-acc-inner p:last-child { margin-bottom: 0; }

/* "You may also like" — reuses the catalog .pcard component */
.pd-related { margin-top: 56px; }
.pd-related-h { text-align: center; font-size: 34px; font-weight: 800; letter-spacing: -0.02em; color: #111; margin: 0 0 30px; }
.pd-related-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 22px; }

/* Admin: variant / spec / image rows.
   Header and rows share ONE column template so every label sits above its field.
   minmax(0,…) + width:100% on the fields stops inputs from blowing out their track
   (which previously squashed the Stock select into a thin vertical sliver). */

.var-row .check { margin-bottom: 0; font-size: 12px; justify-self: center; }

/* Compare-at cell holds the field plus the per-row SALE flag (Compare-at > Price). */


#add-variant, #add-spec { margin-top: 10px; }
.img-row .field input { min-width: 0; width: 100%; box-sizing: border-box; }
.img-row .field { margin-bottom: 0; }

/* Product detail responsive */
@media (max-width: 960px) {
    .pd { padding: 24px 18px 48px; }
    /* Single column below 960. The image and the buy box are the only things in
       .pd-top now, and they are already in the right reading order, so no
       display:contents reordering is needed any more. Everything below .pd-top
       is full width at every size and simply stacks.
       These sit later in the file than the base rules they override, which is
       what makes them win: a media query adds no specificity of its own. */
    .pd-top { grid-template-columns: 1fr; gap: 20px; }
    .pd-trustrow { grid-template-columns: 1fr; gap: 10px; }
    .pd-info { grid-template-columns: 1fr; gap: 12px; }
    .pd-title { font-size: 32px; }
    .pd-main { height: 360px; }
    .pd-related-h { font-size: 27px; }
    .pd-related-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
}


/* =========================================================================
   INVOICE (confirmation embed + download)
   ========================================================================= */
.invoice-block { border: 1px solid var(--border); border-radius: 14px; background: var(--white); padding: 20px 22px; }
.invoice-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.invoice-head .conf-h2 { margin: 0; }
.invoice-dl { white-space: nowrap; }
.invoice-embed { display: block; width: 100%; height: 560px; margin-top: 14px; border: 1px solid var(--border); border-radius: 10px; background: #f4f6f9; }
.invoice-fallback { padding: 24px; text-align: center; color: var(--muted); }
@media (max-width: 640px) {
    .invoice-embed { height: 420px; }
    .invoice-head { justify-content: flex-start; }
}


/* =========================================================================
   PRE-LAUNCH CLEANUP — forgot link + one-time success flash
   ========================================================================= */
.auth-forgot { margin: 12px 0 0; text-align: center; font-size: 13.5px; }
.auth-forgot a { color: var(--accent-blue); text-decoration: none; }
.auth-forgot a:hover { text-decoration: underline; }

.topbar-flash {
    background: #e7f6ee;
    color: #1b7a44;
    border-bottom: 1px solid #cde9d8;
    text-align: center;
    padding: 11px 16px;
    font-size: 14px;
    font-weight: 600;
}

/* =========================================================================
   CONTACT FORM — select, helper text, compliance notice; admin msg detail
   ========================================================================= */
.field select,
.contact-form select {
    width: 100%;
    background-color: var(--white);
    border: 1px solid var(--border-2);
    padding: 12px 40px 12px 14px;
    color: var(--ink);
    font-family: inherit;
    font-size: inherit;
    transition: border-color .15s ease;
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 13px center;
    background-size: 16px;
}
.field select:focus,
.contact-form select:focus { outline: none; border-color: var(--black); }

.field-help { color: var(--muted-2); font-size: 12.5px; margin: 6px 0 0; line-height: 1.45; }

/* Compliance notice: a calm neutral panel (reads as a notice, not an error).
   Full hairline border on all four sides, neutral grey fill, body text colour. */
.contact-disclaimer {
    margin: 22px 0;
    padding: 15px 18px;
    background: var(--off-white);
    border: 1px solid var(--border);
    border-radius: 12px;
    color: var(--ink);
    font-size: 13.5px;
    line-height: 1.55;
}

/* Admin message detail */


/* =========================================================================
   ADMIN — user management column
   ========================================================================= */
.user-manage { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; }
.user-manage .inline-form { margin: 0; }
.user-setpass { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.user-setpass input { width: 128px; padding: 6px 9px; font-size: 12px; border: 1px solid var(--border-2); border-radius: 6px; }

/* Admin email editor: same shape as the password setter, plus room for the hint. */
.user-setemail { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; }
.user-setemail input { width: 190px; padding: 6px 9px; font-size: 12px; border: 1px solid var(--border-2); border-radius: 6px; }
.user-setemail .email-hint { flex: 1 0 100%; margin: 2px 0 0; }

/* Customer says they paid; nobody has verified it yet. A queue marker, not a status. */
.pay-reported-flag {
    display: inline-block; margin-top: 4px; padding: 2px 7px; border-radius: 4px;
    background: #fff4d6; color: #7a5b00; border: 1px solid #f0d38a;
    font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    white-space: nowrap; cursor: help;
}

/* Possible-typo marker on the email cell. Never auto-corrects, just points it out. */


/* ===== QR sales + inventory (v50) ===================================== */
.st-red { background: #fdecec; color: #c5311f; }
.stat-warn { color: #c5311f; }
/* Visually hidden but still announced. clip-path replaces the legacy clip:
   rect() because clip() does NOT stop the element contributing to an ancestor's
   scrollable area — that is what pushed the admin orders page 619px wide when an
   .sr-only label sat inside a horizontally scrolling tab row. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.break { word-break: break-all; }

/* Admin inventory table */

.inv-in { width: 66px; padding: 6px 8px; border: 1px solid var(--border-2); background: #fff; color: var(--ink); }
.inv-qr { margin-top: 8px; }
.inv-qr img { border: 1px solid var(--border); background: #fff; }

/* Salesperson area (mobile-first) */
.sales-body { background: #f4f6f9; }
.sales-topbar { background: #1c1c1c; color: #fff; }
.sales-topbar-inner { max-width: 1240px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; }
.sales-brand { font-weight: 700; font-size: 16px; }
.sales-tag { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #7ea0ff; margin-left: 6px; }
.sales-signout { color: rgba(255,255,255,0.7); font-size: 14px; }
.sales-signout:hover { color: #fff; }
.sales-main { max-width: 1240px; margin: 0 auto; padding: 18px; }
.sales-head { margin: 6px 0 16px; }
.sales-h1 { font-size: 22px; margin: 0 0 4px; }
.sales-list { display: grid; gap: 14px; }
/* Salespeople may use a laptop: single column on phones, two columns >900px,
   three >1400px. Cards stay full-width below 768px as before. */
@media (min-width: 900px)  { .sales-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

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

.sales-card { background: #fff; border: 1px solid var(--border); border-radius: 14px; padding: 16px; }
.sales-card-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.sales-prod { font-weight: 600; margin: 0; }
.sales-size { color: var(--muted); margin: 2px 0 0; font-size: 13px; }
.sales-price { font-size: 18px; font-weight: 700; }
.sales-card-meta { margin: 10px 0; }
.sales-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.sales-qr { margin-top: 12px; text-align: center; }
.sales-qr img { width: 180px; height: 180px; border: 1px solid var(--border); background: #fff; }
.sales-confirm { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 14px; }
.sales-confirm-row { display: flex; gap: 10px; margin-bottom: 10px; }
.sales-qty { width: 80px; padding: 12px; border: 1px solid var(--border-2); text-align: center; }
.sales-method { flex: 1; padding: 12px; border: 1px solid var(--border-2); background: #fff; }
.sales-complete { width: 100%; }

/* Public pay page (matte black, mobile-first) */
.pay-page { background: #0e0e10; color: #fff; }
.pay-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.pay-card { position: relative; width: 100%; max-width: 420px; background: #1c1c1c; border: 1px solid rgba(255,255,255,0.12);
    border-radius: 15px; padding: 28px 24px; overflow: hidden; box-shadow: 0 24px 60px rgba(0,0,0,0.5); }
.pay-glow { position: absolute; top: -80px; right: -80px; width: 220px; height: 220px; pointer-events: none;
    background: radial-gradient(circle, rgba(46,91,224,0.5), transparent 66%); }
.pay-eyebrow { position: relative; font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 2px;
    text-transform: uppercase; color: #7ea0ff; margin: 0 0 10px; }
.pay-product { position: relative; font-size: 26px; font-weight: 800; margin: 0 0 4px; line-height: 1.2; }
.pay-size { position: relative; color: rgba(255,255,255,0.6); margin: 0 0 18px; }
.pay-price-row { position: relative; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.pay-unit { font-size: 19px; font-weight: 700; }
.pay-each { color: rgba(255,255,255,0.5); font-size: 13px; font-weight: 400; }
.pay-qty { position: relative; display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.pay-qty-label { color: rgba(255,255,255,0.7); font-size: 14px; }
.pay-stepper { display: flex; align-items: center; gap: 4px; }
.pay-step { width: 42px; height: 42px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.06);
    color: #fff; border-radius: 10px; font-size: 20px; cursor: pointer; }
.pay-step:hover { background: rgba(255,255,255,0.12); }
.pay-qty-input { width: 54px; height: 42px; text-align: center; background: transparent; border: none; color: #fff; font-size: 18px; font-weight: 700; }
.pay-total-row { position: relative; display: flex; justify-content: space-between; align-items: center; font-size: 18px;
    font-weight: 700; padding: 14px 0; border-top: 1px solid rgba(255,255,255,0.12); margin-bottom: 18px; }
.pay-total { color: #7ea0ff; }
.pay-methods { position: relative; display: grid; gap: 10px; }
.pay-btn { display: block; text-align: center; padding: 15px; border-radius: 11px; font-size: 16px; font-weight: 700; cursor: pointer; border: none; font-family: inherit; }
.pay-venmo { background: #3d95ce; color: #fff; }
.pay-venmo:hover { background: #3484ba; text-decoration: none; }
.pay-zelle { background: #6d1ed4; color: #fff; }
.pay-zelle:hover { background: #5f19bb; }
.pay-zelle-panel { position: relative; margin-top: 14px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.12); border-radius: 11px; padding: 16px; }
.pay-zelle-h { font-weight: 700; margin: 0 0 10px; }
.pay-zelle-dl { margin: 0 0 12px; }
.pay-zelle-dl > div { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 14px; }
.pay-zelle-dl dt { color: rgba(255,255,255,0.55); }
.pay-zelle-dl dd { margin: 0; text-align: right; color: #fff; }
.pay-copy { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.18); color: #fff; border-radius: 9px; padding: 10px 14px; font-size: 13px; cursor: pointer; }
.pay-foot, .pay-oos { position: relative; color: rgba(255,255,255,0.55); font-size: 13px; margin-top: 16px; }
.pay-page .muted { color: rgba(255,255,255,0.55); }

/* =========================================================================
   Inventory manager rework + internal-name chip (admin/inventory + /sales)
   Monochrome, #2E5BE0 accent, #1c1c1c save bar, Inter labels, mono numbers.
   ========================================================================= */

.inv-search-row, .sales-search-row { margin: 0 0 14px; }
.inv-search, .sales-search {
    width: 100%; max-width: 420px; padding: 9px 12px;
    border: 1px solid var(--border-2); background: #fff; color: var(--ink);
    font-family: var(--font-ui); font-size: 13.5px;
}
.sales-search { max-width: none; }
.inv-search:focus, .sales-search:focus { outline: none; border-color: var(--accent-blue); }

.inv-table td { vertical-align: middle; }
.inv-prod strong { font-family: var(--font-ui); font-weight: 600; }

/* Internal-name chip — shared by admin inventory and the sales page. */
.iname-chip, .iname-chip-static {
    display: inline-block; margin-left: 6px; padding: 2px 8px;
    font-family: var(--font-mono); font-size: 11px; font-weight: 500; line-height: 1.5;
    color: #fff; background: var(--accent-blue); border: none; border-radius: 3px;
    vertical-align: middle; max-width: 220px; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; cursor: pointer;
}
.iname-chip:hover { background: #204bc9; }
.iname-chip-static { cursor: default; }

/* Stock stepper (one field, minus/plus either side). */

.inv-dot {
    display: inline-block; margin-left: 8px; font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em; padding: 3px 7px; border-radius: 999px;
    vertical-align: middle;
}

/* Icon buttons (QR / threshold edit). */


/* Detail rows: QR panel + threshold editor. */

.qr-panel { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.qr-panel img { border: 1px solid var(--border); background: #fff; }
.qr-panel-body { display: flex; flex-direction: column; gap: 10px; }
.qr-url { font-size: 12px; color: var(--muted); max-width: 360px; }
.qr-panel-actions { display: flex; gap: 8px; }

/* Changed-row wash (light #2E5BE0). */


/* Sticky save bar — only shown by JS when something changed. */


/* =========================================================================
   Responsive: inventory + sales QR thumbnail, and mobile cards (<768px).
   No horizontal scroll at any width down to 320px.
   ========================================================================= */

/* Shared QR thumbnail (inventory desktop, inventory mobile, sales). */
.qr-wrap { display: inline-block; vertical-align: top; }
.qr-thumb {
    display: block; width: 46px; height: 46px; max-width: 100%;
    border: 1px solid var(--border); background: #fff; cursor: pointer;
}
.qr-thumb-url { display: none; margin: 6px 0 0; font-size: 11px; color: var(--muted); word-break: break-all; }

/* Desktop-first visibility helpers. */
.inv-mobile-only { display: none; }
.inv-cost-mobile, .inv-stockline { display: none; }
.inv-actions .qr-wrap { margin-right: 8px; }

/* Stat cards: two-up on small screens instead of one very tall column. */
@media (max-width: 767px) {
    .stat-grid { grid-template-columns: 1fr 1fr; }
    .stat { padding: 14px; }
    .stat-num { font-size: 22px; }
}


/* ---- Sales cards: QR always visible on the right, tap to expand. ---- */
.sales-card-top { flex-wrap: wrap; }
.sales-card-main { min-width: 0; flex: 1 1 auto; }
.sales-price { margin-top: 6px; font-weight: 600; }
.sales-size { overflow-wrap: anywhere; }
.sales-card .qr-thumb { width: 74px; height: 74px; }
.sales-card .qr-wrap { flex: 0 0 auto; text-align: center; }
.sales-card .qr-wrap.qr-open { flex-basis: 100%; }
.sales-card .qr-wrap.qr-open .qr-thumb { width: 220px; max-width: 100%; height: auto; margin: 8px auto 0; }
.sales-card .qr-wrap.qr-open .qr-thumb-url { display: block; text-align: center; }
.sales-actions .btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* ---- Inventory: table becomes one card per variant below 768px. ---- */
@media (max-width: 767px) {
    .inv-mobile-only { display: block; }
    .inv-desktop-only { display: none !important; }

    /* Drop the scrolling shell for the INVENTORY table only (its content reflows to
       fit). The in-person sales-log table keeps its own .table-wrap scroll so its
       columns never push the page wide. */
    .inv-table-wrap { overflow: visible; border: none; background: transparent; }
    .inv-table, .inv-table > tbody { display: block; width: 100%; }
    .inv-table > thead { display: none; }

    /* Product name + alias (leave room for the QR thumbnail top-right). */
    .inv-prod { padding-right: 86px; }
    .inv-prod strong { font-size: 16px; }

    /* Size wraps, never widens the card. */
    .inv-size-cell { margin-top: 3px; color: var(--muted); overflow-wrap: anywhere; }

    /* Price line, then the combined muted "cost $X · NN%" line. */
    
    .inv-cost-desktop, .inv-margin-cell { display: none; }
    .inv-cost-cell { margin-top: 4px; }
    .inv-cost-mobile { display: inline; font-size: 12px; color: var(--muted); }

    /* Stepper: 44px touch targets, input flexes and never overflows. */
    .inv-stock-cell { margin-top: 12px; }
    .inv-dot-desktop { display: none; }
    .inv-stockline { display: block; margin-top: 6px; font-size: 12px; color: var(--muted); }
    .inv-stockline.is-out { color: #c5311f; }
    .inv-stockline.is-low { color: #b7791f; }

    /* Actions: QR pinned top-right; Share/Edit full-width buttons below. */
    .inv-actions { margin-top: 12px; }
    .inv-actions .qr-wrap { position: absolute; top: 14px; right: 14px; width: 74px; margin: 0; }
    .inv-actions .qr-thumb { width: 74px; height: 74px; }
    .inv-cardbtns { display: flex; gap: 8px; }
    .inv-cardbtns .btn { flex: 1 1 0; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

    /* Expanded QR: full-width block, ~220px, pay URL beneath. */
    .inv-row.qr-open { padding-right: 14px; }
    .inv-row.qr-open .inv-prod { padding-right: 0; }
    .inv-row.qr-open .inv-actions .qr-wrap { position: static; width: 100%; margin-top: 12px; text-align: center; }
    .inv-row.qr-open .inv-actions .qr-thumb { width: 220px; max-width: 100%; height: auto; margin: 0 auto; }
    .inv-row.qr-open .qr-thumb-url { display: block; text-align: center; }

    /* QR detail row unused on mobile (QR is inline). */
    

    /* Edit panel: attached card-style block, stacked fields. */
    
    .inv-edit-print { margin-top: 12px; }
    .inv-edit-print .btn { min-height: 44px; }
}


/* =========================================================================
   Pricing calculator (/admin/pricing). Two stacked cards, single column,
   ~420px, centered. Monochrome + #2E5BE0 accent, Inter labels, mono numbers.
   ========================================================================= */

/* Below 768px: single ~420px centered column (unchanged). */

/* 768–900px: single column at full content width. */

/* Above 900px: two equal-width, equal-height columns (competitor | pack). */

.pricing-btnrow .btn { flex: 1 1 0; min-height: 44px; }

/* =========================================================================
   Login redesign — single centered column on the dark bg, animated blue glows
   behind the WHOLE page, translucent card, Google button. All under .login-page
   so nothing here touches other screens. (The old .login-split/.login-visual
   rules go unused.)
   ========================================================================= */

.login-page {
    position: relative;
    display: flex;
    min-height: 100vh;
    overflow: hidden;              /* clip the off-canvas glow blobs -> no scroll */
    background: #0d0d0f;
}
/* Reuse the existing .login-glow-1/2 blobs, but sit them behind the whole page. */
.login-page .login-glow { position: absolute; z-index: 0; }

.login-single {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 400px;
    margin: auto;
    padding: 48px 20px;
    display: flex;
    flex-direction: column;
    gap: 22px;
}
.login-single .login-logo { align-self: center; display: inline-block; }
.login-single .login-logo-img { height: 62px; width: auto; }
.login-single .logo-wordmark { color: #fff; font-size: 26px; letter-spacing: .01em; }

/* Translucent card so the glows show through. */
.login-page .auth-card {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: none;
    background: rgba(18, 21, 29, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 18px;
    padding: 26px 24px;
    -webkit-backdrop-filter: blur(16px) saturate(140%);
    backdrop-filter: blur(16px) saturate(140%);
}

/* Fields on the dark card (the old dark styles were scoped to .login-auth). */
.login-page .field { margin-bottom: 16px; }
.login-page .field label { display: block; font-size: 14px; font-weight: 500; color: rgba(255, 255, 255, 0.80); margin-bottom: 8px; }
.login-page .field label .hint { color: rgba(255, 255, 255, 0.38); font-weight: 400; }
.login-page .input-icon input {
    width: 100%;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 12px;
    padding: 13px 44px;
    color: #fff;
    transition: border-color .15s ease, background .15s ease;
}
.login-page .input-icon input::placeholder { color: rgba(255, 255, 255, 0.30); }
.login-page .input-icon input:focus { outline: none; border-color: rgba(46, 91, 224, 0.70); background: rgba(255, 255, 255, 0.06); }
.login-page .field-error { color: #f0a58f; }
.login-single :focus-visible { outline: 2px solid rgba(46, 91, 224, 0.75); outline-offset: 2px; }

/* Google button (white, brand G). Disabled state on the Create-account tab. */
.login-page .btn-google {
    display: flex; align-items: center; justify-content: center; gap: 12px;
    width: 100%;
    background: #fff; color: #1f2328;
    border: 1px solid rgba(255, 255, 255, 0.85);
    border-radius: 12px;
    padding: 13px 16px;
    font-size: 15.5px; font-weight: 600;
    cursor: pointer; text-decoration: none;
    transition: background .15s ease, opacity .15s ease;
}
.login-page .btn-google:hover { background: #f2f3f5; text-decoration: none; }
.login-page .btn-google[disabled], .login-page .btn-google:disabled { opacity: 0.45; cursor: not-allowed; }
.login-page .google-g { width: 18px; height: 18px; flex: 0 0 auto; }
.login-page .google-hint { margin: 9px 2px 0; font-size: 12px; line-height: 1.45; color: rgba(255, 255, 255, 0.42); text-align: center; }

.login-page .or-divider { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: rgba(255, 255, 255, 0.35); font-size: 12px; }
.login-page .or-divider::before, .login-page .or-divider::after { content: ""; flex: 1; height: 1px; background: rgba(255, 255, 255, 0.10); }

/* Agreements on the dark card. */
.login-page .disclaimers {
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 12px;
    padding: 16px;
    margin: 4px 0 18px;
    background: rgba(255, 255, 255, 0.02);
}
.login-page .disclaimers legend {
    font-family: var(--font-mono); font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
    color: rgba(255, 255, 255, 0.40); padding: 0; margin-bottom: 6px;
}
.login-page .check { display: flex; gap: 11px; align-items: flex-start; font-size: 13.5px; color: rgba(255, 255, 255, 0.70); line-height: 1.5; margin-bottom: 11px; }
.login-page .check:last-of-type { margin-bottom: 0; }
.login-page .check input { width: 18px; height: 18px; margin-top: 1px; flex: 0 0 auto; accent-color: var(--accent-blue); }
.login-page .req { font-family: var(--font-mono); font-size: 10px; color: rgba(255, 255, 255, 0.34); text-transform: uppercase; margin-left: 4px; }

/* Optional agreements: dimmer full-width row + a small "Optional" tag after the text. */
.login-page .ag-optional { color: rgba(255, 255, 255, 0.48); }
.login-page .ag-optlabel {
    font-style: normal; margin-left: 6px; font-family: var(--font-mono);
    font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: rgba(255, 255, 255, 0.34);
}

.login-page .auth-forgot { text-align: center; margin: 14px 0 0; font-size: 13.5px; }
.login-page .auth-forgot a { color: rgba(255, 255, 255, 0.60); text-decoration: underline; text-underline-offset: 2px; }
.login-page .auth-forgot a:hover { color: #fff; }

.login-page .login-trust {
    margin: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
    text-align: center; font-size: 12.5px; color: rgba(255, 255, 255, 0.42);
}
.login-page .login-trust-dot { color: rgba(255, 255, 255, 0.22); }

/* Mobile: full width, taller touch targets, optional agreements collapse. */
@media (max-width: 640px) {
    .login-single { max-width: none; padding: 32px 18px; gap: 18px; }
    .login-single .login-logo-img { height: 52px; }
    .login-page .auth-card { padding: 22px 18px; border-radius: 16px; }
    .login-page .input-icon input { padding-top: 14px; padding-bottom: 14px; min-height: 48px; }
    .login-page .btn-auth { min-height: 50px; }
    .login-page .btn-google { min-height: 48px; }
}


/* Desktop card width. The column carries 20px side padding, so the card lands at
   max-width - 40px: 560 -> ~520px, 660 -> ~620px. (Below 640px stays full width.) */
@media (min-width: 641px) {
    .login-single { max-width: 560px; }
    .login-single .login-logo-img { height: 76px; }
    .login-single .logo-wordmark { font-size: 30px; }
}

@media (min-width: 1025px) {
    .login-single { max-width: 660px; }
    .login-single .login-logo-img { height: 90px; }
    .login-single .logo-wordmark { font-size: 34px; }
    .login-page .auth-card { padding: 32px; }
    /* Agreements stay a single full-width column at every width (no two-column grid). */
}


/* =========================================================================
   Announcement bar (signed-in storefront) + cart lock UI. #2E5BE0 accent,
   Inter labels, JetBrains Mono numbers. Nothing here affects login/pay/admin
   layout beyond the opt-in classes.
   ========================================================================= */

.announce { width: 100%; height: 38px; overflow: hidden; display: flex; align-items: center; background: #2E5BE0; color: #fff; }
.announce-still { width: 100%; text-align: center; padding: 0 16px; }
.announce-track { display: flex; align-items: center; white-space: nowrap; will-change: transform; animation-name: announce-scroll; animation-timing-function: linear; animation-iteration-count: infinite; }
.announce-set { display: flex; align-items: center; flex: 0 0 auto; }
.announce-msg { font-size: 13.5px; font-weight: 500; line-height: 1; white-space: nowrap; padding: 0 28px; }
@keyframes announce-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Reduced motion: no scroll. Drop the duplicate set and centre the messages. */
@media (prefers-reduced-motion: reduce) {
    .announce-track { animation: none !important; transform: none !important; flex-wrap: wrap; justify-content: center; }
    .announce-set + .announce-set { display: none; }
}

@media (max-width: 640px) {
    .announce { height: 34px; }
    .announce-msg { font-size: 12px; padding: 0 18px; }
}


/* Stick the bar + nav together as one unit. The storefront bar (NOT the admin
   preview) pins to the very top; the nav — already position:sticky top:0 — is
   offset down by the bar's exact height so it sits flush beneath it, no gap/overlap.
   The `.announce + .topbar` offset only applies when the bar is actually present
   (adjacent sibling), so with the bar off the nav keeps top:0. z-index sits at/above
   the nav's 50 so page content never scrolls over either. */
.announce:not(.ann-preview) { position: sticky; top: 0; z-index: 51; }
.announce:not(.ann-preview) + .topbar { top: 38px; }

/* Anchor jumps (e.g. #catalog) clear the sticky nav, plus the bar when it's shown,
   so nothing lands hidden behind the taller sticky block. */
html { scroll-padding-top: 96px; }
html:has(.announce:not(.ann-preview)) { scroll-padding-top: 134px; }

@media (max-width: 640px) {
    .announce:not(.ann-preview) + .topbar { top: 34px; }
    html { scroll-padding-top: 116px; }
    html:has(.announce:not(.ann-preview)) { scroll-padding-top: 150px; }
}


/* Cart-lock inline note (fades in/out beside the Add-to-cart button). */
.cart-lock-note { display: inline-block; margin-left: 10px; font-size: 12.5px; font-weight: 600; color: var(--accent-blue); opacity: 0; transition: opacity .25s ease; }
.cart-lock-note.is-show { opacity: 1; }

/* Catalog notice (e.g. the cart-lock redirect message). */
.catalog-flash { max-width: 760px; margin: 16px auto 0; padding: 12px 18px; background: var(--off-white); border: 1px solid var(--border); border-radius: 10px; color: var(--ink); font-size: 14px; text-align: center; }

/* Settings admin: store availability + announcement editor. */

/* Dim-only: visually muted but fully interactive (e.g. the lock message field). */


/* Quantity tier rows (Settings -> Store): quantity | percent | remove. */

.ann-preview.is-empty .announce-track { padding: 0 14px; font-size: 12.5px; }

/* =========================================================================
   Settings: tabbed layout + search + save bar. Presentation only; the single
   form and every field are unchanged. Monochrome, #2E5BE0 active-tab underline.
   ========================================================================= */


.settings-tab.is-active { color: var(--ink); border-bottom-color: var(--accent-blue); font-weight: 600; }
.settings-panel.is-active { display: block; }

/* Search mode: reveal every panel, drop the chrome, show only matches + a tab badge. */

.search-tab-badge { display: none; }

@media (max-width: 767px) {
    .field-row { grid-template-columns: 1fr; }
}


/* =========================================================================
   Responsive admin tables. Desktop: clip long free-text columns (email,
   message) so the table fits its container — full text stays in the title
   attribute. Below 768px: reflow to cards (same treatment as /admin/inventory)
   via a shared .admin-cards class so all six tables behave identically.
   ========================================================================= */


/* Slightly tighter cell padding on the wide reflow tables so, with the clipped
   columns, they fit their container on desktop (the .table-wrap keeps a contained
   scroll as a fallback at the narrowest widths — the page body never scrolls). */


/* 900, not 767: the desktop grid starts at 901px, so anything below that must be
   a card. The old 767 left 768-900px with neither layout — a plain, overflowing
   table on every admin list. */
@media (max-width: 900px) {
    /* Cells with no label, or block cells (actions, dropdowns, rich content) go full-width. */
    
    /* In a card the clipped columns show in full (wrap, no ellipsis). An email or
       URL is one unbroken token, so it also needs permission to break mid-string —
       without that it pushed past the viewport at 320px. min-width:0 lets the flex
       item shrink below its content width in the first place. */
    
    /* Action controls stay reachable: lay them out, never clipped off an edge. */
    
}


@media (max-width: 900px) {
    /* Users: keep the multi-action Manage stack and the Tax form full-width so
       every control (Make admin, Deactivate, Send reset, Set password, Delete)
       stays reachable — never squeezed into a "label: value" row. */
    
}


@media (min-width: 901px) {
    /* Pin the action column to the right so Delete (messages), the status dropdown
       (orders), and the whole Manage column (users) stay visible even if a wide table
       scrolls inside its own container on a narrow desktop. The page never scrolls. */
    
}


/* =========================================================================
   Content editor: group sidebar + fields panel + search. Presentation only;
   the single form and every field are unchanged. #2E5BE0 accent for the
   active group; JetBrains Mono for keys/counts.
   ========================================================================= */


/* Solid fill, no single-side border. Same treatment as the legal page sidebar:
   the whole item carries the state rather than a lone left edge. */

.content-group.is-active { color: #fff; font-weight: 600; background: var(--accent-blue); }
.content-group.is-active:hover { color: #fff; background: var(--accent-blue); }
/* The count chip has to invert too, or it stays grey-on-blue and disappears. */
.content-group.is-active .content-group-count { background: rgba(255, 255, 255, 0.18); border-color: transparent; color: #fff; }
.content-group.is-active .content-group-count { color: var(--accent-blue); border-color: rgba(46, 91, 224, 0.30); background: #fff; }
.content-panel.is-active { display: block; }

/* Search mode: reveal all panels, filter to matches, tag each with its group. */

.content-searching .field.search-hidden { display: none; }
.search-group-badge { display: none; }

@media (max-width: 900px) {
    /* Scrolling pill row. The fill carries the state here too, so the mobile
       fallback no longer swaps one single-side border for another. */
    
    .content-group.is-active { background: var(--accent-blue); color: #fff; }
}


/* ===== Certificate of Analysis (product page) ===== */
/* Widen the buy row so the secondary "View COA" button sits beside Add to cart. */
.pd-buyrow { max-width: 620px; }

.pd-coa-btn {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center; gap: 9px;
    min-height: 56px; padding: 0 22px;
    background: #fff; color: var(--ink);
    border: 1px solid var(--border-2); border-radius: 999px;
    font-size: 15px; font-weight: 600; font-family: inherit;
    cursor: pointer; transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.pd-coa-btn svg { width: 19px; height: 19px; color: var(--accent-blue); flex: 0 0 auto; }
.pd-coa-btn:hover { border-color: var(--accent-blue); color: var(--accent-blue); }
.pd-coa-btn.is-open { border-color: var(--accent-blue); background: rgba(46, 91, 224, .05); }

.pd-coa-panel {
    margin-top: 16px; max-width: 620px;
    border: 1px solid var(--border); border-radius: 14px;
    background: #fafafa; padding: 18px 20px;
}
.pd-coa-title { margin: 0 0 12px; font-size: 15px; font-weight: 700; color: var(--ink); }
.pd-coa-meta { display: flex; flex-wrap: wrap; gap: 8px 26px; margin-bottom: 14px; }
.pd-coa-k { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted-2); margin-bottom: 2px; font-family: 'Inter', system-ui, sans-serif; }
.pd-coa-lot .mono, .pd-coa-tested .mono { font-size: 14px; color: var(--ink); }
.pd-coa-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.pd-coa-open, .pd-coa-dl, .pd-coa-contact {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 40px; padding: 0 20px; border-radius: 999px;
    font-size: 14px; font-weight: 600; text-decoration: none;
}
.pd-coa-open, .pd-coa-contact { background: var(--accent-blue); color: #fff; }
.pd-coa-open:hover, .pd-coa-contact:hover { background: #264fc4; }
.pd-coa-dl { background: #fff; color: var(--ink); border: 1px solid var(--border-2); }
.pd-coa-dl:hover { border-color: var(--accent-blue); color: var(--accent-blue); }

.pd-coa-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pd-coa-item { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px; padding: 12px 0 0; border-top: 1px solid var(--border); }
.pd-coa-item:first-child { border-top: none; padding-top: 0; }
.pd-coa-item .pd-coa-meta { margin-bottom: 0; }

.pd-coa-soon .pd-coa-title { margin-bottom: 6px; }
.pd-coa-soon-body { margin: 0 0 14px; font-size: 14px; color: var(--muted); line-height: 1.5; }

@media (max-width: 640px) {
    .pd-coa-btn { flex: 1 1 100%; }
    .pd-coa-panel { max-width: 100%; }
}


/* Admin COA panel bits (dark admin theme handles the rest). */


/* ===== Checkout account step (login page in checkout mode) ===== */
/* Account step width: full below 640, 560 up to 1024, 720 above 1024. The card
   and all three chrome blocks (progress row, summary, heading) share one width. */
.login-body.checkout-account .login-single,
.login-body.checkout-account .checkout-steps,
.login-body.checkout-account .checkout-summary,
.login-body.checkout-account .checkout-acct-head { max-width: none; }

.login-single .checkout-steps {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    list-style: none; margin: 0 auto 18px; padding: 0; width: 100%; max-width: none;
    font-size: 12.5px; color: rgba(255, 255, 255, 0.5);
}
.checkout-steps li { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.checkout-steps li:not(:last-child)::after { content: ""; width: 18px; height: 1px; background: rgba(255, 255, 255, 0.18); margin-left: 6px; }
.checkout-step-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.25); flex: 0 0 auto; }
.checkout-steps li.is-done { color: rgba(255, 255, 255, 0.7); }
.checkout-steps li.is-done .checkout-step-dot { background: #2E5BE0; }
.checkout-steps li.is-current { color: #fff; font-weight: 600; }
.checkout-steps li.is-current .checkout-step-dot { background: #2E5BE0; box-shadow: 0 0 0 4px rgba(46, 91, 224, 0.25); }

.login-single .checkout-summary {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    width: 100%; max-width: none; margin: 0 auto 16px; padding: 12px 16px;
    background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 10px; font-size: 13.5px; color: rgba(255, 255, 255, 0.8);
}
.checkout-summary-items { font-weight: 600; color: #fff; }
.checkout-summary-label { color: rgba(255, 255, 255, 0.55); margin-right: 6px; }
.checkout-summary-total .mono { color: #fff; font-weight: 600; }

.login-single .checkout-acct-head { width: 100%; max-width: none; margin: 0 auto 14px; text-align: center; }
.checkout-acct-h1 { margin: 0 0 6px; font-size: 20px; font-weight: 800; color: #fff; line-height: 1.25; }
.checkout-acct-sub { margin: 0; font-size: 13.5px; line-height: 1.5; color: rgba(255, 255, 255, 0.6); }

@media (max-width: 420px) {
    .login-single .checkout-steps { font-size: 11.5px; gap: 4px; }
    .checkout-steps li:not(:last-child)::after { width: 12px; margin-left: 4px; }
    .login-single .checkout-summary { flex-direction: column; align-items: flex-start; gap: 4px; }
}


/* 640–1024px: card + chrome at 560px. */
@media (min-width: 640px) {
    .login-body.checkout-account .login-single,
    .login-body.checkout-account .checkout-steps,
    .login-body.checkout-account .checkout-summary,
    .login-body.checkout-account .checkout-acct-head { max-width: 560px; }
}

/* Above 1024px: 720px, with roomier card padding so it does not look empty. */
@media (min-width: 1025px) {
    .login-body.checkout-account .login-single,
    .login-body.checkout-account .checkout-steps,
    .login-body.checkout-account .checkout-summary,
    .login-body.checkout-account .checkout-acct-head { max-width: 720px; }
    .login-body.checkout-account .auth-card { padding: 32px; }
}


/* =========================================================================
   Admin — product reorder arrows (light admin shell)
   ========================================================================= */

.reorder .inline-form { margin: 0; }

/* =========================================================================
   Dark catalog (/ and /home only). Scoped to body.catalog-dark so product
   detail, cart, checkout, account, contact, and legal stay light.
   ========================================================================= */
body.catalog-dark { background: #070910; color: #c7cbd2; }

/* Blue glow layer behind all content (upper-left, right third-down, lower-centre). */
.catalog-dark .catalog-bg { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
/* Static, unfiltered gradients — same treatment the mnav glows got.
   These three blobs used to carry filter: blur(90px) AND a 19-24s drift each, on a
   position:fixed layer covering the whole viewport behind the entire catalog. That
   is three full-layer blur passes recomposited every frame, for the life of the
   page. The radial-gradient is already soft on its own, so the stops are pulled in
   and the alphas dropped to match what the blur was producing, and nothing moves. */
.catalog-dark .cat-glow { position: absolute; border-radius: 50%; pointer-events: none; }
.catalog-dark .cat-glow-1 {
    width: 620px; height: 620px; top: -150px; left: -130px;
    background: radial-gradient(circle, rgba(46, 91, 224, 0.30) 0%, rgba(46, 91, 224, 0.14) 40%, transparent 74%);
}
.catalog-dark .cat-glow-2 {
    width: 540px; height: 540px; top: 32%; right: -160px;
    background: radial-gradient(circle, rgba(46, 91, 224, 0.22) 0%, rgba(46, 91, 224, 0.10) 42%, transparent 76%);
}
.catalog-dark .cat-glow-3 {
    width: 660px; height: 660px; bottom: -200px; left: 50%; margin-left: -330px;
    background: radial-gradient(circle, rgba(46, 91, 224, 0.18) 0%, rgba(46, 91, 224, 0.08) 42%, transparent 76%);
}

/* Hero: transparent so the page bg + glows show; its own glows give way to the 3 page glows. */
.catalog-dark .hero { background: transparent; }

/* The catalog <main> normally paints a light #fafafa fill that would cover the
   dark body + glows. Make it transparent so #070910 and the glows show through.
   (.catalog-dark .catalog = 0,2,0 beats the base .catalog = 0,1,0 — no !important.) */
.catalog-dark .catalog { background: transparent; }

/* The header band drops away here so the floating glass bar sits directly on the
   dark page background + glows. Light storefront pages keep the dark band. */
.catalog-dark .topbar { background: transparent; }

/* Filter pills + count. */
.catalog-dark .catalog-filter { background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.12); color: #a9b0bc; }
.catalog-dark .catalog-filter:hover { border-color: rgba(255, 255, 255, 0.28); color: #fff; }
.catalog-dark .catalog-filter.is-active { background: #fff; color: #111; border-color: #fff; }
.catalog-dark .catalog-count { color: #7d8494; }
.catalog-dark .empty { color: #a9b0bc; }
.catalog-dark .empty .btn-outline { color: #fff; border-color: rgba(255, 255, 255, 0.30); }
.catalog-dark .catalog-flash { background: rgba(255, 255, 255, 0.06); color: #fff; border-color: rgba(255, 255, 255, 0.12); }

/* Cards on dark. Flat translucent fill + a hairline rim — no backdrop-filter,
   deliberately (it was a measured cost on iPhone and is not coming back). */
.catalog-dark .pcard {
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 11px;
    box-shadow: none;
}
.catalog-dark .pcard:hover { border-color: rgba(255, 255, 255, 0.16); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.3); }

/* Card contents on dark. */
.catalog-dark .pcard-media { background: rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
.catalog-dark .pcard-name-link,
.catalog-dark .pcard-name-link:hover { color: #fff; }
.catalog-dark .pcard-amount { color: #fff; }
.catalog-dark .pcard-compare { color: #8b919c; }
.catalog-dark .pcard-size {
    color: #c7cbd2;
    background-color: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.11);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23c7cbd2' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
.catalog-dark .pcard-size:focus { border-color: var(--accent-blue); }
.catalog-dark .pcard-add { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22); }
/* Both badges must read on the dark cards: OOS pill gains a light rim. */
.catalog-dark .pcard-oos-badge { box-shadow: 0 3px 12px rgba(0, 0, 0, 0.24), inset 0 0 0 1px rgba(255, 255, 255, 0.18); }

/* =========================================================================
   Catalog card motion — "Instrument" handoff. Catalog grid only
   (.catalog-dark .pgrid), so the shared card partial renders unchanged in
   the product page's "You may also like" row.

   Every animated property here is transform or opacity. No filter, no
   backdrop-filter, no animated box-shadow, no width/height/letter-spacing —
   nothing that repaints the whole card or triggers layout, which is what a
   39-card grid cannot afford on iPhone. The one exception is the select's
   border-color (paint-only, no layout); noted at that rule.

   Driven by a single --on: 0|1 on the card root, per the handoff's
   implementation pattern: each child computes its own transform/opacity
   from the inherited value, so hover is one property write on one element.
   --k is the reduced-motion kill switch: it zeroes movement while leaving
   the state changes intact.
   ========================================================================= */
.pcard-fx { display: none; }   /* off everywhere except the catalog grid */

.catalog-dark .pgrid .pcard {
    --on: 0;
    --k: 1;
    --fx-acc: 46, 91, 224;     /* brand blue #2E5BE0, not the Nocturne purple */
    position: relative;        /* anchors the accent rim below */
    transform: translateY(calc(var(--on) * var(--k) * -4px));
    transition: transform 170ms cubic-bezier(.2, .9, .3, 1);
}
/* Accent rim as a faded overlay rather than a border-color transition: same
   result as color-mix(accent 45%, border) at opacity 1, but it animates
   opacity instead of paint-recolouring the card edge. */
.catalog-dark .pgrid .pcard::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 4;
    border: 1px solid rgba(var(--fx-acc), 0.45);
    border-radius: inherit;
    opacity: var(--on);
    pointer-events: none;
    transition: opacity 160ms linear;
}
/* The handoff specifies no elevation ("the card lift is 4px of translate and
   a border brightening, no shadow"), which also drops the animated
   box-shadow the base card had. */
.catalog-dark .pgrid .pcard:hover { box-shadow: none; border-color: rgba(255, 255, 255, 0.08); }

/* Hover only where hover really exists. Touch gets .is-active from app.js,
   so a tap can never leave a card stuck in the hover state. */
@media (hover: hover) and (pointer: fine) {
    .catalog-dark .pgrid .pcard:hover { --on: 1; }
}

.catalog-dark .pgrid .pcard.is-active { --on: 1; }

/* Image area layers. The whole overlay is inert; it sits above the vial but
   below the quick-add (z2), SALE (z2) and out-of-stock (z3) badges. */
.catalog-dark .pgrid .pcard-fx {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    overflow: hidden;
}
.catalog-dark .pgrid .pcard-fx-bloom {
    position: absolute;
    inset: 0;
    background: radial-gradient(70% 55% at 50% 45%, rgba(var(--fx-acc), 0.15), transparent 70%);
    opacity: var(--on);
    transition: opacity 200ms linear;
}
/* Full-size layer, not a 2px element, so the sweep is a plain translateY(100%)
   and works at any card height. 2px accent rule + 22px glow beneath it. */
.catalog-dark .pgrid .pcard-fx-scan {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(90deg, transparent, rgba(var(--fx-acc), 1), transparent),
        linear-gradient(180deg, rgba(var(--fx-acc), 0.3), transparent);
    background-size: 100% 2px, 100% 22px;
    background-repeat: no-repeat, no-repeat;
    background-position: 0 0, 0 0;
    transform: translateY(calc(var(--on) * var(--k) * 100%));
    opacity: calc(var(--on) * 0.95);
    transition: transform 720ms cubic-bezier(.35, .9, .2, 1), opacity 140ms linear;
}
.catalog-dark .pgrid .pcard-fx-br {
    position: absolute;
    width: 13px;
    height: 13px;
    opacity: var(--on);
    transition: opacity 150ms linear;
}
.catalog-dark .pgrid .pcard-fx-tl {
    top: 10px; left: 10px;
    border-top: 1px solid rgb(var(--fx-acc));
    border-left: 1px solid rgb(var(--fx-acc));
    transition-delay: 20ms;
}
.catalog-dark .pgrid .pcard-fx-tr {
    top: 10px; right: 10px;
    border-top: 1px solid rgb(var(--fx-acc));
    border-right: 1px solid rgb(var(--fx-acc));
    transition-delay: 60ms;
}
.catalog-dark .pgrid .pcard-fx-bl {
    bottom: 10px; left: 10px;
    border-bottom: 1px solid rgb(var(--fx-acc));
    border-left: 1px solid rgb(var(--fx-acc));
    transition-delay: 100ms;
}
.catalog-dark .pgrid .pcard-fx-brr {
    bottom: 10px; right: 10px;
    border-bottom: 1px solid rgb(var(--fx-acc));
    border-right: 1px solid rgb(var(--fx-acc));
    transition-delay: 140ms;
}

/* Spec strip. Only present in the markup when the product has product_specs
   rows (first three, in sort_order), so there is never an empty bar. */
.pcard-spec { display: none; }   /* off everywhere except the catalog grid */
.catalog-dark .pgrid .pcard-spec {
    display: flex;
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 2;                       /* over the vial + fx, under the badges */
    gap: 16px;
    padding: 8px 12px;
    background: linear-gradient(180deg, transparent, rgba(15, 17, 24, 0.92) 45%);
    overflow: hidden;
    pointer-events: none;
    /* This transform IS the reveal, not an embellishment, so unlike the card
       lift it is not multiplied by --k — otherwise reduced motion would leave
       the strip parked open over every idle card. Under reduced motion the
       .01ms transition turns the slide into a state change instead. */
    transform: translateY(calc((1 - var(--on)) * 100%));
    transition: transform 260ms cubic-bezier(.2, .9, .3, 1);
}
.catalog-dark .pgrid .pcard-spec-cell {
    display: flex;
    flex-direction: column;
    min-width: 0;                     /* lets a cell shrink instead of overflowing */
}
.catalog-dark .pgrid .pcard-spec-label {
    font-size: 9px;
    font-weight: 500;
    line-height: 1.3;
    text-transform: uppercase;        /* admin types "Purity", strip shows PURITY */
    letter-spacing: 0.14em;
    color: #7e8090;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.catalog-dark .pgrid .pcard-spec-value {
    font-family: var(--font-mono);
    font-size: 11.5px;
    font-weight: 500;
    line-height: 1.35;
    color: #cfdcfd;
    /* Nine monospace characters, then an ellipsis. The row never wraps. */
    max-width: 9ch;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Narrow cards. The handoff's grid never goes below a 258px card, where three
   columns fit; ours are ~157px wide in the 2-up phone layout, where three
   "PURITY"-length labels plus gaps need ~167px. Rather than clip labels and
   ellipsis a 3-character value, the third pair is dropped below 640px and the
   remaining two keep full type fidelity and the full 9-character value width. */
@media (max-width: 640px) {
    .catalog-dark .pgrid .pcard-spec { gap: 12px; padding: 8px 10px; }
    .catalog-dark .pgrid .pcard-spec-cell:nth-child(n + 3) { display: none; }
}


/* Vial lift. The image is inset 82% of the media box, so 1.05 never clips. */
.catalog-dark .pgrid .pcard-media img {
    transform: scale(calc(1 + var(--on) * var(--k) * 0.05));
    transition: transform 170ms cubic-bezier(.2, .9, .3, 1);
}
.catalog-dark .pgrid .pcard-price {
    transform: translateY(calc(var(--on) * var(--k) * -1px));
    transition: transform 160ms cubic-bezier(.2, .9, .3, 1);
}
/* The one paint-only property animated here. A <select> cannot carry a
   pseudo-element overlay, so the accent edge has to be the border itself;
   border-color repaints the control and nothing else — no layout, no blur. */
.catalog-dark .pgrid .pcard.is-active .pcard-size {
    border-color: rgba(46, 91, 224, 0.38);
    border-color: color-mix(in srgb, #2E5BE0 28%, rgba(255, 255, 255, 0.11));
}
/* Gated with the rest of the hover state, so a sticky :hover after a tap can
   never strand the select with an accent border. */
@media (hover: hover) and (pointer: fine) {
    .catalog-dark .pgrid .pcard:hover .pcard-size {
        border-color: rgba(46, 91, 224, 0.38);
        border-color: color-mix(in srgb, #2E5BE0 28%, rgba(255, 255, 255, 0.11));
    }
}

.catalog-dark .pgrid .pcard-size { transition: border-color 160ms linear; }

/* Reduced motion: state still changes (bloom, brackets, rim, scan all still
   appear), movement does not. Transitions collapse to .01ms per the handoff. */
@media (prefers-reduced-motion: reduce) {
    .catalog-dark .pgrid .pcard { --k: 0; }
    .catalog-dark .pgrid .pcard,
    .catalog-dark .pgrid .pcard::after,
    .catalog-dark .pgrid .pcard-fx-bloom,
    .catalog-dark .pgrid .pcard-fx-scan,
    .catalog-dark .pgrid .pcard-fx-br,
    .catalog-dark .pgrid .pcard-spec,
    .catalog-dark .pgrid .pcard-media img,
    .catalog-dark .pgrid .pcard-price,
    .catalog-dark .pgrid .pcard-size {
        transition-duration: .01ms;
        transition-delay: 0s;
    }
}


/* Touch / no-hover devices: neutralise the base card's :hover entirely so a
   tap cannot leave a lifted, shadowed card behind after the finger lifts. */
@media (hover: none), (pointer: coarse) {
    .catalog-dark .pgrid .pcard:hover {
        transform: translateY(calc(var(--on) * var(--k) * -4px));
        box-shadow: none;
        border-color: rgba(255, 255, 255, 0.08);
    }
}


/* =========================================================================
   Certificate on hover (treatment A). The product image dims and page one of
   the certificate rises and straightens into place over it.

   --coa: 0|1 IS ITS OWN STATE AND NOT --on, deliberately. The card already has
   a touch activation that sets .is-active and clears itself after 2600ms. If
   the certificate hung off that, every tap on a phone would slap a document
   over the product image and then snatch it away mid-read. So --coa is set by
   hover on a pointer device, and on touch only by the badge, with no timeout.

   --k is the shared reduced-motion kill switch. It multiplies the rise, the
   scale and the tilt, so at --k:0 the panel simply appears, already square and
   in place, with no movement and no stagger. The state change survives; the
   choreography does not.

   Only ever rendered for a signed-in visitor, and only for a certificate that
   has a cached preview. Both decisions are made in PHP; there is no CSS here
   that could reveal a certificate the markup did not carry.
   ========================================================================= */
.catalog-dark .pgrid .pcard { --coa: 0; }

@media (hover: hover) and (pointer: fine) {
    .catalog-dark .pgrid .pcard.has-coa:hover { --coa: 1; }
}

/* Touch, and keyboard on any device. Set by app.js on the badge, cleared by a
   tap outside, a second tap, or Escape. */
.catalog-dark .pgrid .pcard.has-coa.coa-open { --coa: 1; }

.catalog-dark .pgrid .pcard-coa {
    position: absolute;
    inset: 0;
    z-index: 3;                      /* over the vial and the fx, under the badges */
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: var(--coa);
    /* THE CLIP. The sheet rotates 1.2 degrees as it rises, which swings its
       corners outside the box it is centred in, and for a few frames a bright
       sliver of that corner showed at the edge. Clipping at the container ends
       it. The sheet settles square and well inside these bounds, so nothing is
       cut once it has arrived: measured, not assumed. This also keeps the glow
       below from bleeding past the media box. */
    overflow: hidden;
    /* Inert while closed AND while open on a pointer device: nothing in here is
       interactive, and swallowing clicks would break the card link and the
       quick-add button underneath it. The touch rule below re-enables it only
       when the panel is open, so a tap on the document dismisses it instead of
       navigating away. */
    pointer-events: none;
    transition: opacity 300ms ease;
}

/* The glow. A single SOLID colour, blurred, sitting behind the page, so it is a
   light source rather than a gradient fill. It blooms outward as the sheet rises,
   which is what makes the document read as lit rather than pasted on.
   The scale is multiplied by --k, so under reduced motion it arrives already at
   full size: the panel lands lit but static. */
.catalog-dark .pgrid .pcard-coa::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 46%;
    width: 78%;
    height: 74%;
    border-radius: 50%;
    background: #4c74e8;
    filter: blur(46px);
    opacity: calc(var(--coa) * 0.34);
    transform: translate(-50%, -50%) scale(calc(1 - (1 - var(--coa)) * var(--k) * 0.15));
    transition:
        opacity 420ms ease 40ms,
        transform 500ms cubic-bezier(.2, .8, .25, 1) 40ms;
}

/* The sheet. Sized off the media box HEIGHT, not its width: the box is a fixed
   210/150/130px and a portrait page is height-constrained in it, so a width
   percentage (as the mockup used on a square card) would overflow the top and
   bottom here. width:auto keeps whatever ratio the stored page actually has,
   so an A4 or a landscape scan still fits rather than being stretched. */
.catalog-dark .pgrid .pcard-coa-sheet {
    height: 76%;
    /* NO WHITE FILL, EVER. This box used to paint #fff behind the page, and
       during the tilt the image and that fill rasterised a fraction of a pixel
       apart, which is what put a bright line down the leading edge.
       #141b28 is the placeholder panel the page loads over, and being DARK it
       cannot bring the flash back: a sliver of it against the card is invisible
       where a sliver of white was not. line-height and font-size zeroed so no
       inline descender adds a sliver either. */
    background: #141b28;
    line-height: 0;
    font-size: 0;
    border-radius: 4px;
    overflow: hidden;
    /* The hairline. A 1px light edge lifts the page off the dark card, which is
       what stops a bright document reading as a floating rectangle. Deliberate,
       symmetric and static, unlike the flash it replaces. */
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.22),
        0 18px 44px rgba(0, 0, 0, 0.62);
    opacity: var(--coa);
    transform:
        translateY(calc((1 - var(--coa)) * var(--k) * 16px))
        scale(calc(1 - (1 - var(--coa)) * var(--k) * 0.06))
        rotate(calc((1 - var(--coa)) * var(--k) * -1.2deg));
    transition:
        opacity 320ms ease,
        transform 420ms cubic-bezier(.2, .8, .25, 1);
}
/* The loading placeholder.
   The panel opens at 0ms over this, rather than waiting for the image, so the
   animation never stalls on the network. A solid bar under a blur, swept across
   a solid panel: no gradient anywhere, which is why it survives the solid
   colours rule that a normal shimmer would not.
   It sits UNDER the image (z-index 0 against the image's 1), so the moment the
   paper fades in it is covered rather than needing to be removed. */
.catalog-dark .pgrid .pcard-coa-skel {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: #141b28;
    overflow: hidden;
    opacity: 1;
    transition: opacity 300ms ease;
}
.catalog-dark .pgrid .pcard-coa-skel i {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 42%;
    background: #243049;
    filter: blur(22px);
    opacity: .85;
    animation: pcdCoaSweep 1.15s ease-in-out infinite;
}
@keyframes pcdCoaSweep {
    0%   { transform: translateX(-60%); }
    100% { transform: translateX(240%); }
}
/* Once the paper has arrived the placeholder is redundant. Fading it rather than
   removing it keeps the sheet's box height, which the image depends on. */
.catalog-dark .pgrid .pcard-coa-sheet.is-ready .pcard-coa-skel { opacity: 0; }

/* Four classes deep so this beats .pcard-media img, which owns the vial's hover
   scale and would otherwise grow the certificate by 5 percent along with it. The
   sheet has its own rise; it must not also inherit the product's lift. */
.catalog-dark .pgrid .pcard-coa .pcard-coa-sheet img {
    display: block;
    height: 100%;
    width: auto;
    transform: none;
    /* .pcard-media img centres the vial absolutely at 82%. The sheet image is
       inside that same box but is laid out by its own container, so every one of
       those declarations has to be given back. */
    max-width: none;
    max-height: none;
    position: static;
    margin: 0;
    inset: auto;
    object-fit: contain;
    /* Lift the paper. A filter on the IMAGE, not a pale layer over it: raising
       brightness and contrast together takes the page toward white while pushing
       the type the other way, so it stays black and legible. An overlay would
       have washed both to grey. */
    filter: brightness(1.10) contrast(1.14);
    /* Above the placeholder, and invisible until it has actually decoded. The
       width and height attributes give the box its size before the bytes land,
       so the sheet is never the wrong shape and nothing reflows on arrival. */
    position: relative;
    z-index: 1;
    opacity: 0;
    transition: opacity 340ms ease;
}
.catalog-dark .pgrid .pcard-coa .pcard-coa-sheet img.is-ready { opacity: 1; }

/* Caption. A SOLID bar, not the mockup's gradient scrim: the rule here is solid
   colours, and a solid bar with a defined top edge reads as a caption rather
   than as a fade that failed to load. */
.catalog-dark .pgrid .pcard-coa-cap {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 9px 12px 10px;
    background: #0d1220;
    color: #fff;
    opacity: var(--coa);
    transform: translateY(calc((1 - var(--coa)) * var(--k) * 10px));
    transition:
        opacity 300ms ease 60ms,
        transform 400ms cubic-bezier(.2, .8, .25, 1) 60ms;
}
.catalog-dark .pgrid .pcard-coa-cap b {
    display: block;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.catalog-dark .pgrid .pcard-coa-cap span {
    display: block;
    margin-top: 1px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    line-height: 1.35;
    color: #a9b1c4;
    overflow-wrap: anywhere;
}

/* The dim. filter:brightness on the image itself rather than a translucent
   layer over it, so there is no alpha anywhere and nothing to composite. It
   rides alongside the existing 1.05 lift instead of replacing it, because that
   rule owns transform and this one owns filter.

   DOWN TO 12 PERCENT, not the 34 it started at. The product is not the subject
   while the certificate is open, and the darker base is half of what makes the
   paper read as white: the same page against a lighter backdrop reads grey no
   matter how far its own brightness is pushed.

   TARGETED AT .pcard-media-link img, NOT .pcard-media img. The certificate is
   also an <img> inside the media box, so the broader selector dimmed the
   document by exactly as much as the product. That is why the page read grey:
   it was being dimmed to 34 percent along with the vial, and no amount of
   brightening on top could win, because the dim rule carried more specificity.
   Scoping to the product's own link element separates the two for good. */
.catalog-dark .pgrid .pcard.has-coa .pcard-media-link img {
    filter: brightness(calc(1 - var(--coa) * 0.88));
    transition: transform 170ms cubic-bezier(.2, .9, .3, 1), filter 340ms ease;
}

/* The spec strip and the certificate are both bottom-anchored layers and would
   sit on top of each other. The certificate wins while it is open. */
.catalog-dark .pgrid .pcard.has-coa.coa-open .pcard-spec { opacity: 0; }
@media (hover: hover) and (pointer: fine) {
    .catalog-dark .pgrid .pcard.has-coa:hover .pcard-spec { opacity: 0; }
}

.catalog-dark .pgrid .pcard-spec { transition: transform 260ms cubic-bezier(.2, .9, .3, 1), opacity 200ms ease; }

/* ---- the badge ----
   One control, two presentations. On a pointer device it is invisible and inert
   at rest, so the card looks exactly as treatment A was drawn, but it is still a
   real focusable button: Tab reaches it and :focus-visible brings it into view,
   which is the keyboard route into a design that is otherwise hover-only. On
   touch it is always visible, because a phone has no hover at all. */
.catalog-dark .pgrid .pcard-coa-badge {
    position: absolute;
    left: 8px;
    top: 8px;
    z-index: 4;                      /* above the panel it opens */
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-height: 44px;                /* the hit area, never smaller than this */
    padding: 6px 11px;
    background: #ffffff;
    color: #12603f;
    border: 1px solid #cfe6d9;
    border-radius: 999px;
    font-family: inherit;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
}
.catalog-dark .pgrid .pcard-coa-tick { display: inline-flex; width: 11px; height: 11px; color: #1fa863; }
.catalog-dark .pgrid .pcard-coa-tick svg { width: 100%; height: 100%; }

/* Keyboard: focus makes it real on every device. */
.catalog-dark .pgrid .pcard-coa-badge:focus-visible {
    opacity: 1;
    pointer-events: auto;
    outline: 2px solid #7d9bf0;
    outline-offset: 2px;
}

@media (hover: none), (pointer: coarse) {
    .catalog-dark .pgrid .pcard-coa-badge { opacity: 1; pointer-events: auto; }
    /* Open on touch: the panel takes the taps, so tapping the document closes it
       rather than following the card link underneath. */
    .catalog-dark .pgrid .pcard.coa-open .pcard-coa { pointer-events: auto; }
    /* The badge tucks away as the panel opens, so it is not left sitting on top
       of the certificate it just revealed. */
    .catalog-dark .pgrid .pcard.coa-open .pcard-coa-badge { opacity: 0; pointer-events: none; }
}


/* Smaller media boxes: 150px at 640, 130px at 400. The sheet is a percentage of
   that height so it follows automatically; only the caption needs to give back
   some padding, and below 400 it drops the mono line rather than wrapping a lot
   number across three lines inside a 130px-tall box. */
@media (max-width: 640px) {
    .catalog-dark .pgrid .pcard-coa-cap { padding: 7px 10px 8px; }
    .catalog-dark .pgrid .pcard-coa-cap b { font-size: 10.5px; }
    .catalog-dark .pgrid .pcard-coa-cap span { font-size: 9.5px; }
}

/* Under 400px the media box is only 130px tall and the card about 160px wide, so
   the caption has room for one line of two. It keeps the LOT and drops the title:
   the picture of a certificate already says "certificate", and the lot number is
   the fact that cannot be inferred from the image. Dropping the lot instead would
   leave a caption that names nothing. */

/* THE BADGE IS GONE BELOW 641, NOT SHRUNK AND NOT HIDDEN.
   Every attempt to make it fit a 160px card failed on its own terms: the full
   pill covered the vial, and the 28px chip that cleared the vial was a tick with
   no label, which says nothing to anyone who has not already seen the desktop
   version.

   display: none is the removal, not a dodge. It takes the button out of the box
   tree, out of the tab order and out of the accessibility tree in one move, so
   it occupies no space and no screen reader or keyboard can reach it. visibility
   or opacity would have left a control that is invisible but still focusable,
   which is worse than either state.

   THIS BLOCK MUST STAY BELOW THE (hover: none) RULE ABOVE, which sets opacity 1
   on coarse pointers. Media queries add no specificity, so source order is the
   only thing deciding the winner.

   WHAT THIS COSTS: the badge is the only way to open the certificate panel on a
   touch device, because the panel's other trigger is hover. Below 641 there is
   now no route from a product card to the certificate. The routes that remain
   are the COAs link in the nav, which reaches /coa-library on every page, and
   the certificate line on each product page. Do not treat the panel below as
   reachable on a phone; it is not. */
@media (max-width: 640px) {
    .catalog-dark .pgrid .pcard-coa-badge { display: none; }
}


@media (max-width: 400px) {
    .catalog-dark .pgrid .pcard-coa-sheet { height: 70%; }
    .catalog-dark .pgrid .pcard-coa-cap { padding: 6px 9px 7px; }
    .catalog-dark .pgrid .pcard-coa-cap b { display: none; }
    .catalog-dark .pgrid .pcard-coa-cap span {
        margin-top: 0;
        font-size: 10px;
        color: #ffffff;
    }
}


/* Reduced motion: the panel still appears, the document still dims the vial,
   nothing moves. --k already zeroes the rise, the scale and the tilt; these
   collapse the durations and the caption's stagger so the whole thing lands as
   one state change. */
@media (prefers-reduced-motion: reduce) {
    .catalog-dark .pgrid .pcard-coa,
    .catalog-dark .pgrid .pcard-coa::before,
    .catalog-dark .pgrid .pcard-coa-sheet,
    .catalog-dark .pgrid .pcard-coa-cap,
    .catalog-dark .pgrid .pcard-coa-badge,
    .catalog-dark .pgrid .pcard-coa-skel,
    .catalog-dark .pgrid .pcard.has-coa .pcard-media-link img {
        transition-duration: .01ms;
        transition-delay: 0s;
    }
    /* The shimmer stops. The placeholder stays as a flat panel, which is still
       the right thing to look at while the page loads, and the paper appears
       rather than fading in. */
    .catalog-dark .pgrid .pcard-coa-skel i { animation: none; }
    .catalog-dark .pgrid .pcard-coa .pcard-coa-sheet img { transition: none; }
}


/* =========================================================================
   FAQ page (/faq) — light, consistent with contact/legal. <details> accordions.
   ========================================================================= */
/* Width fix only: the page now sits on the shared .page-shell contract so its
   edges line up with the nav instead of being a 820px column of its own. The
   content, the questions and the category structure are untouched.
   The list below goes multi-column at wide widths, because the one thing a width
   change cannot leave alone is a single 1440px-wide column of accordions. */
.faq-page {
    max-width: calc(var(--content-max) + 2 * var(--gutter));
    margin: 0 auto;
    padding: 48px var(--gutter) 72px;
}
.faq-head { margin-bottom: 26px; }
.faq-h1 { margin: 0 0 10px; font-size: 34px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.faq-intro { margin: 0; font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 60ch; }

.faq-jumpnav { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 34px; }
.faq-jump { font-size: 13px; color: var(--ink); background: #fff; border: 1px solid var(--border-2); border-radius: 999px; padding: 8px 15px; text-decoration: none; transition: border-color .15s ease, color .15s ease; }
.faq-jump:hover { border-color: var(--accent-blue); color: var(--accent-blue); }

.faq-cat { margin-bottom: 34px; scroll-margin-top: 96px; }
.faq-cat-h { margin: 0 0 14px; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent-blue); }
/* Above 900px each question becomes its own card in a two-column grid, so the
   wider shell is used rather than just making one column longer. Below that it
   collapses back to the original single stacked panel. */
.faq-list { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: #fff; }
.faq-item { border-top: 1px solid var(--border); }
.faq-item:first-child { border-top: none; }

@media (min-width: 901px) {
    .faq-list {
        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px;
        align-items: start;
        border: 0; border-radius: 0; overflow: visible; background: transparent;
    }
    .faq-item {
        border: 1px solid var(--border); border-radius: 12px; background: #fff;
        overflow: hidden; min-width: 0;
    }
    .faq-item:first-child { border-top: 1px solid var(--border); }
}

.faq-q {
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    min-height: 44px; padding: 16px 20px; cursor: pointer; list-style: none;
    font-size: 16px; font-weight: 600; color: var(--ink);
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { background: var(--off-white); }
.faq-chev { width: 18px; height: 18px; flex: 0 0 auto; color: var(--muted-2); transition: transform .2s ease; }
.faq-item[open] > .faq-q .faq-chev { transform: rotate(180deg); }
.faq-a { padding: 2px 20px 18px; font-size: 15px; line-height: 1.65; color: #3a3a3a; }
.faq-a p { margin: 0 0 10px; }
.faq-a p:last-child { margin-bottom: 0; }
.faq-a a { color: var(--accent-blue); }

@media (max-width: 640px) {
    .faq-page { padding: 32px 16px 56px; }
    .faq-h1 { font-size: 27px; }
    .faq-q { padding: 14px 16px; font-size: 15px; }
    .faq-a { padding: 2px 16px 16px; }
}


/* =========================================================================
   Homepage FAQ block — dark, glass-consistent with the catalog cards.
   ========================================================================= */
.catalog-dark .home-faq { max-width: 1440px; margin: 44px auto 0; }
.home-faq-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.home-faq-h { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -0.01em; color: #fff; }
.home-faq-all { font-size: 14px; color: #8fadf5; text-decoration: none; }
.home-faq-all:hover { color: #fff; }
.home-faq-list { display: grid; gap: 10px; }
.home-faq-item {
    border-radius: 14px; overflow: hidden;
    background: rgba(18, 21, 29, 0.72);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.31), inset 0 0 0 1px rgba(255, 255, 255, 0.17), inset 0 -1px 0 rgba(255, 255, 255, 0.068);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .home-faq-item { background: rgba(255, 255, 255, 0.075); -webkit-backdrop-filter: blur(18px) saturate(160%); backdrop-filter: blur(18px) saturate(160%); }
}
@media (max-width: 767px) {
    .home-faq-item { background: rgba(18, 21, 29, 0.72); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

.home-faq-q {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    min-height: 44px; padding: 15px 20px; cursor: pointer; list-style: none;
    font-size: 15.5px; font-weight: 600; color: #fff;
}
.home-faq-q::-webkit-details-marker { display: none; }
.home-faq-chev { width: 18px; height: 18px; flex: 0 0 auto; color: #8b919c; transition: transform .2s ease; }
.home-faq-item[open] > .home-faq-q .home-faq-chev { transform: rotate(180deg); }
.home-faq-a { padding: 2px 20px 16px; font-size: 14.5px; line-height: 1.65; color: #c7cbd2; }
.home-faq-a p { margin: 0 0 8px; }
.home-faq-a p:last-child { margin-bottom: 0; }
.home-faq-a a { color: #8fadf5; }
@media (max-width: 640px) {
    .home-faq-q { padding: 14px 16px; font-size: 14.5px; }
    .home-faq-a { padding: 2px 16px 14px; }
}


/* =========================================================================
   Mobile hamburger menu (<768px). Desktop (>=768) keeps the horizontal nav.
   ========================================================================= */
.topbar-mobile { display: none; }
.mnav, .mnav-noscript { display: none; }

@media (max-width: 767px) {
    .topbar-inner { display: none; }
    /* Same two-state treatment as the desktop bar: see the .navbar rules above for
       why only the colours transition and the blur is a fixed, class-toggled value. */
    .topbar-mobile {
        display: flex; align-items: center; justify-content: space-between; gap: 12px;
        height: 76px; padding: 0 var(--gutter);
        background-color: rgba(255, 255, 255, 0.03);
        border-bottom: 1px solid rgba(255, 255, 255, 0.07);
        transition: background-color 0.25s ease, border-color 0.25s ease;
    }
    .topbar.is-scrolled .topbar-mobile {
        background-color: rgba(16, 18, 22, 0.72);
        border-bottom-color: rgba(255, 255, 255, 0.12);
        -webkit-backdrop-filter: blur(16px) saturate(1.3);
        backdrop-filter: blur(16px) saturate(1.3);
    }

    /* Light pages: same treatment mirrored, exactly as the desktop bar. */
    body:not(.catalog-dark) .topbar-mobile { border-bottom-color: rgba(0, 0, 0, 0.07); }
    body:not(.catalog-dark) .topbar.is-scrolled .topbar-mobile {
        background-color: rgba(252, 252, 253, 0.72);
        border-bottom-color: rgba(0, 0, 0, 0.1);
    }
    body:not(.catalog-dark) .tm-burger { color: #111; }
    /* .tm-cart keeps #2E5BE0 + white text on both surfaces. */
}

@media (min-width: 768px) {
    /* Never let the overlay/fallback interfere with the desktop nav. */
    .mnav, .mnav-noscript { display: none !important; }
}


/* Collapsed bar */
.tm-brand { display: inline-flex; align-items: center; flex: 0 0 auto; }
.tm-brand:hover { text-decoration: none; }
.tm-logo { height: 40px; width: auto; display: block; }
.tm-brand .logo-wordmark { color: #fff; font-size: 18px; font-weight: 800; letter-spacing: -0.01em; }
.tm-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.tm-cart {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    min-height: 44px;   /* 44px minimum tap target */
    background: #2E5BE0; color: #fff; border-radius: 999px; padding: 0 14px;
    font-size: 13px; font-weight: 600; text-decoration: none;
}
.tm-cart:hover { text-decoration: none; color: #fff; }
.tm-cart-ico { display: inline-flex; flex: 0 0 auto; }
.tm-cart-ico svg { width: 17px; height: 17px; display: block; }
.tm-cart-count { font-family: var(--font-mono); font-weight: 700; line-height: 1; }
.tm-burger {
    width: 44px; height: 44px; padding: 0; border: none; background: none; color: #fff;
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.tm-burger svg { width: 27px; height: 27px; }

/* Full-screen overlay — SOLID fill.
   No backdrop-filter and no animated glow layers, deliberately. This overlay only
   ever renders below 768px (it is display:none !important above that), so either
   one is a phone-only cost by definition. The blur was blur(24px) saturate(140%)
   over the whole viewport, and because the overlay fades in via opacity, iOS
   Safari had to re-rasterize a blurred copy of the entire page behind it on EVERY
   frame of the 180ms fade. That was the stutter. The fill is opaque instead:
   nothing shows through, so there is nothing worth blurring. */
.mnav {
    position: fixed; inset: 0; z-index: 10050;
    display: flex; overflow: hidden;
    opacity: 0; visibility: hidden;
    background: #0b0e17;
    /* Opacity only. visibility is discrete (no per-frame cost) and just keeps
       the closed overlay out of the tab order. */
    transition: opacity .18s ease, visibility .18s ease;
    font-family: 'Inter', system-ui, sans-serif;
}
.mnav.is-open { opacity: 1; visibility: visible; }

.mnav-inner { position: relative; z-index: 1; display: flex; flex-direction: column; width: 100%; height: 100%; }

.mnav-head { flex: 0 0 auto; height: 76px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; }
.mnav-close { width: 44px; height: 44px; padding: 0; border: none; background: none; color: #fff; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.mnav-close svg { width: 27px; height: 27px; }

.mnav-search { flex: 0 0 auto; position: relative; padding: 0 18px; margin-bottom: 6px; }
.mnav-search-ico { position: absolute; left: 34px; top: 50%; transform: translateY(-50%); display: inline-flex; pointer-events: none; }
.mnav-search-ico svg { width: 18px; height: 18px; color: #7d8494; }
.mnav-search input {
    width: 100%; box-sizing: border-box; height: 48px;
    background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 11px; color: #fff; font-size: 15px; padding: 0 14px 0 44px;
}
.mnav-search input::placeholder { color: #7d8494; }
.mnav-search input:focus { outline: none; border-color: rgba(46, 91, 224, 0.7); }

.mnav-links { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 8px 18px 12px; }
/* Hairlines sit on the TOP of each row, not the bottom, so the group divider
   below never doubles up with a row's own rule. */
.mnav-link {
    display: flex; align-items: center; gap: 14px;
    min-height: 52px;   /* comfortably past the 44px minimum */
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    color: #fff; font-size: 17px; text-decoration: none;
}
.mnav-link:first-child { border-top: none; }
.mnav-link:hover { text-decoration: none; }
.mnav-ico { width: 21px; height: 21px; flex: 0 0 auto; color: #2E5BE0; }

/* Divider between the site links and the account group: a spacer, with the
   heavier rule drawn on the first account row after it. */
.mnav-sep { display: block; height: 14px; }
.mnav-sep + .mnav-link { border-top-color: rgba(255, 255, 255, 0.14); }
.mnav-link-acct { font-size: 16px; min-height: 48px; }
.mnav-link-acct .mnav-ico { color: #7d8494; }
.mnav-link-acct .mnav-label { color: #a9b0bc; }

.mnav-foot { flex: 0 0 auto; border-top: 1px solid rgba(255, 255, 255, 0.08); padding: 14px 18px; display: flex; flex-wrap: wrap; gap: 14px; }
.mnav-foot a { font-size: 12px; color: #6b7280; text-decoration: none; }
.mnav-foot a:hover { color: #a9b0bc; }

/* Body scroll lock while the menu is open (position preserved by JS). */
body.mnav-lock { position: fixed; left: 0; right: 0; width: 100%; overflow: hidden; }

/* No-JS fallback nav (revealed by the <noscript> style on mobile). */
.mnav-noscript { background: #1c1c1c; padding: 6px 18px 16px; border-bottom: 1px solid var(--border-dark); }
.mnav-noscript a { display: block; padding: 12px 0; color: #fff; font-size: 16px; text-decoration: none; border-bottom: 1px solid rgba(255, 255, 255, 0.06); }
.mnav-noscript-legal { display: flex; flex-wrap: wrap; gap: 14px; padding-top: 12px; }
.mnav-noscript-legal a { display: inline; padding: 0; border: none; font-size: 12px; color: #6b7280; }

/* =========================================================================
   Admin drag-to-reorder (products + FAQs). Handle-only drag; arrows remain.
   ========================================================================= */

/* Bigger tap target on touch devices. */


/* Dragged row: 40% opacity + light accent tint. */

/* Drop-line: 2px accent on the edge the row would insert at (table mode). */


/* De-emphasise the arrow fallback once JS drag is active (still present + usable). */


/* Save status indicator near the top of each list. */


/* Card layout (<=767px): handle sits on the card; drop-line moves to the card edge. */


/* Pack cost calculator: shipping row + landed/per-vial output emphasis. */


/* =========================================================================
   Homepage SEO copy (dark catalog), public COA library (light), admin SEO counter.
   ========================================================================= */
/* Same width wrapper as .pgrid / .home-faq (1440px, inside .catalog padding). */
.catalog-dark .home-seo { max-width: 1440px; margin: 48px auto 0; }
.home-seo-inner { display: grid; grid-template-columns: 1fr; gap: 14px; }
@media (min-width: 1025px) { .home-seo-inner { grid-template-columns: repeat(3, 1fr); } }


/* Glass card — identical treatment + fallbacks to the product cards. */
.home-seo-block {
    border-radius: 16px; padding: 22px;
    background: rgba(18, 21, 29, 0.72);   /* flat fallback (no @supports / mobile) */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.31),
        inset 0 0 0 1px rgba(255, 255, 255, 0.17),
        inset 0 -1px 0 rgba(255, 255, 255, 0.068),
        0 8px 28px rgba(0, 0, 0, 0.35);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .home-seo-block {
        background: rgba(255, 255, 255, 0.075);
        -webkit-backdrop-filter: blur(18px) saturate(160%);
        backdrop-filter: blur(18px) saturate(160%);
    }
}
@media (max-width: 767px) {
    .home-seo-block {
        background: rgba(18, 21, 29, 0.72);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
}


.home-seo-head { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; }
.home-seo-icon {
    flex: 0 0 auto; width: 40px; height: 40px; border-radius: 11px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--accent-warm); color: #1c1c1c;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
.home-seo-icon svg { width: 21px; height: 21px; display: block; }
.home-seo-h2 { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -0.01em; color: #fff; }
.home-seo-body { margin: 0; font-size: 14px; line-height: 1.65; color: #a9b0bc; }
.seo-counter.over { color: #c5311f; }

/* ---- Backorder ("more on the way") -------------------------------------
   A buyable state that is neither in stock nor out of stock. */

/* Catalog card: a full-width strip in the normal flow between the image and the
   name. Deliberately NOT absolutely positioned over the image, so it can never
   sit off-centre over artwork of a different size. */
.pcard-backorder {
    background: rgba(224, 166, 58, .14);
    border-radius: 7px;
    padding: 6px 10px;
    margin: 10px 12px 0;
    text-align: center;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.05em;
    color: #e0a63a;
}
.pcard-restock { font-size: 11.5px; color: #8b919c; margin: 2px 0 0; }

/* Product page: amber pill beside the price, matching the in-stock pill pattern. */
.pd-backorder-pill {
    display: inline-block;
    background: #fdf3e0;
    color: #8a5a00;
    border-radius: 999px;
    padding: 4px 12px;
    font-size: 12.5px;
    font-weight: 600;
    margin-left: 8px;
}
.pd-backorder-pill[hidden] { display: none; }

.pd-backorder-note {
    background: #fdf9f0;
    border: 1px solid #f0e0c0;
    border-radius: 10px;
    padding: 13px;
    margin: 12px 0 0;
}
.pd-backorder-note[hidden] { display: none; }
.pd-backorder-h    { margin: 0 0 4px; font-size: 14px; font-weight: 600; color: #111418; }
.pd-backorder-body { margin: 0; font-size: 13.5px; color: #4a5260; line-height: 1.5; }
.pd-backorder-date { margin: 6px 0 0; font-size: 13.5px; color: #4a5260; }
.pd-backorder-date[hidden] { display: none; }

/* Cart line + the order-level notice under the lines. */
.cart-line-backorder { margin: 4px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cart-bo-pill {
    display: inline-block;
    background: #fdf3e0;
    color: #8a5a00;
    border-radius: 999px;
    padding: 3px 10px;
    font-size: 11.5px;
    font-weight: 600;
}
.cart-bo-date { font-size: 11.5px; color: #8b919c; }
.cart-backorder-note {
    background: #fdf9f0;
    border: 1px solid #f0e0c0;
    border-radius: 10px;
    padding: 13px;
    margin: 14px 0 0;
}
.cart-bo-head { margin: 0 0 5px; font-size: 13.5px; color: #111418; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cart-bo-body { margin: 0; font-size: 13px; color: #4a5260; line-height: 1.5; }

/* Admin inventory: a negative count means units OWED, not a broken row. */

.inv-stockline.is-backorder { color: #8a5a00; }

/* Nothing here may push the page sideways on a small screen. */
@media (max-width: 560px) {
    .pcard-backorder { margin: 8px 8px 0; font-size: 10.5px; }
    .cart-line-backorder, .cart-bo-head { gap: 6px; }
    .pd-backorder-pill { margin-left: 0; margin-top: 6px; }
}


/* A grid item defaults to min-width:auto, so its content can force the track
   wider than the container — that is what pushed the cart and checkout columns
   to 336px inside a 320px viewport and produced a sideways scroll. Letting the
   columns shrink fixes it without changing the desktop layout. */
.cart-lines,
.cart > *,
.checkout-main,
.checkout > * { min-width: 0; }

/* At 320px the cart line's 76px thumbnail + 16px gaps + the total leave the
   middle column too little room, and the total pushed ~6px past the viewport.
   Tightening the thumbnail and gap keeps the same three-column layout. */
@media (max-width: 360px) {
    .cart-line { grid-template-columns: 56px 1fr auto; gap: 10px; }
    .cart-line-media { width: 56px; height: 56px; }
    .cart-line-total { font-size: 15px; }
    .cart-line-info, .cart-line-total { min-width: 0; }
    .cart-line-name { overflow-wrap: anywhere; }
}


/* ---- Users list: inline name editing ----------------------------------- */
.uname-cell { min-width: 180px; }

/* Rows with no name on file are meant to stand out in a long list. */

.uname-flag {
    display: inline-block;
    margin-left: 6px;
    padding: 2px 7px;
    border-radius: 999px;
    background: #fdf3e0;
    color: #8a5a00;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    vertical-align: middle;
}
.uname-flag[hidden] { display: none; }

/* ---- Admin orders list: two status columns ------------------------------
   The row is a grid, not table layout, so the customer column can be given a
   real minimum and everything else a fixed track. The old table let the
   channel pill squeeze the customer name until it was clipped. */
@media (min-width: 901px) {
    /* Both of these are DESKTOP-only. Left global, the 962px floor applied on
       phones too: the card became 962px wide inside a 328px window and the order
       number was laid out ~595px from the left, i.e. entirely off-screen. */
    .ord-table-scroll { overflow-x: auto; }
    /* 984 = the real minimum this grid can occupy: 900px of tracks (with the
       customer column at its 220px floor) plus 6 gaps of 14px. Was 962 when the
       Payment and Fulfilment tracks were 118 and 132; widening them to 136 each
       moves the floor by the same 22px. The scroll behaviour is unchanged — this
       number only has to keep matching the tracks above, or they get squeezed. */
    .ord-table-scroll > .orders-table { min-width: 984px; width: 100%; }
}


@media (min-width: 901px) {
    .orders-table thead tr,
    .orders-table tbody tr {
        display: grid;
        /* Order column is 150px, not 110: it also carries the bulk-select checkbox
           and the item count, which the original width could not hold.
           Date is 84px, not 78: that is what "Jul 24, 2026" measures, so it sits in
           its own track instead of spilling into the gap. */
        /* Payment 118 -> 136 and Fulfilment 132 -> 136. Each cell carries 11px of
           padding a side (from .admin-cards), so the old Payment track gave only
           96px of content — exactly the width of the "payment note" pill, i.e. no
           slack at all. 136 leaves ~18px on both. */
        grid-template-columns: 150px minmax(220px, 1.6fr) 92px 84px 136px 136px 82px;
        gap: 14px;
        align-items: start;
    }
    .orders-table th, .orders-table td { display: block; border-bottom: 0; padding: 12px 0; }
    .orders-table tbody tr { border-bottom: 1px solid var(--border); }

    /* The customer cell must be allowed to shrink, or the grid track refuses to
       go below its content width and the name gets cut off. */
    .orders-table td[data-label="Customer"] { min-width: 0; overflow: hidden; }
    .orders-table td[data-label="Customer"] strong,
    .orders-table td[data-label="Customer"] .order-cust-email {
        display: block;
        min-width: 0;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Payment cell: one item per line.
       The note flag used to be an inline-block sitting BESIDE the status pill.
       Neither fits alongside the other in the track, so the flag dropped to a
       second line and then broke inside its own pill — a 35px two-line lozenge
       level with the Fulfilment column's Shipped pill, which is the collision.
       Kept as block layout rather than a flex column on purpose: the select has
       to fill the track, and `width: 100%` on a flex item is a cross-axis size
       that did not resolve against the grid track here. In normal block flow it
       just works. Making the flag and the form block-level also puts the status
       pill on a line of its own, with or without a note. */
    .orders-table td[data-label="Payment"] { min-width: 0; }
    .orders-table td[data-label="Payment"] .pay-note-flag {
        display: block;
        width: max-content;
        max-width: 100%;
        margin: 6px 0 0;
    }
    .orders-table td[data-label="Payment"] .order-status-form {
        display: block;
        margin-top: 6px;
        width: 100%;
    }
    .orders-table td[data-label="Payment"] .status-select {
        display: block;
        width: 100%;
        max-width: none;
    }
}


.ord-items { display: block; font-size: 11px; color: var(--muted); margin-top: 3px; }
.orders-table .order-status-form { margin-top: 6px; }
.orders-table .status-select { max-width: 118px; font-size: 12px; }
.ord-cancelled { opacity: .72; }

/* Status pills. Payment always carries a dot; fulfilment only when shipped. */
.ord-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border-radius: 20px;
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}
.ord-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

.ord-pay-unpaid   { background: #fdf3e0; color: #8a5a00; }
.ord-pay-reported { background: #e8effd; color: #1c3f9e; }
.ord-pay-paid     { background: #e7f5ec; color: #12703f; }
.ord-pay-cancelled { background: #f0f2f5; color: #6b727d; }
/* Carrier status line under the fulfilment pill on the orders list. Text-only so
   it reads as secondary to the fulfilment pill above it. */
.ord-track {
    display: block;
    margin-top: 5px;
    font-size: 11px;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ord-track.st-blue  { color: #6f9bff; }
.ord-track.st-green { color: #4bbf7a; }
.ord-track.st-amber { color: #d99b3a; }
.ord-track.st-red   { color: #d9635c; }
.ord-track.st-gray  { color: #8a94a0; }
.ord-ful-ready   { background: #f0f2f5; color: #4a5560; }
.ord-ful-shipped { background: #efe9fb; color: #5b3fa8; }
/* Hand-set delivery. Same settled green as ord-pay-paid, because it carries the
   same meaning: this one is closed out. Solid, no dot. */
.ord-ful-delivered { background: #e7f5ec; color: #12703f; }

/* Filter bar */
.ord-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin: 0 0 12px;
}
.ord-filter-search { position: relative; flex: 1 1 320px; min-width: 0; }
.ord-filter-icon {
    position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px; color: var(--muted-3); pointer-events: none;
}
.ord-filters input[type="search"] {
    width: 100%; height: 40px; border: 1px solid var(--line); border-radius: 9px;
    padding: 0 12px 0 38px; font-family: inherit; font-size: 14px; background: #fff;
    -webkit-appearance: none; appearance: none;
}
.ord-filters input[type="search"]:focus { outline: none; border-color: var(--black); }
.ord-filters select {
    height: 40px; border: 1px solid var(--line); border-radius: 9px;
    padding: 0 12px; font-family: inherit; font-size: 14px; background: #fff;
    flex: 0 1 175px; min-width: 0;
}

.ord-needs { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 16px; }
.ord-needs-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.ord-needs-btn {
    display: inline-block;
    border: 1px solid var(--line);
    border-radius: 20px;
    padding: 6px 14px;
    font-size: 13px;
    color: var(--ink);
    background: #fff;
}
.ord-needs-btn:hover { border-color: var(--black); text-decoration: none; }
.ord-needs-btn.is-active { background: var(--black); border-color: var(--black); color: #fff; }

@media (max-width: 900px) {
    .ord-filters select { flex: 1 1 100%; }
    .ord-filter-search { flex: 1 1 100%; }
}


/* ---- Orders list: mobile card (below 901px) ----------------------------
   The shared .admin-cards pattern turns every cell into a "label: value" row,
   which is wrong for orders: the order number and total belong together on a
   top line, and the two status pills belong side by side. This overrides the
   shared pattern for this one table. */
@media (max-width: 900px) {
    table.orders-table > tbody > tr {
        display: grid;
        grid-template-areas:
            "ord total"
            "cust cust"
            "pills pills"
            "meta meta";
        grid-template-columns: 1fr auto;
        gap: 8px 12px;
        background: #fff;
        border: 1px solid #e4e8ec;
        border-radius: 11px;
        padding: 14px;
        margin-bottom: 10px;
        position: relative;   /* anchors the whole-card tap target */
    }
    /* The shared pattern's label/value rows and separators do not apply here. */
    table.orders-table > tbody > tr > td {
        display: block;
        padding: 0;
        border: none;
        text-align: left;
        min-width: 0;
    }
    table.orders-table > tbody > tr > td::before { content: none !important; }

    table.orders-table > tbody > tr > td[data-label="Order"] {
        grid-area: ord;
        display: flex;
        align-items: center;
        /* The shared card pattern sets justify-content:space-between, which is what
           pushed the order number to the middle of the card. Cancel it explicitly. */
        justify-content: flex-start;
        gap: 9px;
        min-width: 0;
    }
    .orders-table td[data-label="Order"] .order-link {
        font-family: var(--font-mono);
        font-size: 15px;
        white-space: nowrap;
    }
    /* Item count is desktop detail; the card has no room for it. */
    .orders-table td[data-label="Order"] .ord-items { display: none; }
    /* Above the card's tap layer so it stays clickable. */
    .orders-table td[data-label="Order"] input[type="checkbox"] {
        position: relative; z-index: 2; flex: none; margin: 0;
    }

    table.orders-table > tbody > tr > td[data-label="Total"] {
        grid-area: total;
        font-family: var(--font-mono);
        font-size: 15px;
        text-align: right;
        white-space: nowrap;
    }

    table.orders-table > tbody > tr > td[data-label="Customer"] { grid-area: cust; overflow: hidden; }
    .orders-table td[data-label="Customer"] strong {
        display: block; font-size: 14px; font-weight: 500;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .orders-table td[data-label="Customer"] .order-cust-email {
        display: block; font-size: 11px; font-family: var(--font-mono); color: #8a94a0;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }

    /* Payment + fulfilment share one row and wrap if the pills are long. */
    table.orders-table > tbody > tr > td[data-label="Payment"] {
        grid-area: pills; display: flex; flex-wrap: wrap; align-items: center;
        justify-content: flex-start; gap: 8px;
    }
    table.orders-table > tbody > tr > td[data-label="Fulfilment"] {
        grid-area: pills; display: flex; align-items: center;
        justify-content: flex-end; gap: 8px;
    }
    /* The quick status select is a desktop affordance; the card opens the order. */
    .orders-table td[data-label="Payment"] .order-status-form { display: none; }

    table.orders-table > tbody > tr > td[data-label="Channel"],
    table.orders-table > tbody > tr > td[data-label="Date"] {
        grid-area: meta; font-size: 12px; color: #4a5560;
    }
    table.orders-table > tbody > tr > td[data-label="Channel"] { justify-self: start; }
    table.orders-table > tbody > tr > td[data-label="Date"]    { justify-self: end; }
    .orders-table td[data-label="Channel"] .st-pill {
        background: none; padding: 0; font-size: 12px; font-weight: 400; color: #4a5560;
    }

    /* Whole card is tappable: the link's ::after stretches over the card. It must
       resolve against the tr (position:relative), so the link itself must NOT be
       positioned — making it its own containing block would shrink the overlay to
       the size of the text. The checkbox is lifted above it so it stays clickable. */
    table.orders-table > tbody > tr > td[data-label="Order"] .order-link::after {
        content: ""; position: absolute; inset: 0; z-index: 1;
    }

    /* Filters: everything full width, needs-action buttons wrap. */
    .ord-filters { flex-direction: column; align-items: stretch; }
    .ord-filter-search, .ord-filters select, .ord-filters .btn { width: 100%; flex: 1 1 auto; }
    .ord-needs { flex-wrap: wrap; }
    .ord-needs-btn { flex: 0 1 auto; }
}


/* =========================================================================
   CUSTOMER ACCOUNT: order history (/account)

   This table used to carry .orders-table, which is the ADMIN orders grid. Its
   mobile rules place cells by grid-area keyed off data-label values this table
   never had, so on a phone the row became a two-column grid with every cell
   auto-placed on top of its neighbour: order number printed over the date,
   total printed over the Download link, while the six-column header row stayed
   visible above the wreckage. It has its own class and its own card now.
   ========================================================================= */
.account-orders .order-link { color: var(--ink); font-weight: 600; }
.account-orders .order-link:hover { text-decoration: underline; }

@media (max-width: 767px) {
    /* The wrapper's frame and scrollbar belong to the desktop table only: the
       cards carry their own borders, and nothing may scroll sideways. */
    .account-orders-wrap {
        overflow: visible;
        border: none;
        background: transparent;
    }

    /* Labels move inline with the values, so the header row has no job left. */
    table.account-orders > thead { display: none; }
    table.account-orders,
    table.account-orders > tbody { display: block; width: 100%; }

    table.account-orders > tbody > tr {
        display: grid;
        grid-template-areas:
            "num    status"
            "date   date"
            "items  total"
            "pay    pay"
            "inv    inv";
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 6px 12px;
        background: #fff;
        border: 1px solid var(--border);
        border-radius: 11px;
        padding: 14px;
        margin-bottom: 10px;
    }
    table.account-orders > tbody > tr:last-child { margin-bottom: 0; }

    table.account-orders > tbody > tr > td {
        display: block;
        padding: 0;
        border: none;
        text-align: left;
        min-width: 0;
    }

    /* Row 1: order number bold on the left, status pills right. */
    table.account-orders > tbody > tr > td[data-label="Order"] { grid-area: num; }
    .account-orders td[data-label="Order"] .order-link {
        font-family: var(--font-mono);
        font-size: 15px;
        font-weight: 600;
        overflow-wrap: anywhere;   /* a long order number wraps, never overflows */
    }
    table.account-orders > tbody > tr > td[data-label="Status"] {
        grid-area: status;
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        align-items: flex-start;
        gap: 5px;
    }

    /* Row 2: date, muted. */
    table.account-orders > tbody > tr > td[data-label="Date"] {
        grid-area: date;
        font-size: 12.5px;
        color: var(--muted);
        white-space: normal;
    }

    /* Row 3: item count and total, labelled inline, pushed to opposite edges. */
    table.account-orders > tbody > tr > td[data-label="Items"] {
        grid-area: items;
        justify-self: start;
    }
    table.account-orders > tbody > tr > td[data-label="Total"] {
        grid-area: total;
        justify-self: end;
        text-align: right;
        font-size: 15px;
        font-weight: 600;
    }

    /* Row 4: Pay now, full width and above the invoice link — paying is the
       primary action. A paid row's cell is :empty and takes no space. */
    table.account-orders > tbody > tr > td[data-label="Pay"] {
        grid-area: pay;
        margin-top: 4px;
    }
    table.account-orders > tbody > tr > td[data-label="Pay"]:empty { display: none; }
    .account-orders td[data-label="Pay"] .pay-now-link { width: 100%; }

    /* Row 5: the invoice link, on its own line under a hairline. */
    table.account-orders > tbody > tr > td[data-label="Invoice"] {
        grid-area: inv;
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 12px;
        margin-top: 4px;
        padding-top: 10px;
        border-top: 1px solid var(--border);
    }
    .account-orders td[data-label="Invoice"] .link-action { white-space: nowrap; }

    /* The inline labels the hidden header row used to provide. */
    table.account-orders > tbody > tr > td[data-label="Items"]::before,
    table.account-orders > tbody > tr > td[data-label="Total"]::before,
    table.account-orders > tbody > tr > td[data-label="Invoice"]::before {
        content: attr(data-label);
        margin-right: 8px;
        font-family: var(--font-ui);
        font-size: 11px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: .06em;
        color: var(--muted-2);
    }
}


/* ---- Order pages: payment above the invoice on mobile -------------------
   Paying is the primary action on an unpaid order, so on a phone the payment
   panel is lifted above the invoice download on both the confirmation page and
   the account order page. Desktop order is unchanged. Only the page header
   elements are pinned above it; everything else keeps its document order. */
@media (max-width: 767px) {
    .order-page { display: flex; flex-direction: column; }
    .order-page > .conf-check,
    .order-page > .page-h1,
    .order-page > .page-intro,
    .order-page > .conf-number,
    .order-page > .order-head,
    .order-page > .link-action:first-child { order: -2; }
    .order-page > .orderpay-panel { order: -1; }
}


/* =========================================================================
   Orders views — admin list + customer history (design_mockup_orders).
   One visual language for both: dark stat bar, tab bar with live counts,
   single-line rows, four pill tones. Presentation only.
   ========================================================================= */
/* The admin shell's content column is a flex/grid item; without min-width:0 the
   tab row's intrinsic width expands it and the whole page scrolls sideways.

   SCOPED TO .admin-body deliberately. Unscoped, the max-width:100% here sat later
   in the file than .page-inner's max-width:1240px at the same specificity, so it
   won the cascade on the ONE customer page that also carried .ordv (/account) and
   ran that page edge to edge. This rule solves an admin layout problem and has no
   business reaching a storefront page. Every admin view renders through
   admin_head.php, which sets <body class="admin-body">, so the scope is exact. */


/* Stat bar */

/* A money figure must never break mid-number — "$660.75" wrapping to "$660" /
   ".75" reads as a different amount. overflow-wrap: anywhere did exactly that once
   six stats shared the bar, so the value stays on one line and the type scales
   down with the bar instead. */


/* Card + tabs */

/* position:relative makes this scroll container the containing block for its
   absolutely-positioned descendants. Without it the .sr-only labels inside the
   tool buttons are positioned against a far-off ancestor, escape the scroller's
   overflow entirely (overflow only clips descendants whose containing block is
   inside it), and their static position at the scrolled-out end of the row
   extends the whole document. */

/* Tabs are <a> on the orders page and <button> on products/inventory. The rule
   was written for anchors and never reset the button UA appearance, so the
   <button> ones inherited the browser's grey buttonface + outset border and read
   as solid grey buttons with a shadow. Resetting appearance/background/border
   here makes both elements render identically; .is-on then re-applies the white
   fill and ring. No !important, and nothing outside .ordv-tab is touched. */

.ordv-tab.is-on { background: #fff; color: #1c1c1e; font-weight: 600; box-shadow: 0 0 0 1px #e0e0e4, 0 1px 2px rgba(0,0,0,.05); }
.ordv-tcount.warn { background: #ffe2b8; color: #8a5a00; }
.ordv-tcount.info { background: #dbe7fd; color: #1a4fa8; }
.ordv-tab.is-on .ordv-tcount { background: #2E5BE0; color: #fff; }

/* Collapsible search + filters (same controls as before) */

.ordv-filters .btn { min-height: 44px; }

/* Table */
/* position:relative so this scroller is a containing block for the .sr-only
   labels in the header cells. .sr-only is position:absolute, and an absolutely
   positioned box escapes overflow:hidden on any ancestor that is not itself
   positioned. With .ordv-card unpositioned, those 1px spans were resolving
   against the page and sitting at the right edge of the table's full (unscrolled)
   width, which pushed the DOCUMENT wider than the viewport whenever the table was
   wider than its card: 68px of horizontal page scroll at 1024px before this task,
   120px once the selection column widened the table. Contained here, the table
   scrolls inside the wrap as intended and the page itself never scrolls
   sideways. */


/* Pills */

/* Balance states. part = money owed to us, due = money owed BY us — deliberately
   different hues so the two directions are never confused at a glance. */


/* Carrier badge + tracking state */
/* Fulfilment cell, Option A: pill, carrier badge and tracking text on one line.
   The cell wraps so it degrades in a narrow column, but .ordv-fulgrp keeps the
   pill and its carrier badge welded together, so the badge can never end up
   orphaned on a line of its own. Only the tracking text is allowed to move. */

/* Unchanged: still used by the customer order history in views/account.php. */
.ordv-carrier { display: flex; align-items: center; gap: 7px; margin-top: 7px; font-size: 11.5px; color: #8a8a90; }
/* Option A sizing, scoped to the admin orders list so the customer page keeps its own. */

/* Payment-method chip. Same shape, size and weight as the carrier badge above so
   the payment and fulfilment columns read as one family. Colour comes from the
   brand map inline (payment_method_brand), because PayPal needs black text and a
   per-brand class per provider would drift from the pay buttons. */

/* Balance under the payment pill. Colour matches the pill direction: amber when the
   customer owes us, pink when we owe them. */


/* ---- payment deadline, orders list -------------------------------------
   Only rendered under 12 hours, so this is never a line on every unpaid row.
   Same two colours the clock uses at those thresholds. */

/* Darker than the pill fills on purpose. These are 11px text on a white row, not
   a solid pill, and the pale reds that work on the dark countdown panel wash out
   here. The urgent one has to read as MORE urgent than the amber above it, which
   the pale #ff8b8b did not. */
.ordv-clock.is-warn   { color: #9a6a00; }
.ordv-clock.is-urgent { color: #c22f2f; }

/* ---- payment deadline, order detail ------------------------------------
   Sits under the balance in the totals card, because how long the order has and
   how much is owed are the two halves of deciding whether to chase somebody.

   Tinted rather than solid: this is information about an order that is still
   perfectly alive, and a solid red block next to a balance reads as an error that
   has already happened. The bar is the only filled element. */


.ordcd.is-warn { background: #fdf8e6; border-color: #efdfa8; }
.ordcd.is-warn .ordcd-l { color: #8a5c00; }
.ordcd.is-warn .ordcd-bar { background: rgba(245, 197, 24, 0.22); }
.ordcd.is-warn .ordcd-bar i { background: #f5c518; }
.ordcd.is-warn .ordcd-sub { color: #6b5200; }

.ordcd.is-urgent { background: #fdf0f0; border-color: #f0c4c4; }
.ordcd.is-urgent .ordcd-l { color: #96201f; }
.ordcd.is-urgent .ordcd-clock { color: #96201f; }
.ordcd.is-urgent .ordcd-bar { background: rgba(216, 59, 59, 0.18); }
.ordcd.is-urgent .ordcd-bar i { background: #d83b3b; }
.ordcd.is-urgent .ordcd-sub { color: #96201f; }

/* PAST DUE. Its own state, not the bottom of the urgent one: there is no time
   left to be running short of. Solid red on the bar because the deadline has
   actually gone, and the clock counts up to say how long ago. Nothing here
   claims the order is cancelled: the sweep decides that on its own schedule,
   which is what the sentence underneath explains. */
.ordcd.is-past { background: #fdf0f0; border-color: #e8a9a9; }
.ordcd.is-past .ordcd-l { color: #96201f; font-weight: 700; }
.ordcd.is-past .ordcd-clock { color: #96201f; }
.ordcd.is-past .ordcd-bar { background: rgba(216, 59, 59, 0.18); }
.ordcd.is-past .ordcd-bar i { background: #d83b3b; }
.ordcd.is-past .ordcd-sub { color: #96201f; }

/* Order history (audit trail). One line per change, newest first. Wraps rather
   than scrolls so a long summary is never clipped on a phone.
   The separator and the meta line were rgba(255,255,255,...) but this list has
   only ever rendered on white (.admin-section on the order page, .invv-detail on
   the inventory page), so the rule was translucent white on white: the timestamp
   and author of every order event were invisible. Opaque greys, same values the
   rest of the admin uses (#eeeef0 hairline, #6b6b70 muted text at 5.3:1). */

/* Raw phone + copy button on the order page. Wraps rather than overflowing: a long
   international number plus the button must not push the card wide at 360px. */


/* Manual payment reminder state, under the order row actions. Solid pill, no
   transparency, palette yellow. Wraps rather than pushing the row wide at 360px. */


/* "Not tracking": shipped, but the carrier is reporting nothing. Same solid palette
   yellow and the same weight as the reminder chip above, because it says the same
   kind of thing: this row needs a human. Lives inside .ordv-fulgrp so it can never
   split away from the pill it qualifies. */


/* Why a cancelled order is cancelled, in the fulfilment column, which is empty
   for a cancelled order anyway. Two solid tones and no border: dark grey when a
   person cancelled it, red when the unpaid-order expiry sweep did, because those
   two need telling apart at a glance now that the sweep runs on its own. The full
   sentence, the reason and the timestamp are on the title attribute. */


/* Order line editing. Wraps down the page rather than scrolling sideways, so every
   control stays reachable at 320px. */


/* Refund ledger + recording form. Wraps at every width; nothing scrolls sideways. */

/* Payments-by-method strip. Wraps rather than scrolls, so every method stays
   visible at any width without a hidden overflow. */

.ordv-mbadge {
    display: inline-block;
    font-size: 9.5px; font-weight: 700; padding: 2.5px 7px; border-radius: 5px;
    letter-spacing: .03em; white-space: nowrap;
}
/* A customer's unverified claim: same chip, dimmed and outlined so it never reads
   as a confirmed fact. */
.ordv-mbadge.is-claim { opacity: .55; box-shadow: inset 0 0 0 1px rgba(255,255,255,.35); }

/* Customer actions */
.ordv-actions { white-space: nowrap; }
.ordv-link { color: #2E5BE0; font-weight: 600; margin-right: 18px; display: inline-flex; align-items: center; min-height: 44px; }
.ordv-link:last-child { margin-right: 0; }
.ordv-link:hover { text-decoration: underline; }
.ordv-paynow {
    display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
    background: #2E5BE0; color: #fff; font-size: 13px; font-weight: 600; padding: 8px 16px;
    border-radius: 9px; box-shadow: 0 1px 2px rgba(46, 91, 224, .3);
}
.ordv-paynow:hover { background: #264fc4; color: #fff; text-decoration: none; }

/* Footer */
/* One plain sentence stating the basis of the money figures above it, rather than a
   qualifier bolted onto every stat label. */


/* Admin shell paints a dark surface; these two pages are light cards on it. */
.admin-shell .ordv-title { color: #1c1c1e; }

/* -------- Mobile: rows become stacked cards -------- */
@media (max-width: 780px) {
    /* Tab row scrolls; the tools scroll with it rather than sitting on top of
       the tabs, which is what a sticky block did at 375px. */
    
    /* Explicit grid placement: every cell gets its own track, so nothing can
       land on top of anything else. Col 1 is the checkbox gutter (admin only). */
    
    /* Customer · date · items share one muted line. */
    
    /* Separator only between meta cells, never leading. The customer table has
       no Customer column, so its Date cell is first and takes no separator. */
    
    /* Status pills, then the carrier line under them. */
    
    /* Option A keeps the badge beside the pill at every width; only the tracking
       text drops to its own line when the card is narrow. account.php keeps its
       own stacked treatment. */
    
    .ordv-carrier { flex-basis: 100%; margin-top: 2px; }
    .ordv-actions { grid-column: 1 / -1; margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }
    .ordv-link { margin-right: 0; }
    /* Customer table has no checkbox column, so everything shifts left one track. */
    .ordv-table-account tbody tr { grid-template-columns: minmax(0,auto) minmax(0,auto) minmax(0,1fr); }
    .ordv-table-account tbody td[data-label] { grid-column: auto; }
    .ordv-table-account tbody tr > td:nth-child(1) { grid-column: 1 / 3; grid-row: 1; }
    .ordv-table-account tbody tr > td:nth-child(3) { grid-column: 3;     grid-row: 1; text-align: right; }
    .ordv-table-account tbody tr > td:nth-child(2) { grid-column: 1;     grid-row: 2; }
    .ordv-table-account tbody tr > td:nth-child(5) { grid-column: 2 / -1; grid-row: 2; }
    .ordv-table-account tbody tr > td:nth-child(4) { grid-column: 1 / -1; grid-row: 3; }
    .ordv-table-account tbody tr > td:nth-child(6) { grid-column: 1 / -1; grid-row: 4; }
}

@media (max-width: 380px) {   /* two columns per row: room for full size */
    
}


/* =========================================================================
   Admin dashboard (design_mockup_admin/pcd-dashboard-premium.html).
   Reuses the orders-view shell (.ordv-card, .ordv-stats, .ordv-table).
   ========================================================================= */

/* The dashboard gained a Billed tile beside Received, so it is 8 across now. */

/* The orders bar is the range selector plus 8 figures. Without this it inherits the
   7-column default and the last tile wraps onto a half-empty second row. */

/* Eight or nine cells do not fit across a laptop content column: at 1024 the money
   values ran into each other. Between tablet and full desktop the bar wraps to four
   per row instead of squeezing, so every figure stays readable and none is hidden. */

.dash-kv-v.is-warn { color: #b07d00; }
.dash-kv-v.is-bad { color: #d83b3b; }

/* Solid status pills (shared design language). Overrides the pastel tones for
   the admin only; the customer-facing pages keep the softer set. */
.admin-shell .ordv-pill { font-weight: 650; padding: 5px 11px; }
.admin-shell .ordv-pill .ordv-dot { display: none; }
.admin-shell .ordv-pill.t-ok      { background: #1fa863; color: #fff; border-color: #1a9457; }
.admin-shell .ordv-pill.t-warn    { background: #f5c518; color: #1c1c1e; border-color: #dfb100; }
.admin-shell .ordv-pill.t-info    { background: #2E5BE0; color: #fff; border-color: #2549b8; }
.admin-shell .ordv-pill.t-neutral { background: #54545a; color: #fff; border-color: #45454a; }
.admin-shell .ordv-pill.t-bad     { background: #d83b3b; color: #fff; border-color: #c22f2f; }
.admin-shell .ordv-pill.t-part    { background: #d97a1f; color: #fff; border-color: #c06a15; }
.admin-shell .ordv-pill.t-due     { background: #c2286f; color: #fff; border-color: #a81f5e; }

/* =========================================================================
   Admin products list (design_mockup_admin/pcd-products-premium.html).
   Builds on the shared .ordv-* shell.
   ========================================================================= */


/* Bulk bar extras: the category picker sits inline with the action links and wraps
   onto its own line on a narrow screen rather than squeezing the buttons. */


/* Row-menu category picker. A labelled select inside the actions popover, styled to
   sit with the menu items rather than look like a form dropped into them. */


/* Per-row actions menu — <details>, so it needs no JavaScript. */


@media (max-width: 780px) {
    /* Every index below shifted by one when the select checkbox became column 1.
       The comments are the contract: if a column is ever added or removed again,
       these have to move with it or the card layout silently scrambles. */
           /* select */
           /* drag */
           /* thumb */
           /* product */
           /* actions */
      /* price */
      /* category */
      /* stock */
    
}


/* =========================================================================
   Admin inventory (design_mockup_admin/pcd-inventory-premium.html).
   Grouped by product; builds on the shared .ordv-* shell.
   ========================================================================= */


/* Receive stock: the controls in a row's movement panel. They belong to their
   own per-variant form (form=), not to #inv-form. Wraps down rather than
   scrolling sideways, so every control stays reachable at 320px. */

/* The receive forms carry only CSRF + variant id; their controls live in the
   panels above. Nothing to show. */


/* Movement history inside a row panel. Reuses the order page's .ordv-hist
   treatment rather than inventing a second one. */

.invv-delta.is-out { color: #d83b3b; }

/* Movement reason pills. Solid fills from the palette, no transparency. */


/* "Email the customer" tick box on the cancel action, and its bulk twin. Solid
   controls, no transparency; wraps rather than pushing the action row wide. */


/* Cart: a line asking for more than is available, and the one-shot notice after a
   line has been capped. Both are plain solid blocks, no tint filters. */
.cart-line-short {
    margin: 4px 0 0; font-size: 12.5px; line-height: 1.5; font-weight: 600; color: #d83b3b;
}
.cart-short-line { margin: 0; }
.cart-short-line + .cart-short-line { margin-top: 6px; }
.alert-notice {
    background: #fdf8e6; border: 1px solid #efdfa8; color: #6b5200;
    padding: 12px 14px; margin: 0 0 16px; font-size: 13.5px; line-height: 1.55;
}

/* Inventory bulk bar controls. Both are sized to hold their own placeholder:
   at 96px the threshold field clipped "Threshold" to "Threshol" and the
   availability select clipped to "Availabilitv...". */

/* Wide enough for the longest availability label, "Continue selling when out of
   stock", once it is the SELECTED option. The closed placeholder is short, so this
   only matters after a choice is made, which is exactly when clipping it would be
   worst. max-width on .bulk-cat keeps it inside the bar at 360px. */

/* Same label, in the per-row availability picker. */


@media (max-width: 780px) {
    /* A checkbox gutter plus the same two content tracks as before, so the rest of
       the card auto-flows exactly as it always has.
       NOTE ON SPECIFICITY: the reset below is (0,2,3) and beats a bare .invv-size
       or [data-label="..."] rule, so anything that genuinely needs placing has to
       be written in the same `tr.invv-row > .class` form to bind at all. */
    
    /* Written in the binding `tr.invv-row > ...` form for the reason noted above:
       as plain [data-label] rules these lost to the reset and never applied, which
       left the stepper sharing a narrow middle track and clipping its own digits. */
    
    /* Status and the two action buttons share the closing line: the pill on the
       left, the buttons hard right. Giving each its own full-width line left an
       empty band across the card at 768px. */
    
    /* The actions cell was pinned to the top right, out of flow. That worked while
       it held one 30px button; with two it covered the price and the stepper at
       360px. Given its own full-width line at the foot of the card it cannot
       collide with anything, and no other cell has to reserve space for it. */
    
    /* Detail rows are one full-width panel, not a grid. Without this they inherit
       the card grid from .ordv-table tbody tr and the panel is squeezed into the
       first track. Applies to the cost/threshold panel as well as the movement one. */
    
}


/* Status pill menus on the orders list + inline price correction. */

.pillmenu-item {
    -webkit-appearance: none; appearance: none; background: none; border: 0;
    display: flex; align-items: center; width: 100%; min-height: 44px; padding: 8px 10px;
    border-radius: 7px; font-family: inherit; font-size: 13px; font-weight: 500;
    color: #1c1c1e; text-align: left; cursor: pointer;
}
.pillmenu-item:hover { background: #f6f6f7; }
.pillmenu-item.is-current { font-weight: 650; color: #2E5BE0; }
.pillmenu-item.is-current::after { content: "✓"; margin-left: auto; }
.ordprice-wrap { display: inline-flex; align-items: center; gap: 1px; }

/* =========================================================================
   Admin users & accounts (design_mockup_admin/pcd-users-premium.html).
   ========================================================================= */

/* Solid, matching the weight and padding of the other solid status pills. */

/* The row menu holds small forms, so it is wider and its fields stack. */


/* =========================================================================
   Admin messages (design_mockup_admin/pcd-messages-premium.html). Cards.
   ========================================================================= */

/* Collapsed: exactly one line of body. */

.msgv-card.is-open .msgv-excerpt { display: none; }
.msgv-actions .btn { min-height: 44px; }

/* =========================================================================
   Contact page (design_mockup_admin/pcd-contact-premium.html). Form + sidebar.
   ========================================================================= */
.ctv { max-width: 1180px; }
.ctv-cols { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr); gap: 22px; align-items: start; margin-top: 22px; }
.ctv-main { min-width: 0; }
.ctv-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.ctv-card { background: #fff; border: 1px solid #e6e6e9; border-radius: 14px; box-shadow: 0 1px 3px rgba(0,0,0,.04); overflow: hidden; }
.ctv-cardhead { padding: 18px 20px; border-bottom: 1px solid #eeeef0; }
.ctv-cardtitle { margin: 0; font-size: 17px; font-weight: 680; letter-spacing: -0.01em; color: #1c1c1e; }
.ctv-cardsub { margin: 4px 0 0; font-size: 12.5px; line-height: 1.55; color: #6b6b70; }
.ctv-form { padding: 20px; }
/* Focus state on every control in the form. */
.ctv-form input[type="text"], .ctv-form input[type="email"], .ctv-form input[type="tel"],
.ctv-form select, .ctv-form textarea { min-height: 44px; }
.ctv-form input:focus, .ctv-form select:focus, .ctv-form textarea:focus {
    outline: none; border-color: #2E5BE0; box-shadow: 0 0 0 3px rgba(46, 91, 224, .12);
}
.ctv-submit { background: #2E5BE0; color: #fff; border: none; border-radius: 10px; min-height: 48px; font-weight: 600; }
.ctv-submit:hover { background: #264fc4; color: #fff; }

/* Research-use notice: same dark ground as the admin stat bar. */
.ctv-notice {
    display: flex; gap: 12px; align-items: flex-start; margin: 18px 0;
    background: linear-gradient(180deg, #16181d, #101216);
    border: 1px solid #23252b; border-radius: 11px; padding: 14px 16px;
}
.ctv-notice-ico { flex: 0 0 auto; color: #f5c518; font-size: 15px; line-height: 1.4; }
.ctv-notice-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ctv-notice-h { font-size: 12.5px; font-weight: 650; color: #fff; }
.ctv-notice-t { font-size: 12.5px; line-height: 1.6; color: rgba(255,255,255,.62); }

/* Sidebar: dark chat card. */
.ctv-dark { background: linear-gradient(180deg, #16181d, #101216); border: 1px solid #23252b; border-radius: 14px; padding: 18px 20px; }
.ctv-badge { display: inline-flex; align-items: center; gap: 7px; font-size: 11px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; border-radius: 999px; padding: 5px 11px; }
.ctv-badge.is-open { background: rgba(31,168,99,.16); color: #4fd08a; }
.ctv-badge.is-closed { background: rgba(255,255,255,.08); color: rgba(255,255,255,.62); }
.ctv-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.ctv-dark-h { margin: 12px 0 6px; font-size: 16px; font-weight: 680; color: #fff; letter-spacing: -0.01em; }
.ctv-dark-t { margin: 0; font-size: 12.5px; line-height: 1.6; color: rgba(255,255,255,.62); }
.ctv-dark-hours { margin: 8px 0 0; font-size: 11.5px; color: rgba(255,255,255,.45); }
.ctv-chatbtn {
    -webkit-appearance: none; appearance: none; margin-top: 14px; width: 100%; min-height: 44px;
    background: #fff; color: #1c1c1e; border: none; border-radius: 9px;
    font-family: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
}
.ctv-chatbtn:hover { background: #f1f1f3; }

/* Sidebar: details + FAQ. */
.ctv-details, .ctv-faq { padding: 16px 18px; }
.ctv-sideh { margin: 0 0 12px; font-size: 14px; font-weight: 680; color: #1c1c1e; }
.ctv-row { display: flex; align-items: center; gap: 11px; padding: 8px 0; min-height: 44px; color: inherit; }
a.ctv-row:hover { text-decoration: none; }
a.ctv-row:hover .ctv-rowv { color: #2E5BE0; }
.ctv-ico { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 36px; height: 36px; border-radius: 10px; background: #eaf1fe; color: #2E5BE0; font-size: 15px; }
.ctv-rowbody { display: flex; flex-direction: column; min-width: 0; }
.ctv-rowl { font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: #8a8a90; }
.ctv-rowv { font-size: 13px; color: #1c1c1e; overflow-wrap: anywhere; }
.ctv-faqlink { display: flex; align-items: center; min-height: 44px; padding: 6px 0; font-size: 13px; color: #2E5BE0; font-weight: 500; border-bottom: 1px solid #f4f4f6; }
.ctv-faqlink:last-child { border-bottom: none; }
.ctv-faqlink:hover { text-decoration: underline; }

@media (max-width: 900px) {
    .ctv-cols { grid-template-columns: 1fr; }
    .ctv-side { order: 2; }
}


/* =========================================================================
   PUBLIC PAY PAGE  (/order-pay/{token})  — .opay-*
   Mobile first, matching design_mockup_admin/pcd-pay-page-redesign.html and
   pcd-pay-page-desktop.html. Scoped entirely to .opay so it cannot reach the
   confirmation or account order pages, which still use payment_step.php.
   No backdrop-filter anywhere: the chooser backdrop is a flat rgba fill.
   ========================================================================= */
.opay { background: #f2f2f4; min-height: 100vh; padding: 22px 18px 40px; }
.opay-inner { max-width: 1180px; margin: 0 auto; }

.opay-head { margin-bottom: 18px; }
.opay-eyebrow { display: flex; align-items: center; gap: 6px; margin: 0 0 7px;
    font-size: 11px; font-weight: 650; letter-spacing: .09em; text-transform: uppercase; color: #1fa863; }
.opay-tick { width: 15px; height: 15px; border-radius: 50%; background: #1fa863; color: #fff;
    font-size: 9px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.opay-h1 { font-size: 21px; font-weight: 680; letter-spacing: -0.02em; margin: 0 0 5px; color: #1c1c1e; }
.opay-ordn { font-size: 12.5px; color: #8a8a90; margin: 0; }
.opay-note { font-size: 13.5px; color: #3a3a40; line-height: 1.6; }

/* How long the order is held. Quiet by design: it is information, not a warning,
   so it reads at the weight of a footnote and carries no colour of its own. */
.opay-hold {
    margin: 12px 0 0; font-size: 12.5px; line-height: 1.6; color: #6b6b74;
}

/* 1. Amount due. First thing on screen. */
.opay-due { background: linear-gradient(180deg, #16181d, #101216); border-radius: 14px;
    padding: 18px 20px; margin-bottom: 18px; }
.opay-due-l { font-size: 11.5px; color: rgba(255,255,255,.5); font-weight: 500; margin: 0 0 6px; }
.opay-due-v { font-size: 31px; font-weight: 680; color: #fff; letter-spacing: -0.025em; margin: 0; line-height: 1; }
.opay-due-r { font-size: 11.5px; color: rgba(255,255,255,.42); margin: 8px 0 0; line-height: 1.6; }
.opay-due-r b { color: #fff; display: block; font-size: 12.5px; font-weight: 650; margin-bottom: 2px; }

/* ---- payment countdown -------------------------------------------------
   Lives INSIDE the amount due panel, directly under the number, so it is the
   second thing read and cannot be scrolled past.

   Three states: blue over 12 hours, yellow under 12, red under 3. The colours
   are tints of the palette reds and yellows rather than the flat pills, because
   this sits on the dark panel where a solid #d83b3b block would read as an error
   rather than as a clock. The bar underneath is the only solid fill.

   Server-rendered first, then JS ticks it. Every rule here applies equally to the
   no-JS render, so a page with scripting off shows the same block, just static. */
.opay-due.has-cd { padding-bottom: 20px; }
.opay-cd { margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.12); }
.opay-cd-l {
    margin: 0; font-size: 11.5px; font-weight: 700; letter-spacing: 0.09em;
    text-transform: uppercase; color: rgba(255, 255, 255, 0.55);
}
.opay-cd-t { display: flex; gap: 8px; align-items: flex-end; margin-top: 9px; }
.opay-cd-u {
    background: rgba(255, 255, 255, 0.09); border-radius: 9px; padding: 8px 11px;
    text-align: center; min-width: 56px;
}
.opay-cd-n {
    display: block; font-family: var(--font-mono); font-size: 23px; font-weight: 700;
    line-height: 1; color: #fff; font-variant-numeric: tabular-nums;
}
.opay-cd-x {
    display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.07em;
    text-transform: uppercase; color: rgba(255, 255, 255, 0.45); margin-top: 5px;
}
.opay-cd-bar {
    margin-top: 14px; height: 5px; border-radius: 99px;
    background: rgba(255, 255, 255, 0.12); overflow: hidden;
}
.opay-cd-bar i {
    display: block; height: 100%; border-radius: 99px; background: #2E5BE0;
    transition: width 1s linear;
}
.opay-cd-sub {
    margin: 11px 0 0; font-size: 12.5px; line-height: 1.55; color: rgba(255, 255, 255, 0.55);
}

.opay-cd.is-warn .opay-cd-l { color: #ffd75e; }
.opay-cd.is-warn .opay-cd-u { background: rgba(245, 197, 24, 0.16); }
.opay-cd.is-warn .opay-cd-n { color: #ffd75e; }
.opay-cd.is-warn .opay-cd-bar i { background: #f5c518; }

.opay-cd.is-urgent .opay-cd-l { color: #ff8b8b; }
.opay-cd.is-urgent .opay-cd-u { background: rgba(216, 59, 59, 0.2); }
.opay-cd.is-urgent .opay-cd-n { color: #ff8b8b; }
.opay-cd.is-urgent .opay-cd-bar i { background: #d83b3b; }

/* PAST DUE, on the dark panel. Same reasoning as the admin clock: a distinct
   state rather than the far end of "urgent", with the digits counting up. The
   customer can still pay and save the order until the sweep runs, so this is
   firm without being final. */
.opay-cd.is-past .opay-cd-l { color: #ff8b8b; font-weight: 700; }
.opay-cd.is-past .opay-cd-u { background: rgba(216, 59, 59, 0.28); }
.opay-cd.is-past .opay-cd-n { color: #ff8b8b; }
.opay-cd.is-past .opay-cd-bar i { background: #d83b3b; }
.opay-cd.is-past .opay-cd-sub { color: #ffb3b3; }

@media (prefers-reduced-motion: reduce) {
    .opay-cd-bar i { transition: none; }
}

@media (max-width: 390px) {
    /* Three units plus gaps need 56px each at minimum; below this they share the
       row evenly instead of overflowing it. */
    .opay-cd-u { min-width: 0; flex: 1 1 0; padding: 8px 6px; }
    .opay-cd-n { font-size: 21px; }
}


/* Steps */
.opay-step { display: flex; align-items: center; gap: 9px; margin: 0 0 12px; }
.opay-snum { width: 21px; height: 21px; border-radius: 50%; background: #2E5BE0; color: #fff;
    font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.opay-stxt { font-size: 13.5px; font-weight: 650; color: #1c1c1e; }
/* Desktop-only, per the mockups: the mobile step row is just "Send payment". */
.opay-shint { display: none; font-size: 12.5px; color: #8a8a90; margin-left: auto; }

/* 2. Payment buttons */
.opay-pgrid { display: grid; grid-template-columns: 1fr; gap: 9px; margin-bottom: 9px; }
.opay-pay { border: none; border-radius: 11px; height: 50px; display: flex; align-items: center;
    justify-content: center; font-size: 14px; font-weight: 650; letter-spacing: -0.01em;
    text-decoration: none; cursor: pointer; font-family: inherit; width: 100%; }
.opay-pay:hover { text-decoration: none; filter: brightness(.96); }
.opay-pay.is-soon { background: #e4e4e8; color: #8a8a90; cursor: default; }

/* Zelle detail block. Content unchanged from the previous version. */
.opay-zdrop { background: #faf9fd; border: 1px solid #e6e2f2; border-radius: 11px; padding: 13px 14px; margin-bottom: 11px; }
.opay-zdrop-h { font-size: 12.5px; font-weight: 650; margin: 0 0 9px; display: flex; align-items: center; gap: 7px; }
.opay-zdrop-h span { width: 6px; height: 6px; border-radius: 50%; background: #6D1ED4; }
.opay-zrow { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 5px 0; font-size: 12.5px; }
.opay-zl { color: #8a8a90; flex-shrink: 0; }
.opay-zv { font-weight: 650; font-family: ui-monospace, Menlo, monospace; word-break: break-all; }
.opay-cpy { font-size: 10.5px; color: #2E5BE0; font-weight: 650; border: 1px solid #cfdcfa;
    border-radius: 6px; padding: 3px 8px; margin-left: 8px; background: #fff; cursor: pointer; font-family: inherit; }
.opay-zfoot { font-size: 11px; color: #8a8a90; margin: 8px 0 0; line-height: 1.5; }

.opay-memo { background: #fff9ec; border: 1px solid #ffe2b8; border-radius: 10px; padding: 13px 16px;
    font-size: 12.5px; color: #8a5a00; line-height: 1.6; margin: 0 0 4px; }
.opay-memo b { font-weight: 700; }

.opay-sep { display: flex; align-items: center; gap: 11px; margin: 20px 0 16px; color: #b4b4ba; font-size: 11.5px; }
.opay-sep::before, .opay-sep::after { content: ""; height: 1px; background: #e4e4e8; flex: 1; }

/* Step 2 trigger */
.opay-sent { background: #fff; border: 2px solid #1c1c1e; border-radius: 12px; padding: 15px;
    width: 100%; text-align: center; font-size: 14px; font-weight: 650; color: #1c1c1e;
    cursor: pointer; font-family: inherit; }
.opay-sentnote { font-size: 11.5px; color: #8a8a90; text-align: center; line-height: 1.55; margin: 9px 0 0; }
.opay-reported { background: #e8f7ee; border: 1px solid #c9ecd7; border-radius: 11px; padding: 14px;
    font-size: 13px; color: #0f6b3d; line-height: 1.55; margin: 0; }

/* 3. Sidebar: summary + the invoice button. Below the fold on mobile, on purpose. */
.opay-side { background: #fbfbfc; border: 1px solid #eeeef0; border-radius: 14px; padding: 20px; margin-top: 26px; }
.opay-side-t { font-size: 11.5px; font-weight: 650; letter-spacing: .06em; text-transform: uppercase;
    color: #8a8a90; margin: 0 0 14px; }
.opay-li { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0;
    border-bottom: 1px solid #f2f2f4; font-size: 13px; color: #3a3a40; }
.opay-li-v { font-weight: 600; font-variant-numeric: tabular-nums; }
.opay-tot { display: flex; justify-content: space-between; gap: 12px; padding: 13px 0 0; margin-top: 8px;
    border-top: 1.5px solid #1c1c1e; font-size: 15px; font-weight: 680; }
/* Mobile: the actions sit as a compact row at the foot of the page, matching the
   mini button row in pcd-pay-page-redesign.html. The order summary lines are
   desktop-only there, so they are hidden here rather than invented. */
.opay-acts { margin-top: 18px; display: flex; gap: 8px; }
.opay-side .opay-li, .opay-side .opay-tot, .opay-side .opay-side-t { display: none; }
.opay-side { padding: 14px; }
.opay-abtn { flex: 1; display: flex; align-items: center; justify-content: center; border: 1px solid #e0e0e4;
    border-radius: 10px; padding: 11px 8px; font-size: 12px; font-weight: 600; color: #3a3a40;
    background: #fff; text-decoration: none; text-align: center; }
.opay-abtn:hover { border-color: #c8c8ce; background: #fafafb; text-decoration: none; }

/* 4. The chooser. Full screen on mobile, modal on desktop. Same markup. */
.opay-ovl { position: fixed; inset: 0; z-index: 200; background: #f2f2f4;
    display: flex; align-items: flex-start; justify-content: center; padding: 16px; overflow-y: auto; }
.opay-ovl[hidden] { display: none; }
body.opay-lock { overflow: hidden; }
.opay-modal { background: #fff; border-radius: 16px; padding: 22px 20px 26px; width: 100%; max-width: 520px; }
.opay-mh { font-size: 19px; font-weight: 680; letter-spacing: -0.02em; margin: 0 0 5px; color: #1c1c1e; }
.opay-ms { font-size: 13px; color: #6b6b70; line-height: 1.55; margin: 0 0 18px; }
.opay-mgrid { display: grid; grid-template-columns: 1fr; gap: 9px; }
.opay-mcard { display: flex; align-items: center; gap: 13px; border: 1.5px solid #e4e4e8; border-radius: 12px;
    padding: 14px; background: #fff; width: 100%; text-align: left; cursor: pointer; font-family: inherit; }
.opay-mcard.is-on { border-color: #2E5BE0; background: #f7faff; box-shadow: 0 0 0 3px rgba(46,91,224,.1); }
.opay-mswatch { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center;
    justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
.opay-mtext { min-width: 0; }
.opay-mname { display: block; font-size: 14px; font-weight: 650; color: #1c1c1e; }
.opay-mhint { display: block; font-size: 11.5px; color: #8a8a90; margin-top: 1px; word-break: break-all; }
.opay-mradio { width: 20px; height: 20px; border-radius: 50%; border: 1.5px solid #c8c8ce;
    margin-left: auto; flex-shrink: 0; }
.opay-mcard.is-on .opay-mradio { border-color: #2E5BE0; border-width: 6px; }
/* Optional sender handle. Sits between the cards and the confirm button, where it
   reads as part of the same answer rather than a separate form. */
.opay-sender { margin-top: 16px; }
.opay-sender-l { display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    font-size: 12.5px; font-weight: 650; color: #1c1c1e; margin-bottom: 6px; }
.opay-opt { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: #6b6b70; background: #eeeef0; border-radius: 6px; padding: 2px 7px; }
.opay-sender-in { width: 100%; min-height: 46px; border: 1.5px solid #e4e4e8; border-radius: 11px;
    padding: 0 13px; font-size: 14px; font-family: inherit; color: #1c1c1e; background: #fff; }
.opay-sender-in:focus { outline: none; border-color: #2E5BE0; box-shadow: 0 0 0 3px rgba(46,91,224,.1); }
.opay-sender-h { margin: 6px 0 0; font-size: 11.5px; line-height: 1.5; color: #8a8a90; }
.opay-confirm { background: #2E5BE0; color: #fff; border: none; border-radius: 11px; height: 50px;
    width: 100%; display: flex; align-items: center; justify-content: center; font-size: 14.5px;
    font-weight: 650; margin-top: 16px; cursor: pointer; font-family: inherit; }
.opay-confirm:disabled { background: #b9c6ea; cursor: not-allowed; }
.opay-mfoot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-top: 14px; font-size: 12.5px; color: #8a8a90; flex-wrap: wrap; }
.opay-back { background: none; border: none; color: #6b6b70; font-size: 13px; font-family: inherit;
    cursor: pointer; text-decoration: underline; padding: 0; }

/* -------- Desktop: two columns, buttons in pairs, chooser becomes a modal -------- */
@media (min-width: 900px) {
    .opay { padding: 30px; }
    .opay-h1 { font-size: 28px; letter-spacing: -0.025em; }
    .opay-ordn { font-size: 13px; }
    .opay-head { margin-bottom: 26px; }
    .opay-cols { display: grid; grid-template-columns: 1fr 340px; gap: 32px; align-items: start; }
    .opay-side { margin-top: 0; padding: 22px; }
    .opay-side .opay-li, .opay-side .opay-tot { display: flex; }
    .opay-side .opay-side-t { display: block; }
    .opay-acts { display: block; }
    .opay-abtn { padding: 12px 14px; font-size: 13px; margin-bottom: 8px; }
    .opay-shint { display: inline; }
    .opay-due { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 22px 26px; margin-bottom: 26px; }
    .opay-due-v { font-size: 36px; }
    .opay-due-r { text-align: right; margin: 0; }
    .opay-pgrid { grid-template-columns: 1fr 1fr; gap: 11px; }
    .opay-pay { height: 54px; font-size: 14.5px; }
    .opay-sent { width: auto; display: inline-flex; padding: 15px 32px; font-size: 15px; }
    .opay-sentnote { text-align: left; font-size: 12.5px; }
    /* Now a true overlay: dimmed backdrop, centred panel. Flat fill, no blur. */
    .opay-ovl { background: rgba(16,18,22,.55); align-items: center; padding: 44px; }
    .opay-modal { padding: 30px 32px; box-shadow: 0 20px 60px rgba(0,0,0,.25); }
    .opay-mh { font-size: 22px; }
    .opay-ms { font-size: 13.5px; margin-bottom: 22px; }
    .opay-mgrid { grid-template-columns: 1fr 1fr; gap: 10px; }
    .opay-mcard .opay-mradio { display: none; }
}


/* =========================================================================
   Admin referrals (design_mockup_admin/pcd-referrals-mockup.html).
   Reuses the orders-view shell (.ordv-card, .ordv-stats, .ordv-table, .rowmenu)
   and the users-view name cell (.usrv-nwrap/.usrv-avatar), so only the pieces
   this page adds are defined here.
   ========================================================================= */


/* ---- inline edit panel --------------------------------------------------
   Lives in a second <tr> under the row it edits, so the form is attached to the
   link it changes rather than floating at the top of the page. The cell is a
   containing block with its own width, which stops the form's fields from
   widening the table they sit inside. */

.refv-edit-grid .field { min-width: 0; margin: 0; }
/* The slug, shown as a fact rather than an input. See Referrals::update(). */


/* Owner cell. Avatar colours follow the kind, not the account role, because the
   Type column is what a reader is matching them against. */
.refv-av-rep      { background: #6D1ED4; }
.refv-av-customer { background: #2E5BE0; }

/* Type chips, solid to match .usrv-chip and the solid admin pills. */


/* Link + code, each with its own copy button. */

.refv-cpy.is-done { background: #e8f7ee; color: #0f6b3d; border-color: #c9ecd7; }

/* Create panel: live preview of the link and code that are about to exist. */


/* Detail page: the id line beside the title, and section heads reusing the tab bar. */

.refv-table tbody td.num { font-variant-numeric: tabular-nums; }

/* Nine columns is wide. Between tablet and full desktop the cells tighten so the
   table fits the content column instead of relying on the horizontal scroll that
   .ordv-tablewrap would otherwise provide. */
@media (min-width: 781px) and (max-width: 1240px) {
    /* The address drives the Owner column's width. Clipping it keeps nine columns
       inside the content area instead of pushing Earned into the scroller. */
    
}


@media (max-width: 780px) {
    /* Stacked cards, the users-page treatment: reset the orders-specific
       placements first, then lay out this table's own cells. */
    /* Two equal tracks, not "1fr auto": the five number cells fill them in pairs,
       and an auto second track would shrink-wrap each right-hand figure onto the
       card edge, far from the label it belongs with. */
    
    /* The four numbers share one muted line, each keeping its header as a label. */
    
    /* Detail tables. Their Order/Date/Revenue labels are the same strings the
       orders list places explicitly, so those placements are reset first, then
       each cell is given its own track: order number, then date and source, then
       the two money figures. */
    
    /* Matches the specificity of the orders list's :not() rule that adds a leading
       "· " to its Date cell; without .ordv-table here that rule wins and the date
       renders with an orphan separator in front of it. */
    
}


/* =========================================================================
   Admin abandoned carts. Reuses the orders-view shell (.ordv-card,
   .ordv-stats, .ordv-table) and the users-view name cell
   (.usrv-nwrap/.usrv-avatar), so only what this page adds is defined here.
   Pills are the shared solid set: no tones are introduced.
   ========================================================================= */


/* Nothing on a row is clickable, so the shared list's pointer cursor would be a
   lie. The hover tint stays: it still helps track a row across six columns. */


/* ---- selection and delete ----------------------------------------------
   The bulk bar is rendered hidden and only revealed once something is ticked,
   so it is never an always-present control that does nothing most of the time. */

/* Text button on the row, not a red block. Twelve red buttons down a list reads as
   twelve problems; the bulk bar is where the emphatic version belongs. */


@media (max-width: 900px) {
    /* Rows are stacked cards at this width, so the select box needs its own label
       rather than sitting as a bare tick in a column that no longer exists. */
    
}


/* The cart contents. A list rather than a run-on string, because two or three
   lines at a glance is the whole reason this column exists. */


/* Left out of both the value beside it and the email. Said plainly, as a note
   rather than a badge: the only pills on this page are the two status columns,
   and a third tinted chip here would read as a status it is not. */

.acv-table tbody td.num { font-variant-numeric: tabular-nums; }

/* Between tablet and full desktop the cells tighten so six columns fit the
   content column instead of falling back on .ordv-tablewrap's sideways scroll. */


@media (max-width: 780px) {
    /* Stacked cards. The orders list places its own labels explicitly, so those
       placements are reset first, then this table's cells are laid out: customer
       across the top, contents below, then value, age and the two states. */
    /* Every cell is given BOTH its column and its row explicitly. The orders list
       places its own Customer cell at grid-row 2 with the same specificity these
       rules have, so leaving any row to auto-placement lets that rule through and
       the contents jump above the name. Nothing here is left implicit. */
    
    /* max-width: none undoes the orders list's 12ch clamp, which lands below 380px
       and truncates a name mid-word. That clamp exists because its Customer cell
       shares a row with three others; here the cell has the full card width. */
    
    /* The orders list adds a leading "· " to its Date cell at this width. Same
       specificity, so it is switched off here or the age reads with an orphan dot. */
    
    /* The value keeps its header as an inline label; the other three read for
       themselves (a pill, a date, a pair of pills) and would only be cluttered. */
    
}


/* The narrowest supported width. Two stat columns still fit; the cart contents
   are the first thing to get cramped, so they lose their indent, not their size. */


/* =========================================================================
   STOREFRONT PAGE SHELL (design_mockup_admin/pcd-storefront-pages.html)

   THE one container for storefront page content. It is the SAME contract the
   nav bar, hero, catalog grid and footer already use, written out here so a
   page can opt in by adding one class instead of copying the calc():

       max-width: calc(--content-max + 2 * --gutter)   cap, matches the grid
       margin: 0 auto                                  centre
       padding: 0 --gutter                             gutter, matches the nav

   Before this there were seven different width contracts on the storefront and
   the pages drifted from the nav by between 64px and 768px depending on the
   viewport. Anything that must line up with the nav uses .page-shell. Nothing
   else should invent its own container.

   Body copy does NOT run the full 1440. .prose caps the measure at 72ch inside
   the shell, which is what the mockup does and what makes the wider shell
   readable rather than just wider.
   ========================================================================= */
.page-shell {
    max-width: calc(var(--content-max) + 2 * var(--gutter));
    margin: 0 auto;
    padding: 0 var(--gutter);
}
.prose { max-width: 72ch; }

/* Shared storefront page furniture, straight from the mockup's type scale. */
.sf-eyebrow {
    font-family: var(--font-mono);
    font-size: 11px; font-weight: 600; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--accent-blue); margin: 0;
}
.sf-h1 {
    margin: 12px 0 0; font-size: clamp(28px, 3.4vw, 40px); font-weight: 800;
    letter-spacing: -0.035em; line-height: 1.08; color: var(--ink);
}
.sf-lede { margin: 14px 0 0; font-size: 16.5px; line-height: 1.65; color: #4a4a55; max-width: 66ch; }

/* Solid pills only. No translucency, no blur: these are the mockup's exact
   values and they are the only pill colours these pages use. */
.sf-pill {
    display: inline-flex; align-items: center; white-space: nowrap;
    font-size: 12px; font-weight: 700; line-height: 1;
    padding: 6px 11px; border-radius: 7px; color: #fff;
}
.sf-pill.g { background: #1fa863; }
.sf-pill.y { background: #f5c518; color: #3d3000; }
.sf-pill.r { background: #d83b3b; }
.sf-pill.d { background: #54545a; }
.sf-pill.b { background: #2E5BE0; }

/* =========================================================================
   LEGAL TEMPLATE (Terms / Privacy / Waiver / Refund)
   Sticky derived section nav, numbered sections, capped prose column.
   ========================================================================= */
/* The slim header on legal pages only. .slim-header-inner itself is left alone
   because the error and password-reset pages share it and are out of scope. */
.slim-header-inner.is-wide {
    max-width: calc(var(--content-max) + 2 * var(--gutter));
    padding: 0 var(--gutter);
}

.lg-page { background: var(--off-white); }
.lg-head { padding: 44px 0 30px; border-bottom: 1px solid var(--border); }
.lg-meta { margin-top: 18px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.lg-chip {
    background: #fff; border: 1px solid var(--border); border-radius: 999px;
    padding: 5px 12px; font-size: 12.5px; color: var(--muted);
}

.lg-grid { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 44px; padding: 34px 0 0; align-items: start; }
.lg-grid.is-solo { grid-template-columns: minmax(0, 1fr); }   /* no headings -> no nav column */

.lg-toc { position: sticky; top: 20px; }
.lg-toc-t { font-size: 11px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted-2); margin: 0 0 12px; }
.lg-toc-nav { display: flex; flex-direction: column; gap: 2px; }

/* Option B, solid fill. The whole item carries the state: a solid blue fill with
   white text when active, a light grey fill on hover. No border on any single
   side of any item, which is what the old left-edge treatment was.

   Hover deliberately does NOT use blue. It used to set the same
   var(--accent-blue) as the active item, so hovering any entry made it the same
   colour as the active one and two items read as selected at once. Grey fill plus
   dark text keeps blue meaning exactly one thing on this list. */
.lg-toc-a {
    display: block; font-size: 13.5px; color: #55555f; text-decoration: none;
    padding: 9px 13px; border-radius: 9px; line-height: 1.45;
}
.lg-toc-a:hover { background: #ececf1; color: var(--ink); text-decoration: none; }
.lg-toc-a.on { background: var(--accent-blue); color: #fff; font-weight: 600; }
/* Hovering the active item must not repaint it grey. */
.lg-toc-a.on:hover { background: var(--accent-blue); color: #fff; }
.lg-toc-side { margin-top: 22px; padding: 16px; border-radius: 12px; background: #fff; border: 1px solid var(--border); }
.lg-toc-side b { font-size: 13px; display: block; color: var(--ink); }
.lg-toc-side p { font-size: 12.5px; color: var(--muted); margin: 5px 0 8px; line-height: 1.55; }
.lg-toc-side a { font-size: 12.5px; color: var(--accent-blue); }

.lg-sec { padding-bottom: 34px; margin-bottom: 34px; border-bottom: 1px solid var(--border); scroll-margin-top: 96px; }
.lg-sec:last-of-type { border-bottom: 0; }
.lg-sec-n { font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; color: var(--accent-blue); letter-spacing: 0.06em; margin: 0; }
.lg-sec-h { font-size: 22px; font-weight: 800; letter-spacing: -0.025em; margin: 7px 0 0; color: var(--ink); }
.lg-sec .legal-body-copy { margin-top: 13px; }
.lg-sec-intro { border-bottom: 1px solid var(--border); }

/* Body copy inside the sections. .prose caps the measure at 72ch, so the wider
   shell gives the page air without giving it an unreadable line length. */
.legal-body-copy p { margin: 0 0 13px; }
.legal-body-copy p:last-child { margin-bottom: 0; }
.legal-body-copy ul, .legal-body-copy ol { margin: 14px 0 0; padding: 0; list-style: none; }
.legal-body-copy li { position: relative; padding-left: 24px; margin-top: 9px; line-height: 1.7; }
.legal-body-copy li::before {
    content: ''; position: absolute; left: 6px; top: 11px;
    width: 6px; height: 6px; border-radius: 2px; background: var(--accent-blue);
}
.legal-body-copy a { color: var(--accent-blue); }

/* Critical-items callout. Solid tint, flat border, no blur. */
.legal-callout {
    margin-top: 18px; border: 1px solid #efdfa8; background: #fdf9ec;
    border-radius: 12px; padding: 20px 22px;
}
.legal-callout-t {
    font-size: 11.5px; font-weight: 700; letter-spacing: 0.11em;
    text-transform: uppercase; color: #7a5c00; margin: 0;
}
.legal-callout ul { margin-top: 12px; }
.legal-callout li { color: #5c4a10; }
.legal-callout li::before { background: #f5c518; }

/* Related documents.
   Light cards on the page background. This replaces a dark panel that sat flush
   against the dark footer, where the two merged into a single black mass and the
   grey-on-dark buttons read as disabled. A rule and real space above it, and a
   deliberate gap below so the footer starts clear of it. */
.lg-rel { margin-top: 44px; padding-top: 34px; border-top: 1px solid var(--border); }
.lg-rel-t {
    margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 0.13em;
    text-transform: uppercase; color: var(--muted-2);
}
.lg-rel-grid { margin-top: 16px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.lg-rel-card {
    display: block; text-decoration: none; background: #fff;
    border: 1px solid var(--border); border-radius: 13px; padding: 18px 19px;
    transition: border-color 0.15s ease, transform 0.15s ease;
}
.lg-rel-card:hover { border-color: #c3cff5; transform: translateY(-1px); text-decoration: none; }
.lg-rel-card b { display: block; font-size: 15px; color: var(--ink); letter-spacing: -0.01em; }
/* Direct child only. Unscoped, this also matched the arrow <span> inside the
   Read link and turned it into a block, so the arrow dropped onto its own line. */
.lg-rel-card > span { display: block; margin-top: 5px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.lg-rel-card i { display: block; margin-top: 12px; font-size: 12.5px; font-style: normal; font-weight: 700; color: var(--accent-blue); }
.lg-rel-card i span { display: inline; }

/* Space before the footer, so the page does not end on the cards. */
.lg-page .page-shell { padding-bottom: 56px; }

@media (prefers-reduced-motion: reduce) {
    .lg-rel-card { transition: none; }
    .lg-rel-card:hover { transform: none; }
}


@media (max-width: 900px) {
    .lg-grid { grid-template-columns: minmax(0, 1fr); gap: 26px; }
    .lg-toc { position: static; }
    /* Same solid-fill logic, laid out as a wrapping pill row. The full 1px border
       here is an all-round outline on a pill, not a single-side edge. */
    .lg-toc-nav { flex-direction: row; flex-wrap: wrap; gap: 6px; }
    .lg-toc-a { border: 1px solid var(--border); background: #fff; border-radius: 999px; padding: 8px 14px; font-size: 12.5px; }
    .lg-toc-a:hover { background: #ececf1; border-color: var(--border); }
    .lg-toc-a.on { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }
    .lg-toc-a.on:hover { background: var(--accent-blue); border-color: var(--accent-blue); }
}

@media (max-width: 900px) {
    /* Two up on a tablet, one up on a phone. Three 1fr cards at 768 leaves the
       descriptions too narrow to read. */
    .lg-rel-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
    .lg-head { padding: 30px 0 22px; }
    .lg-sec-h { font-size: 19px; }
    .lg-rel { margin-top: 34px; padding-top: 26px; }
    .lg-rel-grid { grid-template-columns: minmax(0, 1fr); }
    .lg-page .page-shell { padding-bottom: 40px; }
}


/* =========================================================================
   COA LIBRARY (/coa-library)
   Dark stat bar, client-side search + product chips, grid list.
   No purity column: purity is documented inside each PDF and is not stored
   per lot, so a column would be hand-maintained and would go stale.
   ========================================================================= */
.coalib-page { background: var(--off-white); padding: 44px 0 72px; }
.coalib-head { margin-bottom: 0; }

/* ---- Certificate library stat band (approved mockup) ---------------------
   Two tiers inside one rounded panel, spanning the full content width so its
   edges line up with the search field and the table beneath it. The old version
   was a four column grid holding three cells, so the band stopped three quarters
   of the way across and left a dead column. There is also no blue accent on the
   first cell any more: that was an inset box-shadow faking a border-left, which
   is a single side border by another name. */
.coalib-band {
    margin-top: 26px;
    background: #0b0e14;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 16px; overflow: hidden;
}

/* Top tier: the claim, and the link out to how testing works. */
.coalib-band-top {
    display: flex; align-items: center; justify-content: space-between; gap: 20px;
    padding: 16px 30px; background: #12161e;
}
.coalib-band-claim { font-size: 14px; color: #e8eaee; }
.coalib-band-link {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 13px; color: #7fa0f0; white-space: nowrap; text-decoration: none;
}
.coalib-band-link:hover { color: #a9c1ff; }
.coalib-band-link svg { width: 13px; height: 13px; flex: 0 0 auto; }

/* Lower tier: three equal columns. */
.coalib-band-figures { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.coalib-figure { position: relative; padding: 28px 30px 30px; min-width: 0; }
/* Column dividers are inset hairline elements inside the cell, not borders. */
.coalib-figure + .coalib-figure::before {
    content: ""; position: absolute; left: 0; top: 24px; bottom: 26px;
    width: 1px; background: rgba(255, 255, 255, 0.09);
}
.coalib-figure-l {
    font-size: 11px; color: #6d717c; letter-spacing: 0.13em;
    text-transform: uppercase; margin-bottom: 15px;
}
.coalib-figure-v { font-size: 38px; font-weight: 600; line-height: 1; letter-spacing: -0.01em; }
.coalib-figure-v.is-green { color: #1fa863; }
.coalib-figure-v.is-blue { color: #7fa0f0; }
/* A date is a timestamp, not a count, so it stays neutral and steps down a size
   to keep its column the same width as the other two. Never wraps. */
.coalib-figure-v.is-date { font-size: 27px; color: #e8eaee; line-height: 1.35; white-space: nowrap; }

.coalib-signin { font-size: 13px; color: var(--muted); margin: 16px 0 0; }

.coalib-toolbar { margin-top: 22px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.coalib-search { position: relative; flex: 1 1 240px; min-width: 0; }
.coalib-search svg {
    position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px; color: var(--muted-2); pointer-events: none;
}
.coalib-field {
    width: 100%; padding: 12px 15px 12px 40px; min-height: 44px;
    border: 1px solid var(--border); border-radius: 10px;
    font: inherit; font-size: 14.5px; background: #fff; color: var(--ink);
}
.coalib-field:focus { outline: 2px solid var(--accent-blue); outline-offset: -1px; }
.coalib-chips { display: flex; gap: 7px; flex-wrap: wrap; }
.coalib-chip {
    font: inherit; font-size: 13px; font-weight: 600; min-height: 44px;
    padding: 9px 15px; border-radius: 999px;
    background: #fff; border: 1px solid var(--border); color: #4a4a55; cursor: pointer;
}
.coalib-chip:hover { border-color: var(--accent-blue); color: var(--accent-blue); }
.coalib-chip.is-on { background: var(--surface-dark); border-color: var(--surface-dark); color: #fff; }

.coalib-list { margin-top: 18px; border: 1px solid var(--border); border-radius: 14px; overflow: hidden; background: #fff; }
.coalib-lh, .coalib-row { display: grid; grid-template-columns: 1.9fr 0.9fr 1fr 150px; gap: 16px; align-items: center; }
.coalib-lh {
    padding: 13px 20px; background: #fafafb; border-bottom: 1px solid var(--border);
    font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted-2);
}
.coalib-row { padding: 15px 20px; border-bottom: 1px solid var(--border); }
.coalib-row:last-of-type { border-bottom: 0; }
.coalib-cell { min-width: 0; overflow-wrap: anywhere; }
.coalib-product { color: var(--ink); font-weight: 700; font-size: 15px; text-decoration: none; }
.coalib-product:hover { color: var(--accent-blue); }
.coalib-size { font-size: 12.5px; color: var(--muted-2); margin-top: 2px; }
.coalib-act { display: flex; justify-content: flex-end; }
.coalib-dl {
    display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
    font-size: 13.5px; font-weight: 700; color: var(--accent-blue); text-decoration: none;
    padding: 8px 13px; border: 1px solid #ccd9fb; border-radius: 9px; background: #f6f9ff;
}
.coalib-dl svg { width: 15px; height: 15px; flex: none; }
.coalib-dl:hover { background: #eaf1ff; text-decoration: none; }
.coalib-none { padding: 28px 20px; margin: 0; text-align: center; color: var(--muted); font-size: 14.5px; }
.coalib-count { margin: 16px 0 0; font-size: 13px; color: var(--muted); }
.coalib-empty { margin-top: 26px; }

/* At 768 and below the top tier stacks to two lines and the three figures go one
   per row, with the dividers turning from vertical rules between columns into
   horizontal rules between rows. Later in the file than the base rules above,
   which is what makes it win: a media query adds no specificity of its own. */
@media (max-width: 768px) {
    .coalib-band-top { flex-direction: column; align-items: flex-start; gap: 9px; padding: 16px 20px; }
    .coalib-band-figures { grid-template-columns: 1fr; }
    .coalib-figure { padding: 20px; }
    .coalib-figure + .coalib-figure::before {
        left: 20px; right: 20px; top: 0; bottom: auto; width: auto; height: 1px;
    }
    .coalib-figure-v { font-size: 32px; }
    .coalib-figure-v.is-date { font-size: 24px; }
}

@media (max-width: 767px) {
    /* Header row cannot survive four columns on a phone; rows become cards and
       each cell carries its own label. */
    .coalib-lh { display: none; }
    .coalib-row { grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 16px; }
    .coalib-cell[data-label]:not([data-label=""])::before {
        content: attr(data-label) '  ';
        display: block;
        font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
        text-transform: uppercase; color: var(--muted-2); margin-bottom: 2px;
    }
    .coalib-cell[data-label="Product"]::before { content: none; }
    .coalib-act { justify-content: flex-start; margin-top: 4px; padding-top: 12px; border-top: 1px solid var(--border); }
}

@media (max-width: 640px) {
    .coalib-page { padding: 30px 0 56px; }
}


/* =========================================================================
   YOUR ORDERS (/account)
   Order cards, tabs with live counts, improved empty state.
   Deliberately its own namespace: this page used to borrow the admin .ordv-*
   classes, which is how an admin-only max-width rule ran it edge to edge.
   ========================================================================= */
.acct-page { background: var(--off-white); padding: 44px 0 72px; }

.acct-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; flex-wrap: wrap; }
.acct-dl {
    display: inline-flex; flex-direction: column; align-items: flex-start; justify-content: center;
    min-height: 44px; padding: 9px 15px; border-radius: 10px;
    background: #fff; border: 1px solid var(--border); color: var(--ink);
    font-size: 13px; font-weight: 700; text-decoration: none; line-height: 1.3;
}
.acct-dl:hover { border-color: var(--accent-blue); color: var(--accent-blue); text-decoration: none; }
.acct-dl-sub { font-size: 11.5px; font-weight: 500; color: var(--muted-2); }

.acct-tabs { margin-top: 24px; display: flex; gap: 4px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.acct-tab {
    font-size: 13.5px; font-weight: 600; color: var(--muted); text-decoration: none;
    padding: 11px 15px; border-bottom: 2px solid transparent; margin-bottom: -1px;
    display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
}
.acct-tab:hover { color: var(--ink); text-decoration: none; }
.acct-tab.is-on { color: var(--ink); border-bottom-color: var(--accent-blue); }
.acct-tct { font-size: 11.5px; font-weight: 700; background: #ececf1; color: #55555f; border-radius: 999px; padding: 2px 8px; }
.acct-tab.is-on .acct-tct { background: var(--accent-blue); color: #fff; }

.acct-card {
    margin-top: 14px; padding: 20px 22px;
    background: #fff; border: 1px solid var(--border); border-radius: 14px;
    display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px; align-items: start;
}
.acct-main { min-width: 0; }
.acct-top { display: flex; gap: 11px; align-items: center; flex-wrap: wrap; }
.acct-no { font-family: var(--font-mono); font-size: 14px; font-weight: 600; color: var(--ink); }
.acct-date { font-size: 13px; color: var(--muted-2); }

.acct-items { margin-top: 14px; display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }
.acct-thumb {
    width: 42px; height: 42px; flex: none; border-radius: 9px; overflow: hidden;
    background: #eef0f5; border: 1px solid var(--border);
    display: grid; place-items: center;
}
.acct-thumb img { width: 100%; height: 100%; object-fit: contain; }
.acct-thumb-t { font-size: 10px; color: var(--muted-2); font-family: var(--font-mono); }
.acct-names { font-size: 13px; color: var(--muted); min-width: 0; overflow-wrap: anywhere; }

.acct-side { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 11px; }
.acct-total { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.acct-owe { font-size: 13px; font-weight: 700; color: #b06a00; }

/* Card buttons are local rather than the site .btn, which is black. The mockup
   wants the brand blue here and a bordered white secondary. */
.acct-btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 10px 18px; border-radius: 10px;
    background: var(--accent-blue); color: #fff;
    font-size: 13.5px; font-weight: 700; text-decoration: none; white-space: nowrap;
}
.acct-btn:hover { background: #264fc4; color: #fff; text-decoration: none; }
.acct-btn.is-ghost { background: #fff; color: var(--ink); border: 1px solid var(--border); }
.acct-btn.is-ghost:hover { background: var(--off-white); color: var(--ink); border-color: var(--accent-blue); }

.acct-foot {
    grid-column: 1 / -1;
    margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
    display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
    font-size: 13px; color: var(--muted);
}
.acct-carrier { font-size: 10.5px; font-weight: 600; padding: 4px 7px; border-radius: 5px; color: #fff; background: #54545a; }
.acct-carrier.c-fedex { background: #4D148C; }
.acct-carrier.c-usps  { background: #004B87; }
.acct-trk { font-size: 12.5px; color: var(--ink); }

/* ---- order history deadline line ---------------------------------------
   The quiet counterpart to the pay page clock. One sentence and a button, no
   ticking units: this is read at a glance beside several other orders, and a row
   of clocks down the page would be noise. Tinted rather than solid, because it is
   information about an order that is still perfectly fine. */
.acct-cd {
    border-top: 0; margin-top: 13px; padding: 11px 14px; border-radius: 10px;
    background: #fdf8e6; border: 1px solid #efdfa8; color: #8a5c00;
    font-size: 13.5px; line-height: 1.5;
}
.acct-cd b { font-family: var(--font-mono); font-weight: 700; }
.acct-cd.is-urgent { background: #fdf0f0; border-color: #f0c4c4; color: #96201f; }
/* Past due but not yet swept. Red, because it is still actionable: paying now
   still saves the order. Distinct from is-released below, which is history. */
.acct-cd.is-past { background: #fdf0f0; border-color: #e8a9a9; color: #96201f; font-weight: 600; }
/* Already released. Grey, because it is history rather than something to act on. */
.acct-cd.is-released { background: #f4f5f8; border-color: var(--border); color: var(--muted); }
.acct-cd-btn {
    margin-left: auto; font-size: 13px; font-weight: 700; padding: 9px 14px;
    border-radius: 9px; background: #2E5BE0; color: #fff; text-decoration: none;
    min-height: 40px; display: inline-flex; align-items: center; white-space: nowrap;
}
.acct-cd-btn:hover { background: #264fc4; color: #fff; text-decoration: none; }

@media (max-width: 640px) {
    /* The button goes full width rather than sitting in a 90px column beside a
       sentence that has wrapped to three lines. */
    .acct-cd { flex-direction: column; align-items: stretch; gap: 10px; }
    .acct-cd-btn { margin-left: 0; justify-content: center; }
}


.acct-empty { margin-top: 16px; padding: 64px 24px; text-align: center; background: #fff; border: 1px solid var(--border); border-radius: 14px; }
.acct-empty.is-tab { padding: 40px 24px; }
.acct-empty-ic {
    width: 54px; height: 54px; border-radius: 14px; background: #eef2fe;
    margin: 0 auto 18px; display: grid; place-items: center; color: var(--accent-blue);
}
.acct-empty-ic svg { width: 24px; height: 24px; }
.acct-empty-h { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; margin: 0; color: var(--ink); }
.acct-empty-p { margin: 8px 0 0; color: var(--muted); font-size: 14.5px; }
.acct-empty .btn { margin-top: 20px; }

@media (max-width: 640px) {
    .acct-page { padding: 30px 0 56px; }
    /* Card goes single column: the money and the action move under the contents
       rather than being squeezed into a second column on a 360px screen. */
    .acct-card { grid-template-columns: minmax(0, 1fr); padding: 16px; gap: 14px; }
    .acct-side { text-align: left; align-items: stretch; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 10px; }
    .acct-total { flex: 1 1 auto; }
    .acct-btn { width: 100%; }
    .acct-head { align-items: stretch; }
    .acct-dl { width: 100%; align-items: center; }
    .acct-empty { padding: 44px 18px; }
}


/* =========================================================================
   LAB COSTS (/admin/lab-costs)
   Four screens: list, invoice detail, record form, assign-to-COA.
   Money on these screens is INTEGER CENTS, formatted with money_cents().
   ========================================================================= */

.lcv-btn.ghost { background: #fff; color: var(--ink); border: 1px solid var(--border); }
.lcv-btn.ghost:hover { background: var(--off-white); color: var(--ink); border-color: var(--accent-blue); }

/* Stat bar. Same dark treatment as the orders and inventory bars. */


/* Tabs. Solid fill when active, no single-side border. */

.lcv-tab {
    display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
    font-size: 13.5px; font-weight: 600; color: var(--muted); text-decoration: none;
    padding: 9px 14px; border-radius: 9px;
}
.lcv-tab:hover { background: #ececf1; color: var(--ink); text-decoration: none; }
.lcv-tab.is-on { background: var(--accent-blue); color: #fff; }
.lcv-tab.is-on:hover { background: var(--accent-blue); color: #fff; }
.lcv-tab.is-on .lcv-tct { background: rgba(255, 255, 255, 0.24); color: #fff; }

/* List */


/* Detail */


/* Form */


/* Assign */

.lcv-asg-opt .mono { display: block; font-size: 12px; color: var(--muted); }

/* Dashboard cost bar */

/* "Not tracked" must never be mistaken for a figure. Smaller, grey, and in the
   UI font rather than the tabular figures the real numbers use. */

/* The global `a { color: var(--black) }` renders black on these dark tiles, which
   is all but invisible. Links inside the cost bar get the lit blue instead. */

@media (max-width: 900px) {
    /* Header row cannot survive six columns on a narrow screen: rows become
       stacked cards and each cell carries its own label. */
    
}


/* =========================================================================
   ADMIN: RESEARCH CATALOGUE LINK  (/admin/catalogue)
   One page under Store. Same stat strip, panel and grid vocabulary as the lab
   costs screen it sits beside, so the two read as one system.
   ========================================================================= */


/* The link row. The address is the long item, so it takes the free space and
   truncates rather than pushing the buttons off the panel. */

.cvl-btn.is-ghost { background: #fff; color: var(--ink); border-color: var(--border); }
.cvl-btn.is-ghost:hover { background: var(--off-white); color: var(--ink); border-color: var(--accent-blue); }
/* Rotate is not undoable, so it is worded and coloured as a real action, not a
   danger button that invites a double take. Text only, no red fill. */

.cvl-btn.is-copied { background: #1fa863; border-color: #1fa863; color: #fff; }
.cvl-note .sf-pill { margin-right: 6px; vertical-align: middle; }
@media (max-width: 640px) {
    /* Full-width controls on a phone, so nothing is a 30px tap target. */
    
}


/* =========================================================================
   ADMIN: PRIVATE LINKS + RECON SCHEDULES
   Extends the .cvl-* vocabulary the private links page already uses.
   ========================================================================= */
/* One card per link-only page, so several stack without running together. */


/* Schedule list. A grid rather than a table so it can collapse on a phone. */


/* Source tier pills. Solid, same palette as the public page's badges. */

.srcpill.fda { background: #1fa863; }
.srcpill.trial { background: #2E5BE0; }
.srcpill.conv { background: #54545a; }
.srcpill.none { background: #f5c518; color: #3d3000; }

/* Phase editor rows. */


@media (max-width: 900px) {
    /* Each row becomes a stacked block, labelled by its own content rather than by a
       header row that is no longer on screen. */
    
}


/* =========================================================================
   ADMIN: VENDORS  (/admin/vendors)
   Five screens: vendor list, vendor detail, vendor form, add products, paste
   import. Same stat strip, tab, panel and pill vocabulary as the lab costs and
   referrals screens it sits beside, so the three read as one system.

   Money on these screens is INTEGER CENTS. Cost per vial is computed at render
   from a kit price and a kit size, never stored, so nothing here caches it.

   WIDTHS. The admin sidebar is a fixed 240px until it stacks at 900px, so the
   content column is NARROWER at 1024 (about 700px) than at 768 (about 736px).
   The product table therefore drops to stacked cards at 1200px rather than at
   900px, which is where a 1024 screen would otherwise crush eight columns.
   ========================================================================= */

.vnd-btn.ghost { background: #fff; color: var(--ink); border: 1px solid var(--border); }
.vnd-btn.ghost:hover { background: var(--off-white); color: var(--ink); border-color: var(--accent-blue); }

/* Stat bar. Same dark treatment as the lab costs and orders bars. */


/* Tabs. Solid fill when active, no single-side border. */

.vnd-tab.is-on { background: var(--accent-blue); color: #fff; }
.vnd-tab.is-on:hover { background: var(--accent-blue); color: #fff; }
.vnd-tab.is-on .vnd-tct { background: rgba(255, 255, 255, 0.24); color: #fff; }

/* Solid pills only. */


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

.vnd-v.mono { font-family: var(--font-mono); font-size: 13px; }

/* ---- panels ---- */


/* ---- tier picker + search ---- */

.vnd-tier {
    display: inline-flex; align-items: center; min-height: 40px; padding: 8px 14px;
    border-radius: 9px; font-size: 13px; font-weight: 700; text-decoration: none;
    color: var(--ink); background: #ececf1;
}
.vnd-tier:hover { background: #e2e2ea; color: var(--ink); text-decoration: none; }
.vnd-tier.is-on { background: var(--accent-blue); color: #fff; }
.vnd-tier.is-on:hover { background: var(--accent-blue); color: #fff; }

/* ---- product table ---- */


/* Tier prices, shown together with the one in force filled solid. */

.vnd-price.is-on { background: var(--accent-blue); border-color: var(--accent-blue); }
.vnd-price.is-on b { color: rgba(255, 255, 255, 0.78); }
.vnd-price.is-on i { color: #fff; }
.vnd-price.is-on em { color: rgba(255, 255, 255, 0.85); }

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


/* Product rows wrap on their own rather than at a breakpoint, because the number
   of price boxes depends on the vendor's ladder and a fixed column count would be
   wrong for a vendor with one tier or with four. */


/* ---- import preview ---- */

.vnd-sumtile.is-warn { background: #fdf6e3; border-color: #f0e3bd; }
.vnd-readout li.is-warn { color: #9a6a00; }

@media (max-width: 1200px) {

    /* Header rows cannot survive eight columns in a 700px content column, which
       is what a 1024 screen leaves once the 240px sidebar is taken off. Rows
       become blocks and each cell carries its own label. */
    
    /* Code is narrow and fixed, so the product name sits beside it on the first
       line rather than under it. Only the price group needs the full width. */
    
}

@media (max-width: 640px) {
    /* The spans set at 1200 have to be released here as well. Left in place they
       would place a cell in column 2 of a one column grid, which creates an
       implicit second column and squeezes the batch and the prices into strips.
       These repeat the 1200 selectors exactly: an attribute selector outranks a
       plain .vnd-cell, so matching its specificity is what lets source order
       decide. */
    
    /* Full-width controls on a phone, so nothing is a 30px tap target. */
    
    .vnd-tier { flex: 1 1 auto; justify-content: center; }
}


/* =========================================================================
   ADMIN: EXPENSES  (/admin/expenses)

   Built on the vnd-* system rather than beside it: the stat bar, tabs, pills,
   panels, buttons and form fields are all inherited. What is here is only the
   parts this screen adds, which is the table's own column set, the category
   breakdown bars, and the repeatable line rows on the form.
   ========================================================================= */


/* The unpriced count, in the tile rather than in a tooltip. A product cost
   figure that quietly omitted unpriced shipments would read as complete when it
   is not, so it is said on the tile and again in full underneath. */


/* Positive balance. Green on the number only, no pill, no border. */


/* Breakdown bars. Width is relative to the BIGGEST category, so the largest is
   always full and the rest read against it rather than against the total. */


/* Repeatable line rows on the form. */

/* Only the first row shows its labels; the rest repeat the same four fields and
   a second set of headings would be noise. */

/* An Amount being worked out from Qty x Unit. Tinted, but with full-strength
   text and the ordinary border, because the field IS editable and muted text
   would read as disabled. Typing clears the tint, which is the line saying the
   figure is now yours and nothing will overwrite it. Focus goes white so typing
   feels like any other field. */

/* minmax(0, 1fr), not 1fr: a native date input has a wide intrinsic minimum and
   an auto track would let it push out past the panel instead of shrinking. */


/* Five tiles into three columns, then into two, leaves the last one stranded on
   a row of its own. It spans instead, the same way the batches bar does. */


/* A third of the panel is not enough for two date inputs, so the custom range
   takes a row of its own rather than being squeezed. Floored at 641px: below
   that the shared rule puts every field on its own row, and this would override
   it on specificity and put two back side by side. */


/* Floored at 641px for the same reason as the export grid above: the explicit
   cell placement below is more specific than the stacked-card rules at 640, so
   it has to stop rather than be argued with. */
@media (min-width: 641px) and (max-width: 1100px) {
    /* "What it was" cannot hold a column at this width, so it drops to a second
       line under the vendor. It is NOT hidden: the item count and the receipt
       link live in it, and losing the receipt link on a tablet would mean the
       only way to reach a receipt was to open the expense for editing. */
    
}


@media (max-width: 860px) {
    /* Labels come back on every row here. Hiding them past the first only works
       while the rows sit under one shared header; once they wrap, a bare column
       of numbers gives no way to tell quantity from unit price. The :not() is
       repeated so this beats the base rule on specificity rather than order. */
    
    /* And a rule between rows, so it is clear where one line ends. */
    
    /* Darker than the bare cross: at full width in muted grey it reads as a
       disabled button rather than an available one. */
    
}


@media (max-width: 640px) {
    /* The table becomes stacked cards: the header row goes and each cell sits on
       its own line, which is what the mockup does at its own breakpoint. */
    
}


/* =========================================================================
   ADMIN: RESEARCH CATALOGUE  (/admin/catalogue)
   Extends the .vnd-* vocabulary the vendors and lab costs screens use, so the
   three read as one system. Only the pieces with no equivalent there are new:
   the waiting notice, the product row layout, and the category reorder list.
   ========================================================================= */

/* The notice that names what is being held back. Amber because it is a gap to
   fill rather than something broken, and tinted rather than a solid pill because
   it is a paragraph, not a status. */
.cat-notice {
    margin-top: 16px; padding: 13px 16px; border-radius: 10px;
    background: #fdf8e6; border: 1px solid #efdfa8; color: #6b5200;
    font-size: 13.5px; line-height: 1.6;
}
.cat-notice b { font-weight: 700; }
.cat-notice-list { display: block; margin-top: 6px; }
.cat-notice-list a { color: #6b5200; font-weight: 600; text-decoration: underline; }
.cat-notice-list a:hover { color: #4a3900; }

/* Product rows. Five columns, and the same stacked-card treatment below 1200 the
   vendor tables use, for the same reason: the admin content column is narrower at
   1024 than at 768 once the fixed sidebar is taken off. */

/* The catalogue display name, under the real one. Shown only when the two differ,
   so an ordinary row carries no extra line. */


/* Category reorder list. */

/* The per-category save and delete forms are declared outside the reorder form,
   because a form cannot nest inside another. They carry no visible controls. */

@media (max-width: 900px) {
    /* The grip is a pointer affordance and drag is a pointer gesture, so on a touch
       layout the row becomes a plain stacked block and reordering is left to a
       wider screen rather than pretending to be draggable. */
    
}


/* =========================================================================
   ADMIN: BATCHES  (/admin/batches)
   Extends the .vnd-* vocabulary the vendors, lab costs and catalogue screens
   use, so the four read as one system. Only the pieces with no equivalent
   there are new: the number preview, the labelling run list, and the two
   states a COA cell can be in that a vendor row has no equivalent for.
   ========================================================================= */

/* The awaiting-a-COA tile. Amber on the figure only, because the tile is
   counting something that still needs doing rather than reporting a failure. */
.vnd-stat.is-warn .vnd-stat-n { color: #ffd75e; }

/* ---- the list ----
   Five columns: the number, what it is for, the certificate, when it was made,
   and the actions. There is nothing else a batch has. */


/* ---- the number preview on the form ----
   Dark panel with the number large, because the number is the thing being
   decided and it should not be a field halfway down a form. */


/* The rename warning. Amber and a sentence rather than a pill, because it is a
   thing to think about and not a status. It never blocks the save. */



/* ---- detail ---- */


/* ---- certificate preview on the batch page ----
   The facts and the rendered page-one image sit side by side, so the operator can
   see at a glance both what is recorded and that the render actually produced
   something. Below 560px the image drops under the facts rather than squeezing
   them: a 96px-wide certificate is not evidence of anything. */



/* Five columns do not survive a tablet, so the row becomes a stacked card the
   same way the vendor and catalogue tables do. */
@media (max-width: 1100px) {
    /* 160px, not less: Labels and Edit side by side need it, and at anything
       narrower they wrap onto two lines and double the height of every row. */
    
    /* The certificate and the date drop to a second line under the number,
       rather than being hidden: the certificate is the point of the row. */
    
}

/* The size label that failed to parse, quoted inside the notice. Monospace so a
   stray character in the label is visible, which is usually what is wrong. */
/* Label-and-value rows in the summary panels. The lab costs screen has the same
   pattern as .lcv-kv; this is the .vnd-* spelling of it, so the batch panels sit
   in the same vocabulary as the rest of these screens. */

.vnd-kv b.mono { font-family: var(--font-mono); font-size: 13.5px; }

.bat-badlabel { font-family: var(--font-mono); font-size: 12px; }
/* Each reason is its own line inside the notice rather than one running
   paragraph, since they are separate jobs with separate fixes. */
.cat-notice .cat-notice-list + .cat-notice-list { margin-top: 6px; }



/* =========================================================================
   PUBLIC: THE CATEGORY TIER
   The ten class pages, the homepage section that links them, the mobile menu
   group, and the product page's link back up. Built on the existing storefront
   vocabulary: the grid is .pgrid, the same one the catalog uses.
   ========================================================================= */

/* ---- Browse by class, on the shop page ----
   Replaced the .hclass tile grid that used to sit above the catalog. Colours,
   radii, spacing and type sizes below are the approved shop mockup's, and they
   match the nav dropdown, which is the same component in a different place. */

/* The pill row and the trigger. The pills themselves are untouched: this only
   gives them a parent that can hold something at the other end. */
/* 26px, not the mockup's 10px. The mockup was rendered at 680px wide, where 10px
   reads correctly; at 1440 it is too tight against the count line below. */
.cbc-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-bottom: 26px;
}
.cbc-bar .catalog-filters { margin-bottom: 0; min-width: 0; }
.cbc-trigger {
    display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto;
    padding: 9px 15px; border-radius: 999px; cursor: pointer;
    background: #161a22; border: 0.5px solid rgba(255, 255, 255, 0.14);
    color: #fff; font-size: 13px; line-height: 1.2;
}
.cbc-trigger:hover { border-color: rgba(255, 255, 255, 0.28); }
.cbc-chev { width: 12px; height: 12px; flex: 0 0 auto; color: #8b8f9a; transition: transform .15s ease; }
.cbc-trigger[aria-expanded="true"] .cbc-chev { transform: rotate(180deg); }

/* IN NORMAL FLOW. Opening pushes the grid down instead of covering it, so this
   never competes with the cards' certificate hover panels for a stacking
   context. Do not make it absolute. */
.cbc-panel {
    background: #12151c; border: 0.5px solid rgba(255, 255, 255, 0.09);
    border-radius: 12px; overflow: hidden; margin-bottom: 18px;
}
.cbc-cols {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    padding: 18px 18px 16px;
}
.cbc-col { padding-right: 16px; min-width: 0; }
/* The second half of Peptides. No heading of its own, so it needs the height of
   one to line its first row up with the first column's. */
.cbc-col-continued { padding-top: 24px; }
/* THE COLUMN DIVIDER IS AN INSET POSITIONED HAIRLINE, NOT A border-left.
   No single side borders anywhere, structural or otherwise. A positioned element
   also insets cleanly from the column's top and bottom, which a border cannot:
   a border runs the full box height whether or not there is content beside it. */
.cbc-col-divided { position: relative; padding-left: 18px; }
.cbc-col-divided::before {
    content: "";
    position: absolute; left: 0; top: 0; bottom: 0;
    width: 1px; background: rgba(255, 255, 255, 0.06);
}
.cbc-group {
    display: block; margin-bottom: 10px;
    font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: #6d717c;
}
.cbc-row {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 7px 10px; border-radius: 6px; text-decoration: none;
}
.cbc-label { font-size: 13px; color: #c9ccd3; overflow-wrap: anywhere; }
.cbc-count { font-size: 12px; color: #6d717c; flex: 0 0 auto; }
.cbc-row:hover, .cbc-row:focus-visible { background: rgba(255, 255, 255, 0.05); }
.cbc-row:hover .cbc-label, .cbc-row:focus-visible .cbc-label { color: #fff; }
.cbc-foot {
    position: relative;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 15px 28px; text-decoration: none;
    background: #0f1219;
}
.cbc-foot::before {
    content: "";
    position: absolute; left: 0; right: 0; top: 0;
    height: 1px; background: rgba(255, 255, 255, 0.06);
}
.cbc-foot .cbc-label { font-size: 13px; font-weight: 600; color: #2E5BE0; }
.cbc-foot:hover { background: #131720; }

/* Two columns. Peptides still splits across the first two, so its halves stay
   side by side; Compounds and Supplies drop to the second row, where a vertical
   divider would draw a stray line down the container edge. */
@media (max-width: 1024px) {
    .cbc-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cbc-col-divided { padding-left: 0; padding-top: 18px; }
    .cbc-col-divided::before { display: none; }
}


/* One stacked full width list. The trigger becomes a full width row above it,
   because a pill at the end of a row that already scrolls sideways is a control
   nobody finds. */
@media (max-width: 640px) {
    .cbc-bar { display: block; margin-bottom: 0; }
    .cbc-bar .catalog-filters { margin-bottom: 10px; }
    .cbc-trigger {
        display: flex; width: 100%; justify-content: space-between;
        padding: 12px 14px; border-radius: 8px;
        background: #12151c; border-color: rgba(255, 255, 255, 0.09);
        color: #c9ccd3; margin-bottom: 14px;
    }
    .cbc-chev { width: 14px; height: 14px; }
    .cbc-panel { border-radius: 8px; margin: -6px 0 14px; }
    .cbc-cols { grid-template-columns: 1fr; padding: 8px 0; }
    .cbc-col, .cbc-col-divided, .cbc-col-continued { padding: 0; }
    .cbc-col-divided::before { display: none; }
    .cbc-group { padding: 6px 14px; margin-bottom: 0; }
    .cbc-row { padding: 10px 14px; border-radius: 0; }
    .cbc-foot { margin-top: 7px; padding: 12px 14px; }
}

@media (prefers-reduced-motion: reduce) {
    .cbc-chev, .pclass-morechev { transition: none; }
}


/* ---- the class page ---- */
/* THE CAP IS ON THE CONTAINER, NOT ON EACH CHILD, AND THAT IS THE WHOLE POINT.
   This used to be `.pclass > * { max-width: var(--content-max); margin-inline:
   auto }`, which centres every child independently. Any child that then set a
   margin SHORTHAND silently opted itself out, because `margin: 18px 0 12px`
   writes margin-left: 0 over the auto. .pclass-count did exactly that, so past
   the cap the count row sat hard against the gutter while the breadcrumb, title
   and grid were centred: one row on a different left edge from everything above
   and below it, for no visible reason.

   With the cap here, children are ordinary block boxes that fill the padding box
   and all start at the same x. There is no auto margin left for a shorthand to
   overwrite, so the failure cannot come back.

   width: 100% is load bearing. body is a flex COLUMN for the sticky footer, and
   auto inline margins on a flex child override the stretch, so without it this
   would shrink to its content instead of filling the row. */
.pclass {
    width: 100%;
    max-width: calc(var(--content-max) + var(--gutter) * 2);
    margin-left: auto;
    margin-right: auto;
    padding: 26px var(--gutter) 60px;
}
.pclass-crumb { display: flex; flex-wrap: wrap; gap: 7px; font-size: 13px; color: #8b8f9a; }
.pclass-crumb a { color: inherit; text-decoration: none; }
.pclass-crumb a:hover { color: #fff; text-decoration: underline; }
.pclass-crumb-sec { color: #6d717c; }
.pclass-crumb-current { color: #fff; }

/* Compressed head: breadcrumb, title, one paragraph, then the grid. The rest of
   the prose is behind the toggle below, so the products start near the top of
   the page rather than under four paragraphs. */
.pclass-head { margin-top: 14px; }
.pclass-h1 { margin: 0; font-size: clamp(26px, 4.4vw, 38px); font-weight: 800; letter-spacing: -0.02em; color: #fff; }
.pclass-intro { margin-top: 10px; max-width: 78ch; }
.pclass-intro p { margin: 0 0 12px; font-size: 15px; line-height: 1.7; color: #c9ccd3; }
.pclass-intro a { color: #7ea0ff; text-decoration: underline; text-underline-offset: 2px; }
.pclass-intro a:hover { color: #a9c1ff; }
/* Matches .pclass-intro p at 0,2,0 and sits after it, so it wins on order. */
.pclass-intro .pclass-lead { margin-bottom: 8px; }
.pclass-siblings, .pclass-coalink { font-size: 14px; }
.pclass-siblings a + a { margin-left: 4px; }
.pclass-more { padding-top: 4px; }
/* A text control, not a button shape: it sits in a paragraph flow and a filled
   button here would read as the page's primary action, which it is not. */
.pclass-morebtn {
    display: inline-flex; align-items: center; gap: 6px;
    margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
    font-family: inherit; font-size: 14px; color: #7ea0ff;
}
.pclass-morebtn:hover { color: #a9c1ff; text-decoration: underline; }
.pclass-morechev { width: 13px; height: 13px; flex: 0 0 auto; transition: transform .15s ease; }
.pclass-morebtn[aria-expanded="true"] .pclass-morechev { transform: rotate(180deg); }

/* The count row, and on the right the way out to another class. The hairline is
   a positioned element, not a border-top: no single side borders anywhere. */
.pclass-countbar {
    position: relative;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-top: 22px; padding-top: 16px; margin-bottom: 14px;
}
.pclass-countbar::before {
    content: "";
    position: absolute; left: 0; right: 0; top: 0;
    height: 1px; background: rgba(255, 255, 255, 0.09);
}
.pclass-count { margin: 0; min-width: 0; font-size: 13px; color: #6d717c; }

/* THIS BLOCK MUST STAY BELOW THE RULE IT OVERRIDES, AND THAT IS THE WHOLE POINT.
   A media query adds no specificity. These rules and the .pclass-countbar rule
   above are both 0,1,0, so the only thing deciding the winner is source order.
   This block used to live several hundred lines earlier, next to the other .cbc
   mobile rules, where it lost: the bar stayed display:flex on a phone, the
   full-width trigger sat beside the count in a row that could not hold both, and
   the page grew wider than the viewport.

   That one overflow caused three separate visible faults, because Safari widens
   the layout viewport to fit overflowing content and then evaluates media
   queries against the WIDER width. So the nav strip, sized to the real viewport,
   stopped short of the right edge with black beyond it, and narrow breakpoints
   stopped matching on a phone even though they measured correctly in every
   desktop browser. Keep related breakpoints beside the rule they modify. */
@media (max-width: 640px) {
    /* The trigger takes its own full width line under the count. Nothing shares
       a line, so nothing can wrap into anything else. */
    .pclass-countbar { display: block; margin-bottom: 12px; }
    .pclass-count { margin-bottom: 10px; }
}

.pclass-empty { margin-top: 26px; padding: 26px; border-radius: 14px; background: #12151c; border: 0.5px solid rgba(255, 255, 255, 0.09); }
.pclass-empty p { margin: 0 0 14px; color: #c9ccd3; }
.pclass-back { color: #7ea0ff; font-weight: 600; text-decoration: none; }
.pclass-back:hover { text-decoration: underline; }

/* ---- the product page's link up ---- */
/* The certificate source beside a purity figure, and the neutral pending line
   that replaces a figure when no document records one. Both link to the library
   so the claim, or its absence, is checkable in one click. */
.pd-purity-src {
    margin-left: 8px; font-family: var(--font-ui); font-size: 12px;
    color: var(--muted); text-decoration: underline; text-underline-offset: 2px;
}
.pd-purity-src:hover { color: var(--accent-blue); }
.pd-coa-pending { font-size: 13.5px; color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.pd-coa-pending:hover { color: var(--accent-blue); }
.pd-class { margin: 10px 0 0; font-size: 13.5px; line-height: 1.6; color: var(--muted); }
.pd-class a { color: var(--accent-blue); text-decoration: underline; text-underline-offset: 2px; }
.pd-class a:hover { color: #1b3fa8; }

/* ---- the mobile menu category group ----
   NOT .mnav-head. That name belongs to the overlay's 76px header bar at the top
   of this file, which holds the logo and the close button. This block used it
   too, sits later in the file, and so was overriding that bar from flex to
   block and replacing its padding. Do not rename this back. */
.mnav-group {
    display: block; padding: 12px 4px 4px;
    font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
    color: #6d717c;
}
.mnav-link-cat { padding-left: 12px; justify-content: space-between; gap: 12px; }
.mnav-link-cat .mnav-label { font-size: 15px; color: #c9ccd3; }
.mnav-count { font-size: 12px; color: #6d717c; flex: 0 0 auto; }
.mnav-link-all .mnav-label { font-weight: 600; color: #2E5BE0; }

/* ---- the admin gap report ---- */


@media (max-width: 900px) {
    .pclass { padding: 22px var(--gutter) 48px; }
}

/* =========================================================================
   ADMIN: SHIPMENTS  (/admin/shipments)

   Built on the vnd-* system rather than beside it: the stat bar, tabs, pills,
   panels, buttons and form fields are all inherited from the vendors and
   expenses screens. What is here is only what this screen adds, which is its
   own column set, the countdown cell, and the repeatable line rows.
   ========================================================================= */


/* An unpriced line, said on the row rather than only in the total. Amber text
   rather than a pill: it qualifies a figure, it is not a status of its own. */


/* ---- the form ---- */
/* The order panel is four across only where four fit. Every width below is
   left to the shared .vnd-fgrid rules, so there is nothing to override and
   nothing to lose a specificity fight with. */

/* Only the first row shows its labels; the rest repeat the same four fields and
   a second set of headings would be noise. */


/* Floored at 641px: below that the shared .vnd-fgrid rule puts every field on
   its own row, and this has equal specificity and comes later, so without the
   floor it would win and put two back side by side on a phone. */


/* Six columns do not survive a tablet. What is in it and the countdown drop to
   a second line, which keeps the vendor, the cost and the actions on the first. */


@media (max-width: 860px) {
    /* Labels come back on every row here. Hiding them past the first only works
       while the rows sit under one shared header; once they wrap, a bare column
       of numbers gives no way to tell vials from price. The :not() is repeated
       so this beats the base rule on specificity rather than order. */
    
    /* Darker than the bare cross: at full width in muted grey it reads as a
       disabled button rather than an available one. */
    
}


/* =========================================================================
   ADMIN: LABEL GENERATOR  (/admin/batches/{id}/labels)
   Extends the .vnd-* vocabulary the other Store screens use. Only the preview
   stage and the size/zoom pickers are new.
   ========================================================================= */


/* A lot with no certificate. Muted and in the body face rather than the mono
   used for a real figure, so it does not read as a value. */
.lbl-purity-none { font-family: var(--font-ui); font-weight: 500; color: var(--muted); }

/* The label sits on a neutral grey so a pure white print is visible against it. */

#lbl-canvas { display: block; max-width: 100%; height: auto; }
.lbl-dims { margin: 12px 0 0; font-family: var(--font-mono); font-size: 12px; color: var(--muted); text-align: center; }
/* Solid fill for the active state, never a single-side border. */
.lbl-sz.is-on { background: var(--surface-dark); border-color: var(--surface-dark); color: #fff; }
.lbl-sz.is-on:hover { background: var(--surface-dark); color: #fff; }
.lbl-sz:focus-visible, .lbl-zb:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }
.lbl-zb {
    font: inherit; font-size: 12.5px; font-weight: 700; color: var(--muted);
    background: #fff; border: 1px solid var(--border); border-radius: 8px;
    padding: 8px 12px; cursor: pointer; min-height: 38px;
}
.lbl-zb:hover { border-color: var(--accent-blue); color: var(--ink); }
.lbl-zb.is-on { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }
.lbl-zb.is-on:hover { background: var(--accent-blue); color: #fff; }
@media (max-width: 640px) {
    .lbl-sz, .lbl-zb { flex: 1 1 auto; justify-content: center; text-align: center; }
}


/* =========================================================================
   PUBLIC: BATCH VERIFICATION  (/b/{batch_number})
   Storefront standard, not an admin table. For some customers this is the only
   page of ours they look at closely, so it reads as a document rather than a
   data dump. Nothing commercial appears here: no price, no buy button.
   ========================================================================= */

.vfy { padding: 40px 20px 72px; }
.vfy-wrap { max-width: 680px; margin: 0 auto; min-width: 0; }

.vfy-head { text-align: center; margin-bottom: 26px; }
.vfy-eyebrow { font-size: 12px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted-2); margin: 0; }
.vfy-num {
    font-family: var(--font-mono); font-size: clamp(26px, 6.4vw, 40px); font-weight: 700;
    letter-spacing: -0.02em; margin: 8px 0 0; color: var(--ink); overflow-wrap: anywhere;
}
.vfy-compound { margin: 8px 0 0; font-size: 17px; color: var(--ink); font-weight: 600; }
.vfy-strength { color: var(--muted); font-weight: 500; }
.vfy-pill {
    display: inline-flex; align-items: center; margin-top: 14px;
    font-size: 12.5px; font-weight: 700; line-height: 1; letter-spacing: 0.02em;
    padding: 8px 14px; border-radius: 7px; color: #fff; white-space: nowrap;
}
.vfy-pill.g { background: #1fa863; }
.vfy-pill.y { background: #f5c518; color: #3d3000; }
.vfy-pill.d { background: #54545a; }

.vfy-card { background: #fff; border: 1px solid var(--border); border-radius: 14px; padding: 26px 28px; margin-top: 16px; min-width: 0; }
.vfy-card-h { font-size: 19px; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 10px; color: var(--ink); }
.vfy-lede { margin: 0 0 12px; font-size: 15px; line-height: 1.7; color: var(--muted); }
.vfy-lede:last-of-type { margin-bottom: 0; }
/* The state cards are tinted rather than outlined on one side, so the three
   read as different at a glance without a directional border. */
.vfy-card.is-verified { background: #f2fbf6; border-color: #bfe6d1; }
.vfy-card.is-pending { background: #fdf8e6; border-color: #efdfa8; }
.vfy-card.is-unavailable { background: #f6f6f8; border-color: #dcdce2; }

.vfy-act { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.vfy-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    font-size: 15px; font-weight: 700; padding: 13px 22px; min-height: 48px;
    border-radius: 10px; background: var(--accent-blue); color: #fff; text-decoration: none; border: 0;
}
.vfy-btn:hover { background: #264fc4; color: #fff; text-decoration: none; }
.vfy-btn.ghost { background: #fff; color: var(--ink); border: 1px solid var(--border); }
.vfy-btn.ghost:hover { background: var(--off-white); color: var(--ink); border-color: var(--accent-blue); }
.vfy-btn:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }

.vfy-rows { margin: 0; }
.vfy-row { display: flex; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--border-soft, #f2f3f6); }
.vfy-row:last-child { border-bottom: 0; }
.vfy-row dt { font-size: 14.5px; color: var(--muted); margin: 0; }
.vfy-row dd { font-size: 15px; font-weight: 600; color: var(--ink); margin: 0; text-align: right; overflow-wrap: anywhere; }
.vfy-row dd.mono { font-family: var(--font-mono); font-size: 14.5px; }

.vfy-note { margin-top: 24px; padding: 0 4px; }
.vfy-note-h { font-size: 13px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted-2); margin: 0 0 10px; }
.vfy-note p { font-size: 14px; line-height: 1.75; color: var(--muted); margin: 0 0 12px; }
.vfy-ruo { font-size: 13px; color: var(--muted-2); margin: 0; }

.vfy-back { margin: 26px 0 0; text-align: center; font-size: 14px; }
.vfy-back a { color: var(--accent-blue); text-decoration: none; font-weight: 600; }
.vfy-back a:hover { text-decoration: underline; }
.vfy-dot { color: var(--muted-2); margin: 0 8px; }

@media (max-width: 640px) {
    .vfy { padding: 28px 16px 56px; }
    .vfy-card { padding: 20px 18px; }
    .vfy-row { flex-direction: column; gap: 3px; }
    .vfy-row dd { text-align: left; }
    .vfy-act .vfy-btn { width: 100%; }
}



/* =========================================================================
   ADMIN: QR SCAN TEST SHEET  (/admin/batches/{id}/label-test)
   Sizes are in MILLIMETRES on purpose. This page exists to be printed at 100
   percent and held against a vial, so a comparison rendered at the wrong size
   would prove nothing.
   ========================================================================= */


.qrt-meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); margin-top: 8px; }
/* A printed reference, so a printer that silently scaled can be caught. */


@media print {
    /* Everything but the sheet itself goes, so the codes print at true size on
       a clean page. Backgrounds are forced white: a tinted panel behind a QR
       lowers contrast and is exactly what a marginal scan cannot afford. */
    .admin-sidebar, .admin-menu-toggle, .vnd-top, .qrt-noprint, .admin-sidebar-foot { display: none !important; }
    .admin-main, .admin-main-inner { margin: 0 !important; padding: 0 !important; max-width: none !important; }
    body, .admin-body { background: #fff !important; }
}


/* Scan test sheet: three variants per footprint, so the row wraps rather than
   forcing a horizontal scroll on a narrow screen. */

.qrt-meta.is-ok   { color: #1fa863; }
.qrt-meta.is-warn { color: #9a6a00; }
.qrt-meta.is-bad  { color: #d83b3b; }

/* ---- label generator: the live readout beside the QR controls ---- */
.lbl-readout {
    margin-top: 14px; padding: 12px 14px; border-radius: 10px;
    font-family: var(--font-mono); font-size: 12.5px; line-height: 1.7;
    background: var(--off-white); border: 1px solid var(--border); color: var(--ink);
}
.lbl-readout.is-ok   { background: #f2fbf6; border-color: #bfe6d1; }
.lbl-readout.is-warn { background: #fdf8e6; border-color: #efdfa8; }
.lbl-readout.is-bad  { background: #fdf1f1; border-color: #f0d2d2; }
.lbl-save { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-soft, #f2f3f6); }

/* =========================================================================
   ADMIN: CONTACT SHEET  (/admin/batches/{id}/contact-sheet)
   Printed at 100 percent and wrapped round a vial, so the cells are sized in
   millimetres and the grid wraps rather than scrolling.
   ========================================================================= */

.cs-meta.is-ok { color: #1fa863; }
.cs-meta.is-warn { color: #9a6a00; }
.cs-meta.is-bad { color: #d83b3b; }