/*
Theme Name: Clever Dog Base
Theme URI: https://cleverdogdesign.com
Author: Clever Dog Design
Author URI: https://cleverdogdesign.com
Description: Clever Dog Design's lean core-block base theme — the foundation of the block-theme provisioning pipeline. Brand lands via theme.json preset overrides; layouts land as core-block patterns; every client site stays editable in the standard editor.
Requires at least: 6.5
Tested up to: 6.9
Requires PHP: 8.0
Version: 0.1.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: cleverdog-base
*/

/* =====================================================================
   EVERYTHING in this stylesheet colours/typesets through the theme.json
   PRESETS (var(--wp--preset--…)). No hardcoded brand hex anywhere —
   the engine rebrands a whole site by overriding preset VALUES only.
   Derived tones (lines, deep hovers, tints) use color-mix() over the
   presets so they re-derive automatically when the brand changes.
   ===================================================================== */

:root {
  --cdd-line: color-mix(in srgb, var(--wp--preset--color--primary) 14%, var(--wp--preset--color--base));
  --cdd-accent-deep: color-mix(in srgb, var(--wp--preset--color--accent) 82%, black);
  --cdd-primary-mid: color-mix(in srgb, var(--wp--preset--color--primary) 82%, white 6%);
  --cdd-radius: 14px;
  --cdd-ease: cubic-bezier(.22, .61, .36, 1);

  /* ══ #631 — THREE TOKENS FOR THREE RENDERING FAULTS ═══════════════════════════════════
     Rick measured all three on the live Hartley build, 1 Aug. Every one is CSS, none is
     content, which is why the copy, image and palette checks all pass the site clean.

     --cdd-num-ink   The step/benefit counters render the accent on `surface`. MEASURED:
                     #C25F1C on #EEF4F8 = 3.83:1. AA wants 4.5. `--cdd-accent-deep` already
                     existed, is the LIGHTEST darkened accent we define, and measures 5.29 on
                     surface and 5.87 on white — so this is a re-POINT, not a new colour, and
                     the accent keeps its character rather than being thrown away for a
                     neutral (#554 §1's rule).
     --cdd-band-gap  The vertical rhythm. The trust band carried `margin-bottom:
                     var(--wp--preset--spacing--80)` inline and NO margin-top, so it measured
                     20px above and 80.96px below — Rick's *"a thin white band above and a
                     much larger one below"*. One token, used for both edges, so the two can
                     no longer drift apart.
     --cdd-pill-gap  The gap in a wrapping pill row, so the row and its wrap use one value. */
  --cdd-num-ink: var(--cdd-accent-deep);
  /* ⚠ AND A SECOND INK, BECAUSE ONE CANNOT DO BOTH — this is #554 §1's rule arriving as a
     measurement rather than an opinion. The counters appear on a PALE band and on a DARK
     one, and NO single colour passes on both: accent-deep is 5.29 on #EEF4F8 and 2.38 on
     #082F45; white is 13.97 on the dark and 1.11 on the pale. The dark-band check caught a
     THIRD contrast failure nobody had reported — muted on secondary, measuring 2.78:1.
     75% accent is the LIGHTEST tint of the client's own accent that clears 4.5 on the dark
     band (4.86). Derived by walking the mix, not chosen — same method as the overlay. */
  --cdd-num-ink-dark: color-mix(in srgb, var(--wp--preset--color--accent) 75%, white);
  --cdd-band-gap: var(--wp--preset--spacing--60, 56px);
  --cdd-pill-gap: var(--wp--preset--spacing--30, 16px);
  /* #957 rule 8 — the floor for a band's own top and bottom padding. A heading must never sit
     against the edge of the band above it, and one token is what stops the two edges drifting. */
  --cdd-band-pad: var(--wp--preset--spacing--50, 48px);

  /* ══ #945 — TWO TOKENS FOR THE COLOUR CONTRACT, BOTH DERIVED RATHER THAN PICKED ═══════
     --cdd-ink-accent      The accent where the accent is the TEXT — an eyebrow, a
                           "More about…" link, a badge. `--wp--custom--ink-accent` is
                           computed per client by `BuildQuality::inkOn()` and emitted by
                           `cdd_merge_brand()`; it is the client's own accent darkened by the
                           smallest step that clears 4.5:1 on BOTH `base` and `surface`.
                           MEASURED on the first real build: `#C25F1C` on white is 4.24:1 —
                           it misses by a fifth of a point, and a neutral would have been a
                           large visible change to fix a small deficit (§1: their palette is
                           neither waved through nor thrown away). `--cdd-accent-deep`, the
                           existing fixed 82% mix, is the fallback for builds that predate it.

     --cdd-cover-dim-floor The LIGHTEST overlay that gets white text to AA over the worst
                           case behind it — a WHITE photograph. **DERIVED, and the derivation
                           is `BuildQuality::overlayAlphaFor('#ffffff', '#000000')` = 0.54**,
                           which `CoverOverlayFloorIsDerivedTest` recomputes and pins to this
                           number so the two can never drift.
                           ⚠ THE PATTERN LIBRARY PICKED ITS OWN: 72 covers across the
                           harvested patterns carry dimRatio values of 0, 10, 40, 50, 55, 60,
                           70, 80 and 90 — every one chosen by eye. The first real build used
                           40 and 50 and measured 4.09:1 and 3.76:1 on white copy over the
                           photograph. This is a FLOOR, not a value: a cover a designer dimmed
                           harder keeps its own number. */
  --cdd-ink-accent: var(--wp--custom--ink-accent, var(--cdd-accent-deep));
  --cdd-cover-dim-floor: .54;
}

/* ══ #631 (1) — A COUNTER IS A NUMBER, AND A NUMBER NEVER BREAKS ════════════════════════
   MEASURED at 1440 and 390 with a real browser: "01", "02" and "03" each rendered across
   TWO LINES — `0` above, `1` below — in a box 21.9px wide holding 22px type. "04" was the
   only one that held, and only at 1440; at 390 it broke too.

   ⚠ THE CAUSE IS NOT A MISSING WIDTH, IT IS `word-break` INHERITED FROM WORDPRESS.
   Computed style on those elements: `overflow-wrap: break-word` AND `word-break: break-word`,
   neither of them ours. That permission is what lets a two-character token split at all; the
   flex item's `min-width: auto` then squeezes the box under its own min-content and the break
   is taken. **Setting a width alone would not have fixed it** — the browser would still have
   been allowed to break, and a narrower column would bring it straight back.

   So all three are stated: the number may not break, it may not be squeezed under two
   digits, and its digits are tabular so 01 and 04 measure the same. */
.cdd-num {
  white-space: nowrap;
  word-break: keep-all;
  overflow-wrap: normal;
  min-width: 2.5ch;
  font-variant-numeric: tabular-nums;
  color: var(--cdd-num-ink);
}

/* The dark band's counters take the light ink. `.cdd-on-dark` is the convention #619
   established for exactly this — the band, not the element, decides what is readable. */
.cdd-on-dark .cdd-num,
.has-secondary-background-color .cdd-num,
.has-primary-background-color .cdd-num {
  color: var(--cdd-num-ink-dark);
}

/* ══ #631 (2) — ONE ROW; AND WHEN IT CANNOT BE ONE, EVEN ROWS ═══════════════════════════
   MEASURED: four trust pills at 1440 laid out 3 + 1 — three across and one stranded below.
   The container is a wrapping flex row of natural-width items, so the fourth pill falls
   whenever the four natural widths exceed the line. Rick: *"they should sit on one row and
   wrap evenly when they cannot."*

   `flex: 1 1 auto` makes the four share the line, so at 1440 they sit on one row. Below the
   breakpoint a 50% basis forces 2 + 2 — **even by construction rather than by luck of the
   text lengths**, which is the half a plain `flex-wrap: wrap` can never promise. */
/* ══ ⭐⭐ #957 RULE 10 — A SHORT LABEL IN A PILL NEVER RUNS PAST TWO LINES ═════════════════
   Rick, #955 rule 10: *"A short label in a designed container — pill, chip, badge, button —
   NEVER RUNS PAST TWO LINES. Shorten the label or widen the container."*

   ⚠⚠ FOUND TWICE AND FIXED ZERO TIMES: §F reported it on 2 Aug and #948's `line-count` rule
   measured it again on 3 Aug at 100% precision. **Both batches were scoped to DETECT.**
   Detecting is not fixing, and a fault that ships three times after being found twice is
   worse than one nobody has noticed.

   ⚠ AND THE CAUSE WAS #631's OWN FIX. `flex: 1 1 0; min-width: 0` makes every pill EXACTLY
   THE SAME WIDTH regardless of its label, which is what produced even rows — and it is also
   what makes the longest label wrap while the shortest sits in air. MEASURED on the real
   captured build, and the row is not as wide as anyone assumed:

     @1440  row  680px → 3 pills × 155px → "🛡 Locally owned & operated" over **3 LINES**
     @900   row  680px → 2 × 320px       → 1 line   (the NARROW layout was the good one)
     @390   row  359px → 2 × 159px       → 2 lines  (allowed)
     @320   row  294px → 2 × 127px       → **3 LINES**

   **The row is 680px, not the viewport.** Three equal shares of it cannot hold a 24-character
   label, and no amount of even-ness fixes that — the container has to get wider, which is
   exactly what rule 10 says to do.

   ⚠ SO IT IS A GRID, AND THE TRACK HAS A FLOOR. `auto-fit` puts in as many equal tracks as
   fit at the floor and no more, so the row still fills evenly by construction — #631's whole
   point, kept — while a row too narrow for three drops to two, and one too narrow for two
   drops to one. `min(100%, …)` is what stops the floor overflowing a container narrower than
   the floor itself, which is the 320px case.

   The floor is DERIVED, not chosen: the longest label the trust row carries is ~26 characters
   at 14px, which measures ~190px on one line, and 15rem/240px holds it with room for the
   pill's own padding. It is re-measured by `PillNeverRunsPastTwoLinesTest` at four widths
   against the real captured page, so it cannot drift. */
/* ⚠⚠ THE DOUBLED CLASS IS LOAD-BEARING AND THE FIRST ATTEMPT PROVED IT. WordPress gives the
   block its own layout rule — `.wp-container-core-group-is-layout-<hash> { display: flex }`,
   one class, (0,1,0) — and **prints it AFTER the theme stylesheet**. At equal specificity the
   later rule wins, so `display: grid` was overridden while `grid-template-columns` (which the
   flex rule does not set) came through untouched. Measured: the computed style read
   `display: flex` beside a perfectly good `repeat(auto-fit, …)` doing nothing.
   `.cdd-pill-row.cdd-pill-row` is (0,2,0) and outranks it. Same shape as #945's hide rule
   losing to `.has-base-color` — importance ties, specificity decides. */
.cdd-pill-row.cdd-pill-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  justify-content: center;
  gap: var(--cdd-pill-gap);
}
.cdd-pill-row > * {
  min-width: 0;
  justify-content: center;
}

/* ══ #631 (3) — A BAND'S TWO EDGES COME FROM ONE TOKEN ══════════════════════════════════
   They were 20px and 80.96px. They are now the same value because they are the same value —
   the only way two numbers stay equal is to stop being two numbers. */
.cdd-band {
  margin-top: var(--cdd-band-gap);
  margin-bottom: var(--cdd-band-gap);
}

body {
  -webkit-font-smoothing: antialiased;
  line-height: 1.6;
}

/* =====================================================================
   FULL-WIDTH SECTION MODEL (5 Jul fix — Sydney bed rendered ~680 narrow).
   Every pattern section = an alignfull OUTER band + a .cdd-inner rail.
   The rail is constrained HERE, deterministically, so the 1180 layout
   never depends on WP's generated layout CSS; and an alignfull band must
   never be capped by a parent rail (the old page.html wrapped post-content
   in a constrained main, which clamped the whole page to contentSize).
   ===================================================================== */
.cdd-inner {
  max-width: 1180px;
  margin-left: auto;
  margin-right: auto;
  padding-left: clamp(20px, 4vw, 32px);
  padding-right: clamp(20px, 4vw, 32px);
}
main > .alignfull,
.wp-block-post-content > .alignfull {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}

/* 6 Jul render fix #4: theme.json blockGap injected 1.25rem WHITE strips
   between adjacent alignfull colour bands (header→hero, section→section).
   Bands butt seamlessly, like the demos; spacing INSIDE sections is
   authored explicitly in this file. */
main.wp-block-group > * { margin-block-start: 0 !important; }
.wp-block-post-content > .alignfull { margin-block-start: 0 !important; margin-block-end: 0 !important; }
/* 6 Jul (the gaps that SURVIVED the first fix): WP's ROOT layout also gaps
   the top-level template elements — .wp-site-blocks > * + * gets the block
   gap, which is the header→hero strip and the footer→last-band strip. Zero
   it at the root; every visible rhythm inside sections is authored here. */
.wp-site-blocks > * { margin-block-start: 0 !important; margin-block-end: 0 !important; }
body { margin: 0; }
/* The under-footer / overscroll canvas matches the dark chrome instead of
   flashing white on short pages and rubber-band scrolls. */
html { background: var(--wp--preset--color--secondary); }

h1, h2, h3, h4 {
  line-height: 1.05;
  letter-spacing: -.01em;
}

/* ------ signature safety-stripe divider (Local Service motif) ------
   Authored as a wp:separator (an empty group collapses/normalises away —
   the 4 Jul "renders flat" issue). Every core/theme separator default is
   overridden hard: no border, no opacity fade, no max-width cap. */
hr.cdd-stripe,
.cdd-stripe {
  display: block;
  height: 8px;
  margin: 0;
  border: 0;
  max-width: none;
  width: 100%;
  opacity: 1;
  background: repeating-linear-gradient(-45deg,
    var(--wp--preset--color--accent) 0 18px,
    var(--wp--preset--color--primary) 18px 36px);
}

/* 6 Jul render fix #8 — the stripe is a TRADES motif token. Non-trades
   recipes (body class cdd-recipe-* set from the provisioning recipe) get a
   quiet accent hairline instead of hazard stripes. */
/* SEAM-CORNER FIX (7 Jul batch 9, recurring): the stripe is the ONE piece
   of between-band chrome, and a seam divider's unpainted window exposed
   its diagonals as a stray orange/navy sliver (hero→trust on cd269902).
   Rule: a stripe immediately followed by a seam overlay never renders —
   the curve IS that boundary's motif. Blocky/no-seam builds keep it. */
hr.cdd-stripe:has(+ .cdd-shape),
.cdd-stripe:has(+ .cdd-shape) { display: none; }

body[class*="cdd-recipe-"]:not(.cdd-recipe-local_service) hr.cdd-stripe,
body[class*="cdd-recipe-"]:not(.cdd-recipe-local_service) .cdd-stripe {
  height: 4px;
  /* SYMMETRIC fade (6 Jul): the old left-to-right gradient hit the viewport
     edge at full-strength accent and read as a pink BLEED, not a divider.
     Centre-weighted, it reads deliberate — and the section break stays. */
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--wp--preset--color--accent) 15%, transparent),
    var(--wp--preset--color--accent) 25% 75%,
    color-mix(in srgb, var(--wp--preset--color--accent) 15%, transparent));
}

/* ------ image slots: never look broken ------ */
/* A raw pattern (inserted before provisioning fills it) still carries a
   {{TOKEN}} src — paint the slot as a quiet surface tile instead of a
   broken-image icon. The bundled placeholder SVG covers the filled-but-
   no-photo-yet case; this covers the not-even-filled case. */
.wp-block-image img[src^="{{"],
.wp-block-image img[src=""] {
  min-height: 220px;
  width: 100%;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--wp--preset--color--surface) 88%, var(--wp--preset--color--muted)),
    var(--wp--preset--color--surface));
  color: transparent;
}

/* ------ eyebrow kicker ------ */
.cdd-eyebrow {
  font-family: var(--wp--preset--font-family--heading);
  text-transform: uppercase;
  font-weight: 700;
  letter-spacing: .16em;
  font-size: 13px;
  /* #945 — 13px bold in the raw accent measured 4.24:1 on white and needs 4.5. The derived
     ink is the SAME colour with the smallest darkening that clears it; see --cdd-ink-accent. */
  color: var(--cdd-ink-accent);
}

/* ------ HERO ------ */
.cdd-hero {
  background:
    radial-gradient(1100px 600px at 78% -10%, var(--cdd-primary-mid), var(--wp--preset--color--secondary) 60%);
  color: var(--wp--preset--color--base);
  overflow: hidden;
}
.cdd-hero h1,
.cdd-hero h2,
.cdd-hero h3 { color: var(--wp--preset--color--base); }
.cdd-hero .cdd-hero-sub { color: var(--wp--preset--color--accent); }
.cdd-hero h1,
.cdd-hero .cdd-hero-sub {
  font-size: clamp(52px, 8vw, 92px);
  font-weight: 800;
}
.cdd-hero-flag {
  display: inline-block;
  background: color-mix(in srgb, var(--wp--preset--color--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--accent) 40%, transparent);
  color: color-mix(in srgb, var(--wp--preset--color--accent) 35%, white);
  padding: 7px 14px;
  border-radius: 100px;
  font-size: 13px;
  font-weight: 600;
}
.cdd-hero-lead {
  font-size: 19px;
  color: color-mix(in srgb, var(--wp--preset--color--base) 78%, var(--wp--preset--color--muted));
  max-width: 33ch;
}
.cdd-hero-chips {
  color: color-mix(in srgb, var(--wp--preset--color--base) 62%, var(--wp--preset--color--muted));
  font-size: 14.5px;
  font-weight: 500;
}
.cdd-hero-card {
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--wp--preset--color--primary) 92%, white),
    var(--wp--preset--color--secondary));
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--base) 10%, transparent);
  border-radius: 20px;
  box-shadow: 0 30px 60px -20px rgba(0, 0, 0, .6);
}
.cdd-hero-card h3 { font-size: 24px; }
.cdd-hero-card img { border-radius: 12px; }
.cdd-hero-rate {
  color: var(--wp--preset--color--accent);
  font-weight: 700;
  font-size: 15px;
}
.cdd-hero-card ul {
  list-style: none;
  padding-left: 0;
  display: grid;
  gap: 11px;
  color: color-mix(in srgb, var(--wp--preset--color--base) 82%, var(--wp--preset--color--muted));
  font-size: 15px;
}
.cdd-hero-card ul li::before {
  content: "✓ ";
  color: var(--wp--preset--color--accent);
  font-weight: 800;
}

/* ------ TRUST BAR ------ */
.cdd-trust {
  background: var(--wp--preset--color--secondary);
  color: color-mix(in srgb, var(--wp--preset--color--base) 85%, var(--wp--preset--color--muted));
  border-top: 1px solid color-mix(in srgb, var(--wp--preset--color--base) 7%, transparent);
}
.cdd-trust p {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
}
.cdd-trust .cdd-stars,
.cdd-quote-stars {
  color: var(--wp--preset--color--accent);
  letter-spacing: 2px;
}

/* ------ section heads ------ */
.cdd-sec-head h2 {
  font-size: clamp(36px, 4.6vw, 54px);
  font-weight: 800;
  color: var(--wp--preset--color--primary);
}
.cdd-sec-head p {
  font-size: 18px;
  color: var(--wp--preset--color--muted);
}
.cdd-on-dark h2,
.cdd-on-dark h3 { color: var(--wp--preset--color--base); }
.cdd-on-dark p { color: color-mix(in srgb, var(--wp--preset--color--base) 65%, var(--wp--preset--color--muted)); }

/* ------ service cards ------ */
.cdd-card {
  background: var(--wp--preset--color--base);
  border: 1px solid var(--cdd-line);
  border-radius: var(--cdd-radius);
  transition: transform .25s var(--cdd-ease), box-shadow .25s, border-color .25s;
  height: 100%;
}
/* #364 DEFECT 4 — the "Good to know" aside overflowing its own section.
   Rick: "we've been having the same issue right from the start, since the
   first demos we ever did." Never fixed because never diagnosed — only seen.

   MEASURED on the real served /treatments/ page at 1440: the aside runs
   1014→1598 while the row it belongs to runs 641→1225. A 373px spill, which
   is why a tall empty white box sits over the next section's heading.

   CAUSE: `height: 100%` above. It is correct for a card that is the ONLY
   thing in its column — that is what makes card grids line up — but this
   aside SHARES its column with a 327px photo above it. 100% resolves against
   the column's stretched height, so the card claims the whole row height
   starting from below the photo: 327 + 24 (its margin) + 22 (the in-flight
   reveal transform) = 373.

   A card that has siblings must size to its own content. Card grids put one
   card per column and are untouched. */
.cdd-card:not(:only-child) { height: auto; }
.cdd-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 40px -22px color-mix(in srgb, var(--wp--preset--color--primary) 40%, transparent);
}
.cdd-card h3 {
  font-size: 22px;
  font-weight: 700;
  color: var(--wp--preset--color--primary);
}
.cdd-card p {
  font-size: 15px;
  color: var(--wp--preset--color--muted);
}

/* ⚠⚠ #619 (#607) — THE DARK BAND, AND THE CASCADE FAULT THAT MADE IT
   UNREADABLE. MEASURED FROM RENDERED PIXELS on run rh-260731-163450, the
   "Common plumbing problems we solve" cards:

     card heading  #1F5078 on #0C1B2A = 2.05:1   (large text needs 3.0)
     card body     #54697C on #0C1B2A = 3.06:1   (body needs 4.5)

   Rick: "light blue check over dark blue in boxes."

   THE CAUSE IS SOURCE ORDER, NOT A MISSING RULE. The correct rule already
   existed — `.cdd-on-dark h3 { color: base }` at the top of this file — and
   `.cdd-card h3 { color: primary }` immediately above has THE SAME
   SPECIFICITY (0,1,1) and comes LATER, so it wins. A card inside a dark band
   therefore kept its light-band text colours over a dark background. Same
   story for `p` and `muted`.

   ⚠ AND NO PALETTE CLAUSE COULD EVER HAVE CAUGHT THIS. The palette guarantees
   PAIRINGS; this is a pattern putting `primary` text on a `secondary` band —
   a pairing the palette was never asked about. #607's own point (c).

   The fix is scoped and explicit rather than a specificity war: a card inside
   a dark band takes the COMPUTED readable colour for that band
   (`--wp--custom--text-on-secondary`, which SiteAssembler already calculates
   per client and cdd_merge_brand already emits), falling back to `base`.

   ⚠⚠ #945 — AND THAT ANSWER WAS COMPUTED AGAINST THE WRONG BACKGROUND, WHICH
   IS WHY RICK OPENED THE FIRST REAL BUILD AND SAW *"four large white cards
   containing nothing"*.

   `.cdd-card` sets `background: var(--wp--preset--color--base)` — it is a WHITE
   card. It does not take the band's colour; it PAINTS OVER IT. So the rule below
   read the heading against the dark band, painted it `text-on-secondary` (white),
   and put **white type on a white card**. MEASURED at 1440 and 390 on the served
   page: **1.00:1** — the number that means text the same colour as what is behind
   it. The body copy went the same way at 78% of white, and the only thing left
   visible in the card was the `More about… →` link, which is the accent. That is
   Rick's description, to the letter.

   ⚠ AND NO STRING-LEVEL CHECK COULD SEE IT. The markup is perfect: heading
   present, full paragraph present, link present, alt text present. The card is
   empty only in PIXELS, which is the whole argument for the rendered pass.

   ⚠ #619's REASONING STANDS AND IS NOT BEING REVERSED. Source order really did
   make `.cdd-card h3` beat `.cdd-on-dark h3`, and a card inside a dark band
   really did need its own rule. What was wrong was WHICH background the answer
   was computed against. **A surface that paints itself establishes its own
   context** — so a card takes the readable colour for the CARD, and
   `text-on-secondary` goes back to serving the band around it, where it was
   always right. */
.cdd-on-dark .cdd-card h3 {
  color: var(--wp--custom--text-on-base, var(--wp--preset--color--primary));
}
.cdd-on-dark .cdd-card p {
  color: var(--wp--preset--color--muted);
}
.cdd-card-hot { background: var(--wp--preset--color--primary); border-color: var(--wp--preset--color--primary); }
.cdd-card-hot h3 { color: var(--wp--preset--color--base); }
.cdd-card-hot p { color: color-mix(in srgb, var(--wp--preset--color--base) 65%, var(--wp--preset--color--muted)); }
.cdd-card-badge {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  /* #945 — 11px is the smallest accent-as-text on the site and gets the same derived ink.
     It was not in the measured 26 only because this build had no highlighted card. */
  color: var(--cdd-ink-accent);
}

/* ------ about + guarantee ------ */
.cdd-about-photo img { border-radius: 20px; }
.cdd-guarantee {
  background: var(--wp--preset--color--surface);
  border-left: 4px solid var(--wp--preset--color--accent);
  border-radius: 0 12px 12px 0;
}
.cdd-guarantee h4 {
  color: var(--wp--preset--color--primary);
  font-size: 19px;
  margin: 0 0 2px;
}
.cdd-guarantee p { font-size: 15px; margin: 0; }

/* ------ service areas (dark) ------ */
.cdd-areas { background: var(--wp--preset--color--primary); color: var(--wp--preset--color--base); }
.cdd-area {
  background: color-mix(in srgb, var(--wp--preset--color--base) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--base) 10%, transparent);
  border-radius: 10px;
  font-weight: 600;
  font-size: 16px;
  transition: background .2s;
}
.cdd-area:hover { background: color-mix(in srgb, var(--wp--preset--color--accent) 16%, transparent); }
.cdd-area p { margin: 0; padding: 15px 18px; }
.cdd-area p::before { content: "📍 "; }
.cdd-areas-note { color: color-mix(in srgb, var(--wp--preset--color--base) 65%, var(--wp--preset--color--muted)); }
.cdd-areas-note strong { color: var(--wp--preset--color--base); }

/* ------ testimonials ------ */
.cdd-quote {
  background: var(--wp--preset--color--base);
  border: 1px solid var(--cdd-line);
  border-radius: var(--cdd-radius);
  height: 100%;
}
/* #480 — the row carries the number of reviews the client actually has: three,
   two or one. A single card left to span 1180px reads as a layout that broke,
   so it is centred at a readable measure. Two share the width as they always
   did. Nothing here pads the row out — the section is dropped entirely at
   zero (cdd:block HAS_TESTIMONIALS). */
.cdd-quotes .wp-block-column:only-child { max-width: 680px; margin-inline: auto; }
.cdd-quote p { font-size: 16.5px; }
.cdd-quote-who { font-size: 14px; color: var(--wp--preset--color--muted); }
.cdd-quote-who strong { color: var(--wp--preset--color--primary); font-size: 15px; }
.cdd-quote-who a { color: inherit; }
/* #447 — the review-source notice. Quiet, but never hidden: Google requires the
   source and the ordering/filtering disclosure to be visible beside the reviews,
   so this must not be display:none'd or collapsed to zero. Empty for every
   source other than Google, where the paragraph renders with nothing in it. */
.cdd-review-source { font-size: 13px; color: var(--wp--preset--color--muted); margin-top: 22px; }
.cdd-review-source:empty { display: none; }

/* ------ contact / CTA ------ */
.cdd-contact { background: var(--wp--preset--color--surface); }
.cdd-crow {
  border-top: 1px solid var(--cdd-line);
  font-size: 16px;
  color: var(--wp--preset--color--muted);
}
.cdd-crow strong {
  display: block;
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--heading);
  font-size: 18px;
  font-weight: 700;
}
.cdd-emerg {
  background: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
  border-radius: 12px;
}
.cdd-emerg p { margin: 0; }
.cdd-emerg strong { font-family: var(--wp--preset--font-family--heading); font-size: 18px; }
.cdd-form-card {
  background: var(--wp--preset--color--base);
  border: 1px solid var(--cdd-line);
  border-radius: 18px;
  box-shadow: 0 24px 50px -30px color-mix(in srgb, var(--wp--preset--color--primary) 40%, transparent);
}
.cdd-form-card h3 {
  font-size: 26px;
  font-weight: 800;
  color: var(--wp--preset--color--primary);
}
.cdd-form-slot { margin-top: 18px; }
/* The pre-#19 "form goes here" state — an intentional labelled box, only
   when the slot holds the placeholder (a real embed carries no
   .cdd-form-pending and gets none of this chrome). */
.cdd-form-pending {
  display: block;
  border: 2px dashed color-mix(in srgb, var(--wp--preset--color--primary) 25%, var(--wp--preset--color--surface));
  border-radius: 12px;
  background: var(--wp--preset--color--surface);
  padding: 34px 26px;
  text-align: center;
  font-size: 15px;
  color: var(--wp--preset--color--muted);
}
.cdd-form-pending strong {
  display: block;
  margin-bottom: 6px;
  font-family: var(--wp--preset--font-family--heading);
  font-size: 18px;
  color: var(--wp--preset--color--primary);
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* ------ buttons: solid accent + ghost outline (core button styles) ------ */
.wp-block-button__link {
  font-weight: 700;
  border-radius: 12px;
  transition: transform .2s, background .2s, border-color .2s;
}
.wp-block-button .wp-block-button__link:hover { transform: translateY(-2px); }
.cdd-btn-ghost .wp-block-button__link {
  background: transparent;
  border: 2px solid color-mix(in srgb, var(--wp--preset--color--base) 28%, transparent);
  color: var(--wp--preset--color--base);
}
.cdd-btn-ghost .wp-block-button__link:hover {
  border-color: var(--wp--preset--color--base);
  background: color-mix(in srgb, var(--wp--preset--color--base) 6%, transparent);
}

/* ------ pattern chrome: spacing lives HERE, not in per-block style
   attributes — hand-authored pattern markup stays valid-by-construction
   (no attr/inline-style pairs to keep in sync). ------ */
.cdd-section { padding: 92px 0; }
.cdd-hero { padding: 88px 0 96px; }
.cdd-trust { padding: 22px 0; }
.cdd-sec-head { max-width: 640px; margin-bottom: 52px; }
.cdd-sec-head-center { margin-left: auto; margin-right: auto; }
.cdd-services { background: var(--wp--preset--color--surface); }
.cdd-card { padding: 26px 24px; }
.cdd-hero-card { padding: 30px; }
.cdd-quote { padding: 30px; }
.cdd-guarantee { padding: 20px 22px; margin-top: 26px; }
.cdd-form-card { padding: 32px; }
.cdd-crow { padding: 18px 0; margin: 0; }
.cdd-crow a { display: block; }
.cdd-emerg { padding: 18px 20px; margin-top: 8px; }
.cdd-areas-note { margin-top: 28px; font-size: 16px; }

/* ------ interior page header (service / about / contact pages) ------ */
.cdd-page-header {
  background:
    radial-gradient(900px 420px at 80% -20%, var(--cdd-primary-mid), var(--wp--preset--color--secondary) 62%);
  color: var(--wp--preset--color--base);
  padding: 64px 0 60px;
  overflow: hidden;
}
.cdd-page-header h1 {
  color: var(--wp--preset--color--base);
  font-size: clamp(40px, 6vw, 68px);
  font-weight: 800;
  margin: 14px 0 10px;
}
/* #364 DEFECT 3 — the lead paragraph shares the H1's left edge.
   MEASURED on the real served /treatments/ page at 1440: H1 left 155px,
   paragraph left 382px — a 227px indent, with the chips line underneath back
   at 155. Rick: "you got right text going over to one side and then it comes
   back."
   CAUSE: the paragraph is the only thing in this header with a max-width, and
   WordPress's `is-layout-constrained` parent auto-centres any child narrower
   than the content width via `margin-inline: auto`. (1116 - 661) / 2 = 227.4.
   The max-width stays — a 58ch measure is right for reading. The auto-centring
   goes, because nothing else in this header is centred.

   `!important` is not laziness here and it is the reason this survived from
   the first demos. WordPress's own rule is:

     .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull))
     { margin-left: auto !important; margin-right: auto !important; }

   It ships `!important`, so NO ordinary declaration can win, at any
   specificity. Verified against the served page with CSS.getMatchedStylesForNode
   — the plain `margin-inline: 0` was matched and lost. */
.cdd-page-header .cdd-hero-lead { max-width: 58ch; margin-inline: 0 !important; }

/* ------ service detail bands ------ */
.cdd-svc-who { background: var(--wp--preset--color--surface); }
.cdd-svc-expect { background: var(--wp--preset--color--primary); color: var(--wp--preset--color--base); }
.cdd-svc-list {
  list-style: none;
  padding-left: 0;
  display: grid;
  gap: 12px;
  font-size: 16.5px;
  color: color-mix(in srgb, var(--wp--preset--color--contrast) 80%, var(--wp--preset--color--muted));
}
.cdd-svc-list li {
  border-top: 1px solid var(--cdd-line);
  padding-top: 12px;
}
.cdd-svc-list li::before {
  content: "✓ ";
  color: var(--wp--preset--color--accent);
  font-weight: 800;
}
.cdd-svc-note { font-size: 15px; color: var(--wp--preset--color--muted); margin-top: 18px; }
/* #945 — measured 4.24:1 at 15px on five service pages, both widths. Same accent-as-text
   case as the eyebrow and the card link, and it takes the same derived ink. */
.cdd-svc-note a { color: var(--cdd-ink-accent); font-weight: 600; }
.cdd-svc-facts { margin-top: 24px; }
.cdd-svc-facts-list {
  list-style: none;
  padding-left: 0;
  display: grid;
  gap: 10px;
  font-size: 15px;
  color: var(--wp--preset--color--muted);
}
.cdd-svc-facts-list li::before {
  content: "✓ ";
  color: var(--wp--preset--color--accent);
  font-weight: 800;
}

/* ------ what-to-expect steps (dark band) ------ */
.cdd-step {
  background: color-mix(in srgb, var(--wp--preset--color--base) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--base) 10%, transparent);
  border-radius: var(--cdd-radius);
  padding: 26px 24px;
  height: 100%;
}
.cdd-step h3 { font-size: 21px; color: var(--wp--preset--color--base); }
.cdd-step p { font-size: 15px; }
/* ⚠ #631 — THE SAME MEASUREMENT CAUGHT OUR OWN COUNTER, WHICH NOBODY HAD REPORTED.
   `.cdd-step-num` was `secondary` on `accent`: #082F45 on #C25F1C = **3.29:1**, and the ink
   the browser actually computed measured 2.64. Even white on the raw accent is only 4.24 —
   **there is no ink that passes on that background**, so the BACKGROUND is what had to move.
   `--cdd-accent-deep` with white measures 5.87 and is the same token the wrapping counter now
   uses, so the two counters agree instead of each being darkened by hand.

   It also takes the no-break rules: it holds "1" today and would split on "10". */
.cdd-step-num {
  display: inline-block;
  min-width: 38px;
  height: 38px;
  line-height: 38px;
  padding: 0 6px;
  box-sizing: border-box;
  text-align: center;
  border-radius: 100px;
  white-space: nowrap;
  word-break: keep-all;
  overflow-wrap: normal;
  font-variant-numeric: tabular-nums;
  background: var(--cdd-accent-deep);
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--heading);
  font-weight: 800;
  font-size: 18px;
  margin: 0 0 6px;
}

/* ------ home how-it-works: light-band variant of the steps ------ */
.cdd-how .cdd-step {
  background: var(--wp--preset--color--surface);
  border-color: var(--cdd-line);
}
.cdd-how .cdd-step h3 { color: var(--wp--preset--color--primary); }
.cdd-how .cdd-step p { color: var(--wp--preset--color--muted); }

/* ------ home service-card "more" links + about values band ------ */
.cdd-card-more { margin-top: 10px; }
.cdd-card-more a {
  /* #945 — 14px bold accent on a white card measured 4.24:1 and needs 4.5. Same colour,
     smallest darkening that clears it. */
  color: var(--cdd-ink-accent);
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
}
.cdd-card-more a:hover { text-decoration: underline; }
.cdd-card-hot .cdd-card-more a { color: var(--wp--preset--color--accent); }
.cdd-values { background: var(--wp--preset--color--surface); }
.cdd-values .cdd-guarantee { max-width: 760px; }

/* ------ mini FAQ ------ */
.cdd-faq-item {
  border-top: 1px solid var(--cdd-line);
  padding: 22px 0;
  max-width: 760px;
}
.cdd-faq-item h3 {
  font-size: 20px;
  font-weight: 700;
  color: var(--wp--preset--color--primary);
  margin: 0 0 8px;
}
.cdd-faq-item p { font-size: 16px; color: var(--wp--preset--color--muted); margin: 0; }

/* ------ interior CTA band ------ */
.cdd-cta-band {
  background:
    radial-gradient(800px 380px at 20% -30%, var(--cdd-primary-mid), var(--wp--preset--color--secondary) 65%);
  color: var(--wp--preset--color--base);
  padding: 72px 0;
  text-align: center;
}
.cdd-cta-band h2 {
  color: var(--wp--preset--color--base);
  font-size: clamp(32px, 4.4vw, 48px);
  font-weight: 800;
}
.cdd-cta-band p {
  color: color-mix(in srgb, var(--wp--preset--color--base) 72%, var(--wp--preset--color--muted));
  font-size: 18px;
  max-width: 56ch;
  margin: 12px auto 22px;
}
.cdd-cta-band .wp-block-buttons { justify-content: center; }

/* ------ header / footer chrome (all spacing HERE, none in part attrs) ------ */
.cdd-site-header {
  /* SOLID (6 Jul render fix #3): the translucent gamble made nav contrast
     depend on whatever the header floated over (invisible on light 404s). */
  background: var(--wp--preset--color--secondary);
  /* no border-bottom (6 Jul): the 1px light line read as a hairline SEAM
     between the header and the hero photo — the bands meet clean instead. */
  backdrop-filter: blur(10px);
  position: sticky;
  top: 0;
  z-index: 50;
}
.cdd-header-bar { padding-top: 14px; padding-bottom: 14px; gap: 18px; }
.cdd-header-brand { gap: 12px; }
/* ══ #932 — THE WORDMARK IS THE DOMINANT ELEMENT IN THE HEADER ══
   RICK, 2 Aug, on the first real built site: "If it's just a wordmark then the
   size of it is way too small. Nobody would know that's a logo. The font of the
   logo is actually smaller than the menu text."

   MEASURED ON THAT SITE BEFORE CHANGING ANYTHING (1440px):
     · nav links            15px, weight 500
     · site-title (text)    22px, weight 800 — but display:none, 0×0
     · what actually shows  a 175×44px PNG wordmark → three words across 175px,
                            so the glyphs read ~10-12px: SMALLER THAN THE MENU.

   THE RULE, NOT THE INSTANCE. Both the text wordmark and the image are now sized
   as a RELATIONSHIP to the nav, never as a fixed pixel value — a fixed size is
   what broke here and it breaks again the first time a business has a long name.
   `--cdd-nav-size` is the single definition both the nav and the brand read, so
   they cannot drift apart. */
:root {
  --cdd-nav-size: 15px;
  /* Floor 1.6× the nav so a wordmark can never read as menu furniture; ceiling
     2.6× so a five-word name does not push the header apart. `clamp` with a vw
     middle term is what lets a LONG name shrink toward the floor and a SHORT one
     take the ceiling — the behaviour a fixed size cannot have. */
  --cdd-wordmark-size: clamp(calc(var(--cdd-nav-size) * 1.6), 2.4vw, calc(var(--cdd-nav-size) * 2.6));
}
/* An IMAGE wordmark gets the same floor. `max-height` alone let a wide name be
   squeezed into a 44px box until its type was smaller than the menu beside it;
   a min-height keeps the mark legible and lets the header grow instead. */
.cdd-site-header .wp-block-site-logo img {
  min-height: calc(var(--cdd-nav-size) * 2.2);
  max-height: calc(var(--cdd-nav-size) * 3.6);
  width: auto;
}
.cdd-site-header .wp-block-site-title a {
  color: var(--wp--preset--color--base);
  /* #932 — the client's own logo font when they asked for one, the heading
     preset when they did not. `--cdd-logo-font` is written per client into the
     merged theme.json; unset, this is byte-identical to what shipped before. */
  font-family: var(--cdd-logo-font, var(--wp--preset--font-family--heading));
  font-weight: 800;
  font-size: var(--cdd-wordmark-size);
  line-height: 1.05;
  text-decoration: none;
}
/* 6 Jul: NO uploaded logo → a DELIBERATE brand mark, never a blank space.
   WP stamps body.wp-custom-logo only when a real logo exists; without it
   the site-title becomes a palette-coloured badge in the heading font —
   holds the logo's space, looks intentional. The go-live logo gate and
   the Production work item still stand; this is staging dignity only. */
body:not(.wp-custom-logo) .cdd-site-header .wp-block-site-title a {
  display: inline-block;
  background: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--secondary);
  padding: 8px 14px;
  border-radius: 10px;
  /* #932 — was a fixed 18px, which is the same fault one rule along: it
     overrode the relationship above and pinned the badge near menu size. */
  font-size: var(--cdd-wordmark-size);
  line-height: 1.15;
  letter-spacing: .03em;
  text-transform: uppercase;
}
body:not(.wp-custom-logo) .cdd-site-header .wp-block-site-title a:hover {
  filter: brightness(1.08);
}
/* 7 Jul: a REAL logo replaces the wordmark entirely — never both. The
   header part always renders both blocks; when body.wp-custom-logo is
   stamped the site-title must vanish (the double-identity bug on
   cd260032: approved logo image + leftover wordmark text side by side).
   The text wordmark is ONLY the no-logo fallback above. */
body.wp-custom-logo .cdd-site-header .wp-block-site-title {
  display: none;
}
.cdd-site-header .wp-block-navigation a {
  position: relative;
  color: color-mix(in srgb, var(--wp--preset--color--base) 82%, var(--wp--preset--color--muted));
  font-weight: 500;
  /* #932 — ONE definition. The wordmark is sized from this, so a nav change
     moves the wordmark with it and the relationship cannot silently invert. */
  font-size: var(--cdd-nav-size);
  text-decoration: none;
  transition: color .15s ease;
}
/* 6 Jul nav states: accent slide-underline on hover; the CURRENT page sits
   bold + underlined — real interaction states, demo-grade, palette-aware. */
.cdd-site-header .wp-block-navigation a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 2px;
  background: var(--wp--preset--color--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .18s ease;
}
.cdd-site-header .wp-block-navigation a:hover { color: var(--wp--preset--color--accent); }
.cdd-site-header .wp-block-navigation a:hover::after { transform: scaleX(1); }
.cdd-site-header .wp-block-navigation a[aria-current="page"],
.cdd-site-header .wp-block-navigation .current-menu-item > a {
  color: var(--wp--preset--color--base);
  font-weight: 700;
}
.cdd-site-header .wp-block-navigation a[aria-current="page"]::after,
.cdd-site-header .wp-block-navigation .current-menu-item > a::after { transform: scaleX(1); }

/* 6 Jul: the Services DROPDOWN — a solid brand panel so nested service
   links read cleanly over anything; the slide-underline stays a top-level
   affordance only (it looks broken inside a stacked list). */
.cdd-site-header .wp-block-navigation .wp-block-navigation__submenu-container {
  background: var(--wp--preset--color--secondary);
  border: 1px solid color-mix(in srgb, var(--wp--preset--color--base) 12%, transparent);
  border-radius: 12px;
  padding: 8px;
  min-width: 250px;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, .55);
}
.cdd-site-header .wp-block-navigation .wp-block-navigation__submenu-container a {
  padding: 9px 12px;
  border-radius: 8px;
}
.cdd-site-header .wp-block-navigation .wp-block-navigation__submenu-container a::after { display: none; }
.cdd-site-header .wp-block-navigation .wp-block-navigation__submenu-container a:hover {
  background: color-mix(in srgb, var(--wp--preset--color--accent) 18%, transparent);
  color: var(--wp--preset--color--base);
}

/* Buttons everywhere get a consistent hover (palette-safe: brightness, no
   hardcoded colour) — Call now / Get in touch / pattern CTAs alike. */
.wp-block-button__link {
  transition: filter .15s ease, transform .12s ease;
}
.wp-block-button__link:hover {
  filter: brightness(1.12);
  transform: translateY(-1px);
}
.cdd-header-call .wp-block-button__link {
  padding: 10px 20px;
  font-size: 15px;
  white-space: nowrap;
}

.cdd-site-footer {
  background: var(--wp--preset--color--secondary);
  color: color-mix(in srgb, var(--wp--preset--color--base) 56%, var(--wp--preset--color--muted));
  font-size: 15px;
}
/* ⚠⚠ #945 §D.1 — THE FOOTER HEADINGS WERE INVISIBLE ON EVERY SITE THIS THEME SERVES.
   Rick, on the first real build: *"Get in touch", "Opening hours", "Licensed & accredited"
   are dark blue on the dark blue footer. The body beneath them is white and reads fine.*
   MEASURED on the served page at 1440 and 390: **1.52:1**, where 18px bold needs 4.5.

   THE CAUSE IS AN ABSENCE, NOT A WRONG VALUE. `theme.json` sets
   `styles.elements.heading.color.text` to `primary` for the whole site, and the footer is
   `secondary`. `parts/footer.html` marks all three headings `.cdd-footer-h` — a class that
   reads as though it carries the answer and **has no rule anywhere in this stylesheet**.
   The rule was never written; the class has been decorative since the day it was added.

   ⚠ SCOPED TO THE FOOTER, NOT TO THE CLASS. A heading added to the footer tomorrow without
   remembering the class must not be able to come out unreadable — which is exactly how the
   three above happened. The colour is the computed `text-on-secondary` that `SiteAssembler`
   was already producing per client. */
.cdd-site-footer :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--custom--text-on-secondary, var(--wp--preset--color--base));
}
.cdd-footer-bar { padding-top: 52px; padding-bottom: 40px; gap: 24px; }

/* =====================================================================
   ⭐⭐ #998 — TAP TARGETS. FOUR CONTROLS, EVERY CLIENT SITE, 52 OF THE 52
   REMAINING `tap_target_aa` FAULTS ON THE DELIVERED BUILD.
   =====================================================================

   MEASURED on cd260803163531, 7 pages × 3 widths (1440/390/320), the sweep's own
   `boxes` payload run through `ReviewRules::tapTargets()`:

     21 rows   93×19   the FOOTER phone link, all three widths
     21 rows  137×21   the FOOTER site-title link, all three widths
      7 rows   10×10   the desktop submenu chevron, 1440 only
      3 rows  ~17px    a standalone "More about X →" link on one line
     ── 52, and the four groups account for all of them.

   ⚠⚠ THE HEADER IS NOT INVOLVED, AND THE ROW THAT RAISED THIS SAID IT WAS.
   The header phone is **157×44 and already passes** at both widths (it is a
   `wp-block-button__link` — a BUTTON, protected by the style guide, and it is
   not touched). The header site title is **393×56 at 1440 and 282×44 at 390**
   and passes at every width. **Both failing controls are in the FOOTER**, and a
   fix aimed at the header would have changed a passing control and left 42
   failing rows exactly where they were.

   ⚠ EVERY TARGET HERE IS 44px, NOT 24px, AND THAT IS DERIVED RATHER THAN
   PREFERRED. 24 is the AA bar and 44 is our house target; a control that lands
   between them stops being a FAULT and becomes a NOTE. Clearing only to 24
   would have moved 42 rows out of `tap_target_aa` and straight into
   `tap_target_house` — the note count would have gone from 78 to ~120 while the
   report claimed a fix. **Reaching 44 removes them from both lists**, which is
   the only outcome that is honest in the summary as well as in the detail.

   ⚠ AND IT IS HIT AREA, NEVER TYPE SIZE. Every rule below adds `min-height` and
   padding and centres the label inside it. **Not one changes a font-size**, so
   the page reads exactly as designed and only the touchable box grows.
   `inline-flex` is what lets a `min-height` apply at all — the failing elements
   are `display: inline`, whose height is a FONT fact and cannot be set. */

/* (1) THE FOOTER PHONE. `tel:` on a phone is the single most consequential
   control on a trades site, and it was 19px tall. */
.cdd-footer-phone a,
.cdd-footer-email a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* (2) THE FOOTER SITE TITLE — the link home, 21px tall at every width. */
.cdd-footer-biz .wp-block-site-title a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* (3) THE DESKTOP SUBMENU CHEVRON — 10×10, and it is a REAL control: it carries
   `aria-label="Services submenu"` and its own click handler, so it is correctly
   collected as tappable and this is a theme fix rather than an instrument one.

   ⚠ 24, NOT 44, AND THIS IS THE ONE DELIBERATE EXCEPTION ON THIS LIST. The nav
   links it sits between are 24px tall; a 44px chevron would be the tallest thing
   in the bar and would set the height of the whole header. It clears AA, it
   matches its own siblings, and it therefore lands in the note band beside them
   — which is where the rest of the desktop nav already sits. Reported, not hidden. */
.wp-block-navigation__submenu-icon {
  min-width: 24px;
  min-height: 24px;
}

/* (4) A LINK THAT IS THE WHOLE PARAGRAPH IS A CALL TO ACTION, NOT A WORD IN A
   SENTENCE.

   ⚠ THE ROW READ THIS ONE BACKWARDS AND THE MEASUREMENT SAYS SO. It reported
   *"17px at 390 but 39px at 320 — the narrower width behaves"*. It does not. An
   inline element's rect height is the union of its LINE BOXES, so it measures
   ~17px on ONE line and ~39px on TWO — **the 39px ones are the links that
   WRAPPED**, which is worse, not better. "More about hot water →" is 17px at 390
   AND at 320. **The number was counting lines, not hit area** — the same inline
   box artefact #1000 removed from `protrusion`.

   ⚠ SO WHY IS IT A FAULT AT ALL, when a link in a sentence is exempt under SC
   2.5.8 Inline? Because it is not in one. The whole paragraph is the link
   (`p > strong > a`, and the paragraph's entire text is the link's text), so
   there is no non-target text for it to be constrained by, and
   `inlineInSentence` correctly declines to exempt it. It is a standalone CTA on
   a service card and it deserves a real box.

   ⚠ THE SELECTOR IS DELIBERATELY ABOUT SHAPE, NOT ABOUT A CLASS. This markup is
   pattern- and AI-composed, so it carries no class of ours to hook — the theme's
   own `.cdd-card-more` is not what the generator emits. "A paragraph whose only
   content is a link" is a structural fact that holds however the copy is
   written, and the sibling links inside `p.cdd-svc-note` are NOT only-children,
   so they stay inline and stay exempt. */
.entry-content p > a:only-child,
.entry-content p > strong:only-child > a:only-child {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* =====================================================================
   ⭐ #945 §G.3 — "ALL THE TEXT DOWN THE BOTTOM NOT LINING UP PROPERLY."

   ⚠ RICK'S DIAGNOSIS ("it's right on the edge — container padding is
   missing or lost at that breakpoint") IS NOT WHAT THE PAGE MEASURES, AND
   THE MEASUREMENT IS RECORDED HERE SO NOBODY CHASES THE PADDING AGAIN.
   Measured on the live built site at 1440 / 1024 / 768 / 390, every
   `.cdd-inner` rail — the footer's included — is inset identically to the
   sections above it: 130px at 1440, and the clamp padding (32 / 30.7 / 20)
   at the rest. The gutter is present and correct at every width.

   WHAT IS ACTUALLY WRONG IS THE ALIGNMENT, WHICH IS WHAT HE SAW.
   `justify-content: space-between` on three columns of UNEQUAL WIDTH puts
   the first hard left, the last hard right, and the middle wherever the
   content happens to leave it. Measured at 1440, the three column starts
   were 162 / 554 / 1081 — no rhythm, and the last one flush against the
   rail's right edge with nothing beyond it. That reads exactly as "right
   on the edge", and the §E fault made it worse: the third column held only
   an empty "Licensed & accredited" heading, so it collapsed to the width of
   its own words and was thrown even harder to the right.

   Equal tracks line them up, at any column count, at every width, and the
   columns then sit on the same rhythm as the content above.
   ===================================================================== */
/* ⭐⭐ #957 RULE 8 — EVERY BAND HAS TOP AND BOTTOM PADDING.
   Rick, #955 rule 8: *"Get in touch and Opening hours literally sitting on the border of the
   picture above."* MEASURED: this rule set a grid, a gap and an alignment and **no padding at
   all**, so the first heading in the footer began at the exact pixel the band above ended.
   `.cdd-footer-bar` below it has always had 52/40; the columns had nothing, and a gap between
   COLUMNS is not padding above the BAND.
   The value is `--cdd-band-pad` so the floor is one number rather than a number per band —
   the same argument #631 made for `--cdd-band-gap`, which is why the two edges of a band can
   no longer drift apart. */
.cdd-site-footer .cdd-footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 32px 48px;
  align-items: start;
  padding-block: var(--cdd-band-pad);
}
@media (max-width: 599px) {
  .cdd-site-footer .cdd-footer-cols { grid-template-columns: 1fr; }
}
.cdd-site-footer a { color: inherit; }
.cdd-site-footer a:hover { color: var(--wp--preset--color--accent); }
.cdd-site-footer .wp-block-site-title a {
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--heading);
  font-weight: 800;
  text-decoration: none;
}
.cdd-site-footer .wp-block-site-tagline {
  margin-top: 6px;
  color: color-mix(in srgb, var(--wp--preset--color--base) 56%, var(--wp--preset--color--muted));
}
.cdd-footer-credit { margin: 0; align-self: flex-end; }

/* ------ TRADES LONG (screenshot pipeline #1, 7 Jul) ------
   The plumber-PDF rhythm: problems + systems share ONE primary band (the
   dark super-section), about+areas is a light two-column, benefits is an
   image-left bullet band, and the quote CTA is the split image/pitch
   panel the optional parallax treatment moves. */
.cdd-problems,
.cdd-systems { background: var(--wp--preset--color--primary); }
.cdd-problems { padding-bottom: 44px; }
.cdd-systems { padding-top: 0; }
.cdd-system-card h3 { font-size: 18px; }
.cdd-system-card p { font-size: 14px; }

.cdd-areas-card {
  background: var(--wp--preset--color--surface);
  border: 1px solid var(--cdd-line);
  border-radius: 18px;
  padding: 30px;
  box-shadow: 0 24px 50px -30px color-mix(in srgb, var(--wp--preset--color--primary) 40%, transparent);
}
.cdd-areas-card h3 {
  font-size: 24px;
  font-weight: 800;
  color: var(--wp--preset--color--primary);
  margin: 0 0 6px;
}
.cdd-areas-card > p { font-size: 15px; color: var(--wp--preset--color--muted); }
.cdd-areas-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}
.cdd-areas-chips .cdd-area {
  margin: 0;
  padding: 10px 14px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--wp--preset--color--primary);
  background: color-mix(in srgb, var(--wp--preset--color--primary) 6%, var(--wp--preset--color--base));
  border: 1px solid var(--cdd-line);
  border-radius: 10px;
}
.cdd-areas-chips .cdd-area::before { content: "📍 "; }
.cdd-areas-card .cdd-areas-note {
  margin-top: 20px;
  font-size: 15px;
  color: var(--wp--preset--color--muted);
}
.cdd-areas-card .cdd-areas-note strong { color: var(--wp--preset--color--primary); }

.cdd-benefits-photo img { border-radius: 20px; }
.cdd-benefits-list {
  list-style: none;
  padding-left: 0;
  display: grid;
  gap: 14px;
  font-size: 16px;
  color: color-mix(in srgb, var(--wp--preset--color--contrast) 80%, var(--wp--preset--color--muted));
}
.cdd-benefits-list li::before {
  content: "✓ ";
  color: var(--wp--preset--color--accent);
  font-weight: 800;
}
.cdd-benefits-list li strong { color: var(--wp--preset--color--primary); }

.cdd-quote-cta {
  background:
    radial-gradient(900px 460px at 15% -20%, var(--cdd-primary-mid), var(--wp--preset--color--secondary) 62%);
  color: var(--wp--preset--color--base);
  padding: 88px 0;
  overflow: hidden;
}
.cdd-qcta-media {
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, .55);
}
.cdd-qcta-media img { display: block; width: 100%; border-radius: 0; }
.cdd-qcta-panel h2 {
  color: var(--wp--preset--color--base);
  font-size: clamp(34px, 4.6vw, 52px);
  font-weight: 800;
}
.cdd-qcta-panel p {
  color: color-mix(in srgb, var(--wp--preset--color--base) 72%, var(--wp--preset--color--muted));
  font-size: 18px;
  max-width: 48ch;
  margin: 12px 0 22px;
}

/* ------ STANDARD DELIGHT (7 Jul): sticky call + rating badge ------
   Rendered by functions.php's wp_footer hook from the cdd_phone_* /
   cdd_rating_* options the inject step stamps — no option, no element.
   The call button carries a gentle pulse (killed under reduced motion);
   body.cdd-has-sticky pads the footer so the bar never hides content. */
.cdd-sticky-call {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 60;
  background: var(--wp--preset--color--accent);
  /* #945 — white on this client's accent measured 4.24:1 at 16px bold, and the engine had
     ALREADY computed the answer: `--wp--custom--text-on-accent` was emitted on every build
     and read by nothing. Same seam #554 §5 found for `text_on` itself. */
  color: var(--wp--custom--text-on-accent, var(--wp--preset--color--base));
  font-family: var(--wp--preset--font-family--heading);
  font-weight: 700;
  font-size: 16px;
  text-decoration: none;
  padding: 13px 30px;
  border-radius: 100px;
  white-space: nowrap;
  box-shadow: 0 12px 28px -10px rgba(0, 0, 0, .45);
  animation: cdd-call-pulse 2.6s var(--cdd-ease) infinite;
}
.cdd-sticky-call:hover { filter: brightness(1.1); color: var(--wp--custom--text-on-accent, var(--wp--preset--color--base)); }
@keyframes cdd-call-pulse {
  0%   { box-shadow: 0 12px 28px -10px rgba(0, 0, 0, .45), 0 0 0 0 color-mix(in srgb, var(--wp--preset--color--accent) 45%, transparent); }
  70%  { box-shadow: 0 12px 28px -10px rgba(0, 0, 0, .45), 0 0 0 14px transparent; }
  100% { box-shadow: 0 12px 28px -10px rgba(0, 0, 0, .45), 0 0 0 0 transparent; }
}
.cdd-sticky-rating {
  position: fixed;
  left: 18px;
  bottom: 18px;
  z-index: 60;
  display: grid;
  gap: 1px;
  background: var(--wp--preset--color--base);
  border: 1px solid var(--cdd-line);
  border-radius: 12px;
  padding: 10px 14px;
  box-shadow: 0 12px 28px -12px rgba(0, 0, 0, .35);
  font-size: 12px;
  color: var(--wp--preset--color--muted);
  line-height: 1.35;
}
.cdd-sticky-rating strong {
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--heading);
  font-size: 17px;
}
.cdd-sticky-rating .cdd-sticky-stars {
  color: var(--wp--preset--color--accent);
  letter-spacing: 2px;
  font-size: 13px;
}
body.cdd-has-sticky .cdd-site-footer { padding-bottom: 76px; }
@media (max-width: 640px) {
  .cdd-sticky-call {
    left: 12px;
    right: 12px;
    bottom: 12px;
    transform: none;
    text-align: center;
  }
  .cdd-sticky-rating { bottom: 72px; left: 12px; padding: 8px 12px; }
  body.cdd-has-sticky .cdd-site-footer { padding-bottom: 96px; }
}
@media (prefers-reduced-motion: reduce) {
  .cdd-sticky-call { animation: none; }
}

/* ------ OPTIONAL MOTION (upsell family, 7 Jul): quote-CTA parallax ------
   OFF by default: only body.cdd-motion-parallax (the cdd_motion_parallax
   option) arms it, functions.php's tiny scroll script drives --cdd-plx,
   and reduced-motion kills the transform entirely. The scale gives the
   shift headroom so edges never expose. */
.cdd-motion-parallax .cdd-qcta-media img:not(.cdd-parallax-figure),
.cdd-motion-parallax .cdd-parallax-media > img:not(.cdd-parallax-figure),
.cdd-motion-parallax .cdd-parallax-media .wp-block-image img:not(.cdd-parallax-figure) {
  transform: translate3d(0, var(--cdd-plx, 0px), 0) scale(1.14);
  will-change: transform;
}
/* Layered parallax (7 Jul feature layer): an optional transparent
   foreground figure drifts the OPPOSITE way to its background layer
   (--cdd-plxf, driven by the same scroll script). Perfectly still —
   base transform only — when the motion flag is off. */
.cdd-qcta-media,
.cdd-parallax-media { position: relative; }
.cdd-parallax-figure {
  /* PANEL-LAYERING FIX (7 Jul batch 9): the figure lives INSIDE the
     panel — anchored to its bottom edge (physically grounded by the
     crop) instead of overhanging two edges like a sticker, smaller,
     with a tighter contact shadow. Its drift is now the SAME direction
     as the background at half speed (multiplane depth), not a 2×
     counter-motion. */
  position: absolute;
  right: 5%;
  bottom: 0;
  width: 34%;
  max-width: 300px;
  pointer-events: none;
  filter: drop-shadow(0 10px 16px rgba(0, 0, 0, .45));
}
.cdd-motion-parallax .cdd-parallax-figure,
.cdd-motion-parallax .cdd-parallax-media img.cdd-parallax-figure,
.cdd-motion-parallax .cdd-qcta-media img.cdd-parallax-figure {
  transform: translate3d(0, var(--cdd-plxf, 0px), 0);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .cdd-motion-parallax .cdd-qcta-media img,
  .cdd-motion-parallax .cdd-parallax-media > img,
  .cdd-motion-parallax .cdd-parallax-media .wp-block-image img,
  .cdd-motion-parallax .cdd-parallax-figure { transform: none; will-change: auto; }
}

/* ------ SMART STICKY HEADER (feature layer, 7 Jul batch 7) ------
   body.cdd-smart-header + html.cdd-js (the head script's no-JS gate):
   the header floats transparent over the dark hero, then solidifies
   (.cdd-header-solid, toggled by the scroll script) with a soft shadow.
   Without JS — or without the flag — the original solid sticky header
   stands untouched. The opening dark band gets taller top padding so
   its content clears the now out-of-flow header. */
.cdd-js body.cdd-smart-header .cdd-site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: transparent;
  backdrop-filter: none;
  transition: background .35s ease, box-shadow .35s ease;
}
.cdd-js body.cdd-smart-header .cdd-site-header.cdd-header-solid {
  background: var(--wp--preset--color--secondary);
  box-shadow: 0 12px 30px -18px rgba(0, 0, 0, .55);
}
.cdd-js body.cdd-smart-header .cdd-hero { padding-top: 164px; }
.cdd-js body.cdd-smart-header .cdd-page-header { padding-top: 140px; }

/* ------ SCROLL REVEAL (feature layer, 7 Jul batch 7) ------
   The reveal class is assigned by JS at runtime (stored markup never
   carries it), so nothing can be hidden for a no-JS visitor; the CSS
   additionally requires html.cdd-js. Reduced motion = instant show. */
.cdd-js body.cdd-scroll-reveal .cdd-reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .6s var(--cdd-ease), transform .6s var(--cdd-ease);
}
.cdd-js body.cdd-scroll-reveal .cdd-reveal.cdd-in {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .cdd-js body.cdd-scroll-reveal .cdd-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ------ VIDEO HERO (feature layer — the 'video-bg-hero' upsell) ------
   The baked <video> sits behind .cdd-inner under the same dark overlay
   the photo treatment uses; the static HERO_BG_STYLE photo remains the
   layer beneath, so mobile (<700px), reduced-motion and no-JS visitors
   simply see the still hero. */
.cdd-hero { position: relative; }
.cdd-hero .cdd-inner { position: relative; z-index: 1; }
.cdd-hero-video {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  /* TOP-GAP FIX (7 Jul batch 9): WP's flow gap gives this div a 20px
     margin-block-start (it follows the bg <style> tag in the same html
     block), which pushed the video + overlay down and exposed a bare
     strip of hero gradient at the very top of the page on load. An
     inset:0 overlay must never carry flow margins. */
  margin: 0 !important;
}
.cdd-hero-video video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.cdd-hero-video::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    color-mix(in srgb, var(--wp--preset--color--secondary) 78%, transparent),
    color-mix(in srgb, var(--wp--preset--color--secondary) 90%, transparent));
}
@media (max-width: 699px), (prefers-reduced-motion: reduce) {
  .cdd-hero-video { display: none; }
}

/* ------ SVG shape dividers (7 Jul, seam model v2 — overlay cut) ------
   A divider is a ZERO-HEIGHT overlay: the group adds nothing to flow (the
   two sections butt directly) and the band absolutely overlaps the section
   it cuts INTO — rise (default) = up over the section above; --dip =
   flipped, down over the section below (used when the LOWER band is the
   gradient one). The band paints NO background of its own: the overlapped
   section's REAL background (radial gradient, photo, anything) runs
   beneath the curve, and the path's solid edge overpaints the section
   junction itself — so neither a white hairline nor a flat-vs-gradient
   colour step can exist at any seam. Markup lives in pattern-library/
   shapes/ (assemble-time only); only these classes ship with the theme. */
:root {
  --cd-primary: var(--wp--preset--color--primary);
  --cd-secondary: var(--wp--preset--color--secondary);
  --cd-accent: var(--wp--preset--color--accent);
  --cd-base: var(--wp--preset--color--base);
  --cd-contrast: var(--wp--preset--color--contrast);
  --cd-muted: var(--wp--preset--color--muted);
  --cd-surface: var(--wp--preset--color--surface);
  /* The WASH (batch 8): the soft primary tint curvy mode paints across
     same-colour section boundaries — the planner's PHP twin
     (cdd_shape_mix_hex, 10% primary into base) WCAG-checks this exact
     value before ever placing a wash seam. */
  --cd-wash: color-mix(in srgb, var(--wp--preset--color--primary) 10%, var(--wp--preset--color--base));
  --cd-wash-deep: color-mix(in srgb, var(--wp--preset--color--primary) 25%, var(--wp--preset--color--base));
}
.cdd-shape { position: relative; height: 0; line-height: 0; font-size: 0; }
.cdd-shape .cdd-shape-band {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--cd-shape-h, clamp(40px, 7vw, 96px));
  pointer-events: none;
  z-index: 1;
}
.cdd-shape .cdd-shape-band--dip { bottom: auto; top: 0; transform: scaleY(-1); }
/* Flip horizontal (batch 8, Divi parity): mirrors asymmetric seams so a
   repeated shape doesn't visibly repeat; combines with --dip. */
.cdd-shape .cdd-shape-band--flipx { transform: scaleX(-1); }
.cdd-shape .cdd-shape-band--dip.cdd-shape-band--flipx { transform: scale(-1, -1); }
.cdd-shape .cdd-shape-band svg { display: block; width: 100%; height: 100%; }
.cdd-shape .cdd-shape-band path { fill: var(--cd-shape-fill, currentColor); }

/* ══ ⭐⭐ #957 RULE 6 — ONE BOUNDARY PRODUCES EXACTLY ONE SEAM ═══════════════════════════
   Rick, #955 rule 6: *"A curve followed by a thin strip of its own backing colour is two
   seams and reads as a mistake."*

   ⚠ MEASURED IN HIS OWN BUILD'S MARKUP (cd260803102602). Between the `--cd-base` wave and the
   `--cd-wash-deep` wave there is **nothing at all** — no band, no content, not one character
   of text, just the next divider's own wrapper. Two dividers, back to back, at one boundary.

   ⚠ THE PLANNER CANNOT PREVENT IT AND IT IS NOT THE PLANNER'S FAULT. `cdd_apply_shape_dividers`
   splices one divider per boundary, so two can only become adjacent when the BAND BETWEEN THEM
   RENDERS NOTHING — §E's dead-gate class, decided long after the seams were planned. The only
   place that can see the result is the page itself.

   So the second of two consecutive seams is not drawn. `+` is exact: it fires only when
   nothing whatsoever separates them. */
.cdd-shape + .cdd-shape { display: none; }

/* ══ ⭐⭐ #957 RULE 1 — NEVER TWO PHOTOGRAPHIC BANDS IN A ROW ════════════════════════════
   Rick, #955 rule 1: *"Between any two photo bands there is a plain coloured band with real
   height."* And, naming the trap himself: *"A DIVIDER IS NOT A BAND — it's supposed to be
   acting as a spacer or a gap between two items, but by sticking the image in there it doesn't
   work."*

   ⚠⚠ THIS IS RULE 10's SHAPE AND IT HAS BEEN SINCE 2 AUGUST. `BuildQuality::bandRhythmFaults()`
   has existed since #945 §G.1 and is wired at `SiteAssembler:264` — and it **REPORTS and never
   changes the page**. Found, described, wired, and the client still got two photographs stacked
   on top of each other. Detecting is not fixing, which is exactly how the three-line pill
   survived being found twice.

   ⚠ AND IT IS ANSWERED HERE RATHER THAN IN THE COMPOSER FOR A REASON. `$site` is one array for
   the WHOLE BUILD, so unsetting an image slot to break up a pair on the home page would strip
   that photograph from every other page using the same band. The adjacency is a fact about ONE
   page's rendered order, and `+` is the only thing that knows it per page.

   THE SECOND COVER KEEPS ITS HEIGHT AND LOSES ITS PICTURE: the photograph is not painted and
   the tint goes solid, which turns it into the plain coloured band with real height the rule
   asks for. **Contrast is strictly SAFER, never worse** — a solid tint is darker than the same
   tint over a photograph, so #945's cover-dim floor still holds by construction. */
.wp-block-cover + .wp-block-cover > .wp-block-cover__image-background,
.wp-block-cover + .wp-block-cover > .wp-block-cover__video-background {
  display: none;
}
.wp-block-cover + .wp-block-cover > .wp-block-cover__background {
  opacity: 1;
}

/* ══ ⭐⭐ #957 RULE 2 — A DIVIDER BELONGS TO THE BANDS IT JOINS ═══════════════════════════
   Rick, #955 rule 2: *"A full-bleed divider between inset cards is a stripe laid ACROSS them,
   not a join."* And rule 3: *"a divider never overlaps or clips the band above it"* — which is
   what a full-bleed wave does to an inset card, because it runs past the card's edges and cuts
   across its corners.

   ⚠ MEASURED: every shape file in `pattern-library/shapes/` hardcodes `"align":"full"`, and on
   Rick's build the first wave sat between a full-bleed cover and an **alignwide** `.cdd-band`
   card. The wave was wider than the thing it was joining.

   ⚠ FIXED HERE RATHER THAN IN NINETEEN SHAPE FILES. The alignment a divider needs is not a
   property of the SHAPE — the same wave is correct at full width between two full-bleed bands
   and wrong between two cards. It is a property of the BOUNDARY, which only the page knows, and
   `:has()` is what lets the stylesheet ask. A divider with an inset neighbour on either side
   takes the inset width and the same radius, so it lands on the card instead of across it. */
.alignwide + .cdd-shape,
.cdd-shape:has(+ .alignwide) {
  max-width: var(--wp--style--global--wide-size, 1200px);
  margin-inline: auto;
}
.alignwide + .cdd-shape .cdd-shape-band,
.cdd-shape:has(+ .alignwide) .cdd-shape-band {
  border-radius: inherit;
  overflow: hidden;
}

/* #364 — the visible image-credit line is GONE. #343 rendered it because
   docs/image-engine-spec.md claimed Pexels' terms required attribution; the
   licence says the opposite ("Attribution is not required"). The credit is now
   an HTML comment in wp_head and the ImageSelection row. No rule here, because
   nothing renders. */

/* #573 — THE SOCIAL ROW FINALLY HAS SOMEWHERE TO RENDER.
   `cdd_site_tokens()` has built {{SOCIAL_LINKS}} and HAS_SOCIAL since #434 and a
   repo-wide grep on 31 Jul found them read by NOTHING but their own test — so a
   client's Facebook and Instagram answers, collected since the beginning, have
   never appeared on their site. Two library patterns now render them, and this is
   the styling those anchors need. `cdd_social_links_html()` emits one <a
   class="cdd-social cdd-social--{platform}"> per link carrying an inline SVG at
   currentColor, so the row inherits whatever band it sits in and needs no per-
   platform colour. No hover-only affordance: the target is 44px, which is the
   mobile tap size, and the accessible name is on the anchor already. */
.cdd-social-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.5rem; }
.cdd-social {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  color: inherit; text-decoration: none;
  border: 1px solid currentColor; opacity: .8;
  transition: opacity .15s ease, transform .15s ease;
}
.cdd-social:hover, .cdd-social:focus-visible { opacity: 1; transform: translateY(-1px); }
.cdd-social svg { display: block; }

/* ⚠⚠ #943 §C — "01." BROKE ACROSS TWO LINES ON A DELIVERED SITE, AND THE FIX FOR
   IT HAD EXISTED SINCE 30 JULY.

   Rick: the FAQ renders `0` on one line and `1 .` on the next. Four times, on
   every service page.

   ══ THIS IS A 30 JULY FAULT IN THE ACCEPTANCE SET THAT SHIPPED ANYWAY, AND THE
   HONEST ANSWER IS THAT ALL THREE LAYERS MISSED IT ══

   1. THE CSS FIX ALREADY EXISTED. `.cdd-step-num` above carries
      `white-space: nowrap`, `word-break: keep-all`, `overflow-wrap: normal` and
      `font-variant-numeric: tabular-nums` — #631's no-break rules, whose own
      comment says "it holds '1' today and would split on '10'". **The FAQ
      numeral is not that element and never took them.**

   2. THE MARKUP IS HARVESTED THIRD-PARTY PATTERN, ADOPTED UNCHANGED.
      `pattern-library/harvested/greenshift/lib-greenshift-general-faq.php:69`
      hardcodes a `width:"38px"` column around `01.` at `x-large`. 38px is the
      harvested library's number for a library that was never our type scale.
      **Nothing fitted it to this theme.**

   3. THE RENDERED CHECK EXISTED AND COULD NOT SEE IT. `mobile-review.mjs` has a
      `numeral-wrap` rule — and its test is `/^\d{2,}$/`, "digits and nothing
      else". **`01.` has a full stop, so the element was skipped every time.**
      RULE 51 exactly: a check that cannot see what it exists to find.

   The column is now 64px and the numeral takes the same no-break treatment as
   every other counter, so the three agree instead of each being fixed alone. */
.cdd-faq-num-col { min-width: 64px; }
/* ⚠ AND THE NUMERAL ITSELF REUSES `.cdd-num` ABOVE RATHER THAN GETTING A SECOND
   COPY OF THE SAME FOUR DECLARATIONS. #617 — two shapes of one fact is how they
   drift apart and one of them gets fixed. The harvested pattern now carries the
   class the theme already tested. */

/* ══════════════════════════════════════════════════════════════════════════════════════
   #945 §D — A PRESET TEXT COLOUR CHOSEN FOR A LIGHT PAGE, LANDING ON A DARK SURFACE
   ══════════════════════════════════════════════════════════════════════════════════════

   Rick, on the first real build: *"it's a light green on a dark green background so it
   doesn't really stand out — an eligibility issue because the colour's too similar"*, and
   on the process cards: *"the 01/02/03 cards sit over a busy photograph with grey body
   text. Even where it passes the number, it does not read."*

   MEASURED at 1440 and 390, on the pixels under the glyphs: the four step paragraphs came
   in at **3.00 · 2.74 · 2.71 · 1.04** and the quote-CTA copy at **1.04**, against 4.5.

   THE CAUSE IS A BLOCK ATTRIBUTE IN A HARVESTED PATTERN.
   `pattern-library/harvested/greenshift/lib-greenshift-general-features-main.php:76`
   sets `"textColor":"muted"` on a paragraph that sits inside a `.cdd-on-dark` column with
   an 88%-secondary background. `muted` is the body colour for a PALE surface — it is
   correct where the pattern came from and cannot be readable here.

   ⚠ IT IS NOT ONE PATTERN. Thirty-three files in the harvested library set a preset text
   colour, and any of them may be composed onto a dark band or a cover. Editing the ones we
   happen to have caught fixes the sites we happen to have built. **The theme owns colour,
   so the theme is where this is answered** — and it then also covers AI-composed markup,
   which no pattern edit could reach.

   ⚠ `!important` IS REQUIRED HERE AND IS NOT A SPECIFICITY WAR. WordPress emits its colour
   presets as `.has-muted-color { color: … !important }`. Importance ties, so specificity
   decides, and these selectors outrank a single class. Without the `!important` the rules
   below would silently do nothing — which is precisely the failure #619's note warns about.

   ⚠ AND THE ACCENT ON A PHOTOGRAPH IS NOT A TINT PROBLEM, IT IS A COLOUR PROBLEM. Walking
   `overlayAlphaFor()` for the plumbing accent `#C25F1C` over black finds NO tint below 80%
   that reaches 4.5 — the accent's own luminance is too close to the middle for any dim to
   separate it from a photograph. §4 says the layout or the colour changes rather than the
   picture being drowned, and §1 says the accent takes a readable colour on the section
   where it fails while staying everywhere it works. That is exactly this rule, and nowhere
   else. */
.cdd-on-dark .has-muted-color,
.cdd-on-dark .has-primary-color,
.cdd-on-dark .has-contrast-color {
  color: var(--wp--custom--text-on-secondary, var(--wp--preset--color--base)) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ⭐⭐ #994(b) — A DARK BAND DECLARED BY A WORDPRESS BACKGROUND CLASS, WHICH #945 §D
   FIXED FOR `.cdd-on-dark` AND FOR A COVER AND FOR NOTHING ELSE
   ══════════════════════════════════════════════════════════════════════════════════════

   MEASURED on the live build cd260803163531 (Garrick Plumbing, project 22078), home page,
   inside ONE element — `div.wp-block-group.has-base-color.has-contrast-background-color`,
   the dark navy `#0C2E42` panel:

     h3   "Blocked drains"              primary #0E4C6E on #0C2E42   **1.53:1**
     p    "Camera down the line first…"  muted   #5B7284 on #0C2E42   **2.82:1**
     a    "More about blocked drains →"  primary #0E4C6E on #0C2E42   **1.53:1**

   Three of the five contrast findings in `pr-22078-260804073843`, all one cause.

   ⚠ THE PANEL ALREADY KNEW IT WAS DARK. It carries `has-base-color` — WordPress set the
   GROUP's own text to white. What it cannot do is reach the descendants, and every one of
   them carries a colour chosen for a PALE page: `has-muted-color` from the pattern, and
   the heading and link colours from `theme.json`'s element styles. #619's convention is
   that **the band decides what is readable, not the element** — and the band-detection
   list only ever knew `.cdd-on-dark`, so a band that announces itself the WordPress way
   was invisible to it.

   ⚠ AND THE LINK WAS NEVER COVERED EVEN FOR `.cdd-on-dark`. There is no `a` rule in the
   #945 §D set, so a link in ANY dark band has been taking `primary` from theme.json since
   that batch. Fixed for both here rather than for the one that happened to be measured.
   Buttons are excluded: they carry their own background and #945 §D already resolved
   their text with `text_on['accent']`.

   ⚠ `--wp--custom--text-on-contrast` IS COMPUTED PER CLIENT (`SiteAssembler`, #994(b) added
   `contrast` to the `text_on` slots — it was missing because `contrast` is usually the INK
   and nobody thought of it as a surface). `base` is the fallback, which is what this panel
   had already chosen for itself.

   ⚠ THE THREE BACKGROUND CLASSES ARE TAKEN TOGETHER because the theme ALREADY treats all
   three as dark bands — `.cdd-num`'s rule at the top of this file names
   `.has-secondary-background-color` and `.has-primary-background-color` and has since #631.
   `contrast` is the one that was missing from that list, which is why it is the one that
   shipped a 1.53:1 heading. Each keeps its OWN computed ink, because an ink is always an
   ink for a surface — merging them into one value is the mistake line 1585 records. */
.has-contrast-background-color :is(.has-muted-color, .has-primary-color, .has-secondary-color, .has-contrast-color) {
  color: var(--wp--custom--text-on-contrast, var(--wp--preset--color--base)) !important;
}
.has-secondary-background-color :is(.has-muted-color, .has-primary-color, .has-contrast-color) {
  color: var(--wp--custom--text-on-secondary, var(--wp--preset--color--base)) !important;
}
.has-primary-background-color :is(.has-muted-color, .has-secondary-color, .has-contrast-color) {
  color: var(--wp--custom--text-on-primary, var(--wp--preset--color--base)) !important;
}

/* The headings and the links in those bands. ⚠ THE LINK WAS NEVER COVERED EVEN FOR
   `.cdd-on-dark` — there is no `a` rule anywhere in the #945 §D set, so a link in ANY dark
   band has been taking `primary` from theme.json's element styles since that batch. Fixed
   for all four band forms here rather than for the one that happened to be measured.
   Buttons are excluded: they carry their own background and #945 §D already resolved their
   text through `text_on['accent']`. */
.has-contrast-background-color :is(h1, h2, h3, h4, h5, h6),
.has-contrast-background-color a:not(.wp-element-button):not(.wp-block-button__link) {
  color: var(--wp--custom--text-on-contrast, var(--wp--preset--color--base));
}
.has-secondary-background-color :is(h1, h2, h3, h4, h5, h6),
.has-secondary-background-color a:not(.wp-element-button):not(.wp-block-button__link),
.cdd-on-dark a:not(.wp-element-button):not(.wp-block-button__link) {
  color: var(--wp--custom--text-on-secondary, var(--wp--preset--color--base));
}
.has-primary-background-color :is(h1, h2, h3, h4, h5, h6),
.has-primary-background-color a:not(.wp-element-button):not(.wp-block-button__link) {
  color: var(--wp--custom--text-on-primary, var(--wp--preset--color--base));
}

/* ⚠ AND THE ACCENT ON A DARK BAND TAKES THE LIGHT TINT, NOT THE DARKENED INK — the trap
   line 1580 already recorded in the other direction (*"a DARKENED orange on a dark
   photograph… the eyebrow fix making things worse on the one surface it was never meant
   for"*). `--cdd-num-ink-dark` is the LIGHTEST tint of the client's own accent that clears
   4.5 on a dark band, derived by walking the mix — the value already exists, so this
   re-points it rather than deriving a second one. (0,2,0) beats the pale-surface rule
   below, which is (0,1,0). */
.cdd-on-dark .has-accent-color,
.has-contrast-background-color .has-accent-color,
.has-secondary-background-color .has-accent-color,
.has-primary-background-color .has-accent-color {
  color: var(--cdd-num-ink-dark) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ⭐⭐ #994(b) — THE ACCENT AS TEXT ON A PALE SURFACE. THE INK WAS BUILT AND THE CLASS
   THAT NEEDED IT WAS NEVER WIRED TO IT.
   ══════════════════════════════════════════════════════════════════════════════════════

   MEASURED on the same page, the other two of the five findings:

     p    "What we work with"   accent #C25F1C on surface #EEF4F8   **3.83:1**
     strong "Why it pays off"   accent #C25F1C on base    #FFFFFF   **4.24:1**

   ⚠ BOTH NUMBERS ARE ALREADY WRITTEN DOWN IN THIS FILE. Line 36 records *"#C25F1C on
   #EEF4F8 = 3.83:1"* and line 68 records *"#C25F1C on white is 4.24:1"*. `--cdd-ink-accent`
   was DERIVED for exactly these two surfaces by `BuildQuality::inkOn()` — the client's own
   accent darkened by the smallest step that clears 4.5 on `base` AND `surface`.

   ⚠ SO NOTHING NEEDED DERIVING. THE INK WAS APPLIED TO THE THEME'S OWN CLASSES ONLY —
   `.cdd-eyebrow`, `.cdd-svc-note a` and three more — and never to `.has-accent-color`,
   which is what WordPress emits and therefore what every harvested pattern and every
   AI-composed block actually carries. #945 §D's own words, one screen up: *"editing the
   ones we happen to have caught fixes the sites we happen to have built… the theme owns
   colour, so the theme is where this is answered."* It was answered for our markup and
   not for theirs.

   ⚠ `!important` FOR THE REASON GIVEN ABOVE — WordPress emits `.has-accent-color { color:
   … !important }`, so importance ties and specificity decides. And this is deliberately
   the WEAKEST of the three accent rules at (0,1,0): a cover (0,2,0) and a dark band (0,2,0)
   both still win, because an ink is always an ink FOR A SURFACE and this one is for the
   two pale ones it was derived against. */
.has-accent-color {
  color: var(--cdd-ink-accent) !important;
}

.wp-block-cover__inner-container .has-muted-color,
.wp-block-cover__inner-container .has-primary-color,
.wp-block-cover__inner-container .has-secondary-color,
.wp-block-cover__inner-container .has-contrast-color,
.wp-block-cover__inner-container .has-accent-color {
  color: var(--wp--preset--color--base) !important;
}

/* ⚠ AND THE THEME'S OWN DARK-INK CLASSES, WHICH THE PRESET RULES ABOVE DO NOT COVER.
   MEASURED on the service pages after the eyebrow took the derived ink: *"The honest list"*
   came back at **1.20:1** — a DARKENED orange on a dark photograph, which is the eyebrow fix
   making things worse on the one surface it was never meant for. And a heading over a cover
   takes `primary` from theme.json (*"What's included in hot water"*, **2.17:1 at 390**).

   ⚠ THE LESSON IS THAT AN INK IS ALWAYS AN INK *FOR A SURFACE*. `--cdd-ink-accent` is derived
   for `base` and `surface` — the two PALE surfaces it was asked about — and applying it
   anywhere else is the same error #619 made with `text-on-secondary`, in the other direction.
   A cover is a dark surface whatever the photograph, so the text on it is the light one. */
.wp-block-cover__inner-container .cdd-eyebrow,
.wp-block-cover__inner-container :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--base);
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   #945 §D — THE COVER OVERLAY IS DERIVED FROM THE CONTRAST REQUIREMENT, NEVER PICKED
   ══════════════════════════════════════════════════════════════════════════════════════

   Build-quality rule 4 in the briefing says the tint exists to make text legible, so it is
   the LIGHTEST tint that passes rule 1 — never a fixed value. `BuildQuality::overlayAlphaFor()`
   implements it and **had no caller for `wp:cover`**, which is the block every hero and
   every CTA band on a generated site is built from.

   ⚠ SEVENTY-TWO COVERS IN THE HARVESTED LIBRARY CARRY A HAND-PICKED `dimRatio` — 0, 10, 40,
   50, 55, 60, 70, 80 and 90. The first real build used 40 on its hero and 50 on its two CTA
   bands, and its white copy measured **4.09:1** and **3.76:1** across the worst twentieth of
   the pixels under the glyphs. Nobody chose those numbers against a requirement; they were
   chosen to look right against whatever photograph the original theme shipped with.

   `--cdd-cover-dim-floor` is the answer to the question the dimRatios were guessing at, and
   `CoverOverlayFloorIsDerivedTest` recomputes it from `BuildQuality` so the CSS and the rule
   cannot drift apart.

   ⚠ IT IS A FLOOR, NOT A VALUE. A cover a designer deliberately dimmed to 70 keeps its 70 —
   only the ratios BELOW the derived minimum are raised. Overriding a heavier tint would be
   the same mistake in the other direction.

   ⚠ AND A COVER WITH NO TEXT IN IT IS LEFT ALONE. `:has()` is what makes that possible: the
   tint exists to make TEXT legible, and a decorative band has no text to make legible.
   Darkening it would be drowning a photograph for no reason, which §4 names as losing both. */
.wp-block-cover:has(.wp-block-cover__inner-container :is(h1, h2, h3, h4, h5, h6, p, a, li))
  > .wp-block-cover__background:is(
      .has-background-dim-10, .has-background-dim-20, .has-background-dim-30,
      .has-background-dim-40, .has-background-dim-50) {
  opacity: var(--cdd-cover-dim-floor);
}

/* ══ ⭐⭐ #964 RULES 13 & 14 — THE CONTACT POPUP ═══════════════════════════════════════════
   Rick: *"if they click on get in touch or a contact button anywhere it should do a pop-up.
   Don't take them to the contact page and then add another step."*

   ⚠ IT REPLACES A DEAD CTA, NOT A WORKING ONE. Every contact button in this theme and in the
   harvested library points at `#contact`; on cd260803102602 that anchor was not on the home
   page, so "Get a quote today" did nothing at all when Rick clicked it.

   The panel is centred, scrolls internally on a phone, and inherits the client's palette —
   never a colour of its own (#957 rule 4's reasoning applies to a panel as much as to a
   divider). */
.cdd-contact-modal[hidden] { display: none; }
.cdd-contact-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.cdd-contact-modal__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--wp--preset--color--secondary) 72%, transparent);
}
.cdd-contact-modal__panel {
  position: relative;
  width: min(560px, 100%);
  max-height: min(86vh, 760px);
  overflow-y: auto;
  background: var(--wp--preset--color--base);
  color: var(--wp--custom--text-on-base, var(--wp--preset--color--contrast));
  border-radius: var(--cdd-radius);
  padding: 32px 28px;
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .6);
}
.cdd-contact-modal__close {
  position: absolute;
  top: 8px;
  right: 8px;
  /* #948 — 44x44 is the house target and this is a control a person taps in a hurry. */
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
}
.cdd-contact-modal__title {
  margin: 0 0 6px;
  font-size: 24px;
  color: var(--wp--custom--text-on-base, var(--wp--preset--color--primary));
}
.cdd-contact-modal__intent-label {
  display: block;
  margin: 14px 0 6px;
  font-weight: 700;
  font-size: 15px;
}
.cdd-contact-modal__intent {
  width: 100%;
  /* #948 — comfortably past the 24px AA bar and at the house target. */
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--cdd-line);
  border-radius: 10px;
  background: var(--wp--preset--color--base);
  color: inherit;
  font-size: 16px;   /* 16px stops iOS zooming the page when the select is tapped */
}
.cdd-contact-modal__form { margin-top: 18px; }
.cdd-contact-modal__tel {
  display: inline-block;
  min-height: 44px;
  line-height: 44px;
  font-weight: 700;
  color: var(--cdd-ink-accent);
}
