/* Plot single — Gallery section (Figma frame 36:225, "Explore the property
   in detail").

   Pixel reads:
     38:1636 band "Rectangle 54"  x=0 y=2780 w=1920 h=1003, fill #f5f0e8.
                   → NOT an exact match for --vl-cream (#eee9de) — a distinct,
                   slightly lighter literal read. Kept as a literal (no
                   existing token matches) rather than forced onto --vl-cream;
                   flag to Liam — may be a deliberate second cream or a small
                   design-file drift.
     37:1019 heading "Explore the property in detail"  x=523 y=2869 w=881
                   h=75, Omnes 70px/75px leading, text-align centre (heading
                   midpoint 963.5 ≈ container midpoint 960, negligible delta).
                   Mixed weights word-by-word: "Explore" SemiBold, " the
                   property in " Regular, "detail" SemiBold — NOT the italic
                   accent pattern seen elsewhere (plot-intro.css); both
                   accented words are upright SemiBold. Brief's own verbatim
                   markup guessed `<em>in detail</em>` as a single span —
                   adjusted per this node read to two separate <em> spans
                   ("Explore" / "detail") bracketing the plain regular middle
                   clause, and this component's <em> is styled upright
                   SemiBold (font-style: normal override), not italic — a
                   template-local semantic reuse of <em>, same precedent as
                   plot-intro.css's own <em> override (that file makes <em>
                   Light Italic for ITS node; this one makes <em> upright
                   SemiBold for ITS node — component files stay independent
                   per CLAUDE.md's "one file per component" rule).
                   → --vl-text-display (70px, exact match); line-height forced
                   to 1 (= font-size), sitewide big-heading rule (Liam,
                   2026-07-23).
     37:1016 large tile "Mask group"  x=160 y=3014 w=1064.44 h=598.75,
                   rounded-[20px] (read twice in the node's own generated
                   code) — confirms the 20px site card-image radius
                   convention (card-development.css node 36:10) applies here
                   too. object-fit: cover on the photo fill.
     37:1205/40:3158 top-right tile "Rectangle 46"  x=1254 y=3014 w=505.56
                   h=284.38. 37:1206/40:3162 bottom-right tile "Rectangle 47"
                   x=1254 y=3329 w=505.56 h=284.38. No explicit rounded-[]
                   class survived in these two nodes' generated code (Figma
                   simplification), but the rendered screenshots of both show
                   the same ~20px corner treatment as the large tile —
                   confirmed by eye, same 20px applied for consistency.
     Both tile aspect ratios round to 16:9 (1064.44/598.75 = 1.7778,
     505.56/284.38 = 1.7778) — used as the working ratio site-wide for this
     grid rather than the noisy literal decimals.
     Horizontal: large tile right edge (160+1064.44=1224.44) → right column
     left edge (1254) = 29.56px ≈ 30px gap (brief's own "~30px gaps"
     confirmed). Right column right edge (1254+505.56=1759.56) ≈ container's
     own right edge (160+1600=1760) — the two tile widths + gap sum to
     exactly the 1600px container (1064.44+30+505.56=1600), confirming these
     are fr-safe ratios, not independent literals.
     Vertical: top-right tile bottom (3014+284.38=3298.38) → bottom-right
     tile top (3329) = 30.62px ≈ 30px (same gap value, vertical). Bottom-right
     tile bottom (3329+284.38=3613.38) ≈ large tile bottom (3014+598.75=
     3612.75) — the two-stacked-right column's total height matches the
     large tile's height (both bound the same overall grid row height),
     confirming a 2-row grid with the large tile spanning both rows.

   Group 3 nodes (37:990/1008/1039 — 140×20, task brief's naming):
     get_design_context on 37:990 and 37:1039 (37:1008 reads identically —
     same construction) shows FIVE 20px circles at 30px centre spacing, one
     filled ink (#32383b, matches --vl-ink) and four outline-only — a
     5-dot carousel pagination indicator (first dot = active state).
     → REAL UI (pagination dots), not artwork remnants. BUT: per the
     metadata sweep of the whole frame, only ONE of the three instances
     (37:1039 "Group 138", x=890 y=3663) falls inside this section's own
     y-range (band 2780–3783; the dots sit 3663–3683, just below the tile
     row's bottom edge ~3613, i.e. in this section's own bottom padding).
     The other two (37:990 y=2504, 37:1008 y=2660) sit inside the FLOORPLANS
     band (1585–2780) and were already flagged there (plot-floorplans.css
     file-top comment) as not built for that section.
     RESOLVED at live review 2026-07-28: Liam confirmed the dots ARE the
     design — the gallery is a carousel paging the full hero-deduped image
     set in 3-tile compositions (each slide reuses the --3/--2/--1 grids
     below verbatim; the last page degrades exactly like the launch grid
     did). Dot indicator built per the 37:1039 read (ink on this cream
     band, vs the floorplans' white-on-ink twin 37:1008): 20px circles,
     30px pitch (10px gap), centred; tiles bottom (~3613) → dots top (3663)
     = 50px; dots bottom (3683) → band bottom (3783) = 100px — identical
     50/100 geometry to the floorplans carousel, so --carousel trades this
     section's 170px padding-bottom for 100px + the dots block the same
     way. Auto-scroll on both carousels is Liam's addition (2026-07-28),
     not a frame read — see carousel.js.

   Section paddings — band deltas, same method as plot-intro.css/
   plot-floorplans.css:
     band top (2780) → heading top (2869)                = 89px (padding-top;
       same 89px read as every other first-section-in-band on this template
       family — not a coincidence worth fighting, kept as its own literal).
     heading bottom (2869+75=2944) → tiles top (3014)     = 70px (head→grid
       gap; same nominal value as plot-floorplans.css's head→image gap).
     tiles bottom (~3613) → band bottom (2780+1003=3783)  = 170px
       (padding-bottom; same as plot-floorplans.css's own 170px read). */

/* Task 5 (2026-07-29): generalised into a shared component. Everything
   below that is pure carousel geometry (viewport/track/grid/dots/tile
   sizing) is renamed .vl-plot-gallery__* → .vl-gallery__* and now lives in
   template-parts/gallery-carousel.php, shared with About (Task 8). The
   band's own colour/padding and this section's heading stay under
   .vl-plot-gallery — that block is PLOT-OWNED and unchanged; About's
   equivalent band (cornflower) is a separate block in about.css. One
   stylesheet, two callers, each owning its own band. No pixel value below
   changed — only selectors, and two property moves (img → wrapper) forced
   by the new overlay-anchor wrapper; see the Task 5 report for the exact
   before/after. */

.vl-plot-gallery {
    background: #f5f0e8; /* node 38:1636 "Rectangle 54" — see file-top flag: no exact existing token */
    padding-block: 89px 170px;
}

.vl-plot-gallery__heading {
    margin: 0 0 70px; /* node: heading bottom → tiles top */
    text-align: center; /* node: heading midpoint ≈ container midpoint */
    color: var(--vl-black); /* node fill #000000, same convention as plot-intro.css/dev-plots.css */
    font-size: var(--vl-text-display); /* 70px, node-exact */
    font-weight: var(--vl-weight-regular); /* base weight — plain middle clause reads Regular */
    line-height: 1; /* = font-size at every breakpoint (Liam, 2026-07-23: big headings line-height equals size) */
}

/* Template-local <em> reuse: THIS node's accent words are upright SemiBold,
   not italic (contrast plot-intro.css's <em>, which IS italic for its own
   node) — font-style reset is required so <em>'s default italic doesn't leak
   through. */
.vl-plot-gallery__heading em {
    font-style: normal;
    font-weight: var(--vl-weight-semibold);
}

/* ── Carousel shell (live review 2026-07-28) — same contract as
   plot-floorplans.css: hidden-overflow viewport, sliding flex track,
   each slide 100% wide.

   TWO SETS (Liam, same review, later pass): desktop pages the 3-tile
   compositions; tablet+mobile (≤1024) page ONE full-width image per
   slide. Chunking is server-side, so the template renders both sets and
   these rules show exactly one per breakpoint — carousel.js initialises
   each independently. Per-breakpoint padding modifiers because the two
   sets can disagree about having dots (e.g. 2 images = one desktop page,
   no desktop dots, but two compact slides with dots).

   Task 5: the __set/__viewport/__track/__grid/__dots/__tile/__single
   classes below are now .vl-gallery__* (shared, generic — carried by
   template-parts/gallery-carousel.php). The --carousel-desktop/
   --carousel-compact padding MODIFIERS stay named .vl-plot-gallery--…
   because they set padding on the plot's own band section element,
   which the shared template part never renders — that section tag is
   still written by hand in single-hbp_plots.php. */

.vl-gallery__set--compact {
    display: none; /* desktop shows the 3-tile set */
}

.vl-plot-gallery--carousel-desktop {
    padding-bottom: 100px; /* node: dots bottom (3683) → band bottom (3783) */
}

.vl-gallery__viewport {
    /* clip, not hidden (cold review 2026-07-28): an overflow:hidden box is
       still programmatically scrollable — tabbing to a lightbox-tabindexed
       image in an off-screen slide scrolled the viewport and desynced the
       track transform. clip cannot scroll. */
    overflow: clip;
}

.vl-gallery__track {
    display: flex;
    align-items: flex-start; /* a taller last page (single-tile 16:9) top-aligns; cream shows below shorter pages */
    transition: transform 0.45s ease;
}

@media (prefers-reduced-motion: reduce) {
    .vl-gallery__track {
        transition: none;
    }
}

.vl-gallery__grid {
    display: grid;
    flex: 0 0 100%; /* one composition per carousel page */
    min-width: 0; /* same min-size-auto guard as the tiles — a slide is a flex item, its content must never widen it past the viewport */
    gap: 30px; /* node-read, both axes (horizontal + vertical deltas both ≈30px) */
}

.vl-gallery__dots {
    display: flex;
    justify-content: center; /* node: dot run x890–1030, centred on 960 */
    gap: 10px; /* node: 30px pitch − 20px dot */
    margin-top: 50px; /* node: tiles bottom (~3613) → dots top (3663) */
}

/* Arrow row (css/carousel-nav.css): the dot run's 50px clearance moves here,
   because the dots now sit inside this row. Ink on cream, matching the dots. */
.vl-gallery__set .vl-carousel-nav {
    margin-top: 50px;
    color: var(--vl-ink);
    --vl-carousel-arrow-on: var(--vl-cream);
}

.vl-gallery__dot {
    width: 20px; /* node 37:1040–1044 */
    height: 20px;
    padding: 0;
    border: 1px solid var(--vl-ink); /* ink on cream — the floorplans band's white twin, inverted */
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

/* Active state rides aria-current (single source, set by carousel.js;
   PHP stamps the first dot) — no class to keep in sync. */
.vl-gallery__dot[aria-current="true"] {
    background: var(--vl-ink);
}

.vl-gallery__dot:focus-visible {
    outline: 2px solid var(--vl-cornflower);
    outline-offset: 2px;
}

/* Task 5, structural trap #1: an overlay needs a positioned parent, so each
   tile is now a wrapper (.vl-gallery__cell) around the <img>, and the
   wrapper — not the img — is the grid item. grid-column/grid-row moved to
   .vl-gallery__cell--N below; position/min-size guards moved here. Grid
   items stretch by default, so .cell needs no explicit width/height — its
   img child fills it via width/height:100% same as before. */
.vl-gallery__cell {
    position: relative; /* overlay anchor (About's year lozenge; inert for the plot page, which passes no render_overlay) */
    min-width: 0; /* moved from the tile img — same min-size-auto guard, now on the actual grid item */
    min-height: 0;
}

.vl-gallery__tile {
    display: block;
    width: 100%;
    height: 100%;
    /* min 0 (live review 2026-07-28, Liam: "images different sizes"): grid
       items default to min-size auto, so each photo's huge intrinsic HBP
       dimensions were pushing its page's fr tracks around — pages with
       different source images rendered different tile sizes. Zeroing the
       minimums makes every page's tracks the pure node fractions, and the
       container's fixed aspect-ratio + cover crop do the rest. Task 5: the
       guard itself now lives on .vl-gallery__cell (the grid item); this
       comment stays here for provenance since it explains WHY the guard
       exists at all. */
    object-fit: cover;
    border-radius: 20px; /* site card-image convention (card-development.css node 36:10); confirmed by eye on both right tiles, exact node-read on the large tile */
}

/* ── 3 tiles (node-matched layout): large tile spans both rows on the left
   (~1064fr), two tiles stacked on the right (~505fr each), row heights equal
   (each right tile is exactly half the large tile's height, per the pixel
   read above). Ratios kept as literal fr fractions rather than round numbers
   so the 1600px container splits back to the exact node proportions. */
.vl-gallery__grid--3 {
    grid-template-columns: 1064.44fr 505.56fr;
    grid-template-rows: repeat(2, 1fr);
    aspect-ratio: 1600 / 598.75; /* overall bounding box, node-exact */
}

.vl-gallery__grid--3 .vl-gallery__cell--0 {
    grid-column: 1;
    grid-row: 1 / 3;
}

.vl-gallery__grid--3 .vl-gallery__cell--1 {
    grid-column: 2;
    grid-row: 1;
}

.vl-gallery__grid--3 .vl-gallery__cell--2 {
    grid-column: 2;
    grid-row: 2;
}

/* ── 2 tiles (hero-dedupe degrade): large tile left, ONE tile right — that
   single right tile fills the FULL right-column height (there's no second
   image to share it with), so it takes on the large tile's own aspect
   instead of the half-height ratio. No distinct node for this state (only
   ever occurs once hero-dedupe removes a tile); derived from the --3 grid by
   dropping the second row split — flagged for live review. */
.vl-gallery__grid--2 {
    grid-template-columns: 1064.44fr 505.56fr;
    grid-template-rows: 1fr;
    aspect-ratio: 1600 / 598.75;
}

.vl-gallery__grid--2 .vl-gallery__cell--0,
.vl-gallery__grid--2 .vl-gallery__cell--1 {
    grid-column: auto;
    grid-row: 1;
}

/* ── 1 tile (hero-dedupe degrade to a single image): large tile, full
   container width. Uses the large tile's own 16:9 ratio (see file-top pixel
   read) rather than the 2-column bounding box. No distinct node for this
   state either — same derivation caveat as the 2-tile grid above. */
.vl-gallery__grid--1 {
    grid-template-columns: 1fr;
    aspect-ratio: 16 / 9;
}

/* Task 5, structural trap #2: the original made the <img> itself the flex
   item (flex: 0 0 100%). The flex item is now .vl-gallery__single-wrap (the
   same .vl-gallery__cell tile wrapper sits inside it too, for overlay
   parity with the desktop set — inert here, no width/height of its own, a
   plain block that fills its flex-item parent). flex/min-width moved to the
   wrapper below; the img keeps width/aspect-ratio/object-fit/radius. */
.vl-gallery__single-wrap {
    position: relative;
    flex: 0 0 100%; /* one image per carousel page */
    min-width: 0; /* same min-size-auto guard as the desktop tiles — moved from the img */
}

/* Compact slide: one full-width 16:9 image per carousel page — the
   tablet+mobile treatment (Liam 2026-07-28; replaced the earlier tablet
   "1+2 rearranged grid" derivation, which is gone with the desktop set
   hidden below 1025). Same radius as the tiles (inherited from the base
   .vl-gallery__tile rule, which this element also carries — see
   template-parts/gallery-carousel.php). height:auto explicitly overrides
   the base rule's height:100% (harmless in practice — .vl-gallery__cell
   has no definite height here, so the percentage would already resolve as
   auto — but stated explicitly so the compact slide's box is never
   anything but aspect-ratio-driven). */
.vl-gallery__single {
    aspect-ratio: 16 / 9;
    height: auto;
}

/* ── Tablet + mobile (≤1024): swap the sets ── */
@media (max-width: 1024px) {
    .vl-gallery__set--desktop {
        display: none;
    }

    .vl-gallery__set--compact {
        display: block;
    }

    .vl-plot-gallery--carousel-compact {
        padding-bottom: 100px; /* dots geometry carries over from the desktop read */
    }

    /* The desktop-page modifier must not double-apply its padding when
       both modifiers are present (the common case). */
    .vl-plot-gallery--carousel-desktop {
        padding-bottom: 170px;
    }

    .vl-plot-gallery--carousel-desktop.vl-plot-gallery--carousel-compact {
        padding-bottom: 100px;
    }
}

@media (max-width: 767px) {
    .vl-plot-gallery {
        /* Top/bottom both 60px — sitewide mobile section-padding convention
           (CLAUDE.md), same as plot-intro.css/plot-floorplans.css. */
        padding-block: 60px 60px;
    }

    .vl-plot-gallery__heading {
        margin-bottom: 40px; /* scaled down from the 70px desktop read alongside the 60px mobile pad convention, same ratio as plot-floorplans.css's own mobile head-gap rule */
    }

    /* Carousel variants keep the plain 60px mobile padding-bottom; dots gap
       scaled 50→30 — both mirror plot-floorplans.css's own mobile carousel
       rules (derived, no mobile frame, flagged for live review). The old
       mobile tile-stack rules are gone: below 1025 the compact set (one
       16:9 image per slide) is the only one rendered. */
    .vl-plot-gallery--carousel-desktop,
    .vl-plot-gallery--carousel-compact {
        padding-bottom: 60px;
    }

    .vl-gallery__set .vl-carousel-nav {
        margin-top: 30px;
    }
}

/* ── Sticky-tab zone: this section is neither first nor last in the zone —
   Task 12 ("Look at more homes") became the new last section and now owns
   --vl-sticky-stop (moved there from plot-intro.css, which still owns
   --vl-sticky-wait as the first section); this file never touched either
   prop. The tab is OFF for both Development and Plot singles regardless
   (inc/sticky-contact.php), so this has zero visible effect today. */
