/* GALLERY LAYOUT MODULE — photo-gallery styles.
   Every color is driven through the vendor's theme via the --gal-* palette
   variables (set by the consumer: the gallery-lab canvas here, the dashboard
   sample, or the public page) so the color swatch themes them.

   Markup: `.gallery.gl-<layout>` > `.gal-items` > one `<figure.gal-item>` per
   photo, each carrying the SAME slots — an <img> and a <figcaption.gal-caption>.
   Layouts crop, mat, tilt, or caption the photos differently, but the markup is
   identical, so a gallery switches look by changing ONE class. Same discipline
   as faq_layouts.css / pricing_layouts.css / about_layouts.css. */

.gallery{
  container-type: inline-size;
  position: relative;
  /* .gallery is itself a <section> nested inside #section-gallery (so the
     lightbox nav/JS has a local stacking + sizing context) — without this it
     also picks up the shared `section{padding:...}` rule meant for top-level
     sections, doubling up with the outer section's own padding and opening a
     large gap between the heading and the actual photo grid. */
  padding: 0;
  font-family: var(--gal-bf, 'Inter', system-ui, sans-serif);
  color: rgba(var(--gal-ink-rgb), .72);
  margin-inline: auto;
  /* Design tokens — all derived from the theme palette. */
  --c-ink: var(--gal-ink);
/* Secondary text now comes from the derived --*-muted role (see
   _muted_text in style_templates.py) instead of a hardcoded alpha. A fixed
   fade put 43 of 72 themes under AA; the derived value mutes as far as each
   palette allows and stops there. Fallback keeps the old look off-theme. */
  --c-muted: var(--gal-muted, rgba(var(--gal-ink-rgb), .68));
  --c-line: rgba(var(--gal-ink-rgb), .15);
  --c-surface: color-mix(in srgb, var(--gal-ink) 4%, var(--gal-bg));
  --c-accent: var(--gal-primary);
  /* Only for a solid fill with --c-accent-ink painted on top (see the same
     split in link_layouts.css: --lk-primary vs --lk-primary-fill). */
  --c-accent-fill: var(--gal-primary-fill, var(--gal-primary));
  --c-accent-ink: var(--gal-on-primary);
  --c-accent-text: var(--gal-primary-text, var(--gal-primary));
  --c-accent-soft: color-mix(in srgb, var(--gal-primary) 12%, var(--gal-bg));
  /* Polaroid / collage mats read as paper — near-white with a whisper of the
     theme primary so they don't fight the scheme, even on dark backgrounds. */
  --c-mat: color-mix(in srgb, #ffffff 94%, var(--gal-primary));
  --c-shadow-sm: 0 1px 2px rgba(0,0,0,.06), 0 10px 22px -12px rgba(0,0,0,.28);
  /* Was a hardcoded 10px, which ignored the vendor's corner_style. Print-style
     radii below (polaroid mat, editorial/minimal photo edges) stay fixed: those
     are properties of the print, not of the vendor's corner setting. */
  --c-r: var(--gal-radius, 6px);
}

/* ---- shared item primitives ---- */
.gal-items{ display:grid; gap:10px; }
.gal-item{ margin:0; position:relative; overflow:hidden; cursor:pointer; }
.gal-item:focus-visible{ outline:2px solid var(--c-accent); outline-offset:2px; }
.gal-item img{ display:block; width:100%; height:100%; object-fit:cover;
  transition:transform .45s ease; }
/* --type-body-sm */
.gal-caption{ display:none; font-size:13px; line-height:1.55;
  color:var(--c-muted); }
.gallery .gal-item:hover img{ transform:scale(1.035); }

/* Prev/next arrows — present in every gallery's markup, shown only by the
   slider layouts (filmstrip, carousel). The lab/page JS scrolls .gal-items by
   one slide per click and keeps the disabled state in sync. */
.gal-nav{ display:none; position:absolute; top:50%; transform:translateY(-50%); z-index:3;
  /* Was 34-40px, under the kit's 44px control target. --btn-h-md carries the
     pointer:coarse override, so this reaches 46px on a touch device. */
  width:var(--btn-h-md, clamp(40px, 4.4vw, 44px)); height:var(--btn-h-md, clamp(40px, 4.4vw, 44px)); padding:0;
  align-items:center; justify-content:center; border-radius:50%; cursor:pointer;
  border:1px solid var(--c-line); background:var(--gal-bg); color:var(--c-ink);
  box-shadow:var(--c-shadow-sm);
  transition:background .2s, color .2s, border-color .2s, opacity .25s, transform .2s; }
.gal-nav svg{ display:block; width:var(--icon-md, 18px); height:var(--icon-md, 18px); }
.gal-nav:hover{ background:var(--c-accent-fill); color:var(--c-accent-ink); border-color:transparent; }
.gal-nav:active{ transform:translateY(-50%) scale(.94); }
.gal-nav:focus-visible{ outline:2px solid var(--c-accent); outline-offset:2px; }
.gal-nav:disabled{ opacity:0; pointer-events:none; }
.gal-nav-prev{ left:8px; }
.gal-nav-next{ right:8px; }

/* ============================================================ */
/* Named layouts                                                */
/* ============================================================ */

/* 01 · Grid — the safe default: a tight uniform square grid, small radius,
   quiet hover zoom. Reads clean under any font pairing.
   Flex, not CSS grid, with a PER-PHOTO-COUNT basis: the total photo count
   (not the viewport) decides row width, so a gallery of 3 reads as one row of
   thirds, 4 as an even 2x2, 5 as 3-then-2, 6 as two even rows of 3, 7 as
   3-then-2-then-2, and so on — capped at 3 per row, rows sized as evenly as
   possible, any odd remainder given to the front rows so nothing is ever left
   dangling next to blank space. Selected via :has(:nth-child(N):last-child)
   (exact count) + :nth-child ranges (which rows get the bigger basis); rows
   with identical basis wrap on their own since flex-wrap breaks once a row's
   basis values sum to 100%. Enumerated through 15 photos — a realistic
   gallery size — with a 3-up fallback (grow-to-fill) beyond that, which still
   never leaves a gap, just stops perfectly balancing the very last row. Small
   containers (phones) stay simple: always 2-up, with flex-grow absorbing an
   odd one out. */
.gl-grid .gal-items{ display:flex; flex-wrap:wrap; gap:8px; }
.gl-grid .gal-item{ aspect-ratio:1; border-radius:var(--c-r); flex:1 1 calc((100% - 8px) / 2); }
/* A lone photo already fills its row edge to edge (flex-grow does that
   regardless of container width) — forcing it into the same 1:1 tile as a
   multi-photo grid just crops a chunk off a non-square shot for no reason.
   Let it keep its own aspect ratio instead. Scoped to exactly one photo:
   with two, each keeping its own ratio produces a lopsided row (one short
   and wide next to one tall) instead of a matched pair, so two-or-more
   photos stay on the shared cropped/matched-height tile — the click-to-
   expand lightbox (data-full, always the uncropped original) is how a
   multi-photo gallery shows a photo in full. */
.gl-grid .gal-items:has(> :nth-child(1):last-child) .gal-item{ aspect-ratio:auto; }
.gl-grid .gal-items:has(> :nth-child(1):last-child) .gal-item img{ height:auto; }
@container (min-width:481px){
  .gl-grid .gal-item{ flex-basis: calc((100% - 16px) / 3); } /* fallback: >15 photos */

  .gl-grid .gal-items:has(> :nth-child(1):last-child) .gal-item{ flex-basis:100%; }
  .gl-grid .gal-items:has(> :nth-child(2):last-child) .gal-item{ flex-basis: calc((100% - 8px) / 2); }
  .gl-grid .gal-items:has(> :nth-child(3):last-child) .gal-item{ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(4):last-child) .gal-item{ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(5):last-child) > :nth-child(-n+3){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(5):last-child) > :nth-child(n+4){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(6):last-child) .gal-item{ flex-basis: calc((100% - 16px) / 3); }

  .gl-grid .gal-items:has(> :nth-child(7):last-child) > :nth-child(-n+3){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(7):last-child) > :nth-child(n+4){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(8):last-child) > :nth-child(-n+6){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(8):last-child) > :nth-child(n+7){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(9):last-child) .gal-item{ flex-basis: calc((100% - 16px) / 3); }

  .gl-grid .gal-items:has(> :nth-child(10):last-child) > :nth-child(-n+6){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(10):last-child) > :nth-child(n+7){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(11):last-child) > :nth-child(-n+9){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(11):last-child) > :nth-child(n+10){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(12):last-child) .gal-item{ flex-basis: calc((100% - 16px) / 3); }

  .gl-grid .gal-items:has(> :nth-child(13):last-child) > :nth-child(-n+9){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(13):last-child) > :nth-child(n+10){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(14):last-child) > :nth-child(-n+12){ flex-basis: calc((100% - 16px) / 3); }
  .gl-grid .gal-items:has(> :nth-child(14):last-child) > :nth-child(n+13){ flex-basis: calc((100% - 8px) / 2); }

  .gl-grid .gal-items:has(> :nth-child(15):last-child) .gal-item{ flex-basis: calc((100% - 16px) / 3); }
}

/* 02 · Masonry — CSS-columns waterfall that keeps each photo's natural aspect
   ratio; the classic portfolio look for photographers. */
.gl-masonry .gal-items{ display:block; columns:3; column-gap:10px; }
.gl-masonry .gal-item{ break-inside:avoid; margin:0 0 10px; border-radius:var(--c-r); }
.gl-masonry .gal-item img{ height:auto; }
@container (max-width: 520px){ .gl-masonry .gal-items{ columns:2; } }

/* 03 · Mosaic — a featured hero: the first photo spans a 2×2 block and the
   rest tile around it. Good for leading with one signature shot. */
.gl-mosaic .gal-items{ grid-template-columns:repeat(3, 1fr); grid-auto-flow:dense; gap:8px; }
.gl-mosaic .gal-item{ aspect-ratio:1; border-radius:var(--c-r); }
.gl-mosaic .gal-item:first-child{ grid-column:span 2; grid-row:span 2; aspect-ratio:auto; }

/* 04 · Filmstrip — a horizontal slider, like flipping through a contact
   sheet: no scrollbar, arrow nav, and a right-edge fade that appears only
   while there is more to scroll (the page JS toggles .at-end). Slides snap to
   the LEFT edge, so that edge stays clean — no fade there. The gallery stays
   compact no matter how many photos. */
.gl-filmstrip .gal-items{ display:flex; gap:12px; overflow-x:auto;
  scroll-snap-type:x mandatory; scrollbar-width:none; }
.gl-filmstrip .gal-items::-webkit-scrollbar{ display:none; }
.gl-filmstrip .gal-item{ flex:0 0 min(68%, 340px); aspect-ratio:4/3;
  border-radius:var(--c-r); scroll-snap-align:start; }
.gl-filmstrip .gal-nav{ display:flex; }
.gl-filmstrip::after{ content:""; position:absolute; top:0; bottom:0; right:0;
  width:clamp(18px, 5cqi, 36px); z-index:2; pointer-events:none;
  background:linear-gradient(270deg, var(--gal-bg), transparent);
  transition:opacity .25s; }
.gl-filmstrip.at-end::after{ opacity:0; }

/* 04b · Carousel — one near-full-width slide at a time, centered by scroll
   snap, with a sliver of the previous / next photo peeking at each edge under
   a background fade. Arrow nav; the lifted shadow gives the active slide
   depth. */
.gl-carousel .gal-items{ display:flex; gap:clamp(10px, 2.5cqi, 16px); overflow-x:auto;
  scroll-snap-type:x mandatory; padding:6px 0 16px; scrollbar-width:none; }
.gl-carousel .gal-items::-webkit-scrollbar{ display:none; }
/* Spacers so the first / last slide can still snap to center with a peek gap. */
.gl-carousel .gal-items::before, .gl-carousel .gal-items::after{ content:""; flex:0 0 2%; }
.gl-carousel .gal-item{ flex:0 0 84%; aspect-ratio:16/10; border-radius:var(--c-r);
  scroll-snap-align:center;
  box-shadow:0 3px 8px rgba(0,0,0,.10), 0 18px 38px -16px rgba(0,0,0,.40); }
.gl-carousel .gal-nav{ display:flex; }
.gl-carousel::before, .gl-carousel::after{ content:""; position:absolute; top:0; bottom:0;
  width:clamp(22px, 7cqi, 48px); z-index:2; pointer-events:none;
  transition:opacity .25s; }
.gl-carousel::before{ left:0; background:linear-gradient(90deg, var(--gal-bg), transparent); }
.gl-carousel::after{ right:0; background:linear-gradient(270deg, var(--gal-bg), transparent); }
.gl-carousel.at-start::before, .gl-carousel.at-end::after{ opacity:0; }

/* 05 · Polaroid — white paper mats with a deep caption strip, alternating
   tilt, and a lifted shadow. Playful; pairs well with cursive faces. */
.gl-polaroid .gal-items{ grid-template-columns:repeat(3, 1fr); gap:18px 14px; }
.gl-polaroid .gal-item{ background:var(--c-mat); padding:8px 8px 10px;
  border-radius:4px; box-shadow:var(--c-shadow-sm); overflow:visible; }
.gl-polaroid .gal-item img{ height:auto; aspect-ratio:1; border-radius:2px; }
.gl-polaroid .gal-caption{ display:block; padding-top:8px; text-align:center;
  font-family:var(--gal-hf, serif); color:rgba(0,0,0,.62); font-size:13px; }
.gl-polaroid .gal-item:nth-child(odd){ transform:rotate(-2deg); }
.gl-polaroid .gal-item:nth-child(even){ transform:rotate(1.6deg); }
.gl-polaroid .gal-item{ transition:transform .3s ease; }
.gl-polaroid .gal-item:hover{ transform:rotate(0) scale(1.03); z-index:1; }
@container (max-width: 480px){ .gl-polaroid .gal-items{ grid-template-columns:repeat(2, 1fr); } }

/* 06 · Editorial — magazine spread: a full-width opener, then pairs, each with
   an indexed italic caption. Generous whitespace; suits serif templates. */
.gl-editorial .gal-items{ grid-template-columns:repeat(2, 1fr); gap:26px 20px;
  counter-reset:gal; }
.gl-editorial .gal-item{ counter-increment:gal; overflow:visible; }
.gl-editorial .gal-item img{ height:auto; aspect-ratio:4/3; border-radius:2px; }
.gl-editorial .gal-item:first-child{ grid-column:1 / -1; }
.gl-editorial .gal-item:first-child img{ aspect-ratio:21/10; }
.gl-editorial .gal-caption{ display:block; padding-top:9px; font-style:italic; }
/* The index prefix is data — mono and tabular, matching .sc-numbered and
   .fq-numbered. The caption itself stays italic prose. */
.gl-editorial .gal-caption::before{ content:"№ " counter(gal) "  —  ";
  font-style:normal; font-family:var(--gal-mf, ui-monospace, monospace); color:var(--c-accent-text);
  font-variant-numeric:tabular-nums; letter-spacing:.08em; }

/* 07 · Arched — every photo cropped tall under a full arch top; a romantic,
   editorial-wedding look that loves garden and venue shots. */
.gl-arch .gal-items{ grid-template-columns:repeat(3, 1fr); gap:14px; align-items:end; }
/* The arch is a shape, so the top stays a full round; only the foot follows
   the vendor's corner style. */
.gl-arch .gal-item{ aspect-ratio:3/4.4; border-radius:999px 999px var(--c-r) var(--c-r); }
.gl-arch .gal-item:nth-child(3n+2){ margin-top:clamp(14px, 4cqi, 28px); }
@container (max-width: 480px){ .gl-arch .gal-items{ grid-template-columns:repeat(2, 1fr); }
  .gl-arch .gal-item:nth-child(3n+2){ margin-top:0; }
  .gl-arch .gal-item:nth-child(even){ margin-top:clamp(14px, 4cqi, 28px); } }

/* 08 · Framed — a gallery wall: each photo sits in a thin-lined mat card with
   breathing room, hung on the section background. */
.gl-framed .gal-items{ grid-template-columns:repeat(3, 1fr); gap:16px; }
.gl-framed .gal-item{ background:var(--c-surface); border:1px solid var(--c-line);
  padding:clamp(6px, 1.6cqi, 10px); border-radius:var(--c-r); transition:border-color .2s, box-shadow .2s; }
.gl-framed .gal-item img{ aspect-ratio:1; }
.gl-framed .gal-item:hover{ border-color:color-mix(in srgb, var(--gal-primary) 40%, var(--gal-bg));
  box-shadow:var(--c-shadow-sm); }
@container (max-width: 480px){ .gl-framed .gal-items{ grid-template-columns:repeat(2, 1fr); } }

/* 09 · Collage — a loose scatter: white-edged prints, alternating tilt and
   vertical offsets, slight overlap. Feels like a pinboard of the day. */
.gl-collage .gal-items{ grid-template-columns:repeat(3, 1fr); gap:2px; }
.gl-collage .gal-item{ aspect-ratio:1; border:5px solid var(--c-mat);
  border-radius:3px; box-shadow:var(--c-shadow-sm); overflow:hidden;
  transition:transform .3s ease; }
.gl-collage .gal-item:nth-child(3n+1){ transform:rotate(-2.4deg); }
.gl-collage .gal-item:nth-child(3n+2){ transform:rotate(1.8deg) translateY(8px); }
.gl-collage .gal-item:nth-child(3n){ transform:rotate(-1.2deg) translateY(-6px); }
.gl-collage .gal-item:hover{ transform:rotate(0) scale(1.05); z-index:1; }

/* ============================================================ */
/* Lightbox                                                     */
/* ============================================================ */

/* Every gallery photo opens the shared lightbox (wired by the page JS): a
   dark scrim with the photo large, its caption, a counter, prev/next arrows,
   and Esc / arrow-key support. Deliberately neutral-dark rather than themed —
   a lightbox should recede behind the photo on any color scheme. */
.gal-item{ cursor:zoom-in; }
.gal-lightbox{ position:fixed; inset:0; z-index:1000; display:flex; align-items:center;
  justify-content:center; padding:clamp(16px, 4vw, 56px); background:rgba(10,10,14,.92);
  opacity:0; pointer-events:none; transition:opacity .25s ease; }
.gal-lightbox.open{ opacity:1; pointer-events:auto; }
/* This div sits inside <main>, a `.page > *` child stuck at z-index:1
   (agent_bio.css) — its own z-index:1000 only wins fights INSIDE <main>;
   against .vhero-stack's sibling z-index:30 (see the comment there — the
   header needs to out-rank later sections for its own nav overlay), <main>'s
   entire subtree, lightbox included, still loses and the header paints
   through it whenever both are on screen together (a short page, or the
   gallery sitting close under the hero). Bump <main> itself above the header
   for exactly as long as the lightbox is open, so it — and everything inside
   it — legitimately out-ranks .vhero-stack instead of fighting it from
   underneath. */
main:has(> .gal-lightbox.open){ z-index:31; }
.gal-lightbox figure{ margin:0; text-align:center; transform:scale(.975);
  transition:transform .25s ease; }
.gal-lightbox.open figure{ transform:none; }
.gal-lightbox img{ display:block; margin-inline:auto; max-width:min(1100px, 92vw);
  max-height:76vh; width:auto; height:auto; border-radius:6px;
  box-shadow:0 40px 90px -24px rgba(0,0,0,.85); }
/* --type-body */
.gal-lb-caption{ margin-top:14px; color:rgba(255,255,255,.88); font-size:15px;
  line-height:1.6; max-width:var(--measure-body, 68ch); margin-inline:auto; }
/* --type-label — a position counter is data */
.gal-lb-count{ margin-top:5px; color:rgba(255,255,255,.45);
  font-family:var(--gal-mf, ui-monospace, monospace); font-size:12px; line-height:1.2; font-weight:500;
  font-variant-numeric:tabular-nums; letter-spacing:.08em; }
.gal-lb-btn{ position:absolute; z-index:2; width:var(--btn-h-md, 44px); height:var(--btn-h-md, 44px); padding:0;
  display:flex; align-items:center; justify-content:center; border-radius:50%;
  border:1px solid rgba(255,255,255,.18); background:rgba(255,255,255,.08);
  color:#fff; cursor:pointer; -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  transition:background .2s, transform .15s; }
.gal-lb-btn svg{ display:block; width:var(--icon-lg, 22px); height:var(--icon-lg, 22px); }
.gal-lb-btn:hover{ background:rgba(255,255,255,.22); }
.gal-lb-btn:active{ transform:scale(.93); }
.gal-lb-btn:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.gal-lb-close{ top:18px; right:18px; }
.gal-lb-prev{ left:16px; top:50%; transform:translateY(-50%); }
.gal-lb-next{ right:16px; top:50%; transform:translateY(-50%); }
.gal-lb-prev:active{ transform:translateY(-50%) scale(.93); }
.gal-lb-next:active{ transform:translateY(-50%) scale(.93); }
@media (max-width: 640px){
  .gal-lb-prev{ left:6px; } .gal-lb-next{ right:6px; }
  .gal-lightbox img{ max-width:96vw; }
}

/* 10 · Minimal — one photo per row at full width, natural ratio, with a quiet
   caption. Slow, gallery-book pacing; strongest photos only. */
.gl-minimal .gal-items{ grid-template-columns:1fr; gap:30px; }
.gl-minimal .gal-item{ overflow:visible; }
.gl-minimal .gal-item img{ height:auto; border-radius:2px; }
.gl-minimal .gal-item:hover img{ transform:none; }
.gl-minimal .gal-caption{ display:block; padding-top:8px; text-align:center;
  letter-spacing:.02em; }
