/* ═══════════════════════════════════════════════════════════════════════════
   GALLERY SELECTOR — /gallery/, the template gallery's standalone landing.

   Reuses index_v6.css for the filter bar (.tg-pivot-bar / .tg-drop /
   .tg-search / .tg-count) and the card grid (.tg-grid / .tg-card); this file
   owns the page shell, masthead, featured lead, and the grid's page-mode
   overrides (the index_v6 grid was tuned for the home modal's 640px pane).

   Strict PUBLIC_CSP: external CSS only, no inline styles.
   Motion budget: transform / opacity / filter only.
   ═══════════════════════════════════════════════════════════════════════════ */

.gxs-hub {
    position: relative; isolation: isolate;
    max-width: 1180px; margin: 0 auto;
    /* 60px == the fixed nav height (landing_base). */
    padding: calc(60px + 3rem) clamp(1.25rem, 5vw, 3rem) 7rem;
}
/* Ambient wash — off-axis so it never sits behind the headline.
   It lives on the BODY, not on .gxs-hub: the hub is a 1180px centred column
   ~30k tall, so painting the wash there gave it two hard edges — vertical
   seams where the tint met the white gutters, and a smear that rode 14k px
   down the grid before stopping mid-scroll. Both read as a rendering bug.
   Absolute + left/right:0 against the initial containing block gets true
   full bleed with no 100vw scrollbar overflow (same trick as insights.css),
   and the fixed height keeps it a masthead glow that dissolves into the page
   instead of a rectangle laid over it. */
body.gxs-page::before {
    content: ''; position: absolute; left: 0; right: 0; top: 0;
    height: clamp(760px, 125vh, 1400px);
    z-index: -1; pointer-events: none;
    background:
        radial-gradient(58% 44% at 100% -6%, color-mix(in srgb, var(--blurple) 11%, transparent) 0%, transparent 62%),
        radial-gradient(46% 40% at 88% 42%, color-mix(in srgb, var(--indigo, #4f46e5) 6%, transparent) 0%, transparent 68%);
}

/* ── Masthead ──────────────────────────────────────────────────────────── */
.gxs-mast { max-width: 760px; margin: 0 0 2.2rem; }
.gxs-eyebrow {
    display: inline-block;
    font-size: .62rem; font-weight: 700; letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--blurple);
    margin: 0;
}
.gxs-title {
    font-family: var(--brand-font, 'Space Grotesk', sans-serif);
    font-weight: 650;
    font-size: clamp(1.9rem, 4.2vw, 2.7rem);
    line-height: 1.06; letter-spacing: -.03em;
    color: var(--inv-bg-color);
    margin: .7rem 0 0; max-width: 22ch;
}
.gxs-sub {
    font-size: 1rem; line-height: 1.6;
    color: color-mix(in srgb, var(--inv-bg-color) 62%, transparent);
    margin: .9rem 0 0; max-width: 60ch;
}

/* ── Sticky filter rail ────────────────────────────────────────────────── */
/* The bar itself is index_v6's .tg-pivot-bar; this wrapper pins it under the
   fixed nav. Frost model copied from the Insights hub (.cn-hub-head in
   insights.css + [data-frost-on-scroll] in insights/hub.js): TRANSPARENT at
   the top of the page, frosted only once cards are actually sliding under it.
   An always-on frost painted a flat opaque slab across the ambient wash the
   moment the page loaded, which is what made it look pasted on.

   Insights gets its full-bleed frost from negative margins because its bar's
   PARENT is full width (the 1180px cap lives on an inner wrapper). Here the
   1180px cap is on .gxs-hub itself, so a margin trick can only reach the
   column edge — which just reproduces the floating-rectangle look. The frost
   is therefore painted by a ::before bleeding -100vw either side, with
   `overflow-x: clip` on the PAGE to swallow the excess. `clip` (not `hidden`)
   because it does NOT create a scroll container, so the rail still sticks to
   the viewport, and it costs no horizontal scrollbar the way 100vw would.
   The clip must sit on the body, not .gxs-hub: clipping on the hub cuts the
   bleed off at the 1180px column, which is the very edge we're hiding. */
body.gxs-page { overflow-x: clip; }
.gxs-rail {
    position: sticky; top: 60px; z-index: 6;
    margin: 0 0 1rem;
    padding: .55rem 0;
}
.gxs-rail::before {
    content: ''; position: absolute; z-index: -1; pointer-events: none;
    top: 0; bottom: 0; left: -100vw; right: -100vw;
    background: transparent;
    border-bottom: 1px solid transparent;
    transition: background 200ms ease, border-color 200ms ease,
                box-shadow 200ms ease,
                -webkit-backdrop-filter 200ms ease, backdrop-filter 200ms ease;
}
.gxs-rail.is-stuck::before {
    background: color-mix(in srgb, var(--main-bg-color) 88%, transparent);
    -webkit-backdrop-filter: blur(12px) saturate(1.1);
    backdrop-filter: blur(12px) saturate(1.1);
    border-bottom-color: color-mix(in srgb, var(--inv-bg-color) 7%, transparent);
    box-shadow: 0 8px 22px -20px rgba(44, 42, 77, .5);
}

/* Filter-bar page-mode wiring. index_v6.css lays out .tg-pivot-bar__filters
   as a flex row, but the phone-disclosure pair (.tg-filter-fields display:
   contents on desktop / collapsed behind #tgFilterToggle on phones) shipped
   inside experience_modal.css scoped to the home modal — port it here,
   scoped to the rail. v6_gallery.js binds the toggle by id on any page. */
.gxs-rail .tg-pivot-bar { margin-bottom: 0; }
.gxs-rail .tg-filter-fields { display: contents; }
.gxs-rail .tg-filter-toggle { display: none; }
.gxs-rail .tg-count { margin-left: auto; }
/* THE DEAD ZONE RUNS TO 1014px, NOT 760px. Measured on this page at iPad
   portrait (768): the three dropdowns sit on one row and the search wraps to a
   second, which is the "three-line stack" failure this rail is supposed to
   prevent. The modal learned the same lesson and its query runs to 1014px
   (experience_modal.css) — the range 761-1014 is above the phone breakpoint but
   below the ~1015px the four-up desktop row actually needs. Moving the rail's
   query to match puts the disclosure in charge across the whole zone, so the
   bar is one line at every width instead of two. */
@media (max-width: 1014px) {
    .gxs-rail .tg-pivot-bar__filters {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 8px;
        align-items: center;
    }
    .gxs-rail .tg-search-wrap { grid-area: 1 / 1; }
    .gxs-rail .tg-filter-toggle {
        grid-area: 1 / 2;
        display: inline-flex;
        align-items: center;
        gap: 6px;
        padding: 8px 12px;
        border-radius: 8px;
        border: 1px solid color-mix(in srgb, var(--inv-bg-color) 9%, transparent);
        background: color-mix(in srgb, var(--inv-bg-color) 3%, transparent);
        color: color-mix(in srgb, var(--inv-bg-color) 78%, transparent);
        font-family: var(--font-main, 'Satoshi', sans-serif);
        font-weight: 500;
        cursor: pointer;
        white-space: nowrap;
    }
    .gxs-rail .tg-pivot-bar.is-filters-open .tg-filter-toggle {
        border-color: color-mix(in srgb, var(--blurple) 45%, transparent);
        color: var(--blurple);
    }
    /* Collapsed by default — the whole point of the disclosure. */
    .gxs-rail .tg-filter-fields {
        display: none;
        grid-area: 2 / 1 / auto / span 2;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
    }
    .gxs-rail .tg-pivot-bar.is-filters-open .tg-filter-fields { display: grid; }
    .gxs-rail .tg-filter-fields > .tg-drop { width: 100%; }
    /* 44px IS THE TOUCH-TARGET FLOOR, and these landed at 43.4px — close
       enough to look right and still under it. The trigger sizes itself from
       rem padding, and this page's root font-size is 20px rather than 16px, so
       the computed height drifts from what the value suggests. A min-height
       states the contract outright instead of leaving it to that arithmetic. */
    .gxs-rail .tg-filter-fields .tg-drop__trigger { min-height: 44px; }
    .gxs-rail .tg-filter-fields > .tg-drop:nth-child(3) { grid-column: 1 / -1; }
    .gxs-rail .tg-count { grid-area: 3 / 1 / auto / span 2; margin-left: 0; }
}

/* ── Grid: page mode ───────────────────────────────────────────────────── */
/* index_v6's .tg-grid was tuned for the home modal's bounded pane; on a
   standalone page the DOCUMENT is the scroller. */
.gxs-grid {
    max-height: none;
    overflow: visible;
    overscroll-behavior: auto;
    padding-right: 0;
}

/* ── Responsive ────────────────────────────────────────────────────────── */
@media (max-width: 640px) {
    .gxs-hub { padding-top: calc(60px + 2rem); }
}

/* ── Reduced motion ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .gxs-rail::before { transition: none; }
}
