/* v2/assets/forms.css — form styling for the v2 PHP port.
 *
 * WHY THIS FILE EXISTS
 *   The v2 static build contains no forms at all — one incidental `input` rule in the whole
 *   of tokens.css and no field primitives. The design system was never asked to describe a
 *   form. This adds that layer using ONLY existing v2 tokens, so nothing here introduces a
 *   new colour, radius or spacing step.
 *
 *   Load AFTER tokens.css.
 */

/* ── layout ─────────────────────────────────────────────────────────────── */
.form-card {
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: var(--s5);
  box-shadow: var(--shadow-sm);
  max-width: 62ch;
}
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s3) var(--s4);
}
.field { display: flex; flex-direction: column; gap: var(--s1); }
.field-wide { grid-column: 1 / -1; }

/* ── labels ─────────────────────────────────────────────────────────────── */
.field > label {
  font-family: var(--font-body);
  font-size: .82rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.field > label .req { color: var(--accent-text); margin-inline-start: .15em; }

/* ── controls ───────────────────────────────────────────────────────────── */
.field input,
.field select,
.field textarea {
  font: inherit;
  font-family: var(--font-body);
  color: var(--text-primary);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: .7rem .85rem;
  width: 100%;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.field textarea { min-height: 7.5rem; resize: vertical; }

/* Keyboard focus must be visible — the v2 QA gauntlet checks for it. */
/* Walk mark 14 (mc-fjyq): "All of these fields are required fields and they should highlight in red
   when they try to submit." The form already blocked the submit and showed a summary message, but
   never said WHICH field — so on a long form the message was true and useless.

   NEW TOKEN, FLAGGED: --danger. The palette has --warn-* (an amber advisory used by the legal
   under-review banner) and no error colour. The operator asked specifically for RED, and amber is
   not red; reusing the advisory tokens would have quietly answered a different request. #B3261E
   measures 6.4:1 on --surface-1 and 5.9:1 on --canvas, so it clears AA as text, not just as a
   border. It needs BRAND-DESIGN-DOCTRINE sign-off like any other token — raised, not smuggled. */
.field.is-invalid > label { color: var(--danger); }
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea {
  border-color: var(--danger);
  box-shadow: 0 0 0 1px var(--danger);       /* a second pixel of weight — a 1px border alone is
                                                easy to miss on a form this long */
}
/* mc-kmzx (review pin 1): "text above them that says 'required field'".
   IT IS ABOVE THE INPUT, and that is a MARKUP fact, not a CSS one: `.field` is a flex column, so
   the span renders wherever it sits in the source, and consult.php places it between the label and
   the input. This rule only decides whether it shows at all. The vendor form, which authored this
   class, still puts its span after the input — both work, and neither needs an `order` override. */
.field-error {
  display: none;
  color: var(--danger);
  font-size: .85rem;
  line-height: 1.4;
}
.field.is-invalid .field-error { display: block; }
/* An error span the validator left empty must not open a gap above the input. */
.field.is-invalid .field-error:empty { display: none; }

/* mc-kmzx: "I need them to shake." Four pixels each way, twice, over .4s — enough to catch the eye
   at the moment of the blocked submit, small enough that it never moves the layout (transform does
   not reflow). It runs on the class being ADDED, so re-submitting a still-empty form re-runs it
   only if the class was cleared first, which the submit handler does.
   Motion sensitivity is already handled globally: tokens.css zeroes every animation duration under
   prefers-reduced-motion, so this becomes a still red field there, which is the whole point. */
@keyframes field-shake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-4px); }
  40%      { transform: translateX(4px); }
  60%      { transform: translateX(-3px); }
  80%      { transform: translateX(2px); }
}
.field.is-invalid { animation: field-shake .4s ease; }

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}
.field input:invalid:not(:placeholder-shown),
.field select:invalid:not(:placeholder-shown) { border-color: var(--warn-line); }

/* ── checkbox rows (preferred contact method) ───────────────────────────── */
.check-row { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); }
/* THE ROW IS THE TARGET, AND IT IS 44px TALL (mc-86ur, from the mobile audit mc-4yj1, fix 5).
   MEASURED 2026-09-08 at 375 wide: `.check-row > label.check` on consult was 25.08px tall,
   `#cat-group > label.check` on vendors 25.08px, and `#state-picker .state-grid > label.check` —
   fifty-one of them — 23.77px. That last figure is BELOW the WCAG 2.5.8 floor of 24x24, and all of
   them are far below the 44px a thumb wants. Roughly 110 rows on vendors alone.
   ONE RULE FIXES ALL THREE PAGES because each of these rows is ALREADY a <label class="check"> with
   its input inside it — the whole row, box and wording, has always been one hit area; what it
   lacked was height. min-height on the existing inline-flex box supplies that height without moving
   anything: align-items:center was already declared, so the tick box and the words stay vertically
   centred in the taller row and nothing reflows sideways.
   THE HEIGHT IS UNCONDITIONAL, not phone-only: 2.5.8 applies at every width, the rows measured the
   same 25px on a 1280 screen, and a mouse pointer is no worse off for a taller row. */
.check {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px;
  font-family: var(--font-body); font-size: .95rem;
  color: var(--text-primary); text-transform: none; letter-spacing: 0;
  cursor: pointer;
}
.check input { width: auto; accent-color: var(--accent); margin: 0; }

/* ── helper + error text ────────────────────────────────────────────────── */
.field .hint { font-size: .84rem; color: var(--text-muted); }
.form-error {
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  color: var(--warn-text);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--s4);
}
/* mc-kmzx: the base `.form-error` is AMBER — it is the advisory tone, and amber is not red. The
   fields it summarises go red, and a summary in a different colour from the thing it summarises
   makes the reader look for a second problem. --danger is the palette's only red: 6.4:1 as text on
   white, so it carries the sentence as well as the border. */
.form-error--danger {
  background: var(--surface-1);
  border-color: var(--danger);
  color: var(--danger);
  font-weight: 500;
}
/* THE NUMBERED SUMMARY — mc-wx24, operator mark 6: "This should show every error in the form, not
   just one, and it should number each of the errors ... The text should definitely be red and it
   should flash when they try to submit without this stuff filled in."
   An <ol> gives the numbers, so the count is the browser's job and cannot drift from the list.
   Each entry is a link to the field it names, in the same red, underlined so it is not colour
   alone. The box itself takes focus (tabindex=-1 in the markup) so the whole list is read out. */
.form-error--danger .form-error-title { margin: 0; font-weight: 600; }
.form-error--danger ol { margin: .45rem 0 0; padding-inline-start: 1.5rem; }
.form-error--danger li { margin: .25rem 0; }
.form-error--danger a { color: var(--danger); text-decoration: underline; }
.form-error--danger a:hover { color: color-mix(in srgb, var(--danger) 78%, #000); }
.form-error--danger:focus { outline: none; }
.form-error--danger:focus-visible { outline: 3px solid var(--danger); outline-offset: 2px; }

/* q-0008: the field a hero "Apply to partner" jump lands on. The focus ring says where the cursor
   is; this wider gold wash says it a second time, for the eye that just travelled 4,000px, and is
   gone after 2.6 s (site.js). Still, not pulsing, under reduced motion. */
.field .is-jump-lit {
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--gold) 45%, transparent);
  animation: jump-lit 2.6s ease-out 1;
}
@keyframes jump-lit {
  0%   { box-shadow: 0 0 0 12px color-mix(in srgb, var(--gold) 70%, transparent); }
  100% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--gold) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .field .is-jump-lit { animation: none; }
}
/* ONE flash, not a pulse loop: the summary is new on the screen at the moment of the blocked
   submit and has to be noticed, but it must not keep blinking while it is being read. Only the
   ground and the border move — the text never dips below its contrast floor. */
@keyframes form-error-flash {
  0%, 100% { background: var(--surface-1); box-shadow: 0 0 0 1px transparent; }
  30%, 70% { background: color-mix(in srgb, var(--danger) 12%, var(--surface-1)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 35%, transparent); }
}
.form-error--danger.is-flash { animation: form-error-flash .6s ease 1; }
@media (prefers-reduced-motion: reduce) {
  .form-error--danger.is-flash { animation: none; }
}

/* ── honeypot: reachable by bots, invisible and unfocusable to people ───── */
.hp {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* ── actions ────────────────────────────────────────────────────────────── */
.form-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3);
  margin-top: var(--s4);
}
.form-actions .small { color: var(--text-muted); }

/* Respect the reduced-motion preference the rest of v2 already honours. */
@media (prefers-reduced-motion: reduce) {
  .field input, .field select, .field textarea { transition: none; }
}

/* 375px gate: single column, no horizontal overflow. */
@media (max-width: 480px) {
  .form-card { padding: var(--s4); }
  .form-grid { grid-template-columns: 1fr; }
}

/* ── Section heading with the notepad ABOVE it (mc-1838 → mc-4msk) ──────────────────────────
   HISTORY, because this block has been ruled on three times and the shape of it only makes sense
   with the reasons attached.
     mc-1838 put the notepad to the LEFT of "What we need to answer well" in a two-column grid,
       out of a 460px centred inset that used to sit BETWEEN the sentence and the form.
     mc-4msk, first ruling: match its height to the text. Built, then denied — with the caption
       still under the picture the notepad came out drawn at 92x68, an icon.
     mc-4msk, second ruling: move the caption sentence out from under the picture to under the
       paragraph. Built, then denied again.
     mc-4msk, third and current ruling (2026-09-06), operator verbatim: "Can you crop the image down
       to the red rectangle in the screenshot? Also, I changed my mind. I want the image about the
       'Send a message / What we need to answer well / None of this commits you to anything. The
       fuller it is, the more useful our first reply. / The more you write here, the more useful the
       first reply can be.'"

   SO: ONE COLUMN AT EVERY WIDTH, PICTURE FIRST. "About" reads as ABOVE, and the four lines she
   quotes are the whole text group — eyebrow, heading, paragraph, and the sentence moved under the
   paragraph last time, which stays exactly where she put it. There is no side-by-side left at any
   width, so the whole height-matching apparatus that the previous two builds needed is GONE: there
   is no second column to match a height to. That is why this block is shorter than what it
   replaced, not because anything was dropped.

   The markup did not have to move. It has been picture-then-text since mc-1838, chosen then so the
   stacked order would already be the reading order; the grid was the only thing making it
   side-by-side, and removing the grid is the whole change.

   WHY 300px WIDE. The file was cropped to the operator's red rectangle in the same change and is
   now 849x849, so "natural size" would put an 849px notepad above a section heading — three times
   the size it has ever been shown at, and larger than the phone column, so it could not be natural
   size everywhere in any case. 300px keeps the DRAWING itself at about 219px across, which is the
   size she has been looking at through both previous builds (it drew at 219.5 when it was beside
   the text), and it is comfortably below the file's own 849px so the pixel art is only ever scaled
   DOWN — scaling a pixel grid UP past its authored resolution is what makes it look soft. `min()`
   caps it against the column so a 375px phone gets 327px and never a sideways scrollbar.

   LEFT-ALIGNED, not centred: `margin-inline: 0` against the `margin-inline: auto` the base
   `.section-art` rule carries, so the picture's left edge lines up with the eyebrow and the heading
   under it. `img.section-art.section-art--stacked` is written at element-plus-two-classes because
   `img.section-art.section-art--inset` (tokens.css) centres at 460px at the same specificity, and a
   plain `.section-art--stacked` would lose to it; forms.css loads after tokens.css, so equal
   specificity resolves here.
   -------------------------------------------------------------------------------------------- */
.section-header--media {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s4);
  text-align: start;                 /* .section-header.center is a different, opt-in variant */
}
/* The CAP LIVES ON THE FIGURE, not on the picture, and that is not cosmetic. Written as
   `width: min(100%, 300px)` on the <img>, the percentage is circular while the figure is being
   sized — the browser falls back to the file's intrinsic width and the FIGURE comes out 849 wide,
   overrunning its container by 148px at 1280 with no scrollbar to show for it. Measured, then
   fixed: the figure takes the cap, the picture simply fills the figure. */
.section-header--media .section-media { margin: 0; width: min(100%, 300px); }
img.section-art.section-art--stacked {
  width: 100%;
  height: auto;
  margin: 0;
  border-radius: var(--radius);
}
.section-header--media .section-header-text > :first-child { margin-top: 0; }

/* ── The stepped consult flow (mc-kmzx + mc-1838) ───────────────────────────────────────────
   A numbered flow inside ONE <form>: step 1 (who you are), step 2 (optional event details),
   step 3 (the calendar, Book path only). The honeypot and every required field ride the same
   form element, so the single POST at the end of step 2 carries everything.

   NO NEW TOKENS. Everything below is built from --s*, --hairline, --surface-*, --accent,
   --forest, --text-* and --radius(-lg), the same set the rest of this file uses.
   -------------------------------------------------------------------------------------------- */
.stepflow { max-width: 62ch; }
.stepflow .form-card { max-width: none; }     /* the card is inside the flow now, not the flow */

.stepflow-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s2) var(--s3);
  margin-bottom: var(--s3);
}
.step-label {
  margin: 0;
  font-family: var(--font-body); font-size: .82rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-secondary);
}
/* The pills REPORT progress; they do not navigate. An <ol> of non-interactive items with
   aria-current="step" on the live one — the shape of .pill-topic without its button behaviour,
   because a control that looks pressable and is not is worse than no control. */
.step-pills { display: flex; gap: var(--s2); list-style: none; margin: 0; padding: 0; }
.step-pill {
  width: 1.9rem; height: 1.9rem;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-body); font-size: .85rem; font-weight: 600;
  color: var(--text-secondary);
  background: var(--surface-1);
  border: 1px solid var(--hairline); border-radius: 999px;
}
.step-pill.is-done { border-color: var(--forest); color: var(--forest); }
.step-pill.is-current {
  background: var(--forest); color: var(--surface-1); border-color: var(--forest);
}

/* An inactive panel carries hidden + inert + aria-hidden in the markup. `hidden` is the one that
   has to survive the cascade, so it is declared !important — a stray `display` rule on a panel
   would otherwise leave an off-step panel visible AND tabbable. */
.step-panel[hidden] { display: none !important; }
.step-heading {
  margin: 0 0 var(--s3);
  font-family: var(--font-display); font-size: 1.25rem; font-weight: 500;
  color: var(--text-primary);
}
/* Focus lands here on every step change; the ring is the page's, not a new one. */
.step-heading:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent) 60%, transparent);
  outline-offset: 4px;
}

/* Slide in from the right going forward, from the left coming back. Transform + opacity only —
   nothing here reflows, so a slide cannot make the page jump. */
@keyframes step-slide-right { from { transform: translateX(28px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes step-slide-left  { from { transform: translateX(-28px); opacity: 0; } to { transform: none; opacity: 1; } }
.step-panel.slide-in-right { animation: step-slide-right .38s cubic-bezier(.2,.7,.3,1) both; }
.step-panel.slide-in-left  { animation: step-slide-left  .38s cubic-bezier(.2,.7,.3,1) both; }

.step-actions { align-items: flex-start; }
.step-actions .step-note { max-width: 40ch; line-height: 1.45; }
.step-back-row { margin-top: var(--s4); }

/* The questionnaire. Two columns where there is room, one below 700px — the questions are short
   labels over short inputs, and a 62ch single column would run to two screens. */
.step-questions { display: grid; gap: var(--s4); margin-top: var(--s4); }
.q-section { border-top: 1px solid var(--hairline); padding-top: var(--s3); }
.q-section > h4 {
  margin: 0 0 var(--s3);
  font-family: var(--font-body); font-size: .82rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-secondary);
}
.q-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--s3) var(--s4); }
.q-grid .field-wide { grid-column: 1 / -1; }
.q-grid textarea { min-height: 5.5rem; }

/* The calendar host. Calendly renders an iframe into it; the min-height stops the page collapsing
   to nothing while the widget script is still arriving. */
.step-calendar {
  min-height: 700px;
  margin-top: var(--s3);
  border: 1px solid var(--hairline); border-radius: var(--radius);
  overflow: hidden;
}
.step-calendar iframe { width: 100%; min-height: 700px; border: 0; }
@media (max-width: 640px) { .step-calendar, .step-calendar iframe { min-height: 900px; } }

/* Motion sensitivity. tokens.css already zeroes every animation duration site-wide under
   prefers-reduced-motion; this states it locally too, so the panels are provably instant rather
   than instant-by-inheritance — the proof asserts on this rule. */
@media (prefers-reduced-motion: reduce) {
  .step-panel.slide-in-right,
  .step-panel.slide-in-left { animation: none; transform: none; opacity: 1; }
}

@media (max-width: 480px) {
  .stepflow-head { flex-direction: column; align-items: flex-start; }
  .q-grid { grid-template-columns: 1fr; }
}

/* ── Preferred-vendor directory cards ──────────────────────────────────────
   Used by v2/includes/vendor-directory.php. The grid itself is the existing
   .grid.grid-3 + .card; these rules only cover the vendor-specific bits.

   Why each exists:
   - .card p sets margin-bottom:0 in tokens.css, so stacked paragraphs in a
     vendor card would collide. Restore rhythm for the ones we emit.
   - .card has padding var(--s4); a photo has to pull back out to sit
     full-bleed at the top of the card, and clip to the card's own radius.
   - Most vendors publish with NO photo (the planner sends name/category/tag/
     location and little else), so .vendor-photo is absent entirely rather
     than rendering an empty panel. Nothing here fabricates an image.
   ------------------------------------------------------------------------ */
.card .vendor-photo {
  margin: calc(var(--s4) * -1) calc(var(--s4) * -1) var(--s3);
  aspect-ratio: 16 / 10;
  background-size: cover;
  background-position: center;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
/* No emoji pin here. v1's card prefixes the location with 📍, but v2 uses no
   decorative emoji anywhere (a single ⚠ in a form warning is the only one in the
   whole build) and the brand rubric prohibits emoji-as-icons. The location reads
   as plain secondary text. */
.card .vendor-loc { color: var(--text-secondary); margin-bottom: var(--s2); }
.card .vendor-specs { color: var(--text-secondary); font-size: 0.92em; margin-top: var(--s2); }
.card .vendor-loc + p,
.card .vendor-specs { margin-bottom: var(--s2); }

/* 375px gate: the 3-up grid collapses to one column. tokens.css already has a
   breakpoint for .grid-3; this only guards the photo's aspect on narrow cards. */
@media (max-width: 480px) {
  .card .vendor-photo { aspect-ratio: 16 / 9; }
}

/* ── Service-area zone map (mc-cqpe → mc-a0q1 + mc-ge9q) ───────────────────────────────────
   The city checkboxes are gone (marks 35, 36, 32). The map is the tool; a city-by-name field is
   the keyboard path (this page carries an AA conformance claim and a draw-on-map control cannot
   be operated by keyboard); a state picker takes whole states. Any one of the three answers the
   mandatory question, and the block below flashes red when none of them has. */
.zone-tool {
  display: grid; gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  background: var(--surface-1);
}
.zone-tool:focus { outline: none; }
.zone-tool:focus-visible { outline: 3px solid color-mix(in srgb, var(--accent) 60%, transparent); outline-offset: 3px; }
/* MANDATORY, and empty on submit: the border goes red and pulses twice. The wrapping .field gets
   the same .is-invalid the other fields use, so the label turns red and the field-shake runs —
   this rule adds only the border pulse the operator asked for ("flash red if no zone was set"). */
@keyframes zone-flash {
  0%, 100% { box-shadow: 0 0 0 1px var(--danger); }
  25%, 75% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 45%, transparent); }
}
.zone-tool.is-invalid { border-color: var(--danger); animation: zone-flash .8s ease 1; box-shadow: 0 0 0 1px var(--danger); }
/* It is the BLOCK that is invalid, not the city field or the state boxes inside it — the generic
   `.field.is-invalid input` rule would paint those red too and say the wrong thing. */
.field.is-invalid .zone-tool input { border-color: var(--hairline); box-shadow: none; }
.field.is-invalid .zone-tool + .field-error { display: block; }

/* THE DRAW BUTTON — pin 1 of verify-vmap-0906, DENIED in round 1:
   "The Draw Zone needs to be a little bit more prominent with the button. It is just kind of easy
   to miss. Maybe a yellow background with black text that inverts on hover."

   Round 1 used .btn-secondary — an outline button in --forest, which is exactly the thing that is
   easy to miss on a page of outline buttons. Round 2 fills it.

   THE COLOUR IS --gold (#B59556), the palette's only yellow. The operator said "yellow"; the
   design doctrine forbids inventing a colour, and --gold IS the site's yellow, so --gold is what
   was used — it is shown for the operator to rule on, not assumed to be what they pictured.
   The token's own comment says "DARK GROUNDS ONLY — ~3:1 on light", which is about --gold used as
   TEXT on a light ground. Here it is the GROUND: --text-primary (#1F2540) on --gold measures
   5.7:1, over the 4.5:1 AA floor for body text, so the pairing is legitimate in this direction.

   REST     gold ground, dark text.
   HOVER    inverted — dark ground, gold text. Same 5.7:1.
   ARMED    the same inversion, held: dark ground, gold text, so "I am drawing" reads as the
            opposite of "I am idle" without a third colour. The keyframe swaps to the rest
            colours for one beat and settles back on the armed pair (mark 36: "flash inverted
            colors quickly once it's enabled and then set to the highlighted color setting").
   Reduced motion: no animation, straight to the armed fill (rule at the bottom of this file). */
.btn-draw {
  background: var(--gold);
  color: var(--text-primary);
  border-color: var(--gold);
  font-weight: 600;
}
.btn-draw:hover {
  background: var(--text-primary);
  color: var(--gold);
  border-color: var(--text-primary);
}
@keyframes zone-arm-flash {
  0%   { background: var(--text-primary); color: var(--gold); }
  40%  { background: var(--gold); color: var(--text-primary); }
  100% { background: var(--text-primary); color: var(--gold); }
}
#zone-draw[aria-pressed="true"],
#zone-draw.is-armed { background: var(--text-primary); color: var(--gold); border-color: var(--text-primary); }
#zone-draw.is-armed { animation: zone-arm-flash .45s ease-out 1; }

/* The shortcuts, visible at all times under the map (mark 32) — and readable: the operator's
   first ask on this map was that the instructions be "more prominent". */
.zone-keys { padding: var(--s3); background: var(--canvas); border-radius: var(--radius); }
.zone-keys-title {
  margin: 0 0 var(--s2);
  font-family: var(--font-body); font-size: .82rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-secondary);
}
.zone-keys-list { margin: 0; padding: 0; list-style: none; display: grid; gap: .35rem; }
.zone-keys-list li { font-size: .95rem; line-height: 1.45; color: var(--text-primary); }
.zone-keys-list b { font-weight: 600; color: var(--forest); }
@media (min-width: 700px) { .zone-keys-list { grid-template-columns: 1fr 1fr; gap: .35rem var(--s4); } }
/* q-0009: two lists in the markup, one shown. A screen with no hover and a coarse pointer (a
   phone, a tablet) has no Enter, Escape or Backspace to press, so it gets the list of what a
   finger does; a mouse and keyboard get the keys. The instructions now sit ABOVE the map and the
   draw button BELOW it, the operator's convention. */
.zone-keys-list--touch { display: none; }
@media (hover: none) and (pointer: coarse) {
  .zone-keys-list--keys { display: none; }
  .zone-keys-list--touch { display: grid; }
}

.zone-load-note { margin: 0; font-size: .9rem; color: var(--text-secondary); }
.zone-map.is-loading { cursor: progress; }

/* The zone number, inside the shape: Leaflet's permanent tooltip, restyled, no arrow. */
.leaflet-tooltip.zone-label {
  background: var(--surface-1); color: var(--forest);
  border: 1px solid var(--forest); border-radius: 999px;
  font-family: var(--font-body); font-size: .8rem; font-weight: 600; letter-spacing: .04em;
  padding: 2px 10px; box-shadow: none; pointer-events: none;
}
.leaflet-tooltip.zone-label::before { display: none; }

/* The list under the map: one row per zone with its states, then the states row, then the
   cities added by name. */
.zone-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.zone-state { margin-top: var(--s2); }
.zone-state-name {
  margin: 0 0 .2rem;
  font-family: var(--font-body); font-size: .85rem; color: var(--forest);
}
.zone-state-name strong { font-weight: 600; }
.zone-state-count { font-weight: 400; color: var(--text-muted); }
.zone-places { margin: 0; font-size: .88rem; line-height: 1.5; color: var(--text-secondary); }
.zone-more {
  font: inherit; font-size: .85rem; font-weight: 600; color: var(--accent-text);
  background: none; border: 0; padding: 0; cursor: pointer; text-decoration: underline;
}
.zone-more:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }
.zone-row--states, .zone-row--cities { display: block; font-size: .9rem; color: var(--text-primary); }
.zone-row--states .zone-name, .zone-row--cities .zone-name { color: var(--forest); }
.city-chips { list-style: none; margin: .4rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2); }
.city-chip {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: 4px 6px 4px 12px; font-size: .88rem;
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: 999px;
}
.zone-remove--small { width: 26px; height: 26px; font-size: 1rem; }

/* The keyboard path: a combobox whose suggestions drop below the field. */
.city-add { display: grid; gap: var(--s1); }
.city-add > label {
  font-family: var(--font-body); font-size: .82rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-secondary);
}
.city-combo { position: relative; }
.city-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20;
  margin: 0; padding: 4px 0; list-style: none;
  background: var(--surface-1); border: 1px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); max-height: 16rem; overflow-y: auto;
}
.city-list[hidden] { display: none; }
.city-list [role="option"], .city-note { padding: .5rem .85rem; font-size: .92rem; cursor: pointer; }
.city-note { color: var(--text-muted); cursor: default; }
.city-list [role="option"]:hover,
.city-list [role="option"].is-active { background: var(--canvas); color: var(--forest); }

/* Entire states: a native disclosure holding fifty-one checkboxes. */
.state-picker { border: 1px solid var(--hairline); border-radius: var(--radius); background: var(--surface-1); }
.state-picker > summary {
  padding: .7rem .85rem; cursor: pointer; font-size: .95rem; font-weight: 500; color: var(--text-primary);
}
.state-picker > summary:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.state-count { color: var(--text-muted); font-weight: 400; margin-inline-start: .35rem; }
/* PIN 4 of verify-vmap-0906, DENIED in round 1: "There should be an option for Nationwide or
   Select All. Yes, Select All and Deselect All should be somewhere here ... or at the very top in
   its own section above the states."  Its own row, above the fifty-one boxes, on the page's own
   ground so it separates from the grid without a second rule line. */
.state-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3);
  padding: .7rem .85rem;
  border-top: 1px solid var(--hairline);
  background: var(--canvas);
}
/* 44, NOT 38 (mc-86ur round 2, audit finding F3). "Select all" and "Deselect all" measured 39.8px
   tall at 375 — over the 24px legal floor and under the 44 this card sets, in the same stylesheet
   and the same state picker whose fifty-one tick rows this card had just raised. The 38 was written
   before that bar existed. Same one-property treatment as `.check` above: the padding, the type and
   the border are untouched, only the floor moves. */
.state-action {
  font: inherit; font-family: var(--font-body); font-size: .9rem; font-weight: 600;
  padding: 7px 14px; min-height: 44px;
  color: var(--forest); background: var(--surface-1);
  border: 1px solid var(--forest); border-radius: var(--radius); cursor: pointer;
}
.state-action:hover { background: var(--forest); color: var(--surface-1); }
.state-action:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 2px; }
.state-nationwide { font-weight: 600; }
.state-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--s1) var(--s3); padding: 0 .85rem .85rem; border-top: 1px solid var(--hairline); padding-top: .7rem;
}
.state-grid .check { font-size: .9rem; }

@media (prefers-reduced-motion: reduce) {
  .zone-tool.is-invalid { animation: none; }
  #zone-draw.is-armed { animation: none; }
}
.zone-map {
  height: 380px; width: 100%;
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  background: var(--surface-2);            /* something to look at before tiles arrive */
  z-index: 0;                              /* keep Leaflet panes under the fixed nav */
}
@media (max-width: 640px) { .zone-map { height: 300px; } }
/* Crosshair only while a zone is being drawn, so the cursor states what mode you are in. */
.zone-map.is-drawing { cursor: crosshair; }
.zone-map.is-drawing .leaflet-grab { cursor: crosshair; }

.zone-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
/* q-0009: Finish zone and Undo point sit in the bar only while a zone is being drawn
   (service-area-map.js unhides them). The [hidden] rule has to outrank .btn's display. */
.zone-bar-aux[hidden] { display: none; }
.zone-status { font-size: .85rem; color: var(--text-muted); }

.zone-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.zone-empty { font-size: .85rem; color: var(--text-muted); list-style: none; }
.zone-row {
  display: grid; gap: var(--s1);
  padding: 10px 12px;
  background: var(--canvas);
  border: 1px solid var(--hairline); border-radius: var(--radius);
}
.zone-name { font-weight: 600; font-size: .9rem; }
.zone-covers { flex: 1; min-width: 0; font-size: .82rem; color: var(--text-secondary); }
/* THE X — pin 2 of verify-vmap-0906, DENIED in round 1:
   "I need the X next to the zone to be more prominent: red background, white X. On click it
   should say, Are you sure? or Delete zone 1, are you sure?"

   Round 1 drew it as a grey outline circle that only went red on hover, so at rest it read as
   decoration. Round 2 fills it: --danger ground, a white glyph, still the same round shape.
   White on #B3261E measures 6.4:1. 32px is under the 44px touch target the rest of the site holds
   to, so it keeps its padding to reach it without growing the row.

   The confirm step itself is in assets/service-area-map.js — this only paints it. */
.zone-remove {
  flex-shrink: 0;
  width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.25rem; font-weight: 600; line-height: 1;
  color: #FFFFFF; background: var(--danger);
  border: 1px solid var(--danger); border-radius: 999px;
  cursor: pointer;
}
.zone-remove:hover { background: color-mix(in srgb, var(--danger) 78%, #000); border-color: color-mix(in srgb, var(--danger) 78%, #000); color: #FFFFFF; }
.zone-remove:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }

/* The confirm row that replaces nothing and sits under the zone's own heading: a question in red
   and two buttons. No native confirm() — it cannot be styled, cannot be screenshotted, and the
   walk harness cannot see it. Escape answers No; the script puts focus on No, so a stray Enter
   keeps the zone. */
.zone-confirm {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3);
  margin-top: var(--s2); padding: 10px 12px;
  background: color-mix(in srgb, var(--danger) 7%, var(--surface-1));
  border: 1px solid var(--danger); border-radius: var(--radius);
}
.zone-confirm-q { margin: 0; flex: 1 1 14rem; font-size: .9rem; font-weight: 600; color: var(--danger); }
.zone-confirm-yes,
.zone-confirm-no {
  font: inherit; font-family: var(--font-body); font-size: .88rem; font-weight: 600;
  padding: 8px 18px; border-radius: var(--radius); cursor: pointer; min-height: 38px;
}
.zone-confirm-yes { background: var(--danger); color: #FFFFFF; border: 1px solid var(--danger); }
.zone-confirm-yes:hover { background: color-mix(in srgb, var(--danger) 78%, #000); border-color: color-mix(in srgb, var(--danger) 78%, #000); }
.zone-confirm-no { background: var(--surface-1); color: var(--text-primary); border: 1px solid var(--text-primary); }
.zone-confirm-no:hover { background: var(--text-primary); color: var(--surface-1); }
.zone-confirm-yes:focus-visible,
.zone-confirm-no:focus-visible { outline: 3px solid var(--text-primary); outline-offset: 2px; }

/* Leaflet's own attribution/controls, brought into the site's type scale. Attribution is a
   licence requirement, not decoration — it is restyled, never hidden. */
.zone-map .leaflet-container { font-family: var(--font-body); }
.zone-map .leaflet-control-attribution { font-size: .7rem; background: rgba(255,255,255,.85); }
.zone-map .leaflet-control-attribution a { color: var(--accent-text); }

/* THE ZOOM BUTTONS ARE 44x44 (mc-86ur, from the mobile audit mc-4yj1, fix 11).
   MEASURED 2026-09-08: `.leaflet-control-zoom a` — the + and the − — were 30x30 at 375, 768 and
   1280. They come from the vendored Leaflet stylesheet, not from Monarch's CSS, so they are
   overridden here rather than edited there: assets/vendor/leaflet/leaflet.css is third-party code
   kept byte-identical to its release so it can be re-vendored without a merge.
   THE SELECTOR NAMES BOTH CLASSES ON PURPOSE. Leaflet sets the 30px through
   `.leaflet-touch .leaflet-bar a` at (0,2,1), and includes/head.php loads leaflet.css BEFORE this
   file, so a (0,2,1) override would win only on load order — which is exactly the kind of tie that
   breaks the day somebody reorders two <link> tags. Naming the zoom-in/zoom-out classes puts this
   at (0,3,1) and settles it on specificity instead.
   line-height MOVES WITH THE HEIGHT because Leaflet centres the glyph with line-height, not flex:
   leave it at 30px in a 44px box and the + and − sit high in the button.
   THE ATTRIBUTION LINKS ARE DELIBERATELY NOT TOUCHED. They measured 47.94x12 and 79.08x12 — the
   smallest targets on the site — but they are a licence requirement that is meant to be READ, not
   pressed, and enlarging them would put a 44px band of chrome over the map corner. The audit
   excluded them by name and so does this. */
.zone-map .leaflet-control-zoom a.leaflet-control-zoom-in,
.zone-map .leaflet-control-zoom a.leaflet-control-zoom-out {
  width: 44px; height: 44px; line-height: 44px;
}

/* ── THE EMPTY BAND BETWEEN THE HERO AND THE FORM, ON A PHONE ──────────────────────────────────
   mc-86ur, from the mobile audit mc-4yj1 ("one more thing, not a fault but worth your eye"): at 375
   there is a band of nothing between the opening picture and the first heading of the form section.

   WHAT IT ACTUALLY IS, MEASURED 2026-09-08 on consult.php at 375 — three spacings stacked, and no
   fixed min-height or empty slot anywhere:
     48px  the last hero paragraph's own bottom margin  (.hero .lede { margin-block: --s4 --s6 })
     64px  the hero's bottom padding                    (.hero { padding-block: … 64px })
     64px  the next section's top padding               (.section { padding-block: --section-y },
                                                         already reduced from 96 under 700px)
     = 176px of white before the section's first ink, on a screen 812px tall.
   The audit's "500 pixels" counts the 327px-tall notepad illustration that follows as empty too. It
   is not empty: it is the picture the operator ruled on twice (mc-4msk) and had cropped to her own
   red rectangle. It is left exactly as it is. This rule tightens the 176 and nothing else.

   88px AFTER, NOT ZERO. The hero is a dark navy band and the section below it is cream; the join
   needs to read as a change of ground, not a collision. Halving each of the three spacings keeps
   the proportion between them and still gives back 88px — most of a thumb's worth of scrolling.

   PHONE ONLY, AND DESKTOP IS UNTOUCHED. The whole block sits inside max-width: 480px, so 768 and
   1280 keep the 208px trough they have today, measured before and after to prove it.

   IN THIS FILE, NOT tokens.css, BECAUSE OF WHO LOADS IT. forms.css is loaded by consult.php — the
   page the card names — and by vendors.php, and by nobody else; the same rule in tokens.css would
   have re-spaced the hero of all eighteen v2 pages, which is a bigger change than was asked for.
   vendors.php has the same hero-then-form shape and gets the same tightening. booking.php, the
   card's other named page, is a 301 stub to consult.php as of mc-kmzx and renders nothing at all.
   forms.css loads AFTER tokens.css (includes/head.php), so these ties are won on order as well as
   being scoped by the media query. */
@media (max-width: 480px) {
  .hero .lede { margin-block: var(--s4) var(--s4); }   /* 48px bottom -> 24px */
  .hero { padding-block-end: var(--s5); }              /* 64px -> 32px */
  .hero + .section { padding-block-start: var(--s5); } /* 64px -> 32px */
}
