/*
 * Mirath storefront stylesheet.
 *
 * Hand-written and shipped as a static asset on purpose: the deployment target
 * has no Node toolchain, so there is no build step to run. Layout uses CSS
 * logical properties throughout (margin-inline, padding-inline, border-inline)
 * so Persian and Arabic RTL work from `dir="rtl"` alone with no mirrored rules.
 */

/*
 * PERSIAN AND ARABIC TYPE.
 *
 * Both faces are self-hosted rather than pulled from a font CDN: the audience
 * for these two locales is exactly the audience most likely to have Google
 * Fonts blocked or slow, and a webfont that fails to load is worse than never
 * having asked for one.
 *
 * Both are VARIABLE, so one request covers every weight - a page using
 * regular and semibold costs one download rather than two. `font-display:
 * swap` keeps text readable in the fallback face from first paint, and
 * neither file is fetched on an English page at all, because `--font-rtl` is
 * the only place either family is named.
 *
 * LICENCES DIFFER, and this matters before deploying:
 *
 *   Yekan Bakh 4  - a commercial face, supplied by the client. The archive
 *                   ships its own terms (License Help.pdf and
 *                   شرایط-استفاده.pdf). Committing the .woff2 to this repo
 *                   redistributes it, so confirm the licence covers that and
 *                   covers serving it from this domain.
 *   Vazirmatn     - SIL Open Font License 1.1, see fonts/Vazirmatn-OFL.txt.
 *                   Free to bundle and serve; kept as the fallback.
 */
/*
 * Yekan Bakh 4, the brand's Persian and Arabic face.
 *
 * The VARIABLE webfont, so one 168KB request covers every weight from 100 to
 * 950 - the static family in the same archive is ninety-odd files and would
 * need a separate download per weight used.
 *
 * The plain `YekanBakh-VF` cut, NOT `YekanBakhFaNum-VF`. The FaNum variant
 * renders digits as Persian numerals, and this is a shop: prices, UV figures,
 * SKUs and order references all carry Latin digits from the Money and Uv
 * value objects, and the admin panel shows them that way too. Swapping the
 * file here is the whole change if Persian numerals are wanted later.
 *
 * Both axes are declared. Without the `font-stretch` range the width axis is
 * unreachable and the browser clamps to the default instance.
 */
@font-face {
    font-family: "Yekan Bakh";
    src: url("../fonts/YekanBakh-VF.woff2") format("woff2");
    font-weight: 100 950;
    font-stretch: 62.5% 150%;
    font-style: normal;
    font-display: swap;
}

/* Kept as the fallback behind Yekan Bakh. It costs nothing while the first
   family resolves - a @font-face is only fetched when something needs it. */
@font-face {
    font-family: "Vazirmatn";
    src: url("../fonts/vazirmatn-variable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

/*
 * PALETTE — built from the brand colour #00382b.
 *
 * #00382b is hsl(166, 100%, 11%): a deep pine green. It is far too dark to be
 * a mid-tone, so it is not treated as one. It anchors the BOTTOM of a ramp
 * (the 900 step below), and everything else is the same hue at other
 * lightnesses. That is what makes the site read as one colour rather than as
 * grey furniture with green buttons.
 *
 *   50  #f2f8f6   400 #62bca7   800 #065b47
 *   100 #e1efec   500 #38a88e   900 #00382b  <- the brand colour, exactly
 *   200 #c1e1d9   600 #1e8069   950 #00241b
 *   300 #97cec1   700 #11745d
 *
 * The neutrals are not grey. They carry the same 166° hue at 10-30%
 * saturation, so surfaces, borders and body text all sit in the brand family;
 * a truly neutral grey next to this green looks accidental.
 *
 * STATUS COLOURS. The brand being green is a problem for --success, which is
 * also green: if the two match, "success" stops reading as a state and reads
 * as chrome. --success is therefore pulled to 123° (a yellower, brighter
 * green) and kept lighter than the brand, and warning/danger stay warm so
 * they carry weight against an otherwise green page.
 */
:root {
    --brand: #00382b;
    --bg: #f7faf9;
    --surface: #ffffff;
    --surface-alt: #ecf3f1;
    --border: #d5e2df;
    --text: #12211e;
    --text-muted: #5c706b;
    --accent: #11745d;
    --accent-strong: #00382b;
    --accent-soft: #e1efec;
    /* Text drawn ON an accent or success fill. A token rather than a literal
       #fff because in dark mode those fills become light mint, where white
       text falls to about 1.6:1 - it has to flip to near-black. */
    --on-accent: #ffffff;
    --on-success: #ffffff;
    --success: #2e7d32;
    --success-soft: #e4f2e5;
    --warning: #97600f;
    --warning-soft: #fbf0d8;
    --danger: #a32d24;
    --danger-soft: #fae4e1;
    --radius: 10px;
    --radius-sm: 6px;
    /* Tinted with the brand rather than black: a neutral shadow under a green
       card reads as dirt, a green-tinted one as depth. */
    --shadow: 0 1px 2px rgba(0, 30, 22, .07), 0 6px 20px rgba(0, 30, 22, .06);
    --font: "Segoe UI", system-ui, -apple-system, sans-serif;
    --font-rtl: "Yekan Bakh", "Vazirmatn", Tahoma, "Iranian Sans", "Noto Naskh Arabic", system-ui, sans-serif;
    --measure: 68ch;
}

/*
 * Theme has three states, not two: light, dark, and "follow the system".
 *
 * The light palette above is the base. Dark applies either when the system
 * asks for it AND the visitor has not explicitly chosen light, or whenever the
 * visitor has explicitly chosen dark. Those are two different selectors, and
 * one of them lives inside a media query, so CSS cannot express them as one
 * rule.
 *
 * It CAN avoid writing the colours twice, which is the part that matters: a
 * literal that drifts between the two copies is a real bug, whereas the
 * aliasing below is mechanical. The dark values are therefore declared once,
 * unconditionally, as --dk-*, and each selector only re-points the live tokens
 * at them.
 *
 * The explicit choice is stamped on <html> as data-theme by the inline script
 * in the layout head, which runs before first paint so switching never flashes
 * the wrong palette.
 */
:root {
    --dk-bg: #0b1412;
    --dk-surface: #111d1a;
    --dk-surface-alt: #192925;
    --dk-border: #2a413c;
    --dk-text: #eaf1ef;
    --dk-text-muted: #92aaa4;
    /* #00382b is invisible on a dark ground, so the accent climbs the ramp
       instead of sitting at the bottom of it: 500 for fills, 400 for links,
       which need the extra brightness. */
    --dk-accent: #38a88e;
    --dk-accent-strong: #62bca7;
    --dk-accent-soft: #142e28;
    --dk-on-accent: #04150f;
    --dk-success: #5fbf6a;
    --dk-success-soft: #16301a;
    --dk-warning: #d9b45f;
    --dk-warning-soft: #322912;
    --dk-danger: #e08a7e;
    --dk-danger-soft: #331d1a;
    --dk-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 6px 20px rgba(0, 0, 0, .38);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: var(--dk-bg);
        --surface: var(--dk-surface);
        --surface-alt: var(--dk-surface-alt);
        --border: var(--dk-border);
        --text: var(--dk-text);
        --text-muted: var(--dk-text-muted);
        --accent: var(--dk-accent);
        --accent-strong: var(--dk-accent-strong);
        --accent-soft: var(--dk-accent-soft);
        --on-accent: var(--dk-on-accent);
        --on-success: var(--dk-on-accent);
        --success: var(--dk-success);
        --success-soft: var(--dk-success-soft);
        --warning: var(--dk-warning);
        --warning-soft: var(--dk-warning-soft);
        --danger: var(--dk-danger);
        --danger-soft: var(--dk-danger-soft);
        --shadow: var(--dk-shadow);
    }
}

:root[data-theme="dark"] {
    --bg: var(--dk-bg);
    --surface: var(--dk-surface);
    --surface-alt: var(--dk-surface-alt);
    --border: var(--dk-border);
    --text: var(--dk-text);
    --text-muted: var(--dk-text-muted);
    --accent: var(--dk-accent);
    --accent-strong: var(--dk-accent-strong);
    --accent-soft: var(--dk-accent-soft);
    --on-accent: var(--dk-on-accent);
    --on-success: var(--dk-on-accent);
    --success: var(--dk-success);
    --success-soft: var(--dk-success-soft);
    --warning: var(--dk-warning);
    --warning-soft: var(--dk-warning-soft);
    --danger: var(--dk-danger);
    --danger-soft: var(--dk-danger-soft);
    --shadow: var(--dk-shadow);
}

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

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 16px;
    line-height: 1.6;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

[dir="rtl"] body { font-family: var(--font-rtl); }

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 .5em; font-weight: 650; }
h1 { font-size: 1.9rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.15rem; }
p { margin: 0 0 1em; }

a { color: var(--accent-strong); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent); }

/*
   The page width, adjustable from the admin panel.

   `min()` keeps the responsive behaviour whatever the setting: below the
   chosen width the container is always `100% - 2rem`, so the side gutter
   survives at every screen size and no value an admin picks can make the page
   overflow a phone. The fallback is the width the site was designed at.
*/
/*
 * The space between the page and the screen edge grows with the screen: 16px
 * on a phone, about 28px on a tablet held upright, 37px on one held sideways,
 * up to 40px. It used to be 16px everywhere below 1152px, so on every tablet
 * the logo, the menu and the footer sat right against the glass.
 */
:root { --gutter: clamp(1rem, 3.6vw, 2.5rem); }

.container { width: min(var(--site-width, 1120px), 100% - 2 * var(--gutter)); margin-inline: auto; }

.skip-link {
    position: absolute;
    inset-inline-start: -9999px;
}
.skip-link:focus {
    inset-inline-start: 1rem;
    inset-block-start: 1rem;
    z-index: 10;
    background: var(--surface);
    padding: .5rem .75rem;
    border-radius: var(--radius-sm);
}

/* ---------- header ---------- */

.site-header {
    background: var(--surface);
    border-block-end: 1px solid var(--border);
}

.header-inner {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding-block: .85rem;
    flex-wrap: wrap;
}

.brand {
    display: inline-flex;
    align-items: center;
    color: var(--text);
    text-decoration: none;
}

/*
 * The Miras NET lockup.
 *
 * Both colourways are in the markup and CSS picks one, because the theme has
 * THREE states - explicit light, explicit dark, and "follow the system" - and
 * only CSS can see all three. Choosing server-side would need a cookie the
 * first paint does not have, and would flash the wrong one.
 *
 * The supplied artwork is raster, so these are two different images rather
 * than one file tinted with `currentColor`; a filter cannot turn the dark
 * green lockup into the white one without wrecking the accent glyph.
 */
.brand-logo {
    display: inline-flex;
    align-items: center;
    /* Set per use site via the component's inline custom property. */
    block-size: var(--brand-logo-height, 34px);
}

.brand-logo-img {
    display: block;
    block-size: var(--brand-logo-height, 34px);
    inline-size: auto;
    /* The intrinsic width/height attributes reserve the right box before the
       image loads; without this the aspect ratio fights them. */
    max-inline-size: none;
}

/* Light is the default, so the dark-ink lockup shows and the white one is
   removed from layout entirely rather than merely hidden. */
.brand-logo-img.is-on-dark { display: none; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .brand-logo-img.is-on-light { display: none; }
    :root:not([data-theme="light"]) .brand-logo-img.is-on-dark { display: block; }
}

:root[data-theme="dark"] .brand-logo-img.is-on-light { display: none; }
:root[data-theme="dark"] .brand-logo-img.is-on-dark { display: block; }


.site-nav {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/*
 * The blanket `.site-nav a` colour rule that used to live here is gone.
 *
 * When the nav held nothing but plain links it was harmless. Now the mega
 * menu lives inside .site-nav, and at specificity (0,1,1) that rule beat
 * `.btn` (0,1,0) - so the call-to-action button inside the panel rendered its
 * label in muted grey on the accent fill, close to unreadable. Links in the
 * nav carry .nav-link and style themselves; the old .lang-option rules went
 * with it, since the language control is a dropdown now and its outer element
 * is the <details> itself.
 *
 * The rest of the header - actions, dropdowns, mega menu, cart badge - is at
 * the foot of this file.
 */

/* ---------- layout ---------- */

.main { flex: 1; padding-block: 2rem 3rem; }

.site-footer {
    background: var(--surface);
    border-block-start: 1px solid var(--border);
    padding-block: 1.5rem;
    font-size: .9rem;
}
.footer-inner { display: grid; gap: .5rem; }
.footer-nav { display: flex; flex-wrap: wrap; gap: 1rem; }
.footer-nav a { color: var(--text-muted); text-decoration: none; }
.footer-nav a:hover { color: var(--text); }

.muted { color: var(--text-muted); }
.small { font-size: .85rem; }
.prose { max-width: var(--measure); }

/* ---------- components ---------- */

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

.grid { display: grid; gap: 1rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    padding: .55rem 1.1rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--accent-strong);
    background: var(--accent-strong);
    color: var(--on-accent);
    font: inherit;
    font-weight: 550;
    text-decoration: none;
    cursor: pointer;
}
.btn:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-sm { padding: .35rem .75rem; font-size: .9rem; }
.btn-secondary {
    background: transparent;
    color: var(--accent-strong);
}
.btn-secondary:hover { background: var(--accent-soft); color: var(--accent-strong); }

.banner {
    padding: .7rem 1rem;
    border-radius: var(--radius-sm);
    border-inline-start: 4px solid transparent;
    margin-block-end: 1.25rem;
    font-size: .95rem;
}
.banner-info { background: var(--surface-alt); border-inline-start-color: var(--accent); }
.banner-warning { background: var(--warning-soft); border-inline-start-color: var(--warning); color: var(--warning); }
.banner-danger { background: var(--danger-soft); border-inline-start-color: var(--danger); color: var(--danger); }
.banner-success { background: var(--success-soft); border-inline-start-color: var(--success); color: var(--success); }

/*
 * Viewing as somebody else.
 *
 * Full-bleed and above the container rather than inside it, with no
 * margin-block-end, so it reads as a property of the whole session and not as
 * one more notice about the page. An admin who loses track of the fact that
 * they are inside a member's account will misread every figure on it.
 */
.banner-impersonating {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    margin-block-end: 0;
    border-radius: 0;
    border-inline-start: 0;
    background: var(--danger-soft);
    color: var(--danger);
    font-weight: 500;
    text-align: center;
}

.banner-impersonating .btn {
    padding: .25rem .75rem;
    font-size: .85rem;
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.badge {
    display: inline-block;
    padding: .15rem .55rem;
    border-radius: 999px;
    font-size: .8rem;
    background: var(--surface-alt);
    color: var(--text-muted);
}
.badge-success { background: var(--success-soft); color: var(--success); }
.badge-warning { background: var(--warning-soft); color: var(--warning); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: start; padding: .6rem .75rem; border-block-end: 1px solid var(--border); }

/* A table wider than a phone scrolls sideways inside its card. Most member
   pages put a bare <table> straight into a .card, and a table never shrinks
   below its content - so on a phone it widened the whole page, and the tab
   bar and every other fixed element with it. */
.card:has(> table),
.card:has(> form > table) {
    overflow-x: auto;
    overscroll-behavior-x: contain;
}
th { font-weight: 600; color: var(--text-muted); font-size: .85rem; text-transform: uppercase; letter-spacing: .03em; }

/* Numeric columns stay LTR even in an RTL page so amounts read correctly. */
.numeric { font-variant-numeric: tabular-nums; direction: ltr; text-align: end; }

label { display: block; font-weight: 550; margin-block-end: .3rem; font-size: .9rem; }
input, select, textarea {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
}
input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}
.field { margin-block-end: 1rem; }
.field-error { color: var(--danger); font-size: .85rem; margin-block-start: .25rem; }

/* ---------- auth ---------- */

/* .auth-shell is defined with the rest of the auth pages, further down. */

.checkbox-row {
    display: flex;
    align-items: flex-start;
    gap: .55rem;
    font-weight: 400;
    line-height: 1.45;
}
.checkbox-row input[type="checkbox"] {
    width: auto;
    margin-block-start: .25rem;
    flex: none;
}

.inline-form { display: inline; }
.link-button {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--text-muted);
    cursor: pointer;
    text-decoration: none;
}
.link-button:hover { color: var(--text); }

/* ==========================================================================
   Storefront
   --------------------------------------------------------------------------
   The catalogue is the shop window for handmade objects, so the product grid
   gets real visual weight rather than the generic .card treatment used by
   dashboards and forms.

   Product photography is optional in this build (a product may ship before
   its images are uploaded), so .product-media always reserves the same
   square regardless. An unphotographed product falls back to a woven
   pattern and its own initial rather than an empty box or a broken-image
   icon - the grid keeps its rhythm either way, which is the whole reason the
   fallback is drawn rather than left blank.
   ========================================================================== */

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

@media (max-width: 860px) {
    .shop-layout { grid-template-columns: 1fr; gap: 1.5rem; }
}

/* ---------- sidebar ---------- */

.shop-sidebar {
    position: sticky;
    top: 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem;
    box-shadow: var(--shadow);
}

@media (max-width: 860px) {
    .shop-sidebar { position: static; }
}

.shop-sidebar h2 {
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
    margin-block-end: .9rem;
}

.category-nav { display: flex; flex-direction: column; gap: .15rem; }

.category-nav a {
    display: block;
    padding: .5rem .7rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    text-decoration: none;
    font-size: .95rem;
    transition: background .15s ease, color .15s ease;
}

.category-nav a:hover { background: var(--surface-alt); color: var(--text); }

/* The selected category is the emphasised one. It previously used .muted,
   which made the active item look disabled - exactly backwards. */
.category-nav a[aria-current="page"] {
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: 600;
}

/* ---------- toolbar ---------- */

.shop-header {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: space-between;
    align-items: flex-end;
    margin-block-end: 1.75rem;
    padding-block-end: 1.25rem;
    border-block-end: 1px solid var(--border);
}

.shop-header h1 { margin: 0; }
.shop-header .result-count { color: var(--text-muted); font-size: .9rem; margin: .35rem 0 0; }

/* No min-width: the search box now lives inside the 232px filter rail, and a
   240px minimum made it hang out over the edge of its own panel. It fills
   whatever it is given instead. */
.search-field { position: relative; }
.search-field input { width: 100%; padding-inline-end: 2.5rem; }
.search-field button {
    position: absolute;
    inset-inline-end: .35rem;
    inset-block-start: 50%;
    transform: translateY(-50%);
    background: none;
    border: 0;
    padding: .3rem;
    cursor: pointer;
    color: var(--text-muted);
    line-height: 0;
}
.search-field button:hover { color: var(--accent); }

/* ---------- product grid ---------- */

.product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 1.5rem;
}

.product-card {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}

.product-card:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
    box-shadow: 0 10px 28px rgba(34, 29, 23, .12);
    color: inherit;
}

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

.product-media {
    position: relative;
    aspect-ratio: 1;
    background: var(--surface-alt);
    overflow: hidden;
}

.product-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .3s ease;
}

.product-card:hover .product-media img { transform: scale(1.04); }

/*
 * Fallback for a product with no photograph yet: a woven diagonal weave in
 * the brand accent with the product's initial. Drawn in CSS so it costs no
 * request and adapts to light and dark automatically.
 */
.product-media-fallback {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background:
        repeating-linear-gradient(45deg, var(--accent-soft) 0 6px, transparent 6px 12px),
        repeating-linear-gradient(-45deg, var(--accent-soft) 0 6px, transparent 6px 12px),
        var(--surface-alt);
}

.product-media-fallback span {
    font-size: 2rem;
    font-weight: 700;
    color: var(--accent);
    opacity: .55;
    background: var(--surface);
    width: 3.4rem;
    height: 3.4rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    box-shadow: var(--shadow);
}

.product-body {
    padding: .9rem 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: .3rem;
    flex: 1;
}

.product-name {
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.35;
    margin: 0;
}

.product-price {
    font-variant-numeric: tabular-nums;
    direction: ltr;
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--accent-strong);
    margin: 0;
}

/* Pushes the price to the bottom so cards with different name lengths still
   line their prices up across a row. */
.product-body .product-price { margin-block-start: auto; padding-block-start: .35rem; }

.product-flag {
    position: absolute;
    inset-block-start: .6rem;
    inset-inline-start: .6rem;
    background: var(--warning-soft);
    color: var(--warning);
    font-size: .75rem;
    font-weight: 600;
    padding: .2rem .5rem;
    border-radius: 999px;
}

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

.product-detail {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 2.5rem;
    align-items: start;
}

@media (max-width: 860px) {
    .product-detail { grid-template-columns: 1fr; gap: 1.5rem; }
}

/* ---------- product gallery ----------

   A strip of every photograph that swipes on a phone, with arrows, a counter
   and thumbnails; a tap opens the full-screen viewer (gallery.js). The strip
   and the thumbnails are dir="ltr" in the markup, so the physical left/right
   below are deliberate. */

.product-gallery { min-inline-size: 0; }

.product-gallery-main {
    position: relative;
    aspect-ratio: 1;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface-alt);
}

.product-gallery-main img { width: 100%; height: 100%; object-fit: cover; display: block; }

.pg-track {
    display: flex;
    inline-size: 100%;
    block-size: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
}
.pg-track::-webkit-scrollbar { display: none; }
.pg-track:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.pg-slide {
    flex: 0 0 100%;
    block-size: 100%;
    padding: 0;
    border: 0;
    background: none;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    cursor: zoom-in;
}

.pg-slide img { -webkit-user-drag: none; user-select: none; }

.pg-zoom-badge,
.pg-counter,
.pg-arrow {
    position: absolute;
    z-index: 1;
    color: #fff;
    background: rgba(10, 20, 17, .55);
    backdrop-filter: blur(4px);
}

.pg-zoom-badge {
    top: .6rem;
    right: .6rem;
    display: grid;
    place-items: center;
    inline-size: 2rem;
    block-size: 2rem;
    border-radius: 50%;
    pointer-events: none;
}

.pg-counter {
    bottom: .6rem;
    left: .6rem;
    padding: .15rem .6rem;
    border-radius: 999px;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}

.pg-arrow {
    top: 50%;
    display: grid;
    place-items: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    margin-block-start: -1.25rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    cursor: pointer;
    opacity: .85;
    transition: opacity .15s ease, background .15s ease;
}
.pg-arrow:hover { opacity: 1; background: rgba(10, 20, 17, .75); }
.pg-arrow-prev { left: .6rem; }
.pg-arrow-next { right: .6rem; }

.product-thumbs {
    display: flex;
    gap: .55rem;
    margin-block-start: .75rem;
    padding-block-end: .15rem;
    overflow-x: auto;
    scrollbar-width: thin;
}

.pg-thumb {
    flex: 0 0 4.5rem;
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    cursor: pointer;
    opacity: .65;
    transition: opacity .15s ease, border-color .15s ease;
}
.pg-thumb:hover { opacity: 1; }
.pg-thumb.is-active { border-color: var(--accent); opacity: 1; }

.pg-thumb img {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: calc(var(--radius-sm) - 2px);
}

/* ---- the full-screen viewer ---- */

html.is-lightbox-open { overflow: hidden; }

.lightbox {
    position: fixed;
    inset: 0;
    inline-size: 100vw;
    block-size: 100vh;
    block-size: 100dvh;
    max-inline-size: none;
    max-block-size: none;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    color: #fff;
    background: rgba(6, 10, 9, calc(.98 * var(--lb-fade, 1)));
}
.lightbox::backdrop { background: transparent; }

.lb-stage {
    position: absolute;
    inset: 0;
    overflow: hidden;
    /* Every gesture here is ours: swipe, pinch and pan. */
    touch-action: none;
}

.lb-track {
    display: flex;
    block-size: 100%;
    will-change: transform;
}

.lb-slide {
    flex: 0 0 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: 100%;
    overflow: hidden;
}

.lb-slide img {
    display: block;
    max-inline-size: 100%;
    max-block-size: 100%;
    object-fit: contain;
    transform-origin: center;
    user-select: none;
    -webkit-user-drag: none;
    cursor: zoom-in;
}
.lightbox.is-zoomed .lb-slide img { cursor: grab; }

.lb-bar {
    position: absolute;
    top: 0;
    inset-inline: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: max(.75rem, env(safe-area-inset-top, 0px)) 1rem 1.5rem;
    background: linear-gradient(rgba(0, 0, 0, .55), transparent);
    pointer-events: none;
}
.lb-bar > * { pointer-events: auto; }

.lb-counter { font-size: .9rem; font-variant-numeric: tabular-nums; }

.lb-close,
.lb-arrow {
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    color: #fff;
    background: rgba(255, 255, 255, .14);
    cursor: pointer;
    transition: background .15s ease;
}
.lb-close:hover,
.lb-arrow:hover { background: rgba(255, 255, 255, .26); }

.lb-close { inline-size: 2.75rem; block-size: 2.75rem; }

.lb-arrow {
    position: absolute;
    top: 50%;
    z-index: 2;
    inline-size: 3rem;
    block-size: 3rem;
    margin-block-start: -1.5rem;
}
.lb-arrow-prev { left: 1rem; }
.lb-arrow-next { right: 1rem; }

.lb-hint {
    position: absolute;
    bottom: max(1rem, env(safe-area-inset-bottom, 0px));
    inset-inline: 1rem;
    z-index: 2;
    margin: 0;
    font-size: .8rem;
    text-align: center;
    opacity: .7;
    pointer-events: none;
    transition: opacity .2s ease;
}
.lightbox.is-zoomed .lb-hint,
.lightbox.is-zoomed .lb-arrow { opacity: 0; pointer-events: none; }

/* On a touch screen the arrows sit low and small: swiping is the gesture,
   and big arrows over the middle of the photograph would cover it. */
@media (hover: none) {
    .lb-arrow { top: auto; bottom: calc(max(1rem, env(safe-area-inset-bottom, 0px)) + 2rem); inline-size: 2.6rem; block-size: 2.6rem; margin: 0; }
    .pg-arrow { inline-size: 2.2rem; block-size: 2.2rem; margin-block-start: -1.1rem; }
}

@media (max-width: 560px) {
    .pg-thumb { flex-basis: 3.75rem; }
}

.product-detail h1 { margin-block-end: .35rem; }

.product-detail .lede {
    color: var(--text-muted);
    font-size: 1.05rem;
    margin-block-end: 1.25rem;
}

.product-detail .price-large {
    font-size: 1.9rem;
    font-weight: 700;
    color: var(--accent-strong);
    font-variant-numeric: tabular-nums;
    direction: ltr;
    margin-block: 0 1rem;
}

.product-meta {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-block-end: 1.25rem;
}

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

.buy-box .field { margin-block-end: .85rem; }
.buy-box input[type="number"] { max-width: 7rem; }

.spec-list { margin-block-start: 1.5rem; }
.spec-list dt { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.spec-list dd { margin: 0 0 .85rem; font-weight: 550; }

/* Visible to screen readers only - used for labels on icon-only controls. */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---------- theme toggle ---------- */

/*
 * One button, two icons.
 *
 * Both icons are in the markup and CSS picks which one shows, rather than the
 * script swapping innerHTML: a JS swap paints the wrong icon for one frame on
 * a dark-themed first load, and that flash is exactly what the inline
 * theme-boot script in the <head> exists to avoid everywhere else.
 */
.theme-toggle {
    width: auto;
    display: grid;
    place-items: center;
    padding: .4rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-alt);
    color: var(--text-muted);
    cursor: pointer;
    line-height: 0;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}

.theme-toggle:hover {
    color: var(--accent-strong);
    border-color: var(--accent);
    background: var(--accent-soft);
}

.theme-toggle svg { width: 16px; height: 16px; }

/*
 * Stacked in one grid cell so the button never changes size between states -
 * a control that resizes as you press it shifts the whole header row.
 */
.theme-icon { grid-area: 1 / 1; }

/*
 * In light mode the button offers dark, so it shows the moon; in dark mode it
 * offers light, so it shows the sun. The icon is the ACTION, not the state.
 *
 * Keyed ONLY on `data-theme`, which the inline script in the document head
 * always sets before the first paint - including resolving the OS preference
 * when there is no stored choice.
 *
 * There used to be a `prefers-color-scheme` block here as well, for the case
 * where the attribute was absent. It had identical specificity to the
 * `[data-theme="dark"]` rules, so which of them won came down to source
 * order, and whether the attribute existed yet came down to script timing -
 * which is why the glyph was right on one refresh and wrong on the next. With
 * the attribute guaranteed, the ambiguity has nothing to be ambiguous about.
 */
.theme-icon-sun { display: none; }
.theme-icon-moon { display: block; }

:root[data-theme="dark"] .theme-icon-sun { display: block; }
:root[data-theme="dark"] .theme-icon-moon { display: none; }

/* ---------- error pages ---------- */

.error-body {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: 2rem 1rem;
    /* A soft brand glow rather than the diagonal weave that used to be here.
       The hatching read as a placeholder - a stand-in for artwork nobody had
       drawn yet - which is the opposite of what an error page should say. */
    background:
        radial-gradient(60rem 34rem at 50% -12%, var(--accent-soft), transparent 65%),
        var(--bg);
}

.error-shell {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 2.5rem 2rem;
    max-width: 30rem;
    width: 100%;
    text-align: center;
}

.error-brand {
    justify-content: center;
    margin-block-end: 1.75rem;
    font-size: 1.05rem;
}

.error-code {
    font-size: 3.75rem;
    font-weight: 700;
    line-height: 1;
    color: var(--accent);
    opacity: .35;
    margin: 0 0 .5rem;
    font-variant-numeric: tabular-nums;
    direction: ltr;
}

.error-title { font-size: 1.35rem; margin-block-end: .6rem; }

.error-message {
    color: var(--text-muted);
    margin-block-end: 1.75rem;
    text-wrap: pretty;
}

.error-actions {
    display: flex;
    gap: .6rem;
    justify-content: center;
    flex-wrap: wrap;
}

.error-detail {
    margin-block: 1.75rem 0;
    padding-block-start: 1.25rem;
    border-block-start: 1px solid var(--border);
    color: var(--text-muted);
    font-size: .85rem;
}

/* UV is a first-class number in this catalogue, not a footnote: it decides
   what a purchase contributes to activation and to the upline's matching. */
.product-uv {
    margin: .1rem 0 0;
    font-size: .82rem;
    color: var(--success);
    font-variant-numeric: tabular-nums;
    direction: ltr;
    text-align: start;
}

.uv-line {
    display: flex;
    align-items: center;
    gap: .6rem;
    flex-wrap: wrap;
    margin-block-end: 1.25rem;
}

/* ==========================================================================
   Landing page
   ========================================================================== */

.hero {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
    gap: 3rem;
    align-items: center;
    padding-block: 1rem 3.5rem;
}

@media (max-width: 800px) {
    .hero { grid-template-columns: 1fr; gap: 2rem; padding-block-end: 2.5rem; }
    .hero-art { order: -1; max-width: 260px; margin-inline: auto; }
}

.hero-eyebrow {
    text-transform: uppercase;
    letter-spacing: .12em;
    font-size: .78rem;
    font-weight: 600;
    color: var(--accent);
    margin-block-end: .75rem;
}

.hero-title {
    font-size: clamp(2rem, 4.5vw, 3.1rem);
    line-height: 1.12;
    margin-block-end: 1rem;
    text-wrap: balance;
}

.hero-lede {
    font-size: 1.1rem;
    color: var(--text-muted);
    max-width: 48ch;
    margin-block-end: 1.75rem;
    text-wrap: pretty;
}

.hero-actions { display: flex; gap: .75rem; flex-wrap: wrap; }

/*
 * The hero photograph.
 *
 * Replaces the CSS medallion that stood in while the project had no
 * photography. The `.ha-*` rules that drew it are gone with it - keeping them
 * would leave a stylesheet describing an element nothing renders.
 */
.hero-art {
    position: relative;
    border-radius: var(--radius);
    overflow: hidden;
    /* Matches the source frames (2400x1792), so the box is the right shape
       before the image arrives and the hero does not jump on load. */
    aspect-ratio: 2400 / 1792;
    background: var(--surface-alt);
}

.hero-art picture,
.hero-photo {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
}

/* ---------- collections strip ---------- */

/*
 * The collections grid: six equal tiles, three across.
 *
 * Two earlier versions were worse. 4+2 / 2+2+2 / 4 on six columns left a hole
 * in the last row and stretched a 363px-wide photo to 599px tall to match the
 * wide tile next to it. Mirroring the wide tile across two rows tiled exactly
 * but still mixed sizes, which read as unbalanced.
 *
 * Equal tiles suit the material: the photographs are a set of styled scenes
 * shot at one ratio, with no hero among them, so a mosaic would imply a
 * hierarchy that is not there.
 *
 * `aspect-ratio` rather than a fixed row height, so the tiles keep the shape
 * the photographs were composed in at every width instead of being cropped
 * harder as the viewport narrows.
 */
.showcase-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.1rem;
}

.showcase-tile {
    position: relative;
    margin: 0;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--border);
}

.showcase-tile picture,
.showcase-tile img {
    display: block;
    inline-size: 100%;
    block-size: 100%;
}

.showcase-tile img {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform .5s ease;
}

.showcase-tile:hover img { transform: scale(1.04); }

@media (prefers-reduced-motion: reduce) {
    .showcase-tile img,
    .showcase-tile:hover img { transition: none; transform: none; }
}

/*
 * The caption sits ON the photograph, over a scrim.
 *
 * Below it, a caption that wraps to two lines would make that one tile taller
 * than the rest of its row - which is how an even grid goes ragged again, and
 * these captions are full sentences in three languages.
 */
.showcase-tile figcaption {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    padding: 2.4rem .9rem .8rem;
    font-size: .85rem;
    font-weight: 500;
    line-height: 1.45;
    color: #fff;
    /* Dark at the bottom, clear at the top: these scenes are centre-weighted,
       so a full overlay would sit on the subject. */
    background: linear-gradient(to top, rgba(0, 20, 15, .85), rgba(0, 20, 15, .5) 45%, transparent);
    text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
}

/* Two across, which still divides six evenly - three across at tablet width
   makes each tile too small to read the objects in. */
@media (max-width: 1000px) {
    .showcase-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 640px) {
    .showcase-grid { grid-template-columns: 1fr; }
}
/* ---------- sections ---------- */

.home-section { padding-block: 2.5rem; border-block-start: 1px solid var(--border); }

.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-block-end: 1.5rem;
}
.section-head h2 { margin: 0; }
.section-head a { font-size: .9rem; }

/* ---------- categories ---------- */

/*
 * A FIXED column count, not `auto-fill`.
 *
 * auto-fill chose five columns at this width, which left the sixth category
 * alone on a row of its own - and rows size independently, so that orphan
 * came out 83px against its neighbours' 109px. Three divides six exactly, and
 * so does two at the next breakdown, so there is never a stranded tile.
 *
 * `grid-auto-rows: 1fr` then makes every row the height of the tallest, so a
 * category whose translated name wraps to two lines cannot make its own row
 * taller than the other.
 */
.category-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-rows: 1fr;
    gap: 1rem;
}

@media (max-width: 900px) {
    .category-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
    .category-grid { grid-template-columns: 1fr; }
}

/*
 * A craft tile: the mark inline with the name, the count under it, an arrow
 * at the end. On hover the whole tile fills with the brand green and a band
 * of light crosses it.
 *
 * The diagonal hatch that used to sit behind these is gone. It was competing
 * with the craft marks - both are fine line work at a similar weight, so the
 * mark had to fight the surface it sat on.
 *
 * All of this lives in ONE block. There used to be a second `.category-tile`
 * section further down the file that positioned the arrow and set its hover
 * colour, which meant a hover state written here was silently overridden by
 * source order. Merged, so the tile has a single definition.
 */
.category-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: .15rem;
    padding: 1.05rem 2.6rem 1.05rem 1.2rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    text-decoration: none;
    color: inherit;
    overflow: hidden;
    transition:
        background .25s ease,
        border-color .2s ease,
        color .25s ease,
        transform .25s cubic-bezier(.22, .61, .36, 1),
        box-shadow .25s ease;
}

/*
 * THE FILL. A gradient rather than a flat colour, so the tile has a lit edge
 * and reads as a surface catching light rather than a swatch.
 *
 * Both stops come from the palette, which is what makes this work in dark
 * mode: there `--accent` and `--accent-strong` are mints rather than pines,
 * and `--on-accent` flips from white to near-black to stay legible on them.
 * Hard-coding white here would be unreadable on the dark theme's fill.
 */
.category-tile:hover,
.category-tile:focus-visible {
    background: linear-gradient(135deg, var(--accent) 0%, var(--accent-strong) 100%);
    border-color: var(--accent-strong);
    color: var(--on-accent);
    transform: translateY(-3px);
    box-shadow: 0 14px 30px rgba(0, 30, 22, .22);
}

/* Every piece of text and line work inside inherits the on-accent colour, so
   nothing is left at a muted grey that vanishes against the fill. */
.category-tile:hover .category-tile-name,
.category-tile:hover .category-tile-count,
.category-tile:hover .category-tile-icon,
.category-tile:hover .category-tile-go,
.category-tile:focus-visible .category-tile-name,
.category-tile:focus-visible .category-tile-count,
.category-tile:focus-visible .category-tile-icon,
.category-tile:focus-visible .category-tile-go {
    color: var(--on-accent);
}

/*
 * THE STRIKE. A hard diagonal of light crosses the tile once on hover.
 *
 * A single sweeping gradient rather than a drawn bolt: at tile size a literal
 * lightning shape reads as a sticker, while a fast angled glare reads as
 * light travelling across a surface. It runs once per hover because a looping
 * flash across six tiles is a strobe.
 *
 * White at low alpha, not a palette colour - it has to read as a highlight on
 * both the pine fill and the mint one.
 *
 * `::after`, because `::before` carries the pointer spotlight every card on
 * the page shares.
 */
.category-tile::after {
    content: "";
    position: absolute;
    /* Oversized and rotated, so the band still covers the corners as it
       crosses the tile. */
    inset-block: -60%;
    inset-inline-start: -45%;
    inline-size: 45%;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    transform: rotate(18deg) translateX(0);
    background: linear-gradient(
        90deg,
        transparent,
        rgba(255, 255, 255, .10) 30%,
        rgba(255, 255, 255, .45) 50%,
        rgba(255, 255, 255, .10) 70%,
        transparent
    );
}

.category-tile:hover::after,
.category-tile:focus-visible::after {
    animation: craft-strike .6s cubic-bezier(.3, .7, .4, 1) 1;
}

@keyframes craft-strike {
    0% { opacity: 0; transform: rotate(18deg) translateX(0); }
    10% { opacity: 1; }
    100% { opacity: 0; transform: rotate(18deg) translateX(380%); }
}

/* The shared pointer spotlight is tinted for the light surface; on the green
   fill it has to become a soft white glow or it disappears. */
.category-tile:hover::before {
    background: radial-gradient(
        220px circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
        rgba(255, 255, 255, .16),
        transparent 70%
    );
}

/*
 * `inline-flex` with a centred alignment rather than raw inline: an inline
 * SVG sits on the text baseline and drops below it, which knocks the name a
 * couple of pixels out of line with the tiles either side.
 */
.category-tile-name {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    font-weight: 600;
}

/*
 * 28px. The craft marks all span y=3.5 to y=20.5 of the 24-unit drawing grid
 * (see ui-icon), so they line up with each other; this only sets how large
 * that band renders. The -1px nudge is optical, not geometric: the box
 * centres on the text's LINE box, which includes room for descenders, while
 * the eye levels the mark against the letter bodies above that centre.
 */
.category-tile-icon {
    flex: none;
    inline-size: 28px;
    block-size: 28px;
    margin-block-start: -1px;
    color: var(--accent-strong);
    transition: color .25s ease, transform .3s cubic-bezier(.22, .61, .36, 1);
}

.category-tile:hover .category-tile-icon { transform: translateY(-2px); }

/* Indented to the name's text, not the mark, so the two lines read as one
   block: the 28px mark plus the .6rem gap. */
.category-tile-count {
    position: relative;
    z-index: 2;
    margin-inline-start: calc(28px + .6rem);
    font-size: .82rem;
    color: var(--text-muted);
    transition: color .25s ease;
}

.category-tile-go {
    position: absolute;
    z-index: 2;
    inset-block-end: 1rem;
    inset-inline-end: 1rem;
    color: var(--text-muted);
    line-height: 0;
    opacity: 0;
    transition: opacity .2s ease, color .25s ease, transform .2s ease;
}

.category-tile:hover .category-tile-go,
.category-tile:focus-visible .category-tile-go { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
    .category-tile,
    .category-tile:hover,
    .category-tile-icon,
    .category-tile:hover .category-tile-icon {
        transition: none;
        transform: none;
    }

    /* Transition only: this element positions the arrow whose RTL mirror
       lives in `transform`. See the note on the arrows further down. */
    .category-tile-go {
        transition: none;
    }

    /* No strike - a fast flash is exactly what this setting exists to stop.
       The colour change stays, because that is state, not motion. */
    .category-tile::after,
    .category-tile:hover::after { display: none; animation: none; }
}
/* ---------- how it works ---------- */

.steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 1.5rem;
}

.step { position: relative; padding-block-start: .5rem; }

.step-number {
    display: grid;
    place-items: center;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: 700;
    font-size: .95rem;
    margin-block-end: .75rem;
    font-variant-numeric: tabular-nums;
}

.step-title { font-size: 1.02rem; margin-block-end: .35rem; }
.step-body { color: var(--text-muted); font-size: .94rem; margin: 0; text-wrap: pretty; }

.earnings-caveat {
    margin-block: 2rem 0;
    padding: .85rem 1rem;
    border-inline-start: 4px solid var(--warning);
    background: var(--warning-soft);
    color: var(--warning);
    border-radius: var(--radius-sm);
    font-size: .9rem;
}
.earnings-caveat a { color: inherit; }

/* ---------- assurances ---------- */

.assurances {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1.5rem;
}
.assurance h3 { font-size: .98rem; margin-block-end: .3rem; }
.assurance p { color: var(--text-muted); font-size: .9rem; margin: 0; text-wrap: pretty; }

/* ---------- closing CTA ---------- */

.home-cta {
    margin-block-start: 2.5rem;
    padding: 2.5rem 2rem;
    border-radius: var(--radius);
    text-align: center;
    background:
        repeating-linear-gradient(45deg, var(--accent-soft) 0 8px, transparent 8px 16px),
        var(--surface);
    border: 1px solid var(--border);
}
.home-cta h2 { margin-block-end: .5rem; }
.home-cta p { color: var(--text-muted); max-width: 52ch; margin-inline: auto; margin-block-end: 1.5rem; }
.home-cta .hero-actions { justify-content: center; }

/* ==========================================================================
   Catalogue filters, toolbar and list view
   --------------------------------------------------------------------------
   The rail is a single <details> so the same markup is an always-open sidebar
   on desktop and a collapsible drawer on a phone, with no duplicated markup
   and no second breakpoint-only component to keep in sync.

   Every control here lives in one GET form, including the sort <select> that
   sits in the header and is bound back with form="shop-filters". That is why
   filters compose instead of overwriting each other, and why the whole thing
   still works with JavaScript disabled.
   ========================================================================== */

.filter-panel { margin: 0; }

.filter-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    cursor: pointer;
    list-style: none;
    font-size: .8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
    padding-block-end: .9rem;
    margin-block-end: .9rem;
    border-block-end: 1px solid var(--border);
}

/* The default triangle cannot be styled, so it is replaced by a chevron drawn
   from borders. Physical border sides on purpose: a chevron pointing down is
   the same shape in both directions, and the logical properties flipped it to
   point sideways under dir="rtl". Only its position is direction-aware. */
.filter-summary::-webkit-details-marker { display: none; }
.filter-summary::after {
    content: "";
    width: .45rem;
    height: .45rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform .15s ease;
    margin-inline-start: auto;
    margin-block-end: .15rem;
}
.filter-panel[open] > .filter-summary::after { transform: rotate(-135deg); margin-block-end: -.15rem; }

.filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35rem;
    height: 1.35rem;
    padding-inline: .35rem;
    border-radius: 999px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: .72rem;
    letter-spacing: 0;
}

.filter-form { display: flex; flex-direction: column; gap: 1.15rem; }

.filter-group {
    border: 0;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: .55rem;
}

.filter-group + .filter-group {
    border-block-start: 1px solid var(--border);
    padding-block-start: 1.15rem;
}

.filter-label {
    padding: 0;
    font-size: .78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-muted);
}

.filter-options { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .1rem; }

.filter-option {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .42rem .55rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: .93rem;
    color: var(--text);
    transition: background .15s ease;
}

.filter-option:hover { background: var(--surface-alt); }

.filter-option input[type="checkbox"] {
    width: 1rem;
    height: 1rem;
    margin: 0;
    flex: none;
    accent-color: var(--accent);
    cursor: pointer;
}

.filter-option-name { flex: 1 1 auto; min-width: 0; }

.filter-option-count {
    flex: none;
    font-size: .78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* Highlights a selected craft so the rail and the chips agree at a glance. */
.filter-option:has(input:checked) { background: var(--accent-soft); }
.filter-option:has(input:checked) .filter-option-name { color: var(--accent-strong); font-weight: 600; }

.filter-range { display: flex; align-items: center; gap: .5rem; }
.filter-range input { min-width: 0; flex: 1 1 0; text-align: center; }
.filter-range-sep { color: var(--text-muted); flex: none; }

/* Two number inputs side by side leave no room for the browser spinners. */
.filter-range input::-webkit-outer-spin-button,
.filter-range input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.filter-range input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

.filter-actions { display: flex; flex-direction: column; gap: .5rem; }
.filter-actions .btn { width: 100%; text-align: center; }

/* ---------- toolbar: sort + view ---------- */

.shop-toolbar { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }

.sort-field { display: flex; align-items: center; gap: .5rem; }
.sort-field label { font-size: .85rem; color: var(--text-muted); white-space: nowrap; }
.sort-field select { min-width: 190px; }

.view-toggle {
    display: flex;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface);
}

.view-toggle-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    color: var(--text-muted);
    text-decoration: none;
    transition: background .15s ease, color .15s ease;
}

.view-toggle-btn + .view-toggle-btn { border-inline-start: 1px solid var(--border); }
.view-toggle-btn:hover { background: var(--surface-alt); color: var(--text); }
.view-toggle-btn[aria-current="true"] { background: var(--accent-soft); color: var(--accent-strong); }

@media (max-width: 520px) {
    .shop-toolbar { width: 100%; }
    .sort-field { flex: 1 1 auto; }
    .sort-field select { min-width: 0; width: 100%; }
}

/* ---------- active filter chips ---------- */

.filter-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .45rem;
    margin-block-end: 1.5rem;
}

.filter-chips-label {
    font-size: .8rem;
    color: var(--text-muted);
    margin-inline-end: .15rem;
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .65rem;
    border-radius: 999px;
    background: var(--accent-soft);
    border: 1px solid transparent;
    color: var(--accent-strong);
    font-size: .85rem;
    text-decoration: none;
    transition: border-color .15s ease, background .15s ease;
}

.filter-chip span { font-size: 1rem; line-height: 1; opacity: .65; }
.filter-chip:hover { border-color: var(--accent); }
.filter-chip:hover span { opacity: 1; }

.filter-chip-clear {
    background: transparent;
    border-color: var(--border);
    color: var(--text-muted);
}
.filter-chip-clear:hover { color: var(--text); border-color: var(--text-muted); }

/* ---------- list view ---------- */

.product-list { display: flex; flex-direction: column; gap: 1rem; }

.product-card-list { flex-direction: row; align-items: stretch; }

/* The grid card reserves its square with padding-block-end; in a row that
   would collapse the media to nothing, so the width is fixed instead. */
.product-card-list .product-media {
    flex: none;
    width: 168px;
    padding-block-end: 0;
    height: auto;
    min-height: 148px;
}

.product-card-list .product-media img,
.product-card-list .product-media-fallback { position: absolute; inset: 0; }

.product-card-list .product-body { padding: 1.1rem 1.25rem; gap: .3rem; }

.product-summary {
    color: var(--text-muted);
    font-size: .9rem;
    line-height: 1.5;
    margin: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

@media (max-width: 520px) {
    .product-card-list { flex-direction: column; }
    .product-card-list .product-media { width: 100%; padding-block-end: 100%; min-height: 0; }
}

/* ---------- price reductions ---------- */

.product-compare-price {
    color: var(--text-muted);
    font-weight: 400;
    font-size: .85em;
    margin-inline-start: .4rem;
}

.product-flag-sale { background: var(--success-soft); color: var(--success); }

/* A discount rule's badge, next to the price it reduced. */
.discount-badge {
    display: inline-block;
    margin-inline-start: .4rem;
    padding: .05rem .45rem;
    border-radius: 999px;
    background: var(--success-soft);
    color: var(--success);
    font-size: .72rem;
    font-weight: 600;
    vertical-align: middle;
    white-space: nowrap;
}

.discount-steps {
    list-style: none;
    margin: -.25rem 0 .75rem;
    padding: 0;
    font-size: .85rem;
    color: var(--text-muted);
}

.related-products { border-block-start: 1px solid var(--border); }

/* ==========================================================================
   Header: mega menu, dropdowns, cart badge, toasts
   --------------------------------------------------------------------------
   Overrides the flat-link header rules further up the file. Kept here rather
   than edited in place so the earlier block still documents what the old
   layout was and why it was replaced.

   Every popover is a <details class="dropdown">, which gives click-to-open
   and keyboard operation from the browser. The panel is absolutely positioned
   with logical offsets, so it aligns to the correct edge under dir="rtl"
   without a mirrored rule.
   ========================================================================== */

.header-inner { flex-wrap: nowrap; gap: 1rem; }

.brand { flex: none; }

.site-nav {
    margin-inline-start: 1.25rem;
    margin-inline-end: auto;
    gap: .35rem;
    flex-wrap: nowrap;
}

.nav-link {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .45rem .7rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    text-decoration: none;
    font-size: .95rem;
    white-space: nowrap;
    transition: background .15s ease, color .15s ease;
}
.nav-link:hover { background: var(--surface-alt); color: var(--text); }

.header-actions {
    display: flex;
    align-items: center;
    gap: .4rem;
    flex: none;
}

.icon { flex: none; }

.icon-button {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 2.3rem;
    height: 2.3rem;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}
.icon-button:hover { background: var(--surface-alt); color: var(--text); }

/* ---------- dropdowns ---------- */

.dropdown { position: relative; }

.dropdown-trigger {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .4rem .6rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: .9rem;
    cursor: pointer;
    list-style: none;
    white-space: nowrap;
    transition: background .15s ease, color .15s ease;
}
.dropdown-trigger::-webkit-details-marker { display: none; }
.dropdown-trigger:hover { background: var(--surface-alt); color: var(--text); }
.dropdown[open] > .dropdown-trigger { background: var(--surface-alt); color: var(--text); }

.dropdown-caret { transition: transform .15s ease; }
.dropdown[open] > .dropdown-trigger .dropdown-caret { transform: rotate(180deg); }

.dropdown-menu {
    position: absolute;
    inset-block-start: calc(100% + .45rem);
    inset-inline-start: 0;
    z-index: 60;
    min-width: 200px;
    padding: .35rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    animation: dropdown-in .13s ease-out;
}

/* Menus hanging off a control at the end of the row would otherwise run off
   the edge of the viewport. */
.dropdown-menu-end { inset-inline-start: auto; inset-inline-end: 0; }

@keyframes dropdown-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .dropdown-menu { animation: none; }
}

.dropdown-header {
    padding: .5rem .65rem .35rem;
    margin: 0;
    font-size: .78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    border-block-end: 1px solid var(--border);
    margin-block-end: .3rem;
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: .6rem;
    width: 100%;
    padding: .5rem .65rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: .92rem;
    text-align: start;
    text-decoration: none;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}
.dropdown-item:hover { background: var(--surface-alt); }
.dropdown-item span { flex: 1 1 auto; }
.dropdown-item[aria-current="true"] { color: var(--accent-strong); font-weight: 600; }
.dropdown-item .icon { color: var(--text-muted); }
.dropdown-item:hover .icon { color: var(--accent); }

.dropdown-form { margin: 0; border-block-start: 1px solid var(--border); padding-block-start: .3rem; margin-block-start: .3rem; }

.lang-switcher .dropdown-trigger-label { font-size: .88rem; }

.avatar {
    display: grid;
    place-items: center;
    width: 1.85rem;
    height: 1.85rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: .82rem;
    font-weight: 700;
}

/* ---------- mega menu ---------- */

.mega { position: static; }

.mega-menu {
    inset-inline: 0;
    inset-block-start: calc(100% + .1rem);
    display: grid;
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 1.75rem;
    padding: 1.5rem;
    min-width: 0;
}

/* The panel spans the header, so it is positioned against .header-inner
   rather than the trigger - hence position: static on .mega above. */
.header-inner { position: relative; }

.mega-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1.25rem 1.5rem;
}

.mega-heading {
    display: flex;
    align-items: center;
    gap: .4rem;
    color: var(--text);
    font-weight: 600;
    font-size: .95rem;
    text-decoration: none;
    padding-block-end: .5rem;
    margin-block-end: .5rem;
    border-block-end: 1px solid var(--border);
}
.mega-heading:hover { color: var(--accent-strong); }

.mega-count {
    margin-inline-start: auto;
    font-size: .75rem;
    font-weight: 400;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.mega-sublist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .1rem; }
.mega-sublist a {
    display: block;
    padding: .3rem 0;
    color: var(--text-muted);
    text-decoration: none;
    font-size: .89rem;
}
.mega-sublist a:hover { color: var(--accent-strong); }

.mega-aside {
    background: var(--surface-alt);
    border-radius: var(--radius);
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    align-items: flex-start;
}
.mega-aside-title { margin: 0; font-weight: 600; font-size: .95rem; }
.mega-aside-body { margin: 0; color: var(--text-muted); font-size: .88rem; line-height: 1.55; }

/* ---------- cart badge ---------- */

.cart-count {
    position: absolute;
    inset-block-start: .15rem;
    inset-inline-end: .05rem;
    min-width: 1.05rem;
    height: 1.05rem;
    padding-inline: .25rem;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: .68rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.cart-count.is-empty { display: none; }

/* Fires on every add, including a second add of the same product, because
   the script removes and re-adds the class. */
.cart-count.is-bumped { animation: badge-bump .32s ease-out; }

@keyframes badge-bump {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.35); }
    100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .cart-count.is-bumped { animation: none; }
}

/* ---------- toasts ---------- */

.toast-region {
    position: fixed;
    inset-block-end: 1.25rem;
    inset-inline-end: 1.25rem;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    pointer-events: none;
}

.toast {
    padding: .7rem 1rem;
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--shadow);
    font-size: .9rem;
    color: var(--text);
    max-width: 22rem;
    animation: toast-in .18s ease-out;
}

.toast-success { border-inline-start: 3px solid var(--success); }
.toast-error { border-inline-start: 3px solid var(--danger); }
.toast.is-leaving { opacity: 0; transition: opacity .35s ease; }

@keyframes toast-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .toast { animation: none; }
}

.buy-box.is-busy { opacity: .6; }

/* ---------- mobile header ----------

   On a phone the header is one row - menu, brand, cart, bell, account - and
   stays at the top of the screen. Everything else lives in a drawer that
   slides in from the reading side: the catalogue, the content pages, the
   member's shortcuts and the display settings (dark mode, language,
   currency), which used to be squeezed into a second wrapped header row or,
   in the case of dark mode, hidden on phones altogether.

   The drawer-only pieces are hidden on a wide screen, where the header row
   has room for the real controls. */

.nav-toggle { display: none; }

.drawer-head,
.drawer-account,
.drawer-settings,
.drawer-auth,
.nav-backdrop { display: none; }

/* 1180px, not 900: the full header row (menu, currency, language, theme,
   sign-in, register) needs about 1100px, so between 900 and 1100 - every
   tablet held sideways - it ran off the screen and pushed "register" past the
   edge. Below 1180 the menu is the drawer, which leaves room for longer
   labels. (app.js listens for 1181px.) */
@media (max-width: 1180px) {
    /* Sticky so the menu and cart are always one tap away on a long page. */
    .site-header {
        position: sticky;
        inset-block-start: 0;
        z-index: 60;
    }

    /* backdrop-filter makes an element the containing block of its fixed
       descendants - with it, the "fixed" drawer would be pinned inside the
       64px header instead of the screen. A solid header loses nothing here. */
    .site-header.is-scrolled { backdrop-filter: none; }

    .header-inner { gap: .5rem; padding-block: .55rem; }

    .nav-toggle { display: inline-grid; order: -1; margin-inline-end: -.25rem; }
    .brand { margin-inline-end: auto; }

    /* These three move into the drawer. */
    .header-actions > .theme-toggle,
    .header-actions > .currency-switcher,
    .header-actions > .lang-switcher { display: none; }

    .header-actions { gap: .15rem; }

    /* ---- the drawer ---- */

    .site-nav {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        z-index: 80;
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: .1rem;
        inline-size: min(22rem, 86vw);
        margin: 0;
        padding: 0 1rem calc(1.25rem + env(safe-area-inset-bottom, 0px));
        overflow-y: auto;
        overscroll-behavior: contain;
        background: var(--surface);
        border-inline-end: 1px solid var(--border);
        box-shadow: 0 0 40px rgba(0, 20, 15, .25);
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform .28s cubic-bezier(.2, .8, .2, 1), visibility 0s linear .28s;
    }
    [dir="rtl"] .site-nav { transform: translateX(100%); }

    .site-nav.is-open {
        transform: none;
        visibility: visible;
        transition: transform .28s cubic-bezier(.2, .8, .2, 1);
    }

    .nav-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 79;
        background: rgba(5, 15, 12, .5);
        opacity: 0;
        transition: opacity .25s ease;
    }
    /* Only an open backdrop takes taps - one fading out must not swallow
       the tap that reopens the menu. */
    .nav-backdrop { pointer-events: none; }
    .nav-backdrop.is-open { opacity: 1; pointer-events: auto; }
    .nav-backdrop[hidden] { display: none; }

    /* The root only: overflow on <body> would make it a scroll container
       and unstick the sticky header. */
    html.nav-locked { overflow: hidden; }

    .drawer-head {
        position: sticky;
        inset-block-start: 0;
        z-index: 1;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: .5rem;
        margin-inline: -1rem;
        padding: .7rem 1rem;
        margin-block-end: .35rem;
        background: var(--surface);
        border-block-end: 1px solid var(--border);
    }

    /* Rows a thumb can hit: 44px and full width. */
    .site-nav > .nav-link,
    .site-nav > .dropdown > .dropdown-trigger {
        display: flex;
        justify-content: space-between;
        min-block-size: 2.75rem;
        padding: .6rem .7rem;
        color: var(--text);
        font-size: 1rem;
        border-radius: var(--radius-sm);
    }

    /* Menus inside the drawer open in place, as an indented list. */
    .site-nav .dropdown-menu {
        position: static;
        min-width: 0;
        padding: .15rem 0 .35rem;
        padding-inline-start: .6rem;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        background: none;
        animation: none;
    }

    .mega-menu {
        display: block;
        padding: .25rem 0 .5rem;
    }
    .mega-aside { display: none; }
    .mega-columns { grid-template-columns: minmax(0, 1fr); gap: .25rem; }
    .mega-heading { margin-block-end: .25rem; padding-block: .5rem; border-block-end: 0; }
    .mega-sublist { margin-block-end: .35rem; }

    /* ---- the member's corner ---- */

    .drawer-account {
        display: block;
        margin-block-end: .5rem;
        padding-block-end: .75rem;
        border-block-end: 1px solid var(--border);
    }

    .drawer-me {
        display: flex;
        align-items: center;
        gap: .7rem;
        padding: .55rem .6rem;
        border-radius: var(--radius);
        background: var(--accent-soft);
        color: var(--text);
        text-decoration: none;
    }
    .drawer-me .avatar { inline-size: 2.4rem; block-size: 2.4rem; font-size: 1rem; background: var(--surface); }
    .drawer-me-text { display: flex; flex-direction: column; min-inline-size: 0; line-height: 1.45; }
    .drawer-me-text strong { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .drawer-me-text span { font-size: .8rem; color: var(--accent-strong); }

    .drawer-shortcuts {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: .35rem;
        margin-block-start: .6rem;
    }
    .drawer-shortcuts a {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: .3rem;
        padding: .55rem .2rem;
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        color: var(--text);
        font-size: .72rem;
        text-align: center;
        text-decoration: none;
        line-height: 1.3;
    }
    .drawer-shortcuts a .icon { color: var(--accent); }

    /* ---- display settings ---- */

    /* margin-block-start: auto pushes it to the foot of the drawer when the
       links are short; the padding keeps it off the last link when not. */
    .drawer-settings {
        display: block;
        margin-block-start: auto;
        padding-block-start: 1rem;
        border-block-start: 1px solid var(--border);
    }

    /* The install offer: a card rather than a plain row, because it is the
       one thing in the drawer that is an invitation rather than a setting. */
    .drawer-install {
        display: flex;
        align-items: center;
        gap: .7rem;
        inline-size: 100%;
        margin-block-end: .75rem;
        padding: .6rem .7rem;
        border: 1px solid var(--accent);
        border-radius: var(--radius);
        background: var(--accent-soft);
        color: var(--text);
        font: inherit;
        text-align: start;
        cursor: pointer;
    }
    /* `display: flex` outranks the browser's rule for [hidden]; pwa.js hides
       this until the app can actually be installed. */
    .drawer-install[hidden] { display: none; }

    .drawer-install img {
        flex: none;
        inline-size: 2.5rem;
        block-size: 2.5rem;
        border-radius: .6rem;
    }

    .drawer-install-text {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-inline-size: 0;
        line-height: 1.45;
    }
    .drawer-install-text strong { font-size: .92rem; color: var(--accent-strong); }
    .drawer-install-text span { font-size: .76rem; color: var(--text-muted); }

    .drawer-install-go { flex: none; color: var(--accent); }
    [dir="ltr"] .drawer-install-go { transform: scaleX(-1); }

    .drawer-title {
        margin: .5rem 0 .4rem;
        font-size: .74rem;
        font-weight: 700;
        letter-spacing: .04em;
        color: var(--text-muted);
    }

    .drawer-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: .75rem;
        inline-size: 100%;
        min-block-size: 2.9rem;
        padding: .5rem .6rem;
        border: 0;
        border-radius: var(--radius-sm);
        background: none;
        color: var(--text);
        font: inherit;
        font-size: .95rem;
        text-align: start;
    }
    button.drawer-row { cursor: pointer; }
    button.drawer-row:hover { background: var(--surface-alt); }

    .drawer-row-stack { flex-direction: column; align-items: stretch; gap: .5rem; }

    .drawer-row-label { display: inline-flex; align-items: center; gap: .55rem; }
    .drawer-row-label .icon,
    .drawer-row-label .theme-icon { color: var(--text-muted); }
    .drawer-theme .theme-icon { display: inline; }

    .chip-group { display: flex; flex-wrap: wrap; gap: .4rem; }

    .chip {
        display: inline-flex;
        align-items: center;
        gap: .3rem;
        min-block-size: 2.25rem;
        padding: .3rem .85rem;
        border: 1px solid var(--border);
        border-radius: 999px;
        color: var(--text);
        font-size: .88rem;
        text-decoration: none;
    }
    .chip.is-active {
        border-color: var(--accent);
        background: var(--accent-soft);
        color: var(--accent-strong);
        font-weight: 600;
    }

    .drawer-auth {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: .5rem;
        margin-block-start: 1rem;
    }
}

/* The on/off switch. Outside the query so any [role="switch"] row can use it. */
.switch {
    position: relative;
    flex: none;
    inline-size: 2.6rem;
    block-size: 1.5rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--text-muted) 45%, transparent);
    transition: background .2s ease;
}
.switch-knob {
    position: absolute;
    inset-block-start: .2rem;
    inset-inline-start: .2rem;
    inline-size: 1.1rem;
    block-size: 1.1rem;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
    transition: inset-inline-start .2s ease;
}
[aria-checked="true"] > .switch { background: var(--accent); }
[aria-checked="true"] > .switch .switch-knob { inset-inline-start: 1.3rem; }

@media (max-width: 560px) {
    /* Registration is in the drawer; the header keeps the sign-in link,
       which is what a returning member is looking for. */
    .header-register { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .site-nav,
    .site-nav.is-open,
    .nav-backdrop { transition: none; }
}

/* ==========================================================================
   Page heads, journal and articles
   ========================================================================== */

.page-head {
    max-width: var(--measure);
    margin-block-end: 2.5rem;
}
.page-head h1 { margin-block: .25rem .5rem; }

.page-lede {
    color: var(--text-muted);
    font-size: 1.08rem;
    line-height: 1.65;
    margin: 0;
}

/* ---------- post grid ---------- */

.post-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1.5rem;
}

.post-card {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    box-shadow: var(--shadow);
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}

.post-card:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
    box-shadow: 0 2px 4px rgba(0, 0, 0, .06), 0 14px 32px rgba(0, 0, 0, .1);
}

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

.post-body {
    padding: 1.15rem 1.25rem 1.35rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.post-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: 0;
    font-size: .8rem;
    color: var(--text-muted);
}

.post-tag {
    padding: .15rem .5rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: 600;
}

.post-title { margin: 0; font-size: 1.08rem; line-height: 1.35; }
.post-card:hover .post-title { color: var(--accent-strong); }

.post-excerpt {
    margin: 0;
    color: var(--text-muted);
    font-size: .92rem;
    line-height: 1.55;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ---------- article ---------- */

.post-article { max-width: var(--measure); margin-inline: auto; }
.post-header { margin-block-end: 1.75rem; }
.post-header h1 { margin-block: .6rem .5rem; }
.post-byline { color: var(--text-muted); font-size: .9rem; margin-block: .75rem 0; }

.post-cover {
    width: 100%;
    height: auto;
    border-radius: var(--radius);
    margin-block-end: 2rem;
}

/* Article text is set slightly larger and looser than interface copy; it is
   read continuously rather than scanned. */
.post-body-copy {
    font-size: 1.05rem;
    line-height: 1.75;
}
.post-body-copy br + br { content: ""; display: block; margin-block-start: .9em; }

/* The journal filter chips mark the active section with aria-current, which
   the shop's chips do not use - there, a chip means "click to remove". */
.filter-chip[aria-current="true"] {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent-strong);
    font-weight: 600;
}
.filter-chip .mega-count { margin-inline-start: .1rem; }

/* Journal covers are 16:9, not the catalogue square. Overriding the ratio here
   rather than giving the card its own media block keeps one set of media
   rules - the fallback, the hover zoom and the object-fit all still apply. */
.post-card .product-media { padding-block-end: 56.25%; }

/* ==========================================================================
   Network: the position diagram and the organisation chart
   --------------------------------------------------------------------------
   Both diagrams are ordinary flow layout with CSS-drawn connectors, not SVG
   and not absolute positioning. That means they reflow at any width, print,
   survive a longer name in Persian or Arabic, and mirror under dir="rtl"
   without a single direction-specific rule - every connector here is either
   vertical or symmetric about the centre.
   ========================================================================== */

/* ---------- headline strip ---------- */

.stat-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1rem;
    margin-block-end: 1.5rem;
}

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem 1.15rem;
    display: flex;
    flex-direction: column;
    gap: .15rem;
    box-shadow: var(--shadow);
}

.stat-label {
    font-size: .78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-muted);
}
.stat-value { font-size: 1.7rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat-note { font-size: .82rem; color: var(--text-muted); }

/* ---------- position diagram ---------- */

.slot-map { margin-block-end: 1.5rem; }

/*
 * The five-position diagram.
 *
 * The skeleton - where the boxes sit and every connector between them - comes
 * from the organisation explorer's `gx-tree` / `gx-item` / `gx-children`
 * classes in public/css/explorer.css, which this page loads. The two charts
 * are then literally the same shape, and a connector fixed in one is fixed in
 * both.
 *
 * It used to lay the legs out with its own grid and draw the bar between them
 * with `inset-inline: 25%`, which is only correct when the columns have no
 * gap. With one, the bar stopped ten pixels short of each leg's centre and the
 * stubs hung off nothing. The legs also had no box at all, so 002 and 003 read
 * as loose headings rather than as positions.
 *
 * What is left below is only what the cards look like. Where they sit is the
 * explorer's business.
 */

.slot-scroll { margin-block: 1.5rem 2rem; }

/* Every card in the diagram, whatever it represents. */
.gx-slot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    inline-size: 190px;
    padding: .85rem 1rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface);
    color: inherit;
    text-align: center;
    box-shadow: var(--shadow);
}

a.gx-slot { text-decoration: none; transition: border-color .15s ease, transform .15s ease; }
a.gx-slot:hover { border-color: var(--accent); transform: translateY(-2px); }

@media (prefers-reduced-motion: reduce) {
    a.gx-slot { transition: none; }
    a.gx-slot:hover { transform: none; }
}

.slot-main { inline-size: 240px; }

/* A leg is a grouping, not a slot anyone sits in - dashed until its own
   threshold is met, solid once it is active. */
.slot-leg-node { border-style: dashed; background: var(--surface-alt); }
.slot-leg-node.is-active { border-style: solid; border-color: var(--success); }
.slot-leg-node.is-active .slot-uv { color: var(--success); }

.slot-arm { inline-size: 170px; }

.slot-code {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.slot-name { font-weight: 650; font-size: .98rem; }
.slot-note { font-size: .8rem; color: var(--text-muted); line-height: 1.45; }

.slot-state {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    font-size: .82rem;
    font-weight: 600;
}

.state-dot {
    inline-size: .5rem;
    block-size: .5rem;
    border-radius: 999px;
    background: currentColor;
    flex: none;
}

/* The three arm states, in colour AND in words - never colour alone, because
   the whole point of the diagram is that it is readable at a glance and a
   colour-blind member gets the same glance. */
.slot-arm.is-filled { border-color: var(--success); background: var(--success-soft); }
.slot-arm.is-filled .slot-state { color: var(--success); }

.slot-arm.is-open { border-color: var(--accent); border-style: dashed; }
.slot-arm.is-open .slot-state { color: var(--accent-strong); }

.slot-arm.is-locked { border-style: dashed; opacity: .75; }
.slot-arm.is-locked .slot-state { color: var(--text-muted); }
.slot-arm.is-locked:hover { opacity: 1; }

/* ---------- unlock rules ---------- */

.unlock-rules {
    border-block-start: 1px solid var(--border);
    padding-block-start: 1.5rem;
}
.unlock-rules h3 { font-size: 1rem; margin-block-end: 1rem; }

.unlock-rule {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem 1.1rem;
    background: var(--surface-alt);
}
.unlock-rule + .unlock-rule { margin-block-start: .75rem; }
.unlock-rule.is-met { border-color: var(--success); background: var(--success-soft); }

.unlock-rule-head { display: flex; align-items: flex-start; gap: .75rem; }
.unlock-rule-head > div { flex: 1 1 auto; min-inline-size: 0; }
.unlock-rule-title { margin: 0; font-weight: 650; }
.unlock-rule-caption { margin: .15rem 0 0; font-size: .86rem; color: var(--text-muted); line-height: 1.5; }

.unlock-mark {
    display: grid;
    place-items: center;
    inline-size: 1.6rem;
    block-size: 1.6rem;
    border-radius: 999px;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text-muted);
    flex: none;
}
.unlock-rule.is-met .unlock-mark { background: var(--success); border-color: var(--success); color: var(--on-success); }

.rule-legs { display: grid; gap: .6rem; margin-block-start: .9rem; }

.rule-leg {
    display: grid;
    grid-template-columns: minmax(0, 7rem) minmax(0, 1fr) auto;
    align-items: center;
    gap: .75rem;
    font-size: .86rem;
}
.rule-figure { color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

@media (max-width: 560px) {
    .rule-leg { grid-template-columns: 1fr; gap: .3rem; }
}

.meter {
    block-size: .5rem;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
}
.meter-fill { display: block; block-size: 100%; background: var(--accent); border-radius: 999px; }
.meter.is-met .meter-fill { background: var(--success); }

/* ---------- organisation chart ---------- */

.tree-card .section-head { align-items: center; }

.tree-legend {
    display: flex;
    flex-wrap: wrap;
    gap: .9rem;
    margin-block: 1rem;
    font-size: .82rem;
    color: var(--text-muted);
}
.legend-item { display: inline-flex; align-items: center; gap: .4rem; }

.legend-swatch {
    inline-size: .8rem;
    block-size: .8rem;
    border-radius: 3px;
    border: 1px solid var(--border);
}
.legend-swatch.qual-qualified { background: var(--success-soft); border-color: var(--success); }
.legend-swatch.qual-half_qualified { background: var(--warning-soft); border-color: var(--warning); }
.legend-swatch.qual-unqualified { background: var(--surface-alt); }
.legend-swatch-open { border-style: dashed; border-color: var(--accent); }
.legend-swatch-locked { border-style: dashed; }

.tree-scroll {
    overflow-x: auto;
    padding-block: 1rem 1.5rem;
    /* Scroll snapping would fight a pan gesture, so it is left off; the chart
       is dragged, not paged. */
}

/*
 * The classic nested-list tree.
 *
 * Each level is a flex row of items. An item draws a stub up to its parent,
 * and the pair of pseudo-elements on the children draws the horizontal
 * connector - trimmed at the first and last child so the bar stops at the
 * outermost siblings instead of running off the ends.
 */
.tree, .tree-children {
    display: flex;
    justify-content: center;
    list-style: none;
    margin: 0;
    padding: 0;
}

.tree-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    padding-block-start: 1.75rem;
    padding-inline: .4rem;
}

/* Stub from this node up towards its parent. */
.tree-item::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    inline-size: 2px;
    block-size: 1.75rem;
    background: var(--border);
}

/* Horizontal bar across the siblings, drawn in two halves so the ends can be
   trimmed independently. */
.tree-item::after {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 100%;
    block-size: 2px;
    background: var(--border);
}

.tree-item:first-child::after { inset-inline-start: 50%; inline-size: 50%; }
.tree-item:last-child::after { inline-size: 50%; }
.tree-item:only-child::after { display: none; }

/* The root has nothing above it. */
.tree-root > .tree-item { padding-block-start: 0; }
.tree-root > .tree-item::before,
.tree-root > .tree-item::after { display: none; }

.tree-children { padding-block-start: 1.75rem; position: relative; }

/* Drop from a node into its own row of children. */
.tree-children::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    inline-size: 2px;
    block-size: 1.75rem;
    background: var(--border);
}

.tree-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .3rem;
    inline-size: 148px;
    padding: .75rem .6rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface);
    text-align: center;
    box-shadow: var(--shadow);
}

.tree-node.qual-qualified { border-color: var(--success); background: var(--success-soft); }
.tree-node.qual-half_qualified { border-color: var(--warning); background: var(--warning-soft); }

.tree-node.is-viewer {
    border-color: var(--accent);
    border-width: 2px;
    background: var(--accent-soft);
}

.tree-avatar {
    display: grid;
    place-items: center;
    inline-size: 2rem;
    block-size: 2rem;
    border-radius: 999px;
    background: var(--surface);
    border: 1px solid var(--border);
    font-weight: 700;
    font-size: .9rem;
}

.tree-name {
    font-weight: 650;
    font-size: .88rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.tree-date { font-size: .72rem; color: var(--text-muted); }

.tree-node-empty {
    border-style: dashed;
    background: transparent;
    box-shadow: none;
    gap: .15rem;
    justify-content: center;
    min-block-size: 88px;
}
.tree-node-empty.is-open { border-color: var(--accent); }
.tree-node-empty.is-locked { opacity: .6; }

.tree-slot-code {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.tree-slot-state { font-size: .8rem; color: var(--text-muted); }

/* ---------- chart controls: breadcrumb, search, drill-down ---------- */

.tree-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-block: 1rem;
    padding-block-end: 1rem;
    border-block-end: 1px solid var(--border);
}

.tree-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem;
    font-size: .88rem;
    min-inline-size: 0;
}

.crumb {
    padding: .2rem .5rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    text-decoration: none;
}
.crumb:hover { background: var(--surface-alt); color: var(--text); }
.crumb.is-current { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }

/* A "/" between crumbs, not a chevron: a chevron would have to be mirrored
   under dir="rtl" and a slash reads the same either way. */
.crumb-sep { color: var(--text-muted); opacity: .5; }

.tree-search { flex: 0 1 260px; }

.tree-matches {
    display: flex;
    flex-wrap: wrap;
    gap: .45rem;
    margin-block-end: 1rem;
}

/* Nodes are links now; they must not pick up the global anchor colour. */
a.tree-node { color: inherit; text-decoration: none; }
a.tree-node:hover { border-color: var(--accent); }

button.tree-node {
    font: inherit;
    color: inherit;
    cursor: pointer;
}
button.tree-node:hover { border-color: var(--accent); }

a.slot-arm { color: inherit; text-decoration: none; }
a.slot-arm:hover { border-color: var(--accent); }
a.slot-arm.is-locked:hover { opacity: 1; }

/* ---------- slot dialog ---------- */

.slot-dialog {
    inline-size: min(30rem, calc(100vw - 2rem));
    padding: 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 8px 40px rgba(0, 0, 0, .3);
}

.slot-dialog::backdrop { background: rgba(0, 0, 0, .5); }

.slot-dialog-close-form {
    display: flex;
    justify-content: flex-end;
    margin: -.5rem -.5rem 0;
}

.slot-dialog-code {
    margin: 0;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.slot-dialog-title { margin: .2rem 0 .5rem; font-size: 1.2rem; }
.slot-dialog-body { color: var(--text-muted); line-height: 1.6; margin-block-end: 1.25rem; }

.slot-dialog .btn { inline-size: 100%; }

.slot-link {
    display: flex;
    gap: .5rem;
    margin-block: .9rem .6rem;
}
.slot-link input {
    flex: 1 1 auto;
    min-inline-size: 0;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: .82rem;
}
.slot-link .btn { inline-size: auto; flex: none; }

.slot-dialog .unlock-rule { margin-block-end: 1rem; }

/* The UV a position is carrying, shown inside its box on the diagram. */
.slot-uv {
    font-size: .82rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--accent-strong);
}

/*
 * Main position states. Four, not two: the plan has an activation threshold
 * (placement) and a higher earning threshold, and collapsing them into
 * "active/inactive" is what made the diagram claim a 0 UV position was active.
 */
.slot-main.is-inactive { border-color: var(--border); background: var(--surface-alt); }
.slot-main.is-inactive .slot-state { color: var(--text-muted); }
.slot-main.is-inactive .slot-uv { color: var(--text-muted); }

.slot-main.is-active { border-color: var(--accent); background: var(--accent-soft); }
.slot-main.is-active .slot-state { color: var(--accent-strong); }

.slot-main.is-earning { border-color: var(--success); background: var(--success-soft); }
.slot-main.is-earning .slot-state { color: var(--success); }
.slot-main.is-earning .slot-uv { color: var(--success); }

.slot-main.is-suspended { border-color: var(--danger); background: var(--danger-soft); }
.slot-main.is-suspended .slot-state { color: var(--danger); }

/* ==========================================================================
   Landing page and footer, second pass
   --------------------------------------------------------------------------
   Appended after the first-pass rules so it overrides them; the earlier block
   is left in place because it still carries the base layout the additions sit
   on. Icons are the inline SVG component, so they inherit currentColor and
   follow both themes without a second set of assets.
   ========================================================================== */

/* ---------- hero ---------- */

.hero-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: .78rem;
    font-weight: 650;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.hero-facts {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1.5rem;
    list-style: none;
    margin: 1.75rem 0 0;
    padding: 1.25rem 0 0;
    border-block-start: 1px solid var(--border);
}

.hero-facts li {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    font-size: .88rem;
    color: var(--text-muted);
}
.hero-facts .icon { color: var(--accent); }

/* The arrow points "onward", which is leftward under RTL. */
.btn-arrow { transition: transform .15s ease; }
[dir="rtl"] .btn-arrow { transform: scaleX(-1); }

.btn:hover .btn-arrow { transform: translateX(2px); }
[dir="rtl"] .btn:hover .btn-arrow { transform: scaleX(-1) translateX(2px); }

@media (prefers-reduced-motion: reduce) {
    .btn-arrow { transition: none; }
}

.ha-ring-inner { opacity: .5; }
.ha-petal-inner { opacity: .55; }

/* ---------- section heads ---------- */

.section-link {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .9rem;
    text-decoration: none;
    white-space: nowrap;
}
.section-link:hover .btn-arrow { transform: translateX(2px); }
[dir="rtl"] .section-link:hover .btn-arrow { transform: scaleX(-1) translateX(2px); }


/* ---------- steps ---------- */

.step { position: relative; }

.step-icon {
    display: grid;
    place-items: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-strong);
    margin-block-end: .85rem;
}

/* The number becomes a quiet marker beside the icon rather than the whole
   visual - four big numerals competed with the headings they were labelling. */
.step-number {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 2.1rem;
    inline-size: 1.35rem;
    block-size: 1.35rem;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--accent-strong);
    color: var(--on-accent);
    font-size: .7rem;
    font-weight: 700;
    border: 2px solid var(--bg);
}

.earnings-caveat {
    display: flex;
    align-items: flex-start;
    gap: .6rem;
}
.earnings-caveat .icon { flex: none; margin-block-start: .15rem; color: var(--warning); }

/* ---------- assurances ---------- */

.assurance {
    padding: 1.35rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface);
}

.assurance-icon {
    display: grid;
    place-items: center;
    inline-size: 2.6rem;
    block-size: 2.6rem;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-strong);
    margin-block-end: .85rem;
}

.assurance h3 { font-size: 1rem; margin-block-end: .35rem; }
.assurance p { margin: 0; color: var(--text-muted); font-size: .9rem; line-height: 1.6; }

/* ---------- closing call to action ---------- */

.home-cta-art {
    display: grid;
    place-items: center;
    inline-size: 3.25rem;
    block-size: 3.25rem;
    margin-inline: auto;
    margin-block-end: 1rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
    background: var(--surface);
    border-block-start: 1px solid var(--border);
    padding-block: 3rem 0;
    font-size: .9rem;
    margin-block-start: 3rem;
}

.footer-inner {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) repeat(auto-fit, minmax(140px, 1fr));
    gap: 2.5rem;
    align-items: start;
    padding-block-end: 2.5rem;
}

.footer-brand .brand { font-size: 1.1rem; margin-block-end: .6rem; }

.footer-tagline {
    color: var(--text-muted);
    line-height: 1.6;
    margin: 0 0 1.1rem;
    max-inline-size: 34ch;
}

.footer-assurances {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: .5rem;
}

.footer-assurances li {
    display: flex;
    align-items: center;
    gap: .5rem;
    color: var(--text-muted);
    font-size: .85rem;
}
.footer-assurances .icon { color: var(--accent); flex: none; }

.footer-col h2 {
    font-size: .78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
    margin: 0 0 .85rem;
}

.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.footer-col a {
    color: var(--text);
    text-decoration: none;
    font-size: .89rem;
}
.footer-col a:hover { color: var(--accent-strong); }

.footer-base {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem 1.5rem;
    /* Room below the last line: it sat 17px off the bottom of the screen,
       under the home bar on an iPad. The back-to-top button keeps its own
       corner (padding at the end side). */
    padding-block: 1.25rem calc(2rem + env(safe-area-inset-bottom, 0px));
    padding-inline-end: 3.75rem;
    border-block-start: 1px solid var(--border);
}

.footer-copy { margin: 0; color: var(--text-muted); font-size: .85rem; }

/* The income notice keeps the last word on every page. It is the one line
   here that is not decoration. */
.footer-notice {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0;
    color: var(--text-muted);
    font-size: .82rem;
}
.footer-notice .icon { flex: none; color: var(--warning); }

@media (max-width: 720px) {
    .footer-inner { grid-template-columns: 1fr; gap: 1.75rem; }
    .site-footer { padding-block-start: 2rem; }
}

/* ==========================================================================
   Product card: add to cart
   --------------------------------------------------------------------------
   The card stopped being one big <a> so it could hold a button - a form inside
   an anchor is invalid HTML. The product name is the real link now, and its
   ::after covers the card so everywhere is still clickable. The action row
   sits above that overlay.
   ========================================================================== */

.product-card { position: relative; }

.product-card-link {
    color: inherit;
    text-decoration: none;
}

/* The overlay that makes the whole card clickable. Below the action row in
   the stacking order, so the button gets the click it is directly under. */
.product-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

.product-card:hover .product-card-link { color: var(--accent-strong); }

.product-actions {
    position: relative;
    z-index: 2;
    margin-block-start: .75rem;
}

.product-actions form { margin: 0; }

.btn-block {
    inline-size: 100%;
    justify-content: center;
}

.product-unavailable {
    display: block;
    padding: .35rem 0;
    text-align: center;
    font-size: .85rem;
    font-weight: 600;
    color: var(--text-muted);
}

/* The price is pushed to the bottom of the body so cards with different name
   lengths line up. With an action row below it, the row is what should be
   pinned instead. */
.product-body .product-price { margin-block-start: auto; padding-block-start: .35rem; }
.product-body .product-uv + .product-actions { margin-block-start: .75rem; }

/* In list layout the action row sits inline with the copy rather than filling
   the column, which would stretch a button across the whole card. */
.product-card-list .product-actions { max-inline-size: 14rem; }

.product-card.is-busy { opacity: .6; }

/* ==========================================================================
   Product page: the buy box
   --------------------------------------------------------------------------
   Was a bare number input beside a default-sized button, indistinguishable
   from the rest of the page. It is now a bordered panel: the one thing on the
   page a visitor is meant to act on should look like it.
   ========================================================================== */

.buy-box {
    margin-block: 1.75rem;
    padding: 1.25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow);
}

.buy-box-unavailable { background: var(--surface-alt); }

.buy-row {
    display: flex;
    align-items: stretch;
    gap: .75rem;
}

.buy-submit { flex: 1 1 auto; justify-content: center; font-size: 1rem; }

/* ---------- quantity stepper ---------- */

.qty {
    display: flex;
    align-items: stretch;
    flex: none;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface);
}

.qty-step {
    inline-size: 2.4rem;
    border: 0;
    background: var(--surface-alt);
    color: var(--text);
    font: inherit;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}
.qty-step:hover:not(:disabled) { background: var(--accent-soft); color: var(--accent-strong); }
.qty-step:disabled { opacity: .4; cursor: not-allowed; }

.qty input {
    inline-size: 3.25rem;
    border: 0;
    border-radius: 0;
    text-align: center;
    background: var(--surface);
    font-variant-numeric: tabular-nums;
    /* Digits read left-to-right in every locale. */
    direction: ltr;
}
.qty input:focus { outline-offset: -2px; }

/* The browser spinners duplicate the buttons either side of them. */
.qty input::-webkit-outer-spin-button,
.qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

/* ---------- running total ---------- */

.buy-summary {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .35rem 1rem;
    margin: 1rem 0 0;
    padding-block-start: .9rem;
    border-block-start: 1px solid var(--border);
    font-size: .92rem;
}

.buy-summary dt { color: var(--text-muted); }

.buy-summary dd {
    margin: 0;
    text-align: end;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.buy-summary dd[data-buy-uv] { color: var(--accent-strong); }

.buy-note {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin: .75rem 0 0;
    font-size: .8rem;
    color: var(--text-muted);
    line-height: 1.5;
}
.buy-note .icon { flex: none; }

.buy-stock {
    margin: .5rem 0 0;
    font-size: .82rem;
    font-weight: 600;
    color: var(--warning);
}

.buy-unavailable {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .45rem;
    margin: 0 0 .9rem;
    font-weight: 650;
    color: var(--text-muted);
}

@media (max-width: 480px) {
    .buy-row { flex-direction: column; }
    .qty { align-self: stretch; }
    .qty input { flex: 1 1 auto; inline-size: auto; }
}

/* ==========================================================================
   Cart and checkout
   --------------------------------------------------------------------------
   Both pages were a bare table beside an inline-styled <dl>, with no product
   imagery, a "type a number then press Save" quantity control, and a checkout
   button disabled with `pointer-events:none` while still sitting in the tab
   order. They now share one layout and one summary panel, so the figures a
   member checks before paying look identical on the page where they change
   them and on the page where they commit to them.
   ========================================================================== */

.cart-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 21rem;
    gap: 1.75rem;
    align-items: start;
}

/* ---------- lines ---------- */

.cart-lines {
    display: flex;
    flex-direction: column;
    gap: .85rem;
}

.cart-line {
    display: grid;
    grid-template-columns: 5.5rem minmax(0, 1fr) auto;
    gap: 1.1rem;
    align-items: start;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow);
}

.cart-line.is-unavailable { background: var(--surface-alt); }
.cart-line.is-unavailable .cart-thumb { opacity: .5; }

/* While a debounced quantity submit is in flight, so the figures that are
   about to be replaced do not look like the current answer. */
.cart-line.is-busy { opacity: .55; pointer-events: none; }

.cart-thumb {
    position: relative;
    display: block;
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}

.cart-thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.cart-thumb .product-media-fallback span { font-size: 1.5rem; width: 2.2rem; height: 2.2rem; }

.cart-line-body { min-inline-size: 0; }

.cart-line-name {
    margin: 0 0 .2rem;
    font-size: 1rem;
    font-weight: 650;
    line-height: 1.4;
}
.cart-line-name a { color: var(--text); text-decoration: none; }
.cart-line-name a:hover { color: var(--accent-strong); text-decoration: underline; }

.cart-line-unit {
    margin: 0;
    font-size: .87rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.cart-line-uv {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    margin: .45rem 0 0;
    padding: .15rem .5rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: .78rem;
    font-weight: 650;
}

.cart-line-warning {
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    margin: .55rem 0 0;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--warning);
}
.cart-line-warning .icon { flex: none; margin-block-start: .15rem; }
.cart-line-warning.is-danger { color: var(--danger); }

/* ---------- per-line controls ---------- */

.cart-line-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: .55rem;
}

.cart-line-actions form { margin: 0; display: flex; gap: .4rem; align-items: center; }

.cart-line-total {
    margin: 0;
    font-weight: 700;
    font-size: 1.05rem;
    font-variant-numeric: tabular-nums;
}

.cart-remove {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-muted);
    font: inherit;
    font-size: .82rem;
    cursor: pointer;
}
.cart-remove:hover { color: var(--danger); }

.cart-continue {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    align-self: flex-start;
    margin-block-start: .35rem;
    font-size: .9rem;
    color: var(--text-muted);
    text-decoration: none;
}
.cart-continue:hover { color: var(--accent-strong); }

/* Arrows point along the reading direction, so the "go back" one is mirrored
   in both scripts rather than being a second icon. */
.icon-flip { transform: scaleX(-1); }
[dir="rtl"] .icon-flip { transform: none; }

/* ---------- summary panel ---------- */

.cart-summary { position: sticky; inset-block-start: 1.5rem; }

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

.summary-title {
    margin: 0 0 1rem;
    font-size: 1.05rem;
    font-weight: 700;
}

.summary-country { margin-block-end: 1rem; }
.summary-country label {
    display: block;
    margin-block-end: .3rem;
    font-size: .82rem;
    font-weight: 600;
    color: var(--text-muted);
}
.summary-country select { inline-size: 100%; }

.summary-items {
    list-style: none;
    margin: 0 0 1rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .7rem;
}

.summary-items li {
    display: grid;
    grid-template-columns: 2.8rem minmax(0, 1fr) auto;
    gap: .65rem;
    align-items: center;
}

.summary-thumb {
    position: relative;
    display: block;
    aspect-ratio: 1;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
}
.summary-thumb img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    border-radius: var(--radius-sm);
    display: block;
}
.summary-thumb .product-media-fallback { border-radius: var(--radius-sm); }
.summary-thumb .product-media-fallback span { font-size: .9rem; width: 1.4rem; height: 1.4rem; }

/* The quantity as a badge on the thumbnail, the way an order review usually
   shows it - it costs no row height and never wraps. */
.summary-qty {
    position: absolute;
    inset-block-start: -.35rem;
    inset-inline-end: -.35rem;
    min-inline-size: 1.25rem;
    block-size: 1.25rem;
    padding-inline: .25rem;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--accent-strong);
    color: var(--on-accent);
    font-size: .7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.summary-item-body { min-inline-size: 0; display: block; }
.summary-item-name {
    display: block;
    font-size: .87rem;
    font-weight: 600;
    line-height: 1.35;
}
.summary-item-uv { display: block; font-size: .75rem; color: var(--accent-strong); font-weight: 600; }
.summary-item-total { font-size: .87rem; font-weight: 650; font-variant-numeric: tabular-nums; }

.summary-figures {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .45rem 1rem;
    margin: 0;
    padding-block-start: .9rem;
    border-block-start: 1px solid var(--border);
    font-size: .92rem;
}
.summary-figures dt { color: var(--text-muted); }
.summary-figures dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; }
.summary-figures .is-total {
    padding-block-start: .5rem;
    border-block-start: 1px solid var(--border);
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
}

.summary-uv {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-block-start: .9rem;
    padding: .6rem .75rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: .85rem;
    font-weight: 600;
}
.summary-uv span { display: inline-flex; align-items: center; gap: .4rem; }
.summary-uv strong { font-variant-numeric: tabular-nums; }

.summary-blocker {
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    margin: .9rem 0 0;
    padding: .6rem .75rem;
    border-radius: var(--radius-sm);
    background: var(--danger-soft);
    color: var(--danger);
    font-size: .83rem;
    line-height: 1.5;
}
.summary-blocker .icon { flex: none; margin-block-start: .1rem; }

.summary-cta { margin-block-start: 1rem; font-size: 1rem; padding-block: .7rem; }
.summary-cta:disabled { opacity: .45; cursor: not-allowed; }
.summary-cta:disabled:hover { background: var(--accent-strong); border-color: var(--accent-strong); }

.summary-note {
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    margin: .7rem 0 0;
    font-size: .78rem;
    line-height: 1.5;
    color: var(--text-muted);
}
.summary-note .icon { flex: none; margin-block-start: .15rem; }

/* ---------- empty cart ---------- */

.empty-state { text-align: center; padding-block: 3rem; }
.empty-state h2 { margin: 0 0 .5rem; font-size: 1.3rem; }
.empty-state p { max-inline-size: 34rem; margin-inline: auto; margin-block-end: 1.5rem; }

.empty-state-mark {
    display: inline-grid;
    place-items: center;
    inline-size: 4rem;
    block-size: 4rem;
    margin-block-end: 1.1rem;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent-strong);
}

/* ---------- checkout ---------- */

.checkout-steps {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    list-style: none;
    margin: .75rem 0 0;
    padding: 0;
    font-size: .87rem;
    color: var(--text-muted);
}

.checkout-steps li { display: inline-flex; align-items: center; gap: .45rem; }
.checkout-steps .is-current { color: var(--text); font-weight: 650; }
.checkout-steps .is-done { color: var(--success); }

.step-mark {
    display: inline-grid;
    place-items: center;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    border-radius: 50%;
    border: 1px solid currentColor;
    font-size: .75rem;
    font-weight: 700;
}
.checkout-steps .is-done .step-mark { background: var(--success); border-color: var(--success); color: var(--on-success); }
.checkout-steps .is-current .step-mark { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }

.checkout-form { display: flex; flex-direction: column; gap: 1.25rem; }

.section-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0 0 1.1rem;
    font-size: 1.05rem;
    font-weight: 700;
}
.section-title .icon { color: var(--accent-strong); flex: none; }

/* Two columns for the short fields, full width for the street lines, which is
   the shape every address form settles on. */
.field-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 1rem;
}
.field-wide { grid-column: 1 / -1; }

.voucher-chips { display: flex; flex-wrap: wrap; gap: .45rem; margin-block-start: .6rem; }

.voucher-chip {
    padding: .3rem .7rem;
    border: 1px dashed var(--accent);
    border-radius: 999px;
    background: transparent;
    color: var(--accent-strong);
    font: inherit;
    font-size: .8rem;
    font-weight: 650;
    letter-spacing: .02em;
    cursor: pointer;
}
.voucher-chip:hover { background: var(--accent-soft); }
.voucher-chip.is-chosen { border-style: solid; background: var(--accent-strong); color: var(--on-accent); }

.checkout-submit { align-self: flex-start; }
.btn-lg { padding: .75rem 1.6rem; font-size: 1rem; }

/* ---------- narrow screens ---------- */

@media (max-width: 900px) {
    .cart-layout { grid-template-columns: minmax(0, 1fr); }
    /* Sticky is worse than useless once the panel is a full-width block below
       the lines - it would pin the summary over the thing being summarised. */
    .cart-summary { position: static; }
}

@media (max-width: 620px) {
    .cart-line {
        grid-template-columns: 4.5rem minmax(0, 1fr);
        gap: .85rem;
    }
    /* The controls drop under the name rather than squeezing a third column
       into a phone width. */
    .cart-line-actions {
        grid-column: 2;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        inline-size: 100%;
    }
    .field-grid { grid-template-columns: minmax(0, 1fr); }
    .checkout-submit { align-self: stretch; justify-content: center; }
}

/* ==========================================================================
   Auth pages
   --------------------------------------------------------------------------
   Login, register, forgot, reset and verify were each a bare .card in a 480px
   column with a default-width button and a row of tiny underlined links. They
   are the first thing a new member sees and the last thing a returning one
   passes through, so they now carry the same weight as the rest of the site.
   All five share x-auth-card, so they cannot drift apart again.
   ========================================================================== */

.auth-shell {
    max-inline-size: 30rem;
    margin-inline: auto;
    margin-block: 1rem 3rem;
}

/* Register is the only form long enough to earn a second column, and only
   when there is room for one that does not squeeze the form itself. */
.auth-shell-wide {
    max-inline-size: 56rem;
}

@media (min-width: 900px) {
    .auth-shell-wide {
        display: grid;
        grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
        gap: 2.5rem;
        align-items: start;
    }
}

.auth-panel {
    padding: 2rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow);
}

/* ---------- panel head ---------- */

.auth-brand {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    margin-block-end: 1.5rem;
    color: var(--text);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -.01em;
    text-decoration: none;
}
.auth-brand:hover { color: var(--accent-strong); }

.auth-title {
    margin: 0 0 .35rem;
    font-size: 1.6rem;
    line-height: 1.2;
}

.auth-subtitle {
    margin: 0 0 1.75rem;
    color: var(--text-muted);
    line-height: 1.55;
}

.auth-title + form,
.auth-title + .banner,
.auth-title + .auth-note { margin-block-start: 1.75rem; }

/* ---------- inside the form ---------- */

/* "Keep me signed in" and "Forgot your password?" belong on one line: they are
   the two things a returning member decides between the password field and the
   button, and stacking them pushed the button below the fold on a phone. */
.auth-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-block: .25rem 1.25rem;
}

.auth-minor {
    font-size: .87rem;
    color: var(--text-muted);
    text-decoration: none;
}
.auth-minor:hover { color: var(--accent-strong); text-decoration: underline; }

.auth-note {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    margin: 0 0 1.5rem;
    padding: .7rem .85rem;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text-muted);
    font-size: .87rem;
    line-height: 1.55;
}
.auth-note .icon { flex: none; margin-block-start: .15rem; color: var(--accent); }

.auth-secondary-form { margin-block-start: .75rem; }

/* ---------- footer link row ---------- */

.auth-footer {
    margin: 1.5rem 0 0;
    padding-block-start: 1.25rem;
    border-block-start: 1px solid var(--border);
    text-align: center;
    font-size: .9rem;
    color: var(--text-muted);
}
.auth-footer a {
    color: var(--accent-strong);
    font-weight: 650;
    text-decoration: none;
}
.auth-footer a:hover { text-decoration: underline; }

/* ---------- the aside on register ---------- */

.auth-aside { display: none; }

@media (min-width: 900px) {
    .auth-aside {
        display: block;
        position: sticky;
        inset-block-start: 2rem;
        padding: 1.75rem;
        border-radius: var(--radius);
        background: var(--accent-soft);
        color: var(--text);
    }
}

.auth-aside h2 {
    margin: 0 0 1.1rem;
    font-size: 1.05rem;
    color: var(--accent-strong);
}

.auth-points {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.auth-points li {
    display: flex;
    align-items: flex-start;
    gap: .7rem;
    font-size: .89rem;
    line-height: 1.6;
    color: var(--text-muted);
}
.auth-points .icon { flex: none; margin-block-start: .1rem; color: var(--accent-strong); }

@media (max-width: 560px) {
    .auth-panel { padding: 1.5rem 1.25rem; }
    .auth-title { font-size: 1.4rem; }
}

/* Help text under an input. Was an inline style on x-field, which meant every
   help line on the site carried its own copy of the same two rules. */
.field-help {
    margin: .3rem 0 0;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--text-muted);
}

/* ---------- closing call to action, on a photograph ---------- */

/*
 * The CTA keeps its own border and radius; the photograph sits behind the copy
 * rather than replacing the panel, so the section still reads as a panel in
 * both themes.
 */
.home-cta { position: relative; isolation: isolate; overflow: hidden; }

.home-cta-photo {
    position: absolute;
    inset: 0;
    z-index: -1;
}

.home-cta-photo picture,
.home-cta-photo img {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
}

/*
 * The scrim is not decoration - the copy is body text over a photograph, and
 * without it the contrast ratio depends on whatever happens to be behind each
 * line. A flat, heavy tint in the brand colour keeps it a single known value.
 */
.home-cta-photo::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        to bottom,
        rgba(0, 56, 43, .90),
        rgba(0, 56, 43, .82)
    );
}

/*
   Fixed light values rather than the theme tokens, AND ONLY WHERE THERE IS A
   PHOTOGRAPH BEHIND THEM.

   These were written for the landing page, whose CTA holds a scrimmed photo -
   the ground is the same dark image in light mode and dark mode alike, so
   fixed light text is right there. They applied to every `.home-cta` though,
   and the about page reuses that class WITHOUT a photo: its background is
   `--surface`, which is white in light mode. Near-white text on white, at a
   contrast ratio of 1.08 against a minimum of 4.5 - the section was simply
   unreadable for anyone not in dark mode.

   Scoped with `:has()` so the class stays safe to reuse: no photo, no override,
   and the theme tokens are left to do their job.
*/
.home-cta:has(.home-cta-photo) h2,
.home-cta:has(.home-cta-photo) p { color: #f2f7f5; }
.home-cta:has(.home-cta-photo) p { opacity: .92; }

.home-cta:has(.home-cta-photo) .home-cta-art { color: #a7d8c9; }

/* The secondary button's outline is defined against the page surface, which is
   not what is behind it here. */
.home-cta:has(.home-cta-photo) .btn-secondary {
    color: #f2f7f5;
    border-color: rgba(242, 247, 245, .45);
    background: transparent;
}

.home-cta:has(.home-cta-photo) .btn-secondary:hover {
    background: rgba(242, 247, 245, .12);
    border-color: rgba(242, 247, 245, .7);
}

/* ==========================================================================
   SCROLL REVEAL AND MICRO-INTERACTIONS
   ==========================================================================

   The hidden state is `.is-pending`, applied by public/js/reveal.js and ONLY
   to elements that were below the fold when it ran. Nothing already on screen
   is ever given this class, so no visible content can be hidden and brought
   back however late the script arrives.

   That is the third design. The first put the hidden state on a root
   attribute the deferred script switched on, and the page flashed: every
   section painted at 553ms and was blanked at 865ms. The second armed the
   same attribute inline in the head, which replaced the flash with 310ms of
   blank sections - worse. Both hid content and then relied on timing to
   restore it; this one never hides anything the reader can see.

   Because nothing is hidden without the script, there is no blank-page
   failure mode: if reveal.js never loads, the page is simply static.
   ========================================================================== */

.reveal.is-pending,
.reveal-group.is-pending > * {
    opacity: 0;
    /*
     * Rise and settle from slightly small. Vertical motion reads as the page
     * assembling, while horizontal motion on an RTL page has to be mirrored
     * to avoid entering from the wrong side - this needs no direction logic.
     *
     * 18px and no scale was the first attempt and it was invisible: at that
     * distance the movement finishes before the eye reaches it. 34px plus a
     * 2% scale is the smallest pairing that actually registers without
     * tipping into the bouncy-template look.
     */
    transform: translateY(34px) scale(.985);
}

/*
 * The transition lives on the elements themselves, not only on the pending
 * state, so removing `.is-pending` animates rather than snapping.
 */
.reveal,
.reveal-group > * {
    transition:
        opacity .55s ease-out,
        transform .75s cubic-bezier(.16, .84, .44, 1);
}

/* The stagger. `--reveal-index` is set per child by the script and capped
   there, so a long grid does not end with a child that waits a second. */
.reveal-group > * {
    transition-delay: calc(var(--reveal-index, 0) * 90ms);
}
/* ---------- hero entrance ---------- */

/*
 * There isn't one, on purpose.
 *
 * The hero is above the fold by definition, and the reveal now never touches
 * anything already on screen - so an entrance animation for it would have to
 * hide it first and depend on script timing to bring it back, which is the
 * exact defect this whole section was rewritten to remove. The hero's
 * interaction is the pointer tilt below, which needs nothing hidden.
 */
/* ---------- micro-interactions ---------- */

/*
 * A lift plus a shadow on hover, on the things that are actually clickable.
 * The easing is the same curve as the reveal, so the page has one motion
 * character rather than several.
 */
.showcase-tile {
    transition:
        transform .35s cubic-bezier(.22, .61, .36, 1),
        box-shadow .35s cubic-bezier(.22, .61, .36, 1),
        border-color .2s ease;
}

.showcase-tile:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 28px rgba(0, 30, 22, .13);
    border-color: var(--accent);
}

/* The caption lifts with the tile rather than staying put, so the overlay
   reads as part of the card and not as a label stuck on it. */
.showcase-tile figcaption {
    transition: padding-block-end .35s cubic-bezier(.22, .61, .36, 1);
}

.showcase-tile:hover figcaption { padding-block-end: 1.05rem; }

/* The arrow on a section link and a category tile slides toward the edge it
   points at. `margin-inline-start` rather than translateX, so it follows the
   writing direction under dir="rtl" with no second rule. */
.section-link .btn-arrow,
.category-tile-go .btn-arrow {
    transition: margin-inline-start .25s cubic-bezier(.22, .61, .36, 1);
}

.section-link:hover .btn-arrow,
.category-tile:hover .category-tile-go .btn-arrow { margin-inline-start: .3rem; }

/* The craft mark nudges up as its tile lifts - a small thing, but it is what
   stops the icon reading as a sticker on a moving card. */
.category-tile-icon { transition: color .15s ease, transform .3s cubic-bezier(.22, .61, .36, 1); }
.category-tile:hover .category-tile-icon { transform: translateY(-2px); }

/*
 * Everything above is decoration, so all of it is switched off together
 * rather than relying on each rule's own guard. The reveal rules need no
 * entry here: their script never runs under this setting.
 *
 * NOT `transform: none` ON THE ARROWS. This block used to include
 * `.section-link .btn-arrow` and `.category-tile-go .btn-arrow` in a blanket
 * transform reset, which looked harmless and was not: those arrows carry
 * their RTL mirror in `transform: scaleX(-1)`, so resetting it left them
 * pointing rightwards on a Persian or Arabic page while the hero's arrow -
 * not in this list - still mirrored correctly. Two arrows one way, one the
 * other.
 *
 * The distinction to keep: a transform that expresses DIRECTION is layout and
 * must survive this setting; only a transform that expresses MOTION may be
 * reset. Killing the transition is enough for the latter - the hover offset
 * then applies instantly instead of sliding.
 */
@media (prefers-reduced-motion: reduce) {
    .showcase-tile,
    .showcase-tile:hover,
    .showcase-tile figcaption,
    .showcase-tile:hover figcaption,
    .category-tile-icon,
    .category-tile:hover .category-tile-icon {
        transition: none;
        transform: none;
    }

    /* Transition only - the mirror in `transform` is direction, not motion. */
    .section-link .btn-arrow,
    .category-tile-go .btn-arrow {
        transition: none;
    }
}

/* ==========================================================================
   POINTER RESPONSE
   ==========================================================================

   Driven by public/js/interactions.js, which refuses to run under
   `prefers-reduced-motion` - so these rules only ever have values to work
   with when motion is welcome. Each one degrades to its resting state if the
   script never sets the property, because every custom property below has a
   fallback in its own var().
   ========================================================================== */

/* ---------- hero tilt ---------- */

/*
 * The perspective lives on the hero, not on the photograph, so the tilt is
 * read as the image leaning inside the page rather than the whole block
 * skewing.
 */
.hero { perspective: 1200px; }

.hero-art {
    transform:
        rotateX(var(--tilt-x, 0deg))
        rotateY(var(--tilt-y, 0deg));
    transform-style: preserve-3d;
    /* Slow on the way back, quick to follow: returning to rest should feel
       like settling, not like a second animation. */
    transition: transform .5s cubic-bezier(.22, .61, .36, 1), box-shadow .5s ease;
}

/* While the pointer is over it, the transform must not lag behind the mouse,
   so the transition is dropped and the rAF loop drives it directly. */
.hero-art.is-tilting {
    transition: box-shadow .3s ease;
    box-shadow: 0 22px 50px rgba(0, 30, 22, .22);
}

/*
 * A soft gloss that follows the pointer across the photograph. A pseudo
 * element rather than a background on the box, so it sits above the image
 * without needing a wrapper.
 */
.hero-art::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity .3s ease;
    background: radial-gradient(
        circle at var(--tilt-gloss-x, 50%) var(--tilt-gloss-y, 50%),
        rgba(255, 255, 255, .18),
        transparent 55%
    );
}

.hero-art.is-tilting::after { opacity: 1; }

/* ---------- card spotlight ---------- */

/*
 * A highlight that follows the pointer inside whichever card it is over.
 * `--pointer-x/y` are written by the delegated listener; the fallback centres
 * it, so a card the pointer has never entered looks correct.
 */
.showcase-tile,
.category-tile,
.product-card { position: relative; }

.showcase-tile::before,
.category-tile::before,
.product-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    transition: opacity .35s ease;
    background: radial-gradient(
        220px circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
        rgba(255, 255, 255, .14),
        transparent 70%
    );
}

.category-tile::before,
.product-card::before {
    /* On the two light-surfaced cards a white wash is invisible; the brand
       green reads on both themes. */
    background: radial-gradient(
        220px circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
        var(--accent-soft),
        transparent 70%
    );
}

.showcase-tile:hover::before,
.category-tile:hover::before,
.product-card:hover::before { opacity: 1; }

/* The caption has to stay above the wash. */
.showcase-tile figcaption { z-index: 2; }

/* ---------- header on scroll ---------- */

.site-header {
    transition:
        padding-block .25s ease,
        box-shadow .25s ease,
        backdrop-filter .25s ease;
}

/*
 * Condenses once the reader has moved, which is the cheapest possible signal
 * that the page scrolled - useful on a long landing page where the sections
 * look alike.
 */
.site-header.is-scrolled {
    box-shadow: 0 6px 24px rgba(0, 30, 22, .10);
}

/*
 * The frosted glass only where the menu is not a drawer. backdrop-filter makes
 * the header the containing block of its fixed descendants, and the drawer is
 * one of them: on a phone, once the page had scrolled, the "full-screen" menu
 * was squeezed into the 60px header and the products showed where it should
 * have been. The earlier `backdrop-filter: none` for small screens lost to
 * this rule, which comes later in the file.
 */
@media (min-width: 1181px) {
    .site-header.is-scrolled { backdrop-filter: saturate(1.4) blur(8px); }
}

@media (prefers-reduced-motion: reduce) {
    .hero-art,
    .site-header {
        transition: none;
        transform: none;
    }

    .hero-art::after,
    .showcase-tile::before,
    .category-tile::before,
    .product-card::before { display: none; }
}

/* ==========================================================================
   PAGINATION
   ==========================================================================

   Replaces Laravel's Tailwind pagination view, whose utility classes were
   inert here (no build step) - including the `w-5 h-5` that sized its
   chevrons, which is why the default nav rendered 848x1802px with two
   enormous arrows. See resources/views/vendor/pagination/mirath.blade.php.

   Everything uses logical properties, so the same rules lay out correctly in
   both directions; the arrows mirror themselves through the shared
   `.btn-arrow` rule.
   ========================================================================== */

.pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    margin-block-start: 2rem;
}

.pager-step,
.pager-page {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    min-inline-size: 2.4rem;
    justify-content: center;
    padding: .5rem .85rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    color: var(--text);
    font-size: .875rem;
    font-weight: 500;
    text-decoration: none;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.pager-step:hover,
.pager-page:hover {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-strong);
}

/* The page you are on is not a link and must not look like one. */
.pager-page.is-current {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
    color: var(--on-accent);
    font-weight: 650;
    cursor: default;
}

.pager-step.is-disabled {
    opacity: .45;
    cursor: not-allowed;
    background: var(--surface-alt);
    color: var(--text-muted);
}

.pager-step.is-disabled:hover {
    border-color: var(--border);
    background: var(--surface-alt);
    color: var(--text-muted);
}

/*
 * "Previous" points backwards, so its arrow is the forward one turned round.
 * A second flip on top of the RTL mirror, which is why it is expressed as a
 * variable the shared `.btn-arrow` rules already compose with rather than a
 * hard `transform` that would overwrite them.
 */
.pager-prev-icon { transform: scaleX(-1); }
[dir="rtl"] .pager-prev-icon { transform: none; }

.pager-numbers {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem;
}

.pager-gap {
    padding-inline: .2rem;
    color: var(--text-muted);
}

.pager-count {
    margin-block-start: .75rem;
    text-align: center;
    font-size: .8125rem;
    color: var(--text-muted);
}

/* On a phone the two step buttons and the count carry the whole job - a row
   of page numbers is the first thing to overflow a narrow screen. */
@media (max-width: 560px) {
    .pager-numbers { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .pager-step,
    .pager-page { transition: none; }
}

/* ---------------------------------------------------------------------------
   Article comments.

   A reply is indented from the INLINE START so the nesting reads correctly in
   both directions: this site serves Persian, Arabic and English from the same
   stylesheet, and a `margin-left` here would indent replies off the wrong edge
   in two of the three.
--------------------------------------------------------------------------- */

.post-comment-count { font-size: .9rem; color: var(--text-muted); }

.comment {
    padding-block: 1rem;
    border-block-start: 1px solid var(--border);
}

.comment:first-of-type { border-block-start: 0; }

.comment-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .6rem;
    margin-block-end: .4rem;
}

.comment-author { font-size: .95rem; }
.comment-head time { font-size: .82rem; color: var(--text-muted); }

.comment-body { line-height: 1.8; }

.comment-reply-btn {
    margin-block-start: .5rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}

.comment-reply-btn:hover { text-decoration: underline; }

.comment-reply {
    margin-inline-start: 1.75rem;
    margin-block-start: 1rem;
    padding-inline-start: 1rem;
    border-block-start: 0;
    border-inline-start: 2px solid var(--border);
}

.comment-empty { color: var(--text-muted); }

.comment-form {
    margin-block-start: 2rem;
    padding: 1.25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 10px);
    background: var(--surface-alt);
}

.comment-form h3 { margin-block: 0 1rem; font-size: 1.05rem; }

.comment-replying {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-block-end: .75rem;
}

.comment-replying button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    cursor: pointer;
}

/*
   The honeypot. Taken out of the layout and out of the accessibility tree
   rather than `display: none`: some bots skip hidden fields, and a field that
   is merely off-canvas catches the ones that do not. `aria-hidden` and
   `tabindex="-1"` on the input keep it away from screen readers and the
   keyboard, so no real visitor can reach it by accident.
*/
.comment-hp {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

@media (max-width: 480px) {
    .comment-reply { margin-inline-start: .75rem; padding-inline-start: .75rem; }
}

/* Tag chips: quieter than the section chips, because a tag and a category
   are different things and must not read as the same control. */
.filter-chips-tags { margin-block-start: .5rem; }

.filter-chip-tag {
    font-size: .82rem;
    padding-block: .3rem;
    color: var(--text-muted);
}

.filter-chip-tag[aria-current="true"] { color: var(--on-accent, #fff); }

.post-tags {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-block-start: 1.75rem;
}

/* ---------------------------------------------------------------------------
   FAQ accordion.

   Built on <details>, so it opens with JavaScript off, is announced properly
   by a screen reader, and find-in-page still matches text inside a closed
   answer - which is what somebody hunting one specific answer actually does.
--------------------------------------------------------------------------- */

.faq-section { padding-block: 2.5rem; border-block-start: 1px solid var(--border); }
.faq-section h2 { margin-block: 0 1rem; }

.faq-list { display: grid; gap: .5rem; }

.faq-item {
    border: 1px solid var(--border);
    border-radius: var(--radius, 10px);
    background: var(--surface);
}

.faq-item > summary {
    padding: .85rem 1rem;
    font-weight: 550;
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: .6rem;
}

/* The default triangle is removed above and replaced with a marker that
   follows the writing direction, so it sits at the correct edge in Persian
   and Arabic as well as English. */
.faq-item > summary::-webkit-details-marker { display: none; }

.faq-item > summary::before {
    content: "+";
    inline-size: 1.25rem;
    flex: none;
    font-size: 1.15rem;
    line-height: 1;
    color: var(--accent);
}

.faq-item[open] > summary::before { content: "−"; }

.faq-answer {
    padding: 0 1rem 1rem;
    line-height: 1.8;
    color: var(--text-muted);
}

/*
   Said explicitly rather than left to the browser.

   A closed <details> hides its non-summary children through a user-agent
   rule, and at least one engine this is previewed in does not apply it - a
   bare <details> with no styling at all rendered its body open. Relying on a
   default that some engine may not honour means the whole FAQ prints expanded,
   which is the one thing an accordion must not do.
*/
.faq-item:not([open]) > .faq-answer { display: none; }

/* The approximate converted price, quieter than the real one. */
.price { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .4rem; }
.price-approx { font-size: .8rem; color: var(--text-muted); white-space: nowrap; }

/* ===========================================================================
   PAGE CHROME: loading screen, reading progress, back to top.

   All three are switchable from the admin panel, so each one's markup is
   absent rather than hidden when it is off - there is no cost to a visitor for
   a feature the business turned off.
   =========================================================================== */

/* ---- reading progress ---------------------------------------------------

   scaleX on a full-width bar rather than animating `width`: transform is
   composited, width is not, and this element changes on every scroll frame.
------------------------------------------------------------------------- */

.scroll-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 60;
    block-size: 3px;
    background: transparent;
    pointer-events: none;
}

.scroll-progress-bar {
    display: block;
    block-size: 100%;
    /* Anchored at the inline START, so it grows the way the text is read - in
       Persian and Arabic that is from the right. */
    transform-origin: 0 50%;
    transform: scaleX(0);
    background: linear-gradient(90deg, var(--accent), var(--accent-strong, var(--accent)));
}

html[dir="rtl"] .scroll-progress-bar { transform-origin: 100% 50%; }

/* ---- back to top ------------------------------------------------------- */

.back-to-top {
    position: fixed;
    inset-block-end: 1.25rem;
    inset-inline-end: 1.25rem;
    z-index: 55;
    display: grid;
    place-items: center;
    inline-size: 2.9rem;
    block-size: 2.9rem;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 6px 20px rgba(16, 32, 28, .18);
    cursor: pointer;
    /* Resting state is off-screen and inert, set in CSS alone - so a visitor
       whose JavaScript never arrives is not left with a button that does
       nothing. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(0.75rem);
    transition: opacity .2s ease, transform .2s ease, visibility .2s;
}

.back-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.back-to-top:hover { border-color: var(--accent); color: var(--accent); }
.back-to-top svg { transform: rotate(180deg); }

/* ---- loading screen ---------------------------------------------------- */

/* Only ever set by the script that also creates the overlay, so the page can
   never be left scroll-locked by a loader that failed to appear. */
html.is-loading, html.is-loading body { overflow: hidden; }

.site-loader {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    background: var(--bg);
    transition: opacity .4s ease;
}

.site-loader.is-done { opacity: 0; pointer-events: none; }

.site-loader-inner {
    position: relative;
    display: grid;
    place-items: center;
    inline-size: 7rem;
    block-size: 7rem;
}

.site-loader-mark {
    inline-size: 4.5rem;
    block-size: auto;
    animation: site-loader-pulse 1.8s ease-in-out infinite;
}

/* The ring is a circle with one coloured quarter, spun. One element, no
   images, and it reads as motion at any size. */
.site-loader-ring {
    position: absolute;
    inset: 0;
    border: 2px solid var(--border);
    border-block-start-color: var(--accent);
    border-radius: 50%;
    animation: site-loader-spin 1s linear infinite;
}

@keyframes site-loader-spin { to { transform: rotate(360deg); } }

@keyframes site-loader-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: .75; transform: scale(.94); }
}

/*
   Someone who has asked the system for less movement gets a still logo and a
   still ring - the loader still appears and still leaves, it simply does not
   spin or breathe.
*/
@media (prefers-reduced-motion: reduce) {
    .site-loader-mark,
    .site-loader-ring { animation: none; }
    .back-to-top { transition: none; }
}

/* ---- the story steps on the about page ---------------------------------

   Three numbered beats instead of three paragraphs in a column. The number is
   decorative and sits behind the text, so it reads as a marker rather than
   competing with the heading.
------------------------------------------------------------------------- */

.story-steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 1rem;
}

.story-step {
    position: relative;
    overflow: hidden;
    padding: 1.4rem 1.25rem 1.25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 12px);
    background: var(--surface);
}

.story-step h3 { margin-block: .75rem .4rem; font-size: 1.02rem; }
.story-step p { margin: 0; color: var(--text-muted); line-height: 1.8; }

.story-step-index {
    position: absolute;
    inset-block-start: -.75rem;
    inset-inline-end: .25rem;
    font-size: 4.5rem;
    font-weight: 800;
    line-height: 1;
    /* Barely there: it orders the steps without being read as content. */
    color: var(--accent);
    opacity: .1;
    pointer-events: none;
}

.story-step-icon,
.assurance-icon {
    display: grid;
    place-items: center;
    inline-size: 2.6rem;
    block-size: 2.6rem;
    border-radius: 999px;
    background: var(--accent-soft, var(--surface-alt));
    color: var(--accent);
}

.assurance-icon { margin-block-end: .75rem; }

/* A lede under a section heading, above its grid. */
.section-lede {
    max-inline-size: 60ch;
    margin-block: -.25rem 1.25rem;
    color: var(--text-muted);
    line-height: 1.85;
}

/* ---- the journal's lead article ----------------------------------------

   A publication leads with one piece; an archive shows nine of the same size.
   Two columns on desktop, stacked below - the photo keeps its ratio either way.
------------------------------------------------------------------------- */

.lead-post {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 1.5rem;
    align-items: center;
    margin-block-end: 2rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 14px);
    background: var(--surface);
    color: inherit;
    text-decoration: none;
    transition: border-color .2s ease, transform .2s ease;
}

.lead-post:hover { border-color: var(--accent); transform: translateY(-2px); }

.lead-post-media {
    overflow: hidden;
    border-radius: calc(var(--radius, 14px) - 4px);
    aspect-ratio: 16 / 10;
    background: var(--surface-alt);
}

.lead-post-media img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
}

.lead-post-title {
    margin-block: .5rem .6rem;
    font-size: clamp(1.35rem, 2.6vw, 1.9rem);
    line-height: 1.3;
}

.lead-post-excerpt {
    margin-block: 0 1rem;
    color: var(--text-muted);
    line-height: 1.85;
}

/* The accent chip that marks it as the lead, against the plain category
   chip beside it. */
.post-tag-lead {
    background: var(--accent);
    color: var(--on-accent, #fff);
    border-color: transparent;
}

.post-comment-badge {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
}

.journal-pagination { margin-block-start: 2.5rem; }

@media (max-width: 800px) {
    .lead-post { grid-template-columns: 1fr; }
}

/* ---- empty states ------------------------------------------------------- */

.empty-state {
    display: grid;
    justify-items: center;
    gap: .75rem;
    padding: 3rem 1.5rem;
    border: 1px dashed var(--border);
    border-radius: var(--radius, 12px);
    color: var(--text-muted);
    text-align: center;
}

.empty-state svg { color: var(--accent); opacity: .7; }
.empty-state p { margin: 0; }

/* ---- support page -------------------------------------------------------

   Form and channels side by side. The form is the primary action and takes the
   wider column; the channels beside it answer "is there another way to reach
   you" without pushing the form below the fold.
------------------------------------------------------------------------- */

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

.support-form-card {
    padding: 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 14px);
    background: var(--surface);
}

.support-form-card h2 { margin-block: 0 1.25rem; font-size: 1.15rem; }

.form-row-2 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: 0 1rem;
}

.support-aside { display: grid; gap: 1rem; }

.support-channel {
    padding: 1.25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 14px);
    background: var(--surface);
}

.support-channel h3 { margin-block: .75rem .4rem; font-size: 1rem; }
.support-channel p { margin-block: 0 .75rem; color: var(--text-muted); line-height: 1.75; font-size: .92rem; }

.support-channel-link {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-weight: 600;
    font-size: .92rem;
}

.support-policy-list { margin: 0; padding-inline-start: 1.1rem; }
.support-policy-list li { margin-block-end: .3rem; font-size: .92rem; }

/* ---- legal documents ---------------------------------------------------- */

.legal-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 17rem;
    gap: 2rem;
    align-items: start;
}

.legal-body { max-inline-size: 70ch; }

.legal-meta {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem 1rem;
    margin-block-start: .5rem;
    color: var(--text-muted);
    font-size: .85rem;
}

.legal-meta-item { display: inline-flex; align-items: center; gap: .3rem; }

.legal-aside { display: grid; gap: 1rem; }

.legal-nav,
.legal-help {
    padding: 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 12px);
    background: var(--surface);
}

/* Sticky on tall screens only: pinning it on a short one can leave the panel
   taller than the viewport with no way to reach its end. */
@media (min-height: 40rem) {
    .legal-aside { position: sticky; inset-block-start: 1.5rem; }
}

.legal-nav-title {
    margin-block: 0 .6rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.legal-nav ul { margin: 0; padding: 0; list-style: none; }
.legal-nav li { margin-block-end: .15rem; }

.legal-nav a {
    display: block;
    padding: .4rem .55rem;
    border-radius: 7px;
    font-size: .92rem;
    text-decoration: none;
}

.legal-nav a:hover { background: var(--surface-alt); }

.legal-nav a.is-current {
    background: var(--accent-soft, var(--surface-alt));
    color: var(--accent);
    font-weight: 650;
}

.legal-help p { margin-block: .75rem .85rem; color: var(--text-muted); font-size: .9rem; line-height: 1.7; }

@media (max-width: 900px) {
    .support-layout,
    .legal-layout { grid-template-columns: 1fr; }
    .legal-aside { position: static; }
}

/* ---- the header's control row on a phone ---------------------------------

   Six controls sit here - theme, currency, language, cart, notifications and
   the account menu - and the row is `flex: none` with no wrapping, so on a
   375px screen it simply ran off the edge: the document measured 438px wide
   and the whole page scrolled sideways, which also stretched the fixed
   reading-progress bar to 438px.

   Letting it wrap is the fix rather than hiding a control: a second short row
   on a narrow screen costs a few pixels of height, and dropping the currency
   switcher would leave a phone with no way to change currency at all.
--------------------------------------------------------------------------- */

@media (max-width: 560px) {
    .header-actions {
        flex-wrap: wrap;
        justify-content: flex-end;
        row-gap: .35rem;
        /* `flex: none` above stops it ever shrinking; on a phone it has to be
           allowed to take the width it is given and wrap inside it. */
        flex: 1 1 auto;
        min-inline-size: 0;
    }
}

/* ===========================================================================
   THE INSTALL INVITATION

   A <dialog>, so the browser handles the backdrop, focus trapping and Escape.
   =========================================================================== */

.install-modal {
    inline-size: min(26rem, calc(100vw - 2rem));
    padding: 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius, 14px);
    background: var(--surface);
    color: var(--text);
}

.install-modal::backdrop { background: rgba(0, 0, 0, .45); }

.install-modal-head {
    display: flex;
    align-items: center;
    gap: .9rem;
    margin-block-end: 1rem;
}

.install-modal-icon {
    inline-size: 3.5rem;
    block-size: 3.5rem;
    border-radius: 14px;
    flex: none;
}

.install-modal h2 { margin: 0; font-size: 1.1rem; }
.install-modal-sub { margin: .2rem 0 0; color: var(--text-muted); font-size: .9rem; }

.install-benefits {
    margin: 0 0 1.25rem;
    padding: 0;
    list-style: none;
    display: grid;
    gap: .5rem;
}

.install-benefits li {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    font-size: .92rem;
    line-height: 1.6;
}

.install-benefits svg { color: var(--accent); flex: none; margin-block-start: .15rem; }

.install-actions { display: flex; flex-wrap: wrap; gap: .6rem; }

.install-steps {
    margin: 0 0 1.25rem;
    padding-inline-start: 1.2rem;
    display: grid;
    gap: .5rem;
    font-size: .92rem;
    line-height: 1.6;
}

/*
   The quiet way back in after a dismissal. Above the back-to-top button so the
   two never sit on top of each other, and hidden until the browser has told us
   the app can actually be installed.
*/
.install-fab {
    position: fixed;
    inset-block-end: 4.6rem;
    inset-inline-end: 1.25rem;
    z-index: 55;
    display: grid;
    place-items: center;
    inline-size: 2.9rem;
    block-size: 2.9rem;
    padding: 0;
    border: 1px solid var(--accent);
    border-radius: 999px;
    background: var(--accent);
    color: var(--on-accent, #fff);
    box-shadow: 0 6px 20px rgba(16, 32, 28, .22);
    cursor: pointer;
}

.install-fab:hover { filter: brightness(1.08); }

/* Said explicitly: `display: grid` above outranks the browser's own rule for
   [hidden], so without this the button showed even when pwa.js had hidden it
   because there was nothing to install. */
.install-fab[hidden] { display: none; }

/* On a phone the install offer lives in the menu (.drawer-install), not in
   a button floating over the page. */
@media (max-width: 1180px) {
    .install-fab { display: none; }
}

/* ---- the offline notice ---- */

.offline-state { margin-block: 3rem; }
.offline-state h1 { margin-block: .5rem; font-size: 1.4rem; }

/* The password box on the maintenance page.

   Laid out here rather than on the error shell generally: it is the one error
   page that carries a form, and the buttons beside it are already an
   `.error-actions` flex row, which would otherwise stretch the input across
   the whole card. */
.error-unlock {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    justify-content: center;
    inline-size: min(22rem, 100%);
}

.error-unlock input {
    flex: 1 1 11rem;
    inline-size: auto;
    text-align: center;
}

.error-unlock .btn { flex: none; }

/* ---------------------------------------------------------------------------
   The maintenance page

   Its own screen, not the error shell: this is the first thing anybody sees
   of the business for the weeks before launch. Deep brand green, the real
   lockup, and a network that answers the cursor (public/js/maintenance-mesh.js).

   Always dark, in both themes. The mesh is drawn in light on green and there
   is no long-form text to read, so there is nothing for a light variant to
   improve and two variants to keep in step for no gain.
   --------------------------------------------------------------------------- */
.mnt-body {
    --mnt-ink: #eaf6f2;
    --mnt-muted: #9dbdb3;
    --mnt-node: #7fd4bd;
    --mnt-link: 127, 212, 189;

    position: relative;
    min-block-size: 100svh;
    margin: 0;
    display: grid;
    place-items: center;
    padding: 2rem 1rem;
    color: var(--mnt-ink);
    font-family: var(--font);

    /* Two pools of light over the brand green rather than a flat fill, so the
       card sits in something. No hatching: the diagonal weave the error pages
       carry reads as a placeholder at this size. */
    background:
        radial-gradient(85rem 42rem at 18% -10%, rgba(17, 116, 93, .55), transparent 60%),
        radial-gradient(60rem 38rem at 92% 108%, rgba(127, 212, 189, .18), transparent 62%),
        #00241b;
}

html[dir="rtl"] .mnt-body { font-family: var(--font-rtl); }

.mnt-mesh {
    position: fixed;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    /* Behind everything and untouchable: the card above it must stay
       clickable, and the canvas must never eat a tap on the password box. */
    z-index: 0;
    pointer-events: none;
}

.mnt-shell {
    position: relative;
    z-index: 1;
    inline-size: min(30rem, 100%);
    text-align: center;
}

.mnt-card {
    padding: clamp(1.75rem, 5vw, 2.75rem) clamp(1.25rem, 5vw, 2.25rem);
    border: 1px solid rgba(127, 212, 189, .22);
    border-radius: 18px;

    /* Glass, with a fallback: `backdrop-filter` is ignored by older browsers,
       and the panel behind it has to be opaque enough to read the text on
       without it. */
    background: rgba(0, 41, 31, .62);
    backdrop-filter: blur(14px);
    box-shadow: 0 24px 70px rgba(0, 0, 0, .45);
}

.mnt-logo {
    display: block;
    inline-size: auto;
    block-size: clamp(2.6rem, 8vw, 3.4rem);
    margin: 0 auto 1.4rem;
}

.mnt-badge {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    margin: 0 0 1rem;
    padding: .3rem .8rem;
    border: 1px solid rgba(127, 212, 189, .3);
    border-radius: 999px;
    background: rgba(127, 212, 189, .08);
    color: var(--mnt-node);
    font-size: .74rem;
    letter-spacing: .02em;
}

/* A slow pulse, so the page reads as "running, waiting" rather than frozen.
   One small dot: anything larger competes with the mesh. */
.mnt-pulse {
    inline-size: .45rem;
    block-size: .45rem;
    border-radius: 50%;
    background: var(--mnt-node);
    box-shadow: 0 0 0 0 rgba(127, 212, 189, .65);
    animation: mnt-pulse 2.4s ease-out infinite;
}

@keyframes mnt-pulse {
    70% { box-shadow: 0 0 0 .55rem rgba(127, 212, 189, 0); }
    100% { box-shadow: 0 0 0 0 rgba(127, 212, 189, 0); }
}

.mnt-title {
    margin: 0 0 .6rem;
    font-size: clamp(1.4rem, 4.5vw, 1.85rem);
    line-height: 1.3;
    font-weight: 700;
}

.mnt-message {
    margin: 0 auto 1.75rem;
    max-inline-size: 26rem;
    color: var(--mnt-muted);
    line-height: 1.85;
    font-size: .95rem;
}

.mnt-form {
    display: flex;
    flex-wrap: wrap;
    gap: .6rem;
    justify-content: center;
}

.mnt-field {
    position: relative;
    flex: 1 1 12rem;
    display: flex;
    align-items: center;
}

.mnt-lock {
    position: absolute;
    inset-inline-start: .7rem;
    inline-size: 1.05rem;
    block-size: 1.05rem;
    color: var(--mnt-muted);
    pointer-events: none;
}

.mnt-field input {
    inline-size: 100%;
    padding: .7rem .8rem;
    border: 1px solid rgba(127, 212, 189, .25);
    border-radius: 10px;
    background: rgba(0, 25, 19, .55);
    color: var(--mnt-ink);
    font-size: .95rem;
    letter-spacing: .08em;
    transition: border-color .18s ease, box-shadow .18s ease;
}

/* Room for the padlock, on the side the PAGE starts.

   Keyed to the page direction rather than written as `padding-inline-start`,
   because the input carries dir="ltr" of its own - a password is typed
   left to right in every language - and a logical property resolves against
   the element, not its parent. In RTL that put the padlock on the right and
   the space for it on the left, straight through the placeholder. */
html[dir="ltr"] .mnt-field input { padding-inline-start: 2.3rem; }
html[dir="rtl"] .mnt-field input { padding-right: 2.3rem; }

.mnt-field input::placeholder {
    color: var(--mnt-muted);
    letter-spacing: normal;
    opacity: .75;
}

.mnt-field input:focus-visible {
    outline: none;
    border-color: var(--mnt-node);
    box-shadow: 0 0 0 3px rgba(127, 212, 189, .2);
}

.mnt-submit {
    flex: none;
    padding: .7rem 1.4rem;
    border: 0;
    border-radius: 10px;
    background: linear-gradient(135deg, #17a37f, #0d6f57);
    color: #f2fffb;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    transition: transform .15s ease, box-shadow .18s ease, filter .18s ease;
}

.mnt-submit:hover { filter: brightness(1.08); box-shadow: 0 10px 26px rgba(23, 163, 127, .3); }
.mnt-submit:active { transform: translateY(1px); }

.mnt-submit:focus-visible {
    outline: 2px solid var(--mnt-node);
    outline-offset: 2px;
}

.mnt-error {
    margin: .9rem 0 0;
    color: #ffb4a8;
    font-size: .85rem;
}

.mnt-foot {
    margin: 1.5rem 0 0;
    color: rgba(157, 189, 179, .6);
    font-size: .72rem;
    letter-spacing: .12em;
    text-transform: uppercase;
}

/* Nothing here moves for a visitor who asked for stillness. The mesh script
   makes the same check and paints a single frame. */
@media (prefers-reduced-motion: reduce) {
    .mnt-pulse { animation: none; }
    .mnt-submit { transition: none; }
}

/* ==========================================================================
   Exchange rate archive

   Three colours of its own, chosen with the dataviz validator rather than
   borrowed from --success/--danger: those are STATUS colours ("this order
   failed"), and a falling rate is not a failure. The pair was stepped for
   lightness distance as well as hue, so a deutan reader still separates them
   (ΔE 19 light, 11 dark) - and they only ever colour an arrow, never a number.
   ========================================================================== */

:root {
    --rate-line: #057558;
    --rate-up: #5a9f5d;
    --rate-down: #89322c;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --rate-line: #38a88e;
        --rate-up: #5da260;
        --rate-down: #a64c44;
    }
}

:root[data-theme="dark"] {
    --rate-line: #38a88e;
    --rate-up: #5da260;
    --rate-down: #a64c44;
}

.rates > * + * { margin-block-start: 1.5rem; }
.rates h2 { font-size: 1.1rem; margin: 0; }

/* ---------- range filter ---------- */

.rates-ranges {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    padding: .25rem;
    background: var(--surface-alt);
    border-radius: var(--radius);
    inline-size: fit-content;
    max-inline-size: 100%;
}

.rates-range {
    padding: .4rem .9rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-weight: 600;
    font-size: .9rem;
    text-decoration: none;
}
.rates-range:hover { color: var(--text); }
.rates-range.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }
.rates-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- headline ---------- */

.rates-hero {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: 1.5rem 1.75rem;
}

.rates-hero-label { color: var(--text-muted); font-size: .92rem; }

.rates-hero-value {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .5rem .75rem;
    font-size: clamp(2.4rem, 7vw, 3.4rem);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -.01em;
}
.rates-hero-unit { font-size: 1rem; font-weight: 500; color: var(--text-muted); letter-spacing: 0; }

/* ---------- headline trend ---------- */

.rates-hero-main { display: flex; flex-direction: column; gap: .5rem; min-inline-size: 0; }

.rates-hero.has-spark {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
    align-items: center;
    gap: 1.25rem 2.5rem;
}

.rates-spark {
    margin: 0;
    padding: .85rem 1rem .6rem;
    border-radius: var(--radius);
    background: linear-gradient(180deg, var(--surface-alt), transparent);
}

.rates-spark-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .5rem;
    font-size: .82rem;
    color: var(--text-muted);
    margin-block-end: .35rem;
}
.rates-spark-head .rates-delta { color: var(--text); font-weight: 600; }

.rates-spark-svg { display: block; inline-size: 100%; block-size: auto; overflow: visible; }

/* The wash fades out downward, so the line reads as the shape and the
   fill only as its weight. Stop colours as classes: an SVG stop can take
   a custom property from CSS but not from its own attribute. */
.rates-spark-stop-top { stop-color: var(--rate-line); stop-opacity: .28; }
.rates-spark-stop-bottom { stop-color: var(--rate-line); stop-opacity: 0; }

.rates-spark-line {
    fill: none;
    stroke: var(--rate-line);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.rates-spark-halo { fill: var(--rate-line); opacity: .18; }

.rates-spark-ends {
    display: flex;
    justify-content: space-between;
    font-size: .75rem;
    color: var(--text-muted);
    margin-block-start: .2rem;
}

/* The line draws itself once on arrival, the dot and its halo land after
   it, and the halo breathes while the week is open. None of it for anyone
   who has asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
    .rates-spark-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: rates-draw 1.1s cubic-bezier(.4, 0, .2, 1) .15s forwards; }
    .rates-spark-area { opacity: 0; animation: rates-fade .6s ease-out .7s forwards; }
    .rates-spark .rates-dot { opacity: 0; animation: rates-fade .3s ease-out 1.15s forwards; }
    .rates-spark-halo { opacity: 0; transform-box: fill-box; transform-origin: center; animation: rates-halo 2.4s ease-in-out 1.3s infinite; }
}

@keyframes rates-draw { to { stroke-dashoffset: 0; } }
@keyframes rates-fade { to { opacity: 1; } }
@keyframes rates-halo {
    0%, 100% { opacity: .22; transform: scale(.7); }
    50% { opacity: .08; transform: scale(1.25); }
}

@media (max-width: 720px) {
    .rates-hero.has-spark { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- rise / fall marker ---------- */

.rates-delta {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .35rem;
    font-weight: 600;
}
.rates-delta-sm { font-size: .85rem; font-weight: 500; }
.rates-delta-figures { font-variant-numeric: tabular-nums; }

.rates-arrow { font-size: .8em; color: var(--text-muted); }
[data-dir="up"] > .rates-arrow { color: var(--rate-up); }
[data-dir="down"] > .rates-arrow { color: var(--rate-down); }

.rates-tile-value { font-size: 1.45rem; }
.rates-stats { margin-block-end: 0; }

/* ---------- chart ---------- */

.rates-chart { margin: 0; padding: 1.25rem 1.25rem 1rem; }

.rates-chart-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: .25rem 1rem;
    margin-block-end: .75rem;
}

.rates-chart-frame { position: relative; }
.rates-chart-frame.is-narrow { display: none; }

@media (max-width: 600px) {
    .rates-chart-frame.is-wide { display: none; }
    .rates-chart-frame.is-narrow { display: block; }
}

.rates-svg {
    display: block;
    inline-size: 100%;
    block-size: auto;
    overflow: visible;
    touch-action: pan-y;
}
.rates-svg:focus { outline: none; }
.rates-svg:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-sm); }

.rates-grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* --k is set by the page script to viewBox width / drawn width, so labels
   stay the same size on a phone and a wide screen. Without script it is 1. */
.rates-axis { fill: var(--text-muted); font-size: calc(11px * var(--k, 1)); font-variant-numeric: tabular-nums; }

.rates-area { fill: var(--rate-line); opacity: .1; }
.rates-line {
    fill: none;
    stroke: var(--rate-line);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.rates-dot { fill: var(--rate-line); stroke: var(--surface); stroke-width: 2; vector-effect: non-scaling-stroke; }
.rates-focus { pointer-events: none; }
/* The UA stylesheet hides [hidden] HTML elements only, not SVG ones. */
.rates-svg [hidden] { display: none; }

.rates-mark { fill: var(--text-muted); font-size: calc(11px * var(--k, 1)); font-weight: 600; font-variant-numeric: tabular-nums; }
.rates-mark-strong { fill: var(--text); font-size: calc(12px * var(--k, 1)); font-weight: 700; }
.rates-mark-word { font-weight: 500; }

.rates-crosshair { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; pointer-events: none; }
.rates-hit { fill: transparent; cursor: crosshair; }

.rates-tip {
    position: absolute;
    inset-block-start: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: .1rem;
    min-inline-size: 11rem;
    padding: .55rem .75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    font-size: .82rem;
    pointer-events: none;
    white-space: nowrap;
}
.rates-tip[hidden] { display: none; }
.rates-tip-value { font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.rates-tip-unit, .rates-tip-week { color: var(--text-muted); }
.rates-tip-change { font-variant-numeric: tabular-nums; }
.rates-tip-change[data-dir="up"]::first-letter { color: var(--rate-up); }
.rates-tip-change[data-dir="down"]::first-letter { color: var(--rate-down); }

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

.rates-table-card h2 { margin-block-end: .75rem; }
/* relative: the visually-hidden direction words are absolutely positioned,
   and without a containing block in here they escape the scroller and widen
   the whole page on a phone. */
.rates-table-wrap { position: relative; overflow-x: auto; }
.rates-table td.numeric, .rates-table th.numeric { text-align: end; font-variant-numeric: tabular-nums; }
.rates-table td.numeric { white-space: nowrap; }
.rates-table tr.is-current td { background: var(--accent-soft); }
.rates-table .badge { margin-inline-start: .4rem; }
.rates-table .rates-delta-sm { flex-wrap: nowrap; }
.rates-week { white-space: nowrap; }
.rates-pct { display: block; font-size: .78rem; }

.rates-empty p { margin: 0; }
.rates-footnote { max-inline-size: var(--measure); }

@media (max-width: 560px) {
    .rates-hero { padding: 1.15rem; }
    .rates-chart { padding: 1rem .75rem .75rem; }
    .rates-table-card { padding-inline: .75rem; }
    .rates-table th, .rates-table td { padding: .55rem .4rem; }
}

/* ==========================================================================
   Rate ticker - this week's dollar rate, at the top of every member page.
   Same markup as the panels' sidebar card (see panel.css); colours are the
   validated --rate-* tokens defined with the rate archive above.
   ========================================================================== */

.rate-ticker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.75rem;
    padding: .9rem 1.25rem;
    margin-block-end: 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: linear-gradient(90deg, var(--surface-alt), var(--surface));
    color: var(--text);
    text-decoration: none;
    transition: border-color .15s ease, box-shadow .15s ease;
}
[dir="rtl"] .rate-ticker { background: linear-gradient(270deg, var(--surface-alt), var(--surface)); }
.rate-ticker:hover { border-color: var(--rate-line); box-shadow: var(--shadow); }
.rate-ticker:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.rate-ticker-text { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem; flex: 1 1 18rem; min-inline-size: 0; }
.rate-ticker-label { font-size: .8rem; color: var(--text-muted); font-weight: 600; }
.rate-ticker-value { display: inline-flex; align-items: baseline; gap: .4rem; }
.rate-ticker-value strong { font-size: 1.35rem; font-weight: 800; letter-spacing: -.01em; }
.rate-ticker-unit, .rate-ticker-muted { font-size: .8rem; color: var(--text-muted); }
.rate-ticker-delta { display: inline-flex; align-items: baseline; gap: .3rem; font-size: .85rem; font-weight: 600; }
.rate-ticker-arrow { font-size: .75em; color: var(--text-muted); }
[data-dir="up"] > .rate-ticker-arrow { color: var(--rate-up); }
[data-dir="down"] > .rate-ticker-arrow { color: var(--rate-down); }

.rate-ticker-spark { display: inline-flex; flex-direction: column; align-items: center; gap: .1rem; }
.rate-ticker-spark svg { inline-size: 120px; block-size: 36px; overflow: visible; }
.rate-ticker-caption { font-size: .7rem; color: var(--text-muted); }
.rate-ticker-area { fill: var(--rate-line); opacity: .14; }
.rate-ticker-line { fill: none; stroke: var(--rate-line); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.rate-ticker-dot { fill: var(--rate-line); stroke: var(--surface); stroke-width: 1.5; }

.rate-ticker-more { font-size: .85rem; font-weight: 600; color: var(--accent); white-space: nowrap; }
.rate-ticker-chevron { display: inline-block; transition: transform .15s ease; }
.rate-ticker:hover .rate-ticker-chevron { transform: translateX(3px); }
[dir="rtl"] .rate-ticker:hover .rate-ticker-chevron { transform: translateX(-3px); }

@media (max-width: 560px) {
    .rate-ticker { padding: .8rem 1rem; }
    .rate-ticker-more { inline-size: 100%; }
}

/* One week on record: a dashed guide at its level - "nothing before this
   yet", deliberately unlike the solid line a real trend draws. */
.rates-guide, .rate-ticker-guide {
    fill: none;
    stroke: var(--rate-line);
    stroke-width: 1.5;
    stroke-dasharray: 4 5;
    stroke-linecap: round;
    opacity: .55;
    vector-effect: non-scaling-stroke;
}
.rates-chart-note { margin: .6rem 0 0; }

/* ===========================================================================
   SCROLLBARS

   The browser's own grey bar, unstyled, looked out of place under the product
   thumbnails and in every sideways-scrolling table. One thin, rounded bar in
   the site's colours everywhere instead, following the light and dark themes.

   Two syntaxes on purpose. `scrollbar-width` / `scrollbar-color` are the
   standard (Firefox, and Chrome/Edge from 121, where they take precedence);
   the ::-webkit-scrollbar rules are what Safari still reads.
   =========================================================================== */

:root {
    --scrollbar-thumb: color-mix(in srgb, var(--text-muted) 40%, transparent);
    --scrollbar-thumb-hover: color-mix(in srgb, var(--text-muted) 65%, transparent);
}

* {
    scrollbar-width: thin;
    scrollbar-color: var(--scrollbar-thumb) transparent;
}

::-webkit-scrollbar { inline-size: 8px; block-size: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 999px;
    background: var(--scrollbar-thumb) padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover) padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* The thumbnail strip: the bar is the only hint that there are more photos
   to the side, so it is drawn as a slim accent-coloured slider on a faint
   track rather than hidden. */
.product-thumbs {
    padding-block-end: .45rem;
    scrollbar-color: var(--accent) color-mix(in srgb, var(--border) 70%, transparent);
}
.product-thumbs::-webkit-scrollbar { block-size: 6px; }
.product-thumbs::-webkit-scrollbar-track {
    border-radius: 999px;
    background: color-mix(in srgb, var(--border) 70%, transparent);
}
.product-thumbs::-webkit-scrollbar-thumb {
    border: 0;
    background: var(--accent);
}

/* ==========================================================================
   Tablets (roughly 600-1100px)
   --------------------------------------------------------------------------
   The layout was built for phones and for desktop; tablets fell between the
   two and got the worst of each - one card alone on a row, two huge product
   cards across, the hero stacked with a postage-stamp picture. These rules
   sit last so they win over the per-section ones above.
   ========================================================================== */

/* Four steps and four assurances: two by two, never three and one. */
@media (max-width: 1000px) and (min-width: 561px) {
    .steps,
    .assurances { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem 1.5rem; }
}

@media (max-width: 560px) {
    .steps,
    .assurances { grid-template-columns: 1fr; }
}

/* Six categories: three across on a tablet (two rows), two on a large phone. */
@media (max-width: 900px) and (min-width: 701px) {
    .category-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The shop: three products across on a tablet rather than two oversized ones. */
@media (max-width: 1000px) and (min-width: 601px) {
    .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; }
}

/* The landing page's four featured products: four across or two by two. */
.home-section .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 900px) {
    .home-section .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
}

@media (max-width: 480px) {
    .home-section .product-grid { grid-template-columns: 1fr; }
}

/* The hero keeps its two columns on a tablet held upright; only a phone
   stacks it, and then the picture is allowed to be a picture. */
@media (max-width: 1000px) and (min-width: 701px) {
    .hero { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); gap: 2rem; padding-block-end: 2.5rem; }
    .hero-art { order: 0; max-width: none; margin-inline: 0; }
}

@media (max-width: 700px) and (min-width: 561px) {
    .hero-art { max-width: 340px; }
}

/* A help text from the panel (x-tip): an icon that shows the text on hover or focus. */
.tip { position: relative; display: inline-flex; vertical-align: middle; margin-inline-start: .3rem; cursor: help; outline: none; }
.tip-icon { display: inline-grid; place-items: center; width: 1.05rem; height: 1.05rem; border-radius: 50%; background: var(--surface-alt); color: var(--text-muted); font-size: .7rem; font-weight: 700; border: 1px solid var(--border); }
.tip-text { position: absolute; inset-block-end: calc(100% + .4rem); inset-inline-start: 50%; transform: translateX(50%); z-index: 30; width: max-content; max-width: 16rem; padding: .5rem .65rem; border-radius: 8px; background: var(--text); color: var(--bg); font-size: .78rem; font-weight: 400; line-height: 1.6; visibility: hidden; opacity: 0; transition: opacity .15s; pointer-events: none; text-align: start; }
[dir="ltr"] .tip-text { transform: translateX(-50%); }
.tip-text strong { display: block; }
.tip:hover .tip-text, .tip:focus .tip-text, .tip:focus-within .tip-text { visibility: visible; opacity: 1; }

/* The panel's notices at the top of the member area. */
.member-notice { display: flex; justify-content: space-between; gap: .75rem; align-items: flex-start; }
.member-notice .link-button { font-size: 1.2rem; line-height: 1; }

/* Industry / application facet in the shop filter rail. */
.filter-select { width: 100%; padding: .45rem .6rem; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; font-size: .9rem; }

/* ---------------------------------------------------------------- variants
   Size and colour on the product page (shop/_variant-picker). */
.variant-picker { border: 0; padding: 0; margin: 0 0 1rem; }
.variant-legend { font-weight: 700; font-size: .92rem; margin-block-end: .5rem; padding: 0; }
.variant-group + .variant-group { margin-block-start: .75rem; }
.variant-color-name { display: flex; align-items: center; gap: .45rem; margin: 0 0 .4rem; font-size: .9rem; }
.variant-swatch { inline-size: 1.15rem; block-size: 1.15rem; border-radius: 50%; border: 1px solid var(--border); flex: none; }
.variant-options { display: flex; flex-wrap: wrap; gap: .45rem; }
.variant-chip { position: relative; cursor: pointer; }
.variant-chip input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.variant-chip span { display: inline-flex; flex-direction: column; align-items: center; min-inline-size: 3rem; padding: .4rem .7rem; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); font-weight: 600; line-height: 1.3; }
.variant-chip small { font-weight: 400; font-size: .7rem; color: var(--text-muted, #5c706b); }
.variant-chip input:checked + span { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent) inset; }
.variant-chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.variant-chip.is-sold-out { cursor: not-allowed; }
.variant-chip.is-sold-out span { opacity: .5; text-decoration: line-through; }
.size-guide { margin: 0 0 1rem; border: 1px solid var(--border); border-radius: 10px; padding: .5rem .8rem; background: var(--surface); }
.size-guide summary { cursor: pointer; font-weight: 600; font-size: .9rem; }
.size-guide table { width: 100%; border-collapse: collapse; margin-block: .6rem .3rem; font-size: .85rem; }
.size-guide th, .size-guide td { border-bottom: 1px solid var(--border); padding: .35rem .5rem; text-align: center; }
.size-guide th { background: var(--surface-alt); }
.line-variant { display: block; font-size: .82rem; color: var(--text-muted, #5c706b); }
