/* ============================================================================
 *  Gallery page — design pass 2 (2026-05-14)
 *
 *  Loads AFTER photo-library.css per _main.php's per-template CSS pattern,
 *  so this file overrides photo-library.css gallery rules surgically.
 *
 *  Three moves:
 *  1. Hide caption text on gallery cards; lightbox keeps the caption.
 *  2. Restyle .gallery__filters into a dark-token panel that has real weight.
 *  3. Editorial polish on the header (eyebrow, rose rule, fluid display title).
 *
 *  DOMINANT MOMENT: the dark filter panel anchored beneath an oversized
 *  Schibsted title; photos breathe on the cream page background.
 * ============================================================================ */


/* ─── Lightbox tokens (editorial redesign) ──────────────────────────────────
 * Driven by the site-wide [data-theme] attribute on <html>. Light is the
 * default :root block; dark overrides via the data-theme cascade. All
 * lightbox CSS rules later in the file reference these via var(--lb-*).
 * ─────────────────────────────────────────────────────────────────────────── */

:root {
    --lb-scrim:        rgba(246, 239, 230, 0.92);
    --lb-rail-bg:      var(--paper, #fffaf3);
    --lb-fg:           var(--color-heading, #1a1814);
    --lb-fg-soft:      rgba(26, 24, 20, 0.65);
    --lb-rose:         var(--color-rose, #c3796f);
    --lb-rule:         rgba(26, 24, 20, 0.10);
    --lb-thumbs-bg:    rgba(26, 24, 20, 0.04);
    --lb-thumbs-rule:  rgba(26, 24, 20, 0.10);
    /* Chrome chips (close + prev/next) - dark glass on the cream scrim. */
    --lb-chrome-bg:        rgba(26, 24, 20, 0.62);
    --lb-chrome-bg-hover:  rgba(26, 24, 20, 0.82);
    --lb-chrome-border:    rgba(255, 255, 255, 0.12);
    --lb-chrome-fg:        #fff;
    /* Mobile CTA pair: filled Book = rose in light / teal in dark (site CTA
       language); Email = ghost/outline with a tint of the same accent. */
    --lb-book:        #b8807c;
    --lb-book-fg:     #ffffff;
    --lb-ghost-bd:    rgba(184, 128, 124, 0.55);
    --lb-ghost-fg:    #a06b66;
}

[data-theme="dark"] {
    --lb-scrim:        rgba(11, 22, 26, 0.94);
    --lb-rail-bg:      #0f2125;
    --lb-fg:           #f1ece4;
    --lb-fg-soft:      rgba(241, 236, 228, 0.65);
    --lb-rose:         #d4a0a0;
    --lb-rule:         rgba(241, 236, 228, 0.10);
    --lb-thumbs-bg:    rgba(255, 255, 255, 0.03);
    --lb-thumbs-rule:  rgba(255, 255, 255, 0.08);
    /* Chrome chips - stay dark in dark mode too. Light glass on the deep
       teal scrim makes the white icon disappear (both surfaces read as
       light). Dark chip + white icon = consistent high contrast in both
       themes. Border stays light so the chip's silhouette still reads. */
    --lb-chrome-bg:        rgba(0, 0, 0, 0.58);
    --lb-chrome-bg-hover:  rgba(0, 0, 0, 0.78);
    --lb-chrome-border:    rgba(255, 255, 255, 0.18);
    --lb-chrome-fg:        #fff;
    --lb-book:        #4daab8;
    --lb-book-fg:     #04252c;
    --lb-ghost-bd:    rgba(77, 170, 184, 0.45);
    --lb-ghost-fg:    #9fdbe4;
}

/* ─── Header polish ─────────────────────────────────────────────────────────
   DOMINANT MOMENT: editorial eyebrow → 5rem Schibsted title → rose hairline →
   stat strip stack. Signals curated work, not a CMS index page. */

.gallery {
    padding-block: clamp(3rem, 6vw, 5rem);
}

.gallery__header {
    position: relative;
    isolation: isolate;
    text-align: center;
    /* Tightened: once the hero carries visual mass (watermark + oversized stat),
       the header-to-grid gap no longer needs to read as a hollow void. */
    margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
    max-width: 56rem;
    margin-inline: auto;
    padding-block: clamp(1.5rem, 4vw, 3.25rem) clamp(1rem, 2vw, 1.75rem);
}

/* The ghost Kb monogram behind this heading was removed 2026-08-01 (Ben's call,
   matching contact + basic-page): the shared closing CTA already carries a Kb,
   so /gallery/ rendered the mark twice. Worth noting this one was load-bearing
   in a way the others were not - it gave a text-only header real visual mass,
   not just edge filler. The soft radial glow below is deliberately KEPT so the
   header still opens with depth rather than as a bare search-results heading. */
.gallery__header::before {
    content: "";
    position: absolute;
    z-index: -2;
    inset: -12% -18%;
    pointer-events: none;
    background: radial-gradient(ellipse 55% 62% at 50% 42%,
        color-mix(in srgb, var(--color-accent-glow) 10%, transparent) 0%, transparent 66%);
}
[data-theme="dark"] .gallery__header::before {
    background: radial-gradient(ellipse 55% 62% at 50% 42%,
        color-mix(in srgb, var(--color-accent-glow) 20%, transparent) 0%, transparent 66%);
}

.gallery__eyebrow {
    font-family: var(--font-body);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
    margin: 0 0 0.85rem;
    text-align: center;
    max-width: none;
    width: 100%;
}

.gallery__header > h1,
.gallery__header h1#gallery-heading {
    /* Velvet Cinema editorial serif hero (matches the pillar heroes:
       home .hero__heading, svcs-hero__title) - Fraunces, light weight, optical
       sizing, with a rose-deep serif-italic accent word. */
    font-family: var(--font-serif);
    font-optical-sizing: auto;
    font-size: clamp(2.75rem, 1.6rem + 5.5vw, 5rem);
    font-weight: 350;
    line-height: 1;
    letter-spacing: -0.015em;
    color: var(--color-heading);
    margin: 0 auto 0.75rem;
    text-wrap: balance;
    display: block;
    text-align: center;
}
.gallery__header > h1 em {
    font-style: italic;
    font-weight: 480;
    color: var(--color-rose-deep);
}

/* Rose accent rule beneath the title - centered block under the title. */
.gallery__header > h1 + .gallery__meta {
    position: relative;
    padding-top: 1.5rem;
}
.gallery__header > h1 + .gallery__meta::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 3rem;
    height: 2px;
    background: var(--color-rose);
}

.gallery__meta {
    font-family: var(--font-body);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
    margin: 0;
    text-align: center;
    /* style.css global rule `p { max-width: 70ch }` clips the p; override. */
    max-width: none;
    width: 100%;
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: baseline;
    gap: 0.6rem;
}

.gallery__stat-sep {
    color: var(--color-rose);
    font-size: 0.9rem;
    line-height: 1;
    transform: translateY(-1px);
    letter-spacing: 0;
}

/* Oversized editorial stat: the photo count is the hero's one number moment -
   a huge thin (300-weight) serif numeral with the small-caps label riding
   beside it, so the hero has a dominant visual instead of an even stat line. */
.gallery__stat-num {
    font-family: var(--font-display);
    font-weight: 300;
    font-size: clamp(2.25rem, 6vw, 3.75rem);
    letter-spacing: -0.02em;
    color: var(--color-heading);
    line-height: 0.85;
    margin-right: 0.3rem;
}

.gallery__active-filters {
    font-family: var(--font-body);
    font-size: 0.85rem;
    font-style: italic;
    color: var(--color-body);
    margin: 0.75rem 0 0;
    text-align: center;
    max-width: none;
    width: 100%;
}


/* ─── Filter pedestal (light-mode cream surface) ────────────────────────────
   Replaces the prior dark `#073542` panel that read as a dark-mode token leak
   on the cream page. The pedestal is a slightly-deeper cream surface, lifted
   on a soft shadow, edged with a rose hairline, with all controls in the
   page's native warm-brown palette. Reads as "page furniture," not a slab.

   DOMINANT MOMENT: the rose APPLY pill sitting flush-right against quiet
   cream controls — a single moment of color that pulls the eye to the action
   without dressing the whole panel in accent. */

.gallery__filters {
    background: var(--color-bg-elevated);
    border: 1px solid rgba(212, 160, 160, 0.35);
    border-radius: 10px;
    padding: clamp(1.25rem, 2.5vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
    margin-bottom: clamp(2.5rem, 4vw, 3.5rem);

    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: clamp(0.75rem, 1.5vw, 1.25rem) clamp(1rem, 2vw, 1.5rem);

    /* Soft pedestal lift; ground-line top, lifted body. */
    box-shadow:
        0 1px 0 rgba(58, 40, 40, 0.04) inset,
        0 14px 28px -22px rgba(58, 40, 40, 0.18);
    position: relative;
}

/* Atmospheric rose glow top-right - keeps the editorial signature, but at
   half the prior opacity so it whispers on cream instead of shouting. */
.gallery__filters::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(
        ellipse 55% 70% at 95% 10%,
        rgba(212, 160, 160, 0.10) 0%,
        transparent 65%
    );
    pointer-events: none;
}

.gallery__filter {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    font-family: var(--font-body);
    font-size: 0.85rem;
    position: relative;
    z-index: 1;
}

/* Direct-child selector only: when JS upgrades the native <select> to
   kb-custom-select, the trigger button contains its own <span> for the
   selected-value label - we don't want this rule to bleed into that. */
.gallery__filter > span {
    font-family: var(--font-body);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
}

.gallery__filter--checkbox {
    flex-direction: row;
    align-items: center;
    gap: 0.55rem;
    cursor: pointer;
    color: var(--color-body);
    font-family: var(--font-body);
    font-size: 0.82rem;
    letter-spacing: 0.02em;
    align-self: end;
    padding-block: 0.6rem;
}

/* Native <select> is hidden when JS upgrades it to a kb-custom-select
   (main.js initCustomSelects). The fallback styles below are the no-JS
   experience and a safety net while JS is initializing. */
.gallery__filter select {
    appearance: none;
    -webkit-appearance: none;
    padding: 0.7rem 2.4rem 0.7rem 0.95rem;
    background:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='none' stroke='%23c3796f' stroke-width='1.6' d='M1 1.5l5 5 5-5'/></svg>")
        no-repeat right 0.95rem center,
        var(--color-bg-elevated);
    background-size: 12px auto, auto;
    border: 1px solid rgba(212, 160, 160, 0.45);
    border-radius: 4px;
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: 0.9rem;
    line-height: 1.3;
    min-width: 12rem;
    cursor: pointer;
    transition: border-color 200ms ease, box-shadow 200ms ease;
}

/* Hide the native select once kb-custom-select has wrapped it.
   The existing rule in style.css is scoped to .contact-section; this
   generic rule covers any context (gallery, future surfaces). */
select.kb-select-hidden {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    height: 0;
    width: 0;
    overflow: hidden;
}

/* Custom-select trigger restyled for the gallery filter pedestal.
   The contact-section uses an underline-only trigger; the gallery uses a
   bordered/boxed input that matches the pedestal language. */
.gallery__filters .kb-custom-select {
    min-width: 12rem;
    flex: 0 0 auto;
}

.gallery__filters .kb-custom-select__trigger {
    /* Flex row keeps the label and caret on one line (the caret is an inline
       SVG child, not a background image); min-width matches the pre-JS native
       <select> below so the field doesn't shrink + the caret doesn't wrap when
       JS swaps the styled trigger in. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    min-width: 12rem;
    white-space: nowrap;
    padding: 0.7rem 0.95rem;
    background: var(--color-bg-elevated);
    border: 1px solid rgba(212, 160, 160, 0.45);
    border-radius: 4px;
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: var(--weight-regular);
    line-height: 1.3;
    letter-spacing: 0;
    text-transform: none;
    text-align: left;
    cursor: pointer;
    transition: border-color 200ms ease, box-shadow 200ms ease;
    /* Override contact-section underline-style defaults from style.css. */
    border-bottom: 1px solid rgba(212, 160, 160, 0.45);
}

.gallery__filters .kb-custom-select__trigger[data-has-value="false"] {
    color: var(--color-muted);
    font-style: italic;
}

@media (hover: hover) {
    .gallery__filters .kb-custom-select__trigger:hover {
        border-color: var(--color-rose);
        border-bottom-color: var(--color-rose);
        box-shadow: 0 2px 6px -2px rgba(212, 160, 160, 0.4);
    }
}

.gallery__filters .kb-custom-select__trigger[aria-expanded="true"] {
    border-color: var(--color-rose);
    border-bottom-color: var(--color-rose);
    box-shadow: 0 2px 6px -2px rgba(212, 160, 160, 0.4);
}

/* ─── Mobile filter: bottom sheet ────────────────────────────────────────────
   Mobile (<900px, JS-enhanced) swaps the stacked native form for a Filter pill +
   bottom sheet. No-JS keeps the native form (the pill is display:none until JS
   adds .is-enhanced). Desktop (>=900px) restores the native select row.
   DOMINANT MOMENT: the sheet rising over a dimmed gallery, accent-checked rows. */

/* Filter bar (pill + active chips). Hidden until JS enhances. Lives inside the
   centered header, so the pill + chips center directly under the stats and the
   photo grid rises into the reclaimed band. */
.gallery__filterbar { display: none; }
/* `html.js` (set pre-paint in _main.php) mirrors the end-state that photo-library.js
   reaches via .is-enhanced, so first paint already shows the compact pill and hides
   the tall native form - no post-load collapse, no CLS. .is-enhanced stays for the
   live wiring; both selectors drive the same display swap. */
.gallery__filterwrap.is-enhanced .gallery__filterbar,
:root.js .gallery__filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin-top: clamp(1.1rem, 2.2vw, 1.6rem);
}
/* sheet replaces native form (pre-collapsed under html.js to kill the jump) */
.gallery__filterwrap.is-enhanced .gallery__filters,
:root.js .gallery__filterwrap .gallery__filters { display: none; }

.gallery__filter-trigger {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--font-body);
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--color-heading);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-full);
    padding: 0.65rem 1rem;
    box-shadow: var(--shadow-lg);
    cursor: pointer;
}
.gallery__filter-trigger .ph-icon { width: 18px; height: 18px; flex: 0 0 auto; }
@media (prefers-reduced-motion: no-preference) {
    .gallery__filter-trigger { transition: transform 200ms ease, border-color 200ms ease; }
}
@media (hover: hover) {
    .gallery__filter-trigger:hover { transform: translateY(-1px); border-color: var(--color-border-hover); }
}
.gallery__filter-trigger:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }

.gallery__filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.35rem;
    border-radius: var(--radius-full);
    background: var(--color-accent);
    color: var(--color-btn-primary-text);
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}
.gallery__filter-count[hidden] { display: none; }

/* Active-filter chips: horizontally scrollable, tap × to remove. */
.gallery__active-chips {
    flex: 0 1 auto;
    max-width: 100%;
    display: flex;
    gap: 0.4rem;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.gallery__active-chips::-webkit-scrollbar { display: none; }
.gallery__active-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-body);
    font-size: 0.82rem;
    color: var(--color-heading);
    background: color-mix(in srgb, var(--color-accent) 14%, var(--color-bg-elevated));
    border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
    padding: 0.35rem 0.45rem 0.35rem 0.75rem;
    border-radius: var(--radius-full);
    white-space: nowrap;
}
.gallery__active-chip button {
    display: inline-flex;
    border: 0;
    background: none;
    cursor: pointer;
    color: var(--color-muted);
    padding: 0.15rem;
    border-radius: var(--radius-full);
    line-height: 0;
}
.gallery__active-chip button .ph-icon { width: 13px; height: 13px; }
.gallery__active-chip button:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }

/* ── The sheet ── */
.gallery-sheet {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    visibility: hidden;
    pointer-events: none;
}
.gallery-sheet.is-open { visibility: visible; pointer-events: auto; }

.gallery-sheet__scrim {
    position: absolute;
    inset: 0;
    background: rgba(40, 24, 24, 0.42);
    opacity: 0;
}
.gallery-sheet.is-open .gallery-sheet__scrim { opacity: 1; }
@supports (backdrop-filter: blur(2px)) {
    .gallery-sheet.is-open .gallery-sheet__scrim { backdrop-filter: blur(2px); }
}

.gallery-sheet__panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-width: 560px;
    margin: 0 auto;
    max-height: 88dvh;
    display: flex;
    flex-direction: column;
    background: var(--color-bg-elevated);
    border-top: 1px solid var(--color-border);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.28);
    transform: translateY(100%);
}
.gallery-sheet.is-open .gallery-sheet__panel { transform: translateY(0); }
@media (prefers-reduced-motion: no-preference) {
    .gallery-sheet__scrim { transition: opacity 300ms ease; }
    .gallery-sheet__panel { transition: transform 340ms cubic-bezier(0.16, 1, 0.3, 1); }
}

.gallery-sheet__grip { flex: 0 0 auto; display: flex; justify-content: center; padding: 0.65rem 0 0.15rem; }
.gallery-sheet__grip span {
    width: 2.5rem; height: 0.25rem; border-radius: var(--radius-full);
    background: color-mix(in srgb, var(--color-heading) 22%, transparent);
}

.gallery-sheet__head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.4rem 1.25rem 0.75rem;
}
.gallery-sheet__title {
    font-family: var(--font-display);
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-heading);
}
.gallery-sheet__head-actions { display: flex; align-items: center; gap: 0.25rem; }
.gallery-sheet__reset {
    font-family: var(--font-body);
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--color-link);
    background: none;
    border: 0;
    cursor: pointer;
    padding: 0.4rem 0.25rem;
}
.gallery-sheet__reset[hidden] { display: none; }
.gallery-sheet__reset:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.gallery-sheet__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    border: 1px solid var(--color-border);
    background: var(--color-bg-card);
    border-radius: var(--radius-full);
    padding: 0.4rem;
    cursor: pointer;
    color: var(--color-heading);
    line-height: 0;
}
.gallery-sheet__close .ph-icon { width: 18px; height: 18px; }
.gallery-sheet__close:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }

.gallery-sheet__body {
    flex: 1 1 auto;
    /* Vertical only - see .post-toc__sticky in inspiration.css. */
    overflow-x: hidden;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 0 1rem 0.5rem;
}
.gallery-sheet__facet { padding: 0.85rem 0.25rem 0.35rem; }
.gallery-sheet__facet-label {
    font-family: var(--font-body);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
    margin-bottom: 0.65rem;
    padding-left: 0.5rem;
}

/* Segmented Before/After toggle */
.gallery-sheet__seg {
    display: flex;
    gap: 0.35rem;
    background: var(--color-bg-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 0.3rem;
}
.gallery-sheet__seg-btn {
    flex: 1;
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--color-body);
    background: none;
    border: 0;
    border-radius: var(--radius-sm);
    padding: 0.7rem 0.5rem;
    cursor: pointer;
}
.gallery-sheet__seg-btn[aria-pressed="true"] {
    background: var(--color-accent);
    color: var(--color-btn-primary-text);
}
.gallery-sheet__seg-btn:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
    .gallery-sheet__seg-btn { transition: background 200ms ease, color 200ms ease; }
}

/* Option rows */
.gallery-sheet__opts { display: flex; flex-direction: column; gap: 0.2rem; }
.gallery-sheet__opt {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: 500;
    color: var(--color-heading);
    text-align: left;
    background: none;
    border: 0;
    border-radius: var(--radius-md);
    padding: 0.85rem 0.75rem;
    cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
    .gallery-sheet__opt { transition: background 150ms ease; }
}
@media (hover: hover) {
    .gallery-sheet__opt:hover { background: var(--color-bg-card); }
}
.gallery-sheet__opt:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: -2px; }
.gallery-sheet__opt[aria-pressed="true"] {
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
    font-weight: 600;
}
.gallery-sheet__tick {
    flex: 0 0 auto;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: var(--radius-full);
    border: 2px solid var(--color-border-hover);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--color-btn-primary-text);
}
.gallery-sheet__tick .ph-icon { width: 0.8rem; height: 0.8rem; fill: currentColor; opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
    .gallery-sheet__tick { transition: background 150ms ease, border-color 150ms ease; }
    .gallery-sheet__tick .ph-icon { transition: opacity 150ms ease; }
}
.gallery-sheet__opt[aria-pressed="true"] .gallery-sheet__tick {
    background: var(--color-accent);
    border-color: var(--color-accent);
}
.gallery-sheet__opt[aria-pressed="true"] .gallery-sheet__tick .ph-icon { opacity: 1; }

/* Sticky footer CTA */
.gallery-sheet__foot {
    flex: 0 0 auto;
    padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-elevated);
}
.gallery-sheet__cta {
    width: 100%;
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-btn-primary-text);
    background: var(--color-btn-primary-bg);
    border: 0;
    border-radius: var(--radius-md);
    padding: 1rem;
    cursor: pointer;
}
.gallery-sheet__cta:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
    .gallery-sheet__cta { transition: transform 200ms ease; }
}
@media (hover: hover) {
    .gallery-sheet__cta:hover { transform: translateY(-1px); }
}

/* Dark theme: deepen the scrim. Sheet surfaces ride tokens, so no other overrides. */
[data-theme="dark"] .gallery-sheet__scrim { background: rgba(0, 0, 0, 0.6); }

/* Desktop (>=900px): the same drawer, re-anchored to the right edge - full height,
   slides in from the right over the dimmed gallery. The Filter pill + chips stay
   (no native select row), which is what keeps the photos high on the page. */
@media (min-width: 900px) {
    .gallery-sheet__panel {
        top: 0;
        bottom: 0;
        left: auto;
        right: 0;
        margin: 0;
        width: min(560px, 94vw);
        max-width: none;
        max-height: none;
        height: 100%;
        border-top: 0;
        border-left: 1px solid var(--color-border);
        border-radius: var(--radius-xl) 0 0 var(--radius-xl);
        box-shadow: -12px 0 40px rgba(0, 0, 0, 0.28);
        transform: translateX(100%);
    }
    .gallery-sheet.is-open .gallery-sheet__panel { transform: translateX(0); }
    .gallery-sheet__grip { display: none; }
    .gallery-sheet__head { padding-top: 1rem; }

    /* The wider drawer earns two columns for the long Service + Team lists,
       so they read at a glance instead of scrolling. */
    .gallery-sheet__opts {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0.15rem 0.5rem;
    }
    .gallery-sheet__opt { padding: 0.7rem 0.65rem; font-size: 0.95rem; }
    .gallery-sheet__opt-label { line-height: 1.25; }
}

.gallery__filters .kb-custom-select__trigger:focus-visible {
    outline: 2px solid var(--color-rose);
    outline-offset: 2px;
    border-color: var(--color-rose);
    border-bottom-color: var(--color-rose);
}

.gallery__filters .kb-custom-select__chevron {
    color: var(--color-rose);
    opacity: 1;
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
}

/* Global styled-combobox option styling lives in style.css.
   The dropdown is portaled to <body>, so gallery-scoped selectors can't reach
   the option rows. Any colored-edge / hover-edge treatment goes there. */

.gallery__filter input[type="checkbox"] {
    width: 18px;
    height: 18px;
    cursor: pointer;
    accent-color: var(--color-rose);
}

/* Apply pill: the panel's one persistent accent. Solid rose, dark-brown text,
   anchored right of the controls. (No-JS inline form only; JS swaps in the
   filter sheet - see .gallery__filterwrap.is-enhanced rule below.) */
.gallery__filters .btn,
.gallery__filters button[type="submit"] {
    margin-left: auto;
    align-self: end;
    padding: 0.8rem 1.75rem;
    background: var(--color-rose);
    color: var(--color-heading);
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    box-shadow: 0 8px 18px -10px rgba(212, 160, 160, 0.55);
    transition: transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
                box-shadow 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
                background 200ms ease;
}

@media (hover: hover) {
    .gallery__filters .btn:hover,
    .gallery__filters button[type="submit"]:hover {
        transform: translateY(-2px);
        background: var(--color-cta-bg);
        box-shadow: 0 16px 24px -10px rgba(212, 160, 160, 0.55);
    }
}

.gallery__filter-clear {
    align-self: end;
    padding-block: 0.85rem;
    font-family: var(--font-body);
    font-size: 0.78rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-muted);
    text-decoration: none;
    border-bottom: 1px solid rgba(58, 40, 40, 0.22);
    transition: color 200ms ease, border-color 200ms ease;
}

@media (hover: hover) {
    .gallery__filter-clear:hover {
        color: var(--color-rose);
        border-bottom-color: var(--color-rose);
    }
}

.gallery__filter-clear:focus-visible {
    outline: 2px solid var(--color-rose);
    outline-offset: 4px;
    border-radius: 2px;
}

/* Mobile: stack everything full-width, drop the auto-margin push */
@media (max-width: 700px) {
    .gallery__filter,
    .gallery__filter select { width: 100%; }
    /* justify-content, not text-align: .btn is display:flex, so text-align does not move
       its children. Stretched to 100% the label sat against the left padding edge with up
       to 505px of empty pill beside it. Only the no-JS fallback renders this sheet. */
    .gallery__filters .btn,
    .gallery__filters button[type="submit"] { margin-left: 0; width: 100%; text-align: center; justify-content: center; }
    .gallery__filter-clear { align-self: flex-start; padding-block: 0.5rem 0; }
}

/* Dark-theme overrides for the filter pedestal.
   The light-mode rules above hardcode `#fff` on selects and a rose hairline
   that read correctly on the cream surface. Dark mode flips the panel surface
   to deep teal automatically (--color-bg-elevated), but the selects keep the
   white background and look like alien white slabs. These overrides re-skin
   the select chrome to belong on the dark surface. */
[data-theme="dark"] .gallery__filters {
    border-color: rgba(255, 255, 255, 0.08);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.04) inset,
        0 22px 38px -24px rgba(0, 0, 0, 0.55);
}

[data-theme="dark"] .gallery__filters::before {
    /* Slightly lower opacity so the rose halo whispers on the deeper surface. */
    background: radial-gradient(
        ellipse 55% 70% at 95% 10%,
        rgba(212, 160, 160, 0.14) 0%,
        transparent 65%
    );
}

[data-theme="dark"] .gallery__filter > span {
    /* Eyebrow rose-on-cream is too muddy on deep teal; use the dark-theme rose token. */
    color: var(--color-rose);
}

[data-theme="dark"] .gallery__filter select {
    /* Fallback styles for the no-JS / pre-init state. */
    background:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='none' stroke='%23d4a0a0' stroke-width='1.6' d='M1 1.5l5 5 5-5'/></svg>")
        no-repeat right 0.95rem center,
        rgba(255, 255, 255, 0.05);
    background-size: 12px auto, auto;
    border-color: rgba(212, 160, 160, 0.25);
    color: var(--color-body);
}

/* Dark-mode trigger: tinted glass over the dark filter surface. */
[data-theme="dark"] .gallery__filters .kb-custom-select__trigger {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(212, 160, 160, 0.28);
    border-bottom-color: rgba(212, 160, 160, 0.28);
    color: var(--color-body);
}

[data-theme="dark"] .gallery__filters .kb-custom-select__trigger[data-has-value="false"] {
    color: rgba(245, 235, 229, 0.55);
}

@media (hover: hover) {
    [data-theme="dark"] .gallery__filters .kb-custom-select__trigger:hover {
        background: rgba(255, 255, 255, 0.09);
        border-color: rgba(212, 160, 160, 0.55);
        border-bottom-color: rgba(212, 160, 160, 0.55);
        box-shadow: none;
    }
}

[data-theme="dark"] .gallery__filters .kb-custom-select__trigger[aria-expanded="true"] {
    background: rgba(255, 255, 255, 0.09);
    border-color: rgba(212, 160, 160, 0.55);
    border-bottom-color: rgba(212, 160, 160, 0.55);
}

[data-theme="dark"] .gallery__filter--checkbox {
    color: var(--color-body);
}

[data-theme="dark"] .gallery__filters .btn,
[data-theme="dark"] .gallery__filters button[type="submit"] {
    /* In dark mode --color-heading is white; rose pill with white text loses contrast
       and reads as a flat blob. Anchor the text to a deep brown so it pops. */
    color: #2a1a1a;
    box-shadow: 0 10px 22px -10px rgba(0, 0, 0, 0.55);
}

[data-theme="dark"] .gallery__filter-clear {
    color: rgba(255, 255, 255, 0.65);
    border-bottom-color: rgba(255, 255, 255, 0.22);
}

@media (hover: hover) {
    [data-theme="dark"] .gallery__filter-clear:hover {
        color: var(--color-rose);
        border-bottom-color: var(--color-rose);
    }
}


/* ─── Card chrome: chip on photo + attribution + date pill ───────────────────
 *  - On photo, top-left: rose category chip (e.g. "COLOR") injected via JS
 *    from data-service-label.
 *  - On photo, top-right: dark date pill (e.g. "APR 2025") injected via JS
 *    from data-date.
 *  - Below photo: stylist attribution line ("by Jordan Reasoner").
 *  - Long caption text stays hidden (lightbox renders it on click).
 *  ───────────────────────────────────────────────────────────────────────── */

.photo-card--gallery {
    margin-bottom: 0;
    position: relative;
}

/* Masonry column item: the card never splits across columns and carries the
   vertical rhythm (column-gap handles the horizontal gap). */
.photo-card--gallery {
    break-inside: avoid;
    margin-bottom: var(--space-4, 1rem);
}

.photo-card--gallery .photo-card__media {
    overflow: hidden;
    border-radius: 4px;
    display: block;
    position: relative;
    /* aspect-ratio: auto EXPLICITLY overrides the base .photo-card__media 3/4
       (photo-library.css) - omitting it isn't enough, the base would still win.
       The media then sizes to the image's native shape (width/height attrs). */
    aspect-ratio: auto;
    background: rgba(58, 40, 40, 0.06); /* placeholder while image loads */
}

.photo-card--gallery .photo-card__img {
    /* Smooth fade-in: starts transparent, JS adds .is-loaded on load event. */
    opacity: 0;
    transform: scale(1.02);
    transition: opacity 700ms cubic-bezier(0.16, 1, 0.3, 1),
                transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
    width: 100%;
    height: auto;          /* native aspect, no crop */
    display: block;
}

.photo-card--gallery .photo-card__img.is-loaded {
    opacity: 1;
    transform: scale(1);
}

@media (hover: hover) {
    .photo-card--gallery:hover .photo-card__img.is-loaded {
        transform: scale(1.04);
    }
}

/* Category chip - injected by JS as a child of .photo-card__media.
   Visual hierarchy: category is ambient context (every card has one), so
   it reads as white text with a soft shadow over the image - no pill bg.
   That gives the rose status pill (FEATURED / NEW / SEASONAL) sole ownership
   of the corner-pill language on the rare card that gets one. Different
   shapes = different meanings. */
.photo-card__chip {
    position: absolute;
    top: 0.85rem;
    left: 0.85rem;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    font-family: var(--font-body);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: #fff;
    text-shadow:
        0 1px 4px rgba(0, 0, 0, 0.55),
        0 1px 1px rgba(0, 0, 0, 0.35);
    pointer-events: none;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 360ms ease 220ms, transform 360ms cubic-bezier(0.16, 1, 0.3, 1) 220ms;
}

/* :has() not the `~` sibling combinator: the webp <picture> wrapper makes the
   img a grandchild of __media, so `.photo-card__img ~ .chip` no longer matches
   (they stopped being siblings) and the overlay stayed at opacity:0. */
.photo-card--gallery .photo-card__media:has(.photo-card__img.is-loaded) .photo-card__chip,
.photo-card__chip.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* Chip <-> status-pill collision: on narrow 2-col cards the top-left chip and
   the top-right pill overlap (~23px at <=430px). The pill (FEATURED / NEW /
   SEASONAL) is the rarer, higher-priority label, so drop the ambient category
   chip whenever a pill is present at narrow widths; restore it once the card is
   wide enough to hold both. */
.photo-card__media:has(.photo-card__status-pill) .photo-card__chip {
    display: none;
}
@media (min-width: 431px) {
    .photo-card__media:has(.photo-card__status-pill) .photo-card__chip {
        display: inline-flex;
    }
}

/* DOMINANT MOMENT: stylist name in display serif on a low gradient ribbon at
   the photo's bottom-left — recognizable bench, not stock gallery. */

/* Status pill (gallery v3) — injected by JS, top-right.
   At most ONE per card; CSS variants set the color stop.
   Priority: FEATURED > BA > NEW > SEASONAL (enforced in JS).
   Each card has zero pills by default; most cards never receive one. */
.photo-card__status-pill {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 0.7rem 0.32rem;
    font-family: var(--font-body);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #2a1a1a;
    background: var(--color-rose);
    border-radius: 999px;
    box-shadow: 0 6px 14px -8px rgba(58, 40, 40, 0.4);
    pointer-events: none;
    opacity: 0;
    transform: translateY(-4px);
}

@media (prefers-reduced-motion: no-preference) {
    .photo-card__status-pill {
        transition: opacity 360ms ease 280ms,
                    transform 360ms cubic-bezier(0.16, 1, 0.3, 1) 280ms;
    }
}

.photo-card--gallery .photo-card__media:has(.photo-card__img.is-loaded) .photo-card__status-pill,
.photo-card__status-pill.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* Color variants — only the persistent FEATURED uses the gold token; BA
   leans on the existing brand teal, NEW keeps rose default, SEASONAL uses
   the muted eyebrow rose-brown so it reads as ambient, not assertive. */
.photo-card__status-pill--featured {
    background: var(--color-warning);
    color: #fff;
    box-shadow: 0 8px 18px -8px rgba(166, 122, 26, 0.55);
}

/* .photo-card__status-pill--ba intentionally absent — BA is now surfaced inside
   the lightbox (planned v2) rather than as a grid badge. The partial may still
   emit `ba` in data-flags, but gallery.php's PILL_PRIORITY does not consume it. */

.photo-card__status-pill--new {
    /* default rose */
}

.photo-card__status-pill--seasonal {
    background: var(--color-eyebrow);
    color: #fff;
    box-shadow: 0 8px 18px -8px rgba(138, 92, 92, 0.5);
}

/* Stylist overlay (gallery v3) — bottom-left, white text on a bottom-up dark
   gradient so legibility holds against any background. Always visible (touch
   users must read it), not hover-gated. The wrapping div anchors both the
   gradient and the text positioning. */
.photo-card__stylist-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    padding: 1.75rem 0.85rem 0.75rem;
    background: linear-gradient(to top,
        rgba(7, 22, 28, 0.78) 0%,
        rgba(7, 22, 28, 0.55) 45%,
        rgba(7, 22, 28, 0) 100%);
    color: #fff;
    line-height: 1.25;
    pointer-events: none;
    opacity: 0;
    transform: translateY(6px);
}

@media (prefers-reduced-motion: no-preference) {
    .photo-card__stylist-overlay {
        transition: opacity 420ms ease 340ms,
                    transform 480ms cubic-bezier(0.16, 1, 0.3, 1) 340ms;
    }
}

.photo-card--gallery .photo-card__media:has(.photo-card__img.is-loaded) .photo-card__stylist-overlay,
.photo-card__stylist-overlay.is-visible {
    opacity: 1;
    transform: translateY(0);
}

.photo-card__stylist-overlay__prefix {
    display: inline;
    font-family: var(--font-body);
    font-size: 0.74rem;
    font-weight: 300;
    letter-spacing: 0.04em;
    color: rgba(255, 255, 255, 0.78);
}

.photo-card__stylist-overlay__name {
    --hit-expander: 1;
    position: relative;
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 600;
    color: #fff;
    letter-spacing: -0.005em;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    pointer-events: auto;
}
/* Vertical hit-expander on the photo-overlay name link; visible label unchanged. */
.photo-card__stylist-overlay__name::after {
    content: "";
    position: absolute;
    top: 50%; left: 0;
    transform: translateY(-50%);
    width: 100%; height: 100%;
    min-height: 44px;
}

@media (prefers-reduced-motion: no-preference) {
    .photo-card__stylist-overlay__name {
        transition: border-color 220ms ease;
    }
}

@media (hover: hover) {
    .photo-card__stylist-overlay__name:hover {
        border-bottom-color: rgba(255, 255, 255, 0.75);
    }
}

.photo-card__stylist-overlay__name:focus-visible {
    outline: 2px solid var(--color-rose);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Figcaption below the photo is fully redundant once the stylist moves
   on-photo. Suppress it in the gallery context; the partial's contract
   stays intact on every other surface. */
.photo-card--gallery .photo-card__caption {
    display: none;
}


/* ─── Gallery grid breathing room (modest density tune) ─────────────────── */

.gallery__grid {
    column-gap: clamp(0.75rem, 1.5vw, 1.5rem);
}


/* ─── Polish pass (2026-05-15) ───────────────────────────────────────────── */

/* Card lift on hover — translateY + softer shadow, gated on hover + motion.
   photo-library.css's shadow-only :hover fires on touch tap and lacks lift;
   this rule scopes the lift to the gallery context and the right inputs. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .photo-card--gallery .photo-card__media {
        transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1),
                    box-shadow 320ms ease;
    }
    .photo-card--gallery:hover .photo-card__media {
        transform: translateY(-3px);
        box-shadow: 0 14px 36px -12px rgba(58, 40, 40, 0.22);
    }
}

/* Animated underline on "Clear" — replaces the color-only hover. */
.gallery__filter-clear { position: relative; }
.gallery__filter-clear::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -2px;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .gallery__filter-clear::after {
        transition: transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
    }
    .gallery__filter-clear:hover::after { transform: scaleX(1); }
}

/* Two-tone radial atmosphere behind the gallery — rose halo top-center,
   teal whisper bottom-right. Opacities sit in the 4-7% range so the page
   stays cream-dominant but no longer reads as flat. */
.gallery {
    position: relative;
    isolation: isolate;
    /* Contain the decorative bleed. .gallery__header::before is a soft radial
       glow at inset:-12% -18%, so at 1024px it ran from -97px to 1121px and
       set document scrollWidth to 1121 - 97px of phantom horizontal scroll on
       tablets and small laptops, with nothing visible to explain it (every
       element the sweep could see was inside the position:fixed filter sheet,
       which does not contribute to document scroll at all).

       clip, not hidden: `overflow: clip` contains the paint WITHOUT creating a
       scroll container, so the sticky filter bar inside still sticks. Scoped to
       the x axis so vertical bleed between sections is untouched, and the glow
       looks identical - the only thing removed was off-screen. */
    overflow-x: clip;
}
.gallery::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse 60% 30% at 50% 12%,
            rgba(212, 160, 160, 0.07) 0%, transparent 70%),
        radial-gradient(ellipse 45% 25% at 80% 60%,
            color-mix(in srgb, var(--color-accent-glow) 4%, transparent) 0%, transparent 70%);
    pointer-events: none;
    z-index: -1;
}


/* ─── Page-load entrance ─────────────────────────────────────────────────── */

@media (prefers-reduced-motion: no-preference) {
    .gallery__header,
    .gallery__filters {
        opacity: 0;
        transform: translateY(14px);
        animation: gallery-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
    }
    .gallery__header  { animation-delay: 80ms; }
    .gallery__filters { animation-delay: 220ms; }
}

@keyframes gallery-rise {
    to { opacity: 1; transform: translateY(0); }
}

/* ─── Multi-photo session cards (gallery-session-aware) ─────────────────────
   A "+N more" whisper sits bottom-right of the photo; two faint paper edges
   peek behind the card via pseudos. Edges shift further out on hover, motion-
   gated. Single-photo sessions (no data-session-count or value=1) opt out. */

.photo-card--gallery[data-session-count]:not([data-session-count="1"]) {
    position: relative;
}

.photo-card--gallery[data-session-count]:not([data-session-count="1"])::before,
.photo-card--gallery[data-session-count]:not([data-session-count="1"])::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--color-card-edge, #d8cbc4);
    z-index: -1;
    pointer-events: none;
    transition: transform 320ms ease;
}

.photo-card--gallery[data-session-count]:not([data-session-count="1"])::before {
    transform: translate(4px, 4px);
    opacity: 0.55;
}

.photo-card--gallery[data-session-count]:not([data-session-count="1"])::after {
    transform: translate(8px, 8px);
    opacity: 0.3;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .photo-card--gallery[data-session-count]:not([data-session-count="1"]):hover::before {
        transform: translate(6px, 6px);
    }
    .photo-card--gallery[data-session-count]:not([data-session-count="1"]):hover::after {
        transform: translate(11px, 11px);
    }
}

.photo-card__session-count {
    position: absolute;
    right: 0.75rem;
    bottom: 0.75rem;
    z-index: 3;
    font-family: var(--font-body);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
    pointer-events: none;
}

/* ─── Lightbox carousel (editorial redesign, mobile-first) ──────────────────
   Base rules are mobile (<900px): single-column card, thumbs as a horizontal
   strip below the photo, info rail stacked below the thumbs.
   The desktop @media (min-width: 900px) block at the bottom flips to the
   three-region card grid.
   ───────────────────────────────────────────────────────────────────────────── */

/* Backdrop */
.gallery-lightbox:not([hidden]) {
    background: var(--lb-scrim);
    backdrop-filter: blur(24px) saturate(140%);
    display: grid;
    place-items: center;
    padding: 0;
}

/* Card frame - mobile baseline: single column, full viewport.
   Rows = photo(auto) / thumbs(auto) / info(fills remaining). With order:1/2/3
   on the children, grid auto-places photo->r1, thumbs->r2, info->r3, so the
   photo sits flush to the top and the info rail absorbs the slack. 100dvh (not
   100vh) keeps the Book + Email buttons clear of the mobile browser chrome. */
.gallery-lightbox__card {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
    width: 100%;
    height: 100vh;            /* fallback for browsers without dvh */
    height: 100dvh;
    background: var(--lb-rail-bg);
    border-radius: 0;
    overflow: hidden;
    box-shadow:
        0 40px 80px -30px rgba(0, 0, 0, 0.45),
        0 0 0 1px var(--lb-rule);
    position: relative;
}

/* Thumb strip - mobile: horizontal row, ordered below the photo */
.gallery-lightbox__thumbs {
    background: var(--lb-thumbs-bg);
    border-top: 1px solid var(--lb-thumbs-rule);
    border-bottom: 1px solid var(--lb-thumbs-rule);
    padding: 0.5rem;
    display: flex;
    flex-direction: row;
    gap: 0.45rem;
    overflow-x: auto;
    overflow-y: hidden;
    align-items: center;
    justify-content: center;
    order: 2;
}

.gallery-lightbox__thumb {
    flex: 0 0 auto;
    width: 38px;
    aspect-ratio: 3 / 4;
    border: 2px solid transparent;
    border-radius: 3px;
    overflow: hidden;
    background: var(--lb-rail-bg);
    padding: 0;
    cursor: pointer;
    opacity: 0.55;
    position: relative;
    transition: opacity 180ms ease, border-color 180ms ease;
}

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

.gallery-lightbox__thumb.is-active {
    opacity: 1;
    border-color: var(--lb-rose);
}

.gallery-lightbox__thumb:focus-visible {
    outline: 2px solid var(--lb-fg);
    outline-offset: 2px;
}

.gallery-lightbox__thumb--pair::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 1px;
    background: rgba(255, 255, 255, 0.85);
    pointer-events: none;
}

.gallery-lightbox__thumb-label {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 1px;
    text-align: center;
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.5rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    pointer-events: none;
}

/* Photo stage */
.gallery-lightbox__photo-stage {
    position: relative;
    display: grid;
    place-items: center;
    overflow: hidden;
    padding: 1rem;
    order: 1;
}

.gallery-lightbox__photo {
    margin: 0;
    /* Shrink-wrap to the image (or BA frame) so the figure's corners ARE the
       photo's corners. Absolute-positioned chip + type-pill children then
       overlay the photo correctly. photo-stage's place-items:center handles
       centering. */
    position: relative;
    display: inline-block;
    max-width: 100%;
    max-height: 100%;
}

/* WebP <picture> wrapper stays layout-invisible so the figure keeps shrink-
   wrapping to the img and every `.gallery-lightbox__photo img` / `.ba-img` rule
   still applies (same fix as .lib-picture). */
.gallery-lightbox__picture {
    display: contents;
}

.gallery-lightbox__photo img {
    /* Hard cap on the img itself so portrait photos never overflow the
       viewport. Mobile baseline; desktop overrides at the bottom. */
    max-width: 100%;
    max-height: min(60vh, calc(100vh - 220px));
    width: auto;
    height: auto;
    object-fit: contain;
    display: block;
    border-radius: 2px;
}

.gallery-lightbox__photo img {
    box-shadow: 0 20px 40px -20px rgba(0, 0, 0, 0.5);
}

/* Pair-slide styles live with the mobile lightbox block below (the pair
   figure intentionally skips .gallery-lightbox__photo, so none of the
   single-photo img rules above apply to it). */

/* Counter (Session H: moved to thumb rail header, not overlaying the photo).
   Mobile baseline: lives inline with the horizontal thumb row. Desktop
   override below pins it as the rail's masthead. */
.gallery-lightbox__counter {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25rem;
    padding: 0.25rem 0.6rem;
    color: var(--lb-fg-soft);
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    border-radius: 999px;
    background: var(--lb-thumbs-bg);
    flex: 0 0 auto;
    order: -1;
}
.gallery-lightbox__counter-num {
    color: var(--lb-fg);
    font-weight: 700;
}
.gallery-lightbox__counter-sep {
    opacity: 0.45;
    margin: 0 0.1rem;
}
.gallery-lightbox__counter-total { opacity: 0.7; }

/* Editorial chip (Session H): mirrors the grid card chip. Top-left of photo. */
.gallery-lightbox__chip {
    position: absolute;
    top: 1rem;
    left: 1rem;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    padding: 0.4rem 0.8rem 0.36rem;
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #2a1a1a;
    background: var(--color-rose, #d99fa0);
    border-radius: 999px;
    box-shadow: 0 6px 14px -8px rgba(58, 40, 40, 0.4);
    pointer-events: none;
}

/* Type pill (Session H): only on non-after singles. Top-right of photo. */
.gallery-lightbox__type-pill {
    position: absolute;
    top: 1rem;
    right: 1rem;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    padding: 0.4rem 0.8rem 0.36rem;
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    border-radius: 999px;
    box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.45);
    pointer-events: none;
}
.gallery-lightbox__type-pill--before {
    /* Neutral, low-key - "before" is the baseline; cue without screaming. */
    background: rgba(255, 255, 255, 0.9);
    color: #2a1a1a;
}
.gallery-lightbox__type-pill--during {
    /* In-progress: amber/warning to read as transient. */
    background: var(--color-warning, #c79430);
    color: #fff;
}

/* DOMINANT MOMENT: 54px squared-pill nav chips with a hairline ring and
   directional hover slide — feels like museum/print-portfolio chrome rather
   than mobile-app glyphs. */
.gallery-lightbox__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 54px;
    height: 54px;
    border-radius: 14px;
    background: var(--lb-chrome-bg);
    color: var(--lb-chrome-fg);
    border: 1px solid var(--lb-chrome-border);
    cursor: pointer;
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    backdrop-filter: blur(14px) saturate(160%);
    display: grid;
    place-items: center;
    z-index: 5;
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.08) inset,
        0 18px 32px -20px rgba(0, 0, 0, 0.6);
}
.gallery-lightbox__nav svg {
    width: 22px;
    height: 22px;
    stroke: currentColor;
    stroke-width: 2;
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    display: block;
}
@media (prefers-reduced-motion: no-preference) {
    .gallery-lightbox__nav {
        transition: background 220ms ease,
                    border-color 220ms ease,
                    transform 260ms cubic-bezier(0.16, 1, 0.3, 1),
                    box-shadow 240ms ease;
    }
    .gallery-lightbox__nav svg {
        transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1);
    }
}

.gallery-lightbox__nav--prev { left: 1.25rem; }
.gallery-lightbox__nav--next { right: 1.25rem; }

.gallery-lightbox__nav[disabled] {
    opacity: 0.28;
    cursor: not-allowed;
}

.gallery-lightbox__nav:focus-visible {
    outline: 2px solid var(--lb-rose);
    outline-offset: 3px;
    border-color: var(--lb-rose);
}

@media (hover: hover) {
    .gallery-lightbox__nav:not([disabled]):hover {
        background: var(--lb-chrome-bg-hover);
        border-color: var(--lb-rose);
        box-shadow:
            0 1px 0 rgba(255, 255, 255, 0.12) inset,
            0 24px 38px -18px rgba(0, 0, 0, 0.7);
    }
    .gallery-lightbox__nav--prev:not([disabled]):hover svg {
        transform: translateX(-3px);
    }
    .gallery-lightbox__nav--next:not([disabled]):hover svg {
        transform: translateX(3px);
    }
}

/* Info rail */
.gallery-lightbox__info {
    display: grid;
    grid-template-rows: 1fr auto;
    background: var(--lb-rail-bg);
    color: var(--lb-fg);
    border-top: 1px solid var(--lb-rule);
    position: relative;
    order: 3;
}

.gallery-lightbox__info-body {
    padding: 1.25rem 1.25rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    /* Vertical only - see .post-toc__sticky in inspiration.css. */
    overflow-x: hidden;
    overflow-y: auto;
}

.gallery-lightbox__eyebrow {
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--lb-rose);
    display: flex;
    align-items: center;
    gap: 0.65rem;
    margin: 0;
}

.gallery-lightbox__eyebrow::before {
    content: '';
    width: 1.5rem;
    height: 2px;
    background: var(--lb-rose);
}

.gallery-lightbox__photo-stage:has(.gallery-lightbox__photo--pair) ~ .gallery-lightbox__info .gallery-lightbox__eyebrow {
    color: var(--color-accent, #29b3a6);
}

.gallery-lightbox__title {
    font-family: var(--font-display, 'Schibsted Grotesk', sans-serif);
    font-weight: 700;
    font-size: clamp(24px, 2.2vw, 32px);
    line-height: 1.05;
    letter-spacing: -0.025em;
    color: var(--lb-fg);
    text-wrap: balance;
    margin: 0;
}

.gallery-lightbox__byline {
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.95rem;
    font-weight: 400;
    color: var(--lb-fg-soft);
    margin: 0;
}

.gallery-lightbox__stylist-link {
    color: var(--lb-fg);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1px solid var(--lb-rose);
    padding-bottom: 1px;
}

.gallery-lightbox__caption {
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-weight: 300;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--lb-fg-soft);
    margin: 0;
    max-width: 32ch;
    white-space: pre-line;
}

.gallery-lightbox__hint {
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-style: italic;
    font-size: 0.85rem;
    opacity: 0.75;
    color: var(--lb-fg-soft);
    margin: 0;
}

.gallery-lightbox__info-foot {
    padding: 1rem 1.25rem 1.5rem;
    border-top: 1px solid var(--lb-rule);
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
}

/* Next Client (Session H): discreet session-jump affordance below the CTA.
   Quiet by default, slides on hover - mirrors the bio "Browse all N" link.
   Singular ("client") not plural so it reads as a contextual action. */
.gallery-lightbox__next-client {
    appearance: none;
    background: transparent;
    border: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.65rem 0.85rem;
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--lb-fg-soft);
    cursor: pointer;
    border-radius: 4px;
}
.gallery-lightbox__next-client-arrow {
    display: inline-block;
    transition: transform 220ms ease;
}
@media (hover: hover) {
    .gallery-lightbox__next-client:hover {
        color: var(--lb-fg);
    }
    .gallery-lightbox__next-client:hover .gallery-lightbox__next-client-arrow {
        transform: translateX(4px);
    }
}
.gallery-lightbox__next-client:focus-visible {
    outline: 2px solid var(--lb-rose);
    outline-offset: 2px;
}

.gallery-lightbox__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.95rem 1.5rem;
    background: var(--lb-rose);
    color: #fff;
    text-decoration: none;
    font-family: var(--font-body, 'Lexend', sans-serif);
    font-weight: 700;
    font-size: 0.72rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    border-radius: 4px;
    transition: background 180ms ease, transform 180ms ease;
}

@media (hover: hover) {
    .gallery-lightbox__thumb:not(.is-active):hover {
        opacity: 0.9;
    }
    .gallery-lightbox__cta:hover {
        background: var(--lb-fg);
        transform: translateY(-1px);
    }
}

/* ─── Mobile lightbox redesign (base, <900px) ───────────────────────────────
   DOMINANT MOMENT: the photo runs flush to the top of the sheet - 3:4 portraits
   inset in a rounded frame, 1:1/4:3 full-bleed edge to edge - with the thumb
   strip tucked directly beneath it and a centered title + filled Book CTA
   anchoring the rail below. Locked to mockup v6. Desktop (>=900px) overrides all
   of this in the block that follows; every property changed here is restored or
   superseded there so the desktop lightbox is untouched.
   ───────────────────────────────────────────────────────────────────────────── */

/* Neutralize the legacy photo-library.css content constraints (max-width 900 /
   max-height 90vh / display:grid) so the card owns full-viewport layout. */
.gallery-lightbox__content {
    max-width: none;
    max-height: none;
    width: 100%;
    height: 100vh;            /* fallback */
    height: 100dvh;
    display: block;
    gap: 0;
}

/* Photo stage flush to the top; the aspect class sets the framing. grid-row:1
   keeps the photo in the top cell (info is pinned to the 1fr row 3 below), so
   the photo's row always gets its full content height - otherwise a single-photo
   card (no thumb strip) lets the info auto-flow into the photo's row and the
   stage's overflow:hidden clips the photo's rounded bottom. */
.gallery-lightbox__photo-stage {
    padding: 0;
    grid-row: 1;
}

/* 3:4 PORTRAIT -> inset + rounded. Single-photo (no thumb strip) gets a bigger
   photo; multi-photo (thumb strip present) is a touch smaller so the title +
   stylist name still fit above the buttons. */
.gallery-lightbox__card.is-portrait .gallery-lightbox__photo-stage {
    padding: 10px 14px 0;
}
.gallery-lightbox__card.is-portrait .gallery-lightbox__photo img {
    border-radius: 14px;
}
.gallery-lightbox__card.is-portrait:has(.gallery-lightbox__thumbs) .gallery-lightbox__photo img {
    max-height: min(54dvh, calc(100dvh - 268px));
}
.gallery-lightbox__card.is-portrait:not(:has(.gallery-lightbox__thumbs)) .gallery-lightbox__photo img {
    max-height: min(64dvh, calc(100dvh - 215px));
}

/* 1:1 SQUARE + 4:3 LANDSCAPE -> full-bleed, edge to edge, no rounding. */
.gallery-lightbox__card.is-square .gallery-lightbox__photo,
.gallery-lightbox__card.is-landscape .gallery-lightbox__photo {
    width: 100%;
    display: block;
}
.gallery-lightbox__card.is-square .gallery-lightbox__photo img,
.gallery-lightbox__card.is-landscape .gallery-lightbox__photo img {
    width: 100%;
    height: auto;
    max-height: min(60dvh, calc(100dvh - 300px));
    border-radius: 0;
    box-shadow: none;
}

/* ── Pair slides: side-by-side BEFORE / AFTER ─────────────────────────────
   The _beauty-ba-modal.php language (Ben rejected the wipe/drag compare:
   the team can't shoot aligned pairs). Two complete photos, never clipped.
   Mobile baseline: the stage is a scroll-snap track, one photo per screen;
   photo-library.js render() opens it on the AFTER slide so a swipe pulls
   the BEFORE in. The desktop block below shows both with the brand seam.
   The figure deliberately does NOT carry .gallery-lightbox__photo, so the
   single-photo img rules (inset rounding, contain caps) never apply here. */
.gallery-lightbox__photo--pair {
    position: relative;
    margin: 0;
    width: 100%;
    max-width: 100%;
}

.gallery-lightbox__ba {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* photo-stage sets touch-action: pan-y for swipe-nav (bottom of file);
       horizontal pan must stay native inside the track */
    touch-action: pan-x pan-y pinch-zoom;
    border-radius: 14px;
    box-shadow: 0 20px 40px -20px rgba(0, 0, 0, 0.5);
}
.gallery-lightbox__ba::-webkit-scrollbar {
    display: none;
}

.gallery-lightbox__ba-slide {
    position: relative;
    flex: 0 0 100%;
    scroll-snap-align: center;
    aspect-ratio: 3 / 4;
    max-height: min(54dvh, calc(100dvh - 268px));
    overflow: hidden;
}
.gallery-lightbox__card:not(:has(.gallery-lightbox__thumbs)) .gallery-lightbox__ba-slide {
    max-height: min(64dvh, calc(100dvh - 215px));
}
/* The pair shares one shoot/aspect; square + landscape keep their true shape. */
.gallery-lightbox__card.is-square    .gallery-lightbox__ba-slide { aspect-ratio: 1 / 1; }
.gallery-lightbox__card.is-landscape .gallery-lightbox__ba-slide { aspect-ratio: 4 / 3; }

.gallery-lightbox__ba-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 20%;
    display: block;
}

/* BEFORE / AFTER badges - mirror the .bba-modal__badge pill language */
.gallery-lightbox__ba-badge {
    position: absolute;
    bottom: 0.75rem;
    font-family: var(--font-display);
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    padding: 0.3em 0.75em;
    border-radius: 999px;
    pointer-events: none;
    z-index: 2;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}
.gallery-lightbox__ba-badge--before {
    left: 0.75rem;
    background: rgba(58, 40, 40, 0.7);
    color: rgba(255, 255, 255, 0.92);
}
.gallery-lightbox__ba-badge--after {
    right: 0.75rem;
    background: rgba(77, 170, 184, 0.9);
    color: #fff;
}
[data-theme="dark"] .gallery-lightbox__ba-badge--after {
    background: rgba(15, 164, 182, 0.9);
}

/* Caption: hidden on portrait + square (single-line / no room), shown on
   landscape where the wide frame leaves space for the multi-line body. */
.gallery-lightbox__caption {
    display: none;
}
.gallery-lightbox__card.is-landscape .gallery-lightbox__caption {
    display: block;
    margin-inline: auto;     /* center the max-width box (not just the text) */
}

/* Info rail: title always shown, vertically + horizontally centered group.
   min-height:0 lets the rail hold its 1fr share and scroll the body internally
   instead of its tall content collapsing the photo's auto row; `safe center`
   keeps the title from clipping at the top of a short viewport. */
.gallery-lightbox__info {
    min-height: 0;
    grid-row: 3;             /* always the 1fr row, even with no thumb strip */
}
.gallery-lightbox__info-body {
    min-height: 0;
    justify-content: safe center;
    text-align: center;
    align-items: center;
    gap: 0.6rem;
}
.gallery-lightbox__title {
    font-size: clamp(19px, 5.4vw, 23px);   /* legible, not huge */
}

/* Foot: Book full-width on top (filled accent - rose light / teal dark - and
   wide enough for the longest service label on one line), then Email + Next
   Client as two matching ghost buttons side by side. */
.gallery-lightbox__info-foot {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem 0.5rem;
    padding: 0.55rem 1rem 0.7rem;
    align-items: center;
}
.gallery-lightbox__cta {
    grid-column: 1 / -1;
    background: var(--lb-book);
    color: var(--lb-book-fg);
    border-radius: 12px;
    padding: 0.85rem 1rem;
    margin-bottom: 0.05rem;
}
.gallery-lightbox__email-look,
.gallery-lightbox__next-client {
    justify-content: center;
    border: 1.5px solid var(--lb-ghost-bd);
    color: var(--lb-ghost-fg);
    border-radius: 12px;
    padding: 0.7rem 0.3rem;
    font-size: 0.64rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
    gap: 0.3rem;
}
.gallery-lightbox__email-look { grid-column: 1; }
.gallery-lightbox__next-client { grid-column: 2; }
/* Services without a booking page (e.g. nails) emit no Book button - let the
   remaining buttons each take the full width. */
.gallery-lightbox__info-foot:not(:has(.gallery-lightbox__cta)) .gallery-lightbox__email-look,
.gallery-lightbox__info-foot:not(:has(.gallery-lightbox__cta)) .gallery-lightbox__next-client {
    grid-column: 1 / -1;
}

/* "Email this look" expands into an inline form (look-capture.js swaps the
   button in place). In this 2-col grid the form's intrinsic width blew the
   1fr track past the frame and clipped Next Client (Ben's phone,
   2026-06-12). The open form takes the full foot width and Next Client
   stands down while it is open - desktop restores it (room to spare). */
.gallery-lightbox__info-foot .look-capture {
    grid-column: 1 / -1;
    min-width: 0;
}
.gallery-lightbox__info-foot:has(.look-capture) .gallery-lightbox__next-client {
    display: none;
}

/* Thumbnails: ~44px finger-friendly tap target on mobile. */
.gallery-lightbox__thumbs {
    padding: 0.3rem 0.45rem;
    gap: 0.4rem;
}
.gallery-lightbox__thumb {
    width: 40px;
    height: 50px;
    aspect-ratio: auto;
}

/* Desktop overrides (>=900px): three-region card grid */
@media (min-width: 900px) {
    .gallery-lightbox:not([hidden]) {
        padding: 2rem;
    }

    .gallery-lightbox__card {
        grid-template-columns: minmax(0, 1fr) 360px;
        /* 100% (not auto) so the single row spans the full card height even
           when info content is shorter than the photo column. Stops the
           photo from re-centering as the right rail's content varies. */
        grid-template-rows: 100%;
        width: min(1280px, 100%);
        height: min(840px, calc(100vh - 4rem)); /* fold-offset-ok: lightbox shell height, not a header offset */
        border-radius: 6px;
    }

    /* Lock the photo-stage and info column to the card's full height. Without
       min-height:0 on the info, the body's overflow:auto silently grows the
       grid row rather than scrolling - the symptom Ben reported as "photo
       moves as I cycle slides." */
    .gallery-lightbox__photo-stage,
    .gallery-lightbox__info {
        min-height: 0;
        height: 100%;
    }
    .gallery-lightbox__info-body {
        min-height: 0; /* enables overflow-y scroll inside a grid row */
    }

    .gallery-lightbox__card:has(> .gallery-lightbox__thumbs) {
        grid-template-columns: 80px minmax(0, 1fr) 360px;
    }

    .gallery-lightbox__thumbs {
        flex-direction: column;
        border-top: 0;
        border-bottom: 0;
        border-right: 1px solid var(--lb-thumbs-rule);
        padding: 1rem 0.5rem;
        overflow-y: auto;
        overflow-x: hidden;
        order: initial;
    }

    .gallery-lightbox__thumb {
        width: 56px;
    }

    .gallery-lightbox__photo-stage {
        order: initial;
    }

    .gallery-lightbox__photo img {
        max-height: min(82vh, calc(100vh - 130px));
    }

    /* Pair slide, desktop: both photos side by side with the brand seam
       (mirrors .bba-modal__stage "dialog hugs two photos"). The modal widens
       for pair slides (the base 900px content cap leaves the photo stage
       ~430px - two portraits would render thumbnail-small), and the stage
       becomes a 2-col grid that fills it; aspect-ratio sizes the height.
       The vh-based width cap keeps both photos fully visible on short
       viewports (3:4 pair: width = height * 2 * 0.75). */
    .gallery-lightbox__content:has(.gallery-lightbox__photo--pair) {
        max-width: min(1200px, 96vw);
    }
    .gallery-lightbox__photo--pair {
        width: 100%;
        max-width: 100%;
    }
    .gallery-lightbox__ba {
        display: grid;
        grid-template-columns: 1fr 1fr;
        overflow: visible;
        scroll-snap-type: none;
        gap: 3px;
        width: min(100%, calc((100vh - 180px) * 1.5)); /* fold-offset-ok: lightbox chrome - derives WIDTH from the space left inside the modal, not a header offset */
        margin-inline: auto;
        border-radius: 2px;
        /* the 3px gap reveals this seam between the photos */
        background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-rose-deep) 100%);
    }
    .gallery-lightbox__card.is-square .gallery-lightbox__ba {
        width: min(100%, calc((100vh - 180px) * 2)); /* fold-offset-ok: lightbox chrome, not a header offset */
    }
    .gallery-lightbox__card.is-landscape .gallery-lightbox__ba {
        width: min(100%, calc((100vh - 180px) * 2.66)); /* fold-offset-ok: lightbox chrome, not a header offset */
    }
    .gallery-lightbox__ba-slide,
    .gallery-lightbox__card:not(:has(.gallery-lightbox__thumbs)) .gallery-lightbox__ba-slide {
        flex: none;
        width: auto;
        height: auto;
        max-height: none;
    }
    /* Both photos are visible - the mobile swipe hint has nothing to say. */
    .gallery-lightbox__hint--swipe {
        display: none;
    }

    .gallery-lightbox__info {
        border-top: 0;
        border-left: 1px solid var(--lb-rule);
        order: initial;
    }

    .gallery-lightbox__info-body {
        padding: 3rem 2.5rem 1.5rem;
        gap: 1.25rem;
    }

    .gallery-lightbox__info-foot {
        padding: 1.5rem 2.5rem 2rem;
    }

    /* Desktop foot is a roomy flex column - Next Client can stay visible
       while the email form is open (the mobile rule hides it). */
    .gallery-lightbox__info-foot:has(.look-capture) .gallery-lightbox__next-client {
        display: inline-flex;
    }

    .gallery-lightbox__title {
        font-size: clamp(28px, 2.4vw, 36px);
    }

    .gallery-lightbox__nav {
        width: 44px;
        height: 44px;
    }

    .gallery-lightbox__close {
        width: 40px;
        height: 40px;
    }

    /* Restore everything the mobile redesign block changed, so the desktop
       lightbox renders exactly as before. */
    .gallery-lightbox__content {
        max-width: min(900px, 100%);
        max-height: 90vh;
        width: auto;
        height: auto;
        display: grid;
        gap: var(--space-3, 0.75rem);
    }
    .gallery-lightbox__photo-stage,
    .gallery-lightbox__card.is-portrait .gallery-lightbox__photo-stage {
        padding: 1rem;
    }
    .gallery-lightbox__card.is-portrait .gallery-lightbox__photo img,
    .gallery-lightbox__card.is-portrait:has(.gallery-lightbox__thumbs) .gallery-lightbox__photo img,
    .gallery-lightbox__card.is-portrait:not(:has(.gallery-lightbox__thumbs)) .gallery-lightbox__photo img,
    .gallery-lightbox__card.is-square .gallery-lightbox__photo img,
    .gallery-lightbox__card.is-landscape .gallery-lightbox__photo img {
        max-height: min(82vh, calc(100vh - 130px));
        border-radius: 2px;
    }
    .gallery-lightbox__card.is-square .gallery-lightbox__photo,
    .gallery-lightbox__card.is-landscape .gallery-lightbox__photo {
        width: auto;
        display: inline-block;
    }
    .gallery-lightbox__card.is-square .gallery-lightbox__photo img,
    .gallery-lightbox__card.is-landscape .gallery-lightbox__photo img {
        width: auto;
        height: auto;
        box-shadow: 0 20px 40px -20px rgba(0, 0, 0, 0.5);
    }
    .gallery-lightbox__caption {
        display: flex;
    }
    .gallery-lightbox__info-body {
        text-align: left;
        align-items: normal;
        justify-content: normal;
    }
    .gallery-lightbox__cta {
        grid-column: auto;
        background: var(--lb-rose);
        color: #fff;
        border-radius: 4px;
        padding: 0.95rem 1.5rem;
        margin-bottom: 0;
    }
    /* Email + Next Client revert to quiet text links (not ghost buttons). */
    .gallery-lightbox__email-look,
    .gallery-lightbox__next-client {
        border: 0;
        border-radius: 0;
        justify-content: normal;
        color: var(--lb-fg-soft);
        padding: 0.65rem 0.85rem;
        font-size: 0.7rem;
        letter-spacing: 0.18em;
        white-space: normal;
        gap: 0.5rem;
    }
    .gallery-lightbox__info-foot {
        display: flex;
        gap: 0.65rem;
    }
    /* Single-row card grid on desktop - drop the mobile row pins + tap-size
       thumbs so the left thumb rail renders as before. */
    .gallery-lightbox__photo-stage,
    .gallery-lightbox__info {
        grid-row: auto;
    }
    .gallery-lightbox__thumb {
        height: auto;
        aspect-ratio: 3 / 4;
    }
}


/* ─── Empty state ───────────────────────────────────────────────────────────
   When filters return no matches. Replaces the prior single-line link with a
   section-sized editorial empty state. */

/* Empty-facet fallback notice.
 * Shown when an arrival landed on a service facet with no photos and the grid below
 * is therefore the FULL gallery rather than what they clicked for. Deliberately a
 * quiet band rather than a warning: nothing is broken, the visitor just needs to know
 * what they are looking at. Uses the same eyebrow colour as the empty state so the two
 * read as one family.
 * DOMINANT MOMENT: none - this must not compete with the photo grid it introduces. */
.gallery__notice {
    max-width: 44rem;
    margin: 0 auto clamp(1.25rem, 3vw, 2rem);
    padding: 0.85rem 1.15rem;
    border-left: 3px solid var(--color-eyebrow);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: color-mix(in srgb, var(--color-eyebrow) 7%, transparent);
    font-family: var(--font-body);
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--color-body);
    text-align: left;
}

.gallery__empty {
    text-align: center;
    padding: clamp(3rem, 6vw, 5rem) 1rem;
    max-width: 36rem;
    margin: 0 auto;
}

.gallery__empty-eyebrow {
    font-family: var(--font-body);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
    margin: 0 0 0.85rem;
    max-width: none;
}

.gallery__empty-title {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 3.5vw, 2.5rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--color-heading);
    margin: 0 0 1rem;
    text-wrap: balance;
}

.gallery__empty-body {
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.5;
    color: var(--color-body);
    margin: 0 0 1.5rem;
    max-width: 32rem;
    margin-inline: auto;
}

.gallery__empty-cta {
    display: inline-block;
    padding: 0.8rem 1.75rem;
    background: var(--color-rose);
    color: var(--color-heading);
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    text-decoration: none;
    border-radius: 4px;
    box-shadow: 0 8px 18px -10px rgba(212, 160, 160, 0.55);
    transition: transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
                box-shadow 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
                background 200ms ease;
}

@media (hover: hover) {
    .gallery__empty-cta:hover {
        transform: translateY(-2px);
        background: var(--color-cta-bg);
        box-shadow: 0 16px 24px -10px rgba(212, 160, 160, 0.55);
    }
}

.gallery__empty-cta:focus-visible {
    outline: 2px solid var(--color-rose);
    outline-offset: 3px;
}

[data-theme="dark"] .gallery__empty-cta {
    /* Same dark-text-on-rose treatment as the Apply pill. */
    color: #2a1a1a;
    box-shadow: 0 10px 22px -10px rgba(0, 0, 0, 0.55);
}


/* ─── Justified-rows grid (BUILD-QUEUE item 6, direction D, 2026-06-11) ──────
   The museum-archive pattern for mixed-aspect photos (3:4 / 4:3 / 1:1):
   photo-library.js packs cards into rows at a bounded shared height, so
   each photo keeps its true shape and landscape work earns more width.
   The flex rules here are the no-JS fallback (bounded, slightly jittery);
   .is-packed flips control of widths to the packer. Scoped to /gallery/
   via .gallery__grid--justified - bio-portfolio grids keep the masonry.

   DOMINANT MOMENT: feature rows riding 1.55x taller than the archive
   rhythm every 12th session - .photo-card--feat row scale. */

.gallery__grid--justified {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: clamp(0.75rem, 1.5vw, 1.5rem);
    /* no-JS target row height; the packer overrides all tile widths */
    --row-h: clamp(10rem, 22vw, 17rem);
}

/* last-row filler: keeps trailing tiles at honest size instead of ballooning */
.gallery__grid--justified::after {
    content: "";
    flex-grow: 999999;
    flex-basis: 0;
}

.gallery__grid--justified .photo-card--gallery {
    --card-ar: 0.75; /* portrait default; bucket classes override */
    margin-bottom: 0;
    flex-grow: calc(var(--card-ar) * 1000);
    flex-basis: calc(var(--card-ar) * var(--row-h));
    max-width: calc(var(--card-ar) * var(--row-h) * 1.3);
    min-width: 0;
}

.gallery__grid--justified.is-packed .photo-card--gallery {
    flex: none;
    max-width: none;
}

/* Hold justified tiles hidden until the packer sets exact widths, so the
   CSS-approximate flexbox layout never paints and then re-flows into the
   JS-packed one (that hand-off was a deterministic ~0.32 CLS - Phase-4 sweep).
   Gated on `:root.js` (set pre-paint in _main.php) so no-JS still shows the
   CSS-only justified fallback; the packer adds .is-packed almost immediately.
   The grid container keeps its box, so nothing below it moves. */
:root.js .gallery__grid--justified:not(.is-packed) .photo-card--gallery {
    visibility: hidden;
}

/* Bucket overrides MUST out-rank the 0-2-0 default above - the bare
   single-class form silently lost the cascade and every tile rendered
   3:4 regardless of bucket (Ben caught it on staging 2026-06-12). */
.gallery__grid--justified .photo-card--ar-landscape { --card-ar: 1.3333; }
.gallery__grid--justified .photo-card--ar-square    { --card-ar: 1; }

/* The media box IS the bucket shape; the photo sits inside untouched.
   Off-bucket sources (pre Content Studio recrop) letterbox a few percent
   against the tonal placeholder - never cropped, never distorted. */
.gallery__grid--justified .photo-card--gallery .photo-card__media {
    aspect-ratio: var(--card-ar);
}

.gallery__grid--justified .photo-card--gallery .photo-card__img {
    height: 100%;
    object-fit: contain;
}

/* Feature tiles: the packer gives their row a 1.55x-taller target.
   Chrome scales with the bigger canvas. */
.gallery__grid--justified .photo-card--feat .photo-card__stylist-overlay__name {
    font-size: 1.2rem;
}
.gallery__grid--justified .photo-card--feat .photo-card__chip {
    font-size: 0.74rem;
}

/* Dark theme depth: soft accent glow over the archive, never a flat slab
   (wider-sa anchor). Token-derived via color-mix; pointer-transparent. */
[data-theme="dark"] .gallery {
    position: relative;
}
[data-theme="dark"] .gallery::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse 70% 40% at 50% 10%,
        color-mix(in srgb, var(--color-accent-glow) 9%, transparent) 0%,
        transparent 65%);
}

/* ─── Editorial breather TILE (packed inside the rows) ─────────────────────
   Ben 2026-06-11: the full-width band every page was too much. The breather
   is now a photo-sized CTA card that participates in row packing (the
   packer reads its --card-ar like any photo) with photos all around it.
   Phones: a low full-width 16:9 row. Desktop: a 4:3 tile mid-row. One per
   fetch-page, position drifts per page, suppressed on the last page. */

.gallery__breather {
    /* Phones: a full-width band the packer leaves alone. Packing the tile
       into 2-up phone rows crushed the copy and squeezed its row-mate
       (Ben's staging test 2026-06-12). From 700px up it rejoins the rows
       as a 4:3 tile with photos around it. */
    flex-basis: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    gap: var(--space-sm, 0.5rem);
    padding: clamp(2.75rem, 14vw, 4.5rem) var(--space-lg, 1.5rem);
    background: var(--color-bg-alt);
    border-radius: 4px;
    position: relative;
    overflow: hidden;
}

@media (min-width: 700px) {
    .gallery__breather {
        --card-ar: 1.3333;
        flex-basis: calc(var(--card-ar) * var(--row-h));
        width: auto;
        aspect-ratio: var(--card-ar);
        flex-grow: calc(var(--card-ar) * 1000);
        max-width: calc(var(--card-ar) * var(--row-h) * 1.3);
        min-width: 0;
        padding: var(--space-md, 1rem);
    }

    .gallery__grid--justified.is-packed .gallery__breather {
        flex: none;
        max-width: none;
    }

    /* tile width = tighter line budget than the phone band */
    .gallery__breather-line {
        font-size: var(--text-lg);
    }
}

[data-theme="dark"] .gallery__breather::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse 60% 80% at 50% 110%,
        color-mix(in srgb, var(--color-accent-glow) 11%, transparent) 0%,
        transparent 70%);
}

.gallery__breather-eyebrow {
    font-family: var(--font-body);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--color-eyebrow);
}

.gallery__breather-line {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 300;
    color: var(--color-heading);
    max-width: 18ch;
    text-wrap: balance;
    letter-spacing: -0.01em;
    line-height: 1.3;
    position: relative;
}

.gallery__breather-line em {
    font-style: italic;
    font-weight: 600;
    color: var(--color-rose-deep);
}

/* Compact CTA: inherits the .btn--cta pill system, scaled to tile size. */
.gallery__breather-cta {
    margin-top: var(--space-xs, 0.25rem);
    padding: 0.7rem 1.4rem;
    font-size: 0.72rem;
    position: relative;
}

/* Touch floor. These buttons render 32-34px on tablet - the only .btn on the
   site under 44px.
   The selector needs the .btn prefix: `.btn.btn--cta` is (0,2,0) and sets
   `min-height: 0`, so a bare `.gallery__breather-cta` (0,1,0) loses and any
   height declared there is silently inert.
   An invisible expander, NOT real height: the breather is an aspect-ratio-locked
   4:3 tile with overflow:hidden, so growing the button to 44px pushes the tile's
   content past its own box (measured on staging: 191 -> 196px at 768, clean
   baseline before). The expander leaves layout untouched and still hit-tests at
   44px. Nothing else inside the tile is interactive, so the zone cannot steal a
   tap from a neighbour.
   Verified by injecting this rule on populated staging and hit-testing with
   elementFromPoint - the tiles do not render against a local DB. */
.btn.gallery__breather-cta {
    --hit-expander: 1;
}

.gallery__breather-cta::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 44px;
}

/* Ribbon vs "+N more" collision, resolved by moving the count OUT of the ribbon
   entirely.

   The ribbon used to be clamped to one ellipsized line on every justified tile,
   and cards carrying a count additionally reserved a 4.75rem lane for it.
   Measured on populated staging, that left the name 44-107px of an 82-141px
   requirement, so stylist credit was truncated at EVERY width from 320 to 1920,
   12-21 cards per viewport. "by Siobhan Rozinski" needs 141px and had 44.
   Attribution is the whole point of the ribbon - it is scroll-revealed rather
   than hover-gated precisely so it gets read - so it is the one thing that must
   never be cut.

   The count now lives in the card's TOP-RIGHT corner as a compact "+4" badge,
   which gives the credit the full ribbon width back. Measured across 7 widths
   on one pinned card set, counting ribbons that render on two lines:

     deployed "+N MORE" in-ribbon   146
     "+N" badge in-ribbon (float)   117
     "+N" badge top-right            61
     no badge at all (the FLOOR)     61

   Top-right is exactly the floor - it removes 100% of the wrapping the count
   was causing, and at 390, 430, 1280 and 1920 NO credit wraps at all. It cannot
   do better than that: the residual 61 are names like "by Siobhan Rozinski" on
   a ~138px tile at 320/768/1024, which wrap with no badge present at any
   position. That is a tile-width limit, not a badge-placement one.

   An in-ribbon float was the previous approach and is kept in the record
   because it is the intuitive one: a float only narrows the line it sits on, so
   it beat both the words (117 vs 146) and a flex row (123, which taxes every
   line). Moving out of the text flow altogether still beats all of them. */
.gallery__grid--justified .photo-card__stylist-overlay,
.photo-card--gallery[data-session-count]:not([data-session-count="1"]) .photo-card__stylist-overlay {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* The badge itself. A circle at one or two digits, relaxing into a pill at
   three so "+12" cannot break it.

   The white alphas are literal rather than tokens on purpose: this sits on a
   photograph, not on a themed surface, so it must not follow the light/dark
   token swap - the ribbon's own gradient and its `color: #fff` are hardcoded
   for the same reason. Kept deliberately quiet (16% fill, 34% hairline) so it
   reads as a footnote, not a second focal point. 1.15rem is the measured knee:
   smaller stopped buying anything once the badge left the text flow, and the
   numeral stops being comfortably readable below it.

   It sits BELOW the card's top label row, not in the corner itself. The
   top-right belongs to .photo-card__status-pill on the rare card that has one,
   and the top-LEFT category chip can be long enough to reach across a narrow
   tile. Capping the chip was tried and does NOT work: it is an inline-flex whose
   anonymous text item cannot shrink below min-content, and a single word like
   "Highlights" has no break opportunity - so the box shrank and the glyphs kept
   going. Measured on the deployed page that gave 0 box overlaps but 9 real INK
   overlaps, worst 25px at 320. Capping harder would only clip a label, which is
   the defect class this branch exists to remove.

   At 320 the arithmetic says chip and badge cannot share a line at all: a ~138px
   tile owes 13.6px to the chip's left offset, ~94px to "HIGHLIGHTS" at 0.22em
   tracking and ~30px to the badge - ~145px of demand. So the badge takes its own
   row at 2.9rem, clearing a one-line chip (bottom ~27px), a two-line chip
   (~40px) and the status pill (~36px). A single fixed position also stops the
   badge moving around depending on which labels a card happens to carry. */
.photo-card__session-count {
    top: 2.9rem;
    right: 0.75rem;
    bottom: auto;
    display: inline-grid;
    place-items: center;
    min-width: 1.15rem;
    height: 1.15rem;
    padding: 0 0.22rem;
    border-radius: var(--radius-full, 9999px);
    background: rgba(255, 255, 255, 0.16);
    border: 1px solid rgba(255, 255, 255, 0.34);
    font-size: 0.56rem;
    letter-spacing: 0.01em;
    text-transform: none;
}

@supports (backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px)) {
    .photo-card__session-count {
        -webkit-backdrop-filter: blur(2px);
        backdrop-filter: blur(2px);
    }
}

/* ─── Loading whisper (infinite scroll in flight) ──────────────────────────
   Shown by photo-library.js only while a page fetch is pending. A quiet
   eyebrow-voiced line under a slim rose shimmer - present enough that
   nobody mistakes page 1 for the whole gallery, never a spinner. */

.gallery__loading {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm, 0.5rem);
    padding: var(--space-xl, 2rem) 0 var(--space-md, 1rem);
}

.gallery__loading-bar {
    width: 64px;
    height: 2px;
    border-radius: 999px;
    background: var(--color-rose);
    opacity: 0.45;
}

@media (prefers-reduced-motion: no-preference) {
    .gallery__loading-bar {
        background: linear-gradient(90deg,
            var(--color-rose) 0%,
            var(--color-rose-deep) 50%,
            var(--color-rose) 100%);
        background-size: 200% 100%;
        animation: gallery-loading-shimmer 1.2s linear infinite;
        opacity: 0.8;
    }

    @keyframes gallery-loading-shimmer {
        from { background-position: 200% 0; }
        to   { background-position: 0% 0; }
    }
}

.gallery__loading-text {
    font-family: var(--font-body);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--color-muted);
}

/* Swipe navigation: horizontal touch gestures on the stage are slide
   navigation (photo-library.js), so keep the browser from claiming them -
   vertical pans and pinch zoom stay native. */
.gallery-lightbox__photo-stage {
    touch-action: pan-y pinch-zoom;
}
