/* ============================================================
   Monmouth Coffee Roasters. Layout and components
   Depends on tokens.css. Nothing here declares a raw color.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--face-text);
  font-size: var(--t-md);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
}

img, svg { max-width: 100%; display: block; }

h1, h2, h3 {
  font-family: var(--face-display);
  font-weight: 600;
  font-optical-sizing: auto;
  text-wrap: balance;
  margin: 0;
}

/* Vollkorn ships old-style figures by default, so "$100" set in the
   display face reads with lowercase zeros. Every number in Vollkorn is
   forced to lining figures. */
h1, h2, h3, .tier__amt, .story__lead, .tier__name {
  font-variant-numeric: lining-nums tabular-nums;
}

p { margin: 0; }

a { color: inherit; text-decoration-color: var(--line-strong); text-underline-offset: 0.18em; }
a:hover { text-decoration-color: var(--accent); }

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

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

.band { padding-block: var(--band); }
.band + .band { border-top: 1px solid var(--line); }

/* Density variation. Without these the whole scroll is one rhythm:
   identical padding, identical width, identical heading size. */
.band--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }
.band--loud  { padding-block: clamp(5rem, 11vw, 10rem); }

.prose { max-width: 62ch; }
/* .prose carries no margin, which is right when a section has one of them and
   wrong the moment it has two. Only consecutive ones get the gap, so every
   existing single-paragraph section is untouched. */
.prose + .prose { margin-top: var(--s-4); }

/* ---------- Skip link ---------- */

.skip {
  position: absolute;
  left: var(--gutter);
  top: 0;
  transform: translateY(-120%);
  z-index: 100;
  background: var(--ink);
  color: var(--ground);
  padding: var(--s-3) var(--s-4);
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.18s var(--ease);
}
.skip:focus-visible { transform: translateY(0); }

/* ---------- Mono utility ---------- */

.mono {
  font-family: var(--face-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-weight: 500;
}

/* ---------- Header ---------- */

/* A translucent layer with the page moving underneath it, rather than an
   opaque strip that owns a band of the screen.

   The -webkit- prefix is not optional: Safari and iOS Safari both still
   require it, so without it this rendered flat on the exact browsers the
   material is borrowed from. It was missing.

   92% opacity over a 10px blur was barely a material either. At 74% and 20px
   the page genuinely reads through it, which is the point: the blur is what
   tells you there is something behind, and content scrolling under a surface
   is what makes the surface feel like it floats. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--ground) 74%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--line);
}

/* A scroll edge, not a permanent divider. The rule only has a job once there
   is content passing beneath the bar; at the top of the page it is drawing a
   line between the masthead and nothing. It fades in over the first 80px.

   Driven by scroll() rather than a listener, matching how the rest of this
   page animates. The keyframe runs transparent -> line, so a browser without
   scroll-driven animation support keeps the border it has always had: the
   fallback is the old behaviour, never a missing separator. */
@keyframes masthead-edge {
  from { border-bottom-color: transparent; }
  to   { border-bottom-color: var(--line); }
}

@supports (animation-timeline: scroll()) {
  .masthead {
    animation: masthead-edge linear both;
    animation-timeline: scroll();
    animation-range: 0 80px;
  }
}

/* Translucency is a preference, not a given. Both of these collapse the
   material to a solid surface rather than removing the chrome. */
@media (prefers-reduced-transparency: reduce) {
  .masthead {
    background: var(--ground);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (prefers-contrast: more) {
  .masthead {
    background: var(--ground);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-bottom-color: var(--ink);
  }
}

.masthead__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-8);
  padding-block: var(--s-4);
}

.wordmark {
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-lg);
  letter-spacing: -0.03em;
  line-height: 1;
  text-decoration: none;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.wordmark span {
  font-family: var(--face-mono);
  font-size: 8.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.masthead__nav {
  display: flex;
  gap: var(--s-6);
  font-size: var(--t-base);
  font-weight: 500;
}
/* 44px is the touch minimum. These measured 35px tall, so the hit area grows
   while the type stays put. The masthead does not get taller: the primary
   button is already 55px and sets the row height. */
.masthead__nav a {
  text-decoration: none;
  padding-block: var(--s-2);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.masthead__nav a:hover { color: var(--accent); }

/* ---------- Buttons ---------- */

/* ---------- Buttons ----------
   The one raised surface on the page. Everything structural is still ruled and
   flat; a control the finger is meant to press is the one thing that earns
   depth, and it is the difference between a coloured rectangle and something
   that looks pressable.

   Three things do the work, and all three are needed:

   1. Layered shadow, not one blur. A tight contact shadow says the button is
      touching the page; a wider ambient one says there is air under it. One
      large soft blur is a 2008 drop shadow.
   2. The shadow is tinted with the button's own colour. Grey under copper
      reads as grime.
   3. The top edge carries an inset highlight, which is light landing on a
      raised surface. Without it the fill is flat and the shadow looks stuck on.
*/

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  font-family: var(--face-text);
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: -0.005em;
  padding: 0.9rem 1.6rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  text-decoration: none;
  /* transform and box-shadow move together on press, which is what sells it. */
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
              color 0.16s var(--ease), transform 0.1s var(--ease),
              box-shadow 0.18s var(--ease);
}

.btn--primary {
  /* A whisper of a gradient. Enough that the surface is not one flat value,
     not so much that it reads as a 2010 glossy button. */
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--accent) 92%, white) 0%,
              var(--accent) 100%);
  color: var(--ground);
  box-shadow:
    0 1px 1px  color-mix(in srgb, var(--btn-shade) 22%, transparent),
    0 2px 4px  color-mix(in srgb, var(--btn-shade) 18%, transparent),
    0 8px 18px -6px color-mix(in srgb, var(--btn-shade) 42%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, 0.20);
}

/* Hover lifts it further off the page. The shadow grows and softens rather
   than darkening, because a thing further from a surface casts a larger,
   fainter shadow, not a heavier one. */
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background: linear-gradient(180deg,
                color-mix(in srgb, var(--accent-hover) 92%, white) 0%,
                var(--accent-hover) 100%);
    box-shadow:
      0 1px 1px  color-mix(in srgb, var(--btn-shade) 20%, transparent),
      0 4px 8px  color-mix(in srgb, var(--btn-shade) 16%, transparent),
      0 14px 28px -8px color-mix(in srgb, var(--btn-shade) 40%, transparent),
      inset 0 1px 0 rgba(255, 255, 255, 0.22);
  }
  .btn--ghost:hover {
    border-color: var(--ink);
    background: var(--ground-2);
    box-shadow: 0 4px 12px -6px color-mix(in srgb, var(--ink) 22%, transparent);
  }
}

.btn--primary[disabled],
.btn--primary[aria-busy='true'] {
  /* Fill stays solid: the label is a status message and must stay legible.
     The lift goes, because nothing is pressable here. */
  background: var(--accent);
  cursor: default;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
}

/* Press. :active fires on pointer-down, not on release, which is the whole
   point: feedback belongs to the press, and anything waiting for the click
   reads as lag.

   The shadow collapses as the button scales down, so it travels toward the
   page rather than just getting smaller in place. Scaling alone leaves the
   shadow floating at full size underneath and the button reads as shrinking
   instead of being pushed. This is the detail that makes it feel physical.

   .btn--text is excluded: it is a ruled text link, not a surface, and scaling
   type mid-sentence looks like a glitch. It keeps the nudge. */
.btn:not(.btn--text):active {
  transform: scale(0.97);
}
.btn--primary:active {
  box-shadow:
    0 1px 1px color-mix(in srgb, var(--btn-shade) 26%, transparent),
    0 1px 2px color-mix(in srgb, var(--btn-shade) 20%, transparent),
    inset 0 1px 2px color-mix(in srgb, var(--btn-shade) 30%, transparent);
}
.btn--text:active { transform: translateY(1px); }

@media (prefers-reduced-motion: reduce) {
  .btn:active { transform: none; }
}

/* On the inverted band the ground is near black, so a dark shadow is invisible
   and a light one would look like fog. The lift comes from the accent's own
   glow plus a brighter top edge instead. */
.invert {
  --btn-shade: color-mix(in srgb, var(--accent) 70%, #000);
}
.invert .btn--primary {
  box-shadow:
    0 1px 1px rgba(0, 0, 0, 0.40),
    0 10px 26px -10px color-mix(in srgb, var(--accent) 55%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, 0.26);
}
@media (hover: hover) and (pointer: fine) {
  .invert .btn--primary:hover {
    box-shadow:
      0 1px 1px rgba(0, 0, 0, 0.40),
      0 16px 36px -10px color-mix(in srgb, var(--accent) 65%, transparent),
      inset 0 1px 0 rgba(255, 255, 255, 0.30);
  }
}

/* A raised surface is a visual effect, not information. Someone who has asked
   for reduced transparency or more contrast gets the button, flat. */
@media (prefers-contrast: more) {
  .btn--primary,
  .invert .btn--primary { background: var(--accent); box-shadow: none; }
}

.btn--text {
  padding: 0.9rem 0;
  font-weight: 600;
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  text-decoration: none;
}
.btn--text:hover { color: var(--accent); border-color: var(--accent); }

/* ---------- Hero ---------- */

.hero { padding-block: clamp(3rem, 6vw, 6rem) clamp(3rem, 6vw, 6rem); }

/* The headline takes the whole rail so it can run at full scale in three
   lines. The supporting row balances beneath it. An earlier version put
   the headline in a narrow column and left half the viewport empty. */
.hero h1 {
  font-size: var(--t-hero);
  line-height: 0.95;
  letter-spacing: var(--track-hero);
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
/* A real Fraunces Italic ships in src/fonts/. An earlier build requested
   font-style: italic against a family with only roman faces, so the browser
   synthesized an oblique and its slant collided with the next word. */
.hero h1 em { font-style: italic; font-weight: 600; }

/* Top-aligned, not bottom-aligned. Bottom alignment put the taller spec
   table's first rule ~90px above the lede's first line and opened a dead
   pocket beside it; the contract promised the two would align. */
.hero__row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.hero__lede {
  font-size: var(--t-lg);
  line-height: 1.45;
  color: var(--muted);
  max-width: 44ch;
  margin-bottom: var(--s-8);
}

.hero__ctas { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }

/* ---------- Territory ----------
   The one authored graphic on the page. Real Monmouth County boundary
   geometry (OpenStreetMap, 3,978 points simplified to 141), drawn in a
   single hairline stroke. The same material every rule on this page is
   made of. It states the positioning that no competitor can copy: one
   county, on purpose. It draws itself on load, extending the page's
   motion thesis rather than introducing a second one. */

.hero__aside { display: flex; flex-direction: column; }
.territory { margin: 0 0 var(--s-8); }
.territory svg {
  width: 100%;
  height: auto;
  overflow: visible;
}
.territory__line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.25;
  stroke-linejoin: round;
}
.territory__cap {
  color: var(--muted);
  margin-top: var(--s-3);
}

@media (prefers-reduced-motion: no-preference) {
  .territory__line {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: trace 2.2s cubic-bezier(0.16, 1, 0.3, 1) 0.35s forwards;
  }
}
@keyframes trace { to { stroke-dashoffset: 0; } }

/* ---------- Spec table ----------
   Data, not decoration. Every row states a decision the
   business has made and controls.                          */

.spec { position: relative; }

.spec__row {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--s-4);
  align-items: baseline;
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--line);
}

.spec__key { color: var(--muted); white-space: nowrap; }

.spec__val {
  font-family: var(--face-mono);
  font-size: var(--t-mono2);
  font-weight: 500;
  text-align: right;
  font-variant-numeric: tabular-nums;
  transition: color 0.2s var(--ease);
}
.spec__val[data-state='hit'] { color: var(--accent); }
.spec__val[data-state='miss'] { color: var(--muted); }

/* ---------- Story ---------- */

.story__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(2rem, 6vw, 5.5rem);
}

/* The story column reuses the spec list rather than a big-number stat
   stack. Three oversized figures with small labels is the hero-metric
   template, and one of them ("100%") measured nothing. */
.facts { display: flex; flex-direction: column; position: relative; }
.fact {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--s-4);
  align-items: baseline;
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--line);
}
.fact__key { color: var(--muted); white-space: nowrap; }
.fact__txt { font-size: var(--t-base); text-align: right; }

.story__lead {
  font-family: var(--face-display);
  font-size: clamp(1.5rem, 2.5vw, 2.125rem);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.028em;
  margin-bottom: var(--s-6);
  text-wrap: balance;
}
.story__body p + p { margin-top: var(--s-4); }
.story__body p { color: var(--muted); max-width: 58ch; }

/* ---------- Section headings ----------
   An explicit class, not a descendant selector. The earlier
   `.band > .rail > h2` missed every heading nested inside a grid
   column and simultaneously outranked `.legal h2`, so section
   headings came out inconsistent and legal subheadings came out
   at display scale. */

.section-title {
  font-size: var(--t-display);
  letter-spacing: var(--track-display);
  line-height: 1.0;
  max-width: 16ch;
  margin-bottom: var(--s-12);
}
/* The loud band's heading keeps the display scale. It reads as the page's
   second scale event through the inverted band and the shorter measure, not
   through type size: --t-hero is documented "once per page" in tokens.css and
   belongs to the h1. Setting it here tied the h1 at 96px, so the loudest thing
   on the page was a pricing header. */
.band--loud .section-title { max-width: 13ch; }
/* The waitlist is the page's destination; it does not whisper after the
   pricing band shouts. */
#waitlist .section-title { font-size: var(--t-display); max-width: 12ch; }
/* A heading sharing a row with a form sits in roughly a third of the
   rail. At display scale it wraps into a five-line tower, so the
   column-constrained variant steps down a size. */
.signup__grid .section-title,
.teaser__grid .section-title {
  font-size: var(--t-section);
  line-height: 1.05;
  letter-spacing: -0.035em;
  max-width: 15ch;
  margin-bottom: 0;
}

/* ---------- Steps ----------
   A genuine sequence, so the numbering carries information.
   Ruled columns, not cards.                                */

.steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  position: relative;
}

.step {
  padding: var(--s-8) var(--s-6) var(--s-8) 0;
  position: relative;
}
/* The divider is a pseudo-element so it can be drawn independently of
   the column's content. */
.step:not(:last-child)::after {
  content: '';
  position: absolute;
  inset: 0 0 0 auto;
  width: 1px;
  background: var(--line);
  transform-origin: top center;
}
.step:last-child { padding-right: 0; }
.step:not(:first-child) { padding-left: var(--s-6); }

/* No eyebrow above these headings. An earlier build set "01 · Pick"
   above "Pick your size & cadence", which restated the heading's own
   first word and made mono a costume rather than a measurement. */
.step h3 { font-size: var(--t-lg); letter-spacing: -0.02em; margin-bottom: var(--s-3); }
.step p { color: var(--muted); font-size: var(--t-base); }

/* ---------- Inverted band ---------- */

.invert {
  --ground:       #12100F;
  --ground-2:     #1C1917;
  --ink:          #FAF8F5;   /* 17.90 on ground */
  --muted:        #A9A199;   /*  7.45 */
  --line:         #2E2A26;
  --line-strong:  #6E6862;   /*  3.45. Clears 1.4.11 */
  --accent:       #D9915F;   /*  7.38 */
  --accent-hover: #E8A97D;
  background: var(--ground);
  color: var(--ink);
}
.invert .btn--primary { background: var(--accent); color: #12100F; }
.invert .btn--primary:hover { background: var(--accent-hover); }
.invert .btn--ghost { color: var(--ink); border-color: var(--line-strong); }
.invert .btn--ghost:hover { border-color: var(--ink); background: var(--ground-2); }

/* ---------- Pricing ---------- */

.tiers {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 4vw, 3.5rem);
}

.tier { display: flex; flex-direction: column; position: relative; padding-top: var(--s-6); }
/* The tier name is a heading, not a mono label sitting above one. */
.tier__name {
  font-family: var(--face-display);
  font-size: var(--t-xl);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1;
}
.tier__amt {
  font-family: var(--face-display);
  font-size: clamp(3rem, 6vw, 4.5rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  margin-top: var(--s-4);
  font-variant-numeric: tabular-nums;
}
.tier__amt sup {
  font-family: var(--face-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.04em;
  vertical-align: 0.85em;
  margin-left: 0.35rem;
  color: var(--muted);
}
.tier__cadence { color: var(--muted); font-size: var(--t-base); margin-top: var(--s-2); }
.tier__list { list-style: none; padding: 0; margin: var(--s-6) 0 0; display: flex; flex-direction: column; }
.tier__list li {
  font-size: var(--t-base);
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}

.pricing__notes { margin-top: var(--s-12); display: flex; flex-direction: column; gap: var(--s-3); max-width: 62ch; }
.pricing__notes p { color: var(--muted); font-size: var(--t-base); }

/* ---------- Founding members ---------- */

/* Separated by a rule, like .fact, rather than an accent bar down the side.
   The accent appears once, on the label, per the one-accent rule. */
.founding {
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  max-width: 46ch;
}
.founding__key { color: var(--accent); }
.founding__txt { font-size: var(--t-base); }

/* ---------- Forms ---------- */

.signup__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}

.form { display: flex; flex-direction: column; gap: var(--s-5, 1.25rem); }

.field { display: flex; flex-direction: column; gap: var(--s-2); }
.field label { font-size: var(--t-sm); font-weight: 600; }
/* Guidance under an input. Muted, but not so muted it fails contrast: this is
   where the password length rule is stated, so it has to be readable. */
.field__hint { font-size: var(--t-sm); color: var(--muted); max-width: 46ch; }
/* A second form stacked under the first, on the partner access page. */
.form--stacked { margin-top: var(--s-12); }
.field__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-4); }

.input, .select, .textarea {
  font-family: var(--face-text);
  font-size: var(--t-base);
  color: var(--ink);
  background: var(--ground);
  border: 1px solid var(--line-strong);
  /* Matches the buttons. A 10px button beside a 2px input is the tell that a
     control got restyled and its neighbours did not. */
  border-radius: var(--radius);
  padding: 0.75rem 0.85rem;
  /* 0.75rem padding on a 21px line box lands at 42px, two short of the touch
     minimum. The floor costs nothing visually and finishes the target. */
  min-height: 44px;
  width: 100%;
  transition: border-color 0.16s var(--ease), background-color 0.16s var(--ease);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--ink); }
.input[aria-invalid='true'], .select[aria-invalid='true'] { border-color: var(--error); }
.input:disabled, .select:disabled { background: var(--ground-2); color: var(--muted); }
.textarea { min-height: 6rem; resize: vertical; }

.select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) calc(50% + 2px), calc(100% - 13px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2.5rem;
}

/* The input itself is 16px, but the label wraps it, so the real target is the
   whole row. That measured 42px, just under the minimum. */
.check { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-3); align-items: start; min-height: 44px; }
.check input { margin-top: 0.28rem; width: 1rem; height: 1rem; }
.check span { font-size: var(--t-sm); color: var(--muted); }

.hint { font-size: var(--t-sm); color: var(--muted); }
.err { font-size: var(--t-sm); color: var(--error); font-weight: 500; }
.err:empty { display: none; }

/* Honeypot: off-screen rather than display:none, out of the tab order,
   and hidden from assistive tech. All three, or it becomes a trap. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__status { font-size: var(--t-base); }
.form__status[data-tone='ok'] { color: var(--ok); }
.form__status[data-tone='err'] { color: var(--error); }

.success { border-top: 2px solid var(--accent); padding-top: var(--s-6); }
.success h3 { font-size: var(--t-xl); letter-spacing: -0.02em; margin-bottom: var(--s-3); }
.success p { color: var(--muted); max-width: 46ch; }

/* ---------- Gifting teaser ---------- */

.teaser__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
}
/* ---------- Approvals ---------- */

/* Tables scroll inside their own box rather than pushing the page sideways,
   which is what a five column table does on a phone otherwise. */
.admin__scroll { overflow-x: auto; margin-top: var(--s-8); }
.admin { width: 100%; border-collapse: collapse; font-size: var(--t-sm); min-width: 40rem; }
.admin th {
  text-align: left;
  font-family: var(--face-mono);
  font-size: var(--t-mono2);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  padding: 0 var(--s-4) var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.admin td { padding: var(--s-4) var(--s-4) var(--s-4) 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.admin tr:last-child td { border-bottom: 0; }
.admin__muted { color: var(--muted); }
.admin__empty { color: var(--muted); padding-block: var(--s-8); }
.admin__actions { white-space: nowrap; }
.admin__actions .btn { margin-right: var(--s-3); }
.admin__tag {
  font-size: var(--t-mono2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ---------- Packing list ----------
   Built to be worked from at arm's length with hands full, and to print.
   The address is the biggest thing on the row because it is what gets
   copied onto a label. */

.pack__list { list-style: none; padding: 0; margin: var(--s-8) 0 0; display: grid; gap: 0; }
.pack__item {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.4fr) auto;
  gap: var(--s-6);
  align-items: start;
  padding: var(--s-6) 0;
  border-bottom: 1px solid var(--line);
}
.pack__addr { font-size: var(--t-lg); line-height: 1.35; }
.pack__meta { display: grid; gap: var(--s-2); font-size: var(--t-sm); }
.pack__box { color: var(--muted); }
.pack__card { border-left: 2px solid var(--line); padding-left: var(--s-3); color: var(--ink); }
.pack__cardlabel { display: block; color: var(--muted); margin-bottom: 2px; }
.pack__item--overdue { background: color-mix(in oklab, var(--error) 6%, transparent); }
/* Struck through rather than removed: someone packing twenty in a row should
   not lose their place every time one is done. */
.pack__item--done { opacity: 0.4; }
.pack__item--done .pack__addr { text-decoration: line-through; }

@media (max-width: 720px) {
  .pack__item { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
}

@media print {
  .pack__head, .pack__hide, .pack__act, .skip { display: none !important; }
  .pack__item { break-inside: avoid; border-bottom: 1px solid #999; }
  .band { padding-block: 0; }
  body { font-size: 11pt; }
}

/* ---------- Delivery schedule ---------- */

/* Lateness has to be impossible to miss. Colour alone would not do it, so the
   row is also weighted and the words say "3 days late" rather than showing a
   negative number. */
.due__when { font-family: var(--face-mono); font-size: var(--t-mono2); letter-spacing: 0.08em; text-transform: uppercase; }
.due__when--overdue { color: var(--error); font-weight: 500; }
.due__when--today   { color: var(--accent); font-weight: 500; }
.due__when--soon    { color: var(--ink); }
.due__when--later   { color: var(--muted); }
.due--overdue td    { background: color-mix(in oklab, var(--error) 7%, transparent); }
.due__late          { color: var(--error); }

/* A URL that gets copied onto a printed card. Monospace and breakable, so a
   long token wraps rather than pushing the table sideways. */
.admin__link { font-size: var(--t-mono2); color: var(--accent); word-break: break-all; }

/* ---------- Gift basket ---------- */

.basket { border-top: 1px solid var(--line); padding-top: var(--s-4); margin-top: var(--s-8); }
.basket__list { list-style: none; padding: 0; margin: var(--s-4) 0 0; display: grid; gap: 0; }
.basket__item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-sm);
}
.basket__item:last-child { border-bottom: 0; }
/* The number a partner is about to commit to. It gets the display face and a
   rule above it, so it reads as a total rather than another line of the list. */
.basket__total {
  font-family: var(--face-display);
  font-size: var(--t-lg);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--ink);
}

.paste { margin-top: var(--s-8); border-top: 1px solid var(--line); padding-top: var(--s-4); }
.paste summary { cursor: pointer; font-size: var(--t-sm); font-weight: 600; }
.paste__example {
  font-size: var(--t-mono2);
  color: var(--muted);
  background: var(--ground-2);
  padding: var(--s-3);
  overflow-x: auto;
  margin: var(--s-3) 0;
  white-space: pre;
}

/* ---------- Partner kit ---------- */

/* Copy-and-paste language blocks. A hairline above each, the same treatment
   .spec and .fact already use, rather than a tinted card or an accent left
   border: the page should read as the same publication as the rest of the
   site, not as a widget bolted onto it. */
.snips { display: grid; gap: var(--s-8); margin-top: var(--s-8); }
.snip { border-top: 1px solid var(--line); padding-top: var(--s-4); }
.snip__key {
  font-family: var(--face-mono);
  font-size: var(--t-mono2);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s-3);
}
.snip__txt {
  font-family: var(--face-display);
  font-size: var(--t-lg);
  line-height: 1.45;
  max-width: 58ch;
}

/* Two columns that collapse before the type gets too narrow to read. */
.cols2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-10); }
.cols2 h3 { font-size: var(--t-lg); letter-spacing: -0.02em; margin-bottom: var(--s-4); }
.cols2 ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s-3); }
.cols2 li { border-top: 1px solid var(--line); padding-top: var(--s-3); }
@media (max-width: 720px) { .cols2 { grid-template-columns: minmax(0, 1fr); gap: var(--s-8); } }

.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-block: var(--s-6); }
.chip {
  font-family: var(--face-text);
  font-size: var(--t-sm);
  font-weight: 500;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  padding: 0.45rem 0.7rem;
  color: var(--muted);
}

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

.foot { padding-block: var(--s-16) var(--s-8); }
.foot__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: var(--s-8);
  padding-bottom: var(--s-12);
}
.foot h4 { font-family: var(--face-mono); font-size: var(--t-mono); letter-spacing: 0.02em; text-transform: uppercase; color: var(--muted); margin: 0 0 var(--s-4); font-weight: 500; }
/* The footer links measured 16px tall, the worst touch targets on the site.
   The gap moves into the link itself so each row is a 44px target: the pitch
   goes 16+12 to 44+0, which reads the same and taps far better on a phone. */
.foot ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0; font-size: var(--t-base); }
.foot a { text-decoration: none; }
.foot ul a { display: inline-flex; align-items: center; min-height: 44px; }
.foot a:hover { color: var(--accent); }
.foot__blurb { color: var(--muted); font-size: var(--t-base); max-width: 30ch; margin-top: var(--s-4); }
.foot__base {
  border-top: 1px solid var(--line);
  padding-top: var(--s-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: space-between;
  font-size: var(--t-sm);
  color: var(--muted);
}

/* ---------- Legal pages ---------- */

.legal { max-width: 68ch; }
.legal h2 { font-size: var(--t-xl); letter-spacing: -0.02em; margin: var(--s-8) 0 var(--s-3); }
.legal p { color: var(--muted); margin-bottom: var(--s-4); }
.notice {
  border: 1px solid var(--accent);
  border-radius: 2px;
  padding: var(--s-6);
  margin-bottom: var(--s-8);
}
.notice p { color: var(--ink); margin: 0; }

/* ============================================================
   Motion, first movement. The hero arrives in sequence on load.
   It is timed rather than scroll-driven because at the top of
   the document there is no scroll to key off yet.

   Content is visible by default, so a failed animation costs
   nothing. Two further movements follow at the end of this
   file: the rules draw, and the page reads itself.
   ============================================================ */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* The delay is carried on a custom property rather than on animation-delay
   directly, because the group selector below outranks the per-element rules
   that used to set it. `.hero__row > div > *` is (0,2,0); `.hero__lede` is
   (0,1,0). The shorthand here resets animation-delay to 0s, and the lower-
   specificity rules that came after could never win it back, so every element
   except the h1 was landing on the same frame: a sequence authored, a thud
   shipped. A custom property sidesteps the fight, since nothing else declares
   --rise-delay at any specificity.

   `.hero__row > .spec` was also dead on its own terms: .spec sits inside
   .hero__aside, not directly in .hero__row, so it matched nothing. The spec
   table now arrives a row at a time instead, which is what the aside was
   reaching for. */
.hero h1,
.hero__row > div > *:not(.spec),
.hero .spec__row {
  animation: rise 0.6s var(--ease) backwards;
  animation-delay: var(--rise-delay, 0s);
}

.hero h1          { --rise-delay: 0.06s; }
.hero__lede       { --rise-delay: 0.14s; }
.hero__ctas       { --rise-delay: 0.20s; }
.hero .territory  { --rise-delay: 0.26s; }

/* 45ms between rows. Tight enough to read as one table settling rather than
   four separate arrivals, which is the difference between texture and a list
   that keeps you waiting. */
.hero .spec__row:nth-child(1) { --rise-delay: 0.320s; }
.hero .spec__row:nth-child(2) { --rise-delay: 0.365s; }
.hero .spec__row:nth-child(3) { --rise-delay: 0.410s; }
.hero .spec__row:nth-child(4) { --rise-delay: 0.455s; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 900px) {
  .hero__row,
  .story__grid,
  .signup__grid,
  .teaser__grid { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .signup__grid .section-title,
  .teaser__grid .section-title { margin-bottom: var(--s-6); }
  .steps { grid-template-columns: minmax(0, 1fr); }
  .step { border-bottom: 1px solid var(--line); padding: var(--s-6) 0; }
  .step:not(:last-child)::after { display: none; }
  .step:not(:first-child) { padding-left: 0; }
  .step:last-child { border-bottom: 0; }
  .foot__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  /* The nav is not hidden. An earlier build set display:none with no
     replacement, leaving four sections reachable only from the footer.
     It drops to a ruled row under the masthead instead. */
  .masthead__inner { flex-wrap: wrap; row-gap: var(--s-3); }
  .masthead__nav {
    order: 3;
    width: 100%;
    justify-content: space-between;
    gap: var(--s-3);
    border-top: 1px solid var(--line);
    padding-top: var(--s-2);
    font-size: var(--t-xs);
  }
  .tiers { grid-template-columns: minmax(0, 1fr); }
  .field__row { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 520px) {
  .foot__grid { grid-template-columns: minmax(0, 1fr); }
  .hero__ctas { flex-direction: column; align-items: stretch; }
  .hero__ctas .btn--primary,
  .hero__ctas .btn--ghost { width: 100%; }
  /* Underline stays the width of the words. Stretched full-bleed it reads
     as a rule across the page. Left-aligned, because it was the only
     centred element on an otherwise entirely left-aligned page. */
  .hero__ctas .btn--text { align-self: start; width: auto; }
}

/* ============================================================
   MOTION. The rules draw.

   This page has exactly one structural material: ruled lines. No cards,
   no shadows, no imagery. And the product is a delivery route that
   repeats on a cycle. So every major rule arrives by being drawn from
   the left, the way a route is traced, rather than by fading in.

   One idea, carried through. Built on scroll-driven animations, so it
   runs off the main thread with no library and no scroll listener.
   Where the browser lacks support the rules are simply there, which is
   also exactly what a reduced-motion visitor gets.
   ============================================================ */

.spec::before,
.facts::before,
.steps::before,
.tier::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--ink);
  transform-origin: left center;
}

@keyframes draw-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes draw-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .spec::before,
    .facts::before,
    .steps::before {
      animation: draw-x linear both;
      animation-timeline: view();
      animation-range: entry 15% entry 85%;
    }
    /* The dividers draw downward once their top rule has arrived, so the
       section assembles in the order it is read. */
    .step:not(:last-child)::after {
      animation: draw-y linear both;
      animation-timeline: view();
      animation-range: entry 30% cover 12%;
    }
    /* The pricing band is the page's crescendo, so its rules take the
       longest stroke of any on the page. */
    .tier::before {
      animation: draw-x linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 22%;
    }
  }
}

/* ============================================================
   MOTION, second movement. The page reads itself.

   The rules above draw, but a rule is one or two pixels and the
   page below the hero had nothing else: the hero arrived in
   sequence and then four hairlines carried the entire rest of
   the scroll. Everything a visitor actually reads was already
   at rest before it was reached.

   So content rises as it is reached, on the same 14px `rise`
   the hero already uses. Same keyframe, same distance, one
   vocabulary. What changes is the clock: the hero is timed
   because it has no scroll to key off, and these are scroll-
   driven because the reader's own scrolling is the better
   timing signal.

   Three rules held throughout:

   1. `linear`, never --ease. On a view() timeline the scroll
      position IS the progress input; an easing curve on top of
      it eases an already-eased signal and reads as rubbery.
      --ease stays on the time-driven animations, where it belongs.
   2. Every reveal finishes inside `entry`, well before the
      element reaches the middle of the screen. Nobody should
      ever be reading half-faded text.
   3. The form does not move. Revealing the words around it is
      fine; sliding inputs under a cursor that is trying to
      reach them is not.

   The footer is deliberately excluded. It sits at the end of
   the document, where there is no scroll left to finish an
   entry range, so a reveal there can strand itself part-drawn.
   ============================================================ */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    .section-title,
    .story__body p,
    .fact,
    .step,
    .tier,
    .pricing__notes,
    .founding {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 5% entry 55%;
    }

    /* Only elements that share a row need an authored stagger. Row siblings
       enter the viewport on the same frame, so without an offset they would
       resolve in lockstep; each one therefore starts a little further into
       its own entry, in reading order, left to right.

       Stacked content is deliberately NOT staggered here. A vertical stack
       is already staggered by position: each .fact sits 57-81px below the
       last, and its entry range is only as long as its own height, so one
       is nearly resolved before the next begins. Adding offsets on top of
       that staggers an already-staggered sequence twice. */
    .step:nth-child(2) { animation-range: entry 11% entry 61%; }
    .step:nth-child(3) { animation-range: entry 17% entry 67%; }

    .tier:nth-child(2) { animation-range: entry 12% entry 62%; }
  }
}

/* The confirmation replaces the form outright: forms.js calls
   replaceWith(), so the block a visitor waited for used to appear
   between two frames with nothing connecting them. This is the
   single most important moment on the page and it was the one
   moment with no transition at all.

   Time-driven, because there is no scroll here: the state changed
   under a stationary reader. It is content swapped inline in the
   document flow, not a centred overlay, so it takes the UI budget
   and stays under 300ms: an earlier 400ms borrowed the modal band
   it does not belong to. The global reduced-motion reset already
   neutralises this one, since it is duration-based. */
.success { animation: rise 0.28s var(--ease) backwards; }

/* ============================================================
   MOTION, third movement. Navigation, feedback, and the seams.
   ============================================================ */

/* Anchor navigation. Every nav item, the hero's "Our story", and three
   footer links are in-page anchors, and all of them used to cut. On a
   single-page document that cut costs orientation: you arrive somewhere
   with no evidence of where it was relative to where you stood.

   scroll-padding-top is the more important half. The masthead is sticky,
   so an un-padded anchor parks the section heading underneath it: #how
   was landing its title at 65px with the masthead occupying 0-88px, and
   the top of the heading was simply behind the header. The measured
   heights are recorded as tokens so the offset moves with the bar.

   The reduced-motion block already carries `scroll-behavior: auto`, which
   was written for a smooth scroll that had never been turned on. It now
   has something to switch off. */
:root { --masthead-h: 88px; }
@media (max-width: 720px) { :root { --masthead-h: 153px; } }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--masthead-h) + var(--s-6));
}

/* Link feedback. The page's whole motion vocabulary is a rule being drawn,
   so a link earns its underline the same way rather than by fading one in:
   the same gesture at the smallest scale on the page.

   Hover-gated, because touch fires a false hover on tap and would leave the
   rule stuck under the last link pressed. Under reduced motion the global
   reset collapses the transition and the underline simply appears, which is
   gentler, not gone: the affordance survives, only the movement stops. */
@media (hover: hover) and (pointer: fine) {
  .masthead__nav a,
  .foot ul a { position: relative; }

  .masthead__nav a::after,
  .foot ul a::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    /* The links are 44px touch targets wrapping ~21px of text, so the rule
       is placed from the text's own centre, not the box's bottom edge. */
    top: calc(50% + 0.72em);
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.15s var(--ease);
  }

  .masthead__nav a:hover::after,
  .foot ul a:hover::after { transform: scaleX(1); }
}

/* Validation errors. `.err:empty` means the message arrives by display
   switching, which reads as a pop and drags the fields below it down with
   no warning. Short and slight: an error has to be legible immediately,
   so this is texture on the arrival, never a delay before reading it. */
@keyframes err-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}

.err:not(:empty) { animation: err-in 0.15s var(--ease); }

/* Print. Scroll-driven reveals have no scroll to resolve against on paper,
   so every element the page reveals on the way down would otherwise print
   at its start state: blank. Clearing the animations lets each one fall
   back to its natural, fully visible state, which is the same fallback
   Safari and reduced-motion visitors already get. */
@media print {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ============================================================
   MOTION, fourth movement. The scroll, everywhere and continuous.
   ============================================================ */

/* The fallback reveal, driven by scripts/reveal.js where the browser has no
   animation-timeline. Both classes are added by JavaScript, so a browser that
   never runs the script renders the page fully visible: the hiding only ever
   exists once something is guaranteed to undo it.

   A transition rather than a keyframe, because a reader can scroll an element
   back out and in again quickly, and a transition retargets from wherever the
   value currently is instead of restarting from zero. Same 14px and the same
   curve as the native path, so the two are indistinguishable. */
.js-reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

.js-reveal.is-revealed {
  opacity: 1;
  transform: none;
}

/* Read progress. A single rule across the foot of the masthead, tracking the
   document 1:1. It is the page's own material doing the one job a continuous
   scroll animation is actually good for: telling you where you are and how
   much is left. Nothing here is decorative movement.

   It reuses draw-x, the keyframe the section rules already draw with, and
   scroll() rather than view(): its progress is the whole document, not one
   element's trip past the fold.

   Left ungated for reduced motion on purpose. The rule cannot move unless the
   reader moves it, so it can never surprise anyone, and a progress indicator
   is comprehension, which reduced motion keeps. Where scroll() is missing it
   simply stays at scaleX(0) and is invisible: a progress bar that never
   appears costs nothing, unlike content that never appears. */
.masthead::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
}

@supports (animation-timeline: scroll()) {
  .masthead::after {
    animation: draw-x linear both;
    animation-timeline: scroll();
  }
}

/* The pricing band is the page's crescendo and its own thesis says so: its
   rules already take the longest stroke on the page. The tiers travel further
   than anything else to match, which is the difference between a page that
   reveals uniformly and one that has a peak. */
@keyframes rise-far {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .tier { animation-name: rise-far; }
  }
}

.js-reveal.tier { transform: translateY(30px); }
.js-reveal.tier.is-revealed { transform: none; }

/* ---------- Territory: the town under the pointer ---------- */

/* Invisible at rest and still hit-testable: fill:none would take the shape out
   of hit testing entirely, so the fill is a transparent paint and pointer
   events are explicit. The map keeps its single-hairline reading until someone
   actually points at it. */
.territory__town {
  fill: transparent;
  stroke: none;
  pointer-events: all;
  /* evenodd, not the nonzero default. Four townships enclose a borough
     entirely (Freehold Township holds Freehold, Manalapan holds Englishtown,
     Howell holds Farmingdale, and Keansburg has its own hole), and those
     enclaves ship as extra subpaths. Under nonzero a nested subpath fills
     solid instead of cutting a hole, so the township would sit on top of the
     borough and pointing at Freehold would answer "Freehold Township". */
  fill-rule: evenodd;
}

/* The pointed-at town fills with the accent at low alpha and takes a hairline
   of its own: enough to say "this shape, this name", nowhere near enough to
   compete with the county outline drawn over the top. */
@media (hover: hover) and (pointer: fine) {
  .territory__town:hover {
    fill: color-mix(in srgb, var(--accent) 14%, transparent);
    stroke: var(--accent);
    stroke-width: 1;
  }
}

/* Touch has no hover, so the same state is reachable by holding a finger on
   the map. :active works on touch where :hover does not. */
.territory__town:active {
  fill: color-mix(in srgb, var(--accent) 14%, transparent);
  stroke: var(--accent);
  stroke-width: 1;
}

/* The caption is the readout. It carries the town name in the accent so the
   change is legible without moving anything, and reserves its line height so
   swapping a long name for a short one never reflows the column. */
.territory__cap {
  min-height: 1.4em;
  transition: color 0.15s var(--ease);
}
.territory__cap.is-named { color: var(--accent); }

/* ---------- Narrow document pages ---------- */

/* welcome.html and activate.html are single-column documents: a confirmation
   after checkout, and a gift setup form. Both previously carried their own
   layout vocabulary (.wrap, .section, .measure, .footer, .display, .lede)
   of which not one selector existed here, so they rendered with no measure
   and no gutter, text running edge to edge. They now use the same primitives
   as every other page, and these five rules are the only genuinely new
   patterns the two of them need. */

/* Extends the existing .rail--wide modifier in the other direction. */
.rail--narrow { max-width: 720px; }

/* Pairs with .mono. The label above a page's headline. */
.eyebrow {
  color: var(--accent);
  margin-bottom: var(--s-4);
}

/* The standing-alone version of .hero__lede, for pages with no hero. */
.lede {
  font-size: var(--t-lg);
  line-height: 1.45;
  color: var(--muted);
  max-width: 46ch;
  margin-bottom: var(--s-8);
}

/* A horizontal legal nav. .foot ul is a column, which is right in the four
   column footer on the homepage and wrong on a single line. */
.foot__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
}

.masthead__note { color: var(--muted); }

/* The spec table is the last element with a border of its own, so without this
   the paragraph after it sits directly on the final rule. Scoped to the narrow
   document pages: in the homepage hero .spec is the last child of its column
   and needs nothing. */
.rail--narrow .spec { margin-bottom: var(--s-8); }

/* The map names all 53 municipalities under the pointer, and nothing said so.
   An interaction nobody discovers is worse than one that does not exist, so
   the affordance is stated plainly rather than implied by a cursor change.

   It retires the moment a town is named: once the reader has found the
   behaviour the instruction is noise, and the caption itself becomes the
   readout. Wording avoids "point" and "tap" so one line serves mouse, pen
   and touch alike. */
.territory__hint {
  color: var(--muted);
  margin-top: var(--s-2);
  transition: opacity 0.15s var(--ease);
}

.territory.is-active .territory__hint { opacity: 0; }

/* The guarantee. It answers the last question anyone has before subscribing,
   and it was already written and agreed in docs/partner-sales-kit.md while
   appearing nowhere a customer could read it.

   Set on the display face at a size between the tiers and the notes, so it
   reads as a promise the business is making rather than another line of small
   print. A rule above it separates it from the prices without a card. */
.guarantee {
  font-family: var(--face-display);
  font-size: var(--t-lg);
  line-height: 1.4;
  letter-spacing: -0.02em;
  max-width: 34ch;
  margin-top: var(--s-12);
  padding-top: var(--s-6);
  border-top: 2px solid var(--accent);
}

/* The pricing lede sits under a section title that already carries a large
   bottom margin, so it pulls back up to sit with the heading it belongs to. */
.pricing__notes { margin-top: var(--s-8); }

/* ============================================================
   The delivery ledger.

   The page claims countywide delivery in four words. This is that
   claim made checkable: all 53 municipalities, in order, with the
   county filling in beside them as you read.

   The motion is the argument rather than decoration. Every other
   direction considered for this page borrowed its energy from
   software marketing, which is the opposite of what this business
   is. A route advancing through real town names only makes sense
   for a company that delivers to exactly one county.

   The list is rendered by the build from serviceArea.towns, not by
   JavaScript: 53 real municipality names belong in the HTML for
   search, and the list has to read as a plain list when the script
   never arrives. Numbering comes from a CSS counter for the same
   reason, since the template has no index of its own.
   ============================================================ */

.ledger {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
  margin-top: var(--s-12);
}

/* The map holds while the list moves past it. Offset by the sticky masthead
   plus a little air, using the same token the anchor offset uses. */
.ledger__side { position: sticky; top: calc(var(--masthead-h) + var(--s-6)); }
.ledger__map { margin: 0; }
.ledger__map svg { width: 100%; height: auto; display: block; overflow: visible; }
.ledger__map:empty { display: none; }

/* Two states, and the difference between them is the whole idea. `seen` is
   every town the reader has passed and never comes off. `on` is the single
   town level with the line right now. */
.ledger__town { fill: transparent; stroke: none; transition: fill 0.45s var(--ease); }
.ledger__town.is-seen { fill: color-mix(in srgb, var(--accent) 13%, transparent); }
.ledger__town.is-on   { fill: color-mix(in srgb, var(--accent) 38%, transparent); }
.ledger__outline { fill: none; stroke: var(--ink); stroke-width: 1.3; vector-effect: non-scaling-stroke; }

.ledger__count {
  font-family: var(--face-display);
  font-size: var(--t-2xl);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  margin-top: var(--s-6);
}
.ledger__of { font-family: var(--face-text); font-size: var(--t-base); font-weight: 400; color: var(--muted); letter-spacing: 0; }

/* The route. One rule, growing with the scroll, and the only new motion the
   section introduces. */
.ledger__roll {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--s-8);
  counter-reset: town;
}
.ledger__roll::before,
.ledger__roll::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line);
}
.ledger__roll::after {
  width: 2px;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: top center;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .ledger__roll::after {
      animation: draw-y linear both;
      animation-timeline: view();
      animation-range: cover 12% cover 62%;
    }
  }
}

.ledger__row {
  position: relative;
  display: grid;
  grid-template-columns: 3ch minmax(0, 1fr) auto;
  gap: var(--s-4);
  align-items: baseline;
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
}

/* The index is a counter, so the build template never has to produce one. */
.ledger__row::after {
  content: counter(town, decimal-leading-zero);
  counter-increment: town;
  order: -1;
  font-family: var(--face-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.1em;
  color: var(--muted);
}

.ledger__row::before {
  content: '';
  position: absolute;
  left: calc(var(--s-8) * -1 - 4px);
  top: 1.15em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ground);
  border: 1px solid var(--line);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.ledger__row.is-seen::before { background: var(--accent); border-color: var(--accent); }
.ledger__row.is-on::before   { transform: scale(1.4); }

.ledger__name {
  font-family: var(--face-display);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--muted);
  transition: color 0.3s var(--ease);
}
.ledger__row.is-seen .ledger__name { color: var(--ink); }

/* Hidden rather than dimmed: before the route reaches a town there is nothing
   to say about it, and 53 rows of grey "in range" is noise. */
.ledger__stat { color: transparent; transition: color 0.3s var(--ease); }
.ledger__row.is-seen .ledger__stat { color: var(--accent); }

.ledger__note { color: var(--muted); font-size: var(--t-sm); margin-top: var(--s-8); }

/* No script, no scroll-driven support, or reduced motion: every town reads as
   in range, which is the truth. The section degrades to a plain ruled list. */
@media (prefers-reduced-motion: reduce) {
  .ledger__roll::after { transform: scaleY(1); }
  .ledger__row::before { background: var(--accent); border-color: var(--accent); }
  .ledger__name { color: var(--ink); }
  .ledger__stat { color: var(--accent); }
}

@media (max-width: 900px) {
  .ledger { grid-template-columns: minmax(0, 1fr); gap: var(--s-8); }
  .ledger__side { position: static; }
}

/* ---------- FAQ ----------
   Same ruled grid as .fact, but the answer is the long side, so the columns
   are reversed: a narrow question against a wide answer. The list is plain
   markup rather than <details> because the answers are the point. They are
   what an assistant quotes and what a visitor scans, and neither should have
   to click to see them. */
.faq { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.faq__row {
  display: grid;
  grid-template-columns: minmax(0, 22ch) minmax(0, 1fr);
  gap: var(--s-8);
  align-items: baseline;
  padding-block: var(--s-6);
  border-bottom: 1px solid var(--line);
}
.faq__q {
  font-family: var(--face-display);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  text-wrap: balance;
}
.faq__a {
  margin: 0;
  color: var(--muted);
  font-size: var(--t-md);
  line-height: 1.6;
  max-width: 62ch;
}

@media (max-width: 760px) {
  .faq__row { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
}
