/*
 * Translated from Next.js clients/lumberock.json customCSS.
 * Translations applied: .sg-preview → body, --color-* → --wp--preset--color--*,
 * /clients/lumberock/ paths → theme assets URL.
 * Review before shipping — some chrome selectors (.sg-h1 span etc.) may be unused.
 */

h1 span, h2 span, h3 span, h4 span, h5 span, h6 span,
.sg-h1 span, .sg-h2 span, .sg-h3 span, .sg-h4 span, .sg-h5 span, .sg-h6 span {
  color: transparent;
  -webkit-text-stroke: 2px var(--wp--preset--color--primary);
}

/* Lumberock-specific overrides of parent tokens / rules. */
:root {
  /* Body / app chrome background. Sourced from the `canvas` palette slot
     (gray-20 = #eef2f8 — slightly blue-tinted near-white) so palette
     tweaks in theme.json automatically update the body background — no
     separate hex to keep in sync. Parent's blocks.css uses `--sg-bg`
     for both `html, body` background and a few inset panels; this
     redirect keeps that abstraction intact while pulling the value from
     the canonical palette token. */
  --sg-bg: var(--wp--preset--color--canvas);
}

/* Heavier eyebrow weight than the parent's body-inherited default. */
.sg-block-eyebrow,
.is-style-eyebrow {
  font-weight: 900;
}

/* Lumberock-specific: Hero 3 Up image gets the medium drop shadow.
 * Lives in the child theme because Hero 3 Up is a parent block that
 * should stay shadow-less by default — other client sites might want
 * a flat hero. The shadow lands on the image WRAPPER (not the inner
 * img), so the wrapper's `overflow: hidden` clips the image but
 * doesn't clip its own outer shadow. */
.sg-block-hero-3-up__image {
  box-shadow: var(--wp--preset--shadow--md);
}

/* Stackable: bump the sticky top by one block-gap so the first item
 * doesn't kiss the bottom of the header — gives it the same breathing
 * rhythm authors get everywhere else from --wp--style--block-gap.
 * The parent rule sets top to just the configured --stackable-offset;
 * we wrap that in calc() and add a block-gap. Applies to both authoring
 * modes (single-child legacy → wrapper itself sticky; multi-child V3 →
 * children sticky inside the wrapper). No !important because the child
 * stylesheet loads after the parent's blocks.css at the same selector
 * specificity — source order wins. */
.sg-block-stackable {
  top: calc(var(--stackable-offset, var(--wp--preset--spacing--40)) + var(--wp--style--block-gap));
}
@supports selector(:has(*)) {
  .sg-block-stackable:has(> .acf-innerblocks-container > :nth-child(2)) > .acf-innerblocks-container > * {
    top: calc(var(--stackable-offset, var(--wp--preset--spacing--40)) + var(--wp--style--block-gap));
  }
}

body {
  /* These two are CUSTOM PROPERTIES, and their url() is only resolved where
   * var() is substituted — which is the parent's blocks.css
   * (/wp-content/themes/classic-city-core/assets/blocks.css), not this file.
   * A relative 'assets/…' therefore resolved against the PARENT's assets/
   * directory and 404'd on .../classic-city-core/assets/assets/wood-texture.png.
   * Custom-property URLs must be absolute. (Chrome/Safari resolve against the
   * consuming sheet, Firefox against the declaring one — absolute is the only
   * value that is correct in both.) The parent's own docs use the same
   * absolute form. */
  --bg-texture: url('/wp-content/themes/sg-lumberock/assets/wood-texture.png');
  --bg-texture-sand: url('/wp-content/themes/sg-lumberock/assets/sand-texture.png');
  --bg-texture-size: 40% auto;
  --bg-texture-angle: 45deg;
  /* Plain property, consumed in this same stylesheet — relative is correct. */
  background-image: url('assets/bg.png');
  background-repeat: repeat;
}

/* ==================================================================
 * Hero 50/50 (sg-lumberock/hero-50-50)
 *
 * Two-column hero. Content drives row height; the image cell stretches
 * to match via the same min-height: 0 + align-items: stretch trick the
 * parent's image-hero-50-50 uses in its dynamic-height mode.
 *
 * Background color/gradient are painted on the CONTENT panel only.
 * Image side has no bg, no padding, no shadow — it's just the image.
 * Promotion note: when this graduates to classic-city-core, rename the
 * `.sg-lr-hero-50-50*` selectors to `.sg-block-hero-50-50*` and move
 * this block into the parent's assets/blocks.css.
 * ================================================================== */
.sg-lr-hero-50-50 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  gap: 0;
}

.sg-lr-hero-50-50__media {
  /* Collapse to zero min-content so the content side drives the row.
     align-items: stretch on the grid then re-fills this cell to match. */
  min-height: 0;
  overflow: hidden;
}

.sg-lr-hero-50-50__image,
.sg-lr-hero-50-50__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Empty-state placeholder: fills the media cell with a neutral tone so
   the column structure is visible in the editor without an upload.
   Replaced by the <img> as soon as the author picks an image. */
.sg-lr-hero-50-50__placeholder {
  width: 100%;
  height: 100%;
  min-height: 280px;
  background: var(--wp--preset--color--panel, #fbfcfd);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--wp--preset--color--ink, #192234);
  font-family: var(--wp--preset--font-family--body);
  font-size: var(--wp--preset--font-size--small);
  text-transform: uppercase;
  letter-spacing: var(--wp--custom--eyebrow--letter-spacing, 0.08em);
  opacity: 0.7;
}

.sg-lr-hero-50-50__content {
  padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--60);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--wp--style--block-gap);
}

/* ACF wraps InnerBlocks in `.acf-innerblocks-container` — collapse it
   out of the layout tree so authored eyebrow/heading/paragraph/buttons
   become real flex children and inherit the column's gap + alignment.
   Same pattern the parent uses for every hybrid block (see the parent
   theme's blocks.css `display: contents` list). */
.sg-lr-hero-50-50__content > .acf-innerblocks-container {
  display: contents;
}

@media (max-width: 768px) {
  .sg-lr-hero-50-50 {
    grid-template-columns: 1fr;
  }
  .sg-lr-hero-50-50__media {
    aspect-ratio: 16 / 9;
  }
  .sg-lr-hero-50-50__content {
    padding: var(--wp--preset--spacing--40) var(--wp--preset--spacing--40);
  }
}

/* ===================================================================
   Location Finder — Lumberock overrides
   Base structure/tokens live in classic-city-core/features/location-finder.
   custom.css is enqueued after the feature CSS, so these equal-specificity
   rules win by source order.
   =================================================================== */

/* Treat the whole finder as one standard card: panel surface, the theme's
   default card padding + shadow, radius from the token (0 on Lumberock). */
.sg-block-location-finder {
  background-color: var(--wp--preset--color--panel);
  padding: var(--wp--preset--spacing--30);
  border-radius: var(--wp--custom--radius--default);
  box-shadow: var(--wp--custom--elevation--card, var(--wp--preset--shadow--md));
}

/* The card now owns the padding + surface, so the search form no longer needs
   its own panel/padding. */
.sg-block-location-finder__form {
  padding: 0;
  background-color: transparent;
  border-radius: 0;
}

/* Result items on the canvas surface (contrast against the panel card), with a
   cleaner hover: no shadow, CTA-orange border. */
.sg-block-location-finder__result {
  background-color: var(--wp--preset--color--canvas);
}
.sg-block-location-finder__result:hover,
.sg-block-location-finder__result.is-active {
  border-color: var(--wp--preset--color--cta);
  box-shadow: none;
}

/* ===================================================================
   Post Card (sg-lumberock/post-card) — query-loop card layout

   The block itself emits nothing but the parent's `.sg-image-card`
   partial, so it needs no cosmetic CSS. The one thing it DOES need is
   for the card to fill its grid cell: `core/post-template` wraps every
   result in an `<li class="wp-block-post">`, and `.sg-image-card` relies
   on `height: 100%` to keep its footer ("Read More") anchored to the
   bottom edge. A stretched grid item doesn't reliably give a percentage
   height a definite basis, so make the `<li>` a flex column and let the
   card grow into it — same effect, no percentage-resolution guesswork.
   =================================================================== */
.wp-block-post-template > li:has(> .sg-image-card) {
  display: flex;
  flex-direction: column;
}

.wp-block-post-template > li > .sg-image-card {
  flex: 1 1 auto;
}

/* ===================================================================
   Query pagination (blog listing at /blog/)

   Ships bare from core: run-together numbers with no hit area and a
   `.current` that is indistinguishable from its neighbours. Treat each
   number as a small square button on the card surface, with the active
   page in navy (`primary`) — CTA orange is reserved for action
   affordance per CLAUDE.md Rule 1, and this isn't a CTA.

   All values come from presets — spacing 10/20/50, shadow sm, palette
   slugs + their `-opposite` pair tokens. Corners stay sharp because
   `radius.default` is 0px on this site; the token is referenced rather
   than a literal so a future rounding decision lands here for free.
   =================================================================== */
/* Tag qualifier (`nav.`) on purpose: WP emits a per-instance layout rule
   `.wp-container-core-query-pagination-is-layout-… { gap: var(--wp--style--
   block-gap) }` for the block's flex layout at 0,1,0. Matching that with a
   type selector puts us at 0,1,1 so the tighter pagination gap wins
   regardless of which stylesheet the browser sees last. */
nav.wp-block-query-pagination {
  gap: var(--wp--preset--spacing--10);
  align-items: center;
}

.wp-block-query-pagination-numbers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--wp--preset--spacing--10);
  /* Core's markup puts the numbers in one text node; the flex gap above
     replaces the whitespace it relies on for separation. */
  margin: 0;
}

.wp-block-query-pagination .wp-block-query-pagination-previous,
.wp-block-query-pagination .wp-block-query-pagination-next,
.wp-block-query-pagination-numbers .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 50px square = a comfortable pointer + touch target. */
  min-width: var(--wp--preset--spacing--50);
  min-height: var(--wp--preset--spacing--50);
  padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);
  border-radius: var(--wp--custom--radius--default);
  background-color: var(--wp--preset--color--panel);
  color: var(--wp--preset--color--ink);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  transition:
    background-color 120ms ease,
    color 120ms ease,
    box-shadow 120ms ease;
}

.wp-block-query-pagination .wp-block-query-pagination-previous:hover,
.wp-block-query-pagination .wp-block-query-pagination-next:hover,
.wp-block-query-pagination-numbers a.page-numbers:hover,
.wp-block-query-pagination .wp-block-query-pagination-previous:focus-visible,
.wp-block-query-pagination .wp-block-query-pagination-next:focus-visible,
.wp-block-query-pagination-numbers a.page-numbers:focus-visible {
  /* `primary-alt` (lighter navy) rather than `primary` here so hover
     stays visually distinct from the `.current` state below — both are
     navy family, but hover is the lighter step and active is the
     deepest one. See the note above `.current` for why. */
  background-color: var(--wp--preset--color--primary-alt);
  color: var(--wp--custom--color--primary-alt-opposite);
  box-shadow: var(--wp--preset--shadow--sm);
  text-decoration: none;
}

/* Active page — the one state that has to be unmistakable. Navy
   (`primary`) with its paired opposite (never a hand-picked text
   colour) and the small shadow so it sits a step above the rest of the
   row. CTA orange was ruled out here per CLAUDE.md Rule 1 — orange is
   reserved for action affordance, not a pagination indicator.
   `primary` (deep navy) is one step darker than the `primary-alt`
   (lighter navy) used for :hover/:focus-visible above, so the active
   page stays visually distinguishable even when a visitor hovers a
   neighbouring, non-current page. */
.wp-block-query-pagination-numbers .page-numbers.current {
  background-color: var(--wp--preset--color--primary);
  color: var(--wp--custom--color--primary-opposite);
  box-shadow: var(--wp--preset--shadow--sm);
}

/* The "…" gap marker is not a control — strip the button chrome so it
   doesn't read as a clickable page. */
.wp-block-query-pagination-numbers .page-numbers.dots {
  min-width: 0;
  padding-left: 0;
  padding-right: 0;
  background-color: transparent;
  box-shadow: none;
  opacity: 0.6;
}
/* ==================================================================
 * Internal Link Sets
 * Blocks: sg-lumberock/link-set-{projects,collections,performance}
 * See inc/link-sets.php for the shared-items / per-page-copy split.
 * ================================================================== */

/* Editor-only parity. The parent nulls the wrapper bg for these blocks when
 * `has-background` is present (frontend strips it, editor doesn't). Our
 * link-set blocks delegate to the parent templates but emit their OWN
 * wp-block-* class, so they fall out of that selector list — re-add them or
 * the editor preview paints a background the frontend won't have. */
:is(
  .wp-block-sg-lumberock-link-set-projects,
  .wp-block-sg-lumberock-link-set-performance
).has-background {
  background: none !important;
  background-color: transparent !important;
  color: inherit !important;
}

/* Generated link list inside the Project Types overlay card. Sits after the
   author's InnerBlocks copy, so it needs its own top rhythm. */
.sg-link-set__list {
  list-style: none;
  margin: var(--wp--preset--spacing--20) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.sg-link-set__list-heading {
  margin: var(--wp--preset--spacing--30) 0 0;
  opacity: 0.8;
}

.sg-link-set__item + .sg-link-set__item {
  border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
}

.sg-link-set__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wp--preset--spacing--20);
  padding: var(--wp--preset--spacing--10) 0;
  color: inherit;
  text-decoration: none;
  font-weight: 600;
  transition: gap 0.15s ease, opacity 0.15s ease;
}

.sg-link-set__link:hover,
.sg-link-set__link:focus-visible {
  gap: var(--wp--preset--spacing--30);
  text-decoration: underline;
}

.sg-link-set__link i {
  flex: 0 0 auto;
  font-size: 0.8em;
  opacity: 0.7;
}

/* Performance section heading — image-columns has no header of its own. */
.sg-link-set__head {
  margin-bottom: var(--wp--preset--spacing--40);
}

/* ------------------------------------------------------------------
 * Link pods injected into the link-set grids (the five-item case — see
 * blocks/link-set-collections/render.php and link-set-performance/render.php).
 * A `.sg-block-link-pod` normally lives inside the link-pods block's own
 * `--block-columns` grid; the parent's pod CSS handles all of its internals
 * (flex column, padding, radius, hover, palette pair-helpers), and grid items
 * stretch to full cell size in any grid, so only the host-specific deltas
 * below are needed. `.sg-link-set__pod` (Collections "view all" cell) needs
 * none today — the pod's own rules cover it.
 * ------------------------------------------------------------------ */

/* Performance: the folded-head pod prepended into `.sg-block-columns`. Its
   `.sg-block-card` siblings carry the card elevation shadow — match it so the
   pod reads as a peer cell, and kill the pod's hover-lift affordance since
   this cell is deliberately NOT a link. Center the copy vertically so it
   reads as a header cell rather than a card missing its image. */
.sg-link-set__head-pod {
  justify-content: center;
  box-shadow: var(--wp--custom--elevation--card, var(--wp--preset--shadow--md));
}
.sg-link-set__head-pod:hover,
.sg-link-set__head-pod:focus-visible {
  transform: none;
  box-shadow: var(--wp--custom--elevation--card, var(--wp--preset--shadow--md));
}

/* The pod content is a 10px-gap flex column, not a block-gap container; the
   parent's default eyebrow→heading collapse rule subtracts a full block-gap
   and would pull the h2 up over the eyebrow here. Zero the margin — the flex
   gap already provides the intended 10px rhythm. */
.sg-link-set__head-pod .is-style-eyebrow + h2 {
  margin-block-start: 0;
}

/* Keep h2 semantics (it IS the section heading) but step the type down to
   the h3 scale — the full h2 clamp overwhelms a one-third-width card cell. */
.sg-link-set__head-pod .sg-block-link-pod-title {
  font-size: var(--wp--custom--fs--h-3, 34px);
}

/* ==================================================================
 * Product List by Collection — sg-lumberock/product-list
 *
 * The block delegates its head to `ccc_render_block_head()` and every
 * card to `ccc_render_image_card()`, and reuses the parent's
 * `.sg-block-image-card-grid` layout wholesale. So everything below is
 * an override of parent behaviour that product data specifically needs
 * — nothing here re-implements the card.
 * ================================================================== */

/* `.sg-block-head` ships with `margin: 0 auto` and no bottom rhythm; the
   blocks that use it own the gap to their content. Same value the
   link-set blocks use for their head. */
.sg-block-product-list .sg-block-head {
  margin-bottom: var(--wp--preset--spacing--40);
}

/* Sparse collections. Genesis and Veranda have exactly ONE product each
   and Coastal has two — at 1 or 2 grid tracks the cards would stretch to
   full content width and read as a broken hero rather than a card. Cap
   the grid so the cards keep roughly the width they'd have in a normal
   3-up row, and centre what's left. Derived from the layout token, not a
   magic pixel value. */
.sg-block-product-list__grid.is-count-1,
.sg-block-product-list__grid.is-count-2 {
  margin-inline: auto;
}
.sg-block-product-list__grid.is-count-1 {
  max-width: calc(var(--wp--custom--layout--narrow-size) / 2);
}
.sg-block-product-list__grid.is-count-2 {
  max-width: var(--wp--custom--layout--narrow-size);
}

/* Chris: fixed 16/9 frame, `cover`, top-aligned — no padding around the
   media. Overrides the parent's per-card `--card-aspect-ratio` custom
   property (render.php no longer sets one, so this is also just the
   partial's own default) and its centered `object-position`, so the
   top of every image — where the product detail actually is — stays
   visible instead of the middle. Scoped to this block only; never touch
   `.sg-image-card` unscoped or every card grid on the site restyles.
   Tradeoff: the portrait spec-sheet screenshots (~500x650) now crop hard
   at 16/9 cover — only their top slice shows. That's the explicit ask;
   flagged in the deploy report for Chris to eyeball. */
.sg-block-product-list .sg-image-card__media {
  aspect-ratio: 16 / 9;
}
.sg-block-product-list .sg-image-card__image {
  object-fit: cover;
  object-position: center top;
}

/* Dimensions and available sizes are emitted as one newline-separated
   string, because the partial escapes `description` and so can't take
   markup. `pre-line` turns the newline into the line break the two
   labels need. Degrades to one wrapped line if this rule is ever lost. */
.sg-block-product-list .sg-image-card__description {
  white-space: pre-line;
}

/* The card title is an <h3> in this block (a collection page needs a
   real product outline for a11y / SEO) where the shared partial renders
   a <p>. Reset theme.json's `elements.h3` typography — heading face,
   34px clamp, 0.9 line-height, uppercase — back to exactly what
   `.sg-image-card__title` gives the <p>, so the change is semantic only
   and the two are visually identical. Touch this if the parent ever
   restyles `.sg-image-card__title`. */
.sg-block-product-list h3.sg-image-card__title {
  font-family: var(--wp--preset--font-family--body);
  font-size: var(--wp--preset--font-size--large);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: normal;
  text-transform: none;
  margin: 0;
}

/* ==================================================================
 * Multi Column Photo Gallery — sg-lumberock/multi-column-photo-gallery
 *
 * Four-image collage / full-width visual break. One grid, four cells:
 *
 *   desktop (4 tracks)               mobile ≤640px (2 tracks)
 *   ┌─────────┬────wide────┐         ┌──────wide──────┐
 *   │         ├─────┬──────┤         ├───────┬────────┤
 *   │  main   │ sq1 │ sq2  │         │       │  sq1   │
 *   │         │     │      │         │ main  ├────────┤
 *   └─────────┴─────┴──────┘         │       │  sq2   │
 *                                    └───────┴────────┘
 *
 * Height mechanism: the wide (2:1) and square (1:1) cells carry fixed
 * aspect-ratios, so THEY size the grid rows. The main cell spans all
 * rows and its <img> is absolutely positioned — zero intrinsic height —
 * so it stretches to exactly the ratio-driven total. Every edge stays
 * flush at any viewport width; no JS, no magic pixels. (Effective main
 * ratio on desktop = column-width : column-width + gap/2 — a whisker
 * taller than square. On mobile main reads as a ~1:2 portrait.)
 *
 * Gap is the theme block-gap token (spacing--30 fallback — same value
 * theme.json styles.spacing.blockGap resolves to). Corners stay sharp
 * per the theme's radius default of 0.
 * ================================================================== */
.sg-block-multi-column-photo-gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas:
    "main main wide wide"
    "main main sq1  sq2";
  gap: var(--wp--style--block-gap, var(--wp--preset--spacing--30));
}

.sg-block-multi-column-photo-gallery__cell {
  position: relative;
  overflow: hidden;
  min-height: 0;
}

.sg-block-multi-column-photo-gallery__cell--main { grid-area: main; }
.sg-block-multi-column-photo-gallery__cell--wide {
  grid-area: wide;
  aspect-ratio: 2 / 1;
}
.sg-block-multi-column-photo-gallery__cell--sq1 {
  grid-area: sq1;
  aspect-ratio: 1 / 1;
}
.sg-block-multi-column-photo-gallery__cell--sq2 {
  grid-area: sq2;
  aspect-ratio: 1 / 1;
}

.sg-block-multi-column-photo-gallery__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The main image is the one cell WITHOUT a fixed ratio — absolute
   positioning removes it from row sizing so the other three cells
   alone decide the collage height and main just fills it. */
.sg-block-multi-column-photo-gallery__cell--main
  .sg-block-multi-column-photo-gallery__image {
  position: absolute;
  inset: 0;
}

/* Mobile: reflow instead of dumb-stacking. The wide image becomes a
   full-width 2:1 banner; below it the main image runs tall down the
   left while the two squares stack on the right — still a collage,
   same flush-edge mechanism (squares drive the rows, main absolute-
   fills its span). */
@media (max-width: 640px) {
  .sg-block-multi-column-photo-gallery {
    grid-template-columns: repeat(2, 1fr);
    grid-template-areas:
      "wide wide"
      "main sq1"
      "main sq2";
  }
}

/* ==================================================================
 * Swatch Grid by Collection — sg-lumberock/swatch-grid
 *
 * Square color chips for one collection, captioned with the swatch
 * name. Sits alongside product-list on a collection page, so it shares
 * that block's head rhythm — but NOT its grid: swatches are small,
 * uniform, and there can be 2 of them or 12.
 *
 * COLUMNS ARE AUTHORED (2026-08-07). This block originally used an
 * auto-fill/minmax grid so width alone decided the count. That reasoning
 * only holds while the block spans the content width. Chris nests it
 * inside 2-column layouts — especially for Genesis/Veranda, which have
 * two swatches each — and there the block's width stops tracking the
 * viewport entirely. CSS cannot ask how wide its own container is (short
 * of container queries), so an auto count picks a tile size that suits
 * neither the page nor the nested case. The author knows the container;
 * render.php passes their choice down as `--sg-swatch-columns`.
 *
 * Default is 5. One custom property rather than seven `is-columns-N`
 * classes: the value is a magnitude, so it belongs in a variable.
 *
 * MOBILE (<=768px) is a mode, not a magnitude, so it IS a class:
 *   .is-mobile-grid   → 2 across, everything visible (default)
 *   .is-mobile-scroll → one swiping row, chips stay large
 * Below that breakpoint the authored column count is deliberately
 * ignored — 5 columns on a 375px screen is a 60px chip.
 * ================================================================== */

/* Head rhythm: `.sg-block-head` ships with `margin: 0 auto` and no bottom
   gap — the blocks that use it own the spacing to their content. Same
   value product-list and the link-set blocks use. */
.sg-block-swatch-grid .sg-block-head {
  margin-bottom: var(--wp--preset--spacing--40);
}

.sg-block-swatch-grid__grid {
  display: grid;
  /* Authored count, defaulting to 5 if the property is ever missing (older
     block markup saved before the field existed). */
  grid-template-columns: repeat(var(--sg-swatch-columns, 5), minmax(0, 1fr));
  gap: var(--wp--style--block-gap, var(--wp--preset--spacing--30));
  /* It's a <ul> for semantics (it IS a list of swatches); strip the list
     chrome so it reads as a grid. */
  margin: 0;
  padding: 0;
  list-style: none;
}

/* `minmax(0, 1fr)` above rather than plain `1fr`: a 1fr track floors at its
   content's min-content width, so a long single-word swatch name ("Chocolate
   Brown" wrapping to "Chocolate") could push a track wider than its share and
   overflow the block at high column counts. minmax(0, …) lets tracks shrink
   below that and the caption wrap instead. */

/* ---- Desktop hover: zoom the swatch, lift it, hide its name, dim the rest ----
   Gated on BOTH `min-width: 769px` (desktop, matching the block's own
   breakpoint) and `hover: hover`. The second half matters: on a touch screen
   a :hover rule latches after a tap and stays applied until the visitor taps
   elsewhere, so a tapped swatch would sit zoomed with its name invisible and
   every other swatch stuck at 50%. Excluding touch pointers avoids that
   entirely rather than trying to undo it. */
@media (min-width: 769px) and (hover: hover) {
  .sg-block-swatch-grid__item {
    transition: opacity 0.2s ease, transform 0.3s ease;
  }
  .sg-block-swatch-grid__figure {
    transition: box-shadow 0.2s ease;
  }
  .sg-block-swatch-grid__name {
    transition: opacity 0.2s ease;
  }

  /* Dim every sibling while one swatch is hovered.

     `:has()` on the GRID, not `:hover` on the grid: `.grid:hover .item` would
     also fire when the pointer is merely in the gap between tiles, dimming
     everything with nothing highlighted. `:has(.item:hover)` only matches
     when a real tile is under the pointer. Browsers without :has() simply
     skip this rule — the zoom, lift and name fade still work, they just
     don't get the dimming.

     The `:not(:hover)` is load-bearing, NOT belt-and-braces. Written as
     `… .item { opacity: .5 }` plus `.item:hover { opacity: 1 }` to undo it,
     the dim rule wins and the hovered swatch dims along with the rest:
     `:has()` takes the specificity of its most specific argument, so
     `.grid:has(.item:hover) .item` scores (0,4,0) against `.item:hover`'s
     (0,2,0). Measured that exact failure before fixing it. Excluding the
     hovered tile from the selector sidesteps the contest entirely. */
  .sg-block-swatch-grid__grid:has(.sg-block-swatch-grid__item:hover)
    .sg-block-swatch-grid__item:not(:hover) {
    opacity: 0.5;
  }

  .sg-block-swatch-grid__item:hover {
    /* The whole tile pops OUT over its neighbours — the scale is on the grid
       item, not on the image inside a clipped frame, so the swatch grows past
       its cell instead of magnifying within it. `transform` doesn't affect
       layout, so nothing reflows; the tile just overlaps. At 1.3 a tile grows
       by ~15% of its width on each side, which is a little more than the
       block-gap, so it laps slightly onto its neighbours — which is the
       point. */
    transform: scale(1.3);
    /* Above the dimmed pack, so both the tile and its shadow land on top of
       the neighbours rather than under them. */
    position: relative;
    z-index: 1;
  }
  .sg-block-swatch-grid__item:hover .sg-block-swatch-grid__figure {
    box-shadow: var(--wp--custom--elevation--raised, var(--wp--preset--shadow--lg));
  }
  .sg-block-swatch-grid__item:hover .sg-block-swatch-grid__name {
    opacity: 0;
  }
}

/* A 1.3x scale is a large movement in a grid of a dozen tiles. Keep the
   meaning of the hover (lift + dim + name fade, none of which are motion)
   and drop the zoom and the easing for visitors who ask for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .sg-block-swatch-grid__item,
  .sg-block-swatch-grid__figure,
  .sg-block-swatch-grid__name {
    transition: none;
  }
  .sg-block-swatch-grid__item:hover {
    transform: none;
  }
}

@media (max-width: 768px) {
  /* Default mobile mode: two across, everything on screen. The authored
     desktop count is dropped here on purpose — at 375px, 5 columns is a
     ~60px chip. */
  .sg-block-swatch-grid.is-mobile-grid .sg-block-swatch-grid__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Scroll mode: one row that swipes. `grid-auto-flow: column` + an explicit
     track width keeps chips a usable size instead of squeezing N into the
     viewport.

     The track is a DEFINITE size, not `minmax(120px, 42%)` — measured: with
     minmax the track sizes to its content and settles on the 120px floor,
     because a caption's min-content width is one short word. That made
     scroll-mode chips (120px) SMALLER than grid-mode chips (156px), which
     inverts the whole point of the mode.

     `min(40%, 160px)`: 40% keeps two full chips plus a ~39px sliver of the
     third at 375px — that sliver is the only cue that more exists to the
     right, which is why it isn't 50%. The 160px cap stops chips ballooning
     as the viewport approaches the 768px breakpoint. */
  .sg-block-swatch-grid.is-mobile-scroll .sg-block-swatch-grid__grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: min(40%, 160px);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    /* Full-bleed the scroller to the block's edges so the row reads as
       continuing past the viewport rather than stopping at a margin, then
       restore the inset with padding so the first chip still lines up with
       the heading above it. */
    scroll-padding-inline-start: var(--wp--preset--spacing--20);
    padding-bottom: var(--wp--preset--spacing--20);
  }
  .sg-block-swatch-grid.is-mobile-scroll .sg-block-swatch-grid__item {
    scroll-snap-align: start;
  }
  /* The scroll container is a tab stop (see render.php) — give it a visible
     focus ring, since by default a focused <ul> shows nothing. */
  .sg-block-swatch-grid.is-mobile-scroll .sg-block-swatch-grid__grid:focus-visible {
    outline: 2px solid var(--wp--preset--color--primary);
    outline-offset: 2px;
  }
}

.sg-block-swatch-grid__figure {
  margin: 0;
  /* Positioning context for the name chip laid over the swatch below. */
  position: relative;
  /* Deliberately NOT `overflow: hidden`. The hover zoom scales the whole
     tile outward over its neighbours (see the hover block below) rather
     than magnifying the image inside a fixed frame, so there is nothing to
     clip — and clipping here would have no effect on a scaled ancestor
     anyway. */
}

/* Square, because the source images are square close-ups of board surface
   (500×500) — `cover` here only guards against a future non-square upload.
   Corners stay sharp per the theme's radius default of 0.
   No tile background / border / shadow: the image fills the tile edge to
   edge, so panel chrome behind it would never be visible (CLAUDE.md Rule 4
   is about surfaces you can actually see). The `--solid` hex fallback
   needs no extra rule — it's a <span> with the same class, so it inherits
   the square + block display and paints its own inline background. */
.sg-block-swatch-grid__image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* Extra-small caption, per the brief. Uses the theme's `x-small` preset
   (12px) rather than a literal — it tracks theme.json if the scale moves.
   Body face + 600 weight so the name reads as a label, not a heading. */
/* Name chip, laid over the bottom of the swatch rather than captioned
   beneath it. Taking it out of flow makes every tile exactly as tall as its
   square image, so rows stay even regardless of column count or how many
   lines a name wraps to — which is what lets fewer/wider columns still read
   as a tidy grid.

   Background + text color come from the `has-canvas-background-color
   has-background` utilities in render.php, not from here, so the pair helper
   supplies canvas-opposite automatically. Radius uses the theme token, which
   is 0 on Lumberock — a square chip, consistent with every other surface on
   the site. Change that token reference to a large value if it should read
   as a rounded pill instead. */
.sg-block-swatch-grid__name {
  position: absolute;
  bottom: var(--wp--preset--spacing--10);
  /* Centered via inset-0 + auto margins, NOT `left: 50%` + translateX.
     Measured: with `left: 50%` and `right: auto` an absolutely positioned
     box gets its available width from the left offset to the containing
     block's right edge — i.e. HALF the tile. The transform then slides it
     into place visually, but the shrink-to-fit width was already computed
     against 112px, so "Chocolate Brown" wrapped to three lines inside a
     224px swatch. Pinning both insets restores the full width as the
     basis; `width: fit-content` keeps the chip hugging its text and the
     auto margins do the centering. */
  left: 0;
  right: 0;
  width: fit-content;
  margin-inline: auto;
  /* Only genuinely long names wrap, and they wrap inside the chip rather
     than running to the swatch edges. */
  max-width: calc(100% - (var(--wp--preset--spacing--10) * 2));
  /* em-relative so the chip stays proportional to the label: the spacing
     scale starts at 10px, which reads chunky wrapped around 12px text. */
  padding: 0.35em 0.7em;
  border-radius: var(--wp--custom--radius--default);
  font-family: var(--wp--preset--font-family--body);
  font-size: var(--wp--preset--font-size--x-small);
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
}

/* ===================================================================
   Header nav — desktop dropdown horizontal alignment

   BUG: WP core's navigation block right-aligns every first-level
   dropdown panel to its own trigger whenever the row itself is
   right-justified:

     wp-includes/blocks/navigation/style.css (~L393-398)
       .wp-block-navigation.items-justified-right .wp-block-navigation__container
       .has-child .wp-block-navigation__submenu-container { left: auto; right: 0; }

   Our header nav IS right-justified (`"layout":{"justifyContent":"right"}`
   in parts/header.html) — and so is the PARENT theme's own default
   header.html, so every CCC client nav trips this same core rule. Core's
   assumption is "there's usually one dropdown near the right edge, so
   pin its RIGHT edge to its trigger's RIGHT edge." Fine with one dropdown.
   We have five. Whenever a trigger label is narrower than its panel
   content (Collections, Resources — the Collections repro Chris flagged),
   right-pinning makes the panel hang leftward off the trigger instead of
   dropping straight down under it. Confirmed empirically: rendering the
   real header markup with the real CSS cascade at 1280px showed every
   panel's right edge exactly matching its trigger's right edge
   (sub_right === li_right for all five), with panels wider than their
   trigger hanging left — precisely the reported symptom.

   FIX: left-align first-level dropdowns under their own trigger — applied
   uniformly to all five items, including the last one ("Get Started").

   Right-edge overflow check (the concern: once left-aligned, a dropdown
   under a nav item near the viewport's right edge could push its panel
   off-screen). Measured empirically against the real rendered header
   (real markup + real CSS cascade, forced open via the real :hover
   min-width:200px rule) at the three required widths, checking the
   rightmost/most-at-risk item ("Get Started"):

     1280px → panel would span to x=892,  1058px of clearance → 388px margin
     1440px → panel would span to x=898,  1440px viewport      → 542px margin
     1920px → panel would span to x=898,  1920px viewport      → 1022px margin

   (The header content anchors to the right edge with a fixed 30px pad
   regardless of viewport — widening the viewport only adds room to the
   LEFT of the logo/nav block, not between the nav and the right edge, so
   margin only grows at wider widths, never shrinks.) No overflow at any
   required width, with 388px+ to spare even at the narrowest — no
   :last-child exception needed. If a future content change (a much
   longer "Get Started" submenu label, or a 6th top-level item after it)
   erodes that margin, revisit with a `max-width` clamp on the submenu
   container rather than reintroducing the right-aligned exception, so
   every dropdown keeps dropping straight down under its trigger.

   Specificity: core's rule is `.wp-block-navigation.items-justified-right
   .wp-block-navigation__container .has-child .wp-block-navigation__
   submenu-container` = 5 classes. We match it class-for-class and add the
   `header.wp-block-template-part` prefix the parent's own blocks.css
   already uses to scope header-only nav rules (~L2495), landing at 6
   classes + 1 type — ahead regardless of cascade order, though this
   stylesheet also loads after blocks.css per functions.php.

   Scope: `>` combinators restrict this to FIRST-level submenus only, so
   any future nested flyout keeps core's default. Mobile is untouched —
   the off-canvas overlay resets these same elements to `position: static`
   via a much higher-specificity core selector (repeated-class hack on
   `.is-menu-open ... .wp-block-navigation__submenu-container`), so `left`/
   `right` are inert there regardless of what we set here.

   Child vs. parent: the triggering `justifyContent:"right"` layout also
   ships in classic-city-core's own default header.html, so this is
   arguably a shared-default bug, not a Lumberock-only one. But whether it
   actually surfaces depends on each site's specific trigger-label-length
   vs. submenu-content-width mix — a judgment call, not an obvious
   "every client wants this" fix. Landing it in the child per this child
   theme's own CLAUDE.md guidance ("when in doubt, prefer the child");
   worth a parent-theme promotion once proven out across more than one
   client nav. */
header.wp-block-template-part .wp-block-navigation.items-justified-right
  .wp-block-navigation__container
  > .has-child
  > .wp-block-navigation__submenu-container {
  left: 0;
  right: auto;
}

/* ===================================================================
   Header nav — interaction states (hover / focus-visible / active page)

   No hover/focus/active convention existed for this nav before. The
   nearest precedent in this file is the query-pagination treatment above
   (`nav.wp-block-query-pagination` block, ~L218) — hover = `primary-alt`,
   active/current = `primary` (one step darker), CTA orange ruled out for
   either because Rule 1 in CLAUDE.md reserves `cta` for action affordance,
   not navigational indicators. Same logic applies here: reused rather
   than reinvented, just translated from that block's filled-square
   treatment into an underline for the header bar's inline-text context.

   Contrast checked against the actual rendered colors (not eyeballed):
     primary-alt on light-alt/panel header bar   → 11.68:1
     primary     on light-alt/panel header bar   → 15.53:1
     primary-alt text on canvas wash (submenu)   → 10.68:1
     primary     text on white (submenu)         → 15.95:1
   All comfortably clear AA (4.5:1) and AAA (7:1).
   =================================================================== */

/* Top-level items: reserve a 2px transparent underline space at all times
   so hover/focus/active never shifts layout — only its color/opacity
   changes. `::before` (not `::after` — the parent's blocks.css already
   uses `::after` on `.has-child` for its invisible hover-bridge, L2513). */
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: var(--wp--preset--color--primary-alt);
  opacity: 0;
  transform: scaleX(0.6);
  transition: opacity 120ms ease, transform 120ms ease, background-color 120ms ease;
  pointer-events: none;
}
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-item__content,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-submenu__toggle {
  transition: color 120ms ease;
}
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item:hover::before,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item:focus-within::before {
  opacity: 1;
  transform: scaleX(1);
}
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item:hover > .wp-block-navigation-item__content,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item:hover > .wp-block-navigation-submenu__toggle,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-item__content:focus-visible,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-submenu__toggle:focus-visible {
  color: var(--wp--preset--color--primary-alt);
}

/* Submenu (dropdown) rows: subtle background wash + text color shift.
   Panel bg is core's default white; `canvas` (the body-background token,
   a hair off pure white) reads as a soft wash without competing with the
   active-row accent bar below. */
header.wp-block-template-part .wp-block-navigation__submenu-container .wp-block-navigation-item > a.wp-block-navigation-item__content {
  transition: background-color 120ms ease, color 120ms ease;
}
header.wp-block-template-part .wp-block-navigation__submenu-container .wp-block-navigation-item > a.wp-block-navigation-item__content:hover,
header.wp-block-template-part .wp-block-navigation__submenu-container .wp-block-navigation-item > a.wp-block-navigation-item__content:focus-visible {
  background-color: var(--wp--preset--color--canvas);
  color: var(--wp--preset--color--primary-alt);
}

/* Active page. Deliberately distinct from hover, not just a darker shade
   of it: hover is transient (`primary-alt`, appears only on interaction);
   active is permanent (`primary` — one step darker — plus bold weight),
   so a visitor can tell "where I am" from "what I'm pointing at" even
   while hovering a neighboring item. `current-menu-item` /
   `current-menu-ancestor` are the class names WP core's navigation block
   itself would emit for a normal post/page-linked nav; this nav's links
   are hand-typed URLs without an `id` attribute so core never adds them
   (confirmed by rendering the header part server-side against a
   Collections sub-page — no such classes appeared). A small script in
   inc/header-nav-active-state.php adds the same classes client-side by
   comparing each link's href to the current URL, so these rules are live
   rather than inert. Covers both the item that IS the current page and a
   top-level item whose CHILD is the current page (ancestor) — several nav
   targets are collection/performance sub-pages, so the ancestor case
   fires often. */
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-item::before,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-ancestor::before {
  opacity: 1;
  transform: scaleX(1);
  background-color: var(--wp--preset--color--primary);
}
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-item > .wp-block-navigation-item__content,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-ancestor > .wp-block-navigation-item__content,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-item > .wp-block-navigation-submenu__toggle,
header.wp-block-template-part .wp-block-navigation__container > .wp-block-navigation-item.current-menu-ancestor > .wp-block-navigation-submenu__toggle {
  color: var(--wp--preset--color--primary);
  font-weight: 600;
}

/* Current item inside an open dropdown — same navy, but a left accent bar
   instead of the underline (the row is already a horizontal band, and a
   left bar reads better than a bottom line on a list row). Not combined
   with the hover wash's background-color so a hovered *and* current row
   still shows both cues (wash from :hover, bar + color from .current-menu-item). */
header.wp-block-template-part .wp-block-navigation__submenu-container .wp-block-navigation-item.current-menu-item > a.wp-block-navigation-item__content {
  color: var(--wp--preset--color--primary);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--wp--preset--color--primary);
}

/* ===================================================================
   Image Hero 50/50 (classic-city-core/image-hero-50-50, ".sg-block-hero-bg")
   — kill the 100vh floor site-wide on Lumberock.

   Parent's base rule (blocks.css ~1352) sets `min-height: 100vh` on
   `.sg-block-hero-bg`. Parent also ships a `.dynamic-height` mode
   (blocks.css ~1394-1412, applied when the block's `full_height` ACF
   field is false) that does THREE things together: drops the min-height,
   adds top/bottom padding to the content pane so it isn't cramped without
   the tall floor, and — critically — collapses the media pane's
   min-height to 0 with overflow hidden so the IMAGE's intrinsic height
   can't drive the grid row taller than the content pane (grid's
   `align-items: stretch` then re-fills the media cell to match content
   height, and `object-fit: cover` on the <img>/<video> crops cleanly).
   A naive `min-height: 0` alone reproduces that image-drives-row-height
   bug.

   No !important: this file loads after the parent's blocks.css, and at
   equal selector specificity source order wins (same pattern as the
   `.sg-block-stackable` override above).

   Consequence: applying `.dynamic-height`'s rules unconditionally to the
   base `.sg-block-hero-bg` (rather than only under `.dynamic-height`)
   makes the block's per-instance "Full height" field inert on Lumberock —
   both full_height=true and full_height=false now render the same
   (dynamic) way. That's the intended effect of a site-wide override; if a
   future page genuinely needs a 100vh hero, it'll need its own opt-in
   rule here.

   Pages using classic-city-core/image-hero-50-50 as of 2026-08-07 (8
   published pages, confirmed via a post_content LIKE query over SSH —
   note this is a different, larger set than the 4 "Performance" pages
   with the rogue leading-empty-paragraph, see BUILD_LOG / content-files):
     - marine-grade (post 263)             — "Marine-Grade Performance"
     - custom-specialty (post 317)         — "Custom & Specialty"
     - no-rot (post 507)                   — "No Rot / No Wood Fiber"
     - agricultural-specialty (post 535)   — "Agricultural & Specialty Decking"
     - low-maintenance (post 573)          — "Low-Maintenance Decking"
     - heavy-duty (post 574)               — "Heavy-Duty Plastic Lumber"
     - profile-flexibility (post 575)      — "Profile & Size Flexibility"
     - span-24 (post 576)                  — '24" On-Center Span'
   =================================================================== */
.sg-block-hero-bg {
  min-height: 0;
}
.sg-block-hero-bg .sg-block-hero-bg-content {
  padding-top: var(--wp--preset--spacing--40);
  padding-bottom: var(--wp--preset--spacing--40);
}
.sg-block-hero-bg .sg-block-hero-bg-media {
  min-height: 0;
  overflow: hidden;
}

/* ===================================================================
   Product Feature Toggles (classic-city-core/product-feature-toggles)
   — tab row restyle

   Parent CSS: classic-city-core/assets/blocks.css ~L3630-3705 (block +
   tab styles) and ~L3858-3867 (mobile @media max-width:900px). Parent
   centers the tab row and pairs a `border-color` change with BOTH
   :hover and :focus-visible (`outline: 0`), so removing the border for
   hover would also silently remove the keyboard focus ring. Chris wants
   a left-aligned, borderless tab row instead, with active/hover/focus
   as three visually distinct states. No !important anywhere below —
   same selectors/specificity as the parent, and this stylesheet loads
   after blocks.css, so source order wins (same pattern as the
   `.sg-block-stackable` / `.sg-block-hero-bg` overrides above).

   1) SPACING — decouple tabs→divider from divider→panels.
   The parent's root is a flex column with ONE `gap` shared by every
   child pairing (head→tabs, tabs→divider, divider→panels alike:
   spacing--40 = 40px each). Chris asked to tighten the tab-row-to-
   content gap specifically, while keeping the divider off the panel
   image. Shrink the shared gap to spacing--20 (20px) — tightening
   tabs→divider from 40px to 20px — then give `__panels` its own
   margin-top of spacing--30 (30px) so divider→panels nets out to
   20+30 = 50px, MORE breathing room than the original 40px, not less.
   (Side effect: head→tabs also tightens from 40px to 20px, since it's
   the same shared gap — not in scope per Chris's ask, but reads fine;
   flagged in the build report.)

   2) LEFT-ALIGN the tab row instead of centering. flex-wrap is
   inherited from the parent rule, so wrapping behavior on narrow
   viewports is unchanged — tabs just stack from the left edge.

   3) TAB PADDING — Chris's spec: tighter vertical (spacing--10),
   roomier horizontal (spacing--30).
   ⚠️ CASCADE TRAP: the parent's own mobile rule narrows this same
   property (`padding-inline: spacing--20`) so 4-5 tabs still fit on a
   phone. Because this child stylesheet loads after blocks.css, an
   unqualified `padding:` shorthand here would beat that mobile rule at
   EVERY width (including under 900px) via source order, silently
   undoing the phone-only narrowing and risking overflow. Reproduce the
   parent's mobile narrowing here, inside the same breakpoint, so
   phones keep the tighter inline padding.

   4) HOVER / ACTIVE / FOCUS — replace the parent's shared border
   treatment with three distinct, borderless states:
     - :hover — a faint `ink` wash (color-mix, 6%) — perceptible
       without reading as "selected." Ink-6%-over-canvas is a
       deliberately low-contrast, decorative-only cue (not a state
       required to be perceivable on its own — see :focus-visible).
     - .is-active — solid `panel` fill (CLAUDE.md Rule 4: panel = the
       card/raised-surface neutral) with no border. panel-on-canvas is
       ALSO a very low-contrast pairing (~1.09:1 — both are near-white
       by design), so the active tab's real signal is the icon's
       existing secondary→cta color flip (kept as-is per the brief) —
       a high-contrast, unambiguous cue independent of the faint fill.
       Flagged as a soft spot in the build report: on a page where this
       block sits directly on a `panel`-colored section (rather than
       the default `canvas` page background), the active fill would
       all but disappear and the icon-color flip would be doing 100%
       of the work.
     - :focus-visible — decoupled from :hover (the parent groups them;
       we don't), with a real 2px outline in `primary` + offset. Solid
       ~14:1 contrast against `canvas`, comfortably clears WCAG 1.4.11
       non-text contrast (3:1) — keyboard users always get an
       unambiguous ring regardless of pointer position or the active
       tab's low-contrast fill.
   =================================================================== */
.sg-block-product-feature-toggles {
  gap: var(--wp--preset--spacing--20, 1.25rem);
}

.sg-block-product-feature-toggles__panels {
  margin-top: var(--wp--preset--spacing--30, 1.875rem);
}

.sg-block-product-feature-toggles__tabs {
  justify-content: flex-start;
}

.sg-block-product-feature-toggles__tab {
  padding: var(--wp--preset--spacing--10, 0.625rem) var(--wp--preset--spacing--30, 1.875rem);
}

@media (max-width: 900px) {
  /* Reproduces the parent's mobile-only inline-padding narrowing
     (blocks.css ~L3863-3866) — see the CASCADE TRAP note above. */
  .sg-block-product-feature-toggles__tab {
    padding-inline: var(--wp--preset--spacing--20, 0.75rem);
  }
}

.sg-block-product-feature-toggles__tab:hover {
  border-color: transparent;
  background-color: color-mix(in srgb, var(--wp--preset--color--ink, #192234) 6%, transparent);
}

.sg-block-product-feature-toggles__tab:focus-visible {
  border-color: transparent;
  outline: 2px solid var(--wp--preset--color--primary, currentColor);
  outline-offset: 2px;
}

.sg-block-product-feature-toggles__tab.is-active {
  border-color: transparent;
  background-color: var(--wp--preset--color--panel, #fbfcfd);
}

/* ===================================================================
   Image Overlay (classic-city-core/image-overlay) — make the card's
   top/bottom margin an actual guarantee.

   THE BUG. Parent (blocks.css ~1447) puts both children in the SAME
   grid row, different columns:

     .sg-block-overlay          grid; 65vw | 1fr; align-items: center
       .sg-block-overlay-image      row 1, col 1, width:100%, aspect-ratio:16/9
       .sg-block-overlay-content    row 1, col 2, margin: 50px …, align-self:center

   The row resolves to max(image height, card height + its margins). The
   image never takes part in that: its height is pinned to the COLUMN
   width by the aspect ratio (65vw x 9/16 = 0.3656 x viewport), and
   `align-items: center` centers it in the row rather than stretching it.

   So the 50px margin is not "make the image 50px taller than the card" —
   it's 50px added to the CARD's side of that max(). While the card is
   short the image wins, the row is image-height, and the margin reads as
   the intended inset. Once the card is tall the CARD wins, the row grows
   to fit it, the image stays its fixed height, and the card overhangs it
   top and bottom — the margin is what pushed the row past the image.

   Worse, the two move toward each other as the viewport narrows: the
   image shortens with 0.3656 x W while the content column (1fr, ~35vw)
   narrows, so the copy rewraps taller. With a ~460px card the crossover
   is near a 1537px viewport — fine on a wide desktop, broken on most
   laptops.

   THE FIX. Give the ROW an explicit floor of the 16/9 height and let the
   image fill whatever the row ends up being. Short copy still gets the
   16/9 look (the floor); long copy grows the row and the image grows
   with it, so the margin becomes a real inset at every width. Same
   content-drives-height / media-fills pattern as the hero's
   dynamic-height mode.

   `height: 100%` (not `align-self: stretch`) is what does the filling:
   `stretch` behaves as `start` on a replaced element with an intrinsic
   ratio, so an <img> as a direct grid item will NOT stretch — only a
   definite height makes it fill. `aspect-ratio: auto` clears the
   parent's ratio so it can't fight the definite height.

   DESKTOP ONLY, deliberately. Below 768px the parent restacks to one
   column with the image in row 1 and the card in row 2 — there the image
   is the only thing sizing its row, so `height: 100%` would resolve
   against a row that the image itself defines and collapse it. Scoped to
   769px+ so the mobile layout is untouched.

   Scoped to Lumberock for now (Chris's call) — image-overlay is a parent
   block and this is arguably a cross-client bug fix, so it's a candidate
   for promotion to classic-city-core once it's proven here.
   =================================================================== */
@media (min-width: 769px) {
  .sg-block-overlay,
  .sg-block-overlay.content-left {
    /* min = the parent's 16/9 height for the 65vw image column; max =
       auto so a tall card can still grow the row. */
    grid-template-rows: minmax(calc(65vw * 9 / 16), auto);
  }
  .sg-block-overlay-image,
  .sg-block-overlay.content-left .sg-block-overlay-image {
    height: 100%;
    /* KEEP the parent's aspect-ratio: 16/9 — do NOT set `aspect-ratio: auto`.
       It is load-bearing in two ways:

       1. During the grid's intrinsic sizing pass a percentage height counts
          as auto, so the image's contribution to the row falls back to the
          RATIO — that's what establishes the 16/9 floor. With `auto` it
          instead contributes its NATURAL height, and the row inflates to
          match the source file.
       2. Hero art here is not 16/9. The homepage image (attachment 1335) is
          1122x1402 — PORTRAIT, ratio 0.80. At 65vw wide that renders 1.25x
          taller than it is wide (~812px at a 1000px viewport) versus 366px
          at 16/9. Clearing the ratio blew the hero up to roughly double
          height. Cropping to 16/9 is the whole point of the block.

       Once the row's height is resolved, `height: 100%` becomes definite;
       with both axes definite the ratio is ignored and the image fills the
       row, with the parent's `object-fit: cover` cropping. So the ratio acts
       purely as the floor + fallback, never as a cap. */
  }
}

/* ==================================================================
 * Single blog post — prose rhythm
 *
 * The parent applies the page-level section rhythm to every direct
 * child of `.wp-block-post-content`:
 *
 *     .wp-block-post-content > * + * { margin-block-start:
 *       var(--ccc-section-gap); }   // 70px
 *
 * That is right for a page built out of full-width section blocks, but
 * on a blog post the same children are paragraphs, lists and headings —
 * so every paragraph break reads as a 70px section break and the article
 * loses its shape.
 *
 * Scoped to `body.single` so pages (which DO want the section gap) are
 * untouched. Both values are exposed as vars, so retuning is one number.
 *
 * Values are Chris's call: 10px paragraph rhythm, 50px above a heading.
 * ================================================================== */
body.single .wp-block-post-content {
	/* Paragraph-to-paragraph rhythm. */
	--lr-prose-gap: var(--wp--preset--spacing--10);        /* 10px */
	/* Space ABOVE a heading — the visual break between subsections. */
	--lr-prose-heading-gap: var(--wp--preset--spacing--50); /* 50px */
}

body.single .wp-block-post-content > * + * {
	margin-block-start: var(--lr-prose-gap);
}

body.single .wp-block-post-content > * + :is(h1, h2, h3, h4, h5, h6),
body.single .wp-block-post-content > * + .wp-block-heading {
	margin-block-start: var(--lr-prose-heading-gap);
}

/* ==================================================================
 * FAQ accordion — native core/details inside a Group carrying the
 * class `sg-block-faq` (FAQ page 590, Milestone 12).
 *
 * The parent theme already ships `.sg-block-faq` / `.sg-block-faq-item`
 * styles (blocks.css "FAQ accordion") for an FAQ block that never
 * landed. This maps the stock Details block's <summary> and body onto
 * those same styles, so the client adds a question with the ordinary
 * Details block (className `sg-block-faq-item`) and gets the panel
 * card, seafoam left bar, and rotating "+" for free. Give every
 * Details in one group the same `name` attribute and the browser
 * closes the others when one opens — a true accordion, no JS.
 * ================================================================== */
.sg-block-faq > * + * {
	/* The parent flex gap spaces the items; kill the flow-layout margin
	   so the two don't stack. */
	margin-block-start: 0;
}

.sg-block-faq .wp-block-details > summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--30);
	font-weight: 600;
	padding: var(--wp--preset--spacing--20) 0;
	cursor: pointer;
	list-style: none;
}

.sg-block-faq .wp-block-details > summary::-webkit-details-marker {
	display: none;
}

.sg-block-faq .wp-block-details > summary::after {
	content: "+";
	flex: none;
	font-size: 1.5em;
	font-weight: 300;
	line-height: 1;
	color: var(--wp--preset--color--primary);
	transition: transform 0.2s ease;
}

.sg-block-faq .wp-block-details[open] > summary::after {
	transform: rotate(45deg);
}

.sg-block-faq .wp-block-details > :not(summary) {
	margin-block-start: 0;
}

.sg-block-faq .wp-block-details > :not(summary):last-child {
	padding-bottom: var(--wp--preset--spacing--20);
}

/* ==================================================================
 * In-page anchors land under the sticky header without this. The
 * header is ~96px tall (56px logo + spacing--20 top and bottom); 120px
 * leaves daylight. Applies to any Group given an HTML anchor in the
 * editor (Profiles & Sizes spec-sheet groups, Milestone 15) — and to ACF
 * block wrappers with an anchor (the Installation Hub guides grid, 16.1).
 * ================================================================== */
.wp-block-post-content [id] {
	scroll-margin-top: 120px;
}
