/*
Theme Name: Coaching with Nicollete
Template: devq-starter
Author: Josh Yager
Author URI: https://thedevq.com/
Description: Child theme for Coaching with Nicollete, built on DevQ Starter
Version: 1.0.0
*/

/* ==========================================================================
   Coaching with Nicollete — design system

   Structure is the BMW corporate design kit (getdesign.md/bmw); colour is drawn
   from Nicollete's own photography (teal and rose studio backdrops).

   Five moves make this read as one system:
     1. Zero border-radius on everything. Circular only for avatars.
     2. Inter 700 display against Inter 300 body. Weight 500 is absent.
     3. 80px section rhythm, generous card padding.
     4. No shadows anywhere — depth comes from colour-block contrast and photos.
     5. Bands alternate light -> dark -> light. Never two dark bands touching.

   SPECIFICITY NOTE — read before editing:
   Every DevQ block prints its own <style> block in the BODY, which lands after
   this stylesheet in <head>. At equal specificity the parent wins on document
   order. Parent block rules are `.x-block .x-element` (0,0,2,0), so block-level
   overrides here are prefixed with `body` (0,0,2,1) to win cleanly instead of
   carpet-bombing the file with !important.

   Header CSS is not here at all — it lives in the child's
   template-parts/header/style-standard.php for the same reason.
   ========================================================================== */

:root {
  --ink: #262626;
  --body-text: #3C3C3C;
  --muted: #6B6B6B;
  --hairline: #E6E6E6;
  --hairline-strong: #CCCCCC;
  --surface-card: #F2FAFB;
  --surface-soft: #F7F9FA;
  --primary-active: #0A5F6B;
  --primary-light: #12A0B2;

  /* theme-settings-css.php resolves the radius with `get_field(...) ?: '4'`, so
     the 0 stored in theme options is falsy and comes back as 4px. That block
     prints above <head>, i.e. before this stylesheet, so redeclaring the token
     here wins on document order and the whole system stays square. */
  --button-radius: 0px;
}

/* --------------------------------------------------------------------------
   1. Global
   -------------------------------------------------------------------------- */

/* The two font-weight declarations below are load-bearing for the whole system,
   and the reason they have to exist here is a cascade order problem in the parent.

   theme-settings-css.php prints `h1..h6 { font-weight: var(--heading-weight) }`
   and `body, p, ul, li { font-weight: var(--body-weight) }` in a <style> block
   ABOVE <head>. devq-starter/style.css then declares `h1,h2,h3,h4,h5 {
   font-weight: 500 }` and its own body weight from inside <head> — same
   specificity, later in document order, so the parent stylesheet wins and the
   ACF Heading Weight / Body Weight settings do nothing at all.

   The visible result was that every heading on the site rendered at 500. The
   font embed loads 300/400/600/700 and deliberately not 500, so the browser was
   synthesising the single most prominent typeface on the page — and the "Inter
   700 display against Inter 300 body" pairing this whole design rests on was
   never actually on screen. It reads as slightly soft rather than broken, which
   is why it survived the first QA pass.

   `body h1` (0,0,1,1) outranks the parent's `h1`; the plain `body` rule matches
   the parent's specificity and wins on order, because this sheet is enqueued
   with devq-style as a dependency. Both read the theme-option token, so changing
   the weight in ACF works again rather than being silently ignored. */

body {
  color: var(--body-text);
  background: #fff;
  font-weight: var(--body-weight, 300);
  -webkit-font-smoothing: antialiased;
}

body h1, body h2, body h3, body h4, body h5, body h6 {
  color: var(--ink);
  letter-spacing: 0;
  font-weight: var(--heading-weight, 700);
}

/* --------------------------------------------------------------------------
   2. Buttons

   The parent sets .btn { min-width: 250px }. Inside any constrained row (card
   footer, header, form) that floor refuses to shrink and shoves siblings out of
   the container — the symptom always looks like the *neighbour* is broken.
   Reset it globally, once.
   -------------------------------------------------------------------------- */

body .btn {
  min-width: 0;
  font-family: var(--font1);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1;
  padding: 16px 32px;
  border-radius: 0;
  box-shadow: none;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

body .btn:hover {
  background-color: var(--primary-active);
  border-color: var(--primary-active);
}

body .btn-outline {
  border: 1px solid var(--hairline-strong);
  color: var(--ink);
  background: transparent;
}

body .btn-outline:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

body .btn-white { color: var(--primary); background: #fff; }
body .btn-white:hover { background: var(--surface-card); color: var(--primary-active); }
body .btn-outline-white { border: 1px solid rgba(255,255,255,0.6); color: #fff; }
body .btn-outline-white:hover { background: #fff; border-color: #fff; color: var(--ink); }

/* --------------------------------------------------------------------------
   3. Eyebrows — the BMW label-uppercase token (13px / 700 / 1.5px)
   -------------------------------------------------------------------------- */

body .cs-topper,
body .hero-block .hero-eyebrow,
body .cta-block .cta-eyebrow,
body .textimage-block .textimage-eyebrow,
body .featureslist-block .featureslist-eyebrow {
  font-family: var(--font1);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--primary);
  display: block;
  margin-bottom: 18px;
}

/* --------------------------------------------------------------------------
   4. Display type — fixed px, no clamp()
   -------------------------------------------------------------------------- */

body .hero-block .hero-heading           { font-size: 60px; line-height: 1.05; }
body .herosplit-block .herosplit-heading { font-size: 56px; line-height: 1.05; }
body .cta-block .cta-heading             { font-size: 34px; line-height: 1.15; }
body .cards-block .cards-heading,
body .testimonials-block .testimonials-heading,
body .featureslist-block .featureslist-heading,
body .textimage-block .textimage-heading { font-size: 38px; line-height: 1.12; }

body .hero-block .hero-subheading,
body .cta-block .cta-text,
body .cards-block .cards-subheading,
body .featureslist-block .featureslist-subheading { font-size: 17px; line-height: 1.6; font-weight: 300; }

/* --------------------------------------------------------------------------
   5. Hero Split — the BMW hero-photo-band

   Her intro is two paragraphs, but the block pushes `content` through
   esc_html() into a single <p>. pre-line preserves the paragraph break without
   needing HTML in an escaped field.
   -------------------------------------------------------------------------- */

body .herosplit-block {
  background-color: #fff;
  padding-top: 48px;
  padding-bottom: 0;
}

body .herosplit-block .herosplit-content {
  white-space: pre-line;
  color: var(--body-text);
  font-size: 17px;
  line-height: 1.65;
  font-weight: 300;
  max-width: 34em;
}

body .herosplit-block .herosplit-image img {
  border-radius: 0;
  box-shadow: none;
  width: 100%;
  height: auto;
  display: block;
}

body .herosplit-block .herosplit-buttons { gap: 14px; }

/* --------------------------------------------------------------------------
   6. Dark bands

   The Hero block derives is_dark_bg from style === image|gradient only, so a
   `solid` hero on #14303A still gets .hero-text-dark, which paints the heading
   in var(--primary) teal on deep navy — barely legible. The is-dark-band class
   is applied to those blocks in .site-build/04-pages.php purely to fix it here.
   -------------------------------------------------------------------------- */

body .hero-block.is-dark-band .hero-text-dark .hero-heading    { color: #fff; }
body .hero-block.is-dark-band .hero-text-dark .hero-subheading { color: rgba(255,255,255,0.82); font-size: 18px; font-weight: 300; }
body .hero-block.is-dark-band .hero-text-dark .cs-topper,
body .hero-block.is-dark-band .hero-text-dark .hero-eyebrow    { color: rgba(255,255,255,0.72); }

body .hero-block.is-dark-band .hero-content {
  padding-top: 96px;
  padding-bottom: 96px;
}

body .cta-block .cta-content { padding-top: 88px; padding-bottom: 88px; }
body .cta-block .cta-heading { color: #fff; }
body .cta-block .cta-text {
  color: rgba(255,255,255,0.82);
  max-width: 46em;
  margin-left: auto;
  margin-right: auto;
  font-weight: 300;
}
body .cta-block .cta-eyebrow { color: rgba(255,255,255,0.6); }

/* --------------------------------------------------------------------------
   7. Cards — the BMW model-card

   White plate, hairline edge, no shadow, no lift. Hover moves the border only.
   A card that rises off the page is the SaaS dialect, not this one.
   -------------------------------------------------------------------------- */

body .cards-block { background: var(--surface-soft); }

body .cards-block .cards-card {
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 0;
  box-shadow: none;
  padding: 34px 30px;
  transition: border-color 0.2s ease;
}

body .cards-block .cards-card:hover {
  border-color: var(--primary);
  transform: none;
  box-shadow: none;
}

body .cards-block .cards-card-icon i { color: var(--primary); font-size: 30px; }
body .cards-block .cards-card-title  { font-size: 21px; line-height: 1.3; margin-bottom: 12px; }
body .cards-block .cards-card-description { font-size: 15px; line-height: 1.65; color: var(--body-text); font-weight: 300; }

/* Her three service descriptions are different lengths, so the LEARN MORE links
   landed on three different baselines. Push them to a common bottom edge. */
body .cards-block .cards-card { display: flex; flex-direction: column; }
body .cards-block .cards-card-description { flex: 1 1 auto; }
body .cards-block .cards-card-link { margin-top: 22px; align-self: flex-start; }

/* The BMW "LEARN MORE" inline link. */
body .cards-block .cards-card-link {
  font-family: var(--font1);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--primary);
  padding-bottom: 3px;
  transition: color 0.2s ease;
}

body .cards-block .cards-card-link:hover { color: var(--primary); }

/* --------------------------------------------------------------------------
   8. Text Image / Features List / Testimonials
   -------------------------------------------------------------------------- */

body .textimage-block .textimage-content p { margin-bottom: 1.15em; font-weight: 300; line-height: 1.7; }
body .textimage-block .textimage-content p:last-child { margin-bottom: 0; }

/* Both headshots are tall portraits (1600px on the long edge). Left unbounded
   they run far past the end of the copy and leave a dead column of whitespace
   beside them. Cap the height and crop toward the top so her face stays in
   frame rather than her waist. */
body .textimage-block .textimage-image img {
  border-radius: 0;
  box-shadow: none;
  max-height: 560px;
  width: 100%;
  object-fit: cover;
  object-position: center 18%;
}

body .featureslist-block { background: #fff; }
body .featureslist-block .featureslist-item {
  border-top: 1px solid var(--hairline);
  padding-top: 26px;
  box-shadow: none;
  border-radius: 0;
}
body .featureslist-block .featureslist-icon i { color: var(--primary); }
body .featureslist-block .featureslist-title { font-size: 18px; line-height: 1.4; }
body .featureslist-block .featureslist-description { font-size: 15px; color: var(--muted); font-weight: 300; }

body .testimonials-block { background: var(--surface-card); }
body .testimonials-block .testimonials-card {
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 0;
  box-shadow: none;
  padding: 44px;
}
body .testimonials-block .testimonials-quote {
  font-size: 18px;
  line-height: 1.7;
  font-weight: 300;
  font-style: normal;
  color: var(--body-text);
}
body .testimonials-block .testimonials-name { font-family: var(--font1); font-weight: 700; font-size: 16px; color: var(--ink); }
body .testimonials-block .testimonials-role { font-size: 14px; color: var(--muted); font-weight: 300; }

/* Two things the block invents that Nicollete never supplied:

   1. The star rating defaults to five. Showing it would publish a score her
      client never gave.
   2. The photo falls back to a random picsum.photos stock portrait — a
      stranger's face presented as Ashley Lee's next to a real, attributed
      testimonial. That is the more serious of the two. It is also a
      third-party request from every page view.

   Both are hidden rather than styled. */
body .testimonials-block .testimonials-stars { display: none; }
body .testimonials-block .testimonials-photo { display: none; }
body .testimonials-block .testimonials-author-text { margin-left: 0; }

/* One testimonial in a 2-up grid renders as a tall narrow column. Collapse to a
   single centred card when it is the only one; the rule stops applying by
   itself once she supplies a second. */
body .testimonials-block .testimonials-grid:has(> .testimonials-card:only-child) {
  grid-template-columns: 1fr;
  max-width: 780px;
  margin-left: auto;
  margin-right: auto;
}

/* --------------------------------------------------------------------------
   9. Contact Split
   -------------------------------------------------------------------------- */

body .contactsplit-block { background: #fff; }

/* The icon sits inside a filled teal disc, so a teal glyph is invisible. */
body .contactsplit-block .contactsplit-item-icon i { color: #fff; font-size: 15px; }
body .contactsplit-block .contactsplit-item-label {
  font-family: var(--font1);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--muted);
}
body .contactsplit-block .contactsplit-item-value a { color: var(--ink); text-decoration: none; }
body .contactsplit-block .contactsplit-item-value a:hover { color: var(--primary); }
/* --------------------------------------------------------------------------
   10. Gravity Forms

   EVERY declaration in this section carries !important, deliberately.

   Gravity Forms 2.9 ships the Orbital framework, which sets its own values from
   an inline <style> scoped to `#gform_wrapper_1[data-form-index="0"].gform-theme`
   — an id selector, printed in the body, after this stylesheet. A plain rule here
   does not lose loudly, it loses silently: the form just renders in someone
   else's brand and nothing in devtools points at why. This is the one surface on
   the site where blanket !important is the correct tool rather than a shortcut.

   The wrapper class on this install is
     `gform_wrapper gform-theme gform-theme--foundation
      gform-theme--framework gform-theme--orbital`
   — NOT `gravity-theme`. Selectors are read off the rendered page rather than
   assumed; targeting `.gform_wrapper.gravity-theme` silently matches nothing.
   -------------------------------------------------------------------------- */

/* Field surfaces.

   Two passes got this wrong before it got right, and both reasons are worth
   keeping so nobody reintroduces either:

   - #E6E6E6 borders on white measured 1.58:1. The fields read as stray
     underlines rather than as things you can type into, and it fails WCAG
     1.4.11, which wants 3:1 on any boundary that is what identifies a control.
   - A tinted grey fill fixed the visibility and introduced a worse problem. On
     this site every interactive rectangle — service card, scorecard tile, option
     mark — is a WHITE plate with a hairline edge, so a grey-filled input inside
     a white card was the one element not speaking the same language. Filled grey
     is also the universal convention for *disabled*: it read as a form you were
     not allowed to use.

   So: a white plate like everything else, and the contrast comes from the border
   instead of a fill. #858585 is 3.69:1 against white — past the 3:1 floor with
   room to spare, crisp at 1px, and not so dark that eight stacked fields turn
   the card into a grid. */
:root {
  --field-bg: #FFFFFF;
  --field-border: #858585;
  --field-border-hover: var(--ink);
}

/* 10a. TOKENS FIRST.

   Overriding Orbital's *rules* one at a time leaves its token layer intact, so
   every widget nobody remembered to name — a datepicker header, a checkbox tick,
   a focus ring, a validation icon — keeps rendering in GF blue #204ce5.
   Redefining the tokens fixes all of them at once. */

.gform_wrapper.gform-theme,
.gform_wrapper.gform-theme--orbital {
  --gf-radius: 0 !important;

  --gf-color-primary: var(--primary) !important;
  /* The rgb triplet must be the literal channels of --primary; GF composes
     rgba() from it. Keep in step with styles_primary_color (#0E7C8A). */
  --gf-color-primary-rgb: 14, 124, 138 !important;
  --gf-color-primary-contrast: #ffffff !important;
  --gf-color-primary-contrast-rgb: 255, 255, 255 !important;
  --gf-color-primary-darker: var(--primary-active) !important;
  --gf-color-primary-lighter: var(--primary-light) !important;

  --gf-color-in-ctrl-primary: var(--primary) !important;
  --gf-color-in-ctrl-primary-rgb: 14, 124, 138 !important;
  --gf-color-in-ctrl-primary-contrast: #ffffff !important;
  --gf-color-in-ctrl-primary-darker: var(--primary-active) !important;
  --gf-color-in-ctrl-primary-lighter: var(--primary-light) !important;

  --gf-ctrl-border-color: var(--field-border) !important;
  --gf-ctrl-label-color-primary: var(--ink) !important;
  --gf-ctrl-label-color-secondary: var(--muted) !important;
  --gf-color-out-ctrl-dark: var(--muted) !important;

  --gf-font-size-secondary: 13px !important;
  --gf-font-size-tertiary: 12px !important;
}

/* 10b. Row rhythm and the two-up row.

   Orbital lays .gform_fields out as a 12-column grid with every field spanning
   the full width, which turns eight fields into eight full-width rows and a form
   you have to scroll to finish. Email and phone are short, related, and always
   filled together — pairing them removes a row and makes the form read as two
   blocks (who you are / what you want) rather than one long list.

   Only that pair is paired. The remaining labels ("What service are you
   interested in?") are too long to survive at half width and would wrap to two
   lines, which costs back the row it saved. */

.gform_wrapper .gform_fields {
  row-gap: 24px !important;
  column-gap: 20px !important;
}

.gform_wrapper #field_1_2,
.gform_wrapper #field_1_3 {
  grid-column: span 6 !important;
}

/* 10c. Labels. */

.gform_wrapper .gfield_label,
.gform_wrapper legend.gfield_label,
.gform_wrapper .gform-field-label {
  font-family: var(--font1) !important;
  font-size: 13px !important;
  font-weight: 700 !important;
  letter-spacing: 1.2px !important;
  text-transform: uppercase !important;
  line-height: 1.35 !important;
  color: var(--ink) !important;
  margin-bottom: 8px !important;
  padding: 0 !important;
}

/* "First" / "Last" under the name field are captions, not labels — they should
   not be shouting in the same voice as the field they sit beneath. The
   `.ginput_complex label` rule below is one class more specific, so !important
   alone lost here; the extra `.ginput_complex` settles it on specificity. */
.gform_wrapper .ginput_complex .gform-field-label--type-sub,
.gform_wrapper .gform-field-label--type-sub {
  font-family: var(--font2) !important;
  font-size: 12px !important;
  font-weight: 300 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--muted) !important;
  margin: 6px 0 0 !important;
}

/* GF prints "(Required)" inside the label, where it inherits the label's weight
   and tracking and ends up as loud as the question itself. It is an annotation. */
.gform_wrapper .gfield_required,
.gform_wrapper .gfield_required_text {
  font-family: var(--font2) !important;
  font-size: 11px !important;
  font-weight: 300 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--muted) !important;
  margin-left: 6px !important;
}

/* The First/Last pair has to land on exactly the same rhythm as the Email/Phone
   pair directly beneath it, or the right edges miss each other by a few pixels
   and the whole form looks subtly crooked without it being obvious why.

   GF builds this row with its own grid: each column carries `padding: 0 6px` and
   the wrapper pulls itself 6px wider on both sides to compensate. Adding a gap on
   top of that gutter stacks 20px onto the existing 12px and narrows both columns.
   Zero the column padding and the wrapper offset first, then let one gap own the
   spacing. */
.gform_wrapper .ginput_complex.ginput_container--name {
  gap: 20px !important;
  margin: 0 !important;
  width: 100% !important;
}

.gform_wrapper .ginput_complex .gform-grid-col {
  padding: 0 !important;
  margin: 0 !important;
}

/* 10d. Controls.

   The left edge is the site's own recurring device — a 3px teal rule marks the
   confirmation message, the calculator's cost panel, the privacy note and every
   scorecard band. Reusing it as the focus indicator means the form is marked
   "you are here" in the page's existing vocabulary rather than a new one.

   It is transparent at rest rather than added on focus, so gaining focus changes
   colours and nothing else. An earlier version grew the border from 1px to 2px,
   which reflowed the field's contents by a pixel on all four sides every time
   someone tabbed in. Padding-left is 14px against 16px right so the 3px rule and
   the 1px border still put the text on the same optical inset. */

.gform_wrapper .gfield input[type=text],
.gform_wrapper .gfield input[type=email],
.gform_wrapper .gfield input[type=tel],
.gform_wrapper .gfield input[type=number],
.gform_wrapper .gfield input[type=url],
.gform_wrapper .gfield select,
.gform_wrapper .gfield textarea {
  width: 100% !important;
  /* Uniform on all four sides. An earlier version made the left edge
     `3px solid transparent` so focus could tint it into the site's teal rule
     without reflowing the field — but a transparent border still reads as no
     border, so every input sat there with its left side visibly open. The
     transition was already animating border-COLOR only, so keeping the width
     fixed at 1px and changing just the colour on focus gets the same zero-shift
     behaviour with all four edges actually drawn. */
  border: 1px solid var(--field-border) !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  /* height:auto is doing real work. Orbital pins text inputs to a fixed
     --gf-ctrl-size, and with box-sizing:border-box that swallows the padding —
     inputs came out 38px tall while the select (which already had height:auto
     for its chevron) sat at 54px, so no two rows of the form lined up. Letting
     padding decide the height puts every control on the same 50px:
     12 + 12 padding + 24 line-height + 2 border. */
  height: auto !important;
  /* Symmetric again now that the left border matches the other three; the 14px
     left was compensating for a 3px edge that no longer exists. */
  padding: 12px 16px !important;
  font-family: var(--font2) !important;
  font-size: 16px !important;
  font-weight: 300 !important;
  line-height: 1.5 !important;
  color: var(--ink) !important;
  background-color: var(--field-bg) !important;
  transition: background-color 0.18s ease, border-color 0.18s ease !important;
}

.gform_wrapper .gfield input:hover,
.gform_wrapper .gfield select:hover,
.gform_wrapper .gfield textarea:hover {
  border-color: var(--field-border-hover) !important;
}

.gform_wrapper .gfield input::placeholder,
.gform_wrapper .gfield textarea::placeholder {
  color: var(--muted) !important;
  opacity: 1 !important;
}

/* Focus changes colour only, never width — the border goes from #858585 to ink
   all round, with the left edge picking up the site's teal. Nothing in the box
   model moves, so tabbing through the form does not nudge the layout. */
.gform_wrapper .gfield input:focus,
.gform_wrapper .gfield select:focus,
.gform_wrapper .gfield textarea:focus {
  background-color: #ffffff !important;
  border-color: var(--ink) !important;
  border-left-color: var(--primary) !important;
  outline: none !important;
}

/* An explicit height, not just a min-height. `height: auto` above is what lets
   the text inputs size from their padding, but a textarea's auto height comes
   from its `rows` attribute — GF sets rows="10", which rendered a 266px box that
   made the form look like it wanted an essay. resize:vertical means anyone who
   does want to write one can still drag it open. */
.gform_wrapper .gfield textarea {
  height: 124px !important;
  min-height: 124px !important;
  resize: vertical !important;
}

/* Orbital sets `appearance: none` on selects but supplies no replacement arrow,
   so the dropdown renders indistinguishable from a text input. Put a chevron
   back. Inline SVG (not an external asset) so it can't 404 or go stale. */
.gform_wrapper .gfield select {
  appearance: none !important;
  -webkit-appearance: none !important;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'%3E%3Cpath fill='none' stroke='%23262626' stroke-width='1.6' d='M1 1l6 6 6-6'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 16px center !important;
  background-size: 13px 8px !important;
  padding-right: 44px !important;
  height: auto !important;
  cursor: pointer !important;
}

.gform_wrapper .gfield_description,
.gform_wrapper .gfield_description.instruction {
  font-size: 13px !important;
  line-height: 1.55 !important;
  color: var(--muted) !important;
  font-weight: 300 !important;
  padding-top: 7px !important;
}

/* 10e. Checkboxes and radios as a grid of tiles.

   Stacked one per line, six options ate roughly 350px of the form for six words
   of content, and a bare browser control next to loose text is the single
   clearest tell that a form was never styled. Auto-fit gives three columns at
   full width, two in the narrow contact column, one on a phone — without a
   media query and without caring how many options an editor adds later.

   :has() paints the checked state on the tile; where it is unsupported the
   control still shows its own check, so the fallback is plain rather than
   broken.

   HIT AREA — see 10f. An earlier version of this comment claimed a click
   anywhere in the tile toggled the input "because .gchoice contains the
   <label>". It does not. GF renders the input and the label as SIBLINGS inside
   the .gchoice div, so the label wraps the word and nothing else; the tile's
   padding was dead space. The tile carries `cursor: pointer` across its whole
   rectangle, which made it look clickable everywhere while roughly two thirds
   of it did nothing. 10f fixes that. */

.gform_wrapper .gfield_checkbox,
.gform_wrapper .gfield_radio {
  display: grid !important;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important;
  gap: 10px !important;
  margin: 0 !important;
  padding: 0 !important;
}

.gform_wrapper .gchoice {
  display: flex !important;
  align-items: center !important;
  gap: 11px !important;
  margin: 0 !important;
  /* 14 + 14 + a 20px label line + 2px border = 50px, the same as every text
     control, so a tile row and an input row are interchangeable to the eye. */
  padding: 14px 14px !important;
  border: 1px solid var(--field-border) !important;
  border-radius: 0 !important;
  background: var(--field-bg) !important;
  cursor: pointer !important;
  transition: background-color 0.15s ease, border-color 0.15s ease !important;
}

.gform_wrapper .gchoice:hover {
  border-color: var(--primary) !important;
}

.gform_wrapper .gchoice:has(input:checked) {
  border-color: var(--primary) !important;
  background: var(--surface-card) !important;
}

.gform_wrapper .gchoice input[type=checkbox],
.gform_wrapper .gchoice input[type=radio],
.gform_wrapper .gfield-choice-input {
  flex: 0 0 auto !important;
  width: 19px !important;
  height: 19px !important;
  margin: 0 !important;
  accent-color: var(--primary) !important;
  cursor: pointer !important;
}

/* Square boxes, round radios — the one place a circle is allowed in a system
   that is otherwise 0px radius, because the shape is what tells someone "pick
   exactly one" before they read a word of it. */
.gform_wrapper .gchoice input[type=checkbox] { border-radius: 0 !important; }

.gform_wrapper .gchoice label,
.gform_wrapper .gform-field-label--type-inline {
  margin: 0 !important;
  font-family: var(--font2) !important;
  font-size: 15px !important;
  font-weight: 300 !important;
  line-height: 1.35 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--body-text) !important;
  cursor: pointer !important;
}

.gform_wrapper .gchoice:has(input:checked) label {
  color: var(--ink) !important;
  font-weight: 400 !important;
}

/* Keyboard users get a visible ring on the tile, not just on the 19px control. */
.gform_wrapper .gchoice:has(input:focus-visible) {
  outline: 2px solid var(--ink) !important;
  outline-offset: 2px !important;
}

/* 10f. The whole tile is the hit target.

   GF renders <input> and <label> as siblings, so the label's clickable area is
   the width of its own text — "Text" is a 26px target inside a 200px tile that
   is styled to look clickable end to end. Stretching the LABEL over the tile
   with a transparent ::after is what makes the rest of the rectangle work,
   because a click on a <label for> is forwarded to its input by the browser.
   No JS, and it keeps working if an editor adds or renames a choice.

   Why the label and not a click handler on .gchoice: a handler would have to
   toggle `checked` itself, which does not fire the change event GF's
   conditional logic listens to, and would double-toggle on the label and the
   control. Forwarding through the label is the browser's own mechanism — one
   toggle, real events, correct behaviour for radios (which must not untoggle).

   The overlay sits above the 19px control too. That is intentional and not a
   double-toggle: a pointer event is delivered to exactly one element, so the
   click lands on the label and is forwarded once.

   `inset: -1px`, not 0. An absolutely positioned box resolves its offsets
   against the containing block's PADDING box, so `inset: 0` stops inside the
   tile's 1px border and leaves a dead ring around the visible rectangle. The
   -1px pushes the overlay out over the border. It cannot reach a neighbour:
   the grid gap is 10px. */
.gform_wrapper .gchoice {
  position: relative !important;
}

.gform_wrapper .gchoice label::after {
  content: '' !important;
  position: absolute !important;
  inset: -1px !important;
  cursor: pointer !important;
}

/* Legacy complex-field labels (not the inline choice labels above). */
.gform_wrapper .ginput_complex label {
  font-size: 15px !important;
  font-weight: 300 !important;
  color: var(--body-text) !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
}

/* 10f. Submit. */

.gform_wrapper .gform_footer {
  margin: 8px 0 0 !important;
  padding: 0 !important;
}

.gform_wrapper .gform_footer input[type=submit],
.gform_wrapper .gform_footer button {
  background: var(--primary) !important;
  color: #fff !important;
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  font-family: var(--font1) !important;
  font-size: 14px !important;
  font-weight: 700 !important;
  letter-spacing: 0.5px !important;
  padding: 18px 40px !important;
  width: auto !important;
  min-width: 0 !important;
  cursor: pointer !important;
  transition: background-color 0.2s ease !important;
}

.gform_wrapper .gform_footer input[type=submit]:hover,
.gform_wrapper .gform_footer button:hover {
  background: var(--primary-active) !important;
}

/* The button has no border to change colour, so focus needs its own mark. */
.gform_wrapper .gform_footer input[type=submit]:focus-visible,
.gform_wrapper .gform_footer button:focus-visible {
  outline: 2px solid var(--ink) !important;
  outline-offset: 3px !important;
}

/* 10g. Errors.

   The failing field carries the same left rule as a focused one, in red instead
   of teal — one device, two states, so a scan down the form finds the problem
   without reading anything. The message underneath is plain text; the boxed,
   tinted "error card" GF ships turns one missed field into an incident. */

.gform_wrapper .gfield_error input[type=text],
.gform_wrapper .gfield_error input[type=email],
.gform_wrapper .gfield_error input[type=tel],
.gform_wrapper .gfield_error input[type=number],
.gform_wrapper .gfield_error select,
.gform_wrapper .gfield_error textarea {
  /* The whole border goes red, not just the left edge. Back when the left edge
     was 3px it could carry the state on its own; at a uniform 1px it is too
     quiet to find by scanning, and colour alone is never the only signal here
     anyway — the message underneath says what is wrong. */
  border-color: #B3261E !important;
  background-color: #FDF8F8 !important;
}

.gform_wrapper .gfield.gfield_error,
.gform_wrapper .gfield--has-error {
  background: transparent !important;
  border: none !important;
  padding: 0 !important;
}

.gform_wrapper .gfield_validation_message,
.gform_wrapper .validation_message {
  border: none !important;
  background: transparent !important;
  padding: 6px 0 0 !important;
  color: #B3261E !important;
  font-size: 13px !important;
  font-weight: 300 !important;
}

.gform_wrapper .gform_validation_errors {
  border-radius: 0 !important;
  box-shadow: none !important;
  border: none !important;
  border-left: 3px solid #B3261E !important;
  background: #FDF3F2 !important;
  padding: 18px 22px !important;
  margin-bottom: 26px !important;
}

.gform_wrapper .gform_validation_errors > h2 {
  font-size: 16px !important;
  line-height: 1.4 !important;
  color: #8C1D18 !important;
  margin: 0 !important;
}

/* 10h. Datepicker.

   jQuery UI ships its own theme and it looks nothing like anything else here —
   rounded corners, gradients, its own blue. It is the last widget in the form
   that would give the build away, and it only appears once someone clicks into
   the date field, which is exactly when they are paying attention. */

.gform_wrapper .gfield--type-date .ginput_container_date { max-width: 240px !important; }

#ui-datepicker-div {
  border-radius: 0 !important;
  box-shadow: none !important;
  border: 1px solid var(--field-border-hover) !important;
  background: #fff !important;
  padding: 10px !important;
  font-family: var(--font2) !important;
  font-size: 14px !important;
  z-index: 9999 !important;
}

#ui-datepicker-div .ui-datepicker-header {
  background: var(--secondary) !important;
  border: none !important;
  border-radius: 0 !important;
  padding: 10px 8px !important;
}

#ui-datepicker-div .ui-datepicker-title,
#ui-datepicker-div .ui-datepicker-title select {
  font-family: var(--font1) !important;
  font-weight: 700 !important;
  font-size: 13px !important;
  letter-spacing: 1px !important;
  text-transform: uppercase !important;
  color: #fff !important;
}

/* The month/year selects sit on the dark header, so they need a dark field. */
#ui-datepicker-div .ui-datepicker-title select {
  background: transparent !important;
  border: 1px solid rgba(255, 255, 255, 0.4) !important;
  border-radius: 0 !important;
  padding: 4px 6px !important;
}

#ui-datepicker-div .ui-datepicker-title select option { color: var(--ink) !important; }

#ui-datepicker-div .ui-datepicker-prev,
#ui-datepicker-div .ui-datepicker-next {
  border: none !important;
  background: transparent !important;
  border-radius: 0 !important;
  top: 10px !important;
  cursor: pointer !important;
}

#ui-datepicker-div th {
  font-family: var(--font1) !important;
  font-size: 11px !important;
  font-weight: 700 !important;
  letter-spacing: 0.8px !important;
  text-transform: uppercase !important;
  color: var(--muted) !important;
  padding: 8px 0 !important;
}

#ui-datepicker-div td .ui-state-default {
  border: 1px solid transparent !important;
  border-radius: 0 !important;
  background: #fff !important;
  color: var(--ink) !important;
  text-align: center !important;
  padding: 8px 6px !important;
  font-weight: 300 !important;
  transition: background-color 0.15s ease, border-color 0.15s ease !important;
}

#ui-datepicker-div td .ui-state-default:hover {
  border-color: var(--primary) !important;
  background: var(--surface-card) !important;
}

#ui-datepicker-div td .ui-state-active,
#ui-datepicker-div td .ui-state-highlight {
  background: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
  font-weight: 700 !important;
}

/* 10i. Confirmation.

   The one message on the site someone reads after doing something brave. It gets
   the left rule and room to breathe, and nothing else. */

.gform_confirmation_message {
  border-left: 3px solid var(--primary) !important;
  padding: 24px 28px !important;
  background: var(--surface-card) !important;
  font-weight: 300 !important;
  font-size: 17px !important;
  line-height: 1.7 !important;
  color: var(--ink) !important;
}

/* 10j. Narrow widths.

   The two-up email/phone row and the choice grid both collapse here. 767 rather
   than a custom width so the form turns over at the same moment as the rest of
   the site. */

@media (max-width: 767px) {
  .gform_wrapper .gform_fields { row-gap: 20px !important; }

  .gform_wrapper #field_1_2,
  .gform_wrapper #field_1_3 { grid-column: 1 / -1 !important; }

  .gform_wrapper .gfield_checkbox,
  .gform_wrapper .gfield_radio { grid-template-columns: 1fr !important; }

  .gform_wrapper .ginput_complex.ginput_container--name { gap: 12px !important; }

  .gform_wrapper .gform_footer input[type=submit],
  .gform_wrapper .gform_footer button { width: 100% !important; }
}
/* --------------------------------------------------------------------------
   11. Footer

   The parent paints .devq-footer-main with var(--primary) and hardcodes white
   text throughout. #0E7C8A against white is 4.93:1, so that pairing is safe.

   .devq-footer-copyright is a SIBLING of the main footer carrying an
   rgba(0,0,0,.15) tint — a translucent tint on a sibling composites against the
   PAGE, not the footer, so it renders near-white with white text on it. Force a
   solid colour.
   -------------------------------------------------------------------------- */

body .devq-footer-copyright {
  background: var(--secondary);
}

/* The parent paints footer text at rgba(255,255,255,0.7) on the teal. That
   measures 3.24:1 — below the 4.5:1 AA floor for body-size text. Solid white on
   #0E7C8A is 4.93:1, so the fix is simply to stop fading it. Verified with a
   contrast sweep over every page, not by eye. */
body .devq-footer-brand .devq-footer-desc,
body .devq-footer-links a,
body .devq-footer-contact a,
body .devq-footer-copyright p,
body .devq-footer-copyright a {
  color: #fff;
}

body .devq-footer-links a:hover,
body .devq-footer-contact a:hover { color: rgba(255,255,255,0.78); }

/* The parent renders these as bare inline links, which measured 17px tall on a
   phone — under the 24px minimum in WCAG 2.5.8. Inline elements ignore vertical
   padding for layout purposes but still take it for hit-testing, so inline-block
   is what actually makes the target bigger. The 4px is small enough that the
   footer's spacing does not visibly change. */
body .devq-footer-links a,
body .devq-footer-contact a {
  display: inline-block;
  padding: 4px 0;
}

body .devq-footer-brand .devq-footer-desc {
  font-family: var(--font1);
  font-weight: 700;
  font-size: 20px;
}

body .devq-footer-social a,
body .devq-footer-contact i { color: #fff; }

/* --------------------------------------------------------------------------
   12. Motion

   Every rule here is scoped to `.cwn-anim`, a class added to <html> by
   assets/js/reveal.js. Nothing is hidden unless that script has already run, so
   a failed or slow script load leaves the page fully visible rather than blank.
   That inversion is the whole point — see the note in reveal.js.

   The motion itself stays inside the BMW dialect: a short rise, a fade, ease-out,
   no scale and no bounce. Machined, not playful.
   -------------------------------------------------------------------------- */

/* No `will-change` here on purpose. It pins a compositor layer on every one of
   ~20 elements for the life of the page, and it makes getComputedStyle report
   the layer's opacity rather than the painted value — which reads as "0" on
   elements that are plainly visible on screen. The transition is cheap enough
   without it. */
.cwn-anim .cwn-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.55s cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.cwn-anim .cwn-reveal.cwn-in {
  opacity: 1;
  transform: none;
}

/* Stagger siblings in CSS rather than from JS. The observer callback can fire
   for several cards in one batch, so a JS-assigned delay races; nth-child is
   deterministic and needs no timing at all. */
.cwn-anim .cards-grid > .cwn-reveal:nth-child(2),
.cwn-anim .featureslist-grid > .cwn-reveal:nth-child(2) { transition-delay: 80ms; }
.cwn-anim .cards-grid > .cwn-reveal:nth-child(3),
.cwn-anim .featureslist-grid > .cwn-reveal:nth-child(3) { transition-delay: 160ms; }
.cwn-anim .cards-grid > .cwn-reveal:nth-child(4),
.cwn-anim .featureslist-grid > .cwn-reveal:nth-child(4) { transition-delay: 240ms; }

/* Hero and section headers cascade their own children. */
.cwn-anim .herosplit-text > .cwn-reveal:nth-child(2),
.cwn-anim .hero-content > .cwn-reveal:nth-child(2),
.cwn-anim .textimage-text > .cwn-reveal:nth-child(2),
.cwn-anim .cta-content > .cwn-reveal:nth-child(2) { transition-delay: 90ms; }
.cwn-anim .herosplit-text > .cwn-reveal:nth-child(3),
.cwn-anim .hero-content > .cwn-reveal:nth-child(3),
.cwn-anim .textimage-text > .cwn-reveal:nth-child(3),
.cwn-anim .cta-content > .cwn-reveal:nth-child(3) { transition-delay: 180ms; }
.cwn-anim .herosplit-text > .cwn-reveal:nth-child(4),
.cwn-anim .hero-content > .cwn-reveal:nth-child(4),
.cwn-anim .cta-content > .cwn-reveal:nth-child(4) { transition-delay: 270ms; }

/* The hero portrait comes in from its own side rather than upward — it reads as
   the photo settling into the band instead of the whole page sliding. */
.cwn-anim .herosplit-image.cwn-reveal { transform: translateX(26px); }
.cwn-anim .textimage-image.cwn-reveal { transform: translateX(-26px); }
.cwn-anim .herosplit-image.cwn-in,
.cwn-anim .textimage-image.cwn-in { transform: none; }

/* Motion polish on interactive elements — these are unconditional because they
   only ever run on hover, and can't hide anything. */
body .btn { transition: background-color 0.22s ease, color 0.22s ease, border-color 0.22s ease, transform 0.22s ease; }
body .btn:active { transform: translateY(1px); }

body .cards-block .cards-card-link { transition: color 0.2s ease, border-color 0.2s ease, letter-spacing 0.2s ease; }
body .cards-block .cards-card:hover .cards-card-link { letter-spacing: 1.9px; }

body .devq-footer-links a,
body .devq-footer-contact a { transition: color 0.2s ease, opacity 0.2s ease; }

.gform_wrapper .gfield input,
.gform_wrapper .gfield select,
.gform_wrapper .gfield textarea {
  transition: border-color 0.18s ease !important;
}

/* Anyone who has asked their OS for less motion gets none of it. */
@media (prefers-reduced-motion: reduce) {
  .cwn-anim .cwn-reveal,
  .cwn-anim .cwn-reveal.cwn-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  body .btn:active { transform: none; }
  body .cards-block .cards-card:hover .cards-card-link { letter-spacing: 1.5px; }
}

/* --------------------------------------------------------------------------
   13. Responsive — only the theme's two breakpoints
   -------------------------------------------------------------------------- */

@media (max-width: 1199px) {
  body .hero-block .hero-heading           { font-size: 44px; }
  body .herosplit-block .herosplit-heading { font-size: 40px; }
  body .cards-block .cards-heading,
  body .testimonials-block .testimonials-heading,
  body .featureslist-block .featureslist-heading,
  body .textimage-block .textimage-heading { font-size: 32px; }
  body .cta-block .cta-heading             { font-size: 28px; }

  body .hero-block.is-dark-band .hero-content { padding-top: 72px; padding-bottom: 72px; }
  body .cta-block .cta-content { padding-top: 64px; padding-bottom: 64px; }
  body .testimonials-block .testimonials-card { padding: 34px; }
}

@media (max-width: 767px) {
  body .hero-block .hero-heading           { font-size: 34px; }
  body .herosplit-block .herosplit-heading { font-size: 32px; }
  body .cards-block .cards-heading,
  body .testimonials-block .testimonials-heading,
  body .featureslist-block .featureslist-heading,
  body .textimage-block .textimage-heading { font-size: 26px; }
  body .cta-block .cta-heading             { font-size: 23px; }

  body .hero-block .hero-subheading,
  body .herosplit-block .herosplit-content,
  body .cta-block .cta-text { font-size: 16px; }

  body .cs-topper,
  body .hero-block .hero-eyebrow,
  body .cta-block .cta-eyebrow { font-size: 12px; margin-bottom: 14px; }

  body .hero-block.is-dark-band .hero-content { padding-top: 56px; padding-bottom: 56px; }
  body .cta-block .cta-content { padding-top: 52px; padding-bottom: 52px; }

  body .cards-block .cards-card { padding: 26px 22px; }
  body .testimonials-block .testimonials-card { padding: 26px 22px; }
  body .herosplit-block { padding-top: 28px; }

  /* Now that min-width is off, buttons stack full-width cleanly. */
  body .herosplit-block .herosplit-buttons .btn,
  body .hero-block .hero-buttons .btn { width: 100%; text-align: center; }

  /* On the Resources page these four links are the only way into the tools from
     a phone, and they measured 23px tall — under the 24px WCAG 2.5.8 minimum and
     a long way under a comfortable thumb.

     The height is bought with padding-top rather than a min-height so the
     underline stays tight against the text: the border-bottom follows the box,
     so growing the box downward would detach the rule from the words. Margin
     drops by the same amount padding gains, which leaves the link exactly where
     it was and only enlarges what is tappable. */
  body .cards-block .cards-card-link {
    margin-top: 1px;
    padding-top: 21px;
    padding-bottom: 3px;
  }
}

/* --------------------------------------------------------------------------
   14. Single posts and archives

   Nothing here existed until Nicollete published her first three newsletters —
   before that the site was five static pages and no post ever rendered.

   The parent's single template paints its hero band with var(--primary), which
   puts a bright teal slab at the top of every article while every other hero on
   the site is the deep --secondary. And it never styles body links at all, so
   the two links inside her first post rendered in the browser default #0000EE
   with a purple visited state — on a page where the only other blue is her
   brand teal.
   -------------------------------------------------------------------------- */

body .single-hero {
  background: var(--secondary);
  padding-top: 76px;
  padding-bottom: 76px;
}

body .single-hero .single-title,
body .single-hero h1 {
  color: #fff;
  font-size: 44px;
  line-height: 1.1;
  max-width: 22ch;
  margin: 0;
}

/* The category badge is an outline chip rather than a filled one — a solid teal
   pill on the dark band would be the only rounded, saturated object on the site. */
body .single-hero .single-cat {
  display: inline-block;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 0;
  padding: 6px 12px;
  margin-bottom: 20px;
  font-family: var(--font1);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: #fff;
}

/* The title's own margin is zeroed above so the wrap stays tight; the gap to the
   byline has to come from here or the date sits on the title's descenders. */
body .single-hero .single-meta {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

body .single-hero .single-meta,
body .single-hero .single-date,
body .single-hero .single-author {
  color: rgba(255, 255, 255, 0.82);
  font-size: 14px;
  font-weight: 300;
}

/* --- Article body ------------------------------------------------------- */

/* A measure, not a container width. Long-form prose set to the full 1140px
   container runs to ~130 characters a line, which is roughly double what anyone
   can track back to the start of.

   The side padding is NOT optional and must stay as longhands. The parent's
   single template prints an inline <style> in the body —

       .single-content-classic { padding: var(--section-padding-top) 0 var(--section-padding-bottom) }

   — and that SHORTHAND resets left and right to 0, wiping the 30px this element
   would otherwise inherit from reflex.css's `.container`. Declaring only
   padding-top/padding-bottom here (as this rule originally did) leaves the
   zeros standing. On desktop the max-width hides it, because the measure is
   narrower than the viewport and the text never reaches an edge. On a phone the
   max-width does not bind, so every paragraph ran flush to both edges of the
   screen with no gutter at all. Found on the live site 2026-08-05.

   30px matches `.container` everywhere else on the page — the post hero and the
   footer both use it — so the body copy lines up exactly under the post title
   instead of sitting proud of it.

   max-width is 780px, not 720px: box-sizing is border-box (reflex sets it on
   .container), so the cap has to carry the 60px of gutter for the text column
   itself to stay at the 720px measure this rule exists to create. Verified at
   1440 — text width is exactly 720. */
body .single-content {
  max-width: 780px;
  padding-top: 64px;
  padding-bottom: 64px;
  padding-left: 30px;
  padding-right: 30px;
}

body .single-content p,
body .single-content li {
  font-size: 17px;
  line-height: 1.75;
  font-weight: 300;
  color: var(--body-text);
}

body .single-content p { margin: 0 0 1.3em; }

body .single-content h2 {
  font-size: 30px;
  line-height: 1.2;
  margin: 2em 0 0.6em;
}

body .single-content h3 {
  font-size: 22px;
  line-height: 1.3;
  margin: 1.8em 0 0.5em;
}

body .single-content ul,
body .single-content ol { margin: 0 0 1.3em; padding-left: 1.2em; }
body .single-content li { margin-bottom: 0.5em; }

body .single-content strong { font-weight: 700; color: var(--ink); }

/* The whole reason this section exists. Teal, underlined, thickening on hover —
   the link has to be identifiable without relying on colour alone, which an
   underline handles. */
body .single-content a {
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color 0.2s ease, text-decoration-thickness 0.2s ease;
}

body .single-content a:visited { color: var(--primary); }

body .single-content a:hover {
  color: var(--primary-active);
  text-decoration-thickness: 2px;
}

body .single-content blockquote {
  margin: 1.8em 0;
  padding: 4px 0 4px 24px;
  border-left: 3px solid var(--primary);
  font-size: 19px;
  line-height: 1.65;
  font-weight: 300;
  color: var(--ink);
}

/* --- Post navigation ---------------------------------------------------- */

body .single-nav,
body .single-post-nav {
  max-width: 720px;
  border-top: 1px solid var(--hairline);
  padding-top: 28px;
}

/* The parent sets this at #999, which is 2.85:1 on white. Same uppercase label
   token used everywhere else on the site, in --muted at 5.33:1. */
body .single-post-nav .single-nav-label {
  font-family: var(--font1);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--muted);
}

body .single-nav a,
body .single-post-nav a {
  color: var(--ink);
  text-decoration: none;
  font-weight: 700;
}

body .single-nav a:hover,
body .single-post-nav a:hover { color: var(--primary); }

@media (max-width: 1199px) {
  body .single-hero { padding-top: 60px; padding-bottom: 60px; }
  body .single-hero .single-title,
  body .single-hero h1 { font-size: 36px; }
  body .single-content { padding-top: 48px; padding-bottom: 48px; }
  body .single-content h2 { font-size: 26px; }
}

@media (max-width: 767px) {
  body .single-hero { padding-top: 44px; padding-bottom: 44px; }
  body .single-hero .single-title,
  body .single-hero h1 { font-size: 28px; }
  body .single-content { padding-top: 36px; padding-bottom: 36px; }
  body .single-content p,
  body .single-content li { font-size: 16px; }
  body .single-content h2 { font-size: 23px; }
  body .single-content h3 { font-size: 19px; }
  body .single-content blockquote { font-size: 17px; padding-left: 18px; }
}

/* --------------------------------------------------------------------------
   15. Atmosphere — depth, background field, and scroll motion

   The homepage read as correct but flat: four near-white bands in a row
   (#fff, #F7F9FA, #fff, #F2FAFB), hairline-edged white plates on top of them,
   and the brand's rose doing no work anywhere outside the photographs.
   Everything below adds depth WITHOUT breaking the five rules at the top of
   this file — still zero radius, still no shadows, still light/dark
   alternation. Depth comes from colour-block contrast, a hairline field, and
   motion.

   Three devices, used consistently so this reads as a system rather than as
   decoration:

     1. THE FIELD — a vertical hairline grid at 5.5% teal on a 96px rhythm,
        masked so it is strongest in dead space and gone behind body copy. It
        appears on the hero's white side, the cards band, and the testimonials
        band. It is the engineering-paper texture that sits under the BMW kit's
        light sections.
     2. THE PLATE — the hero photo stops being a floating rectangle and becomes
        a full-bleed colour block running the band's full height to the right
        viewport edge, on a --secondary ground, with a 4px teal seam where it
        meets the type. This is the single largest change on the page.
     3. THE KEYLINE — a 1px rule offset behind an element (rose, behind the
        About portrait). It is how this system gets a second plane without a
        drop shadow, which it does not allow.

   Motion is all transform / clip-path / background-size. Nothing here animates
   a layout property, so none of it can reflow. Everything that HIDES content
   stays scoped to `.cwn-anim`, which only exists once reveal.js has run, so a
   failed script load still cannot blank a section. See the note at §12.
   -------------------------------------------------------------------------- */

:root {
  /* The field. A token because three bands share it and the alpha is the one
     value worth tuning in a single place. */
  --cwn-field: repeating-linear-gradient(
    to right,
    rgba(14, 124, 138, 0.055) 0 1px,
    transparent 1px 96px
  );

  /* Distance from the grid's right content edge to the viewport edge: half the
     leftover width outside the 1400px container, plus the container's own 40px
     padding. 100vw includes the scrollbar, so this overshoots by the scrollbar
     width — deliberately. The hero clips, so overshoot guarantees a true bleed
     while an undershoot would leave a visible sliver of white down the edge. */
  --cwn-bleed: calc((100vw - min(100vw, 1400px)) / 2 + 40px);
}

/* ── 15.1 The hero plate ───────────────────────────────────────────────────
   Scoped to :not(.herosplit-image-left) throughout. The image-left variant
   flips the grid with `direction: rtl`, which would send the bleed the wrong
   way. It is unused on this site and stays on its original styling. */

body .herosplit-block {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  overflow: clip;
}

/* The field, fading in downward: absent behind the headline, present in the
   empty lower half of the band. The plate paints over its own half at z-index 1. */
body .herosplit-block::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--cwn-field);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 46%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 46%, #000 100%);
}

body .herosplit-block .herosplit-grid {
  position: relative;
  z-index: 1;
}

/* Full band height for the plate. The parent centres the grid inside a flex
   column; stretching it is what lets the image column run edge to edge
   vertically. The type keeps its own centring via align-self. */
/* No min-height here — the parent's 85vh (and its `auto` below 1199px) already
   governs the band, and restating it would be a second number to keep in step.
   The padding goes to 0 because the plate has to reach both band edges; the
   type column carries its own padding instead, below. */
body .herosplit-block:not(.herosplit-image-left) {
  align-items: stretch;
  padding-top: 0;
  padding-bottom: 0;
}

body .herosplit-block:not(.herosplit-image-left) .herosplit-grid {
  flex: 1 1 100%;
  align-items: stretch;
  gap: 0;
}

/* Padding replaces the grid gap. A gap would hold the plate 80px off the right
   edge and the bleed would have to compensate for it. */
body .herosplit-block:not(.herosplit-image-left) .herosplit-text {
  align-self: center;
  padding: 92px 76px 92px 0;
}

body .herosplit-block:not(.herosplit-image-left) .herosplit-image {
  position: relative;
  margin-right: calc(-1 * var(--cwn-bleed));
  background-color: var(--secondary);
  overflow: hidden;
  min-height: 520px;
}

/* 110% tall at a -5% offset leaves 5% of vertical slack in each direction,
   which is the room the parallax in reveal.js drifts within. Without the slack
   the drift would expose the plate's ground at one edge. */
body .herosplit-block:not(.herosplit-image-left) .herosplit-image img {
  position: absolute;
  left: 0;
  top: -5%;
  width: 100%;
  height: 110%;
  max-height: none;
  object-fit: cover;
  object-position: center 20%;
  border-radius: 0;
  transform: translate3d(0, var(--cwn-shift, 0px), 0);
}

/* The seam. A 4px teal rule where the plate meets the type is the whole reason
   the two halves read as one composition rather than as text beside a photo. */
body .herosplit-block:not(.herosplit-image-left) .herosplit-image::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--primary);
  z-index: 2;
}

/* A short teal leader rule before the tagline, echoing the seam. */
body .herosplit-block .herosplit-text .cs-topper::before {
  content: '';
  display: inline-block;
  width: 34px;
  height: 2px;
  background: var(--primary);
  vertical-align: middle;
  margin-right: 14px;
  position: relative;
  top: -1px;
}

/* ── 15.2 The field on the light bands ─────────────────────────────────────
   Masked to nothing at both ends so it never reaches the band edge, where it
   would read as a seam between sections rather than as texture. */

body .cards-block,
body .testimonials-block {
  position: relative;
  isolation: isolate;
}

body .cards-block::before,
body .testimonials-block::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--cwn-field);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
}

/* `> .container`, NOT `> *`. Every DevQ block wraps its content in a single
   .container, but several also carry absolutely positioned decorative siblings
   at that same level — the Hero block's `.hero-background`, which paints the
   band's colour, is one. A blanket `> *` sets those to position: relative,
   which drops them out of absolute positioning and collapses them to nothing.
   That is exactly what happened here: the About page's dark hero lost its
   #14303A ground and printed white-on-white until this was narrowed. */
body .cards-block > .container,
body .testimonials-block > .container {
  position: relative;
  z-index: 1;
}

/* ── 15.3 Section heading rules ────────────────────────────────────────────
   Every section heading on the site was a bare line of type on a bare band. A
   56px teal rule under it gives the band a fixed anchor point, and doubles as
   the motion hook in 15.7 — it draws itself when the header reveals. */

body .cards-block .cards-heading::after,
body .testimonials-block .testimonials-heading::after,
body .featureslist-block .featureslist-heading::after,
body .textimage-block .textimage-heading::after {
  content: '';
  display: block;
  width: 56px;
  height: 3px;
  background: var(--primary);
  margin-top: 24px;
}

/* Cards / testimonials / features headers are centred by the parent, so their
   rules centre with them. Text Image is left-aligned and stays left. */
body .cards-block .cards-heading::after,
body .testimonials-block .testimonials-heading::after,
body .featureslist-block .featureslist-heading::after {
  margin-left: auto;
  margin-right: auto;
}

/* ── 15.4 Cards — the hover wipe ───────────────────────────────────────────
   §7 moves the border colour on hover, which is the correct gesture but almost
   invisible coming off #E6E6E6. A 3px teal rule wiping across the top edge is
   the same gesture at a legible amplitude, still with no lift and no shadow.

   Absolutely positioned on purpose: .cards-card is a flex column, and an
   in-flow pseudo-element would become a flex item and displace the icon. */
body .cards-block .cards-card { position: relative; }

body .cards-block .cards-card::after {
  content: '';
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  height: 3px;
  background: var(--primary);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1);
}

body .cards-block .cards-card:hover::after,
body .cards-block .cards-card:focus-within::after { transform: scaleX(1); }

/* ── 15.5 The rose keyline ─────────────────────────────────────────────────
   The one place the brand's third colour does any work outside the
   photographs. Rose is 3.1:1 on white and must never carry text (see the note
   in 04-pages.php); a 1px decorative rule is exactly what it is for.

   Offset behind the portrait and peeking out bottom-left, it puts a second
   plane on the page — which is what a drop shadow would do in a system that
   allows them, and this one does not. */
body .textimage-block .textimage-image { position: relative; }

body .textimage-block .textimage-image::before {
  content: '';
  position: absolute;
  z-index: 0;
  left: -18px;
  top: 30px;
  right: 30px;
  bottom: -18px;
  border: 1px solid rgba(199, 123, 160, 0.6);
  pointer-events: none;
}

body .textimage-block .textimage-image img {
  position: relative;
  z-index: 1;
}

/* ── 15.6 Dark bands — glow and hatch ──────────────────────────────────────
   A flat #14303A slab is the flattest surface on the site. Two very low-alpha
   radials (teal from the top right, rose from the bottom left) plus a fine
   diagonal hatch give it a light source and a surface, with no shadow.

   CONTRAST — the teal radial is capped at 0.18 for a measured reason. It lifts
   the band from #14303A to rgb(20,68,80) at its brightest point, and the
   lowest-contrast text on these bands is the eyebrow at rgba(255,255,255,0.6),
   which lands at 4.94:1 there. At 0.24 — where this was first written, and
   where the difference is barely visible — the same eyebrow measures 4.61:1.
   Both pass AA, but 4.61 leaves no headroom for a future eyebrow colour tweak
   or a darker rendering intent. Heading (#fff) is 9.7:1 and body text
   (rgba(255,255,255,0.82)) is 7.1:1 at the same point, so neither is at risk;
   the eyebrow is the number that governs this value. */

body .cta-block,
body .hero-block.is-dark-band {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  overflow: clip;
}

/* inset: -8% gives the drift in 15.7 room to move without exposing an edge. */
body .cta-block::before,
body .hero-block.is-dark-band::before {
  content: '';
  position: absolute;
  inset: -8%;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(115% 95% at 78% 4%, rgba(18, 160, 178, 0.18), transparent 62%),
    radial-gradient(95% 85% at 10% 100%, rgba(199, 123, 160, 0.14), transparent 60%),
    repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.03) 0 1px, transparent 1px 11px);
}

/* The Hero block is deliberately absent from this rule. Its parent CSS already
   stacks the band for us — `.hero-background` at z-index 1, `.container` at
   z-index 3 — so the content needs no lift, and touching `.hero-background`
   destroys the band (see the note at 15.2). The glow slots between the two at
   z-index 2, which is where `.hero-overlay` would sit on an image hero. */
body .cta-block > .container {
  position: relative;
  z-index: 1;
}

body .hero-block.is-dark-band::before { z-index: 2; }

/* A teal leader rule on the dark eyebrow, matching the hero's. --primary-light
   rather than --primary: the darker teal disappears against #14303A. */
body .cta-block .cta-eyebrow::before {
  content: '';
  display: inline-block;
  width: 28px;
  height: 2px;
  background: var(--primary-light);
  vertical-align: middle;
  margin-right: 12px;
  position: relative;
  top: -1px;
}

/* ── 15.7 Motion ───────────────────────────────────────────────────────────
   Rules that hide something stay under .cwn-anim. Rules that only run on hover,
   or that animate an already-visible decorative layer, are unconditional. */

/* The hero plate wipes open from the seam outward instead of sliding in as a
   rectangle — it now bleeds off the viewport, so a translate would drag a strip
   of white in behind it. clip-path has no such edge. Overrides §12. */
.cwn-anim .herosplit-block:not(.herosplit-image-left) .herosplit-image.cwn-reveal {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0 100%);
  transition: clip-path 0.9s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.cwn-anim .herosplit-block:not(.herosplit-image-left) .herosplit-image.cwn-in {
  clip-path: inset(0 0 0 0);
}

/* A longer rise for the hero type only. The rest of the page keeps §12's 18px —
   the hero is the one place a bigger gesture is not noise. */
.cwn-anim .herosplit-text > .cwn-reveal { transform: translateY(26px); }
.cwn-anim .herosplit-text > .cwn-reveal.cwn-in { transform: none; }
.cwn-anim .herosplit-text > .cwn-reveal:nth-child(4) { transition-delay: 260ms; }

/* The heading rules draw themselves. Scoped to .cwn-anim because scaleX(0)
   hides them; without the script they are simply already drawn. */
.cwn-anim .cards-block .cards-heading::after,
.cwn-anim .testimonials-block .testimonials-heading::after,
.cwn-anim .featureslist-block .featureslist-heading::after,
.cwn-anim .textimage-block .textimage-heading::after {
  transform: scaleX(0);
  transition: transform 0.65s cubic-bezier(0.22, 0.61, 0.36, 1) 0.2s;
}

.cwn-anim .cards-block .cards-heading.cwn-in::after,
.cwn-anim .testimonials-block .testimonials-heading.cwn-in::after,
.cwn-anim .featureslist-block .featureslist-heading.cwn-in::after,
.cwn-anim .textimage-block .textimage-heading.cwn-in::after {
  transform: scaleX(1);
}

.cwn-anim .cards-block .cards-heading::after,
.cwn-anim .testimonials-block .testimonials-heading::after,
.cwn-anim .featureslist-block .featureslist-heading::after { transform-origin: center; }
.cwn-anim .textimage-block .textimage-heading::after { transform-origin: left center; }

/* The dark-band light source breathes. 28s is slow enough that it reads as
   depth rather than as an animation — you notice it only if you stop and look,
   which is the intent. Decorative layer only, so it needs no .cwn-anim guard. */
@keyframes cwn-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-2.5%, 1.8%, 0); }
}

body .cta-block::before,
body .hero-block.is-dark-band::before {
  animation: cwn-drift 28s ease-in-out infinite alternate;
}

/* Buttons fill from the left rather than swapping colour in one step.
   background-size is animatable and needs no extra element, which matters
   because .btn is an <a> with no inner wrapper to slide.

   §2's :hover uses the `background` SHORTHAND, which resets background-image
   and background-size — so these hover rules restate background-color
   explicitly, and this section must stay after §2 in this file. */
body .btn:not(.btn-outline):not(.btn-white):not(.btn-outline-white) {
  background-image: linear-gradient(var(--primary-active), var(--primary-active));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 100%;
  transition: background-size 0.34s cubic-bezier(0.22, 0.61, 0.36, 1),
              color 0.22s ease, border-color 0.22s ease, transform 0.22s ease;
}

body .btn:not(.btn-outline):not(.btn-white):not(.btn-outline-white):hover {
  background-color: var(--primary);
  background-size: 100% 100%;
}

body .btn-outline {
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 100%;
  transition: background-size 0.34s cubic-bezier(0.22, 0.61, 0.36, 1),
              color 0.22s ease, border-color 0.22s ease, transform 0.22s ease;
}

body .btn-outline:hover {
  background-color: transparent;
  background-size: 100% 100%;
  border-color: var(--ink);
  color: #fff;
}

/* ── 15.8 Testimonials — the lone card ─────────────────────────────────────
   One testimonial in a three-column grid reads as two missing testimonials. A
   teal edge makes it read as a pull quote instead, which is what a single one
   actually is. */
body .testimonials-block .testimonials-card {
  border-left: 3px solid var(--primary);
}

/* ── 15.9 Reduced motion ───────────────────────────────────────────────────
   §12 already flattens the reveal. This covers everything added above: the
   plate wipe, the heading rules, the drift, the hover wipes, the parallax.
   The parallax custom property is reset here as well as guarded in JS, so the
   image sits exactly where it is composed even if the script has already run. */
@media (prefers-reduced-motion: reduce) {
  .cwn-anim .herosplit-block .herosplit-image.cwn-reveal,
  .cwn-anim .herosplit-block .herosplit-image.cwn-in {
    clip-path: none !important;
    transition: none !important;
  }

  .cwn-anim .cards-block .cards-heading::after,
  .cwn-anim .testimonials-block .testimonials-heading::after,
  .cwn-anim .featureslist-block .featureslist-heading::after,
  .cwn-anim .textimage-block .textimage-heading::after {
    transform: none !important;
    transition: none !important;
  }

  body .cta-block::before,
  body .hero-block.is-dark-band::before { animation: none !important; }

  body .cards-block .cards-card::after { transition: none !important; }

  body .btn,
  body .btn-outline { transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; }

  body .herosplit-block .herosplit-image img { --cwn-shift: 0px !important; }
}

/* ── 15.10 Responsive ──────────────────────────────────────────────────────
   The bleed has to track the parent's container padding, which steps
   40 -> 30 -> 15 at the theme's two breakpoints. */

@media (max-width: 1199px) {
  :root { --cwn-bleed: calc((100vw - min(100vw, 1400px)) / 2 + 30px); }

  body .herosplit-block:not(.herosplit-image-left) .herosplit-text { padding: 64px 40px 64px 0; }
  body .herosplit-block:not(.herosplit-image-left) .herosplit-image { min-height: 440px; }

  body .textimage-block .textimage-image::before { left: -12px; top: 22px; right: 22px; bottom: -12px; }
}

@media (max-width: 767px) {
  /* Single column below 767: the image stacks above the type at full width, so
     it bleeds BOTH ways and the vertical rhythm goes back to padding. */
  body .herosplit-block:not(.herosplit-image-left) .herosplit-text {
    padding: 40px 0 48px 0;
  }

  body .herosplit-block:not(.herosplit-image-left) .herosplit-image {
    margin-right: -15px;
    margin-left: -15px;
    min-height: 380px;
  }

  /* The seam rule is a vertical edge between two columns. With one column there
     is no seam, so it moves to the bottom edge where the plate meets the type. */
  body .herosplit-block:not(.herosplit-image-left) .herosplit-image::after {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: auto;
    height: 4px;
  }

  body .herosplit-block .herosplit-text .cs-topper::before { width: 24px; margin-right: 10px; }

  /* At one column the portrait is only ~340px wide, and an 18px keyline offset
     is a visible chunk of it. Tighten. */
  body .textimage-block .textimage-image::before { left: -10px; top: 18px; right: 18px; bottom: -10px; }

  /* The field's 96px rhythm gives only three lines on a phone, which reads as
     stripes rather than as texture. Halve the spacing. */
  body .herosplit-block::before,
  body .cards-block::before,
  body .testimonials-block::before {
    background-image: repeating-linear-gradient(
      to right,
      rgba(14, 124, 138, 0.05) 0 1px,
      transparent 1px 54px
    );
  }
}
