/*
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; }

/* =====================================================================
   ⭐ #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;
}

.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);
}
