/* =========================================================================
   Central Valley Design System — Colors & Type
   Source of truth for brand tokens. Import this first.
   ========================================================================= */

/* ---------- FONTS ---------- */
/* Homestead: headline typeface. Uppercase only. */
@font-face {
  font-family: "Homestead";
  src: url("https://content.central-valley.com/brand/fonts/HomesteadRegular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Akzidenz-Grotesk Next: body typeface. (Only Regular TTF on hand;
   Bold/Black/Light fall back to system weights until more cuts are licensed.) */
@font-face {
  font-family: "Akzidenz-Grotesk Next";
  src: url("https://content.central-valley.com/brand/fonts/AkzidGrtskNextRegular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Wisdom Script: descriptor / "Established" script. Use sparingly. */
@font-face {
  font-family: "Wisdom Script";
  src: url("https://content.central-valley.com/brand/fonts/WisdomScriptAJ.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* =======================================================================
     PRIMARY PALETTE (from CVBS Brand Guidelines, p.17)
     ======================================================================= */
  --cv-red:       #B40C2A;   /* PMS 200 C  · CMYK 16/100/93/2 · RGB 180 12 42 */
  --cv-beige:     #F0EDE4;   /* PMS 7527 U · CMYK 5/5/10/0   · RGB 240 237 228 */
  --cv-black:     #343230;   /* PMS Black 7 C · CMYK 32/30/37/91 · RGB 52 50 48 */

  /* Paper white — used for page backgrounds inside digital surfaces.
     Not a brand primary, but documented in CVB-DESIGN-SYSTEM.md */
  --cv-white:     #FFFFFF;

  /* =======================================================================
     SUPPORTING PALETTE — tints of Red and Black (guidelines p.18)
     Use only for infographics / data hierarchy. Never replace a primary.
     ======================================================================= */
  --cv-red-70:    #C74354;
  --cv-red-50:    #D98594;
  --cv-red-30:    #EAB6BE;
  --cv-red-10:    #F8E1E5;

  --cv-black-70:  #6B6966;   /* also serves as text-muted */
  --cv-black-50:  #9A9895;
  --cv-black-30:  #C2C1BF;
  --cv-black-10:  #EBEAE9;

  /* Warm border derived from the CV Beige → CV Black gradient.
     Used for dividers in digital surfaces. */
  --cv-border:    #D4D1C7;

  /* =======================================================================
     SEMANTIC — digital surfaces only.
     These extend the primary palette for states the print guide doesn't cover.
     ======================================================================= */
  --cv-success:   #2A7D4F;   /* muted forest green; reads next to CV Red */
  --cv-warning:   #C28B2B;   /* amber in the same warm family as CV Beige */
  --cv-danger:    var(--cv-red);
  --cv-info:      #2C5E7A;

  /* Status dots (ERP / internal tool lineage) */
  --status-active:  #22C55E;
  --status-pending: #F59E0B;
  --status-idle:    #6B7280;

  /* =======================================================================
     BASE COLOR TOKENS — reference by role, not by name
     ======================================================================= */
  --page-bg:      var(--cv-white);
  --surface-bg:   var(--cv-beige);
  --panel-bg:     var(--cv-white);
  --panel-header-bg: var(--cv-black);

  --fg-1:         var(--cv-black);     /* primary text */
  --fg-2:         #6B6966;             /* muted / secondary */
  --fg-3:         #9A9895;             /* hints, placeholders */
  --fg-inverse:   var(--cv-beige);     /* text on CV Red / CV Black */

  --accent:       var(--cv-red);
  --accent-hover: #8F0A22;
  --accent-press: #7A091D;

  --border:       var(--cv-border);
  --border-strong:#A6A39A;
  --rule:         var(--cv-black);     /* headline ball-terminal rule color */

  /* =======================================================================
     TYPE — FAMILIES
     ======================================================================= */
  --font-headline:   "Homestead", "Saira Condensed", "Oswald", Impact, sans-serif;
  --font-body:       "Akzidenz-Grotesk Next", "Inter", -apple-system,
                     BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-script:     "Wisdom Script", "Allura", "Great Vibes", cursive;
  --font-mono:       "JetBrains Mono", Monaco, Menlo, "Ubuntu Mono", monospace;

  /* =======================================================================
     TYPE — SCALE (px, matched to print guide's 3× ratio between body and headline)
     ======================================================================= */
  --fs-12:  0.75rem;
  --fs-14:  0.875rem;
  --fs-16:  1rem;
  --fs-18:  1.125rem;
  --fs-20:  1.25rem;
  --fs-24:  1.5rem;
  --fs-32:  2rem;
  --fs-40:  2.5rem;
  --fs-56:  3.5rem;
  --fs-72:  4.5rem;
  --fs-96:  6rem;

  /* =======================================================================
     SPACING — 4px base
     ======================================================================= */
  --space-1:  0.25rem;   /*  4 */
  --space-2:  0.5rem;    /*  8 */
  --space-3:  0.75rem;   /* 12 */
  --space-4:  1rem;      /* 16 */
  --space-5:  1.5rem;    /* 24 */
  --space-6:  2rem;      /* 32 */
  --space-7:  3rem;      /* 48 */
  --space-8:  4rem;      /* 64 */
  --space-9:  6rem;      /* 96 */

  /* =======================================================================
     RADII — keep conservative; print-first brand
     ======================================================================= */
  --radius-sm:   2px;
  --radius-md:   4px;
  --radius-lg:   6px;
  --radius-xl:   10px;
  --radius-pill: 999px;

  /* =======================================================================
     ELEVATION — subtle, never flashy. Paper-on-paper feel.
     ======================================================================= */
  --shadow-1: 0 1px 2px rgba(52, 50, 48, 0.06),
              0 1px 1px rgba(52, 50, 48, 0.04);
  --shadow-2: 0 2px 6px rgba(52, 50, 48, 0.08),
              0 1px 2px rgba(52, 50, 48, 0.04);
  --shadow-3: 0 8px 24px rgba(52, 50, 48, 0.10),
              0 2px 4px rgba(52, 50, 48, 0.06);
  --shadow-inset: inset 0 1px 2px rgba(52, 50, 48, 0.08);

  /* =======================================================================
     MOTION
     ======================================================================= */
  --ease-standard: cubic-bezier(0.2, 0.0, 0.0, 1.0);
  --ease-in:       cubic-bezier(0.4, 0.0, 1.0, 1.0);
  --ease-out:      cubic-bezier(0.0, 0.0, 0.2, 1.0);
  --duration-1:    120ms;
  --duration-2:    200ms;
  --duration-3:    300ms;

  /* =======================================================================
     LAYOUT
     ======================================================================= */
  --container-sm: 680px;
  --container-md: 960px;
  --container-lg: 1200px;
  --container-xl: 1440px;
}

/* =========================================================================
   SEMANTIC ELEMENT STYLES — drop-in defaults for pages using this CSS
   ========================================================================= */
html {
  color: var(--fg-1);
  background: var(--page-bg);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body { margin: 0; }

/* Homestead is uppercase only — enforce it. */
h1, h2, h3,
.headline,
.display {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 0.93;   /* per print guide: 93% of font size */
  color: var(--fg-1);
  margin: 0;
  text-wrap: balance;
}

h1, .display { font-size: var(--fs-72); letter-spacing: 0.03em; }
h2            { font-size: var(--fs-40); }
h3            { font-size: var(--fs-24); }

/* Headlines may appear on CV Red — allow inversion */
.on-red   { color: var(--cv-beige); }
.on-black { color: var(--cv-beige); }

/* Sub-head / small caps in body type (Akzidenz) */
h4, h5, h6, .eyebrow {
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-1);
  margin: 0;
}
h4 { font-size: var(--fs-16); }
h5 { font-size: var(--fs-14); }
h6, .eyebrow { font-size: var(--fs-12); }

/* Body */
p, .body {
  font-family: var(--font-body);
  font-size: var(--fs-16);
  line-height: 1.55;
  color: var(--fg-1);
  margin: 0 0 var(--space-4);
  text-wrap: pretty;
}
.body-sm { font-size: var(--fs-14); line-height: 1.5; }
.body-lg { font-size: var(--fs-18); line-height: 1.55; }

/* Descriptor (Wisdom Script) — use with restraint, per guide */
.descriptor {
  font-family: var(--font-script);
  font-weight: 400;
  font-style: normal;
  color: var(--fg-1);
  font-size: var(--fs-24);
  line-height: 1;
}

/* Ball-terminal rule lockup (guidelines p.29) —
   a descriptor centered between two horizontal rules, each capped with a ball. */
.ball-rule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75em;
  color: var(--rule);
}
.ball-rule::before,
.ball-rule::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: currentColor;
  position: relative;
  max-width: 4em;
}
.ball-rule::before { box-shadow: -0.4em 0 0 0 currentColor; border-radius: 999px; }
.ball-rule::after  { box-shadow:  0.4em 0 0 0 currentColor; border-radius: 999px; }
.ball-rule > .descriptor { font-size: 0.45em; /* ~1/3 of parent headline */ }

code, pre, .mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.6;
}

a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--duration-1) var(--ease-standard),
              color var(--duration-1) var(--ease-standard);
}
a:hover { border-bottom-color: currentColor; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* Colour, type-scale, spacing, radius and shadow tokens come from the canonical
 * Central Valley design system, which buildSite() prepends to this file — the
 * same colors_and_type.css that framework's web app and the kyw site use. Only
 * tokens that file does not define belong here. Fallback values are kept on
 * every var() below so this stylesheet still renders in brand if it is ever
 * served without the token block. */
:root {
  /* 60ch. NOT the same number as characters-per-line: 1ch is the width of "0",
     which is wider than an average lowercase letter, so a 66ch column measured
     79 actual characters. 60ch lands at ~72, inside the comfortable 45-75.
     Measured on the rendered page, not calculated — 78ch was 113. */
  --cv-measure:  60ch;
  /* Motion vocabulary, identical to the kyw site's design-system tokens so CV
   * properties move the same way. Gentle decel, no bounce, no overshoot. */
  --cv-ease:      cubic-bezier(0.2, 0, 0, 1);
  --cv-ease-out:  cubic-bezier(0, 0, 0.2, 1);
  --cv-dur-1:     120ms;
  --cv-dur-2:     200ms;
  --cv-dur-3:     300ms;
  --cv-dur-enter: 600ms;
  --cv-gutter:   clamp(1.25rem, 4vw, 3.5rem);
  /* DO NOT redefine --cv-red / --cv-black / --cv-beige / --cv-border or the
   * tint scales here. The prepended design system already defines all of them,
   * and it derives its semantic tokens FROM them (--fg-1: var(--cv-black)).
   * Aliasing back the other way (--cv-black: var(--fg-1)) makes both sides of
   * the cycle invalid: the hero band lost its black and the headline its red,
   * with no CSS error anywhere. Only add tokens the design system lacks. */
  --cv-red-hover: var(--accent-hover, #8F0A22);
}

/* @font-face for Homestead / Akzidenz / Wisdom Script comes from the prepended
 * design-system file; buildSite() rebases its relative ./fonts/ paths onto this
 * origin, because after Toolbx injects the <link> a relative path would resolve
 * against central-valley.com and 404. */

/* Homestead Inline is the ONE brand face the design-system file does not
 * declare, and the hero pages use it. Declared here rather than inlined as
 * base64 in each page's own CSS — an imported artboard carries a ~40 KB copy of
 * every face it uses, on every page load, for a font the site already serves. */
@font-face {
  font-family: 'Homestead Inline';
  src:
    url('https://content.central-valley.com/brand/fonts/webfont/Homestead-Inline.woff2') format('woff2'),
    url('https://content.central-valley.com/brand/fonts/webfont/Homestead-Inline.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.cv-page {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  color: var(--cv-black, #343230);
  /* CV Beige is the page ground, matching the WordPress original — not white
   * with a beige band. */
  background: var(--cv-beige, #F0EDE4);
  line-height: 1.5;
  font-size: 1.1875rem;
  -webkit-font-smoothing: antialiased;
}
.cv-page * { box-sizing: border-box; }

/*
 * EXCEPT MARKUP THAT CAME FROM A DESIGN EXPORT, WHICH IS AUTHORED IN CONTENT-BOX.
 *
 * Claude Design sets box-sizing only on its own .sc-* scaffolding, so every box
 * a designer draws is content-box: width 700px with 56px of padding is 812px
 * wide, which is what they see on their canvas and what the artboard thumbnail
 * shows.
 *
 * The reset above then re-boxed the whole design on the published page,
 * shrinking that panel to 700px total with 588px of content. b-2026-011's
 * headline wrapped from two lines to four, grew 168px and overflowed the 620px
 * hero it sits in -- so the artboard rendered correctly and the PAGE did not,
 * which is a difference nobody can debug by looking at the design.
 *
 * Measured at 1440, the design's own canvas:
 *
 *   with the bare reset    panel 700px, h1 335px, hero content 765px, CLIPPED
 *   with this exception    panel 812px, h1 167px, hero content 620px, fits
 *
 * The data-dc-tpl attribute is what the exporter puts on every element it
 * emits, so this reaches design markup and nothing we wrote ourselves. It is
 * (0,2,0) against the (0,1,0) above, so it wins without needing important.
 *
 * THE RESET ITSELF STAYS for everything else: our own templates, the relayout
 * primitives and the migrated pages are all written expecting border-box, and
 * changing that would re-lay-out the entire site.
 *
 * NO BACKTICKS IN THIS FILE, comments included. The first version of this note
 * quoted a CSS declaration in backticks, which closed the template literal and
 * turned every line after it into TypeScript -- reported as "an identifier
 * cannot follow a numeric literal" on a px value, nowhere near the cause. That
 * is the fourth time; the rule is in CLAUDE.md for exactly this reason.
 */
.cv-page [data-dc-tpl] { box-sizing: content-box; }
/* Paint the page ground behind the article, but ONLY when the article is a
 * direct child of body — i.e. when this page is being viewed standalone on the
 * content origin. Once Toolbx injects the article deep inside its storefront
 * DOM the selector stops matching, so we never restyle their body. */
body:has(> .cv-page) { background: var(--cv-beige, #F0EDE4); }
body:has(> .cv-page) .cv-page { min-height: 100vh; }

/* Container and measure are SEPARATE elements. Putting both on one div makes
 * 'margin: 0 auto' center the 68ch text column inside the 74rem container, so
 * the prose floats mid-page while the headline sits flush left. */
.cv-page .cv-wrap {
  max-width: 74rem;
  margin: 0 auto;
  padding: 0 var(--cv-gutter);
}
.cv-page .cv-prose {
  max-width: var(--cv-measure);
  margin-left: 0;
  margin-right: 0;
}

/* A PARAGRAPH WAS 16px INSIDE A COLUMN MEASURED FOR 19px.
 *
 * .cv-page sets 1.1875rem, so --cv-measure: 78ch resolves against 19px and
 * gives an 821px column. But the prepended design system carries a BARE
 * p, .body { font-size: var(--fs-16) }, and a direct rule beats inheritance —
 * so every paragraph rendered at 16px in a column sized for 19px. Measured on
 * /winterizing-garden/: 12 of 13 paragraphs at 16px, 821px wide, 113 characters
 * per line against a comfortable 45-75.
 *
 * The tell was visible without measuring anything: a LIST ITEM next to those
 * paragraphs rendered at 19px, because nothing in the design system sets a bare
 * li. Two type sizes in one prose block, and neither was chosen.
 *
 * This is the same class as the bare a underline already documented here: the
 * design system is unscoped and our own rules are scoped under .cv-page, so
 * ours has to reclaim what it means to own.
 */
.cv-page .cv-prose p,
.cv-page .cv-prose li,
.cv-page .cv-prose blockquote { font-size: inherit; }

/* Heading scale. WordPress pages jump h2 -> h6 with h6 used as a visual
 * subtitle; the converter keeps the source tags, so the scale is driven by
 * these rules rather than by trusting the document outline. Fix the outline in
 * the structured content types, not here. */
.cv-page h1, .cv-page h2, .cv-page h3, .cv-page h4, .cv-page h5, .cv-page h6 {
  font-family: 'Homestead', 'Playfair Display', Georgia, serif;
  text-transform: uppercase;
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: 0.005em;
  margin: 0 0 0.5em;
  text-wrap: balance;
}
.cv-page h1 { font-size: clamp(2.5rem, 6vw, 4.25rem); }
/* The lead headline carries CV Red, as on the WordPress original. Red as type
 * color is accent use, not a fill. */
.cv-page h2 {
  font-size: clamp(2rem, 4vw, 3rem);
  color: var(--cv-red, #B40C2A);
  margin-top: 2.25em;
}
.cv-page h3 { font-size: clamp(1.5rem, 2.4vw, 1.875rem); margin-top: 2em; }
.cv-page h4 { font-size: 1.375rem; margin-top: 1.75em; }
/* h5 and h6 are SECTION HEADINGS in this content, not eyebrows. Measured across
 * data/clean: 97 h5 and 144 h6 against 150 h2, and the WordPress original
 * renders them as prominent black uppercase headings. An earlier version styled
 * them as small grey letterspaced eyebrows on the strength of a note in
 * docs/website-migration.md about h6 being used as a visual subtitle; that is
 * true on a handful of pages and wrong on the bulk of them. Anything that
 * genuinely wants the eyebrow look should use .cv-eyebrow explicitly. */
/* h5/h6 are set in Akzidenz bold, NOT Homestead. Comparing our render against
 * the WordPress original side by side: the lead h2 is the slab (Homestead), but
 * the section heads under it are a heavy sans. Getting this wrong makes every
 * page read as slab-heavy. */
.cv-page h5,
.cv-page h6 {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.15;
}
.cv-page h5 { font-size: 1.625rem; margin-top: 2em; }
.cv-page h6 { font-size: 1.3125rem; margin-top: 1.75em; }
/* AN OUTLINE FIX MUST NOT BE A REDESIGN.
 *
 * 92 published rows skipped a heading level: 24 article pages whose only
 * headings were h5, plus pages going h2 straight to h6. A screen reader user
 * navigating by heading jumps h1 -> h5 and cannot tell what nests inside what,
 * so the tags are renumbered to close the gap (scripts/site-heading-levels.mjs).
 *
 * But the tag carries the design as well as the outline, and here the two
 * disagree completely: h5 is 1.625rem Akzidenz bold in CV Black, while h2 is up
 * to 3rem Homestead in CV Red. Renumbering alone would have turned every
 * section heading on those 24 live pages into a lead headline -- a different
 * typeface, a different size and a different colour. So the rewrite carries a
 * class naming the treatment the heading already had, and the appearance is
 * unchanged. Whether the type should follow the new outline is a design
 * decision, taken separately, on purpose.
 *
 * These are (0,2,1) against the (0,1,1) of .cv-page h2, so the class wins --
 * and each one states its colour, because h2 would otherwise supply red. */
.cv-page .cv-h-lead {
  font-family: 'Homestead', 'Playfair Display', Georgia, serif;
  font-size: clamp(2rem, 4vw, 3rem);
  color: var(--cv-red, #B40C2A);
  letter-spacing: 0.005em;
  line-height: 1.02;
  margin-top: 2.25em;
}
.cv-page .cv-h-major,
.cv-page .cv-h-minor {
  font-family: 'Homestead', 'Playfair Display', Georgia, serif;
  color: var(--cv-black, #343230);
  letter-spacing: 0.005em;
  line-height: 1.02;
}
.cv-page .cv-h-major { font-size: clamp(1.5rem, 2.4vw, 1.875rem); margin-top: 2em; }
.cv-page .cv-h-minor { font-size: 1.375rem; margin-top: 1.75em; }
.cv-page .cv-h-section,
.cv-page .cv-h-sub {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  color: var(--cv-black, #343230);
  letter-spacing: 0.01em;
  line-height: 1.15;
}
.cv-page .cv-h-section { font-size: 1.625rem; margin-top: 2em; }
.cv-page .cv-h-sub { font-size: 1.3125rem; margin-top: 1.75em; }
/* Section spacing must not apply to the first thing in the column, or the body
 * opens with a dead gap under the header band. */
.cv-page .cv-prose > :first-child { margin-top: 0; }
.cv-page h1 + .cv-sub { margin-top: -0.25em; }

.cv-page p, .cv-page ul, .cv-page ol, .cv-page blockquote, .cv-page table {
  margin: 0 0 1.25em;
}
.cv-page li { margin-bottom: 0.4em; }
.cv-page strong { font-weight: 600; }

/* Full-bleed pages are excluded: a page imported from a design carries its own
   button and link colors inline, and repainting them here turned white button
   text CV black — red on red, invisible. A design that brings its own palette
   must be left alone.

   The exclusion is :where(:not(...)), NOT :not(...). :not() contributes its
   argument's specificity, so a bare .cv-page:not(.cv-page-full) a is (0,2,1)
   and outranks .cv-page .cv-btn at (0,2,0) — which silently repainted every
   button's text CV black and put a red underline through it. :where() matches
   identically and contributes nothing, leaving this rule exactly as weak as the
   plain .cv-page a it replaced. (This file is a template literal: no backticks
   in these comments.) */
/* THE OTHER UNDERLINE.
   colors_and_type.css marks links with a 1px bottom BORDER rather than
   text-decoration - transparent at rest, currentColor on hover - so setting
   text-decoration: none leaves it untouched and a thin line remains.
   Neutralised here, where this file is composed AFTER the design system and
   wins on equal specificity.

   Deliberately a BARE selector, matching the rule it cancels. That rule is bare
   too, so it reaches every link on the page this stylesheet lands in - including
   the storefront's own chrome, since Toolbx injects our sheet into its SPA. A
   .cv-page-scoped override would cancel it on our content and leave it on
   theirs, which is the half-fix that looks like it worked. */
a {
  border-bottom: 0;
}
a:hover,
a:focus-visible {
  border-bottom-color: transparent;
}

.cv-page:where(:not(.cv-page-full)) a {
  color: var(--cv-black, #343230);
  /* No underline by default. It was on every link and read as heavy on the
     ones that are already obviously links - a card, a phone number, a heading,
     an item in a list. */
  text-decoration: none;
  text-decoration-color: var(--cv-red, #B40C2A);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
  transition: color var(--cv-dur-2) var(--cv-ease);
}
/* EXCEPT inside running prose, where it stays.
   A link in a paragraph has nothing else marking it: it is the same CV Black as
   the text around it, and colouring it CV Red instead would not be enough
   either - red against the body black is 1.84:1, where 3:1 is the floor for
   colour to carry that job alone. Underlined in prose, plain everywhere else. */
.cv-page:where(:not(.cv-page-full)) :is(p, li, blockquote, td) > a,
.cv-page:where(:not(.cv-page-full)) :is(p, li, blockquote, td) :is(strong, em, span) > a {
  text-decoration: underline;
}
.cv-page:where(:not(.cv-page-full)) a:hover,
.cv-page:where(:not(.cv-page-full)) a:focus-visible {
  color: var(--cv-red, #B40C2A);
  /* The cue comes back on hover and focus, so a link that is plain at rest is
     still confirmable by pointer and by keyboard. */
  text-decoration: underline;
}

.cv-page img, .cv-page iframe, .cv-page video {
  max-width: 100%;
  height: auto;
  display: block;
}
.cv-page figure { margin: 2em 0; }
.cv-page figcaption {
  font-size: 0.8125rem;
  color: var(--cv-black-70, #6B6966);
  margin-top: 0.6em;
}

/* CV Red is an accent, never a fill. */
.cv-page .cv-rule {
  border: 0;
  border-top: 2px solid var(--cv-red, #B40C2A);
  width: 4rem;
  margin: 2.5rem 0;
}
.cv-page .cv-eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cv-red, #B40C2A);
  margin: 0 0 1rem;
}

/* Hero. The WordPress original reverses the page title in white Homestead over
 * a full-bleed photograph. With no hero image yet (all media still points at
 * the dead WordPress domain — see the R2 work) this falls back to a solid CV
 * Black band, which reads as deliberate rather than unfinished. */
.cv-page .cv-header {
  position: relative;
  background: var(--cv-black, #343230);
  color: var(--cv-white, #FFFFFF);
  padding: clamp(4rem, 12vw, 9rem) 0 clamp(3rem, 8vw, 6rem);
  overflow: hidden;
}
.cv-page .cv-header h1 { color: var(--cv-white, #FFFFFF); }
.cv-page .cv-header .cv-sub { color: var(--cv-beige, #F0EDE4); }
.cv-page .cv-header .cv-rule { border-top-color: var(--cv-red, #B40C2A); }
/* Free-form pages with a hero image get the SAME blurred treatment as the
 * record types — Tim asked to carry it forward to every page type. See
 * .cv-hero-photo below for why the photograph is blurred rather than cropped. */
.cv-page .cv-header[data-cv-hero]::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--cv-hero-img);
  background-size: cover;
  background-position: 50% 35%;
  background-repeat: no-repeat;
  filter: blur(28px) saturate(0.85);
  transform: scale(1.12);
}
.cv-page .cv-header[data-cv-hero]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(52, 50, 48, 0.58);
}
.cv-page .cv-header[data-cv-hero] > .cv-wrap { position: relative; z-index: 1; }
.cv-page .cv-header > .cv-wrap { position: relative; }

.cv-page .cv-body { padding: clamp(3rem, 7vw, 5.5rem) 0 clamp(4rem, 9vw, 7rem); }

.cv-page table { border-collapse: collapse; width: 100%; }
.cv-page th, .cv-page td {
  text-align: left;
  padding: 0.6em 0.9em;
  border-bottom: 1px solid var(--cv-black-10, #EBEAE9);
}
.cv-page th {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cv-black-70, #6B6966);
}

/* A section label that was bold body copy and is now a heading.
 *
 * These pages carried their sections as a <strong> on a line of its own --
 * /california-residents/ had eleven of them and no headings at all, so there
 * was no outline to navigate a 2,518-word legal notice by. The tag is the fix;
 * the type is not part of it. .cv-page h2 is up to 3rem Homestead in CV Red, so
 * converting the tag alone would restyle a legal page into eleven lead
 * headlines. This is what they already looked like.
 */
.cv-page .cv-h-strong {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  /* INHERIT, not 1rem. The body is 1.1875rem, so a fixed 1rem made every one of
   * these SMALLER than the bold text it replaced — the opposite of the promise
   * that converting the tag does not move the type. Inheriting is the only
   * value that is right by construction if the body size ever changes. */
  font-size: inherit;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: normal;
  text-transform: none;
  color: var(--cv-black, #343230);
  margin: 1.75em 0 0.35em;
}

/* LEGAL PAGES ARE READ, not scanned, and these four run to 1,500-2,600 words.
 * data-cv-kind is already on the article, so this needs no template change.
 *
 * Three things, all about comfort rather than decoration:
 *
 *  - a shorter measure. 78ch is fine for an article and long for dense clause
 *    prose. 72ch is chosen rather than the 68ch that reads best, because the
 *    CCPA disclosure table is ~650px and 68ch would put it into its scroll
 *    region on a DESKTOP — trading a reading improvement for a worse table.
 *  - looser leading. Long sentences full of defined terms need the extra line.
 *  - PROXIMITY. A heading belongs to the text below it, so the space above is
 *    large and the space below is small. Equal space either side is what makes
 *    a long document read as undifferentiated.
 */
/* 62ch, tightened with the site measure — 72ch was chosen when paragraphs were
   rendering at 16px and measured 86 characters once they inherited 19px. The
   floor is the disclosure table: it is ~650px, and 62ch at 19px is ~660px, so
   this is the tightest legal measure that does not push the table into its
   scroll region on a desktop. */
.cv-page[data-cv-kind="LEGAL"] .cv-prose { max-width: 62ch; }
.cv-page[data-cv-kind="LEGAL"] .cv-prose p,
.cv-page[data-cv-kind="LEGAL"] .cv-prose li { line-height: 1.65; }
.cv-page[data-cv-kind="LEGAL"] .cv-prose p { margin-bottom: 1.4em; }
.cv-page[data-cv-kind="LEGAL"] .cv-prose .cv-h-strong {
  margin-top: 2.75em;
  margin-bottom: 0.45em;
}
/* The table is data, not prose, so the reading measure must not squeeze it. */
.cv-page[data-cv-kind="LEGAL"] .cv-prose .cv-scroll { max-width: none; }

/* "Last updated", at the foot of a policy page.
 *
 * Quiet but not faint: it is a fact somebody may specifically be looking for —
 * whether the notice they are reading is current — so it sits above the 4.5:1
 * floor rather than being treated as fine print. --cv-black-70 measures 4.67:1
 * on the beige ground.
 */
.cv-page .cv-policy-updated {
  margin-top: 3rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--cv-black-30, #B8B6B4);
  font-size: 0.9375rem;
  color: var(--cv-black-70, #6B6966);
}

/* The contents list on a long legal page.
 *
 * A plain list of links, not a sidebar: it has to work at 390px on a storefront
 * that is majority mobile, and a floating panel there is a thing that covers
 * the text you are trying to read. Two columns from 48rem, where there is room.
 *
 * The links keep their underline. Everywhere else on the site a link in running
 * prose is underlined and a standalone one is not, but every line here IS a
 * link, so removing the underline would leave a list of identical-looking items
 * with no indication that any of them do anything.
 */
.cv-page .cv-toc {
  /* --cv-black-30, not -20: the design system defines 10/30/50/70 and nothing
   * else, so var(--cv-black-20, ...) silently uses the literal fallback and
   * stops following the palette. The rule is decorative, so it is exempt from
   * the 3:1 floor that the form-field boundaries have to meet. */
  border-top: 1px solid var(--cv-black-30, #B8B6B4);
  border-bottom: 1px solid var(--cv-black-30, #B8B6B4);
  padding: 1.25rem 0 1.5rem;
  margin: 0 0 2.5rem;
}
.cv-page .cv-toc-title {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cv-black-70, #6B6966);
  margin: 0 0 0.75rem;
  line-height: 1.2;
}
.cv-page .cv-toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.9375rem;
  line-height: 1.4;
}
.cv-page .cv-toc li { margin: 0 0 0.5em; }
.cv-page .cv-toc a { color: var(--cv-black, #343230); }
.cv-page .cv-toc a:hover,
.cv-page .cv-toc a:focus-visible { color: var(--cv-red, #B40C2A); }
@media (min-width: 48rem) {
  .cv-page .cv-toc ol { columns: 2; column-gap: 2.5rem; }
  /* A list item must not break across the column boundary. */
  .cv-page .cv-toc li { break-inside: avoid; }
}
/* A heading jumped to from the contents must not land under a sticky header,
 * and should be obvious when you arrive. */
.cv-page .cv-prose h2[id] { scroll-margin-top: 1.5rem; }

/* The CCPA disclosure table on /california-residents/.
 *
 * Its first column is a ROW HEADER, not a label: it carries a whole category
 * with its statutory citation, up to a line and a half of it. The generic
 * .cv-page th treatment -- 0.75rem, uppercase, letterspaced, CV Black at 70% --
 * is right for a column heading and unreadable for that, so a row header is set
 * as body copy that happens to be bold.
 *
 * Cells align to the TOP: the purpose column runs to several lines while
 * "Collected" is one word, and centred cells would leave every Yes floating in
 * the middle of its row with nothing to line up against.
 */
.cv-page .cv-ccpa th,
.cv-page .cv-ccpa td { vertical-align: top; }
.cv-page .cv-ccpa th[scope="row"] {
  font-size: 1rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--cv-black, #343230);
  font-weight: 700;
  min-width: 14rem;
}
/* One word per cell, so it must not take a share of the width the prose needs. */
.cv-page .cv-ccpa td:nth-child(3) { width: 1%; white-space: nowrap; }

/* MOBILE. These pages are injected into a majority-mobile storefront, so a
 * narrow viewport is the primary case, not an afterthought. Everything above
 * is fluid by construction (clamp() type, max-width measure, no fixed widths);
 * these rules cover what content can still do to break it. */

/* Wide content scrolls in its own box; the page body never scrolls sideways. */
.cv-page .cv-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}
/* Long unbroken strings (URLs, part numbers, SKUs) must not widen the page. */
.cv-page p, .cv-page li, .cv-page td, .cv-page h1, .cv-page h2, .cv-page h3 {
  overflow-wrap: break-word;
}
/* Google Maps embeds arrive fixed-size from WordPress; prepareBodyHtml strips
 * the width/height attributes and wraps them in this fluid box. */
.cv-page .cv-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: 2em 0;
  background: var(--cv-black-10, #EBEAE9);
}
.cv-page .cv-embed .cv-embed-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
@media (max-width: 560px) {
  .cv-page .cv-embed { aspect-ratio: 4 / 3; }
  /* Tap targets: inline links in prose get vertical breathing room. */
  .cv-page li { margin-bottom: 0.7em; }
}

/* =========================================================================
   STRUCTURED RECORDS — portrait-led (design direction B)
   -------------------------------------------------------------------------
   The photograph carries the page: name reversed over it, a CV Red band of
   facts directly beneath, then the biography at full measure and the contact
   actions last. Chosen from three directions on 2026-08-20.
   ========================================================================= */

/* Hero with a photograph. The scrim is a FLAT wash, not a gradient — "no
 * gradients" is a non-negotiable brand rule and at this opacity the two are
 * near-indistinguishable over a photo. The design canvas mocked a gradient;
 * this is the deliberate divergence. */
/* The hero is the portrait BLURRED, not cropped. A single crop position cannot
 * work across twelve photographs: at 50% 12% Lori framed well and Jack's landed
 * on the wall behind him, because the subjects sit at different heights. Out of
 * focus, composition stops mattering — the photo becomes color and place — and
 * the real portrait is shown properly in the body instead.
 *
 * This is a blurred IMAGE, not backdrop-filter; the brand's "no backdrop-blur"
 * rule is about glassmorphism over content, and Tim asked for this treatment
 * directly. */
.cv-page .cv-hero-photo {
  min-height: 26rem;
  display: flex;
  align-items: flex-end;
  padding-top: clamp(5rem, 14vw, 9rem);
}
.cv-page .cv-hero-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--cv-hero-img);
  background-size: cover;
  background-position: 50% 35%;
  background-repeat: no-repeat;
  filter: blur(28px) saturate(0.85);
  /* Scale past the edges so the blur does not feather into bare background. */
  transform: scale(1.12);
}
.cv-page .cv-hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(52, 50, 48, 0.58);
}
.cv-page .cv-hero-photo > .cv-wrap { position: relative; z-index: 1; width: 100%; }
@media (max-width: 40rem) {
  .cv-page .cv-hero-photo { min-height: 17rem; }
  .cv-page .cv-hero-photo::before { filter: blur(18px) saturate(0.85); }
}

/* Facts band. CV Red as a band is the strongest use of the accent on the site;
 * it is deliberate here and should not spread to other page types. */
.cv-page .cv-factstrip {
  background: var(--cv-red, #B40C2A);
  color: var(--cv-white, #FFFFFF);
}
.cv-page .cv-factstrip-grid {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.cv-page .cv-factstrip-grid > div {
  padding: 1.35rem 0 1.35rem 1.75rem;
  border-left: 1px solid rgba(255, 255, 255, 0.24);
}
.cv-page .cv-factstrip-grid > div:first-child { padding-left: 0; border-left: 0; }
/* The materials are links to the storefront's search, and they sit on the red
   band where every other word is plain text. The design system marks a link
   with a BORDER, not text-decoration, and our own rules keep that border only
   inside running prose — a dd is not prose, so without this they would be
   styled exactly like the text beside them and nothing would say they can be
   clicked. A half-strength rule at rest, full strength on hover. */
.cv-page .cv-factstrip a {
  color: inherit;
  border-bottom: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  transition: border-color 120ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-factstrip a:hover,
.cv-page .cv-factstrip a:focus-visible {
  border-bottom-color: currentColor;
}
.cv-page .cv-factstrip dt {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.74;
}
.cv-page .cv-factstrip dd { margin: 0.25rem 0 0; font-size: 1.125rem; }
/* The strip is a reversed surface, so its links must inherit its color. The
   ambient link rule paints them CV black, which on the red band is nearly
   unreadable — and it is exactly the values that are links (address, phone) that
   went dark while the plain text beside them stayed white. Any reversed band
   carrying links needs this; the underline keeps them recognizable as links. */
.cv-page .cv-factstrip a,
.cv-page .cv-factstrip a:hover,
.cv-page .cv-factstrip a:focus-visible {
  color: inherit;
  text-decoration-color: rgba(255, 255, 255, 0.55);
}
.cv-page .cv-factstrip a:hover { text-decoration-color: currentColor; }
@media (max-width: 48rem) {
  .cv-page .cv-factstrip-grid > div {
    padding: 1rem 0;
    border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.24);
  }
  .cv-page .cv-factstrip-grid > div:first-child { border-top: 0; }
}

/* Body: biography beside the real portrait. The right half of the page was
 * empty when the bio ran alone, and the portrait had nowhere honest to live
 * once the hero stopped being a crop of it. */
.cv-page .cv-record-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
@media (min-width: 62rem) {
  .cv-page .cv-record-split { grid-template-columns: minmax(0, 1fr) 22rem; }
}
.cv-page .cv-record-split > .cv-prose { max-width: 68ch; }
.cv-page .cv-record-split > .cv-prose > :first-child { margin-top: 0; }

.cv-page .cv-portrait { margin: 0; overflow: hidden; background: var(--cv-black-10, #EBEAE9); }
.cv-page .cv-portrait img {
  display: block;
  width: 100%;
  /* Contain, not crop: the whole photograph, whatever its composition. */
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 25%;
  transition: transform var(--cv-dur-3) var(--cv-ease);
}
.cv-page .cv-portrait:hover img { transform: scale(1.02); }
/* On a phone the portrait leads — it is what someone wants to see first. */
@media (max-width: 61.99rem) {
  .cv-page .cv-record-split { display: flex; flex-direction: column; }
  .cv-page .cv-portrait { order: -1; max-width: 20rem; }
}

/* Contact actions close the page. The body's own generous bottom padding would
 * otherwise leave a dead band between the last paragraph and the buttons. */
.cv-page.cv-record .cv-body { padding-bottom: clamp(1.5rem, 3vw, 2.5rem); }
.cv-page .cv-actions { padding: 0 0 clamp(4rem, 9vw, 7rem); }
.cv-page .cv-actions-row { display: flex; flex-wrap: wrap; gap: 1rem; }
.cv-page .cv-actions-note {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  color: var(--cv-black-70, #6B6966);
}
.cv-page .cv-btn {
  /* WRITTEN FOR AN <a>, AND A SUBMIT BUTTON IS A <button>.
   *
   * Measured on the live customer-voice form: the tokens resolved correctly
   * (--cv-red #B40C2A, --cv-white #FFFFFF) and the button still painted
   * rgb(191,54,78) on rgb(208,208,208) in Arial with a 2px outset bevel.
   *
   * appearance: auto is why. A <button> is a NATIVE control, so the browser
   * composites its own shading over whatever the author sets — the colours were
   * never being ignored, they were being repainted. It also brings a UA border
   * and its own font-family, and font-family is one of the few properties a
   * form control does not inherit. None of that shows on an <a>, which is why
   * every other button on the site looked right.
   */
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  /* A ground of its own, for the same reason as the outline variant below: this
   * markup is injected into somebody else's SPA and must not depend on what they
   * paint behind it. */
  background-color: transparent;
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  /* 3px, matching the storefront: the Toolbx head sets --button-border-radius
   * to 3px on every control, so a square button here would read as foreign. */
  border-radius: 3px;
  font-weight: 700;
  letter-spacing: 0.02em;
  font-size: 1rem;
  padding: 1rem 2.1rem;
  text-decoration: none;
  background-image: none;
  /* 44px minimum hit target on touch. */
  min-height: 3rem;
  transition: transform var(--cv-dur-1) var(--cv-ease),
              background-color var(--cv-dur-2) var(--cv-ease),
              color var(--cv-dur-2) var(--cv-ease);
}
.cv-page .cv-btn:hover, .cv-page .cv-btn:focus-visible { transform: translateY(-2px); }
.cv-page .cv-btn:active { transform: translateY(0); }
.cv-page .cv-btn-primary {
  background: var(--cv-red, #B40C2A);
  color: var(--cv-white, #FFFFFF);
}
.cv-page .cv-btn-primary:hover, .cv-page .cv-btn-primary:focus-visible {
  background: var(--cv-red-hover);
  color: var(--cv-white, #FFFFFF);
}
.cv-page .cv-btn-outline {
  border: 2px solid var(--cv-black, #343230);
  color: var(--cv-black, #343230);
  /* Explicitly transparent. Declaring the border and the text but NOT the
   * background is the exact shape of the storefront footer bug - that set its
   * text beige, inherited whatever Toolbx painted behind it, and went invisible
   * the day their theme turned white. Here the button inherited a WHITE fill on
   * our beige ground and read as a form input rather than a secondary button.
   * Set both halves of the pair, always. */
  background-color: transparent;
  padding: 0.875rem 1.975rem;
}
.cv-page .cv-btn-outline:hover, .cv-page .cv-btn-outline:focus-visible {
  background: var(--cv-black, #343230);
  color: var(--cv-white, #FFFFFF);
}
@media (max-width: 30rem) {
  .cv-page .cv-actions-row { flex-direction: column; align-items: stretch; }
  .cv-page .cv-btn { justify-content: center; }
}

.cv-page .cv-eyebrow-inverse { color: var(--cv-beige, #F0EDE4); opacity: 0.88; }

/* Language toggle. Sits above the page title, quiet — it is wayfinding, not a
 * call to action, and only appears when the other language actually exists. */
.cv-page .cv-record-link { margin: 2rem 0 0; }

/* Roster: the sales-team page listing its people. */
.cv-page .cv-roster { margin-top: clamp(3rem, 7vw, 5rem); }
.cv-page .cv-roster h2 { margin-top: 0; }
.cv-page .cv-roster-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.cv-page .cv-roster-item a {
  display: block;
  text-decoration: none;
  color: var(--cv-black, #343230);
  background-image: none;
  transition: transform var(--cv-dur-2) var(--cv-ease), color var(--cv-dur-2) var(--cv-ease);
}
.cv-page .cv-roster-item a:hover { transform: translateY(-3px); color: var(--cv-red, #B40C2A); }
.cv-page .cv-roster-item img,
.cv-page .cv-roster-placeholder {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  background: var(--cv-black-10, #EBEAE9);
  margin-bottom: 0.7rem;
}
.cv-page .cv-roster-name { display: block; font-weight: 700; }
.cv-page .cv-roster-title {
  display: block;
  font-size: 0.8125rem;
  color: var(--cv-black-70, #6B6966);
}

/* A full-bleed page carries its own complete design: no measure, no page
 * ground of ours, nothing that would compete with it. Only the reset and the
 * loader apply. */
.cv-page.cv-page-full { background: transparent; font-size: inherit; line-height: inherit; }
body:has(> .cv-page.cv-page-full) { background: #FFFFFF; }

/* =========================================================================
   PAGE-LOAD STAMP
   -------------------------------------------------------------------------
   CV Red fills the screen with the white stamp, then lifts. Carried over from
   the WordPress theme, where it was a swing on cv_stamp_white.svg.

   THREE SAFETY PROPERTIES, none optional:

   1. It only exists when the article is a DIRECT CHILD OF BODY — i.e. this page
      is the top-level document on the content origin. Once Toolbx injects the
      article into their storefront the selector stops matching, so we can never
      drop a red curtain over their site.
   2. It FAILS OPEN. The base rule is hidden; the element is visible only for
      the duration of the animation. No CSS, no animation, reduced motion, an
      unsupported :has() — every one of those leaves the page uncovered rather
      than permanently blanked. Getting this backwards would white-screen the
      site for anyone whose animations do not run.
   3. It never takes a click: pointer-events is none throughout.
   ========================================================================= */
.cv-loader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--cv-red, #B40C2A);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}
.cv-loader-mark {
  display: block;
  width: clamp(132px, 21vw, 198px);
  aspect-ratio: 257 / 262;
  background: no-repeat center / contain;
  background-image: url("https://content.central-valley.com/brand/logo/stamp-white.svg");
}
@media (prefers-reduced-motion: no-preference) {
  body:has(> .cv-page) .cv-loader {
    animation: cv-loader-lift 1500ms var(--cv-ease) forwards;
  }
  body:has(> .cv-page) .cv-loader .cv-loader-mark {
    animation: cv-loader-mark 1500ms var(--cv-ease);
  }
}
/* Both layers cross-fade rather than cutting: the mark settles in, holds, then
 * fades a beat BEFORE the red does, so the stamp is never left hanging on a
 * half-transparent field. */
@keyframes cv-loader-lift {
  0%   { opacity: 1; visibility: visible; }
  58%  { opacity: 1; visibility: visible; }
  99%  { opacity: 0; visibility: visible; }
  100% { opacity: 0; visibility: hidden; }
}
@keyframes cv-loader-mark {
  0%   { opacity: 0; transform: scale(0.96); }
  22%  { opacity: 1; transform: scale(1); }
  52%  { opacity: 1; transform: scale(1); }
  82%  { opacity: 0; transform: scale(1.03); }
  100% { opacity: 0; transform: scale(1.03); }
}

/* =========================================================================
   MOTION
   -------------------------------------------------------------------------
   Toolbx injects this page into its own SPA, and we cannot rely on our
   JavaScript running there or on their scroll container behaving like ours.
   So the rule for every effect below: THE PAGE MUST BE COMPLETE AND READABLE
   WITH NO SCRIPT AND NO ANIMATION AT ALL. Nothing starts at opacity 0 in the
   base cascade; entrance animations declare their hidden state inside
   @keyframes with a 'backwards' fill, so if animation never runs the content is
   simply there. No scroll-triggered hiding anywhere.
   ========================================================================= */

/* Hero: the CV ink-drop — words rise into a clipped line, as on the kyw site.
   The clip is on a wrapper so a non-animating browser still shows the text. */
.cv-page .cv-header h1 { overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  .cv-page .cv-header h1 {
    animation: cv-ink-drop var(--cv-dur-enter) var(--cv-ease) backwards;
  }
  .cv-page .cv-header .cv-rule {
    animation: cv-rule-draw var(--cv-dur-enter) var(--cv-ease) 220ms backwards;
  }
  .cv-page .cv-header .cv-sub {
    animation: cv-fade-in 500ms var(--cv-ease) 300ms backwards;
  }
}
@keyframes cv-ink-drop { from { transform: translateY(105%); } }
@keyframes cv-rule-draw { from { width: 0; opacity: 0; } }
@keyframes cv-fade-in { from { opacity: 0; transform: translateY(8px); } }

/* Links: the red underline grows from the left rather than just recolouring. */
.cv-page .cv-prose a {
  background-image: linear-gradient(var(--cv-red, #B40C2A), var(--cv-red, #B40C2A));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 2px;
  text-decoration: none;
  padding-bottom: 0.08em;
  transition: background-size var(--cv-dur-2) var(--cv-ease),
              color var(--cv-dur-2) var(--cv-ease);
}
.cv-page .cv-prose a:hover,
.cv-page .cv-prose a:focus-visible {
  /* The red fill grows to cover the whole link, so the text MUST invert here.
   * Leaving it red produced red-on-red: the label vanished on hover. */
  color: var(--cv-white, #FFFFFF);
  background-size: 100% 100%;
  background-position: 0 100%;
}

/* Calls to action. The converted markup has zero classes across all 123 items,
   so structure is the only signal: a link that stands alone as a block — either
   a bare anchor between paragraphs, or the sole child of its own paragraph — is
   what the WordPress original renders as the red "Talk to a PRO" button.
   The bare-anchor case is the common one; the delivery page emits it. */
.cv-page .cv-prose > a,
.cv-page .cv-prose p > a:only-child {
  display: inline-block;
  border-radius: 3px;
  background: var(--cv-red, #B40C2A);
  background-image: none;
  color: var(--cv-white, #FFFFFF);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.85em 1.9em;
  margin-top: 0.5em;
  text-decoration: none;
  transition: transform var(--cv-dur-1) var(--cv-ease),
              background-color var(--cv-dur-2) var(--cv-ease);
}
.cv-page .cv-prose > a:hover,
.cv-page .cv-prose > a:focus-visible,
.cv-page .cv-prose p > a:only-child:hover,
.cv-page .cv-prose p > a:only-child:focus-visible {
  color: var(--cv-white, #FFFFFF);
  background: var(--cv-red-hover);
  transform: translateY(-2px);
}
.cv-page .cv-prose > a:active,
.cv-page .cv-prose > a:active,
.cv-page .cv-prose p > a:only-child:active { transform: translateY(0); }

/* Images sit in a clipped frame and lift very slightly on hover. */
.cv-page .cv-prose img {
  transition: transform var(--cv-dur-3) var(--cv-ease);
}
.cv-page .cv-prose a:hover img { transform: scale(1.015); }


/* ---- Customer projects ------------------------------------------------- */
/* The slider is scroll-snap: the browser pages it, so it works with zero
   JavaScript, keyboard and swipe included. With no CSS at all it is still a
   readable vertical list of photographs — nothing here can hide content. */
/* NOT .cv-gallery. That name is taken, by the relayout primitive further down
   this file — an auto-fill grid of minmax(9rem, 1fr) — which is UNSCOPED and
   declared later, so it won on the display property against this block and
   turned the project page into a 12-column grid: the heading took one 152px
   column, the slider took the next, and 46rem slides resolved to 56px slivers.
   Nothing here
   set a display of its own, so there was nothing to override it.
   The other name cannot move: site-relayout-landing.mjs writes .cv-gallery into
   stored page markup, so it is in the database. This one is generated at render
   time and is free to be renamed, so it is this one that moved. */
.cv-page .cv-project-gallery { padding: clamp(2rem, 5vw, 4rem) 0 clamp(1rem, 3vw, 2rem); }
.cv-page .cv-project-gallery h2 { margin: 0 0 0.25rem; }
.cv-page .cv-project-gallery-hint {
  margin: 0 0 clamp(1rem, 2.5vw, 1.75rem);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}
/* AN EDITORIAL MOSAIC, NOT A FILMSTRIP.
 *
 * These pages carry 255 photographs between them, a median of 26 and one run of
 * 65, against a paragraph or two of prose. The photography IS the page. It used
 * to be a horizontal scroll-snap strip one image wide, which hid its own scale:
 * no overview, no sense of progression, and 65 swipes to reach the end of a
 * sequence that runs foundation to framing to finish. On a phone, where most of
 * this traffic is, vertical scrolling is free and horizontal swiping is work.
 *
 * A fixed column count per breakpoint rather than auto-fill, because a wide cell
 * spans two columns, and a two-column span in a one-column grid overflows the
 * row. Two columns is the narrowest it goes, so a wide cell is at most full
 * width.
 *
 * Dense packing lets a later small photograph backfill the hole a wide one
 * leaves, so the mosaic has no gaps at any width without anyone choosing a
 * running order. Aspect ratios rather than row spans: the cell reserves its
 * own space before the image loads, so nothing shifts as the page fills in.
 *
 * With no CSS at all it is a plain vertical list of photographs. */
.cv-page .cv-mosaic {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: dense;
  gap: clamp(0.375rem, 1vw, 0.75rem);
  margin: 0;
  padding: 0 clamp(1rem, 5vw, 3rem);
  list-style: none;
}
@media (min-width: 48rem) {
  .cv-page .cv-mosaic { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 75rem) {
  .cv-page .cv-mosaic { grid-template-columns: repeat(4, 1fr); }
}
/* ONE ROW HEIGHT FOR EVERY CELL, and a wide cell is wider, not taller.
   Cells used to carry their own aspect ratios -- 4/3 normally, 16/9 when wide.
   At 1440 that is 245px against 375px, so any row holding a wide cell left a
   130px hole under its neighbours. Varied scale is the point; ragged holes read
   as breakage. A fixed row height makes every row flush, and object-fit: cover
   means no photograph is distorted to fill one. */
.cv-page .cv-mosaic {
  grid-auto-rows: clamp(9rem, 17vw, 16rem);
}
.cv-page .cv-mosaic > li {
  margin: 0;
  /* A grid track sized 1fr is minmax(AUTO, 1fr), and that automatic minimum is
     the item's min-content size -- so a cell holding a photograph refuses to
     shrink below it and the whole row blows out. Measured: four tracks sizing
     themselves to 500px each and a body 2082px wide inside a 1440px viewport.
     min-width: 0 removes the floor and lets 1fr mean what it says.
     The strip this replaced hid the same problem behind overflow-x: auto. */
  min-width: 0;
}
.cv-page .cv-mosaic > .cv-mosaic-wide {
  grid-column: span 2;
}
.cv-page .cv-mosaic img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent);
}

/* ---- Moving between projects ------------------------------------------- */
/* Previous / next at the foot of a project, with the position in the set.
   A rule above it rather than a filled band: the brand keeps fills for accents,
   and this sits directly under a wall of photographs that has already done all
   the shouting.

   Three columns so the position stays centred even when one end has no link --
   the missing side renders an empty span rather than collapsing, or the single
   remaining link would jump to the middle of the row. Below 40rem it stacks and
   the position comes first, because on a phone "3 of 8" is the useful part and
   two stacked names are not a row any more. */
.cv-page .cv-projnav {
  border-top: 1px solid var(--cv-border, #D4D1C7);
  /* Bottom padding is this element's job: .cv-actions sets none of its own and
     has always taken its top spacing from whatever ran before it. Without this
     the call to action sits directly under the project names. */
  padding: clamp(1.5rem, 3vw, 2.25rem) 0 clamp(1.75rem, 4vw, 3rem);
}
.cv-page .cv-projnav-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: clamp(1rem, 3vw, 2.5rem);
}
.cv-page .cv-projnav-link {
  display: block;
  border-bottom: 0;
  color: inherit;
  min-width: 0;
}
.cv-page .cv-projnav-next { text-align: right; }
.cv-page .cv-projnav-dir {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
  margin-bottom: 0.35rem;
}
.cv-page .cv-projnav-name {
  display: block;
  font-family: var(--font-headline, "Homestead"), sans-serif;
  font-size: clamp(1.0625rem, 2vw, 1.375rem);
  text-transform: uppercase;
  line-height: 1.15;
  transition: color 120ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-projnav-link:hover .cv-projnav-name,
.cv-page .cv-projnav-link:focus-visible .cv-projnav-name {
  color: var(--cv-red, #B40C2A);
}
.cv-page .cv-projnav-pos {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}
@media (max-width: 40rem) {
  .cv-page .cv-projnav-row {
    grid-template-columns: 1fr;
    gap: 1.25rem;
    text-align: left;
  }
  .cv-page .cv-projnav-pos { order: -1; }
  .cv-page .cv-projnav-next { text-align: left; }
  .cv-page .cv-projnav-gap { display: none; }
}

.cv-page .cv-project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.cv-page .cv-project-card a {
  display: grid;
  gap: 0.375rem;
  text-decoration: none;
  color: inherit;
  /* CLIPS THE GROW. Without this the scaled image spills over the grid gap and
     onto its neighbour. Safe here: this card has no sticky child and no popup,
     which is the reason an overflow needs checking before being added anywhere
     in the dashboard. */
  overflow: hidden;
}
.cv-page .cv-project-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent);
  transition:
    opacity 160ms cubic-bezier(0.2, 0, 0.2, 1),
    transform var(--cv-dur-3, 300ms) var(--cv-ease, cubic-bezier(0.2, 0, 0, 1));
}
/* The same slight grow the prose images have on /fire-hardening — 1.5% over
   the shared duration, with the existing fade kept. Small on purpose: a card
   that leaps under the cursor reads as a different component. */
.cv-page .cv-project-card a:hover img,
.cv-page .cv-project-card a:focus-visible img {
  opacity: 0.94;
  transform: scale(1.03);
}
@media (prefers-reduced-motion: reduce) {
  .cv-page .cv-project-card a:hover img,
  .cv-page .cv-project-card a:focus-visible img { transform: none; }
}
.cv-page .cv-project-name {
  font-family: var(--cv-font-display, "Homestead"), Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 1.0625rem;
}
.cv-page .cv-project-where {
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}

/* ---- Scrollable regions ------------------------------------------------- */
/* A KEYBOARD USER CANNOT SCROLL AN UNFOCUSABLE BOX. Wide tables, diagrams and
   code blocks sit in their own overflow-x auto container so the page body
   never scrolls sideways — which is right, and it puts the content to the
   right of the fold out of reach of anybody not using a pointer. WCAG 2.1.1.

   A tabindex of zero is what makes it focusable, and that has to be on the ELEMENT
   — CSS cannot add it. prepareBodyHtml does that at render time. These rules
   are the visible half: the region has to show that it has focus, and it has
   to look scrollable, or a sighted keyboard user tabs into a box with no
   apparent purpose. */
.cv-page .cv-scroll {
  overflow-x: auto;
  /* Momentum on touch, and a scrollbar that is actually visible on macOS
     rather than fading out before anybody notices the region scrolls. */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.cv-page .cv-scroll:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
/* A hint that there IS more to the right, painted only while the region can
   still be scrolled. No JavaScript: a gradient would be a fill, which the
   brand keeps for accents, so this is a hairline rule at the edge. */
.cv-page .cv-scroll > table {
  min-width: 100%;
}

/* ---- Promotions --------------------------------------------------------- */
/* /promotions/ — every live promotion, not just the four the hero holds.
   Deliberately NOT a hero page: these are a set of equals, and a full-bleed
   photograph at the top would promote one of them twice and push the rest
   below the fold. The first card takes a wide slot instead, which gives the
   page a focal point without spending a screen on it. */
.cv-page .cv-header--slim {
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(1.75rem, 4vw, 3rem);
}
.cv-page .cv-header--slim .cv-sub {
  max-width: 38rem;
  margin: 1rem 0 0;
}
.cv-page .cv-promo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1.25rem, 3vw, 2.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* A TRACK SIZED 1fr IS minmax(AUTO, 1fr), and that automatic minimum is the
   item's min-content size — a cell holding a photograph then refuses to shrink
   below it, which is how the project mosaic came out 2082px wide inside a
   1440px viewport. This line is what stops it. */
.cv-page .cv-promo-card { min-width: 0; }
.cv-page .cv-promo-card a {
  /* FLEX COLUMN, not grid, so an auto top margin on the call to action pins it
     to the bottom of the card. Cards in a row are stretched to a shared height,
     and a promotion with no teaser otherwise leaves its "read more" floating
     level with the middle of its neighbours' copy. */
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  height: 100%;
  text-decoration: none;
  color: inherit;
  /* CLIPS THE GROW, so the scaled photograph cannot spill across the gap onto
     its neighbour. Safe here: this card has no sticky child and no popup. */
  overflow: hidden;
}
.cv-page .cv-promo-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent);
  transition:
    opacity 160ms cubic-bezier(0.2, 0, 0.2, 1),
    transform var(--cv-dur-3, 300ms) var(--cv-ease, cubic-bezier(0.2, 0, 0, 1));
}
.cv-page .cv-promo-card a:hover img,
.cv-page .cv-promo-card a:focus-visible img {
  opacity: 0.94;
  transform: scale(1.03);
}
@media (prefers-reduced-motion: reduce) {
  .cv-page .cv-promo-card a:hover img,
  .cv-page .cv-promo-card a:focus-visible img { transform: none; }
}
/* The wide slot, from two columns up only. A lone wide card in a one-column
   grid is just a card, so the renderer also withholds it when there is only
   one promotion. */
@media (min-width: 62rem) {
  .cv-page .cv-promo-card--wide { grid-column: span 2; }
  .cv-page .cv-promo-card--wide img { aspect-ratio: 16 / 9; }
  .cv-page .cv-promo-card--wide .cv-promo-title { font-size: 1.625rem; }
}
.cv-page .cv-promo-title {
  font-family: var(--cv-font-display, "Homestead"), Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 1.25rem;
  line-height: 1.15;
}
.cv-page .cv-promo-teaser {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: color-mix(in srgb, var(--cv-black, #343230) 78%, transparent);
  max-width: 42rem;
}
/* CV Red carries the affordance and nothing is filled with it. 5.93:1 on the
   CV Beige ground — measured, not eyeballed; this palette has produced
   red-on-red three times. */
.cv-page .cv-promo-cta {
  margin-top: auto;
  padding-top: 0.25rem;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cv-red, #B40C2A);
}
.cv-page .cv-promo-cta::after { content: " →"; }

/* ---- The home page's project pan ---------------------------------------- */
/* One box per FEATURED customer project, each cycling through that project's
   own photographs. Scoped under .cv-th because that is the designed home page's
   own namespace — Toolbx lifts our inline style into its SPA, so a bare rule
   here would restyle the whole storefront.

   THE CROSS-FADE IS PURE CSS, deliberately. Page scripts are not proven to run
   inside Toolbx's SPA; a fade that depended on one would show a single frozen
   photograph on the live site while looking correct everywhere we can test.
   The horizontal pan is separate and already driven by HOME_FLOW_JS, which
   takes its travel from strip.scrollWidth and so needs nothing when the number
   of boxes changes. */
.cv-th .pan-link {
  display: block;
  text-decoration: none;
  color: inherit;
  border-bottom: 0;
}
.cv-th .pan-stack {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink, #343230) 8%, transparent);
}
.cv-th .pan-stack img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* NOTHING FAILS TO A BLANK BOX. With no animation running — reduced motion,
     or a browser that ignores the keyframes — the first photograph shows and
     the rest stay hidden. That is the rule every scroll device here follows. */
  opacity: 0;
  animation-duration: var(--cv-pan-cycle, 16s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  /* UNSYNCHRONISED, by a NEGATIVE offset per box.
     Every box fading at the same instant reads as one mechanism rather than
     several projects; half a second apart it reads as a wall of them.
     The offset is negative — each box starts as if it were already part way
     through — rather than positive, and that choice matters. A positive delay
     leaves the element on its BASE style until the delay elapses, so the box
     would hold its first photograph and then blink to the keyframe's opacity 0
     before fading back in. Running from the start means one photograph is
     always inside its visible window, so a staggered box can neither blink nor
     go blank. */
  animation-delay: calc(
    var(--i, 0) * var(--cv-pan-cycle, 16s) / var(--n, 1) -
      var(--b, 0) * var(--cv-pan-stagger, 0.5s)
  );
}
.cv-th .pan-stack img:first-child { opacity: 1; }
/* A single photograph needs no cycle at all. */
.cv-th .pan-stack[data-n="1"] img { animation: none; opacity: 1; }
.cv-th .pan-stack[data-n="2"] img { animation-name: cv-pan-2; }
.cv-th .pan-stack[data-n="3"] img { animation-name: cv-pan-3; }
.cv-th .pan-stack[data-n="4"] img { animation-name: cv-pan-4; }
.cv-th .pan-cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin-top: 0.75rem;
}
.cv-th .pan-title {
  font-family: var(--cv-font-display, "Homestead"), Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 1.0625rem;
}
.cv-th .pan-where {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #343230) 70%, transparent);
}
/* The whole box lifts slightly, the same 1.03 the project cards and the prose
   images on /fire-hardening use. Small on purpose. */
.cv-th .pan-link:hover .pan-stack img,
.cv-th .pan-link:focus-visible .pan-stack img { transform: scale(1.03); }
.cv-th .pan-stack img { transition: transform var(--cv-dur-3, 300ms) var(--cv-ease, cubic-bezier(0.2, 0, 0, 1)); }
.cv-th .pan-link:hover .pan-title { color: var(--cv-red, #B40C2A); }
.cv-th .pan-link:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .cv-th .pan-stack img { animation: none; transition: none; }
  .cv-th .pan-stack img:not(:first-child) { display: none; }
}
@keyframes cv-pan-2 {
  0% { opacity: 0; }
  3.00% { opacity: 1; }
  50.00% { opacity: 1; }
  53.00% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes cv-pan-3 {
  0% { opacity: 0; }
  3.00% { opacity: 1; }
  33.33% { opacity: 1; }
  36.33% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes cv-pan-4 {
  0% { opacity: 0; }
  3.00% { opacity: 1; }
  25.00% { opacity: 1; }
  28.00% { opacity: 0; }
  100% { opacity: 0; }
}

/* ---- Locations --------------------------------------------------------- */
/* Tabs with no JavaScript: radios drive everything through :checked. The radios
   themselves are moved off-screen rather than display:none — a hidden radio is
   unfocusable, which would take the keyboard away from the whole control. */
.cv-page .cv-loc-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.cv-page .cv-loc-tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: clamp(1.25rem, 3vw, 2rem);
  border-bottom: 1px solid color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page .cv-loc-tab {
  padding: 0.5rem 0.875rem;
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 3px 3px 0 0;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
  transition:
    color 160ms cubic-bezier(0.2, 0, 0.2, 1),
    background-color 160ms cubic-bezier(0.2, 0, 0.2, 1);
  margin-bottom: -1px;
}
.cv-page .cv-loc-tab:hover { color: var(--cv-red, #B40C2A); }

/* Only the selected panel shows. Every panel is display:block in the base
   cascade and hidden from here, so a stylesheet that never arrives leaves the
   whole page readable instead of empty. */
.cv-page .cv-loc-tabs .cv-loc-panel { display: block; }
.cv-page .cv-loc-radio ~ .cv-loc-panels > .cv-loc-panel { display: none; }

.cv-page .cv-loc-panel-head {
  display: flex;
  align-items: flex-start;
  gap: clamp(1rem, 3vw, 2rem);
  flex-wrap: wrap;
  padding-bottom: clamp(1rem, 2.5vw, 1.5rem);
  border-bottom: 1px solid color-mix(in srgb, var(--cv-black, #343230) 12%, transparent);
}
.cv-page .cv-loc-logo {
  width: clamp(4.5rem, 10vw, 6.5rem);
  height: auto;
  flex-shrink: 0;
}
.cv-page .cv-loc-panel-head h2 { margin: 0 0 0.25rem; }
.cv-page .cv-loc-panel-head > div { flex: 1 1 16rem; }
.cv-page .cv-loc-status { margin-left: auto; }
.cv-page .cv-loc-address,
.cv-page .cv-loc-phones,
.cv-page .cv-loc-email { margin: 0.25rem 0 0; font-size: 0.9375rem; }
.cv-page .cv-loc-address a,
.cv-page .cv-loc-phones a,
.cv-page .cv-loc-email a { color: inherit; }
.cv-page .cv-loc-panel-body {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
  padding-top: clamp(1.25rem, 3vw, 2rem);
}
.cv-page .cv-loc-hours h3,
.cv-page .cv-loc-about h3 {
  margin: 0 0 0.625rem;
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.cv-page .cv-loc-hours h3 ~ h3 { margin-top: clamp(1.25rem, 3vw, 2rem); }
.cv-page .cv-loc-hoursnote { font-size: 0.9375rem; margin: 0.75rem 0 0; }
.cv-page .cv-loc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(1.25rem, 3vw, 2rem);
}

.cv-page .cv-loc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: clamp(0.75rem, 2vw, 1.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.cv-page .cv-loc-note {
  margin: 0 0 0.5rem;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cv-red, #B40C2A);
}
.cv-page .cv-loc-rows,
.cv-page .cv-hours-list { display: grid; gap: 0.375rem; margin: 0; }
.cv-page .cv-loc-rows > div,
.cv-page .cv-hours-list > div {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.5rem;
  align-items: baseline;
}
.cv-page .cv-loc-rows dt,
.cv-page .cv-hours-list dt {
  margin: 0;
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}
.cv-page .cv-loc-rows dd,
.cv-page .cv-hours-list dd { margin: 0; font-size: 0.9375rem; }
.cv-page .cv-loc-rows a { color: inherit; }

/* The open/closed badge is written by script. Until then the slot is empty and
   collapses to nothing — never a placeholder that could be mistaken for a
   status the page does not actually know. */
.cv-page .cv-openbadge {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.125rem 0.4375rem;
  border-radius: 3px;
  white-space: nowrap;
}
.cv-page .cv-openbadge.is-open { background: #e4ede4; color: #2f5d34; }
.cv-page .cv-openbadge.is-closed { background: #f3e3e5; color: var(--cv-red, #B40C2A); }
.cv-page .cv-loc-badge-line { margin: 0.75rem 0 0; min-height: 1.25rem; }

.cv-page .cv-loc-layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
.cv-page .cv-loc-hours h2,
.cv-page .cv-loc-about h2 { margin-top: 0; }
.cv-page .cv-loc-hours h3 {
  margin: clamp(1.25rem, 3vw, 2rem) 0 0.5rem;
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.cv-page .cv-loc-holiday { font-size: 0.9375rem; line-height: 1.6; }
/* The upcoming closures, generated from the holiday rules. No bullets: it is a
   short list of dates, and the date is doing the marker's job. */
.cv-page .cv-loc-holidays {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.9375rem;
  line-height: 1.6;
}
.cv-page .cv-loc-holidays li { margin: 0; }
.cv-page .cv-loc-holiday-when {
  display: inline-block;
  min-width: 7.5rem;
  font-variant-numeric: tabular-nums;
  color: color-mix(in srgb, currentColor 62%, transparent);
}
.cv-page .cv-loc-services { margin: 0; padding-left: 1.125rem; font-size: 0.9375rem; }

/* One pairing per tab. Written out rather than expressed with :nth-child so the
   rule reads the same as the markup, and so a location added past the limit
   fails visibly (its panel never opens) instead of opening the wrong one. */
.cv-page #cv-loc-tab-1:checked ~ .cv-loc-panels > .cv-loc-panel-1 { display: block; }
.cv-page #cv-loc-tab-1:checked ~ .cv-loc-tablist label[for="cv-loc-tab-1"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-2:checked ~ .cv-loc-panels > .cv-loc-panel-2 { display: block; }
.cv-page #cv-loc-tab-2:checked ~ .cv-loc-tablist label[for="cv-loc-tab-2"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-3:checked ~ .cv-loc-panels > .cv-loc-panel-3 { display: block; }
.cv-page #cv-loc-tab-3:checked ~ .cv-loc-tablist label[for="cv-loc-tab-3"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-4:checked ~ .cv-loc-panels > .cv-loc-panel-4 { display: block; }
.cv-page #cv-loc-tab-4:checked ~ .cv-loc-tablist label[for="cv-loc-tab-4"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-5:checked ~ .cv-loc-panels > .cv-loc-panel-5 { display: block; }
.cv-page #cv-loc-tab-5:checked ~ .cv-loc-tablist label[for="cv-loc-tab-5"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-6:checked ~ .cv-loc-panels > .cv-loc-panel-6 { display: block; }
.cv-page #cv-loc-tab-6:checked ~ .cv-loc-tablist label[for="cv-loc-tab-6"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-7:checked ~ .cv-loc-panels > .cv-loc-panel-7 { display: block; }
.cv-page #cv-loc-tab-7:checked ~ .cv-loc-tablist label[for="cv-loc-tab-7"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-8:checked ~ .cv-loc-panels > .cv-loc-panel-8 { display: block; }
.cv-page #cv-loc-tab-8:checked ~ .cv-loc-tablist label[for="cv-loc-tab-8"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-9:checked ~ .cv-loc-panels > .cv-loc-panel-9 { display: block; }
.cv-page #cv-loc-tab-9:checked ~ .cv-loc-tablist label[for="cv-loc-tab-9"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-10:checked ~ .cv-loc-panels > .cv-loc-panel-10 { display: block; }
.cv-page #cv-loc-tab-10:checked ~ .cv-loc-tablist label[for="cv-loc-tab-10"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-11:checked ~ .cv-loc-panels > .cv-loc-panel-11 { display: block; }
.cv-page #cv-loc-tab-11:checked ~ .cv-loc-tablist label[for="cv-loc-tab-11"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-12:checked ~ .cv-loc-panels > .cv-loc-panel-12 { display: block; }
.cv-page #cv-loc-tab-12:checked ~ .cv-loc-tablist label[for="cv-loc-tab-12"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-13:checked ~ .cv-loc-panels > .cv-loc-panel-13 { display: block; }
.cv-page #cv-loc-tab-13:checked ~ .cv-loc-tablist label[for="cv-loc-tab-13"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-14:checked ~ .cv-loc-panels > .cv-loc-panel-14 { display: block; }
.cv-page #cv-loc-tab-14:checked ~ .cv-loc-tablist label[for="cv-loc-tab-14"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-15:checked ~ .cv-loc-panels > .cv-loc-panel-15 { display: block; }
.cv-page #cv-loc-tab-15:checked ~ .cv-loc-tablist label[for="cv-loc-tab-15"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-16:checked ~ .cv-loc-panels > .cv-loc-panel-16 { display: block; }
.cv-page #cv-loc-tab-16:checked ~ .cv-loc-tablist label[for="cv-loc-tab-16"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-17:checked ~ .cv-loc-panels > .cv-loc-panel-17 { display: block; }
.cv-page #cv-loc-tab-17:checked ~ .cv-loc-tablist label[for="cv-loc-tab-17"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-18:checked ~ .cv-loc-panels > .cv-loc-panel-18 { display: block; }
.cv-page #cv-loc-tab-18:checked ~ .cv-loc-tablist label[for="cv-loc-tab-18"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-19:checked ~ .cv-loc-panels > .cv-loc-panel-19 { display: block; }
.cv-page #cv-loc-tab-19:checked ~ .cv-loc-tablist label[for="cv-loc-tab-19"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-20:checked ~ .cv-loc-panels > .cv-loc-panel-20 { display: block; }
.cv-page #cv-loc-tab-20:checked ~ .cv-loc-tablist label[for="cv-loc-tab-20"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-21:checked ~ .cv-loc-panels > .cv-loc-panel-21 { display: block; }
.cv-page #cv-loc-tab-21:checked ~ .cv-loc-tablist label[for="cv-loc-tab-21"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-22:checked ~ .cv-loc-panels > .cv-loc-panel-22 { display: block; }
.cv-page #cv-loc-tab-22:checked ~ .cv-loc-tablist label[for="cv-loc-tab-22"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-23:checked ~ .cv-loc-panels > .cv-loc-panel-23 { display: block; }
.cv-page #cv-loc-tab-23:checked ~ .cv-loc-tablist label[for="cv-loc-tab-23"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}
.cv-page #cv-loc-tab-24:checked ~ .cv-loc-panels > .cv-loc-panel-24 { display: block; }
.cv-page #cv-loc-tab-24:checked ~ .cv-loc-tablist label[for="cv-loc-tab-24"] {
  color: var(--cv-red, #B40C2A);
  background: #fff;
  border-color: color-mix(in srgb, var(--cv-black, #343230) 14%, transparent);
}

/* ---- Featured customer projects (homepage section) --------------------- */
.cv-page .cv-featured { padding: clamp(2.5rem, 6vw, 5rem) 0; }
.cv-page .cv-featured-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: clamp(1rem, 2.5vw, 1.75rem);
}
.cv-page .cv-featured-head h2 { margin: 0; }
/* With no heading of its own the row is just the "see them all" link, which
   belongs on the right rather than stranded on the left. */
.cv-page .cv-featured-head-bare { justify-content: flex-end; margin-bottom: 0.75rem; }
.cv-page .cv-featured-all {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cv-red, #B40C2A);
  text-decoration: none;
  white-space: nowrap;
}
.cv-page .cv-featured-all:hover { text-decoration: underline; }
.cv-page .cv-featured-slider {
  display: flex;
  gap: 1rem;
  margin: 0;
  padding: 0 clamp(1rem, 5vw, 3rem) 0.5rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: clamp(1rem, 5vw, 3rem);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.cv-page .cv-feat-slide {
  flex: 0 0 min(100%, 26rem);
  scroll-snap-align: start;
}

/* ---- The sliders' scrollbar --------------------------------------------
   The featured slider was the only scroller on the site with no scrollbar
   treatment, so the home page rendered a full native bar - grey thumb, and on
   Windows the two stepper ARROWS at each end, which is what made it look like a
   stray OS control sitting under the design.

   Kept visible rather than hidden: it is the affordance that says there is more
   to the right, and these are scroll-snap sliders with no arrow buttons of our
   own. A thin CV Red bar reads as the same accent as .cv-rule under a heading.

   scrollbar-color is the standard property and modern Chrome, Edge and Firefox
   honour it - and when they do, they ignore the ::-webkit-scrollbar block
   entirely, which is why the arrows disappear there without being asked to. The
   webkit rules stay for older Safari, where they are the only thing that works.

   No fade at the edge: a mask fade is a gradient, and the brand does not use
   them. */
.cv-page .cv-featured-slider {
  scrollbar-width: thin;
  scrollbar-color: var(--cv-red, #B40C2A) color-mix(in srgb, var(--cv-black, #343230) 12%, transparent);
}
.cv-page .cv-featured-slider::-webkit-scrollbar {
  height: 6px;
}
.cv-page .cv-featured-slider::-webkit-scrollbar-track {
  background: color-mix(in srgb, var(--cv-black, #343230) 12%, transparent);
}
.cv-page .cv-featured-slider::-webkit-scrollbar-thumb {
  background: var(--cv-red, #B40C2A);
}
/* The stepper arrows at each end. Square, unbranded, and the loudest part of
   the default bar. */
.cv-page .cv-featured-slider::-webkit-scrollbar-button {
  display: none;
  width: 0;
  height: 0;
}
.cv-page .cv-feat-slide a {
  display: block;
  text-decoration: none;
  color: inherit;
}
.cv-page .cv-feat-slide img {
  display: block;
  width: 100%;
  height: clamp(13rem, 26vw, 19rem);
  object-fit: cover;
  background: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent);
  transition: opacity 160ms cubic-bezier(0.2, 0, 0.2, 1);
}
.cv-page .cv-feat-slide a:hover img { opacity: 0.9; }
.cv-page .cv-feat-meta { display: block; padding-top: 0.75rem; }
.cv-page .cv-feat-where {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--cv-red, #B40C2A);
  margin-bottom: 0.25rem;
}
.cv-page .cv-feat-name {
  display: block;
  font-family: var(--cv-font-display, "Homestead"), Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: clamp(1.0625rem, 2vw, 1.25rem);
}
.cv-page .cv-feat-blurb {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: color-mix(in srgb, var(--cv-black, #343230) 75%, transparent);
}

/* ---- Language switcher ------------------------------------------------- */
/* Matched to the two widgets already in these corners on the live storefront:
   GTranslate's language pill bottom left (white, flag, language code in CV red
   bold, 30px inset) and the Toolbx chat button bottom right (white, soft
   shadow, ~48px square). All three should read as the same furniture.

   z-index stays at 99, the value GTranslate's own custom CSS uses — these pages
   are injected into the storefront, and climbing above its chrome would be a
   regression, not an improvement.

   A plain link: no JavaScript, so it cannot fail closed. */
.cv-langswitch {
  position: fixed;
  /* 35px, which is the chat button's own inset - read off its computed style,
     and the same on mobile as on desktop. The two sat at different heights on a
     phone anyway, because a fixed element inside the storefront's transformed
     wrapper measures from THAT box rather than the viewport; the head block
     lifts this node up to body so the number means what it says. */
  bottom: 35px;
  left: 35px;
  z-index: 99;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* 54px, from the chat button's own getBoundingClientRect().height. It was
   * recorded here as 56 for a while, which is close enough to look right and
   * wrong enough to see once the two share a bottom edge. Read it, do not
   * eyeball it:
   *   document.querySelector('[aria-label="Contact Us"]')
   *     .getBoundingClientRect().height
   */
  height: 54px;
  padding: 0 1rem;
  /* MEASURED off Toolbx's chat button, not matched by eye. It reads
   *   background rgb(240,237,228)  radius 3px  54x54  inset 35px
   *   shadow rgba(0,0,0,0.1) 0 3px 23px 0
   * and the two sit on the same bottom edge, so any difference reads as a
   * mistake rather than a choice. The previous values here claimed to be the
   * "same soft lift as the chat button" and were white with a much tighter,
   * darker shadow - matched by eye and wrong on both counts.
   *
   * Beige rather than white is the surprising part, and it is deliberate on
   * their side: the widget takes --message-widget-primary-color, which our own
   * head block points at CV Beige. Beige on a beige page is separated by the
   * shadow, exactly as theirs is. */
  background: var(--cv-beige, #F0EDE4);
  border-radius: 3px;
  box-shadow: 0 3px 23px rgb(0 0 0 / 0.10);
  text-decoration: none;
  transition: box-shadow 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-langswitch:hover,
.cv-langswitch:focus-visible {
  box-shadow: 0 4px 28px rgb(0 0 0 / 0.17);
}
.cv-langswitch-flag {
  display: block;
  width: 24px;
  height: 18px;
  flex-shrink: 0;
  /* The flags are photographic-detail SVGs; a hairline keeps a white field
     (the Mexican flag's center band) from bleeding into the white pill. */
  box-shadow: 0 0 0 1px rgb(0 0 0 / 0.12);
}
.cv-langswitch-code {
  font-family: 'Akzidenz-Grotesk Next', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: 0.06em;
  /* Explicit, because the ambient link color does not reach a fixed element
     placed outside .cv-page. */
  color: var(--cv-red, #B40C2A);
}
/* THERE IS NO PHONE VARIANT, because the chat button has none.
 *
 * A media query here used to move it to bottom/left 18px and shrink it to 48px,
 * on the reasoning that a phone's bottom edge is crowded. The chat button does
 * neither: its computed style is bottom: 35px, right: 35px at every width, and
 * it is 54x54 on a phone. So the query produced a chip sitting 17px lower than
 * the thing beside it and 6px shorter than it, which is the difference the rule
 * above exists to prevent.
 *
 * If a phone variant is ever wanted again, measure the chat button first. The
 * whole point of these numbers is that they are its numbers. */
@media print {
  .cv-langswitch { display: none; }
}

/* ---- Narrow screens: center the display blocks ------------------------- */
/* On a phone the stat grid collapses to one column, and left-aligning a
   9-rem-tall outlined numeral against a full-width column strands it — the
   digit reads as an orphan with a paragraph beside it. Body copy stays left
   aligned (centered prose is harder to read); only the display blocks center.
   Scoped to imported design pages, whose layout is the design's own. */
@media (max-width: 40rem) {
  .cv-page-full .stat-digit,
  .cv-page-full .stat-label,
  .cv-page-full .stat-note {
    text-align: center;
  }
  .cv-page-full .stat-note {
    /* A centered note reads better a little narrower than the column. */
    margin-inline: auto;
    max-width: 34ch;
  }
  /* Center the grid tracks too, so a single column is not pinned left. */
  .cv-page-full dl {
    justify-items: center;
  }
}

/* ---- Forms -------------------------------------------------------------- */
/* A form here is a real HTML form that posts to the edge. It works with no
   JavaScript, so nothing is hidden or disabled in the base cascade. */
/*
 * THE hidden ATTRIBUTE IS ONLY A UA STYLESHEET RULE, and it loses.
 *
 * [hidden] { display: none } comes from the browser own sheet at specificity
 * (0,1,0), so ANY class rule of ours that sets display beats it —
 * .cv-page .cv-form { display: grid } is (0,2,0). Setting el.hidden = true then
 * changes the property, passes a test that reads the property back, and leaves
 * the element on the screen. That is exactly how a submitted contact form went
 * on being displayed with its thank-you sitting above it.
 *
 * Scoped under .cv-page rather than bare: Toolbx lifts this stylesheet into
 * their SPA, so a bare [hidden] rule would reach the whole storefront.
 */
.cv-page [hidden] {
  display: none !important;
}

.cv-page .cv-form {
  display: grid;
  gap: clamp(0.875rem, 2vw, 1.25rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  max-width: 46rem;
  margin: clamp(1.5rem, 4vw, 2.5rem) 0;
}
/*
 * THE FILE FIELD.
 *
 * A bare file input is a browser button with a filename beside it, in whatever
 * the platform fancies, and it sits on the form a customer uses to send back a
 * completed credit application. Styled to match the other controls: the same
 * border, the same beige ground, and the picker button given the same treatment
 * as a secondary action.
 *
 * ::file-selector-button needs the -webkit- prefix as well; the unprefixed form
 * is not honoured by Safari, which is a real share of this storefront.
 */
.cv-page .cv-form-file {
  display: block;
  width: 100%;
  min-width: 0;
  border: 1px solid color-mix(in srgb, var(--cv-black, #343230) 55%, transparent);
  background: var(--cv-white, #FFFFFF);
  color: var(--cv-black, #343230);
  font: inherit;
  padding: 0.5rem;
  border-radius: 2px;
}
.cv-page .cv-form-file::-webkit-file-upload-button,
.cv-page .cv-form-file::file-selector-button {
  font: inherit;
  margin-right: 0.75rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--cv-black, #343230);
  background: transparent;
  color: var(--cv-black, #343230);
  cursor: pointer;
  border-radius: 2px;
}
.cv-page .cv-form-file:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
/* What is accepted and how big, stated ON THE PAGE rather than only in a
 * validation message somebody reads after losing their upload. */
.cv-page .cv-form-filenote {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.875rem;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}

.cv-page .cv-form-intro,
.cv-page .cv-form-actions,
.cv-page .cv-form-row:not(.cv-form-half),
.cv-page .cv-form-check { grid-column: 1 / -1; }
/* See the note on .cv-form-input: the grid ITEM has to be allowed to shrink or
   a long option widens the whole form regardless of what the control does. */
.cv-page .cv-form-row { min-width: 0; }
.cv-page .cv-form-label,
.cv-page .cv-form-row > .cv-form-label {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
  margin-bottom: 0.375rem;
}
.cv-page .cv-form-req { color: var(--cv-red, #B40C2A); margin-left: 0.2em; }
.cv-page .cv-form-input {
  display: block;
  width: 100%;
  /* A SELECT SIZES ITSELF TO ITS LONGEST OPTION, and width:100% does not stop
   * it: the intrinsic minimum still propagates up. On /consumer-request/ one
   * 114-character option — "Request to know the categories of personal
   * information collected or disclosed, source, and third parties involved." —
   * gave the control an 880px min-content width and pushed the whole document
   * to 900px in a 390px viewport. Nothing was visibly past the edge; the page
   * just swiped into half a screen of empty ground, on the form a California
   * resident has to use to exercise a statutory right.
   *
   * min-width:0 lets it shrink; the grid item needs it too, because a track is
   * minmax(AUTO,1fr) and that auto floor is the item's min-content — the same
   * trap as the home page arc and the project mosaic. */
  min-width: 0;
  font: inherit;
  font-size: 1rem;
  color: inherit;
  background: #fff;
  /* 55%, not 22%: a control's boundary needs 3:1 (WCAG 1.4.11) and 22%
     measured 1.49:1 on beige. 55% is 3.10:1 on beige and 3.26:1 on paper —
     the first step that clears it. Verified by scripts/contrast-audit.mjs. */
  border: 1px solid color-mix(in srgb, var(--cv-black, #343230) 55%, transparent);
  border-radius: 3px;
  padding: 0.75rem 0.875rem;
  /* 44px minimum touch target. */
  min-height: 3rem;
  transition: border-color 160ms cubic-bezier(0.2, 0, 0.2, 1);
}
.cv-page textarea.cv-form-input { min-height: 8rem; resize: vertical; }
.cv-page .cv-form-input:focus {
  outline: none;
  border-color: var(--cv-red, #B40C2A);
}
.cv-page .cv-form-help {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.8125rem;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}
.cv-page .cv-form-radios { display: grid; gap: 0.375rem; }
.cv-page .cv-form-radio,
.cv-page .cv-form-checkbox {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.9375rem;
  line-height: 1.4;
  cursor: pointer;
}
.cv-page .cv-form-radio input,
.cv-page .cv-form-checkbox input { margin-top: 0.2rem; flex-shrink: 0; }

/* The honeypot. Off-screen rather than display:none — some bots skip hidden
   fields, and a real visitor never reaches it because it is not in the flow. */
.cv-page .cv-form-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.cv-page .cv-form-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
/* The rating slider.
 *
 * A range input is one of the few controls a browser refuses to style through
 * ordinary properties, so the track and the thumb are addressed through their
 * pseudo-elements and the whole control needs appearance:none first — the same
 * reason the submit button was painting Arial on a bevel.
 *
 * The thumb is CV Red because it is a small accent on a long neutral track,
 * which is accent use rather than a fill. The track stays CV Black at a tint so
 * the control does not read as a second red band across the form.
 *
 * The value sits ABOVE the track, between the two end labels, so the scale
 * reads left to right as "1 — <value> — 10" and the number is where the eye
 * already is rather than hanging off one end.
 */
.cv-page .cv-form-range { display: block; }
.cv-page .cv-form-slider {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  margin: 0.5rem 0 0.35rem;
  background: transparent;
  /* A 44px target on touch: the thumb is 26px, so the input carries the rest. */
  height: 2.75rem;
  cursor: pointer;
}
.cv-page .cv-form-slider::-webkit-slider-runnable-track {
  height: 0.375rem;
  border-radius: 3px;
  background: color-mix(in srgb, var(--cv-black, #343230) 30%, transparent);
}
.cv-page .cv-form-slider::-moz-range-track {
  height: 0.375rem;
  border-radius: 3px;
  background: color-mix(in srgb, var(--cv-black, #343230) 30%, transparent);
}
.cv-page .cv-form-slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: var(--cv-red, #B40C2A);
  border: 3px solid var(--cv-white, #FFFFFF);
  /* Centres the thumb on a 6px track. */
  margin-top: -0.625rem;
  transition: transform var(--cv-dur-1) var(--cv-ease);
}
.cv-page .cv-form-slider::-moz-range-thumb {
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: var(--cv-red, #B40C2A);
  border: 3px solid var(--cv-white, #FFFFFF);
  transition: transform var(--cv-dur-1) var(--cv-ease);
}
.cv-page .cv-form-slider:hover::-webkit-slider-thumb { transform: scale(1.12); }
.cv-page .cv-form-slider:hover::-moz-range-thumb { transform: scale(1.12); }
/* The ring goes on the INPUT, not the thumb: a pseudo-element outline is
   clipped by the control in several browsers, and a focus ring that is
   sometimes invisible is the same as not having one. */
.cv-page .cv-form-slider:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .cv-page .cv-form-slider::-webkit-slider-thumb,
  .cv-page .cv-form-slider::-moz-range-thumb { transition: none; }
  .cv-page .cv-form-slider:hover::-webkit-slider-thumb,
  .cv-page .cv-form-slider:hover::-moz-range-thumb { transform: none; }
}
.cv-page .cv-form-scale {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.8125rem;
  color: var(--cv-black-70, #6B6966);
}
.cv-page .cv-form-scale-value {
  font-family: 'Homestead', 'Playfair Display', Georgia, serif;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--cv-black, #343230);
  min-width: 2ch;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.cv-page .cv-form-slider.is-set + .cv-form-scale .cv-form-scale-value {
  color: var(--cv-red, #B40C2A);
}

/* The thank-you, shown in place of the form on the page the visitor was
   already on — see FORM_DONE_JS. It replaces a whole form, so it needs enough
   presence not to read as a stray sentence where a form used to be, and it
   takes focus, so it needs a visible focus state of its own. */
.cv-page .cv-form-done {
  border-left: 3px solid var(--cv-red, #B40C2A);
  padding: 1rem 0 1rem 1.25rem;
  margin: clamp(1.5rem, 4vw, 2.5rem) 0;
  max-width: 46rem;
}
.cv-page .cv-form-done > :first-child { margin-top: 0; }
.cv-page .cv-form-done > :last-child { margin-bottom: 0; }
.cv-page .cv-form-done:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }

.cv-page .cv-form-status { font-size: 0.9375rem; }
.cv-page .cv-form-status[data-state="error"] { color: var(--cv-red, #B40C2A); }
.cv-page .cv-form-missing {
  padding: 1rem;
  border: 1px dashed color-mix(in srgb, var(--cv-black, #343230) 25%, transparent);
  font-size: 0.9375rem;
}
.cv-page .cv-form-errors { color: var(--cv-red, #B40C2A); font-size: 0.9375rem; }

/* ---- Post lists (tag-driven section pages) ----------------------------- */
.cv-page .cv-post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
  margin: clamp(1.5rem, 4vw, 2.5rem) 0 0;
  padding: 0;
  list-style: none;
}
.cv-page .cv-post-card a {
  display: grid;
  gap: 0;
  text-decoration: none;
  color: inherit;
  background-image: none;
}
.cv-page .cv-post-card img,
.cv-page .cv-post-noimage {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  transition: opacity 160ms cubic-bezier(0.2, 0, 0.2, 1);
}
.cv-page .cv-post-card img { background: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent); }

/* An article with no photograph. Seven of the press releases never had one and
   there is nothing to recover, so the card carries the stamp on the same tinted
   ground the photo tiles use, instead of an empty grey rectangle: the tile still
   belongs to the row, and the mark makes it deliberate rather than a hole where
   a picture failed.

   The artwork is the supplied stamp, placed and not redrawn. background-size is
   a percentage of the tile so it holds its proportion at every column width. */
.cv-page .cv-post-noimage {
  /* The SAME tint the photo tiles carry, so the row of cards keeps one ground
     and the mark reads as a knockout on it rather than as a lighter hole. */
  background-color: color-mix(in srgb, var(--cv-black, #343230) 8%, transparent);
  background-image: url("https://content.central-valley.com/brand/logo/stamp-black.svg");
  background-repeat: no-repeat;
  background-position: center center;
  background-size: 28%;
  /* The mark sits back a little: it identifies the card without competing with
     the headline underneath it, which is the actual content. */
  opacity: 0.55;
}
.cv-page .cv-post-card a:hover .cv-post-noimage { opacity: 0.75; }
.cv-page .cv-post-card a:hover img { opacity: 0.9; }
.cv-page .cv-post-meta { display: block; padding-top: 0.75rem; }
.cv-page .cv-post-date {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
  margin-bottom: 0.25rem;
}
.cv-page .cv-post-title {
  display: block;
  font-family: var(--cv-font-display, "Homestead"), Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: clamp(1rem, 1.8vw, 1.1875rem);
  line-height: 1.25;
}
.cv-page .cv-post-card a:hover .cv-post-title { color: var(--cv-red, #B40C2A); }
.cv-page .cv-post-blurb {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: color-mix(in srgb, var(--cv-black, #343230) 75%, transparent);
}
/* The events index is emitted even when nothing is scheduled — see the note in
   build.ts. Same treatment as an empty section list: quiet, but not so quiet
   that a page somebody navigated to on purpose looks broken. */
.cv-page .cv-events-empty,
.cv-page .cv-postlist-empty {
  font-size: 0.9375rem;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}
/* FOCUS IS ALWAYS VISIBLE, and that beats always being in brand.
   CV Red measures 5.93:1 on the beige page ground and 6.94:1 on paper, so it
   looked correct — and 1.84:1 on a dark band and 1.00:1 on a red department
   tile, where the ring is literally invisible. A keyboard user on those
   sections had no idea where they were.
   currentColor inherits the text colour of whatever it lands in, so the ring
   is black on beige (10.91:1), white on the dark band (12.77:1) and white on
   red (6.94:1). Measured by scripts/contrast-audit.mjs, which fails the build
   on a pair in use that drops below the threshold.
   The same call was made for the home page's own stylesheet earlier; this is
   the site-wide rule, which was missed. */
.cv-page :focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* ---------------------------------------------------------------------------
   THE WALKTHROUGH: artwork pinned, a window on the part being described

   For a page whose subject has PARTS. Fire hardening is six house systems, and
   as six paragraphs in three columns it reads as a brochure. Here the drawing
   stays on screen while the systems scroll past it, and a hard-edged window
   opens on the one being described, dimming the rest -- so the reader never
   loses where that part sits on the building.

   NOTHING IS HIDDEN UNTIL THE DRIVER SAYS SO. Every rule that dims or crops is
   scoped under [data-cv-live="1"], which only WALKTHROUGH_JS sets. With no
   JavaScript, or with reduced motion asked for, the block is the whole picture
   followed by every part in document order -- a complete document. A reveal
   that fails closed is content nobody can read, which is why this site has no
   scroll-triggered reveals of the usual kind.

   The window is HARD-EDGED, and a soft spotlight was the alternative. A wash is
   a gradient, which the brand keeps out, and a technical window suits a
   construction drawing better anyway: it reads as a detail callout.
   --------------------------------------------------------------------------- */

.cv-walk { margin: 2.5rem 0; }

.cv-walk-intro { max-width: 62rem; margin: 0 auto 2rem; }

.cv-walk-track {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: start;
}

/* Pinned. If an ancestor in the storefront's own layout carries an overflow
   this silently stops working, and the stage simply scrolls with the page --
   the window still tracks the steps, so it degrades rather than breaks. */
.cv-walk-stage-col { position: sticky; top: 12vh; }

.cv-walk-stage {
  position: relative;
  overflow: hidden;
  background: var(--cv-white, #FFFFFF);
  border: 1px solid var(--cv-border, #D4D1C7);
}

.cv-walk-stage img.cv-walk-art {
  display: block;
  width: 100%;
  height: auto;
}

/* The dimming. A flat wash of the page ground, not a gradient, and light
   enough that the whole building stays legible underneath -- the claim of this
   layout is that you can still see where the part sits. */
.cv-walk-scrim {
  position: absolute;
  inset: 0;
  background: rgba(240, 237, 228, 0.56);
  opacity: 0;
  transition: opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-walk[data-cv-live="1"] .cv-walk-scrim { opacity: 1; }

.cv-walk-window {
  position: absolute;
  left: var(--cv-wx, 10%);
  top: var(--cv-wy, 40%);
  width: var(--cv-ww, 30%);
  height: var(--cv-wh, 30%);
  overflow: hidden;
  outline: 2px solid var(--cv-red, #B40C2A);
  opacity: 0;
  transition:
    left 380ms cubic-bezier(0.2, 0, 0, 1),
    top 380ms cubic-bezier(0.2, 0, 0, 1),
    width 380ms cubic-bezier(0.2, 0, 0, 1),
    height 380ms cubic-bezier(0.2, 0, 0, 1),
    opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-walk[data-cv-live="1"] .cv-walk-window { opacity: 1; }

/* The same picture at the same scale, offset so the region registers with the
   dimmed copy behind it. Sized in pixels by the driver: a percentage here would
   be a percentage of the window.
   Two ancestors deep on purpose. The rule ".cv-band img" setting max-width to
   100% is (0,1,1), the same as a bare ".cv-walk-window img", and sits LATER in
   this file, so it
   won on source order and clamped this copy to the width of the window --
   189px instead of 677px. The cut-out then showed the same pixels as the dimmed
   picture behind it, at the same scale, so the window looked like an empty red
   rectangle and the whole mechanism read as broken. Same trap as the storefront
   cart button losing to a duplicate selector further down. */
.cv-walk-stage .cv-walk-window img {
  position: absolute;
  width: var(--cv-iw, 100%);
  max-width: none;
  height: auto;
  left: var(--cv-ix, 0);
  top: var(--cv-iy, 0);
}

.cv-walk-label {
  position: absolute;
  left: var(--cv-lx, 10%);
  top: var(--cv-wy, 40%);
  max-width: 100%;
  transform: translateY(-100%);
  background: var(--cv-white, #FFFFFF);
  color: var(--cv-red, #B40C2A);
  border: 1px solid var(--cv-red, #B40C2A);
  border-bottom: 0;
  font-weight: 700;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.25rem 0.5rem;
  white-space: nowrap;
  opacity: 0;
  transition:
    left 380ms cubic-bezier(0.2, 0, 0, 1),
    top 380ms cubic-bezier(0.2, 0, 0, 1),
    opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-walk[data-cv-live="1"] .cv-walk-label { opacity: 1; }
.cv-walk-stage[data-cv-flip="1"] .cv-walk-label { transform: translate(-100%, -100%); }

/* CV Red as a rule, not a fill. */
.cv-walk-progress { display: flex; gap: 4px; margin-top: 0.75rem; }
.cv-walk-progress span {
  flex: 1;
  height: 2px;
  background: var(--cv-border, #D4D1C7);
  transition: background 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-walk-progress span[data-cv-on="1"] { background: var(--cv-red, #B40C2A); }
.cv-walk:not([data-cv-live="1"]) .cv-walk-progress { display: none; }

.cv-walk-steps { min-width: 0; }

/* A step is a normal block until the driver takes over, at which point it
   becomes a full screen of scroll so the window has room to move. */
/* 56vh, not 84. At nearly a full screen of scroll per part, six parts made the
   walkthrough five screens long and the transitions felt far apart -- the
   reader is waiting for the next one rather than being carried to it. This
   still gives every part more than half a screen, which is enough to read the
   copy before it moves on.

   And 0.55, not 0.34. With six parts, five are always dimmed, so too deep a dim
   makes the whole column read as grey. The point is to say which one is current,
   not to make the rest unreadable to somebody scanning. */
.cv-walk[data-cv-live="1"] .cv-walk-step {
  min-height: 56vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 1.25rem 0;
  opacity: 0.55;
  transition: opacity 240ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-walk[data-cv-live="1"] .cv-walk-step[data-cv-on="1"] { opacity: 1; }
.cv-walk-step > :first-child { margin-top: 0; }

/* The brand logos each part arrived with. Normalised on HEIGHT: a shared width
   makes a circular seal tower over a wordmark. */
.cv-walk-logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  margin-top: 1.25rem;
}
.cv-walk-logos img { max-height: 2.25rem; max-width: 9rem; width: auto; height: auto; }

@media (max-width: 900px) {
  .cv-walk-track { grid-template-columns: 1fr; gap: 0; }
  /* The picture pins to the top of the phone screen and the parts scroll under
     it. 12vh of offset would put it under the storefront's own header. */
  .cv-walk-stage-col { top: 0; padding-top: 0.5rem; background: var(--cv-beige, #F0EDE4); z-index: 2; }
  .cv-walk-steps { margin-top: 1.5rem; }
  .cv-walk[data-cv-live="1"] .cv-walk-step { min-height: 46vh; }
}

/* Asked for less motion: the document, with nothing moving and nothing dimmed.
   The driver returns early in this case, so [data-cv-live] is never set and
   these are belt and braces. */
@media (prefers-reduced-motion: reduce) {
  .cv-walk-stage-col { position: static; }
  .cv-walk-scrim,
  .cv-walk-window,
  .cv-walk-label { display: none; }
  .cv-walk[data-cv-live="1"] .cv-walk-step { min-height: 0; opacity: 1; }
  .cv-walk-scrim,
  .cv-walk-window,
  .cv-walk-label,
  .cv-walk-progress span,
  .cv-walk[data-cv-live="1"] .cv-walk-step { transition: none; }
}

/* ---------------------------------------------------------------------------
   THE TIMELINE: a date scale that stays on screen while history scrolls past

   For a page whose subject is a SPAN. /about-us/ is seventy years in ten
   milestones, and as a list it reads as a list. Here the rule stays pinned so
   you can always see where in the span you are, and each archive plate drifts
   inside its frame against the copy beside it.

   THE RULE IS AN INSTRUMENT, NOT A SPINE. A tick for every year, longer every
   fifth, longest and labelled every tenth -- the thing a builder measures with,
   which is the right register for a lumber yard's own history. A spine that
   merely fills up as you scroll cannot show that 1955 to 1992 is most of the
   span and the last decade is a flurry; a fixed scale with a marker can, and
   that compression is true.

   NOTHING MOVES OR DIMS UNTIL THE DRIVER SAYS SO. Every such rule is scoped
   under [data-cv-live="1"], set only by WALKTHROUGH_JS. With no JavaScript, or
   with reduced motion asked for, the scale is still a complete scale, every
   plate sits correctly in its frame, and the milestones read in order.
   --------------------------------------------------------------------------- */

.cv-timeline { margin: 2rem 0; }

.cv-timeline-track {
  display: grid;
  grid-template-columns: 16.5rem minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: start;
}

.cv-timeline-gauge {
  position: sticky;
  top: 12vh;
  display: flex;
  gap: 1.75rem;
  align-items: flex-start;
}
.cv-timeline-gauge-inner {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  align-self: stretch;
}

/* The year RIDES the marker. Pinned apart from it, the eye has to connect a
   figure in one place to a mark in another; on the rule the figure becomes the
   marker's own label and the pair reads as one gauge. */
.cv-timeline-year {
  position: absolute;
  right: 0;
  top: var(--cv-mark, 0%);
  transform: translateY(-50%);
  text-align: right;
  margin: 0;
  font-family: var(--font-headline, "Homestead"), sans-serif;
  font-size: clamp(2.25rem, 3.8vw, 3.5rem);
  line-height: 0.92;
  color: var(--cv-red, #B40C2A);
  font-variant-numeric: tabular-nums;
  transition: top 420ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-timeline-year-label {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--font-body, sans-serif);
  font-weight: 700;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
}

.cv-timeline-scale {
  position: relative;
  width: 3.25rem;
  height: 62vh;
  flex: 0 0 auto;
}
.cv-timeline-tick {
  position: absolute;
  left: 0;
  top: calc(var(--i) / var(--cv-span, 70) * 100%);
  height: 1px;
  width: 0.5rem;
  background: var(--cv-border, #D4D1C7);
}
.cv-timeline-tick--five { width: 0.875rem; }
.cv-timeline-tick--decade {
  width: 1.5rem;
  background: color-mix(in srgb, var(--cv-black, #343230) 62%, transparent);
}
.cv-timeline-tick-label {
  position: absolute;
  left: 1.875rem;
  top: -0.55em;
  font-weight: 700;
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  color: color-mix(in srgb, var(--cv-black, #343230) 70%, transparent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cv-timeline-marker {
  position: absolute;
  left: 0;
  top: var(--cv-mark, 0%);
  width: 2.25rem;
  height: 2px;
  background: var(--cv-red, #B40C2A);
  opacity: 0;
  transition: top 420ms cubic-bezier(0.2, 0, 0, 1), opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-timeline[data-cv-live="1"] .cv-timeline-marker { opacity: 1; }
.cv-timeline-marker::after {
  content: "";
  position: absolute;
  right: -1px;
  top: -3px;
  width: 8px;
  height: 8px;
  background: var(--cv-red, #B40C2A);
}

.cv-timeline-items {
  display: flex;
  flex-direction: column;
  gap: clamp(2.5rem, 8vw, 6rem);
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.cv-timeline-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1rem, 2.5vw, 1.75rem);
}

/* THE PARALLAX. The frame clips and the plate inside it is oversized, so it
   drifts against the copy without ever revealing an edge. With no JavaScript
   --cv-p stays 0 and the plate simply sits still, correctly framed. */
.cv-timeline-plate {
  position: relative;
  margin: 0;
  overflow: hidden;
  background: var(--cv-white, #FFFFFF);
  border: 1px solid var(--cv-border, #D4D1C7);
  aspect-ratio: 3 / 2;
}
.cv-timeline-plate img {
  position: absolute;
  left: 0;
  top: -9%;
  width: 100%;
  max-width: none;
  height: 118%;
  object-fit: cover;
  transform: translate3d(0, calc(var(--cv-p, 0) * 2.6rem), 0);
  will-change: transform;
}

.cv-timeline-copy { max-width: 60ch; }
/* The scoped heading scale sets a 2em top margin on every h3 for running prose,
   which here opened a hole between a milestone's year and its title. These are
   (0,1,1) like the rules they override and sit later in the file, so they win on
   source order -- no !important needed. */
.cv-timeline-copy h3 { margin-top: 0.4rem; }
.cv-timeline-copy p { margin-top: 0.7rem; }
.cv-timeline-item-year {
  margin: 0;
  font-family: var(--font-headline, "Homestead"), sans-serif;
  font-size: 1.125rem;
  color: var(--cv-red, #B40C2A);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.cv-timeline-copy > :first-child { margin-top: 0; }

/* The one being read comes forward. Quietly: this is a document, and dimming
   the rest toward invisibility makes it unreadable to anyone scrolling fast. */
.cv-timeline[data-cv-live="1"] .cv-timeline-item {
  opacity: 0.62;
  transition: opacity 300ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-timeline[data-cv-live="1"] .cv-timeline-item[data-cv-on="1"] { opacity: 1; }

/* Side by side above 60rem, and the side ALTERNATES -- which is what stops ten
   milestones reading as a list. */
@media (min-width: 60rem) {
  .cv-timeline-item {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: clamp(1.5rem, 3vw, 2.75rem);
    align-items: center;
  }
  .cv-timeline-item:nth-child(even) .cv-timeline-plate { order: 2; }
}

/* On a phone the rule turns horizontal and pins to the top: the same
   instrument read the other way, not a second design. */
@media (max-width: 60rem) {
  .cv-timeline-track { grid-template-columns: 1fr; gap: 0; }
  .cv-timeline-gauge {
    top: 0;
    z-index: 3;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 0 1.125rem;
    /* The bar has to paint the ground it sits on, or the copy scrolling under it
       shows through its edge. There is no token for "whatever is behind me" and
       an inherited background resolves to transparent on a parent that paints
       nothing -- so it reads the STOREFRONT's own ground variable.
       --primary-bg-color is set to CV Beige by the pasted head block, which is
       what is actually behind this bar for a customer.
       This defaulted to paper because our own origin renders a full-bleed page
       on white, and the result was a white bar on the storefront's tan ground on
       every phone. The lesson is the one the locations embed already taught:
       take the ground from the context, never from the context you previewed
       in. */
    background: var(--cv-timeline-bar-bg, var(--primary-bg-color, var(--cv-beige, #F0EDE4)));
    border-bottom: 1px solid var(--cv-border, #D4D1C7);
    margin-bottom: 2rem;
  }
  .cv-timeline-gauge-inner { flex: 0 0 auto; }
  .cv-timeline-year {
    position: static;
    transform: none;
    font-size: 2rem;
    min-width: 4.5ch;
    transition: none;
  }
  .cv-timeline-year-label { display: none; }
  .cv-timeline-scale { width: auto; flex: 1 1 auto; height: 1.75rem; }
  .cv-timeline-tick {
    left: calc(var(--i) / var(--cv-span, 70) * 100%);
    top: auto;
    bottom: 0.75rem;
    width: 1px;
    height: 0.375rem;
  }
  .cv-timeline-tick--five { height: 0.625rem; }
  .cv-timeline-tick--decade { height: 1rem; }
  /* Eight decade figures across 250px of scale run into each other. The ticks
     still carry the span and the year figure gives the exact value, so only the
     two ends stay labelled here. */
  .cv-timeline-tick-label { display: none; }
  .cv-timeline-tick--end .cv-timeline-tick-label {
    display: block;
    left: -0.9rem;
    top: -1rem;
    font-size: 0.5625rem;
  }
  .cv-timeline-marker {
    left: var(--cv-mark, 0%);
    top: auto;
    bottom: 0.5rem;
    width: 2px;
    height: 1.25rem;
    transition: left 420ms cubic-bezier(0.2, 0, 0, 1), opacity 200ms cubic-bezier(0.2, 0, 0, 1);
  }
  .cv-timeline-marker::after { right: -3px; top: auto; bottom: -3px; }

  /* On OUR origin a full-bleed page is genuinely white. On the storefront
     --primary-bg-color is defined and wins, so this only changes the internal
     view. */
  .cv-page-full .cv-timeline-gauge {
    background: var(--cv-timeline-bar-bg, var(--primary-bg-color, var(--cv-white, #FFFFFF)));
  }
}

@media (prefers-reduced-motion: reduce) {
  .cv-timeline-gauge { position: static; }
  .cv-timeline-plate img { transform: none; }
  .cv-timeline-marker { display: none; }
  .cv-timeline[data-cv-live="1"] .cv-timeline-item { opacity: 1; }
  .cv-timeline-year,
  .cv-timeline-marker,
  .cv-timeline[data-cv-live="1"] .cv-timeline-item { transition: none; }
}

/* ---------------------------------------------------------------------------
   LANDING-PAGE LAYOUT

   The handful of designed pages — fire-hardening, timbertech — are not
   documents. Their arrangement IS the content: a photograph-backed hero band,
   three-up feature grids, tiles with a picture behind them. These primitives are
   what scripts/site-relayout-landing.mjs emits when it reads the layout intent
   out of the original Elementor markup.

   They deliberately do NOT reproduce Elementor's CSS, which is 89 KB of theme
   variables that would undo the shared design system. Padding is bucketed into
   three sizes rather than copied in pixels: the original's 175px desktop hero
   pad is absurd on a phone.
   --------------------------------------------------------------------------- */

/* The yard strip: today's hours and a live open/closed pill ---------------- */

/*
 * PROMOTED OUT OF THE HOME PAGE so any page can carry it.
 *
 * These rules only ever existed inside the home page's own stored CSS, scoped
 * under .cv-th by site-home-split.py -- fifteen of them, and none in this file.
 * So a <!--cv:yard-strip--> on a campaign page rendered correct markup with no
 * styling whatsoever.
 *
 * UNSCOPED AND cv-PREFIXED, like .cv-band and the other relayout primitives.
 * Scoping under .cv-page would be a no-op on exactly the pages that need it: a
 * fullBleed design page renders its body alone with no wrapper. The prefix is
 * load-bearing rather than decorative -- Toolbx lift this stylesheet into their
 * SPA, so a bare .yard rule would reach their markup, which is how .cv-gallery
 * once turned the project slider into 56px slivers.
 *
 * SYMMETRICAL BY CONSTRUCTION. The column count comes from the ITEM COUNT via
 * --cv-yard-cols, set inline by yardColumns() -- eight yards give four, five
 * give five -- so the last row is never short. The 1px gaps are a painted
 * ground rather than empty space, which is why a ragged row reads as a hole and
 * why centring one is not an option here.
 */
.cv-yards {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--cv-border, #D4D1C7);
  border: 1px solid var(--cv-border, #D4D1C7);
  margin: 1.75rem 0 0;
}
@media (min-width: 34rem) {
  .cv-yards { grid-template-columns: repeat(2, 1fr); }
  /*
   * AN ODD COUNT IN TWO COLUMNS LEAVES A HOLE, and here the gaps are a painted
   * ground so the hole is visible rather than merely empty. Five yards came out
   * 2 + 2 + 1 with a hairline box of border colour beside the last one.
   *
   * The last card takes the whole row instead. Dropping to one column would
   * also be symmetrical and is three times taller, which on the phone-to-tablet
   * band is the wrong trade.
   */
  .cv-yards[data-cv-odd] > .cv-yard:last-child { grid-column: 1 / -1; }
}
@media (min-width: 62rem) {
  /* The count decides it. Falls back to 4 if the property is ever absent. */
  .cv-yards { grid-template-columns: repeat(var(--cv-yard-cols, 4), 1fr); }
  /* Above this width the column count divides the item count, so the row is
   * already full and the span above must not apply. The exception is a prime
   * over five (7, 11), where yardColumns falls back to 4 and the last row is
   * genuinely short — accepted rather than papered over with one enormous card. */
  .cv-yards[data-cv-odd] > .cv-yard:last-child { grid-column: auto; }
}
.cv-yard {
  background: var(--cv-white, #FFFFFF);
  padding: 0.85rem 1rem;
  text-decoration: none;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  color: var(--cv-black, #343230);
  /* A grid track is minmax(auto, 1fr) and that automatic floor is the item's
   * min-content width, so a long yard name would refuse to shrink and push the
   * whole strip wider than the page. Fourth time this has been the answer. */
  min-width: 0;
}
.cv-yard:hover,
.cv-yard:focus-visible {
  background: var(--cv-red, #B40C2A);
  color: #FFF;
}
.cv-yard .yard-name {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1.0625rem;
  line-height: 1.1;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.cv-yard .yard-hours { font-size: 0.9375rem; line-height: 1.35; }
/* Second and later days sit quieter than today's line. */
.cv-yard .yard-hours em {
  display: block;
  font-style: normal;
  color: color-mix(in srgb, currentColor 70%, transparent);
}
.cv-yard .yard-tel { font-size: 0.9375rem; }
.cv-yard:hover .yard-tel,
.cv-yard:focus-visible .yard-tel { color: #FFF; }
.cv-yard:hover .yard-hours em,
.cv-yard:focus-visible .yard-hours em { color: rgba(255, 255, 255, 0.78); }
/*
 * The pill is filled in by the head block, which reads the yard's own Pacific
 * clock and its holiday closures. EMPTY UNTIL THEN, and it has to stay
 * invisible while empty: a bordered blank chip beside every yard name reads as
 * a broken page, and with no JavaScript that is the state it stays in.
 */
.cv-yard .yard-pill:empty { display: none; }
.cv-yard .yard-pill {
  font-family: var(--font-body);
  text-transform: uppercase;
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  padding: 0.1rem 0.4rem;
  border: 1px solid currentColor;
  border-radius: 2px;
  white-space: nowrap;
}

.cv-band {
  padding: 3.5rem 1.5rem;
  background-color: var(--cv-band-bg, transparent);
  position: relative;
}
.cv-band--tall { padding: 6rem 1.5rem; }
.cv-band--short { padding: 1.25rem 1.5rem; }
.cv-band--none { padding: 0 1.5rem; }
.cv-band--plain { background-color: transparent; }

.cv-band__inner {
  max-width: 1200px;
  margin: 0 auto;
  position: relative;
  z-index: 1;
}

.cv-band--image {
  background-image: var(--cv-band-img);
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}
/* A scrim, not a filter: the headline has to stay legible over whatever part of
   the photograph a given viewport crops to. */
.cv-band--image::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(52, 50, 48, 0.55);
}

.cv-band--dark,
.cv-band--dark h1,
.cv-band--dark h2,
.cv-band--dark h3,
.cv-band--dark h4,
.cv-band--dark h5,
.cv-band--dark h6,
.cv-band--dark p,
.cv-band--dark li {
  color: var(--cv-beige, #F0EDE4);
}
.cv-band--dark a { color: var(--cv-beige, #F0EDE4); text-decoration: underline; }

.cv-grid {
  display: grid;
  /* Takes the original's own track list -- 1fr 4fr 1fr, repeat(3, 1fr) -- so an
     asymmetric layout stays asymmetric instead of being evened out.
     No backticks in here: this whole file is one TS template literal. */
  grid-template-columns: var(--cv-cols, repeat(2, minmax(0, 1fr)));
  gap: 2rem;
  align-items: start;
}
.cv-grid > * { min-width: 0; }
/* A GRID THAT IS NOT INSIDE A BAND HAS NO GUTTER.
 *
 * .cv-band carries the horizontal padding; .cv-grid carries none, on the
 * assumption that it always sits inside one. On /timbertech/ the relayout
 * emitted .cv-grid blocks as DIRECT children of a full-bleed article, so four
 * paragraphs and two headings ran edge to edge — measured left inset 0px at
 * 390px, where every other page on the site is a consistent 20px.
 *
 * page-check.mjs cannot see this and never will: nothing OVERFLOWS, the text is
 * simply touching the glass. A missing gutter is not an overflow, which is why
 * this survived every sweep that asked whether anything was too wide.
 *
 * :where() so the fallback contributes no specificity and any band or wrap
 * around it still wins. */
:where(.cv-page-full, .cv-page) > .cv-grid,
:where(.cv-page-full, .cv-page) > .cv-gallery,
:where(.cv-page-full, .cv-page) > .cv-measure {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

/* The centered single column that the narrow-wide-narrow idiom really meant. */
.cv-measure { max-width: 62rem; margin: 0 auto; }
.cv-row { display: flex; flex-wrap: wrap; gap: 2rem; align-items: flex-start; }
.cv-row > * { flex: 1 1 18rem; min-width: 0; }
.cv-col { min-width: 0; }

/* One column on a phone, always. A three-up grid of product cards at 375px is
   three unreadable slivers. */
@media (max-width: 720px) {
  .cv-grid { grid-template-columns: 1fr; gap: 1.5rem; }
  .cv-band { padding: 2.5rem 1.25rem; }
  .cv-band--tall { padding: 3.5rem 1.25rem; }
}

.cv-tile {
  background-image: var(--cv-tile-img);
  background-size: cover;
  background-position: center center;
  min-height: 16rem;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.cv-tile--dark, .cv-tile--dark * { color: var(--cv-beige, #F0EDE4); }

.cv-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 1rem;
}
.cv-gallery img { width: 100%; height: auto; display: block; }

/* The original used a JavaScript accordion. <details> is the same affordance
   and survives having no JavaScript, which everything here has to. */
.cv-disclosure { border-top: 1px solid var(--cv-border, #D4D1C7); }
.cv-disclosure:last-of-type { border-bottom: 1px solid var(--cv-border, #D4D1C7); }
.cv-disclosure > summary {
  cursor: pointer;
  padding: 1rem 0;
  font-family: "Homestead", Georgia, serif;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  list-style: none;
}
.cv-disclosure > summary::-webkit-details-marker { display: none; }
.cv-disclosure > summary::after { content: " +"; color: var(--cv-red, #B40C2A); }
.cv-disclosure[open] > summary::after { content: " –"; }
.cv-disclosure__body { padding-bottom: 1.5rem; }

.cv-band img, .cv-tile img { max-width: 100%; height: auto; }
/* Scoped with the page class deliberately. A .cv-page p selector is (0,1,1) and
   a bare .cv-cta is (0,1,0), so the paragraph rule won and the button sat hard
   against the photograph above it. NO BACKTICKS IN THIS FILE, comments
   included: it is one big template literal and tsc reports the break tens of
   lines away. */
.cv-page .cv-cta { margin: 1.5rem 0 0; }

/* Back to the section ----------------------------------------------------- */
/* An article reached from a search result or a shared link is otherwise a dead
   end: the storefront's navigation has no entry for Garden Central, so nothing
   on the page leads to the other forty-two articles. One link above the
   headline for orientation, one after the body for the reader who has just
   finished and is most likely to want another. */

.cv-page .cv-parent {
  margin: 0 0 0.75rem;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
}
.cv-page .cv-parent a {
  color: var(--cv-black);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-parent a::before {
  /* A left-pointing mark, so the link reads as going back without needing an
     icon file. Generated content, so it is never selected or read aloud. */
  content: "\2190";
  margin-right: 0.4rem;
}
/* Its hover cue moves to text-decoration, since the border it used to rely on
   is now cancelled above. */
.cv-page .cv-parent a:hover,
.cv-page .cv-parent a:focus-visible { text-decoration: underline; }

/* On a photographic header the band is dark, so the link reverses out of it
   along with the headline. Scoped to the hero that actually has an image --
   a page with no photograph keeps a black band and beige text already. */
.cv-page .cv-header[data-cv-hero] .cv-parent a { color: var(--cv-beige); }

.cv-page .cv-parent-foot {
  margin: clamp(2.5rem, 6vw, 4rem) 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--cv-border);
}
.cv-page .cv-parent-foot a {
  display: inline-block;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--cv-red);
  color: var(--cv-red);
  text-decoration: none;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  transition: color 200ms cubic-bezier(0.2, 0, 0, 1),
    background-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-parent-foot a:hover,
.cv-page .cv-parent-foot a:focus-visible {
  background-color: var(--cv-red);
  color: var(--cv-white);
}

/* Document downloads ------------------------------------------------------ */
/* A page whose job is handing over the right PDF. The migrated version had nine
   links all reading "Download Application Here" or "Download Form Here", which
   is the same failure as a button pointing at nothing: it looks finished and
   tells the reader nothing about which file they are about to open. Each one is
   now a card naming the document and what it is for. */

.cv-page .cv-docs {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.cv-page .cv-doc a {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  height: 100%;
  padding: 1.1rem 1.25rem;
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
  border-left: 2px solid var(--cv-red);
  color: var(--cv-black);
  text-decoration: none;
  background-image: none;
  transition: border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-doc a:hover,
.cv-page .cv-doc a:focus-visible {
  border-color: var(--cv-red);
  background-color: var(--cv-white);
  color: var(--cv-black);
}
.cv-page .cv-doc-name {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.95rem;
  line-height: 1.15;
  color: var(--cv-red);
}
.cv-page .cv-doc-what {
  font-size: 0.875rem;
  line-height: 1.45;
  opacity: 0.78;
  flex: 1 1 auto;
}
.cv-page .cv-doc-file {
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  opacity: 0.55;
  margin-top: 0.35rem;
}

/* The brand a set of forms belongs to. Same idea as the location banner: shown
   because it tells you WHICH yard's paperwork this is, not for decoration. */
.cv-page .cv-doc-brand {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: clamp(2.5rem, 6vw, 3.5rem) 0 0.75rem;
  padding-top: clamp(1.5rem, 4vw, 2.5rem);
  border-top: 1px solid var(--cv-border);
}
.cv-page .cv-doc-brand:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.cv-page .cv-doc-brand img {
  width: 4.5rem;
  height: auto;
  flex: 0 0 auto;
}
.cv-page .cv-doc-brand h2 { margin: 0; }

.cv-page .cv-steps {
  counter-reset: cv-step;
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  gap: 1rem;
}
.cv-page .cv-steps > li {
  position: relative;
  padding-left: 2.75rem;
}
.cv-page .cv-steps > li::before {
  counter-increment: cv-step;
  content: counter(cv-step);
  position: absolute;
  left: 0;
  top: -0.1em;
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--cv-red);
  border-radius: 50%;
  color: var(--cv-red);
  font-family: var(--font-headline);
  font-size: 0.9rem;
}

/* Follow us -------------------------------------------------------------- */
/* Three brands, each with its own accounts. The WordPress theme drew the icons
   with an icon font that died with it, so they are inline SVG here — no font,
   no request, and they cannot go missing.

   Central Valley's own logo rules do not apply to the platform marks: those are
   Instagram's, Facebook's and LinkedIn's, drawn as their standard glyphs. */

.cv-page .cv-social {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  align-items: stretch;
}
.cv-page .cv-social-card {
  display: flex;
  flex-direction: column;
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
  border-left: 2px solid var(--cv-red);
  overflow: hidden;
}
.cv-page .cv-social-body { padding: 1.5rem; display: flex; flex-direction: column; gap: 0.6rem; }
.cv-page .cv-social-name {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1.15rem;
  line-height: 1.1;
  margin: 0;
  color: var(--cv-black);
}
.cv-page .cv-social-note {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--cv-black);
  opacity: 0.75;
  /* Holds the buttons on one line across cards of different text lengths. */
  flex: 1 1 auto;
}

/* The buttons. Icon plus the platform's name: an icon alone is a guess for
   anyone who does not recognise the glyph, and this page exists to be
   welcoming. */
.cv-page .cv-social-links {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.cv-page .cv-social-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.85rem;
  /* --cv-border is 1.31:1 on beige, which is a decorative rule rather than
     a control boundary. A signup field has to be findable. */
  border: 1px solid color-mix(in srgb, var(--cv-black, #343230) 55%, transparent);
  border-radius: 3px;
  color: var(--cv-black);
  text-decoration: none;
  font-size: 0.85rem;
  line-height: 1;
  transition: color 200ms cubic-bezier(0.2, 0, 0, 1),
    border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-social-links a:hover,
.cv-page .cv-social-links a:focus-visible {
  color: var(--cv-red);
  border-color: var(--cv-red);
}
.cv-page .cv-social-links svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  fill: currentColor;
}

/* A strip of what they actually post, so the card shows rather than tells. */
.cv-page .cv-social-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.cv-page .cv-social-strip--one { grid-template-columns: 1fr; }
.cv-page .cv-social-strip img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--cv-beige);
}
.cv-page .cv-social-strip--one img { aspect-ratio: 16 / 9; }

.cv-page .cv-social-signup {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-top: clamp(2rem, 5vw, 3rem);
  border-top: 1px solid var(--cv-border);
}
.cv-page .cv-social-signup h2 { color: var(--cv-red); }

/* Component links inside .cv-prose opt OUT of the prose anchor treatment.
   That treatment is a red underline drawn as a background-image which GROWS to
   fill the whole link on hover — lovely on a line of body copy, wrong on a
   bordered button. On the base state it reads as a doubled bottom edge, and on
   hover it floods the button red; with a red label that is red on red and the
   button goes blank. The prose rule's own comment warns about exactly this, and
   it caught the social buttons and the twenty-two PRO desk contact buttons.

   Only background-image is reset: each component sets its own padding shorthand
   later, so the prose padding-bottom is already overridden. */
.cv-page .cv-prose .cv-social-links a,
.cv-page .cv-prose .cv-card-action a,
.cv-page .cv-prose .cv-loc-cards a,
.cv-page .cv-prose .cv-social-strip a {
  background-image: none;
}
/* And the hover ground is stated outright rather than left to whatever else on
   the page might paint it. */
.cv-page .cv-prose .cv-social-links a:hover,
.cv-page .cv-prose .cv-social-links a:focus-visible {
  background-color: var(--cv-white);
  color: var(--cv-red);
  border-color: var(--cv-red);
}

/* Location page: own-brand banner and the switcher row -------------------- */

/* The yard's banner mark, sitting with the copy about the place rather than in
   a band of its own — there it reads as a mark for THIS yard instead of a
   second site logo. Vallejo and Fairfield carry Foster Lumber Yards, Grass
   Valley carries Caseywood, and the rest carry Central Valley. */
.cv-page .cv-loc-about .cv-loc-brand {
  display: block;
  max-width: min(100%, 8.5rem);
  height: auto;
  margin: 0 0 1.25rem;
}

/* The grey row of other yards. A customer who arrived from a search for one
   very often wants another one's hours, and the storefront navigation has no
   entry for them. Plain links: no script, and it scrolls on a phone rather
   than wrapping into a tall stack. */
.cv-page .cv-loc-switch {
  background: color-mix(in srgb, var(--cv-black, #343230) 6%, transparent);
  border-bottom: 1px solid var(--cv-border);
  padding: 0.9rem 1.5rem;
}
.cv-page .cv-loc-switch-label {
  margin: 0 0 0.5rem;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--cv-black);
  opacity: 0.55;
}
.cv-page .cv-loc-switch ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.5rem;
}
.cv-page .cv-loc-switch a {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
  border-radius: 3px;
  color: var(--cv-black);
  text-decoration: none;
  font-size: 0.85rem;
  transition: color 200ms cubic-bezier(0.2, 0, 0, 1),
    border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-loc-switch a:hover,
.cv-page .cv-loc-switch a:focus-visible {
  color: var(--cv-red);
  border-color: var(--cv-red);
}

@media (max-width: 640px) {
  /* Nine chips wrap into a wall on a phone. One scrolling row instead, which
     keeps the address and hours above the fold where they belong. */
  .cv-page .cv-loc-switch ul {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 0.25rem;
    scrollbar-width: thin;
  }
  .cv-page .cv-loc-switch a { white-space: nowrap; }
}

/* Location cards ---------------------------------------------------------- */
/* Rendered from the Location table by the <!--cv:locations:...--> token, so a
   campaign page shows the hours and phone number that are true TODAY rather
   than the ones typed into the artboard the week it was drawn. */

.cv-page .cv-loc-cards {
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  align-items: stretch;
}
.cv-page .cv-loc-card {
  padding: 1.5rem;
  background: var(--cv-beige);
  border: 1px solid var(--cv-border);
  border-radius: 6px;
}
.cv-page .cv-loc-card-name {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1.25rem;
  line-height: 1.1;
  margin: 0 0 0.6rem;
}
.cv-page .cv-loc-card-name a {
  color: var(--cv-red);
  text-decoration: none;
}
.cv-page .cv-loc-card-name a:hover,
.cv-page .cv-loc-card-name a:focus-visible { text-decoration: underline; }
.cv-page .cv-loc-card-where { margin: 0 0 0.5rem; font-size: 0.95rem; }
.cv-page .cv-loc-card-hours {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--cv-black);
  opacity: 0.7;
}
.cv-page .cv-loc-card-phone { margin: 0; font-size: 0.95rem; }
.cv-page .cv-loc-card-phone a {
  color: var(--cv-black);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.cv-page .cv-loc-card-phone a:hover,
.cv-page .cv-loc-card-phone a:focus-visible { color: var(--cv-red); }

/* On a dark band the card reverses, so it stays readable wherever a design
   drops the token. */
.cv-page .cv-band--dark .cv-loc-card,
.cv-page section[style*="cv-black"] .cv-loc-card {
  background: rgba(240, 237, 228, 0.06);
  border-color: rgba(240, 237, 228, 0.25);
}

/* Email signup ------------------------------------------------------------ */
/* Mailchimp's embed, which was styled by the WordPress theme that is going
   away. Its own markup carries a few inline colours and nothing else, so
   without this it lands as unstyled browser-default inputs.

   The form POSTs cross-origin straight to Mailchimp and needs no JavaScript of
   ours, which is why it survives being injected into Toolbx's SPA. */

.cv-page .mailchimp_form {
  max-width: 32rem;
  margin: 0 auto;
}
.cv-page .mailchimp_form .indicates-required {
  color: var(--cv-black);
  opacity: 0.6;
  margin-bottom: 0.75rem;
}
.cv-page .mailchimp_form .mc-field-group { margin: 0 0 1rem; }
.cv-page .mailchimp_form label {
  display: block;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  margin-bottom: 0.3rem;
  /* The embed sets this inline to the brand red already; kept here so the rule
     holds if that inline attribute is ever tidied out. */
  color: var(--cv-red);
}
.cv-page .mailchimp_form .asterisk { color: var(--cv-red); }
.cv-page .mailchimp_form input[type="email"],
.cv-page .mailchimp_form input[type="text"],
.cv-page .mailchimp_form select {
  display: block;
  width: 100%;
  padding: 0.6rem 0.7rem;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--cv-black);
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
  border-radius: 3px;
}
.cv-page .mailchimp_form input:focus,
.cv-page .mailchimp_form select:focus {
  outline: 2px solid var(--cv-red);
  outline-offset: -2px;
}
.cv-page .mailchimp_form input.button {
  display: inline-block;
  width: auto;
  padding: 0.7rem 1.6rem;
  background: var(--cv-red);
  color: var(--cv-white);
  border: 1px solid var(--cv-red);
  font-family: var(--font-headline);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .mailchimp_form input.button:hover,
.cv-page .mailchimp_form input.button:focus-visible {
  background: var(--cv-black);
  border-color: var(--cv-black);
}
.cv-page .mailchimp_form .response { margin: 0.75rem 0 0; }
/* Mailchimp's spam trap. It positions itself off-screen inline; this keeps it
   from widening the page on a phone if that attribute is ever lost. */
.cv-page .mailchimp_form div[aria-hidden="true"] { overflow: hidden; }

/* Reversed out of a dark band, which is where the home page puts it. */
.cv-page .cv-band--dark .mailchimp_form label,
.cv-page .cv-band--dark .mailchimp_form .indicates-required { color: var(--cv-beige); }
.cv-page .cv-band--dark .mailchimp_form .asterisk { color: var(--cv-beige); }

/* Contact cards ---------------------------------------------------------- */
/* A name, what they cover, and one way to reach them. Used by the PRO desk
   page for both the sales teams and the specialist reps, which arrived from the
   migration as a flat run of h6 + p + link and read as one long column.

   auto-fit rather than a fixed column count: the two lists on that page are
   eight and twelve items, and hard-coding three would leave a ragged last row
   on one of them at every width. */
.cv-page .cv-cards {
  list-style: none;
  margin: 1.5rem 0 2.75rem;
  padding: 0;
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  align-items: stretch;
}
.cv-page .cv-card {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1.25rem;
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
  /* The accent is a rule, not a fill -- CV Red is never a background. */
  border-left: 2px solid var(--cv-red);
}
.cv-page .cv-card-name {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1rem;
  line-height: 1.2;
  margin: 0;
  color: var(--cv-black);
}
.cv-page .cv-card-note {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--cv-black-70);
  /* Pushes the action to the bottom so every card's button lines up. */
  flex: 1 1 auto;
}
.cv-page .cv-card-action { margin: 0.65rem 0 0; }
.cv-page .cv-card-action a {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--cv-red);
  color: var(--cv-red);
  text-decoration: none;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  transition: color 200ms cubic-bezier(0.2, 0, 0, 1),
    background-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-card-action a:hover,
.cv-page .cv-card-action a:focus-visible {
  background-color: var(--cv-red);
  color: var(--cv-white);
}
/* A phone number is the action on the two yards with no team page. It is still
   a link, so it dials on a phone, but it is not dressed as a button -- that
   would imply it goes somewhere. */
.cv-page .cv-card-action-tel a {
  border: 0;
  padding: 0;
  font-family: var(--font-body);
  text-transform: none;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.cv-page .cv-card-action-tel a:hover,
.cv-page .cv-card-action-tel a:focus-visible {
  background: transparent;
  color: var(--cv-red);
}

/* The home page's "Brands we trust" wall. Eight marks, so four and four.
   It arrived from a design as an inline auto-fit grid, which fitted FIVE across
   on a wide screen and orphaned three on a second row. The column count is here
   rather than inline because an inline style cannot carry a media query - and
   four columns of logo on a phone would be unreadable. */
.cv-page .cv-brandwall {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 48rem) {
  .cv-page .cv-brandwall {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* A row of partner or family-of-companies marks.

   Normalised on HEIGHT, not width. These marks have very different aspect
   ratios - a circular seal, a wide short wordmark, a compact block - and a
   width cap makes the circle tower over the wordmark while the wordmark reads
   as an afterthought. A shared height is what makes a logo wall read as a set
   of equals, and it is how the marks sat on the original page.

   A flex row rather than an auto-fit grid: three marks in equal fractions of
   the full measure end up flung to the far edges with a lake of space between
   them. This centres them and keeps the gap deliberate. */
.cv-page .cv-logos {
  list-style: none;
  margin: clamp(2rem, 5vw, 3rem) auto 0;
  padding: 0;
  max-width: 58rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}
.cv-page .cv-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  /* One optical band, so a short wordmark does not ride up against a seal. */
  min-height: 6rem;
}
/* The mark carries the company name inside the artwork, so a caption beside it
   prints the same words twice - which is why the original page had none. The
   image's alt text is what a screen reader and a failed load both fall back to,
   so nothing is lost by taking the caption out of the page. */
.cv-page .cv-logo-name {
  display: none;
}
.cv-page .cv-logo img {
  display: block;
  max-height: 5.5rem;
  /* Still bounded horizontally so a very wide mark cannot crowd its neighbours,
     and never blown up past its intrinsic size. */
  max-width: min(100%, 15rem);
  width: auto;
  height: auto;
  margin: 0 auto;
}

/* Events ------------------------------------------------------------------ */
/* The calendar is a plain CSS grid rendered at publish time. No script runs
   here on purpose: Toolbx injects these pages into its own SPA, so a scripted
   calendar is one that sometimes shows nothing. */

.cv-page .cv-cal { margin: 0 0 2.5rem; }
.cv-page .cv-cal-month {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1.25rem;
  margin: 0 0 0.75rem;
}
.cv-page .cv-cal-names,
.cv-page .cv-cal-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}
.cv-page .cv-cal-dayname {
  font-family: var(--font-headline);
  font-size: 0.75rem;
  text-align: center;
  padding: 0.25rem 0;
  color: var(--cv-black);
  opacity: 0.55;
}
.cv-page .cv-cal-day,
.cv-page .cv-cal-blank {
  min-height: 4.5rem;
  padding: 0.3rem;
  background: var(--cv-white);
  border: 1px solid var(--cv-border);
}
.cv-page .cv-cal-blank { background: transparent; border-color: transparent; }
.cv-page .cv-cal-num {
  display: block;
  font-size: 0.75rem;
  opacity: 0.6;
  margin-bottom: 0.2rem;
}
.cv-page .cv-cal-has { border-left: 2px solid var(--cv-red); }
.cv-page .cv-cal-event {
  display: block;
  font-size: 0.7rem;
  line-height: 1.25;
  padding: 0.15rem 0.25rem;
  margin-bottom: 0.15rem;
  background: var(--cv-beige);
  color: var(--cv-black);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.cv-page .cv-cal-event:hover,
.cv-page .cv-cal-event:focus-visible { color: var(--cv-red); }

/* A day whose events have passed since publish. Set by the small script on the
   index; with no script nothing is marked and everything stays visible. */
.cv-page [data-cv-past="true"] { display: none; }

.cv-page .cv-event-list { list-style: none; margin: 0; padding: 0; }
.cv-page .cv-event-card {
  border-top: 1px solid var(--cv-border);
  padding: 1.1rem 0;
}
.cv-page .cv-event-card-title {
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 1.15rem;
  color: var(--cv-red);
  text-decoration: none;
}
.cv-page .cv-event-card-title:hover,
.cv-page .cv-event-card-title:focus-visible { text-decoration: underline; }
.cv-page .cv-event-when { margin: 0.3rem 0 0; font-weight: 700; }
.cv-page .cv-event-where { margin: 0.15rem 0 0; opacity: 0.75; }
.cv-page .cv-event-summary { margin: 0.4rem 0 0; }

.cv-page .cv-event-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 1.75rem 0 0;
}
.cv-page .cv-button-quiet {
  display: inline-block;
  padding: 0.5rem 1rem;
  border: 1px solid var(--cv-black);
  color: var(--cv-black);
  text-decoration: none;
  font-family: var(--font-headline);
  text-transform: uppercase;
  font-size: 0.8rem;
  transition: color 200ms cubic-bezier(0.2, 0, 0, 1), border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.cv-page .cv-button-quiet:hover,
.cv-page .cv-button-quiet:focus-visible {
  color: var(--cv-red);
  border-color: var(--cv-red);
}
.cv-page .cv-event-back { margin: 1.5rem 0 0; }

@media (max-width: 640px) {
  /* Seven columns of usable day cells do not fit a phone, so the grid becomes a
     list of the days that actually have something on them. The empty cells and
     the weekday header carry no information once it is a list. */
  .cv-page .cv-cal-names { display: none; }
  .cv-page .cv-cal-grid { display: block; }
  .cv-page .cv-cal-blank { display: none; }
  .cv-page .cv-cal-day { display: none; }
  .cv-page .cv-cal-day.cv-cal-has {
    display: block;
    min-height: 0;
    margin-bottom: 2px;
  }
  .cv-page .cv-cal-num { display: inline-block; font-weight: 700; opacity: 1; }
  .cv-page .cv-cal-event { display: inline-block; margin-left: 0.5rem; font-size: 0.8rem; }
}

@media (prefers-reduced-motion: reduce) {
  .cv-page *,
  .cv-page *::before,
  .cv-page *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}