/* ═══════════════════════════════════════════════════════════════════════════
   custom.css — THIS CLIENT'S bespoke design layer (Layer 3)
   ═══════════════════════════════════════════════════════════════════════════
   Per-client. Lives in the cloned Template (the client theme), NOT in the
   shared engine plugin. The engine loads it LAST — after primitives.css,
   tokens.css, base.css and the recipe-derived tokens — so anything here
   extends or overrides the shared `.hzo-*` components for THIS site only.
   Ships with `hozio-studio theme deploy`.

   ── ⛔ THE SEED BELOW IS THE DESIGN CONTRACT. DO NOT DELETE IT. ────────────
   Everything from "THE CONTRACT, IN TOKENS" down is not example code and it
   is not a starting suggestion. It is
   `Hozio-Studio-Playbook/design-system/design-contract.md` expressed in CSS —
   the one page of literal numbers that outranks every other design document
   in the fleet, including this site's own DESIGN.md.

   EXTEND IT. RETUNE ITS VALUES. NEVER STRIP IT OUT AND START OVER.
   Deleting the seed does not give a build a clean slate; it gives it the
   engine's 2018 defaults (12px radius, a hairline shadow, border-and-shadow
   on one object) and a fresh obligation to re-derive the contract privately.
   That is exactly what happened before this file existed: one audited client
   re-declared 381 custom properties across 834 declarations, under its own
   prefix, and the next client re-declared them differently. The seed exists
   so a new site STARTS at the contract instead of arriving at it.

   Retuning a value is normal and expected — that is what the tokens are for.
   Removing a rule needs a reason written on the line.

   ── THE CLIENT PREFIX ─────────────────────────────────────────────────────
   The seed ships under the placeholder prefix `.cl-` (client). Rename it to
   this site's own prefix once, at clone time, before any page is built:

       (Get-Content assets/css/custom.css) -replace '\.cl-', '.xx-' |
           Set-Content assets/css/custom.css -Encoding utf8

   …and use the same prefix for every class this site adds afterwards. Never
   `.hzo-*` — that is the shared engine namespace and this file never
   redefines it. Setting an engine *variable* on an engine element is a
   different thing and is the sanctioned override route (styling-layers.md);
   the seed uses it once, in THE BINDING, below.

   ── WHAT THIS FILE HOLDS: two layers, two budgets ─────────────────────────
   Exactly TWO of the five routing layers (page-build.md §"THE ROUTING
   TABLE"). Nothing else is allowed in here.

   1. COMPONENT TREATMENT — the per-site FORM of a signature component
      family, decided once site-wide in docs/component-treatment.md.
      Presentation only: never markup contracts (order, tags, roles, ARIA,
      data-hzo-*, focus order). BUDGET: 350 lines, counted by marker.
      Every block MUST be opened by an `@treatment <family>` marker comment
      and closed by an `@end-treatment` one — both written as ordinary CSS
      block comments — or gate T3 cannot see it and the family reads as
      untreated; T3 wants >= 15 declarations inside the marker. One marker per
      family: nav/header, cards, cta, steps, pricing, testimonial, faq, stats.
      (Those two markers are spelled out literally, in comment form, at the
      first @treatment block a build adds — they are NOT written here,
      because a comment terminator inside a comment ends it early.)

   2. SITE SKIN — this client's token values plus site-wide client-prefixed
      decisions that recur on >= 2 page classes. BUDGET: 450 lines.
      Treatment + skin <= 800 total. Above that the build STOPS and the excess
      is justified as an engine gap, a treatment, or a signature.
      THE SEED COUNTS AS SKIN, and it is deliberately small so it leaves room.
      If the skin is at budget, the answer is never to delete the seed.

   ── ⛔ SIGNATURE MOMENTS ARE **NOT** IN THIS FILE ──────────────────────────
   A page's signature moment lives in the `_hzostudio_page_custom_css` /
   `_hzostudio_page_custom_js` PAGE META, namespaced under its
   docs/signature-ledger.md `id` (versioned source: docs/page-code/<slug>.css).
   Gate S1 reads that meta: a signature declared in the ledger but implemented
   here FAILS, because the gate looks in the meta and finds nothing. Likewise
   not here: anything the spec vocabulary already composes, and raw section
   markup (that is the spec's {"raw": …} with a reason).

   ── Rules of the road ─────────────────────────────────────────────────────
   1. REUSE FIRST. Compose from the shared `.hzo-*` components before writing
      anything here. Only add CSS for the bespoke *delta* a section needs.
   2. CLIENT PREFIX on every new class (above). Page-scope page-specific
      rules so one page's CSS never bleeds onto another.
   3. USE TOKENS. Colour, space, radius, shadow and type come from the engine
      variables. A raw px or hex is for things the token system genuinely does
      not describe — a bespoke gradient, an aspect ratio, a physics constant.
   4. INVOKE THE DESIGN SKILLS. New CSS here is written WITH the design-skill
      stack (`hozio-design` routes it), never from memory.
   5. NEW CLIENT-SITE FILES LIVE ONLY IN THIS REPO — never the engine plugin
      or the Playbook.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══ ENGINE-ROLE POLYFILL — DELETE WHEN THE FLEET MINIMUM CARRIES IT ═══
   Every custom property below is an ENGINE role, not a client invention. Each
   one is declared here at EXACTLY the value the engine declares it at on
   `Hozio-Studio @ feat/wave-t-composition` (assets/css/tokens.css, see
   assets/css/TOKENS-MIGRATION.md §“Depth roles” / “Rhythm + rail roles” /
   “Type roles” / “--radius-4 + --radius-object”).

   WHY THIS EXISTS. The contract seed below READS these roles. They ship on
   that branch and NOWHERE ELSE: the released engine the fleet actually runs
   (v1.11.5 — the current fleet minimum) declares none of them. A `var()` with
   no fallback naming an undefined property invalidates the WHOLE declaration
   at computed-value time, so without this block the seed deploys as
   radius 0, padding 0 and no shadow — silently, with every gate GREEN.

   HARMLESS ONCE THE ENGINE SHIPS THEM. The engine declares its tokens inside
   `@layer tokens.base`; this file is unlayered, so this block wins — but it
   wins with the SAME value, which is the whole point. Identical to engine
   >= the first release carrying feat/wave-t-composition (unreleased as of
   2026-08-28). DELETE THIS BLOCK the day the fleet minimum engine carries
   these roles; nothing else in the file changes.

   NO ENGINE CONSUMER AT v1.11.5: --section-pad-y / --rail-gutter are read by
   nothing the engine ships — base.css and container.css still hardcode those
   ladders — so the contract's one-line rhythm retune below would be inert on
   its own. THE SECTION ROOT block below gives --section-pad-y a consumer
   (`.cl-sec`), which is what makes that retune real; --rail-gutter is still
   spent only by the client's own container selector, so mirror the ladder,
   never flatten it. The day the engine ships its own consumers they agree.
   Same open question for --frame-radius in THE BINDING.
   ═════════════════════════════════════════════════════════════════════════════ */
:root {
    /* SPACING — the three numeric steps (tokens.css L86-88). */
    --space-32: 32px;
    --space-40: 40px;
    --space-56: 56px;

    /* RADIUS — the surface rung and its role name (tokens.css L180, L520).
       --radius-pill / --radius-control already exist at v1.11.5; not polyfilled. */
    --radius-4:      24px;
    --radius-object: var(--radius-4);

    /* DEPTH — the ink-tinted ladder (tokens.css L211-220, L546-547). The
       released engine's --shadow-md/-lg are the OLD flat-black hairlines
       (0 4px 12px rgba(0,0,0,.08)), which is below the alpha >= .10 floor, so
       the object roles are declared at the branch's LITERAL recipe here rather
       than aliased onto the local ladder. --brand-deep is a recipe token and
       already exists fleet-wide, so the tint follows this client's brand. */
    --shadow-ink:    var(--brand-deep);
    --shadow-a-hair: color-mix(in srgb, var(--shadow-ink) 10%, transparent);
    --shadow-a-rest: color-mix(in srgb, var(--shadow-ink) 14%, transparent);
    --shadow-a-lift: color-mix(in srgb, var(--shadow-ink) 22%, transparent);
    --shadow-a-deep: color-mix(in srgb, var(--shadow-ink) 28%, transparent);
    --shadow-object:       0 8px 32px var(--shadow-a-rest);
    --shadow-object-hover: 0 18px 44px var(--shadow-a-lift);

    /* TYPE — the three role names the engine had no name for (tokens.css
       L450-452), at the engine's values. The contract retunes --fs-lead and
       --fs-label below; that retune is the point, and it needs the role to
       exist first. */
    --fs-lead:  1.25rem;
    --fs-small: 0.875rem;
    --fs-label: 15px;

    /* RHYTHM — tokens.css L660-663. Base rung only: the branch's 48/64/80
       breakpoint ladder is deliberately NOT mirrored, because the contract
       pins ONE value from one unlayered line immediately below and mirroring
       the ladder here would fight it. */
    --section-pad-y:    var(--space-xl);                    /* 48 */
    --section-pad-y-sm: calc(var(--section-pad-y) * 0.6);
    --section-pad-y-lg: calc(var(--section-pad-y) * 1.4);

    /* RAIL — tokens.css L663 + L1139-1144. The full ladder IS mirrored (16 /
       24 / 32 / 48 / 64), because the seed leaves the gutter at the engine's
       values on purpose and a flat 16px would desync every hand-mirrored
       section. */
    --rail-gutter: var(--space-md);                         /* 16  <480 */
}
@media (min-width: 480px)  { :root { --rail-gutter: var(--space-lg);  } }  /* 24 */
@media (min-width: 768px)  { :root { --rail-gutter: var(--space-32);  } }  /* 32 */
@media (min-width: 1024px) { :root { --rail-gutter: var(--space-xl);  } }  /* 48 */
@media (min-width: 1250px) { :root { --rail-gutter: var(--space-2xl); } }  /* 64 */
/* ═══ END ENGINE-ROLE POLYFILL ══════════════════════════════════════════════ */


/* ═══ THE CONTRACT, IN TOKENS ══════════════════════════════════════════════
   Where the engine already has the role, the seed READS it and never
   re-declares it. Where the engine has the role at the wrong value for the
   contract, the seed retunes it in ONE line. Only four values are
   client-local, because the engine has no name for them at all. */
:root {

    /* SPACING · ONE scale, six values, every one a multiple of 8 and every
       one an engine step. This is the whole scale; a value off it is a
       defect, not a nuance. There is deliberately no client alias for these
       — an alias is a second scale waiting to happen.
           8   var(--space-sm)    24  var(--space-lg)    64  var(--space-2xl)
           16  var(--space-md)    40  var(--space-40)    96  var(--space-4xl)
       Below 768 the top two steps step down to 40 and 64, so the mobile six
       are 8 · 16 · 24 · 32 (--space-32) · 40 · 64. */

    /* RHYTHM · 96 top and bottom on every section at 1440, 64 below 768 —
       the same number all the way down the page, from ONE declaration.
       tokens.css declares --section-pad-y's ladder inside @layer tokens.base
       and this file is unlayered, so this line wins at every breakpoint with
       no specificity fight. --section-pad-y-sm / -lg follow it automatically.
       A section that needs more presence gets a bigger photograph, not more
       padding. */
    --section-pad-y: var(--space-4xl);                      /* 96 */

    /* FRAME · ONE container and ONE rail, both the engine's. --rail-gutter
       (16/24/32/48/64) is left at the engine ladder on purpose: client
       sections are known to hand-mirror it, and moving a rung desyncs every
       mirror silently. The container is --container-max-width. There is no
       --container-max; writing var(--container-max, 1220px) reads an
       undefined property and silently falls back to a literal. */

    /* THE OBJECT · one card or band, radius 24, padding 64 top and bottom /
       40 left and right (40 / 24 below 768). Both client-local: the engine has
       --card-padding for the card but no name for the object padding a
       hand-authored section composes on.

       THE VERTICAL AXIS IS THE CORRECTED ONE. Owner, 2026-08-31: "we should
       always have more padding to start around a container like this on top
       and bottom. Like it should be at least 60px top and bottom on desktop."
       64 is the step above 60 on the one scale (clause 1: 8/16/24/40/64/96),
       so the correction lands on the scale instead of opening a seventh value.
       THE INLINE AXIS STAYS 40 on purpose: --pad is also what puts a bare
       lockup and a card's first line on the same left x, and every section
       that mirrors the rail writes `padding-inline: var(--pad)`. Moving the
       inline value would move the rail on the object and not on its mirrors,
       breaking clause 3's tolerance-0 rule at every seam. Two values, both
       members of the ONE scale - not two scales. */
    --pad:   var(--space-40);               /* inline · the rail              */
    --pad-y: var(--space-2xl);              /* block  · 64, contract clause 4 */

    /* RADIUS · two radii carry the page. Both are engine roles:
           surfaces  var(--radius-object)   24  (card, band, panel, frame)
           controls  var(--radius-pill)    999  (every control)
       Role values, not extra choices: inputs var(--radius-control), <img> 0
       (the frame rounds it). A third surface radius is a bug. */

    /* TYPE · 5–6 rungs page-wide, at most 4 in any one section. Three are
       engine roles retuned here; three are client-local because the engine
       has no name for them.
           display   var(--fs-display)     ONE per page, below the hero
           h1        var(--fs-h1)
           title     --fs-title            THE SAME VALUE IN EVERY SECTION
           card      --fs-card
           body      var(--fs-body) at 1.6
           label     var(--fs-label)       uppercase, wide
       --fs-stat is the one display-scale numeral a stat band spends. Retune
       the clamps to this brand; keep the count. */
    --fs-title: clamp(30px, 3vw, 42px);     /* contract: section title 40–44  */
    --fs-card:  1.375rem;                   /* contract: card title 20–28     */
    --fs-stat:  clamp(32px, 3vw, 44px);     /* the one display moment         */
    --fs-lead:  1.0625rem;                  /* engine ships 20px; contract 17 */
    --fs-label: 12px;                       /* engine ships 15px; contract 12 */
    --eyebrow-tracking: 0.14em;             /* engine ships 0.7px             */


    /* COLOUR · 60/30/10, one hue family, accent on primary actions only.
       Every value is a recipe token, so this seed carries no hex: the brand
       is published from the design widget and this file must never fork it.
           60  var(--bg-page) / var(--surface)      neutral grounds
           30  var(--bg-dark-section) / var(--brand)  ink and dark bands
           10  var(--accent)                         primary actions only
       The page needs >= 3 grounds and >= 2 dark-or-deep, one below the hero. */

    /* DEPTH · ONE recipe, two states, ink-tinted, alpha >= .10, on elevated
       objects only — never a band, a photograph or the page ground. Never a
       border AND a shadow on the same object. Grids over 12 cards ship none.
           rest   var(--shadow-object)        (--shadow-md, alpha .14)
           hover  var(--shadow-object-hover)  (--shadow-lg, alpha .22) */

    /* MOTION · ONE hover vocabulary page-wide: translateY(-3px) + shadow
       bloom + inner image scale(1.04), on click targets only, with a
       :focus-visible reaching the same element. The contract says 180ms; the
       engine's duration scale steps 120 / 200 / 300 / 450 and 200 is the
       rung, so the page spends --dur-2 rather than opening a seventh
       duration nobody else reads. */
    --lift: -3px;
}

@media (max-width: 767px) {
    :root {
        --section-pad-y: var(--space-2xl);  /* 64 below 768 */
        /* THE OBJECT below 768. The block axis steps to 40 rather than staying
           at 24: the correction Kyle asked for is vertical, and 40 is the
           mobile scale's step under 64 (8/16/24/32/40/64), so the phone keeps
           the same generosity at one rung down. The inline axis stays 24 —
           at 393 the object is nearly full-width and every extra inline pixel
           comes straight out of the measure. */
        --pad:   var(--space-lg);           /* 24 below 768 · inline */
        --pad-y: var(--space-40);           /* 40 below 768 · block  */
    }
}


/* ═══ THE BINDING ══════════════════════════════════════════════════════════
   The contract's one test is whether a stranger can name the single object
   the page is made of. That fails the moment a hand-authored `.cl-card` and
   a shipped `.hzo-card` are two different objects on one page.

   So the engine's card is pointed at the same recipe, from its own tokens,
   in four lines. This is variable shadowing — the sanctioned override route
   — not a redefinition of the component: the engine's own declarations read
   these names and simply resolve differently here.

   Any component that exposes its radius / padding / shadow as tokens joins
   the object the same way. One that does not is an engine gap: raise it,
   do not fork the component's selector. */
:root {
    --card-radius:  var(--radius-object);
    /* A `padding:` shorthand pair, block then inline — the same two values
       .cl-card spends. No component reads --card-padding at engine v1.11.5,
       so this is a forward binding: the day one does, it inherits the object's
       corrected vertical padding rather than a stale single value. */
    --card-padding: var(--pad-y) var(--pad);
    --card-shadow:  var(--shadow-object);
    --card-border:  transparent;   /* never a border and a shadow together */
    --frame-radius: var(--radius-object);

    /* THE CONTROL BOUNDARY. components/form/form.css sets every field's
       border with `!important`, reading `var(--input-border, var(--border))`.
       So a Layer-3 `border:` on an input is a DEAD declaration — the only
       thing that reaches the form is this token. The engine's fallback
       --border is #E5E7EB, which on a white field is 1.24:1, against WCAG
       1.4.11's 3:1 for the boundary of a user-interface component: measured
       on 6.hoziodev.com/home-r2/ 2026-08-28, where the page's own
       `border: 1px solid rgba(22,33,31,.52)` (3.42:1) never applied.
       Ink-tinted from the brand rather than a grey, at the alpha that clears
       3:1 on the field ground. RETUNE PER BRAND and re-measure: the value
       must clear 3:1 against --input-bg, and a light --brand-deep will not. */
    --input-border: color-mix(in srgb, var(--shadow-ink) 52%, transparent);
}


/* ═══ THE SECTION ROOT ═════════════════════════════════════════════════════
   EVERY section root on EVERY page of this site carries BOTH classes:

       <section class="hzo-section cl-sec cl-<name> …">

   `hzo-section` is the ENGINE's section — `page_spec` refuses a raw section
   root without it (PS-RAW-NO-SKELETON) — and `cl-sec` is this site's. Two
   readers, one decision, stated once in each place they look. The three
   paragraphs below are what that pairing costs and what it buys.

   1 · THE SEAM. WordPress emits

           :root :where(.is-layout-flow) > * { margin-block-start: 24px }   (0,1,0)

   over the children of a flow-layout post body, so without a counter-rule
   every section wears a 24px stripe of the PAGE ground above it — a white
   seam between every pair of sections on any page whose sections are
   coloured. The engine ships a counter-rule but keys it on the engine class
   alone (`:is(.is-layout-flow,.is-layout-constrained) > .hzo-section`), so a
   Layer-3 root carrying only the client class is never reached by it.
   MEASURED 2026-08-28 on 6.hoziodev.com/home-r2/: five 24px #FFFFFF seams
   down an ivory-and-ink page — one of them directly under the hero
   photograph, where it reads as a rendering fault rather than a design
   choice — with nothing wrong in the page's own stylesheet and every gate
   GREEN. The reset below names the client class too and lands at (0,2,0), so
   it BEATS the platform rule outright instead of tying it and depending on
   sheet order.

   2 · THE RHYTHM. `.cl-sec` is where --section-pad-y is finally SPENT. At
   engine v1.11.5 nothing reads that token (base.css still hardcodes the
   .hzo-section 48/64/80 ladder), so the contract's one-line retune above is
   inert until a selector consumes it. Padding, not margin: on a continuous
   coloured field a section's own air belongs inside its own box, and adjacent
   margins collapse into one.

   3 · WHY THE RHYTHM IS WRITTEN AS A COMPOUND. Carrying `hzo-section` also
   hands the root to a second engine rule —

           .hzo-section + .hzo-section { padding-top: 0 }                   (0,2,0)

   (components/_base/doc-layout-toc.css) — which collapses the doubled padding
   of a white-on-white run and restores it only for the grounds it can SEE:
   `--pale`, `--tint`, `--dark`, `.hzo-hero`. A section whose ground is a
   CLIENT class is invisible to those exceptions. Left alone on the six-section
   page above, that rule computes top padding 80 · 0 · 0 · 80 · 80 for sections
   2–6: two sections with their content jammed against the colour edge over
   them, three more at the engine's 80 instead of the contract's 96.
   `.hzo-section.cl-sec` is also (0,2,0) and this file is enqueued LAST, so it
   ties on specificity and wins on order, and every section is back to 96/64
   from the page's one number. The bare `.cl-sec` selector stays for a root
   that carries no `hzo-section`; the compound is what makes the pairing safe.
   Do not "tidy" the two selectors into one — they are two specificities on
   purpose. */
.cl-sec,
.hzo-section.cl-sec { padding-block: var(--section-pad-y); }

:is(main, .entry-content, .is-layout-flow, .is-layout-constrained)
    > :is(.cl-sec, .hzo-section, [class*="hzo-section--"]) {
    margin-block-start: 0;
}


/* ═══ THE OBJECT ═══════════════════════════════════════════════════════════
   The vocabulary a hand-authored AMPLIFY section composes on, so a RAW
   section and a shipped component are the same object. Every rule carries
   the contract clause it came from. */

/* THE OBJECT · a card on the page ground. Radius 24, padding 64/40 (40/24
   below 768), one ground from the palette. Do not invent a second object. */
.cl-card {
    background: var(--surface);
    border-radius: var(--radius-object);
    padding: var(--pad-y) var(--pad);
}
/* DEPTH · elevated objects only. A flat card in a grid of 12+ ships none. */
.cl-card--lift { box-shadow: var(--shadow-object); }

/* THE OBJECT · the same object as a band. A band is a ground change, so it
   takes no shadow — depth belongs to things that are lifted off the page. */
.cl-band {
    background: var(--bg-alt);
    border-radius: var(--radius-object);
    padding: var(--pad-y) var(--pad);
}
.cl-band--ink   { background: var(--bg-dark-section); color: var(--text-on-brand); }
.cl-band--brand { background: var(--brand);           color: var(--text-on-brand); }

/* PHOTOGRAPHY LEADS · photos fill their frame, no filter, no tint. The frame
   rounds the image, so the image itself is square (radius 0 is its role
   value). Ratio composes from .hzo-aspect--*; this owns only the clipping. */
.cl-frame {
    display: block;
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-object);
    background: var(--bg-alt);
    margin: 0;
}
.cl-frame > img,
.cl-frame > video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 0;
}

/* TYPE · the section opening, as ONE lockup on the rail. Every section opens
   with eyebrow → title → lead → actions, in that order, on the same left x.
   Four rungs, which is the per-section ceiling. */
.cl-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    font-family: var(--font-body);
    font-size: var(--fs-label);
    font-weight: var(--fw-600);
    line-height: var(--lh-none);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: uppercase;
    /* COLOUR · --brand-text, NOT --accent-text. The two role names read as
       interchangeable and are not: --accent-text is the accent hue darkened
       just far enough to be legible, and at 12px/600 on a warm neutral it
       lands at 4.00:1 — under AA's 4.5 for text that is not "large". Measured
       2026-08-28 on 6.hoziodev.com/home-r2/: #208760 on ivory #F5F2EC = 4.00:1
       and on a white card = 4.47:1, i.e. EVERY eyebrow on a light ground
       failed, in all six sections, with the page's contrast audit passing
       because it had checked --brand-text. --brand-text is the role for
       "the brand colour used AS TEXT" and measures 9.19:1 / 10.27:1 on the
       same two grounds. The accent stays where clause 10 puts it: on the
       primary action. On a dark band the inversion below takes over. */
    color: var(--brand-text);
    margin: 0;
    /* CLAUSE 8 · THE EYEBROW SITS 24px ABOVE ITS TITLE. ONE fixed number, box
       to box, the same in every section and at every rung. The owner set it by
       eye on a slider on 2026-08-31 at 25px; it ships as 24, the step on
       clause 1's scale, because 1px is invisible and the ONE-spacing-scale
       rule depends on staying on it.

       v1.1.2's font-metric trim (`--cap-trim`) is retired. The parsed metrics
       did not match what the browser rendered: one 8px computation drew ~4px
       in the hero and ~35px in a section title, and the value went stale
       whenever a rule retuned a heading's leading without restating the trim
       beside it. A fixed number does neither.

       THE ARITHMETIC. The lockup is a flex column with `gap`, so this margin
       ADDS to that gap. The eyebrow therefore spends the DIFFERENCE, nothing
       else — two terms, one line:
           .cl-lockup       gap 16  ->  24 - 16 = margin  8  ->  16 + 8 = 24
           a hero lockup    gap 24  ->  24 - 24 = margin  0  ->  24 + 0 = 24
           no gap declared  fallback 0  ->      margin 24    ->   0 + 24 = 24

       THE CONTRACT, unchanged: a stack holding an eyebrow declares
       --lockup-gap and reads it back into `gap`. Retune THAT, never `gap`
       directly. Because this line names the same variable the gap reads, both
       halves move together and the pair cannot go stale. */
    margin-bottom: calc(var(--space-lg) - var(--lockup-gap, 0px));
}
/* TYPE · the section title. ONE value in every section — repeating a rung is
   the norm, not a failure of imagination. */
.cl-title {
    font-family: var(--font-heading);
    font-weight: var(--fw-700);
    font-size: var(--fs-title);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-tight);
    text-wrap: balance;
    max-width: 20ch;
    margin: 0;
    color: inherit;
}
/* TYPE · body at 1.6, measure 50–80ch. */
.cl-lead {
    font-size: var(--fs-lead);
    line-height: var(--lh-normal);
    max-width: 56ch;
    margin: 0;
    color: var(--text-body);
}
/* AIR · minimum 12px between two unrelated elements. Under 12px is legal
   only INSIDE one lockup, which is what this is. */
.cl-lockup {
    /* The stack's general gap, named so .cl-eyebrow can offset against it.
       A section retunes THIS, never `gap`. */
    --lockup-gap: var(--space-md);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--lockup-gap);
}
/* CLAUSE 19 · ONE ALIGNMENT PER SECTION. Every element in a section shares
   one text alignment, and this page's is left, on the one rail (clause 3).
   Owner, 2026-08-31: "dont align some text to left and center some. They
   should be uniform." A section that centres, centres its eyebrow too — so a
   centring section flips BOTH axes on the lockup and never `text-align`
   alone, which is the mixed state the correction names: `text-align: center`
   with `align-items: flex-start` renders a left-hung eyebrow and a left-hung
   button row over centred body copy, and every one of those declarations
   looks correct on its own line. */
.cl-lockup--center {
    align-items: center;
    text-align: center;
}
/* On a dark ground the lockup inverts from the band, not from a second set
   of classes. */
.cl-band--ink .cl-eyebrow,
.cl-band--brand .cl-eyebrow { color: var(--accent); }
.cl-band--ink .cl-lead,
.cl-band--brand .cl-lead { color: var(--text-on-brand); }

/* RADIUS · 999 on every control. THE ONE CONTROL, three treatments that stay
   tellable apart with the colour removed: filled / outlined / bare. */
.cl-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;                       /* >= 44 touch target */
    padding: 0 var(--space-lg);
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    font-weight: var(--fw-600);
    line-height: var(--lh-none);
    white-space: nowrap;
    border: var(--border-2) solid transparent;
    transition: transform var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-out),
                border-color var(--dur-2) var(--ease-out),
                box-shadow var(--dur-2) var(--ease-out);
}
/* COLOUR · the accent appears on the primary action and nowhere else.

   ⚠ THE DOUBLED CLASS IS THE POINT — DO NOT "TIDY" IT TO ONE.
   Each variant is written as a COMPOUND on the control itself,
   `.cl-pill.cl-pill--primary`, which is (0,2,0). Written as the single class
   it looks like, it is (0,1,0), and the engine's own context rule
   `.hzo-section--dark a { color: #fff }` (components/_base/dark-section.css)
   is (0,1,1) and REPAINTS IT — silently, with the page's own stylesheet
   saying exactly the right thing and every gate GREEN.
   MEASURED 2026-08-28 on 6.hoziodev.com/home-r2/: the hero's and the CTA
   band's primary pill rendered #FFFFFF on the mint accent at 1.70:1 against
   the intended 8.65:1 — a WCAG-AA failure on the two loudest buttons on the
   page, invisible in the source CSS and invisible to every static gate.
   The compound is on the CONTROL and not on the ancestor
   (`.hzo-section--dark .cl-pill--primary` would also win here): a rule that
   does not have to name the ancestor repainting it also survives `.on-photo`,
   a dark card, a dark widget surface, and the next engine context class
   nobody has written yet. Enumerating ancestors fixes one site once; this
   fixes the class of bug. Same reason on --outline and --ghost. */
.cl-pill.cl-pill--primary { background: var(--accent); color: var(--text-on-accent); }
.cl-pill.cl-pill--outline { background: transparent;   color: var(--brand-text); border-color: var(--brand); }
/* On a photograph or a dark band. No shadow at any state: the contract
   forbids a border and a shadow on one object, so its bloom is a ground
   bloom. */
.cl-pill.cl-pill--ghost   { background: transparent;   color: var(--text-on-brand); border-color: var(--text-on-brand); }


/* ═══ THE ONE HOVER VOCABULARY ═════════════════════════════════════════════
   translateY(-3px) + shadow bloom + inner image scale(1.04), on click
   targets only. Declared once, here, for the whole page. Anything that
   hovers differently is a second vocabulary and a defect. */
@media (hover: hover) {
    a.cl-card:hover,
    .cl-card-link:hover .cl-card {
        transform: translateY(var(--lift));
        box-shadow: var(--shadow-object-hover);
    }
    .cl-pill--primary:hover { transform: translateY(var(--lift)); box-shadow: var(--shadow-object-hover); }
    .cl-pill--outline:hover { transform: translateY(var(--lift)); background: var(--brand); color: var(--text-on-brand); }
    .cl-pill--ghost:hover   { transform: translateY(var(--lift)); background: color-mix(in srgb, var(--text-on-brand) 18%, transparent); }
    a:hover > .cl-frame--zoom > img,
    a:hover .cl-frame--zoom > img,
    .cl-card-link:hover .cl-frame--zoom > img { transform: scale(1.04); }
}
.cl-frame--zoom > img { transition: transform var(--dur-4) var(--ease-out); }

/* FOCUS PARITY · every :hover subject above has a :focus-visible reaching the
   same element. 3px outline at 3px offset — the contract's numbers, which are
   one step above the engine's 2/2 default. This is not optional polish; a
   hover with no focus partner is a keyboard user with no affordance. */
:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 3px; }
.cl-band--ink :focus-visible,
.cl-band--brand :focus-visible { outline-color: var(--accent); }
a:focus-visible > .cl-frame--zoom > img,
a:focus-visible .cl-frame--zoom > img { transform: scale(1.04); }

/* The engine's a11y net zeroes transition DURATION under reduced motion; it
   cannot zero a transform, which would then apply instantly. Name the
   subjects that move. */
@media (prefers-reduced-motion: reduce) {
    a.cl-card:hover,
    .cl-card-link:hover .cl-card,
    .cl-pill:hover { transform: none; }
    .cl-frame--zoom > img { transition: none; }
    a:hover .cl-frame--zoom > img,
    a:focus-visible .cl-frame--zoom > img,
    .cl-card-link:hover .cl-frame--zoom > img { transform: none; }
}


/* ═══ THE LINK POLICY (clause 17) ══════════════════════════════════════════
   An underline means "this word, in this sentence, is a link". It does NOT
   mean "this is clickable": a pill, a nav item, a card and a phone control
   each say that with their own shape, and underlining them says it twice.
   Owner, 2026-08-31: "we shouldn't underline all the links, only underline if
   it is a hyperlink in body text."

   WHY THIS BLOCK HAS TO EXIST. The engine's base.css ships
   `a { text-decoration: var(--link-decoration, underline) }`, so every anchor
   on a Hozio page arrives underlined and each engine component has to cancel
   it for itself. Nothing cancels it for a class this site invents, which is
   how an underlined pill label shipped — and nothing cancels it for the
   chrome, fact rails and tel links a page adds later either.

   SO THE DEFAULT IS INVERTED, NOT PATCHED. Enumerating every control that
   must not be underlined is a list that is wrong the moment a page adds a
   class to it; the measured proof is a live page where six control classes
   were named and four more (a utility bar, a hero fact rail, a tel control)
   were still underlined because nobody had thought to name them. So the
   engine's own hook is re-pointed once, and body copy opts back in. Reading
   an engine token this way is the sanctioned override route
   (styling-layers.md, "scoped custom-property shadowing"), and it is one
   declaration instead of a list that has to be maintained.

   REST STATE, NOT HOVER. Hover keeps the page's ONE vocabulary (lift + bloom
   + inner image scale). An underline is never the hover treatment — adding
   one on hover is a second vocabulary and a defect. */

/* 0 · THE DEFAULT. The engine reads this token for every `a`; unset it here
      and controls, nav, chrome, card links, fact rails and tel links are all
      clean at rest without being named. */
:root { --link-decoration: none; }

/* 1 · BODY COPY OPTS BACK IN — the one place an underline belongs: an inline
      link inside a paragraph, a lead, prose list text, or a form's consent
      sentence (Fluent renders that one inside `label > span`, not a `<p>`,
      which is why it is named).
      `li a` is deliberately NOT blanket, and `.entry-content` is deliberately
      NOT one of its ancestors: that wrapper is the whole page, so
      `.entry-content li a` reaches every list on it. Measured 2026-08-31 on
      6.hoziodev.com/home-r2/ — it underlined the hero's trust rail, which is a
      link list, not prose. A list item is as often a link list (a footer
      column, a fact rail, a card grid) as it is prose. `.hzo-prose` is the one
      region that is prose by declaration, so it is the only one here.
      The ancestors sit in `:where()` so each selector stays at (0,0,2) and a
      control class (0,1,0) beats it without needing an exception. */
:where(main, .hzo-section, .hzo-prose, .entry-content, .cl-card) p a,
:where(.hzo-prose) li a,
:where(.hzo-form) label a,
.cl-lead a {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
}

/* 2 · CONTROLS AND LINK LISTS — never underlined, including inside body copy.
      Rule 0 already gets them there; this list is what holds when a control
      sits inside a paragraph or a lead. `.cl-cue` is the name for a card's or
      a section's text cue ("Read the kitchen page"): a card link, not body
      copy. quality-rules §11.5 still wants a rest signal on a card link and
      allows three — chevron, underlined title, visible edge — so a card that
      drops the underline spends one of the other two.
      `.cl-lead a` is (0,1,1) and outranks a bare `.cl-pill`, so the pill
      inside a lead is written compound. */
.cl-pill,
.cl-eyebrow,
.cl-cue,
a.cl-card,
.cl-card-link,
.cl-tel,
.cl-link-list a,
.cl-lead a.cl-pill {
    text-decoration: none;
}


/* ═══ SITE-WIDE ════════════════════════════════════════════════════════════ */

/* Bare-prose safety net — ships with the flow-layout <main> (PF-064).
   templates/index.html uses layout:"default" so every .hzo-section can paint
   its background across the full viewport. The trade-off is that post_content
   is no longer constrained by the template, and post_content is editable in
   wp-admin: a paragraph pasted outside the section skeleton would run the
   full viewport width, which at 1920px is unreadable.

   So bare text-level blocks that are NOT inside a section get the container
   treatment back. Deliberately narrow — it names text-level blocks only and
   never touches section, .hzo-section, or anything a page intentionally
   full-bleeds. :where() keeps specificity at 0 so any real rule still wins.
   Keep this when adding site-wide rules below. */
:where(.entry-content) > :is(p, h1, h2, h3, h4, h5, h6, ul, ol, dl, blockquote, figure, table, pre) {
    max-width: var(--container-max-width, 1350px);
    margin-inline: auto;
    padding-inline: var(--space-md);
}


/* ─── Home ──────────────────────────────────────────────────────────────── */


/* ─── Services ──────────────────────────────────────────────────────────── */


/* ─── About ─────────────────────────────────────────────────────────────── */


/* ─── Contact ───────────────────────────────────────────────────────────── */
