/* 04-storefront.css: catalog surfaces (product grid, category nav), the
   contact form, and utility pages (404 / placeholder / parked / dev-error).

   The centre of gravity here is the product card, because our tenants are gun
   stores with bad phone photos or no photography at all and the card is where
   that shows. Three systems carry it:

     1. The SPECIMEN PLATE, a CSS-only type-led stand-in for a missing image.
     2. The OPTICAL BOUNDING BOX correction, which is the single most
        important fix for a firearms grid (see --sf-optical below).
     3. A CLOSED SET of aspect ratios enforced on the module rather than the
        image, so a dealer physically cannot break a row with a bad upload. */

/* ================================================================
   product grid
   ================================================================ */

.sf-product-grid {
    display: grid;
    gap: var(--sf-card-gap);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sf-product-card {
    position: relative;
    background: var(--sf-surface);
    border-radius: var(--sf-radius);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition:
        transform var(--sf-dur-2) var(--sf-ease-out),
        box-shadow var(--sf-dur-2) var(--sf-ease-out);

    /* Measured at roughly 7x on off-screen cards, and the single biggest
       rendering win available here. contain-intrinsic-size is not optional:
       without it the scrollbar jumps as cards realise their height. */
    content-visibility: auto;
    contain-intrinsic-size: auto 360px;
}

/* NOT a translateY lift. The 3px card rise is the most template-coded
   interaction on the web and it is the first thing that gives a site away.
   The card instead lights its own edge and pushes the image a hair, which is
   still transform and opacity only and still free on the compositor. */
.sf-product-card:hover {
    box-shadow:
        inset 0 0 0 1px color-mix(in oklab, var(--sf-brand) 55%, transparent),
        var(--sf-elev-3);
}

/* Subgrid so every card's price and stock line sit on the SAME baseline
   regardless of how many lines its title wraps to, which is the ragged
   bottom edge that makes a catalogue look unfinished. It needs an explicit
   column count on the parent, which the grid above has (repeat(n), never
   auto-fill). The flex fallback keeps the price bottom-aligned per card
   where subgrid is missing, so nothing regresses. */
/* SUBGRID MACHINERY REMOVED (live QA 2026-08-25). The cards carry
   content-visibility: auto, and its layout containment stops card content
   from sizing the shared outer tracks - so the promised cross-card price
   baseline never happened on the live site, while the auto-sized body
   column (~205px min-content from the price row) overflowed the 158px
   phone track and the card's overflow:hidden sliced title glyphs in half.
   The flex composition below is the design: equal-height cards from the
   grid stretch, price pinned to the card foot by margin-top: auto, which
   IS a shared baseline whenever the row's cards stretch to one height. */

.sf-product-media {
    position: relative;
    background: var(--sf-surface-sunken);

    /* THE DISPLAY PLINTH. The well is no longer a full-bleed panel that
       punches a white hole in the card: it is an object INSET in the card,
       with a machined edge, sitting on a ground that MediaPresentation mixes
       toward the theme's shared plate tone (35% image, 65% plate), so every
       well in a row lands near one material. The inset amount is a theme
       token lane A owns: dark themes frame the plate, light "paper" themes
       zero it and go full bleed. The fallback keeps the geometry sane until
       the token lands (ultraplan cross-lane rule: reference, never define). */
    margin: var(--sf-plate-inset, var(--sf-space-3)) var(--sf-plate-inset, var(--sf-space-3)) 0;
    border-radius: var(--sf-radius-sm);
    box-shadow: inset 0 0 0 1px var(--sf-plate-edge, rgba(0, 0, 0, 0.08));

    /* One of the four site-wide ratios, set on the MODULE and not the image.
       The merchandise ratio is landscape because this tile CONTAINS its worst
       cases rather than cropping them; see --sf-ratio-product for the whole
       argument and MediaFit.ProductTileRatio for the renderer half of it. */
    aspect-ratio: var(--sf-ratio-product);
    overflow: hidden;

    /* Per-image backdrop. A mixed grid of cutouts on four different whites
       looks like a ransom note; giving each tile a ground derived from its
       own image is what makes the row read as a set. Rides in on the style
       attribute, which the CSP permits (style-src carries 'unsafe-inline';
       there is no nonce plumbing in this renderer). */
    background: var(--sf-media-bg, var(--sf-surface-sunken));

    /* The container the specimen mark sizes itself against, so one card
       component is correct in a four-up grid, a two-up related row and a
       full-width PDP slot with no variant and no dealer-facing choice. */
    container-type: inline-size;

    /* mix-blend-mode needs an opaque ground and its own stacking context. */
    isolation: isolate;
}

.sf-product-media img {
    width: 100%;
    height: 100%;

    /* ---- THE CUTOUT BUG ----

       This was `object-fit: cover` unconditionally, and it is the single most
       damaging thing in the old stylesheet. A distributor cutout of a rifle
       is typically about 3000x800. `cover` on a portrait tile crops that to
       the middle of the barrel, so EVERY long-gun listing in every dealer's
       catalogue rendered as an anonymous tube: the most important category in
       a gun store, unrecognisable.

       The fit is now decided per image from its own intrinsic dimensions,
       which ResponsiveImageResolver already has, so it needs no schema and no
       upload-pipeline change. A frame whose aspect ratio is wildly different
       from the tile is a cutout or a panorama and gets `contain` plus
       percentage padding; a frame close to the tile is a photograph and gets
       `cover` with a focal point. Percentage padding, never px, so it stays
       proportional at every breakpoint. */
    object-fit: var(--sf-media-fit, cover);
    padding: var(--sf-media-pad, 0);

    /* Placement, and it is a DIFFERENT decision per fit branch.

       --sf-focal (50% 42%) is a CROP bias: amateur photographers centre the
       subject in the viewfinder and include too much floor, so the subject of
       a photograph sits above the middle of the frame and cover should keep
       that part. Applied to a CONTAINED image it stops being a crop bias and
       becomes a placement bias: it lifts the whole image inside the tile and
       pushes all the leftover space down against the bottom edge, which on a
       product card is exactly where the text starts. That is what put a dead
       band under every contained image in the grid. MediaFit now sends the
       right one per image; the fallback is the historic behaviour. */
    object-position: var(--sf-media-position, var(--sf-focal));

    /* ---- the optical bounding box ----

       object-fit normalises BOUNDING BOXES, not perceived size. A rifle is
       about 4:1 and thin; a red dot is small and chunky. Scaled to fill the
       same tile, the optic ends up looking physically bigger than the rifle.
       That is categorically wrong, and it is most of why a firearms grid
       reads amateur even when every photo is fine.

       --sf-optical is the correction, seeded from the product's category by
       OpticalScale.ForCategory and overridable per product. transform: scale
       is compositor-only, so this costs nothing at any grid size. */
    transform: scale(var(--sf-optical, 1));
    transition: transform var(--sf-dur-4) var(--sf-ease-out);
}

.sf-product-card:is(:hover, :focus-within) .sf-product-media img {
    transform: scale(calc(var(--sf-optical, 1) * 1.04));
}

/* THE SECOND-PHOTO CROSSFADE (motion 2.1). When the product has a second
   gallery image the card renders it as a hidden layer; hover or keyboard
   focus fades it in over the first. Both layers are `.sf-product-media img`,
   so they share the fit, the inset, the placement and the optical transform,
   and the swap is opacity-only: compositor-free, honest under reduced motion
   (a fade is state, not movement), and lazy-loaded so a grid of cards costs
   nothing until someone actually reaches one. */
.sf-product-media .sf-media-alt {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition:
        opacity var(--sf-dur-3) var(--sf-ease-out),
        transform var(--sf-dur-4) var(--sf-ease-out);
}

.sf-product-card:is(:hover, :focus-within) .sf-media-alt { opacity: 1; }

/* The tone normaliser, per card. Zero by default: a dealer with good
   photography gets their photography. */
.sf-product-media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(150deg, var(--sf-hero-grad-a), var(--sf-hero-grad-b));
    mix-blend-mode: color;
    opacity: var(--sf-tint-strength, 0);
    pointer-events: none;
}

.sf-product-noimage {
    height: 100%;
}

/* THREE CARDS IN A ROW, ONE MEDIA TREATMENT.

   A photographed card shows a plate floating inside its well with an inset
   around it; a specimen card used to show a full-bleed panel with no inset
   and no edge, so the odd one out did not read as a considered stand-in, it
   read as an image that had failed to load. The plate is an OBJECT in the
   well exactly like a contained image is, so it takes the same inset the
   contained branch takes, from the same custom property, and the row reads as
   a set whether or not any given dealer has uploaded a photograph.

   Percentage inset, so it resolves against the tile width and stays
   proportional at every breakpoint, same as the image branch. Without :has()
   the plate is full-bleed, which is where it was, so the degradation is the
   old behaviour rather than a broken one. */
.sf-product-media:has(> .sf-specimen),
.sf-pdp-main-image:has(> .sf-specimen) {
    padding: var(--sf-media-pad, 8%);
}

/* ---- THE CUTOUT FINISH ----

   The upload pipeline now MEASURES cutouts (four-corner sampling at the
   re-encode; site_assets.is_cutout) instead of inferring them from the
   frame, and the renderer branches the class on the ground it measured:

   sf-cutout-opaque: a cutout ON WHITE (JPEG, or an opaque PNG). Its file
   carries its own white rectangle, so two things follow. First the halo
   fix: the white ground is multiplied away against the tile, and because
   multiply darkens the PRODUCT's pixels against a non-white tile too (by
   about 4.3%), the brightness compensation rides along or an entire
   catalogue drifts muddy. Second, drop-shadow is off the table - it wraps
   the alpha silhouette, and an opaque file's silhouette is its rectangle -
   so the ground shadow is a painted ellipse UNDER the image (z-index -1
   inside the isolated tile), which the multiplied white then lets show
   through exactly where the ground is.

   sf-cutout-alpha: a real alpha cutout (transparent corners). No halo to
   remove and a true silhouette to work with, so it takes the two-layer
   drop-shadow: one tight contact layer, one soft ambient layer, both from
   the theme's own shadow colour.

   A bare sf-cutout (measured cutout on a ground that is neither white nor
   transparent) gets the contain framing only: both effects above are only
   valid against the grounds they assume. */

.sf-cutout-opaque img {
    mix-blend-mode: multiply;
    filter: brightness(1.045);
}

.sf-cutout-opaque::before {
    content: "";
    position: absolute;
    z-index: -1;
    left: 22%;
    right: 22%;
    bottom: 6.5%;
    height: 7%;
    background: radial-gradient(
        50% 50% at 50% 50%,
        rgb(var(--sf-shadow-rgb) / calc(0.2 * var(--sf-shadow-strength))),
        rgb(var(--sf-shadow-rgb) / 0) 70%);
    pointer-events: none;
}

.sf-cutout-alpha img {
    filter:
        drop-shadow(0 2px 3px rgb(var(--sf-shadow-rgb) / calc(0.22 * var(--sf-shadow-strength))))
        drop-shadow(0 12px 14px rgb(var(--sf-shadow-rgb) / calc(0.16 * var(--sf-shadow-strength))));
}

/* The specimen plate agrees with its photographed siblings: same plate-mixed
   ground family the measured wells land on, so a row of two photos and a
   specimen reads as one display case. (The base radial lives in
   01-foundation; this layers the plate tone into it inside the well only.) */
.sf-product-media .sf-specimen,
.sf-pdp-main-image .sf-specimen {
    background:
        radial-gradient(120% 90% at 50% 0%,
            color-mix(in oklab, var(--sf-surface-alt) 65%, var(--sf-plate, #F2EFE9)),
            color-mix(in oklab, var(--sf-surface-sunken) 65%, var(--sf-plate, #F2EFE9)) 72%);
}

/* The plate's label line: SKU (or the first words of the name) in the machine
   face, so the stand-in reads as a catalogued specimen, not a failed load. */
.sf-specimen-microline {
    position: absolute;
    left: 50%;
    bottom: 9%;
    transform: translateX(-50%);
    max-width: 86%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums slashed-zero;
    color: var(--sf-text-faint);
    user-select: none;
}

/* CORNER TABS, welded to the plinth: the badge is part of the well's own
   edge (outer corners share the well radius, inner corners square into it),
   not a sticker floating over the photograph. Mono, quiet, shadowless. Tabs
   STACK - the old markup's else-if silently dropped the FFL tab from any
   discounted FFL item - capped at two, priority SALE > FFL > NEW. */
.sf-product-flags {
    position: absolute;
    z-index: 2;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* 1px read as the two chips touching (live, sale + new stacked). */
    gap: 5px;
}

.sf-product-flag {
    background: var(--sf-surface-raised);
    color: var(--sf-badge-ink);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    border-radius: var(--sf-radius-sm) 0 var(--sf-radius-sm) 0;
    padding: 4px 10px;
}

.sf-product-sale {
    background: var(--sf-brand);
    color: var(--sf-on-brand);
}

/* FFL is routing information, not a promotion: it never takes the brand
   fill, just a quiet ring on the card surface. */
.sf-product-ffl {
    background: var(--sf-surface);
    color: var(--sf-text);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sf-ink) 40%, transparent);
}

.sf-product-body {
    padding: var(--sf-space-4) var(--sf-space-4) var(--sf-space-5);
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex-grow: 1;
}

.sf-product-category {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-100);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
}

/* One size step DOWN from the old card (fs-500 -> fs-400): the name is a
   label, the price is the object. Clamped at three lines so a distributor's
   40-word compliance-laden name cannot push its price off the shared
   baseline; text-wrap: pretty spends the extra line-break budget avoiding
   single-word orphans. */
.sf-product-name {
    font-size: var(--sf-fs-400);
    line-height: var(--sf-lh-400);
    letter-spacing: calc(var(--sf-track-400) + var(--sf-track-offset));
    color: var(--sf-ink);
    margin-top: 2px;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
    text-wrap: pretty;
}

/* BRAND · SKU: machine facts in the machine face (law 5). */
.sf-product-meta {
    margin-top: 3px;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums slashed-zero;
    color: var(--sf-text-faint);
}

.sf-product-price {
    margin-top: auto;
    padding-top: var(--sf-space-3);
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
}

.sf-product-price > span:first-child {
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-heading-weight);
    font-size: var(--sf-fs-600);
    letter-spacing: calc(var(--sf-track-600) + var(--sf-track-offset));
    font-variant-numeric: tabular-nums lining-nums;
    color: var(--sf-ink);
}

.sf-product-price s {
    color: var(--sf-text-faint);
    font-size: var(--sf-fs-200);
    font-variant-numeric: tabular-nums lining-nums;
}

/* Computed from the real compare-at delta, never typed: the markup only
   emits it when compare-at genuinely exceeds price. */
/* Own line, always: inline-when-it-fits left one card in a row with the
   chip beside the price and its neighbour with the chip below (live). The
   zero-height full-basis break does the wrapping; basis on the chip itself
   would be clamped by its own max-content before line packing. */
.sf-price-break {
    flex-basis: 100%;
    height: 0;
}

.sf-price-save {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums;
    color: var(--sf-brand);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sf-brand) 45%, transparent);
    border-radius: var(--sf-radius-xs);
    padding: 1px 6px;
}

.sf-product-statusline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sf-space-2);
    min-height: 1em;
}

.sf-product-stock {
    font-size: var(--sf-fs-100);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-family: var(--sf-font-heading);
}

/* Quote-mode hover microline: the card whispers its own next step. Hidden
   from AT (aria-hidden in markup) because the whole card is already one
   link; this is decoration for pointers and keyboards. */
.sf-card-quote-cue {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-space-2);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-brand);
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--sf-dur-2) var(--sf-ease-out);
}

.sf-card-quote-cue::after {
    content: "";
    width: 14px;
    height: 1px;
    background: currentColor;
}

.sf-product-card:is(:hover, :focus-within) .sf-card-quote-cue { opacity: 1; }

.sf-in-stock { color: var(--sf-positive); }
.sf-out-stock { color: var(--sf-text-faint); }
/* Quote-only, untracked quantity: an invitation, not a stock claim. */
.sf-ask-stock { color: var(--sf-text-soft); }

/* 834 (tablet portrait): three columns. The old cascade jumped straight from
   two to four at 860, so an iPad in portrait showed a phone grid. */
@media (min-width: 700px) and (max-width: 899px) {
    .sf-product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
    .sf-product-grid { grid-template-columns: repeat(var(--sf-grid-cols, 4), minmax(0, 1fr)); }
}

/* ================================================================
   grid layouts: bento + rail (ProductGridProps.Layout)

   The wall above stays the default. Bento gives the first product a 2x2
   hero cell; rail is a scroll-snap band. Neither EVER uses dense packing
   (grid-auto-flow: dense rearranges visual order against DOM order on a
   grid of links; a design-system test bans it).
   ================================================================ */

/* The subgrid baseline machinery assumes every card spans the same four
   outer tracks; a 2x2 hero and a horizontally-flowing rail both break that
   assumption, so inside these two layouts the cards fall back to the flex
   composition (the same one non-subgrid browsers use everywhere). */
.sf-grid-bento .sf-product-card,
.sf-grid-feature .sf-product-card,
.sf-grid-rail .sf-product-card,
.sf-grid-bento .sf-product-link,
.sf-grid-feature .sf-product-link,
.sf-grid-rail .sf-product-link {
    display: flex;
    flex-direction: column;
    grid-template-rows: none;
    grid-row: auto;
}

.sf-grid-bento .sf-product-body,
.sf-grid-feature .sf-product-body,
.sf-grid-rail .sf-product-body {
    display: flex;
    flex-direction: column;
    grid-template-rows: none;
    grid-row: auto;
}

/* Mobile bento: the hero keeps its promotion by going full width; the rest
   stack in the ordinary two-up grid. */
.sf-grid-bento > .sf-product-card:first-child,
.sf-grid-feature > .sf-product-card:first-child { grid-column: 1 / -1; }

@media (min-width: 900px) {
    /* Explicit tracks, explicit spans, no auto-fill and no dense: the hero
       occupies 2x2 by declaration and everything else flows around it in
       DOM order. The shop's featured cell (page 1, unfiltered only) is the
       same object under a different flag. */
    .sf-grid-bento { grid-template-columns: repeat(4, minmax(0, 1fr)); }

    .sf-grid-bento > .sf-product-card:first-child,
    .sf-grid-feature > .sf-product-card:first-child {
        grid-column: span 2;
        grid-row: span 2;
    }
}

/* The rail (motion 1.5): one row, snap points, contained overscroll so the
   page does not rubber-band when the rail runs out. Proximity snapping,
   NEVER mandatory: mandatory snap steals half-finished flicks. The rail is
   its own scroll container, so wide content scrolls inside it and the page
   never grows a horizontal scrollbar. */
.sf-grid-rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: clamp(240px, 26vw, 320px);
    grid-template-columns: none;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--sf-space-4);
    padding-bottom: var(--sf-space-3);
    scrollbar-width: thin;
    scrollbar-color: var(--sf-hairline) transparent;
}

.sf-grid-rail > .sf-product-card { scroll-snap-align: start; }

/* Progressive rail chrome: browsers that ship the CSS carousel
   pseudo-elements get real buttons and markers for free, still zero JS;
   everyone else simply scrolls. */
@supports selector(::scroll-button(right)) {
    .sf-grid-rail::scroll-button(left) { content: "←" / "Scroll back"; }
    .sf-grid-rail::scroll-button(right) { content: "→" / "Scroll forward"; }

    .sf-grid-rail::scroll-button(left),
    .sf-grid-rail::scroll-button(right) {
        font-family: var(--sf-font-mono);
        font-size: var(--sf-fs-300);
        color: var(--sf-ink);
        background: var(--sf-surface);
        border: 0;
        border-radius: var(--sf-radius-pill);
        box-shadow: inset 0 0 0 1px var(--sf-hairline);
        width: 40px;
        height: 40px;
        cursor: pointer;
    }

    .sf-grid-rail::scroll-button(*):disabled { opacity: 0.35; }
}

@supports selector(::scroll-marker) {
    .sf-grid-rail { scroll-marker-group: after; }

    .sf-grid-rail::scroll-marker-group {
        display: flex;
        justify-content: center;
        gap: var(--sf-space-2);
        margin-top: var(--sf-space-3);
    }

    .sf-grid-rail > .sf-product-card::scroll-marker {
        content: "";
        width: 6px;
        height: 6px;
        border-radius: var(--sf-radius-pill);
        background: var(--sf-hairline);
    }

    .sf-grid-rail > .sf-product-card::scroll-marker:target-current {
        background: var(--sf-brand);
    }
}

/* The card is a container, so the SAME card component recomposes by the
   space it is given: in the bento hero (and any tile past ~340px inline)
   the name and price step up a size. This is what makes bento read as a
   hierarchy rather than a zoomed screenshot. */
.sf-product-card { container: sf-card / inline-size; }

@container sf-card (min-width: 340px) {
    .sf-product-name {
        font-size: var(--sf-fs-500);
        line-height: var(--sf-lh-500);
        letter-spacing: calc(var(--sf-track-500) + var(--sf-track-offset));
    }

    .sf-product-price > span:first-child {
        font-size: var(--sf-fs-700);
        letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    }
}

/* Asymmetric grid header (the ultraplan section-header ruling): heading
   holds the left edge, the mono count and the view-all link hang off the
   right, all on one baseline. */
.sf-grid-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sf-space-3) var(--sf-space-5);
    margin-bottom: var(--sf-space-5);
}

.sf-grid-header .sf-block-heading { margin-bottom: 0; }

.sf-grid-header-meta {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sf-space-4);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums;
    color: var(--sf-text-faint);
}

.sf-grid-header-meta a { color: var(--sf-text); }
.sf-grid-header-meta a:hover { color: var(--sf-brand); }

/* ================================================================
   category nav
   ================================================================ */

.sf-category-nav {
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--sf-card-gap);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sf-category-tile {
    position: relative;
    background: var(--sf-surface);
    border-radius: var(--sf-radius-sm);
    padding: var(--sf-space-5) var(--sf-space-4);
    display: flex;
    flex-direction: column;
    gap: 2px;
    box-shadow: var(--sf-signature-edge), var(--sf-elev-1);
    transition: transform var(--sf-dur-2) var(--sf-ease-out);
}

.sf-category-tile:hover {
    box-shadow:
        inset 0 0 0 1px color-mix(in oklab, var(--sf-brand) 55%, transparent),
        var(--sf-elev-2);
}

/* Photo tiles (CategoryNavProps.Tiles): the image bleeds to the tile edges
   above the name, so the padded text block keeps its rhythm while the photo
   fills the card. Text-only tiles are untouched. */
.sf-category-tile-photo {
    padding: 0;
    overflow: hidden;
}

.sf-category-tile-media {
    display: block;
    aspect-ratio: 392 / 300;
    overflow: hidden;
}

.sf-category-tile-media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--sf-dur-2) var(--sf-ease-out);
}

.sf-category-tile-photo:hover .sf-category-tile-media img {
    transform: scale(1.03);
}

.sf-category-tile-photo .sf-category-name {
    padding: var(--sf-space-4) var(--sf-space-4) 0;
}

.sf-category-tile-photo .sf-category-count {
    padding: 0 var(--sf-space-4) var(--sf-space-4);
}

.sf-category-name {
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-heading-weight);
    font-size: var(--sf-fs-500);
    letter-spacing: calc(var(--sf-track-500) + var(--sf-track-offset));
    color: var(--sf-ink);
}

.sf-category-count {
    font-size: var(--sf-fs-100);
    font-family: var(--sf-font-heading);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
}

@media (min-width: 860px) {
    .sf-category-nav { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}

/* ================================================================
   forms
   ================================================================ */

.sf-contact-form { max-width: var(--sf-measure); }

.sf-form {
    display: flex;
    flex-direction: column;
    gap: var(--sf-space-4);
    margin-top: var(--sf-space-5);
}

.sf-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1;
}

.sf-field > span {
    font-family: var(--sf-font-heading);
    font-weight: 600;
    color: var(--sf-ink);
    font-size: var(--sf-fs-300);
}

.sf-field em { color: var(--sf-brand); font-style: normal; }

/* The control boundary is the ONE place a real border survives this pass, and
   it uses --sf-control-border rather than --sf-hairline for a reason: WCAG
   1.4.11 requires 3:1 for the edge of an interactive control, and the old
   --sf-border on --sf-surface measured 1.41:1 on gunmetal, 1.50 on heritage,
   1.49 on classic-brick and 1.53 on tactical. Every theme failed it. */
.sf-field input, .sf-field textarea, .sf-field select {
    background: var(--sf-surface-sunken);
    border: 1.5px solid var(--sf-control-border);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-ink);
    padding: 12px 14px;
    transition: border-color var(--sf-dur-2) var(--sf-ease-out);
}

.sf-field input:focus, .sf-field textarea:focus, .sf-field select:focus {
    border-color: var(--sf-brand);
}

.sf-field-row { display: flex; flex-direction: column; gap: var(--sf-space-4); }

.sf-field-choice {
    border: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
}

.sf-field-choice legend {
    font-family: var(--sf-font-heading);
    font-weight: 600;
    color: var(--sf-ink);
    font-size: var(--sf-fs-300);
    margin-bottom: 6px;
}

.sf-field-choice label {
    display: flex;
    gap: 8px;
    align-items: center;
    color: var(--sf-text);
    padding: 10px 14px 10px 0;
    min-height: 44px;
    cursor: pointer;
}

.sf-field-choice input[type="radio"] {
    accent-color: var(--sf-brand);
    width: 18px;
    height: 18px;
}

.sf-form .sf-btn { align-self: flex-start; }

/* Honeypot: visually removed, still in the DOM for bots. */
.sf-hp {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.sf-form-success {
    background: color-mix(in srgb, var(--sf-positive) 12%, transparent);
    box-shadow: inset 3px 0 0 var(--sf-positive);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-ink);
    padding: var(--sf-space-4) var(--sf-space-5);
    font-weight: 600;
    margin-top: var(--sf-space-4);
}

.sf-form-error {
    background: color-mix(in srgb, var(--sf-critical) 12%, transparent);
    box-shadow: inset 3px 0 0 var(--sf-critical);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-ink);
    padding: var(--sf-space-4) var(--sf-space-5);
    margin-top: var(--sf-space-4);
}

/* ---------- product cards as links ---------- */

.sf-product-link {
    display: flex;
    flex-direction: column;
    height: 100%;
    color: inherit;
    text-decoration: none;
}

.sf-product-link:hover { text-decoration: none; }

/* ================================================================
   shop + category pages
   ================================================================ */

.sf-breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
    align-items: center;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    margin-bottom: var(--sf-space-4);
}

.sf-breadcrumbs a { color: var(--sf-text-faint); text-decoration: none; }
.sf-breadcrumbs a:hover { color: var(--sf-brand); }
.sf-breadcrumbs span[aria-current] { color: var(--sf-ink); }

.sf-shop-title { margin-bottom: var(--sf-space-4); }

.sf-shop-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-3);
    margin-bottom: var(--sf-space-4);
}

.sf-shop-search {
    flex: 1 1 240px;
    background: var(--sf-surface-sunken);
    border: 1.5px solid var(--sf-control-border);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-ink);
    padding: 11px 14px;
}

.sf-shop-sort {
    background: var(--sf-surface-sunken);
    border: 1.5px solid var(--sf-control-border);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-ink);
    padding: 11px 14px;
}

.sf-shop-count {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums;
    color: var(--sf-text-faint);
}

/* ---------- results masthead ---------- */

/* The count as a design object: the numeral at display scale (the numeral
   token is lane A's; the fallback keeps the scale until it lands), a
   hairline, and a mono caps label. Asymmetry law: title holds the left
   edge, the number hangs off the right. */
.sf-shop-masthead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sf-space-5);
    margin-bottom: var(--sf-space-5);
}

.sf-shop-masthead .sf-shop-title { margin-bottom: 0; }

.sf-shop-count-object {
    display: none;
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
}

.sf-shop-count-numeral {
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-display-weight);
    font-size: var(--sf-fs-numeral, clamp(3.5rem, 1.6rem + 8.5vw, 9rem));
    line-height: 0.9;
    letter-spacing: var(--sf-track-900);
    font-variant-numeric: tabular-nums;
    color: var(--sf-ink);
    opacity: 0.14;
}

.sf-shop-count-label {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    box-shadow: inset 0 1px 0 var(--sf-hairline);
    padding-top: var(--sf-space-2);
    margin-top: var(--sf-space-2);
    min-width: 9ch;
    text-align: right;
}

@media (min-width: 700px) {
    .sf-shop-count-object { display: flex; }
}

/* ---------- results toolbar row ---------- */

.sf-shop-toolbar-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sf-space-3);
    padding-bottom: var(--sf-space-3);
    margin-bottom: var(--sf-space-4);
    box-shadow: inset 0 -1px 0 var(--sf-hairline);
}

.sf-shop-toolbar-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sf-space-3);
}

.sf-shop-sort-form {
    display: flex;
    align-items: stretch;
    gap: var(--sf-space-2);
}

.sf-shop-sort-form .sf-shop-sort { padding-block: 8px; }
.sf-shop-sort-form .sf-shop-sort-apply { min-height: 0; padding: 8px 14px; }

.sf-density-toggle {
    display: inline-flex;
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
    border-radius: var(--sf-radius-xs);
    overflow: hidden;
}

.sf-density-toggle a {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
    text-decoration: none;
    padding: 8px 12px;
    min-height: 36px;
    display: inline-flex;
    align-items: center;
}

.sf-density-toggle a:hover { color: var(--sf-ink); }

.sf-density-toggle .sf-density-active {
    background: var(--sf-surface-alt);
    color: var(--sf-ink);
    box-shadow: inset 0 -2px 0 var(--sf-brand);
}

/* ---------- dense grid ---------- */

/* The trade-density view: four columns, no eyebrow, no meta line, names on
   two lines. A dealer's regulars scan it like a price sheet. */
.sf-grid-dense .sf-product-category,
.sf-grid-dense .sf-product-meta { display: none; }

.sf-grid-dense .sf-product-name {
    font-size: var(--sf-fs-300);
    line-height: var(--sf-lh-300);
    -webkit-line-clamp: 2;
}

.sf-grid-dense .sf-product-body {
    padding: var(--sf-space-3) var(--sf-space-3) var(--sf-space-4);
}

.sf-grid-dense .sf-product-price > span:first-child { font-size: var(--sf-fs-500); }

.sf-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
    margin-bottom: var(--sf-space-5);
}

.sf-chip {
    background: var(--sf-surface);
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
    border-radius: var(--sf-radius-pill);
    color: var(--sf-text);
    font-size: var(--sf-fs-200);
    font-weight: 600;
    padding: 8px 15px;
    text-decoration: none;
}

.sf-chip:hover { box-shadow: inset 0 0 0 1px var(--sf-brand); color: var(--sf-ink); }

.sf-chip-active {
    background: var(--sf-brand);
    color: var(--sf-on-brand);
    box-shadow: none;
}

.sf-category-description {
    max-width: var(--sf-measure);
    color: var(--sf-text);
    margin-bottom: var(--sf-space-5);
}

.sf-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
    margin-top: var(--sf-space-7);
}

.sf-pagination-status {
    color: var(--sf-text-soft);
    font-size: var(--sf-fs-200);
    font-variant-numeric: tabular-nums;
}

/* Numbered pagination: mono tabular page numbers as real crawlable anchors,
   the current page marked, prev/next as quiet caps. No buttons: this is
   navigation, not action. */
.sf-page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 0 10px;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    font-variant-numeric: tabular-nums;
    color: var(--sf-text);
    text-decoration: none;
    border-radius: var(--sf-radius-xs);
}

a.sf-page-link:hover {
    color: var(--sf-ink);
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
}

.sf-page-current {
    color: var(--sf-ink);
    font-weight: 700;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sf-brand) 55%, transparent);
}

.sf-page-step {
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-size: var(--sf-fs-100);
    color: var(--sf-text-soft);
}

/* ================================================================
   product detail page

   Apple's strategy rather than Stripe's: a narrow text column with the media
   allowed to go wide. It reads more editorial and, for us, it means a weak
   photograph gets cropped hard and confidently instead of being displayed at
   full frame next to a short paragraph.
   ================================================================ */

.sf-pdp {
    display: grid;
    gap: var(--sf-space-6);
    margin-top: var(--sf-space-2);
}

.sf-pdp-gallery { min-width: 0; }

.sf-pdp-main-image {
    position: relative;
    /* Same per-image ground as the grid tile (--sf-media-bg rides in on the
       style attribute; theme surface when the pipeline measured nothing). */
    background: var(--sf-media-bg, var(--sf-surface-sunken));
    border-radius: var(--sf-radius);
    overflow: hidden;
    /* The same MERCHANDISE ratio as the grid tile, not the square one. It is
       the same content class seen larger, and a square slot letterboxed every
       contained frame here for the same reason it did in the grid, only 660px
       wide instead of 285 so the dead band was proportionally as big and
       absolutely much bigger. --sf-ratio-square stays where cropping is the
       job: the gallery thumbs below. */
    aspect-ratio: var(--sf-ratio-product);
    container-type: inline-size;
    isolation: isolate;
    /* Same plinth edge as the grid wells, so the PDP hero is the same
       object the shopper clicked, one size up. */
    box-shadow:
        inset 0 0 0 1px var(--sf-plate-edge, rgba(0, 0, 0, 0.08)),
        var(--sf-signature-edge),
        var(--sf-elev-2);
}

.sf-pdp-main-image img,
.sf-pdp-main-image .sf-product-noimage {
    width: 100%;
    height: 100%;

    /* Same per-image branch as the card: a cutout gets contain plus
       percentage padding and an optical centre, a photograph gets cover plus
       its focal point. */
    object-fit: var(--sf-media-fit, contain);
    object-position: var(--sf-media-position, var(--sf-focal));
    padding: var(--sf-media-pad, 8%);
    transform: scale(var(--sf-optical, 1));
}

.sf-pdp-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
    margin-top: var(--sf-space-3);
}

.sf-pdp-thumb {
    display: block;
    width: 60px;

    /* The square ratio from the closed set, and this is where it belongs: a
       thumb CROPS (object-fit: cover below), so a square one is a uniform
       swatch row whatever the source frames are. 60px: a swatch, not a
       second gallery. */
    aspect-ratio: var(--sf-ratio-square);
    border-radius: var(--sf-radius-sm);
    overflow: hidden;
    background: var(--sf-surface-sunken);
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
}

.sf-pdp-thumb img { width: 100%; height: 100%; object-fit: cover; }
.sf-pdp-thumb:hover, .sf-pdp-thumb-active { box-shadow: inset 0 0 0 2px var(--sf-brand); }

.sf-pdp-info { min-width: 0; }

/* One step DOWN from the old fs-900 default: the title is one voice in a
   composed rail, not a hero. Balanced wrap so a two-line name breaks as a
   couplet instead of a long line and an orphan. */
.sf-pdp-title {
    font-size: var(--sf-fs-700);
    line-height: var(--sf-lh-700);
    letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    margin-bottom: var(--sf-space-3);
    max-width: 24ch;
    text-wrap: balance;
}

/* The identity line: SKU and UPC in the machine face. user-select: all on
   each VALUE, so a single click selects the whole number for copying - the
   labels stay outside the selection. */
.sf-pdp-identity {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2) var(--sf-space-4);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums slashed-zero;
    color: var(--sf-text-faint);
    margin-bottom: var(--sf-space-3);
}

.sf-identity-value {
    user-select: all;
    color: var(--sf-text-soft);
}

.sf-pdp-price-band { margin-bottom: var(--sf-space-2); }

.sf-pdp-price {
    display: flex;
    align-items: baseline;
    gap: var(--sf-space-3);
}

.sf-pdp-price span {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: var(--sf-lh-800);
    letter-spacing: calc(var(--sf-track-800) + var(--sf-track-offset));
    font-weight: var(--sf-heading-weight);
    font-variant-numeric: tabular-nums lining-nums;
    color: var(--sf-ink);
}

.sf-pdp-price s { color: var(--sf-text-faint); font-size: var(--sf-fs-500); }

/* Renders only when a real compare-at exists: pricing context, not a claim
   invented for effect. */
.sf-pdp-map-note {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-variant-numeric: tabular-nums;
    color: var(--sf-text-faint);
    margin-top: 2px;
}

.sf-pdp-stock {
    font-family: var(--sf-font-heading);
    font-weight: 600;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    margin-bottom: var(--sf-space-4);
}

.sf-stock-in { color: var(--sf-positive); }
.sf-stock-low { color: var(--sf-caution); }
.sf-stock-out { color: var(--sf-text-faint); }
/* Quote-only, untracked quantity: an invitation, not a stock claim. */
.sf-stock-ask { color: var(--sf-text-soft); }

/* ---------- FFL trust furniture (shared: PDP, checkout, confirmation) ---------- */

/* The routing card: caps header with a square tick, one sentence, the mono
   how-it-works link, and the auto-attached compliance preset merged INTO the
   card (scoped de-box below) so FFL context is one object, not a stack of
   three banners. Sunken surface + 3px brand rule: information, not promotion. */
.sf-ffl-card {
    background: var(--sf-surface-sunken);
    box-shadow: inset 3px 0 0 var(--sf-brand);
    border-radius: var(--sf-radius-sm);
    color: var(--sf-text);
    padding: var(--sf-space-4) var(--sf-space-5);
    font-size: var(--sf-fs-300);
    margin: var(--sf-space-5) 0 var(--sf-space-4);
}

.sf-ffl-card-head {
    display: flex;
    align-items: center;
    gap: var(--sf-space-2);
    font-family: var(--sf-font-heading);
    font-weight: 600;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-ink);
    margin-bottom: var(--sf-space-2);
}

/* CSS square tick: a checkbox-shaped datum mark, drawn, not shipped as an
   icon font or an image request. */
.sf-ffl-card-head::before {
    content: "";
    flex-shrink: 0;
    width: 11px;
    height: 11px;
    box-shadow: inset 0 0 0 1.5px var(--sf-brand);
    background:
        linear-gradient(to top right,
            transparent 42%, var(--sf-brand) 43%, var(--sf-brand) 56%, transparent 57%);
}

.sf-ffl-card-body { color: var(--sf-text-soft); margin-bottom: var(--sf-space-2); }

.sf-ffl-card-link {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-space-2);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-brand);
}

.sf-ffl-card-link::after {
    content: "";
    width: 14px;
    height: 1px;
    background: currentColor;
}

/* The merged preset: same words, no second box inside the card. */
.sf-ffl-card .sf-compliance {
    background: transparent;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
    padding: var(--sf-space-3) 0 0;
    margin: var(--sf-space-3) 0 0;
}

.sf-ffl-card .sf-compliance h3 {
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
}

/* The FFL chip: ONE spec for the small "FFL" marker wherever a line item
   needs it (cards, cart lines, review, orders). Mono, quiet inset ring,
   never brand-filled: routing metadata, not a promotion. */
.sf-ffl-chip {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sf-ink) 40%, transparent);
    border-radius: var(--sf-radius-xs);
    padding: 1px 6px;
}

/* The counter-band: how buying here actually works, as furniture. Sunken
   surface, brand left rule, three mono steps and the caller's honest
   sentence. Shared by the PDP buy box, cart and checkout. */
.sf-counter-band {
    background: var(--sf-surface-sunken);
    box-shadow: inset 3px 0 0 var(--sf-rule-accent, var(--sf-brand));
    border-radius: var(--sf-radius-sm);
    padding: var(--sf-space-4) var(--sf-space-5);
    margin: var(--sf-space-3) 0 var(--sf-space-5);
}

.sf-counter-steps {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--sf-space-2);
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sf-space-2) var(--sf-space-3);
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text);
}

.sf-counter-steps li {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sf-space-2);
}

/* The connector between steps: a drawn arrow, not a glyph. */
.sf-counter-steps li:not(:last-child)::after {
    content: "";
    align-self: center;
    width: 14px;
    height: 1px;
    background: var(--sf-text-faint);
    margin-left: var(--sf-space-1);
}

.sf-counter-step-num {
    font-variant-numeric: tabular-nums;
    color: var(--sf-rule-accent, var(--sf-brand));
}

.sf-counter-band-note {
    font-size: var(--sf-fs-300);
    color: var(--sf-text-soft);
    margin: 0;
}

/* ---------- the spec sheet ---------- */

/* Specs are the highest-density information on the site and the place where
   monospace metadata earns the most: a spec table in the text face reads like
   a paragraph, and in mono it reads like a datasheet. Promoted to a
   full-width section between the buy composition and the prose. */
.sf-pdp-specs-section { margin-top: var(--sf-space-8); }

.sf-pdp-specs-eyebrow {
    font-family: var(--sf-font-heading);
    font-weight: 600;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    padding-bottom: var(--sf-space-2);
    margin-bottom: var(--sf-space-3);
    /* The accent rule: brand for a short measure, hairline the rest of the
       way, so the section opens with one deliberate mark. */
    background:
        linear-gradient(to right,
            var(--sf-rule-accent, var(--sf-brand)) 0 48px,
            var(--sf-hairline) 48px);
    background-repeat: no-repeat;
    background-size: 100% 2px;
    background-position: left bottom;
}

.sf-pdp-specs {
    border-radius: var(--sf-radius-sm);
    overflow: hidden;
    margin-bottom: var(--sf-space-5);
}

@media (min-width: 900px) {
    /* Two newspaper columns: the sheet reads down, then over, and a row
       never splits across the fold. */
    /* Only when the data earns the fold: with fewer than six rows a
       two-column sheet scatters (three rows read as confetti - verified
       live). The template sets the class from the row count. */
    .sf-pdp-specs.sf-specs-columns {
        columns: 2;
        column-gap: clamp(32px, 5vw, 80px);
    }

    .sf-spec-row { break-inside: avoid; }
}

.sf-spec-row {
    display: flex;
    justify-content: space-between;
    gap: var(--sf-space-4);
    padding: 10px 0;
    font-size: var(--sf-fs-300);
    box-shadow: inset 0 -1px 0 var(--sf-hairline);
}

.sf-spec-row span:first-child {
    color: var(--sf-text-faint);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
}

.sf-spec-row span:last-child {
    color: var(--sf-ink);
    text-align: right;
    word-break: break-word;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    font-variant-numeric: tabular-nums slashed-zero;
}

.sf-pdp-quote {
    background: var(--sf-surface);
    border-radius: var(--sf-radius);
    padding: var(--sf-card-pad);
    box-shadow: var(--sf-signature-edge), var(--sf-elev-1);
}

.sf-pdp-quote h2 { font-size: var(--sf-fs-600); margin-bottom: 4px; }
.sf-pdp-quote-note { color: var(--sf-text-soft); font-size: var(--sf-fs-300); }
.sf-pdp-quote .sf-form { margin-top: var(--sf-space-4); }

.sf-pdp-description { max-width: var(--sf-measure); margin-top: var(--sf-space-8); }
.sf-pdp-description h2 { margin-bottom: var(--sf-space-3); }
.sf-pdp-description p { margin-bottom: 0.8em; }
.sf-pdp-description ul, .sf-pdp-description ol { padding-left: 1.4em; margin-bottom: 0.8em; }
.sf-pdp-description h3 { margin-block: 1em 0.4em; }

.sf-pdp-related { margin-top: var(--sf-space-9); }
.sf-pdp-related h2 { margin-bottom: var(--sf-space-5); }

/* Phone-width buy bar: the buy box (unchanged DOM position at the top of
   the rail) sticks to the bottom edge once scrolled past, so the action
   stays under the thumb through the spec sheet and the prose. Safe-area
   padded for home-indicator phones. Desktop unsticks it (the rail is
   sticky-adjacent to the gallery there and the box never leaves view). */
@media (max-width: 899px) {
    .sf-pdp-buy {
        position: sticky;
        bottom: 0;
        z-index: 4;
        background: var(--sf-bg);
        box-shadow: inset 0 1px 0 var(--sf-hairline);
        padding: var(--sf-space-3) 0 calc(var(--sf-space-3) + env(safe-area-inset-bottom, 0px));
        margin: var(--sf-space-4) 0;
    }

    .sf-mobile-quote-bar {
        position: sticky;
        bottom: calc(var(--sf-space-3) + env(safe-area-inset-bottom, 0px));
        z-index: 4;
        display: inline-flex;
        width: 100%;
        margin-top: var(--sf-space-4);
    }
}

@media (min-width: 900px) {
    /* 7:5. Below about 1.2:1 two tracks read as an accident and at 1:1 they
       read as a template. */
    .sf-pdp { grid-template-columns: 7fr 5fr; gap: clamp(32px, 5vw, 80px); }

    /* Sticky gallery (motion 1.3): the object holds still while the shopper
       reads down the rail. align-self: start is load-bearing - a stretched
       grid item can never stick. */
    .sf-pdp-gallery {
        position: sticky;
        top: calc(var(--sf-header-height) + var(--sf-space-4));
        align-self: start;
    }

    .sf-mobile-quote-bar { display: none; }
}

/* CSS-only inspect (desktop pointer users): the checkbox overlay magnifies
   the photograph in place at 1.8x. No JS, no overlay chrome; unchecking (or
   tabbing away and toggling with space) returns it. Gated to hover-capable
   desktop: on touch the same gesture is pinch-zoom, which the browser
   already owns. */
.sf-pdp-zoom-label { display: none; }

@media (hover: hover) and (min-width: 900px) {
    .sf-pdp-zoom-label {
        display: block;
        position: absolute;
        inset: 0;
        z-index: 2;
        cursor: zoom-in;
    }

    .sf-pdp-main-image:has(.sf-pdp-zoom-input:checked) .sf-pdp-zoom-label {
        cursor: zoom-out;
    }

    .sf-pdp-main-image:has(.sf-pdp-zoom-input:checked) img {
        transform: scale(calc(var(--sf-optical, 1) * 1.8));
    }

    .sf-pdp-main-image img {
        transition: transform var(--sf-dur-4) var(--sf-ease-out);
    }

    .sf-pdp-zoom-input:focus-visible + .sf-pdp-zoom-label {
        outline: 2px solid var(--sf-focus);
        outline-offset: -2px;
    }
}

/* CARD -> PDP MORPH (motion 3.1). Cross-document view transitions are
   already live on desktop (01-foundation): naming the hovered card's media
   WELL and the PDP hero the same lets the browser morph the one the shopper
   actually clicked into the detail hero. Hover-scoped on purpose: a
   view-transition-name must be unique per document, and only one card is
   ever hovered; per-product generated names would leak an unbounded name
   set into the cascade (a design-system test bans that shape). */
@media (min-width: 900px) {
    .sf-product-card:is(:hover, :focus-within) .sf-product-media {
        view-transition-name: sf-product-media;
    }

    .sf-pdp-main-image {
        view-transition-name: sf-product-media;
    }

    /* On the PDP itself the hero already owns the name, and a
       view-transition-name must be unique per document: hovering a RELATED
       card would otherwise mint a duplicate and skip the transition. The
       related rail therefore morphs on arrival, not departure. */
    .sf-pdp-related .sf-product-card:is(:hover, :focus-within) .sf-product-media {
        view-transition-name: none;
    }
}

::view-transition-group(sf-product-media) {
    animation-duration: var(--sf-dur-4);
    animation-timing-function: var(--sf-ease-spring, cubic-bezier(0.23, 1, 0.32, 1));
}

/* ================================================================
   utility pages
   ================================================================ */

.sf-utility-page {
    max-width: var(--sf-measure);
    margin: 15vh auto 0;
    padding: 0 var(--sf-gutter);
    text-align: center;
}

.sf-utility-page h1 { margin-bottom: var(--sf-space-3); }

.sf-notfound { text-align: center; padding-block: var(--sf-space-7); }

.sf-notfound-code {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-1000);
    font-weight: var(--sf-display-weight);
    letter-spacing: var(--sf-track-1000);
    color: var(--sf-ink);
    opacity: 0.08;
    line-height: 1;
}

.sf-notfound h1 { margin-block: calc(var(--sf-space-5) * -1) var(--sf-space-3); }
.sf-notfound .sf-btn { margin-top: var(--sf-space-5); }

@media (min-width: 700px) {
    .sf-field-row { flex-direction: row; }
    /* Inputs carry an intrinsic min-width; without flex-basis 0 + min-width 0
       the second field overflows its panel (verified live: the PDP quote
       form's Phone input escaped the card edge). */
    .sf-field-row > * { flex: 1 1 0; min-width: 0; }
    .sf-field-choice { flex-direction: row; gap: var(--sf-space-5); align-items: center; }
    .sf-field-choice legend { margin-bottom: 0; }
}

/* ================================================================
   facet rail, brand pages, quote page
   ================================================================ */

/* `outline: none` above kills the keyboard ring, not just the mouse one, so
   every input that clears it restores a :focus-visible ring here. Rauno's
   rule: the replacement is a box-shadow rather than an outline so it follows
   border-radius, but outline survives overflow: hidden clipping, which is why
   the pair is used together. */
.sf-field input:focus-visible,
.sf-field textarea:focus-visible,
.sf-field select:focus-visible,
.sf-shop-search:focus-visible,
.sf-shop-sort:focus-visible {
    outline: 2px solid var(--sf-focus);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--sf-focus-contrast);
}

.sf-shop-layout {
    display: grid;
    gap: var(--sf-space-5);
    align-items: start;
}

/* align-self: stretch is what makes the desktop rail's position: sticky
   actually engage - the layout's align-items: start otherwise collapses this
   form to its content height and the rail has zero room to travel (verified
   live: the rail scrolled straight off the page). */
.sf-facet-form { min-width: 0; align-self: stretch; }
.sf-shop-results { min-width: 0; }

/* Mobile and tablet: the rail is a drawer. A closed <details> never paints
   its children at any breakpoint, and duplicating the rail would duplicate
   every form field, so the disclosure is a nameless checkbox (never
   submitted, keyboard operable) plus a sibling selector. */
.sf-facet-rail { display: none; }
.sf-facet-toggle-input:checked ~ .sf-facet-rail { display: block; }

/* The one place in this codebase where a display transition is genuinely the
   right tool: the rail goes display:none to display:block, and
   transition-behavior: allow-discrete plus @starting-style is what lets that
   animate at all. Both are progressive enhancement; without them the rail
   simply appears, which is what it did before. */
@media (prefers-reduced-motion: no-preference) {
    @supports (transition-behavior: allow-discrete) {
        .sf-facet-rail {
            opacity: 0;
            translate: 0 -8px;
            transition:
                opacity var(--sf-dur-2) var(--sf-ease-out),
                translate var(--sf-dur-2) var(--sf-ease-out),
                display var(--sf-dur-2) allow-discrete;
        }

        .sf-facet-toggle-input:checked ~ .sf-facet-rail {
            opacity: 1;
            translate: none;
        }

        @starting-style {
            .sf-facet-toggle-input:checked ~ .sf-facet-rail {
                opacity: 0;
                translate: 0 -8px;
            }
        }
    }
}

.sf-facet-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-space-2);
    min-height: 44px;
    padding: 10px 18px;
    margin-bottom: var(--sf-space-4);
    border-radius: var(--sf-btn-radius);
    background: var(--sf-surface);
    color: var(--sf-ink);
    font-family: var(--sf-font-heading);
    font-weight: 600;
    cursor: pointer;
    box-shadow: inset 0 0 0 1px var(--sf-control-border);
}

.sf-facet-toggle-input:focus-visible + .sf-facet-toggle {
    outline: 2px solid var(--sf-focus);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--sf-focus-contrast);
}

.sf-facet-toggle-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: var(--sf-radius-pill);
    background: var(--sf-brand);
    color: var(--sf-on-brand);
    font-size: var(--sf-fs-100);
    font-variant-numeric: tabular-nums;
}

.sf-facet-rail {
    background: var(--sf-surface);
    border-radius: var(--sf-radius);
    padding: var(--sf-card-pad);
    /* The drawer panel needs an actual edge: on themes whose surface sits a
       hair off the page ground (armstopia cream-on-cream, verified live)
       the unbounded panel read as a giant faint slab - "blocky" - rather
       than a designed sheet. A hairline ring plus soft elevation separates
       it from the page on every theme. */
    box-shadow: inset 0 0 0 1px var(--sf-hairline), var(--sf-elev-3);
    margin-bottom: var(--sf-space-5);
}

/* Split-window and tablet widths: an open drawer spanning the full ~760px
   container stretched every row bar-wide, counts floating 600px from their
   labels (verified live at 800px - a snapped desktop window lands here
   too). Capped, it reads as a filter sheet instead of a slab. */
@media (min-width: 640px) and (max-width: 899px) {
    .sf-facet-rail { max-width: 430px; }
}

.sf-facet-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sf-space-3);
    margin-bottom: var(--sf-space-1);
}

.sf-facet-title { font-size: var(--sf-fs-500); }
.sf-facet-clear { font-size: var(--sf-fs-200); font-weight: 600; }

.sf-facet-group {
    border: 0;
    padding: 0;
    margin: var(--sf-space-5) 0 0;
}

/* De-boxed group labels: mono caps over a trailing hairline, so the rail
   reads as an index column rather than a stack of cards (law 4: hairlines
   and whitespace separate; boxes are the last resort). */
.sf-facet-label {
    display: block;
    width: 100%;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    margin-bottom: var(--sf-space-2);
    padding: 0 0 var(--sf-space-2);
    box-shadow: inset 0 -1px 0 var(--sf-hairline);
}

.sf-facet-hint { font-size: var(--sf-fs-200); color: var(--sf-text-soft); margin-top: 6px; }

.sf-facet-options { display: flex; flex-direction: column; gap: 2px; }

.sf-facet-option {
    display: flex;
    align-items: center;
    gap: var(--sf-space-3);
    min-height: 44px;
    padding: 4px 6px;
    border-radius: var(--sf-radius-sm);
    color: var(--sf-text);
    cursor: pointer;
}

.sf-facet-option:hover { background: var(--sf-surface-alt); color: var(--sf-ink); }

.sf-facet-option input[type="checkbox"],
.sf-facet-option input[type="radio"] {
    accent-color: var(--sf-brand);
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

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

.sf-facet-count {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    color: var(--sf-text-faint);
    font-variant-numeric: tabular-nums;
    min-width: 3ch;
    text-align: right;
}

.sf-facet-tree, .sf-facet-subtree { list-style: none; padding: 0; margin: 0; }

.sf-facet-subtree {
    padding-left: var(--sf-space-3);
    box-shadow: inset 1px 0 0 var(--sf-hairline);
    margin: 2px 0 2px var(--sf-space-2);
}

.sf-facet-tree-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sf-space-3);
    min-height: 40px;
    padding: 4px 6px;
    border-radius: var(--sf-radius-sm);
    color: var(--sf-text);
    text-decoration: none;
}

.sf-facet-tree-link:hover { background: var(--sf-surface-alt); color: var(--sf-ink); text-decoration: none; }

/* The active category: a 2px brand inset on the left edge, not a filled
   pill - selection is a position in the index, not a button state. */
.sf-facet-active {
    box-shadow: inset 2px 0 0 var(--sf-brand);
    border-radius: 0;
    color: var(--sf-ink);
    font-weight: 700;
}

/* Underline-style search inside the de-boxed rail: the field boundary is
   the baseline rule, using the same 3:1 control-border token the boxed
   fields use (WCAG 1.4.11 still applies to an underline). */
/* A real field, not a bare underline (the underline read as unfinished
   chrome, verified live). Surface, machined edge, and a magnifier glyph in
   mid-grey - one tone that reads on light and dark rail grounds alike. */
.sf-facet-rail .sf-shop-search {
    background: var(--sf-surface);
    border: 0;
    border-radius: var(--sf-radius-sm);
    box-shadow: inset 0 0 0 1px var(--sf-control-border);
    padding: 11px 12px 11px 38px;
    width: 100%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23858b91' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20.5 20.5-4.8-4.8'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: 12px center;
    background-size: 16px 16px;
}

.sf-facet-rail .sf-shop-search:focus {
    box-shadow: inset 0 0 0 2px var(--sf-brand);
}

.sf-facet-actions { margin-top: var(--sf-space-5); }
.sf-facet-actions .sf-btn { width: 100%; text-align: center; }

/* The brand fold's disclosure: a quiet row with a drawn chevron, styled to
   sit in the option column. Native details, so it costs no JS and the open
   state survives nothing it shouldn't. */
.sf-facet-more > summary {
    list-style: none;
    display: flex;
    align-items: center;
    gap: var(--sf-space-2);
    min-height: 40px;
    padding: 4px 6px;
    border-radius: var(--sf-radius-sm);
    color: var(--sf-text-soft);
    font-size: var(--sf-fs-200);
    font-weight: 600;
    cursor: pointer;
    user-select: none;
}

.sf-facet-more > summary::-webkit-details-marker { display: none; }

.sf-facet-more > summary::before {
    content: "";
    width: 7px;
    height: 7px;
    margin-inline: 4px 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    rotate: 45deg;
    translate: 0 -2px;
    transition: rotate var(--sf-dur-2) var(--sf-ease-out), translate var(--sf-dur-2) var(--sf-ease-out);
}

.sf-facet-more[open] > summary::before {
    rotate: 225deg;
    translate: 0 1px;
}

.sf-facet-more > summary:hover { background: var(--sf-surface-alt); color: var(--sf-ink); }
.sf-facet-more > .sf-facet-options { margin-top: 2px; }

/* In the phone drawer, Apply rides the drawer bottom so a long rail never
   hides the commit action. */
@media (max-width: 899px) {
    .sf-facet-actions {
        position: sticky;
        bottom: 0;
        background: var(--sf-surface);
        padding-block: var(--sf-space-3);
        margin-top: var(--sf-space-4);
    }
}

/* ACTIVE, over the pills: the row now announces what it is. */
.sf-active-filters-label {
    flex-basis: 100%;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
}

/* ---------- active filter pills ---------- */

.sf-active-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sf-space-2);
    margin-bottom: var(--sf-space-4);
}

.sf-filter-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-space-2);
    min-height: 36px;
    padding: 6px 12px;
    border-radius: var(--sf-radius-pill);
    background: var(--sf-surface);
    box-shadow: inset 0 0 0 1px var(--sf-brand);
    color: var(--sf-ink);
    font-size: var(--sf-fs-200);
    font-weight: 600;
    text-decoration: none;
}

.sf-filter-pill:hover { background: var(--sf-surface-alt); text-decoration: none; }
.sf-filter-clear { font-size: var(--sf-fs-200); font-weight: 600; }

/* Press feedback on chips and pills (motion 2.4): a fast 120ms press-in,
   a spring release. Deliberately NO @starting-style arrival animation:
   filter pills appear as the result of a full navigation, and choreography
   on arrival would read as the page still loading. */
.sf-filter-pill,
.sf-chip {
    transition: transform var(--sf-dur-3) var(--sf-ease-spring, cubic-bezier(0.23, 1, 0.32, 1));
}

.sf-filter-pill:active,
.sf-chip:active {
    transform: scale(0.96);
    transition-duration: 120ms;
}

/* ---------- brand pages ---------- */

.sf-brand-grid {
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--sf-card-gap);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sf-brand-tile {
    background: var(--sf-surface);
    border-radius: var(--sf-radius-sm);
    box-shadow: var(--sf-signature-edge), var(--sf-elev-1);
    transition: transform var(--sf-dur-2) var(--sf-ease-out);
}

.sf-brand-tile:hover {
    box-shadow:
        inset 0 0 0 1px color-mix(in oklab, var(--sf-brand) 55%, transparent),
        var(--sf-elev-2);
}

.sf-brand-tile a {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--sf-space-4);
    min-height: 44px;
    text-decoration: none;
}

.sf-brand-name {
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-heading-weight);
    color: var(--sf-ink);
}

.sf-brand-count {
    font-size: var(--sf-fs-100);
    font-family: var(--sf-font-heading);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
}

/* ---------- PDP buy box ---------- */

.sf-pay-note {
    background: var(--sf-surface-sunken);
    border-radius: var(--sf-radius-sm);
    padding: var(--sf-space-3) var(--sf-space-4);
    font-size: var(--sf-fs-300);
    margin: var(--sf-space-3) 0 var(--sf-space-5);
}

.sf-pdp-buy { margin: var(--sf-space-4) 0; }
/* flex-direction is explicit: .sf-form's column otherwise applies, and in a
   column align-items: flex-end reads as "push the qty field to the far right
   of the buy box", which is exactly how it rendered live. */
.sf-buy-form { display: flex; flex-direction: row; gap: var(--sf-space-3); align-items: flex-end; flex-wrap: wrap; }
.sf-qty-field { max-width: 90px; }

.sf-pdp-stock-asof {
    font-size: var(--sf-fs-200);
    color: var(--sf-text-faint);
    margin-top: calc(var(--sf-space-3) * -1);
    margin-bottom: var(--sf-space-4);
}

/* Demoted quote box: still present, no longer a second primary CTA. */
.sf-pdp-quote-secondary {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
    padding: 6px var(--sf-space-4);
}

.sf-quote-disclosure summary {
    cursor: pointer;
    font-family: var(--sf-font-heading);
    font-weight: 600;
    color: var(--sf-ink);
    padding: 12px 4px;
    min-height: 44px;
}

.sf-quote-disclosure summary:hover { color: var(--sf-brand); }
.sf-quote-disclosure[open] summary { margin-bottom: 4px; }

/* ---------- standalone quote page ---------- */

.sf-quote-intro { max-width: var(--sf-measure); margin-bottom: var(--sf-space-4); }
.sf-quote-form { max-width: var(--sf-measure); }

.sf-quote-product {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sf-space-3);
    background: var(--sf-surface);
    box-shadow: inset 3px 0 0 var(--sf-brand);
    border-radius: var(--sf-radius-sm);
    padding: var(--sf-space-4);
    margin-bottom: var(--sf-space-4);
}

.sf-quote-product-label {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-100);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
}

.sf-quote-product-brand { font-size: var(--sf-fs-200); color: var(--sf-text-soft); }

.sf-compliance-auto { margin-bottom: var(--sf-space-4); }

@media (min-width: 700px) and (max-width: 899px) {
    .sf-brand-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* 1280 (desktop): the rail becomes a real sidebar and sticks. */
@media (min-width: 900px) {
    .sf-shop-layout { grid-template-columns: 264px minmax(0, 1fr); gap: var(--sf-space-7); }
    .sf-facet-toggle, .sf-facet-toggle-input { display: none; }

    .sf-facet-rail {
        display: block;
        opacity: 1;
        translate: none;
        position: sticky;
        top: calc(var(--sf-header-height) + var(--sf-space-4));
        max-height: calc(100dvh - var(--sf-header-height) - var(--sf-space-7));
        overflow-y: auto;

        /* The OS scrollbar painted a grey pill down the whole rail with the
           hairlines and the search field flush against it - on a 1024x820
           laptop viewport it read as the rail being cut off (verified
           live). Three-part fix: the gutter is RESERVED so content always
           stops short of the scroll zone (that is the missing right
           margin), the scrollbar is invisible until the pointer is over
           the rail, and the brand fold keeps the rail short enough that
           most viewports never scroll it at all. */
        scrollbar-width: thin;
        scrollbar-gutter: stable;
        scrollbar-color: transparent transparent;

        /* De-boxed: on desktop the rail is an index column separated by
           whitespace and its own hairlines, not a card floating next to the
           grid. The phone drawer above keeps its surface (it overlays). */
        background: transparent;
        padding: 0;
        border-radius: 0;
        /* The drawer sheet's ring and elevation stay below 900px; the
           de-boxed index column carries neither. */
        box-shadow: none;
        margin-bottom: 0;
        max-width: none;
    }

    .sf-facet-rail:hover,
    .sf-facet-rail:focus-within {
        scrollbar-color: color-mix(in srgb, var(--sf-ink) 24%, transparent) transparent;
    }

    /* Pointer-first density: 44px touch rows read as double-spaced with a
       mouse. 36px keeps every target comfortably past the 24px AA floor.
       Groups tighten one step for the same reason. */
    .sf-facet-option, .sf-facet-tree-link { min-height: 36px; }
    .sf-facet-group { margin-top: var(--sf-space-4); }

    /* Apply rides the rail's bottom edge on desktop too, fading the content
       behind it, so committing never requires scrolling the rail to its end. */
    .sf-facet-actions {
        position: sticky;
        bottom: 0;
        margin-top: var(--sf-space-4);
        padding-block: var(--sf-space-3) var(--sf-space-2);
        background: linear-gradient(to top, var(--sf-bg) 62%, transparent);
    }

    .sf-brand-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
}
