/* ============================================================================
   GENERAL CLIMATE - DESIGN CONTRACT
   Direction C "Service Record" (SITE-PLAN 5). Owner: stream S1.

   The site should read like the report of a careful tradesman: what he found,
   what he did, what can wait, with photographs and readings. Lines, not cards.
   Paper, not gradients. The grotesque is the company talking; the serif italic
   is the customer talking, and nothing else ever uses it.

   ----------------------------------------------------------------------------
   HOW TO SPEND THIS FILE

   Every value here is a token. Colour, type size, gap, radius and duration come
   from inc/design-tokens.php (--cx-*) and inc/design-brand.php (--gc-*). If you
   are typing a hex code or a raw px into a block stylesheet, the token for it is
   missing: raise a task to S1 instead of inventing the value locally. Same rule
   for classes - a block that needs a new global class raises a task to S1, it
   does not add one.

   Block stylesheets live in blockstudio/<block>/style.css, use .gc-<block>__*
   names of their own, and may compose the global classes below. Nothing outside
   this file writes a .gc-hdr, .gc-ftr, .gc-sticky or .gc-404 rule.

   ----------------------------------------------------------------------------
   1. CLASS CATALOG (global, available to every block and template)

   LAYOUT
   .gc-section              one band of the page. padding-block: --gc-section.
     .gc-section--surface     galvanized ground (--cx-surface), for rows and forms
     .gc-section--ink         navy ground, paper text (footer, closing CTA)
     .gc-section--flush-top    no top padding (band sits against the one above)
     .gc-section--flush-bottom no bottom padding
     .gc-section--tight        half the vertical rhythm
   .gc-wrap                 centred column, max-width --cx-maxw, page gutters
     .gc-wrap--narrow         reading measure, 68ch (prose, legal, FAQ)
     .gc-wrap--mid            48ch-ish intro column inside a wide band
   .gc-stack                vertical flow, gap --gc-stack-gap (default --gc-5)
   .gc-cluster              horizontal wrap (button rows, meta lines), gap --gc-4
   .gc-cols                 responsive columns, min column width --gc-col-min

   TYPE
   .gc-head                 section header block
     .gc-head__kicker         one short line above the title. NOT on every section
     .gc-head__title          h2 of the band
     .gc-head__lead           one paragraph under the title
   .gc-label                small record label, caps, tabular. "WHAT WE FOUND"
   .gc-num                  tabular numerals (readings, dates, counts, phone)
   .gc-note                 small muted paragraph (captions, disclaimers)
   .gc-quote                the customer's voice: Newsreader italic. Only here
     .gc-quote__text          the quotation
     .gc-quote__who           the byline row
     .gc-quote__meta          name, job and date, comma separated. The link to
                              Google is a sibling, not the last item of it: it
                              carries a 44px target and wraps to its own line
   .gc-sr                   visually hidden, still read by a screen reader
   .gc-nowrap               one value that must not break across a line:
                            "5-ton", "24,000 BTU", "R-410A". Added at render
                            time by gc_nobreak() (inc/design-brand.php)

   PIECES
   .gc-btn                  action. min 44px tall, 48px in the sticky bar
     .gc-btn--primary         service blue fill, paper text
     .gc-btn--ghost           1px outline, ink text
     .gc-btn--on-photo        for use inside .gc-onphoto
     .gc-btn--block           full width of its container
   .gc-link                 text link, underlined on hover, ink or accent
     .gc-link--out            adds the arrow-up-right icon (leaves the site)
     .gc-link--tap            standing on its own rather than inside a sentence:
                              takes the full 44px hit area
   .gc-rows / .gc-row       the record list: rows separated by 1px rules
     .gc-row--media           the row carries a .gc-row__thumb of its own work
     .gc-row__thumb           66px photograph, decorative (the title says it)
     .gc-row__main            left side, the thing itself
     .gc-row__meta            right side, town / month / count, tabular
     .gc-row__month           the month, in a slot that holds when it is empty
   .gc-rule                 a single 1px hairline
   .gc-find                 a finding: 3px gauge-yellow bar on the left, square
                            corners. Yellow appears ONLY on a real finding or a
                            real reading (SITE-PLAN 5.2). Never as decoration
   .gc-photo                figure. .gc-photo__img, .gc-photo__cap
     .gc-photo--illustrative  an AI-generated picture, never evidence
                              (DECISIONS 2026-09-17). Printed only together
                              with .gc-photo__cap--illustrative, the small
                              muted "Illustrative example" line right under it
   .gc-onphoto              any container that paints text over a photograph:
                            rebinds the foreground pair to the on-photo pair and
                            pins the accent to white. .gc-onphoto__media holds
                            the image or video, .gc-onphoto__scrim is the
                            gradient that guarantees the contrast, and
                            .gc-onphoto__scrim--side adds a second pass down the
                            side the text column sits on (the hero),
                            .gc-onphoto__scrim--deep takes the whole image to a
                            night exposure when body copy crosses it
   .gc-filters              a row of filter links. Current one carries
                            aria-current="page"
   .gc-icon                 emitted by gc_icon(); sized in em, colour inherited

   CHROME (S1 only, listed so nobody re-implements it)
   .gc-skip .gc-hdr .gc-mob .gc-ftr .gc-sticky .gc-404

   ----------------------------------------------------------------------------
   2. SPACING SCALE

   One scale, derived from --cx-unit (8px at density "normal"). Blocks use these
   names, never a raw px:

     --gc-1  4px    hairline gaps, icon to label
     --gc-2  8px    inside a row
     --gc-3  12px
     --gc-4  16px   default gap in a cluster
     --gc-5  24px   default gap in a stack
     --gc-6  32px   between a heading and its body
     --gc-7  48px   between groups inside a band
     --gc-8  64px   between bands that belong together
     --gc-9  96px
     --gc-section   the band rhythm itself: clamp(64px, 7.5vw, 112px)
     --gc-gutter    page gutter: clamp(20px, 4.5vw, 64px)

   Deliberate deviation from the DESIGN-RULES marketing-surface standard
   (160-240px bands, large motion): direction C is a report, and that rhythm
   would break it. Recorded in DEBT.md as an accepted deviation, not as debt.

   ----------------------------------------------------------------------------
   3. CONTRAST, MEASURED

   Every pair the tokens produce, computed from the token values themselves
   (WCAG 2.1 relative luminance). All pass AA; the two marked "large" are only
   ever set at display sizes and clear the 3.0 bar by a wide margin anyway.

     ink on bg            #141C2B on #FAFAF8   16.33:1   body, headings
     muted on bg          #5C6670 on #FAFAF8    5.60:1   labels, captions
     accent on bg         #1462D1 on #FAFAF8    5.43:1   links, rules
     ink on surface       #141C2B on #E8EBED   14.25:1   rows, form
     muted on surface     #5C6670 on #E8EBED    4.89:1   secondary in a row
     accent on surface    #1462D1 on #E8EBED    4.74:1   links on galvanized
     bg on accent         #FAFAF8 on #1462D1    5.43:1   primary button label
     ink on accent2       #141C2B on #F5C518   10.47:1   the only legal use of
                                                         the gauge yellow: navy
                                                         text or a bar under it
     bg on ink            #FAFAF8 on #141C2B   16.33:1   footer, ink bands
     muted on ink         #BABCBF on #141C2B    8.96:1   footer secondary
     accent2 on ink       #F5C518 on #141C2B   10.47:1   footer accent
     logo blue on ink     #298BFF on #141C2B    5.06:1   mark on navy
     logo sky on ink      #45ABFF on #141C2B    6.92:1   mark on navy
     logo blue on bg      #298BFF on #FAFAF8    3.22:1   GRAPHIC ONLY, never text

   Over a photograph the bed is the image, so those pairs are not computed, they
   are measured: the text is made transparent, the page is captured, and the
   luminance of every pixel under every text run is compared with the text
   colour. The numbers below are the WORST single pixel, not an average, on the
   reference pages in prototype/ at 390, 768 and 1440:

                             390      768     1440    needs
     hero H1, white          6.33     6.29     4.52    3.0 (display size)
     hero lead, white        6.44     7.02    10.15    4.5
     hero proof line         8.81     6.31    11.38    4.5
     latest-job strip       12.69    11.90    12.34    4.5
     page-hero H1            8.31     7.94     8.02    3.0
     page-hero lead          8.24     7.86     8.46    4.5
     quote text, deep scrim     -        -     6.65    4.5
     quote byline, muted        -        -     5.22    4.5

   Two findings worth keeping:

     - .gc-onphoto re-mixes --cx-on-photo-muted, because the token engine's own
       value measured 4.47:1 on the deep scrim, a third of a point short of AA;
     - below 48rem the text column is the whole frame, so the side scrim stops
       being a gradient and becomes coverage. Without that the hero lead came
       out at 4.35:1 against a bright patch of brick.

   ----------------------------------------------------------------------------
   4. REVEAL CONVENTION

     [data-reveal]             the element itself fades up once, on entry
     [data-reveal="stagger"]   its direct children do, 60ms apart

   NEVER put data-reveal on an H1, on the LCP image, on anything in the first
   viewport, or on every section in a row. On this site it is worth about one
   element per page: the latest-job strip under the hero, a job record entering
   the list. Rules:

     - hidden only under html.js, so with JavaScript off nothing is invisible;
     - prefers-reduced-motion switches it off completely;
     - assets/editor.css kills it inside the editor, so blocks are visible there;
     - site.js reveals anything already in view at load, and has a 1.2s safety
       net in case IntersectionObserver never fires.

   ----------------------------------------------------------------------------
   5. RULES OTHER STREAMS HAVE TO KNOW

   TAP TARGETS   Every control is at least 44px tall (--gc-tap) and at least
                 48px in the sticky bar: buttons, rows, navigation, filters,
                 form fields. Checked at 390px width with JavaScript off.
                 A link inside a sentence keeps the line height it has, which is
                 what WCAG 2.2 exempts; a link standing on its own line is not a
                 sentence and carries .gc-link--tap.
                 Form controls get the floor from the base rules in section 0,
                 as min-height, so a block never has to remember it. Setting an
                 explicit height in a block stylesheet defeats that - use
                 padding, or min-height, and let the floor hold.

   LIGHT-TOP     body.light-top means the page does not open on a photograph.
                 The chrome does not branch on it - the header is paper with a
                 1px rule on every page, which is why nothing needs a scroll
                 state and the mobile menu needs no JavaScript. It only adds top
                 space to the first band, so a page that opens on text does not
                 start hard against the header. A CPT template that opens on a
                 photograph does not need to do anything about it.

   THE FORM HOOK The lead form's outermost element carries [data-gc-form]. The
                 sticky call bar watches exactly that attribute and hides itself
                 while the form is on screen. One attribute, any number of forms.

   ICONS         gc_icon( name, [ 'size' => '1.1em', 'class' => '', 'label' => '' ] ).
                 Names: phone, chat-text, camera, map-pin, caret-down,
                 arrow-up-right, check, x, list, star-fill. Nothing else exists,
                 and no emoji, unicode arrow, star or cross entity is ever
                 a substitute. An icon with no label is decorative and hidden from
                 assistive tech; pass 'label' when the icon IS the control.

   PHOTOGRAPHY   Always through gc_pic( $id, $sizes, $opts ). Real photographs
                 only. The wrapper that paints text on one is .gc-onphoto.

   NO THIRD PARTY. No font, script, icon or frame is fetched from another
   origin. Zero requests to fonts.googleapis.com, fonts.gstatic.com,
   api.iconify.design on every template.
   ============================================================================ */

/* ---------------------------------------------------------------- *
   0. Reset and scale
   ---------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; padding: 0; }
ul, ol { list-style: none; }

:root {
  /* Spacing scale (section 2 of the contract). */
  --gc-1: calc(var(--cx-unit) * 0.5);
  --gc-2: var(--cx-unit);
  --gc-3: calc(var(--cx-unit) * 1.5);
  --gc-4: calc(var(--cx-unit) * 2);
  --gc-5: calc(var(--cx-unit) * 3);
  --gc-6: calc(var(--cx-unit) * 4);
  --gc-7: calc(var(--cx-unit) * 6);
  --gc-8: calc(var(--cx-unit) * 8);
  --gc-9: calc(var(--cx-unit) * 12);
  --gc-section: var(--cx-section-y);
  --gc-gutter: var(--cx-gutter);
  --gc-hdr-h: calc(var(--gc-9) * 0.72 + var(--gc-2) * 2);

  /* Defaults for the layout primitives; a block may re-bind them locally. */
  --gc-stack-gap: var(--gc-5);
  --gc-col-min: 16rem;

  /* Minimum tap target. */
  --gc-tap: 44px;

  /* Hero type, SITE-PLAN 5.2: three lines maximum at 390px. */
  --gc-fs-hero: clamp(2.75rem, 7vw, 7rem);
  --gc-lh-hero: 0.95;

  /* Hairline. One border width for the whole site. */
  --gc-hair: var(--cx-border-w) solid var(--cx-border);
  --gc-hair-strong: var(--cx-border-w) solid var(--cx-on-bg);
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The sticky header must not cover the target of an in-page anchor. */
  scroll-padding-top: calc(var(--gc-9) + var(--gc-2));
}

body {
  background: var(--cx-bg);
  color: var(--cx-on-bg);
  font-family: var(--cx-font-body);
  font-size: var(--cx-fs-base);
  font-weight: var(--cx-weight-body);
  font-stretch: var(--gc-body-stretch);
  line-height: var(--cx-lh);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, video, picture { display: block; max-width: 100%; }
img, video { height: auto; }

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }

/*
   Tap target floor for form controls, set here rather than in each block that
   happens to have a form. A text field is a control and gets the full 44px; a
   checkbox is a small square next to a label and gets 24px, which is the WCAG
   2.2 minimum for a target that sits in a line of text. A block may make them
   bigger, never smaller, because these are min-* values.
*/
input:not([type='checkbox']):not([type='radio']):not([type='hidden']),
select,
textarea {
  min-height: var(--gc-tap);
}

input[type='checkbox'],
input[type='radio'] {
  width: 24px;
  height: 24px;
  min-width: 24px;
  accent-color: var(--cx-accent);
}

:where(a, button, summary, input, select, textarea):focus-visible {
  outline: 2px solid var(--cx-accent-on-bg);
  outline-offset: 2px;
  border-radius: var(--cx-radius-sm);
}

/* --gc-display-lh is the leading of the two big headings, and the one type
   value that is not derived from a token: 1.08 is the number direction C asks
   for at 78% width. It is named here rather than typed so that a font scheme
   (inc/design-typeset.php) can open it up along with the width and the weight,
   because leading is half of what makes a heading read as heavy. */
h1, h2, h3, h4 {
  font-family: var(--cx-font-display);
  font-weight: var(--cx-weight-display);
  font-stretch: var(--gc-display-stretch);
  font-style: var(--cx-display-style);
  letter-spacing: var(--cx-tracking);
  line-height: var(--gc-display-lh, 1.08);
  color: var(--cx-ink);
  text-wrap: balance;
}

h1 { font-size: var(--cx-fs-2xl); }
h2 { font-size: var(--cx-fs-xl); }
h3 { font-size: var(--cx-fs-lg); line-height: 1.2; }
h4 { font-size: var(--cx-fs-md); line-height: 1.3; }

p { text-wrap: pretty; }

::selection { background: var(--cx-accent2); color: var(--cx-ink); }

/* ---------------------------------------------------------------- *
   1. Layout primitives
   ---------------------------------------------------------------- */

.gc-section {
  padding-block: var(--gc-section);
  background: var(--cx-bg);
  color: var(--cx-on-bg);
}

.gc-section--surface {
  background: var(--cx-surface);
  color: var(--cx-on-surface);
  --cx-on-bg: var(--cx-on-surface);
  --cx-on-bg-muted: var(--cx-on-surface-muted);
  --cx-accent-on-bg: var(--cx-accent-on-surface);
}

.gc-section--ink {
  background: var(--cx-ink);
  color: var(--cx-bg);
  --cx-on-bg: var(--cx-bg);
  --cx-on-bg-muted: color-mix(in srgb, var(--cx-bg) 72%, var(--cx-ink));
  --cx-accent-on-bg: var(--cx-accent-2);
  --cx-border: color-mix(in srgb, var(--cx-bg) 22%, transparent);
}

.gc-section--ink h1,
.gc-section--ink h2,
.gc-section--ink h3,
.gc-section--ink h4 { color: var(--cx-bg); }

.gc-section--flush-top { padding-top: 0; }
.gc-section--flush-bottom { padding-bottom: 0; }
.gc-section--tight { padding-block: calc(var(--gc-section) * 0.5); }

.gc-wrap {
  width: 100%;
  max-width: var(--cx-maxw);
  margin-inline: auto;
  padding-inline: var(--gc-gutter);
}

.gc-wrap--narrow { max-width: 68ch; }
.gc-wrap--mid { max-width: 52ch; }

.gc-stack { display: flex; flex-direction: column; gap: var(--gc-stack-gap); }
.gc-stack > * { min-width: 0; }

.gc-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gc-4);
}

.gc-cols {
  display: grid;
  gap: var(--gc-7) var(--gc-6);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--gc-col-min), 100%), 1fr));
}

/* A page that opens on text, not on a photograph, needs air under the header. */
body.light-top .gc-section:first-of-type { padding-top: var(--gc-8); }

/* ---------------------------------------------------------------- *
   2. Type pieces
   ---------------------------------------------------------------- */

.gc-head { display: flex; flex-direction: column; gap: var(--gc-3); max-width: 46ch; }
.gc-head--wide { max-width: none; }

.gc-head__kicker {
  font-size: var(--cx-fs-sm);
  font-weight: 600;
  color: var(--cx-on-bg-muted);
  font-variant-numeric: tabular-nums;
}

.gc-head__title { font-size: var(--cx-fs-xl); }

.gc-head__lead {
  font-size: var(--cx-fs-md);
  color: var(--cx-on-bg-muted);
  max-width: 56ch;
}

.gc-label {
  display: inline-block;
  font-size: var(--cx-fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--cx-on-bg-muted);
}

.gc-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

.gc-note {
  font-size: var(--cx-fs-sm);
  color: var(--cx-on-bg-muted);
  max-width: 62ch;
}

.gc-quote {
  display: flex;
  flex-direction: column;
  gap: var(--gc-4);
  border-left: 2px solid var(--cx-border-strong);
  padding-left: var(--gc-5);
}

.gc-quote__text {
  font-family: var(--gc-font-voice);
  font-style: italic;
  font-weight: 400;
  font-size: var(--cx-fs-md);
  line-height: 1.45;
  color: var(--cx-on-bg);
}

.gc-quote__who {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--gc-3);
  font-family: var(--cx-font-body);
  font-size: var(--cx-fs-sm);
  color: var(--cx-on-bg-muted);
  font-variant-numeric: tabular-nums;
}

.gc-quote__meta { min-width: 0; }

.gc-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A number and its unit, or a hyphenated code, stays on one line. */
.gc-nowrap { white-space: nowrap; }

/* ---------------------------------------------------------------- *
   3. Actions and links
   ---------------------------------------------------------------- */

.gc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gc-2);
  min-height: var(--gc-tap);
  padding: var(--gc-3) var(--gc-5);
  border: var(--cx-border-w) solid transparent;
  border-radius: var(--cx-radius);
  background: none;
  font-weight: 600;
  font-size: var(--cx-fs-base);
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  transition: background-color var(--cx-dur) var(--cx-ease),
              border-color var(--cx-dur) var(--cx-ease),
              color var(--cx-dur) var(--cx-ease);
}

.gc-btn--primary {
  background: var(--cx-accent);
  border-color: var(--cx-accent);
  color: var(--cx-on-accent);
}

.gc-btn--primary:hover { background: color-mix(in srgb, var(--cx-accent) 86%, var(--cx-ink)); }

.gc-btn--ghost {
  border-color: color-mix(in srgb, var(--cx-on-bg) 32%, transparent);
  color: var(--cx-on-bg);
}

.gc-btn--ghost:hover { border-color: var(--cx-on-bg); }

.gc-btn--on-photo {
  border-color: color-mix(in srgb, var(--cx-on-photo) 55%, transparent);
  color: var(--cx-on-photo);
  background: color-mix(in srgb, var(--cx-ink) 32%, transparent);
  backdrop-filter: blur(2px);
}

.gc-btn--on-photo:hover { border-color: var(--cx-on-photo); }

.gc-btn--block { width: 100%; }

.gc-link {
  color: var(--cx-accent-on-bg);
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.18em;
  display: inline-flex;
  align-items: baseline;
  gap: var(--gc-1);
}

.gc-link:hover { text-decoration-thickness: 2px; }

.gc-link--out .gc-icon { align-self: center; }

.gc-link--tap { min-height: var(--gc-tap); align-items: center; }

/* ---------------------------------------------------------------- *
   4. Records: rows, rules, findings
   ---------------------------------------------------------------- */

.gc-rows { border-top: var(--gc-hair); }

.gc-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gc-2) var(--gc-5);
  padding-block: var(--gc-4);
  border-bottom: var(--gc-hair);
}

.gc-row > a { display: contents; }

.gc-row__main { flex: 1 1 18rem; min-width: 0; }

.gc-row__meta {
  color: var(--cx-on-bg-muted);
  font-size: var(--cx-fs-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* When the meta line wraps (town, service, month on a phone), its lines sit
   together as one caption rather than a cluster gap apart. */
.gc-row__meta .gc-cluster { row-gap: var(--gc-1); }

/* A row that carries a photograph of its own work. Same shape as the service
   index: thumbnail on the left, the thing itself and its meta on the right.
   The grid is there rather than the flex row above because a wrapped 66px
   image with the title flowing under it is not a row. */
.gc-row--media {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--gc-4);
  row-gap: var(--gc-1);
}

.gc-row__thumb {
  grid-row: span 2;
  width: calc(var(--gc-9) * 0.68);
  height: calc(var(--gc-9) * 0.85);
  object-fit: cover;
  border-radius: var(--cx-radius-sm);
  flex: none;
}

.gc-row--media .gc-row__main,
.gc-row--media .gc-row__meta { grid-column: 2; }

.gc-row--media .gc-row__meta { white-space: normal; }

/* The month keeps a slot of its own width, so a record still waiting on its
   date does not pull the service label into the date column. */
.gc-row__month {
  display: inline-block;
  min-width: 7.5ch;
  text-align: right;
}

@media (min-width: 48rem) {
  .gc-row--media {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .gc-row--media .gc-row__thumb { grid-row: 1; }
  .gc-row--media .gc-row__main { grid-column: 2; grid-row: 1; }

  .gc-row--media .gc-row__meta {
    grid-column: 3;
    grid-row: 1;
    white-space: nowrap;
    text-align: right;
  }
}

/* Whole-row links: keep the 44px target without nesting interactive elements. */
a.gc-row { min-height: var(--gc-tap); }
a.gc-row:hover .gc-row__main { color: var(--cx-accent-on-bg); }

.gc-rule { border: 0; border-top: var(--gc-hair); margin: 0; }

.gc-find {
  border-left: 3px solid var(--cx-accent-2);
  border-radius: 0; /* square, so the marker reads as a gauge mark, not a pill */
  padding-left: var(--gc-4);
}

/* ---------------------------------------------------------------- *
   5. Photography
   ---------------------------------------------------------------- */

.gc-photo { margin: 0; display: flex; flex-direction: column; gap: var(--gc-2); }

.gc-photo__img {
  width: 100%;
  border-radius: var(--cx-radius-sm);
  background: var(--cx-surface);
}

.gc-photo__cap {
  font-size: var(--cx-fs-sm);
  color: var(--cx-on-bg-muted);
}

/* An illustrative picture. The caption is the disclosure, so it sits right
   under the frame and is set like any other caption: small and muted, never
   smaller, fainter or further away, and never over the image. */
.gc-photo--illustrative { gap: var(--gc-2); }

.gc-photo__cap--illustrative {
  font-size: var(--cx-fs-sm);
  line-height: var(--cx-lh);
  color: var(--cx-on-bg-muted);
}

/*
   Text over a photograph. The container rebinds the page foreground pair to the
   on-photo pair, so anything painted with --cx-on-bg inside it switches by
   itself; and it pins the accent to white, because the token engine would pick
   the gauge yellow on a dark bed and yellow on this site means a real finding.
*/
.gc-onphoto {
  position: relative;
  isolation: isolate;
  color: var(--cx-on-photo);
  /* The token engine mixes the muted foreground 78% toward the photo bed, which
     measured 4.47:1 on the deep scrim - a third of a point short of AA for the
     small print under a quote. Rebound here rather than globally, because on
     paper the same token is fine at 5.6:1. */
  --cx-on-photo-muted: color-mix(in srgb, var(--cx-on-photo) 88%, var(--cx-ink));
  --cx-on-bg: var(--cx-on-photo);
  --cx-on-bg-muted: var(--cx-on-photo-muted);
  --cx-accent-on-bg: var(--gc-on-photo-pinned);
  --cx-accent-on-photo: var(--gc-on-photo-pinned);
  --cx-border: color-mix(in srgb, var(--cx-on-photo) 28%, transparent);
}

.gc-onphoto h1, .gc-onphoto h2, .gc-onphoto h3, .gc-onphoto h4 { color: var(--cx-on-photo); }

.gc-onphoto__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.gc-onphoto__media > img,
.gc-onphoto__media > video,
.gc-onphoto__media > picture,
.gc-onphoto__media > picture > img { width: 100%; height: 100%; object-fit: cover; }

.gc-onphoto__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to top, var(--cx-scrim) 0%, var(--cx-scrim-soft) 55%, transparent 100%),
    color-mix(in srgb, var(--cx-ink) 42%, transparent);
}

.gc-onphoto__scrim--deep {
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--cx-ink) 60%, transparent) 0%,
      transparent 60%),
    color-mix(in srgb, var(--cx-ink) 72%, transparent);
}

/*
   How much scrim a band needs is decided by where its text sits, and the three
   cases are different enough to be three rules. All three were measured on the
   real photographs, white on the rendered bed, not guessed:

     default        text along the bottom edge, large. The upward gradient does
                    the work and the photograph stays a photograph.
     --side         text in a column down one side (the hero). A second pass
                    goes down that side so a bright patch of brick behind the
                    headline can not drop it below AA.
     --deep         body copy across the whole frame (the quote band). There is
                    no safe corner in that case, so the whole image goes down to
                    a night exposure. Use it only when the copy genuinely has to
                    be on the photograph.
*/
.gc-onphoto__scrim--side {
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--cx-ink) 78%, transparent) 0%,
      color-mix(in srgb, var(--cx-ink) 50%, transparent) 14%,
      transparent 42%),
    linear-gradient(to right,
      color-mix(in srgb, var(--cx-ink) 74%, transparent) 0%,
      color-mix(in srgb, var(--cx-ink) 50%, transparent) 50%,
      color-mix(in srgb, var(--cx-ink) 22%, transparent) 80%,
      transparent 100%),
    color-mix(in srgb, var(--cx-ink) 26%, transparent);
}

/* On a phone the text column is the whole frame, so there is no "side" left to
   keep bright: the horizontal pass stops being a gradient and becomes coverage.
   Measured: without this the hero lead bottomed out at 4.35:1 against the
   brick, which is under AA for body copy. */
@media (max-width: 47.99rem) {
  .gc-onphoto__scrim--side {
    background:
      linear-gradient(to top,
        color-mix(in srgb, var(--cx-ink) 78%, transparent) 0%,
        color-mix(in srgb, var(--cx-ink) 50%, transparent) 16%,
        transparent 46%),
      linear-gradient(to right,
        color-mix(in srgb, var(--cx-ink) 74%, transparent) 0%,
        color-mix(in srgb, var(--cx-ink) 58%, transparent) 60%,
        color-mix(in srgb, var(--cx-ink) 48%, transparent) 100%),
      color-mix(in srgb, var(--cx-ink) 26%, transparent);
  }
}

/* ---------------------------------------------------------------- *
   6. Filters (our-work, reviews)
   ---------------------------------------------------------------- */

.gc-filters { display: flex; flex-wrap: wrap; gap: var(--gc-2); }

.gc-filters a {
  display: inline-flex;
  align-items: center;
  min-height: var(--gc-tap);
  padding: var(--gc-2) var(--gc-4);
  border: var(--gc-hair);
  border-radius: var(--cx-radius);
  font-size: var(--cx-fs-sm);
  color: var(--cx-on-bg-muted);
}

.gc-filters a:hover { border-color: var(--cx-on-bg); color: var(--cx-on-bg); }

.gc-filters a[aria-current] {
  background: var(--cx-ink);
  border-color: var(--cx-ink);
  color: var(--cx-bg);
}

/* On a phone the row scrolls sideways instead of wrapping. With nine service
   names wrapped one per line, /our-work/ opened on half a screen of buttons
   and no records. The strip runs to both screen edges, so the cut-off button
   at the right edge shows there is more. */
@media (max-width: 47.99rem) {
  .gc-filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: calc(var(--gc-gutter) * -1);
    padding-inline: var(--gc-gutter);
    scroll-padding-inline: var(--gc-gutter);
  }

  .gc-filters::-webkit-scrollbar { display: none; }

  .gc-filters a {
    flex: none;
    white-space: nowrap;
  }
}

/* ---------------------------------------------------------------- *
   7. Icons
   ---------------------------------------------------------------- */

.gc-icon {
  flex: none;
  display: inline-block;
  vertical-align: -0.125em;
  fill: currentColor;
}

/* ---------------------------------------------------------------- *
   8. Reveal
   ---------------------------------------------------------------- */

html.js [data-reveal] {
  opacity: 0;
  transform: translateY(var(--cx-reveal-y));
  transition: opacity var(--cx-dur-slow) var(--cx-ease),
              transform var(--cx-dur-slow) var(--cx-ease);
}

html.js [data-reveal="stagger"] { opacity: 1; transform: none; }

html.js [data-reveal="stagger"] > * {
  opacity: 0;
  transform: translateY(var(--cx-reveal-y));
  transition: opacity var(--cx-dur-slow) var(--cx-ease),
              transform var(--cx-dur-slow) var(--cx-ease);
}

html.js [data-reveal].is-revealed,
html.js [data-reveal="stagger"].is-revealed > * { opacity: 1; transform: none; }

/* ---------------------------------------------------------------- *
   9. Header
   ---------------------------------------------------------------- */

.gc-skip {
  position: absolute;
  left: var(--gc-4);
  top: var(--gc-4);
  z-index: 60;
  transform: translateY(-200%);
  background: var(--cx-ink);
  color: var(--cx-bg);
  padding: var(--gc-3) var(--gc-4);
  border-radius: var(--cx-radius);
}

.gc-skip:focus { transform: none; }

/* --gc-hdr-h is the height of this bar. Anything else that sticks has to sit
   under it rather than behind it, and the number belongs in one place. */
.gc-hdr {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--cx-bg);
  border-bottom: var(--gc-hair);
}

.gc-hdr__inner {
  display: flex;
  align-items: center;
  gap: var(--gc-5);
  min-height: calc(var(--gc-9) * 0.72);
  padding-block: var(--gc-2);
}

.gc-hdr__logo {
  display: inline-flex;
  align-items: center;
  gap: var(--gc-3);
  min-height: var(--gc-tap);
  margin-right: auto;
}

.gc-hdr__mark { width: calc(var(--gc-7) * 0.72); height: auto; }

.gc-hdr__word {
  font-family: var(--cx-font-display);
  font-weight: var(--cx-weight-display);
  font-stretch: var(--gc-display-stretch);
  font-size: var(--cx-fs-md);
  letter-spacing: var(--cx-tracking);
  line-height: 1;
  color: var(--cx-ink);
  white-space: nowrap;
}

.gc-hdr__nav { display: none; }

.gc-hdr__nav ul { display: flex; align-items: center; gap: var(--gc-5); }

.gc-hdr__nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--gc-tap);
  font-size: var(--cx-fs-sm);
  font-weight: 500;
  color: var(--cx-on-bg);
  border-bottom: 2px solid transparent;
}

.gc-hdr__nav a:hover { border-bottom-color: var(--cx-accent); }

.gc-hdr__nav .current-menu-item > a,
.gc-hdr__nav a[aria-current] { border-bottom-color: var(--cx-accent); }

.gc-hdr__call { white-space: nowrap; }
.gc-hdr__call .gc-hdr__call-label { display: none; }

/* Mobile menu: a details element, so it opens, closes and is keyboard
   reachable with JavaScript switched off. site.js only adds Escape and
   close-on-navigate. */
.gc-mob { position: relative; }

.gc-mob__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--gc-tap);
  height: var(--gc-tap);
  border: var(--gc-hair);
  border-radius: var(--cx-radius);
  color: var(--cx-ink);
  cursor: pointer;
  list-style: none;
}

.gc-mob__toggle::-webkit-details-marker { display: none; }
.gc-mob__toggle .gc-mob__icon-close { display: none; }

.gc-mob[open] .gc-mob__toggle {
  position: fixed;
  top: var(--gc-4);
  right: var(--gc-gutter);
  z-index: 55;
  background: var(--cx-bg);
}

.gc-mob[open] .gc-mob__icon-open { display: none; }
.gc-mob[open] .gc-mob__icon-close { display: inline-block; }

.gc-mob__panel {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--cx-bg);
  padding: var(--gc-4) var(--gc-gutter) var(--gc-8);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gc-4);
}

/* The brand, on the same line as the close button that is fixed to the top
   right of the sheet - hence the padding, which keeps the wordmark clear of it. */
.gc-mob__brand {
  display: flex;
  align-items: center;
  gap: var(--gc-3);
  min-height: var(--gc-tap);
  padding-right: calc(var(--gc-tap) + var(--gc-4));
  text-decoration: none;
}

/* :not(.gc-btn) matters: this selector is more specific than .gc-btn--primary,
   so without it the call button in the panel painted its label in ink on the
   service blue instead of paper on it. */
.gc-mob__panel a:not(.gc-btn):not(.gc-mob__brand) {
  display: flex;
  align-items: center;
  min-height: calc(var(--gc-tap) + var(--gc-2));
  border-bottom: var(--gc-hair);
  font-family: var(--cx-font-display);
  font-weight: var(--cx-weight-display);
  font-stretch: var(--gc-display-stretch);
  font-size: var(--cx-fs-lg);
  color: var(--cx-ink);
}

.gc-mob__panel .gc-btn { margin-top: var(--gc-4); }

@media (min-width: 60rem) {
  .gc-hdr__nav { display: block; }
  .gc-hdr__call .gc-hdr__call-label { display: inline; }
  .gc-mob { display: none; }
}

/* ---------------------------------------------------------------- *
   10. Footer
   ---------------------------------------------------------------- */

.gc-ftr {
  background: var(--cx-ink);
  color: var(--cx-bg);
  padding-block: var(--gc-8) var(--gc-7);
  --cx-on-bg: var(--cx-bg);
  --cx-on-bg-muted: color-mix(in srgb, var(--cx-bg) 72%, var(--cx-ink));
  --cx-border: color-mix(in srgb, var(--cx-bg) 20%, transparent);
}

.gc-ftr a { color: var(--cx-bg); }
.gc-ftr a:hover { color: var(--cx-accent-2); }

.gc-ftr__top {
  display: grid;
  gap: var(--gc-7);
  grid-template-columns: 1fr;
  padding-bottom: var(--gc-7);
  border-bottom: var(--gc-hair);
}

.gc-ftr__brand { display: flex; flex-direction: column; gap: var(--gc-4); max-width: 34ch; }

.gc-ftr__mark { width: calc(var(--gc-7) * 0.8); height: auto; }

.gc-ftr__word {
  font-family: var(--cx-font-display);
  font-weight: var(--cx-weight-display);
  font-stretch: var(--gc-display-stretch);
  font-size: var(--cx-fs-md);
  color: var(--cx-bg);
}

.gc-ftr__nap { font-style: normal; color: var(--cx-on-bg-muted); font-variant-numeric: tabular-nums; }
.gc-ftr__nap a { display: inline-block; min-height: var(--gc-tap); line-height: var(--gc-tap); }

/* Both column titles carry the same box whether or not they are a link, so the
   two columns still start on the same line. "Service area" is a link (the hub),
   "Services" is not, and the 44px floor belongs to the box rather than to the
   anchor for exactly that reason. */
.gc-ftr__col-title {
  display: flex;
  align-items: center;
  min-height: var(--gc-tap);
  font-size: var(--cx-fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--cx-on-bg-muted);
  margin-bottom: var(--gc-2);
}

.gc-ftr__col-title a {
  display: flex;
  align-items: center;
  align-self: stretch;
  color: inherit;
}

.gc-ftr__col-title a:hover {
  color: var(--cx-accent-2);
}

.gc-ftr__list { display: flex; flex-direction: column; gap: var(--gc-1); font-size: var(--cx-fs-sm); }
.gc-ftr__list li { color: var(--cx-on-bg-muted); }
.gc-ftr__list a { display: inline-flex; align-items: center; min-height: var(--gc-tap); }

/* A town name never breaks in the middle: "Prospect Heights" set as "Prospect"
   over "Heights" reads as two towns, and this list is the service-area proof on
   every page. The column width is a floor, so the browser drops to fewer
   columns instead of splitting a name. */
.gc-ftr__towns { columns: 2 9rem; column-gap: var(--gc-6); font-size: var(--cx-fs-sm); }
.gc-ftr__towns li { break-inside: avoid; white-space: nowrap; color: var(--cx-on-bg-muted); }
.gc-ftr__towns a { display: inline-flex; align-items: center; min-height: var(--gc-tap); }

.gc-ftr__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--gc-4);
  padding-top: var(--gc-5);
  font-size: var(--cx-fs-sm);
  color: var(--cx-on-bg-muted);
}

.gc-ftr__legal { display: flex; flex-wrap: wrap; gap: var(--gc-5); }
.gc-ftr__legal a { display: inline-flex; align-items: center; min-height: var(--gc-tap); }

@media (min-width: 48rem) {
  .gc-ftr__top { grid-template-columns: 1.2fr 1fr 1.6fr; }
  .gc-ftr__towns { columns: 3 9rem; }
}

/* ---------------------------------------------------------------- *
   11. Sticky call bar (mobile)
   ---------------------------------------------------------------- */

.gc-sticky {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 45;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--gc-2);
  padding: var(--gc-2) var(--gc-3) calc(var(--gc-2) + env(safe-area-inset-bottom));
  background: var(--cx-bg);
  border-top: var(--gc-hair);
  transition: transform var(--cx-dur) var(--cx-ease);
}

.gc-sticky .gc-btn { min-height: 48px; }

.gc-sticky.is-hidden { transform: translateY(110%); }

/* The bar covers the end of the page, so give the page that height back. */
body { padding-bottom: calc(48px + var(--gc-5) + env(safe-area-inset-bottom)); }

@media (min-width: 48rem) {
  .gc-sticky { display: none; }
  body { padding-bottom: 0; }
}

@media print {
  .gc-sticky, .gc-hdr, .gc-mob { display: none; }
  body { padding-bottom: 0; }
}

/* ---------------------------------------------------------------- *
   12. 404
   ---------------------------------------------------------------- */

.gc-404__code {
  font-family: var(--cx-font-display);
  font-weight: var(--cx-weight-display);
  font-stretch: var(--gc-display-stretch);
  font-size: var(--cx-fs-2xl);
  line-height: 1;
  color: var(--cx-on-bg-muted);
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- *
   13. Editor-adjacent: the generic page template
   ---------------------------------------------------------------- */

.gc-page__title { font-size: var(--cx-fs-xl); }

.gc-page__body > * + * { margin-top: var(--gc-5); }
.gc-page__body h2 { margin-top: var(--gc-7); font-size: var(--cx-fs-lg); }
.gc-page__body h3 { margin-top: var(--gc-6); font-size: var(--cx-fs-md); }
.gc-page__body ul { list-style: disc; padding-left: var(--gc-5); }
.gc-page__body ol { list-style: decimal; padding-left: var(--gc-5); }
.gc-page__body li + li { margin-top: var(--gc-2); }
.gc-page__body a { color: var(--cx-accent-on-bg); text-decoration: underline; text-underline-offset: 0.18em; }

/* Guides use the native featured image and retain a comfortable text measure. */
.gc-guide__wrap { max-width: 64rem; }
.gc-guide__header, .gc-guide .gc-page__body { width: 100%; max-width: 68ch; margin-inline: auto; }
.gc-guide__header .gc-page__title { font-size: clamp(2rem, 3.5vw, 3rem); line-height: 1.12; }
.gc-guide__lead { font-size: var(--cx-fs-md); color: var(--cx-on-bg-muted); }
.gc-guide__cover { margin-block: var(--gc-6) var(--gc-7); }
.gc-guide__image { display: block; width: 100%; height: auto; }
.gc-guide .gc-page__body { line-height: 1.8; }
.gc-guide .gc-page__body h2 { padding-top: var(--gc-5); border-top: 1px solid var(--cx-border); }
.gc-guide .gc-page__body ul { padding: var(--gc-5) var(--gc-6) var(--gc-5) var(--gc-7); background: var(--cx-surface); }

/* ---------------------------------------------------------------- *
   14. Motion off
   ---------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  html.js [data-reveal],
  html.js [data-reveal="stagger"] > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
