/* ═══════════════════════════════════════════════════════════════
   D4D Site Platform — forms chassis
   Shared by volunteer / contact / yard-sign (one chassis, mounted
   wherever a form module is on — the fix for S's two-page yard-sign
   drift). Pairs with form.js. Requires tokens.css + base.css +
   buttons.css. All colors are tokens or color-mix() over tokens —
   no raw hex anywhere in this file.

   GROUND: the chassis is G's glass card and is designed for DARK
   sections (.sec--dark / --ink-950 family). Compose forms on dark
   ground; the on-dark alpha ramp does the text work. (G, the
   adopted spec, only ever mounts forms on dark.)

   TEXTURES: never on form surfaces (Session-0 rule). The section
   may carry one texture; the formcard itself stays clean — and as
   of decision 25 the card is OPAQUE, so a section texture cannot
   physically cross an input even if one is present (enforce.js
   rejects such plans; this is the CSS-side guarantee).

   THE SPECIFICITY FIX (COMPONENTS.md, volunteer form):
   G needed two documented !important fights because bare
   `.field label` (0,1,1) outranked `.chk` (0,1,0) and `.field input`
   styled the checkbox boxes. This chassis removes both structurally:
     · field labels/legends carry an explicit .field__label class —
       a checkbox's .chk label never matches the field-label selector;
     · checkboxes live inside fieldset.checks, which is NOT a .field,
       so `.field input` can never reach them.
   Zero !important in this file, by construction.

   ERROR/SUCCESS COLOR NOTE (flag for design review): tokens.css
   ships no semantic error/success tokens, and the no-raw-hex rule
   is absolute, so validation states use the house attention family
   (--gold-hi text + brightened border) plus, always, a text message
   wired to the input (errors are never color alone). If the
   foundation later adds --err/--ok tokens, swap them in here.

   Defects this file fixes (COMPONENTS.md):
     · G's two !important specificity fights (structural fix above)
     · C/S's combined "Email or Phone" free-text field → typed,
       separated email + tel inputs (the Reply-To header defect)
     · C's inert honeypot (handler checked a field no form had) —
       the honeypot is present in every form's markup
     · S's yard-sign form duplication drift — one definition
     · touch targets: checks ≥2.875rem, follow link ≥44px (G's own
       footer fix, applied here too)
   ═══════════════════════════════════════════════════════════════ */

/* ── Layout: the two-card grid (G verbatim, system breakpoints) ──
   420px floor keeps paired name/town + email/phone rows from
   squeezing to ~347px side-by-side on tablet (G's comment, kept);
   min(420px,100%) so the floor can never overflow a narrow phone. */
.forms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
  gap: 26px; /* G optical verbatim — --sp-5 +2, sanctioned deviation */
  align-items: start;
  max-width: 1080px;
  margin-inline: auto;
}

/* ── Form card — G's glass card, tokenized, OPAQUE (decision 25) ──
   The card used to be a translucent tint (white 5.5%/10% over
   transparent + backdrop blur), so a section texture ran straight
   through the form surface — and textures must never cross an input
   (Session-0 rule; enforce.js also rejects texture-on-form-section
   plans — this is the rendering-side guarantee to that plan-side
   gate). Fix: bake what the tint effectively showed over the dark
   ground it is designed for. color-mix(in srgb, white N%, ink-950)
   IS the source-over composite of the old N% white layer on an
   opaque --ink-950 section (sRGB gamma-space interpolation = CSS
   alpha compositing), so the resting look is pixel-identical on the
   canonical ground, tracks the build-time ink re-hue via the token,
   and keeps the no-raw-hex rule. The backdrop blur went with the
   transparency: nothing can show through an opaque card, which is
   the point. */
.formcard {
  background: color-mix(in srgb, var(--white) 5.5%, var(--ink-950));
  border: 1px solid var(--on-dark-hairline);
  border-radius: var(--r-surface); /* G measured 20px → nearest theme token */
  padding: clamp(26px, 3.4vw, 38px); /* G verbatim */
}
.formcard--primary {
  background: color-mix(in srgb, var(--white) 10%, var(--ink-950));
  border-color: color-mix(in srgb, var(--gold) 42%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--gold) 12%, transparent),
    0 30px 60px -40px rgba(0, 0, 0, 1);
}

.formcard__head { margin-bottom: var(--sp-5); }
/* Type-only head — G's decorative icon chip is not carried forward
   (icon sets standing in for content, DESIGN.md doctrine). */
.formcard__head h3 {
  font-family: var(--ff-d);
  font-weight: 600;
  font-size: var(--fs-1); /* G measured 1.55rem → nearest ladder step */
  font-variation-settings: 'SOFT' 20;
  line-height: 1.12;
  margin: 0 0 7px; /* G optical verbatim — --sp-2 −1 */
  color: var(--on-dark-heading);
}
.formcard__head p {
  margin: 0;
  color: var(--on-dark-secondary);
  font-size: .96em;
}

/* ── Form grid ─────────────────────────────────────────────────── */
.form { display: grid; gap: 15px; } /* G optical verbatim — --sp-4 −1 */
.f-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px; /* matches .form gap */
}
.field { display: grid; gap: 7px; min-width: 0; } /* G optical — --sp-2 −1 */

/* Explicit class — the structural end of G's `.field label` fights */
.field__label {
  font: 650 0.7188rem/1 var(--ff-b); /* G verbatim */
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--on-dark-secondary);
  padding: 0;
}
.field__req { color: var(--gold); }

/* Resting border: white 36% clears the 3:1 non-text floor against
   every surface it touches (white 20% sat at 1.92:1 — invisible).
   Recomputed 2026-08-15 against the now-OPAQUE card bases (WCAG
   relative luminance, border composited over each base; default
   tokens, with the worst case across the four bank accents' re-hued
   --ink-950 in parentheses — the lightness lock keeps that spread
   ≤0.04):
     vs quiet card base (white 5.5% mix):  3.29:1 (≥3.26)
     vs primary card base (white 10% mix): 3.15:1 (≥3.15)
     vs input fill on quiet card:          3.33:1 (≥3.33)
     vs input fill on primary card:        3.32:1 (≥3.29)
   All ≥3:1 — no adjustment needed. Because the base is opaque these
   ratios now hold no matter what sits behind the card (a texture
   can no longer shift the effective ground). */
.field input,
.field select,
.field textarea {
  width: 100%;
  font: 400 1rem/1.45 var(--ff-b);
  color: var(--white);
  background: color-mix(in srgb, var(--black) 24%, transparent);
  border: 1px solid color-mix(in srgb, var(--white) 36%, transparent);
  border-radius: var(--r-surface); /* G measured 10px → theme token (sharp = 0) */
  padding: 13px 15px; /* G optical verbatim — --sp-3 +1 / --sp-4 −1 */
}
/* Placeholder: white 48% clears the 4.5:1 text floor vs the input
   fill (white 34% sat at 3.12:1). Recomputed 2026-08-15 against the
   opaque card bases: 4.94:1 on the quiet card's fill · 4.77:1 on
   the primary card's fill (worst case across the re-hued bank
   inks: 4.90 · 4.73 — both ≥4.5). */
.field input::placeholder,
.field textarea::placeholder {
  color: color-mix(in srgb, var(--white) 48%, transparent);
}
/* Focus: border + ground deepen. The global gold :focus-visible
   outline from base.css is NOT suppressed — it is the house focus
   indicator; this is the complementary surface response. */
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--black) 34%, transparent);
}
.field textarea { resize: vertical; min-height: 88px; }

/* ── Checkboxes — G's drawn checks, no specificity fights ──────── */
.checks { border: 0; margin: 0; padding: 6px 0 0; min-width: 0; } /* 6px: G optical — --sp-2 −2 */
.checks .field__label { display: block; } /* legend, same label voice */
.checks__grid {
  display: grid;
  /* em-based track minimum, NOT px: a 160px min doesn't grow with the
     reader's font setting, and at root fs=24 the multi-column layout
     collapsed labels onto neighboring pills (responsive gate,
     2026-08-15). 10em scales with the font so columns drop instead. */
  grid-template-columns: repeat(auto-fit, minmax(min(10em, 100%), 1fr));
  gap: var(--sp-2);
  margin-top: 10px; /* G optical — --sp-3 −2 */
}
.chk {
  /* anywhere (not break-word): break-word never lowers min-content, so a
     long label as a bare text node still forced pill overlap at large
     reader fonts; anywhere lets the intrinsic minimum shrink. */
  overflow-wrap: anywhere;
}
.chk {
  display: flex;
  align-items: center;
  gap: 10px; /* G optical — --sp-2 +2 */
  cursor: pointer;
  font: 500 0.875rem/1.3 var(--ff-b);
  color: var(--on-dark-body);
  background: color-mix(in srgb, var(--black) 20%, transparent);
  border: 1px solid var(--on-dark-hairline); /* G .16 → ramp token (.14) */
  border-radius: var(--r-surface); /* G measured 10px */
  padding: .7em .8em;
  min-height: 2.875rem; /* ≥44px touch target, scales with the reader */
}
.chk:hover {
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
}
.chk input {
  appearance: none;
  -webkit-appearance: none;
  width: 1.35em;
  height: 1.35em;
  min-width: 18px;
  min-height: 18px;
  flex: none;
  margin: 0;
  padding: 0;
  /* ≈5px at soft, 0 at sharp — an 18px box can't carry the full
     14px surface radius; derived from the theme token, not local */
  border-radius: calc(var(--r-surface) * 0.36);
  border: 2px solid color-mix(in srgb, var(--white) 42%, transparent);
  background: transparent;
  display: grid;
  place-items: center;
}
.chk input::after {
  content: "";
  width: .5em;
  height: .3em;
  margin-top: -.1em;
  border-left: .12em solid var(--gold-ink);
  border-bottom: .12em solid var(--gold-ink);
  transform: rotate(-45deg) scale(0);
}
.chk input:checked {
  background: var(--gold);
  border-color: var(--gold);
}
.chk input:checked::after { transform: rotate(-45deg) scale(1); }
.chk:has(input:checked) {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}

/* ── Honeypot — PRESENT in markup, invisible, never focusable ───
   (C's handlers checked a honeypot no form contained — inert spam
   protection. Here the field ships in every form.) */
.hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
  margin: 0;
}

/* ── Validation + status ───────────────────────────────────────── */
/* Pre-rendered live region: every .err slot ships in markup with
   role="status" + id and stays RENDERED (never display:none — a
   display:none live region isn't registered by AT, so the first
   error would go unannounced). form.js only swaps textContent.
   Empty at rest = a zero-height block (costs one 7px field-grid
   gap, the price of a live region that actually announces). */
.err {
  font: 500 0.7812rem/1.3 var(--ff-b);
  color: var(--gold-hi); /* see header note — no semantic token exists */
  display: block;
}
.field.has-err input,
.field.has-err select,
.field.has-err textarea {
  border-color: var(--gold-hi);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold-hi) 20%, transparent);
}

.form__note {
  margin: 2px 0 0;
  font-size: 0.7812rem;
  color: var(--on-dark-caption);
  text-align: center;
}
.form__status {
  margin: 4px 0 0;
  font: 600 0.875rem/1.45 var(--ff-b);
  text-align: center;
}
.form__status.is-ok  { color: var(--on-dark-heading); }
.form__status.is-bad { color: var(--gold-hi); }
.form.is-sending button[type="submit"] {
  opacity: .6;
  pointer-events: none;
}

/* ── Social follow link (renders ONLY when `social` is non-empty) ── */
.formcard__follow {
  display: flex;
  align-items: center;
  gap: 11px; /* G optical — --sp-3 −1 */
  margin-top: 22px; /* G optical — --sp-5 −2 */
  padding-top: var(--sp-5); /* G measured 20px, snapped to scale */
  border-top: 1px solid var(--on-dark-hairline);
  color: var(--on-dark-body);
  text-decoration: none;
  font: 600 0.875rem/1 var(--ff-b);
  min-height: 44px; /* touch target — G's own footer fix */
}
.formcard__follow:hover { color: var(--gold-hi); }
.formcard__follow span { flex: 1; }
.formcard__follow svg { flex: none; }

/* ── Yard-sign section layout + pure-CSS sign preview (S's mock,
      re-cut in house tokens; fed by the wordmark slot) ──────────── */
.yardsign {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: clamp(28px, 4vw, 56px); /* S measured verbatim */
  align-items: start;
}
.signprev {
  margin-top: var(--sp-6);
  max-width: 320px;
  background: var(--accent);
  border-radius: var(--r-surface);
  /* S measured 24/22/20px — 22 is --sp-5 −2 (sanctioned), 20 snapped */
  padding: var(--sp-5) 22px var(--sp-4);
  display: grid;
  gap: var(--sp-3);
  justify-items: center;
  text-align: center;
}
.signprev__first {
  font: 600 1rem/1 var(--ff-b);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--on-dark-secondary);
}
.signprev__last {
  font-family: var(--ff-d);
  font-weight: 650;
  font-variation-settings: 'SOFT' 0, 'WONK' 0; /* crisp on the wordmark */
  font-size: var(--fs-3);
  line-height: .85;
  text-transform: uppercase;
  color: var(--white);
  overflow-wrap: normal; /* surnames never break (base.css rule, restated) */
}
.signprev__rule {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  color: var(--gold);
}
.signprev__rule::before,
.signprev__rule::after {
  content: "";
  flex: 1;
  height: 3px;
  background: currentColor;
}
.signprev__office {
  font: 600 1rem/1 var(--ff-b);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--white);
}
.signprev__bar {
  width: 100%;
  background: var(--gold);
  color: var(--gold-ink);
  font: 600 0.9375rem/1 var(--ff-b);
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 6px 0; /* S verbatim — --sp-2 −2 */
  border-radius: calc(var(--r-surface) * 0.36); /* echoes the check box */
}

/* ── Thank-you page (the form system's conversion beacon) ──────── */
.ty {
  min-height: 100svh;
  display: grid;
  place-items: center;
  text-align: center;
  background: var(--ink-950);
  color: var(--on-dark-body);
  padding: var(--sp-7) var(--pad-inline); /* G measured 40px, snapped */
}
.ty__in { max-width: 620px; }
.ty__check {
  width: 78px;
  height: 78px;
  color: var(--gold);
  margin: 0 auto var(--sp-5);
}
.ty h1 {
  font-family: var(--ff-d);
  font-weight: 600;
  font-variation-settings: 'SOFT' 20, 'WONK' 1;
  /* G measured clamp(2.1rem,7vw,3.3rem) → ladder endpoints */
  font-size: clamp(var(--fs-2), 7vw, var(--fs-4));
  line-height: 1.04;
  margin: 0 0 var(--sp-4);
  color: var(--on-dark-heading);
}
.ty h1 em { font-style: italic; color: var(--gold-hi); }
.ty__msg {
  color: var(--on-dark-secondary);
  font-size: var(--fs-0);
  margin: 0 0 var(--sp-6);
}
.ty__cta {
  display: flex;
  gap: var(--sp-3);
  justify-content: center;
  flex-wrap: wrap;
}
/* rem, not px — fixes G's one shipped 13px literal on this page */
.ty__vote {
  margin: var(--sp-7) 0 0;
  font-size: 0.8125rem;
  color: var(--on-dark-caption);
}
.ty__disclaimer {
  margin: 10px 0 0; /* optical — --sp-2 +2 */
  font-size: 0.7812rem;
  color: var(--on-dark-legal);
}

/* ═══ Motion-safe enhancements only below this line ═══════════════
   Everything decorative. Reduced motion (or no JS, or ?noanim=1)
   gets fully readable static states: the check mark still appears
   on :checked, the thank-you badge renders fully drawn. */
@media (prefers-reduced-motion: no-preference) {
  .field input,
  .field select,
  .field textarea {
    transition: border-color .3s, background .3s, box-shadow .3s;
  }
  .chk {
    transition: border-color .28s, background .28s, transform .28s var(--ease);
  }
  .chk:hover { transform: translateY(-2px); }
  .chk input { transition: background .22s, border-color .22s; }
  .chk input::after { transition: transform .22s var(--ease); }
  .formcard__follow { transition: color .3s; }
  .formcard__follow svg:last-child { transition: transform .35s var(--ease); }
  .formcard__follow:hover svg:last-child { transform: translateX(4px); }

  /* thank-you check draw-on — the UNDRAWN state lives INSIDE the
     keyframes (from → to), never in the resting CSS: kill the
     animation any way at all (?noanim=1's animation:none, reduced
     motion, a dropped keyframe) and the resting state IS the fully
     drawn badge. fill-mode: both covers the start delays with the
     from-state while the animation is alive. */
  .ty__check rect {
    stroke-dasharray: 416;
    animation: ty-draw .9s var(--ease) .15s both;
  }
  .ty__check .tick {
    stroke-dasharray: 150;
    animation: ty-draw-tick .7s var(--ease) .85s both;
  }
  @keyframes ty-draw {
    from { stroke-dashoffset: 416; }
    to   { stroke-dashoffset: 0; }
  }
  /* the tick's own keyframes: its undrawn offset is its own dash
     length (150) — starting it at 416 would wrap the 150-dash
     pattern and leave part of the tick visible before the draw */
  @keyframes ty-draw-tick {
    from { stroke-dashoffset: 150; }
    to   { stroke-dashoffset: 0; }
  }
}

/* ── Responsive (system breakpoints only) ──────────────────────── */
@media (max-width: 1080px) {
  .forms { grid-template-columns: 1fr; max-width: 640px; }
}
@media (max-width: 620px) {
  .f-row { grid-template-columns: 1fr; }
  .checks__grid { grid-template-columns: 1fr; }
}
@media (min-width: 1700px) {
  .forms { max-width: 1180px; }
}
@media (min-width: 2300px) {
  .forms { max-width: 1280px; }
}
