/* 03-blocks.css: the 20-block library. Every block is styled entirely off
   --sf-* tokens so it looks right in all four themes. Mobile-first.

   The brief this pass answers: "a stack of identical full-width boxes; every
   section is a heading top-left then a row of equal cards". So the rules
   below deliberately break the grid. Feature entries are numbered editorial
   items with an offset first entry, not equal cards. The gallery is a mosaic,
   not four squares. Split blocks bleed their image past the container edge.
   Testimonials stagger. Nothing here is a 1px border on a flat panel any
   more: surfaces separate by lightness, a lit top edge, and a shadow tinted
   with the theme's own shadow hue. */

/* ================================================================
   hero

   The old hero was a flat gradient rectangle with left-aligned text and an
   empty right half. This one is an editorial masthead: an oversized display
   headline on a tight leading that spans most of the measure, a hairline
   under it, and a two-column footer where the copy and CTAs sit left and a
   real NAP rail (locality, phone) sits right. The right half now carries
   information a local-SEO page actually wants, rather than being padding.

   When there is no photograph - the common case for our tenants - a CSS-only
   plate fills the frame: a machined radial ground, a technical grid, and a
   long brass datum rule. No image request, no placeholder apologetics.
   ================================================================ */

.sf-hero {
    position: relative;
    display: flex;
    align-items: center;
    overflow: hidden;
    color: var(--sf-hero-ink);
    isolation: isolate;
}

.sf-hero-compact { min-height: clamp(300px, 42vh, 380px); }
.sf-hero-standard { min-height: clamp(420px, 62vh, 600px); }
.sf-hero-tall { min-height: clamp(520px, 82vh, 780px); }

.sf-hero-gradient {
    background:
        radial-gradient(120% 130% at 78% -20%, var(--sf-hero-grad-b), transparent 62%),
        linear-gradient(158deg, var(--sf-hero-grad-a), var(--sf-hero-grad-b));
}

/* The CSS-only plate that fills a hero with no photograph. Three layers, all
   painted, none of them a request. */
.sf-hero-plate {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}

.sf-hero-plate::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.055) 1px, transparent 1px);
    /* Round 5: six of ten themes still have dark heroes where hero-ink IS
       white, but the light-hero themes (iron-press, blackout, arctic,
       desert-mesa) need the plate grid in their own ink. The white pair
       above stays as the no-color-mix fallback throughout this hero. */
    background-image:
        linear-gradient(color-mix(in srgb, var(--sf-hero-ink) 5.5%, transparent) 1px, transparent 1px),
        linear-gradient(90deg, color-mix(in srgb, var(--sf-hero-ink) 5.5%, transparent) 1px, transparent 1px);
    background-size: 64px 64px;
    mask-image: radial-gradient(110% 90% at 72% 18%, #000, transparent 70%);
}

/* The datum rule: one long brass hairline running off the right edge. This is
   the hero's single accent, and the reason the brand colour does not have to
   be spent on the announcement bar or every button. */
.sf-hero-plate::after {
    content: "";
    position: absolute;
    right: 0;
    top: 26%;
    width: 46%;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--sf-brand));
    opacity: 0.75;
}

.sf-hero-bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: -2;
}

.sf-hero-overlay {
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        linear-gradient(100deg, var(--sf-hero-overlay) 18%, transparent 78%),
        var(--sf-hero-overlay);
}

/* Duotone normalisation. Our tenants upload phone photos taken under mixed
   fluorescent light, at four different white balances, on the same page. A
   theme-tinted `color` blend pulls all of them onto one axis so a page of
   mismatched photography reads as a deliberate set. Kept mild (0.42) so a
   genuinely good photograph still looks like a photograph, and applied ONLY
   to decorative imagery: product photos are never touched, because a shopper
   has to see the real colour of the thing they are buying. */
.sf-hero-duotone {
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(150deg, var(--sf-hero-grad-a), var(--sf-hero-grad-b));
    mix-blend-mode: color;
    opacity: 0.42;
    pointer-events: none;
}

.sf-hero-content {
    position: relative;
    width: 100%;
    max-width: var(--sf-container);
    margin-inline: auto;
    padding: clamp(48px, 7vw, 96px) var(--sf-gutter);
}

.sf-hero-kicker {
    font-family: var(--sf-font-heading);
    /* Pure brand ink dies on brand-hued hero grounds (heritage green-on-
       green, classic-brick red-on-red, verified live). The hero ink is by
       definition the contrast-correct ink for the hero ground, so the kicker
       leans on it and keeps a 35% brand tint as the whisper of brand voice.
       Photo layouts still override to full on-photo ink + shadow below. */
    color: color-mix(in srgb, var(--sf-brand) 35%, var(--sf-hero-ink));
    font-weight: 600;
    text-transform: var(--sf-eyebrow-transform);
    letter-spacing: var(--sf-track-caps);
    font-size: var(--sf-fs-200);
    margin-bottom: var(--sf-space-4);
}

/* On a photograph the brand-coloured kicker disappears into whatever the
   photo happens to be (sage on grass, verified live). Photo layouts set the
   kicker in on-photo ink with a soft shadow; the brand voice returns on the
   headline rule and CTAs, where it sits on controlled ground. */
.sf-hero-photo-editorial .sf-hero-kicker,
.sf-hero-cinematic .sf-hero-kicker {
    color: var(--sf-hero-ink, #FFFFFF);
    text-shadow: 0 1px 10px rgb(0 0 0 / 0.5);
}


/* Display size, display leading, display tracking. This one declaration is
   the largest single share of the perceived-quality change on the page. */
.sf-hero-heading {
    color: var(--sf-hero-ink);

    /* The display STEP tops out at 132px, which is right for a short string
       like a 404 code or a specimen mark. A hero headline is dealer-authored
       prose of unknown length, and a 58-character sentence at 132px is four
       lines and 900px of masthead. This caps the masthead at ~96px before the
       theme's display scale, which is where a sentence-length headline reads
       as editorial rather than as a billboard, and still leaves a 6:1 size
       contrast against body copy. */
    font-size: calc(clamp(2.5rem, 0.75rem + 7.8vw, 6rem) * var(--sf-display-scale, 1));
    line-height: var(--sf-lh-1000);
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    max-width: 17ch;
    text-wrap: balance;
}

.sf-hero-rule {
    margin-top: clamp(28px, 4vw, 48px);
    height: 1px;
    border: 0;
    background: linear-gradient(90deg, rgba(255, 255, 255, 0.34), transparent 72%);
    background: linear-gradient(90deg, color-mix(in srgb, var(--sf-hero-ink) 34%, transparent), transparent 72%);
}

.sf-hero-foot {
    display: grid;
    gap: var(--sf-space-6);
    margin-top: var(--sf-space-5);
    align-items: start;
}

.sf-hero-sub {
    max-width: 46ch;
}

.sf-hero-sub p {
    color: rgba(255, 255, 255, 0.84);
    color: color-mix(in srgb, var(--sf-hero-ink) 84%, transparent);
    font-size: var(--sf-fs-500);
    line-height: var(--sf-lh-600);
}

.sf-hero-ctas {
    margin-top: var(--sf-space-6);
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-3);
}

/* The NAP rail that fills the right half. Every line comes from Store
   settings; nothing here is prefilled or guessed, and a store that has not
   filled in an address simply renders no rail. */
.sf-hero-meta {
    display: flex;
    flex-direction: column;
    gap: var(--sf-space-4);
    font-size: var(--sf-fs-300);
}

.sf-hero-meta-item {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding-top: var(--sf-space-3);
    border-top: 1px solid rgba(255, 255, 255, 0.16);
    border-top: 1px solid color-mix(in srgb, var(--sf-hero-ink) 16%, transparent);
}

.sf-hero-meta-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: rgba(255, 255, 255, 0.62);
    color: color-mix(in srgb, var(--sf-hero-ink) 62%, transparent);
}

.sf-hero-meta-value {
    color: var(--sf-hero-ink);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

a.sf-hero-meta-value:hover { color: var(--sf-brand-strong); }

.sf-hero .sf-btn-secondary {
    border-color: rgba(255, 255, 255, 0.5);
    border-color: color-mix(in srgb, var(--sf-hero-ink) 50%, transparent);
    color: var(--sf-hero-ink, #FFFFFF);
}

.sf-hero .sf-btn-secondary:hover {
    background: rgba(255, 255, 255, 0.12);
    background: color-mix(in srgb, var(--sf-hero-ink) 12%, transparent);
    border-color: var(--sf-hero-ink, #FFFFFF);
    color: var(--sf-hero-ink, #FFFFFF);
}

@media (min-width: 860px) {
    .sf-hero-heading { max-width: 17ch; }

    .sf-hero-foot {
        grid-template-columns: minmax(0, 1.45fr) minmax(220px, 0.75fr);
        gap: clamp(32px, 6vw, 96px);
    }

    .sf-hero-meta { padding-top: var(--sf-space-2); }
}

/* ================================================================
   trust bar

   Was: a row of equal boxed items on a bordered band. Now: an inline rail
   seamed to the page with hairlines, items divided by rules rather than
   boxed, icons in brass at a size that reads as punctuation.
   ================================================================ */

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

.sf-trustbar-band {
    background: var(--sf-surface-sunken);
    padding-block: var(--sf-space-2);
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-trustbar-inline { padding-block: var(--sf-space-2); }

.sf-trust-item {
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: var(--sf-font-heading);
    font-weight: 600;
    color: var(--sf-ink);
    font-size: var(--sf-fs-300);
    letter-spacing: 0.015em;
    padding: var(--sf-space-4) var(--sf-space-4);
    min-width: 0;
}

.sf-trust-item .sf-icon { color: var(--sf-brand); width: 19px; height: 19px; }

/* Hairline dividers between items instead of a border around each. */
.sf-trustbar > .sf-trust-item + .sf-trust-item {
    box-shadow: inset 1px 0 0 var(--sf-hairline);
}

@media (min-width: 860px) {
    .sf-trustbar { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
    .sf-trustbar-band { border-radius: 0; }
}

/* ================================================================
   feature grid

   Was: N identical bordered cards in a row. Now: numbered editorial entries
   separated by a hairline, with the index set in the display face at a size
   that carries the rhythm, and the FIRST entry given more room on wide
   screens so the row has a reading order instead of being a set of equals.
   ================================================================ */

.sf-feature-grid {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr;
    counter-reset: sf-feature;
}

.sf-feature {
    position: relative;
    counter-increment: sf-feature;
    background: transparent;
    padding: var(--sf-space-7) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-feature::before {
    content: counter(sf-feature, decimal-leading-zero);
    display: block;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-300);
    font-weight: 600;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-brand);
    margin-bottom: var(--sf-space-4);
    font-variant-numeric: tabular-nums;
}

.sf-feature h3 { margin-bottom: var(--sf-space-3); max-width: 20ch; }
.sf-feature p { max-width: 42ch; color: var(--sf-text-soft); }

/* The icon is now a quiet mark, not a filled tile competing with the number. */
.sf-feature-icon {
    width: 26px;
    height: 26px;
    color: var(--sf-brand);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--sf-space-4);
    opacity: 0.85;
}

@media (min-width: 700px) {
    .sf-feature-grid {
        grid-template-columns: repeat(var(--sf-grid-cols, 3), minmax(0, 1fr));
        column-gap: clamp(24px, 4vw, 64px);
    }

    .sf-feature { padding-right: var(--sf-space-4); }
}

@media (min-width: 1100px) {
    /* The offset: the first entry drops half a step, which is enough to stop
       the row reading as a set of identical tiles without breaking the grid. */
    .sf-feature-grid > .sf-feature:first-child { transform: translateY(calc(var(--sf-space-5) * -1)); }
    .sf-feature-grid > .sf-feature:nth-child(3n) { transform: translateY(var(--sf-space-3)); }
}

/* ================================================================
   image-text split

   The image now bleeds past the container edge on wide screens, and the copy
   column is offset rather than vertically centred against it. Two changes,
   and the block stops being "picture next to paragraph".
   ================================================================ */

.sf-split {
    display: grid;
    gap: var(--sf-space-6);
    align-items: center;
}

.sf-split-media {
    position: relative;
    border-radius: var(--sf-radius);
    overflow: hidden;
    box-shadow: var(--sf-edge), var(--sf-elev-3);
}

.sf-split-media img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    display: block;
}

.sf-split-copy h2 { margin-bottom: var(--sf-space-4); max-width: 18ch; }
.sf-split-copy .sf-richtext { margin-bottom: var(--sf-space-4); max-width: 46ch; }

@media (min-width: 860px) {
    .sf-split {
        grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
        gap: clamp(40px, 6vw, 96px);
        align-items: start;
    }

    .sf-split-copy { padding-top: var(--sf-space-7); }

    /* Aggressive crop, deliberately taller than wide: a tight crop of a bad
       photograph looks considered where the whole frame looks like a snapshot. */
    .sf-split-media img { aspect-ratio: 5 / 6; }

    /* Bleed toward the viewport edge. calc keeps it inside the page even at
       narrow desktop widths, so nothing ever causes a horizontal scrollbar. */
    .sf-split-media {
        margin-left: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        border-start-start-radius: 0;
        border-end-start-radius: 0;
    }

    .sf-split-right .sf-split-media {
        order: 2;
        margin-left: 0;
        margin-right: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        border-radius: var(--sf-radius);
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }
}

/* ================================================================
   cta band

   The brand-coloured variant used to be a full-bleed slab of yellow, which
   reads as a warning label rather than a premium accent. It is now a raised
   panel on the theme's own dark ground with ONE brass rule and a display-size
   heading; the brand colour is spent on the rule and the button, not on 200
   square inches of background.
   ================================================================ */

.sf-ctaband {
    position: relative;
    border-radius: var(--sf-radius-lg);
    overflow: hidden;
    padding: clamp(32px, 5vw, 64px) clamp(24px, 4vw, 56px);
    background: var(--sf-surface);
    box-shadow: var(--sf-edge), var(--sf-elev-3);
}

.sf-ctaband::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: linear-gradient(var(--sf-brand), transparent);
}

.sf-ctaband h2 { font-size: var(--sf-fs-800); max-width: 20ch; }

.sf-ctaband-brand {
    background:
        radial-gradient(120% 140% at 8% 0%, color-mix(in srgb, var(--sf-brand) 20%, var(--sf-surface)), var(--sf-surface) 70%);
}

.sf-ctaband-brand h2 { color: var(--sf-ink); }
.sf-ctaband-brand p { color: var(--sf-text); }

.sf-ctaband-dark { background: var(--sf-dark-bg); }
.sf-ctaband-dark h2 { color: var(--sf-dark-ink); }
.sf-ctaband-dark p { color: var(--sf-dark-text); }

.sf-ctaband-accent {
    background:
        radial-gradient(120% 140% at 8% 0%, color-mix(in srgb, var(--sf-accent) 26%, var(--sf-surface)), var(--sf-surface) 70%);
}

.sf-ctaband-accent h2 { color: var(--sf-ink); }
.sf-ctaband-accent p { color: var(--sf-text); }
.sf-ctaband-accent::before { background: linear-gradient(var(--sf-accent), transparent); }

.sf-ctaband-bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.16;
}

.sf-ctaband-content {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sf-space-5);
}

.sf-ctaband-content h2 { margin-bottom: var(--sf-space-2); }

.sf-ctaband-dark .sf-btn-oncolor,
.sf-ctaband-brand .sf-btn-oncolor,
.sf-ctaband-accent .sf-btn-oncolor {
    background: var(--sf-brand);
    color: var(--sf-on-brand);
}

@media (min-width: 860px) {
    .sf-ctaband-content {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--sf-space-9);
    }

    .sf-ctaband-content .sf-btn { flex-shrink: 0; }
}

/* ================================================================
   faq
   ================================================================ */

.sf-faq { max-width: var(--sf-measure-wide); }

.sf-faq-item {
    background: transparent;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
    margin-bottom: 0;
}

.sf-faq-item:last-child { box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline); }

.sf-faq-item > summary {
    cursor: pointer;
    padding: var(--sf-space-5) var(--sf-space-8) var(--sf-space-5) 0;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-600);
    font-weight: var(--sf-heading-weight);
    letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    color: var(--sf-ink);
    list-style: none;
    position: relative;
    transition: color var(--sf-dur-2) var(--sf-ease-out);
}

.sf-faq-item > summary:hover { color: var(--sf-brand); }
.sf-faq-item > summary::-webkit-details-marker { display: none; }

/* A rotating cross rather than a swapped glyph: transform only, and it does
   not reflow the summary line the way changing "+" to a minus sign does. */
.sf-faq-item > summary::after,
.sf-faq-item > summary::before {
    content: "";
    position: absolute;
    right: 6px;
    top: 50%;
    width: 14px;
    height: 2px;
    background: var(--sf-brand);
    transition: transform var(--sf-dur-3) var(--sf-ease-out);
}

.sf-faq-item > summary::before { transform: translateY(-50%); }
.sf-faq-item > summary::after { transform: translateY(-50%) rotate(90deg); }
.sf-faq-item[open] > summary::after { transform: translateY(-50%) rotate(0deg); }

.sf-faq-answer {
    padding: 0 var(--sf-space-8) var(--sf-space-5) 0;
    max-width: var(--sf-measure);
    color: var(--sf-text-soft);
}

/* ================================================================
   gallery

   A mosaic, not four equal squares. The spans are assigned by position so a
   dealer who uploads five snapshots gets a composition rather than a
   contact sheet.
   ================================================================ */

.sf-gallery-grid {
    display: grid;
    gap: var(--sf-space-2);
    grid-template-columns: repeat(2, 1fr);
    /* NOT grid-auto-flow: dense. Dense packing rearranges VISUAL order while
       preserving DOM order, so visual order diverges from tab order the
       moment a cell contains a link (WCAG 1.3.2 and 2.4.3). The mosaic below
       gets its composition from deterministic nth-child spans instead, which
       leaves both orders identical. */
}

.sf-gallery-carousel {
    display: flex;
    gap: var(--sf-space-3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: var(--sf-space-2);
    scrollbar-width: thin;
}

.sf-gallery-carousel .sf-gallery-item {
    flex: 0 0 min(440px, 84%);
    scroll-snap-align: start;
}

.sf-gallery-item {
    position: relative;
    overflow: hidden;
    border-radius: var(--sf-radius-sm);
    box-shadow: var(--sf-edge), var(--sf-elev-1);
}

.sf-gallery-item img {
    width: 100%;
    height: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform var(--sf-dur-4) var(--sf-ease-out);
}

.sf-gallery-item:hover img { transform: scale(1.04); }

@media (min-width: 860px) {
    .sf-gallery-grid {
        grid-template-columns: repeat(6, 1fr);
        grid-auto-rows: 12vw;
    }

    /* The mosaic. Five positions repeating, so any number of images lands in
       a composition and nothing is ever orphaned in a half-empty row. */
    .sf-gallery-grid .sf-gallery-item { grid-column: span 2; grid-row: span 1; }
    .sf-gallery-grid .sf-gallery-item:nth-child(5n + 1) { grid-column: span 4; grid-row: span 2; }
    .sf-gallery-grid .sf-gallery-item:nth-child(5n + 4) { grid-column: span 3; }
    .sf-gallery-grid .sf-gallery-item:nth-child(5n + 5) { grid-column: span 3; }
    .sf-gallery-grid .sf-gallery-item img { aspect-ratio: auto; height: 100%; }
}

/* ================================================================
   testimonials

   Dealer-entered quotes, never fabricated (docs/DESIGN.md), and never
   emitted as review structured data (SeoRendererTests pins that). Set as
   editorial pull quotes with a display-face opening mark, staggered so three
   quotes do not read as three identical cards.
   ================================================================ */

.sf-testimonials {
    display: grid;
    gap: var(--sf-space-7);
    grid-template-columns: 1fr;
}

.sf-testimonial {
    position: relative;
    background: transparent;
    padding: var(--sf-space-6) 0 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
    display: flex;
    flex-direction: column;
    gap: var(--sf-space-4);
}

.sf-stars { display: flex; gap: 2px; color: var(--sf-brand); }
.sf-stars .sf-icon { width: 15px; height: 15px; fill: currentColor; }

.sf-testimonial blockquote {
    color: var(--sf-ink);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-600);
    font-weight: var(--sf-heading-weight);
    line-height: var(--sf-lh-600);
    letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    text-wrap: pretty;
}

.sf-testimonial figcaption {
    color: var(--sf-text-soft);
    font-size: var(--sf-fs-200);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    margin-top: auto;
}

@media (min-width: 860px) {
    .sf-testimonials { grid-template-columns: repeat(3, 1fr); gap: clamp(32px, 4vw, 64px); }
    .sf-testimonials > .sf-testimonial:nth-child(2) { transform: translateY(var(--sf-space-6)); }
}

/* ================================================================
   ffl transfer info: the money page
   ================================================================ */

.sf-fee-tiers {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr;
    margin-block: var(--sf-space-7);
}

.sf-fee-tier {
    background: transparent;
    padding: var(--sf-space-6) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-fee-price {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: 1;
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    color: var(--sf-brand);
    font-variant-numeric: tabular-nums;
}

.sf-fee-label {
    color: var(--sf-ink);
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-heading-weight);
    font-size: var(--sf-fs-500);
    margin-top: var(--sf-space-2);
}

.sf-fee-desc { color: var(--sf-text-soft); font-size: var(--sf-fs-300); margin-top: var(--sf-space-2); max-width: 34ch; }

.sf-transfer-steps {
    list-style: none;
    padding: 0;
    counter-reset: sf-step;
    margin-block: var(--sf-space-6);
    display: grid;
    gap: var(--sf-space-5);
}

.sf-transfer-step {
    counter-increment: sf-step;
    position: relative;
    padding-left: 62px;
}

.sf-transfer-step::before {
    content: counter(sf-step, decimal-leading-zero);
    position: absolute;
    left: 0;
    top: 0;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-600);
    font-weight: var(--sf-display-weight);
    color: var(--sf-brand-dim);
    font-variant-numeric: tabular-nums;
}

.sf-transfer-step h3 { margin-bottom: var(--sf-space-1); }
.sf-transfer-step p { color: var(--sf-text-soft); max-width: 48ch; }

.sf-transfer-aside {
    display: grid;
    gap: var(--sf-space-5);
    margin-top: var(--sf-space-8);
}

.sf-what-to-bring, .sf-ffl-card {
    background: var(--sf-surface);
    border-radius: var(--sf-radius);
    padding: var(--sf-card-pad);
    box-shadow: var(--sf-edge), var(--sf-elev-2);
}

.sf-what-to-bring h3, .sf-ffl-card h3 { margin-bottom: var(--sf-space-4); font-size: var(--sf-fs-600); }

.sf-what-to-bring ul {
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--sf-space-3);
}

.sf-what-to-bring li { display: flex; gap: 10px; align-items: flex-start; }
.sf-what-to-bring .sf-icon { color: var(--sf-brand); width: 18px; height: 18px; margin-top: 4px; }

/* The FFL card is the one place a brand-filled surface is right: it is the
   single most valuable object on the page for a transfer customer. */
.sf-ffl-card {
    background: var(--sf-surface-raised);
    box-shadow: var(--sf-edge), var(--sf-elev-3), inset 3px 0 0 var(--sf-brand);
}

.sf-ffl-card p { margin-bottom: var(--sf-space-2); }
.sf-ffl-name { font-weight: 700; color: var(--sf-ink); }

.sf-ffl-number {
    font-family: var(--sf-font-mono);
    font-weight: 600;
    color: var(--sf-brand);
    letter-spacing: 0.04em;
}

.sf-ffl-pending { color: var(--sf-text-soft); font-size: var(--sf-fs-300); }

@media (min-width: 860px) {
    .sf-fee-tiers { grid-template-columns: repeat(3, 1fr); column-gap: clamp(24px, 4vw, 56px); }
    .sf-transfer-aside { grid-template-columns: 1fr 1fr; }
}

/* ================================================================
   services list
   ================================================================ */

.sf-services {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr;
}

.sf-service-card {
    background: transparent;
    padding: var(--sf-space-6) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-service-head {
    display: flex;
    align-items: baseline;
    gap: var(--sf-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--sf-space-3);
}

.sf-service-head .sf-feature-icon { margin-bottom: 0; width: 22px; height: 22px; align-self: center; }
.sf-service-head h3 { font-size: var(--sf-fs-600); }
.sf-service-card p { color: var(--sf-text-soft); max-width: 48ch; }

.sf-service-price {
    margin-left: auto;
    background: transparent;
    color: var(--sf-brand);
    font-family: var(--sf-font-heading);
    padding: 0;
    font-size: var(--sf-fs-500);
    font-weight: var(--sf-heading-weight);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

@media (min-width: 700px) {
    .sf-services {
        grid-template-columns: repeat(var(--sf-grid-cols, 2), minmax(0, 1fr));
        column-gap: clamp(28px, 4vw, 72px);
    }
}

/* ================================================================
   hours + map
   ================================================================ */

.sf-hoursmap {
    display: grid;
    gap: var(--sf-space-7);
}

.sf-open-now {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-space-2);
    font-family: var(--sf-font-heading);
    font-weight: 600;
    font-size: var(--sf-fs-300);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    margin-block: var(--sf-space-3);
}

.sf-open-now.sf-open { color: var(--sf-positive); }
.sf-open-now.sf-closed { color: var(--sf-text-soft); }

.sf-open-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 22%, transparent);
}

.sf-hours-table {
    border-collapse: collapse;
    width: 100%;
    max-width: 400px;
    margin-block: var(--sf-space-3);
}

.sf-hours-table th, .sf-hours-table td {
    text-align: left;
    padding: 9px 0;
    box-shadow: inset 0 -1px 0 var(--sf-hairline);
    color: var(--sf-text);
    font-weight: 400;
    font-size: var(--sf-fs-300);
}

.sf-hours-table th { color: var(--sf-ink); font-weight: 600; }
.sf-hours-table td { text-align: right; font-variant-numeric: tabular-nums; }

.sf-hours-today th, .sf-hours-today td { color: var(--sf-brand); font-weight: 700; }

.sf-address {
    font-style: normal;
    margin-block: var(--sf-space-3);
    color: var(--sf-text);
}

.sf-map-frame {
    position: relative;
    min-height: 340px;
    border-radius: var(--sf-radius);
    background: radial-gradient(120% 100% at 50% 0%, var(--sf-surface-alt), var(--sf-surface-sunken) 75%);
    box-shadow: var(--sf-edge), var(--sf-elev-2);
    overflow: hidden;
}

.sf-map-grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(var(--sf-hairline) 1px, transparent 1px),
        linear-gradient(90deg, var(--sf-hairline) 1px, transparent 1px);
    background-size: 44px 44px;
    opacity: 0.7;
    mask-image: radial-gradient(circle at 50% 50%, #000, transparent 82%);
}

.sf-map-pin {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sf-space-2);
    color: var(--sf-ink);
    font-family: var(--sf-font-heading);
    font-weight: 600;
}

.sf-map-pin .sf-icon {
    width: 32px;
    height: 32px;
    color: var(--sf-brand);
}

@media (min-width: 860px) {
    .sf-hoursmap { grid-template-columns: 1fr 1.25fr; align-items: start; gap: clamp(32px, 5vw, 80px); }
}

/* ================================================================
   compliance notice
   ================================================================ */

.sf-compliance {
    display: flex;
    gap: var(--sf-space-4);
    background: var(--sf-surface-sunken);
    border-radius: var(--sf-radius-sm);
    box-shadow: inset 3px 0 0 var(--sf-brand-dim);
    padding: var(--sf-space-5);
    max-width: var(--sf-measure-wide);
}

.sf-compliance .sf-icon { color: var(--sf-text-soft); margin-top: 3px; width: 18px; height: 18px; }
.sf-compliance h3 { font-size: var(--sf-fs-400); margin-bottom: var(--sf-space-1); }
.sf-compliance p { font-size: var(--sf-fs-300); color: var(--sf-text-soft); }

/* ================================================================
   policy page
   ================================================================ */

.sf-policy { display: grid; gap: var(--sf-space-7); }

.sf-policy-toc {
    background: transparent;
    border-radius: var(--sf-radius);
    padding: 0 0 0 var(--sf-space-4);
    box-shadow: inset 2px 0 0 var(--sf-hairline);
    align-self: start;
}

.sf-policy-toc-heading {
    font-family: var(--sf-font-heading);
    font-weight: 600;
    color: var(--sf-text-soft);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    font-size: var(--sf-fs-100);
    margin-bottom: var(--sf-space-3);
}

.sf-policy-toc ol { display: grid; gap: var(--sf-space-2); margin: 0; font-size: var(--sf-fs-300); }

@media (min-width: 860px) {
    .sf-policy { grid-template-columns: 240px minmax(0, 1fr); gap: clamp(40px, 5vw, 80px); }
    .sf-policy-toc { position: sticky; top: calc(var(--sf-header-height) + var(--sf-space-4)); }
}

/* ================================================================
   local area links
   ================================================================ */

.sf-localarea-chips {
    list-style: none;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
    margin-bottom: var(--sf-space-6);
}

.sf-localarea-cards {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr;
}

.sf-localarea-card {
    background: transparent;
    padding: var(--sf-space-6) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-localarea-card h3 { margin-bottom: var(--sf-space-3); }
.sf-localarea-card p { color: var(--sf-text-soft); max-width: 48ch; }

@media (min-width: 860px) {
    .sf-localarea-cards { grid-template-columns: 1fr 1fr; column-gap: clamp(32px, 4vw, 72px); }
}

/* ================================================================
   page-title header (hero-less pages)
   ================================================================ */

/* Rendered by the page shell when a page's block stack has no hero, so
   every page carries exactly one H1 (SEO-med-6). It gets the same editorial
   treatment as the hero masthead: eyebrow, display-size title, hairline. */
.sf-page-header {
    padding-bottom: 0;
    position: relative;
}

.sf-page-title {
    font-size: var(--sf-fs-900);
    line-height: var(--sf-lh-900);
    margin: 0;
    max-width: 18ch;
}

.sf-page-header .sf-container::after {
    content: "";
    display: block;
    height: 1px;
    margin-top: var(--sf-space-6);
    background: linear-gradient(90deg, var(--sf-brand) 0, var(--sf-hairline) 14%, transparent 70%);
}

/* Local-area coverage chips are PLAIN LIST ITEMS, not links: keep them flat
   text tags so they stop advertising interactivity (UX-minor-10). Anchor
   chips (shop filters) keep the raised pill look. */
li.sf-chip {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--sf-hairline);
    color: var(--sf-text-soft);
}

/* ================================================================
   section transitions and scroll reveals

   Appended, never inserted (cascade order). Two jobs:

   1. Stop every section boundary being identical. A three-step repeating
      pattern of hairline / brass tick / nothing gives the page a rhythm
      without any markup change, because the treatments are assigned by
      nth-of-type. Sections that own a hero or a full-bleed band are
      excluded via :has(); if a browser does not understand :has() the whole
      rule is dropped and the page simply has no seam, which is exactly the
      old behaviour and therefore a safe degradation.

   2. Reveal blocks on scroll with animation-timeline: view(), which is
      driven by the compositor and needs no IntersectionObserver and no
      JavaScript at all. Declared inside @supports so an unsupported browser
      renders the content already visible: nothing on this page becomes
      visible BECAUSE of an animation.
   ================================================================ */

main > .sf-section:not(:has(.sf-hero)):not(.sf-bg-brand):not(.sf-bg-dark):nth-of-type(3n + 2)::before {
    content: "";
    position: absolute;
    inset-inline: var(--sf-gutter);
    top: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--sf-hairline) 10%, var(--sf-hairline) 90%, transparent);
}

main > .sf-section:not(:has(.sf-hero)):not(.sf-bg-brand):not(.sf-bg-dark):nth-of-type(3n)::before {
    content: "";
    position: absolute;
    inset-inline-start: var(--sf-gutter);
    top: 0;
    width: clamp(40px, 7vw, 96px);
    height: 2px;
    background: var(--sf-brand);
    opacity: 0.9;
}

@media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) and (animation-range: entry 4%) {
        /* Blocks below the first section settle in as they enter. The first
           section is excluded: it is on screen at load and the LCP path,
           and Chrome drops an opacity:0 element from LCP candidacy entirely,
           so a reveal on the hero would directly delay the headline metric. */
        main > .sf-section:nth-of-type(n + 2) .sf-block {
            animation: sf-reveal-in linear both;
            animation-timeline: view();
            animation-range: entry 4% cover 24%;
        }

        /* Grid children stagger against their own position in the viewport
           rather than against a timer, so a fast scroll never leaves a row
           half-revealed. The stagger is a per-child ANIMATION-RANGE offset
           (--sf-si steps of --sf-stagger-range): animation-delay is
           meaningless on a progress timeline, so the offset has to live in
           the range itself. Capped at six steps; deeper children reuse the
           middle offset so a long list never drifts a full viewport out of
           sync with its own row. */
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > * {
            animation: sf-reveal-in linear both;
            animation-timeline: view();
            animation-range: entry calc(var(--sf-si, 0) * var(--sf-stagger-range, 5%))
                             cover calc(22% + var(--sf-si, 0) * var(--sf-stagger-range, 5%));
        }

        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(2) { --sf-si: 1; }
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(3) { --sf-si: 2; }
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(4) { --sf-si: 3; }
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(5) { --sf-si: 4; }
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(6) { --sf-si: 5; }
        main > .sf-section:nth-of-type(n + 2)
            :is(.sf-feature-grid, .sf-feature-cards, .sf-feature-ledger,
                .sf-gallery-grid, .sf-testimonials, .sf-pullquote-rest,
                .sf-catindex, .sf-classes-list, .sf-range-facts, .sf-range-rates) > *:nth-child(n + 7) { --sf-si: 3; }

        /* A block that is animating must not also be the animation target of
           its parent, or the two opacities multiply into a visible flash. */
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-feature-grid),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-feature-cards),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-feature-ledger),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-gallery-grid),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-testimonials),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-pullquote-block),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-catindex),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-classes),
        main > .sf-section:nth-of-type(n + 2) .sf-block:has(.sf-range) {
            animation: none;
        }
    }
}

/* A section that owns a hero handles its own padding: the masthead is
   full-bleed by definition, and wrapping it in a padded band put a stripe of
   page background above and below the one element that should touch the
   header. The section immediately after it gets the chapter rhythm, which is
   the widest of the five and is what makes the page open rather than start.
   Both rules degrade to the previous behaviour where :has() is missing. */
main > .sf-section:has(> .sf-container > .sf-block > .sf-hero),
main > .sf-section:has(> .sf-container-full > .sf-block > .sf-hero),
main > .sf-section:has(.sf-hero) {
    padding-block: 0;
}

/* The chapter break opens the page after the masthead: the widest step in the
   ramp, and what makes a page open rather than start. It is skipped when the
   section immediately after the hero is a thin band (a trust bar), because a
   chapter-sized void above a 40px strip reads as a rendering fault rather
   than as air.

   It used to be MOVED past the trust bar onto the next section instead of
   being skipped, and that relocated the fault rather than fixing it: the
   demo home page then opened with a 300px void between the trust strip and
   the heading under it, which is the largest gap on the page sitting in the
   one place nothing authored it. A trust bar belongs to the masthead, it is
   not a chapter of its own, and the section after it is just the next
   section: it opens on its own authored step, like every other one. */
main > .sf-section:has(.sf-hero) + .sf-section:not(:has(.sf-trustbar)) {
    padding-block-start: var(--sf-pad-chapter);
}

/* A hero section must not be clipped by the container either. */
main > .sf-section:has(.sf-hero) > .sf-container,
main > .sf-section:has(.sf-hero) > .sf-container-narrow,
main > .sf-section:has(.sf-hero) > .sf-container-wide {
    max-width: none;
    padding-inline: 0;
}

/* ================================================================
   ULTRAPLAN ROUND 4 (blocks lane). Appended, never inserted.

   Everything below is the layout-grammar work: the four new hero
   compositions, the type-led category index, the stats band and brand
   strip, the split/feature/testimonial variants, and the progress-linked
   motion. Tokens owned by the foundation lane are referenced by name WITH
   FALLBACK (var(--sf-plate, #F2EFE9) and friends) and never defined here.
   ================================================================ */

/* ----------------------------------------------------------------
   hero: typographic

   The no-photo flagship: the section ground is the existing plate
   (blueprint grid + datum rule + grain) and the composition is carried by
   an oversized stroke-outlined ghost word bleeding off the right edge,
   with the real H1 overlapping its lower third. Zero image requests on
   the LCP path, which makes this the FASTEST hero, not the fallback one.
   ---------------------------------------------------------------- */

.sf-hero-typographic {
    --sf-ghost-size: var(--sf-fs-1100, clamp(4.5rem, 1.8rem + 12vw, 13rem));
    align-items: stretch;

    /* clip, not hidden: the ghost word bleeds past the edge and must never
       mint a horizontal scrollbar. */
    overflow: clip;
}

.sf-hero-ghost {
    position: absolute;
    z-index: 0;
    top: clamp(8px, 3vw, 40px);
    right: calc(-0.14 * var(--sf-ghost-size, 8rem));
    font-family: var(--sf-font-heading);
    font-size: var(--sf-ghost-size);
    font-weight: var(--sf-display-weight);
    line-height: 0.82;
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    text-transform: uppercase;
    white-space: nowrap;
    color: transparent;
    -webkit-text-stroke: 1px var(--sf-outline-ink, currentColor);
    opacity: 0.32;
    pointer-events: none;
    user-select: none;
}

/* Engines without text-stroke get a solid ghost in the numeral ink instead
   of an invisible element. */
@supports not (-webkit-text-stroke: 1px #fff) {
    .sf-hero-ghost {
        color: var(--sf-numeral-ink, rgba(201, 164, 92, 0.16));
        opacity: 1;
    }
}

.sf-hero-typo {
    z-index: 1;
    display: grid;
    gap: var(--sf-space-5);
    align-content: end;
}

/* The H1 rides over the ghost word's lower third. */
.sf-hero-typo-lead {
    padding-top: calc(0.42 * var(--sf-ghost-size, 8rem));
}

.sf-hero-typo-standfirst {
    max-width: 46ch;
}

@media (max-width: 859px) {
    /* Single column; the ghost word clamps to the floor behind the H1. */
    .sf-hero-typographic { --sf-ghost-size: clamp(3.25rem, 22vw, 6.5rem); }

    .sf-hero-ghost {
        top: auto;
        bottom: -0.08em;
        right: -6vw;
    }

    .sf-hero-typo-lead { padding-top: clamp(24px, 8vw, 56px); }
}

@media (min-width: 860px) {
    .sf-hero-typo {
        grid-template-columns: minmax(0, 7fr) minmax(220px, 4fr);
        grid-template-rows: 1fr auto auto;
        column-gap: clamp(32px, 6vw, 96px);
    }

    .sf-hero-typo-lead { grid-column: 1; grid-row: 2; }

    .sf-hero-typo-standfirst { grid-column: 1; grid-row: 3; }

    /* The rail spans all three rows and bottom-anchors: it starts and ends
       on different rows than the headline (law 1: never mirrored voids). */
    .sf-hero-typo-meta {
        grid-column: 2;
        grid-row: 1 / 4;
        align-self: end;
        justify-self: end;
        min-width: 220px;
    }
}

/* ----------------------------------------------------------------
   hero: cinematic

   A single grid cell: media, tint, scrim, content all stacked. The
   container sizes to its tallest child, so the photograph's intrinsic
   dimensions still reserve the LCP box (CLS 0) even before the
   min-height applies.
   ---------------------------------------------------------------- */

.sf-hero-cinematic {
    display: block;
    overflow: clip;
}

.sf-hero-cine-stack {
    display: grid;
    min-height: inherit;
}

.sf-hero-cine-stack > * { grid-area: 1 / 1; }

.sf-hero-cinematic .sf-hero-cine-stack > .sf-hero-duotone {
    position: static;
    inset: auto;
    z-index: auto;
}

.sf-hero-cine-media {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.sf-hero-cine-plate {
    position: static;
    inset: auto;
    z-index: auto;
}

.sf-hero-cine-overlay {
    background: var(--sf-hero-overlay);
    pointer-events: none;
}

.sf-hero-cine-scrim {
    background: var(--sf-scrim, linear-gradient(180deg, transparent 40%, rgba(6, 5, 4, 0.78)));
    pointer-events: none;
}

/* Content on an inner feature-width cell, one type step up. */
.sf-hero-cine-content {
    align-self: end;
    width: 100%;
    max-width: var(--sf-canvas-feature, var(--sf-container));
    margin-inline: auto;
    padding: clamp(48px, 8vw, 120px) var(--sf-gutter) clamp(40px, 6vw, 88px);
    position: relative;
    z-index: 1;
}

.sf-hero-heading-up {
    font-size: calc(clamp(2.75rem, 0.8rem + 8.8vw, 7rem) * var(--sf-display-scale, 1));
}

/* ----------------------------------------------------------------
   hero: photo-editorial

   The photo bleeds full width at ~76svh; a paper panel underlaps its
   lower-right quarter and carries the business facts (NAP + hours) in
   mono over hairlines. The photo is the LCP element: intrinsic
   dimensions plus a fixed block-size reserve the box, CLS 0.
   ---------------------------------------------------------------- */

.sf-hero-photo-editorial {
    display: block;
    overflow: visible;
}

.sf-hero-pe-grid {
    display: grid;
    grid-template-columns: 1fr;
}

.sf-hero-pe-media {
    position: relative;
    overflow: clip;
    isolation: isolate;
}

.sf-hero-pe-img {
    display: block;
    width: 100%;
    height: clamp(420px, 76svh, 820px);
    object-fit: cover;
}

@media (max-width: 699px) {
    /* A phone gets the message inside the first screenful: the photo band
       drops from 76svh to a capped window so the headline is visible without
       scrolling past a full viewport of photograph. */
    .sf-hero-pe-media { max-height: min(52svh, 460px); }
}


/* The duotone machinery applies at decorative-context strength here (the
   theme decides; 0 = untouched photograph). */
.sf-hero-photo-editorial .sf-hero-duotone {
    opacity: var(--sf-duotone, 0);
}

.sf-hero-pe-scrim {
    position: absolute;
    inset: 0;
    background: var(--sf-scrim, linear-gradient(180deg, transparent 40%, rgba(6, 5, 4, 0.78)));
    pointer-events: none;
}

.sf-hero-pe-title {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    z-index: 1;
    max-width: var(--sf-container);
    margin-inline: auto;
    padding: 0 var(--sf-gutter) clamp(28px, 4vw, 56px);
}

.sf-hero-pe-panel {
    background: var(--sf-surface);
    color: var(--sf-text);
    padding: clamp(20px, 3vw, 44px);
    display: grid;
    gap: var(--sf-space-4);
    align-content: start;
}

/* The panel's rail rows swap the on-photo whites for ink on paper. */
.sf-hero-pe-panel .sf-hero-meta-item { border-top: 1px solid var(--sf-hairline); }
.sf-hero-pe-panel .sf-hero-meta-label { color: var(--sf-text-soft); }
.sf-hero-pe-panel .sf-hero-meta-value { color: var(--sf-ink); }
.sf-hero-pe-panel a.sf-hero-meta-value:hover { color: var(--sf-brand); }

.sf-hero-pe-standfirst { max-width: 38ch; }
.sf-hero-pe-standfirst p { color: var(--sf-text-soft); font-size: var(--sf-fs-400); }

.sf-hero-pe-hours {
    font-family: var(--sf-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--sf-fs-200);
    display: grid;
    gap: var(--sf-space-1);
    color: var(--sf-ink);
}

.sf-hero-pe-hours-row {
    display: flex;
    justify-content: space-between;
    gap: var(--sf-space-4);
    box-shadow: inset 0 -1px 0 var(--sf-hairline);
    padding-block: 3px;
}

@media (min-width: 860px) {
    .sf-hero-pe-grid {
        grid-template-columns: minmax(0, 1fr) minmax(300px, 34%);
    }

    .sf-hero-pe-media { grid-column: 1 / 3; grid-row: 1; }

    /* The underlap: the panel pulls itself up over the photo's lower-right
       quarter with a STATIC negative margin (no animation, no CLS). */
    .sf-hero-pe-panel {
        grid-column: 2;
        grid-row: 2;
        position: relative;
        z-index: 2;
        margin-top: calc(-1 * clamp(96px, 14vw, 200px));
        margin-right: var(--sf-gutter);
        box-shadow: var(--sf-edge), var(--sf-elev-3);
    }

    /* Keep the headline clear of the panel's underlap zone. */
    .sf-hero-pe-title { padding-right: max(36%, 340px); }
}

/* ----------------------------------------------------------------
   hero: split

   Copy on the wide track, media on the narrow one bleeding to the
   canvas edge; the NAP rail sits under the copy.
   ---------------------------------------------------------------- */

.sf-hero-splitlayout {
    align-items: stretch;
    overflow: clip;
}

.sf-hero-sl-grid {
    width: 100%;
    max-width: var(--sf-container);
    margin-inline: auto;
    padding: clamp(40px, 6vw, 88px) var(--sf-gutter);
    display: grid;
    gap: var(--sf-space-6);
    align-items: center;
}

.sf-hero-sl-media {
    position: relative;
    overflow: clip;
    border-radius: var(--sf-radius);
    box-shadow: var(--sf-edge), var(--sf-elev-3);
}

.sf-hero-sl-media img {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

.sf-hero-sl-meta { margin-top: var(--sf-space-6); }

@media (min-width: 900px) {
    .sf-hero-sl-grid {
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
        gap: clamp(40px, 6vw, 96px);
        padding-block: clamp(56px, 8vw, 120px);
    }

    /* Bleed toward the viewport edge, never past it (same guarded calc as
       the image-text split). */
    .sf-hero-sl-media {
        margin-right: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }

    .sf-hero-sl-media img { aspect-ratio: auto; min-height: 100%; }
}

/* ----------------------------------------------------------------
   hero: two-layer exit parallax (motion 1.1)

   The finished state is the base rule (media at scale 1.12 so the exit
   translate never reveals an edge); the animation runs on the EXIT range
   only. Never entry: the hero is the LCP path, and Chrome drops an
   opacity-0 element from LCP candidacy entirely.
   ---------------------------------------------------------------- */

.sf-hero-cine-media,
.sf-hero-pe-img {
    transform: scale(1.12);
}

@media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) and (animation-range: entry 5%) {
        .sf-hero-cine-media,
        .sf-hero-pe-img {
            animation: sf-hero-media-exit linear both;
            animation-timeline: view();
            animation-range: exit 0% exit 100%;
        }

        .sf-hero-cine-content,
        .sf-hero-pe-title {
            animation: sf-hero-copy-exit linear both;
            animation-timeline: view();
            animation-range: exit 0% exit 100%;
        }
    }
}

@keyframes sf-hero-media-exit {
    from { transform: scale(1.12); }
    to { transform: scale(1.12) translateY(9%); }
}

@keyframes sf-hero-copy-exit {
    from { transform: none; opacity: 1; }
    to { transform: translateY(-7%); opacity: 0.35; }
}

/* ----------------------------------------------------------------
   category index

   Type-led full-width rows: mono ordinal, the category name AS the
   display element, mono count, arrow. The whole row is one link. Hover
   is transform/color only: the ground steps one surface, the name slides
   a spacing step, the arrow fills the accent.
   ---------------------------------------------------------------- */

.sf-catindex-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--sf-space-4);
    margin-bottom: var(--sf-space-4);
}

.sf-catindex-heading {
    font-size: var(--sf-fs-700);
    max-width: 20ch;
}

.sf-catindex-total {
    font-family: var(--sf-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    padding-bottom: var(--sf-space-2);
}

.sf-catindex {
    list-style: none;
    padding: 0;
    margin: 0;
    overflow: clip;
}

.sf-catindex > li { box-shadow: inset 0 1px 0 var(--sf-hairline); }
.sf-catindex > li:last-child {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-catindex-row {
    position: relative;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: baseline;
    column-gap: clamp(16px, 3vw, 40px);
    padding: var(--sf-space-5) var(--sf-space-3);
    text-decoration: none;
    color: var(--sf-ink);
    transition: background-color var(--sf-dur-3) var(--sf-ease-out);
}

.sf-catindex-row:hover { background: var(--sf-surface); }

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

.sf-catindex-name {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-700);
    line-height: var(--sf-lh-800);
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-900) + var(--sf-track-offset-display));
    text-transform: var(--sf-heading-transform);
    transition: transform var(--sf-dur-3) var(--sf-ease-spring, cubic-bezier(0.23, 1, 0.32, 1));
}

.sf-catindex-row:hover .sf-catindex-name { transform: translateX(var(--sf-space-3)); }

.sf-catindex-arrow {
    color: var(--sf-text-faint);
    font-size: var(--sf-fs-600);
    line-height: 1;
    align-self: center;
    transition: color var(--sf-dur-3) var(--sf-ease-out),
                transform var(--sf-dur-3) var(--sf-ease-spring, cubic-bezier(0.23, 1, 0.32, 1));
}

.sf-catindex-row:hover .sf-catindex-arrow {
    color: var(--sf-rule-accent, var(--sf-brand));
    transform: translateX(4px);
}

/* The featured (largest) category's thumb strip: decorative plates that
   bleed toward the right canvas edge on wide screens only. */
.sf-catindex-thumbs { display: none; }

@media (min-width: 860px) {
    .sf-catindex-name { font-size: var(--sf-fs-900); line-height: var(--sf-lh-900); }
}

@media (min-width: 1100px) {
    .sf-catindex-row[data-featured] { padding-right: clamp(220px, 24vw, 420px); }

    .sf-catindex-thumbs {
        display: flex;
        gap: var(--sf-space-2);
        position: absolute;
        top: 50%;
        right: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        transform: translateY(-50%);
    }

    .sf-catindex-thumb {
        width: clamp(88px, 8vw, 128px);
        aspect-ratio: 4 / 3;
        background: var(--sf-plate, #F2EFE9);
        border-radius: var(--sf-radius-sm);
        overflow: clip;
        box-shadow: var(--sf-edge), var(--sf-elev-1);
    }

    .sf-catindex-thumb img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
    }
}

/* ----------------------------------------------------------------
   stats band

   Dealer-entered claims as ghost-backed numerals on the theme's dark
   band. The 2x2 grid is a staircase, not a table: even items carry a
   deliberate top-margin step. One accent moment: the 24px rule over
   each label.
   ---------------------------------------------------------------- */

.sf-statsband {
    overflow: clip;
    display: grid;
    gap: clamp(32px, 5vw, 72px);
}

.sf-statsband-band {
    background: var(--sf-dark-bg);
    color: var(--sf-dark-text);
    border-radius: var(--sf-radius-lg);
    padding: clamp(40px, 6vw, 96px) clamp(24px, 4vw, 72px);
    box-shadow: var(--sf-edge), var(--sf-elev-3);
}

.sf-statsband-heading {
    font-size: var(--sf-fs-800);
    max-width: 16ch;
    text-wrap: balance;
}

.sf-statsband-band .sf-statsband-heading { color: var(--sf-dark-ink); }

.sf-statsband-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: clamp(28px, 4vw, 56px);
    margin: 0;
}

.sf-stat {
    position: relative;
    display: flex;
    flex-direction: column;
}

/* dt before dd is the required DOM order; the display order (numeral,
   rule, label, note) is flex order, so the markup stays a real
   description list. */
.sf-stat-value { order: 1; }
.sf-stat-label { order: 2; }
.sf-stat-note { order: 3; }

.sf-stat-ghost {
    position: absolute;
    left: -0.06em;
    bottom: 24%;
    z-index: 0;
    font-family: var(--sf-font-heading);
    font-size: calc(var(--sf-fs-numeral, clamp(3.5rem, 1.6rem + 8.5vw, 9rem)) * 1.6);
    font-weight: var(--sf-display-weight);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--sf-numeral-ink, rgba(201, 164, 92, 0.16));
    pointer-events: none;
    user-select: none;
}

.sf-stat-value {
    position: relative;
    z-index: 1;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-numeral, clamp(3.5rem, 1.6rem + 8.5vw, 9rem));
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    line-height: 1;
    font-variant-numeric: tabular-nums;
    margin: 0;
}

.sf-statsband-band .sf-stat-value { color: var(--sf-dark-ink); }

.sf-stat-label {
    position: relative;
    z-index: 1;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    margin-top: var(--sf-space-4);
}

.sf-stat-label::before {
    content: "";
    display: block;
    width: 24px;
    height: 2px;
    background: var(--sf-rule-accent, var(--sf-brand));
    margin-bottom: var(--sf-space-3);
}

.sf-stat-note {
    position: relative;
    z-index: 1;
    font-size: var(--sf-fs-200);
    color: var(--sf-text-soft);
    margin: var(--sf-space-2) 0 0;
    max-width: 30ch;
}

.sf-statsband-band .sf-stat-label { color: var(--sf-dark-text); }
.sf-statsband-band .sf-stat-note { color: var(--sf-dark-text); opacity: 0.75; }

@media (min-width: 700px) {
    .sf-statsband-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* The staircase: items 2 and 4 drop a step. */
    .sf-statsband-grid > .sf-stat:nth-child(even) { margin-top: var(--sf-space-7); }
}

@media (min-width: 900px) {
    /* Claim copy left on the narrow track, numerals right on the wide one. */
    .sf-statsband:has(> .sf-statsband-copy) {
        grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
        align-items: start;
    }
}

.sf-statsband-inline { gap: clamp(24px, 4vw, 48px); }
.sf-statsband-inline .sf-stat-value { color: var(--sf-ink); }
.sf-statsband-inline .sf-stat-label { color: var(--sf-text-soft); }
.sf-statsband-inline .sf-statsband-heading { color: var(--sf-ink); }

/* ----------------------------------------------------------------
   brand strip

   A typographic manufacturer rail: uppercase condensed names, faint,
   hairline verticals. Static scrolls with snap on narrow screens. The
   ticker style is a decorative marquee: server-duplicated aria-hidden
   track, pauses on hover AND focus-within, fully static wrap under
   prefers-reduced-motion.
   ---------------------------------------------------------------- */

.sf-brandstrip-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--sf-space-4);
    margin-bottom: var(--sf-space-4);
}

.sf-brandstrip-heading { font-size: var(--sf-fs-600); }

.sf-brandstrip-total {
    font-family: var(--sf-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    padding-bottom: var(--sf-space-1);
}

.sf-brandstrip {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-brandstrip-track {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
}

.sf-brandstrip-item {
    flex: 0 0 auto;
    padding: var(--sf-space-4) clamp(20px, 3vw, 48px);
    box-shadow: inset 1px 0 0 var(--sf-hairline);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-300);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    white-space: nowrap;
}

.sf-brandstrip-track > .sf-brandstrip-item:first-child { box-shadow: none; }

.sf-brandstrip-item a {
    color: inherit;
    text-decoration: none;
    transition: color var(--sf-dur-2) var(--sf-ease-out);
}

.sf-brandstrip-item a:hover { color: var(--sf-brand); }

.sf-brandstrip-static {
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
}

.sf-brandstrip-static .sf-brandstrip-item { scroll-snap-align: start; }

/* Ticker base state is STATIC: one wrapped track, duplicate hidden. This
   is both the reduced-motion state and the no-animation state, so motion
   is opt-in, never cut short. */
.sf-brandstrip-ticker { display: flex; overflow: clip; }

.sf-brandstrip-ticker .sf-brandstrip-track { flex: 0 0 auto; flex-wrap: wrap; }

.sf-brandstrip-ticker .sf-brandstrip-track[aria-hidden="true"] { display: none; }

@media (prefers-reduced-motion: no-preference) {
    .sf-brandstrip-ticker .sf-brandstrip-track {
        flex-wrap: nowrap;
        min-width: max-content;
        animation: sf-brandstrip-marquee var(--sf-marquee-speed, 42s) linear infinite;
    }

    .sf-brandstrip-ticker .sf-brandstrip-track[aria-hidden="true"] { display: flex; }

    /* Pause on hover AND on keyboard focus landing anywhere inside. */
    .sf-brandstrip-ticker:hover .sf-brandstrip-track,
    .sf-brandstrip-ticker:focus-within .sf-brandstrip-track {
        animation-play-state: paused;
    }
}

@keyframes sf-brandstrip-marquee {
    from { transform: translateX(0); }
    to { transform: translateX(-100%); }
}

/* ----------------------------------------------------------------
   image-text split: layout variants

   bleed = the shipped render, untouched above. The variants below only
   ADD a modifier class, so the default path cannot drift.
   ---------------------------------------------------------------- */

/* contained: no bleed, both columns inside the container. */
@media (min-width: 860px) {
    .sf-split-contained .sf-split-media,
    .sf-split-right.sf-split-contained .sf-split-media {
        margin-left: 0;
        margin-right: 0;
        border-radius: var(--sf-radius);
    }
}

/* offset: media and copy on staggered row bands; the copy panel pulls up
   over the media row with a STATIC margin step (CLS 0, aspect-locked). */
.sf-split-offset .sf-split-copy {
    background: var(--sf-surface);
    border-radius: var(--sf-radius);
    padding: clamp(24px, 3.5vw, 56px);
    box-shadow: var(--sf-edge), var(--sf-elev-3);
    position: relative;
    z-index: 1;
}

@media (min-width: 860px) {
    .sf-split-offset {
        grid-template-columns: repeat(12, minmax(0, 1fr));
        gap: 0;
        align-items: start;
    }

    .sf-split-offset .sf-split-media {
        grid-column: 1 / 8;
        grid-row: 1;
        margin-left: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
    }

    .sf-split-offset .sf-split-media img { aspect-ratio: 16 / 10; }

    .sf-split-offset .sf-split-copy {
        grid-column: 6 / 13;
        grid-row: 2;
        margin-top: calc(-1 * clamp(64px, 9vw, 160px));
        padding-top: clamp(24px, 3.5vw, 56px);
    }

    /* Mirrored when the image sits right. */
    .sf-split-right.sf-split-offset .sf-split-media {
        grid-column: 6 / 13;
        order: initial;
        margin-left: 0;
        margin-right: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        border-radius: var(--sf-radius);
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }

    .sf-split-right.sf-split-offset .sf-split-copy { grid-column: 1 / 8; }
}

/* overlap: one grid cell, copy card floated over the media. */
.sf-split-overlap .sf-split-copy {
    background: var(--sf-surface-raised);
    border-radius: var(--sf-radius);
    padding: clamp(24px, 3vw, 48px);
    box-shadow: var(--sf-edge), var(--sf-elev-4);
    position: relative;
    z-index: 1;
}

@media (min-width: 860px) {
    .sf-split-overlap { display: grid; }

    .sf-split-overlap > * { grid-area: 1 / 1; }

    .sf-split-overlap .sf-split-media,
    .sf-split-right.sf-split-overlap .sf-split-media {
        margin-left: 0;
        margin-right: 0;
        order: initial;
        border-radius: var(--sf-radius);
    }

    .sf-split-overlap .sf-split-media img { aspect-ratio: 21 / 10; }

    .sf-split-overlap .sf-split-copy {
        justify-self: end;
        align-self: center;
        max-width: min(46ch, 88%);
        margin-right: clamp(16px, 3vw, 56px);
        padding-top: clamp(24px, 3vw, 48px);
    }

    /* Image right = card left. */
    .sf-split-right.sf-split-overlap .sf-split-copy {
        justify-self: start;
        margin-right: 0;
        margin-left: clamp(16px, 3vw, 56px);
    }
}

/* ----------------------------------------------------------------
   feature grid: cards and ledger layouts
   ---------------------------------------------------------------- */

.sf-feature-cards {
    display: grid;
    gap: var(--sf-grid-gap, var(--sf-space-4));
    grid-template-columns: 1fr;
}

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

.sf-feature-card h3 { margin-bottom: var(--sf-space-3); }
.sf-feature-card p { color: var(--sf-text-soft); }
.sf-feature-card .sf-feature-icon { margin-bottom: var(--sf-space-4); }

@media (min-width: 700px) {
    .sf-feature-cards { grid-template-columns: repeat(var(--sf-grid-cols, 3), minmax(0, 1fr)); }
}

/* ledger: full-width rows where the counter ordinal IS the display
   element. */
.sf-feature-ledger {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: sf-ledger;
}

.sf-feature-ledger-row {
    counter-increment: sf-ledger;
    display: grid;
    grid-template-columns: minmax(72px, auto) minmax(0, 1fr);
    column-gap: clamp(20px, 4vw, 64px);
    align-items: start;
    padding: var(--sf-space-6) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-feature-ledger-row:last-child {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-feature-ledger-row::before {
    content: counter(sf-ledger, decimal-leading-zero);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: 1;
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-800) + var(--sf-track-offset-display));
    color: var(--sf-brand-dim);
    font-variant-numeric: tabular-nums;
}

.sf-feature-ledger-row h3 { font-size: var(--sf-fs-600); margin-bottom: var(--sf-space-2); }
.sf-feature-ledger-row p { color: var(--sf-text-soft); max-width: 52ch; }

/* ----------------------------------------------------------------
   testimonials: pullquote layout

   Items[0] as an oversized editorial quote behind a ghost glyph; the
   rest are quiet hairline rows. No boxes, no stars, no avatars.
   ---------------------------------------------------------------- */

.sf-pullquote-block {
    display: grid;
    gap: clamp(32px, 5vw, 72px);
}

.sf-pullquote {
    position: relative;
    max-width: var(--sf-measure-wide);
}

.sf-pullquote-mark {
    position: absolute;
    top: -0.08em;
    left: -0.04em;
    z-index: 0;
    /* System serif, NOT the heading face: the self-hosted display subset
       does not carry U+201C, which rendered as two tofu boxes on the live
       page. Every system serif has the glyph, and at ghost opacity the
       face mismatch is imperceptible. */
    font-family: Georgia, 'Times New Roman', serif;
    font-size: var(--sf-fs-1100, clamp(4.5rem, 1.8rem + 12vw, 13rem));
    font-weight: var(--sf-display-weight);
    line-height: 1;
    color: var(--sf-numeral-ink, rgba(201, 164, 92, 0.16));
    pointer-events: none;
    user-select: none;
}

.sf-pullquote blockquote {
    position: relative;
    z-index: 1;
    padding-top: clamp(32px, 6vw, 88px);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-700);
    line-height: var(--sf-lh-700);
    font-weight: var(--sf-heading-weight);
    letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    color: var(--sf-ink);
    text-wrap: pretty;
}

.sf-pullquote figcaption {
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
    margin-top: var(--sf-space-4);
    padding-left: var(--sf-space-7);
}

.sf-pullquote-rest { display: grid; }

.sf-pullquote-row {
    padding: var(--sf-space-5) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
    display: grid;
    gap: var(--sf-space-2);
}

.sf-pullquote-row:last-child {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-pullquote-row blockquote {
    color: var(--sf-text);
    font-size: var(--sf-fs-400);
    max-width: var(--sf-measure);
    text-wrap: pretty;
}

.sf-pullquote-row figcaption {
    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);
}

@media (min-width: 1100px) {
    /* Asymmetric: the lead quote takes the wide track, the quiet rows the
       narrow one, bottom-aligned so the two columns never mirror. */
    .sf-pullquote-block {
        grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
        align-items: end;
    }
}

/* ----------------------------------------------------------------
   progress-linked develop effect (motion 1.4)

   DECORATIVE media only (gallery, image-text split): a photographic
   settle from 1.14 to the 1.06 base, and a theme-tint overlay whose
   opacity runs from the theme's tint strength to 0 as the block reaches
   the middle of the viewport. Product imagery is NEVER touched: a
   shopper has to see the real colour of the thing they are buying.

   The gallery keeps its base scale at 1 because its hover zoom is a
   transform and a held scroll-driven animation would override it; the
   gallery gets the tint develop only.
   ---------------------------------------------------------------- */

.sf-split-media,
.sf-gallery-item {
    isolation: isolate;
}

.sf-split-media::after,
.sf-gallery-item::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(150deg, var(--sf-hero-grad-a), var(--sf-hero-grad-b));
    mix-blend-mode: color;
    opacity: 0;
    pointer-events: none;
}

.sf-split-media img { transform: scale(1.06); }

@media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) and (animation-range: entry 5%) {
        .sf-split-media img {
            animation: sf-develop-settle linear both;
            animation-timeline: view();
            animation-range: entry 0% cover 45%;
        }

        .sf-split-media::after,
        .sf-gallery-item::after {
            animation: sf-develop-tint linear both;
            animation-timeline: view();
            animation-range: entry 0% cover 45%;
        }
    }
}

@keyframes sf-develop-settle {
    from { transform: scale(1.14); }
    to { transform: scale(1.06); }
}

@keyframes sf-develop-tint {
    from { opacity: var(--sf-tint-strength, 0); }
    to { opacity: 0; }
}

/* ================================================================
   ULTRAPLAN ROUND 8 (renderer lane). Appended, never inserted.

   The class-schedule and range-info blocks. Both are built from grammar
   that already ships: the category index (hairline-seamed rows, one grid
   per row, the name as the display element), the fee tiers, the
   counter-band step rail and the FFL card. Nothing here is a new
   keyframe: the rows join the stagger lists above. Foundation-owned
   tokens are referenced WITH FALLBACK and never defined here.
   ================================================================ */

/* ----------------------------------------------------------------
   class schedule

   One row per upcoming session on the category-index grammar: mono
   date/time in the ordinal slot, the class title AS the display
   element, price and the register CTA baseline-aligned on the right.
   Zero background, radius or shadow on a row (laws 1 and 4); the one
   accent moment is the primary button.
   ---------------------------------------------------------------- */

.sf-classes-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--sf-space-4);
    margin-bottom: var(--sf-space-4);
}

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

/* Mono face and tabular digits come from the foundation identifier list;
   only the size, case and colour are decided here. */
.sf-classes-count {
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    padding-bottom: var(--sf-space-2);
    white-space: nowrap;
}

.sf-classes > .sf-prose { margin-bottom: var(--sf-space-5); }

.sf-classes-list {
    list-style: none;
    padding: 0;
    margin: 0;
    overflow: clip;
}

.sf-classes-list > li { box-shadow: inset 0 1px 0 var(--sf-hairline); }
.sf-classes-list > li:last-child {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

/* Mobile-first: the date owns its own line, the title the next, price and
   action share the last. At 860px the four cells become the one baseline
   grid the index uses, and every cell is pinned to its column so a row
   with no price or no button never shifts the shared column line. */
.sf-class-row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    column-gap: clamp(16px, 3vw, 40px);
    row-gap: var(--sf-space-2);
    padding: var(--sf-space-5) var(--sf-space-3);
    color: var(--sf-ink);
}

/* The date and the time span are each unbreakable, but the line may break
   between them: "SAT SEP 12 2027 / 8:00 AM TO 5:00 PM" never overflows a
   390px row, and never breaks inside a time. On the wide grid the whole
   cell is one line. */
.sf-class-date {
    grid-column: 1 / -1;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
}

.sf-class-day,
.sf-class-time { white-space: nowrap; }

.sf-class-body { grid-column: 1 / -1; min-width: 0; }

.sf-class-title {
    margin: 0;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-700);
    line-height: var(--sf-lh-800);
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-900) + var(--sf-track-offset-display));
    text-transform: var(--sf-heading-transform);
    text-wrap: balance;
}

/* Instructor and note are one line of mono caps separated by a real comma
   in the markup (a screen reader hears it; a pseudo-element it would not). */
.sf-class-meta {
    margin: var(--sf-space-2) 0 0;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
}

/* The description is capped by measure only, never clamped: a dealer's
   "bring your own ammunition" line must never be cut mid-sentence. */
.sf-class-desc {
    margin-top: var(--sf-space-2);
    max-width: var(--sf-measure);
    color: var(--sf-text-soft);
    font-size: var(--sf-fs-300);
}

.sf-class-desc p { margin: 0; }

.sf-class-price {
    grid-column: 1;
    color: var(--sf-brand);
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-500);
    font-weight: var(--sf-heading-weight);
    white-space: nowrap;
}

.sf-class-action {
    grid-column: 2;
    justify-self: end;
    display: flex;
    align-items: baseline;
    gap: var(--sf-space-3);
    white-space: nowrap;
}

.sf-class-status {
    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);
}

/* The featured row's strip lives on wide screens only. */
.sf-class-feature { display: none; }

@media (min-width: 860px) {
    .sf-class-row {
        grid-template-columns: auto minmax(0, 1fr) auto auto;
        row-gap: 0;
    }

    .sf-class-date { grid-column: 1; grid-row: 1; white-space: nowrap; }
    .sf-class-body { grid-column: 2; grid-row: 1; }
    .sf-class-price { grid-column: 3; grid-row: 1; }
    .sf-class-action { grid-column: 4; grid-row: 1; }

    .sf-class-title { font-size: var(--sf-fs-900); line-height: var(--sf-lh-900); }
}

/* No hover nudge on the title: the row itself is not a link, and a title
   that moves under the pointer promises a click it cannot honour. The one
   interactive element in a row is the button, and it has its own state. */

@media (min-width: 1100px) {
    /* The block's ONE bleed: the next session's photo (or its ghost day
       numeral) on a strip that runs toward the right canvas edge. The
       gutter is reserved only on the row that actually carries the strip,
       and the row is at least as tall as the strip plus its own padding so
       the centred strip can never spill over the neighbouring rows. */
    .sf-class-row[data-featured] {
        padding-right: clamp(200px, 20vw, 340px);
        min-height: calc(clamp(180px, 18vw, 300px) * 3 / 4 + 2 * var(--sf-space-5));
    }

    .sf-class-feature {
        display: block;
        position: absolute;
        top: 50%;
        right: calc(-1 * min(var(--sf-gutter), (100vw - var(--sf-container)) / 2 + var(--sf-gutter)));
        transform: translateY(-50%);
        width: clamp(180px, 18vw, 300px);
        aspect-ratio: 4 / 3;
        overflow: clip;
    }

    .sf-class-feature:has(> img) {
        background: var(--sf-plate, #F2EFE9);
        border-radius: var(--sf-radius-sm);
        box-shadow: var(--sf-edge), var(--sf-elev-1);
    }

    .sf-class-feature img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
    }

    /* The ghost numeral starts at the strip's LEFT edge and keeps the
       gutter clear on the right: the strip runs past the canvas edge, so a
       right-aligned "12" would lose its 2 and read as "1". */
    .sf-class-feature-ghost {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        height: 100%;
        box-sizing: border-box;
        padding-right: var(--sf-gutter);
        font-family: var(--sf-font-heading);
        font-size: var(--sf-fs-numeral, clamp(3.5rem, 1.6rem + 8.5vw, 9rem));
        font-weight: var(--sf-display-weight);
        line-height: 1;
        letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
        font-variant-numeric: tabular-nums;
        color: var(--sf-numeral-ink, rgba(201, 164, 92, 0.16));
        user-select: none;
    }
}

/* ----------------------------------------------------------------
   range info

   Facts as a hairline rail, rates on the fee-tier grammar, the visit
   steps on the counter-band rail, prose in the body face, amenities as
   mono chips, and exactly ONE raised object: the reservation card.
   ---------------------------------------------------------------- */

.sf-range {
    display: grid;
    gap: var(--sf-space-7);
}

.sf-range-header .sf-block-heading { margin-bottom: var(--sf-space-4); }

/* Facts rail: the dealer's numbers (lanes, distance) at the fee-price
   scale over mono labels, seamed by hairlines, no boxes. */
.sf-range-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 0;
    margin: 0;
}

.sf-range-fact {
    padding: var(--sf-space-5) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-range-fact-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-soft);
}

.sf-range-fact-value {
    margin: var(--sf-space-2) 0 0;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: 1;
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    color: var(--sf-ink);
}

/* Rate tiers: the transfer fee-tier grammar, verbatim. */
.sf-range-rates {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr;
}

.sf-range-rate {
    background: transparent;
    padding: var(--sf-space-6) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-range-rate-price {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: 1;
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-1000) + var(--sf-track-offset-display));
    /* Ink, not brand: the accent moment is the reserve button. */
    color: var(--sf-ink);
}

.sf-range-rate-label {
    color: var(--sf-ink);
    font-family: var(--sf-font-heading);
    font-weight: var(--sf-heading-weight);
    font-size: var(--sf-fs-500);
    margin-top: var(--sf-space-2);
}

.sf-range-rate-note { color: var(--sf-text-soft); font-size: var(--sf-fs-300); margin-top: var(--sf-space-2); max-width: 34ch; }

/* The visit rail: the counter-band grammar (sunken ground, accent left
   rule, mono ordinals, drawn connectors). Informational, never animated. */
.sf-range-steps {
    list-style: none;
    margin: 0;
    padding: var(--sf-space-4) var(--sf-space-5);
    background: var(--sf-surface-sunken);
    box-shadow: inset 3px 0 0 var(--sf-rule-accent, var(--sf-brand));
    border-radius: var(--sf-radius-sm);
    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-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text);
}

.sf-range-step {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sf-space-2);
}

.sf-range-step:not(:last-child)::after {
    content: "";
    align-self: center;
    width: 14px;
    height: 1px;
    background: var(--sf-text-faint);
    margin-left: var(--sf-space-1);
}

/* Ordinals in ink: the rail's accent is its left rule and nothing else,
   and the small mono numerals need full contrast on the sunken ground. */
.sf-range-step-num {
    font-variant-numeric: tabular-nums;
    color: var(--sf-ink);
}

.sf-range-body {
    display: grid;
    gap: var(--sf-space-7);
}

.sf-range-main,
.sf-range-aside {
    display: grid;
    gap: var(--sf-space-6);
    align-content: start;
}

.sf-range-subhead {
    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-soft);
    margin-bottom: var(--sf-space-3);
}

.sf-range-hours .sf-hours-table { margin-block: 0 var(--sf-space-3); }

/* Amenity chips: mono caps in a hairline ring, plain text, no fill. */
.sf-range-amenities {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-space-2);
}

.sf-range-amenity {
    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 var(--sf-hairline);
    border-radius: var(--sf-radius-pill);
    padding: 5px 12px;
}

/* The reservation card is the block's one raised object, on the FFL-card
   argument: it is the single most valuable thing on the page for a range
   visitor, and it is interactive as a unit (the button and the phone), so
   it earns the box the rest of the block refuses. */
.sf-range-card {
    background: var(--sf-surface-raised);
    border-radius: var(--sf-radius);
    padding: var(--sf-card-pad);
    box-shadow: var(--sf-edge), var(--sf-elev-3), inset 3px 0 0 var(--sf-brand);
}

.sf-range-card p { margin-bottom: var(--sf-space-3); }
.sf-range-card p:last-child { margin-bottom: 0; }

.sf-range-card-eyebrow {
    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);
}

.sf-range-card-name { font-weight: 700; color: var(--sf-ink); }

.sf-range-card-phone {
    font-family: var(--sf-font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

@media (min-width: 860px) {
    .sf-range-facts { column-gap: clamp(24px, 4vw, 56px); }
    .sf-range-rates { grid-template-columns: repeat(3, 1fr); column-gap: clamp(24px, 4vw, 56px); }
}

@media (min-width: 1100px) {
    /* Asymmetric: prose on the wide track, the card and chips on the
       narrow one (law 1). The aside is first in the markup (it reads first
       on a phone), so the columns are placed explicitly here. */
    .sf-range-body {
        grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
        column-gap: clamp(40px, 6vw, 96px);
        align-items: start;
    }

    .sf-range-main { grid-column: 1; grid-row: 1; }
    .sf-range-aside { grid-column: 2; grid-row: 1; }
}

/* ================================================================
   ULTRAPLAN ROUND 9 - staff / logo / financing (renderer lane).
   Appended, never inserted.

   Three blocks on grammar that already ships: the asymmetric bento and
   the category-index hairline rows (staff), the brand-strip item/divider
   rail without its ticker (logo strip), the cta-band and trust-bar
   band|inline pair (financing). No keyframes, no opaque border, no raised
   object: the one accent moment across all three is the financing apply
   button. Foundation-owned tokens are referenced WITH FALLBACK and never
   defined here.
   ================================================================ */

/* ----------------------------------------------------------------
   staff bios

   grid: portrait plates on the bento grammar. At two columns the tracks
   are 7fr 5fr (law 1: never 1fr 1fr); with three or more members the
   first spans both tracks as a lead composition, portrait on the wide
   track and the copy bottom-aligned on the narrow one. list: hairline
   rows with an 88px portrait, for long bios. Names in the heading face,
   roles and credentials in mono caps (the foundation identifier list
   sets the face), no card boxes.
   ---------------------------------------------------------------- */

.sf-staff-header { margin-bottom: var(--sf-space-6); }

.sf-staff-header .sf-block-heading { margin-bottom: var(--sf-space-4); }

.sf-staff-header .sf-prose { color: var(--sf-text-soft); }

.sf-staff-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: var(--sf-space-7) clamp(24px, 4vw, 56px);
}

.sf-staff-member { min-width: 0; }

/* The portrait plate: square, on the theme plate ground, radius-sm, lit
   edge and the first elevation (the category-nav thumb recipe). The
   ratio is locked on the MODULE so a bad upload cannot move the grid. */
.sf-staff-photo {
    display: grid;
    place-items: center;
    aspect-ratio: var(--sf-ratio-square, 1 / 1);
    background: var(--sf-plate, #F2EFE9);
    border-radius: var(--sf-radius-sm);
    overflow: clip;
    box-shadow: var(--sf-edge), var(--sf-elev-1);
    margin-bottom: var(--sf-space-4);
}

.sf-staff-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* No photo: the initials in the display face, in the deep plate ink at
   reduced opacity. The plate ground is light on EVERY theme (it is the
   media plate, not the page ground), so the ink has to be the plate's own
   dark partner rather than the theme's faint text, which on a gunmetal
   theme is a light tint and fell to ~2.6:1 against the plate. */
.sf-staff-initials {
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-800);
    line-height: 1;
    font-weight: var(--sf-display-weight);
    letter-spacing: calc(var(--sf-track-800) + var(--sf-track-offset-display));
    color: var(--sf-plate-deep, #161310);
    opacity: .62;
    user-select: none;
}

/* A member with no portrait keeps a modest plate: an empty square the
   width of a whole grid track is a hole, not a placeholder. */
.sf-staff-member[data-photo="none"] .sf-staff-photo { max-width: 200px; }

.sf-staff-name {
    margin: 0;
    font-family: var(--sf-font-heading);
    font-size: var(--sf-fs-600);
    line-height: var(--sf-lh-600);
    font-weight: var(--sf-heading-weight);
    letter-spacing: calc(var(--sf-track-600) + var(--sf-track-offset));
    text-transform: var(--sf-heading-transform);
    text-wrap: balance;
    color: var(--sf-ink);
}

/* Role and credentials both at fs-200 in mono caps; the role is the
   primary metadata and reads in the soft ink, the credentials step back
   to faint. */
.sf-staff-role,
.sf-staff-credentials {
    margin: var(--sf-space-2) 0 0;
    font-size: var(--sf-fs-200);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
}

.sf-staff-credentials { color: var(--sf-text-faint); }

/* The bio is capped by measure only, never clamped. */
.sf-staff-bio {
    margin-top: var(--sf-space-3);
    font-size: var(--sf-fs-300);
    color: var(--sf-text-soft);
}

/* list: hairline rows, the small portrait on a fixed track, every copy
   line pinned to its own max-content row so a member with no role never
   shifts the shared column line. */
.sf-staff[data-layout="list"] .sf-staff-list { gap: 0; }

.sf-staff[data-layout="list"] .sf-staff-member {
    display: grid;
    grid-template-columns: 88px minmax(0, 1fr);
    grid-template-rows: repeat(4, max-content);
    column-gap: clamp(16px, 3vw, 32px);
    align-items: start;
    padding: var(--sf-space-5) 0;
    box-shadow: inset 0 1px 0 var(--sf-hairline);
}

.sf-staff[data-layout="list"] .sf-staff-member:last-child {
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-staff[data-layout="list"] .sf-staff-photo {
    grid-column: 1;
    grid-row: 1 / -1;
    width: 88px;
    margin-bottom: 0;
}

.sf-staff[data-layout="list"] .sf-staff-initials { font-size: var(--sf-fs-600); }

.sf-staff[data-layout="list"] .sf-staff-name { grid-column: 2; grid-row: 1; }
.sf-staff[data-layout="list"] .sf-staff-role { grid-column: 2; grid-row: 2; }
.sf-staff[data-layout="list"] .sf-staff-credentials { grid-column: 2; grid-row: 3; }
.sf-staff[data-layout="list"] .sf-staff-bio { grid-column: 2; grid-row: 4; }

@media (min-width: 860px) {
    .sf-staff[data-layout="grid"] .sf-staff-list {
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    }

    /* The lead: the first member spans both tracks ONLY when there are
       three or more (:first-child:nth-last-child(n+3)) AND it has a
       portrait (the partial stamps data-photo="none" otherwise: an initials
       plate is not a lead composition); with two, each keeps its own track
       and the 7/5 split carries the asymmetry. Inside
       the lead the portrait takes the wide track and the copy sits on the
       narrow one, bottom-aligned (the pullquote rule: the two columns
       never mirror). Rows are max-content so an absent role or bio
       collapses to nothing; the leading 1fr row absorbs the portrait's
       height above the copy. */
    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) {
        grid-column: 1 / -1;
        display: grid;
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
        grid-template-rows: minmax(0, 1fr) repeat(4, max-content);
        column-gap: clamp(24px, 4vw, 56px);
    }

    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) .sf-staff-photo {
        grid-column: 1;
        grid-row: 1 / -1;
        margin-bottom: 0;
    }

    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) .sf-staff-name {
        grid-column: 2;
        grid-row: 2;
        font-size: var(--sf-fs-700);
        line-height: var(--sf-lh-700);
        letter-spacing: calc(var(--sf-track-700) + var(--sf-track-offset));
    }

    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) .sf-staff-role { grid-column: 2; grid-row: 3; }
    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) .sf-staff-credentials { grid-column: 2; grid-row: 4; }
    .sf-staff[data-layout="grid"] .sf-staff-member:first-child:nth-last-child(n+3):not([data-photo="none"]) .sf-staff-bio { grid-column: 2; grid-row: 5; }

    .sf-staff[data-layout="list"] .sf-staff-member { column-gap: clamp(24px, 4vw, 56px); }
}

/* ----------------------------------------------------------------
   logo strip

   The brand-strip item/divider rail, without the ticker: dealer-uploaded
   marks capped at 40px tall like the header logo, hairline verticals,
   scroll-snap on narrow screens. The intrinsic width/height attributes
   come from the resolver (CLS ~0).

   mono normalizes every mark to the section's ink. On a light ground
   that is grayscale at reduced opacity (internal detail survives); on a
   dark ground the mark is flattened to a light silhouette, the
   .sf-logo-forced-light idea from the header. WHICH ground is decided by
   the partial, not here: CSS cannot select on a computed color-scheme
   (the theme's polarity is a token value, not a selector, and style()
   container queries are not yet safe to rely on), so the partial reads
   the section's ground token and stamps data-ground="dark|light" with
   the header's own luminance rule. .sf-bg-dark is dark on every theme
   and doubles as a selector-level backstop.
   ---------------------------------------------------------------- */

.sf-logostrip-header { margin-bottom: var(--sf-space-4); }

.sf-logostrip-header .sf-block-heading {
    margin-bottom: 0;
    font-size: var(--sf-fs-600);
}

.sf-logostrip-track {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-logostrip-item {
    flex: 0 0 auto;
    scroll-snap-align: start;
    display: flex;
    align-items: center;
    box-shadow: inset 1px 0 0 var(--sf-hairline);
}

.sf-logostrip-track > .sf-logostrip-item:first-child { box-shadow: none; }

/* The padding sits on the link/span, not the cell, so the whole cell is
   the hit target: 40px mark + two space-5 steps clears 44px on its own. */
.sf-logostrip-item > a,
.sf-logostrip-item > span {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sf-space-2);
    padding: var(--sf-space-5) clamp(20px, 3vw, 48px);
    color: inherit;
    text-decoration: none;
}

/* A fixed 40px height with auto width: the box exists before the bytes
   arrive (the attributes reserve a ratio, this reserves the height), and
   the real ratio takes over on load. contain keeps a very wide mark true
   inside the 200px cap instead of squashing it. */
.sf-logostrip-item img {
    display: block;
    height: 40px;
    width: auto;
    max-width: 200px;
    object-fit: contain;
}

.sf-logostrip-label {
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-faint);
    white-space: nowrap;
}

.sf-logostrip[data-style="mono"] img { filter: grayscale(1) opacity(0.82); }

.sf-logostrip[data-style="mono"][data-ground="dark"] img,
.sf-bg-dark .sf-logostrip[data-style="mono"] img {
    filter: grayscale(1) brightness(0) invert(1) opacity(0.85);
}

.sf-bg-dark .sf-logostrip-label { color: var(--sf-dark-text); }

/* The one motion in the three blocks: a linked mark lifts on hover,
   transform only, opt-in behind the no-preference query. */
@media (prefers-reduced-motion: no-preference) {
    .sf-logostrip-item > a {
        transition: transform var(--sf-dur-2) var(--sf-ease-out);
    }

    .sf-logostrip-item > a:hover,
    .sf-logostrip-item > a:focus-visible {
        transform: translateY(-2px);
    }
}

/* ----------------------------------------------------------------
   financing band

   band: the cta-band recipe, the surface ground with the brand radial
   tint at 20% and the 3px brand rule down the start edge, the brand
   apply button as the block's one accent moment, no elevation (nothing
   here needs a raised object). Never a solid brand slab: on a brand
   section it would vanish, and the page already has one brand plane.
   inline: the trust-bar pattern, a hairline row on the section ground.
   Both keep the dealer's own button style. Never animated. The
   disclosure renders in the body face at fs-200 under the copy, at the
   reading measure.

   Vertical rhythm is the owl (margin on every following child), NOT
   row-gap: at 860px the button spans rows that may be empty (no provider,
   no body) and a row-gap would pay for those phantom rows.
   ---------------------------------------------------------------- */

.sf-financing {
    display: grid;
    row-gap: 0;
}

.sf-financing > * + * { margin-top: var(--sf-space-3); }

.sf-financing-heading {
    margin: 0;
    font-size: var(--sf-fs-800);
    max-width: 20ch;
}

.sf-financing-provider {
    margin: 0;
    font-size: var(--sf-fs-100);
    text-transform: uppercase;
    letter-spacing: var(--sf-track-caps);
    color: var(--sf-text-soft);
}

.sf-financing-body { color: var(--sf-text); }

.sf-financing > .sf-btn { justify-self: start; }

.sf-financing-disclosure {
    font-size: var(--sf-fs-200);
    color: var(--sf-text-soft);
}

.sf-financing-disclosure .sf-richtext > * + * { margin-top: 0.5em; }

.sf-financing[data-style="band"] {
    position: relative;
    overflow: hidden;
    color: var(--sf-ink);
    background:
        radial-gradient(120% 140% at 8% 0%, color-mix(in srgb, var(--sf-brand) 20%, var(--sf-surface)), var(--sf-surface) 70%);
    border-radius: var(--sf-radius-lg);
    padding: clamp(32px, 5vw, 64px) clamp(24px, 4vw, 56px);
    box-shadow: var(--sf-edge);
}

.sf-financing[data-style="band"]::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: linear-gradient(var(--sf-brand), transparent);
}

/* Inside a brand section the tint would read as a second brand plane, so
   the band goes flat surface with the surface inks restated at full
   strength (the foundation paints p/li on-brand on that ground; these
   outrank it by specificity). */
.sf-bg-brand .sf-financing[data-style="band"] {
    background: var(--sf-surface);
    color: var(--sf-ink);
}

.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-heading { color: var(--sf-ink); }

.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-provider,
.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-disclosure,
.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-disclosure :is(p, li) {
    color: var(--sf-text-soft);
}

.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-body,
.sf-bg-brand .sf-financing[data-style="band"] .sf-financing-body :is(p, li) {
    color: var(--sf-text);
}

.sf-financing[data-style="inline"] {
    padding-block: var(--sf-space-5);
    box-shadow: inset 0 1px 0 var(--sf-hairline), inset 0 -1px 0 var(--sf-hairline);
}

.sf-financing[data-style="inline"] .sf-financing-heading { font-size: var(--sf-fs-600); }

@media (min-width: 860px) {
    /* Copy on the wide track, the button on the narrow one, centred
       against the copy; the disclosure runs under both. Rows are auto and
       row-gap is 0, so an absent provider or body collapses to nothing
       and the button's spanned rows cost no gap; the owl margin comes off
       the button since it no longer follows anything in flow. */
    .sf-financing {
        grid-template-columns: minmax(0, 1fr) auto;
        column-gap: var(--sf-space-9);
        align-items: center;
    }

    .sf-financing-heading,
    .sf-financing-provider,
    .sf-financing-body { grid-column: 1; }

    .sf-financing > .sf-btn {
        grid-column: 2;
        grid-row: 1 / span 3;
        align-self: center;
        margin-top: 0;
    }

    .sf-financing-disclosure { grid-column: 1 / -1; }
}

/* ================================================================
   ULTRAPLAN ROUND 9 - video. Appended, never inserted.

   The video-embed block: one YouTube or Vimeo player in a ratio-locked
   plate. The plate carries the theme plate ground while the iframe loads
   (the same edge + first elevation the gallery tile wears), so the slot
   never flashes white on a dark theme and never moves the layout (CLS 0:
   the MODULE reserves the 16/9 box, not the iframe). No script, no poster
   fetch, no motion. Foundation-owned tokens are referenced WITH FALLBACK
   and never defined here.
   ================================================================ */

/* Widths: content = a capped player width (760px, a comfortable 16/9 at
   the reading distance; a ch measure is a prose unit and made the player
   too narrow), wide = the full section container. */
.sf-video {
    margin: 0;
    max-width: min(100%, 760px);
}

.sf-video[data-width="wide"] { max-width: 100%; }

.sf-video-frame {
    position: relative;
    aspect-ratio: var(--sf-ratio-video, 16 / 9);
    background: var(--sf-plate, #F2EFE9);
    border-radius: var(--sf-radius-sm, 6px);
    overflow: clip;
    box-shadow: var(--sf-edge), var(--sf-elev-1);
}

/* The iframe fills the plate; the user agent's 2px inset border is a
   frame around a frame and goes. border-width (not a border shorthand):
   nothing here separates by border. */
.sf-video-player {
    display: block;
    width: 100%;
    height: 100%;
    border-width: 0;
}

/* The focus ring is drawn on the PLATE, not the iframe: overflow: clip on
   the plate would swallow a ring on the iframe, and the plate's rounded
   corner is the shape the ring should follow. The two-tone pair is the
   foundation's own focus recipe. */
.sf-video-frame:has(> .sf-video-player:focus-visible) {
    outline: 2px solid var(--sf-focus);
    outline-offset: 2px;
    box-shadow: var(--sf-edge), var(--sf-elev-1), 0 0 0 6px var(--sf-focus-contrast);
}

.sf-video-player:focus-visible {
    outline: none;
    box-shadow: none;
}

/* PREVIEW ONLY (the partial renders it only under a preview token): the
   empty plate carries a mono hint where the player will sit. */
.sf-video-hint {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    margin: 0;
    padding: var(--sf-space-5, 20px);
    text-align: center;
    font-family: var(--sf-font-mono);
    font-size: var(--sf-fs-200);
    color: var(--sf-plate-deep, #161310);
    opacity: .72;
}

/* Caption in the body face at the small size, soft ink, on the reading
   measure the figure already holds. */
.sf-video-caption {
    margin-top: var(--sf-space-3, 12px);
    font-size: var(--sf-fs-200);
    color: var(--sf-text-soft);
}

.sf-video-caption .sf-richtext > * + * { margin-top: 0.5em; }
