/**
 * Pattern styles — shared stylesheet for all block patterns.
 *
 * Keyed to each pattern's root class (rwest-<slug>). Tokens only —
 * no literal hex/px/spacing except container-query thresholds (rem)
 * and line-measure values (ch/em).
 *
 * Enqueued front-end via inc/setup.php rwest_enqueue_assets() and
 * registered for the editor via add_editor_style().
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   NarrativePullQuote — rwest/narrative-pull-quote
   ========================================================================== */

.rwest-narrative-pull-quote {
  container-type: inline-size;
}

.rwest-narrative-pull-quote__eyebrow {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  margin-bottom: var(--wp--preset--spacing--6);
}

.rwest-narrative-pull-quote__eyebrow::before {
  /* decorative r\ glyph — hidden from the accessibility tree via empty alt text */
  content: "r\\" / "";
  color: var(--wp--preset--color--brand);
  font-weight: 700;

  /* em-relative glyph-to-label gap: scales with the eyebrow font size, same
     presentational class as letter-spacing/line-height. No spacing token applies
     (those are fixed rem steps and would not scale). */
  margin-right: 0.35em;
}

/* Direct child only — must not bleed into author-nested headings (a nested
   Group/Columns heading inside this section keeps its own measure). */
.rwest-narrative-pull-quote > .wp-block-heading {
  /*
   * Statement size — matches the wireframe .wf-narrative-statement
   * clamp(2rem, 4.5vw, 3.75rem) exactly (57.6px at the 1280 design viewport).
   * The pattern heading carries no fontSize, so without this it inherits the
   * default h2 (~36px) and renders badly undersized. Viewport-fluid is allowed
   * here: this is a full-width, section-level centered statement (the altitude
   * split in css-conventions permits viewport units for section chrome), and
   * matching the wireframe source verbatim keeps validation 1:1.
   */
  font-size: clamp(2rem, 4.5vw, 3.75rem);
  max-width: 18ch;
  margin-inline: auto;
  line-height: 1.1;
}

/* ==========================================================================
   CTA Blade — rwest/cta-blade
   ========================================================================== */

/*
 * Heading — matches the wireframe .wf-cta-title (.wf-h2 clamp(1.5rem, 3vw,
 * 2.25rem) → 36px at the 1280 design viewport). No font-size preset maps to
 * 2.25rem, so replicate the wireframe clamp exactly; the pattern heading
 * otherwise inherits heading-md (~31px) and renders undersized. Section-level
 * centered statement → viewport units allowed (css-conventions altitude split).
 */
.rwest-cta-blade__heading {
  font-size: clamp(1.5rem, 3vw, 2.25rem);
}

/* ==========================================================================
   Hero — rwest/hero
   ========================================================================== */

/*
 * Container context: the full-bleed Cover section is the container; its inner
 * content column responds to this inline-size. Verify abs-positioned
 * media/overlay layers still anchor after establishing this container context
 * (cq gotcha — container = new containing block for abs-positioned descendants).
 */
.rwest-hero {
  container-type: inline-size;

  /* Drop the Cover's own 16px horizontal padding (the WP root-padding-aware
     default on full-width blocks) so the hero copy gutter is the inner-
     container's padding-inline ALONE (set below). Otherwise the two stack and
     the hero copy sits 16px further in than every other section — 40px vs the
     24px site gutter at mobile (QA #76, 2026-06-22). The full-bleed background
     is unaffected (its image + overlay are absolute layers). */
  padding-inline: 0;
}

/* Content column width + vertical rhythm.
 * Re-applied in CSS because the equivalent Cover attributes (layout contentSize:900px
 * + style.spacing.blockGap) inject auto-generated `wp-container-*` classes that cannot
 * be hand-authored in a static pattern without breaking block validation — so the
 * pattern ships a plain flow Cover and the column is constrained here instead.
 * 900px is an allowed layout literal (a content-width threshold, not a design token). */
.rwest-hero .wp-block-cover__inner-container {
  max-width: 900px;

  /* Horizontal gutter — wireframe `.wf-hero__content` ships `padding: 3rem` (spacing-12) on the
   * 900px content box. Vertical 3rem comes from the Cover's spacing-12 padding; the inline gutter
   * is pinned here (narrow-width reductions below). Without this the content sits flush to the edge. */
  padding-inline: var(--wp--preset--spacing--12);
}

.rwest-hero .wp-block-cover__inner-container > * + * {
  margin-block-start: var(--wp--preset--spacing--5);
}

/* Eyebrow — uppercase, letter-spaced, full white over media (no opacity reduction).
 * At body-xs over author-variable media, 70%-opacity white fails 4.5:1;
 * we drop the opacity and rely on dimRatio:65 overlay for the contrast floor. */
.rwest-hero__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

/* Headline — line-height + max-width only; weight/family/size inherit from
 * elements.heading + elements.h1 (display-md, fluid 2.5rem→5rem).
 * Never set font-size here — the substrate already matches the wireframe 5rem cap. */
.rwest-hero h1 {
  /* No max-width — the wireframe .wf-hero__headline has none; line breaks come
     from the authored <br>, and the content container (max-width 900) bounds it. */
  line-height: 1.03;

  /* The wireframe hero headline tracking is `normal`; ours otherwise inherits a
     global heading -0.01em that narrows it. */
  letter-spacing: normal;
}

/* Lede — max-width + line-height; color inherits (white over media / black on
 * light via the is-style-light light.json root color.text cascade — no baked
 * textColor). line-height 1.45 matches the wireframe .wf-hero__lede. */
.rwest-hero__lede {
  max-width: 52ch;
  line-height: 1.45;
}

/* --------------------------------------------------------------------------
   Lede inline-link states (gate-0 RESOLVED — load-bearing)

   Architecture: base .rwest-hero link defaults establish the over-media
   treatment (white, underline). The .is-style-light rules have higher
   specificity (extra class) and override when that variation is applied.
   This cascade-based approach satisfies no-descending-specificity — plain
   `a` always precedes `a:hover` / `a:focus-visible` in document order.

   Gate-0 note: the default link color is NEVER red over media — a red link
   over the ~65% overlay computes ~1.77:1 (fails AA). The global
   elements.link brand red is overridden here. Underline is the affordance.
   -------------------------------------------------------------------------- */

/* DEFAULT / over-media: link resting.
 * color:inherit keeps white from the Cover's dark context.
 * Overrides the global elements.link brand red (unreadable over media). */
.rwest-hero .rwest-hero__lede a {
  color: inherit; /* stays white — NEVER red over media */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* DEFAULT / over-media: link :hover
 * Underline thickening is the sole hover affordance — never a color shift to red. */
.rwest-hero .rwest-hero__lede a:hover {
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

/* DEFAULT / over-media: link :focus-visible (MANDATORY — WCAG 2.2 SC 2.4.7/2.4.13)
 * Hover is color-held-white + underline-only, so without this a keyboard user
 * sees NO change at all. Ring color = pure-white token; geometry = only literals. */
.rwest-hero .rwest-hero__lede a:focus-visible {
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 3px;
  border-radius: 2px;
}

/* is-style-light SURFACE: solid light hero. The shared light.json variation flips heading +
 * links (element-scoped), but core/Cover's background context swallows the variation's ROOT
 * color/background — so the section bg + body text (eyebrow/lede) are pinned here.
 * pure-white bg + black text = 21:1 (AA). Verified via live editor probe 2026-06-09. */
.rwest-hero.is-style-light {
  background-color: var(--wp--preset--color--pure-white);
}

.rwest-hero.is-style-light .rwest-hero__eyebrow,
.rwest-hero.is-style-light .rwest-hero__lede {
  color: var(--wp--preset--color--black);
}

/* is-style-light state: link resting.
 * Higher specificity (.rwest-hero.is-style-light vs .rwest-hero) overrides
 * the default above. brand-aa = #D63A2D = 4.664:1 on pure-white (AA pass).
 * raw brand = #E74536 = 3.961:1 — fails 4.5:1. */
.rwest-hero.is-style-light .rwest-hero__lede a {
  color: var(--wp--preset--color--brand-aa);
}

/* is-style-light state: link :hover — brand-aa for BOTH resting AND hover */
.rwest-hero.is-style-light .rwest-hero__lede a:hover {
  color: var(--wp--preset--color--brand-aa);
}

/* is-style-light state: link :focus-visible — brand-aa ring
 * (#D63A2D = 4.664:1 on pure-white group bg, clears 3:1 SC 1.4.11 non-text floor). */
.rwest-hero.is-style-light .rwest-hero__lede a:focus-visible {
  text-decoration-thickness: 2px;
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 3px;
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   Button secondary — scrim + blur (base rule before :focus-visible below)
   Scrim = surface-3 (token) at α 0.65 via color-mix (NOT rgba(0,0,0,0.35) wash).
   Contrast mechanism: 65% over worst-case white → composites to #646464 (5.92:1 on pure-white) →
   pure-white text ≥4.5:1 independent of author media.
   rgba(0,0,0,0.35) over white = ~#a6a6a6 = 2.43:1 — BLOCKING fail.
   Blur is decorative only, not the contrast mechanism.
   Allowed literals: α 0.65 (contrast threshold) + blur radius (decorative).
   VARIATION JSON NOTE: styles/hero-secondary.json is INTENTIONALLY identical to
   styles/media-blade-secondary.json — separate slugs are load-bearing because these
   .rwest-hero-scoped rules must not leak onto MediaBlade buttons (and vice versa).
   Do NOT "deduplicate" by merging the slugs; JSON carries no comments, so this note +
   docs/specs/media-blade.md §TextTone hold the rationale.
   -------------------------------------------------------------------------- */
.rwest-hero .is-style-hero-secondary .wp-block-button__link {
  background: color-mix(in srgb, var(--wp--preset--color--surface-3) 65%, transparent);
  /* stylelint-disable-next-line property-no-vendor-prefix */
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* Button :hover — WCAG 1.4.3 safety (same B1 class as MediaBlade, fixed 2026-06-10).
   theme.json elements.button:hover (specificity 0,1,1 via :root :hover) beats the variation
   base and injects brand-hover (#ff5a4a) as the background — 3.08:1 against pure-white text,
   a 1.4.3 failure. Restore the scrim at hover (hover keeps the base treatment, matching the
   MediaBlade resolution). */
.rwest-hero .is-style-hero-secondary .wp-block-button__link:hover {
  background: color-mix(in srgb, var(--wp--preset--color--surface-3) 65%, transparent);
}

/* --------------------------------------------------------------------------
   Button :focus-visible (MANDATORY — WCAG 2.2 SC 1.4.11/2.4.13)
   theme.json elements.button ships only :hover (no focus key — verified).
   Base button rules (secondary scrim above, primary from variation JSON) come
   before :focus-visible per no-descending-specificity.
   -------------------------------------------------------------------------- */

/* Default / over media: pure-white ring with outline-offset:2px so on the
 * warm-white primary face the ring sits on the dark 65%-overlay backdrop (≥3:1),
 * not the light button face. Geometry literals only; color = token. */
.rwest-hero .is-style-hero-primary .wp-block-button__link:focus-visible,
.rwest-hero .is-style-hero-secondary .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Light-mode button flip (is-style-light)
   light.json blockTypes are group/columns (core/cover removed — BLOCKING-1 resolution) — does NOT reach nested buttons.
   These scoped rules provide the per-variation button recolor.
   Base rules before :focus-visible per no-descending-specificity.
   -------------------------------------------------------------------------- */

/* Primary on light: dark bg / warm-white text (mirrors wireframe Hero.css:185–195) */
.rwest-hero.is-style-light .is-style-hero-primary .wp-block-button__link {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

/* Secondary on light: light scrim / dark text + dark outline */
.rwest-hero.is-style-light .is-style-hero-secondary .wp-block-button__link {
  background: color-mix(in srgb, var(--wp--preset--color--pure-white) 65%, transparent);
  color: var(--wp--preset--color--surface-3);
  border-color: var(--wp--preset--color--surface-3);
}

/* Light-mode secondary :hover — same WCAG 1.4.3 safety as the dark-mode rule above:
   without this, theme.json injects brand-hover behind dark surface-3 text (~1.9:1). */
.rwest-hero.is-style-light .is-style-hero-secondary .wp-block-button__link:hover {
  background: color-mix(in srgb, var(--wp--preset--color--pure-white) 65%, transparent);
}

/* is-style-light: brand-aa ring (#D63A2D = 4.664:1 on pure-white group bg).
 * NEVER raw brand. Must come after the base light-mode button rules above. */
.rwest-hero.is-style-light .is-style-hero-primary .wp-block-button__link:focus-visible,
.rwest-hero.is-style-light .is-style-hero-secondary .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Responsive — container-queries (NOT @media, per css-conventions)
   Threshold triggers: content padding reduction at narrow container widths.
   Wireframe drops content padding to 2rem/1.5rem at ≤768px.
   -------------------------------------------------------------------------- */

/* ≤ 48rem (~768px): reduce content padding sides */
@container (max-width: 48rem) {
  .rwest-hero .wp-block-cover__inner-container {
    padding-inline: var(--wp--preset--spacing--8); /* 2rem — wireframe 2rem/1.5rem */
  }
}

/* ≤ 30rem (~480px): further reduce for small mobile */
@container (max-width: 30rem) {
  .rwest-hero .wp-block-cover__inner-container {
    padding-inline: var(--wp--preset--spacing--6); /* 1.5rem */
  }
}

/* ==========================================================================
   MediaBlade — rwest/media-blade
   ========================================================================== */

/* Component-scoped layout constants — centralized per css-conventions.md §Breakpoints.
   Breakpoint px/rem values and other untokenizable layout thresholds are the one allowed
   literal category; they must be centralized as CSS custom properties at the root scope,
   not scattered as inline values.
   --media-blade-inner-width: layout threshold (max-width of the inner content column).
   --media-blade-inner-padding: inline (horizontal) padding on the inner container at normal
     widths. Set to spacing-8 (2rem) — matches the wireframe's
     .wf-media-blade-content { padding: 3rem 2rem } inline value and mirrors the Hero pattern
     (.rwest-hero .wp-block-cover__inner-container uses spacing-12). WP core Cover provides no
     default padding-inline on the inner container, so without this rule content text runs flush
     to screen edges at narrow viewports.
   --media-blade-inner-padding-narrow: reduced inline padding at ≤30rem container width.
     Set to spacing-6 (1.5rem) — mirrors the wireframe's
     @media (max-width: 768px) { padding: 2rem 1.5rem } in container-query form.
   NOTE ON --media-blade-narrow-breakpoint: css-conventions.md §Breakpoints requires
     container-query thresholds to be centralized as CSS custom properties. The threshold
     value for the narrow-padding reduction is 30rem (at this container width the 2rem gutter
     starts to crowd the content column noticeably; 1.5rem provides comfortable reading margins
     at mobile widths — 390px). HOWEVER, CSS custom properties cannot be used inside @container
     query conditions in current CSS (@container (max-width: var(--foo)) is not valid syntax —
     CSS Values Level 5 env()/custom-property substitution in media/container query conditions
     is not yet supported in any shipping browser). Therefore the 30rem value CANNOT be moved
     behind a var() reference in the @container rule itself. The correct resolution per
     css-conventions.md §Breakpoints is to document the literal explicitly here with the layout
     shift it triggers and include it in the AC#8 allowed-literals inventory as a named literal
     with the triggering behavior described. The @container rule ships the 30rem bare literal;
     its definition lives here as the canonical reference. Triggering behavior: at container
     width ≤ 30rem (~480px), padding-inline on the inner container reduces from spacing-8
     (2rem) to spacing-6 (1.5rem) — prevents the gutter from crowding the content column at
     narrow mobile widths.
   --media-blade-lede-measure: 52ch readability cap; not a breakpoint and no --wp--preset--*
     equivalent exists; acknowledged untokenizable CSS-only exception (see token mapping table).
   --media-blade-focus-ring-width: 2px (focus ring geometry; no --wp--preset--border-width--*
     family exists in WP core; untokenizable — centralized here per css-conventions.md §Breakpoints
     pattern for allowed literals).
   --media-blade-focus-ring-offset: 2px (same rationale; no spacing token maps to 2px;
     spacing-1 is 0.25rem = 4px, the smallest spacing token).
   NOTE: no --media-blade-mobile-min-height custom property. The 60dvh min-height is a block
   attribute (minHeight: 60, minHeightUnit: "dvh") — editor-adjustable via the Dimensions panel.
   No CSS min-height override is specified. If a mobile floor is discovered to be required during
   implementation, the implementer MUST first verify whether WP's Cover block honors the
   minHeight/minHeightUnit attributes at narrow viewports before adding any CSS override. If a
   CSS override IS required after that verification, it MUST be documented here as CSS-only and
   not Dimensions-panel-adjustable (editability-bias caveat). Do not ship a silent CSS min-height
   that contradicts the block attribute without updating this spec. */
.rwest-media-blade {
  --media-blade-inner-width: 1200px;
  --media-blade-inner-padding: var(--wp--preset--spacing--8);
  --media-blade-inner-padding-narrow: var(--wp--preset--spacing--6);
  --media-blade-lede-measure: 52ch;
  --media-blade-focus-ring-width: 2px;
  --media-blade-focus-ring-offset: 2px;

  container-type: inline-size;
  container-name: media-blade;
  background-color: var(--wp--preset--color--surface-2);
}

/* Eyebrow + heading now provided by the rwest/section-intro block
   (showBrandMark:false — no glyph, matching the wireframe media blade). */

/* Inner container width + inline padding — centralized layout threshold, referenced via var().
   NOT set via layout.contentSize block attribute (that injects unhand-authorable
   wp-container-* classes — per hero.php precedent and block-build-loop rule).
   Mirrors hero.php approach: width and gap are CSS-only on the inner container.
   padding-inline prevents content text from running flush to screen edges at narrow viewports.
   WP core Cover provides no default padding-inline on .wp-block-cover__inner-container.
   spacing-8 = 2rem matches wireframe .wf-media-blade-content { padding: 3rem 2rem } inline value.
   The @container reduction below mirrors the wireframe's
   @media (max-width: 768px) { padding: 2rem 1.5rem } in container-query form (F12 resolution). */
.rwest-media-blade .wp-block-cover__inner-container {
  /* AB6: WP's .has-custom-content-position rule forces the row-flex inner-container
     to shrink-wrap (was 532px) + margin:0. Override (!important per css-conventions
     WP-container exception) to a centered 1200px column — matches the wireframe
     (media-blade content + other sections both 1200px, left:40px). */
  width: 100% !important;
  max-width: var(--media-blade-inner-width) !important;
  margin-inline: auto !important;
  padding-inline: var(--media-blade-inner-padding);
}

/* Narrow inline padding reduction — triggered at ≤30rem container width (~480px).
   The .rwest-media-blade root is the container (container-type: inline-size above).
   spacing-6 = 1.5rem mirrors wireframe @media (max-width: 768px) inline gutter.
   30rem threshold: layout literal — documented in the component-scoped custom-property block
   above (NOTE ON --media-blade-narrow-breakpoint) per css-conventions.md §Breakpoints. This
   value cannot be moved behind var() in an @container condition (not valid CSS syntax); the
   literal is centralized by documentation (comment block) and listed in the AC#8
   allowed-literals inventory. Triggering behavior: padding-inline reduces from spacing-8
   (2rem) to spacing-6 (1.5rem), preventing the gutter from crowding the content column at
   mobile widths. */
@container media-blade (max-width: 30rem) {
  .rwest-media-blade .wp-block-cover__inner-container {
    padding-inline: var(--media-blade-inner-padding-narrow);
  }
}

/* Content gap — RESOLVED via style.spacing.blockGap block attribute (BLOCKING-6 verification
   run 2026-06-10): the createBlock → serialize probe confirmed blockGap on core/cover does
   NOT alter save() markup (no wp-container-* class in static markup; the layout class +
   gap rule are injected at render time by the layout support, which needs no hand-authoring).
   The pattern ships "blockGap":"var:preset|spacing|4" on the Cover — editor-visible in the
   Dimensions panel (editability bias). The former CSS lobby rule
   (.wp-block-cover__inner-container > * + * { margin-block-start }) was removed in favor of it. */

/* Lede measure — core/paragraph has no style.typography.maxWidth attribute in WP 7.0.
   CSS is the correct implementation; mirrors Hero lede approach.
   Value referenced via --media-blade-lede-measure (defined at root scope above). */
.rwest-media-blade__intro .wf-section-intro__lede {
  max-width: var(--media-blade-lede-measure);
}

/* CTA buttons extra top margin — wireframe .wf-media-blade-cta { margin-top: 0.75rem }
   adds an extra 0.75rem above the button, on top of the 1rem lobby-rule gap.
   Total lede-to-button visual offset: ~1.75rem (matches wireframe design intent).
   Applied to the core/buttons wrapper (the immediate child of the inner container). */
.rwest-media-blade .wp-block-buttons {
  margin-block-start: var(--wp--preset--spacing--3);
}

/* Button :hover — WCAG 1.4.3 safety rule (B1 resolution).
   WordPress theme.json wraps all generated CSS in :where() (specificity 0). The variation base
   selector (is-style-media-blade-secondary on a button) resolves to specificity 0,0,1. The
   theme.json elements.button :hover selector resolves to 0,1,1 (:root + :hover pseudo-class)
   — it beats the variation's 'background: transparent' on hover, injecting brand-hover
   (#ff5a4a, L≈0.291) as the button background. brand-hover #ff5a4a vs pure-white #ffffff =
   3.08:1 — fails WCAG 1.4.3 SC (4.5:1 required; button text is ~1rem/12pt, below 18pt
   large-text threshold). This rule restores transparent at hover, keeping white text on the
   composited dark overlay (dimRatio:55 serializes as dim-60 — rendered opacity 0.60 → 5.74:1; see media-blade.php header).
   NOTE: hero-secondary carried the identical gap — FIXED 2026-06-10 (see the
   .rwest-hero .is-style-hero-secondary :hover rules in the Hero section above).
   VARIATION JSON NOTE: styles/media-blade-secondary.json is INTENTIONALLY identical to
   styles/hero-secondary.json — separate slugs are load-bearing for .rwest-hero /
   .rwest-media-blade CSS scoping isolation; do NOT merge them. Rationale:
   docs/specs/media-blade.md §TextTone. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-media-blade .is-style-media-blade-secondary .wp-block-button__link:hover {
  background-color: transparent;
}

/* Button :focus-visible — keyboard/AT accessibility.
   Button color/border/background are set by the styles/media-blade-secondary.json variation
   stylesheet (is-style-media-blade-secondary) — NOT via CSS here. The variation approach
   mirrors hero-secondary.json and avoids unverified cascade conflicts with WP core's
   is-style-outline CSS.
   background-color: transparent is set in the variation stylesheet (LOAD-BEARING — theme.json
   elements.button.color.background is brand-red; transparent must be explicit).
   theme.json elements.button ships only :hover (no focus key — verified, Hero spike 2026-06-09).
   Dark-tone default: white ring (visible on dark overlay). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-media-blade .wp-block-button__link:focus-visible {
  outline: var(--media-blade-focus-ring-width) solid var(--wp--preset--color--pure-white);
  outline-offset: var(--media-blade-focus-ring-offset);
}

/* is-style-dark and is-style-light: NEITHER variation is available on core/cover.
   light.json blockTypes excludes core/cover (BLOCKING-1 resolution, 2026-06-09).
   dark.json blockTypes excludes core/cover (BLOCKING-C resolution, 2026-06-09).
   No is-style-light or is-style-dark CSS rules exist or should be added for this pattern.
   The Styles panel does not offer either variation on Cover blocks site-wide. */

/* Container-query responsiveness: one @container rule is specified for this pattern —
   the narrow padding-inline reduction at ≤30rem (see the rule above the CTA buttons rule).
   The inner container uses max-width (not a fixed width), so layout otherwise narrows
   gracefully without additional breakpoint-triggered changes. The full-bleed section always
   spans the viewport; content reflows within the 1200px max-width column. */

/* Reduced-motion: N/A (no animation in this pattern) */

/* ==========================================================================
   PairCard — rwest/pair-card
   ========================================================================== */

/*
 * Component-scoped layout constants — centralized per css-conventions.md §Breakpoints.
 * Allowed literals documented here; cannot move into @container query conditions (CSS
 * custom properties are not valid in @container condition syntax).
 *
 * --pair-card-media-radius: 2px border-radius on the image. Allowed literal;
 *   no --wp--preset--border-radius--* family in WP core.
 * --pair-card-cap-font-size: 0.65rem for cap chip text. body-xs (0.75rem) is too large;
 *   no smaller token exists; allowed per spec token-mapping table.
 * --pair-card-cap-padding-block: 0.625rem vertical padding on cap chip links.
 *   Wireframe value 0.625rem = 10px exactly; no matching spacing token; allowed literal.
 * --pair-card-focus-ring: 2px — focus ring width. No --wp--preset--border-width--*
 *   family in WP core; allowed literal (see css-conventions.md §Allowed literals).
 *
 * Container model: root = pair-card container. Components respond to the grid cell
 * width (card lives in a core/columns 2-up grid; viewport @media not used here).
 */
.rwest-pair-card {
  --pair-card-media-radius: 2px;
  --pair-card-cap-font-size: 0.65rem;
  --pair-card-cap-padding-block: 0.625rem;
  --pair-card-focus-ring: 2px;

  container-type: inline-size;
  container-name: pair-card;

  /* Prevent faux-bold on macOS — wireframe sets antialiased site-wide. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Image — aspect-ratio + border-radius applied via block attributes (4/3, scale:cover).
   CSS centralizes the radius allowed-literal via var() reference. */
.rwest-pair-card__media img {
  border-radius: var(--pair-card-media-radius);
  display: block;
  width: 100%;
}

/* Meta group — padding-top is the block spacing attribute; gap is block blockGap.
   The CSS here only adds non-attribute overrides. */

/* Kicker — uppercase label with brand r\ glyph prefix.
   font-weight: 500, letter-spacing: 0.15em — untokenizable; allowed literals.
   font-size: 0.7rem (11.2px) — wireframe .wf-work-card-kicker computed 11.2px;
     no WP body-xs preset maps to 0.7rem (body-xs = 0.75rem); allowed literal
     (component threshold, documented per css-conventions.md). Probed 2026-06-13.
   color: surface-3 (#111) = 18.88:1 on white — AA pass. */
.rwest-pair-card__kicker {
  font-size: 0.7rem;
  font-weight: 500;
  line-height: 1.5; /* wireframe kicker = 1.5 (ours otherwise inherits body 1.6) */
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--wp--preset--color--surface-3);
}

/* Kicker r\ glyph — decorative brand prefix; hidden from the accessibility tree via
   the CSS alt-text form `/ ""`. Same technique as NarrativePullQuote eyebrow. */
.rwest-pair-card__kicker::before {
  content: "r\\" / "";
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.32em;
}

/* Title — wireframe .wf-work-pair-title is 1.4rem (no preset maps; the markup's
   heading-sm preset class is !important, so the override must be too).
   letter-spacing, line-height, max-width are untokenizable (allowed per spec). */
.rwest-pair-card__title {
  /* stylelint-disable-next-line declaration-no-important */
  font-size: 1.4rem !important;
  letter-spacing: -0.005em;
  line-height: 1.2;
  max-width: 26ch;
  color: var(--wp--preset--color--surface-3);
}

/* Tagline — wireframe .wf-work-card-tagline is 0.9rem (markup body-sm preset is
   !important). line-height + max-width untokenizable (allowed per spec).
   color: gray-aa (#555555) = 7.46:1 on white — AA pass. */
.rwest-pair-card__tagline {
  /* stylelint-disable-next-line declaration-no-important */
  font-size: 0.9rem !important;
  line-height: 1.45;
  max-width: 50ch;
  color: var(--wp--preset--color--gray-aa);
}

/* Capability chip list — reset list defaults; flex-wrap for multi-row chips.
   gap and margin-block-start use spacing tokens per spec token table. */
.rwest-pair-card__caps {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--2);
  margin-block: var(--wp--preset--spacing--2) 0;
}

/* Cap chip links — pill shape, gray-aa text, border-subtle 1px border.
   font-size: 0.65rem — centralized via --pair-card-cap-font-size (no smaller token).
   padding-block: 0.625rem — centralized via --pair-card-cap-padding-block (no token maps to 10px).
   padding-inline: spacing-4 (1rem) — token.
   border-width: 1px — allowed literal (no WP preset border-width family).
   border-radius: 999px — pill; allowed literal (no full-radius token in WP core).
   color: gray-aa = 7.46:1 on white — AA pass.
   Global styles.elements.link sets brand red on every <a>; this scoped rule
   out-specifies the :where()-wrapped global rule without !important. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__caps li a {
  display: inline-block;
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--pair-card-cap-font-size);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--wp--preset--color--gray-aa);
  border: 1px solid var(--wp--preset--color--border-subtle);
  border-radius: 999px;
  padding: var(--pair-card-cap-padding-block) var(--wp--preset--spacing--4);
}

/* Cap chip :hover — surface-3 bg / white text inversion = 18.88:1 — AA pass. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__caps li a:hover {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
  border-color: var(--wp--preset--color--surface-3);
}

/* Cap chip :focus-visible — surface-3 ring = 18.88:1 contrast on white background.
   2px outline + 2px offset are allowed outline literals (no WP preset maps to these). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__caps li a:focus-visible {
  outline: var(--pair-card-focus-ring) solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* QA R42 — the industry kicker + capability pills are non-interactive labels, not
   links. inc/work-card.php strips the term anchors' href/rel (non-navigable,
   non-focusable); this neutralizes the pointer cursor and any :hover state while
   leaving the resting chip/kicker styling (color, border, pill radius — all set on
   the <a>) intact. pointer-events:none also lets clicks fall through to the card's
   own stretched link. */
/* stylelint-disable no-descending-specificity */
.rwest-pair-card__kicker a,
.rwest-pair-card__caps a,
.rwest-featured-card__caps a {
  cursor: default;
  pointer-events: none;
}
/* stylelint-enable no-descending-specificity */

/* See-project link — inline-block; text-decoration none; align-self flex-start keeps
   it left-aligned. padding-top uses spacing token. color: surface-3 (#111) overrides
   global styles.elements.link brand red (same :where() out-specify technique as caps).
   font-weight: 400 and letter-spacing: 0.04em are untokenizable (allowed per spec). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__see-project a {
  display: inline-block;
  text-decoration: none;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--wp--preset--color--surface-3);
  padding-top: var(--wp--preset--spacing--2);
}

/* See-project :hover — brand-aa (#D63A2D) = 4.66:1 on white — AA pass.
   Global :hover sets brand (#E74536 = 3.96:1 — fails 4.5:1); override with brand-aa. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__see-project a:hover {
  color: var(--wp--preset--color--brand-aa);
}

/* See-project :focus-visible — surface-3 ring matches cap chip focus ring style. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card__see-project a:focus-visible {
  outline: var(--pair-card-focus-ring) solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* Arrow span — decorative translateX slide on hover.
   transition: transform 0.18s ease — decorative motion; prefers-reduced-motion stop below.
   The span carries .rwest-pair-card__see-project-arrow for reliable selector targeting.
   display: inline-block required for transform to apply to an inline element — which TRIMS
   the span's leading text space (white-space processing at an inline-block boundary), so the
   label-to-arrow gap is restored with an em-relative margin (scales with the font; the same
   presentational class as letter-spacing — no spacing token applies). Caught by the
   visual read 2026-06-10: rendered "See project→" vs wireframe "See project →". */
.rwest-pair-card__see-project-arrow {
  display: inline-block;
  margin-left: 0.3em;
  transition: transform 0.18s ease;
}

.rwest-pair-card__see-project a:hover .rwest-pair-card__see-project-arrow {
  transform: translateX(4px);
}

/* prefers-reduced-motion stop — mandatory per css-conventions.md §Motion. */
@media (prefers-reduced-motion: reduce) {
  .rwest-pair-card__see-project-arrow {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Container-query breakpoints
   No @media — card responds to its container (core/columns cell), not the viewport.
   -------------------------------------------------------------------------- */

/* ≤ 22.5rem (~360px cell width): narrow card — title/tagline measure relaxed.
   Triggering behavior: when the card's grid cell is ≤360px wide (single-column context
   or very narrow viewport), ch-based max-width constraints are released so the title and
   tagline fill the available width without truncating awkwardly. */
@container pair-card (max-width: 22.5rem) {
  .rwest-pair-card__title {
    max-width: none;
  }

  .rwest-pair-card__tagline {
    max-width: none;
  }
}

/* ==========================================================================
   ServiceCard — rwest/service-card
   ========================================================================== */

/*
 * Component-scoped layout constants — centralized per css-conventions.md §Breakpoints.
 * Container model: .rwest-services-grid (query wrapper) is the outer container;
 * .rwest-service-card (group inside each li) is the inner card container.
 *
 * --service-card-oneliner-max: 38rem readability cap on the excerpt; wireframe-exact.
 *   No --wp--preset--* equivalent; allowed layout literal per spec.
 * --service-card-focus-ring: 2px focus ring width; no WP preset border-width family.
 *
 * Grid breakpoints (services-grid container):
 *   64rem (1024px section column width) — 3→2 columns
 *   43.75rem (700px section column width) — 2→1 column
 *
 * Card-internal breakpoint (service-card container):
 *   30rem (~480px card width) — oneliner uncaps at 1-up context
 */

/* ─── Grid ──────────────────────────────────────────────────────────────── */

/* Grid container — responds to section column width, not viewport */
.rwest-services-grid {
  container-type: inline-size;
  container-name: services-grid;
}

/* grid lives here — ul is the direct parent of li card items.
   Card numbers are set per-position via :nth-child below (NOT a CSS counter):
   WebKit/Safari refuses to apply counter-increment to flex/grid items in this
   Query-Loop DOM, so every card rendered "00" in Safari (QA #67, 2026-06-22). */
.rwest-services-grid .wp-block-post-template {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3-up: desktop default */
  gap: var(--wp--preset--spacing--6); /* 1.5rem — matches wireframe gap */
}

/* [recipe:flow-gap-reset] — CANONICAL instance; other grids reference this anchor.
   WP's flow-layout block-gap emits `> * + * { margin-block-start: 24px }` on the
   post-template — corrupts grid row alignment (li 1 sat 24px above its row,
   caught by the visual read 2026-06-10). Grid gap owns the spacing; the fix is
   `> * { margin-block: 0 }` on the gap-emitting parent. The same injection hits
   any flow Group (card level too — see TeamCard), so apply at EVERY level WP
   emits the margins, not just the UL. */
.rwest-services-grid .wp-block-post-template > * {
  margin-block: 0;
}

/* ≤ 64rem (1024px section column width): 2-up grid — layout shift: 3→2 columns */
@container services-grid (max-width: 64rem) {
  .rwest-services-grid .wp-block-post-template {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ≤ 43.75rem (700px section column width): 1-up stack — layout shift: 2→1 column */
@container services-grid (max-width: 43.75rem) {
  .rwest-services-grid .wp-block-post-template {
    grid-template-columns: 1fr;
  }
}

/* ─── Card root (inner group — ul > li > div.wp-block-group.rwest-service-card) ── */

.rwest-service-card {
  /* layout — flex column (set via group layout attribute; CSS reinforces) */
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--4); /* 1rem between card sections */
  height: 100%; /* fill the li grid cell for equal-height cards */

  /* box */
  background: var(--wp--preset--color--pure-white);
  border: 1px solid var(--wp--preset--color--border-subtle);
  padding-block: var(--wp--preset--spacing--10); /* 2.5rem top/bottom */
  padding-inline: var(--wp--preset--spacing--8); /* 2rem left/right */

  /* interaction baseline — transition at rest so reduced-motion stop is effective;
     card-level: 0.2s (wireframe --duration-base 200ms); arrow stays 0.18s (wireframe-exact) */
  transition:
    transform 0.2s ease,
    border-color 0.2s ease;

  /* stretched-link container */
  position: relative; /* contains the stretched post-title <a> */

  /* container query — components respond to their container, not viewport */
  container-type: inline-size;
  container-name: service-card;

  /* component-scoped tokens */
  --service-card-oneliner-max: 38rem;
  --service-card-focus-ring: 2px;
}

/* ─── Number (per-position, purely presentational) ───────────────────────────
   Set explicitly by card position via :nth-child rather than a CSS counter,
   because Safari/WebKit does not honour counter-increment on the flex/grid
   items in this Query-Loop DOM (rendered "00" on every card — QA #67).
   The "NN" / "" alt-text form zero-pads the number AND suppresses it from
   screen readers (decorative). Covers the Query Loop's perPage:12. */

.rwest-service-card__num::before {
  content: '00' / ''; /* fallback for any card beyond position 12 */
  font-family: var(--wp--preset--font-family--roboto);
  font-weight: 700;
  font-size: var(--wp--preset--font-size--body-xl); /* 1.25rem */
  color: var(--wp--preset--color--brand-aa); /* #D63A2D — 4.66:1 on white, AA ✓ */
  letter-spacing: 0.08em;
}

.rwest-services-grid li:nth-child(1) .rwest-service-card__num::before { content: '01' / ''; }
.rwest-services-grid li:nth-child(2) .rwest-service-card__num::before { content: '02' / ''; }
.rwest-services-grid li:nth-child(3) .rwest-service-card__num::before { content: '03' / ''; }
.rwest-services-grid li:nth-child(4) .rwest-service-card__num::before { content: '04' / ''; }
.rwest-services-grid li:nth-child(5) .rwest-service-card__num::before { content: '05' / ''; }
.rwest-services-grid li:nth-child(6) .rwest-service-card__num::before { content: '06' / ''; }
.rwest-services-grid li:nth-child(7) .rwest-service-card__num::before { content: '07' / ''; }
.rwest-services-grid li:nth-child(8) .rwest-service-card__num::before { content: '08' / ''; }
.rwest-services-grid li:nth-child(9) .rwest-service-card__num::before { content: '09' / ''; }
.rwest-services-grid li:nth-child(10) .rwest-service-card__num::before { content: '10' / ''; }
.rwest-services-grid li:nth-child(11) .rwest-service-card__num::before { content: '11' / ''; }
.rwest-services-grid li:nth-child(12) .rwest-service-card__num::before { content: '12' / ''; }

/* ─── Service name (h3) ──────────────────────────────────────────────────── */

.rwest-service-card__name {
  color: var(--wp--preset--color--surface-3); /* #111 — 18.88:1 on white ✓ */
  line-height: 1.1;

  /* font-family/weight/size come from substrate styles.elements.heading (Prata 400, heading-md) */
}

/* ─── One-liner (post excerpt) ───────────────────────────────────────────── */

.rwest-service-card__oneliner {
  color: var(--wp--preset--color--gray-dark); /* #333333 — 12.6:1 on white, wireframe-exact */
  font-size: var(--wp--preset--font-size--body-md); /* 1rem */
  line-height: 1.5;
  max-width: var(--service-card-oneliner-max);
}

/* ─── CTA paragraph ("Explore →") ───────────────────────────────────────── */

/* Plain static <p> — sighted-only affordance; no aria-hidden on the block.
   margin-top: auto floats CTA to card bottom. Selector is parent-scoped
   (0,2,0) to beat WP core's flex child-margin reset `.is-layout-flex >
   :is(*, div) { margin:0 }` (0,1,1) — the flex analogue of the flow-gap-reset
   at the top of this file; a bare `.rwest-service-card__cta` (0,1,0) loses to it
   and the CTA never bottom-aligns. */
.rwest-service-card > .rwest-service-card__cta {
  margin-top: auto;
  padding-top: var(--wp--preset--spacing--4); /* 1rem */
  color: var(--wp--preset--color--surface-3); /* #111 at rest */
  font-size: var(--wp--preset--font-size--body-sm); /* 0.875rem */
  font-weight: 400;
  letter-spacing: 0.04em;
}

/* Arrow: transition at rest (0.18s wireframe-exact); reduced-motion block sets none.
   display:inline-block is REQUIRED for the hover translateX to apply (transform
   is a no-op on inline elements) — without it the arrow-slide silently dead.
   inline-block trims the span's leading text space, so the label-to-arrow gap is
   restored with an em margin (same fix as pair-card/featured-card). */
.rwest-service-card__cta-arrow {
  display: inline-block;
  margin-left: 0.3em;
  transition: transform 0.18s ease;
}

/* ─── Stretched post-title link ──────────────────────────────────────────── */

/* [recipe:stretched-link] — CANONICAL instance; other cards reference this anchor.
   The <a> CARRIES the title text (core/post-title isLink wraps the content),
   so it must stay in flow — absolutizing the anchor itself pulls the heading
   out of the card layout (caught by the visual read 2026-06-10: titles painted
   over the card edge and collided with the numbers; OfficeCard repeated the
   same break from a reviewed spec the same day). The recipe, all parts required:
   1. card root: position: relative (the ::after's positioning context);
   2. a::after { content:""; position:absolute; inset:0; z-index:1 } — the
      pseudo stretches, never the anchor;
   3. focus ring on the CARD boundary via :has(a:focus-visible) — the in-flow
      anchor's own outline would trace only the text box; the card must NEVER
      get overflow: hidden (it would clip the ring);
   4. single-link cards: blanket pointer-events suppression on non-title
      children; multi-link cards: NO blanket — lift the other links to
      z-index: 2 instead (see PairCard query form).
   Color pinned — the global elements.link brand red fails parity AND contrast
   intent for the title (#111 per the token table). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-service-card .wp-block-post-title a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: none;
}

.rwest-service-card .wp-block-post-title a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Focus ring on the CARD boundary via :has() — the in-flow anchor's own outline
   would trace only the title text box. .rwest-service-card must NEVER get
   overflow: hidden — it would clip this ring. */
.rwest-service-card:has(a:focus-visible) {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* Prevent inner elements swallowing clicks from the stretched link.
   WARNING: if a second interactive element is ever added inside the card,
   restore its pointer-events explicitly — this blanket rule will disable it. */
.rwest-service-card > *:not(.wp-block-post-title) {
  pointer-events: none;
}

/* ─── Hover / focus-visible (card-level) ─────────────────────────────────── */

/* Border darkens + card lifts */
.rwest-service-card:has(a:hover),
.rwest-service-card:has(a:focus-visible) {
  border-color: var(--wp--preset--color--surface-3);
  transform: translateY(-2px);
}

/* CTA text shifts to brand-aa on hover/focus */
.rwest-service-card:has(a:hover) .rwest-service-card__cta,
.rwest-service-card:has(a:focus-visible) .rwest-service-card__cta {
  color: var(--wp--preset--color--brand-aa);
}

/* Arrow slides right on hover/focus */
.rwest-service-card:has(a:hover) .rwest-service-card__cta-arrow,
.rwest-service-card:has(a:focus-visible) .rwest-service-card__cta-arrow {
  transform: translateX(4px);
}

/* ─── Container breakpoints (card-internal reflow) ───────────────────────── */

/* ≤ 30rem (~480px card width): narrow — oneliner uncaps at 1-up context */
@container service-card (max-width: 30rem) {
  .rwest-service-card__oneliner {
    --service-card-oneliner-max: none;
  }
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .rwest-service-card {
    transition: none;
  }

  .rwest-service-card__cta-arrow {
    transition: none;
  }
}

/* ==========================================================================
   OfficeCard — rwest/office-card
   ========================================================================== */

/*
 * Container model: .rwest-offices-grid (query wrapper) is the outer container;
 * .rwest-office-card (group inside each li) is the inner card container.
 *
 * Grid breakpoints (offices-grid container):
 *   56.25rem (900px section column width) — 3→1 column (layout shift)
 *
 * Allowed literals:
 *   aspect-ratio: 4 / 3 (image — layout threshold)
 *   translateY(-2px) (hover — decorative)
 *   transition declarations (reduced-motion stop required)
 *   letter-spacing: 0.12em (CTA — untokenizable)
 *   text-transform: uppercase (CTA — CSS keyword)
 *   outline-width: 2px (focus ring — no WP preset border-width family)
 *   outline-offset: 2px (focus ring — same rationale)
 *   font-weight: 400 (integer — Prata only weight)
 *
 * No counter — OfficeCard is counterless (no service sequence numbers).
 */

/* ─── Grid ──────────────────────────────────────────────────────────────── */

.rwest-offices-grid {
  container-type: inline-size;
  container-name: offices-grid;
}

/* [recipe:flow-gap-reset] — see the ServiceCard canonical instance */
.rwest-offices-grid .wp-block-post-template {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3-up: desktop default */
  gap: var(--wp--preset--spacing--6); /* 1.5rem — matches wireframe gap */
}

.rwest-offices-grid .wp-block-post-template > * {
  margin-block: 0; /* neutralize WP > * + * flow gap on li items */
}

/* ≤ 56.25rem (900px section column width): 1-up — layout shift: 3→1 column */
@container offices-grid (max-width: 56.25rem) {
  .rwest-offices-grid .wp-block-post-template {
    grid-template-columns: 1fr;
  }
}

/* ─── Card root (ul > li > div.wp-block-group.rwest-office-card) ─────── */

.rwest-office-card {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--3); /* 0.75rem between image and meta */
  height: 100%;
  background: var(--wp--preset--color--pure-white);
  transition: transform 0.2s ease;
  position: relative; /* stretched-link container */
  container-type: inline-size;
  container-name: office-card;
}

/* ─── Featured image ──────────────────────────────────────────────────── */

/* WP emits figure.wp-block-post-featured-image containing an img */
.rwest-office-card .wp-block-post-featured-image {
  aspect-ratio: 4 / 3;
  overflow: hidden; /* clips the img at 4:3 — safe here (not the card root) */
}

.rwest-office-card .wp-block-post-featured-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ─── City name (h3) — direct child of card root ─────────────────────── */

/* No meta wrapper group. Padding applied here instead. WP flow block-gap  */

/* margins on direct children are zeroed by the > * { margin-block: 0 }   */

/* grid rule (ServiceCard-verified); CSS gap is unaffected by the injected */

/* blockGap (gap property vs margin mechanism).                            */

.rwest-office-card__city {
  padding-top: var(--wp--preset--spacing--3); /* 0.75rem — space below image */
  padding-inline: var(--wp--preset--spacing--4); /* 1rem left/right */
  color: var(--wp--preset--color--surface-3); /* #111 — 18.88:1 on white ✓ */

  /* font-family/weight come from substrate (Prata 400, styles.elements.heading);
     fontSize: "body-xl" attribute emits has-body-xl-font-size utility class,
     which out-specifies the styles.elements.h3 heading-md default — same
     mechanism as service-card. If a second link is ever added inside the card,
     update the pointer-events selector below. */
}

/* ─── CTA paragraph ("Explore the studio →") ────────────────────────── */

.rwest-office-card__cta {
  margin-top: auto; /* floats CTA to card bottom */
  padding-inline: var(--wp--preset--spacing--4); /* 1rem */
  padding-bottom: var(--wp--preset--spacing--3); /* 0.75rem */
  color: var(--wp--preset--color--gray-aa);
  font-size: var(--wp--preset--font-size--body-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ─── Stretched post-title link ───────────────────────────────────────── */

/* [recipe:stretched-link] — see the ServiceCard canonical instance for the
   full rationale (in-flow anchor + ::after + :has() focus ring + no
   overflow:hidden). Instance-specific: color pinned to surface-3 (#111,
   wireframe About.css:134) vs the global elements.link brand red. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-office-card .wp-block-post-title a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: none;
}

.rwest-office-card .wp-block-post-title a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Focus ring per [recipe:stretched-link] part 3 */
.rwest-office-card:has(a:focus-visible) {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* Single-link blanket pointer-events suppression per [recipe:stretched-link]
   part 4 — if a second interactive element is ever added, switch to the
   multi-link z-index form instead. */
.rwest-office-card > *:not(.wp-block-post-title) {
  pointer-events: none;
}

/* ─── Hover / focus-visible (card-level) ─────────────────────────────── */

.rwest-office-card:has(a:hover),
.rwest-office-card:has(a:focus-visible) {
  transform: translateY(-2px);
}

.rwest-office-card:has(a:hover) .rwest-office-card__cta,
.rwest-office-card:has(a:focus-visible) .rwest-office-card__cta {
  color: var(--wp--preset--color--brand-aa);
}

/* ─── Reduced motion ─────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .rwest-office-card {
    transition: none;
  }
}

/* ==========================================================================
   TeamCard — rwest/team-card
   ========================================================================== */

/*
 * Grid-overlay mechanism: .rwest-team-card uses display:grid. Both the
 * featured-image figure and the meta group share grid-area 1/1 (same cell) —
 * meta overlays the image without position:absolute, avoiding the containing-block
 * ambiguity that container-type:inline-size introduces (see css-conventions cq gotcha).
 * Only the figure's ::after pseudo-element uses position:absolute — it anchors
 * inside the figure's own position:relative context.
 *
 * Allowed literals (per spec):
 *   aspect-ratio: 1 / 1 (image — layout threshold)
 *   position: relative / overflow: hidden (figure — stacking context + clip)
 *   content: "" (::after — decorative pseudo-element)
 *   inset: 0 (::after)
 *   gradient values (overlay — contrast-mechanism values, WCAG math documented)
 *   rgba(255,255,255,0.8) / rgba(0,0,0,0.6) (overlay/below text — decorative opacity)
 *   font-weight: 600 (name — Roboto weight integer, untokenizable)
 *   font-weight: 400 (title — Roboto weight integer, untokenizable)
 *   line-height: 1.25 / 1.4 (untokenizable)
 *   scale(1.04) (image hover — decorative)
 *   transition declarations (reduced-motion stop required)
 *   0.2rem (gap between name and title — em-relative, scales with font)
 *   45rem (layout-toggle container threshold — ≤768px viewport ≈ 45rem content column)
 *   64rem / 36rem (grid column breakpoints)
 */

/* ─── Grid ──────────────────────────────────────────────────────────────── */

.rwest-team-grid {
  container-type: inline-size;
  container-name: team-grid;
}

/* [recipe:flow-gap-reset] — see the ServiceCard canonical instance */
.rwest-team-grid .wp-block-post-template {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* 4-up: desktop default */
  gap: var(--wp--preset--spacing--8) var(--wp--preset--spacing--6); /* 2rem row / 1.5rem col */
}

.rwest-team-grid .wp-block-post-template > * {
  margin-block: 0; /* neutralize WP > * + * flow gap on li items */
}

/* ≤ 64rem (1024px): 3-up — layout shift: 4→3 columns */
@container team-grid (max-width: 64rem) {
  .rwest-team-grid .wp-block-post-template {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ≤ 37rem (592px container = 640px viewport − global padding): 1-up — layout shift: 3→1
   column. Threshold raised from 36rem→37rem (2026-06-14): at 36rem the container at a
   640px viewport (≈592px) just missed the cutoff and stayed 3-up, while the wireframe's
   `@media (max-width: 640px)` is already single-column there. 37rem (the empirical
   container width at the 640px-viewport wireframe breakpoint) makes the 3→1 transition
   land on the same device width. Breakpoint literal = layout threshold (allowed). */
@container team-grid (max-width: 37rem) {
  .rwest-team-grid .wp-block-post-template {
    grid-template-columns: 1fr;
    gap: var(--wp--preset--spacing--8) 0;
  }
}

/* ─── Card root (ul > li > div.wp-block-group.rwest-team-card) ──────────── */

/* Grid-overlay stacking: container-type implies layout containment (see css-conventions
   cq gotcha — container becomes the containing block for abs descendants). Use CSS grid
   instead of position:absolute on the meta to avoid that ambiguity entirely. */
.rwest-team-card {
  container-type: inline-size;
  container-name: team-card;
  display: grid;

  /* grid-template-rows defined implicitly: row 1 = image+overlay, row 2 = below-meta (narrow) */
}

/* [recipe:flow-gap-reset] at CARD level (the injection hits plain Groups too,
   not just the post-template UL). Instance-specific: the margin ate 24px of the
   meta's grid stretch — gradient started below the image top; caught by probe
   2026-06-10. The grid owns spacing inside the card. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-team-card > * {
  margin-block: 0;
}

/* ─── Featured image figure ─────────────────────────────────────────────── */

/* figure gets position:relative so ::after (gradient) anchors inside image bounds.
   Grid area 1/1 — shares the cell with .rwest-team-card__meta in wide mode. */
.rwest-team-card .wp-block-post-featured-image {
  grid-area: 1 / 1;
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden; /* clips img at 1:1; also clips img scale(1.04) on hover */
}

.rwest-team-card .wp-block-post-featured-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s ease;
}

/* Subtle scale on hover — decorative; stopped by reduced-motion */
.rwest-team-card:hover .wp-block-post-featured-image img {
  transform: scale(1.04);
}

/* ─── Meta group — ONE instance, carries the gradient, repositions per mode ── */

/* Wide: grid-area 1/1 overlapping the image, stretched to the FULL cell with
   the text flex-pushed to the bottom — and the contrast-mechanism gradient is
   the meta's OWN background. Two reasons (2026-06-10):
   1. axe/pa11y walks the TEXT element's ancestor chain for background — a
      gradient on the sibling figure's ::after is invisible to it and white
      text reported 1:1 (false fail). With the background-image on the meta
      itself, axe marks contrast "incomplete" (needs human review) instead —
      and the human math is in the spec: floor rgba(0,0,0,0.72) over white
      image = RGB(71,71,71), L=0.0630 (full WCAG formula); name #fff = 9.29:1
      AA, title rgba(255,255,255,0.8) composited = 6.65:1 AA.
   2. Full-cell stretch makes the gradient geometry identical to the
      wireframe's inset:0 overlay div — zero extra DOM.
   NO position:absolute on the meta — grid stacking avoids the containing-block
   gotcha that container-type:inline-size introduces.
   Narrow (≤768px viewport ≈ 45rem content column): grid-area 2/1 moves meta
   below the image row (below colors, gradient off). */

/* Base meta properties — bare selector before the compound grid-area rule below
   to satisfy no-descending-specificity (ascending order: 0,1,0 → 0,2,0). */
.rwest-team-card__meta {
  align-self: stretch; /* full cell — gradient covers the whole image */
  justify-content: flex-end; /* text anchors to image bottom */
  padding: var(--wp--preset--spacing--4) var(--wp--preset--spacing--4) var(--wp--preset--spacing--5);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgb(0 0 0 / 72%) 0%,
    rgb(0 0 0 / 18%) 45%,
    transparent 70%
  );
  transition: background 0.3s ease;

  /* paints above the image in the shared grid cell */
  z-index: 1;
}

/* Grid-area assignment — compound selectors place both image and meta in cell 1/1. */
.rwest-team-card .wp-block-post-featured-image,
.rwest-team-card .rwest-team-card__meta {
  grid-area: 1 / 1; /* wide mode: both in cell 1 — meta overlays image */
}

/* Overlay darken on hover */
.rwest-team-card:hover .rwest-team-card__meta {
  background: rgb(0 0 0 / 62%);
}

/* ─── Name (h3) ─────────────────────────────────────────────────────────── */

/* Substrate applies Prata 400 via styles.elements.heading — override to Roboto 600 */
.rwest-team-card__name {
  font-family: var(--wp--preset--font-family--roboto);

  /* fontSize attr emits has-body-sm-font-size; explicit token here too for specificity */
  font-size: var(
    --wp--preset--font-size--body-sm
  ); /* 0.875rem; wireframe: 0.9375rem — delta ~1.5px, accepted */

  font-weight: 600;
  line-height: 1.25;
  color: var(--wp--preset--color--pure-white);
}

/* ─── Job title (bound paragraph) ──────────────────────────────────────── */

.rwest-team-card__title {
  font-size: var(
    --wp--preset--font-size--body-xs
  ); /* 0.75rem; wireframe: 0.8125rem — delta ~1px, accepted */

  font-weight: 400;
  line-height: 1.4;
  color: rgb(255 255 255 / 80%);
}

/* ─── Narrow layout — ≤768px viewport ≈ 45rem content column ───────────── */

/* Hides gradient overlay, moves meta to row 2 below image, applies below colors.
   Container query on team-grid (the outer named container) — components
   respond to their container column, not the viewport. */

@container team-grid (max-width: 45rem) {
  /* Meta: move to row 2 (below image) via grid-area; gradient off (below colors).
     Selector matches the wide-mode compound rule's specificity (0,2,0) — a bare
     class here silently LOSES to the wide grid-area rule (caught 2026-06-10). */
  .rwest-team-card .rwest-team-card__meta {
    grid-area: 2 / 1;
    align-self: auto;
    justify-content: flex-start;
    padding: var(--wp--preset--spacing--2) 0 var(--wp--preset--spacing--1);

    /* spacing-2 = 0.5rem top; wireframe narrow top ≈ 0.625rem — delta ~2px, accepted */
    pointer-events: auto;
    z-index: auto;
    background: none;
  }

  /* Below-mode colors — wireframe:
     .wf-team-card-meta--below .wf-team-card-name  { color: var(--color-black, #111) }
     .wf-team-card-meta--below .wf-team-card-title { color: rgba(0,0,0,0.6) }        */
  .rwest-team-card__name {
    color: var(--wp--preset--color--surface-3); /* #111 — 18.88:1 on white ✓ */
  }

  .rwest-team-card__title {
    color: rgb(0 0 0 / 60%); /* composited ≈ RGB(102,102,102) — 5.74:1 on white ✓ */
  }
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .rwest-team-card .wp-block-post-featured-image img,
  .rwest-team-card .rwest-team-card__meta {
    transition: none;
  }
}

/* ==========================================================================
   PairCard Query-Loop form — rwest/pair-card-query
   Recomposes the standalone PairCard (classes shared — its full contract above
   applies). Additions: the work grid, post-terms re-keying (kicker + caps emit
   flat <a> runs, not list items), the stretched title link, and the
   multi-link stacking map (image/kicker/caps stay clickable above the stretch).
   ========================================================================== */

/* ─── Grid ──────────────────────────────────────────────────────────────── */

.rwest-work-grid {
  container-type: inline-size;
  container-name: work-grid;
}


.rwest-work-grid .wp-block-post-template {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr); /* pair grid — wireframe Work index */
  gap: var(--wp--preset--spacing--8); /* 2rem — wireframe pair gap */

  /* Each card sizes to its own content — do NOT row-equalize. Grid's default
     align-items: stretch makes both <li> slots in a 2-up row as tall as the
     taller card, but the inner .rwest-pair-card sizes to its content, leaving
     blank space below the shorter card (probed 2026-06-14: up to 52px at 768px).
     The wireframe's pair grid does not equalize (card heights differ per row);
     align-items: start matches that — the card height tracks its content. */
  align-items: start;
}

/* [recipe:flow-gap-reset] — see the ServiceCard canonical instance */
.rwest-work-grid .wp-block-post-template > * {
  margin-block: 0;
}

/* ≤ 36rem container (≈640px viewport): 1-up — layout shift: 2→1 column */
@container work-grid (max-width: 36rem) {
  .rwest-work-grid .wp-block-post-template {
    grid-template-columns: 1fr;
  }
}

/* Featured rhythm (every 7th card → full-width poster) lives near the end of
   this file — see "Work feed — featured rhythm". Its selectors are high-
   specificity scoped overrides of the pair-card base rules above, so they must
   come AFTER them (no-descending-specificity). */

/* [recipe:flow-gap-reset] at CARD level (TeamCard lesson); the standalone card's
   gap comes from its own child margins/paddings, preserved via shared classes */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card > * {
  margin-block: 0;
}

/* Restore the standalone card's vertical rhythm (its meta-group blockGap does
   not exist here — children are direct; spacing-2 ≈ the wireframe meta gap
   0.55rem between text slots). */
.rwest-work-grid .rwest-pair-card > * + * {
  margin-block-start: var(--wp--preset--spacing--2);
}

/* See-project type parity (wireframe: 14px / line-height 1.5 / 0.04em tracking). */
.rwest-work-grid .rwest-pair-card__see-project {
  line-height: 1.5;
  letter-spacing: 0.04em; /* untokenizable — allowed literal */
}

.rwest-work-grid .rwest-pair-card .rwest-pair-card__media {
  margin-block-end: var(--wp--preset--spacing--4); /* image-to-meta gap (standalone meta padding-top) */
}

/* ─── Stretched title link — [recipe:stretched-link], multi-link variant ──
   See the ServiceCard canonical instance for the full rationale. This card has
   OTHER links (image/kicker/caps), so recipe part 4 takes the multi-link form:
   no blanket pointer-events — the stacking map below lifts them instead. */

.rwest-work-grid .rwest-pair-card {
  position: relative;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__title a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: none;
}

.rwest-work-grid .rwest-pair-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Focus ring per [recipe:stretched-link] part 3 */
.rwest-work-grid .rwest-pair-card:has(a:focus-visible) {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* ─── Multi-link stacking map ────────────────────────────────────────────── */

/* [recipe:stretched-link] part 4, multi-link form: image, kicker, and capability
   links stay clickable ABOVE the title stretch (z-index 2 > the ::after's 1). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__media a,
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__kicker a,
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__caps a {
  position: relative;
  z-index: 2;
}

/* ─── Kicker via post-terms (flat <a> run) ──────────────────────────────── */

/* Typography comes from the standalone __kicker contract (shared class).
   Links pinned to the kicker treatment vs global elements.link brand red;
   underline on hover/focus is the link affordance (block-native delta:
   wireframe kicker is plain text — ours links to the industry archive). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__kicker a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: none;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__kicker a:hover,
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__kicker a:focus-visible {
  text-decoration: underline;
}

/* ─── Capability chips via post-terms (flat <a> run, re-keyed from li>a) ─── */

.rwest-work-grid .rwest-pair-card__caps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--2);

  /* Chip ul sits 0.5rem below the meta gap (wireframe ≈17px total); line-height
     1.5 matches the wireframe chip row (ours otherwise inherits the body 1.6). */
  margin-block-start: var(--wp--preset--spacing--4);
  line-height: 1.5;
}

/* Hide the separator text nodes between terms (gap owns the spacing) */
.rwest-work-grid .rwest-pair-card__caps .wp-block-post-terms__separator {
  display: none;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__caps a {
  color: var(--wp--preset--color--gray-aa); /* #555 — 7.46:1 on white ✓ */
  border: 1px solid var(--wp--preset--color--border-subtle); /* #d4d4d4 pill */
  border-radius: 999px;
  padding: 0.25em 0.9em;
  font-size: var(--pair-card-cap-font-size, 0.65rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 0.18s ease, color 0.18s ease;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__caps a:hover {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--pure-white);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-grid .rwest-pair-card__caps a:focus-visible {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  /* stylelint-disable-next-line no-descending-specificity */
  .rwest-work-grid .rwest-pair-card__caps a,
  .rwest-work-grid .rwest-pair-card__see-project-arrow {
    transition: none;
  }
}

/* ==========================================================================
   Footer — parts/footer.html (template chrome)
   Black band: centered eyebrow, the word-ticker block, legal strip.
   ========================================================================== */

.rwest-footer {
  overflow: hidden;

  /* Match the site gutter. The footer group carries `has-background` (black),
     so WP core applies its default `.has-background` padding of 2.375em (38px)
     horizontally — wider than the 24px root gutter every other section uses,
     so the footer content sat too far in (QA #79, 2026-06-22). Pin the inline
     padding to the gutter token; vertical padding stays as-is. */
  padding-inline: var(--wp--preset--spacing--6);
}

.rwest-footer__eyebrow {
  padding-top: var(--wp--preset--spacing--12);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 500;
  margin-bottom: 0;
}

/* Eyebrow r\ glyph — brand prefix matching wireframe (R\ WHY THE R?); brand red
   on the dark footer. Decorative; suppressed from the a11y tree via / "". */
.rwest-footer__eyebrow::before {
  content: "r\\" / "";
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.35em;
}

/* Legal strip — wireframe #666 on black is 3.66:1 (AA fail); gray token #888
   = 5.9:1 AA pass (same correction class as gray-aa). Links inherit, hover
   white (wireframe affordance) + underline for the non-color cue. */
.rwest-footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--6);
  align-items: center;
  padding: var(--wp--preset--spacing--5) var(--wp--preset--spacing--8);
  border-top: 1px solid var(--wp--preset--color--border);
  color: var(--wp--preset--color--gray);
  letter-spacing: 0.02em;
  margin-top: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-footer__legal a {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-footer__legal a:hover {
  color: var(--wp--preset--color--pure-white);
  text-decoration: underline;
}

/* Focus ring required: color+underline alone does not satisfy WCAG 2.2
   SC 2.4.11 (enclosed perimeter) — axe/pa11y only partially covers this. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-footer__legal a:focus-visible {
  color: var(--wp--preset--color--pure-white);
  text-decoration: underline;
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .rwest-footer__legal a {
    transition: none;
  }
}

/* ==========================================================================
   Utilities
   ========================================================================== */

/* Screen-reader-only text — the standard WP recipe. Core does not ship a
   front-end .screen-reader-text rule for block themes; page-composition
   patterns use it for outline-preserving section headings the design shows
   no visible heading for (h1 hero → h3 card titles would skip a level). */
.screen-reader-text {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  word-wrap: normal !important;
}

/* ==========================================================================
   Page: Services — rwest/page-services (intro + logo band sections)
   The hero/grid/CTA sections reuse their own pattern contracts above.
   ========================================================================== */

.rwest-services-intro {
  container-type: inline-size;
}

/* Eyebrow — mirrors the NPQ canonical eyebrow (see rwest-narrative-pull-quote__eyebrow
   at the top of this file) incl. the decorative brand r\ glyph, SR-hidden via the
   alt-text content form. Weight/letter-spacing are untokenizable presentational
   literals (acknowledged exception, css-conventions). */
.rwest-services-intro__eyebrow {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  margin-bottom: var(--wp--preset--spacing--6);
}

.rwest-services-intro__eyebrow::before {
  content: "r\\" / "";
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.35em;
}

/* Intro statement — wireframe .wf-services-intro-body: Roboto 300, ~26pt,
   line-height 1.6, centered, 72rem measure (the group's contentSize carries
   the measure). heading-md (2rem) is the nearest type preset; weight 300 is
   a Roboto weight integer (untokenizable literal, acknowledged exception). */
.rwest-services-intro__body {
  /* wireframe .wf-services-intro-body: 26pt (34.67px) flat, weight 300, lh 1.6.
     The body's own max-width is 72rem, but its WRAPPER (.wf-services-intro) caps
     at 56rem, so the EFFECTIVE measure is 56rem (896px) — replicate that here
     (ours has no equivalent 56rem wrapper; our section contentSize is wider).
     Fluid clamp maxes at the wireframe value (no preset = 34.67px). */
  font-size: clamp(1.5rem, 2.7vw, 2.1667rem);
  font-weight: 300;
  line-height: 1.6;
  max-width: 56rem;
  margin-inline: auto;
}

/* ==========================================================================
   Social Feed — rwest/social-feed
   Eyebrow + "Follow along." heading with handle link, plugin feed slot,
   centered outlined CTA. The live grid arrives with Smash Balloon (SB-2).
   ========================================================================== */

.rwest-social-feed {
  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block
   (one shared rule set in src/section-intro/style.css). */

/* Handle link — wireframe .wf-social-handle: italic display face (Playfair
   Display, --font-display-italic), brand color, hover underline with 4px offset.
   brand-aa (not brand) so the link passes AA below the 24px large-text threshold
   (fluid heading at narrow widths). The inline link otherwise inherits Prata
   from the heading, which has no italic face and renders faux-slanted (R29).
   letter-spacing literal: untokenizable (acknowledged exception). */
.rwest-social-feed__handle {
  color: var(--wp--preset--color--brand-aa);
  font-family: var(--wp--preset--font-family--playfair-display);
  font-style: italic;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.rwest-social-feed__handle:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.rwest-social-feed__handle:focus-visible {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Feed slot placeholder — reads as an empty slot, not site content. Replaced by
   the Smash Balloon feed block when SB-2 lands. */
.rwest-social-feed__slot {
  border: 1px dashed var(--wp--preset--color--border-subtle);
  color: var(--wp--preset--color--gray-aa);
  padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--8);

  /* Placeholder text centers inside the slot; the slot box stays on the 1200px
     content column. box-sizing:border-box is load-bearing — the slot defaults
     to content-box, so the constrained layout's inherited max-width:1200 capped
     only the content box and the horizontal padding pushed the border-box to
     1232 (L24), 16px wider than the heading. border-box makes the cap apply to
     the whole box; the layout then centers it back to L40 (align-review,
     toggle-verified 2026-06-12). */
  box-sizing: border-box;
  text-align: center;
}

/* Secondary (Light) button variation — hover/focus companions to
   styles/secondary-light.json (rest face). The global theme.json
   elements.button:hover injects brand-active as background; counter it with
   the wireframe's subtle dark wash (rgba(0,0,0,0.06) → 6% black color-mix). */
/* stylelint-disable-next-line no-descending-specificity -- ordered by component
   section per file convention; the hero-scoped button rules above are unrelated. */
.is-style-secondary-light .wp-block-button__link:hover {
  background: color-mix(
    in srgb,
    var(--wp--preset--color--black) 6%,
    transparent
  );
  color: var(--wp--preset--color--surface-3);
}

/* stylelint-disable-next-line no-descending-specificity -- same as above. */
.is-style-secondary-light .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* ============ sprint 2026-06-12 :: wave1-b ============ */

/**
 * wave1-b.css — Sprint 2026-06-12, wave 1-b pattern CSS
 *
 * Patterns: rwest/who-we-r, rwest/about-story, rwest/principles,
 *           rwest/culture-snippet, rwest/dei-blade, rwest/page-about (offices-preview intro)
 *
 * Conventions: container-queries-first; tokens only (--wp--preset--*);
 * allowed literals: container-query thresholds (rem), letter-spacing (em),
 * font-weight integers, border/outline widths (px), line-height scalars,
 * ch/em measure values. Per css-conventions.md.
 *
 * Destination: merge relevant sections into assets/css/patterns.css when
 * these patterns graduate from sprint-scratch to reviewed.
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   WhoWeR — rwest/who-we-r
   ========================================================================== */

/*
 * Container model: the full-bleed section group is the container; inner
 * content is constrained by the block's layout.contentSize (900px).
 *
 * Component-scoped layout constants:
 *   --who-we-r-body-measure: 80ch — wireframe .wf-who-we-r__body max-width: 80ch.
 *     No --wp--preset-- equivalent; allowed layout literal (ch measure).
 *   --who-we-r-body-weight: 300 — wireframe font-weight: 300 (Roboto light).
 *     Untokenizable; allowed font-weight integer.
 *
 * Breakpoints: no container-query breakpoints needed — the 900px contentSize
 * constrains the column; text reflows within it without layout shifts. A single
 * threshold at ≤30rem tightens body line-height for very narrow contexts.
 */

.rwest-who-we-r {
  /* 68rem = the wireframe .wf-who-we-r__body RENDERED width (its 80ch caps at the
     72rem container minus 2rem padding = 1088px @1280). Our section is full-bleed,
     so cap the body directly at 68rem to match — 80ch alone would fill the section
     (~1232px, too wide; the flagged "lead-in width" delta). */
  --who-we-r-body-measure: 68rem;
  --who-we-r-body-weight: 300;

  container-type: inline-size;
}

/* Eyebrow — uppercase brand marker, matching the NPQ/Hero eyebrow pattern */
.rwest-who-we-r__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 500;
  color: var(--wp--preset--color--surface-3);
}

/* Eyebrow decorative r\ glyph — same technique as NarrativePullQuote */
.rwest-who-we-r__eyebrow::before {
  /* decorative brand glyph — suppressed from accessibility tree via / "" */
  content: 'r\\' / '';
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.35em;
}

/*
 * Body — large editorial statement. Wireframe: 26pt = 34.67px flat. heading-md
 * (2rem fluid) tops out at ~30.8px @1280 — too small — so size with a bespoke
 * clamp maxing at 2.1667rem (34.67px); no token maps to 26pt (same precedent as
 * services-intro__body). font-weight: 300 (Roboto Light). line-height: 1.6.
 * max-width: 80ch — centralized via --who-we-r-body-measure.
 * color: surface-3 (#111) = 18.88:1 on pure-white — AA pass.
 */
.rwest-who-we-r__body {
  font-size: clamp(1.75rem, 2.7vw, 2.1667rem);
  font-weight: var(--who-we-r-body-weight);
  line-height: 1.6;
  max-width: var(--who-we-r-body-measure);
  margin-inline: auto;
  color: var(--wp--preset--color--surface-3);
}

/* ≤ 30rem (~480px): tighten line-height slightly for narrow reading column */
@container (max-width: 30rem) {
  .rwest-who-we-r__body {
    line-height: 1.45;
  }
}

/* ==========================================================================
   AboutStory — rwest/about-story
   ========================================================================== */

/*
 * Container model: section root is the container; two columns respond to it.
 *
 * Component-scoped layout constants:
 *   --about-story-body-measure: 62ch — wireframe .wf-about-story-body p max-width: 62ch.
 *   --about-story-sticky-top: 6rem — wireframe position:sticky top:6rem on the intro col.
 *     Allowed layout literal; no spacing token = 6rem (spacing-24 = 6rem but that is a
 *     section-padding value, not a sticky offset — using spacing-24 token here would be
 *     semantically wrong). Inline as literal per css-conventions allowed-literals.
 *
 * Breakpoints (section container):
 *   ≤ 56.25rem (900px): single-column stack — intro col becomes static (no sticky).
 *     Triggering behavior: at narrow container the two-column layout collapses; position:sticky
 *     on the intro would cause it to overlap body text in a stacked flow.
 */

.rwest-about-story {
  --about-story-body-measure: 62ch;

  container-type: inline-size;
}

/*
 * Intro column — sticky positioning keeps the eyebrow+heading in view while
 * the body copy scrolls. top: 6rem clears the sticky header.
 * Wireframe: .wf-about-story-intro { position: sticky; top: 6rem }.
 */
.rwest-about-story__intro {
  position: sticky;
  top: 6rem;
}

/* Eyebrow + heading now provided by the rwest/section-intro block
   (one shared rule set in src/section-intro/style.css). */

/* Body paragraphs — measure cap */
.rwest-about-story__body p {
  max-width: var(--about-story-body-measure);
}

/* ≤ 56.25rem (900px column width): stack to single column, release sticky */
@container (max-width: 56.25rem) {
  .rwest-about-story__intro {
    position: static;
  }

  /* Stack to single column the way WP's isStackedOnMobile does — flex-wrap +
     full-width columns, each sized to its content.
     Do NOT use flex-direction:column here: the columns carry an inline
     flex-basis (40%/60%) which WP also forces to 100% on mobile, and in a
     column-direction flex container that 100% resolves against the container
     HEIGHT — stretching the short intro column to ~800px and opening a large
     empty gap below the heading (QA #66, 2026-06-22). Row + wrap + basis:100%
     sizes each stacked column to its content instead. */
  .rwest-about-story__cols {
    flex-wrap: wrap !important;
  }

  .rwest-about-story__cols > .wp-block-column {
    flex-basis: 100% !important;
  }
}

/* ==========================================================================
   Principles — rwest/principles
   ========================================================================== */

/*
 * Container model: section root is the outer container; the grid responds to it.
 *
 * Component-scoped layout constants:
 *   --principles-item-border-width: 2px — wireframe .wf-principles-item border-top: 2px solid.
 *     Allowed border-width literal (no WP preset border-width family).
 *   --principles-body-measure: 38ch — wireframe max-width: 38ch on item body.
 *   --principles-tagline-line-height: 1.3 — untokenizable line-height scalar.
 *
 * Breakpoints (principles grid container):
 *   ≤ 48rem (768px column width): single-column stack — 3→1 column.
 *     Triggering behavior: three-column principles grid collapses to single column
 *     for tablet/mobile contexts where 260px min-width columns would overflow.
 *
 * Note: WP core/columns collapses on mobile natively (isStackedOnMobile flag in
 * the block attribute on the columns wrapper). The @container rule below reinforces
 * this for container-query responsiveness (the block's stack threshold is a viewport
 * media query; our rule fires on the container width — different context).
 */

.rwest-principles {
  --principles-item-border-width: 2px;
  --principles-body-measure: 38ch;
  --principles-tagline-line-height: 1.3;

  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block
   (measure:"narrow" reproduces the old 18ch heading cap). */

/* Each principle column — top border rule (wireframe: 2px solid #222) */
.rwest-principles__item {
  padding-top: var(--wp--preset--spacing--6);
  border-top: var(--principles-item-border-width) solid var(--wp--preset--color--surface-3);
}

/* Principle title (h3) — Prata 400, inherits from elements.heading substrate.
   line-height: 1.1 — matches the global heading substrate; explicit here for clarity. */
.rwest-principles__item-title {
  /* wireframe .wf-principles-item-title: clamp(2rem,3.5vw,2.75rem) = 44px @1280.
     Set here (not via block attr) — no token maps to 44px; was heading-md (~30.8px). */
  font-size: clamp(2rem, 3.5vw, 2.75rem);
  line-height: 1.1;
}

/* Tagline — italic Playfair Display (display-italic face, set via the block's
   has-playfair-display-font-family class; wireframe .wf-principles-item-tagline
   uses --font-display-italic). font-style not a WP token. line-height above. */
.rwest-principles__item-tagline {
  font-style: italic;
  line-height: var(--principles-tagline-line-height);
  color: var(--wp--preset--color--gray-aa);
}

/* Body — measure cap; color: gray-aa (#555555) = 7.46:1 on white — AA pass. */
.rwest-principles__item-body {
  max-width: var(--principles-body-measure);
  line-height: 1.55;
  color: var(--wp--preset--color--gray-aa);
}

/* ≤ 48rem (768px column width): stack columns — 3→1 layout shift */
@container (max-width: 48rem) {
  .rwest-principles__grid {
    flex-direction: column;
  }
}

/* ==========================================================================
   CultureSnippet — rwest/culture-snippet
   ========================================================================== */

/*
 * Container model: section root is the container; contents respond to it.
 *
 * Component-scoped layout constants:
 *   --culture-snippet-lede-measure: 56ch — wireframe .wf-about-culture-lede max-width: 56ch.
 *   --culture-snippet-intro-measure: 60ch — wireframe .wf-about-culture-intro max-width: 60ch.
 *
 * Breakpoints: no layout shifts needed — single-column stacked layout at all widths.
 * Image is full-width via the block's width attributes.
 *
 * Note: wireframe uses a dark/gray section tone. Pattern ships is-style-dark;
 * text colors inherit from the dark variation root.
 */

.rwest-culture-snippet {
  --culture-snippet-lede-measure: 56ch;
  --culture-snippet-intro-measure: 60ch;

  container-type: inline-size;
}

/* Inner flex column: align-items:stretch keeps the media full-width and the
   button row centered, while the capped intro (max-width 60ch) left-anchors —
   instead of WP's constrained layout auto-centering it. (0,2,0) beats WP's
   per-container flex align default. Matches wireframe .wf-about-culture. */
.rwest-culture-snippet .rwest-culture-snippet__inner {
  align-items: stretch;

  /* wireframe .wf-about-culture has padding:2rem inside its 1200 container —
     content insets to x=72 / width 1136 @1280 (vs x=40 / 1200 without it). */
  padding-inline: var(--wp--preset--spacing--8);
}

/* Eyebrow + heading now provided by the rwest/section-intro block.
   The block carries the .rwest-culture-snippet__intro class (below) so the
   60ch whole-intro measure still applies. */

/* Intro group — max measure (now on the section-intro block wrapper) */
.rwest-culture-snippet__intro {
  max-width: var(--culture-snippet-intro-measure);
}

/* Lede — narrower 56ch cap, re-scoped onto the block's lede element
   (was .rwest-culture-snippet__lede; the block renders .wf-section-intro__lede). */
.rwest-culture-snippet__intro .wf-section-intro__lede {
  max-width: var(--culture-snippet-lede-measure);
}

/*
 * Media placeholder — a core/group renders the 16:9 slot when no real image
 * is available. The ::after pseudo-element provides the aspect-ratio box,
 * diagonal-cross hatch, and "Image pending" label, matching the wireframe's
 * wf-ph-image + wf-ph-aspect-16-9 treatment.
 *
 * Asset pending flag: culture photography / video asset not yet supplied.
 * When a real image lands, swap the core/group for a core/image and remove
 * .rwest-culture-snippet__media::after.
 */
.rwest-culture-snippet__media {
  /* Reset group margins so it sits flush in the section flow */
  margin-block: 0;
}

.rwest-culture-snippet__media::after {
  content: "Image pending";
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  width: 100%;

  /* Intentionally higher contrast on the dark surface — matches the wireframe's
     light-gray (#e5e5e5) placeholder slot visible against a dark bg. Use
     gray-dark (--wp--preset--color--gray-dark = #333) at 100% so it reads
     clearly as a placeholder element without introducing a hardcoded hex. */
  background: var(--wp--preset--color--gray-dark);
  border: 1px dashed var(--wp--preset--color--gray);
  color: var(--wp--preset--color--gray);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ==========================================================================
   DEI Blade — rwest/dei-blade
   ========================================================================== */

/*
 * Container model: section root is the container; contents respond to it.
 *
 * Component-scoped layout constants:
 *   --dei-blade-body-measure: 56ch — wireframe .wf-about-dei-body max-width: 56ch.
 *
 * Tone: light (is-style-light). Wireframe background is white/light.
 *
 * Breakpoints: no layout shifts needed — single-column stacked flow at all widths.
 */

.rwest-dei-blade {
  --dei-blade-body-measure: 56ch;

  container-type: inline-size;
}

/* Inner flex column — left-aligns all content (wireframe .wf-about-dei
   align-items:flex-start). The constrained section centers this group as a
   content-width block; the capped body inside is a flex item, so it stays
   LEFT-flush instead of being auto-centered by WP's constrained-layout
   !important margins. */
.rwest-dei-blade__inner {
  align-items: flex-start;

  /* wireframe .wf-about-dei has padding:2rem inside its 1200 container —
     content insets to x=72 @1280 (vs x=40 without it). */
  padding-inline: var(--wp--preset--spacing--8);
}

/* Eyebrow + heading now provided by the rwest/section-intro block
   (eyebrow color inherits the section tone = near-black on light, matching the
   wireframe .wf-narrative-eyebrow; the old gray-aa was a deviation). */

/* Heading — color: surface-3 (#111) = 18.88:1 on white — AA pass. No block
   max-width cap: it auto-centered the heading (constrained-layout margin) 221px
   right of the left-flush eyebrow. Full-column keeps it left-aligned
   (align-review 2026-06-12). */
.rwest-dei-blade .wp-block-heading {
  color: var(--wp--preset--color--surface-3);
}

/* Body — measure cap; color: gray-aa (#555555) = 7.46:1 on white — AA pass.
   Left-flush is handled by the __inner flex column (align-items:flex-start);
   the body is a flex item so its max-width caps without auto-centering. */
.rwest-dei-blade__body {
  max-width: var(--dei-blade-body-measure);
  color: var(--wp--preset--color--gray-aa);
}

/* ==========================================================================
   Offices Preview intro — rwest/page-about (section only; grid styles live
   in the existing OfficeCard section of patterns.css)
   ========================================================================== */

/*
 * The .rwest-offices-preview section wraps the intro group + the Query Loop.
 * The Query Loop uses .rwest-offices-grid and .rwest-office-card which are already
 * defined in patterns.css (OfficeCard section). This block only covers the intro
 * group unique to the page-about composition.
 *
 * Component-scoped constants:
 *   --offices-preview-title-measure: 18ch — comparable to wireframe heading measure.
 */

.rwest-offices-preview {
  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* ============ sprint 2026-06-12 :: wave2-d ============ */

/**
 * wave2-d.css — FeaturedCard + page-work CSS scratch
 *
 * Scope: .rwest-featured-card (all variants) + .rwest-work-head / .rwest-work-feed
 * page-level bits.
 *
 * PROMOTE to assets/css/patterns.css after block-build-loop QA pass.
 * Container-queries-first per css-conventions.md. Tokens only — no hardcoded
 * hex/px/spacing. Allowed literals: container-query thresholds (rem), focus-ring
 * geometry (px), layout clip-path percentages, aspect-ratio values,
 * untokenizable typographic values (letter-spacing em, font-weight integers,
 * line-height unitless).
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   Work page heading — rwest-work-head
   ========================================================================== */

.rwest-work-head {
  container-type: inline-size;
  container-name: work-head;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Eyebrow — uppercase, letter-spaced.
   font-weight: 500 + letter-spacing: 0.2em — untokenizable; allowed literals.
   color: surface-3 (#111) — wireframe .wf-narrative-eyebrow is rgb(17,17,17), NOT gray-aa.
   letter-spacing: 0.2em — wireframe computed 2.4px / 12px = 0.2em.
   Wireframe-probed values 2026-06-13. */
.rwest-work-head__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 500;
  color: var(--wp--preset--color--surface-3);

  /* wireframe computed eyebrow→h1 gap = 24px (spacing-6); was spacing-3 (12px),
     which halved the vertical rhythm vs the reference. Probed 2026-06-14. */
  margin-block-end: var(--wp--preset--spacing--6);
}

/* Eyebrow r\ glyph — brand prefix matching wireframe .wf-narrative-eyebrow::before.
   Wireframe: content "r\\" red bold, margin-right ~0.35em.
   Decorative; hidden from accessibility tree via CSS alt-text `/ ""`.
   Same pattern as .rwest-pair-card__kicker::before and .rwest-narrative-pull-quote__eyebrow::before. */
.rwest-work-head__eyebrow::before {
  content: "r\\" / "";
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.35em;
}

/* Title — h1. line-height + measure; font-family/weight/size from substrate
   (elements.h1: Prata 400, display-lg fluid). Never set font-size here. */
.rwest-work-head__title {
  /* Wireframe safety cap: .wf-work-title has max-width: 18ch (a wrap safeguard
     for long titles; the current copy is one line at this size). WP's
     constrained layout emits `margin-left/right: auto !important` on every
     max-width child, which CENTERS the capped h1 (it was pushed off the
     content-column edge in the align-review 2026-06-12, which is why the cap was
     removed then). Restore the cap LEFT-ANCHORED: override only the start margin
     with !important to beat WP core's !important rule; the end margin stays auto
     to absorb the column slack. This is a sanctioned WP-core override.
     Re-added per wireframe parity 2026-06-14. */
  max-width: 18ch;
  /* stylelint-disable-next-line declaration-no-important */
  margin-inline-start: 0 !important;
  margin-inline-end: auto;
  line-height: 1.05;
  letter-spacing: -0.01em;
}

/* ==========================================================================
   FeaturedCard — rwest/featured-card
   ========================================================================== */

/*
 * Component-scoped layout constants — centralized per css-conventions.md §Breakpoints.
 *
 * --featured-card-media-radius: 2px — matches pair-card image radius.
 *   No --wp--preset--border-radius--* family in WP core; allowed literal.
 * --featured-card-focus-ring: 2px — focus ring width.
 *   No --wp--preset--border-width--* family; allowed literal.
 * --featured-card-tagline-max: 50ch readability cap. No WP preset equivalent; allowed.
 * --featured-card-title-max: 22ch for default/below headline measure. Allowed literal.
 *
 * Container model:
 *   .rwest-featured-card is the container. Media and meta respond to the card width.
 *   Full-bleed variants (overlay, slanted, poster) set explicit stage geometry inside.
 *
 * Grid breakpoints (featured-card container):
 *   56rem (~900px) — poster: side-by-side grid collapses to stack
 *   48rem (~768px) — offset/slanted: complex overlap collapses to simple stack
 */
.rwest-featured-card {
  --featured-card-media-radius: 2px;
  --featured-card-focus-ring: 2px;
  --featured-card-tagline-max: 50ch;
  --featured-card-title-max: 22ch;

  container-type: inline-size;
  container-name: featured-card;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ─── Query wrapper — no visual treatment; contains the card ─────────────── */

.rwest-featured-card-query {
  width: 100%;
}

/* ─── Stage — wraps media + meta for all variants ───────────────────────── */

.rwest-featured-card__stage {
  position: relative;
  width: 100%;
}

/* ─── Media ──────────────────────────────────────────────────────────────── */

/* WP core Query Loop emits wp-block-post-featured-image figure containing img.
   isLink wraps the img in an <a> — the <a> IS the stretched link for single-link
   cards. See [recipe:stretched-link] note in patterns.css ServiceCard section. */
.rwest-featured-card__media {
  position: relative;
  width: 100%;
  overflow: hidden; /* clips the img at aspect-ratio; do NOT set on card root */
  display: block;
}

/* Default aspect ratio — 16:9 matches wireframe .wf-work-featured-media.
   Allowed layout literal (aspect-ratio is a threshold, not a design token). */
.rwest-featured-card__media .wp-block-post-featured-image {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  display: block;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__media .wp-block-post-featured-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: var(--featured-card-media-radius);
}

/* ─── Stretched-link recipe — [recipe:stretched-link] instance for FeaturedCard ─
   The core/post-featured-image isLink wraps the entire figure in an <a>.
   For the default (below) layout, the image link IS the primary card link.
   The ::after on the title <a> is for editor display only; front-end relies on
   the featured-image <a> as the primary hit target. Below variant uses the
   meta "See project" link as a secondary CTA (pointer-events: all).
   card root: position relative (already set above).
   Focus ring on the card via :has(a:focus-visible). */

/* Title link — surface-3 (#111) overrides global elements.link brand red.
   The isLink <a> wraps the title text; we do NOT absolutize it (in-flow). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card .wp-block-post-title a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: none;
}

/* Title hover — brand-aa passes 4.5:1 on white; raw brand fails. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card .wp-block-post-title a:hover {
  color: var(--wp--preset--color--brand-aa);
}

/* Focus ring on the card boundary (part 3 of [recipe:stretched-link]).
   The title in-flow anchor's own outline would trace only the text box;
   the card boundary ring gives a clear visual focus indicator. */
.rwest-featured-card:has(a:focus-visible) {
  outline: var(--featured-card-focus-ring) solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* ─── Meta ───────────────────────────────────────────────────────────────── */

/* Meta group — flex column with token gap (set via block blockGap attribute).
   padding-top set via block style attribute. CSS only adds non-attribute overrides. */

/* Title — h2 level. font-family/weight/size from substrate (elements.h2: Prata 400).
   letter-spacing, line-height, max-width are untokenizable; allowed per spec.
   Wireframe .wf-work-featured-title: font-size clamp(1.75rem,3.5vw,2.75rem), max-width 22ch.
   Substrate heading-md (fluid) approximates the wireframe; CSS adds measure + rhythm. */
.rwest-featured-card__title {
  max-width: var(--featured-card-title-max);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--wp--preset--color--surface-3);
}

/* Tagline — readability cap + color + rhythm.
   color: gray-aa (#555555) = 7.46:1 on white — AA pass.
   line-height: 1.45 — untokenizable; allowed literal.
   max-width: --featured-card-tagline-max (50ch). */
.rwest-featured-card__tagline {
  max-width: var(--featured-card-tagline-max);
  line-height: 1.45;
  color: var(--wp--preset--color--gray-aa);
}

/* Capability chip list — reuses pair-card cap styling conventions.
   core/post-terms emits a <div class="wp-block-post-terms rwest-featured-card__caps">.
   Terms are rendered as links by WP. The list flex + chip styles mirror pair-card__caps. */
.rwest-featured-card__caps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wp--preset--spacing--2);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__caps a {
  display: inline-block;
  font-family: var(--wp--preset--font-family--roboto);

  /* 0.65rem: no smaller token exists; allowed per pair-card spec. */
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--wp--preset--color--gray-aa);
  border: 1px solid var(--wp--preset--color--border-subtle);
  border-radius: 999px;

  /* 0.625rem vertical padding: wireframe-exact, no token maps to 10px; allowed literal.
     padding-inline: spacing-4 (1rem) — token. */
  padding: 0.625rem var(--wp--preset--spacing--4);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__caps a:hover {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
  border-color: var(--wp--preset--color--surface-3);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__caps a:focus-visible {
  outline: var(--featured-card-focus-ring) solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

/* See-project CTA — same treatment as pair-card__see-project.
   font-weight: 400 + letter-spacing: 0.04em — untokenizable; allowed literals. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__see-project a {
  display: inline-block;
  text-decoration: none;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--wp--preset--color--surface-3);
  padding-top: var(--wp--preset--spacing--2);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__see-project a:hover {
  color: var(--wp--preset--color--brand-aa);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__see-project a:focus-visible {
  outline: var(--featured-card-focus-ring) solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
}

.rwest-featured-card__see-project-arrow {
  display: inline-block;
  margin-left: 0.3em;
  transition: transform 0.18s ease;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card__see-project a:hover .rwest-featured-card__see-project-arrow,
.rwest-featured-card__see-project a:focus-visible .rwest-featured-card__see-project-arrow {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .rwest-featured-card__see-project-arrow {
    transition: none;
  }
}

/* ==========================================================================
   FeaturedCard — variant: overlay (is-style-featured-overlay)
   ========================================================================== */

/*
 * Variant mapping: wireframe .wf-work-featured-card--overlay
 * Copy lives inside the media area, bottom-left, with a dark gradient.
 * Media: 16:7 aspect ratio. Meta: absolute, bottom-left, max-width 60%, white text.
 * Gradient: top-right diagonal, rgba(0,0,0,0.78) → transparent at 60%.
 * Wireframe color literals (gradient stop, max-width 60%) are layout/contrast-mechanism
 * values — allowed per css-conventions.md §Allowed literals.
 *
 * Container threshold: ≤ 48rem (~768px) — overlay collapses to below-meta stack.
 * Triggering behavior: meta absolute positioning collapses to in-flow below the image;
 * gradient hidden; text reverts to dark on light bg.
 */

/* Stage: establishes positioning context for the absolute meta. */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__stage {
  position: relative;
}

/* Media: taller crop for the overlay variant — 16:7. */
.rwest-featured-card.is-style-featured-overlay
  .rwest-featured-card__media
  .wp-block-post-featured-image {
  aspect-ratio: 16 / 7;
}

/* Gradient overlay — decorative contrast mechanism via ::after on the media wrapper.
   pointer-events: none keeps the image link clickable.
   rgba values are contrast-mechanism literals (WCAG math: dark bottom-left corner
   at 0.78 opacity over any image ensures ≥4.5:1 for white text). */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top right, rgb(0 0 0 / 78%) 0%, rgb(0 0 0 / 0%) 60%);
  pointer-events: none;
  z-index: 0;
  border-radius: var(--featured-card-media-radius);
}

/* Meta: absolute bottom-left over the media. max-width: 60% — layout proportion;
   allowed literal (not a design token). padding: spacing-8/spacing-10 — tokens. */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__meta {
  position: absolute;
  inset: auto 0 0;
  padding: var(--wp--preset--spacing--8) var(--wp--preset--spacing--10);
  max-width: 60%;
  z-index: 1;
}

/* Text flips to white over the dark gradient — all text descendants. */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__title,
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__tagline,
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__see-project a {
  color: var(--wp--preset--color--pure-white);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__title a {
  color: var(--wp--preset--color--pure-white);
}

/* Cap links: translucent white border + white text over dark gradient.
   rgba border-color is an allowed contrast-mechanism literal. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__caps a {
  color: rgb(255 255 255 / 85%);
  border-color: rgb(255 255 255 / 45%);
}

/* Cap hover on overlay: invert to white bg + dark text (21:1). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__caps a:hover {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--surface-3);
  border-color: var(--wp--preset--color--pure-white);
}

/* Focus ring on overlay: pure-white ring (visible on dark gradient). */
.rwest-featured-card.is-style-featured-overlay:has(a:focus-visible) {
  outline-color: var(--wp--preset--color--pure-white);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__caps a:focus-visible,
.rwest-featured-card.is-style-featured-overlay .rwest-featured-card__see-project a:focus-visible {
  outline-color: var(--wp--preset--color--pure-white);
}

/* Collapse to below-stack at ≤ 48rem container width (~768px).
   Layout shift: meta absolute → in-flow; gradient hidden; text → dark on light bg. */
@container featured-card (max-width: 48rem) {
  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__meta {
    position: relative;
    inset: auto;
    max-width: none;
    padding: var(--wp--preset--spacing--6) 0;
  }

  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__title,
  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__title a,
  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__tagline,
  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__see-project a {
    color: var(--wp--preset--color--surface-3);
  }

  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__media::after {
    display: none;
  }

  /* stylelint-disable-next-line no-descending-specificity */
  .rwest-featured-card.is-style-featured-overlay .rwest-featured-card__caps a {
    color: var(--wp--preset--color--gray-aa);
    border-color: var(--wp--preset--color--border-subtle);
  }
}

/* ==========================================================================
   FeaturedCard — variant: offset (is-style-featured-offset)
   ========================================================================== */

/*
 * Variant mapping: wireframe .wf-work-featured-card--offset
 * Media: 16:8. Meta: a white card that overlaps the media corner with a
 * parallelogram clip-path (R\West backslash angle). Default anchor: bottom-right.
 *
 * CSS clip-path percentages are layout/geometry literals — allowed.
 * margin-top negative overlap (-5rem) is a layout literal — no token maps to -5rem
 * (spacing-20 = 5rem; negating a spacing token is allowed per css-conventions.md
 * §Breakpoints pattern for allowed literals, same class as focus-ring geometry).
 * width: min(58%, 32rem) — 58% + 32rem are layout proportions; allowed.
 *
 * Container threshold: ≤ 48rem (~768px) — clip-path + overlap collapse to stack.
 * Triggering behavior: meta returns to in-flow below media, clip-path removed,
 * negative margin zeroed.
 */

/* Media: wider crop for the offset overlap. */
.rwest-featured-card.is-style-featured-offset
  .rwest-featured-card__media
  .wp-block-post-featured-image {
  aspect-ratio: 16 / 8;
}

/* Meta panel: positioned relative (in-flow sibling, pulled up with negative margin).
   background: pure-white — token. The parallelogram is the R\West backslash shape.
   clip-path percentages are geometry literals. */
.rwest-featured-card.is-style-featured-offset .rwest-featured-card__meta {
  position: relative;
  background-color: var(--wp--preset--color--pure-white);
  padding: var(--wp--preset--spacing--8) var(--wp--preset--spacing--12);
  width: min(58%, 32rem);

  /* Negative overlap — pulls the meta card up over the media.
     -5rem: no token maps to this; allowed literal (layout geometry). */
  margin-top: -5rem;

  /* Parallelogram: top-left→bottom-right lean (R\West backslash direction).
     Seam: top at x=0/92%, bottom at x=8%/100%.
     clip-path clips box-shadow, so shadow is omitted (same as wireframe). */
  clip-path: polygon(0 0, 92% 0, 100% 100%, 8% 100%);

  /* Right-anchor default: push to right with a small gap so the card
     isn't flush against the media edge. */
  margin-left: auto;
  margin-right: var(--wp--preset--spacing--6);
  z-index: 1;
}

/* Title: same dark treatment as below variant. */
.rwest-featured-card.is-style-featured-offset .rwest-featured-card__title {
  color: var(--wp--preset--color--surface-3);
}

/* Collapse to below-stack at ≤ 48rem.
   Layout shift: clip-path removed, margin zeroed, full-width, standard padding. */
@container featured-card (max-width: 48rem) {
  .rwest-featured-card.is-style-featured-offset .rwest-featured-card__meta {
    width: 100%;
    margin: 0;
    padding: var(--wp--preset--spacing--6) 0;
    clip-path: none;
  }
}

/* ==========================================================================
   FeaturedCard — variant: slanted (is-style-featured-slanted)
   ========================================================================== */

/*
 * Variant mapping: wireframe .wf-work-featured-card--slanted
 * The card's own background is the meta surface (white/cream). The media is
 * clipped with a backslash wedge so the card bg shows through on the left.
 * Meta sits in that cut-out at width ~28% (left column); media fills ~72% (right).
 *
 * clip-path percentages are layout/geometry literals — allowed.
 * min-height: 26rem — layout literal (ensures adequate card height for the
 * slanted composition); no token equivalent.
 * width: 28% — layout proportion; allowed literal.
 *
 * Container threshold: ≤ 48rem — absolute-positioned media + clip-path collapse
 * to full-width in-flow image above the meta.
 */

/* Card root: establishes the bg surface (pure-white = cream-like; white token).
   The meta sits on this bg; media is clipped and overlaid. */
.rwest-featured-card.is-style-featured-slanted {
  background-color: var(--wp--preset--color--pure-white);
}

/* Stage: relative + min-height for the absolute-positioned media. */
.rwest-featured-card.is-style-featured-slanted .rwest-featured-card__stage {
  position: relative;

  /* 26rem min-height: layout literal — ensures composition depth without
     squishing the copy column at normal viewport widths. */
  min-height: 26rem;
}

/* Media: absolute fill; backslash wedge clip removes the left portion so
   the card bg (white meta surface) shows through.
   Seam: top at x=28%, bottom at x=33% — ~5% horizontal travel for a
   gentle slope matching the R\West backslash angle. */
.rwest-featured-card.is-style-featured-slanted .rwest-featured-card__media {
  position: absolute;
  inset: 0;
  overflow: hidden;
  clip-path: polygon(28% 0, 100% 0, 100% 100%, 33% 100%);
}

.rwest-featured-card.is-style-featured-slanted
  .rwest-featured-card__media
  .wp-block-post-featured-image {
  aspect-ratio: auto;
  height: 100%;
}

.rwest-featured-card.is-style-featured-slanted
  .rwest-featured-card__media
  .wp-block-post-featured-image
  img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0; /* full-bleed within the stage; no radius on clipped media */
}

/* Meta: in-flow left column at width 28% (matches the clip seam top x=28%).
   position: relative + z-index keeps meta above the absolute media.
   flex column with vertical centering for balanced composition.
   Padding mirrors wireframe slanted meta padding. */
.rwest-featured-card.is-style-featured-slanted .rwest-featured-card__meta {
  position: relative;
  z-index: 1;

  /* 28%: layout proportion, matches the clip-path seam — allowed literal. */
  width: 28%;
  min-height: inherit;
  padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--4)
    var(--wp--preset--spacing--12) var(--wp--preset--spacing--8);
  display: flex;
  flex-direction: column;
  justify-content: center;

  /* Override the block's default padding-top attribute on the meta group.
     On slanted, vertical centering handles the internal rhythm. */
  padding-top: var(--wp--preset--spacing--10) !important;
}

/* Title: constrained size for the narrow column.
   clamp(1.4rem, 2cqi, 1.85rem): fluid within container units.
   2cqi: 2% of the card container inline-size — scales the title with the card width.
   1.4rem min / 1.85rem max match wireframe slanted title size range. */
.rwest-featured-card.is-style-featured-slanted .rwest-featured-card__title {
  font-size: clamp(1.4rem, 2cqi, 1.85rem);
  max-width: none; /* narrow column fills available width */
}

/* Collapse to below-stack at ≤ 48rem.
   Layout shift: absolute media → in-flow; clip-path removed; meta width = 100%. */
@container featured-card (max-width: 48rem) {
  .rwest-featured-card.is-style-featured-slanted .rwest-featured-card__stage {
    min-height: 0;
    display: block;
  }

  .rwest-featured-card.is-style-featured-slanted .rwest-featured-card__media {
    position: relative;
    inset: auto;
    clip-path: none;
  }

  .rwest-featured-card.is-style-featured-slanted
    .rwest-featured-card__media
    .wp-block-post-featured-image {
    aspect-ratio: 16 / 9;
    height: auto;
  }

  .rwest-featured-card.is-style-featured-slanted
    .rwest-featured-card__media
    .wp-block-post-featured-image
    img {
    border-radius: var(--featured-card-media-radius);
  }

  .rwest-featured-card.is-style-featured-slanted .rwest-featured-card__meta {
    width: 100%;
    padding: var(--wp--preset--spacing--6) 0;
    min-height: 0;
  }

  .rwest-featured-card.is-style-featured-slanted .rwest-featured-card__title {
    font-size: clamp(1.85rem, 4cqi, 2.75rem);
  }
}

/* ==========================================================================
   FeaturedCard — variant: poster (is-style-featured-poster)
   ========================================================================== */

/*
 * Variant mapping: wireframe .wf-work-featured-card--poster (.wf-work-featured-card--meta-black)
 * Editorial poster: copy-left (3fr) + media-right (4fr), side by side.
 * Copy gets visual primacy via oversized Prata display type; media is supporting.
 * Symmetric inner padding so media isn't flush against card edges.
 * Dark background: surface-3 (#111) + white text — wireframe probed 2026-06-13:
 *   poster bg = rgb(17,17,17); title/tagline color = rgb(255,255,255).
 *
 * grid-template-columns: 3fr 4fr — layout proportion; allowed literal (no token).
 * padding: spacing-5 (1.25rem) — token.
 * font-size clamp(1.85rem, 3.5cqi, 3rem): container-unit fluid scaling.
 *   cqi replaces vw (css-conventions.md §Container-queries-first).
 *   3.5cqi ≈ wireframe 3.5vw at normal content-column widths.
 *
 * Container threshold: ≤ 56rem (~900px) — grid collapses to 1-column stack,
 * media-before-meta source order (media moves below via order).
 * Triggering behavior: grid → 1fr; meta order resets; media renders below the copy.
 */

/* Card root: dark background + white text for the poster editorial treatment. */
.rwest-featured-card.is-style-featured-poster {
  background-color: var(--wp--preset--color--surface-3);
}

/* Title and tagline: white on the dark surface.
   17.73:1 on surface-3 — AAA pass. */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__title,
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__tagline {
  color: var(--wp--preset--color--white);
}

/* <a> overrides: wp-block-post-title a and see-project a override global elements.link brand red.
   no-descending-specificity: these scoped a overrides intentionally follow broader a rules above. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__title a {
  color: var(--wp--preset--color--white);
}

/* The see-project affordance is a bare <p> (no anchor — the whole card is a
   stretched post-title link), so the recolor must target the element itself.
   The previous `a`-scoped selector matched nothing and the text stayed black on
   the dark poster bg (1.11:1 — pa11y BLOCKING, caught 2026-06-13). */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__see-project {
  color: var(--wp--preset--color--white);
}

/* Cap chips: white border + white text on the dark surface.
   stylelint no-descending-specificity: scoped override; intentional. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__caps a {
  color: var(--wp--preset--color--white);
  border-color: var(--wp--preset--color--white);
}

/* Stage: grid layout with inner padding for breathing room. */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__stage {
  display: grid;

  /* 3fr meta left, 4fr media right — allowed layout proportion. */
  grid-template-columns: 3fr 4fr;
  gap: var(--wp--preset--spacing--8);
  align-items: center;
  padding: var(--wp--preset--spacing--5);
}

/* Media: 16:10 crop; fill grid cell height; source-order second → CSS pushes to column 2. */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__media {
  order: 2;
  align-self: stretch;
}

.rwest-featured-card.is-style-featured-poster
  .rwest-featured-card__media
  .wp-block-post-featured-image {
  aspect-ratio: 16 / 10;
  height: 100%;
}

.rwest-featured-card.is-style-featured-poster
  .rwest-featured-card__media
  .wp-block-post-featured-image
  img {
  border-radius: var(--featured-card-media-radius);
}

/* Meta: source-order first → naturally in column 1 (left). */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__meta {
  order: 1;
  padding: var(--wp--preset--spacing--6) var(--wp--preset--spacing--5);
}

/* Poster title: oversized Prata for editorial primacy.
   clamp with cqi: fluid within the card container, not the viewport.
   letter-spacing: -0.012em — untokenizable; allowed literal. */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__title {
  font-size: clamp(1.85rem, 3.5cqi, 3rem);
  line-height: 1.2;
  letter-spacing: -0.012em;

  /* Allow the title to fill the wider poster column. */
  max-width: 18ch;
}

/* Tagline: slightly larger for the poster's editorial weight. */
.rwest-featured-card.is-style-featured-poster .rwest-featured-card__tagline {
  max-width: 38ch;
}

/* Collapse to 1-column stack at ≤ 56rem container width (~900px).
   Layout shift: grid 3fr/4fr → 1fr; media moves below copy (order).
   Triggering behavior: at ≤ ~900px card width the side-by-side layout
   crowds both copy and image to unreadable widths; 1-column stacks them. */
@container featured-card (max-width: 56rem) {
  .rwest-featured-card.is-style-featured-poster .rwest-featured-card__stage {
    grid-template-columns: 1fr;
    gap: var(--wp--preset--spacing--6);
  }

  .rwest-featured-card.is-style-featured-poster .rwest-featured-card__media {
    order: 0; /* media first (above copy) in stacked mobile layout */
  }

  .rwest-featured-card.is-style-featured-poster .rwest-featured-card__title {
    font-size: clamp(2rem, 6cqi, 3rem);
  }
}

/* ==========================================================================
   Work feed section — rwest-work-feed
   ========================================================================== */

/*
 * Feed section container — constrained layout section wrapping the
 * featured-card + pair-card-query sequence.
 * The inner group carries a token blockGap; CSS here only adds feed-level
 * typography/smoothing and removes any inherited WP flow margins on the
 * pattern group children.
 *
 * [recipe:flow-gap-reset] — see ServiceCard canonical instance in patterns.css.
 * The inner wp:group with blockGap uses WP's flow > * + * margin injection.
 * Neutralize on the feed wrapper to prevent double-spacing between sections.
 */
.rwest-work-feed {
  container-type: inline-size;
  container-name: work-feed;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Neutralize WP flow > * + * blockGap injection on the work-feed's WRAPPER group
   children (filter-bar ↔ query) — its blockGap is owned by the wp:group attr;
   CSS margin must not double it. The `>` to the wrapper is load-bearing: a
   descendant combinator also matched the pair card (which IS a .wp-block-group),
   zeroing its copy gaps. [recipe:flow-gap-reset] instance. */
.rwest-work-feed > .wp-block-group > * {
  margin-block: 0;
}

/* ============ sprint 2026-06-12 :: wave2-e ============ */

/**
 * Wave 2-E — Work Masonry Grid (rwest/work-masonry)
 * Sprint: 2026-06-12  Verdict: V9 agent-proposed
 *
 * Keyed to .rwest-work-masonry (pattern root class per naming.md).
 * Tokens only — no literal hex/px/spacing except:
 *   - container-query thresholds (rem, documented per css-conventions.md §Breakpoints)
 *   - border-radius: 2px (no WP preset; validated precedent in pair-card)
 *   - translateY values (decorative hover motion)
 *   - opacity/visibility (animation mechanic)
 *   - transition declarations (reduced-motion stop required + supplied)
 *   - letter-spacing/font-weight integers (untokenizable per css-conventions.md)
 *   - aspect-ratio: 4/3 (image layout threshold)
 *   - z-index integers (stacking — no preset equivalent)
 *   - inset: 0 (stretch-fill shorthand — no spacing token maps to 0)
 *
 * Container model:
 *   .rwest-work-masonry  — outer section container (inline-size)
 *   .rwest-work-card     — inner card container (inline-size)
 *   Grid responds to the section container width, not the viewport.
 *
 * nth-child size cycle (6 items, maps to wireframe sizes array [tall,wide,regular,regular,tall,wide]):
 *   :nth-child(1), :nth-child(5) → tall   (grid-column span 2, grid-row span 5)
 *   :nth-child(2), :nth-child(6) → wide   (grid-column span 4, grid-row span 4)
 *   :nth-child(3), :nth-child(4) → regular (grid-column span 2, grid-row span 2)
 *
 * Mobile workaround (V9, logged decision):
 *   The wireframe masonry is entirely undesigned at 390px (renders empty).
 *   Below ≤24rem container threshold the grid collapses to single-column stack;
 *   all nth-child span overrides are reset to span 1 / auto.
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   Component-scoped layout constants
   ========================================================================== */

/*
 * --work-masonry-gap: 1.25rem column/row gap — wireframe-exact (1.25rem).
 *   No spacing token maps to 1.25rem; spacing-5 = 1.25rem IS available — using token.
 *   Allowed literal: grid-auto-rows base unit (120px) has no spacing token equivalent.
 * --work-masonry-row-unit: 120px — the grid-auto-rows unit from the wireframe.
 *   No spacing token maps to 120px; documented as allowed literal (layout threshold).
 * --work-masonry-radius: 2px — border-radius on cards. Validated precedent: pair-card.
 * --work-masonry-focus-ring: 2px — focus ring width. No WP preset border-width family.
 *
 * Container breakpoints:
 *   56rem (~896px section column) — 6→4 cols (layout shift: tall/wide recalc)
 *   24rem (~384px section column) — mobile workaround: 4→1 col, all items reset to regular
 *
 * NOTE: CSS custom properties cannot be used inside @container query conditions
 * (not valid syntax per CSS spec); thresholds are documented here and used bare in rules.
 */
.rwest-work-masonry {
  --work-masonry-gap: var(--wp--preset--spacing--5); /* 1.25rem */
  --work-masonry-radius: 2px;
  --work-masonry-focus-ring: 2px;

  container-type: inline-size;
  container-name: work-masonry;
}

/* ==========================================================================
   Grid — post-template list
   ========================================================================== */

/*
 * [recipe:flow-gap-reset] — WP flow-layout blockGap emits `> * + * { margin-block-start }`
 * on the post-template list, corrupting grid row alignment (same issue as ServiceCard,
 * OfficeCard, TeamCard — canonical instance in ServiceCard). Grid gap owns spacing; reset here.
 *
 * grid-auto-rows: 120px — wireframe-exact unit. Each size variant spans N rows:
 *   regular = span 3  → 3 × 120px + 2 gaps = 360px + 2×1.25rem visual height
 *   tall    = span 5  → 5 × 120px + 4 gaps = 600px + 4×1.25rem visual height
 *   wide    = span 4  → 4 × 120px + 3 gaps = 480px + 3×1.25rem visual height
 */
.rwest-work-masonry .wp-block-post-template {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: 120px; /* layout threshold — no spacing token maps to 120px */
  gap: var(--work-masonry-gap);
}

/* [recipe:flow-gap-reset] — neutralize WP injected > * + * margins on li items */
.rwest-work-masonry .wp-block-post-template > * {
  margin-block: 0;
}

/* ==========================================================================
   nth-child size cycle
   Wireframe sizes array: [tall, wide, regular, regular, tall, wide]
   ========================================================================== */

/* positions 1, 5 — tall */
.rwest-work-masonry .wp-block-post-template > li:nth-child(1),
.rwest-work-masonry .wp-block-post-template > li:nth-child(5) {
  grid-column: span 2;
  grid-row: span 5;
}

/* positions 2, 6 — wide */
.rwest-work-masonry .wp-block-post-template > li:nth-child(2),
.rwest-work-masonry .wp-block-post-template > li:nth-child(6) {
  grid-column: span 4;
  grid-row: span 4;
}

/* positions 3, 4 — regular (wireframe: row span 2; span 3 left col 1 short) */
.rwest-work-masonry .wp-block-post-template > li:nth-child(3),
.rwest-work-masonry .wp-block-post-template > li:nth-child(4) {
  grid-column: span 2;
  grid-row: span 2;
}

/* ==========================================================================
   Card root — .rwest-work-card
   ========================================================================== */

/*
 * [recipe:stretched-link] — per css-conventions + ServiceCard canonical instance:
 *   1. card root: position:relative (::after positioning context)
 *   2. a::after { content:""; position:absolute; inset:0; z-index:1 } — pseudo stretches
 *   3. focus ring on card boundary via :has(a:focus-visible)
 *   4. single-link card: blanket pointer-events suppression on non-title children
 *
 * overflow:hidden — required to clip the image and story layer to the card boundary.
 * WARNING: do NOT add outline on the card element itself — it will be clipped.
 * The focus ring is painted via :has(a:focus-visible) outline, which sits OUTSIDE
 * overflow:hidden because outline is not clipped by overflow (CSS spec — outlines are
 * painted outside the border-box but are NOT subject to overflow clipping in any
 * current browser). Verified pattern: ServiceCard, OfficeCard.
 *
 * container-type: inline-size — card responds to its grid cell, not the viewport.
 */
.rwest-work-card {
  --work-masonry-focus-ring: 2px; /* re-declared inside card scope for :has() rule */

  position: relative;
  overflow: hidden;
  border-radius: var(--work-masonry-radius);
  display: block;

  /* Fill the masonry grid cell (the li) — all children are absolute, so
     without this the card collapses to 0 (caught in sprint QA, 2026-06-12). */
  height: 100%;
  container-type: inline-size;
  container-name: work-card;

  /* card-level hover lift — decorative; stopped by reduced-motion */
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);

  /* prevent faux-bold on macOS */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Lift on hover/focus */
.rwest-work-card:has(a:hover),
.rwest-work-card:has(a:focus-visible) {
  transform: translateY(-2px);
}

/* ==========================================================================
   Featured image — full-bleed background fill
   ========================================================================== */

/*
 * The card is position:relative + overflow:hidden. The image figure fills the card
 * via position:absolute inset:0 — it is NOT a container (no container-type) so the
 * stretched ::after pseudo on the link stays anchored to the card root.
 * object-fit:cover + height:100% ensure image fills all size variants without distortion.
 */
.rwest-work-card .rwest-work-card__media {
  position: absolute;
  inset: 0;
  margin: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-card .rwest-work-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ==========================================================================
   Persistent meta overlay — always visible over the image
   ========================================================================== */

/*
 * Gradient contrast mechanism: bottom-up dark overlay ensures white text meets AA
 * (WCAG 1.4.3) over any author image. Floor math: rgba(0,0,0,0.7) over worst-case
 * white image = composited RGB(77,77,77), L=0.0799; white text (#fff) = 11.97:1 — AA pass.
 * Gradient starts at 0% (full opacity) and fades to transparent at 70%.
 * Same WCAG approach as TeamCard (validated gradient contrast, 2026-06-10).
 *
 * z-index:2 — above the image (z:0 default), below the story overlay (z:3).
 * transition: opacity — fades out on hover/focus-within to reveal the story layer.
 */
.rwest-work-card .rwest-work-card__meta {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;

  /* Spacing comes ONLY from the children's margin-bottom (title 0.25rem), matching
     the wireframe. Zero WP's injected flex blockGap so it doesn't stack on top of
     the margins. Ancestor-scoped to beat the generated .wp-container-* gap rule. */
  gap: 0;
  padding: var(--wp--preset--spacing--5); /* 1.25rem — wireframe 1.25rem padding */
  background: linear-gradient(to top, rgb(0 0 0 / 70%) 0%, rgb(0 0 0 / 25%) 40%, transparent 70%);
  transition: opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

/* Hide meta when story is revealed */
.rwest-work-card:hover .rwest-work-card__meta,
.rwest-work-card:focus-within .rwest-work-card__meta {
  opacity: 0;
}

/* Descriptor tag — sits BELOW the title (wireframe .wf-work-card-tag). No r\
   prefix: the wireframe work cards omit it (the glyph lives only on section
   eyebrows), so it was removed here for parity. */
.rwest-work-card__kicker {
  font-weight: 500;
  font-size: var(--wp--preset--font-size--body-xs); /* 0.75rem; wireframe 0.68rem — ~1px, no smaller token */
  text-transform: uppercase;
  letter-spacing: 0.15em; /* untokenizable — allowed CSS literal */
  color: rgb(255 255 255 / 75%); /* matches wireframe tag rgba(255,255,255,0.75) */
  margin: 0;
}

/* post-terms renders the term as a taxonomy <a> (brand-red link color); the
   wireframe tag is plain white text — inherit the kicker color, drop underline. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-card__kicker a {
  color: inherit;
  text-decoration: none;
}

/* Title — Prata, white, sits above the descriptor tag (wireframe .wf-work-card-title) */
.rwest-work-card__title {
  color: var(--wp--preset--color--pure-white);
  line-height: 1.25; /* untokenizable — allowed CSS literal */
  font-weight: 400; /* Prata is 400-only; prevent faux-bold */
  letter-spacing: normal; /* wireframe card title has no tracking; override the global heading -0.01em */
  margin-bottom: var(--wp--preset--spacing--1); /* 0.25rem — wireframe title→tag gap */

  /* font-size: body-lg comes from the block attribute (has-body-lg-font-size);
     wireframe is 1.16rem — body-lg (1.1rem) is the nearest token, ~1px under. */
}

/* ==========================================================================
   Story overlay — hover/focus-within reveal layer
   ========================================================================== */

/*
 * Reveal mechanic: opacity 0 + visibility:hidden at rest; opacity 1 + visibility:visible
 * on :hover and :focus-within. Using visibility (not display:none) so the CSS transition
 * fires without a reflow; visibility is toggled in sync with the opacity transition end.
 *
 * transform: translateY(12px) at rest → translateY(0) on reveal — wireframe-exact slide-up.
 * The transition duration (0.5s) and easing (cubic-bezier(0.22,1,0.36,1)) match wireframe.
 *
 * z-index:3 — above both the image (z:0) and the persistent meta (z:2).
 * background: rgba(0,0,0,0.85) — dark solid overlay; matches wireframe.
 *   Contrast: #fff text over rgba(0,0,0,0.85) composited on worst-case white image =
 *   RGB(38,38,38), L=0.0173; white #fff = 18.08:1 — AA pass.
 *   Allowed literal: alpha value is a contrast-mechanism value (WCAG math documented here).
 *
 * pointer-events:none at rest — prevents invisible overlay intercepting mouse events.
 * pointer-events restored on reveal.
 *
 * Touch devices (@media(hover:none)): story panel hidden unconditionally (display:none).
 * The always-visible meta serves touch users; story is hover-only UX.
 *
 * Keyboard (:focus-within): the stretched link <a> receives focus; :focus-within on the
 * card triggers the reveal — keyboard users see the story panel while the card is focused.
 */
.rwest-work-card .rwest-work-card__story {
  position: absolute;
  inset: 0;
  margin-block: 0; /* [recipe:flow-gap-reset] WP flow blockGap injects a 24px top margin → gap above the hover overlay */
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;

  /* Spacing comes ONLY from the children's margin-bottom (label 0.75rem, text 1rem),
     matching the wireframe. Zero WP's injected flex blockGap so it doesn't stack. */
  gap: 0;
  padding: var(--wp--preset--spacing--6); /* 1.5rem — wireframe 1.5rem */
  background: rgb(0 0 0 / 85%); /* contrast-mechanism value — WCAG math above */
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px); /* decorative slide — allowed literal (no spacing token) */
  transition:
    opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.5s; /* delay matches opacity so it hides after fade-out */

  pointer-events: none;
}

/* Reveal on hover and keyboard focus-within */
.rwest-work-card:hover .rwest-work-card__story,
.rwest-work-card:focus-within .rwest-work-card__story {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition:
    opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s; /* no delay on reveal — immediate visibility */

  pointer-events: auto;
}

/* Story label — "The story" eyebrow */
.rwest-work-card__story-label {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em; /* untokenizable — allowed CSS literal */
  color: var(--wp--preset--color--brand); /* brand red — visible on dark overlay */
  margin-bottom: var(--wp--preset--spacing--3); /* 0.75rem */
}

/* Story text — excerpt, Prata serif */
.rwest-work-card__story-text {
  color: var(--wp--preset--color--pure-white);
  line-height: 1.3; /* untokenizable — allowed CSS literal */
  font-weight: 400; /* Prata 400-only */
  margin-bottom: var(--wp--preset--spacing--4); /* 1rem */
}

/* Story CTA — "Read case study →" */
.rwest-work-card__story-cta {
  color: var(--wp--preset--color--pure-white);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em; /* untokenizable — allowed CSS literal */
}

/* Arrow — inline-block for transform; margin restores the gap trimmed by inline-block boundary */
.rwest-work-card__story-arrow {
  display: inline-block;
  margin-left: 0.3em;
  transition: transform 0.18s ease;
}

.rwest-work-card:hover .rwest-work-card__story-arrow,
.rwest-work-card:focus-within .rwest-work-card__story-arrow {
  transform: translateX(4px);
}

/* ==========================================================================
   [recipe:stretched-link] — stretched post-title anchor
   ========================================================================== */

/*
 * Full recipe per css-conventions + ServiceCard canonical instance:
 * 1. card root has position:relative (above).
 * 2. The <a> stays IN-FLOW (absolutizing the anchor itself pulls it out of layout).
 *    ::after pseudo stretches over the full card — never the anchor itself.
 * 3. Focus ring on the CARD boundary via :has(a:focus-visible) — the in-flow anchor's
 *    own outline would trace only the text box. Card has overflow:hidden — outline
 *    is NOT subject to overflow clipping (CSS spec), so the ring renders correctly.
 * 4. Single-link card: blanket pointer-events suppression on non-title children.
 *    If a second interactive element is ever added, switch to z-index:2 form instead.
 *
 * .rwest-work-card__link-title.screen-reader-text: visually hidden duplicate heading.
 * GOTCHA (fixed 2026-06-18): core .screen-reader-text is position:absolute + clip 1×1.
 * An abspos ::after{inset:0} resolves against its nearest POSITIONED ancestor — that
 * 1×1 box — NOT the card, so the stretched link collapsed to ~1px and every home work
 * card was effectively unclickable (QA hit-test: card centers hit the <article>, no
 * link). Fix: re-base the link-title itself as a full-card positioned layer (incl.
 * :focus, to beat core's skip-link un-hide) so the ::after stretches over the whole
 * card. The title text stays in the a11y tree as the link's accessible name
 * (font-size:0 + transparent — NOT display:none/visibility:hidden).
 */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-card .rwest-work-card__link-title,
.rwest-work-card .rwest-work-card__link-title:focus {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: transparent;
  clip: auto;
  clip-path: none;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-work-card .rwest-work-card__link-title a {
  font-size: 0; /* hide the duplicate title glyphs; accessible name preserved for AT */
  color: transparent;
  text-decoration: none;
}

.rwest-work-card .rwest-work-card__link-title a::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4; /* above story overlay (z:3) so the link receives pointer events */
}

/* Focus ring on card boundary — outline NOT clipped by card overflow:hidden */
.rwest-work-card:has(a:focus-visible) {
  outline: var(--work-masonry-focus-ring) solid var(--wp--preset--color--pure-white);
  outline-offset: 3px;
}

/* Suppress pointer-events on non-link children — single-link card blanket rule.
   WARNING: add a second interactive element → remove this rule, use z-index:2 instead. */
.rwest-work-card > *:not(.rwest-work-card__link-title) {
  pointer-events: none;
}

/* ==========================================================================
   Container-query responsiveness
   ========================================================================== */

/*
 * ≤ 56rem (~896px section column): 6→4 columns.
 * Layout shift: 6-col masonry recalculates to 4-col.
 * Triggering behavior: section column narrows past 896px; grid collapses from
 * 6-col to 4-col. Size-variant span recalculations preserve relative proportions.
 *
 * Recalculated spans at 4-col (mirrors wireframe @media(max-width:1024px)):
 *   tall    → col span 2, row span 4
 *   wide    → col span 4, row span 3
 *   regular → col span 2, row span 3
 */

/* Tablet (37.5rem–56rem): a clean uniform 2-up grid (2x2). The desktop masonry's
   tall/wide spans only tile gap-free at the 6-col width; in a 4-col grid the
   "wide" span-4 card can't sit beside the "tall" span-2 card, so it wrapped and
   left a blank column (QA #75 — broken on a real tablet). Below the 6-col band we
   drop the masonry: 2 uniform 4:3 columns here, 1 column on phones (below).
   (Brandon's call, 2026-06-22.) */
@container work-masonry (max-width: 56rem) {
  .rwest-work-masonry .wp-block-post-template {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: auto;
  }

  /* Reset every size-variant span to a single uniform cell */
  .rwest-work-masonry .wp-block-post-template > li:nth-child(1),
  .rwest-work-masonry .wp-block-post-template > li:nth-child(2),
  .rwest-work-masonry .wp-block-post-template > li:nth-child(3),
  .rwest-work-masonry .wp-block-post-template > li:nth-child(4),
  .rwest-work-masonry .wp-block-post-template > li:nth-child(5),
  .rwest-work-masonry .wp-block-post-template > li:nth-child(6) {
    grid-column: span 1;
    grid-row: auto;
  }

  /* Uniform 4:3 cards. The aspect-ratio goes on the CARD, not the image, and the
     media keeps its base position:absolute;inset:0 at EVERY breakpoint — so the
     image always fills the cell. This is resize-robust: dragging from this band up
     into the desktop masonry can't leave a stale figure that fails to re-fill (the
     gray gap QA caught on manual drag-resize). On desktop the base height:100% wins
     over any lingering aspect-ratio (height beats aspect-ratio), and the media
     covers regardless; here, height:auto lets the 4:3 drive the card height.
     No inline-aspect-ratio fight either — an inset:0 figure is sized by its insets,
     not its aspect-ratio. */
  .rwest-work-card {
    height: auto;
    aspect-ratio: 4 / 3;
    min-height: 0;
  }
}

/*
 * ≤ 37.5rem (~600px section column) — mobile single-column stack.
 * The masonry tiling (tall/wide/regular spans) needs real width to read; below
 * ~600px it tiles brokenly — a half-width "tall" card with a blank column beside
 * it, then a full "wide", then regulars (QA #75 — seen on a real Pixel/Android by
 * Dan; the threshold was originally 24rem/384px, but real phones report a wider
 * CSS viewport than headless emulation, so their container landed in the 4-col
 * band and never hit the stack). Resolution: single-column, all size-variant
 * nth-child spans reset to span 1 / auto, every card a uniform 4:3 (above).
 * Triggering behavior: section column ≤ ~600px → 1-up uniform stack.
 */
@container work-masonry (max-width: 37.5rem) {
  /* Phones: a single column. The span reset, uniform 4:3 card, and media-fill all
     come from the ≤56rem block above (which also matches at this width); only the
     column count differs here. */
  .rwest-work-masonry .wp-block-post-template {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Touch device fallback — @media(hover:none)
   ========================================================================== */

/*
 * On touch-only devices (no fine pointer / no hover capability) the story panel
 * is hidden unconditionally (display:none) — hover-based UX does not work.
 * The persistent meta (kicker + title) is always visible and provides sufficient context.
 * This mirrors the wireframe's own @media(hover:none) { .wf-work-card-story { display:none } }.
 *
 * viewport @media is correct here — this is a device-capability query, not a component
 * container query. (css-conventions.md §altitude: device-capability = viewport @media.)
 */
@media (hover: none) {
  .rwest-work-card__story {
    display: none;
  }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

/*
 * Mandatory per css-conventions.md §Motion / accessibility.md §Motion.
 * All transitions and transforms stopped. opacity/visibility toggling retained
 * (removing transitions means instant snap — still works; display:none not needed
 * because the no-motion version still reveals story on focus-within, just without animation).
 */
@media (prefers-reduced-motion: reduce) {
  .rwest-work-card {
    transition: none;
  }

  .rwest-work-card__meta {
    transition: none;
  }

  .rwest-work-card__story {
    transition: none;
    transform: none; /* no translateY snap */
  }

  /* Reveal still fires on hover/focus-within — instant, no animation */
  .rwest-work-card:hover .rwest-work-card__story,
  .rwest-work-card:focus-within .rwest-work-card__story {
    transform: none;
  }

  .rwest-work-card__story-arrow {
    transition: none;
  }

  .rwest-work-card:hover .rwest-work-card__story-arrow,
  .rwest-work-card:focus-within .rwest-work-card__story-arrow {
    transform: none;
  }
}

/* ============ sprint 2026-06-12 :: wave2-f ============ */

/**
 * Wave 2-F — article reading rhythm (single post).
 *
 * Convention reminders:
 *   - tokens only: --wp--preset--* (no literal hex/px/spacing)
 *   - container-queries-first for components; @media only for template chrome
 *   - breakpoint rem literals are allowed (centralized as CSS custom properties
 *     at component root; cannot be used inside @container conditions)
 *   - allowed literals: letter-spacing (em), font-weight (int), border-width (px),
 *     outline-width (px), outline-offset (px), aspect-ratio, line-height,
 *     ch/em readability caps, translateY/translateX decorative values,
 *     transition durations, and container breakpoint thresholds (rem)
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   Single post — reading rhythm (templates/single.html)
   ========================================================================== */

/*
 * Reading measure: 46rem column (locked Session 7 — ArticleDetail.css
 * .wf-article-body { max-width: 46rem }).
 * Set via the wrapping group's layout.contentSize attr (46rem), so post-content
 * inherits the constrained column automatically. These CSS rules supply the
 * typography treatment only — measure is handled by the template attribute.
 *
 * Session 7 locked values (ArticleDetail.css):
 *   body font-size: 1.25rem   → body-xl token
 *   body line-height: 1.75    → untokenizable literal (allowed)
 *   Prata h3: font-size 1.4rem, font-weight 400, line-height 1.3
 *             (no exact token for 1.4rem; heading-sm = 1.5rem fluid max,
 *              body-xl = 1.25rem fixed. heading-sm is the closest token.
 *              Flag: inexpressible-flag — 1.4rem is between body-xl and heading-sm.)
 *
 * Inexpressible flag: the wireframe's h3 font-size of 1.4rem falls between
 * body-xl (1.25rem) and heading-sm (1.5rem fluid max) in our token scale.
 * heading-sm at its minimum (1.1rem) fluid value is too small; body-xl (1.25rem)
 * is also too small. Best-fit: heading-sm (1.5rem max / 1.1rem min, fluid) — this
 * is a SHOULD-FIX delta, not a BLOCKING difference. The h3 fontSize block attr
 * on post-content headings is author-set; single.html does not pin it.
 * The CSS here applies line-height + color for the reading-column h3 treatment.
 *
 * font-smoothing: inherited from body (theme.json styles.css global rule).
 */

/* Article reading column wrapper */
.rwest-article-body {
  container-type: inline-size;
  container-name: article-body;
}

/* Post title (h1) — Prata 400 from substrate; reading-column measure cap */
.rwest-article-body .wp-block-post-title {
  color: var(--wp--preset--color--surface-3);
  line-height: 1.1;
  max-width: 24ch;
  padding-block: var(--wp--preset--spacing--12) var(--wp--preset--spacing--8); /* 3rem top breathing room */ /* 2rem before post-content */
}

/* Post content — body paragraph rhythm.
   font-size and line-height applied to paragraph blocks inside the reading column.
   These override the global body-md default for the reading context. */
.rwest-article-body .wp-block-post-content p {
  font-size: var(--wp--preset--font-size--body-xl); /* 1.25rem — Session 7 locked */
  line-height: 1.75; /* locked — untokenizable literal */
  color: var(--wp--preset--color--gray-dark); /* #333 — 12.6:1 on white ✓ */
  margin-block-end: var(--wp--preset--spacing--6); /* 1.5rem — paragraph gap */
}

/* h2 inside post content — Prata 400, Roboto size override for article rhythm.
   Wireframe wf-article-h2: font-size 1.75rem, font-weight 400, line-height 1.25.
   No exact token for 1.75rem; heading-md = 2rem fluid max. Closest fit: heading-md.
   SHOULD-FIX delta: 1.75rem vs 2rem — acceptable for a first-pass reading rhythm. */
.rwest-article-body .wp-block-post-content h2 {
  line-height: 1.25;
  color: var(--wp--preset--color--surface-3);
  margin-block: var(--wp--preset--spacing--12) var(--wp--preset--spacing--4); /* 3rem — breathing room above */ /* 1rem — below before paragraph */
}

/* h3 inside post content — Prata 400, reading column h3 per Session 7.
   Session 7 locked: font-family Prata, font-size 1.4rem, font-weight 400,
   line-height 1.3. Using heading-sm (1.5rem max / 1.1rem min, fluid) as
   the closest token — SHOULD-FIX delta noted in header comment. */
.rwest-article-body .wp-block-post-content h3 {
  font-family: var(--wp--preset--font-family--prata);
  font-weight: 400; /* Prata is 400-only; substrate sets this globally but explicit here */
  line-height: 1.3; /* locked Session 7 — untokenizable literal */
  color: var(--wp--preset--color--surface-3);
  margin-block: var(--wp--preset--spacing--10) var(--wp--preset--spacing--3); /* 2.25rem */ /* 0.75rem */
}

/* Post content — bottom padding */
.rwest-article-body .wp-block-post-content {
  padding-block-end: var(--wp--preset--spacing--12); /* 3rem */
}

/* ─── Narrow reading column container queries ────────────────────────────── */

/* ≤ 30rem (~480px): relax paragraph measure, increase line-height slightly */
@container article-body (max-width: 30rem) {
  .rwest-article-body .wp-block-post-title {
    max-width: none;
  }
}

/* ============ sprint 2026-06-12 :: wave1-a ============ */

/**
 * wave1-a.css — Sprint scratch CSS for Stats Band + Locations page patterns.
 *
 * Root classes covered:
 *   .rwest-anchor-nav   — Locations sub-anchor nav bar
 *   .rwest-location-section    — per-office section wrapper
 *   .rwest-location-section__* — section sub-elements
 *   .rwest-location-meta-block — address/phone/vibe metadata blocks
 *
 * Conventions (css-conventions.md):
 *   - container-type: inline-size on every pattern root.
 *   - @container queries with rem thresholds; each threshold has a comment
 *     naming the layout shift it triggers.
 *   - Tokens only: --wp--preset--*. No hardcoded hex/px/spacing.
 *   - Allowed literals: container breakpoint rem values, letter-spacing em,
 *     font-weight integers, 1-2px border widths.
 *   - prefers-reduced-motion guard on any animation/transition that involves
 *     transform or opacity.
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   Sub-anchor nav — rwest-anchor-nav (Locations page only)
   ========================================================================== */

/*
 * Sticky sub-nav bar below the hero. Sticks below the site header (which is
 * approximately 56px tall; exact value depends on the header template part).
 * Light surface, border top and bottom.
 *
 * Container context: the nav is full-bleed; its flex row content is the
 * inner inline-size we respond to.
 */
.rwest-anchor-nav {
  container-type: inline-size;
  container-name: anchor-nav;
  position: sticky;

  /* Allowed literal: top offset = main nav height estimate (56px).
   * This is a layout threshold, not a design token. */
  top: 56px;
  z-index: 90;

  /* Light surface — pure-white bg with subtle border */
  background-color: var(--wp--preset--color--pure-white);
  border-top: 1px solid var(--wp--preset--color--border-subtle);
  border-bottom: 1px solid var(--wp--preset--color--border-subtle);
}

/* Nav inner row — constrained, centered, fixed height */
.rwest-anchor-nav.wp-block-group {
  /* WP flex layout handles the row; we set height + padding here */
  min-height: 3rem;
  padding-block: var(--wp--preset--spacing--3);
  padding-inline: var(--wp--preset--spacing--8);

  /* Horizontal scroll lives on the inner .wp-block-buttons row below. */
  overflow-x: auto;
  scrollbar-width: none;
}

.rwest-anchor-nav.wp-block-group::-webkit-scrollbar {
  display: none;
}

/* The city anchors live in a nested .wp-block-buttons flex row — that is the
   container that wrapped "Amsterdam" to a 2nd line at 390 (not the nav itself).
   Force a single nowrap row; the nav above provides the horizontal scroll.
   WP's doubled-container flex layout (0,2,0) needs !important to override
   (align-review 2026-06-12). */
/* stylelint-disable-next-line declaration-no-important */
.rwest-anchor-nav .wp-block-buttons {
  flex-wrap: nowrap !important;
}

/* "Jump to" label */
.rwest-anchor-nav__label {
  text-transform: uppercase;

  /* Allowed literal: letter-spacing. */
  letter-spacing: 0.2em;
  color: var(--wp--preset--color--gray-aa);
  white-space: nowrap;
}

/* Anchor nav buttons — minimal pill style.
 * is-style-secondary-light renders as outlined buttons per styles/; here we
 * override to match the wireframe's inline link-style appearance (no border,
 * no background, just a text link with an underline on hover). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-anchor-nav .wp-block-button__link {
  background: none;
  border: none;
  padding-block: 0.25rem;
  padding-inline: var(--wp--preset--spacing--4);
  color: var(--wp--preset--color--black);

  /* These are link-style anchors, not CTAs — opt out of the global button
   * type treatment (theme.json elements.button: uppercase / body-xs / 500 /
   * 0.1em) and match the wireframe's inline subnav link (~0.9rem, 0.04em,
   * sentence case). Allowed literal: letter-spacing em. */
  text-transform: none;
  font-size: var(--wp--preset--font-size--body-sm);
  font-weight: 400;
  letter-spacing: 0.04em;

  /* Allowed literal: 2px border-bottom underline affordance. */
  border-bottom: 2px solid transparent;
  transition:
    border-color 200ms ease,
    color 200ms ease;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-anchor-nav .wp-block-button__link:hover {
  background: none;
  border-bottom-color: var(--wp--preset--color--black);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-anchor-nav .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Reduce animation for users who prefer it */
@media (prefers-reduced-motion: reduce) {
  .rwest-anchor-nav .wp-block-button__link {
    transition: none;
  }
}

/* Threshold: compact nav below 40rem container — hide "Jump to" label,
 * reduce button padding so links fit on narrow viewports. */
@container anchor-nav (max-width: 40rem) {
  .rwest-anchor-nav__label {
    display: none;
  }

  .rwest-anchor-nav .wp-block-button__link {
    padding-inline: var(--wp--preset--spacing--3);
  }
}

/* ==========================================================================
   Location section — .rwest-location-section (per-office wrapper)
   ========================================================================== */

/*
 * Container context: each office section is the container; its inner columns
 * and sub-groups respond to section inline-size.
 */
.rwest-location-section {
  container-type: inline-size;
  container-name: location-section;

  /* Scroll offset: account for sticky header + sticky sub-nav when jumping
   * via anchor links.
   * Allowed literal: scroll-margin-top = header + sub-nav height estimate. */
  scroll-margin-top: 120px;
}

/* ---- Section head (eyebrow + city h2 + tagline) ---- */

.rwest-location-section__head {
  margin-block-end: var(--wp--preset--spacing--12);
}

/* Eyebrow — "01 · OR" etc. — uppercase, tracked, muted.
 * letter-spacing 0.2em matches the canonical narrative eyebrow (was 0.18em — drift). */
.rwest-location-section__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--wp--preset--color--gray-aa);
  font-weight: 500;
}

/* Eyebrow decorative r\ glyph (wireframe wf-narrative-eyebrow::before) */
.rwest-location-section__eyebrow::before {
  content: 'r\\' / '';
  color: var(--wp--preset--color--brand);
  font-weight: 700;
  margin-right: 0.35em;
}

/* City h2 — inherits Prata 400 heading-xl from substrate.
 * Only max-width + line-height here. */
.rwest-location-section .rwest-location-section__head .wp-block-heading {
  line-height: 1.05;
  max-width: 20ch;
  margin-block: var(--wp--preset--spacing--4) var(--wp--preset--spacing--4);
}

/* Tagline — Roboto body-lg italic feel.
 * font-style italic is a design decision: the wireframe's `.wf-location-section-tagline`
 * uses a display font (Georgia) for the tagline, but our stack is Prata + Roboto.
 * Decision: render as Roboto body-lg normal (not italic) since Roboto italic is
 * not in the design's primary type palette. The tagline reads as a subtitle.
 * Flag: if designer wants italic, add a custom Roboto Italic woff2. */
.rwest-location-section__tagline {
  color: var(--wp--preset--color--surface-2);
  max-width: 40ch;
}

/* Light-section override for tagline */
.rwest-location-section.is-style-light .rwest-location-section__tagline {
  color: var(--wp--preset--color--gray-aa);
}

/* ---- Section body (2-col: description | meta aside) ---- */

.rwest-location-section__body.wp-block-columns {
  gap: var(--wp--preset--spacing--16);
  align-items: start;
  margin-block-end: var(--wp--preset--spacing--12);
}

/* Description column — body text, comfortable measure */
.rwest-location-section__desc .wp-block-paragraph {
  max-width: 64ch;
  line-height: 1.65;
}

/* Meta aside — address/phone/vibe.
 * Left border divider (visible on light sections; lighter on dark). */
.rwest-location-section__meta.wp-block-column {
  /* Allowed literal: 1px border. */
  border-left: 1px solid var(--wp--preset--color--border-subtle);
  padding-left: var(--wp--preset--spacing--10);
}

.rwest-location-section.is-style-dark .rwest-location-section__meta.wp-block-column {
  border-left-color: var(--wp--preset--color--surface-2);
}

/* Individual meta block (Address / Phone / Vibe) */
.rwest-location-meta-block {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--2);
}

.rwest-location-meta__label {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--wp--preset--color--gray-aa);
  font-weight: 500;
}

.rwest-location-meta__value {
  line-height: 1.55;
}

/* ---- Map placeholder image ---- */

.rwest-location-section__map {
  margin-block-end: var(--wp--preset--spacing--12);
  width: 100%;
}

/* Ensure the map image fills its container and clips to the aspect ratio */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-location-section__map img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---- Container-query responsive layout ---- */

/*
 * Threshold: 2-col body → stacked at 52rem container (~832px).
 * Below this the description + meta sidebar become uncomfortably narrow
 * at their 60/40 flex-basis split. Stack to single column.
 */
@container location-section (max-width: 52rem) {
  .rwest-location-section__body.wp-block-columns {
    flex-direction: column;
    gap: var(--wp--preset--spacing--10);
  }

  /* Remove left border on meta aside; add top border instead */
  .rwest-location-section__meta.wp-block-column {
    border-left: none;
    padding-left: 0;
    border-top: 1px solid var(--wp--preset--color--border-subtle);
    padding-top: var(--wp--preset--spacing--10);
    flex-basis: 100% !important;
  }

  .rwest-location-section.is-style-dark .rwest-location-section__meta.wp-block-column {
    border-top-color: var(--wp--preset--color--surface-2);
    border-left: none;
  }

  /* Description takes full width */
  .rwest-location-section__desc.wp-block-column {
    flex-basis: 100% !important;
  }
}

/*
 * Threshold: reduce section head margin at 36rem container (~576px) for mobile.
 */
@container location-section (max-width: 36rem) {
  .rwest-location-section__head {
    margin-block-end: var(--wp--preset--spacing--8);
  }

  .rwest-location-section__body.wp-block-columns {
    gap: var(--wp--preset--spacing--8);
  }
}

/* ============ sprint 2026-06-12 :: wave1-c ============ */

/**
 * wave1-c.css — Sprint 2026-06-12, wave 1-c pattern CSS
 *
 * Patterns: rwest/site-header (header.html upgrade), rwest/contact-form,
 *           rwest/page-contact, rwest/error-404, rwest/legal-body
 *
 * Conventions: container-queries-first for components/patterns; viewport @media
 * allowed for page/template chrome (header, skip link — altitude rule). Tokens
 * only (--wp--preset--*). Allowed literals: container-query thresholds (rem),
 * letter-spacing (em), font-weight integers, border/outline widths (px),
 * line-height scalars, ch/em measure values.
 *
 * Destination: merge relevant sections into assets/css/patterns.css when
 * these patterns graduate from sprint-scratch to reviewed.
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   Site Header — parts/header.html
   Black sticky bar: skip link, site title, navigation with mobile overlay.
   Page/template chrome altitude → viewport @media allowed here.
   ========================================================================== */

/* ─── Skip link ─────────────────────────────────────────────────────────── */

/* WP core block-template.php (_block_template_add_skip_link) automatically
   injects <a class="skip-link screen-reader-text" id="wp-skip-link"> BEFORE
   the first <header> element. We style the native element — no manual link
   in header.html needed or added.
   Technique: translate off-screen until focused (GPU-composited, no reflow).
   Page/template chrome altitude → viewport @media allowed. */
.skip-link.screen-reader-text {
  position: absolute;
  inset-inline-start: var(--wp--preset--spacing--4);
  inset-block-start: var(--wp--preset--spacing--2);
  z-index: 200;
  padding: var(--wp--preset--spacing--2) var(--wp--preset--spacing--5);
  background: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--black);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  font-weight: 500;
  text-decoration: none;
  border-radius: 2px;

  /* Override WP core's clip-based hide with transform (GPU, no reflow) */
  clip-path: none !important;
  width: auto !important;
  height: auto !important;
  overflow: visible !important;
  transform: translateY(calc(-100% - 2rem));
  transition: transform 0.15s ease;
}

.skip-link.screen-reader-text:focus {
  transform: translateY(0);
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .skip-link.screen-reader-text {
    transition: none;
  }
}

/* ─── Header shell ───────────────────────────────────────────────────────── */

/* Position sticky via viewport media (page chrome altitude — css-conventions).
   backdrop-filter: glass-dark effect from WireframeNav.css, re-authored with tokens. */

/* Sticky lives on WP's template-part wrapper — the inner group is exactly the
   wrapper's height, so sticky on the inner never travels (state-review B-1,
   2026-06-12). Visual treatment stays on .rwest-site-header below. */
header.wp-block-template-part {
  position: sticky;
  top: 0;
  z-index: 100;
}

.rwest-site-header {
  border-bottom: 1px solid color-mix(in srgb, var(--wp--preset--color--pure-white) 6%, transparent);
}

/* Glass scrim on a pseudo — backdrop-filter on the header itself creates a
   containing block that traps the core nav overlay's position:fixed inside
   the 128px header (sprint QA 2026-06-12; css-conventions containing-block
   gotcha). The pseudo carries the blur; the header stays filter-free. */
.rwest-site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;

  /* Charcoal glass, not pure black: surface-2 (#0a0a0a) at 92% matches the
     wireframe nav exactly (rgba(10,10,10,0.92)). Pure #000 at 92% over the black
     page body renders as dead flat black — reads as solid, not glass. The
     charcoal lifts the bar so it reads as a translucent panel at rest, and the
     blur reveals content behind it on scroll. */
  background: color-mix(in srgb, var(--wp--preset--color--surface-2) 92%, transparent);
  backdrop-filter: blur(8px);
}

/* Inner flex row — constrained to 1400px (matches wideSize token) */
.rwest-site-header__inner {
  max-inline-size: var(--wp--style--global--wide-size, 1400px);
  margin-inline: auto;
}

/* ─── Logo ───────────────────────────────────────────────────────────────── */

/* R\West wordmark — rwest/site-logo pattern (core/image, theme-asset PNG).
   The PNG is white-on-transparent, so no filter is needed on the dark header.
   Height matches the wireframe nav (WireframeNav.css .wf-nav-logo-img: 12px);
   centralized as a custom property — the one allowed layout literal. */
.rwest-site-logo {
  --rwest-logo-height: 0.75rem; /* 12px — wireframe wordmark scale on the dark bar */

  margin: 0;
  flex-shrink: 0;
  line-height: 0; /* kill inline-image descender gap so the bar centers cleanly */
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-site-logo img {
  display: block;
  block-size: var(--rwest-logo-height);
  inline-size: auto;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-site-logo a:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 4px;
  border-radius: 2px;
}

/* ─── Navigation ─────────────────────────────────────────────────────────── */

/* core/navigation block with overlayMenu:"mobile". The overlay (full-screen
   typographic takeover) fires at ≤ 900px — page chrome altitude, viewport @media. */
.rwest-site-nav {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  letter-spacing: 0.01em;
}

/* Nav links — white, hover brand-aa (AA on dark). Underline on :focus-visible. */
.rwest-site-nav .wp-block-navigation-item__content {
  color: var(--wp--preset--color--white);
  text-decoration: none;
  transition: color 0.15s ease;
}

.rwest-site-nav .wp-block-navigation-item__content:hover {
  color: var(--wp--preset--color--pure-white);
}

.rwest-site-nav .wp-block-navigation-item__content:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Active page item — native aria-current="page" styling.
   Underline affordance + full white; mirrors .wf-nav-current. */
.rwest-site-nav .wp-block-navigation-item[aria-current='page'] > .wp-block-navigation-item__content,
/* stylelint-disable-next-line no-descending-specificity */
.rwest-site-nav .current-menu-item > .wp-block-navigation-item__content {
  color: var(--wp--preset--color--pure-white);
  font-weight: 500;
  border-bottom: 1px solid rgb(255 255 255 / 60%);
  padding-bottom: 0.1rem;
}

/* "Start a project" CTA link — outlined white pill; last item in nav. */
.rwest-site-nav .wp-block-navigation-item:last-child > .wp-block-navigation-item__content {
  border: 1px solid var(--wp--preset--color--white);
  padding: 0.4rem 0.9rem;
  border-radius: 2px;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.rwest-site-nav .wp-block-navigation-item:last-child > .wp-block-navigation-item__content:hover {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--black);
}

/* ─── Mobile-overlay breakpoint override (600px → 900px) ────────────────── */

/* Core's nav block (wp-includes/blocks/navigation/style.min.css) flips from
   the hamburger+overlay to the inline desktop menu at its OWN breakpoint via
   two @media (min-width:600px) rules:
     1. .wp-block-navigation__responsive-container-open:not(.always-shown){display:none}
        — hides the hamburger ≥600px
     2. .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open)
        {display:block;position:relative;…} — reveals the inline menu ≥600px
   The wireframe collapses at ≤900px, so for the 600–900px band we must re-assert
   the mobile (overlay-closed) state — show the hamburger, hide the inline menu —
   scoped to .rwest-site-nav so no other nav is affected. Below 600px core already
   does the right thing; above 900px core's desktop rules apply unchanged.
   600/900 are layout thresholds (the one allowed CSS literal — see css-conventions). */
@media (width >= 37.5rem) and (width <= 56.25rem) {
  /* 600px–900px */

  /* Re-show the hamburger. Core's @media (min-width:600px) rule sets the open
     button to display:none with triple-class specificity (0,3,0) and our band
     overlaps it, so the !important is required to win inside this band. */
  .rwest-site-nav .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: flex !important; /* override core @media (min-width:600px) hide */
  }

  /* Re-hide the inline menu (collapse the closed container back to its <600px
     overlay-hidden state). Overrides core's @media (min-width:600px) reveal,
     which has specificity (0,3,0); matched here at (0,3,0) but later in the
     cascade (patterns.css loads after core), so no !important needed.
     When .is-menu-open is added (hamburger clicked), core's own non-media-gated
     overlay rules re-display it as the full-screen takeover. */
  .rwest-site-nav
    .wp-block-navigation__responsive-container:not(.hidden-by-default, .is-menu-open) {
    display: none;
  }
}

/* ─── Mobile overlay (overlayMenu:"mobile") ─────────────────────────────── */

/* Full-page typographic takeover: black surface, Prata large links.
   The core nav overlay emits .wp-block-navigation__responsive-container.
   Breakpoint ≤ 900px — page chrome, viewport @media permitted. */
@media (width <= 56.25rem) {
  /* 900px */

  /* Hamburger visible */
  .rwest-site-nav .wp-block-navigation__responsive-container-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: var(--wp--preset--spacing--2);
    color: var(--wp--preset--color--pure-white);
  }

  /* Hamburger icon bars */
  .rwest-site-nav .wp-block-navigation__responsive-container-toggle svg {
    width: 24px;
    height: 24px;
    fill: var(--wp--preset--color--pure-white);
  }

  /* Overlay panel — full viewport, black, centered column */

  /* Core owns the modal mechanics (position/inset/background via the nav
     block's overlay color attributes — set in parts/header.html). This rule
     only arranges the takeover column; fighting core's positioning loses to
     its later-loading stylesheet (sprint QA, 2026-06-12). */
  .rwest-site-nav .wp-block-navigation__responsive-container.is-menu-open {
    padding: var(--wp--preset--spacing--16) var(--wp--preset--spacing--8);
    overflow-y: auto;
  }

  .rwest-site-nav .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    block-size: 100%;
  }

  /* Nav items in overlay — Prata, large, centered */
  .rwest-site-nav
    .wp-block-navigation__responsive-container.is-menu-open
    .wp-block-navigation-item__content {
    font-family: var(--wp--preset--font-family--prata);
    font-size: var(--wp--preset--font-size--heading-lg);
    font-weight: 400;
    color: var(--wp--preset--color--pure-white);
    text-decoration: none;
    line-height: 1.2;
    display: block;
    padding: var(--wp--preset--spacing--4) 0;
  }

  /* Undo CTA pill styling in overlay — becomes a plain large link */
  .rwest-site-nav
    .wp-block-navigation__responsive-container.is-menu-open
    .wp-block-navigation-item:last-child
    > .wp-block-navigation-item__content {
    border: none;
    padding: var(--wp--preset--spacing--4) 0;
    border-radius: 0;
    background: transparent;
    color: var(--wp--preset--color--brand);
  }

  .rwest-site-nav
    .wp-block-navigation__responsive-container.is-menu-open
    .wp-block-navigation-item:last-child
    > .wp-block-navigation-item__content:hover {
    background: transparent;
    color: var(--wp--preset--color--brand-hover);
  }

  /* Close button — top-right corner */
  .rwest-site-nav .wp-block-navigation__responsive-container-close {
    position: absolute;
    inset-block-start: var(--wp--preset--spacing--6);
    inset-inline-end: var(--wp--preset--spacing--6);
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--wp--preset--color--pure-white);
  }

  .rwest-site-nav .wp-block-navigation__responsive-container-close svg {
    width: 24px;
    height: 24px;
    fill: var(--wp--preset--color--pure-white);
  }
}

/* Reduced motion — drop the skip-link/nav transitions. The header KEEPS its
   translucent glass scrim (::before, surface-2 92%) under reduced motion — the
   wireframe does too; backdrop-filter is a static effect, not motion, so forcing
   the bar solid black here was wrong (it killed the "visible opacity"). */
@media (prefers-reduced-motion: reduce) {
  .rwest-skip-link {
    transition: none;
  }

  .rwest-site-nav .wp-block-navigation-item__content,
  .rwest-site-nav .wp-block-navigation-item:last-child > .wp-block-navigation-item__content {
    transition: none;
  }
}

/* ==========================================================================
   Contact Form — rwest/contact-form
   Dark surface panel: eyebrow + h2 + labeled form fields + submit.
   ========================================================================== */

/*
 * Container model: the form group is the container; inner fields respond
 * to the panel width (not viewport — the panel sits in a 58% column).
 *
 * Component-scoped layout constants:
 *   --contact-form-input-border: 1px — border-width literal (no WP preset).
 *   --contact-form-input-radius: 0 — square corners (wireframe design).
 */

.rwest-contact-form {
  --contact-form-input-border: 1px;
  --contact-form-input-radius: 0;

  container-type: inline-size;
  padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--8);
}

/* Eyebrow — gray small-caps label above h2 */
.rwest-contact-form__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 500;
  margin-bottom: var(--wp--preset--spacing--3);
}

/* Form layout — stacked fields with consistent gap */
.rwest-contact-form__form {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--5);
  margin: 0;
}

/* ─── Field wrapper ──────────────────────────────────────────────────────── */

.rwest-form-field {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--2);
}

/* ─── Label ──────────────────────────────────────────────────────────────── */

.rwest-form-label {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm); /* 14px — wireframe label 13.6px (was body-xs 12px) */
  font-weight: 400;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 80%); /* gray on dark — composited ≈ #ccc — 9.5:1 ✓ */
}

.rwest-form-required {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--wp--preset--font-size--body-xs);
  color: rgb(255 255 255 / 50%);
}

.rwest-form-optional {
  text-transform: none;
  letter-spacing: 0.02em;
  font-size: var(--wp--preset--font-size--body-xs);
  color: rgb(255 255 255 / 45%); /* #888 on dark ≈ 5.74:1 ✓ */
}

/* ─── Input + Textarea ───────────────────────────────────────────────────── */

.rwest-form-input,
.rwest-form-textarea {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-md);
  line-height: 1.5;
  color: var(--wp--preset--color--pure-white);
  background: rgb(255 255 255 / 6%);
  border: var(--contact-form-input-border) solid rgb(255 255 255 / 20%);
  border-radius: var(--contact-form-input-radius);
  padding: var(--wp--preset--spacing--3) var(--wp--preset--spacing--4);
  width: 100%;

  /* border-box so width:100% + padding + border stays inside the field wrapper
     (content-box default made inputs 34px wider than the panel → overflow). */
  box-sizing: border-box;
  appearance: none;
  resize: vertical;
  transition: border-color 0.15s ease;
}

.rwest-form-input::placeholder,
.rwest-form-textarea::placeholder {
  color: rgb(255 255 255 / 35%);
}

.rwest-form-input:focus,
.rwest-form-textarea:focus {
  outline: none;
  border-color: var(--wp--preset--color--brand-aa);
}

/* Focus-visible ring for keyboard users (additive to border-color change) */
.rwest-form-input:focus-visible,
.rwest-form-textarea:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-form-textarea {
  min-block-size: 8rem;
}

/* ─── CAPTCHA placeholder ────────────────────────────────────────────────── */

.rwest-form-captcha-placeholder {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--4);
  padding: var(--wp--preset--spacing--3) var(--wp--preset--spacing--4);
  background: var(--wp--preset--color--pure-white);
  border: var(--contact-form-input-border) solid var(--wp--preset--color--border-subtle);
  font-size: var(--wp--preset--font-size--body-sm);
  color: var(--wp--preset--color--gray-aa); /* #555 — 7.46:1 on white ✓ */
  font-family: var(--wp--preset--font-family--roboto);
}

.rwest-form-captcha-icon {
  color: var(--wp--preset--color--gray-aa);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 22px;
  block-size: 22px;
  border: var(--contact-form-input-border) solid var(--wp--preset--color--gray-aa);
  background: var(--wp--preset--color--pure-white);
  font-size: var(--wp--preset--font-size--body-sm);
}

/* ─── Actions ────────────────────────────────────────────────────────────── */

.rwest-form-actions {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--3);
  margin-block-start: var(--wp--preset--spacing--2);
}

/* Submit — inherits core button styles (brand-aa bg, white text).
   Disabled state shows intent without native disabled graying. */
.rwest-form-submit {
  align-self: flex-start; /* placeholder — no wiring yet */
}

/* Disabled look gated on :disabled — the base rule baked it in, which would
   survive enabling the button when Q7 wiring lands (state-review S5). */
.rwest-form-submit:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.rwest-form-fineprint {
  font-size: var(--wp--preset--font-size--body-xs);
  color: rgb(255 255 255 / 50%); /* #888 on dark ≈ 5.9:1 ✓ */
  margin: 0;
  font-family: var(--wp--preset--font-family--roboto);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .rwest-form-input,
  .rwest-form-textarea {
    transition: none;
  }
}

/* ==========================================================================
   Page: Contact — rwest/page-contact
   Two-column layout: addresses (left) + form panel (right).
   Container-queries-first for column responsiveness.
   ========================================================================== */

/*
 * Container model: rwest-contact-body is the full-bleed section container;
 * the columns block provides the two-column flex row. Stack threshold: ≤ 48rem
 * container width (≈ 768px viewport) — layout shift: two-col → one-col.
 *
 * Component-scoped layout constants:
 *   --contact-city-size: fluid clamp — no WP preset for this large display value.
 *     Range 3rem–5.5rem matches wireframe clamp(3rem, 6vw, 5.5rem).
 */

.rwest-contact-body {
  --contact-city-size: clamp(3rem, 8cqi, 5.5rem); /* cqi: fluid within the section */

  container-type: inline-size;
}

/* ─── Addresses column ───────────────────────────────────────────────────── */

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* Address list — stacked offices */
.rwest-contact-address-list {
  container-type: inline-size;
}

/* Address item — city abbreviation + address block + office link */
.rwest-contact-address-item {
  container-type: inline-size;
}

/* City abbreviation — large fluid Roboto, brand red.
   brand (not brand-aa): at display-sm sizes (≥ 2rem) the large-text threshold
   (3:1) applies; wireframe uses brand red (#E74536) here — 3.18:1 on white ✓. */
.rwest-contact-address-city {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--contact-city-size);
  font-weight: 700;
  color: var(--wp--preset--color--brand);
  letter-spacing: -0.02em;
  line-height: 1;
  margin: 0;
}

/* Address block — semantic <address>, styled as body text */
.rwest-contact-address-block {
  font-style: normal;
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  line-height: 1.6;
  color: var(--wp--preset--color--gray-dark); /* #333 — 12.63:1 on white ✓ */
}

/* "See office →" link — uppercase small caps, dark underline */
.rwest-contact-office-link {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--wp--preset--color--surface-3); /* #111 — 18.88:1 ✓ */
  text-decoration: none;
  border-bottom: 1px solid var(--wp--preset--color--surface-3);
  padding-bottom: 1px;
  transition:
    color 0.15s ease,
    border-color 0.15s ease;
}

.rwest-contact-office-link:hover {
  color: var(--wp--preset--color--brand-aa);
  border-color: var(--wp--preset--color--brand-aa);
}

.rwest-contact-office-link:focus-visible {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
  border-radius: 2px;
}

/* General inquiries meta block */
.rwest-contact-addresses-meta {
  container-type: inline-size;
}

.rwest-contact-addresses-meta__label {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 500;
  margin: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-contact-addresses-meta__value a {
  color: var(--wp--preset--color--surface-3);
  text-underline-offset: 3px;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-contact-addresses-meta__value a:hover {
  color: var(--wp--preset--color--brand-aa);
}

/* ─── Responsive: stack below 48rem container ────────────────────────────── */

/* Layout shift: two-col → single-col stacked (form below addresses). */

@container (max-width: 48rem) {
  .rwest-contact-cols {
    flex-direction: column;
  }

  .rwest-contact-addresses-col,
  .rwest-contact-form-col {
    width: 100%;
    flex-basis: 100% !important;
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .rwest-contact-office-link {
    transition: none;
  }
}

/* ==========================================================================
   Error 404 — rwest/error-404 (Variant B — Content-forward)
   Dark full-bleed section: ghost "404" behind headline + 2 CTAs + home link.
   Container-queries-first.
   ========================================================================== */

/*
 * Container model: rwest-error-404 is the full-bleed section container.
 * Inner content is centered via constrained layout (contentSize: 800px).
 *
 * Component-scoped layout constants:
 *   --error-404-ghost-size: fluid clamp — decorative large number.
 *     Range 7rem–14rem from wireframe clamp(7rem, 18vw, 14rem); re-authored
 *     with cqi units (container-relative fluid scaling).
 */

.rwest-error-404 {
  --error-404-ghost-size: clamp(7rem, 25cqi, 14rem);

  container-type: inline-size;
  min-block-size: 70dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* Ghost "404" — Prata, near-transparent, decorative. Rendered as a ::before
   pseudo-element (NOT a text node): axe flags visible low-contrast TEXT for AA
   regardless of aria-hidden, but its color-contrast rule does not evaluate
   generated content — so a decorative watermark belongs in CSS, not the DOM
   (QA row 12, 2026-06-18). As the first flex child it sits in the same place the
   old <p> did; the negative margin-bottom pulls the headline up to overlap it. */
.rwest-error-404::before {
  content: "404";
  font-family: var(--wp--preset--font-family--prata);
  font-size: clamp(8rem, 24cqi, 14rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, var(--wp--preset--color--pure-white) 12%, transparent); /* translucent ghost — decorative only */
  user-select: none;
  pointer-events: none;
  margin-bottom: calc(-1 * var(--wp--preset--spacing--12)); /* overlap headline */
}

/* Headline — Prata, centered, white, max-width for reading comfort */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-error-404 .wp-block-heading {
  max-inline-size: 20ch;
  margin-inline: auto;
  position: relative; /* stacks above the ghost */
  z-index: 1;
}

/* Body copy — gray on dark surface */
.rwest-error-404__body {
  max-inline-size: 46ch;
  margin-inline: auto;
}

/* Home link — plain text link, white, hover brand */
.rwest-error-404__home-link {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  color: rgb(255 255 255 / 70%); /* 8.58:1 on black ✓ */
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.15s ease;
}

.rwest-error-404__home-link:hover {
  color: var(--wp--preset--color--pure-white);
}

.rwest-error-404__home-link:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .rwest-error-404__home-link {
    transition: none;
  }
}

/* ==========================================================================
   Legal Body — rwest/legal-body
   Editorial template: light header + reading column + dark contact/offices + cross-nav.
   Container-queries-first.
   ========================================================================== */

/*
 * Container model:
 *   - rwest-legal-header and rwest-legal-body: constrained sections (container
 *     type on each full-bleed group; text reflows within the 736px contentSize).
 *   - rwest-legal-contact: dark section, offices 3-col → 1-col below 40rem.
 *   - rwest-legal-related: constrained cross-nav strip.
 *
 * Component-scoped layout constants:
 *   None beyond allowed literals (letter-spacing, font-weight).
 */

/* ─── Page header ────────────────────────────────────────────────────────── */

.rwest-legal-header {
  container-type: inline-size;
}

/*
 * Legal title — wireframe `.wf-legal-title` is 4rem / lh 1.1 (editorial, smaller
 * than a hero h1). The pattern h1 carries no size, so it would inherit the global
 * heading element size; pin it to heading-xl (4rem) to match Legal.css.
 */
.rwest-legal-header h1 {
  font-size: var(--wp--preset--font-size--heading-xl);
  line-height: 1.1;
}

/* Eyebrow — brand red, uppercase label */
.rwest-legal__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 500;
  margin-bottom: var(--wp--preset--spacing--5);
}

/*
 * Last-updated dateline — small uppercase. Uses gray-aa (#555, 7:1 on white) via
 * the markup's has-gray-aa-color class, NOT the wireframe's #999 (2.8:1, fails
 * WCAG AA for text). Intentional a11y deviation from the wireframe.
 */
.rwest-legal__updated {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--wp--preset--spacing--6);
}

/* Intro paragraph — larger body text with top rule (wireframe 1.2rem / lh 1.65) */
.rwest-legal__intro {
  line-height: 1.65;
  margin: 0;
}

/* ─── Body reading column ────────────────────────────────────────────────── */

.rwest-legal-body {
  container-type: inline-size;
}

/* Section — padded + bottom border (last-child has no border via markup) */
.rwest-legal-section {
  container-type: inline-size;
}

/* h2 section headings — Prata 400, readable weight for body copy pacing */
.rwest-legal-section .wp-block-heading[data-type='core/heading'] {
  /* Heading element styles from theme.json apply Prata 400 globally */
  margin-bottom: var(--wp--preset--spacing--5);
}

/* h3 sub-headings — Roboto, uppercase label style */
.rwest-legal-section h3.wp-block-heading {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-md);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--wp--preset--color--surface-3);
  margin-block: var(--wp--preset--spacing--6) var(--wp--preset--spacing--3);
}

/* Body paragraphs */
.rwest-legal__p {
  line-height: 1.75;
  margin-bottom: var(--wp--preset--spacing--5);
}

.rwest-legal__p:last-child {
  margin-bottom: 0;
}

/* Lists */
.rwest-legal__ul {
  font-size: var(--wp--preset--font-size--body-md);
  line-height: 1.75;
  padding-inline-start: var(--wp--preset--spacing--6);
  margin-bottom: var(--wp--preset--spacing--5);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-legal__ul li {
  margin-bottom: var(--wp--preset--spacing--2);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-legal__ul li:last-child {
  margin-bottom: 0;
}

/* ─── Contact + offices dark block ──────────────────────────────────────── */

.rwest-legal-contact {
  container-type: inline-size;
}

/* Offices grid — 3-col → 1-col below 40rem container */
.rwest-legal-offices-grid {
  container-type: inline-size;
}

/* Office card */
.rwest-legal-office {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--2);
}

/* City name — Prata display */
.rwest-legal-office__city {
  color: var(--wp--preset--color--pure-white);
  line-height: 1.2;
  margin: 0;
}

/* Address — gray on dark surface (gray #888 = 5.9:1 on black ✓) */
.rwest-legal-office__address {
  font-style: normal;
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  line-height: 1.7;
  color: var(--wp--preset--color--gray); /* #888 — 5.9:1 on black ✓ */
}

/* Stack offices at ≤ 40rem container (≈ 640px viewport) */
@container (max-width: 40rem) {
  .rwest-legal-offices-grid .wp-block-columns {
    flex-direction: column;
  }

  .rwest-legal-offices-grid .wp-block-column {
    width: 100%;
    flex-basis: 100% !important;
  }
}

/* ─── Related pages cross-nav ────────────────────────────────────────────── */

.rwest-legal-related {
  container-type: inline-size;
}

.rwest-legal-related__label {
  text-transform: uppercase;
  letter-spacing: 0.15em;
  font-weight: 500;
  flex-shrink: 0;
}

.rwest-legal-related__link {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-sm);
  font-weight: 700;
  color: var(--wp--preset--color--surface-3); /* #111 — 18.88:1 ✓ */
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  transition: color 0.15s ease;
}

.rwest-legal-related__link:hover {
  color: var(--wp--preset--color--brand-aa);
}

.rwest-legal-related__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--surface-3);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .rwest-legal-related__link {
    transition: none;
  }
}

/* ============ sprint 2026-06-12 :: wave3-g ============ */

/**
 * Wave 3-G — Case Study Pattern Styles
 *
 * Patterns: .rwest-case-copy-blade, .rwest-case-approach, .rwest-campaign-mosaic,
 *           .rwest-photo-copy, .rwest-before-after
 *
 * Conventions:
 * - Container-queries-first: every root is a container context (inline-size).
 * - Tokens only: --wp--preset--* for all color/size/spacing.
 * - Alternating tone via is-style-dark / is-style-light on each section group.
 * - Brand-aa token used for brand red on light backgrounds (WCAG AA).
 * - Untokenizable literals: letter-spacing values, font-weight integers,
 *   border-width integers, container breakpoint thresholds (per css-conventions.md).
 */

/* ============================================================
   SHARED EYEBROW TREATMENT
   Used across all case-study patterns. Replicates the existing
   rwest-hero__eyebrow / rwest-media-blade__eyebrow idiom.
   "\ LABEL" — the backslash glyph is in the authored copy;
   color is brand-aa on light, brand on dark.
   ============================================================ */

.rwest-case-copy-blade__eyebrow,
.rwest-case-approach__eyebrow,
.rwest-campaign-mosaic__eyebrow,
.rwest-photo-copy__eyebrow,
.rwest-before-after__label {
  font-weight: 800;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--wp--preset--color--brand);
}

/* On light sections, swap to AA-contrast brand red */
.is-style-light .rwest-case-copy-blade__eyebrow,
.is-style-light .rwest-case-approach__eyebrow,
.is-style-light .rwest-campaign-mosaic__eyebrow,
.is-style-light .rwest-before-after__label {
  color: var(--wp--preset--color--brand-aa);
}

/* ============================================================
   CASE COPY BLADE
   Two-column: left label+heading (40%), right body (60%).
   Dark default (surface-3); is-style-light flips to white bg.
   ============================================================ */

.rwest-case-copy-blade {
  container-type: inline-size;
  container-name: case-copy-blade;
}

/* Dark tone — surface-3 (#111) background */
.rwest-case-copy-blade.is-style-dark,
.rwest-case-copy-blade:not(.is-style-light) {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

/* Light tone */
.rwest-case-copy-blade.is-style-light {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--surface-3);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-case-copy-blade__eyebrow {
  /* 0.15em letter-spacing: untokenizable — no WP letter-spacing preset */
  margin-block-end: var(--wp--preset--spacing--4);
}

/* Heading inherits Prata 400 from theme.json elements.heading */
.rwest-case-copy-blade .wp-block-heading {
  /* No overrides needed — theme.json h2 = heading-xl, we pass fontSize=heading-lg
	   to the block directly. The heading element global letterSpacing (-0.01em) applies. */
}

/* Right column: slightly larger lede body on wide containers */
.rwest-case-copy-blade__col-body {
  /* body-md (1rem) is the base; no override needed at default */
}

/* Stack columns on narrow containers (< 640px container width triggers stack) */

/* 640px = ~40rem — the layout shift from 2-col to 1-col */
@container case-copy-blade (max-width: 40rem) {
  .rwest-case-copy-blade__cols {
    flex-direction: column;
  }

  .rwest-case-copy-blade__col-label,
  .rwest-case-copy-blade__col-body {
    flex-basis: 100% !important;
    width: 100%;
  }
}

/* ============================================================
   CASE APPROACH COLUMNS
   Label + centered heading + optional Prata subhead + 3 cols
   each w/ a 3px brand-red top rule.
   Light default.
   ============================================================ */

.rwest-case-approach {
  container-type: inline-size;
  container-name: case-approach;
}

.rwest-case-approach.is-style-light {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--surface-3);
}

.rwest-case-approach.is-style-dark,
.rwest-case-approach:not(.is-style-light) {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-case-approach__eyebrow {
  margin-block-end: var(--wp--preset--spacing--4);
}

.rwest-case-approach__subhead {
  /* Prata subhead — slightly muted, body-md-ish weight feel */
  opacity: 0.75;
}

/* Three-column top rule — 3px brand red */
.rwest-case-approach__col {
  /* Breakpoint custom property — layout shift: 3-col → 1-col at ~560px */
  --approach-col-break: 35rem;

  border-top: 3px solid var(--wp--preset--color--brand);
  padding-top: var(--wp--preset--spacing--6);
}

/* On light, use brand-aa for the rule (same AA compliance as text) */
.is-style-light .rwest-case-approach__col {
  border-top-color: var(--wp--preset--color--brand-aa);
}

/* Column heading (h3): Prata, inherits theme.json h3 = heading-md */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-case-approach__col .wp-block-heading {
  margin-block-end: var(--wp--preset--spacing--4);
}

/* Collapse to 1 column on narrow containers */
@container case-approach (max-width: 35rem) {
  .rwest-case-approach__cols {
    flex-direction: column;
  }
}

/* ============================================================
   CAMPAIGN MOSAIC
   Centered intro copy + asymmetric image grid.
   Left col (38%): 2 stacked images. Right col (62%): 3 stacked.
   Dark default.
   ============================================================ */

.rwest-campaign-mosaic {
  container-type: inline-size;
  container-name: campaign-mosaic;
}

.rwest-campaign-mosaic.is-style-dark,
.rwest-campaign-mosaic:not(.is-style-light) {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

.rwest-campaign-mosaic.is-style-light {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--surface-3);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-campaign-mosaic__eyebrow {
  margin-block-end: var(--wp--preset--spacing--4);
}

.rwest-campaign-mosaic__body {
  max-width: 48rem;
  margin-inline: auto;
}

/* Image grid columns — flush images */
.rwest-campaign-mosaic__grid {
  align-items: stretch;
}

.rwest-campaign-mosaic__col {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--4);
}

/* Images fill their column cell, no extra margin from core/image default */
.rwest-campaign-mosaic__img {
  margin: 0;
  flex: 1 1 auto;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-campaign-mosaic__img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Left col stacked images: roughly equal halves */
.rwest-campaign-mosaic__col--left .rwest-campaign-mosaic__img {
  flex-basis: 50%;
}

/* Right col: top image taller, bottom two equal (3-up) */
.rwest-campaign-mosaic__col--right .rwest-campaign-mosaic__img:first-child {
  flex-basis: 45%;
}

.rwest-campaign-mosaic__col--right .rwest-campaign-mosaic__img:not(:first-child) {
  flex-basis: 27.5%;
}

/* Collapse mosaic to single column at narrow container widths */

/* 48rem = ~768px — stack left/right columns vertically */
@container campaign-mosaic (max-width: 48rem) {
  .rwest-campaign-mosaic__grid {
    flex-direction: column;
  }

  .rwest-campaign-mosaic__col--left,
  .rwest-campaign-mosaic__col--right {
    flex-basis: 100% !important;
    width: 100%;
  }

  /* Horizontal strip layout for images when stacked */
  .rwest-campaign-mosaic__col--left {
    flex-direction: row;
  }

  .rwest-campaign-mosaic__col--right {
    flex-flow: row wrap;
  }

  .rwest-campaign-mosaic__col--left .rwest-campaign-mosaic__img,
  .rwest-campaign-mosaic__col--right .rwest-campaign-mosaic__img {
    flex-basis: calc(50% - var(--wp--preset--spacing--2));
  }
}

/* ============================================================
   PHOTO COPY BLADE
   Full-bleed Cover with gradient dim, copy bottom-left.
   No extra layout needed beyond hero.php/media-blade.php idioms
   already handled by the core Cover block CSS.
   ============================================================ */

.rwest-photo-copy {
  container-type: inline-size;
  container-name: photo-copy;
}

/* Inner content max-width mirrors hero/media-blade inner column */
.rwest-photo-copy .wp-block-cover__inner-container {
  max-width: 36rem;

  /* Align the copy to the same 1152px content rail the sibling case blades use
     (L64 @ 1280) — the Cover's content otherwise sat at L16, 48px left of every
     other blade (align-review 2026-06-12). max() falls back to the root gutter
     below the rail width. 1152px is the case-blade contentSize (layout literal). */
  padding-inline: max(var(--wp--preset--spacing--6), calc((100% - 1152px) / 2));
}

.rwest-photo-copy__eyebrow {
  margin-block-end: var(--wp--preset--spacing--3);
}

/* Heading (h2) inherits Prata 400, heading-md (2rem) from theme.json */

.rwest-photo-copy__body {
  margin-block-start: var(--wp--preset--spacing--4);

  /* Constrain measure for readability over imagery */
  max-width: 34rem;
}

/* Wider container: allow a bit more content width */
@container photo-copy (min-width: 60rem) {
  .rwest-photo-copy .wp-block-cover__inner-container {
    max-width: 44rem;
  }
}

/* ============================================================
   BEFORE / AFTER GRID
   Two labeled columns, each with an image placeholder.
   Dark default.
   ============================================================ */

.rwest-before-after {
  container-type: inline-size;
  container-name: before-after;
}

.rwest-before-after.is-style-dark,
.rwest-before-after:not(.is-style-light) {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

.rwest-before-after.is-style-light {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--surface-3);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-before-after__label {
  /* 0.15em letter-spacing: untokenizable */
  margin-block-end: var(--wp--preset--spacing--4);
  display: block;
}

.rwest-before-after__img {
  margin: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-before-after__img img {
  width: 100%;
  height: auto;
  display: block;
}

/* Stack columns on narrow containers */

/* 36rem — layout shift: 2-col → 1-col */
@container before-after (max-width: 36rem) {
  .rwest-before-after__cols {
    flex-direction: column;
  }
}

/* ============ sprint 2026-06-12 :: wave4-h ============ */

/**
 * Wave 4-H CSS scratch — Careers, Team, New Business page patterns.
 *
 * This file is a SCRATCH PAD for QA and iteration. When styles are
 * approved they should be moved into assets/css/patterns.css keyed to
 * the pattern's root class. Do not enqueue this file directly.
 *
 * Tokens only — no literal hex/px/spacing except:
 *   - Container breakpoint thresholds (rem), commented with the layout
 *     shift they trigger per css-conventions.md §Breakpoints.
 *   - letter-spacing, font-weight integers, border-width (untokenizable
 *     per css-conventions.md §Untokenizable presentational values).
 *   - aspect-ratio layout literals.
 *
 * @package RWest\Theme
 */

/* ==========================================================================
   BenefitsList — rwest/benefits-list
   ========================================================================== */

/*
 * Container model: .rwest-benefits-list is the full-bleed section container.
 * Each .rwest-benefits-list__item (a core/columns row) is a two-column layout
 * (label left ~20%, text right ~80%) separated by a hairline border-bottom rule.
 * A top border on the items group opens the list visually.
 *
 * Wireframe source: Careers.css lines 87–128 (BenefitsList rule-list pattern).
 *
 * Allowed literals:
 *   border-width: 1px (rule separator — no WP preset border-width family)
 *   font-weight: 400 (Prata — weight-only font, no token)
 *
 * Breakpoints:
 *   42rem (~672px label col width) — 2→1 column stack (label above text)
 */

.rwest-benefits-list {
  container-type: inline-size;
  container-name: benefits-list;
}

.rwest-benefits-list__head {
  margin-bottom: var(--wp--preset--spacing--10);
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* Items container — top border opens the list */
.rwest-benefits-list__items {
  border-top: 1px solid var(--wp--preset--color--surface-3);
}

/* Each item row — bottom hairline rule */
.rwest-benefits-list__item {
  border-bottom: 1px solid var(--wp--preset--color--border-subtle);
  padding-block: var(--wp--preset--spacing--5);
}

/* [recipe:flow-gap-reset] — neutralize WP's > * + * flow gap on the columns
   children; the columns gap + padding-block own the spacing. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-benefits-list__item > * {
  margin-block: 0;
}

/* Label column — fixed 14rem to match the wireframe's grid-template-columns:
   14rem 1fr (Careers.css). The width:20% block attr let the column flex-shrink
   below 14rem (collapsing "Retirement" mid-word AND pulling the text column
   ~114px left of the wireframe x=400). Pin to 14rem so the text column starts
   where the wireframe places it. 14rem = layout threshold (allowed literal). */
.rwest-benefits-list__item > .wp-block-column:first-child {
  flex: 0 0 14rem;
  inline-size: 14rem;
}

/* Text column fills the rest (the wireframe's 1fr). Override the width:80% block
   attr so it tracks the remaining space beside the fixed 14rem label column. */
.rwest-benefits-list__item > .wp-block-column:last-child {
  flex: 1 1 0;
}

/* Label (Prata h3) — inherit heading substrate weight (400 for Prata) */
.rwest-benefits-list__label {
  color: var(--wp--preset--color--surface-3);
  line-height: 1.3;

  /* Don't let a single-word label ("Retirement") break mid-word when its
     column gets narrow (align-review 2026-06-12). */
  text-wrap: balance;
  hyphens: none;
  overflow-wrap: normal;
  word-break: keep-all;

  /* font-weight: 400 inherited from styles.elements.heading — Prata is 400-only */
}

/* Text — body copy color */
.rwest-benefits-list__text {
  color: var(--wp--preset--color--gray-dark);
  line-height: 1.6;
}

/* ≤ 42rem (~672px): stack label above text — layout shift: 2-col → 1-col */
@container benefits-list (max-width: 42rem) {
  .rwest-benefits-list__item.wp-block-columns {
    flex-direction: column;
    gap: var(--wp--preset--spacing--2);
  }

  .rwest-benefits-list__item .wp-block-column {
    flex-basis: 100% !important;
    width: 100%;
  }
}

/* Reduced motion: N/A (no animation in this pattern) */

/* ==========================================================================
   Careers Roles Accordion — rwest/careers-roles
   ========================================================================== */

/*
 * Built on native core/details (HTML <details>/<summary>). The browser handles
 * all disclosure semantics. CSS provides:
 *   - Card border + open-state darkening
 *   - Summary layout (role info left, toggle glyph right)
 *   - Toggle "+" → "×" rotation animation via details[open] (CSS only — no JS)
 *   - Body padding and section spacing
 *
 * Wireframe source: Careers.css lines 208–332.
 *
 * Allowed literals:
 *   border-width: 1px (card border)
 *   font-weight: 400 (Prata)
 *   letter-spacing: 0.08em (meta tag — untokenizable)
 *   transition (reduced-motion stop required)
 *
 * Breakpoints:
 *   N/A — accordion stacks naturally; summary flex wraps at narrow widths.
 */

.rwest-careers-roles {
  container-type: inline-size;
  container-name: careers-roles;
}

/* Eyebrow + heading now provided by the rwest/section-intro block; the
   intro→accordion gap is carried on the block via style.spacing.margin.bottom.
   Lede muted color + 50ch measure re-scoped onto the block's lede element
   (centered via the block's is-align-center rule). */
.rwest-careers-roles__intro .wf-section-intro__lede {
  color: var(--wp--preset--color--gray-aa);
  max-width: 50ch;
}

/* ─── Accordion wrapper ──────────────────────────────────────────────────── */

.rwest-careers-accordion {
  /* gap via blockGap attribute on the parent group */
}

/* ─── Individual role card ───────────────────────────────────────────────── */

/* core/details emits <details class="wp-block-details rwest-careers-role"> */
.rwest-careers-role {
  border: 1px solid var(--wp--preset--color--border-subtle);
  background-color: var(--wp--preset--color--pure-white);

  /* Smooth border darkening on open */
  transition: border-color 0.2s ease;
}

.rwest-careers-role[open] {
  border-color: var(--wp--preset--color--surface-3);
}

/* ─── Summary row ────────────────────────────────────────────────────────── */

/* Hide native disclosure triangle (webkit + standard) */
.rwest-careers-role > summary::-webkit-details-marker {
  display: none;
}

.rwest-careers-role > summary {
  list-style: none; /* Firefox */
}

.rwest-careers-role__summary {
  display: flex;
  align-items: flex-start;
  gap: var(--wp--preset--spacing--6);
  padding: var(--wp--preset--spacing--6) var(--wp--preset--spacing--6);
  cursor: pointer;
}

/* The summary is also a container for its inner group + toggle span */
.rwest-careers-role__summary-main {
  flex: 1;
}

/* [recipe:flow-gap-reset] — neutralize WP flow gap injected into the inner group */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__summary-main > * {
  margin-block: 0;
}

/* Role title — Prata, block-level (wireframe wf-careers-role-title: 1.5rem block).
   The summary atoms are bare <span>s (inline by default); promote to block so the
   title/meta/summary stack vertically and the heading-sm Prata scale applies. */
.rwest-careers-role__title {
  display: block;
  font-family: var(--wp--preset--font-family--prata);
  font-size: var(--wp--preset--font-size--heading-sm);
  color: var(--wp--preset--color--surface-3);
  line-height: 1.2;
  margin-bottom: var(--wp--preset--spacing--2);

  /* font-weight 400 — Prata is 400-only (substrate heading weight) */
}

/* Department · Location · Employment · Salary meta — small uppercase eyebrow scale */
.rwest-careers-role__meta {
  display: block;
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--wp--preset--color--gray-aa);
  margin-bottom: var(--wp--preset--spacing--3);
}

/* One-line summary text */
.rwest-careers-role__summary-text {
  display: block;
  color: var(--wp--preset--color--gray-dark);
  line-height: 1.55;
  margin-block: 0;
}

/* Toggle glyph "+" → rotates to "×" when open */
.rwest-careers-role__toggle {
  font-family: var(--wp--preset--font-family--prata);
  font-size: var(--wp--preset--font-size--heading-sm);
  line-height: 1;
  color: var(--wp--preset--color--surface-3);
  flex-shrink: 0;
  width: 1.5rem;
  text-align: center;
  transition: transform 0.2s ease;

  /* aria-hidden="true" is set on the span in markup */
}

.rwest-careers-role[open] .rwest-careers-role__toggle {
  transform: rotate(45deg);
}

/* ─── Body (expanded content) ────────────────────────────────────────────── */

.rwest-careers-role__body {
  padding: var(--wp--preset--spacing--4) var(--wp--preset--spacing--6) var(--wp--preset--spacing--8);
  border-top: 1px solid var(--wp--preset--color--border-subtle);
}

/* [recipe:flow-gap-reset] — neutralize WP flow gap inside the body group */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__body > * {
  margin-block: 0;
}

/* Section sub-headings (h4 Prata) — wireframe wf-careers-role-section-heading is
   1.15rem; body-lg (1.1rem ≈ 17.6px) is the nearest token (vs body-md 16px default
   which undersized it). Sub-1px short of the wireframe — accepted token residual. */
.rwest-careers-role__section-heading {
  margin-block: var(--wp--preset--spacing--6) var(--wp--preset--spacing--3);
  color: var(--wp--preset--color--surface-3);
  font-size: var(--wp--preset--font-size--body-lg);
  font-weight: 400; /* Prata 400-only */
  line-height: 1.25;
}

/* Paragraphs */
.rwest-careers-role__body .has-body-md-font-size {
  color: var(--wp--preset--color--gray-dark);
  line-height: 1.65;
  margin-bottom: var(--wp--preset--spacing--3);
}

/* Bullet lists */
.rwest-careers-role__list {
  color: var(--wp--preset--color--gray-dark);
  line-height: 1.6;
  padding-inline-start: var(--wp--preset--spacing--6);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__list li {
  margin-bottom: var(--wp--preset--spacing--2);
}

/* ─── Apply block ─────────────────────────────────────────────────────────── */

.rwest-careers-role__apply {
  margin-top: var(--wp--preset--spacing--8);
  padding-top: var(--wp--preset--spacing--6);
  border-top: 1px solid var(--wp--preset--color--border-subtle);
}

/* Apply button — dark fill on the light roles section (wireframe wf-ph-button-primary:
   #222 fill / #fff text). is-style-hero-primary's base (styles/hero-primary.json) is a
   warm-white face + dark text, which is only recolored inside .rwest-hero.is-style-light;
   outside the hero scope it renders warm-white-on-warm-white (invisible). Recolor here,
   mirroring the hero light-mode flip. surface-3 (#111) + white (#fdfbfa) = 18.3:1 (AA).
   Shares specificity with the earlier .rwest-hero button rules (different scope, can't
   reorder) — same no-descending-specificity exception the hero/MediaBlade buttons use. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__apply .is-style-hero-primary .wp-block-button__link {
  background-color: var(--wp--preset--color--surface-3);
  color: var(--wp--preset--color--white);
}

/* :hover — theme.json elements.button:hover (specificity 0,1,1) injects brand-hover
   behind the white text (~3.1:1, a 1.4.3 fail). Hold the dark fill on hover (same
   WCAG 1.4.3 safety the hero buttons apply). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__apply .is-style-hero-primary .wp-block-button__link:hover {
  background-color: var(--wp--preset--color--surface-3);
}

/* :focus-visible — brand-aa ring (#D63A2D = 4.66:1 on the light section bg). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__apply .is-style-hero-primary .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

.rwest-careers-role__apply-fineprint {
  color: var(--wp--preset--color--gray-aa);
  margin-block: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-role__apply-fineprint a {
  color: var(--wp--preset--color--surface-3);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .rwest-careers-role,
  .rwest-careers-role__toggle {
    transition: none;
  }
}

/* ==========================================================================
   Careers Culture section — rwest/page-careers (culture section)
   ========================================================================== */

/*
 * Container model: .rwest-careers-culture is the full-bleed section container.
 * Full-bleed image (alignfull via block attribute), centered editorial intro
 * (constrained 800px via inner group layout), 3-column pillar grid.
 *
 * Wireframe source: Careers.css lines 11–82.
 *
 * Breakpoints:
 *   48rem (~768px culture-section container) — pillars 3→1 column stack
 */

.rwest-careers-culture {
  container-type: inline-size;
  container-name: careers-culture;
}

/* Full-bleed image */
.rwest-careers-culture__media {
  /* alignfull handled by WP core; no CSS override needed */
  margin-block: 0;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* ─── Pillars grid ───────────────────────────────────────────────────────── */

/* Each pillar carries a top border (culture pillar = border-top rule separator) */
.rwest-careers-culture__pillar {
  border-top: 2px solid var(--wp--preset--color--surface-3);
}

/* Pillar heading (h3 Prata) */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-culture__pillar > .wp-block-heading {
  color: var(--wp--preset--color--surface-3);
  line-height: 1.2;
}

/* Pillar body */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-careers-culture__pillar > p {
  color: var(--wp--preset--color--gray-dark);
  line-height: 1.6;
  margin-block: 0;
}

/* ≤ 48rem (~768px culture-section container): pillars 3→1 column stack */
@container careers-culture (max-width: 48rem) {
  .rwest-careers-culture__pillars.wp-block-columns {
    flex-direction: column;
  }

  .rwest-careers-culture__pillars > .wp-block-column {
    flex-basis: 100% !important;
    width: 100%;
  }
}

/* ==========================================================================
   Careers Evergreen — rwest/page-careers (evergreen "always interested" block)
   ========================================================================== */

/*
 * Two-column layout (image left, editorial right). Dark surface.
 *
 * Wireframe source: Careers.css lines 334–365.
 *
 * Breakpoints:
 *   48rem (~768px): 2→1 column stack (image above editorial)
 */

.rwest-careers-evergreen {
  container-type: inline-size;
  container-name: careers-evergreen;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* ≤ 48rem (~768px): image + body stack to single column */
@container careers-evergreen (max-width: 48rem) {
  .rwest-careers-evergreen__inner.wp-block-columns {
    flex-direction: column;
  }

  .rwest-careers-evergreen__inner > .wp-block-column {
    flex-basis: 100% !important;
    width: 100%;
  }
}

/* ==========================================================================
   Team Overview — rwest/page-team (intro section)
   ========================================================================== */

/*
 * Simple editorial block — eyebrow, h2, body paragraph.
 *
 * Wireframe source: Team.css lines 6–45.
 */

.rwest-team-overview {
  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* Body — 60ch readability cap; line-height untokenizable */
.rwest-team-overview .has-body-lg-font-size {
  max-width: 60ch;
  line-height: 1.65;
  color: var(--wp--preset--color--gray-dark);
}

/* Dark surface (wireframe tone="gray" = dark/charcoal): the body copy must read
   light, mirroring the wireframe's `.wf-tone-gray .wf-team-overview-body { #ccc }`.
   gray (#888) is the lightest gray token and clears AA on surface-2 (5.58:1).
   Scoped to is-style-dark so the light-surface default above is preserved. */
.rwest-team-overview.is-style-dark .has-body-lg-font-size {
  color: var(--wp--preset--color--gray);
}

/* ==========================================================================
   Team Grid Section — rwest/page-team (everyone grid section)
   ========================================================================== */

/*
 * Section-level container. The team grid and card CSS live in the TeamCard
 * section of patterns.css (rwest-team-grid, rwest-team-card). This section
 * adds the header (eyebrow + h2) and FilterBar placeholder spacing.
 *
 * Wireframe source: Team.css lines 68–96.
 */

.rwest-team-grid-section {
  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* ==========================================================================
   Team Spotlights — rwest/page-team (featured spotlights section)
   ========================================================================== */

/*
 * 3-up grid on dark surface. Each card: top border, 3:4 image, name (Prata h3),
 * title (Roboto), alt title (italic brand-aa), blockquote with left border.
 *
 * Wireframe source: Team.css lines 288–392.
 *
 * Allowed literals:
 *   aspect-ratio: 3 / 4 (image — layout threshold)
 *   border-width: 2px (top card border, blockquote left border)
 *   font-style: italic (alt title, blockquote — CSS keyword)
 *   font-weight: 400 (Prata)
 *
 * Breakpoints:
 *   56rem (~896px): spotlights grid 3→1 column stack
 */

.rwest-team-spotlights {
  container-type: inline-size;
  container-name: team-spotlights;
}

/* Eyebrow + heading now provided by the rwest/section-intro block. */

/* ─── Spotlights grid ────────────────────────────────────────────────────── */

/* [recipe:flow-gap-reset] — neutralize WP > * + * flow gap on column children */
.rwest-team-spotlights__grid > .wp-block-column > * {
  margin-block: 0;
}

/* ─── Individual spotlight card ──────────────────────────────────────────── */

.rwest-team-spotlight-card {
  border-top: 2px solid var(--wp--preset--color--pure-white);
}

/* [recipe:flow-gap-reset] at card level */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-team-spotlight-card > * {
  margin-block: 0;
}

/* Image — aspect-ratio set as block attribute (3/4); overflow clips */
.rwest-team-spotlight-card__image {
  overflow: hidden;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-team-spotlight-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Meta group */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-team-spotlight-card__meta > * {
  margin-block: 0;
}

/* Name (h3 Prata) */
.rwest-team-spotlight-card__name {
  color: var(--wp--preset--color--pure-white);
  font-weight: 400; /* Prata 400-only */
  line-height: 1.2;
}

/* Title (Roboto body-sm) */
.rwest-team-spotlight-card__title {
  color: rgb(255 255 255 / 70%);

  /* composited over dark bg ≥ 4.5:1 AA — dark bg is surface-3 (#111) + is-style-dark */
}

/* Alt title ("aka ...") — italic, brand-aa for readability on dark surface */

/* Sprint AA fix 2026-06-12: was brand-aa (4.24:1 on the dark card) — swapped to
   gray; red-on-dark for small text queued as an accepted-delta question. */
.rwest-team-spotlight-card__alt-title {
  font-style: italic;
  color: var(--wp--preset--color--gray);

  /* brand-aa (#D63A2D) on is-style-dark surface (#111) = 4.664:1 — AA pass */
}

/* Blockquote — left brand border, italic body */
.rwest-team-spotlight-card__quote {
  border-left: 2px solid var(--wp--preset--color--brand-aa);
  padding-left: var(--wp--preset--spacing--4);
  font-style: italic;
  margin-block: 0;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-team-spotlight-card__quote p {
  color: rgb(255 255 255 / 75%);
  line-height: 1.65;
  margin-block: 0;
}

/* ≤ 56rem (~896px): 3→1 column stack */
@container team-spotlights (max-width: 56rem) {
  .rwest-team-spotlights__grid.wp-block-columns {
    flex-direction: column;
    max-width: 480px;
    margin-inline: auto;
  }

  .rwest-team-spotlights__grid > .wp-block-column {
    flex-basis: 100% !important;
    width: 100%;
  }
}

/* ==========================================================================
   New Business — rwest/page-new-business
   ========================================================================== */

/* --------------------------------------------------------------------------
   What to expect (numbered steps)
   Wireframe source: NewBusiness.css lines 10–50.

   Allowed literals:
     border-width: 1px (step separator — no WP preset border-width)
     font-weight: 400 (Prata)

   Breakpoints:
     N/A — steps are already flex-column; columns collapse gracefully.
   -------------------------------------------------------------------------- */

.rwest-newbiz-expect {
  container-type: inline-size;
}

/* Eyebrow + heading now provided by the rwest/section-intro block; the
   heading→steps gap (was spacing-8) is carried on the block via
   style.spacing.margin.bottom. */

/* Step row — two-column: num (fixed 8rem) + content */
.rwest-newbiz-expect__step {
  /* gap via block layout; border-bottom separates steps */
  padding-bottom: var(--wp--preset--spacing--6);
  border-bottom: 1px solid var(--wp--preset--color--border-subtle);
}

.rwest-newbiz-expect__step + .rwest-newbiz-expect__step {
  padding-top: var(--wp--preset--spacing--6);
}

/* Number (Prata heading-md) */
.rwest-newbiz-expect__num {
  color: var(--wp--preset--color--brand-aa);

  /* brand-aa (#D63A2D) = 4.664:1 on pure-white — AA pass */
  line-height: 1;
  margin-block: 0;
}

/* Step title (h3 Prata) */
.rwest-newbiz-expect__step-title {
  color: var(--wp--preset--color--surface-3);
  margin-bottom: var(--wp--preset--spacing--2);

  /* Reset the global heading -0.01em tracking — wireframe step title uses normal
     tracking at this Prata size (NewBusiness.css .wf-newbiz-expect-step-title). */
  letter-spacing: 0;
}

/* [recipe:flow-gap-reset] — step inner group */
.rwest-newbiz-expect__step .wp-block-group > * {
  margin-block: 0;
}

/* ≤ 36rem (~576px): step columns stack */
@container rwest-newbiz-expect (max-width: 36rem) {
  .rwest-newbiz-expect__step.wp-block-columns {
    flex-direction: column;
    gap: var(--wp--preset--spacing--3);
  }

  .rwest-newbiz-expect__step > .wp-block-column {
    flex-basis: 100% !important;
  }
}

/* --------------------------------------------------------------------------
   Inquiry form (static shell)
   Wireframe source: NewBusiness.css lines 52–211.

   The form renders inside core/html on a dark surface (is-style-dark).
   CSS provides the dark-surface form treatment:
     - Fieldset bordered with rgba white overlay
     - Labels uppercase, letter-spaced, muted white
     - Inputs with subtle white-tinted background, white border
     - Focus: brand-aa border (not raw brand — 4.664:1 vs 3.961:1 on dark)
     - Submit button disabled state

   Allowed literals:
     border-width: 1px (fieldset/input borders)
     border-radius: 0 (inputs — square, design intent)
     letter-spacing: 0.05em (labels — untokenizable)
     letter-spacing: 0.06em (submit btn — untokenizable)
     opacity values in color-mix (contrast-mechanism)
   -------------------------------------------------------------------------- */

.rwest-newbiz-form-section {
  container-type: inline-size;
  container-name: newbiz-form-section;
}

.rwest-newbiz-form-intro {
  margin-bottom: var(--wp--preset--spacing--12);
}

/* Lede measure re-scoped onto the section-intro block's lede (42rem layout
   threshold; centered by the block's is-align-center rule). The block carries
   the .rwest-newbiz-form-intro class (margin-bottom above). Eyebrow + heading
   now provided by the block. */
.rwest-newbiz-form-intro .wf-section-intro__lede {
  max-width: 42rem;
}

/* ─── Form shell ─────────────────────────────────────────────────────────── */

.rwest-newbiz-form {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--10);
}

/* Fieldset */
.rwest-newbiz-fieldset {
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--pure-white) 18%, transparent);
  padding: var(--wp--preset--spacing--8);
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--6);
  background: color-mix(in srgb, var(--wp--preset--color--pure-white) 3%, transparent);
}

/* Nested fieldset (budget, timeline) — no double border */
.rwest-newbiz-fieldset--nested {
  border: none;
  padding: 0;
  background: none;
  gap: var(--wp--preset--spacing--3);
}

/* Legend */
.rwest-newbiz-legend {
  font-family: var(--wp--preset--font-family--prata);
  font-size: var(--wp--preset--font-size--heading-sm);
  color: var(--wp--preset--color--pure-white);
  padding: 0 var(--wp--preset--spacing--2);
  letter-spacing: 0.02em;
  font-weight: 400; /* Prata 400-only */
}

/* Two-column row */
.rwest-newbiz-row {
  display: grid;
  gap: var(--wp--preset--spacing--5);
}

.rwest-newbiz-row--2 {
  grid-template-columns: 1fr 1fr;
}

/* Field wrapper */
.rwest-newbiz-field {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--2);
}

/* Conditional field (referral detail) */
.rwest-newbiz-field--conditional {
  margin-top: var(--wp--preset--spacing--4);
  padding-top: var(--wp--preset--spacing--4);
  border-top: 1px solid color-mix(in srgb, var(--wp--preset--color--pure-white) 10%, transparent);
}

/* Label */
.rwest-newbiz-label {
  display: block;
  font-family: var(--wp--preset--font-family--roboto);

  /* 0.85rem (13.6px) — wireframe NewBusiness.css line 113. No WP font-size token
     maps to 0.85rem (nearest are body-xs 0.75rem / body-sm 0.875rem); the wireframe
     uses a literal, an allowed untokenizable presentational value. */
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--wp--preset--color--pure-white) 75%, transparent);
}

/* Span-label (for non-<label> role descriptors) */
span.rwest-newbiz-label {
  display: block;
}

/* Input + textarea */
.rwest-newbiz-input,
.rwest-newbiz-textarea {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-md);
  line-height: 1.5;
  padding: var(--wp--preset--spacing--3) var(--wp--preset--spacing--4);
  background: color-mix(in srgb, var(--wp--preset--color--pure-white) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--pure-white) 20%, transparent);
  color: var(--wp--preset--color--pure-white);
  border-radius: 0;
  transition: border-color 150ms ease;
  width: 100%;
  box-sizing: border-box;
}

/* Focus: brand-aa border (4.664:1 on dark bg — AA pass) */
.rwest-newbiz-input:focus,
.rwest-newbiz-textarea:focus {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
  border-color: var(--wp--preset--color--brand-aa);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-newbiz-textarea {
  resize: vertical;
  min-height: 9rem;
}

/* Checkboxes grid */
.rwest-newbiz-checkgrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--wp--preset--spacing--2) var(--wp--preset--spacing--4);
}

/* Check + radio labels */
.rwest-newbiz-check,
.rwest-newbiz-radio {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--2);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-md);
  color: color-mix(in srgb, var(--wp--preset--color--pure-white) 87%, transparent);
  cursor: pointer;
  padding-block: var(--wp--preset--spacing--1);
}

/* accent-color: brand-aa so checkmarks/radio fills pass contrast on dark bg */
.rwest-newbiz-check input,
.rwest-newbiz-radio input {
  margin: 0;
  accent-color: var(--wp--preset--color--brand-aa);
  cursor: pointer;
}

.rwest-newbiz-radiogroup {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--1);
}

/* Submit area */
.rwest-newbiz-submit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--wp--preset--spacing--3);
  padding-top: var(--wp--preset--spacing--4);
}

/* Submit button */
.rwest-newbiz-submit-btn {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-md);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--wp--preset--spacing--4) var(--wp--preset--spacing--10);
  background: var(--wp--preset--color--brand-aa);
  color: var(--wp--preset--color--pure-white);
  border: none;
  cursor: pointer;
  transition: background 150ms ease;
}

.rwest-newbiz-submit-btn:hover:not(:disabled) {
  background: var(--wp--preset--color--brand);
}

/* Disabled state — submission pending */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-newbiz-submit-btn:disabled,
.rwest-newbiz-submit-btn[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-newbiz-submit-btn:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
}

.rwest-newbiz-submit-note {
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  color: color-mix(in srgb, var(--wp--preset--color--pure-white) 65%, transparent);
  margin: 0;
}

/* ≤ 44rem (~704px): two-column rows collapse to single column */
@container newbiz-form-section (max-width: 44rem) {
  .rwest-newbiz-row--2 {
    grid-template-columns: 1fr;
  }

  .rwest-newbiz-checkgrid {
    grid-template-columns: 1fr;
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .rwest-newbiz-input,
  .rwest-newbiz-textarea,
  .rwest-newbiz-submit-btn {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   "Not ready?" block
   Wireframe source: NewBusiness.css lines 198–211.
   -------------------------------------------------------------------------- */

.rwest-newbiz-notready {
  container-type: inline-size;
}

/* Eyebrow + heading + lede now provided by the rwest/section-intro block.
   Heading near-black + lede muted color re-scoped onto the block elements;
   the block→button gap is carried via the block's style.spacing.margin.bottom. */
.rwest-newbiz-notready .wf-section-intro__heading {
  color: var(--wp--preset--color--surface-3);
}

.rwest-newbiz-notready .wf-section-intro__lede {
  color: var(--wp--preset--color--gray-dark);
}

/* ============ sprint 2026-06-12 :: AA contrast on light surfaces ============ */

/* gray (#888) is AA-passing on black but 3.54:1 on white. Light contexts swap to
   gray-aa (#555). Tone-variable components key to is-style-light; always-light
   surfaces (legal, contact, new-business form, team spotlights) override directly. */
.is-style-light .rwest-location-meta__label.has-gray-color,
.is-style-light .rwest-location-section__tagline.has-gray-color,
.is-style-light .rwest-location-section__eyebrow.has-gray-color,
.rwest-team-spotlight-card__alt-title.has-gray-color,
.rwest-legal .has-gray-color,
.rwest-contact-addresses-meta__label.has-gray-color,
.rwest-newbiz-form .has-gray-color {
  /* stylelint-disable-next-line declaration-no-important -- WP preset color classes are !important */
  color: var(--wp--preset--color--gray-aa) !important;
}


/* Dark-office-tone overrides (sprint 2026-06-12): gray-aa is the on-light muted
   tone; on is-style-dark sections it drops to 2.66:1. gray (#888) is AA on dark. */
.rwest-location-section.is-style-dark .rwest-location-section__eyebrow,
.rwest-location-section.is-style-dark .rwest-location-section__tagline,
.rwest-location-section.is-style-dark .rwest-location-meta__label {
  color: var(--wp--preset--color--gray);
}

/* ============ sprint 2026-06-12 :: QA fixes ============ */

/* Contact offices: WP vertical-flex defaults to align-items:flex-start, which
   shrinks items to min-content (city abbreviations stacked one letter per line).
   Stretch the address items to the column width. */
.rwest-contact-address-list > .rwest-contact-address-item {
  align-self: stretch;
}


/* ============ state-review fixes (2026-06-12) ============ */

/* S-1: hero-primary had no perceptible hover (white stayed white; theme.json's
   brand-active hover loses to the variation's source order). Warm-white dims
   to the accent tone. */
/* stylelint-disable-next-line no-descending-specificity */
.is-style-hero-primary .wp-block-button__link {
  transition: background-color 150ms cubic-bezier(0.25, 0, 0.25, 1);
}

/* stylelint-disable-next-line no-descending-specificity */
.is-style-hero-primary .wp-block-button__link:hover {
  background-color: var(--wp--preset--color--accent-light);
}

/* S-2: featured card — propagate hover from the stretched image link to title
   + see-project arrow (mirrors the service/work-card :has cascade). */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card:has(a:hover) .wp-block-post-title a {
  color: var(--wp--preset--color--brand-aa);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-featured-card:has(a:hover) .rwest-featured-card__see-project a {
  transform: translateX(4px);
}

/* S-3: site-logo focus ring matched to the nav-link treatment (was the 1px UA
   blue, invisible on the dark header). */
/* stylelint-disable-next-line no-descending-specificity */
.wp-block-site-title a:focus-visible,
/* stylelint-disable-next-line no-descending-specificity */
.wp-block-site-logo a:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
}

/* S-4: pair card reads as interactive at card level. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card:has(a) {
  cursor: pointer;
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-pair-card:has(a:hover) .wp-block-post-title a {
  color: var(--wp--preset--color--brand-aa);
}

/* SF-2: 404 CTAs were on the UA blue ring. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-error-404 .wp-block-button__link:focus-visible {
  outline: 2px solid var(--wp--preset--color--pure-white);
  outline-offset: 2px;
}

/* Nav CTA ("Start a project") — boxed pill per the wireframe wf-nav-cta. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-site-nav .rwest-nav-cta a {
  border: 1px solid var(--wp--preset--color--pure-white);
  padding: var(--wp--preset--spacing--2) var(--wp--preset--spacing--4);
  transition: background-color 150ms cubic-bezier(0.25, 0, 0.25, 1), color 150ms cubic-bezier(0.25, 0, 0.25, 1);
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-site-nav .rwest-nav-cta a:hover {
  background-color: var(--wp--preset--color--pure-white);
  color: var(--wp--preset--color--black);
}


/* ============ state-review fixes round 2 (2026-06-12) ============ */

/* S2: the site-blocks root flow gap painted 24px page-background bars between
   header<->main and main<->footer (the template-level remnant of the
   black-bars report; post-content gap was fixed earlier). Header/main/footer
   butt together; sections own their own rhythm. */
/* stylelint-disable-next-line no-descending-specificity */
.wp-site-blocks > * + * {
  margin-block-start: 0;
}

/* S3: careers accordion summaries were on the UA blue ring. */
.rwest-careers-role > summary:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

/* N3: checkbox/radio focus to the brand ring for consistency. */
.rwest-newbiz-form input[type="checkbox"]:focus-visible,
.rwest-newbiz-form input[type="radio"]:focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-aa);
  outline-offset: 2px;
}

/* ==========================================================================
   Execution Row — rwest/execution-row
   ========================================================================== */

/*
 * Two-column image + copy row used in case studies.
 * Design source: Figma bQKW4b3uNMJEFBiyD7LGGd nodes 317:24802, 317:24815, 317:24817.
 *
 * Container model:
 *   .rwest-execution-row        — section root, container context
 *   .rwest-execution-row__cols  — core/columns (50/50, gap spacing-16≈60px)
 *   .rwest-execution-row__media-col — image half
 *   .rwest-execution-row__copy-col  — copy half
 *   .rwest-execution-row__copy      — inner flex-column group (gap spacing-6)
 *   .rwest-execution-row__eyebrow   — optional label: red r\ + uppercase text
 *   Reversed variant: pick the "Reversed" style on __cols (styles/reversed.json,
 *       emits is-style-reversed) → copy-left / image-right. No class typing.
 *
 * Breakpoints (per-component, empirical — css-conventions rule):
 *   < 42rem — stack columns vertically (media above copy).
 *   ≥ 42rem — side-by-side (default two-column WP Columns behavior).
 *
 * Token map:
 *   Section bg      → is-style-light (--wp--preset--color--white = #fdfbfa)
 *   Eyebrow mark    → --wp--preset--color--brand (#e74536)
 *   Eyebrow text    → --wp--preset--color--surface-3 (#111, uppercase, tracking 2.1px)
 *   Heading         → element h2 default (Prata 400 36px lh1.25, set globally via B2)
 *   Body            → body-md preset (Roboto 400 16px lh1.5)
 *   Section padding → spacing-20 (5rem ≈ 80px; Figma 72px)
 *   Column gap      → spacing-16 (4rem ≈ 64px; Figma 60px)
 *
 * Image placeholder:
 *   An empty <img src=""> causes WP to emit a bare <figure> with no <img>.
 *   The ::after rule below catches that and renders "Image pending" identically
 *   to the other placeholder treatments in this file.
 */

.rwest-execution-row {
  container-type: inline-size;
  container-name: execution-row;
}

/* Columns: equal-width two-up; align items center on the block axis */
.rwest-execution-row .rwest-execution-row__cols {
  align-items: center;
}

/* Reversed variant: swap column order (copy-left / image-right in execution-row).
   Generic core/columns block style (styles/reversed.json) → the editor toggles
   "Reversed" in the Styles panel instead of typing a class. Works on any
   core/columns; execution-row's container query (below) re-stacks it on narrow. */
.wp-block-columns.is-style-reversed {
  flex-direction: row-reverse;
}

/* Stack below ~42rem (Columns default already stacks, but confirm center-align) */
@container execution-row (max-width: 42rem) {
  .rwest-execution-row .rwest-execution-row__cols {
    flex-direction: column;
  }

  .rwest-execution-row .rwest-execution-row__cols.is-style-reversed {
    flex-direction: column;
  }
}

/* Image column: force the figure to fill its column; WP's block aspect-ratio
   attribute + object-fit handling on core/image handles the img sizing. */
.rwest-execution-row__media {
  width: 100%;
  margin-block: 0;
}

/* Image pending placeholder — WP strips an empty-src <img> AND its <figure>
   entirely, leaving a bare empty .rwest-execution-row__media-col column; hang the
   placeholder on the column (always survives), guarded so it disappears the moment
   a real image/figure lands. */
.rwest-execution-row__media-col:not(:has(figure), :has(img))::after {
  content: "Image pending";
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  width: 100%;
  background: color-mix(in srgb, var(--wp--preset--color--gray) 8%, transparent);
  border: 1px dashed var(--wp--preset--color--border-subtle);
  color: var(--wp--preset--color--gray-aa);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* Eyebrow: Roboto ExtraBold 14px / 2.1px tracking / brand-red r\ mark + surface-3 label */
.rwest-execution-row__eyebrow {
  font-weight: 800; /* Roboto ExtraBold — allowed per css-conventions (font-weight integer) */
  letter-spacing: 0.15em; /* ≈ 2.1px at 14px — allowed per css-conventions */
  text-transform: uppercase;
  color: var(--wp--preset--color--surface-3);
  margin-block: 0;
}

/* brand-aa (#D63A2D) meets 4.5:1 on the white section background;
   brand (#E74536) is 3.96:1 and fails WCAG AA. Same red tone, accessible. */
.rwest-execution-row__eyebrow-mark {
  color: var(--wp--preset--color--brand-aa);
}

/* Copy heading: inherits element h2 default (Prata 400, 36px, lh1.25 from B2 global fix).
   No override needed — the element style is the correct Figma display/md spec. */

/* ==========================================================================
   Image Showcase — rwest/image-showcase
   ========================================================================== */

/*
 * Full-width indented image section — one visual moment between copy blades.
 * Design source: Figma 317:25264 (Cinefi case study, node "copy block / image margin").
 *
 * Container model:
 *   .rwest-image-showcase        — section root (is-style-light, light bg)
 *   .rwest-image-showcase__frame — horizontal padding wrapper (spacing-16 each side)
 *   .rwest-image-showcase__slot  — inner group; carries the placeholder guard
 *   .rwest-image-showcase__img   — core/image (16:9, cover)
 *
 * Image pending placeholder:
 *   When src is empty, WP strips the figure. The ::after guard on __slot catches
 *   the empty state (no figure, no img) and renders "Image pending" until an asset lands.
 */

.rwest-image-showcase {
  container-type: inline-size;
  container-name: image-showcase;
}

.rwest-image-showcase__img {
  width: 100%;
  margin-block: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;

  /* img sizing handled by core/image block CSS via aspectRatio + scale="cover" attrs */
}

/* Image pending placeholder — mirrors execution-row and culture-snippet treatment */
.rwest-image-showcase__slot:not(:has(figure), :has(img))::after {
  content: "Image pending";
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  width: 100%;
  background: color-mix(in srgb, var(--wp--preset--color--gray) 8%, transparent);
  border: 1px dashed var(--wp--preset--color--border-subtle);
  color: var(--wp--preset--color--gray-aa);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ==========================================================================
   Location Leadership — rwest/page-locations (static leader cards per office)
   ========================================================================== */

/*
 * Section wrapper for the "Leadership in <city>" sub-section inside each
 * office blade. Contains the eyebrow label and a 3-up leader card grid.
 *
 * Container model: .rwest-location-section (the office blade) is the outer
 * container; .rwest-location-team-grid is the inner grid container.
 *
 * Cards are static (no Query Loop) — each is a core/group with a core/image,
 * core/heading (h3), and core/paragraph (title). CSS reuses .rwest-team-card
 * image/meta class contracts where possible; the static equivalent swaps the
 * query-loop blocks (wp-block-post-featured-image, wp-block-post-title) for
 * plain core/image and core/heading respectively.
 *
 * Allowed literals:
 *   aspect-ratio: 1 / 1 (image — layout threshold)
 *   overflow: hidden (image clip)
 *   3 / 1fr grid columns (fixed 3-up — location leadership never changes arity)
 *   56rem (layout-toggle threshold ≈ 896px section column)
 *   content: "Headshot pending" (placeholder ::after text)
 *   min-block-size: 14rem (placeholder height — consistent with image-slot rule)
 *   border-width: 1px (placeholder border — no WP preset border-width)
 */

/* ─── Section wrapper ────────────────────────────────────────────────────── */

.rwest-location-section__team {
  margin-block-start: var(--wp--preset--spacing--16); /* 4rem above the team band */
  container-type: inline-size;
  container-name: location-team;
}

/* Eyebrow — same visual contract as team-grid-section eyebrow */
.rwest-location-section__team-eyebrow {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  margin-block-end: var(--wp--preset--spacing--8); /* 2rem below eyebrow */
}

/* ─── 3-up leader grid ───────────────────────────────────────────────────── */

.rwest-location-team-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--wp--preset--spacing--8) var(--wp--preset--spacing--6); /* 2rem row / 1.5rem col */
}

/* [recipe:flow-gap-reset] — neutralize WP > * + * flow gap inside the grid group */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-location-team-grid > * {
  margin-block: 0;
}

/* ≤ 56rem (~896px section column): 3→1 column stack */
@container location-team (max-width: 56rem) {
  .rwest-location-team-grid {
    grid-template-columns: 1fr;
    max-width: 320px;
  }
}

/* ─── Static leader card ─────────────────────────────────────────────────── */

/* Reuses .rwest-team-card display:grid + grid-overlay mechanism (image + meta
   share grid-area 1/1). The card root class is .rwest-team-card so the wide-mode
   overlay gradient from the TeamCard section applies without duplication. */

/* Image wrapper — static core/image emits .wp-block-image > img; unlike Query
   Loop's .wp-block-post-featured-image, it is not a flex/grid item by default.
   Apply the same 1:1 aspect + overflow clip. */
.rwest-location-team-grid .rwest-team-card .wp-block-image {
  grid-area: 1 / 1;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  margin: 0; /* reset core/image bottom margin */
}

/* stylelint-disable-next-line no-descending-specificity */
.rwest-location-team-grid .rwest-team-card .wp-block-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ─── Placeholder (Esther Cheong — no headshot yet) ─────────────────────── */

/* An empty core/image has no <img> child — the :not(:has(img)) guard fires and
   renders a tinted box so the layout stays intact. Matches the image-slot rule
   below but scoped to leader cards only (prevents the global rule double-applying). */
.rwest-location-team-grid .rwest-team-card .wp-block-image:not(:has(img))::after {
  content: "Headshot pending";
  display: grid;
  place-items: center;
  aspect-ratio: 1 / 1;
  min-block-size: 14rem;
  background: color-mix(in srgb, var(--wp--preset--color--gray) 8%, transparent);
  border: 1px dashed var(--wp--preset--color--border-subtle);
  color: var(--wp--preset--color--gray-aa);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ─── Stacked leader card (wireframe parity) ─────────────────────────────── */

/* The wireframe Locations leadership uses a STACKED card at ALL widths — square
   image on top, serif (Prata) name + gray title BELOW — NOT the Team-page
   overlay. Force the shared .rwest-team-card meta below the image (grid row 2)
   at every width and restore the serif display name. (.rwest-team-card stays the
   overlay variant for the non-approved Team page; this override is scoped to the
   location grid only, so Team is untouched.) Previously this lived behind an
   @container ≤56rem query, so at the 1280 design width the overlay showed. */
/* stylelint-disable-next-line no-descending-specificity */
.rwest-location-team-grid .rwest-team-card .rwest-team-card__meta {
  grid-area: 2 / 1;
  align-self: auto;
  justify-content: flex-start;
  padding: var(--wp--preset--spacing--2) 0 var(--wp--preset--spacing--1);
  pointer-events: auto;
  z-index: auto;
  background: none;
  transition: none; /* base meta transitions background; nothing animates when stacked */
}

.rwest-location-team-grid .rwest-team-card__name {
  /* wireframe .wf-location-team-name: serif display (Prata), 1.4rem, #111.
     Family + size come from the block attributes (has-prata-font-family +
     has-heading-sm-font-size — WP emits those utilities !important, so they
     can't be overridden from here). This rule only forces weight 400 (over the
     base team-card 600 → no Prata faux-bold) and the dark color. */
  font-weight: 400;
  color: var(--wp--preset--color--surface-3);
}

.rwest-location-team-grid .rwest-team-card__title {
  /* wireframe .wf-location-team-title: 0.875rem, #666, ls 0.04em.
     Size comes from the block attribute (has-body-sm-font-size, !important) —
     only letter-spacing + color are set here. */
  letter-spacing: 0.04em;
  color: rgb(0 0 0 / 60%);
}

/* Tone-aware on dark office sections (NY) — mirror the wireframe's
   .wf-tone-gray name (#fff) + title (#aaa). Dark-on-dark would fail AA
   (caught by pa11y). Higher specificity (0,3,0) beats the light defaults. */
.rwest-location-section.is-style-dark .rwest-location-team-grid .rwest-team-card__name {
  color: var(--wp--preset--color--pure-white);
}

.rwest-location-section.is-style-dark .rwest-location-team-grid .rwest-team-card__title {
  color: rgb(255 255 255 / 70%); /* ≈ #b3b3b3 on dark — AA (≈8.6:1), wireframe #aaa */
}

/* ==========================================================================
   empty image-slot placeholders (align-review 2026-06-12)
   ========================================================================== */

/* Placeholder asset slots render as dead air: WP strips an empty-src <img>, and
   in column layouts strips the whole figure too — so the before/after + mosaic
   columns and the offices-preview figures end up empty (visually confirmed, not
   just probed: the climb-higher before/after rendered as a black void below its
   labels). An ::after box reserves the space and reads "Image pending"; the
   :not(:has(...)) guard removes it the moment real image content lands. */
.entry-content figure.wp-block-image:not(:has(img))::after,
.rwest-before-after__col:not(:has(figure))::after,
.rwest-campaign-mosaic__col:not(:has(figure))::after {
  content: "Image pending";
  display: grid;
  place-items: center;
  min-block-size: 14rem;
  margin-block-start: var(--wp--preset--spacing--4);
  background: color-mix(in srgb, var(--wp--preset--color--gray) 8%, transparent);
  border: 1px dashed var(--wp--preset--color--border-subtle);
  color: var(--wp--preset--color--gray-aa);
  font-family: var(--wp--preset--font-family--roboto);
  font-size: var(--wp--preset--font-size--body-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ==========================================================================
   Work feed — featured rhythm (every 7th card → full-width poster)
   ==========================================================================
   Work.tsx chunkForRhythm puts a featured card at index 0, 7, 14 → :nth-child(7n+1).
   The featured and pair cards carry IDENTICAL content (same Query Loop post-
   template), so this is a presentation difference → CSS, not a separate block.
   :nth-child counts DOM position, so featured slots stay fixed under the client-
   side filter (matching the wireframe, which chunks the full set).

   Placed at end-of-file because these are high-specificity scoped overrides of
   the pair-card base rules and must follow them (no-descending-specificity). */

/* The grid item spans both columns → full-width row. The extra block margin
   lifts the wireframe's looser feed rhythm (3rem around featured) over the 2rem
   grid gap shared by the pair rows. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) {
  grid-column: 1 / -1;
  margin-block: var(--wp--preset--spacing--4);
}

/* Promoted card → poster: copy-left (3fr) / media-right (4fr), dark surface,
   white text. Mirrors .rwest-featured-card.is-style-featured-poster, applied to
   the pair-card namespace (the flat post-template item). */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card {
  display: grid;
  grid-template-columns: 3fr 4fr; /* copy / media — layout proportion, allowed literal */

  /* spacer · title · tagline · caps · see-project · spacer. The 1fr spacers
     absorb the media's excess height so the copy stays a TIGHT cluster (row-gap
     only) and centers — instead of Grid distributing the taller media's height
     across the copy rows, which spread them evenly (53px gaps vs the wireframe's
     14-22px; caught by an element-gap probe 2026-06-13). */
  grid-template-rows: 1fr auto auto auto auto 1fr;

  /* row-gap 0.85rem = the wireframe meta gap (measured 14px). No --wp--preset--
     spacing token matches 0.85rem (nearest are 0.75/1rem), and Brandon's
     exact-parity directive takes precedence over snapping to a token here; if
     the convention should win instead, snap to spacing-3 (0.75rem). column-gap
     is the 2rem stage gap (spacing-8). */
  gap: 0.85rem var(--wp--preset--spacing--8);
  padding: var(--wp--preset--spacing--5); /* 20px — matches the wireframe media inset (measured) */
  background-color: var(--wp--preset--color--surface-3);
}

/* Pin each copy element to its row so the auto rows stay content-sized (tight)
   and the 1fr spacers take the slack. (title + tagline placement live with their
   type rules below to avoid duplicate selectors.) */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__caps {
  grid-column: 1;
  grid-row: 4;
}

/* The wireframe FeaturedCard has NO industry kicker (only pair cards do) — hide
   it on the promoted card. Pixel-diff caught it (2026-06-13): the extra kicker
   row pushed the card ~77px taller than the wireframe. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__kicker {
  display: none;
}

/* Media in the right column. The `.rwest-pair-card__media` element IS the
   core/post-featured-image <figure>, which carries an INLINE aspect-ratio:4/3
   (the block's aspectRatio attr) — so the poster's 16:10 crop must override it
   on the figure itself with !important (a nested-img selector matches nothing;
   pixel-diff 2026-06-13 caught the 4:3 render — 494px media vs the wireframe's
   ~386px — as the card's height excess). Natural aspect height (not stretched)
   so the card is media-driven like the wireframe, not copy-driven. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card .rwest-pair-card__media {
  grid-column: 2;
  grid-row: 1 / -1; /* full card height; its 16:10 aspect drives the card height */
  align-self: stretch;

  /* stylelint-disable-next-line declaration-no-important */
  aspect-ratio: 16 / 10 !important; /* override inline 4/3 on the figure */
  margin-block-end: 0; /* reset the stacked image-to-meta gap */
}

/* Copy rows: the grid row-gap handles rhythm; drop the stacked sibling margin. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card > * + * {
  margin-block-start: 0;
}

/* Oversized poster title (Prata) — mirrors the wireframe poster title exactly
   (WorkCard.css: clamp(1.85rem,3.5vw,3rem), line-height 1.2, letter-spacing
   -0.012em, max-width 18ch). The pair title carries .has-heading-sm-font-size,
   which core marks !important, so the override must match it. The max-width is
   load-bearing: without it the title fills the wide copy column and wraps 2 lines
   instead of the wireframe's 3 (pixel-diff caught it 2026-06-13). vw (not cqi)
   matches the source's viewport scaling so the size tracks 1:1. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__title {
  grid-column: 1;
  grid-row: 2;

  /* The wireframe's copy lives in a meta block with 1.5rem inner padding, which
     insets the title's right edge; ours has no such wrapper. This inset is
     LOAD-BEARING, not redundant with max-width: the copy column (~459px) is
     narrower than 18ch (~561px), so max-width never binds — without this inset
     the title fills the column and wraps 2 lines instead of the source's 3. */
  padding-inline-end: var(--wp--preset--spacing--6);

  /* stylelint-disable-next-line declaration-no-important */
  font-size: clamp(1.85rem, 3.5vw, 3rem) !important;
  line-height: 1.2;
  letter-spacing: -0.012em; /* untokenizable — allowed literal */
  max-width: 18ch;
}

/* Tagline: 1rem (body-md) on the poster — the pair card uses body-sm (14px);
   core marks the preset class !important so the override must too. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__tagline {
  grid-column: 1;
  grid-row: 3;

  /* stylelint-disable-next-line declaration-no-important */
  font-size: var(--wp--preset--font-size--body-md) !important;
  max-width: 38ch; /* wireframe poster tagline measure */
}

.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__title,
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__title a,
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__tagline,
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__kicker,
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__kicker a,
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__see-project {
  color: var(--wp--preset--color--white);
}

/* See-project type parity with the wireframe (line-height 1.5, 0.04em tracking)
   + grid placement (row 5, copy column). */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__see-project {
  grid-column: 1;
  grid-row: 5;
  line-height: 1.5;
  letter-spacing: 0.04em; /* untokenizable — allowed literal */
}

/* Cap chips: white border + white text on the dark surface. */
.rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card__caps a {
  color: var(--wp--preset--color--white);
  border-color: var(--wp--preset--color--white);
}

/* Stack the poster to media-over-copy at ≤ 56rem container. Breakpoint literal
   (layout threshold, not a token): mirrors the wireframe FeaturedCard, which
   stacks .wf-work-featured-card--poster .wf-work-featured-stage to a single
   column at @media (max-width: 768px). The poster li spans the full grid width
   (grid-column: 1 / -1) even after the pair grid goes 2-up at >36rem, so its
   3fr/4fr stage stays side-by-side into tablet widths — where the media's large
   min-content starves the `fr`-sized copy column to ~94px (probed 2026-06-14:
   container 40rem → meta 94px, 48rem → 167px). 56rem is the first container
   width where the 3fr copy column reads ≥ a pair-card width (~352px); below it,
   stack media-over-copy so the copy sizes to its content (no starved column,
   no blank space). */
@container work-grid (max-width: 56rem) {
  .rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card {
    grid-template-columns: 1fr;

    /* Drop the desktop poster's 1fr spacer rows when the card stacks. Those
       spacers exist to center the copy beside the tall media column in the
       side-by-side layout; once stacked (media on top via order:-1) they just
       expand into ~377px of empty space below the card (QA #74, 2026-06-22).
       `none` lets the rows auto-size to content — media + copy, tight. */
    grid-template-rows: none;
  }

  .rwest-work-grid .wp-block-post-template > li:nth-child(7n+1) .rwest-pair-card .rwest-pair-card__media {
    grid-column: 1;
    grid-row: auto;
    order: -1; /* media above copy when stacked */
  }
}

/* ==========================================================================
   Work feed — FilterBar grid reflow (end-of-file: high-specificity :has())
   ==========================================================================
   The FilterBar (rwest/filter-bar view.ts) hides non-matching cards by setting
   [hidden] on the .rwest-pair-card wrapper (the element carrying
   data-rwest-capability). That alone doesn't reflow the grid: [hidden] only
   hides the inner card (its grid <li> stays as an empty slot), and promoted
   featured cards set display:grid which overrides [hidden]. Hide the whole grid
   ITEM so the grid reflows AND featured cards hide. Placed at EOF — high
   specificity (:has + li) must follow the lower-specificity nth-child rules
   (no-descending-specificity). */
.rwest-work-grid .wp-block-post-template > li:has(.rwest-pair-card[hidden]) {
  /* stylelint-disable-next-line declaration-no-important */
  display: none !important; /* beat the featured li's grid-column/display */
}

/* ==========================================================================
   Cover Video Background — .rwest-cover-video-vm + .rwest-cover-video-poster
   Applies to any core/cover block where the author has set rwestBgType via
   the R/West Video Background editor panel (inc/cover-video.php + src/cover-video/).
   ========================================================================== */

/* Vimeo iframe cover-crop wrapper.
 * Positions the wrapper to fill the Cover block's full bounds (absolute,
 * inset:0). Overflow:hidden clips the oversized iframe to the section edges.
 * pointer-events:none prevents the iframe from capturing clicks/keyboard
 * focus from the content layer above it.
 * aria-hidden="true" is set on the element itself (inc/cover-video.php). */
.rwest-cover-video-vm {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;

  /* Make the wrapper a sizing context so the iframe can cover-crop relative to
   * the SECTION (container units) rather than the viewport. The old vh/vw cover
   * only filled when the Cover == viewport, so non-viewport-height heroes (e.g.
   * the home hero) letterboxed. inset:0 gives a definite block-size, so size is safe. */
  container-type: size;
}

/* Vimeo iframe — oversized and centered to cover-crop the SECTION (not the
 * viewport). Sized in container query units relative to .rwest-cover-video-vm
 * (container-type: size), so it always fills the Cover regardless of the Cover's
 * own height — fixing the letterbox on non-viewport-height heroes.
 * Geometry literals (layout thresholds, not design tokens — permitted per
 * css-conventions.md §Tokens "Untokenizable presentational values"):
 *   177.78cqh = (16/9) × 100cqh — min width to fill the container height at 16:9.
 *   56.25cqw  = (9/16) × 100cqw — min height to fill the container width at 16:9.
 * max() picks whichever dimension is needed to fully cover the container.
 * translate(-50%,-50%) re-centers the overflow; border:0 drops the iframe border. */
.rwest-cover-video-vm iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100cqw, 177.78cqh);
  height: max(100cqh, 56.25cqw);
  transform: translate(-50%, -50%);
  border: 0;
}

/* Poster image — covers the Cover bounds ON TOP of the iframe so first paint
 * shows the poster, never the Vimeo player's black load gap (QA #73). The
 * runtime in inc/cover-video.php adds .is-hidden to fade it out once the player
 * reports 'play'; if the player never loads (blocked/offline/JS off) the poster
 * simply stays — a graceful still instead of a black/error frame. Also serves
 * as the reduced-motion fallback (see @media block below).
 * z-index:0 sits ABOVE the iframe wrapper (z-index:auto, earlier in DOM) but
 * BELOW the Cover's dim overlay (.wp-block-cover__background, z-index:1) and the
 * content — so the dim tints the poster exactly like it tints the video, instead
 * of the poster covering the overlay and washing out the hero text (caught on a
 * real iPhone Safari session, 2026-06-22).
 * position:absolute + inset:0 fills the Cover section bounds.
 * object-fit:cover / object-position:center are layout geometry literals —
 * no --wp--preset--* equivalent; permitted per css-conventions.md. */
.rwest-cover-video-poster {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 1;
  transition: opacity 0.6s ease;
}

/* Faded out by the runtime once the Vimeo player reports it is playing. */
.rwest-cover-video-poster.is-hidden {
  opacity: 0;
  pointer-events: none;
}

/* prefers-reduced-motion — mandatory stop per css-conventions.md §Motion.
 * Hides autoplaying video/iframe; reveals the static poster fallback.
 * Uses viewport @media (not @container) — the section background is
 * page/template chrome, not a component-scoped property; viewport @media
 * is explicitly permitted for this altitude (css-conventions.md §Altitude). */
@media (prefers-reduced-motion: reduce) {
  /* Local video: hide the autoplay <video> element. */
  .rwest-hero .wp-block-cover__video-background,
  .rwest-media-blade .wp-block-cover__video-background {
    display: none;
  }

  /* Vimeo: hide the iframe wrapper entirely. */
  .rwest-cover-video-vm {
    display: none;
  }

  /* Poster fallback: the static still IS the background under reduced motion.
     Force it opaque even if the runtime added .is-hidden, and drop the fade. */
  .rwest-cover-video-poster,
  .rwest-cover-video-poster.is-hidden {
    opacity: 1;
    transition: none;
  }
}
