/* ==========================================================================
   theme.css — the "architectural" direction.

   Loaded LAST, after site.css and motion.css. It restyles the existing
   components in the layout language of glydearchitectural.com.au, by request:
   a thin accent utility bar over a charcoal header, a full-bleed photographic
   hero with centred white type, tight bold Open Sans headings over small
   tracked caps with a short accent rule, flat white tiles on a cool grey
   ground, near-black bands and a near-black footer.

   Only the LAYOUT LANGUAGE was taken. No copy, photograph, logo, icon or code
   from that site is used, and where it uses gold this site uses the logo's
   own cyan (`--brand-deep` / `--brand-mark`, #1CABDE) — chosen by the client.

   Rules that bind here:
   · The raw cyan is never type on a LIGHT ground (2.5:1). On light grounds
     labels and links take --brand #08597A (4.98:1 worst). On the near-black
     grounds the raw cyan IS text-safe: 6.9:1 on #1A1A1A.
   · A cyan FILL carries a dark label, never white (white on #1CABDE is 2.65:1).
   · No :hover rule in this file may set a transform other than `none`. This
     file sorts after the touch-hover reset at the end of motion.css, so a
     hover transform written here would latch on touch again. Image zoom on
     hover is inherited from motion.css, which is already gated.
   ========================================================================== */

:root {
  --night:      #1A1A1A;   /* near-black band, footer, page heads */
  --night-2:    #222426;   /* header */
  --night-3:    #2A2D30;   /* raised object on night */
  --on-night:   #FFFFFF;   /* 17.4:1 on --night */
  --on-night-mute: #C4C9CE;/* 11.0:1 on --night, 10.2:1 on --night-2 */
  --night-edge: rgba(255, 255, 255, 0.12);
  --accent:     #1CABDE;   /* the logo cyan — rules, fills, text on night only */

  --t-hero: clamp(2.5rem, 1.4rem + 4.2vw, 4.75rem);
  --t-h1:   clamp(2.25rem, 1.35rem + 3.6vw, 4rem);
  --t-h2:   clamp(1.9rem, 1.3rem + 2.4vw, 3.4rem);
  --t-h3:   clamp(1.2rem, 1.1rem + 0.5vw, 1.45rem);
  --t-lead: clamp(1.0313rem, 0.99rem + 0.25vw, 1.1563rem);
  --t-body: 1.0313rem;
  --t-mono: 0.8125rem;
  --pad-y: clamp(4.5rem, 8vw, 7.5rem);
  --max: 80rem;
}

body { line-height: 1.7; color: #1A1A1A; }
body::after { display: none; }   /* no film grain in this direction */
::selection { background: var(--accent); color: #14181B; }

/* ------------------------------- type ---------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 700;
  font-variation-settings: normal;
  line-height: 1.08;
  letter-spacing: -0.035em;
  color: #1A1A1A;
}
h3, h4 { font-weight: 600; letter-spacing: -0.015em; line-height: 1.25; }

/* Eyebrow: small tracked caps, then a short accent rule, then the heading.
   The rule is the eyebrow's existing ::before, moved after the words with
   `order` — so the scroll-drawn dash (`.eyebrow[data-anim]::before` in
   motion.css) keeps working unchanged. */
.eyebrow {
  font-family: var(--body);
  font-size: 0.875rem; font-weight: 400;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--brand);
  flex-direction: column; align-items: flex-start; gap: 1.1rem;
}
.eyebrow::before {
  order: 2; width: 4.5rem; height: 2px;
  background: var(--accent); opacity: 1;
}
.dark .eyebrow, .rail-sec .eyebrow { color: var(--brand); }
.dark .eyebrow::before, .rail-sec .eyebrow::before { background: var(--accent); }
.section-head h2, .rail-head h2, .rdx-intro h2, .process-aside h2 { margin-top: 1.25rem; }

.lead { color: #3F454A; }
.muted { color: #4A5157; }

/* ------------------------------ grounds -------------------------------- */
/* Three flat grounds, nothing veiled: cool grey, white and near-black.
   `.light` is the grey page ground, `.dark` is now WHITE (the second light
   ground — its old meaning, just flatter), and `.night` is the genuinely dark
   band. Every text colour inside `.night` is named below; see the CLAUDE.md
   rule that any element setting its own ground must colour its own text. */
.light { background: #F3F4F7; }
.dark  { background: #FFFFFF; color: #1A1A1A; }
.dark h1, .dark h2, .dark h3 { color: #1A1A1A; }
.rail-sec { background: #FFFFFF; color: #1A1A1A; }

.night, .site-foot, .page-head, .cta-band {
  background: var(--night); color: var(--on-night-mute);
}
.night h1, .night h2, .night h3, .night h4,
.page-head h1, .page-head h2, .cta-band h2 { color: var(--on-night); }
.night .eyebrow, .page-head .eyebrow, .cta-band .eyebrow { color: var(--accent); }
.night .lead, .night .muted, .night p,
.page-head .lead, .cta-band .lead { color: var(--on-night-mute); }
.night .tlink, .cta-band .tlink { color: var(--accent); }
.night .step, .night .steps-spine .step { border-top-color: var(--night-edge); }
.night .step h3 { color: var(--on-night); }
.night .step p { color: var(--on-night-mute); }
.night .steps-spine::before { background: var(--night-edge); }
.night .steps-spine::after { background: var(--accent); }
.night .step::before, .night .step::after { color: var(--accent); border-color: var(--night-edge); }

/* The rotated drawing-sheet margin does not belong to this direction. The
   mobile sticky series label on the products page is kept — it is navigation. */
@media (min-width: 62rem) {
  .railed { padding-left: 0; }
  .railed::before, .rail-label { display: none; }
}

/* ------------------------------ utility bar ---------------------------- */
.topbar {
  background: var(--accent); color: #14181B;   /* 6.74:1 */
  font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  position: relative; z-index: 101;
}
.topbar .wrap {
  display: flex; justify-content: flex-end; align-items: center;
  gap: clamp(1rem, 2.5vw, 1.75rem); min-height: 2.25rem;
}
.topbar a { text-decoration: none; color: inherit; padding-block: 0.4rem; }
.topbar a:hover { text-decoration: underline; text-underline-offset: 3px; }
/* The email address is set in lower case, as it is written — by request.
   The bar's uppercase is for labels; an address in capitals reads wrong. */
.topbar a[href^="mailto:"] { text-transform: none; letter-spacing: 0.02em; }
@media (max-width: 40rem) {
  .topbar .wrap { justify-content: center; }
  .topbar a[href^="mailto:"] { display: none; }
}

/* -------------------------------- header ------------------------------- */
.site-head,
html.head-solid .site-head {
  background: var(--night-2);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border-bottom: 1px solid var(--night-edge);
  color: var(--on-night);
}
.site-head .wrap { min-height: 5.5rem; }
/* 2.6rem, stepping down on small phones. theme.css used to set 2.6rem at
   every width, which overrode site.css's own small-screen step, and at
   320px the logo + gap + 44px menu button ran 6px past the right edge —
   enough for iOS Safari to let the whole page pan sideways. The brand may
   also shrink, so no future logo can push the button off again. */
.brand-logo { height: 2.6rem; }
@media (max-width: 30rem) { .brand-logo { height: 2.1rem; } }
.site-head .brand { min-width: 0; flex: 0 1 auto; }
.site-head .brand-logo { max-width: 100%; object-fit: contain; object-position: left center; }
.nav a {
  font-family: var(--body); font-size: 0.8125rem; font-weight: 400;
  letter-spacing: 0.04em; color: var(--on-night);
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--accent); }
.nav a::after { background: var(--accent); height: 2px; bottom: -0.2rem; }
.nav a.btn-primary, .nav a.btn-primary:hover {
  background: var(--accent); border-color: var(--accent); color: #14181B;
  font-weight: 700;
}
.nav a.btn-primary:hover { background: var(--on-night); border-color: var(--on-night); }
.nav-toggle { border-color: var(--night-edge); color: var(--on-night); }
@media (max-width: 60rem) {
  .nav {
    background: var(--night-2); border-bottom-color: var(--night-edge);
    inset: auto 0 auto 0; top: auto;
  }
  .nav { position: absolute; top: 100%; }
  .nav a { border-bottom-color: var(--night-edge); color: var(--on-night); }
}

/* -------------------------------- buttons ------------------------------ */
/* Square, flat, small tracked caps. On light grounds the fill is near-black
   with a white label and turns logo-cyan with a dark label on hover; on dark
   grounds it starts cyan and turns white. */
.btn {
  font-family: var(--body); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: 0.08em; padding: 1rem 1.75rem; border-radius: 0;
  transition: background-color 220ms var(--ease), border-color 220ms var(--ease),
              color 220ms var(--ease);
}
.btn:hover, .btn:active { transform: none; }
.btn-primary {
  background: var(--night); color: var(--on-night);
  border-color: var(--night); box-shadow: none;
}
.btn-primary:hover { background: var(--accent); border-color: var(--accent); color: #14181B; box-shadow: none; }
.btn-ghost { border-color: var(--night); color: var(--night); }
.btn-ghost::before { background: var(--night); }
.btn-ghost:hover { color: var(--on-night); }
.dark .btn-ghost, .btn-ghost.on-light { border-color: var(--night); color: var(--night); }
.dark .btn-ghost::before, .btn-ghost.on-light::before { background: var(--night); }
.dark .btn-ghost:hover, .btn-ghost.on-light:hover { color: var(--on-night); }

.hero .btn-primary, .night .btn-primary, .cta-band .btn-primary, .page-head .btn-primary {
  background: var(--accent); border-color: var(--accent); color: #14181B;
}
.hero .btn-primary:hover, .night .btn-primary:hover,
.cta-band .btn-primary:hover, .page-head .btn-primary:hover {
  background: var(--on-night); border-color: var(--on-night); color: #14181B;
}
.hero .btn-ghost, .night .btn-ghost, .cta-band .btn-ghost, .page-head .btn-ghost {
  border-color: rgba(255, 255, 255, 0.7); color: var(--on-night);
}
.hero .btn-ghost::before, .night .btn-ghost::before,
.cta-band .btn-ghost::before, .page-head .btn-ghost::before { background: var(--on-night); }
.hero .btn-ghost:hover, .night .btn-ghost:hover,
.cta-band .btn-ghost:hover, .page-head .btn-ghost:hover { border-color: var(--on-night); color: #14181B; }

.tlink { font-family: var(--body); font-weight: 600; letter-spacing: 0.08em; font-size: 0.8125rem; }

/* --------------------------------- hero -------------------------------- */
.hero { background: var(--night); color: var(--on-night); padding-block: 0; }
.hero-top {
  min-height: min(90vh, 880px);
  align-items: center; justify-content: center; text-align: center;
  padding-block: clamp(6rem, 12vw, 9rem) clamp(5rem, 9vw, 7rem);
  color: var(--on-night);
}
.hero-media { background: var(--night); }
/* A NEUTRAL DARK SCRIM, and it is what guarantees the white type now. At
   0.58 black over the brightest possible pixel the ground composites to
   rgb(107,107,107), where #FFFFFF reads 5.3:1 — so any banner photograph
   holds body-size white text, not just this three. The bottom deepens to
   seat the stat rail. */
.hero-media::after {
  background:
    linear-gradient(180deg, rgba(12, 12, 12, 0.58) 0%, rgba(12, 12, 12, 0.58) 70%, rgba(12, 12, 12, 0.78) 100%);
}
@media (max-width: 63.999rem) {
  .hero-media::after {
    background: linear-gradient(180deg, rgba(12, 12, 12, 0.60) 0%, rgba(12, 12, 12, 0.60) 70%, rgba(12, 12, 12, 0.80) 100%);
  }
}
@media (min-width: 64rem) {
  .hero-top > .wrap { padding-right: var(--gut); }
}
.hero-top > .wrap { max-width: 64rem; }
.hero-copy { justify-items: center; }
.hero-top .eyebrow,
.hero-top .lead, .hero-top .hero-lead, .hero-top h1 { color: var(--on-night); }
.hero-top .eyebrow {
  /* white, not cyan: over the photograph the scrim guarantees ~5.3:1 for
     white but only ~2:1 for the cyan at this size */
  color: var(--on-night); font-weight: 600; align-items: center; font-size: 0.8125rem; letter-spacing: 0.18em;
}
.hero-top .eyebrow::before { display: none; }
.hero-title {
  font-family: var(--display); font-weight: 700;
  font-variation-settings: normal;
  font-size: var(--t-hero); line-height: 1.06; letter-spacing: -0.035em;
  margin-block: 1.1rem 1.6rem; max-width: 18ch; margin-inline: auto;
  color: var(--on-night);
}
.hero-strong { display: block; font-weight: 700; }
.hero-title .decay {
  display: block; font-style: normal; font-weight: 300;
  color: var(--on-night); letter-spacing: -0.03em;
}
.hero-rule { display: block; width: 4.5rem; height: 2px; background: var(--accent); }
.hero-foot { justify-items: center; gap: 1.6rem; }
.hero-lead { color: var(--on-night); max-width: 52ch; margin-inline: auto; }
.hero-cta { justify-content: center; margin-top: 0.75rem; }

.hero-nav {
  top: 50% !important; margin-top: -1.5rem;
  max-width: none; padding-inline: clamp(0.75rem, 2vw, 1.5rem);
}
.hero-nav-btn {
  width: 3rem; height: 3rem; border-radius: 50%;
  background: rgba(12, 12, 12, 0.6); color: var(--on-night);
  border: 1px solid rgba(255, 255, 255, 0.18); box-shadow: none;
}
.hero-nav-btn:hover { background: var(--accent); color: #14181B; border-color: var(--accent); }

/* The stat rail: a near-black strip closing the hero. */
.hero-rail {
  background: var(--night); border-bottom: 0;
}
.hero-specs { margin-top: 0; border-top: 1px solid var(--night-edge); padding-bottom: clamp(2.25rem, 4vw, 3rem); }
.hero-spec { text-align: left; }
.hero-spec + .hero-spec { border-left-color: var(--night-edge) !important; }
.hero-spec::before { background: var(--accent); width: 2.5rem; height: 2px; }
.hero-spec dt { color: var(--on-night-mute); font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase; }
.hero-spec dd {
  font-family: var(--display); font-weight: 700; font-variation-settings: normal;
  color: var(--on-night); letter-spacing: -0.03em;
}

/* ------------------------------ page heads ----------------------------- */
.page-head { padding-block: clamp(4.5rem, 9vw, 7rem) clamp(3.5rem, 6vw, 5rem); }
.page-head::before { display: none; }
.page-head h1 { font-size: var(--t-h1); margin-block: 1.4rem 1.1rem; max-width: 22ch; }
.page-head .lead { max-width: 60ch; }
.page-head .crumbs { color: var(--on-night-mute); font-family: var(--body); letter-spacing: 0.08em; }
.page-head .crumbs a:hover { color: var(--accent); }
.page-head .crumbs span[aria-hidden] { opacity: 0.5; }
/* the NRC bar's empty run is "what comes back off the wall" — on this ground
   that is the ground's own lift, not a paper tint */
.page-head .nrc-track { background: var(--night-3); border-color: var(--night-edge); }
.page-head .nrc-track::after { background: rgba(255, 255, 255, 0.28); }
.page-head .nrc-fig, .page-head .nrc-fig b, .page-head .nrc-unit, .page-head .nrc-raw { color: var(--on-night); }

/* ------------------------------ cta band ------------------------------- */
.cta-band::before, .cta-band::after { display: none; }
.cta-band h2 { font-size: var(--t-h2); max-width: 20ch; }

/* -------------------------------- cards -------------------------------- */
/* Flat white tiles: photograph on top, the words underneath, no outline and
   no offset block. Hover is a soft lift of the shadow and the photograph
   easing in (that scale lives in motion.css, already touch-gated). */
.card, .dark .card {
  border: 0; border-radius: 0; box-shadow: none; background: #FFFFFF;
  transition: box-shadow 320ms var(--ease);
}
.dark .card, .rail-sec .card { background: #F3F4F7; }
a.card:hover, .dark a.card:hover { transform: none; box-shadow: var(--lift-2); }
a.card:active { transform: none; box-shadow: none; }
.card h3 { font-size: 1.125rem; font-weight: 600; letter-spacing: -0.01em; }
.card-cat, .card-foot, .gal-cap p, .crumbs, .foot-bar, .rdx-cols, .note-meta,
.spec caption, .filter, .fampick a, .bento-i span, .rail-hint, .field label {
  font-family: var(--body);
}
.card-cat { letter-spacing: 0.1em; font-size: 0.75rem; }
.card-foot { letter-spacing: 0.08em; font-weight: 600; font-size: 0.75rem; }

.panel, .dark .panel { border: 0; box-shadow: none; background: #FFFFFF; }
.dark .panel { background: #F3F4F7; }

.fam, .fam-all { border: 0; border-radius: 0; }
.fam-all { background: var(--night); color: var(--on-night); }
.fam-all h3, .fam-all .fam-n, .fam-all .fam-note, .fam-all .card-foot { color: var(--on-night); }
.fam-all .card-foot { color: var(--accent); }
.fam-all:hover { background: var(--night-3); border-color: transparent; transform: none; }

/* ------------------------------- gallery ------------------------------- */
.gal { gap: 1.5rem; }
.gal-item { border: 0; border-radius: 0; background: #FFFFFF; transition: box-shadow 320ms var(--ease); }
.light .gal-item { background: #FFFFFF; }
.dark .gal-item { background: #F3F4F7; }
.gal-item:hover { box-shadow: var(--lift-2); }
.gal-cap { padding: 1.1rem 1.25rem 1.3rem; }
.gal-cap h3 {
  font-size: 0.9375rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: #1A1A1A; line-height: 1.35;
}
.gal-item:first-child .gal-cap h3 { font-size: 1.0625rem; }
.gal-cap p { letter-spacing: 0.08em; color: #4A5157; }

.filter {
  border-radius: 0; border-color: #C9CED3; color: #3F454A;
  letter-spacing: 0.08em; font-weight: 600;
}
.filter[aria-pressed="true"],
.dark .filter[aria-pressed="true"] { background: var(--night); border-color: var(--night); color: var(--on-night); }
.fampick a { border-radius: 0; letter-spacing: 0.08em; font-weight: 600; background: #FFFFFF; }
.dark .fampick a { background: #F3F4F7; }

/* ---------------------------- other objects ---------------------------- */
.bento-i { border: 0; box-shadow: none; background: #F3F4F7; }
.light .bento-i { background: #FFFFFF; }
.bento-i b { font-variation-settings: normal; letter-spacing: -0.03em; }
.bento-i span { letter-spacing: 0.08em; }
.quote, .dark .quote { box-shadow: none; }
.pd-aside { border: 0; box-shadow: none; background: #F3F4F7; }
.light .pd-aside { background: #FFFFFF; }
.dip { background: #FFFFFF; }
.review { box-shadow: none; border: 0; background: #F3F4F7; }
.review-avatar { font-family: var(--display); font-weight: 700; }
.statement-em { font-style: normal; font-weight: 300; color: #1A1A1A; }
.field input, .field select, .field textarea { background: #FFFFFF; border-color: #C9CED3; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--night); outline: none; }
.pills li { border-radius: 0; }
.spec caption { letter-spacing: 0.1em; }
.rdx-fig { font-variation-settings: normal; letter-spacing: -0.03em; }

/* -------------------------------- footer ------------------------------- */
.site-foot { border-top: 0; }
.foot-h {
  font-family: var(--body); font-size: 0.8125rem; font-weight: 700;
  letter-spacing: 0.1em; color: var(--accent);
  border-bottom-color: var(--night-edge);
}
.foot-list a, .foot-list li { color: var(--on-night-mute); }
.foot-list a:hover { color: var(--on-night); }
.foot-blurb { color: var(--on-night-mute); }
.foot-bar { border-top-color: var(--night-edge); color: #9AA1A7; letter-spacing: 0.08em; }   /* 6.9:1 */

/* ------------------------------- floats -------------------------------- */
.float { border-radius: 0; background: #FFFFFF; color: var(--brand); box-shadow: 0 8px 24px -12px rgba(0,0,0,0.35); }
.float:hover { background: var(--accent); color: #14181B; }
/* product pages: the bottom-left float is a labelled "Back to Products
   Page" link instead of the back-to-top arrow (10 Oct 2026) */
.float.float-back {
  width: auto; padding: 0 1.15rem; text-decoration: none; white-space: nowrap;
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
}
.float-wa { background: var(--night); color: var(--on-night); }

:focus-visible { outline-color: var(--accent); }

/* The drafting-sheet corner ticks were Workshop furniture. */
.card::before, .panel::before, .quote::before, .pd-aside::before, .sector::before { display: none; }

/* On a phone the centred copy fills the hero's width, so mid-height arrows
   would sit on the lead. They go back to the top edge, over photograph only. */
@media (max-width: 47.999rem) {
  .hero-nav { top: 1rem !important; margin-top: 0; }
}

/* ==========================================================================
   HOMEPAGE — the reference layout, section for section:
   hero · intro over a photograph · Our Products tiles · why-list ·
   near-black three-column band · projects grid · footer.
   ========================================================================== */

/* 1 · intro: copy in the left half, a photograph owns the right half */
.g-intro { position: relative; background: #FFFFFF; padding-block: 0; overflow: hidden; }
.g-intro-media { display: none; }
.g-intro .wrap { position: relative; z-index: 2; }
.g-intro-copy { padding-block: var(--pad-y); max-width: 34rem; }
.g-intro-copy h2, .g-intro-copy h1 { font-size: var(--t-h2); margin-block: 1.25rem 1.5rem; }
@media (min-width: 60rem) {
  .g-intro-media { display: block; position: absolute; top: 0; right: 0; bottom: 0; width: 50%; }
  .g-intro-media img { width: 100%; height: 100%; object-fit: cover; }
  .g-intro-copy { max-width: calc(50% - var(--gut)); padding-right: 2rem; }
}
.g-stats {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem 2rem;
  margin-block: 2.25rem 2.5rem; padding-top: 2rem; border-top: 1px solid #DDE1E5;
}
.g-stats div { display: flex; flex-direction: column-reverse; gap: 0.3rem; }
.g-stats dd {
  font-size: clamp(1.75rem, 1.3rem + 1.4vw, 2.4rem); font-weight: 700;
  letter-spacing: -0.03em; line-height: 1; color: #1A1A1A;
  font-variant-numeric: tabular-nums;
}
.g-stats dt { font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase; color: #4A5157; }

/* 2 · Our Products: photograph tiles, centred caption on white */
.g-products { background: #F3F4F7; }
.g-products h2 { font-size: var(--t-h2); margin-block: 1.25rem clamp(2rem, 4vw, 3rem); }
/* products page: the carousel opens the page under its h1 (8 Oct 2026),
   and the filter grid follows straight on, so the gap below is tightened */
.g-products-top { padding-bottom: clamp(2rem, 4vw, 3rem); }
.g-products-top h1 { margin-block: 0 clamp(2rem, 4vw, 3rem); }
.g-products-top + .light { padding-top: clamp(2rem, 4vw, 3rem); }
.g-tiles {
  list-style: none; padding: 0; display: grid; gap: 0;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 40rem) { .g-tiles { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 64rem) { .g-tiles { grid-template-columns: repeat(5, 1fr); } }
.g-tile a { display: flex; flex-direction: column; height: 100%; text-decoration: none; color: #1A1A1A; background: #FFFFFF; }
.g-tile-img { position: relative; aspect-ratio: 1 / 1; overflow: hidden; display: block; }
.g-tile-img > img { width: 100%; height: 100%; object-fit: cover; transition: transform 700ms var(--ease); }
.g-tile-img.is-cut > img { object-fit: contain; padding: 10%; }
.g-plus {
  position: absolute; inset: 0; z-index: 5; display: grid; place-items: center;
  background: rgba(26, 26, 26, 0.55); color: #FFFFFF;
  font-size: 2.5rem; font-weight: 300; line-height: 1;
  opacity: 0; transition: opacity 260ms var(--ease);
}
.g-tile a:hover .g-plus, .g-tile a:focus-visible .g-plus { opacity: 1; }
.g-tile-cap {
  display: block; padding: 1.25rem 1rem 1.4rem; text-align: center;
  font-size: 1.0313rem; font-weight: 600; letter-spacing: -0.02em; line-height: 1.3;
}
.g-tile a:hover .g-tile-cap { color: var(--brand); }
.g-more { margin-top: clamp(2rem, 4vw, 3rem); }

/* PRODUCTS CAROUSEL -------------------------------------------------------
   Native scroll-snap, not a JS-driven track. The browser does the dragging,
   the momentum and the snapping, so with `site.js` blocked this is still a
   draggable, snapping row of ten products rather than a dead grid — the
   arrows, the dots and the keyboard are the only things the script adds, and
   it injects them itself so they are never present without their behaviour.

   THE TRACK IS A SCROLL CONTAINER. Nothing inside it may carry `[data-anim]`:
   a `view()` timeline cannot resolve inside a scroller, so the tile would
   hold its from-state (opacity 0) for good. That is the frozen-clock trap in
   CLAUDE.md, which the family rail hit once already. The section's reveal
   sits on `.g-car`, which is outside the scroller.

   `--car-n` is how many tiles are in view, and it is the ONE number the
   component is built on: the track's column width, the page size the arrows
   step by and the number of dots are all derived from it in CSS and in JS
   (`site.js` reads it back with getComputedStyle). Change it in one place. */
.g-car { position: relative; }

/* Higher specificity than `.g-tiles` so the 40rem / 64rem `repeat()` rules
   above cannot win the template back. */
.g-tiles.g-car-track {
  --car-n: 2;
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: calc(100% / var(--car-n));
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;           /* the dots are the position indicator */
  -ms-overflow-style: none;
}
.g-tiles.g-car-track::-webkit-scrollbar { display: none; }
.g-car-track > .g-tile { scroll-snap-align: start; min-width: 0; }
/* 2 / 3 / 3. It was 2 / 3 / 5; desktop came down to three by request
   ("increase the size and 3 images in 1 row"), which is the only edit that
   change needs — the column width, the arrows' page size and the dot count
   all read --car-n. Ten products over three is four pages, the last of them
   short, which targetFor() in site.js already clamps so the track never
   scrolls past its end and leaves a gap. The 64rem step is kept rather than
   deleted: it states the desktop count instead of leaving it to be inferred
   from the tablet rule, which is where anyone will look to change it. */
@media (min-width: 40rem) { .g-tiles.g-car-track { --car-n: 3; } }
@media (min-width: 64rem) { .g-tiles.g-car-track { --car-n: 3; } }

/* While a pointer drag is in progress the snap is suspended — otherwise the
   browser fights every mousemove by snapping back to the nearest tile. */
.g-car[data-drag="on"] .g-car-track { scroll-snap-type: none; }
.g-car[data-drag="on"] .g-tile a { cursor: grabbing; }

/* Controls sit UNDER the track rather than over the photographs: the tiles
   are square with the caption below, so an overlaid arrow would either cover
   a product or need the tile's height in CSS, which varies with `--car-n`. */
.g-car-ctl {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(0.75rem, 2vw, 1.25rem); margin-top: 1.5rem;
}
.g-car-btn {
  flex: none; width: 2.75rem; height: 2.75rem;      /* 44px touch target */
  display: grid; place-items: center;
  border: 0; padding: 0; cursor: pointer;
  background: var(--night);       /* the logo cyan on this light ground */
  color: var(--on-night);         /* DARK label — white on cyan is 2.65:1 */
}
/* Disabled is a real end-stop, not a dimmed cyan: dropping the opacity of a
   cyan fill takes the arrow under contrast as well as the fill. */
.g-car-btn:disabled {
  background: #FFFFFF; color: #5A646E; cursor: default;   /* 6.03:1 */
}
.g-car-btn svg { width: 1.125rem; height: 1.125rem; display: block; }
/* contact page — the Google map under the form and office blocks */
.g-map { position: relative; margin-top: clamp(2.5rem, 5vw, 4rem); }
.g-map-frame {
  display: block; width: 100%; border: 0; background: #E3E8EB;
  height: clamp(18rem, 42vw, 30rem);
}
.g-map-btn { margin-top: 1rem; }
.g-map-hit { position: absolute; inset: 0; z-index: 1; cursor: pointer; }
/* a visible cue that the map is a link — bottom-left, clear of Google's own
   attribution, which sits bottom-right */
.g-map-tag {
  position: absolute; left: 0.75rem; bottom: 0.75rem;
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.6rem 0.9rem; background: #FFFFFF; color: #14181B;
  font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  box-shadow: 0 6px 18px -8px rgba(14, 26, 31, 0.45);
  transition: background-color 200ms ease, color 200ms ease;
}
.g-map-hit:hover .g-map-tag { background: var(--accent); color: #14181B; }
.g-map-hit:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
/* the autoplay pause/play toggle (site.js) sits apart from the paging arrows */
.g-car-play { margin-left: clamp(0.25rem, 1.5vw, 1rem); }
/* Colour-only, so it is safe outside the pointer gate — the touch-hover reset
   at the end of motion.css neutralises `transform` and leaves colour alone. */
.g-car-btn:not(:disabled):hover { background: var(--brand); color: #FFFFFF; }  /* 7.72:1 */

.g-car-dots { display: flex; align-items: center; gap: 0; list-style: none; padding: 0; margin: 0; }
/* The button is the 44px target; the dot people actually see is the
   pseudo-element inside it. */
.g-car-dot {
  /* 24 x 44. The dots butt together (gap 0), so the width IS the whole
     target and 20px would sit under the 24px floor with no spacing to make
     it up. The visible dot stays small — it is the ::before. */
  width: 1.5rem; height: 2.75rem; padding: 0; border: 0;
  background: none; cursor: pointer; display: grid; place-items: center;
}
.g-car-dot::before {
  content: ""; width: 0.5rem; height: 0.5rem; background: #5A646E;   /* 5.48:1 */
}
.g-car-dot[aria-current="true"]::before {
  background: var(--brand);       /* 7.02:1 — the cyan is 2.53:1 here and
                                     would miss the 3:1 a UI component needs */
  width: 1.25rem;
}

.center-row { justify-content: center; }

/* 3 · why-list: heading left, two-column check list right, on white */
.g-why { background: #FFFFFF; }
.g-why .wrap { display: grid; gap: clamp(2rem, 5vw, 4.5rem); }
/* heading left, the client's paragraph right, bottom-aligned; the room
   tiles run full width underneath */
.g-why-head { display: grid; gap: 1rem clamp(2rem, 5vw, 4.5rem); }
@media (min-width: 60rem) { .g-why-head { grid-template-columns: 1.15fr 1fr; align-items: end; } }
.g-why-head h2 { font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.4rem); margin-block: 1.25rem 0; }
.g-why-head > p { margin: 0; max-width: 46ch; }

/* the ten room types as photograph tiles — 2 across on phones, 5 on desktop
   (10 divides by both, so there is never a ragged row) */
.g-rooms { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.75rem; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 48rem) { .g-rooms { grid-template-columns: repeat(5, 1fr); } }
.g-room a {
  position: relative; display: block; overflow: hidden; aspect-ratio: 4 / 5;
  background: #E3E8EB; text-decoration: none; isolation: isolate;
}
/* two-up between phone and desktop: landscape, or five rows of 4:5 tiles
   run ~2000px tall on a tablet */
@media (min-width: 30rem) and (max-width: 47.999rem) { .g-room a { aspect-ratio: 3 / 2; } }
.g-room img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* the scrim is what the white name is read against: 0.78 black at the foot
   holds white text at >= 9:1 over even a white patch of photograph */
.g-room a::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.78) 100%);
}
.g-room-name {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 1rem 1rem 1.1rem; color: #FFFFFF;
  font-size: clamp(0.9375rem, 0.85rem + 0.3vw, 1.0625rem); font-weight: 600; line-height: 1.3;
}
.g-room-name::before {
  content: ""; display: block; width: 1.75rem; height: 2px; margin-bottom: 0.6rem;
  background: var(--accent);
}
.g-room a:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.g-checks { list-style: none; padding: 0; display: grid; gap: 0.25rem 2rem; grid-template-columns: 1fr; }
@media (min-width: 36rem) { .g-checks { grid-template-columns: 1fr 1fr; } }
.g-checks a {
  display: flex; gap: 0.85rem; align-items: flex-start;
  padding-block: 0.8rem; text-decoration: none; color: #303030;
}
.g-check { width: 1.15rem; height: 1.15rem; flex: none; margin-top: 0.2rem; color: var(--brand); }
.g-checks b { display: block; font-size: 1.0625rem; font-weight: 600; line-height: 1.4; }
.g-checks small { display: block; font-size: 0.875rem; color: #4A5157; margin-top: 0.1rem; }
.g-checks a:hover b { color: var(--brand); }

/* 4 · near-black band: icon, capitals heading, paragraph */
.g-band { padding-block: clamp(4.5rem, 8vw, 6.5rem); }
.g-cols { display: grid; gap: clamp(2.5rem, 5vw, 4rem); grid-template-columns: 1fr; }
@media (min-width: 56rem) { .g-cols { grid-template-columns: repeat(3, 1fr); } }
.g-col { display: flex; flex-direction: column; gap: 1rem; text-decoration: none; color: inherit; }
.g-ico { width: 3.5rem; height: 3.5rem; color: var(--accent); margin-bottom: 0.5rem; }
.g-col h3 {
  font-size: 1.25rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: -0.02em; color: var(--on-night);
}
.g-col p { color: var(--on-night-mute); font-size: 1rem; line-height: 1.75; }
.g-col .tlink { margin-top: auto; padding-top: 0.5rem; align-self: flex-start; }

/* 5 · projects: centred label, 3-up grid, hover veil with title + View */
.g-work { background: #F3F4F7; }
.g-work-head { text-align: center; margin-bottom: clamp(2rem, 4vw, 3rem); }
.center-eyebrow {
  align-items: center; font-size: 1.25rem; font-weight: 600; color: #1A1A1A;
  letter-spacing: 0.02em; line-height: 1.3;
}
.center-eyebrow::before { width: 1.4rem; }
/* the "50 clients" label, homepage and About, larger by request */
.center-eyebrow.clients-title { font-size: clamp(1.75rem, 1.2rem + 2vw, 2.5rem); font-weight: 700; }
.g-proj { list-style: none; padding: 0; display: grid; gap: clamp(1rem, 2vw, 1.75rem); grid-template-columns: 1fr; }
@media (min-width: 40rem) { .g-proj { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .g-proj { grid-template-columns: repeat(3, 1fr); } }
.g-pj a { display: flex; flex-direction: column; text-decoration: none; color: #1A1A1A; }
.g-pj-img { position: relative; aspect-ratio: 4 / 3; overflow: hidden; display: block; }
.g-pj-img > img { width: 100%; height: 100%; object-fit: cover; transition: transform 700ms var(--ease); }
/* 0.82 so white 1.25rem type holds ≥5:1 even where the photo is white */
.g-pj-over {
  position: absolute; inset: 0; z-index: 5;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1rem; padding: 1.5rem; text-align: center;
  background: rgba(26, 26, 26, 0.82); color: #FFFFFF;
  opacity: 0; transition: opacity 300ms var(--ease);
}
.g-pj a:hover .g-pj-over, .g-pj a:focus-visible .g-pj-over { opacity: 1; }
.g-pj-t { font-size: 1.25rem; font-weight: 600; letter-spacing: -0.02em; line-height: 1.3; }
.g-pj-rule { width: 1.4rem; height: 2px; background: #FFFFFF; }
.g-pj-btn {
  background: var(--accent); color: #14181B;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.6rem 1.2rem;
}
.g-pj-cap {
  margin-top: 1.1rem; font-size: 1.0625rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.03em; line-height: 1.35;
}
.g-pj-sub { margin-top: 0.3rem; font-size: 0.8125rem; letter-spacing: 0.06em; text-transform: uppercase; color: #4A5157; }

/* touch: the veils are hover-only affordances; captions carry the words */
@media (hover: none) {
  .g-plus, .g-pj-over { display: none; }
}
/* left-aligned figures here; the right-aligned 2ch box was for the old rail */
.g-stats .tick { min-width: 0; text-align: left; }
/* `isolation` makes each tile its own blend group, so the multiply of a
   cut-out shot always composites against the tile's own mount. Without it
   Chrome occasionally composited a tile on its own layer and the blend had
   nothing to multiply — the product vanished (seen on the wedge foam tile). */
.g-tile-img, .g-pj-img { isolation: isolate; }

/* ==========================================================================
   INNER PAGES — the reference layout. Pages open on a LIGHT intro, not a
   dark banner; the page-head rules above are now unused by the templates.
   ========================================================================== */

/* light page intro — optional photograph in the right half */
.g-head { background: #FFFFFF; padding-block: clamp(4rem, 8vw, 6.5rem) clamp(3rem, 6vw, 4.5rem); }
.g-head .wrap { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 60rem) { .g-head.has-img .wrap { grid-template-columns: 1fr 1fr; } }
.g-head h1 { font-size: var(--t-h1); margin-block: 1.25rem 1.1rem; max-width: 20ch; }
.g-head .lead { max-width: 60ch; }
.g-head-media img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.g-head.is-center { text-align: center; }
.g-head.is-center .eyebrow { align-items: center; }
.g-head.is-center h1, .g-head.is-center .lead { margin-inline: auto; }
.g-head.is-center .fampick { justify-content: center; margin-top: 2rem; }
.g-head .crumbs { color: #4A5157; font-family: var(--body); letter-spacing: 0.08em; }
.g-head .crumbs a:hover { color: var(--brand); }

/* products: three-photograph strip over the intro */
.g-strip { display: grid; grid-template-columns: repeat(3, 1fr); height: clamp(11rem, 30vw, 26rem); }
.g-strip-i { overflow: hidden; }
.g-strip-i img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 40rem) { .g-strip { grid-template-columns: 1fr; } .g-strip-i + .g-strip-i { display: none; } }
.fampick { display: flex; flex-wrap: wrap; gap: 0.35rem; }

/* product detail: near-black intro, product photograph beside the copy */
.pd-top { padding-block: clamp(4rem, 8vw, 6.5rem); }
.pd-top-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 60rem) { .pd-top-grid { grid-template-columns: 1fr 1.1fr; } }
.pd-top h1 { font-size: var(--t-h1); margin-block: 1.25rem 1rem; }
.pd-top .crumbs { margin-top: 2rem; color: var(--on-night-mute); font-family: var(--body); letter-spacing: 0.08em; }
.pd-top .crumbs a:hover { color: var(--accent); }
.pd-top .nrc { margin-top: 1.75rem; max-width: 26rem; }
.night .nrc-track { background: var(--night-3); border-color: var(--night-edge); }
.night .nrc-track::after { background: rgba(255, 255, 255, 0.28); }
.night .nrc-fig, .night .nrc-fig b, .night .nrc-unit, .night .nrc-raw { color: var(--on-night); }
.pd-top .pd-hero { border: 0; aspect-ratio: 16 / 10; }
.pd-over { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .pd-over.has-shots { grid-template-columns: 1.2fr 1fr; } }
.pd-over .pd-body { max-width: 68ch; }
.pd-over .pd-body h2 { font-size: var(--t-h2); margin-block: 1.25rem 1.25rem; }
.pd-over .pd-gallery { grid-template-columns: 1fr 1fr; }
.g-apps {
  list-style: none; padding: 0; margin-top: 2rem;
  display: grid; gap: 1rem 2.5rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
}
.g-apps li {
  display: flex; gap: 0.85rem; align-items: flex-start; padding-block: 0.9rem;
  border-top: 1px solid var(--night-edge);
  color: var(--on-night); font-size: 1.0625rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.01em; line-height: 1.4;
}
.g-apps .g-check { color: var(--accent); }
.pd-tech { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .pd-tech { grid-template-columns: 1fr 1.6fr; } }
.pd-tech h2 { font-size: var(--t-h2); margin-top: 1.25rem; }
.pd-tech .spec { background: #FFFFFF; }
.pd-tech .spec th, .pd-tech .spec td { padding: 1rem 1.25rem; }
.pd-related h2 { font-size: var(--t-h2); margin-block: 1.25rem clamp(2rem, 4vw, 3rem); }

/* projects: the gallery IS the tile grid */
.g-pj[hidden] { display: none; }
.g-proj .gal-cap { padding: 0; background: none; }
.g-proj .gal-cap h3.g-pj-cap { font-size: 1rem; margin-top: 1.1rem; margin-bottom: 0; color: #1A1A1A; }
.g-proj .gal-cap p.g-pj-sub { font-family: var(--body); }
#gal { margin-top: 0.5rem; }

/* about: story with photographs, logo strip, 8/4 founder + figures */
.g-story { padding-block: clamp(4rem, 8vw, 6.5rem); }
.g-story-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .g-story-grid { grid-template-columns: 1.1fr 1fr; } }
.g-story h1 { font-size: var(--t-h1); margin-top: 0.25rem; }
.g-story-media { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.g-story-media .surface { aspect-ratio: 4 / 5; }
.g-story-media.is-single { grid-template-columns: 1fr; }
.g-story-media.is-single .surface { aspect-ratio: 3 / 4; }
.g-logos { padding-block: clamp(3.5rem, 7vw, 5.5rem); }
.g-logos .client-wall { margin-top: clamp(2rem, 4vw, 3rem); }
.center-eyebrow.on-night { color: var(--on-night); }
.g-8-4 { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .g-8-4 { grid-template-columns: 2fr 1fr; } }
.g-8-4 .founder { display: block; }
.g-8-4 .founder > p + p { margin-top: 1.1rem; }
.g-8-4 .founder-lead { margin-bottom: 1.1rem; }
.bento-col { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.bento-col .bento-i:first-child { grid-column: 1 / -1; }
.dark .bento-i { background: #F3F4F7; }

/* four columns of short items on a light ground */
.g-cols-4 { grid-template-columns: 1fr; }
@media (min-width: 40rem) { .g-cols-4 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 72rem) { .g-cols-4 { grid-template-columns: repeat(4, 1fr); } }
.light .g-col h3, .dark .g-col h3 { color: #1A1A1A; font-size: 1.0625rem; }
.light .g-col, .dark .g-col { border-top: 2px solid var(--accent); padding-top: 1.5rem; }
.light .g-col p, .dark .g-col p { color: #4A5157; font-size: 0.9688rem; }

/* notes: FAQ accordion rows */
.g-faq-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .g-faq-grid { grid-template-columns: 1fr 1.6fr; } }
.g-faq-grid h2 { font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.4rem); margin-top: 1.25rem; }
.g-faq-i { background: #F3F4F7; margin-bottom: 0.6rem; }
.g-faq-i summary {
  list-style: none; cursor: pointer; position: relative;
  padding: 1.2rem 3.5rem 1.2rem 1.5rem;
  font-weight: 600; font-size: 1.0625rem; color: #1A1A1A; line-height: 1.4;
}
.g-faq-i summary::-webkit-details-marker { display: none; }
.g-faq-i summary::after {
  content: '+'; position: absolute; right: 1.4rem; top: 50%; translate: 0 -50%;
  font-size: 1.5rem; font-weight: 300; color: var(--brand);
}
.g-faq-i[open] summary::after { content: '\2212'; }
.g-faq-i p { padding: 0 1.5rem 1.4rem; color: #3F454A; max-width: 70ch; }

/* contact: form beside the contact blocks */
/* contact page: the h1 and lead introduce the form, above it (9 Oct 2026) */
.g-contact-head { margin-bottom: clamp(2.5rem, 5vw, 3.5rem); }
.g-contact-head h1 { font-size: var(--t-h1); margin-block: 1.25rem 1.1rem; max-width: 20ch; }
.g-contact-head .lead { max-width: 60ch; }
.g-contact-grid { display: grid; gap: clamp(2.5rem, 5vw, 4.5rem); align-items: start; }
@media (min-width: 60rem) { .g-contact-grid { grid-template-columns: 1.5fr 1fr; } }
.g-offices { display: grid; gap: 1.75rem; }
.g-office { padding-top: 1.25rem; border-top: 1px solid #DDE1E5; }
.g-office .eyebrow { font-size: 0.8125rem; gap: 0.6rem; }
.g-office .eyebrow::before { width: 2rem; }
.g-office .v { display: block; margin-top: 0.65rem; font-size: 1.125rem; font-weight: 600; color: #1A1A1A; text-decoration: none; overflow-wrap: anywhere; }
.g-office a.v:hover { color: var(--brand); }
.g-checks-3 { margin-top: 2rem; }
@media (min-width: 60rem) { .g-checks-3 { grid-template-columns: repeat(3, 1fr); } }
.g-check-row { display: flex; gap: 0.85rem; align-items: flex-start; padding-block: 0.8rem; color: #303030; }
.g-check-row b { font-weight: 600; font-size: 1.0625rem; line-height: 1.45; }

/* THE CLOSING CONTACT BAND is a light tint of the accent, as the reference
   closes its inner pages on a tint of its own. #D6F0F9 is the logo cyan at
   18% on white; ink is 16.0:1 on it, the muted lead 9.1:1, --brand 6.2:1. */
.cta-band.g-tint { background: #D6F0F9; color: #3F454A; }
.g-tint h2 { color: #1A1A1A; }
.g-tint .eyebrow { color: var(--brand); }
.g-tint .lead { color: #3F454A; }
.g-tint .btn-primary { background: var(--night); border-color: var(--night); color: var(--on-night); }
.g-tint .btn-primary:hover { background: var(--accent); border-color: var(--accent); color: #14181B; }
.g-tint .btn-ghost { border-color: var(--night); color: var(--night); }
.g-tint .btn-ghost::before { background: var(--night); }
.g-tint .btn-ghost:hover { color: var(--on-night); }

/* spec table on a near-black band (the products page's quick guide) */
.night .spec caption { color: var(--accent); }
.night .spec th { color: var(--on-night-mute); }
.night .spec td { color: var(--on-night); }
.night .spec tr + tr { border-top-color: var(--night-edge); }

/* ==========================================================================
   NO BLACK GROUNDS — THE LOGO CYAN INSTEAD (by request).
   Every near-black ground (hero scrim, `.night` bands, product intros, the
   footer) is now the logo's own #1CABDE, and the header is white.

   The consequence that governs everything below: WHITE ON THIS CYAN IS
   2.65:1 AND FAILS, so every word on a cyan ground is DARK.
     --on-night       #0E1A1F   6.8:1 on #1CABDE
     --on-night-mute  #1C2B31   5.5:1
   Nothing on a cyan ground may be cyan (eyebrows, rules, icons, ticks, links
   all move to the dark ink), and a cyan button on a cyan ground is white.
   The logo itself is cyan, so it cannot sit on this ground: the header is
   white and the footer puts the lockup on a white mount. The artwork is
   untouched.
   The token names keep saying "night" because ~60 rules read them; read
   them as "the strong band".
   ========================================================================== */
:root {
  --night:         #1CABDE;
  --night-2:       #FFFFFF;          /* header */
  --night-3:       #FFFFFF;          /* raised object on the band */
  --on-night:      #0E1A1F;
  --on-night-mute: #1C2B31;
  --night-edge:    rgba(14, 26, 31, 0.22);
}

/* header: white, the full-colour logo, dark links */
.site-head, html.head-solid .site-head { border-bottom-color: #E3E6EA; }
.nav a:hover, .nav a[aria-current="page"] { color: var(--brand); }
.nav a.btn-primary:hover { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }
@media (max-width: 60rem) { .nav a { border-bottom-color: #E3E6EA; } }

/* buttons on LIGHT grounds: logo-cyan fill, dark label; hover deepens to
   --brand with a white label (7.5:1). Ghosts take --brand, never cyan text. */
.btn-primary:hover { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }
.btn-ghost, .dark .btn-ghost, .btn-ghost.on-light,
.g-tint .btn-ghost { border-color: var(--brand); color: var(--brand); }
.btn-ghost::before, .dark .btn-ghost::before, .btn-ghost.on-light::before,
.g-tint .btn-ghost::before { background: var(--brand); }
.btn-ghost:hover, .dark .btn-ghost:hover, .btn-ghost.on-light:hover,
.g-tint .btn-ghost:hover { color: #FFFFFF; border-color: var(--brand); }
.g-tint .btn-primary { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }
.g-tint .btn-primary:hover { background: var(--accent); border-color: var(--accent); color: #14181B; }

/* buttons on the CYAN grounds: white fill, dark label */
.hero .btn-primary, .night .btn-primary {
  background: #FFFFFF; border-color: #FFFFFF; color: var(--on-night);
}
.hero .btn-primary:hover, .night .btn-primary:hover {
  background: var(--brand); border-color: var(--brand); color: #FFFFFF;
}
.hero .btn-ghost, .night .btn-ghost { border-color: var(--on-night); color: var(--on-night); }
.hero .btn-ghost::before, .night .btn-ghost::before { background: #FFFFFF; }
.hero .btn-ghost:hover, .night .btn-ghost:hover { border-color: #FFFFFF; color: var(--on-night); }

/* hero: the photograph under a CYAN scrim, dark type. 0.85 over the darkest
   possible pixel still composites to a ground where #0E1A1F reads 4.9:1, so
   the small eyebrow holds over any banner; the edges fall to 0.55 so the
   photograph shows outside the type column. */
.hero-media::after {
  background: linear-gradient(90deg,
    rgba(28, 171, 222, 0.55) 0%, rgba(28, 171, 222, 0.85) 20%,
    rgba(28, 171, 222, 0.85) 80%, rgba(28, 171, 222, 0.55) 100%);
}
@media (max-width: 63.999rem) {
  .hero-media::after { background: rgba(28, 171, 222, 0.86); }
}
.hero-rule { background: var(--on-night); }
.hero-nav-btn { background: #FFFFFF; color: var(--on-night); border-color: transparent; }
.hero-nav-btn:hover { background: var(--brand); color: #FFFFFF; border-color: var(--brand); }

/* everything that was cyan ON a dark band is dark ink on the cyan band */
.night .eyebrow, .night .tlink, .night .g-ico, .night .g-check,
.night .spec caption, .fam-all .card-foot { color: var(--on-night); }
.night .eyebrow::before { background: #FFFFFF; }
.night .steps-spine::after { background: var(--on-night); }
.night .step::before, .night .step::after { color: var(--on-night); }
.night .tlink::after { opacity: 0.6; }
.night .nrc-track::after { background: rgba(14, 26, 31, 0.3); }
.pd-top .crumbs a:hover, .night a:hover { color: var(--on-night); text-decoration: underline; text-underline-offset: 3px; }
.night :focus-visible { outline-color: var(--on-night); }

/* footer: cyan ground, dark type, the logo on a white mount */
.foot-h { color: var(--on-night); border-bottom-color: var(--night-edge); }
.foot-list a:hover { color: var(--on-night); text-decoration: underline; text-underline-offset: 3px; }
.foot-bar { color: var(--on-night-mute); }
.site-foot .brand { background: #FFFFFF; padding: 0.85rem 1.1rem; }
.site-foot .brand-logo-full { height: 2.6rem; }
.site-foot :focus-visible { outline-color: var(--on-night); }
/* footer social icons — each on a disc of its network's own colour, glyph
   in white (by request). White on every disc is >= 4.5:1 except YouTube red
   (4.0:1), which still clears the 3:1 a non-text graphic needs. X is black,
   so it carries a light ring to stay visible on the charcoal footer.
   Hover is a brightness lift only — no transform: this file sorts after the
   touch-hover reset. 2.5rem, so all five fit one row. */
/* ONE LINE, always (by request). The Certified column narrowed this column
   and the fifth icon wrapped. Each disc is a share of the row, at most
   2.75rem (44px, the comfortable tap target), so where five do not fit at
   full size they shrink together instead of one dropping to a second line.
   The glyph scales with the disc. */
.foot-social { list-style: none; margin: 1.25rem 0 0; padding: 0; display: flex; flex-wrap: nowrap; gap: 0.35rem; }
.foot-social li { flex: 0 1 2.75rem; min-width: 0; }
.foot-social svg { width: 46%; height: 46%; }
.foot-social a {
  display: grid; place-items: center; width: 100%; aspect-ratio: 1;
  border-radius: 50%; color: #FFFFFF; background: var(--net, #444);
  transition: filter 200ms ease, box-shadow 200ms ease;
}
.foot-social a[data-net="facebook"]  { --net: #1877F2; }
.foot-social a[data-net="instagram"] { background: radial-gradient(circle at 30% 107%, #FDF497 0%, #FDF497 5%, #FD5949 45%, #D6249F 60%, #285AEB 90%); }
.foot-social a[data-net="linkedin"]  { --net: #0A66C2; }
.foot-social a[data-net="youtube"]   { --net: #FF0000; --tri: #FF0000; }
.foot-social a[data-net="x"]         { --net: #000000; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35); }
.foot-social a:hover { filter: brightness(1.15); }
.foot-social a[data-net="x"]:hover { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7); }

/* hover veils over photographs: cyan, dark type (5.7:1 over a black pixel) */
.g-pj-over { background: rgba(28, 171, 222, 0.92); color: var(--on-night); }
.g-pj-rule { background: var(--on-night); }
.g-pj-btn { background: #FFFFFF; color: var(--on-night); }
.g-plus { background: rgba(28, 171, 222, 0.85); color: var(--on-night); }

/* filters: the pressed state takes --brand, so no black pill remains */
.filter[aria-pressed="true"],
.dark .filter[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }

.float-wa { background: var(--accent); color: #14181B; }
.fam-all:hover { color: var(--on-night); }

/* the WhatsApp float is cyan; its outline keeps it visible over the cyan footer */
.float-wa { border: 2px solid #FFFFFF; }

/* ==========================================================================
   THE BANNER PHOTOGRAPHS CARRY NO SCRIM (by request, 30 Sep 2026: "remove
   the shadow from the banner images, keep the image color as it is"). The
   0.58 black wash that used to sit over every photograph is gone, so the
   pictures show in their own colours.

   THE COST: that wash was what guaranteed white type at 5.3:1 over ANY
   banner. Without it, the type's contrast is a property of each photograph
   again. What holds it now is a text-shadow on the type itself — a tight
   dark edge plus a soft halo — which darkens only the pixels directly around
   each glyph, never the picture. It is not a WCAG-measurable guarantee: over
   a bright patch of a banner, re-check by eye (and re-check whenever a
   banner image changes).
   ========================================================================== */
.hero, .hero-media { background: #1A1A1A; }
.hero-media::after { background: none; }
.hero, .hero-top,
.hero-top .eyebrow, .hero-top h1, .hero-title, .hero-title .decay,
.hero-top .lead, .hero-top .hero-lead { color: #FFFFFF; }
.hero-top .eyebrow, .hero-top h1, .hero-title, .hero-top .lead, .hero-top .hero-lead {
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), 0 2px 18px rgba(0, 0, 0, 0.45);
}
.hero-rule { background: var(--accent); }
/* cyan fill, dark label (6.74:1); hover to white */
.hero .btn-primary { background: var(--accent); border-color: var(--accent); color: #14181B; }
.hero .btn-primary:hover { background: #FFFFFF; border-color: #FFFFFF; color: #14181B; }
.hero-nav-btn { background: rgba(12, 12, 12, 0.6); color: #FFFFFF; border-color: rgba(255, 255, 255, 0.18); }
.hero-nav-btn:hover { background: var(--accent); color: #14181B; border-color: var(--accent); }
.hero :focus-visible { outline-color: var(--accent); }

/* homepage counter band — the client's figures, counting up on arrival */
.g-count { padding-block: clamp(3.5rem, 7vw, 5.5rem); }
.g-count-row { display: grid; gap: 2.5rem 2rem; grid-template-columns: 1fr; text-align: center; }
@media (min-width: 40rem) { .g-count-row { grid-template-columns: repeat(3, 1fr); } }
.g-count-i { display: flex; flex-direction: column-reverse; align-items: center; gap: 0.6rem; }
.g-count-i + .g-count-i { border-top: 1px solid var(--night-edge); padding-top: 2.5rem; }
@media (min-width: 40rem) {
  .g-count-i + .g-count-i { border-top: 0; padding-top: 0; border-left: 1px solid var(--night-edge); }
}
.g-count-i dd {
  font-size: clamp(3rem, 2rem + 3.5vw, 4.75rem); font-weight: 800;
  letter-spacing: -0.04em; line-height: 1; color: var(--on-night);
  font-variant-numeric: tabular-nums;
}
.g-count-i dd .tick { min-width: 0; text-align: center; }
.g-count-i dt {
  font-size: 0.875rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--on-night-mute);
}
.g-clients .client-wall { margin-top: clamp(2rem, 4vw, 3rem); }

/* CLIENT TICKER — see `clientRows()` in src/pages.js. The default below is
   the static grid: rows and tracks dissolve into `.client-wall`'s auto-fit
   grid and the duplicate plates are removed. Only where scroll timelines
   exist and motion is welcome do the rows become full-bleed strips, and only
   there do they move. A row is `overflow: clip`, NOT `hidden`: `hidden`
   would make it a scroll container and freeze the view() timeline on the
   track inside it (the frozen-clock note in CLAUDE.md). */
.client-row, .client-track { display: contents; }
.client-track { list-style: none; margin: 0; padding: 0; }
.client-track > [data-dup] { display: none; }
@keyframes gTick    { from { translate: 0 0; }    to { translate: -25% 0; } }
@keyframes gTickRev { from { translate: -25% 0; } to { translate: 0 0; } }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .client-rows { display: flex; flex-direction: column; gap: clamp(0.5rem, 1.2vw, 0.875rem); }
    .client-row {
      display: block; overflow: clip;
      margin-inline: calc(50% - 50vw);
    }
    .client-track {
      display: flex; gap: clamp(0.5rem, 1.2vw, 0.875rem); width: max-content;
      animation: gTick linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    .client-row.is-rev .client-track { animation-name: gTickRev; }
    .client-track > .client { flex: 0 0 clamp(8.5rem, 7rem + 5vw, 12rem); }
    .client-track > [data-dup] { display: flex; }
  }
}

/* ==========================================================================
   MOTION ADDED WITH THE ARCHITECTURAL LAYOUT (by request: "add animations").
   Everything here obeys the rules the rest of the site's motion keeps:
   · ALL of it sits under `prefers-reduced-motion: no-preference`.
   · Scroll-linked pieces are CSS scroll timelines inside
     `@supports (animation-timeline: view())`; where that is missing they are
     simply static. Nothing here can leave content hidden: every from-state
     is visible (a scale, a drawn stroke, a small offset), except the load-in
     entrances, which are time-based and finish in under a second.
   · Hover motion is gated on `(hover: hover) and (pointer: fine)`, so it can
     never latch on touch — which is why it may live in this file even though
     the ungated touch reset sits at the end of motion.css.
   · It moves `scale`/`translate` (the individual properties) rather than
     `transform` wherever an element might already carry a transform
     animation, so the two compose instead of fighting.
   ========================================================================== */
@keyframes gIn     { from { opacity: 0; translate: 0 26px; } }
@keyframes gGrow   { from { scale: 0 1; } }
/* The banner push-in, now with the rightward travel that makes the rotation
   read as a SLIDE rather than a dissolve. Every banner moves the same way —
   right — so the hero has one direction: a photograph drifts right while it
   is on screen, keeps going as it fades out, waits off to the left while it
   is hidden, and slides right into place as it fades in.

   THE JUMP AT 33.34% IS INVISIBLE AND HAS TO BE. mHeroFade holds opacity 0
   from 33.33% to 96.67%, so that is where the slide is re-cocked from +2.6%
   to -2.6%. Move either number and the reset happens in front of the reader.

   THE SCALE IS NOT DECORATION — IT IS THE TRAVEL'S COVER. An `inset: 0`
   slide translated by n% exposes n% of `.hero-media`'s #1A1A1A ground at the
   trailing edge unless the photograph is oversized by at least 2n%. Every
   row below holds `(scale - 1) / 2 >= |translate|` with margin, and both
   properties share one easing so the ratio holds between rows too. It does
   NOT lean on mWall's 1.16 on the <img> for that headroom: mWall is behind
   `@supports (animation-timeline: view())`, so on Firefox it is not there.
   Change a translate here and re-check the scale on the same row. */
@keyframes gKen {
  0%     { scale: 1.06; translate: 0 0; }        /* settled, just arrived   */
  30%    { scale: 1.12; translate: 1.4% 0; }     /* drifts right, on screen */
  33.33% { scale: 1.13; translate: 2.6% 0; }     /* leaves right, fading    */
  33.34% { scale: 1.06; translate: -2.6% 0; }    /* re-cocked, opacity 0    */
  96.67% { scale: 1.06; translate: -2.6% 0; }    /* waits off to the left   */
  100%   { scale: 1.06; translate: 0 0; }        /* slides right, fading in */
}
/* The arrow-driven version: one travel, not a loop, so it settles at scale 1
   instead of gKen's 1.06 floor — nothing follows it that needs the headroom. */
@keyframes gBannerIn   { from { translate: -2.6% 0; scale: 1.08; } to { translate: 0 0; scale: 1; } }
@keyframes gBannerBack { from { translate:  2.6% 0; scale: 1.08; } to { translate: 0 0; scale: 1; } }
/* The headline's travel. Same six stops and the same 33.34% re-cock as gKen,
   so the words leave and arrive with their own photograph — but FLAT from 0%
   to 30%, where gKen drifts. That stretch is the whole time the headline is
   legible, and the one thing a reader must not have to do is follow moving
   type. All the movement is in the two changeover windows, where mHeroFade
   has the block at or near opacity 0. */
@keyframes gSay {
  0%     { translate: 0 0; }           /* arrived                          */
  30%    { translate: 0 0; }           /* still — this is the reading time */
  31.5%  { translate: 2.5rem 0; }      /* leaves right, fading             */
  31.6%  { translate: -2.5rem 0; }     /* re-cocked, opacity 0             */
  98.2%  { translate: -2.5rem 0; }     /* waits off to the left            */
  100%   { translate: 0 0; }           /* slides right, fading in          */
}

/* THE WORDS FADE THROUGH; THE PHOTOGRAPHS CROSS-FADE. Both still run on one
   period and one set of delays — the sync CLAUDE.md insists on is timing,
   and that is untouched — but they cannot share an opacity curve.

   mHeroFade overlaps by design: two photographs are both part-opaque at the
   changeover, because a gap would show `.hero-media`'s #1A1A1A between them.
   Run the HEADLINES on that same curve and you get two headlines and two
   eyebrows at ~50% on top of each other, which on a phone is unreadable —
   the title wraps to three lines there, so the two sets interleave rather
   than merely overlapping. Measured at the worst frame: 0.49 and 0.51.

   So the outgoing headline is fully out (31.5%) BEFORE the incoming starts
   in. The three blocks are offset by exactly a third, so the incoming is at
   31.5% + 66.67% = 98.17% of its own loop at that moment: starting its rise
   at 98.2% clears the outgoing by 0.03% of the loop, about 5ms, which reads
   as continuous while never drawing both at once. Those two numbers are a
   matched pair — move one and you either reopen the overlap or leave a
   visible hole with no headline. gSay's stops above were moved onto the same
   two boundaries so the travel and the fade start and finish together. */
@keyframes gSayFade {
  0%     { opacity: 1; }
  30%    { opacity: 1; }               /* legible, still                   */
  31.5%  { opacity: 0; }               /* fully out before the next begins */
  98.2%  { opacity: 0; }
  100%   { opacity: 1; }
}
/* The arrow-driven headline travel: one trip, no loop, no re-cock. */
@keyframes gSayIn   { from { translate: -2.5rem 0; } to { translate: 0 0; } }
@keyframes gSayBack { from { translate:  2.5rem 0; } to { translate: 0 0; } }
@keyframes gDraw   { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes gPar    { from { scale: 1.14; } to { scale: 1; } }
@keyframes gOpen   { from { opacity: 0; translate: 0 -6px; } }
@keyframes gStrip  { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
/* 1.06 is not a new number: it is exactly what the pointer settles a tile
   photograph on in block 7. Touch gets the same value from a different
   trigger — see block 8. */
@keyframes gSettle { from { scale: 1.06; } to { scale: 1; } }
@keyframes gNudge  { from { translate: -4px 0; } to { translate: 0 0; } }

@media (prefers-reduced-motion: no-preference) {

  /* 1 · Inner-page intros land the way the hero does. They had no entrance
     at all: `[data-in]` was only wired for .hero and the retired .page-head. */
  .g-head [data-in], .pd-top [data-in], .g-story [data-in] {
    animation: gIn 760ms var(--m-in, cubic-bezier(.22,1,.36,1)) both;
    animation-delay: calc(var(--d, 0) * 1ms);
  }
  /* the products page's photograph strip opens top-down, one after another */
  .g-strip-i { animation: gStrip 900ms cubic-bezier(.22,1,.36,1) both; }
  .g-strip-i:nth-child(2) { animation-delay: 120ms; }
  .g-strip-i:nth-child(3) { animation-delay: 240ms; }

  /* 2 · The banner photographs breathe and travel: a push-in while each is
     on screen, and the rightward slide in gKen. 15s, matching motion.css —
     5s a photograph across three.

     THE TWO RULES BELOW ARE ONE EDIT, NOT TWO. Each restates mHeroFade's
     period so a second animation can hang off it, so each is capable of
     drifting away from the other and from motion.css. The headline carries
     the client's own words for the room in the photograph; if the periods
     disagree it describes the wrong room until the two loops happen to meet
     again. Period and both delays are identical on both, and on mHeroFade in
     motion.css. Change one, change all three. */
  .hero-slide { animation: mHeroFade 15s linear infinite, gKen 15s ease-in-out infinite; }
  .hero-slide:nth-child(2) { animation-delay: -10s; }
  .hero-slide:nth-child(3) { animation-delay: -5s; }

  /* The words slide with the picture. gSay holds the headline STILL for the
     whole time it is legible and moves it only on the change — text that
     drifts under the eye while you are reading it is a different and worse
     effect than a slider. No scale, either: scaling type resamples the
     glyphs and the headline is the largest text on the site.

     rem, not %, because a % here is a share of the copy block's own width,
     which is a clamp() that changes with the viewport — the travel would be
     wider on a desktop than a phone for no reason. Nothing to expose behind
     it, so this needs none of gKen's cover arithmetic; `.hero-top` is
     `overflow: hidden` and the block is grid-stacked, so the travel cannot
     spill or shift the layout. */
  .hero-say { animation: gSayFade 15s linear infinite, gSay 15s var(--ease) infinite; }
  .hero-say:nth-child(2) { animation-delay: -10s; }
  .hero-say:nth-child(3) { animation-delay: -5s; }

  /* THE ARROWS SLIDE TOO, AND THEY SLIDE THE WAY THEY POINT. Taking an arrow
     puts the hero in `.is-manual`, which sets `animation: none` on every
     slide and hands the change to a 420ms opacity transition — so without
     this the auto rotation slid and the reader's own click only dissolved.
     `step()` in site.js writes `data-dir` on `.hero-top`, so forward enters
     from the left travelling right and back does the reverse.

     Gated on no-preference, unlike the fade it rides on. That fade is
     deliberately outside the gate — Reduce Motion does not ask for buttons
     to stop working, and an opacity cross-fade is what `iomin` already
     allows — but a translate IS movement, so only the travel is dropped
     there and the arrows keep working as a dissolve.

     Same cover rule as gKen: 1.08 against 2.6% is (1.08-1)/2 = 4% of
     headroom, and because scale and translate share one easing the ratio
     holds for every frame between, not just at the ends. */
  .hero-top[data-dir] .hero-media.is-manual .hero-slide.is-on {
    animation: gBannerIn 640ms var(--ease) both;
  }
  /* The words travel with the picture on an arrow too, the same way and over
     the same 640ms. They are NOT inside `.hero-media` — that subtree is
     aria-hidden decoration and these are the page's real words — so this is
     a second selector rather than a wider one. rem, as in gSay. */
  .hero-top[data-dir] .hero-say.is-on {
    animation: gSayIn 640ms var(--ease) both;
  }
  .hero-top[data-dir="prev"] .hero-say.is-on {
    animation-name: gSayBack;
  }
  .hero-top[data-dir="prev"] .hero-media.is-manual .hero-slide.is-on {
    animation-name: gBannerBack;
  }
  .hero-rule { transform-origin: 50% 50%; animation: gGrow 800ms cubic-bezier(.22,1,.36,1) 480ms both; }

  /* 3 · The header settles once you scroll: a soft shadow under the bar */
  .site-head { transition: transform 380ms var(--m-out, ease), box-shadow 300ms ease; }
  html.head-solid .site-head { box-shadow: 0 8px 24px -18px rgba(14, 26, 31, 0.45); }

  /* the FAQ answer eases in when a row opens */
  .g-faq-i[open] p { animation: gOpen 320ms ease both; }

  @supports (animation-timeline: view()) {
    /* 4 · The band icons and the check marks DRAW themselves as they arrive.
       `pathLength="1"` in the markup makes the dash maths unit-free. The
       from-state is an undrawn stroke, so if the timeline never resolves an
       icon is missing — acceptable only because every one is decorative
       (aria-hidden) and its heading carries the meaning. */
    .g-ico path, .g-check path {
      stroke-dasharray: 1; stroke-dashoffset: 0;
      animation: gDraw linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 40%;
    }

    /* 5 · The intro photograph eases out of a slight zoom as the section
       passes. It is inside `overflow: hidden`, so it borrows the section's
       `--box` timeline rather than view() (see the CLAUDE.md note). The
       from-state is a 1.14 zoom — still a visible picture. */
    .g-intro-media img {
      animation: gPar linear both;
      animation-timeline: --box;
      animation-range: entry 0% exit 100%;
    }

    /* ...and the same photograph splits open from its centre line — the
       `open` reveal from motion.css. `.g-intro` is `overflow: hidden`, so a
       `data-anim` (view()) could not resolve here; this is the same keyframe
       on the section's `--box`, over the same "start end" -> "center
       center" range. */
    .g-intro-media {
      animation: mOpen linear both;
      animation-timeline: --box;
      animation-range: entry 0% cover 50%;
    }

  }

  /* 6 · The carousel's controls. The active dot WIDENS as well as changing
     colour, so which page you are on is not carried by hue alone. Only the
     movement is gated here; the colours are named on the component itself,
     because a colour change is not motion and should survive Reduce Motion
     (the `iomin` test is "does anything move", and this does not). */
  .g-car-dot::before { transition: width 320ms var(--ease), background-color 260ms ease; }
  .g-car-btn { transition: background-color 260ms ease, color 260ms ease; }
}

/* 7 · Hover, pointer devices only */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .g-tile-img > img, .g-pj-img > img { transition: scale 700ms cubic-bezier(.22,1,.36,1); }
  .g-tile a:hover .g-tile-img > img,
  .g-pj a:hover .g-pj-img > img { scale: 1.06; }
  /* the veil's words rise into place as it fades up */
  .g-pj-t, .g-pj-rule, .g-pj-btn { transition: translate 420ms cubic-bezier(.22,1,.36,1); translate: 0 14px; }
  .g-pj-rule { transition-delay: 40ms; } .g-pj-btn { transition-delay: 80ms; }
  .g-pj a:hover .g-pj-t, .g-pj a:hover .g-pj-rule, .g-pj a:hover .g-pj-btn { translate: 0 0; }
  .g-plus { transition: opacity 260ms ease, scale 360ms cubic-bezier(.22,1,.36,1); scale: 0.85; }
  .g-tile a:hover .g-plus { scale: 1; }
  /* band columns and principle columns: the icon lifts, the link arrow runs */
  .g-col .g-ico { transition: translate 360ms cubic-bezier(.22,1,.36,1); }
  .g-col:hover .g-ico { translate: 0 -6px; }
  .g-col:hover .btn-arrow { translate: 4px 0; }
  .btn-arrow { transition: translate 260ms ease; }
  /* client logos come forward a touch */
  .client { transition: box-shadow 260ms ease, translate 260ms ease; }
  .client:hover { translate: 0 -3px; box-shadow: 0 10px 22px -16px rgba(14, 26, 31, 0.5); }
  /* why-list rows nudge right */
  .g-checks a { transition: translate 260ms ease; }
  /* room tiles: the photograph pushes in */
  .g-room img { transition: scale 700ms cubic-bezier(.22,1,.36,1); }
  .g-room a:hover img { scale: 1.06; }
  .g-checks a:hover { translate: 4px 0; }
}

/* 8 · THE SAME MOVES ON TOUCH, TRIGGERED BY SCROLL INSTEAD OF HOVER.
   Everything in block 7 is behind `(hover: hover) and (pointer: fine)`, so a
   phone sees none of it. It cannot simply be ungated: an unscoped `:hover`
   LATCHES on touch — a tapped tile stays lifted until you tap elsewhere,
   which is the whole reason the reset at the end of motion.css exists. So
   the VALUES are kept and only the TRIGGER changes; 1.06 and 4px below are
   the same numbers the pointer uses above.

   Two clipping problems, both solved the way CLAUDE.md prescribes:

   - `.g-pj-img` and `.g-tile-img` are both `overflow: hidden`, so `view()`
     cannot resolve on the photograph itself. Each borrows a named timeline
     published OUTSIDE its clip.
   - the product tiles are additionally inside the carousel's horizontal
     SCROLL CONTAINER, where a timeline does not fail loudly but FREEZES at
     whatever constant it first resolved to. They therefore take `--box`,
     which `.g-products` publishes outside the scroller — so all ten settle
     on the section's own pass, not on a frozen clock.

   Fail-safe, like the rest: every from-state here is a visible photograph at
   a slight zoom, so a timeline that never resolves leaves the picture on
   screen rather than blank.

   SIX OF THE TEN PRODUCT TILES DO NOT SETTLE, AND THAT IS CORRECT. The
   cut-out samples keep `.surface.is-cut > img { animation: none }` from
   motion.css, which outranks the rule below — a `contain` image inside
   padding has no overflow to spend, so a scale pushes the sample past its
   mount and the surface clips it. Only the four real photographs move. Do
   not raise the specificity here to "fix" the other six. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    @media (hover: none), (pointer: coarse) {

      /* The work grid is a plain grid — nothing clips `.g-pj` itself, so it
         can publish its own timeline and each project settles on its own
         pass rather than all nine together. */
      .g-pj { view-timeline-name: --pj; view-timeline-axis: block; }
      .g-pj-img > img {
        animation: gSettle linear both;
        animation-timeline: --pj;
        animation-range: entry 0% cover 45%;
      }

      .g-tile-img > img {
        animation: gSettle linear both;
        animation-timeline: --box;
        animation-range: entry 0% cover 50%;
      }

      .g-checks a {
        animation: gNudge linear both;
        animation-timeline: view();
        animation-range: entry 10% cover 35%;
      }
    }
  }
}

/* OUR VISION — a centred statement between two bands */
.g-vision { text-align: center; padding-block: clamp(4.5rem, 8vw, 7rem); }
.g-vision-lead {
  max-width: 34ch; margin: 1.75rem auto 0;
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem); font-weight: 600;
  letter-spacing: -0.025em; line-height: 1.35; color: #1A1A1A;
}
.g-vision-more { max-width: 62ch; margin: 1.25rem auto 0; }

/* OUR CORE STRENGTH (10 Oct 2026, tightened the same day: "it has become
   lengthy"). Heading beside the opening paragraph; the four stages as a
   one-line numbered strip; the other two paragraphs side by side, with the
   three aims as a single row. Left-aligned, unlike the centred vision band. */
.g-capacity { padding-block: clamp(3.5rem, 6vw, 5rem); }
.g-cap-top { display: grid; gap: 1.25rem clamp(2rem, 5vw, 4.5rem); }
@media (min-width: 60rem) { .g-cap-top { grid-template-columns: 1.1fr 1fr; align-items: end; } }
.g-capacity h2 {
  max-width: 24ch; margin: 1.25rem 0 0;
  font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.4rem);
}
.g-cap-lead {
  margin: 0; color: #303030;
  font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem); line-height: 1.7;
}

/* the four stages: one compact line each, joined by the accent rule */
.g-cap-steps {
  list-style: none; margin: clamp(2rem, 4vw, 2.75rem) 0 0; padding: 0;
  display: grid; gap: 0.5rem; grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 48rem) { .g-cap-steps { grid-template-columns: repeat(4, 1fr); gap: 0; } }
.g-cap-steps li {
  display: flex; align-items: baseline; gap: 0.75rem;
  padding: 0.95rem 1.1rem; background: #FFFFFF; border-top: 3px solid var(--accent);
}
@media (min-width: 48rem) { .g-cap-steps li + li { border-left: 1px solid #E3E8EB; } }
.g-cap-n {
  font-size: 1.375rem; font-weight: 800; letter-spacing: -0.03em; line-height: 1;
  color: var(--brand); font-variant-numeric: tabular-nums;
}
.g-cap-steps b { font-size: 0.9375rem; font-weight: 600; color: #1A1A1A; }

/* the other two paragraphs, side by side */
.g-cap-cols { display: grid; gap: 1.25rem clamp(2rem, 5vw, 4.5rem); margin-top: clamp(1.5rem, 3vw, 2rem); }
@media (min-width: 60rem) { .g-cap-cols { grid-template-columns: 1fr 1fr; } }
.g-cap-cols p { margin: 0; }
.g-cap-aims { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; }
.g-cap-aims li { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.9375rem; color: #1A1A1A; }
.g-cap-aims .g-check { margin-top: 0; }


/* ==========================================================================
   PAGE TRANSITION — the curtain wipe (by request: Motion+'s
   `curtains(..., { effect: wipe({ direction: 'left', angle: 12 }) })`).

   A cyan curtain, sheared 12deg, sweeps LEFT across the screen when a link
   is followed; the next page opens under it and the curtain carries on
   leftwards to uncover it. One continuous leftward pass split across two
   documents. It is `html::after`, so it exists from the first paint of the
   new page with no markup, and it is driven by two classes:
     html.curtain-out  — set by site.js on click; covers, then navigates
     html.curtain-in   — set by HEAD_BOOT when arriving via a curtain;
                         uncovers, and is REMOVED after 1.4s regardless
   FAIL-SAFE: with neither class the pseudo does not exist. If script fails
   nothing is ever covered; if the reveal animation somehow does not run,
   the timer still strips `curtain-in` and the page shows. Reduced Motion:
   neither class is ever set (checked in both scripts), and the block is
   gated here too.

   (A cross-document View Transition was tried first. It only runs in
   Chrome/Edge 126+ and Safari 18.2+ and is skipped whenever the OS asks for
   reduced motion, which is why it was reported as "not working".)
   ========================================================================== */
@keyframes gCurtainOut { from { transform: skewX(-12deg) translateX(105%); } to { transform: skewX(-12deg) translateX(0); } }
@keyframes gCurtainIn  { from { transform: skewX(-12deg) translateX(0); }   to { transform: skewX(-12deg) translateX(-105%); } }
@media (prefers-reduced-motion: no-preference) {
  html.curtain-out::after, html.curtain-in::after {
    content: ''; position: fixed; z-index: 2147483000; pointer-events: none;
    top: -5vh; bottom: -5vh; left: -25vw; right: -25vw;
    background:
      linear-gradient(90deg, #08597A 0, #08597A 1.25rem, #1CABDE 1.25rem);
    will-change: transform;
  }
  html.curtain-out::after { animation: gCurtainOut 520ms cubic-bezier(0.65, 0, 0.35, 1) both; }
  html.curtain-in::after  { animation: gCurtainIn  620ms cubic-bezier(0.65, 0, 0.35, 1) 60ms both; }
}

/* ==========================================================================
   STICKY REVEAL FOOTER (by request, after Motion's "Footer: Sticky reveal" —
   a sticky under-page footer that fades and scales in as scroll uncovers
   it). Built here, not installed: that is a React/shadcn component and this
   is a static site.

   How: the footer is `position: sticky; bottom: 0` UNDER `main`, and `main`
   is an opaque layer above it — so the footer is always there, hidden, and
   the page lifts off it as you reach the end. Its contents scale and fade
   in over exactly the distance that uncovers it: `main` publishes `--main`,
   and the range ends at `--foot-end`, the footer's height as a share of the
   viewport, which site.js measures.

   ONLY under `html.foot-reveal`, which site.js sets only while the footer
   fits in 85% of the viewport. A footer taller than the screen (phones)
   would have its top permanently cut off by a sticky bottom edge, so there
   it stays an ordinary footer. No script: ordinary footer.
   ========================================================================== */
html.foot-reveal main {
  position: relative; z-index: 1;
  background: #F3F4F7;
  box-shadow: 0 28px 44px -26px rgba(14, 26, 31, 0.42);
}
html.foot-reveal .site-foot { position: sticky; bottom: 0; z-index: 0; }
@keyframes gFootIn {
  from { opacity: 0.25; scale: 0.94; translate: 0 48px; }
  to   { opacity: 1;    scale: 1;    translate: 0 0; }
}
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* `--main` is published by <main> but read by the footer, which is its
       SIBLING. A named timeline is only visible to the publisher's own
       descendants, so without `timeline-scope` on the shared parent the
       footer finds no timeline and the animation simply finishes (measured:
       timeline null, progress 1). Hoisting the name to <body> fixes it. */
    html.foot-reveal body { timeline-scope: --main; }
    html.foot-reveal main { view-timeline-name: --main; }
    html.foot-reveal .site-foot > .wrap {
      transform-origin: 50% 100%;
      animation: gFootIn linear both;
      animation-timeline: --main;
      animation-range: exit 0% exit var(--foot-end, 60%);
    }
  }
}
@media print {
  html.foot-reveal main { box-shadow: none; }
  html.foot-reveal .site-foot { position: static; }
}

/* ==========================================================================
   STICKY REVEAL BANNER — the footer's reveal, mirrored onto the homepage
   hero (by request). The banner is `position: sticky; top: 0` and every
   section after it is an opaque layer above it, so the page slides UP OVER
   the banner instead of the banner scrolling away. As it is covered the
   banner recedes: `.hero-top` scales down a touch (the darkening veil that
   went with it was removed with the scrim, by request), over the first `--hero-h` of scroll (its own height, set by site.js).

   `scale` (the individual property) on `.hero-top`, so it composes with
   mDrift's `transform`/opacity on the copy, mWall's on the <img> and gKen's
   `scale` on each slide — four animations, four different elements or
   properties.

   ONLY under `html.hero-reveal`, which site.js sets only while the banner
   fits the viewport. A sticky banner taller than the screen would never
   show its lower part — the headline button included — so there it is an
   ordinary banner. No script: ordinary banner.
   ========================================================================== */
html.hero-reveal .hero { position: sticky; top: 0; z-index: 0; }
html.hero-reveal main > :not(.hero) { position: relative; z-index: 1; }
html.hero-reveal .hero + * { box-shadow: 0 -28px 44px -26px rgba(14, 26, 31, 0.5); }
@keyframes gHeroBack { to { scale: 0.93; } }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    html.hero-reveal .hero-top {
      transform-origin: 50% 30%;
      animation: gHeroBack linear both;
      animation-timeline: scroll(root block);
      animation-range: 0px var(--hero-h, 90vh);
    }
    /* The darkening veil (gHeroDim) was removed with the scrim, by request:
       the photograph keeps its own colour while it is being covered too. */
  }
}
@media print { html.hero-reveal .hero { position: static; } }

/* About · founder statement: all three paragraphs at one size and colour */
.g-8-4 .founder-p {
  font-size: var(--t-lead); line-height: 1.75; color: #3F454A;
  max-width: 74ch; margin-top: 1.1rem;
}

/* ==========================================================================
   THE STRONG BAND IS GREY (by request, 30 Sep 2026: "change the background
   color to grey"). `--night` was the logo cyan #1CABDE; it is a charcoal
   #303438 now, so the counter strip, the three-column band, the product
   intros, the family "see all" card and the footer all read grey.

   THE CONSEQUENCE THAT GOVERNS THIS WHOLE BLOCK IS THE REVERSE OF THE CYAN
   ONE ABOVE: on cyan every word had to be DARK (white was 2.65:1); on
   charcoal every word is LIGHT again, and the ~30 rules that flipped the
   band to dark ink have to flip back or they put near-black on near-black.
     (1 Oct 2026: #262626, read off a screenshot the client supplied as
     "this exact same color"; it replaced #303438)
     #FFFFFF  on #262626  15.1:1   headings, figures, links
     #C4C9CE  on #262626   9.1:1   body and labels
     #1CABDE  on #262626   5.7:1   eyebrows, rules, icons, step numerals
   THE CYAN IS NOT GONE — it is the accent again, which is what it was before
   it became a ground: the topbar, the rule under every eyebrow, the icons,
   and the button fills on both grounds.

   TWO THINGS DELIBERATELY DO NOT FOLLOW `--night`:
   · THE HEADER STAYS WHITE (`--night-2`). The lockup is cyan artwork with
     near-black type, so it needs a light ground — that is why the footer
     mounts it on a white plate. But a white header reading `--on-night` for
     its link colour now means WHITE ON WHITE, so every header colour that
     used to come from that token is restated below as dark ink. Point
     `--night-2` at `--night` and the nav disappears.
   · `.cta-band.g-tint` (#D6F0F9) is a light tint of the accent, not the
     strong band, and closes every inner page. It is untouched.
   ========================================================================== */
:root {
  --night:         #262626;          /* the strong band — matched to the client's sample 1 Oct 2026, was #303438 */
  --night-2:       #FFFFFF;          /* header — STAYS LIGHT, see above */
  --night-3:       #303030;          /* raised object on the band */
  --on-night:      #FFFFFF;
  --on-night-mute: #C4C9CE;
  --night-edge:    rgba(255, 255, 255, 0.16);
}

/* header: white ground, so it stops reading --on-night / --night-edge */
.site-head, html.head-solid .site-head {
  background: #FFFFFF; color: #14181B; border-bottom-color: #E3E6EA;
}
.nav a { color: #14181B; }
.nav a:hover, .nav a[aria-current="page"] { color: var(--brand); }
.nav a.btn-primary { background: var(--accent); border-color: var(--accent); color: #14181B; }
.nav a.btn-primary:hover { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }
.nav-toggle { border-color: #C8CDD3; color: #14181B; }
@media (max-width: 60rem) {
  .nav { background: #FFFFFF; border-bottom-color: #E3E6EA; }
  .nav a { border-bottom-color: #E3E6EA; color: #14181B; }
}

/* buttons on LIGHT grounds keep the logo cyan — the fill used to come from
   --night, so without this they would all have turned charcoal */
.btn-primary { background: var(--accent); border-color: var(--accent); color: #14181B; }
.btn-primary:hover { background: var(--brand); border-color: var(--brand); color: #FFFFFF; }

/* buttons on the GREY band: cyan fill / dark label, hovering to white.
   The ghost's ::before fills white, so its label goes dark on hover. */
.night .btn-primary { background: var(--accent); border-color: var(--accent); color: #14181B; }
.night .btn-primary:hover { background: #FFFFFF; border-color: #FFFFFF; color: #14181B; }
.night .btn-ghost { border-color: rgba(255, 255, 255, 0.7); color: #FFFFFF; }
.night .btn-ghost::before { background: #FFFFFF; }
.night .btn-ghost:hover { border-color: #FFFFFF; color: #14181B; }

/* the accent marks come back off the ink and onto the band */
.night .eyebrow { color: var(--accent); }
.night .eyebrow::before { background: var(--accent); }
.night .tlink, .night .g-ico, .night .g-check, .night .spec caption { color: var(--accent); }
.night .steps-spine::after { background: var(--accent); }
.night .step::before, .night .step::after { color: var(--accent); }
/* THE NRC BAR KEEPS THE LIGHT TRACK IT HAS ON EVERY OTHER PAGE. The dark
   design gave `.night .nrc-track` a RAISED DARK track (--night-3), which is
   fine for a white fill and wrong for this one: the fill is the product's
   own material colour, and those are mid-to-dark. Acoustic foam is #33383C
   and would have drawn on #3B4045 at 1.13:1 — an invisible bar understating
   a published absorption figure, which is the one fail-UNSAFE failure this
   component has. Falling back to the paper track makes every bar on the
   site identical: foam 9.6:1, wood 4.0:1, PET 3.1:1 against it. */
.night .nrc-track { background: var(--paper-lo); border-color: var(--paper-edge); }
.night .nrc-track::after { background: var(--paper-edge); }
.fam-all .card-foot { color: var(--accent); }

/* hover veils over photographs go back to the near-black wash they were
   built for — the comment beside .g-pj-over sets 0.82 so white 1.25rem type
   holds >=5:1 even where the photograph underneath is white */
.g-pj-over { background: rgba(26, 26, 26, 0.82); color: #FFFFFF; }
.g-pj-rule { background: #FFFFFF; }
.g-pj-btn  { background: #FFFFFF; color: #14181B; }
.g-plus    { background: rgba(26, 26, 26, 0.55); color: #FFFFFF; }

/* Footer · Certified column — the two certificate badges, each on a white
   plate (their artwork is drawn on white) like the logo above them. */
@media (min-width: 56rem) {
  .foot-grid { grid-template-columns: 1fr 1fr 0.8fr 1.3fr 1.05fr; }
}
.foot-certs { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.75rem; }
@media (max-width: 55.999rem) { .foot-certs { grid-template-columns: repeat(2, minmax(0, 11rem)); } }
.foot-certs li { background: #FFFFFF; padding: 0.4rem; }
/* flex, not grid: in a grid the implicit row is auto-sized, so the image's
   max-height: 100% had nothing definite to resolve against and the round
   badge spilled out of its plate on phones. */
.foot-certs li {
  display: flex; align-items: center; justify-content: center;
  height: 7.5rem; box-sizing: border-box;
}
.foot-certs img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; }

/* ==========================================================================
   TOUCH EQUIVALENTS, PART 2 (mobile audit, 1 Oct 2026).
   Block 8 above gave touch the photo settle and the why-list nudge. Two
   pointer-only moves were still missing on phones:
   · the band-column icon lift — now the icon rises into place as its column
     scrolls in (same 6px the hover uses, plus a little), on view();
   · press feedback — a tapped tile, project or button visibly gives. It is
     `:active`, which ends with the touch, so it cannot latch the way an
     unscoped `:hover` does. iOS Safari only applies `:active` once the page
     has a touchstart listener; site.js registers an empty passive one.
   It dims rather than scales: the photographs already animate `scale` on
   touch (block 8), and an animation outranks a state rule on that property.
   The hover veils (`.g-pj-over`, `.g-plus`) stay pointer-only on purpose —
   on a phone a tap opens the photograph or the page immediately, so a veil
   could only ever flash.
   ========================================================================== */
@keyframes gLift { from { translate: 0 12px; } to { translate: 0 0; } }
@media (hover: none), (pointer: coarse) {
  .g-tile a, .g-pj a, .g-col, .btn, .card, .client, .g-car-btn, .float, .filter {
    -webkit-tap-highlight-color: transparent;
  }
  /* Only elements with no transition of their own get one here. The buttons
     restate theirs so opacity JOINS the colour transitions rather than
     replacing them; .float and .card are left alone entirely, because their
     own transitions run the show/hide and the shadow. */
  .g-tile a, .g-pj a, a.g-col { transition: opacity 140ms ease; }
  .btn { transition: background-color 220ms ease, border-color 220ms ease, color 220ms ease, opacity 140ms ease; }
  .g-tile a:active, .g-pj a:active, a.g-col:active, a.card:active { opacity: 0.82; }
  .btn:active, .g-car-btn:active, .float:active, .filter:active { opacity: 0.78; }
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      .g-col .g-ico {
        animation: gLift linear both;
        animation-timeline: view();
        animation-range: entry 10% cover 35%;
      }
    }
  }
}

/* The footer logo was removed by request (1 Oct 2026). site.css still spans
   the footer's FIRST column full width on phones — a rule written for the
   logo — so without this "Products" would take that slot alone. */
.foot-grid > div:first-child { grid-column: auto; }

/* WHATSAPP CHAT BUTTON (by request, 1 Oct 2026): the familiar green disc
   with a white pictogram, bottom right, and ALWAYS shown — not revealed on
   scroll like back-to-top, because a chat button that is missing on the
   first screen is the one most people never find. White on #25D366 is a
   pictogram, not text; it is ringed in white so it still reads over the
   green-adjacent cyan of buttons and the photographs behind it. */
.float-wa,
.floats .float-wa {
  width: 3.5rem; height: 3.5rem; border-radius: 50%;
  background: #25D366; color: #FFFFFF; border: 2px solid #FFFFFF;
  box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.45);
  opacity: 1; visibility: visible; transform: none;
}
.float-wa svg { width: 1.9rem; height: 1.9rem; }
.float-wa:hover, .floats .float-wa:hover { background: #1EBE5A; color: #FFFFFF; }

/* The WhatsApp button is always on and sits over the bottom-right corner,
   so the bar keeps clear space below it the height of that button plus
   its margin. (The credit and "Mumbai, India" were removed from the bar
   on 7 Oct 2026; the copyright line remains.) */
/* footer credit line: sentence case, not the bar's tracked caps, and the
   link underlined so it reads as a link on the charcoal. */
.foot-credit { letter-spacing: 0.02em; text-transform: none; }
.foot-credit a { color: #FFFFFF; text-decoration: underline; text-underline-offset: 3px; }
.foot-credit a:hover { color: var(--accent); }
.site-foot .foot-bar { padding-bottom: calc(3.5rem + clamp(1rem, 3vw, 1.75rem) + env(safe-area-inset-bottom, 0px)); }

/* ==========================================================================
   7 OCT 2026 CHANGE LIST
   ========================================================================== */

/* LOGO, larger (asked for). The header lost its "Book a survey" button in
   the same list, which is what makes the room. The phone step stays small
   enough that the menu button never gets pushed off a 320px screen (the
   mobile audit note in CLAUDE.md: re-check scrollWidth at 320). */
.brand-logo { height: 3.4rem; }
@media (max-width: 60rem) { .brand-logo { height: 2.9rem; } }
@media (max-width: 30rem) { .brand-logo { height: 2.3rem; } }

/* MENU TEXT ROLL (asked for: "add text animation on menu"). Each label sits
   in a one-line window with a copy of itself underneath; on hover the pair
   rolls up, so the word appears to flip over. It animates the individual
   `translate` property, never `transform`, and only for a fine pointer, so
   it cannot latch on touch. The menu items also drop in one after another
   when the page loads. Both sit out Reduce Motion. */
.nav-roll { display: inline-grid; overflow: hidden; vertical-align: top; line-height: 1.25; }
.nav-roll .nav-t { grid-area: 1 / 1; }
.nav-roll .nav-t + .nav-t { translate: 0 105%; }
@media (prefers-reduced-motion: no-preference) {
  .nav-roll .nav-t { transition: translate 420ms cubic-bezier(.22, 1, .36, 1); }
  @media (hover: hover) and (pointer: fine) {
    .nav a:hover .nav-t { translate: 0 -105%; }
    .nav a:hover .nav-t + .nav-t { translate: 0 0; }
  }
  @keyframes gNavIn { from { opacity: 0; translate: 0 -0.6rem; } }
  @media (min-width: 60.0625rem) {
    .nav > a { animation: gNavIn 560ms cubic-bezier(.22, 1, .36, 1) both; }
    .nav > a:nth-child(2) { animation-delay: 70ms; }
    .nav > a:nth-child(3) { animation-delay: 140ms; }
    .nav > a:nth-child(4) { animation-delay: 210ms; }
    .nav > a:nth-child(5) { animation-delay: 280ms; }
    .nav > a:nth-child(6) { animation-delay: 350ms; }
  }
}

/* PRODUCT DETAIL: the way back to the catalogue, above the eyebrow.
   A labelled button, "Back to Products Page", not an arrow link (by
   request, 10 Oct 2026). Outlined in white on the charcoal intro; fills
   white with dark ink on hover — colour only, so it cannot latch on touch. */
.pd-back {
  display: inline-flex; align-items: center; min-height: 2.75rem;
  padding: 0.7rem 1.25rem; margin-bottom: 1.75rem;
  border: 1px solid rgba(255, 255, 255, 0.55);
  font-size: 0.8125rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-night); text-decoration: none;
  transition: background-color 220ms ease, color 220ms ease, border-color 220ms ease;
}
.pd-back:hover { background: #FFFFFF; border-color: #FFFFFF; color: #14181B; }
.pd-back:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/* PRODUCTS PAGE: centred heading over centred family filters. */
.filters.center-row { justify-content: center; }
.g-work-head h1 { font-size: var(--t-h1); }

/* CONTACT: the estimate calculator. */
.g-calc-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 60rem) { .g-calc-grid { grid-template-columns: 1fr 1.25fr; } }
.g-calc-intro h2 { margin-top: 0.25rem; }
.g-calc-intro .muted { margin-top: 1.25rem; max-width: 42ch; }
.g-calc-form {
  background: #FFFFFF; padding: clamp(1.5rem, 4vw, 2.5rem);
  display: grid; gap: 1.5rem; border-top: 3px solid var(--accent);
}
.g-calc-sub { margin-top: -0.75rem; font-size: 0.9375rem; }
.g-calc-set { border: 0; margin: 0; padding: 0; min-width: 0; }
.g-calc-set legend {
  padding: 0; margin-bottom: 0.85rem; font-size: 0.8125rem; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: #1A1A1A;
}
.g-calc-opts { display: grid; gap: 0.5rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.g-calc-opt { display: flex; align-items: center; gap: 0.7rem; padding: 0.8rem 1rem; border: 1px solid #DDE1E5; cursor: pointer; font-size: 0.9375rem; color: #1A1A1A; transition: border-color 200ms ease, background-color 200ms ease; }
.g-calc-opt input { accent-color: var(--brand); width: 1.1rem; height: 1.1rem; margin: 0; flex: none; }
.g-calc-opt:has(input:checked) { border-color: var(--brand); background: #EEF7FB; }
.g-calc-opt:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.g-calc-dims { display: grid; gap: 0.75rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 30rem) { .g-calc-dims { grid-template-columns: 1fr; } }
.g-calc-form .btn { justify-self: start; }
.g-calc-out { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
.g-calc-out[hidden] { display: none; }
.g-calc-res { background: #F3F4F7; padding: 1.1rem 1.25rem; display: grid; gap: 0.3rem; }
.g-calc-res span { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #3F454A; }
.g-calc-res b { font-size: clamp(1.5rem, 1.2rem + 1vw, 1.9rem); font-weight: 700; color: #1A1A1A; letter-spacing: -0.01em; }
.g-calc-res small { font-size: 0.8125rem; color: #3F454A; line-height: 1.45; }
.g-calc-note { font-size: 0.8125rem; line-height: 1.55; color: #3F454A; }
.g-calc-note b { color: #1A1A1A; }
/* A filtered-out product card: `.card` sets its own display, which beats
   the [hidden] attribute's, so the filter has to say it outright. */
#gal > [hidden] { display: none !important; }
/* The homepage carousel carries all eighteen products now (the client's
   sequence), so on a phone it has nine pages: nine dots plus three buttons
   no longer fit one 320px row. Below 40rem the dots take a row of their own
   above the buttons. */
@media (max-width: 40rem) {
  .g-car-ctl { flex-wrap: wrap; row-gap: 0.25rem; }
  .g-car-dots { order: -1; flex-basis: 100%; justify-content: center; flex-wrap: wrap; }
}
/* Contact (9 Oct 2026): the calculator runs alone at the top, so its card
   takes a centred measure instead of the right half of a two-up grid; its
   heading and lead sit under the enquiry form instead (.g-calc-after). */
.g-calc-grid.is-solo { grid-template-columns: minmax(0, 52rem); justify-content: center; }
.g-calc-after { margin-top: clamp(2rem, 4vw, 3rem); padding-top: clamp(1.5rem, 3vw, 2rem); border-top: 1px solid #DDE1E5; }
.g-calc-after .muted { margin-top: 0.85rem; max-width: 52ch; }
