/* ==========================================================================
   BizVoice, site stylesheet.

   Palette is sampled from the real logo. Left over from the VOIP59 fork, this
   header used to describe that brand's orange-red #d84818; the colours below
   are BizVoice's own. The bright green on the old site was a
   template default and is dropped; it fought the brand color.

   Enterprise register: deep navy surfaces, white content, one warm accent,
   generous type, real tables and rows rather than decoration. Rounded corners
   throughout. No monospace anywhere.

   TYPE (client-supplied, 2026-10-07): Dalton for headers, Moonbeams Sans for
   subheads and body.
   ⚠️ The `ch` measures below were recalibrated for Moonbeams, which is why
   they read high (80ch, 85ch) next to the usual 65-75ch advice. `ch` is the
   advance of "0", and Moonbeams' is .47em against system-ui's ~.55em — so
   swapping the font silently narrowed EVERY ch-based max-width by about 15%
   and body copy fell from ~77 characters a line to ~67, with the tighter
   rules down at 39-49. Measured, not guessed: bvmeasure2.mjs drops each
   max-width and reports what it was costing. Re-run it if the body face
   changes again; the numbers are font-specific, not universal.

   ⚠️ Dalton is a CAPS-ONLY face — its lowercase glyphs are duplicates of the
   capitals, identical outlines and widths — so anything set in it renders as
   capitals whatever the markup says. That is why it is scoped to h1/h2 (short,
   large, deliberate) and kept off h3 and below, where a wall of caps would
   hurt scanning, and why the heading tracking below is no longer negative:
   -.02em was tuned for system-ui lowercase and pinches caps together.
   ========================================================================== */

/* Self-hosted, converted from the supplied desktop originals to woff2. The
   originals live in fonts-src/ OUTSIDE the deployed tree: they were uploaded
   straight into the live docroot, where the raw .otf/.ttf were publicly
   downloadable. Only these subsetted woff2 are served.
   `swap` over `block`: the fallback metrics below are close enough that a
   flash of fallback text beats a flash of invisible text. */
@font-face { font-family: 'Dalton'; src: url('font-dalton.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Moonbeams Sans'; src: url('font-moonbeams-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Moonbeams Sans'; src: url('font-moonbeams-400i.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Moonbeams Sans'; src: url('font-moonbeams-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Moonbeams Sans'; src: url('font-moonbeams-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'Moonbeams Sans'; src: url('font-moonbeams-700.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap; }

:root {
  --font: 'Moonbeams Sans', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-head: 'Dalton', 'Moonbeams Sans', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Sampled from assets/bizvoice-logo.png and -light.png. These three ARE the
     logo, pixel for pixel:
       #213340  the navy        39% of the dark mark
       #1969b3  the blue        29% of the dark mark
       #28a4ff  the lifted blue 29% of the light mark
     Everything else below is color-mix of those three, so there is no colour
     on this site that did not come out of the logo. The one exception is the
     form-error red, which is a semantic signal and should not be on-brand. */
  --navy:      #213340;   /* primary dark surface, straight from the logo */
  --navy-deep: #16222c;   /* footer, deepest */
  --navy-soft: #2d4356;   /* raised dark */
  /* The logo navy taken darker, for the wash over photographic heroes. This
     used to be #0e1c26, a greener navy that belonged to nothing. */
  --navy-scrim: #131e25;
  --ink:       #1b2a35;   /* body text */
  /* The logo blue, exactly: #1969b3 sampled, not #1a6ab4 approximated. It
     already clears AA as a filled button with white text, so the UI accent IS
     the logo colour and hover simply goes darker. */
  --accent:    #1969b3;
  --accent-br: #14558f;   /* hover: darker */
  --on-accent: #ffffff;
  /* The same blue on the navy surfaces is only 2.34:1, which is unreadable.
     This lifted tint is 4.87:1 on --navy. Eyebrows, section tags, footer. */
  --accent-dk: #28a4ff;
  /* Tints of the lifted blue, for small text on dark grounds where #28a4ff
     itself is short of 4.5:1. Mixed, not picked, so they track the logo. */
  --accent-lt:   color-mix(in srgb, var(--accent-dk) 76%, #fff);
  --accent-pale: color-mix(in srgb, var(--accent-dk) 16%, #fff);  /* 26% left Yukon's eyebrow at 4.43:1 */
  /* Semantic, deliberately not a brand colour. */
  --danger: #c0392b;

  --paper: #ffffff;
  --tint:  #f4f7f9;

  --muted: color-mix(in srgb, var(--ink) 70%, transparent);
  --faint: color-mix(in srgb, var(--ink) 68%, transparent);
  --line:  color-mix(in srgb, var(--ink) 15%, transparent);
  --hair:  color-mix(in srgb, var(--ink) 9%, transparent);

  --d-ink:   #eef4f8;
  --d-muted: color-mix(in srgb, #eef4f8 70%, transparent);
  --d-line:  color-mix(in srgb, #eef4f8 18%, transparent);

  --wrap: 1280px;
  --pad: clamp(1.15rem, 4vw, 2.5rem);
  --ease: cubic-bezier(.22,.68,.28,1);

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 18px;
  --shadow: 0 18px 40px -26px rgba(7,23,38,.55);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 92px; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  /* 18px, not the old 17px: Moonbeams Sans has an average advance of .453em
     against system-ui's ~.55em, so like-for-like it reads about a size down.
     Line-height eases to 1.58 because the face carries no built-in line gap. */
  font-family: var(--font); font-size: 18px; line-height: 1.58;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
h1, h2, h3 { margin: 0 0 .5em; font-weight: 700; line-height: 1.14; letter-spacing: -.02em; text-wrap: balance; }
/* Dalton is the header face. One weight only, so 700 would synthesise a fake
   bold — it is already heavy. Caps need air, not the -.02em above. */
h1, h2 { font-family: var(--font-head); font-weight: 400; letter-spacing: .005em;
  line-height: 1.08; }
h1 { font-size: clamp(2rem, 3.9vw, 3.4rem); }
h2 { font-size: clamp(1.6rem, 2.9vw, 2.35rem); }
h3 { font-size: 1.14rem; }
p { margin: 0 0 1rem; }
::selection { background: var(--accent); color: #fff; }

.wrap { width: min(var(--wrap), 100% - (var(--pad) * 2)); margin-inline: auto; }
.wrap.narrow { width: min(820px, 100% - (var(--pad) * 2)); }

.skip { position: absolute; left: -9999px; background: var(--accent); color: #fff;
  padding: .8rem 1.2rem; z-index: 200; font-weight: 700; border-radius: 0 0 var(--r-md) 0; }
.skip:focus { left: 0; top: 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }

.eyebrow, .sec-index, .contact-label, .side-h, .plan-flag, .track-kicker, .hero-stats .k {
  font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em;
}
.eyebrow, .sec-index { color: var(--accent); margin: 0 0 .9rem; }
/* On the dark bands the accent has to lift or it drops to 3.85:1. */
.hero .eyebrow, .page-head .eyebrow, .hero-stats .k,
.promise-bar svg, .foot-tag { color: var(--accent-dk); }

/* ---- motion (scoped to .js so no-JS renders fully visible) ------------- */
.js [data-reveal] {
  opacity: 0; transform: translateY(16px);
  transition: opacity .65s var(--ease) calc(var(--i,0) * 70ms),
              transform .65s var(--ease) calc(var(--i,0) * 70ms);
}
.js [data-reveal].in { opacity: 1; transform: none; }

/* ---- header ------------------------------------------------------------ */
.site-head {
  position: sticky; top: 0; z-index: 100; background: var(--navy); color: var(--d-ink);
  border-bottom: 1px solid transparent; transition: box-shadow .3s var(--ease);
}
.site-head.is-stuck { box-shadow: 0 6px 24px -14px rgba(0,0,0,.7); }
/* The BizVoice lockup is the supplied artwork, stacked and near-square (1.16:1)
   rather than the 5:1 wordmark this template was built around. So the bar is
   taller and the logo is sized by height, not squeezed into a wordmark slot. */
/* Full bleed. The bar spans the viewport with only the page gutter either
   side, rather than being pinned to the 1280px content column, which left
   the logo and the nav marooned in the middle of a wide screen. */
.head-inner {
  width: 100%; margin-inline: 0; padding-inline: var(--pad);
  display: flex; align-items: center; gap: 1.5rem; min-height: 96px; }
/* text-decoration: none matters now there is text in this link. While the logo
   held only an <img> the underline had nothing to draw on, so it never showed;
   adding the place label made it appear under every town and state name. */
.logo { flex: none; display: flex; align-items: center; text-decoration: none; }
/* On a town or state page the place sits under the mark, so the lockup reads
   "BizVoice / Ponca City". The logo becomes a column only on those pages, which
   is what .has-place is for: the brand pages keep the original single-row
   lockup rather than inheriting a gap for a label that is not there. */
/* Centred under the mark, not flush left. The logo image is the widest thing in
   the column, so centring the column items centres the label against the mark
   rather than against the header edge. */
.has-place .logo { flex-direction: column; align-items: center; gap: .15rem; }
.logo-place {
  font-size: .72rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-dk); line-height: 1; text-align: center;
  /* The tracking adds a trailing space on the last letter, which reads as a
     slight left shift when centred. Pull it back by half of it. */
  text-indent: .1em;
}
/* Was .62rem, which computes to 9.9px — under the 12px floor Google flags and
   genuinely hard to read. The city/state name is the one thing telling a
   visitor they are on their own town's page, so it holds at 12px and loses the
   tracking instead; the longest name (Oklahoma City) still clears the burger. */
@media (max-width: 480px) { .logo-place { font-size: .75rem; letter-spacing: .05em; } }
.logo img { height: 66px; width: auto; }
@media (max-width: 900px) {
  .head-inner { min-height: 80px; }
  .logo img { height: 54px; }
}

.site-nav { display: flex; align-items: center; gap: 1.6rem; margin-left: auto; }
.nav-link {
  display: inline-flex; align-items: center; gap: .35rem;
  background: none; border: 0; padding: .4rem 0; cursor: pointer;
  font: inherit; font-size: .94rem; font-weight: 500; color: inherit; text-decoration: none;
  position: relative;
}
.nav-link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform .3s var(--ease);
}
.nav-link:hover::after, .nav-link[aria-current="page"]::after,
.nav-group.is-active > .nav-link::after { transform: scaleX(1); }
.nav-group { position: relative; }
.nav-group svg { transition: transform .25s var(--ease); }
.nav-group.open svg { transform: rotate(180deg); }
.nav-menu {
  position: absolute; top: calc(100% + .55rem); left: 50%; translate: -50% 0;
  min-width: 250px; background: var(--paper); color: var(--ink);
  border-radius: var(--r-md); box-shadow: var(--shadow); padding: .5rem;
  display: grid; opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
.nav-group.open .nav-menu { opacity: 1; visibility: visible; transform: none; }
.nav-menu a {
  padding: .6rem .8rem; border-radius: var(--r-sm); text-decoration: none;
  font-size: .93rem; font-weight: 500; transition: background .15s var(--ease), color .15s var(--ease);
}
.nav-menu a:hover { background: var(--tint); color: var(--accent); }
.nav-menu a[aria-current="page"] { color: var(--accent); font-weight: 600; }

.nav-tail { display: flex; align-items: center; gap: 1rem; }
.nav-phone { font-size: .92rem; font-weight: 600; text-decoration: none; color: var(--d-muted);
  display: inline-flex; align-items: center; min-height: 32px; }
.nav-phone:hover { color: var(--d-ink); }
.burger { display: none; }

/* ---- buttons ----------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .8rem 1.4rem; border: 1.5px solid transparent; border-radius: var(--r-md);
  font: inherit; font-size: .95rem; font-weight: 600; text-decoration: none;
  cursor: pointer; white-space: nowrap;
  transition: background .2s var(--ease), color .2s var(--ease),
              border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.btn svg { transition: transform .25s var(--ease); }
.btn:hover svg { transform: translateX(3px); }
.btn-lg { padding: .95rem 1.7rem; font-size: 1rem; }
.btn-sm { padding: .5rem 1rem; font-size: .88rem; border-radius: var(--r-sm); }
.btn-block { display: flex; width: 100%; margin-top: auto; }

.btn-accent { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn-accent:hover { background: var(--accent-br); border-color: var(--accent-br);
  box-shadow: 0 10px 22px -12px color-mix(in srgb, var(--accent) 80%, transparent); }
.btn-outline { border-color: var(--line); color: var(--ink); background: transparent; }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
/* on dark */
.btn-ghost { border-color: color-mix(in srgb, currentColor 34%, transparent); color: inherit; background: transparent; }
.btn-ghost:hover { border-color: currentColor; background: color-mix(in srgb, currentColor 10%, transparent); }

/* ---- hero -------------------------------------------------------------- */
.hero { background: var(--navy); color: var(--d-ink); padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(3rem, 6vw, 4.5rem); }
.hero-grid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); align-items: center; }
@media (min-width: 960px) { .hero-grid { grid-template-columns: 1.25fr .75fr; } }
.hero h1 { font-size: clamp(2.2rem, 4.4vw, 3.6rem); margin-bottom: 1rem; }
.hero .lede { font-size: clamp(1.03rem, 1.4vw, 1.18rem); color: var(--d-muted); max-width: 64ch; margin-bottom: 1.9rem; }
.hero-cta { display: flex; flex-wrap: wrap; gap: .75rem; margin: 0; }

.hero-stats { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px;
  background: var(--d-line); border: 1px solid var(--d-line); border-radius: var(--r-lg); overflow: hidden; }
@media (min-width: 560px) { .hero-stats { grid-template-columns: 1fr 1fr; } }
/* Translucent over the mesh rather than a solid navy block, so the network
   reads through the panel instead of being punched out by it. The alpha is
   high enough that the stat text keeps its contrast whatever lands behind it
   (checked by bvcontrast.mjs, which composites the layers properly), and the
   blur keeps the mesh from competing with the numbers. */
.hero-stats li {
  background: rgba(33, 51, 64, .88);   /* .78 dropped the small stat labels to 4.35:1 */
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  padding: 1.25rem 1.4rem; display: grid; gap: .2rem;
}
/* A browser without backdrop-filter gets the original solid panel rather than
   a thin wash with the mesh showing through the numbers. */
@supports not (backdrop-filter: blur(6px)) {
  .hero-stats li { background: var(--navy); }
}
.hero-stats .k { color: var(--accent-dk); }
.hero-stats .v { font-size: 1.5rem; font-weight: 700; letter-spacing: -.025em; }
.hero-stats .s { font-size: .85rem; color: var(--d-muted); }

/* ---- promise bar ------------------------------------------------------- */
.promise-bar { background: var(--navy-deep); color: var(--d-ink); }
.promise-bar ul {
  list-style: none; margin: 0; padding: .9rem 0; display: flex; flex-wrap: wrap;
  gap: .6rem 2.5rem; justify-content: center; font-size: .92rem; font-weight: 600;
}
.promise-bar li { display: inline-flex; align-items: center; gap: .55rem; }
.promise-bar svg { color: var(--accent-dk); flex: none; }

/* ---- sections ---------------------------------------------------------- */
.band { padding: clamp(3.5rem, 7vw, 6rem) 0; }
.band-tint {
  background-color: var(--tint);
  background-image: radial-gradient(circle at 1px 1px,
    color-mix(in srgb, var(--ink) 9%, transparent) 1px, transparent 0);
  background-size: 26px 26px;
}
/* The dark bands get the same grid, lighter, so the texture is one idea across
   the whole site rather than a light-mode-only flourish. */
.promise-bar, .cta-band {
  background-image: radial-gradient(circle at 1px 1px,
    rgba(255,255,255,.055) 1px, transparent 0);
  background-size: 26px 26px;
}
.sec-head { max-width: 760px; margin: 0 auto clamp(2rem, 4vw, 3rem); text-align: center; }
.sec-head h2 { margin: 0; }
/* A short gradient rule above each section heading. Decorative, so it is a
   pseudo-element and never reaches the accessibility tree. */
.sec-head::before {
  content: ""; display: block; width: 34px; height: 3px; margin: 0 auto 1.1rem;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 25%, transparent));
}
.band-ink .sec-head::before, .cta-band .sec-head::before {
  background: linear-gradient(90deg, var(--accent-dk), color-mix(in srgb, var(--accent-dk) 20%, transparent));
}
.sec-lede { color: var(--muted); margin: .9rem auto 0; max-width: 67ch; }

.why-grid { display: grid; gap: 2rem clamp(2rem, 5vw, 4rem); }
@media (min-width: 900px) { .why-grid { grid-template-columns: .85fr 1.15fr; align-items: start; } }
.why-h { margin: 0; }
.why-body { color: var(--muted); font-size: 1.05rem; margin: 0; }

/* ---- product tracks ---------------------------------------------------- */
.track-grid { display: grid; gap: 1.5rem; }
@media (min-width: 820px) { .track-grid { grid-template-columns: repeat(3, 1fr); } }
/* With only two product lines the 3-column grid left them hanging off to one
   side; centre them on a narrower measure instead. */
.track-grid[data-count="2"] { max-width: 860px; margin-inline: auto; }
@media (min-width: 820px) { .track-grid[data-count="2"] { grid-template-columns: repeat(2, 1fr); } }

.track {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 2rem 1.9rem 1.9rem;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease), transform .25s var(--ease);
}
/* Accent rule across the top that fills in on hover. */
.track::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent);
  transform: scaleX(0); transform-origin: left; transition: transform .4s var(--ease);
}
.track:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: var(--shadow); }
.track:hover::before { transform: scaleX(1); }

.track-icon { display: grid; place-items: center; width: 46px; height: 46px; margin-bottom: 1.1rem;
  border-radius: var(--r-md); background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent); flex: none; }
.track h3 { font-size: 1.5rem; margin-bottom: .3rem; }
.track-kicker { color: var(--faint); margin-bottom: 1.4rem; line-height: 1.45;
  /* Reserve two lines so a one-line kicker still leaves the price rows
     aligned across cards. */
  min-height: 2.9em; }

.track-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: .1rem .45rem;
  margin: 0 0 1.4rem; padding-bottom: 1.4rem; border-bottom: 1px solid var(--line); }
.track-price .from { font-size: .8rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .09em; color: var(--faint); width: 100%; }
.track-price .amt { font-size: 3rem; font-weight: 700; letter-spacing: -.04em; line-height: 1; }
.track-price .per { font-size: .9rem; color: var(--muted); }
.track .ticks { margin-bottom: 1.6rem; }
.track .ticks li { font-size: .95rem; }

/* ---- feature grid ------------------------------------------------------ */
.feat-grid { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); }
.feat { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 1.4rem 1.5rem; transition: border-color .25s var(--ease); }
.feat { transition: border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease); }
.feat:hover {
  border-color: var(--accent); transform: translateY(-2px);
  box-shadow: 0 10px 24px -16px rgba(33,51,64,.55);
}
@media (prefers-reduced-motion: reduce) { .feat:hover { transform: none; } }
.feat h3 { margin-bottom: .4rem; }
.feat p { margin: 0; color: var(--muted); font-size: .95rem; }

.feat-cols { display: grid; gap: clamp(2rem, 4vw, 3.5rem); }
@media (min-width: 880px) { .feat-cols { grid-template-columns: 1.4fr 1fr; align-items: start; } }
.feat-list h3 { font-size: 1.05rem; margin-bottom: 1rem; padding-bottom: .7rem; border-bottom: 2px solid var(--accent); display: inline-block; }
.feat-list ul { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 2rem; }
@media (max-width: 560px) { .feat-list ul { columns: 1; } }
.feat-list li { break-inside: avoid; padding: .4rem 0 .4rem 1.4rem; position: relative; font-size: .94rem; color: var(--muted); }
.feat-list li::before { content: ""; position: absolute; left: 0; top: .78em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.hw-body { color: var(--muted); font-size: .95rem; }

/* ---- plans ------------------------------------------------------------- */
.mode-switch { display: inline-flex; gap: .3rem; padding: .3rem; margin: 0 auto 1.5rem;
  background: var(--tint); border-radius: var(--r-md); border: 1px solid var(--line); }
.band-tint .mode-switch { background: var(--paper); }
.mode-switch { display: flex; width: fit-content; }
.mode-btn { border: 0; background: transparent; cursor: pointer; font: inherit; font-size: .93rem;
  font-weight: 600; padding: .6rem 1.2rem; border-radius: var(--r-sm); color: var(--muted);
  /* The padding alone came to 43px — one pixel under the tap-target floor. */
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
  transition: background .2s var(--ease), color .2s var(--ease); }
.mode-btn.is-on { background: var(--navy); color: #fff; }
.mode-blurb { text-align: center; color: var(--muted); max-width: 67ch; margin: 0 auto 2rem; }
.mode-panel.is-hidden { display: none; }

.plan-grid { display: grid; gap: 1.25rem; align-items: stretch; }
@media (min-width: 820px) { .plan-grid { grid-template-columns: repeat(3, 1fr); } }
.plan-grid-2 { max-width: 780px; margin-inline: auto; }
@media (min-width: 820px) { .plan-grid-2 { grid-template-columns: repeat(2, 1fr); } }

.plan {
  position: relative; display: flex; flex-direction: column;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.75rem;
}
.plan.is-featured { border-color: var(--accent); border-width: 2px; box-shadow: var(--shadow); }
.plan-flag { position: absolute; top: -.75rem; left: 1.75rem; margin: 0;
  background: var(--accent); color: #fff; padding: .25rem .7rem; border-radius: 100px; }
.plan h3 { font-size: 1.25rem; margin-bottom: .6rem; }
.plan-price { display: flex; align-items: baseline; gap: .3rem; margin: 0 0 .2rem; }
.plan-price .amt { font-size: 2.9rem; font-weight: 700; letter-spacing: -.04em; line-height: 1; }
.plan-price .per { font-size: .9rem; color: var(--faint); }
.plan-meter { font-size: .9rem; color: var(--accent); font-weight: 600; margin: 0 0 1.1rem; }
.plan .ticks { margin-bottom: 1.2rem; }
.plan-hw { font-size: .9rem; font-weight: 600; margin: 0 0 1.2rem; color: var(--ink); }
.plan-note { font-size: .78rem; color: var(--faint); margin: .9rem 0 0; line-height: 1.5; }

.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.ticks li { position: relative; padding-left: 1.5rem; font-size: .94rem; color: var(--muted); }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: .42em; width: .5rem; height: .28rem;
  border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}

.callout {
  display: grid; gap: 1.25rem; align-items: center; margin-top: 2.5rem;
  background: var(--navy); color: var(--d-ink); border-radius: var(--r-lg);
  padding: clamp(1.5rem, 3vw, 2.25rem);
}
@media (min-width: 760px) { .callout { grid-template-columns: 1fr auto; } }
.callout h3 { margin: 0 0 .3rem; font-size: 1.3rem; }
.callout p { margin: 0; color: var(--d-muted); }

/* ---- add-ons ----------------------------------------------------------- */
.addon-grid { display: grid; gap: 1.25rem; }
@media (min-width: 680px) { .addon-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .addon-grid { grid-template-columns: repeat(4, 1fr); } }
.addon { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.5rem; }
.addon h3 { margin-bottom: .5rem; }
.addon-price { display: flex; align-items: baseline; gap: .25rem; margin: 0 0 .3rem; }
.addon-price .amt { font-size: 1.85rem; font-weight: 700; letter-spacing: -.03em; }
.addon-price .per { font-size: .85rem; color: var(--faint); }
.addon-meta { font-size: .85rem; color: var(--muted); margin: 0 0 .9rem; }

/* ---- quotes ------------------------------------------------------------ */
.quote-grid { display: grid; gap: 1.25rem; }
@media (min-width: 720px) { .quote-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .quote-grid { grid-template-columns: repeat(3, 1fr); } }
.quote { margin: 0; background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.6rem; }
.quote blockquote { margin: 0 0 1rem; font-size: 1.02rem; color: var(--ink); }
.quote blockquote::before { content: "\201C"; color: var(--accent); font-size: 2.4rem;
  line-height: 0; vertical-align: -.35em; margin-right: .1em; }
.quote figcaption { font-weight: 700; font-size: .9rem; color: var(--faint); }

/* ---- page heads -------------------------------------------------------- */
.page-head { background: var(--navy); color: var(--d-ink);
  padding: clamp(3rem, 6vw, 4.5rem) 0 clamp(2.5rem, 5vw, 3.5rem); text-align: center; }
/* 32ch, not 20ch. At 20ch a long page title stacked into three short lines in
 * the middle of a 1200px band and read as bunched up; the About headline is
 * 68 characters. 32ch puts it on two lines and still stops ~90px short of the
 * container edge at 1280px, where 36ch would pin it to the full width. The cap
 * only bites on wide screens - on a phone the wrap is narrower than 32ch. */
.page-head h1 { margin: 0 auto .9rem; max-width: 32ch; }
.page-head .lede { color: var(--d-muted); font-size: 1.06rem; max-width: 83ch; margin: 0 auto; }

/* ---- article (10DLC) --------------------------------------------------- */
.article-grid { display: grid; gap: clamp(2rem, 4vw, 3.5rem); }
@media (min-width: 940px) { .article-grid { grid-template-columns: 250px minmax(0,1fr); align-items: start; } }
.article-side { position: sticky; top: 92px; display: grid; gap: .6rem; }
.side-h { color: var(--faint); margin: 0 0 .2rem; }
.article-side ul { list-style: none; margin: 0 0 .8rem; padding: 0; display: grid; gap: .1rem; }
.article-side ul a { display: block; padding: .45rem .7rem; border-radius: var(--r-sm);
  text-decoration: none; font-size: .93rem; color: var(--muted); border-left: 2px solid var(--line); }
.article-side ul a:hover { color: var(--accent); border-left-color: var(--accent); background: var(--tint); }
.article-side .btn { margin-bottom: .5rem; }
.article > section { margin-bottom: 2.75rem; }
.article h2 { font-size: clamp(1.4rem, 2.2vw, 1.85rem); }
/* Same accent mark as .sec-head, for the pages built from articles, forms and
   legal copy rather than from bands. Left-aligned here because these headings
   are, unlike the centred section heads. */
.article h2::before, .form-card > h2::before, .legal h2::before, .chooser-pane h3::before {
  content: ""; display: block; width: 28px; height: 3px; margin: 0 0 .7rem;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 25%, transparent));
}
.article p { color: var(--muted); max-width: 90ch; }   /* ~81 chars, fills ~80% of the article column */
.terms { margin: 0; display: grid; gap: .2rem; }
.terms dt { font-weight: 700; margin-top: 1rem; }
.terms dd { margin: 0; color: var(--muted); max-width: none; }   /* see .legal p */

/* ---- faq --------------------------------------------------------------- */
.faq-list { border-top: 1px solid var(--line); }
.faq { border-bottom: 1px solid var(--line); }
.faq summary { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  cursor: pointer; list-style: none; padding: 1.1rem 0; font-weight: 600; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent); }
.faq-mark { position: relative; width: 13px; height: 13px; flex: none; }
.faq-mark::before, .faq-mark::after { content: ""; position: absolute; inset: 50% 0 auto 0;
  height: 2px; background: var(--accent); transition: transform .3s var(--ease); }
.faq-mark::after { transform: rotate(90deg); }
.faq[open] .faq-mark::after { transform: rotate(0); }
.faq-a p { margin: 0 0 1.2rem; color: var(--muted); }
.js .faq[open] .faq-a { animation: acc .35s var(--ease); }
@keyframes acc { from { opacity: 0; transform: translateY(-6px); } }

/* ---- steps ------------------------------------------------------------- */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; }
.step { display: grid; grid-template-columns: auto 1fr; gap: 1.25rem; align-items: start;
  background: var(--paper); padding: 1.6rem; }
.step-n { font-size: 1.1rem; font-weight: 700; color: var(--accent); }
.step h3 { margin-bottom: .35rem; }
.step p { margin: 0; color: var(--muted); }
.step-actions { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; margin-top: 2rem; }

/* ---- forms ------------------------------------------------------------- */
.form-frame { background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: clamp(.75rem, 2vw, 1.25rem); overflow: hidden; }
.form-frame iframe { display: block; border-radius: var(--r-sm); }
.form-fallback { text-align: center; color: var(--muted); font-size: .92rem; margin-top: 1.25rem; }
.form-fallback a { color: var(--accent); font-weight: 600; }

.split { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
@media (min-width: 880px) { .split { grid-template-columns: 1fr 1fr; } }
.contact-details h2 { font-size: 1.35rem; }
.contact-more { margin-top: 2.25rem; }
.contact-line { margin-bottom: 1.25rem; }
.contact-label { display: block; color: var(--faint); margin-bottom: .2rem; }
.contact-big { font-size: 1.45rem; font-weight: 700; letter-spacing: -.025em; text-decoration: none; }
.contact-big:hover { color: var(--accent); }
.contact-links { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.contact-links a { color: var(--accent); font-weight: 600; text-decoration: none; }
.contact-links a:hover { text-decoration: underline; }

.form-card { position: relative; overflow: hidden; }
/* The two page types that reach neither accent: the Cognito embed pages
   (.form-frame, an iframe with no headings of ours) and the 10DLC process page
   (a .steps list, no .sec-head). Checked with ~/accentcheck.mjs, which reads
   the rendered pseudo-elements — grepping class attributes got this wrong twice.
   Without these two rules, recurring-payment and 10dlc-registration-process
   carry nothing but the mesh. */
.form-frame { position: relative; border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--line); background: var(--paper); padding-top: 3px; }
.form-frame::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-dk));
}
.steps { position: relative; }
.steps::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 15%, transparent));
  border-radius: 2px;
}

/* A brand edge along the top of every form card. These are the pages a visitor
   actually converts on, so they get a little more presence than a plain box. */
.form-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-dk));
}
.form-card { background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: clamp(1.5rem, 3vw, 2.1rem); }
.form-card h2 { font-size: 1.3rem; margin-bottom: 1.3rem; }
.form-card label { display: block; margin-bottom: 1rem; }
.form-card label > span { display: block; font-size: .8rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--faint); margin-bottom: .35rem; }
.form-card input, .form-card textarea {
  width: 100%; padding: .78rem .9rem; font: inherit; font-size: .96rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper); color: var(--ink);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.form-card input:focus, .form-card textarea:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
.field-row { display: grid; gap: 0 1rem; grid-template-columns: 1fr 1fr; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-note { font-size: .84rem; color: var(--faint); text-align: center; margin: .8rem 0 0; }
.form-status { text-align: center; margin: .7rem 0 0; font-size: .93rem; }
.form-status.ok { color: #0f7a46; font-weight: 600; }
.form-status.err { color: var(--danger); font-weight: 600; }

/* ---- legal ------------------------------------------------------------- */
.legal { color: var(--muted); }
.legal h2 { font-size: 1.2rem; color: var(--ink); margin: 2.2rem 0 .8rem; }
/* No second measure inside .wrap.narrow. The wrap is already an 820px reading
   column; capping the paragraph at 85ch inside it left a 140px gap on the RIGHT
   ONLY, which is what read as lopsided rather than merely narrow. Policy pages
   are scanned for a clause rather than read start to finish, so the longer line
   is an acceptable trade for a column that looks deliberate. */
.legal p { max-width: none; }
.legal-eff { font-weight: 700; color: var(--ink); }

/* ---- cta band ---------------------------------------------------------- */
.cta-band { background: var(--navy); color: var(--d-ink); text-align: center; }
.cta-inner h2 { margin: 0 auto .6rem; max-width: 22ch; }
.cta-inner p { color: var(--d-muted); max-width: 62ch; margin: 0 auto 1.8rem; }
.cta-actions { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; margin: 0; }

/* ---- footer ------------------------------------------------------------ */
.site-foot { background: var(--navy-deep); color: var(--d-ink); padding: clamp(3rem, 6vw, 4.5rem) 0 1.75rem; }
/* Count the tracks against footer() before changing this. It was
   `1.4fr 1fr 1fr 1fr`, four tracks for five children, so the last column
   wrapped onto its own row under the logo with the rest of that row empty.
   There are six children now that the towns have a column of their own, and
   auto-fit keeps it honest if another is added. */
.foot-top { display: grid; gap: 2.5rem; --foot-cols: 4; }
@media (min-width: 900px) {
  .foot-top { grid-template-columns: 1.5fr repeat(var(--foot-cols), minmax(0, 1fr)); gap: clamp(1.5rem, 2.6vw, 2.75rem); }
}
.foot-brand img { height: 72px; width: auto; }   /* near-square lockup, see .logo img */
.foot-tag { color: var(--accent-dk); font-weight: 700; margin: .9rem 0 .6rem; }
.foot-contact { color: var(--d-muted); font-size: .94rem; }
.foot-contact a { text-decoration: none; }
.foot-contact a:hover { color: var(--accent-dk); }
.foot-col h3 { font-size: .78rem; text-transform: uppercase; letter-spacing: .1em;
  color: color-mix(in srgb, var(--d-ink) 55%, transparent); margin-bottom: .9rem; }
.foot-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.foot-col a { color: var(--d-muted); text-decoration: none; font-size: .94rem; }
.foot-col a:hover { color: var(--accent-dk); }
.foot-legal { display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; justify-content: space-between;
  align-items: center; margin-top: clamp(2.5rem, 5vw, 3.5rem); padding-top: 1.5rem;
  border-top: 1px solid var(--d-line); font-size: .85rem;
  color: color-mix(in srgb, var(--d-ink) 55%, transparent); }
.foot-links { display: flex; gap: 1.5rem; }
.foot-links a { text-decoration: none; display: inline-flex; align-items: center; min-height: 24px; }
.foot-links a:hover { color: var(--accent-dk); }

/* ---- small screens ----------------------------------------------------- */
/* The desktop nav needs ~1090px to lay out 7 links plus the phone number and
 * quote button on one row. Below that it spilled past the viewport, so the
 * burger takes over at 1100px rather than 900px. */
@media (max-width: 1100px) {
  /* 44px hit area (WCAG 2.5.8); the 26x20 bar icon stays centred inside it */
  .burger { display: block; margin-left: auto; margin-right: -10px; width: 44px; height: 44px;
    position: relative; background: none; border: 0; cursor: pointer; color: inherit; }
  .burger span, .burger span::before, .burger span::after {
    content: ""; position: absolute; left: 0; width: 26px; height: 2px; background: currentColor;
    border-radius: 2px; transition: transform .3s var(--ease), opacity .2s; }
  .burger span { top: 21px; left: 9px; }
  .burger span::before { top: -7px; }
  .burger span::after { top: 7px; }
  .burger[aria-expanded="true"] span { background: transparent; }
  .burger[aria-expanded="true"] span::before { transform: translateY(7px) rotate(45deg); }
  .burger[aria-expanded="true"] span::after { transform: translateY(-7px) rotate(-45deg); }

  .site-nav {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0; margin: 0;
    background: var(--navy); border-top: 1px solid var(--d-line);
    padding: .5rem var(--pad) 1.4rem; max-height: calc(100vh - 68px); overflow-y: auto;
  }
  .site-nav.open { display: flex; }
  .nav-link { padding: .85rem 0; justify-content: space-between; width: 100%; }
  .nav-link::after { display: none; }
  .nav-group { border-bottom: 1px solid var(--d-line); }
  .site-nav > .nav-link { border-bottom: 1px solid var(--d-line); }
  .nav-menu { position: static; translate: none; transform: none; opacity: 1; visibility: visible;
    display: none; background: transparent; color: inherit; box-shadow: none; padding: 0 0 .6rem .8rem; }
  .nav-group.open .nav-menu { display: grid; }
  .nav-menu a { color: var(--d-muted); padding: .55rem .2rem; }
  .nav-menu a:hover { background: transparent; }
  .nav-tail { flex-direction: column; align-items: stretch; gap: .8rem; margin-top: 1.1rem; }
  .nav-phone { text-align: center; }
}

/* Not nav: these belong to the 900px layout breakpoint, and must not ride
 * along with the burger above. .article-side in particular would fight
 * .article-grid, which goes two-column at 940px. */
@media (max-width: 900px) {
  .field-row { grid-template-columns: 1fr; }
  .article-side { position: static; }
}

/* ---- reduced motion / print -------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
}
@media print {
  .site-head, .promise-bar, .cta-band, .article-side { display: none !important; }
  .hero, .page-head, .callout, .site-foot { background: #fff !important; color: #000 !important; }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* ---- request a quote (native form) ------------------------------------- */
.quote-layout { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
@media (min-width: 900px) { .quote-layout { grid-template-columns: 1.3fr .7fr; } }

.q-fieldset { border: 0; padding: 0; margin: 0 0 1.4rem; }
.q-fieldset legend { padding: 0; font-size: .8rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--faint); margin-bottom: .6rem; }
.q-options { display: grid; gap: .5rem; }
@media (min-width: 560px) { .q-options { grid-template-columns: 1fr 1fr; } }
.q-option { position: relative; display: block; margin: 0; cursor: pointer; }
.q-option input { position: absolute; opacity: 0; width: 0; height: 0; }
.q-option span { display: block; padding: .75rem .9rem; border: 1px solid var(--line);
  border-radius: var(--r-md); font-size: .94rem; font-weight: 600;
  transition: border-color .2s var(--ease), background .2s var(--ease), color .2s var(--ease); }
.q-option:hover span { border-color: var(--accent); }
.q-option input:checked + span { border-color: var(--accent); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
.q-option input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.quote-aside { background: var(--tint); border-radius: var(--r-lg);
  padding: clamp(1.5rem, 3vw, 2rem); }
.quote-aside h2 { font-size: 1.2rem; margin-bottom: 1.2rem; }
.q-steps { list-style: none; margin: 0 0 1.5rem; padding: 0; display: grid; gap: 1.1rem; }
.q-steps li { display: grid; grid-template-columns: auto 1fr; gap: .85rem; align-items: start; }
.q-n { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
  background: var(--accent); color: #fff; font-size: .8rem; font-weight: 700; }
.q-steps p { margin: 0; font-size: .93rem; color: var(--muted); }
.q-call { margin: 0; padding-top: 1.2rem; border-top: 1px solid var(--line);
  font-size: .92rem; color: var(--muted); }
.q-call .contact-big { font-size: 1.3rem; display: inline-block; margin-top: .25rem; }

/* ---- platform band ----------------------------------------------------- */
.band-dark { background: var(--navy); color: var(--d-ink); }
.band-dark .sec-lede, .band-dark .platform-body { color: var(--d-muted); }
.band-dark .eyebrow { color: var(--accent-dk); }
.platform-grid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); align-items: center; }
@media (min-width: 960px) { .platform-grid { grid-template-columns: 1fr .9fr; } }
.platform-body { color: var(--d-muted); max-width: 62ch; }
.platform-stats { list-style: none; margin: 2rem 0 0; padding: 0;
  display: grid; gap: 1px; background: var(--d-line);
  border: 1px solid var(--d-line); border-radius: var(--r-lg); overflow: hidden; }
@media (min-width: 560px) { .platform-stats { grid-template-columns: 1fr 1fr; } }
.platform-stats li { background: var(--navy); padding: 1.2rem 1.35rem; display: grid; gap: .15rem; }
.platform-stats .v { font-size: 1.55rem; font-weight: 700; letter-spacing: -.03em; line-height: 1.1; }
.platform-stats .k { font-size: .78rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .09em; color: var(--accent-dk); }
.platform-stats .s { font-size: .86rem; color: var(--d-muted); }

.platform-art { display: flex; justify-content: center; }
.net-art { width: 100%; max-width: 580px; height: auto;
  color: color-mix(in srgb, var(--d-ink) 42%, transparent); }

.na-edge path { fill: none; stroke: currentColor; stroke-width: 1.6; }
.na-standby path, line.na-standby { stroke-dasharray: 5 4; opacity: .65; }
.na-sync path { fill: none; stroke: var(--accent-dk); stroke-width: 1.4; stroke-dasharray: 4 4; }
.na-sync-label { fill: var(--accent-dk); font-size: 10.5px; font-weight: 600; font-family: var(--font); }

.na-node rect { fill: color-mix(in srgb, #ffffff 6%, transparent); stroke: var(--d-line); stroke-width: 1; }
.na-core rect { fill: color-mix(in srgb, var(--accent) 18%, transparent);
  stroke: var(--accent-dk); stroke-width: 1.6; }
.na-core-standby rect { fill: color-mix(in srgb, #ffffff 5%, transparent);
  stroke: var(--d-line); stroke-dasharray: 5 4; }

.na-label text, .na-core-label text { fill: var(--d-ink); font-size: 12px; font-weight: 600;
  text-anchor: middle; font-family: var(--font); }
.na-label .na-sub, .na-core-label .na-sub { fill: var(--d-muted); font-size: 10.5px; font-weight: 500; }
.na-pulse circle { fill: var(--accent-dk); }

.na-legend line { stroke: currentColor; stroke-width: 1.6; }
.na-legend text { fill: var(--d-muted); font-size: 11px; font-family: var(--font); }

@media (prefers-reduced-motion: reduce) { .na-pulse { display: none; } }

/* ---- plan comparison matrix -------------------------------------------- */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; }
.table-scroll { overflow-x: auto; border-radius: var(--r-lg);
  border: 1px solid var(--line); background: var(--paper); }
.compare { width: 100%; border-collapse: collapse; min-width: 620px; }
.compare th, .compare td { padding: .95rem 1.1rem; text-align: center;
  border-bottom: 1px solid var(--line); font-size: .94rem; }
.compare tbody tr:last-child th, .compare tbody tr:last-child td { border-bottom: 0; }
.compare thead th { font-size: 1.02rem; font-weight: 700; padding-top: 1.25rem;
  padding-bottom: 1.25rem; background: var(--tint); }
.compare thead th.col-hot { color: var(--accent); box-shadow: inset 0 -3px 0 var(--accent); }
.compare tbody th { text-align: left; font-weight: 600; color: var(--ink); min-width: 230px; }
.compare tbody tr:nth-child(even) th, .compare tbody tr:nth-child(even) td {
  background: color-mix(in srgb, var(--tint) 55%, transparent); }
.compare .yes { color: var(--accent); }
.compare .no  { color: color-mix(in srgb, var(--ink) 34%, transparent); }
.compare .part { color: var(--muted); font-weight: 600; }
.compare .yes svg, .compare .no svg { display: inline-block; vertical-align: middle; }

/* ---- feature sets ------------------------------------------------------ */
.set-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.set { position: relative; background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.6rem 1.7rem; overflow: hidden; }
.set-count { margin: 0 0 .2rem; font-size: 2.6rem; font-weight: 700; line-height: 1;
  letter-spacing: -.04em; color: var(--accent); }
.set h3 { margin: 0 0 .15rem; font-size: 1.25rem; }
.set-on { margin: 0 0 .8rem; font-size: .76rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .09em; color: var(--faint); }
.set-blurb { margin: 0; color: var(--muted); font-size: .95rem; }
.smallprint { max-width: none; margin-inline: auto; }   /* see .legal p */

/* ---- annual-term offer -------------------------------------------------- */
.offer-band { padding: clamp(2.5rem, 5vw, 3.5rem) 0; }
.offer-inner {
  display: grid; gap: 1.5rem 2rem; align-items: center;
  background: var(--navy); color: var(--d-ink);
  border-radius: var(--r-lg); padding: clamp(1.75rem, 3.5vw, 2.5rem);
  position: relative; overflow: hidden;
}
/* A quiet accent edge rather than a loud full-orange panel: this sits next to
   plan cards that already use the accent for their primary buttons. */
.offer-inner::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--accent); }
@media (min-width: 860px) { .offer-inner { grid-template-columns: auto 1fr auto; } }

.offer-figure {
  display: grid; place-content: center; text-align: center; flex: none;
  min-width: 132px; padding: 1rem 1.25rem; border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.of-n { display: block; font-size: 1.9rem; font-weight: 700; letter-spacing: -.03em; line-height: 1; color: var(--accent-dk); }
/* Not --accent-dk. At 12.8px this needs 4.5:1, and the brand's lifted blue
   (#28a4ff) measures 4.28:1 on the tinted badge over the navy band. Measured,
   not guessed: var(--accent-lt) is 5.50:1 on the same composite. The big "2 months"
   above it keeps --accent-dk because at ~30px it only needs 3:1.
   (The VOIP59 build does not hit this: its orange clears AA at this size.) */
.of-s { display: block; font-size: .8rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .12em; color: var(--accent-lt); margin-top: .25rem; }

.offer-copy h2 { font-size: clamp(1.35rem, 2.2vw, 1.75rem); margin: 0 0 .5rem; }
.offer-copy p:last-child { margin: 0; color: var(--d-muted); max-width: 71ch; }
.offer-copy .eyebrow { color: var(--accent-dk); margin-bottom: .5rem; }
.offer-cta { flex: none; }

/* ---------- phones page ---------------------------------------------------
   A tall list rather than a card grid: each device needs a paragraph and a
   list of points, which a three-across grid would squeeze into a column of
   slivers. The mark sits in its own column on wide screens and above the text
   on a phone. */
.phone-list { display: grid; gap: clamp(1.5rem, 3vw, 2.2rem); }
.phone {
  display: grid; grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
  gap: clamp(1.2rem, 2.8vw, 2.4rem);
  align-items: center;
  padding: clamp(1.4rem, 3vw, 2.1rem);
  border: 1px solid var(--line); border-radius: var(--r-lg, 16px);
  background: var(--paper);
}
.phone-mark { color: var(--accent); }
.phone-mark svg { width: 100%; height: auto; display: block; }
.phone-tag {
  font-size: .78rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 .3rem;
}
.phone-body h2 { font-size: clamp(1.25rem, 2.4vw, 1.6rem); margin: 0 0 .55rem; }
.phone-who { color: var(--muted); margin: 0 0 .7rem; }
.phone-who strong { color: var(--ink); }
.phone-body > p { margin: 0 0 .9rem; }
/* No panel behind the shot: the manufacturer PNGs carry real transparency, so
   they sit straight on the card. A tint block behind them just read as a grey
   box round every phone. */
.phone-shot {
  display: grid; place-items: center;
  padding: clamp(.2rem, 1vw, .6rem);
}
.phone-shot img { width: 100%; height: auto; display: block; }
.phone-mark { color: var(--accent); display: block; width: 64px; }
.phone-mark svg { width: 100%; height: auto; }
.phone-credit { margin-top: 1.4rem; font-size: .82rem; color: var(--faint); }
@media (max-width: 720px) {
  .phone { grid-template-columns: 1fr; gap: 1rem; align-items: start; }
  .phone-shot { max-width: 280px; }
}



/* The "which one do I need" ladder. Numbered because the order is the advice. */
.pick-list { list-style: none; counter-reset: pick; margin: 1.6rem 0 0; padding: 0; display: grid; gap: 1.1rem; }
/* The counter is a ::before, which is itself a grid item - so the heading and
   the paragraph have to live inside ONE wrapper element, or the paragraph wraps
   round into the 2.1rem counter column and renders one word per line. */
.pick-list li {
  counter-increment: pick;
  display: grid; grid-template-columns: 2.1rem minmax(0, 1fr); gap: .9rem;
  align-items: start;
}
.pick-list li::before {
  content: counter(pick); font-weight: 700; color: var(--accent);
  font-size: 1.05rem; line-height: 1.5;
}
.pick-list h3 { font-size: 1.05rem; margin: 0 0 .2rem; }
.pick-list p { color: var(--muted); margin: 0; }
.pick-note {
  margin-top: clamp(1.6rem, 3vw, 2.2rem); padding-top: 1.1rem;
  border-top: 1px solid var(--line); color: var(--muted); font-size: .96rem;
}

/* ---------- features chooser ---------------------------------------------
   38 features is a wall of text if you print it. So: a menu of buttons and a
   single detail pane. Everything is in the markup regardless - the no-JS rule
   below reveals every pane and turns the page into a plain readable list, which
   is why nothing here is fetched. */
.chooser { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: clamp(1.4rem, 3vw, 2.6rem); align-items: start; }
.chooser-group + .chooser-group { margin-top: 1.5rem; }
.chooser-g {
  font-size: .74rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); margin: 0 0 .5rem;
}
.chooser-menu ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .15rem; }
.chooser-btn {
  width: 100%; text-align: left; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: .96rem; color: var(--ink);
  padding: .5rem .7rem; border-radius: var(--r-sm);
  display: flex; align-items: center; min-height: 44px;   /* 44 = tap-target floor */
  transition: background .15s var(--ease), color .15s var(--ease);
}
.chooser-btn:hover { background: var(--tint); }
.chooser-btn[aria-current="true"] { background: var(--accent); color: var(--on-accent); font-weight: 600; }

.chooser-panes { position: sticky; top: 108px; }
.chooser-pane {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: clamp(1.3rem, 3vw, 2rem); background: var(--paper);
}
.chooser-cat {
  font-size: .76rem;   /* .74 computed to 11.8px, just under the 12px floor */ font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 .5rem;
}
.chooser-pane h3 { font-size: clamp(1.25rem, 2.4vw, 1.6rem); margin: 0 0 .5rem; }
.chooser-what { font-size: 1.08rem; color: var(--ink); margin: 0 0 .8rem; }
.chooser-how { color: var(--muted); margin: 0 0 1.1rem; }
.chooser-plan { margin: 0; }
.chooser-plan span {
  display: inline-block; font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 999px; padding: .28rem .7rem;
}
.chooser-note { margin-top: clamp(1.4rem, 3vw, 2rem); color: var(--faint); font-size: .9rem; }

/* With JS, one pane at a time. Without it, every pane shows and the page is
   simply a long list, which is the correct fallback. */
.js .chooser-pane { display: none; }
.js .chooser-pane.is-open { display: block; }
:not(.js) .chooser-pane + .chooser-pane { margin-top: 1rem; }

@media (max-width: 860px) {
  .chooser { grid-template-columns: 1fr; }
  .chooser-panes { position: static; }
}

/* The About page promise list, a size up from the inline ticks. */
.ticks-lg { font-size: 1.04rem; }
.ticks-lg li { margin-bottom: .55rem; }

/* "What we will not do" - a tick against each would read as an endorsement of
   the thing we are refusing to do. Struck through with a cross instead. */
.nots { list-style: none; margin: 1.4rem 0 0; padding: 0; display: grid; gap: .7rem; font-size: 1.04rem; }
.nots li { display: grid; grid-template-columns: 1.6rem minmax(0, 1fr); gap: .6rem; align-items: start; }
.nots li::before {
  content: "\00d7"; color: var(--accent); font-weight: 700; font-size: 1.25rem; line-height: 1.25;
}

/* The two practical lines under each feature: who it suits, and the thing you
   only find out after installing a few hundred of them. A definition list
   because that is what it is, and it keeps the labels readable to screen
   readers rather than being bold text pretending to be structure. */
.chooser-meta { margin: 0 0 1.2rem; display: grid; gap: .55rem; }
.chooser-meta dt {
  font-size: .72rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint); margin: 0;
}
.chooser-meta dd { margin: 0; color: var(--muted); }
.chooser-meta dt + dd { margin-top: -.3rem; }
.chooser-how { padding-bottom: .2rem; }

/* ---------- long native forms (porting, 10DLC) ---------------------------- */
.form-intro { font-size: 1.06rem; color: var(--muted); max-width: 74ch; }
.form-fees {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--r-md); padding: clamp(1.1rem, 2.4vw, 1.6rem);
  margin: clamp(1.4rem, 3vw, 2rem) 0; background: var(--tint);
}
.form-fees h2 { font-size: 1.08rem; margin: 0 0 .6rem; }
.form-long { margin-top: clamp(1.6rem, 3vw, 2.4rem); }
.form-sec { border: 0; padding: 0; margin: 0 0 clamp(1.8rem, 4vw, 2.6rem); }
.form-sec-h {
  padding: 0; font-size: .76rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--accent);
  border-bottom: 1px solid var(--line); width: 100%; padding-bottom: .5rem; margin-bottom: 1.1rem;
}
.fld-hint { font-size: .88rem; color: var(--faint); margin: -.15rem 0 .45rem; }
.fld-choice { border: 0; padding: 0; margin: 0 0 1.1rem; }
.fld-choice legend { padding: 0; font-weight: 600; margin-bottom: .35rem; }
.fld-opts { display: grid; gap: .35rem; }
.opt { display: grid; grid-template-columns: 1.1rem minmax(0, 1fr); gap: .6rem;
  align-items: start; cursor: pointer; min-height: 32px; align-content: center; }
.opt input { margin-top: .12rem; width: 24px; height: 24px; flex: none; }
.opt { grid-template-columns: 24px minmax(0, 1fr); }
.fld-check { margin-bottom: 1.1rem; }
.form-actions { margin-top: 1.4rem; }
.form-after {
  margin-top: 1.6rem; padding: 1rem 1.2rem; border-radius: var(--r-md);
  background: var(--tint); color: var(--muted); font-size: .96rem;
}

/* The city page stacks three blocks inside one band: the local points, the
   facts, then the towns. .sec-head carries no top margin (it is normally the
   first thing in its band), so without this the second and third headings sat
   hard against the grid above them. */
#local .sec-head { margin-top: clamp(2.5rem, 5vw, 4rem); }

/* ---- mesh backdrop (dark bands) ----------------------------------------- */
/* Decorative node mesh behind the dark sections. Two rules keep it decoration
   rather than a contrast problem:
     - mask-image fades it out across the left 52%, which is where every
       headline and paragraph on these bands sits. Nothing legible ever has it
       behind it; the texture lives in the empty right-hand space.
     - strokes are 1px at <=15% alpha and the nodes <=28%.
   Verified with ~/bvcontrast.mjs after adding it. */
.mesh-art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 0; pointer-events: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, transparent 32%, #000 60%, #000 100%);
          mask-image: linear-gradient(to right, transparent 0%, transparent 32%, #000 60%, #000 100%);
}
.ma-edge { stroke: var(--accent-lt); stroke-opacity: .22; stroke-width: 1; fill: none; }
.ma-node { fill: var(--accent-pale); fill-opacity: .40; }
/* The handful of "live" nodes get the brand blue and a wider ring, so the mesh
   reads as a network with traffic on it rather than wallpaper. */
.ma-live { fill: none; stroke: var(--accent-dk); stroke-opacity: .62; stroke-width: 1.6; }

/* A slow breath on the live nodes. Long and low-amplitude on purpose: it should
   register as a network doing something, not as an animation demanding
   attention, and it must never pull the eye off the headline. Honoured only
   when the visitor has not asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .ma-live circle { animation: ma-breathe 4.5s var(--ease) infinite; transform-origin: center; }
  .ma-live circle:nth-child(2) { animation-delay: 1.1s; }
  .ma-live circle:nth-child(3) { animation-delay: 2.2s; }
  .ma-live circle:nth-child(4) { animation-delay: 3.3s; }
}
@keyframes ma-breathe {
  0%, 100% { opacity: .35; r: 7; }
  50%      { opacity: .95; r: 10; }
}
/* Interior page heads are short; at full strength the mesh crowded them. */
.ma-quiet { opacity: .6; }

@media (prefers-reduced-transparency: reduce) { .mesh-art { display: none; } }
/* Below the two-column breakpoint the copy runs full width, so there is no
   empty side left to hold texture and the lines would cross the headline. */
@media (max-width: 900px) { .ma-edge, .ma-node, .ma-live { display: none; } }

/* Each band that hosts the mesh needs its own stacking context, with its
   content lifted above it. */
.hero, .page-head, .cta-band { position: relative; isolation: isolate; }
.hero > .wrap, .page-head > .wrap, .cta-band > .wrap { position: relative; z-index: 1; }

/* ---- photographic hero -------------------------------------------------- */
/* Used on the state page and any town page with a cleared photograph.
   The scrim is a contrast requirement, not decoration: white text over an
   arbitrary photograph fails AA the moment the sky is bright, so the image
   always sits under a dark gradient and the text sits on that. Verified with
   ~/bvherocontrast.mjs, which samples the actual pixels behind the glyphs
   rather than trusting the CSS. */
.hero-photo { position: relative; isolation: isolate; overflow: hidden;
  padding: clamp(4rem, 9vw, 7rem) 0 clamp(3.5rem, 7vw, 5.5rem); }
.hero-photo .hero-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center; z-index: -2;
}
/* Weighted to the left, where the text is, and nearly clear on the right so the
   photograph is actually visible. It used to be a flat .52-.93 wash across the
   whole frame, which kept contrast comfortably but hid the picture. The right
   hand side is now .18, so the photograph reads; the verifier below is what
   makes that safe rather than a guess. */
.hero-scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to right,
      rgba(19,30,37,.93) 0%, rgba(19,30,37,.91) 52%,
      rgba(19,30,37,.66) 70%, rgba(19,30,37,.32) 88%, rgba(19,30,37,.22) 100%),
    linear-gradient(to top, rgba(19,30,37,.50) 0%, rgba(19,30,37,0) 36%);
}
/* Below the two-column breakpoint the copy runs the full width, so the
   left-weighted gradient no longer lines up with it and the wash has to be
   even again. */
@media (max-width: 860px) {
  .hero-scrim { background:
    linear-gradient(to top, rgba(19,30,37,.88) 0%, rgba(19,30,37,.74) 60%, rgba(19,30,37,.66) 100%); }
}
.hero-photo-inner { position: relative; z-index: 1; max-width: 60ch; }
.hero-photo h1 { color: #fff; }
.hero-photo .lede { color: rgba(255,255,255,.90); }
/* Not --accent-dk. Measured at 2.82:1 against the brightest sky pixel behind it
   on the Oklahoma hero, where small text needs 4.5. The photo underneath is
   arbitrary, so the eyebrow cannot rely on the scrim alone and goes near-white.
   Re-check with ~/bvherocontrast.mjs after changing any hero image. */
.hero-photo .eyebrow { color: var(--accent-pale); }

/* The credit line. Rendered only for licences that require attribution
   (CC BY, CC BY-SA); public domain and CC0 images omit it. Small and in the
   corner, but it must be present and the licence must be linked. */
/* Deliberately tiny. It is a licence obligation, not a caption anyone needs to
   read, so it sits at the smallest size that is still legible and still passes
   AA against the pixels behind it. */
.hero-credit {
  position: absolute; right: 0; bottom: 0; z-index: 2; margin: 0;
  padding: .2rem .5rem; font-size: .62rem; line-height: 1.3;
  letter-spacing: .01em;
  /* Near-opaque on purpose. The chip sits in whichever corner the photograph
     happens to be brightest in (Miami and Catoosa both failed at .82), and it
     is 12px tall, so making it independent of the image is cheaper than tuning
     the scrim per photo. */
  color: rgba(255,255,255,.92); background: rgba(19,30,37,.93);
  border-top-left-radius: var(--r-sm);
}
.hero-credit a { color: #fff; }
/* ⚠️ Deliberate trade-off, flagged to blampe. These were .48rem/.44rem — about
   7px — which is small enough that the attribution arguably stops discharging
   the CC BY obligation it exists for. Raised to ~10px: still the smallest thing
   on the page and still unobtrusive, but actually readable. */
@media (max-width: 620px) { .hero-credit { font-size: .58rem; padding: .18rem .42rem; } }

/* The state page's index of every town. 73 of them, so: centred tiles showing
   nothing but the name. The county line that used to sit under each one tripled
   the height of the block and nobody scanning for their own town was reading it.
   auto-fill with a narrow min keeps the rows dense on a wide screen and reflows
   to two columns on a phone. */
.town-index {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
/* The li stretches to the row height and the a fills it, so a tile whose name
   wraps onto two lines does not leave its neighbours short in the same row. */
.town-index li { display: flex; }
.town-index a {
  flex: 1;
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 52px; padding: .7rem .9rem;
  border: 1px solid var(--line); border-radius: var(--r-md);
  font-weight: 600; color: var(--ink); text-decoration: none; line-height: 1.25;
  transition: border-color .2s var(--ease), background .2s var(--ease), color .2s var(--ease);
}
.town-index a:hover { border-color: var(--accent); color: var(--accent); background: var(--paper); }

/* The region strip on the home page. The state's postal abbreviation carries
   the tile and the name sits under it, so seven states read as a set at a
   glance. States without a page yet are the same tile without the link
   affordance, rather than a link that goes nowhere. */
.state-strip {
  list-style: none; margin: 0; padding: 0; justify-content: center;
  display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(118px, 148px));
}
.state-strip a, .state-strip .state-tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .25rem; min-height: 96px; padding: 1rem .6rem; text-align: center;
  border: 1px solid var(--line); border-radius: var(--r-md);
  text-decoration: none; transition: border-color .2s var(--ease), background .2s var(--ease);
}
.state-abbr {
  font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; line-height: 1;
  color: var(--faint);
}
.state-name { font-size: .88rem; font-weight: 600; color: var(--muted); }
/* The state you can actually click into gets the brand colour and a solid
   ground; the rest stay quiet so the one with detail behind it reads first. */
.state-strip .has-page a { border-color: var(--accent); background: var(--paper); }
.state-strip .has-page .state-abbr { color: var(--accent); }
.state-strip .has-page .state-name { color: var(--ink); font-weight: 700; }
.state-strip a:hover { border-color: var(--accent); background: var(--paper); }

/* ---- towns -------------------------------------------------------------- */
/* The list of towns a city page covers. A plain wrapped list of place names
   rather than cards: boxing each one would turn twelve words into a grid of
   empty rectangles. */
.area-list {
  list-style: none; margin: 1.4rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .55rem .6rem;
}
.area-list li {
  border: 1px solid var(--line); border-radius: 999px;
  padding: .35rem .9rem; font-size: .92rem; color: var(--muted);
}

/* The home page's pointer at the town pages. Sized as real tap targets, since
   on a phone these are the main route into the local content. */

/* ---- touch tap targets ------------------------------------------------- */
/* Measured with bvmobile.mjs across all 90 pages at 390px and 320px. Layout
 * was already clean — zero sideways scroll, zero overflowing elements — but
 * every page shipped 14 footer links at 18px tall, well under the 44px floor,
 * plus form option labels at 24px and the contact links at 27px.
 *
 * Keyed on `pointer: coarse` rather than a width, because this is about
 * fingers, not screens: a narrow desktop window does not need 44px rows and a
 * large tablet does. The footer lists drop their `gap` to compensate, so the
 * padding absorbs the spacing instead of adding to it and the footer grows by
 * far less than 14 x 26px would suggest. */
@media (pointer: coarse) {
  /* Full-row targets: the whole line is tappable, not just the glyphs. */
  .foot-col ul { gap: 0; }
  .foot-col a,
  .foot-contact a {
    display: flex; align-items: center; min-height: 44px;
  }
  .foot-contact a { display: inline-flex; }

  .foot-links { gap: .5rem 1.5rem; flex-wrap: wrap; }
  .foot-links a { min-height: 44px; }

  /* The input stays its native 24px; the LABEL is the hit area, which is why
     the control is wrapped in <label class="opt"> to begin with — tapping
     anywhere on the row toggles it. Keep the two-column grid (24px + text);
     only centre it and give it height, since switching to flex here would
     collapse the checkbox/label alignment. */
  .opt { align-items: center; min-height: 44px; }
  .opt input { margin-top: 0; }   /* the nudge is for top-alignment, not centred */

  /* Phone and email on the contact page. 1.45rem text, but only 27px of box. */
  .contact-big { display: inline-flex; align-items: center; min-height: 44px; }

  /* The contact page's "what do you need" list and the on-this-page jump nav
     on the 10DLC articles. Both are pure link lists, so the same rule applies:
     drop the gap, let the padding make the row. */
  .contact-links { gap: 0; }
  .contact-links a,
  .article-side ul a { display: flex; align-items: center; min-height: 44px; }
  .article-side ul { gap: 0; }
}
