/* ==========================================================================
   site.css — per-site layout & components, built FROM tokens.

   HARD RULE (house-tech-spec §3): no raw hex, no px/rem size literals, no
   font-name literals in this file. Tokens only. The Critic greps for it.
   The two escape hatches are the house breakpoints and an explicit
   `@allow-literal: reason` comment on the preceding line.
   ========================================================================== */

/* --- 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 ----------------------------------------------------------------
   No nav menu (copy.md, header section: "No nav menu. The brief specifies no
   header content beyond sticky chrome and the plank-1 eyebrow/H1/CTA
   sequence" — a one-pager this short reads end to end without one). A bare,
   unclassed <nav aria-label="Contact"> wraps the phone control for the
   landmark (house-tech-spec §8) — it carries no styling of its own and no
   menu items; the header is a plain two-item row at every width: wordmark,
   phone. */

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

.site-header__inner {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs);
  block-size: var(--layout-header-height);
}

@media (min-width: 48em) {
  .site-header__inner {
    gap: var(--space-sm);
  }
}

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

@media (min-width: 48em) {
  .site-header__brand {
    font-size: var(--text-h3);
  }
}

.site-header__phone {
  font-family: var(--font-body);
  font-weight: var(--font-weight-body-strong);
  font-size: var(--text-eyebrow);
  color: var(--color-ink-muted);
  text-decoration: none;
  white-space: nowrap;
}

@media (min-width: 48em) {
  .site-header__phone {
    font-size: var(--text-small);
  }
}

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

/* --- 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-strong);
  color: var(--color-accent-ink);
}

.btn--quiet {
  /* --color-border aliases the brief's decorative-only --color-rule (R5); a
     button's own edge is a control boundary, so it uses --color-ink-muted
     instead — already contrast-verified at 5.84:1 against bg (brief §3.1
     F3), well past the 3:1 non-text floor. */
  border-color: var(--color-ink-muted);
  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);
  /* A form field's border is a real control boundary, not decoration — it
     uses --color-ink-muted (5.37:1 against surface, brief §3.1 F4), never
     --color-border/--color-rule, which R5 restricts to decorative use only. */
  border: var(--border-hairline) var(--border-style) var(--color-ink-muted);
  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;
}

.site-footer__services,
.site-footer__area {
  color: var(--color-ink-muted);
}

/* ============================================================================
   BRIEF COMPONENTS — runs/df-landscapes-grand-junction-co/brief.md
   family: clean-clinical · archetype: split-hero
   signature: "the valley line settles" (brief §8)
   ============================================================================ */

/* --- Section eyebrow + heading (brief §4.1) --------------------------------
   Eyebrow: Display 600, uppercase, ink-muted. Section h2s carry no
   recurring icon of their own — the idx-num numeral opens SERVICE ROWS
   only, never a section head (brief §6.1 rule 2). */

.eyebrow {
  margin-block-end: var(--hero-gap-sm);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display-sub);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--color-ink-muted);
}

.section__title {
  margin-block-end: var(--space-md);
  font-size: var(--text-h2);
  line-height: var(--leading-tight);
}

/* --- The reused static head-rule (brief §8.5) -------------------------------
   The settled horizon line becomes the static head-rule of every section
   below the hero, reused rather than duplicated as a new device — full
   container width, 3px, --color-accent, no animation, no border-radius. */

.head-rule {
  margin-block-end: var(--space-md);
  height: var(--head-rule-height);
  background-color: var(--color-accent);
}

/* --- Hero (brief §7, §9.2) --------------------------------------------------
   DOM order: eyebrow -> h1 -> primary CTA -> field -> verbatim CTA-heading
   line -> secondary CTA (plank 1), fixed at every width. Text sits on the
   flat gradient (brief §3.3), entirely in --color-ink — no accent-coloured
   word anywhere in the H1 or hero (R2). */

.hero {
  position: relative;
  padding-block-start: var(--hero-inset-top);
  padding-block-end: var(--section-gap);
  background: linear-gradient(160deg, var(--color-bg) 0%, var(--color-hero-gradient-end) 100%);
  color: var(--color-ink);
}

.hero__eyebrow {
  margin-block-end: var(--hero-gap-sm);
}

.hero__title {
  margin-block-end: var(--hero-gap-lg);
  font-size: var(--text-h1);
}

.hero__cta {
  margin-block-end: var(--hero-gap-lg);
  height: var(--cta-height);
  padding-block: 0;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display-sub);
}

.hero__quote-line-row {
  margin-block-start: var(--hero-gap-lg);
}

.hero__quote-line {
  display: inline-block;
  max-width: var(--hero-quote-measure);
  font-family: var(--font-body);
  font-style: italic;
  font-weight: var(--font-weight-body-strong);
  font-size: var(--text-small);
  color: var(--color-ink-muted);
}

.hero__cta-secondary {
  display: inline-block;
  margin-block-start: var(--space-xs);
  font-size: var(--text-small);
  color: var(--color-ink-muted);
  text-decoration: underline;
  text-underline-offset: var(--space-3xs);
}

/* DOM order stays fixed at every width (brief §7's plank-1 shape) — the
   two-column layout at >=48rem is CSS Grid's own item placement, never a
   reordering of the markup a screen reader or a JS-off visitor would see.
   Below 48em .hero__inner is plain block flow, so the grid rules simply do
   not apply and everything stacks in source order (brief §6.2's
   single-column case). */
@media (min-width: 48em) {
  .hero__inner {
    display: grid;
    grid-template-columns: 1fr var(--hero-field-inline-size);
    grid-template-rows: auto auto auto auto auto;
    column-gap: var(--space-xl);
    align-items: start;
  }

  .hero__eyebrow,
  .hero__title,
  .hero__cta-row,
  .hero__quote-line-row,
  .hero__cta-secondary-row {
    grid-column: 1;
  }

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

/* --- The signature device: "the valley line settles" (brief §8) -----------
   Paint order (R4), back to front: atmosphere plate (--z-atmosphere, the
   field's own CSS background-image) -> flat surface tint overlay
   (--z-field-tint) -> the signature SVG (--z-field-svg): horizon line,
   marker dot, sage sprig. One shared viewBox at every breakpoint
   (0 0 375 220); preserveAspectRatio="none" (brief §8.2's own markup)
   stretches the composition to fill either box exactly. */

.hero__field {
  position: relative;
  overflow: hidden;
  block-size: var(--field-height);
  background-color: var(--color-surface);
  background-image: url('../assets/img/hero-atmosphere-valley.webp');
  background-image: image-set(
    url('../assets/img/hero-atmosphere-valley.avif') type('image/avif'),
    url('../assets/img/hero-atmosphere-valley.webp') type('image/webp')
  );
  background-size: cover;
  background-position: center;
  border: var(--border-hairline) var(--border-style) var(--color-field-border);
}

@media (min-width: 48em) {
  .hero__field {
    block-size: var(--field-height-wide);
  }
}

.hero__field-tint {
  position: absolute;
  inset: 0;
  z-index: var(--z-field-tint);
  background-color: var(--color-field-tint);
}

.hero__svg {
  display: block;
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  z-index: var(--z-field-svg);
}

/* base = SETTLED end-state (brief §8.4): the horizon-line's default
   transform (unset, scaleX(1)) renders fully drawn; the sprig/marker sit at
   rest — no inline transform needed outside the media query, the keyframes'
   own 100% states are the only place a different value is declared.
   transform-origin lives here rather than as an inline style attribute on
   the <rect> (brief §8.2's own markup carries it inline; moved into the
   stylesheet per spec §3/§12 — no design value is lost, only where it is
   declared). */
.horizon-line {
  transform-origin: left center;
}

.sprig-group {
  /* transform-box: fill-box makes the 50%/100% below resolve against the
     sprig's OWN shape bounding box (its base, where the stem meets the
     ground) — the SVG default (the outer viewBox's own coordinate space)
     would pivot the sway/open around a point in the middle of the whole
     scene, far from the sprig itself (found in self-check: at rest this
     is invisible since there is no rotation to reveal it, but the instant
     the open/sway animation ran, the sprig swung wildly off its own
     position and out of the field's clipped view). */
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

.marker-dot {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .horizon-line {
    animation: lineDraw var(--duration-line-draw) var(--ease-line) forwards;
  }

  /* .sprig and .sprig-group are the SAME element (brief §8.2's own markup:
     <g class="sprig-group sprig">), so their two `animation` declarations
     cannot live in two same-specificity rules — the CSS `animation`
     shorthand does not merge across rules, only the rule that is later in
     source order applies at all, and the one-shot open animation would
     never run (found in self-check: the sprig stayed at opacity:0
     indefinitely with motion enabled, because .sprig-group's `animation:
     sprigSway…` silently discarded .sprig's `animation: sprigOpen…` the
     instant both rules matched the same element). Combined into one
     declaration on the shared selector, comma-separated, exactly as the
     CSS spec composes multiple animations on one element — sprigOpen
     first (governs opacity and the one-shot rotate/scale), sprigSway
     second (takes over the rotate axis once its own delay elapses, at
     which point sprigOpen has already finished and is only holding its
     forwards-filled end state) — the same visual result the brief's own
     prose describes, reached without the shorthand collision. */
  .sprig-group.sprig {
    animation:
      sprigOpen var(--duration-sprig-open) var(--ease-out) var(--delay-sprig-open) forwards,
      sprigSway var(--duration-sprig-sway) ease-in-out infinite var(--delay-sprig-sway);
    transform-origin: 50% 100%;
    opacity: 0;
  }

  .marker-dot {
    animation: markerPulse var(--duration-marker-pulse) ease-in-out infinite var(--delay-marker-pulse);
  }
}

@keyframes lineDraw     { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes sprigOpen    { 0% { transform: rotate(-70deg) scale(0.5); opacity: 0; } 100% { transform: rotate(0deg) scale(1); opacity: 1; } }
@keyframes sprigSway    { 0%, 100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
@keyframes markerPulse  { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }

/* --- Three services, plainly (#services, brief §9.3) -----------------------
   A plain hairline-divided editorial index, no cards, no icons (ref-011).
   Each row opens with a plain Figtree numeral (01/02/03) — the ONLY
   recurring mark below the hero (brief §6.1 rule 2), visually distinct from
   the head-rule above by both role and by carrying no border/fill/motion. */

.services__divider {
  margin-block: var(--space-lg);
  overflow: hidden;
  border: var(--border-hairline) var(--border-style) var(--color-rule);
}

.services__divider img {
  width: 100%;
  height: auto;
}

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

.services__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  padding-block: var(--space-md);
  border-block-end: var(--border-hairline) var(--border-style) var(--color-rule);
}

.idx-num {
  flex: 0 0 auto;
  min-inline-size: var(--idx-num-col);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display-sub);
  font-size: var(--text-idx-num);
  color: var(--color-accent);
}

.services__body {
  display: block;
  min-width: 0;
}

.services__name {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: var(--text-body);
}

.services__desc {
  display: block;
  margin-block-start: var(--space-3xs);
  color: var(--color-ink-muted);
  font-size: var(--text-small);
}

/* --- Request a quote (#quote, brief §9.4) -----------------------------------
   Name/Phone required, Email/Message optional (brief §12). The form has its
   own wrapper so an empty FORM_ENDPOINT would hide only the <form>, never
   the always-visible heading/lead/phone/email fallback around it. */

.quote__divider {
  margin-block: var(--space-lg);
  overflow: hidden;
  border: var(--border-hairline) var(--border-style) var(--color-rule);
}

.quote__divider img {
  width: 100%;
  height: auto;
}

.quote__lead {
  margin-block-end: var(--space-md);
}

.quote__fallback {
  margin-block-start: var(--space-lg);
  margin-block-end: var(--space-2xs);
  font-weight: var(--font-weight-body-strong);
}

.quote__contacts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  margin: 0;
}

/* --- thanks.html (brief §7's three-answer table) ---------------------------
   A thin confirmation page: everything sits near the top, well clear of the
   full inter-section rhythm a content page uses. Reuses the hero's own
   tighter rhythm tokens instead of the section scale. */

.thanks {
  padding-block-start: var(--hero-inset-top);
  padding-block-end: var(--section-gap);
}

.thanks__title {
  margin-block-end: var(--hero-gap-sm);
  font-size: var(--text-h2);
}

.thanks__body {
  margin-block-end: var(--hero-gap-lg);
}

.thanks__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
}
