/* Development single — interactive site plan.
   Figma reads: section background 5:1365 (full-bleed rect, x=0 w=1920,
   y=3257 h=1741), heading 28:3 ("The Willows site plan"), artwork/stage area
   5:1363 (full-bleed placeholder image, x=0 w=1920, y=3473 h=1425).

   NOTE: the legend/key and hover card have NO Figma node — this is a
   behaviour (interactive site plan), not a static frame element (task brief,
   confirmed against the full page tree — no key/card nodes exist anywhere
   near this section). Styled from the plot-card design language already
   established in dev-plots.css (ink panel #32383b, 20px radius, badge
   colours/sizes, cream/ink chip pattern). Flag to Liam for live review —
   this is the one part of the section built without a design reference. */

.vl-dev-siteplan {
    background: var(--vl-cornflower); /* node 5:1365 fill, exact token match */
    /* Top: section top (3257) to heading top (3345) = 88.
       Bottom: image bottom (3473+1425=4898) to section bottom (3257+1741=4998) = 100
       (matches the 100px bottom convention already used in dev-plots.css). */
    padding-block: 88px 100px;
}

.vl-dev-siteplan__heading {
    /* Node 28:3 sits centred on the full 1920 canvas (x=645, w=630 ->
       centre 960 = canvas centre), NOT left-aligned inside a container like
       the "Available homes" heading — read directly off the node, not
       assumed from the sitewide heading convention. */
    margin: 0 0 56px; /* Liam review 2026-07-23 (node delta read 54; rounded up) */
    /* The SECTION is intentionally full-bleed (artwork spans the viewport)
       so it carries no .vl-edge — this heading must supply its own gutters
       (container-rules rule 1: content never touches the viewport edge;
       violation caught live at tablet width, 2026-07-24). No visual effect
       on desktop where the centred text sits far inside the padding. */
    padding-inline: var(--vl-edge);
    text-align: center;
    color: var(--vl-black); /* node fill is literally #000000 */
    font-size: var(--vl-text-display); /* 70px */
    font-weight: var(--vl-weight-semibold); /* the development-name phrase is Omnes SemiBold in the node */
    line-height: 1; /* = font-size at every breakpoint (Liam, 2026-07-23: big headings line-height equals size) */
}

.vl-dev-siteplan__heading-thin {
    /* " site plan" reads in Omnes Regular in the node — the OPPOSITE
       weighting to dev-plots.css's heading <em> (there the second phrase is
       the heavier one). Read directly off node 28:3's two text spans. */
    font-weight: var(--vl-weight-regular);
}

/* ── Legend / key (no Figma coverage — see note above) ──────────────────── */

/* Key columns (Liam, 2026-07-24, iterated): fixed column COUNTS per
   breakpoint with cells filling their column — 8 desktop / 3 tablet /
   2 mobile — AND partial last rows centred. CSS grid can't centre a
   partial row (tracks are rigid), so this is flex EMULATING the grid:
   each cell's basis is exactly one column width (100% minus the row's
   gaps, over N), which fills full rows identically to repeat(N, 1fr)
   while justify-content centres any remainder. Key stays capped at the
   sitewide content width. */
.vl-siteplan-key {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px;
    width: calc(100% - 2 * var(--vl-edge)); /* never touches the viewport edge below the cap */
    max-width: var(--vl-container); /* 1600 — matches the sitewide content width (Liam, 2026-07-23) */
    list-style: none;
    margin: 0 auto 50px; /* Liam, 2026-07-23 (was 40) */
    padding: 0;
}

.vl-siteplan-key li {
    flex: 0 0 calc((100% - 7 * 12px) / 8); /* one eighth-column, gaps accounted */
    min-width: 0;
}

/* Key-only sub-bands (Liam, 2026-07-24 — content-driven, NOT the global
   breakpoints): 8 cols above 1400 · 5 across small-desktop/large-tablet
   (901–1400, bridging the 8→3 jump; 6 tried live, 5 chosen) · 3 down
   through small tablet and large mobile (501–900) · 2 on small phones. */
@media (max-width: 1400px) {
    .vl-siteplan-key li {
        flex-basis: calc((100% - 4 * 12px) / 5); /* fifths (Liam, 2026-07-24 — tried 6, settled on 5) */
    }
}

@media (max-width: 900px) {
    .vl-siteplan-key li {
        flex-basis: calc((100% - 2 * 12px) / 3); /* thirds — runs down through
                                                    large mobile to 501px
                                                    (Liam, 2026-07-24) */
    }
}

@media (max-width: 500px) {
    .vl-siteplan-key li {
        flex-basis: calc((100% - 12px) / 2); /* halves on small phones */
    }
}

.vl-siteplan-key__btn {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: flex-start; /* swatch + label left-aligned in the equal cells, per Renmore */
    gap: 10px;
    padding: 10px 20px;
    border: 1px solid var(--vl-ink);
    border-radius: var(--vl-radius-pill);
    background: var(--vl-cream);
    color: var(--vl-ink);
    font: inherit;
    font-size: var(--vl-text-nav); /* 18px, matches other pill/chip controls */
    font-weight: var(--vl-weight-regular);
    cursor: pointer;
    transition: background-color 0.2s ease, color 0.2s ease;
}

/* Hover: full ink with white text — identical to the active state (Liam,
   2026-07-23; revised from the 12% wash same day). */
.vl-siteplan-key__btn:hover:not(.is-active) {
    background: var(--vl-ink);
    color: var(--vl-white);
}

.vl-siteplan-key__swatch {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    border-radius: 50%;
}

.vl-siteplan-key__btn.is-active {
    background: var(--vl-ink);
    color: var(--vl-white);
}

/* ── Stage ────────────────────────────────────────────────────────────── */

.vl-siteplan-container {
    position: relative;
    width: 100%; /* full-bleed to the cap below (node 5:1363 spans the 1920
                    canvas); beyond that the plan stops growing and the
                    section's cornflower shows left/right (Liam, 2026-07-23) */
    /* Tokenised 2026-08-26 so both page containers live in variables.css and
       the scale can be reverted from one place — see the REVERT SWITCH block
       there. Node value is 1920px; the token currently holds 1536px (0.8). */
    max-width: var(--vl-siteplan-max, 1920px);
    margin-inline: auto;
    line-height: 0; /* removes the inline-svg baseline gap */
}

.vl-siteplan-container svg {
    display: block;
    width: 100%;
    height: auto;
}

/* ── Fit + zoom viewport (Liam review 2026-07-23) ──────────────────────────
   The whole plan always fits on screen: the viewport takes the SVG's own
   aspect ratio (JS reads the viewBox into --vl-sp-aspect) but is capped at
   80vh — when the cap bites, the SVG letterboxes itself (preserveAspectRatio
   meet) on WHITE, which reads as more of the architect's paper. Zoom/pan is
   handled in siteplan.js (buttons, drag, pinch; wheel only while zoomed so
   page scrolling is never hijacked). */
.vl-siteplan-viewport {
    position: relative;
    overflow: hidden;
    width: 100%; /* ALWAYS full width up to the container's 1920px cap — the
                    plan opens at full size per the client (Liam, 2026-07-23);
                    no viewport-height fitting. Zoom covers the detail needs. */
    aspect-ratio: var(--vl-sp-aspect, 1566 / 939); /* fallback = current artwork */
    background: var(--vl-white);
    touch-action: pan-y; /* unzoomed: the page scrolls normally over the plan */
}

.vl-siteplan-viewport.is-zoomed {
    touch-action: none; /* zoomed: gestures pan the plan, not the page */
    cursor: grab;
}

.vl-siteplan-viewport.is-panning {
    cursor: grabbing;
}

.vl-siteplan-stage {
    width: 100%;
    height: 100%;
    transform-origin: 0 0;
}

/* Inside the viewport the SVG must fill BOTH axes so its own meet-scaling
   letterboxes it (overrides the width/height-auto rule above). */
.vl-siteplan-stage svg {
    width: 100%;
    height: 100%;
}

.vl-siteplan-zoom {
    position: absolute;
    right: 16px;
    bottom: 16px;
    display: flex;
    gap: 8px;
    line-height: normal; /* container has line-height:0 */
}

.vl-siteplan-zoom__btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    height: 40px;
    padding: 0 12px;
    border: 0;
    border-radius: var(--vl-radius-pill);
    background: var(--vl-brick); /* brick = CTA colour (designer soft rules; Liam 2026-07-23) */
    color: var(--vl-white);
    font: inherit;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(50, 56, 59, 0.25);
}

/* +/− drawn as centred bars, not font glyphs — Omnes' glyph metrics sat the
   "+" visibly off-centre in the pill (Liam review 2026-07-23). */
.vl-siteplan-zoom__btn--in::before,
.vl-siteplan-zoom__btn--in::after,
.vl-siteplan-zoom__btn--out::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 14px;
    height: 2px;
    background: currentColor;
    transform: translate(-50%, -50%);
}

.vl-siteplan-zoom__btn--in::after {
    transform: translate(-50%, -50%) rotate(90deg);
}

.vl-siteplan-zoom__btn--reset {
    font-size: 14px;
    font-weight: var(--vl-weight-semibold);
}

.vl-siteplan-zoom__btn:disabled {
    opacity: 0.35;
    cursor: default;
}

/* Colour washes blend in multiply so the artwork's dark detail — plot
   numbers, bay dividers, outlines — reads THROUGH the colour instead of
   being painted over (Liam review 2026-07-23: "numbers on top"). */
.vl-siteplan-container #hotspots .plot polygon,
.vl-siteplan-container #hotspots .plot polyline,
.vl-siteplan-container #hotspots .plot path,
.vl-siteplan-container #hotspots .plot rect {
    mix-blend-mode: multiply;
}

/* Hotspots (built server-side by inc/site-plan-svg.php): invisible at rest
   (fill-opacity="0" set inline), pointer affordance only for plots that
   carry HBP data (JS adds .has-data). Colour feedback is legend-driven. */
.vl-siteplan-container #hotspots .plot {
    cursor: default;
}

.vl-siteplan-container #hotspots .plot.has-data {
    cursor: pointer;
}

/* Plot-page mode (4b): every plot outside this page's house type — including
   hotspots with no HBP data — washes out white so the matching plots stand
   out. Renmore's house-type-page behaviour, Vivly styling.
   Spec: docs/superpowers/specs/2026-09-08-siteplan-mute-design.md

   ⚠️ THREE THINGS HERE ARE LOAD-BEARING. Remove any one and this rule
   silently paints NOTHING — no error, no console warning, it just stops
   working (which is exactly how it shipped broken from 2026-07-28 to
   2026-09-08):

   1. mix-blend-mode: normal — the hotspot shapes are `multiply` by default
      (so artwork numbers read through the colour, Liam 2026-07-23). White is
      the IDENTITY for multiply: white x anything = anything. Under multiply
      this wash is a mathematical no-op.
   2. !important — siteplan.js paints fill/fill-opacity as INLINE styles via
      paintShapes(), and a normal stylesheet rule cannot beat an inline one.
      An !important AUTHOR declaration does outrank a normal inline style,
      which is the cascade rule this depends on. Unmuting needs no cleanup:
      drop the class and the inline key colour resumes.
   3. The element selectors — `mix-blend-mode` is NOT an inherited property.
      Rule above sets `multiply` per SHAPE, so a group-level `normal` would
      leave every shape still multiplying and the wash still invisible. (The
      group does carry the at-rest fill — the injector writes fill on the <g>
      and strips it from the shapes — so inheritance is not the issue here;
      the blend mode and paintShapes()' inline fill are.)

   Tune --vl-mute-fade if the wash is too strong or too weak. */
.vl-siteplan-container {
    --vl-mute-fade: 0.72;
}

.vl-siteplan-container #hotspots .plot.is-muted,
.vl-siteplan-container #hotspots .plot.is-muted polygon,
.vl-siteplan-container #hotspots .plot.is-muted polyline,
.vl-siteplan-container #hotspots .plot.is-muted path,
.vl-siteplan-container #hotspots .plot.is-muted rect {
    fill: #ffffff !important;
    fill-opacity: var( --vl-mute-fade, 0.72 ) !important;
    mix-blend-mode: normal !important;
    stroke: none;
    transition: fill-opacity 0.2s ease;
    /* Fully inert (Liam live review 2026-07-28, closing decision #5 the
       Renmore way): muted plots — every plot outside this page's house
       type — show no hover card, no hover wash, and never navigate.
       pointer-events lets touches/clicks fall through to the artwork, so
       the document-level touchstart handler in siteplan.js also treats
       them as "outside #hotspots" and hides any open card. */
    pointer-events: none;
}

/* Hover wash (siteplan.js toggles .is-hovered on mouseenter/mouseleave and
   touchstart — Task 11 cold-review fix, no CSS previously consumed the
   class). Hotspot shapes carry fill="#000000" inline (inc/site-plan-svg.php),
   so a low fill-opacity reads as a dark wash over the artwork underneath. */
.vl-siteplan-container #hotspots .plot.has-data.is-hovered {
    fill-opacity: 0.15;
}

/* ── Hover / touch card ───────────────────────────────────────────────── */

.vl-siteplan-card {
    position: fixed;
    z-index: 60;
    pointer-events: none; /* purely informational — never blocks the cursor */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease;
}

.vl-siteplan-card.is-active {
    opacity: 1;
    visibility: visible;
}

.vl-siteplan-card__inner {
    width: 240px;
    border-radius: 12px; /* deliberately LESS than the plot card's 20px —
                            full radius read too heavy at this size (Liam
                            review 2026-07-23) */
    overflow: hidden;
    background: var(--vl-ink);
    box-shadow: 0 8px 24px rgba(50, 56, 59, 0.35); /* = --vl-ink shadow, consistent with the plot-card badge shadow */
}

.vl-siteplan-card__media {
    position: relative;
    aspect-ratio: 513 / 342; /* same media proportions as .vl-plot-card__media */
    /* Same content-minimum guard as .vl-dev-card__media (2026-07-23). */
    min-height: 0;
    overflow: hidden;
}

.vl-siteplan-card__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: var(--vl-grey);
}

/* Empty src (photo-less plot) — hide to avoid a broken-image glyph (Task 6 review finding) */
.vl-siteplan-card__image[src=""] { visibility: hidden; }

/* Reserved/sold: greyscale image, same treatment as the plot grid cards
   (dev-plots.css) — data-status set by siteplan.js (Liam review 2026-07-23). */
.vl-siteplan-card[data-status="reserved"] .vl-siteplan-card__image,
.vl-siteplan-card[data-status="sold"] .vl-siteplan-card__image {
    filter: grayscale(1);
}

.vl-siteplan-card__badge {
    position: absolute;
    top: 12px;
    right: 12px;
    padding: 4px 14px;
    border-radius: var(--vl-radius-pill);
    font-size: var(--vl-text-badge); /* 16px, matches .vl-plot-card__badge */
    font-weight: var(--vl-weight-semibold);
    line-height: 16px;
    white-space: nowrap;
}

/* Same three status colours as .vl-plot-card__badge-- (dev-plots.css) —
   kept identical so the site-plan hover card and the plot grid read as one
   status language. */
.vl-siteplan-card__badge--available {
    background: #00b388;
    color: var(--vl-ink);
}

.vl-siteplan-card__badge--reserved {
    background: var(--vl-cornflower);
    color: var(--vl-ink);
}

.vl-siteplan-card__badge--sold {
    background: var(--vl-pink); /* Figma "Color 6" — now a token (see variables.css); the About year lozenge is the second use */
    color: var(--vl-white);
}

/* Coming soon (HBP Pending, 0/7) — mirrors the plot card badge in
   dev-plots.css. Image deliberately NOT greyscaled (see the rule above,
   which lists reserved/sold only). */
.vl-siteplan-card__badge--coming-soon {
    background: var(--vl-ink);
    color: var(--vl-cream);
    /* Same 1px optical lift as the plot card badge (dev-plots.css) so the
       label sits identically in both places. Total vertical padding stays 8px,
       so the pill keeps its height — the split just moves 3/5 instead of 4/4. */
    padding: 3px 14px 5px;
}

/* ── Card body: a mini .vl-plot-card__body (Liam review 2026-07-23) —
   identical structure, weights and row rhythm to the grid cards
   (dev-plots.css), scaled to the 240px hover card. No CTA. ── */

.vl-siteplan-card__body {
    display: flex;
    flex-direction: column;
    gap: 8px; /* mini of the plot card's 12px row gap */
    padding: 20px; /* Liam review 2026-07-23 (up from the scaled 18px) */
}

.vl-siteplan-card__toprow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.vl-siteplan-card__type {
    margin: 0;
    color: var(--vl-white);
    font-size: 20px; /* mini of the plot card's 30px */
    font-weight: var(--vl-weight-regular); /* matches .vl-plot-card__type */
    line-height: 24px;
}

.vl-siteplan-card__plotno {
    margin: 0;
    flex-shrink: 0;
    color: var(--vl-white);
    font-size: 14px; /* mini of the plot card's 18px */
    font-weight: var(--vl-weight-regular);
    line-height: 18px;
    white-space: nowrap;
}

.vl-siteplan-card__price {
    margin: 0; /* row rhythm from the body gap, like the grid cards */
    color: var(--vl-white);
    font-size: 20px; /* mini of the plot card's 30px */
    font-weight: var(--vl-weight-semibold); /* matches .vl-plot-card__price */
    line-height: 24px;
}

.vl-siteplan-card__bottomrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

/* No price row (reserved/sold): top up the 8px gap to 13px under the title —
   the mini of the grid cards' 20px price-less spacing (Liam, 2026-07-23). */
.vl-siteplan-card[data-status="reserved"] .vl-siteplan-card__bottomrow,
.vl-siteplan-card[data-status="sold"] .vl-siteplan-card__bottomrow {
    margin-top: 5px;
}

.vl-siteplan-card__building {
    margin: 0;
    color: var(--vl-white);
    font-style: italic; /* matches .vl-plot-card__building */
    font-weight: var(--vl-weight-regular);
    font-size: 14px;
    line-height: 18px;
}

.vl-siteplan-card__facts {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 16px; /* mini of the plot card's 30px */
}

.vl-siteplan-card__fact {
    display: flex;
    align-items: center;
    gap: 6px; /* mini of the plot card's 10px */
    color: var(--vl-white);
    font-size: 14px;
    font-weight: var(--vl-weight-regular);
    line-height: 18px;
}

.vl-siteplan-card__fact::before {
    content: '';
    display: inline-block;
    background-color: currentColor;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}

.vl-siteplan-card__fact--beds::before {
    width: 20px;  /* plot card 27×19, scaled */
    height: 14px;
    -webkit-mask-image: url("../assets/icon-bed.svg");
    mask-image: url("../assets/icon-bed.svg");
}

.vl-siteplan-card__fact--baths::before {
    width: 15px; /* plot card 20×19, scaled */
    height: 14px;
    -webkit-mask-image: url("../assets/icon-shower.svg");
    mask-image: url("../assets/icon-shower.svg");
}

/* ── Responsive: no Figma coverage below desktop (frame is desktop-only per
   CLAUDE.md) — provisional, Liam reviews live per the task brief. ── */
@media (max-width: 767px) {
    .vl-dev-siteplan {
        padding-block: 60px 60px;
    }

    .vl-siteplan-card__inner {
        width: 200px;
    }
}

/* Brick-button hover (Liam, 2026-07-23). */
.vl-siteplan-zoom__btn {
    transition: background-color 0.2s ease;
}

.vl-siteplan-zoom__btn:hover:not(:disabled) {
    background: var(--vl-brick-dark);
}
