/* site.css — per-site layout & components, built FROM tokens.
   HARD RULE (spec §3): no raw hex/size/font literals in this file. Tokens
   only; escape hatches are the house breakpoints and `@allow-literal`. */

/* --- Layout primitives ---------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--layout-container);
  margin-inline: auto;
  padding-inline: var(--layout-gutter);
}

/* Vertical rhythm hook. --section-gap is the only inter-section spacing value
   on a site (design-rules §7.1); the brief picks which scale step it is. */
.section {
  padding-block: var(--section-gap);
}

.measure {
  max-width: var(--layout-measure);
}

/* --- Header & navigation --------------------------------------------------
   Progressive enhancement: with JS off the nav list is a plain, always-visible
   list of links and the toggle stays [hidden] (it ships hidden in the HTML;
   main.js reveals it). With JS on, narrow viewports collapse the list behind
   the toggle. Nothing about navigation depends on JavaScript.              */

.site-header {
  position: relative;
  z-index: var(--z-header);
  padding-block: var(--space-sm);
  border-bottom: var(--border-hairline) var(--border-style) var(--color-border);
  background-color: var(--color-bg);
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.site-header__brand {
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: var(--font-weight-display);
  text-decoration: none;
  letter-spacing: var(--tracking-tight);
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: var(--space-2xs) var(--space-xs);
  /* The nav-toggle's only shape is this hairline, so it is a CONTROL boundary
     (WCAG 1.4.11, >= 3:1), not a decorative one — use --color-border-strong. */
  border: var(--border-hairline) var(--border-style) var(--color-border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-small);
  font-weight: var(--font-weight-body-strong);
}

.site-nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav__link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  font-size: var(--text-small);
  font-weight: var(--font-weight-body-strong);
}

.site-nav__link:hover,
.site-nav__link:focus-visible {
  text-decoration: underline;
  text-underline-offset: var(--space-3xs);
}

/* House default (QA r3 perf/cls fix, house-wide): closed below 48em with no
   JS gate, so there is no post-paint collapse race. JS-off visitors get the
   nav back via the <noscript> override in @shared:head-boilerplate. */
.site-nav {
  display: none;
}

[data-nav-ready] .site-nav[data-nav-open='true'] {
  display: block;
  flex-basis: 100%;
}

@media (min-width: 48em) {
  .site-nav {
    display: block;
  }

  [data-nav-ready] .nav-toggle {
    display: none;
  }
}

/* --- Buttons --------------------------------------------------------------
   Two roles, no more. Radius, weight and colour all come from the brief via
   tokens — including --radius-none, which is a legitimate answer.          */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding: var(--space-xs) var(--space-md);
  border: var(--border-hairline) var(--border-style) transparent;
  border-radius: var(--radius-sm);
  font-weight: var(--font-weight-body-strong);
  line-height: var(--leading-snug);
  text-align: center;
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.btn--primary {
  background-color: var(--color-accent);
  color: var(--color-accent-ink);
}

.btn--quiet {
  /* Outline button: the border is the whole control, so it takes the 3:1
     control-boundary token, not the decorative hairline (WCAG 1.4.11). */
  border-color: var(--color-border-strong);
  color: var(--color-ink);
}

/* --- Form -----------------------------------------------------------------
   One form per site (spec §6). POSTs to the endpoint constant in main.js;
   works as a plain HTML POST with JS off; auto-hides when no endpoint is
   configured so a spec site never shows a dead form.                       */

.form {
  display: grid;
  gap: var(--space-md);
  max-width: var(--layout-measure);
}

.field {
  display: grid;
  gap: var(--space-3xs);
}

.field__label {
  font-size: var(--text-small);
  font-weight: var(--font-weight-body-strong);
}

.field__control {
  width: 100%;
  min-height: var(--layout-tap-min);
  padding: var(--space-2xs) var(--space-xs);
  background-color: var(--color-surface);
  color: var(--color-ink);
  border: var(--border-hairline) var(--border-style) var(--color-border-strong);
  border-radius: var(--radius-sm);
}

textarea.field__control {
  min-height: var(--space-3xl);
  resize: vertical;
}

.field__hint {
  font-size: var(--text-small);
  color: var(--color-ink-muted);
}

/* Honeypot: present in the DOM for bots, unreachable for humans and assistive
   tech. Not display:none — some bots skip those. */
.form__trap {
  position: absolute;
  /* @allow-literal: off-canvas parking position, not a design value */
  left: -9999px;
  opacity: 0;
  pointer-events: none;
}

.form__status {
  font-size: var(--text-small);
  color: var(--color-ink-muted);
}

.form__status[data-state='error'] {
  color: var(--color-ink);
  font-weight: var(--font-weight-body-strong);
}

/* --- Footer ---------------------------------------------------------------- */

.site-footer {
  padding-block: var(--space-xl);
  border-top: var(--border-hairline) var(--border-style) var(--color-border);
  font-size: var(--text-small);
}

.site-footer a {
  text-underline-offset: var(--space-3xs);
}

.site-footer__nap {
  font-style: normal;
  line-height: var(--leading-body);
}


/* INSIDE OUT PAINTING — brief.md components, archetype `swatch-stack`. The
   settled seam head-rule is the ONE repeated ornament (brief §12): a 2px
   kiva-turquoise line heading Services, Index of Care, Service area only —
   never the CTA-repeat band or the quote form. */

.section--services,
.section--care,
.section--service-area {
  border-block-start: var(--border-seam) var(--border-style) var(--color-accent);
}


/* --- Header — phone control OUTSIDE the collapsible nav (design-rules
   §7.3 rule 6, brief §6 row 1).

   QA r3 FIX: r2's nowrap+ellipsis truncated "Inside Out Painting" at
   320-440px (brief §6 item 1 — the full wordmark must render). Below
   48em brand+call+toggle never fit one line at these widths, so the
   brand gets its own row by WIDTH (`flex-basis: 100%`; base `.inner`
   is already `wrap`) — a media query, not nav/JS state, so the toggle's
   post-paint reveal never changes which row wraps. Zero CLS.

   `.controls`' own `:has()` (unchanged) still gives the open nav its
   full-width row; JS-off's <noscript> override is untouched too. */
@media (max-width: 47.9375em) {
  .site-header__brand {
    flex-basis: 100%;
  }

  .site-header__controls {
    margin-inline-start: auto;
  }
}

.site-header__controls {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-sm);
}

.site-header__controls:has(.site-nav[data-nav-open='true']) {
  flex-wrap: wrap;
  flex-basis: 100%;
}


/* --- Hero — swatch-stack (brief §6, §7, §9, §10). Mobile single column /
   44%-1fr desktop grid. House order: eyebrow -> h1 -> CTA -> field -> lead
   -> facts (brief §7 rule 1). The field is the living-hero moment: a
   cut-in-edge photo, a one-shot clip-path settle, a looping seam-glint, and
   three independently drifting swatch chips (brief §10). ------------------- */

.hero {
  padding-block-start: var(--space-xl);
  padding-block-end: var(--section-gap);
}

.hero__inner {
  display: grid;
  gap: var(--space-md);
}

.hero__eyebrow {
  margin: 0;
  color: var(--color-accent-text);
  font-size: var(--text-eyebrow);
  font-weight: var(--font-weight-body-strong);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.hero__headline {
  margin: 0;
  font-size: var(--text-h1);
  font-weight: var(--font-weight-display);
}

.hero__cta-row {
  margin: 0;
}

.hero__lead {
  margin: 0;
  max-width: var(--layout-measure);
  font-size: var(--text-lead);
  line-height: var(--leading-body);
  color: var(--color-ink-muted);
}

.hero__facts {
  margin: 0;
  color: var(--color-ink-muted);
  font-size: var(--text-small);
}

/* Decorative dot, CSS-generated so each fact's text node matches its
   copy.md value exactly (qa-DoD §5a). */
.hero__fact-phone::before {
  content: '\00B7';
  margin-inline-end: var(--space-2xs);
}

/* The living-hero field itself (brief §9's "the .visual box", §7's "field").
   Zero text nodes inside it at any width (brief §4) — the atmosphere and the
   coat plane carry no copy, so no contrast pairing against the gradient is
   ever needed. */
.hero__visual {
  position: relative;
  height: var(--hero-field-height);
  overflow: hidden;
  background: radial-gradient(ellipse at 30% 20%, var(--color-atmosphere-hi) 0%, var(--color-bg) 55%, var(--color-atmosphere-lo) 100%);
}

.hero__visual-picture,
.hero__visual-img {
  display: block;
  width: 100%;
  height: 100%;
}

.hero__visual-img {
  object-fit: cover;
  object-position: center;
}

/* The one-shot "cut-in settles" reveal (brief §10 point 1): the coat
   photograph's own clip-path animates once from a thin sliver at the seam
   to its final, fully-revealed shape. Rest state / reduced motion: the
   final clip-path applied directly, no animation (brief §10's reduced-
   motion statement — "renders directly at its final clip-path"). */
.hero__visual-img {
  clip-path: inset(0 0 0 0);
  animation: hero-settle var(--duration-settle) var(--ease-out) 1 both;
}

@keyframes hero-settle {
  0%   { clip-path: inset(0 0 0 calc(100% - var(--seam-x-sm))); }
  100% { clip-path: inset(0 0 0 0); }
}

/* Ambient satellite 1 — the specular seam-glint, a thin bright band that
   travels the seam's own position top-to-bottom on a continuous,
   independent loop (brief §10 point 2). Rest state / reduced motion: held
   at a fixed opacity, no travel. */
.hero__seam-glint {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--seam-x-sm);
  width: var(--space-sm);
  background: linear-gradient(180deg, transparent 0%, var(--color-accent-ink) 50%, transparent 100%);
  opacity: 0.3;
  mix-blend-mode: soft-light;
  pointer-events: none;
  animation: hero-glint var(--duration-glint) var(--ease-in-out) infinite;
}

@keyframes hero-glint {
  0%   { transform: translateY(-120%); }
  50%  { transform: translateY(120%); }
  100% { transform: translateY(-120%); }
}

/* Ambient satellite 2 — the three drifting swatch chips (brief §10 point
   2): turquoise, chamisa-gold, bone, each on an independent, non-synced
   loop (7.1s / 8.6s / 6.4s) so they never lock into a visible combined
   cycle (design-rules §0). Positioned around the field's own edges, never
   inside its clipped region, and never carrying text (decorative only). */
.hero__chips {
  position: relative;
}

.hero__chip {
  position: absolute;
  inline-size: var(--swatch-chip-size);
  block-size: var(--swatch-chip-size);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  pointer-events: none;
}

.hero__chip--1 {
  background-color: var(--color-accent);
  inset-inline-start: 8%;
  inset-block-end: calc(100% - var(--space-sm));
  animation: hero-chip-1 var(--duration-chip-1) var(--ease-in-out) infinite;
}

.hero__chip--2 {
  background-color: var(--color-swatch-gold);
  inset-inline-end: 12%;
  inset-block-end: calc(100% - var(--space-lg));
  animation: hero-chip-2 var(--duration-chip-2) var(--ease-in-out) infinite;
}

.hero__chip--3 {
  background-color: var(--color-bg);
  border: var(--border-hairline) var(--border-style) var(--color-border-strong);
  inset-inline-end: 22%;
  inset-block-end: calc(100% - var(--space-xs));
  animation: hero-chip-3 var(--duration-chip-3) var(--ease-in-out) infinite;
}

@keyframes hero-chip-1 {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(var(--chip-1-drift-x), var(--chip-1-drift-y)); }
}

@keyframes hero-chip-2 {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(var(--chip-2-drift-x), var(--chip-2-drift-y)); }
}

@keyframes hero-chip-3 {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(var(--chip-3-drift-x), var(--chip-3-drift-y)); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__visual-img {
    animation: none;
    clip-path: inset(0 0 0 0);
  }

  .hero__seam-glint {
    animation: none;
    transform: translateY(0);
    opacity: 0.3;
  }

  .hero__chip--1,
  .hero__chip--2,
  .hero__chip--3 {
    animation: none;
    transform: none;
  }
}

/* No extra text-column wrapper: DOM order stays eyebrow->h1->CTA->field->
   lead->facts (brief §7 rule 1) at every width. At >=64em the field alone
   gets an explicit placement (column 2, spanning every row); auto-placement
   funnels every other child into column 1, since column 2 is already taken
   on every row they could reach. */
@media (min-width: 64em) {
  .hero__inner {
    grid-template-columns: 44% 1fr;
    column-gap: var(--space-lg);
    align-items: center;
  }

  .hero__inner > :not(.hero__visual-wrap) {
    max-width: var(--hero-col-measure);
  }

  .hero__visual-wrap {
    grid-column: 2;
    grid-row: 1 / -1;
  }

  .hero__visual {
    height: var(--hero-field-height-lg);
  }

  .hero__seam-glint {
    inset-inline-start: var(--seam-x);
  }

  @keyframes hero-settle {
    0%   { clip-path: inset(0 0 0 calc(100% - var(--seam-x))); }
    100% { clip-path: inset(0 0 0 0); }
  }
}


/* --- Services (brief §6 row 3, §8) — plain list, never a card grid
   (design-rules §4). Two sub-columns at desktop. Slot-2 image sits behind
   the heading at low opacity, decorative; text sits on flat --color-bg. --- */

.services__inner {
  position: relative;
  max-width: var(--measure-services);
  margin-inline: auto;
}

.services__bg {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  opacity: 0.08;
  pointer-events: none;
}

.services__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.services__content {
  position: relative;
  z-index: 1;
}

.services__list {
  margin-block-start: var(--space-lg);
  border-block-start: var(--border-hairline) var(--border-style) var(--color-border);
}

.services__row {
  padding-block: var(--space-sm);
  border-block-end: var(--border-hairline) var(--border-style) var(--color-border);
  max-width: var(--layout-measure);
}

@media (min-width: 64em) {
  .services__list {
    max-width: var(--measure-services);
    column-count: 2;
    column-gap: var(--space-xl);
  }

  .services__row {
    break-inside: avoid;
  }
}


/* --- CTA-repeat band (brief §6 row 4) — second quote-path occurrence,
   single column. Slot-3 image is a low-opacity decorative backdrop; text
   sits on flat --color-bg, never on the image. --------------------------- */

.cta-repeat__outer {
  position: relative;
  max-width: var(--measure-cta-repeat);
  margin-inline: auto;
}

.cta-repeat__bg {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  opacity: 0.1;
  pointer-events: none;
}

.cta-repeat__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cta-repeat__inner {
  position: relative;
  z-index: 1;
}

.cta-repeat__line {
  margin: 0 0 var(--space-md);
  font-size: var(--text-lead);
}


/* --- Index of Care (brief §6 row 6, §12): verbatim review beside a
   three-row index. 1fr/1fr grid at desktop (brief §8). -------------------- */

.care__quote {
  margin: 0;
  padding-inline-start: var(--space-md);
  border-inline-start: var(--border-thick) var(--border-style) var(--color-accent);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-lead);
  line-height: var(--leading-body);
}

.care__attrib {
  margin: var(--space-sm) 0 0;
  color: var(--color-ink-muted);
  font-size: var(--text-small);
}

.care__rows {
  margin-block-start: var(--space-lg);
  display: grid;
  gap: var(--space-lg);
}

.care__row {
  max-width: var(--layout-measure);
}

.care__num {
  margin: 0 0 var(--space-3xs);
  color: var(--color-accent-text);
  font-family: var(--font-display);
  font-size: var(--text-index);
  font-weight: var(--font-weight-display-num);
  line-height: 1;
}

.care__row-text {
  margin: 0;
  font-size: var(--text-body);
}

@media (min-width: 64em) {
  .care__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-lg);
    align-items: start;
  }

  .care__rows {
    margin-block-start: 0;
  }
}


/* --- Quote / consultation form (brief §6 row 7, §8) — the conversion
   instrument. 28rem/30rem two columns at desktop: reassurance copy left,
   form right. --------------------------------------------------------------- */

.quote__content form {
  margin-block-start: var(--space-lg);
}

@media (min-width: 64em) {
  .quote__grid {
    display: grid;
    grid-template-columns: var(--measure-quote-left) var(--measure-quote-right);
    column-gap: var(--space-lg);
    align-items: start;
  }
}


/* --- Service area (brief §6 row 8) — one honest sentence, no device. ----- */

.service-area__inner {
  max-width: var(--measure-service-area);
}


/* --- Footer (brief §6 row 9) — a dark BAND within this light-mode site
   (brief §3 note), never a second mode. Focus rings inside it re-point at
   the dark-band-safe token (tokens.css). ----------------------------------- */

.site-footer {
  background-color: var(--color-ink);
  color: var(--color-bg);
}

.site-footer :focus-visible {
  outline-color: var(--color-focus-on-band);
}

.site-footer__nap a {
  color: var(--color-bg);
}

.site-footer p {
  color: var(--color-surface);
}

.site-footer small {
  color: var(--color-surface);
}

@media (min-width: 64em) {
  .site-footer__grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    column-gap: var(--space-lg);
  }
}


/* --- Sticky chrome (brief §7's own stack arithmetic: 44px expiry bar + a
   56px/<768 or 64px/>=768 header). Both bars sticky, never fixed (house
   shape, preview.css documents it); brief §6 row 1 and §8 "Chrome" row call
   for it explicitly. --------------------------------------------------------- */

:root {
  --expiry-stack: calc(var(--expiry-height) + var(--space-3xs));
}

.expiry {
  position: sticky;
  inset-block-start: 0;
  block-size: var(--expiry-stack);
}

.site-header {
  position: sticky;
  inset-block-start: var(--expiry-stack);
  padding-block: var(--space-sm);
}

@media (min-width: 48em) {
  .site-header {
    padding-block: var(--space-md);
  }
}

section[id] {
  scroll-margin-block-start: calc(var(--expiry-stack) + var(--space-xl));
}
