/* ===========================================================================
   INFINIUM Partner Coverage Map — the INFINIUM design system, applied.

   The system is extracted from infiniumwalls.com and committed at
   docs/design/. infinium-tokens.json is the source of truth,
   DESIGN-HANDOFF.md governs, and where the handoff is silent the README
   stands. Every value below is from those files unless a comment says why not.

   THE FIVE THINGS THAT MAKE THE RESEMBLANCE (handoff §10)
     1. Montserrat 100 on every large heading. This is the whole look.
     2. Zero border radius. Everywhere. Inputs, buttons, cards, swatches.
     3. 0.25em tracking on labels and buttons. Not negotiable.
     4. The 4px solid offset block. NEVER a soft drop shadow.
     5. Restraint with green: logo, list bullets, link hover, focus ring.

   THE ONE PLACE THE DESIGN AND THE BUILD CONFLICT
   The design's rhythm — 110px header, 80px section padding, 70–90px headings —
   cannot coexist with the map step's hard constraint: 1366×768, no vertical
   page scroll, action bar always visible. It is resolved per step.

     STEP 1, the form   scrolls already, so it takes the full treatment:
                        band-headed sections, 64px block rhythm, a 70px thin
                        page title.
     STEP 2, the map    keeps the fixed viewport and a compact rhythm. It takes
                        the typography, the colour, the zero radius and the
                        offset block, but not the spacing — 80px of section
                        padding above a map is 80px of map nobody can see.

   The 110px header applies to both, per D5.
   =========================================================================== */

:root{
  /* ---- Type ------------------------------------------------------------- */
  --inf-display:'Montserrat',Arial,Helvetica,sans-serif;
  --inf-body-font:'Roboto','Segoe UI',Arial,Helvetica,sans-serif;

  /* ---- Palette. Nine values, achromatic plus one green ------------------- */
  --inf-ink:#2D2D2D;         /* headings, labels, form values                */
  --inf-body:#444444;        /* body copy, field outline                     */
  --inf-muted:#7C7C7C;       /* meta text, button border, placeholder        */
  --inf-rule:#D9D9D9;        /* hairlines, button hover block                */
  --inf-shade:#EEEEEE;       /* the offset solid-shadow block                */
  --inf-surface-alt:#F8F8F8; /* alternate section band                       */
  --inf-surface:#FFFFFF;
  --inf-green:#76A134;       /* logo mark, plus bullets, focus ring          */
  --inf-green-lt:#9EBD70;    /* LINK HOVER UNDERLINE ONLY. Reserved.        */
  --inf-error:#B3261E;       /* EXTENSION, approved D8. Validation only.     */

  /* ---- Type scale, base (<1024px) --------------------------------------- */
  --inf-h1:2.875rem;         /* 46px */
  --inf-h2:2.25rem;          /* 36px */
  --inf-h3:1.0625rem;        /* 17px */
  --inf-body-size:1.0625rem; /* 17px */
  --inf-small:.9375rem;      /* 15px */
  --inf-label:.625rem;       /* 10px */
  --inf-label-xs:.5625rem;   /* 9px  */

  --inf-ls-heading:.025em;
  --inf-ls-h3:.11em;
  --inf-ls-label:.25em;      /* the distinctive one */

  /* ---- Layout ----------------------------------------------------------- */
  --inf-gutter:2.25rem;      /* 36px mobile */
  --inf-gutter-lg:3rem;      /* 48px desktop */
  --inf-block-gap:4rem;      /* 64px */
  --inf-stack-gap:3rem;      /* 48px */
  --inf-section-pad:5rem;    /* 80px */
  --inf-shadow-solid:4px 4px 0 0 var(--inf-shade);
  --inf-ease:cubic-bezier(.4,0,.2,1);
  --inf-dur-fast:.1s;
  --inf-dur-base:.2s;

  /* D5: raised from the site's 88px because the tagline lockup renders at
     240x44 and needs room to breathe. Applies to both steps. */
  --head-h:110px;
  --side-w:400px;

  /* Zero. Everywhere. Declared as a token so that any rule reaching for a
     radius has to reach for this one and get 0. */
  --inf-radius:0;
}

@media (min-width:1024px){
  :root{
    --inf-h1:4.375rem;       /* 70px — interior page title, lg:text-4xl */
    --inf-h2:3.4375rem;      /* 55px */
    --inf-h3:1.375rem;       /* 22px */
    --inf-label:.75rem;      /* 12px */
  }
}

*{box-sizing:border-box;margin:0;padding:0;border-radius:var(--inf-radius)}
html,body{height:100%}
html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
body{
  background:var(--inf-surface); color:var(--inf-body);
  font:400 var(--inf-body-size)/1.5 var(--inf-body-font);
  display:flex; flex-direction:column;
  overflow:hidden;                    /* step 2 must not scroll the page */
}
.hide{display:none !important}
.muted{color:var(--inf-muted)}
.small{font-size:var(--inf-small)}

.visually-hidden,.sr-label{
  position:absolute!important; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

/* ---- Headings. Montserrat 100 is the whole look ------------------------- */
h1,.inf-h1{
  font:100 var(--inf-h1)/1.05 var(--inf-display);
  letter-spacing:var(--inf-ls-heading); color:var(--inf-ink);
}
h2,.inf-h2{
  font:100 var(--inf-h2)/1.1 var(--inf-display);
  letter-spacing:var(--inf-ls-heading); color:var(--inf-ink);
}
h3,.inf-h3{
  font:100 var(--inf-h3)/1.375 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-h3); color:var(--inf-ink);
}

/* The eyebrow / kicker. ALL CAPS, wide-tracked, usually ends in a period. */
.inf-label{
  display:inline-block; font:700 var(--inf-label)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}

/* Green plus bullet, from the tokens' own data URI. */
.inf-list-plus{list-style:none; margin:0 0 0 1rem; padding:0}
.inf-list-plus li{
  position:relative; font-size:var(--inf-small); line-height:1.6;
  color:var(--inf-body);
}
.inf-list-plus li+li{margin-top:.35rem}
.inf-list-plus li::before{
  content:""; position:absolute; display:block; left:-1em; top:.75em;
  width:.5em; height:.5em; transform:translateY(-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath fill='%2376A134' d='M4 0h2v4h4v2H6v4H4V6H0V4h4z'/%3E%3C/svg%3E") 0 0/.5em .5em no-repeat;
}

/* ------------------------------------------------------------------- focus
   One treatment everywhere, never removed. Brand green with a soft ring —
   handoff D8 approves this as an accessibility extension; the live site ships
   no focus state at all, which this deliberately does not copy. */
:focus{outline:none}
:focus-visible{outline:2px solid var(--inf-green); outline-offset:2px}
.leaflet-interactive:focus-visible{outline-offset:0}

.skip-link{
  position:absolute; left:0; top:-60px; z-index:1000;
  background:var(--inf-surface); color:var(--inf-ink); padding:12px 18px;
  border:1px solid var(--inf-green);
  font:700 var(--inf-label)/1 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); text-decoration:none;
  transition:top var(--inf-dur-fast) var(--inf-ease);
}
.skip-link:focus{top:0}

/* ===========================================================================
   HEADER — 110px, logo and the partner's company name. Nothing else. (D4)
   =========================================================================== */
.hero{
  flex:0 0 var(--head-h); background:var(--inf-surface);
  border-bottom:1px solid var(--inf-shade);
}
.hero-inner{
  height:100%; display:flex; align-items:center; gap:2rem;
  padding:0 var(--inf-gutter-lg);
}
/* 240px wide, 44px tall. The tagline lockup is 5.402:1 and the tagline is
   illegible below about this width — handoff D3 and §5.1. */
.hero-logo{display:block; width:240px; height:auto; flex:none}
.hero-for{
  margin-left:auto; display:flex; align-items:baseline; gap:.75rem;
  min-width:0; text-align:right;
}
.hero-for-label{color:var(--inf-muted); flex:none}
@media (max-width:1100px){.hero-for-label{display:none}}
/* THE COMPANY NAME IS EXEMPT FROM THE MONTSERRAT 100 TREATMENT.
 *
 * It was `font:100 1.375rem` - Montserrat Thin at 22px - and it was hard to
 * read. That weight is not compliance with the handoff, it is drift: the
 * mandate is Montserrat 100 on LARGE HEADINGS, the 70px page title and the
 * 55px section heads, where hairline strokes are a deliberate effect at a size
 * that can carry them. At 22px the same strokes are simply thin.
 *
 * Nothing about contrast was wrong - it measured 13.77:1 at #2D2D2D, the
 * highest on the page - and that is the point worth remembering: WCAG contrast
 * does not model stroke weight, so this passed every automated check while
 * being the hardest thing in the header to read. Audit D never caught it
 * because it never tested the header.
 *
 * Now 500 at --inf-body. 400 was tried and still reads thin at 22px against a
 * 240px logo; 500 has presence without approaching the logo's weight. The
 * colour drops the ratio to 9.74:1, still twice the 4.5 floor, and it is a
 * SUBORDINATION choice rather than a legibility one - it sits the name between
 * the #7C7C7C eyebrow beside it and the #2D2D2D headings on the page below.
 *
 * Do not restore the 100. See README, "The header company name is exempt". */
.hero-for strong{
  font:500 1.375rem/1.2 var(--inf-display); color:var(--inf-body);
  letter-spacing:var(--inf-ls-heading);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
/* PHASE 4C, change 11 — THE INS BADGE LIVES IN THE ACTION BAR NOW.
 *
 * It moved out of the header and into the footer bar, immediately left of the
 * checklist. It is NOT decoration and was not dropped: it is how a partner
 * confirms they are looking at their own record rather than somebody else's,
 * and it is how INFINIUM identifies a screenshot that arrives by email with no
 * other context. In the action bar it is on screen at all times on both steps,
 * which the header also gave it - but there it competed with the company name
 * for the same corner.
 *
 * Raised from 9px to 10px and off --inf-muted, which measured 4.17:1. */
.pid{
  font:700 var(--inf-label)/1 var(--inf-display);
  letter-spacing:var(--inf-ls-label); color:var(--inf-body);
  border:1px solid var(--inf-rule); padding:6px 9px; flex:none;
  white-space:nowrap;
}
.actions .pid{align-self:center; margin-right:4px}
/* PHASE 4C, change 1. Was 10px, too small to read as anything. Raised to 13px,
   which is still a clear step below the 22px company name beside it - this is
   status, not content, and must not compete with whose record you are on.
   Change 11 moves the INS badge out of this header, leaving the stamp alone on
   the right; the min-width floor goes with it so the stamp sits against the
   company name rather than hanging 104px out on its own with nothing to
   anchor it. */
.hero-save{
  font:500 .8125rem/1.5 var(--inf-body-font); color:var(--inf-muted);
  text-align:right; flex:none; white-space:nowrap;
}
.hero-save.is-saved{color:var(--inf-green)}

/* --------------------------------------------------------- gate and loading */
.gate,.loading{
  margin:auto; max-width:560px; padding:var(--inf-section-pad) var(--inf-gutter);
  background:var(--inf-surface); text-align:center;
}
.gate h2{margin-bottom:1rem}
.gate p{margin-bottom:.75rem}
.loading-bar{height:2px; background:var(--inf-shade); overflow:hidden; margin-bottom:1.25rem}
.loading-bar i{display:block; height:100%; width:40%; background:var(--inf-green);
  animation:slide 1.1s ease-in-out infinite}
@keyframes slide{0%{margin-left:-40%}100%{margin-left:100%}}

/* ===========================================================================
   BUTTON — the signature control (handoff §4.1)

   A thin outlined box with a solid #EEEEEE block offset 4px down and right. On
   hover the block snaps flush and darkens. NEVER a soft drop shadow.

   Every button in the app is <button class="btn"><span>LABEL</span></button>;
   the span is the box and ::before is the block.
   =========================================================================== */
.btn{
  position:relative; display:inline-flex; align-items:center;
  border:0; background:none; padding:0; cursor:pointer; font:inherit;
  text-decoration:none; color:inherit;
}
.btn>span{
  position:relative; z-index:1; display:inline-flex; align-items:center; gap:.5rem;
  border:1px solid var(--inf-muted); background:var(--inf-surface);
  padding:8px 24px;
  font:700 var(--inf-label)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label);
  color:var(--inf-ink); white-space:nowrap;
}
.btn::before{
  content:""; pointer-events:none; position:absolute; top:4px; left:4px; z-index:0;
  display:block; height:100%; width:100%; background:var(--inf-shade);
  transition:all var(--inf-dur-fast) var(--inf-ease);
}
.btn:hover:not(:disabled)::before{top:0; left:0; background:var(--inf-rule)}
.btn:disabled{cursor:not-allowed}
.btn:disabled>span{color:var(--inf-muted); border-color:var(--inf-rule)}
.btn:disabled::before{background:var(--inf-surface-alt)}
.btn .ico{width:12px; height:12px; fill:currentColor; flex:none}
.btn-sm>span{padding:6px 14px; font-size:var(--inf-label-xs)}
.btn-wide{flex:1 1 auto}
.btn-wide>span{width:100%; justify-content:center; padding:14px 24px}
.btn-danger:hover:not(:disabled)>span{border-color:var(--inf-error); color:var(--inf-error)}
/* The disabled Submit is switched off, not broken: in place, legible, saying
   why on its face. No stripes — the system has no such texture. */
.btn-disabled>span{background:var(--inf-surface-alt)}
.btn i{font-style:normal}

/* Link hover — a 2px #9EBD70 rule sweeping in from the left. This is the ONLY
   place green-light appears. */
.inf-hover-link{
  position:relative; text-decoration:none; color:var(--inf-ink); font-weight:500;
}
.inf-hover-link::after{
  content:""; display:block; position:absolute; bottom:-2px; left:0;
  width:0; height:2px; background:var(--inf-green-lt);
  transition:all var(--inf-dur-base) var(--inf-ease);
}
.inf-hover-link:hover::after{width:100%}

/* PHASE 4C, change 8 — THE DOCUMENT LINKS HAVE TO LOOK LIKE LINKS AT REST.
 *
 * Both hrefs work and always did. The defect was that they rendered as plain
 * bold ink text with the underline hidden until hover, so a partner had no
 * reason to think they were clickable and never hovered to find out. It fails
 * silently, which is worse than a 404: 47 of 73 partners see the COI link and
 * 37 of 73 see the agreement link, and neither reads as an affordance.
 *
 * Green AND underlined, permanently. Colour alone would not do it - the
 * underline is what survives for a colour-blind reader, and it is why the
 * assertion checks for a decoration rather than for a hue. Kept off the shared
 * .inf-hover-link, which is the site's own sweep-in treatment and is still
 * right everywhere else. */
.cf-link{
  color:var(--inf-green); font-weight:500;
  text-decoration:underline; text-decoration-thickness:1px;
  text-underline-offset:3px; text-decoration-color:var(--inf-green);
}
.cf-link:hover{text-decoration-thickness:2px; color:var(--inf-ink);
  text-decoration-color:var(--inf-ink)}

/* Segmented control — two buttons sharing one outline. */
.seg{display:inline-flex; border:1px solid var(--inf-muted)}
.seg-btn{
  display:inline-flex; align-items:center; gap:6px; border:0; background:var(--inf-surface);
  padding:8px 14px; cursor:pointer;
  font:700 var(--inf-label-xs)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
.seg-btn .ico{width:12px; height:12px; fill:currentColor}
.seg-btn+.seg-btn{border-left:1px solid var(--inf-muted)}
.seg-btn.is-on{background:var(--inf-ink); color:var(--inf-surface)}
.seg-btn:focus-visible{outline-offset:-3px}

/* ===========================================================================
   FORM FIELD — the "notched outline" (handoff §4.3)

   1px box, and the label sits ON the top border line, knocked out in white.
   That is why every label in this app is short: at 10px Montserrat 700 with
   0.25em tracking, a long one runs past the box and looks broken. See the note
   at the top of js/confirm-spec.js.
   =========================================================================== */
.inf-field{
  position:relative; border:1px solid var(--inf-body);
  background:var(--inf-surface); margin-top:.5rem;
}
.inf-field>label,.inf-field>.cf-label{
  position:absolute; top:0; left:16px; transform:translateY(-50%);
  background:var(--inf-surface); padding:0 8px;
  font:700 var(--inf-label)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label);
  color:var(--inf-ink); white-space:nowrap; max-width:calc(100% - 32px);
  overflow:hidden; text-overflow:ellipsis;
}
.inf-field input,.inf-field textarea,.inf-field select{
  width:100%; border:0; outline:none; appearance:none; background:transparent;
  padding:16px; font:400 var(--inf-body-size)/1.5 var(--inf-body-font);
  color:var(--inf-ink);
}
.inf-field textarea{resize:vertical; min-height:56px; display:block}
.inf-field select{cursor:pointer}
.inf-field input::placeholder,.inf-field textarea::placeholder{color:var(--inf-muted)}
/* AUDIT D, D-10. Green on white is 3.04:1, which clears the 3:1 non-text
   threshold - but the border it replaces is --inf-body, and in greyscale
   that is luminance 68 going to 152. Focusing a field made its outline
   FAINTER, so a partner who cannot see the hue change saw the focused
   field become less prominent than its neighbours. Weight carries the
   change instead: 2px against the resting 1px, doubled rule in greyscale.
   The negative margin absorbs the extra pixel so no row reflows on focus,
   and the halo goes from .18 (1.13:1 over white - invisible) to .35. */
.inf-field:focus-within{
  border-color:var(--inf-green); border-width:2px; margin:-1px;
  box-shadow:0 0 0 2px rgba(118,161,52,.35);
}
.inf-field.is-error{border-color:var(--inf-error)}
.inf-field.is-error>label{color:var(--inf-error)}
/* Read-only derived values sit in the same box so the form keeps one rhythm,
   but without the outline that says "type here". */
.inf-field.is-derived{border-style:dashed; border-color:var(--inf-rule)}

/* Errors: 9px caps, very short. Handoff §7.6. */
.inf-error-text,.req{
  font:700 var(--inf-label-xs)/1.5 var(--inf-display);
  letter-spacing:var(--inf-ls-label); text-transform:uppercase;
}
/* AUDIT D, D-06. Green at 9px/700 is 3.04:1. This word marks a field that
   blocks the submission, so it is the last thing that should be faint. */
.req{color:var(--inf-body); margin-left:.35rem}

select,input[type=text],input[type=email],input[type=tel],input[type=search],textarea{
  font:400 var(--inf-small)/1.4 var(--inf-body-font); color:var(--inf-ink);
  background:var(--inf-surface); border:1px solid var(--inf-body); padding:9px 11px;
}
/* PHASE 4C, change 6 — ONE BOX PER FIELD, AND THE WRAPPER DRAWS IT.
 *
 * The rule directly above styles a STANDALONE control - the search box, the
 * Tier 2 fields - which has no notched outline around it and therefore has to
 * draw its own border. Inside .inf-field the wrapper draws the box and the
 * control must not draw a second one.
 *
 * It was drawing one anyway. `.inf-field input` (0,1,1) and
 * `input[type=text]` (0,1,1) are the SAME specificity, so source order decided
 * and the generic rule, 35 lines later, won. Every control inside a notched
 * outline was painting a second border inside the first: 8 edges where there
 * should be 4.
 *
 * On a plain text field the two borders sit flush and the doubling is
 * invisible. On a money field they do not, because the $ affix is a sibling
 * OUTSIDE the input: you get the wrapper's edge, then the $, then the input's
 * own left border as a vertical rule mid-box, then doubled thickness above and
 * below the input for the rest of the row. That is the reported artefact
 * exactly, and it was in every one of the five money fields.
 *
 * Restated after the generic rule rather than by raising specificity, because
 * only the border is wrong. The 9px/11px padding from that rule is what the
 * whole form is currently laid out on - `.inf-field input`'s own padding:16px
 * has been overridden for as long as both rules have existed - and restoring
 * it would grow every field on the form. That is a separate decision, noted in
 * the README, not smuggled in behind a border fix. */
.inf-field input,.inf-field textarea,.inf-field select{
  border:0; background:transparent;
}
/* AUDIT D, D-02 — SCOPED TO .inf-field, WHICH IS THE ONLY THING THAT REPLACES
   THE RING WITH SOMETHING ELSE.

   This used to read `select:focus,input:focus,textarea:focus{...outline:none}`.
   At specificity (0,1,1) that beat the global `:focus-visible` rule above at
   (0,1,0) and switched the ring off for EVERY input on the page. Fields inside
   a notched outline survive it, because .inf-field:focus-within moves the
   indicator to the wrapper's border - but a checkbox has no wrapper, so the
   role boxes, the not-applicable boxes and the affirmation had no visible
   focus state at all. The affirmation is required to submit, and a keyboard
   partner tabbing the contacts block could not tell where they were.

   Scoped, so anything without a wrapper falls through to the 2px green ring. */
.inf-field select:focus,
.inf-field input:focus,
.inf-field textarea:focus{border-color:var(--inf-green); outline:none}

/* ===========================================================================
   STEPS
   =========================================================================== */
.steps{
  flex:0 0 auto; display:flex; background:var(--inf-surface-alt);
  border-bottom:1px solid var(--inf-shade); padding:0 var(--inf-gutter-lg);
  position:relative; z-index:1;
}
/* TABS, NOT A PROGRESS BAR.
 *
 * They relied on an underline and a filled number chip, which reads as "step 1
 * of 2 complete" rather than "two pages, and you are on this one". The active
 * tab now sits in a container - surface panel, ink top edge, no bottom border
 * so it joins the page below it - and the inactive tab carries a hover and a
 * pointer that say it is somewhere to go.
 *
 * NO EXTRA HEIGHT. The strip is above a step that must not scroll, so the
 * container is painted inside the padding the tab already had - the 11px/9px
 * becomes 9px/9px and the 2px border moves from the bottom edge to the top.
 * Measured after: 53px at 1366 and 1180, unchanged, and 55px at 1440 and 1920,
 * which is 2px SHORTER than before. The strip costs the map nothing.
 */
.step{
  display:flex; align-items:center; gap:10px; cursor:pointer;
  background:none; border:0; border-top:2px solid transparent;
  padding:9px 16px 9px 14px; margin-right:.5rem; font:inherit;
  color:var(--inf-muted); text-align:left; position:relative;
  transition:background var(--inf-dur-fast) var(--inf-ease);
}
.step:hover{color:var(--inf-ink); background:var(--inf-surface-alt)}
.step.is-on{
  color:var(--inf-ink); background:var(--inf-surface);
  border-top-color:var(--inf-green);
  box-shadow:1px 0 0 var(--inf-shade), -1px 0 0 var(--inf-shade);
}
/* The active tab covers the strip's own bottom rule, so it reads as joined to
   the page beneath rather than as a segment on a bar. */
.step.is-on::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:var(--inf-surface);
}
.step-n{
  width:22px; height:22px; flex:none; display:grid; place-items:center;
  font:700 var(--inf-label-xs)/1 var(--inf-display); letter-spacing:0;
  border:1px solid var(--inf-rule); color:var(--inf-muted);
}
/* AUDIT D, D-07. White on green is 3.04:1 at 9px/700; ink on green is
   4.54:1 and passes. The done state below is ink-on-ink-bg and already
   measures 13.77:1, so only the active state moves. */
.step.is-on .step-n{background:var(--inf-green); border-color:var(--inf-green);
  color:var(--inf-ink)}
.step.is-done .step-n{background:var(--inf-ink); border-color:var(--inf-ink); color:#fff}
.step-t{
  display:block; font:700 var(--inf-label)/1.4 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label);
}
.step-s{
  display:block; font:400 .6875rem/1.4 var(--inf-body-font);
  text-transform:none; letter-spacing:0; color:var(--inf-muted); margin-top:2px;
}
.step-s.is-todo{color:var(--inf-error)}

.stage{flex:1 1 auto; display:flex; min-height:0}

/* ===========================================================================
   STEP 1 — THE CONFIRMATION FORM

   This step scrolls, so it takes the full design rhythm.

   TWO SEMANTIC STATES, RESOLVED IN PALETTE

   The old form painted "we hold nothing" pink and "you changed this" a green
   wash. Neither colour exists in this system, which is achromatic plus one
   green. They are now:

     we hold nothing for this   the #F8F8F8 surface-alt band, no rule
     you changed this           a 3px #76A134 left rule and a green label,
                                on white

   Both stay distinct from each other and from an untouched row, and both
   survive greyscale — one is a light wash with no rule, the other is white
   with a solid mid-grey rule, and an untouched row is white with neither.
   Nothing depends on hue.
   =========================================================================== */
.confirm{flex:1 1 auto; overflow-y:auto; min-height:0; background:var(--inf-surface)}
.confirm-inner{max-width:1180px; margin:0 auto; padding:var(--inf-stack-gap) var(--inf-gutter-lg) var(--inf-section-pad)}
/* A DELIBERATE DEPARTURE FROM HANDOFF §3.4.
   The system insets text columns to 140/240px and caps them narrow, which is
   right for an article and wrong here: at 1366x768 a 60ch header pushed the
   first company field almost off the screen, so the opening impression of a
   form about the partner's own record was three paragraphs of prose. Widened
   toward the container and the gap below it cut from the 64px block rhythm to
   the 48px stack rhythm. Type, colour, radius, tracking and the offset block
   are untouched; only the measure and one gap move. */
.confirm-top{margin-bottom:var(--inf-stack-gap); max-width:none}
.confirm-h{margin:.5rem 0 1rem; max-width:22ch}
.confirm-top .confirm-lede,
.confirm-top .confirm-key{max-width:78ch}
.confirm-lede{
  font:300 1.375rem/1.6 var(--inf-display); letter-spacing:var(--inf-ls-heading);
  color:var(--inf-body); margin-bottom:1.5rem;
}
.confirm-key{margin-top:1.25rem}
.cf-key-empty{background:var(--inf-surface-alt); border-left:3px solid var(--inf-rule);
  padding:2px 8px; color:var(--inf-ink)}
.cf-key-changed{
  border-left:3px solid var(--inf-green); padding-left:8px; color:var(--inf-green);
  font-weight:500;
}
.confirm-count{
  margin-top:1.5rem; display:inline-block;
  font:700 var(--inf-label)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label);
  color:var(--inf-green); border:1px solid var(--inf-green); padding:8px 16px;
}
.confirm-count.is-none{color:var(--inf-muted); border-color:var(--inf-rule)}

/* §4.7 — the gray band with an all-caps subhead, the site's own way of
   grouping. Full-bleed inside the form column. */
.cf-section{margin-bottom:var(--inf-block-gap)}
.cf-band{
  background:var(--inf-surface-alt); padding:14px var(--inf-gutter);
  margin:0 calc(var(--inf-gutter) * -1) var(--inf-stack-gap);
  display:flex; align-items:baseline; gap:1rem; flex-wrap:wrap;
}
/* The band's sentence-case heading (§7.4), beside the all-caps eyebrow (§7.3).
   Montserrat 100 at the h3 size, not the 70px page title — this is a section,
   and five page-sized headings inside one form is a scrolling exercise. */
/* PHASE 4C, change 4. 24px -> 32px. Only the contacts band carries one of
   these, and it is the section a partner spends longest in. Still well under
   the 70px page title, so the hierarchy holds. */
.cf-band-h{
  font:100 2rem/1.2 var(--inf-display); letter-spacing:var(--inf-ls-heading);
  color:var(--inf-ink); flex:0 0 auto;
}
/* The CONTACTS. eyebrow, raised with its heading. Scoped by id to that one
   band - the other five eyebrows keep the system's 12px label size. */
#cfs-contacts{font-size:.875rem}
/* D-03: the band instruction is 15px and was 4.17:1. */
.cf-sub{font:400 var(--inf-small)/1.5 var(--inf-body-font); color:var(--inf-body)}
/* The instruction line wraps under the eyebrow and heading rather than
   squeezing beside them, so a long one does not shrink the heading. */
.cf-band .cf-sub{flex:1 1 100%; max-width:80ch}

/* ---------------------------------------------------------------------------
   THE TWO ASSERTED COMPLIANCE ROWS

   INFINIUM's statement of its own record, read-only. Rendered as a statement
   and not a control: no input, nothing focusable to type into, because giving
   a partner a box for something they cannot change is the lie this whole
   change exists to remove.

   The dashed border is the same treatment the derived OT differentials get —
   both are "shown, not asked", and the form already teaches that reading.
   `is-missing` adds the green rule, because a No is not an error: it is the
   most useful row on the page for 47 of 73 partners, and the one with
   something to do.
   --------------------------------------------------------------------------- */
.inf-field.is-asserted{
  border-style:dashed; border-color:var(--inf-rule);
  display:flex; align-items:center; min-height:56px;
}
.cf-assert{
  padding:16px; font:500 var(--inf-body-size)/1.5 var(--inf-body-font);
  color:var(--inf-ink); letter-spacing:.02em;
}
/* AUDIT D — THE "WE HOLD NOTHING" COMPLIANCE ROW IS NEUTRAL, NOT GREEN.
 *
 * It used to render the value and the field outline in --inf-green. Two things
 * wrong with that. Green measures 3.04:1 on white at 17px/500, under the 4.5:1
 * floor. And green carries valence: everywhere else in this app and on
 * infiniumwalls.com it means good, done, yours - the changed-row rule, the
 * ticked checklist mark, the plus bullets. Here it was marking the two rows
 * where INFINIUM holds NOTHING and the partner owes us a document. The signal
 * was inverted on exactly the fields we most need back.
 *
 * The replacement is ink, and ink only:
 *
 *   value       --inf-ink #2D2D2D at 13.77:1, the page's own text colour and
 *               the most neutral mark available. No hue, so no valence.
 *   field box   --inf-ink, one step darker than the resting --inf-body border,
 *               so the row reads as emphasised without reading as alarmed.
 *   row rule    3px --inf-ink in the slot every .cf-row already has.
 *
 * Distinguishable from the other three row states by luminance alone, which is
 * what keeps it working in greyscale:
 *
 *   untouched row       rule transparent      (255)
 *   we hold nothing     rule --inf-rule       (217)
 *   you changed this    rule --inf-green      (144)
 *   we hold nothing,    rule --inf-ink        ( 45)
 *   and you cannot fix
 *   it here
 *
 * No red: this is not an error, the partner has done nothing wrong, and the
 * copy beside it is an instruction rather than a warning. */
.cf-row-asserted.is-missing{border-left-color:var(--inf-ink)}
.cf-row-asserted.is-missing .inf-field.is-asserted{border-color:var(--inf-ink)}
.cf-row-asserted.is-missing .cf-assert{color:var(--inf-ink); font-weight:500}
/* D-03: 12px --inf-muted is 4.17:1. */
.cf-assert-by{
  margin-top:6px; font:400 .75rem/1.45 var(--inf-body-font); color:var(--inf-body);
}
/* Never banded as "we hold nothing" and never marked changed — it is not a
   field the partner can leave empty or edit. */
.cf-row-asserted.is-empty-onfile{background:none; border-left-color:transparent}

/* PHASE 4C, change 9 — THESE TWO ROWS BREAK THE GRID, ON PURPOSE.
 *
 * Every other .cf-row is two columns: the control on the left, a 34ch notes
 * column on the right. The COI and Installer Agreement rows collapse to ONE
 * column and put their instruction underneath the field and its "On file at
 * INFINIUM" subtext, spanning the full content width.
 *
 * WHY, so nobody later "fixes" it back into line: these are the only two rows
 * on the form carrying an action the partner must complete OUTSIDE the form,
 * after they have submitted. 47 of 73 partners get the COI instruction and 37
 * of 73 get the agreement one, and most get both. A narrow aside column is
 * where you put "leave blank if it is the same as your office address"; it is
 * not where you put "ask your insurance agent for a certificate covering
 * INFINIUM work, then email it to Seb Layne". The instruction is the row.
 *
 * Recorded in README.md under "Two rows deliberately break the grid". */
.cf-instruction{
  margin-top:14px; padding-top:14px; border-top:1px solid var(--inf-rule);
  display:flex; flex-direction:column; gap:.6rem; max-width:78ch;
}
/* Change 8 asked for the instruction blocks to grow with the links. 15px ->
   17px, matching the field value above them - these are the only notes on the
   form that are instructions rather than asides, and they read at the same
   weight as the answer they are about. */
.cf-instruction .cf-note{
  font:400 var(--inf-body-size)/1.6 var(--inf-body-font); color:var(--inf-body);
}

/* A contact with no role ticked. Same green rule the changed state uses, on the
   fieldset rather than the row, so the partner's eye lands on the boxes. */
.cf-person.is-roleless .cf-fns{
  border-left:3px solid var(--inf-error); padding-left:12px; margin-left:-15px;
}
.cf-fn-err{margin-top:4px}
.cf-fields{display:flex; flex-direction:column; gap:1.75rem}

.cf-row{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,34ch);
  gap:var(--inf-gutter); align-items:start;
  padding:12px 16px; margin:0 -16px; border-left:3px solid transparent;
}
/* PHASE 4C change 9, and it MUST sit after .cf-row rather than beside the other
   .cf-row-asserted rules further up. Those all carry a second class and win on
   specificity; this one is a single class, the same as .cf-row, so source order
   is what decides it. Moving it back up silently restores the two-column grid. */
.cf-row-asserted{grid-template-columns:minmax(0,1fr)}
/* State 1 — we hold nothing for this. The surface-alt band, plus a hairline in
   the palette's own rule grey. The band alone is #F8F8F8 against #FFFFFF, a
   3% step that survives a screen but not a photocopier; the rule is what makes
   the state legible without colour. */
.cf-row.is-empty-onfile{background:var(--inf-surface-alt); border-left-color:var(--inf-rule)}
/* State 2 — you changed this. A green rule and a green label, on white.
   The field's own outline goes green too: the 3px rule sits at the row edge,
   clear of the control, and on a wide row that is a long way from the thing
   the partner just typed in. The rule is what carries the state in greyscale;
   the outline is what makes it obvious in colour. */
.cf-row.is-changed{background:var(--inf-surface); border-left-color:var(--inf-green)}
.cf-row.is-changed .cf-label{color:var(--inf-green)}
.cf-row.is-changed .inf-field{border-color:var(--inf-green)}
.cf-label{color:var(--inf-ink)}
.cf-control{min-width:0}
.cf-derived{
  display:block; padding:16px; font:500 var(--inf-body-size)/1.5 var(--inf-body-font);
  color:var(--inf-body);
}
/* The currency prefix sits inside the field, on the same line as the value.
   Without the flex row the width:100% input drops below the $ and the field
   renders as two stacked boxes. */
.cf-money{display:flex; align-items:center}
.cf-money i{font-style:normal; flex:none; padding-left:16px; color:var(--inf-muted)}
.cf-money input{padding-left:6px}
.cf-notes{padding-top:1.25rem; display:flex; flex-direction:column; gap:.5rem}
/* ACCEPTANCE.md C — why the map is empty. The surface-alt band with a hairline,
   the same treatment "we hold nothing for this" gets on step 1, because it is
   the same statement about the same kind of gap. */
.blankmap{
  margin-top:10px; padding:9px 12px;
  background:var(--inf-surface-alt); border-left:3px solid var(--inf-rule);
  font:400 .75rem/1.45 var(--inf-body-font); color:var(--inf-body);
}
/* The map step has no spare vertical room at 1366x768 - the page must not
   scroll - so this paragraph gives some back on short screens. */
@media (max-height:820px){
  .blankmap{margin-top:8px; padding:8px 11px; font-size:.6875rem}
}
.blankmap b{color:var(--inf-ink); font-weight:500}

/* ===========================================================================
   CITY LABELS — the orientation layer that replaced the basemap

   Deliberately the quietest thing on the map. Muted grey at 11px, a 3px dot,
   and a white halo built from four text-shadows so the name survives being
   drawn over the 70%-opacity green fill and over the hatch. In greyscale the
   halo is what keeps it legible: the label is mid-grey on whatever is beneath,
   and the white ring separates the two.

   pointer-events:none is set THREE times for this feature - here, on the pane
   in mapview.js, and via interactive:false on every marker. A label that
   swallowed a click would present as "some counties near cities cannot be
   selected", which is a bug nobody would diagnose from a partner's report.
   =========================================================================== */
.city-label{
  pointer-events:none !important;
  white-space:nowrap; width:auto !important; height:auto !important;
  display:flex; align-items:center; gap:4px;
  transform:translate(-3px,-7px);
}
.city-label i{
  display:block; width:3px; height:3px; flex:none;
  background:var(--inf-ink); opacity:.55;
}
.city-label b{
  font:400 11px/1 var(--inf-body-font); font-weight:500;
  color:var(--inf-body); letter-spacing:.01em;
  text-shadow:
     0 1px 0 #fff,  0 -1px 0 #fff,  1px 0 0 #fff, -1px 0 0 #fff,
     1px 1px 0 #fff, -1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff;
}

/* B2 — the explicit not-applicable path. A quiet checkbox under the field,
   never a second control competing with it: this is a partner saying "no such
   thing", which is a smaller act than filling the field in. */
/* PHASE 4C, change 3. 13px -> 15px, and the colour off --inf-muted, which was
   4.17:1. Still a step below the 17px field value it sits under, which is the
   subordination that matters: the value is the answer, this is the escape
   hatch. The box grows with it so the target is not a 14px speck. */
.cf-na{
  display:inline-flex; align-items:center; gap:10px; margin-top:10px; cursor:pointer;
  font:400 var(--inf-small)/1.5 var(--inf-body-font); color:var(--inf-body);
}
.cf-na input{accent-color:var(--inf-green); width:16px; height:16px; flex:none}

/* ===========================================================================
   PHASE 4D — WAREHOUSING BY LOCATION, AND THE TWO RATE-SCOPE BOXES
   =========================================================================== */

/* Warehousing is a fieldset of places, not a Yes/No. The legend is the field
   label and is NOT a notched outline: there is no single control for a label to
   notch into, and faking one round a group of checkboxes would be the design
   system's field pattern used for something that is not a field. */
.cf-whs{border:0; padding:0; margin:0; display:flex; flex-direction:column; gap:2px}
.cf-label-static{
  display:block; position:static; margin-bottom:12px; padding:0;
  font:700 var(--inf-label)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
.cf-wh{
  display:flex; align-items:flex-start; gap:12px; padding:9px 11px; cursor:pointer;
  border:1px solid transparent; font-size:var(--inf-small); color:var(--inf-body);
}
.cf-wh:hover{border-color:var(--inf-rule)}
.cf-wh input{accent-color:var(--inf-green); width:16px; height:16px; flex:none;
  margin-top:3px}
.cf-wh-t{display:flex; flex-direction:column; gap:2px; min-width:0}
/* The address the box refers to, so three boxes are not told apart by order. */
.cf-wh-addr{
  font-size:.8125rem; color:var(--inf-muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.cf-wh-sep{height:1px; background:var(--inf-rule); margin:8px 11px}

/* The rate-scope box sits in the notes column of the FIRST field of its pair
   and is pulled down half a row, so it reads as belonging to both halves
   rather than to the field it happens to be rendered beside. */
.cf-scope{
  display:flex; align-items:flex-start; gap:10px; cursor:pointer;
  margin-top:30px; padding:10px 12px;
  border:1px solid var(--inf-rule); background:var(--inf-surface);
  font-size:.8125rem; line-height:1.4; color:var(--inf-body);
}
.cf-scope input{accent-color:var(--inf-green); width:16px; height:16px; flex:none;
  margin-top:1px}
.cf-scope.is-on{border-color:var(--inf-ink); color:var(--inf-ink); font-weight:500}
/* The other scope, while one is chosen. Shown rather than hidden, so the
   partner can see both choices and see which one they made. */
.cf-scope.is-blocked{opacity:.45; cursor:not-allowed}
.cf-scope.is-blocked input{cursor:not-allowed}

/* A RATE PAIR SWITCHED OFF BY A SCOPE — the fifth row state.
 *
 * It does NOT take a fifth colour in the left rule, and that is deliberate.
 * The rule column encodes what changed about the partner's record, and a
 * switched-off pair has nothing to say there - so it keeps the untouched row's
 * transparent rule and the four-state luminance ladder from AUDIT-D-VISUAL.md
 * §0.4 is untouched:
 *
 *     untouched            transparent   255
 *     we hold nothing      --inf-rule    217
 *     you changed this     --inf-green   144
 *     we hold nothing and
 *     you cannot fix it    --inf-ink      45
 *
 * A fifth rule colour would have had to sit inside those gaps - the only
 * unused palette value near them is --inf-muted at 124, twenty from the green -
 * and twenty is not a distinction anybody can rely on in greyscale. So this
 * state is carried by a TEXT tag and a dashed box instead, which need no
 * luminance budget at all and survive desaturation by construction.
 *
 * Dashed rather than solid because the system already uses dashed for a box
 * that is not typed into: .inf-field.is-derived and the asserted rows. */
.cf-row.is-scope-off .inf-field{border-style:dashed; border-color:var(--inf-rule)}
.cf-row.is-scope-off .cf-money i,
.cf-row.is-scope-off .cf-label,
.cf-row.is-scope-off .cf-derived{color:var(--inf-muted)}
.cf-row.is-scope-off .cf-money input{background:var(--inf-surface-alt)}

.cf-off{display:flex; align-items:center; gap:12px; margin-top:10px;
  font:400 var(--inf-small)/1.5 var(--inf-body-font); color:var(--inf-body)}
.cf-off[hidden]{display:none}
/* Neutral, like the compliance rows: this is not an error and not an edit. */
.cf-tag-off{color:var(--inf-body); border-color:var(--inf-muted)}
.cf-off-why{color:var(--inf-muted); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; min-width:0}
.cf-row.is-na .inf-field{border-color:var(--inf-rule)}
.cf-row.is-na .inf-field input,.cf-row.is-na .inf-field textarea{
  background:var(--inf-surface-alt); color:var(--inf-muted);
}
.cf-row.is-na .cf-na{color:var(--inf-ink)}

/* Handoff §7.6: error messages render at 9px caps and must be very short.
   "Must be a future date", not a sentence. */
.cf-err{
  margin-top:6px;
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-error);
}
.cf-row.is-bad .inf-field{border-color:var(--inf-error)}
.cf-row.is-bad .inf-field>label{color:var(--inf-error)}

/* AUDIT D, D-03. Was var(--inf-muted) #7C7C7C, which measures 4.17:1 on white
   and 3.93:1 on the grey band - under the 4.5:1 floor, on all 26 helper notes,
   which are the instructions this form runs on. --inf-muted is the live site's
   colour for footer meta at 9-10px; the port promoted it to 15px instructional
   copy. --inf-body is the site's own body colour: 9.74:1 on white, 9.17:1 on
   the band, and already in the palette. */
.cf-note{font:400 var(--inf-small)/1.6 var(--inf-body-font); color:var(--inf-body)}
.cf-note.is-flag{color:var(--inf-ink); border-left:2px solid var(--inf-rule); padding-left:12px}

.cf-was{display:flex; align-items:center; gap:12px; margin-top:10px;
  font:400 var(--inf-small)/1.5 var(--inf-body-font); color:var(--inf-body)}
/* display:flex outranks the user agent's [hidden]{display:none}, so without
   this every row announces itself as changed. */
.cf-was[hidden]{display:none}
.cf-wasval{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0}
.cf-tag{
  font:700 var(--inf-label-xs)/1 var(--inf-display); letter-spacing:var(--inf-ls-label);
  text-transform:uppercase; color:var(--inf-green); border:1px solid var(--inf-green);
  padding:4px 7px; flex:none;
}
.cf-tag-new{color:var(--inf-muted); border-color:var(--inf-rule)}
.cf-revert{
  background:none; border:0; padding:0; cursor:pointer; flex:none;
  font:700 var(--inf-label-xs)/1 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
  border-bottom:1px solid var(--inf-green-lt);
}

/* ---------------------------------------------------------------- contacts */
.cf-person{border:1px solid var(--inf-rule); border-left:3px solid var(--inf-rule);
  padding:1.25rem 1.5rem 1.5rem; margin-bottom:1.25rem}
.cf-person.is-changed{border-left-color:var(--inf-green)}
.cf-person.is-spare{border-style:dashed}
.cf-person-h{display:flex; align-items:center; gap:12px; margin-bottom:1.25rem}
.cf-person-n{
  font:700 var(--inf-label)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
.cf-person-grid{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1.5rem}
.cf-field{margin-top:.5rem}
.cf-fns{border:0; margin-top:1.5rem; padding-top:1.25rem; border-top:1px solid var(--inf-shade)}
.cf-fns legend{
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-muted); margin-bottom:.75rem; padding:0;
}
.cf-fn{
  display:inline-flex; align-items:center; gap:8px; margin:0 1.5rem .5rem 0;
  font-size:var(--inf-small); color:var(--inf-body); cursor:pointer;
}
.cf-fn input{accent-color:var(--inf-green)}
.cf-fn.is-danger{color:var(--inf-error)}
.cf-fn.is-danger input{accent-color:var(--inf-error)}
.cf-orphans{margin-top:1.5rem; padding:1.25rem 1.5rem; background:var(--inf-surface-alt)}
.cf-orphan-h{
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink); margin-bottom:1rem;
}

/* --------------------------------------------------------------- affirmation
   Carries the signature offset block. */
.affirm{
  background:var(--inf-surface); border:1px solid var(--inf-muted);
  box-shadow:var(--inf-shadow-solid); padding:1.5rem 1.75rem;
  margin-top:var(--inf-block-gap);
}
.affirm.is-on{border-color:var(--inf-green)}
/* AUDIT D, D-08. 122 characters on one 810px line - every other block on
   the page wraps, this one had no measure cap. 68ch at 15px Roboto lands
   at about 66 real characters, inside the 45-75 range, and breaks the bold
   claim away from the sentence that qualifies it.
   D-16: the label is also the hit area for a REQUIRED checkbox and was
   24px tall. padding-block takes it to 32px without touching type. */
.affirm-label{display:flex; align-items:flex-start; gap:14px; cursor:pointer;
  font-size:var(--inf-small); line-height:1.6; padding-block:4px}
.affirm-label span{max-width:68ch}
.affirm-label input{accent-color:var(--inf-green); width:18px; height:18px;
  margin-top:2px; flex:none}
.affirm-label b{color:var(--inf-ink); font-weight:500}

/* Restored-draft banner. */
.restored{
  margin-bottom:var(--inf-stack-gap); padding:1rem 1.25rem;
  background:var(--inf-surface-alt); border-left:3px solid var(--inf-green);
  font-size:var(--inf-small); color:var(--inf-body);
  display:flex; align-items:flex-start; gap:12px;
}
.restored p{flex:1 1 auto}
.restored.is-warn{border-left-color:var(--inf-error)}

/* ===========================================================================
   STEP 2 — THE MAP

   Fixed viewport, compact rhythm, no page scroll. Same type, colour, radius
   and offset block; not the 80px spacing.
   =========================================================================== */
.workspace{flex:1 1 auto; display:flex; min-height:0}
.mapcol{flex:1 1 auto; display:flex; flex-direction:column; min-width:0; min-height:0}

/* STEP 2's TITLE.
 *
 * Eyebrow and heading share ONE LINE - the §4.7 band pattern - because this
 * step cannot scroll and every pixel of heading is a pixel off the map. Step
 * 1's 70px treatment is not available here at any weight.
 *
 * MONTSERRAT 500, NOT 100, AND THAT IS THE DOCUMENTED RULE RATHER THAN A
 * PREFERENCE. This was written first as `font:100 1.25rem` on the reasoning
 * that the thin weight is the identity. It is the same drift the header
 * company name was: the handoff mandates Montserrat 100 on LARGE headings -
 * the 70px title, the 55px section heads - where hairline strokes are an
 * effect at a size that can carry them. At 20px they are just thin, and the
 * README says in as many words that nothing in this repository will fail if
 * you set 100 below about 24px. So the compact heading takes a real weight and
 * carries its identity through the eyebrow and the tracking instead.
 */
.step2-head{
  flex:0 0 auto; display:flex; align-items:baseline; gap:14px; flex-wrap:nowrap;
  padding:6px var(--inf-gutter-lg); background:var(--inf-surface);
  border-bottom:1px solid var(--inf-shade); min-width:0;
}
.step2-head .inf-label{flex:none; color:var(--inf-muted)}
.step2-h{
  font:500 1.25rem/1.3 var(--inf-display); letter-spacing:var(--inf-ls-heading);
  color:var(--inf-ink); margin:0; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* flex-wrap:wrap, deliberately. The toolbar must fit on ONE row at 1366 - a
   second row shortens the map and every shape moves out from under the pointer
   between one click and the next, and there is an assertion for it. But nowrap
   is the wrong way to guarantee that: it makes the toolbar push the map column
   past its share of the page at narrower widths, which turns a shorter map into
   a sideways-scrolling one. Wrapping is the safety valve; the compact control
   sizing below is what stops it ever being needed at 1366. */
.toolbar{
  flex:0 0 auto; display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:8px var(--inf-gutter-lg); background:var(--inf-surface);
  border-bottom:1px solid var(--inf-shade); min-width:0;
}
.toolbar>*{min-width:0}
.tool-group{display:flex; align-items:center; gap:8px}
.tool-spacer{flex:1 1 auto}

/* The jump control, in the toolbar row. WIDE ENOUGH FOR ITS OWN POPUP:
   Chromium sizes a select's popup to the widest option and anchors it to the
   left edge, so a control narrower than its content grows to the right over
   whatever is beside it. The widest option measures 215px; 260 clears it with
   the padding and the arrow, and row 1 has the slack because the spacer was
   absorbing 139px of it at 1366. */
.tool-label{
  font:700 var(--inf-label-xs)/1 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink); flex:none;
}
.toolbar select{width:260px; max-width:260px; font-size:.75rem; padding:6px 8px}

/* The per-tool hint, overlaid on the map's top-left rather than given a row of
   its own: a row costs map height everywhere, and at 1100x700 the map is
   already within 8px of its 400px floor. */
.tool-hint{
  /* left:56px clears Leaflet's zoom control, which owns the top-left corner
     (34px wide at left:10). At left:12 the two overlapped. */
  position:absolute; left:56px; top:12px; z-index:600; max-width:min(46ch,48%);
  background:var(--inf-surface); border:1px solid var(--inf-rule);
  padding:8px 12px; font-size:.75rem; line-height:1.4; color:var(--inf-body);
  pointer-events:none;
}
.tool-hint b{color:var(--inf-ink); font-weight:500}
/* The toolbar is the one place the design and the constraint really bite. The
   0.25em tracking is not negotiable, so a label at 10px is about 40% wider than
   it reads - and six controls at that width do not fit across a 966px map
   column at 1366. The tracking stays and the labels give, which is what
   handoff §7.2 asks for anyway: one or two words. The full sense is kept on
   aria-label and title for anyone who needs it.
   This must stay on ONE ROW. A toolbar that wraps makes the map shorter, and
   every shape then moves out from under the pointer between one click and the
   next. There is an assertion for it. */
.toolbar .btn>span,.toolbar .seg-btn{
  padding:7px 11px; font-size:var(--inf-label-xs);
}
.toolbar .btn .ico,.toolbar .seg-btn .ico{width:11px; height:11px}
@media (max-width:1250px){
  .toolbar select{max-width:150px}
  #undoLabel{max-width:8ch}
}
/* The undo button names what it will undo, so its label is a county name of
   unknown length. Capped: a label that grows wraps the toolbar onto a second
   row, the map gets shorter, and every shape moves out from under the pointer
   mid-click. Full text stays on title and aria-label. */
#undoLabel{max-width:14ch; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; display:inline-block}

.mapwrap{flex:1 1 auto; position:relative; min-height:0}
#map{position:absolute; inset:0; background:#E8EDF2}
#map.is-lasso{cursor:crosshair; touch-action:none}
#map.is-lasso .leaflet-interactive{cursor:crosshair}
.leaflet-container{font:400 .8125rem/1.4 var(--inf-body-font)}
.leaflet-control-attribution{font-size:10px; line-height:1.35; max-width:56ch}
.leaflet-bar a{border-radius:0 !important}

.lasso-overlay{position:absolute; inset:0; pointer-events:none; z-index:700}
.lasso-path{fill:rgba(118,161,52,.16); stroke:var(--inf-ink); stroke-width:2;
  stroke-dasharray:6 4; stroke-linejoin:round}

/* WHICH TIER THE NEXT CLICK LANDS IN, and it is the most consequential thing
   on this screen: forty counties painted into the wrong one is forty undos.
   It was a quiet 12px chip in the corner - correct, and easy to miss. Now it
   carries the tier's own swatch at full size, the name at the label size, and
   an ink border rather than a muted one, so it reads as a state rather than as
   a caption. */
.painting-chip{
  position:absolute; right:12px; top:12px; z-index:600;
  display:flex; align-items:center; gap:11px;
  background:var(--inf-surface); border:2px solid var(--inf-ink);
  box-shadow:var(--inf-shadow-solid);
  padding:10px 16px; font-size:.8125rem; color:var(--inf-body);
}
.painting-chip .sw{width:20px; height:20px}
.painting-chip strong{
  font:700 var(--inf-label)/1 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
/* ===========================================================================
   THE EMPTY STATE — instructions in the space the blank map leaves
   =========================================================================== */
.map-empty{
  position:absolute; z-index:600; left:50%; top:46%; transform:translate(-50%,-50%);
  width:min(520px, 78%); padding:22px 26px;
  /* `position:absolute` above is also the containing block for .map-empty-x. */
  background:rgba(255,255,255,.94); border:1px solid var(--inf-muted);
  box-shadow:var(--inf-shadow-solid);
  /* The whole block is inert. A click passes through to the county underneath
     and a lasso drawn across it is a lasso - the same contract the city labels
     keep. Without this the instruction would be a hole in the map. */
  pointer-events:none;
}
.map-empty-tier{
  display:flex; align-items:center; gap:10px; margin-bottom:14px;
  padding-bottom:12px; border-bottom:1px solid var(--inf-rule);
  font-size:.9375rem; color:var(--inf-body);
}
.map-empty-tier strong{
  font:700 var(--inf-label)/1 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
.map-empty-list{list-style:none; display:flex; flex-direction:column; gap:9px}
.map-empty-list li{font-size:.8125rem; line-height:1.5; color:var(--inf-body)}
.map-empty-list b{color:var(--inf-ink); font-weight:500}

/* WHAT THE TWO TIERS MEAN. Written from TIER_COPY by renderTiers(); there is no
   copy of management's wording in the markup or in here. */
.map-empty-tiers{margin:0 0 14px; padding:0 0 12px;
  border-bottom:1px solid var(--inf-rule)}
.mem-row{display:grid; grid-template-columns:88px 1fr; gap:10px; align-items:baseline;
  padding:3px 0}
.mem-row dt{display:flex; align-items:center; gap:8px;
  font:700 var(--inf-label)/1.4 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-muted)}
.mem-row dd{margin:0; font-size:.8125rem; line-height:1.5; color:var(--inf-body)}
/* The armed tier, marked so the sentence above it still has a referent. Weight
   and colour rather than a fill: this is a legend, not a control. */
.mem-row.is-on dt{color:var(--inf-ink)}
.mem-row.is-on dd{color:var(--inf-ink)}
.mem-row .sw{flex:none}

/* THE ONE THING IN THE OVERLAY THAT TAKES THE POINTER.
 *
 * .map-empty is pointer-events:none so a click through it selects the county
 * underneath and a lasso across it stays a lasso. This opts back in for its own
 * 26px box and nothing else - no parent, no sibling, no ::before. Its box IS a
 * real dead zone on the map, which is true of any control placed over a canvas
 * and is why it is small, in a corner, and not a full-width bar.
 *
 * If you add a second interactive element in here, it needs its own
 * pointer-events:auto and its own assertion that the body beside it still
 * passes clicks through. Do not put pointer-events:auto on .map-empty. */
.map-empty-x{
  pointer-events:auto;
  position:absolute; top:6px; right:6px; width:26px; height:26px;
  display:grid; place-items:center; cursor:pointer;
  background:none; border:1px solid transparent; color:var(--inf-muted);
  font:400 18px/1 var(--inf-body-font); padding:0;
  transition:color var(--inf-dur-fast) var(--inf-ease),
             border-color var(--inf-dur-fast) var(--inf-ease);
}
.map-empty-x:hover{color:var(--inf-ink); border-color:var(--inf-rule)}
.map-empty-x:focus-visible{outline:2px solid var(--inf-green); outline-offset:1px}

/* THE RE-OPEN AFFORDANCE — AND WHY IT IS NOT IN THE TOOLBAR.
 *
 * The brief asked for it in the toolbar. It cannot go there, and the number is
 * the argument: AT 1180x800 THE TOOLBAR ROW HAS THREE PIXELS OF FREE WIDTH.
 * Measured by freezing the spacer and reading the gap between Reset's right
 * edge and the row's content edge. No control of any size fits, and a second
 * toolbar row costs 40px of map — 484px to 444px, which this phase was
 * explicitly told not to spend.
 *
 * Three things were tried first, in order, and all of them are recorded because
 * each looked like the answer:
 *
 *   1. A 34px button. Wrapped the row at 1180. Caught by measuring five
 *      viewports, not by looking at 1366 — 1366 had room and 1100 was already
 *      on two rows, so ONE viewport in five showed it.
 *   2. Shrinking it to 22px. Still wrapped: 3px of room, not 19.
 *   3. Grouping it with Reset so `.tool-spacer` (flex:1 1 auto) could not grow
 *      between them. Still wrapped, for the same reason — the row is full.
 *
 * Taking the width back from the jump control was the remaining option and is
 * the wrong one: at 1180 that select is 200px and its placeholder needs 237px,
 * so it is ALREADY truncated there. Narrowing it further makes a real problem
 * worse to solve a self-inflicted one.
 *
 * So it lives on the map, where it costs no layout height at any viewport —
 * the same reasoning that put the empty state and the tool hint there in 4e.
 * One home at every width rather than two, because a control that moves between
 * screen sizes is worse than one that is somewhere slightly unexpected. */
.btn-icon>span{padding:6px 0; width:22px; display:grid; place-items:center;
  font-size:.8125rem}
.btn-icon.is-on>span{background:var(--inf-shade)}

/* On the map, under the painting chip, sharing its right edge. Same z-index as
   the other overlays. It takes the pointer — it is a control — and its box is a
   real dead zone, which is why it is 26px and in a corner. */
.guide-toggle{
  position:absolute; right:12px; top:60px; z-index:600;
  background:var(--inf-surface);
}

.map-hint{
  position:absolute; left:12px; bottom:12px; z-index:600; max-width:min(52ch,60%);
  background:var(--inf-surface); border:1px solid var(--inf-rule);
  padding:9px 13px; font-size:.75rem; color:var(--inf-muted);
}
.map-hint strong{color:var(--inf-ink); font-weight:500}

/* ---- Tier swatches. ONE GREEN.
   Tier 1 is solid #76A134; Tier 2 is the same green as a diagonal hatch on
   white. There is no second green — the old #DFEACE / #C7D9AD pair is gone,
   and #9EBD70 is reserved for the link hover underline. The two stay apart in
   greyscale because one is solid and the other is textured, not because they
   differ in lightness. */
.sw{display:inline-block; width:16px; height:16px; flex:none;
  border:1px solid var(--inf-ink)}
.sw-primary{background:var(--inf-green)}
.sw-secondary{
  background:repeating-linear-gradient(45deg,
    var(--inf-surface),var(--inf-surface) 3px,var(--inf-green) 3px,var(--inf-green) 6px);
}

/* ------------------------------------------------------------------ sidebar */
/* PINNED TO ITS OWN WIDTH — `min-width:0` ADDED 13 AUG 2026, PHASE 4J.
 *
 * `flex:0 0 var(--side-w)` sets the basis and refuses to shrink, which reads as
 * "this column is 400px". It was not. A flex item's default `min-width:auto`
 * lets its CONTENT push it past its basis, and the selected-county list is full
 * of names like "United Counties of Stormont, Dundas and Glengarry". So the
 * sidebar grew with the selection - 412px empty, 453px at 1,500 areas - and
 * every pixel came out of the map column beside it.
 *
 * WHAT THAT COST, measured at 1366x768: the map fell from 452px to 412px from
 * 500 selected areas upward, because a narrower map column also wrapped the
 * toolbar onto a second row. Twelve pixels above the 400px floor rather than
 * under it, but moving in the wrong direction for the most ordinary thing a
 * national installer does.
 *
 * NOTHING CAUGHT IT because every floor check in the suite measured an empty or
 * lightly-painted map. There is now one that sweeps six selection counts. */
.side{
  flex:0 0 var(--side-w); display:flex; flex-direction:column; min-height:0;
  min-width:0;
  background:var(--inf-surface); border-left:1px solid var(--inf-shade);
}
.side-scroll{flex:1 1 auto; overflow-y:auto; overscroll-behavior:contain}
.panel{padding:16px var(--inf-gutter-lg); border-bottom:1px solid var(--inf-shade)}
.panel-h{
  font:700 var(--inf-label)/1.5 var(--inf-display); letter-spacing:var(--inf-ls-label);
  text-transform:uppercase; color:var(--inf-ink); margin-bottom:12px;
  display:flex; align-items:baseline; gap:8px;
}
.panel-count{
  margin-left:auto; letter-spacing:0; text-transform:none;
  font:400 .75rem/1 var(--inf-body-font); color:var(--inf-muted);
}
.hint{font-size:.75rem; line-height:1.5; color:var(--inf-muted); margin-top:10px}
.field{margin-bottom:14px}
.field:last-child{margin-bottom:0}

/* ------------------------------------------------------------------- search */
.find{display:flex; gap:8px}
.find input{flex:1 1 auto; min-width:0}
.results{list-style:none; margin-top:12px}
.results:empty{margin-top:0}
.result{
  display:block; width:100%; text-align:left; background:var(--inf-surface);
  cursor:pointer; border:1px solid var(--inf-rule); padding:10px 12px;
  margin-bottom:8px; font:inherit; color:inherit;
}
.result:hover{border-color:var(--inf-green)}
.result-title{font-weight:500; display:block; color:var(--inf-ink); font-size:.8125rem}
.result-detail{font-size:.75rem; color:var(--inf-muted); display:block; margin-top:3px}
.result-tag{
  float:right; font:700 var(--inf-label-xs)/1 var(--inf-display);
  letter-spacing:var(--inf-ls-label); text-transform:uppercase;
  color:var(--inf-ink); border:1px solid var(--inf-rule); padding:4px 7px; margin-left:8px;
}
.find-note{margin-top:10px; font-size:.75rem; padding:10px 12px;
  background:var(--inf-surface-alt); color:var(--inf-body)}
.picked{margin-top:10px; padding:12px; background:var(--inf-surface-alt)}
.picked-name{font-size:.8125rem; color:var(--inf-body); margin-bottom:10px}
.picked-name b{font-weight:500; color:var(--inf-ink)}
.picked-btns{display:flex; gap:8px}

/* -------------------------------------------------------------- tier picker */
.tiers{display:flex; flex-direction:column; gap:10px}
.tier{
  display:flex; align-items:flex-start; gap:12px; text-align:left; cursor:pointer;
  border:1px solid var(--inf-rule); padding:12px 14px; background:var(--inf-surface);
  font:inherit; color:inherit;
}
.tier:hover{border-color:var(--inf-muted)}
.tier.is-on{border-color:var(--inf-green)}
.tier-sw{width:18px; height:18px; flex:none; margin-top:2px;
  border:1px solid var(--inf-ink)}
.tier-body{flex:1 1 auto; min-width:0}
.tier-name{
  display:block; font:700 var(--inf-label)/1.5 var(--inf-display);
  text-transform:uppercase; letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
/* AUDIT D, D-04. COLOUR ONLY — NOT ONE WORD OF TIER_COPY IS TOUCHED.
   These two lines carry management's wording of 10 August, and 73 partners
   classify their whole territory from them. They were the faintest and second
   smallest text on the map step: --inf-muted at 12px is 4.17:1, under the
   4.5:1 floor. --inf-body is 9.74:1. The text itself is rendered from
   TIER_COPY in js/ui.js and is unchanged; this file has never held it. */
.tier-desc{display:block; font-size:.75rem; line-height:1.5;
  color:var(--inf-body); margin-top:5px}
.tier-count{
  font:100 1.75rem/1 var(--inf-display); color:var(--inf-ink);
  min-width:32px; text-align:right; flex:none;
}
.tier.is-on .tier-count{color:var(--inf-green)}
.kbd{
  display:inline-block; font:500 .625rem/1 var(--inf-body-font); letter-spacing:0;
  color:var(--inf-muted); border:1px solid var(--inf-rule);
  padding:3px 5px; margin-left:6px; text-transform:none;
}
/* "Leave Tier 2 empty if your rates are the same everywhere you work." Many
   partners have no Tier 2 and must not read a blank one as an unanswered
   question, so this sits inside the card rather than in a footnote. */
/* D-04, second half. Green at 12px is 3.04:1. The weight-500 already separates
   this from .tier-desc, so the colour was carrying emphasis it did not need to.
   Wording unchanged - it is TIER_COPY[SECONDARY].note. */
.tier-note{display:block; margin-top:7px; font:500 .75rem/1.45 var(--inf-body-font);
  color:var(--inf-body)}
.natflag{
  display:inline-block; font:700 var(--inf-label-xs)/1 var(--inf-display);
  letter-spacing:var(--inf-ls-label); text-transform:uppercase; color:#fff;
  background:var(--inf-green); padding:4px 7px; margin-left:6px;
}

/* --------------------------------------------------- Tier 2 follow-up block */
.radios{border:0; display:flex; flex-direction:column; gap:6px}
.radio{
  display:flex; align-items:flex-start; gap:10px; cursor:pointer;
  border:1px solid var(--inf-rule); padding:10px 12px;
  font-size:.8125rem; line-height:1.45; color:var(--inf-body);
}
.radio:hover{border-color:var(--inf-muted)}
.radio:has(input:checked){border-color:var(--inf-green); color:var(--inf-ink)}
.radio input{accent-color:var(--inf-green); margin-top:2px; flex:none}
.follow{margin-top:14px; padding-top:14px; border-top:1px solid var(--inf-shade)}
.amount{display:flex; align-items:center}
.amount input{flex:1 1 auto; min-width:0}
.amount-fix{font:400 var(--inf-body-size)/1 var(--inf-body-font);
  color:var(--inf-muted); flex:none; padding:0 4px 0 16px}
.amount-fix:empty{display:none}
.mobline{
  font-size:.75rem; line-height:1.5; color:var(--inf-body);
  background:var(--inf-surface-alt); padding:10px 12px; margin-bottom:12px;
}
.mobline b{font-weight:500; color:var(--inf-ink)}
.radios-tight{margin-top:14px; padding-top:14px; border-top:1px solid var(--inf-shade)}
.radios-tight legend{
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
  margin-bottom:10px; padding:0;
}
.nudge{
  margin-top:12px; padding:12px; background:var(--inf-surface-alt);
  border-left:3px solid var(--inf-green); font-size:.75rem; line-height:1.5;
  color:var(--inf-body);
}
.nudge p{margin-bottom:10px}
.nudge-btns{display:flex; gap:8px; flex-wrap:wrap}

/* ---------------------------------------------------------------- selection */
.selection:empty::after{
  content:'Nothing selected yet. Click an area on the map, or draw a loop round several.';
  display:block; font-size:.75rem; color:var(--inf-muted); line-height:1.6;
  border:1px dashed var(--inf-rule); padding:14px;
}
.grp{margin-bottom:14px}
.grp-h{display:flex; align-items:baseline; gap:8px; padding:6px 0;
  border-bottom:1px solid var(--inf-shade); margin-bottom:4px}
.grp-name{
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}
.grp-tally{margin-left:auto; font-size:.6875rem; color:var(--inf-muted);
  display:flex; gap:10px; align-items:center}
.grp-tally b{font-weight:500; color:var(--inf-ink)}
.tally-sw{width:9px; height:9px; display:inline-block; margin-right:4px;
  border:1px solid var(--inf-ink)}
.crow{display:flex; align-items:center; gap:10px; padding:5px 0}
.crow+.crow{border-top:1px solid var(--inf-shade)}
.crow-sw{width:11px; height:11px; flex:none; border:1px solid var(--inf-ink)}
/* THE NAMES WRAP RATHER THAN TRUNCATE — CHANGED 13 AUG 2026, PHASE 4J.
 *
 * These were `white-space:nowrap` with an ellipsis, which was invisible while
 * the sidebar was quietly growing to fit them. Pinning it at 400px made the
 * truncation appear: six names in a national selection lost their tails, among
 * them "Lower Connecticut River Valley Planning Region" and "United Counties of
 * Stormont, Dundas and Glengarry".
 *
 * Those are exactly the names this app has taken trouble to get right - the
 * alias table, unitNoun(), the Connecticut planning regions - so truncating
 * them to buy map height would be paying in the wrong currency.
 *
 * THE VERTICAL SPACE IS FREE HERE and that is the whole argument. This list
 * lives inside #sideScroll, which already scrolls to 115,000px at national
 * scale; a second line on six rows is nothing. Horizontal space is what is
 * scarce, because it is shared with the map. So: wrap. */
.crow-name{
  flex:1 1 auto; min-width:0; overflow-wrap:break-word;
  font:400 .8125rem/1.4 var(--inf-body-font); text-align:left;
  background:none; border:0; padding:0; color:var(--inf-body); cursor:pointer;
}
.crow-name:hover{color:var(--inf-ink); border-bottom:1px solid var(--inf-green-lt)}
.crow-tier{
  font:700 var(--inf-label-xs)/1 var(--inf-display); flex:none;
  width:16px; height:16px; display:grid; place-items:center; letter-spacing:0;
  color:var(--inf-muted); border:1px solid var(--inf-rule);
}
.icon-btn{
  flex:none; width:24px; height:24px; border:1px solid transparent; background:none;
  cursor:pointer; color:var(--inf-muted); font:16px/1 var(--inf-body-font);
  display:grid; place-items:center;
}
.icon-btn:hover{border-color:var(--inf-error); color:var(--inf-error)}

/* ===========================================================================
   ACTION BAR — spans both steps, pinned so Download is never below the fold
   =========================================================================== */
.actions{
  flex:0 0 auto; display:flex; align-items:center; gap:24px;
  padding:12px var(--inf-gutter-lg); border-top:1px solid var(--inf-shade);
  background:var(--inf-surface);
}
.checklist{list-style:none; display:flex; flex-wrap:wrap; gap:6px 20px;
  flex:1 1 auto; min-width:0}
.checklist li{display:flex; align-items:center; gap:7px;
  font:400 .75rem/1.4 var(--inf-body-font); color:var(--inf-muted)}
.checklist li.done{color:var(--inf-ink)}
.checklist .mark{
  width:14px; height:14px; flex:none; display:grid; place-items:center;
  border:1px solid var(--inf-rule);
}
.checklist li.done .mark{background:var(--inf-green); border-color:var(--inf-green)}
.checklist .mark svg{width:8px; height:8px; fill:none; stroke:#fff; stroke-width:2.5}
.checklist button{
  background:none; border:0; padding:0; font:inherit; color:inherit; cursor:pointer;
  border-bottom:1px solid var(--inf-green-lt);
}
.action-row{display:flex; gap:12px; flex:0 0 auto}
.action-row .btn-wide{flex:0 0 auto}
.action-row .btn-wide>span{min-width:200px}
.note{flex:0 1 300px; font-size:.6875rem; line-height:1.45; color:var(--inf-body)}
.actions .btn.is-busy>span{color:var(--inf-muted)}

/* AUDIT D, D-01 — THE ACTION BAR HAS TO WRAP BEFORE IT CLIPS.

   The bar is one non-wrapping flex row, which is right at 1366x768 and above:
   the checklist, the two buttons and the note fit on one line and the bar's
   height is what is left of the map. Below about 560px they do not fit, and
   because body{overflow:hidden} the excess is CLIPPED rather than scrolled -
   at 390x844 the Send button's right edge measured 460px in a 390px viewport
   and could not be reached at all. The form step is the half of this app that
   is supposed to work on a phone.

   Wrapping rather than shrinking: the offset block and the 0.25em tracking on
   the button labels are fixed, so a narrower button is a button with clipped
   text. Given a row of its own, each button keeps its full label.

   The desktop row is untouched - this rule cannot apply above 560px, and the
   map step's toolbar-height budget is a 1366px concern. */
@media (max-width:560px){
  .actions{flex-wrap:wrap; gap:12px}
  .checklist{flex:1 1 100%; order:1}
  .action-row{flex:1 1 100%; order:2}
  .action-row .btn-wide{flex:1 1 0}
  .action-row .btn-wide>span{min-width:0}
  .note{flex:1 1 100%; order:3}
  /* Two more things pushed past the right edge at 390px, neither of them a
     control. The save stamp is `flex:none` with a 104px floor, which makes the
     header as wide as logo + name + 104 whatever the screen is; it is allowed
     to shrink here instead of being removed, because it is the only feedback
     that a draft is being kept. The band bleeds by --inf-gutter while its
     container is padded by --inf-gutter-lg, which is inside the viewport at
     1366 and outside it here; matched to the container so it sits flush. */
  .hero-save{min-width:0; flex:0 1 auto; overflow:hidden; text-overflow:ellipsis;
    white-space:nowrap}
  .cf-band{margin-left:calc(var(--inf-gutter-lg) * -1);
    margin-right:calc(var(--inf-gutter-lg) * -1)}
}

/* ---------------------------------------------------------------------------
   THE FAILURE SURFACE

   A send that fails must cost the partner nothing, so this appears and the
   rest of the screen does not move: the fields, the map and the draft are
   exactly as they were. It takes the whole width of the bar and pushes the
   checklist down rather than sitting over anything.

   #B3261E is the approved error extension (handoff D8) and this is validation,
   which is the only thing it is for. It is a 3px left rule and the message
   colour, not a red panel - a red block behind a hundred words would be the
   loudest thing in an otherwise achromatic design, and the partner has done
   nothing wrong.

   Height is capped and the list scrolls. On the map step the action bar eats
   the map, and there are 91px of slack above the 400px floor; a partner with
   six outstanding items must not be the one who discovers that.
   --------------------------------------------------------------------------- */
.sendfail{
  flex:0 0 auto; border-top:1px solid var(--inf-shade);
  border-left:3px solid var(--inf-error);
  background:var(--inf-surface-alt);
  padding:10px var(--inf-gutter-lg); max-height:128px; overflow-y:auto;
}
.sendfail-head{display:flex; align-items:flex-start; gap:12px}
.sendfail-msg{
  flex:1 1 auto; font:500 var(--inf-small)/1.45 var(--inf-body-font);
  color:var(--inf-error);
}
.sendfail-list{list-style:none; margin:6px 0 0; display:flex; flex-wrap:wrap; gap:4px 18px}
.sendfail-list li{
  font:400 .8125rem/1.5 var(--inf-body-font); color:var(--inf-body);
  padding-left:12px; position:relative;
}
.sendfail-list li::before{
  content:""; position:absolute; left:0; top:.6em; width:5px; height:1px;
  background:var(--inf-muted);
}
.sendfail-out{margin-top:6px; font-size:.75rem; line-height:1.5; color:var(--inf-muted)}
/* A button that has to read as prose, because it sits inside a sentence. The
   green-light underline is the system's own link treatment. */
.linkish{
  background:none; border:0; padding:0; font:inherit; color:var(--inf-ink);
  cursor:pointer; border-bottom:1px solid var(--inf-green-lt);
}
.sendfail-list button{
  background:none; border:0; padding:0; font:inherit; color:var(--inf-ink);
  cursor:pointer; border-bottom:1px solid var(--inf-green-lt); text-align:left;
}

/* ===========================================================================
   THE SUCCESS SCREEN

   Handoff §8 lists "Success state — what replaces the form after submit" as an
   open item, and it had never been designed. This is it, and it is the form
   step's rhythm rather than the map step's: it scrolls, so it takes the full
   treatment. 70px Montserrat 100 heading, the eyebrow above it, the §4.7 band
   for the section, the offset-block button, zero radius. No new components.

   It replaces the stage entirely and the action bar is hidden with it. A
   checklist and a Send button under a confirmation would be an invitation to
   press Send again, and the checklist is describing a form that is no longer
   on screen.
   =========================================================================== */
.done{flex:1 1 auto; overflow-y:auto; min-height:0; background:var(--inf-surface)}
.done:focus{outline:none}
.done-inner{
  max-width:760px; margin:0 auto;
  padding:var(--inf-block-gap) var(--inf-gutter-lg) var(--inf-section-pad);
}
.done-h{margin:.75rem 0 1.5rem}
/* Stack rhythm rather than block rhythm below the meta line. The design's 64px
   gap belongs between unrelated blocks; here the very next thing is the only
   thing on the screen a partner still has to act on, and 64px of white above it
   at 768px puts it under the fold. */
.done-meta{
  margin-bottom:var(--inf-stack-gap);
  font:700 var(--inf-label-xs)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-muted);
}
.done-next{display:flex; flex-direction:column; gap:1.5rem}
/* For most partners there are TWO of these - 47 of 73 are owed a COI ask and
   37 of 73 an agreement ask - so the pair has to read as a short numbered list
   of next steps rather than as two paragraphs of bad news. Hence the count
   line and the numerals; hence no warning colour anywhere near it. */
.done-count{
  font:700 var(--inf-label)/1.5 var(--inf-display); text-transform:uppercase;
  letter-spacing:var(--inf-ls-label); color:var(--inf-ink);
}

.done-item{position:relative; padding-left:2.25rem}
.done-n{
  position:absolute; left:0; top:0; width:1.5rem; height:1.5rem;
  display:grid; place-items:center; border:1px solid var(--inf-green);
  font:700 var(--inf-label-xs)/1 var(--inf-display); color:var(--inf-green);
  letter-spacing:0;
}
.done-item b{display:block; color:var(--inf-ink); font-weight:500; margin-bottom:2px}
.done-item span{font-size:var(--inf-small); line-height:1.6; color:var(--inf-body)}
.done-to{display:block; margin-top:6px; color:var(--inf-muted)}
.done-to a{color:var(--inf-ink); font-weight:500}
.done-where{
  margin-top:.5rem; padding:14px 16px; background:var(--inf-surface-alt);
  font-size:var(--inf-small); line-height:1.6; color:var(--inf-body);
}
.done-where b{color:var(--inf-ink); font-weight:500}
/* The "nothing outstanding" branch. Said in a sentence rather than shown as an
   empty list — an empty checklist reads as something that failed to load. */
.done-clear{font-size:var(--inf-body-size); line-height:1.6; color:var(--inf-body)}
.done-clear b{color:var(--inf-ink); font-weight:500}
.done-actions{margin-top:var(--inf-block-gap)}
.done-mock{
  margin-top:var(--inf-stack-gap); padding:14px 16px;
  border:1px solid var(--inf-error); color:var(--inf-error);
  font:400 var(--inf-small)/1.6 var(--inf-body-font);
}

/* ===========================================================================
   1366 x 768 — the target machine.

   The header costs 110px of a 768px viewport. What is left after the step bar,
   the toolbar and the action bar is the map, and it must not go below 400px.
   These rules tighten the map step only; step 1 scrolls and keeps its rhythm.
   =========================================================================== */
@media (max-height:820px){
  .panel{padding:12px var(--inf-gutter-lg)}
  .tier{padding:10px 12px}
  .tier-desc{margin-top:3px}
  .map-hint{font-size:.6875rem; padding:7px 11px}
  .actions{padding:10px var(--inf-gutter-lg)}
  .toolbar{padding:7px var(--inf-gutter-lg)}
  .step{padding:9px 0 7px}
  .confirm-inner{padding-top:2rem; padding-bottom:3rem}
  /* Same trade as step 1: on a 768px screen the identity is the type, not the
     white space, and what a partner needs to see here is the list of documents
     they still owe us. */
  /* AUDIT D, D-05. This block already tightened the success screen; it was not
     tightened enough. At 768px the scroller is 658px and the SECOND outstanding
     item started at y=706 - off screen, with no scroll cue, under a count line
     that had just said there were two. 47 of 73 partners are owed the COI ask
     and 37 of 73 the agreement ask, so two items is the common case and the
     hidden one is the common one. Space comes from above the list, never from
     the list itself. */
  .done-inner{padding-top:2rem; padding-bottom:3rem}
  .done-h{margin:.25rem 0 .75rem}
  .done-meta{margin-bottom:1.25rem}
  .done .cf-band{margin-bottom:1.25rem}
  .done-next{gap:1.25rem}
}
@media (max-width:1180px){
  :root{--side-w:340px; --inf-gutter-lg:1.5rem}
  /* Below 1180 the map column loses 60px to the sidebar and the toolbar's width
     budget will not carry a 260px select AND stay on one row - and a wrapped
     toolbar costs map height at a viewport that is already close to the 400px
     floor. So the select goes back to a width that fits the row, and the popup
     overhang this phase fixed at 1366 and above returns here. That is a
     deliberate trade, not an oversight: a transient popup over a disabled
     button costs less than 40px of permanent map. */
  .toolbar select{width:200px; max-width:200px}
  .map-hint{max-width:min(40ch,70%)}
  .cf-row{grid-template-columns:minmax(0,1fr)}
  .cf-notes{padding-top:0}
}
