/* ==========================================================================
   JUMP — build additions
   ==========================================================================

   Loaded AFTER jump.css. Kept in a separate file on purpose: jump.css is
   synced verbatim from the static source, so anything appended to it is lost
   on the next re-sync. Rules that belong to this build live here instead.

   ── Feed Schema Preview modal ──────────────────────────────────────────────

   marketplace's JS has always done:

       showCatalogSample() -> sample-modal.classList.add('open')
       closeSample()       -> sample-modal.classList.remove('open')

   ...but `.sample-modal`, `.modal-box`, `.modal-head`, `.modal-body` and
   `.modal-foot` are defined in no stylesheet anywhere in the source tree.
   With no rules at all, the modal had no `display:none` and no positioning,
   so it rendered inline as a visible full-width block partway down the page —
   an unstyled "Feed Schema Preview" bar with Close / Request Feed Access
   buttons, above the closing CTA.

   This is a pre-existing bug: the original static marketplace.html renders it
   exactly the same way (verified — display:block, position:static, 110px
   tall). The port reproduced it faithfully rather than introducing it.

   Styling follows the existing design language: 3px ink borders, 6px hard
   shadow, cream/sheet surfaces, amber and orange accents.
   ========================================================================== */

.sample-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 900;
  padding: 24px;
  background: rgba(22, 24, 28, 0.62);
  overflow-y: auto;
}

.sample-modal.open {
  display: grid;
  place-items: center;
}

.sample-modal .modal-box {
  width: min(680px, 100%);
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  overflow: hidden;
}

.sample-modal .modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  background: var(--amber);
  border-bottom: 3px solid var(--ink);
  flex: 0 0 auto;
}

.sample-modal .modal-body {
  padding: 18px;
  overflow-y: auto;
  flex: 1 1 auto;
}

.sample-modal .modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 18px;
  background: var(--tint);
  border-top: 3px solid var(--ink);
  flex: 0 0 auto;
}

/* The JSON sample is preformatted and frequently wider than the dialog. */
.sample-modal .modal-body pre {
  white-space: pre;
  overflow-x: auto;
}

@media (max-width: 560px) {
  .sample-modal { padding: 12px; }
  .sample-modal .modal-box { max-height: calc(100vh - 24px); }
  .sample-modal .modal-foot { flex-wrap: wrap; }
}

/* An open dialog should not scroll the page behind it. Set by the page
   script; harmless if that class is never applied. */
body.modal-open { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  .sample-modal.open .modal-box {
    animation: jumpModalIn 0.16s ease-out;
  }
  @keyframes jumpModalIn {
    from { transform: translateY(8px); opacity: 0; }
    to   { transform: none; opacity: 1; }
  }
}

/* ==========================================================================
   Pricing — rate table and capacity panel
   ==========================================================================

   Same pre-existing gap as the modal above: pricing.html uses 16 classes that
   are defined in no stylesheet anywhere in the source tree, so both the
   "Transparent Flat Monthly Plans" table and the "How Your Capacity Works"
   panel rendered as unstyled stacked text. Verified against the original
   static build — .rate-table-card, .rate-modern, .capacity-panel and friends
   all compute to 0 border and 0 padding there too.

   Follows the existing design language: 3px ink borders, 6px hard shadow,
   amber headers, tint banded rows.
   ========================================================================== */

/* ── Rate table ─────────────────────────────────────────────────────────── */

/* Five columns will not fit a narrow viewport, so the CARD scrolls
   horizontally. The table itself must stay `display: table` — setting it to
   block to get a scrollbar is a common shortcut that silently destroys column
   alignment, because the cells stop participating in table layout. */
.rate-table-card {
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  overflow-x: auto;
  overflow-y: hidden;
}

.rate-modern {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  font-family: var(--sans);
}

/* Amber header bar with ink text, matching the calculator card's head. */
.rate-modern thead th {
  background: var(--amber);
  border-bottom: 3px solid var(--ink);
  padding: 12px 16px;
  text-align: left;
  font: 800 12px/1.2 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}

/* Soft ink for the per-row rules — full-strength ink on every row reads as a
   grid rather than separators. */
.rate-modern tbody td {
  padding: 14px 16px;
  border-bottom: 1px solid rgba(22, 24, 28, .16);
  vertical-align: middle;
  font-size: 14px;
  color: var(--ink-2);
}

.rate-modern tbody tr:last-child td { border-bottom: 0; }

.rate-modern tbody tr:not(.section-banner):hover { background: rgba(240, 180, 41, .07); }

/* Group headers spanning the full width ("Cloud Lanes", "Private Hardware") */
.rate-modern .section-banner td {
  background: var(--ink);
  color: var(--cream);
  font: 800 12.5px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 10px 16px;
  border-bottom: 3px solid var(--ink);
}

/* ── The highlighted "Popular" tier (Core) ───────────────────────────────
   Colours sampled from the calculator card's savings banner (#calc-save) and
   its "Unmetered" badge, so the recommended row reads as part of the same
   system rather than a new green.

     #D1FAE5  mint          — savings banner background
     #065F46  dark emerald  — savings banner text
     #059669  emerald       — savings banner border
     #10B981  emerald       — "Unmetered" badge

   #065F46 on #D1FAE5 is the pairing already in use on that card, and clears
   AA comfortably at both the price size and the smaller "/ month". */
.rate-modern tbody tr.featured { background: #D1FAE5; }
.rate-modern tbody tr.featured:hover { background: #BFF5DC; }

/* The two price cells on that row carry style="color:var(--orange)" inline in
   the markup. Inline styles outrank every selector, and the markup is synced
   verbatim from the static source — editing it would be silently reverted by
   the next re-sync, taking the green with it. !important in this build's own
   stylesheet is the one thing that survives both. */
.rate-modern tbody tr.featured td.price-cell {
  color: #065F46 !important;
}
.rate-modern tbody tr.featured td.price-cell span {
  color: #047857 !important;
}

.plan-name {
  font-size: 15px;
  color: var(--ink);
  margin-bottom: 2px;
}
.plan-name b { font-weight: 800; }

.pop-badge {
  display: inline-block;
  margin-left: 6px;
  background: var(--amber);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 2px 6px;
  font: 800 10px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  vertical-align: middle;
}

/* Matched to `.rate-modern tbody td` specificity (0,1,2) — a bare
   `.price-cell` (0,1,0) loses its font-size to that rule. */
.rate-modern tbody td.price-cell {
  font: 800 16px/1.2 var(--sans);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.rate-modern tbody td.price-cell span {
  font: 500 12px/1 var(--sans);
  color: var(--muted);
  margin-left: 2px;
}

/* Amber footer bar, bookending the amber header. Text goes ink rather than
   --muted: the muted grey is tuned for the cream/white surfaces and drops
   below AA on amber. */
.rate-table-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px;
  background: var(--amber);
  border-top: 3px solid var(--ink);
}

.rate-table-foot-info { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.rate-table-foot-info b { font: 800 14.5px/1.2 var(--sans); color: var(--ink); }
.rate-table-foot-info span { font-size: 13px; color: var(--ink-2); }

.rate-table-acts { display: flex; flex-wrap: wrap; gap: 10px; }

/* ── Capacity panel ─────────────────────────────────────────────────────── */

.capacity-panel {
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  overflow: hidden;
}

.capacity-panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  background: var(--amber);
  border-bottom: 3px solid var(--ink);
}
.capacity-panel-head h3 {
  margin: 0;
  font: 800 17px/1.2 var(--sans);
  color: var(--ink);
}

.capacity-panel-pill {
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 4px 9px;
  font: 800 10.5px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}

/* Four specification cells. */
.capacity-panel-body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
}

.capacity-cell {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 18px;
  border-right: 1px solid var(--rowline);
  border-bottom: 1px solid var(--rowline);
}
/* Right column and bottom row lose their outer edges. */
.capacity-cell:nth-child(2n) { border-right: 0; }
.capacity-cell:nth-last-child(-n+2) { border-bottom: 0; }

/* Ink on amber rather than amber text: #F0B429 on white is ~1.9:1, well under
   the 4.5:1 needed for text this size, and the -webkit-text-stroke trick that
   would have masked it only works in WebKit. A chip matches .pop-badge and
   .tag elsewhere in the system and stays legible everywhere. */
.capacity-num {
  flex: 0 0 auto;
  display: inline-block;
  min-width: 30px;
  text-align: center;
  background: var(--amber);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 4px 6px;
  font: 800 13px/1 var(--data);
  color: var(--ink);
  letter-spacing: .02em;
}

.capacity-cell-content { min-width: 0; }
.capacity-cell-content h4 {
  margin: 0 0 6px;
  font: 800 15px/1.25 var(--sans);
  color: var(--ink);
}
.capacity-cell-content p {
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted);
}

.capacity-cell-tag {
  display: inline-block;
  background: var(--tint);
  border: 1.5px solid var(--rule);
  border-radius: 4px;
  padding: 3px 8px;
  font: 700 11px/1 var(--data);
  letter-spacing: .04em;
  color: var(--ink-2);
}

@media (max-width: 720px) {
  .capacity-panel-body { grid-template-columns: minmax(0, 1fr); }
  .capacity-cell { border-right: 0; }
  .capacity-cell:nth-last-child(-n+2) { border-bottom: 1px solid var(--rowline); }
  .capacity-cell:last-child { border-bottom: 0; }
  .rate-table-foot { flex-direction: column; align-items: stretch; }
}

/* ==========================================================================
   Cloud page — lane card, JUMP vs metered comparison, onboarding steps
   ==========================================================================

   44 classes used by cloud.html are defined in no file anywhere in the source
   tree — not jump.css, not jump-redesign.css, not the app shells, not
   _legacy. The whole page rendered as unstyled stacked text.

   No prior design exists to copy, so these follow the same language as the
   rest of the site: 3px ink borders, 6px hard shadow, amber headers, and the
   mint / warm pairing already established on the pricing calculator.
   ========================================================================== */

/* -- Lane card ----------------------------------------------------------- */

.lane-card {
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  overflow: hidden;
}

.lane-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 20px;
  background: var(--amber);
  border-bottom: 3px solid var(--ink);
}

.lane-pill {
  display: inline-block;
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 4px 9px;
  margin-bottom: 8px;
  font: 800 10.5px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
}

.lane-card-title {
  margin: 0;
  font: 800 clamp(20px, 2.4vw, 26px)/1.15 var(--sans);
  color: var(--ink);
}

.lane-price-tag { text-align: right; }
.lane-price-tag .p-sub {
  display: block;
  font: 700 11px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 4px;
}
.lane-price-tag .p-val {
  display: block;
  font: 800 30px/1 var(--sans);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.lane-price-tag .p-val small {
  font: 700 13px/1 var(--sans);
  color: var(--ink-2);
}

.lane-card-body { padding: 4px 20px; }

.lane-feature {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 18px 0;
  border-bottom: 1px solid rgba(22, 24, 28, .16);
}
.lane-feature:last-child { border-bottom: 0; }

.lane-num {
  flex: 0 0 auto;
  min-width: 34px;
  text-align: center;
  background: var(--ink);
  color: var(--cream);
  border-radius: 4px;
  padding: 6px 7px;
  font: 800 13px/1 var(--data);
}

.lane-text { min-width: 0; }

.lane-feature-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}
.lane-feature-head b { font: 800 15.5px/1.25 var(--sans); color: var(--ink); }
.lane-text p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--muted); }

.lane-badge {
  display: inline-block;
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 3px 7px;
  font: 800 10px/1 var(--data);
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.lane-badge.green { background: var(--live); color: var(--ink); }
.lane-badge.teal  { background: var(--good); color: #FFFFFF; }
.lane-badge.amber { background: var(--amber); color: var(--ink); }

.lane-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px;
  background: var(--tint);
  border-top: 3px solid var(--ink);
}
.lane-live-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 700 12.5px/1 var(--data);
  color: var(--ink-2);
}
.lane-foot-link {
  font: 800 13px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid var(--amber);
}
.lane-foot-link:hover { border-bottom-color: var(--ink); }

/* -- JUMP vs metered comparison ------------------------------------------ */

.lane-vs-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  align-items: start;
}

.comp-col {
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  overflow: hidden;
}

.comp-header {
  padding: 18px 20px;
  border-bottom: 3px solid var(--ink);
}

/* Mint for the JUMP column, a warm wash for the metered one — the same
   positive / negative pairing the pricing calculator uses. */
.jump-col .comp-header    { background: #D1FAE5; }
.metered-col .comp-header { background: #FBE4DC; }

.comp-tag {
  display: inline-block;
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 4px 9px;
  margin-bottom: 10px;
  font: 800 10.5px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.jump-tag    { background: var(--live); color: var(--ink); }
.metered-tag { background: var(--orange); color: var(--ink); }

.comp-title {
  margin: 0 0 8px;
  font: 800 19px/1.2 var(--sans);
  color: var(--ink);
}

.comp-price {
  font: 800 17px/1.2 var(--sans);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.comp-price span {
  font: 500 12.5px/1 var(--sans);
  color: var(--ink-2);
  margin-left: 4px;
}

.comp-list { padding: 6px 20px 18px; }

.comp-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid rgba(22, 24, 28, .12);
}
.comp-item:last-child { border-bottom: 0; }
.comp-item b { display: block; font: 800 14.5px/1.3 var(--sans); color: var(--ink); margin-bottom: 4px; }
.comp-item p { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--muted); }

.comp-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 2px solid var(--ink);
  border-radius: 50%;
  font: 800 13px/1 var(--sans);
}
.comp-icon.ok  { background: var(--live); color: var(--ink); }
.comp-icon.bad { background: var(--orange); color: var(--ink); }

/* -- Onboarding steps ---------------------------------------------------- */

.onboarding-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
  align-items: stretch;
}

.onboard-card {
  display: flex;
  flex-direction: column;
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
  padding: 20px;
}

.onboard-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.onboard-num {
  font: 800 11px/1 var(--data);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.onboard-badge {
  display: inline-block;
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 3px 8px;
  font: 800 10px/1 var(--data);
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.onboard-badge.instant { background: var(--amber); color: var(--ink); }
.onboard-badge.time    { background: var(--tint);  color: var(--ink); }

/* White on this green measures 2.54:1 and fails AA — my own mistake when this
   block was written. Ink on the same green is 7.01:1 and keeps the colour. */
.onboard-badge.live    { background: var(--live); color: var(--ink); }

.onboard-title {
  margin: 0 0 8px;
  font: 800 17px/1.25 var(--sans);
  color: var(--ink);
}

.onboard-desc {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  flex: 1 1 auto;
}

.onboard-foot {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(22, 24, 28, .16);
  font: 700 12px/1.4 var(--data);
  color: var(--ink-2);
}

@media (max-width: 900px) {
  .onboarding-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .lane-vs-grid,
  .onboarding-grid { grid-template-columns: minmax(0, 1fr); }
  .lane-card-head { flex-direction: column; align-items: flex-start; }
  .lane-price-tag { text-align: left; }
}

/* ==========================================================================
   REVERTED: "remaining 32" batch
   ==========================================================================

   A batch of styles for build-vs-buy, security, vs-metered, reseller,
   careers, privacy, terms, index and marketplace's figure list once lived
   here. It was removed.

   Those classes had no CSS, but unlike the modal, the pricing table and the
   cloud page, the sections they belonged to still rendered readably — plain,
   but not broken. Styling them changed pages that were working, so the port
   no longer matched the original. vs-metered was the clearest case: .stats,
   .stat, .ctl and .verdict turned four plain text blocks into cards and grew
   the page from 2333px to 2632px.

   The rule this file now follows: style a class only where the original is
   genuinely unusable, not merely unstyled.

   The removed block is kept at
   scratchpad/jump-additions.with-batch.css if any of it is wanted back.
   ========================================================================== */

/* ==========================================================================
   Contact form — server-rendered states

   These two classes have no counterpart in the static build because the
   static build never submitted anything: handleFormSubmit() faked success
   in JavaScript. A real POST needs a honeypot that is actually hidden and
   a way to show validation errors, so both are new here rather than ported.

   .hp-field must stay off-screen rather than display:none — a bot that skips
   display:none inputs would walk past the trap.
   ========================================================================== */

.hp-field {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-alert {
  margin: 20px 24px 0;
  padding: 12px 16px;
  border: 3px solid #B91C1C;
  border-radius: 8px;
  background: #FEE2E2;
  box-shadow: 4px 4px 0 0 #B91C1C;
  font: 500 14px/1.5 var(--sans);
  color: #7F1D1D;
}
.form-alert b {
  font-weight: 800;
}

/* ==========================================================================
   vs-metered — the volume calculator's controls

   .ctl was never given a rule in the static build, so the volume box and the
   preset radios rendered as raw browser widgets in the middle of an otherwise
   styled page. This is the one piece of the reverted "remaining 32" batch that
   is worth keeping: it dresses the two controls to match .form-input without
   touching .stats, .stat or .verdict, which is what previously changed the
   page's structure and grew it by ~300px.

   Scoped deliberately. .chips also appears on index and designer, but only
   ever wrapping <span>s there — no input or label — so neither selector below
   can reach those pages.
   ========================================================================== */

.ctl {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.ctl .lbl {
  font: 800 11px/1 var(--data);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.ctl input[type="text"] {
  width: 180px;
  padding: 9px 12px;
  font: 700 14px/1.2 var(--data);
  color: var(--ink);
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 6px;
  box-shadow: 2px 2px 0 0 var(--ink);
  outline: none;
}
.ctl input[type="text"]:focus {
  border-color: var(--amber);
  box-shadow: 2px 2px 0 0 var(--amber);
}

.chips label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--sheet);
  cursor: pointer;
  user-select: none;
}
.chips label:hover {
  background: var(--tint);
}
.chips label:has(input:checked) {
  background: var(--amber);
  box-shadow: 2px 2px 0 0 var(--ink);
}

/* ==========================================================================
   COMPARISON PAGES — design review fixes
   Applies to /build-vs-buy and /vs-metered. Every class touched below is used
   ONLY on those two pages (verified across all 14 templates), so nothing here
   can reach the other twelve.

   These are deliberate departures from the static build, not port drift: the
   original renders this way too. They were requested after review.
   ========================================================================== */

/* --- Danger token ---------------------------------------------------------
   :root defined --good and --warn but no danger colour, so "bad" had nowhere
   to live and got hardcoded inline as #ef4444 — which measures 3.76:1 on white
   and fails AA. Sized the same way --warn was: #B91C1C is 6.47:1 on white and
   5.66:1 on its own tint.
   ------------------------------------------------------------------------- */
:root {
  --bad:      #B91C1C;
  --bad-bg:   #FDECEC;
  --warn-bg:  #FBF1DE;
  --good-bg:  #E2F1EE;
  --good-row: #DFF3E4;
}

/* --- The three-state comparison badges ------------------------------------
   badge-burden / badge-partial / badge-handled existed in the markup with no
   rule anywhere, so all 21 rendered as identical grey body text and the only
   difference between "handled at a 75x markup" and "included" was the glyph.
   One shape, three states; currentColor keeps each state a one-token change.
   ------------------------------------------------------------------------- */
.badge-burden,
.badge-partial,
.badge-handled {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 2px solid currentColor;
  border-radius: 999px;
  font: 800 11px/1 var(--data);
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-burden  { color: var(--bad);  background: var(--bad-bg); }
.badge-partial { color: var(--warn); background: var(--warn-bg); }
.badge-handled { color: var(--good); background: var(--good-bg); }

/* The verdict and its qualifier were meant to stack. .sub had no rule, so it
   stayed inline and every cell read as a run-on: "x Your Burden Ongoing dev". */
.sub {
  display: block;
  margin-top: 7px;
  font: 500 12.5px/1.45 var(--sans);
  color: var(--muted);
}
table.grid .sub { text-wrap: balance; }

/* --- Claim the JUMP column ------------------------------------------------
   .grid.own carried an "own" modifier with no rule: all four columns got
   identical treatment, so nothing marked which one was the answer. Kept
   restrained — the column already wins on content in all seven rows, and a
   heavy highlight on top of a 7-0 result reads as strident.
   ------------------------------------------------------------------------- */
table.grid.own th:last-child,
table.grid.own td:last-child {
  border-left: 2.67px solid var(--ink);
}
table.grid.own td:last-child {
  background: #FBFAF6;
}
table.grid.own td:last-child .sub {
  color: var(--good);
  font-weight: 600;
}

/* --- Card interiors -------------------------------------------------------
   .chart and its figcaption both had padding 0, so the heading, the intro and
   the table all sat 3px from the card edge — the border width and nothing
   more, against a 2.67px frame with a 6px hard shadow.
   ------------------------------------------------------------------------- */
.chart > figcaption {
  padding: clamp(16px, 2vw, 24px);
  border-bottom: 2.67px solid var(--ink);
}
.chart > figcaption h2 {
  font: 800 clamp(17px, 1.6vw, 20px)/1.25 var(--sans);
  letter-spacing: -.01em;
}
.chart > figcaption p {
  margin: 7px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 74ch;
}

/* --- Headline figures -----------------------------------------------------
   .stats/.stat had no rule, so each page's three key numbers rendered as
   plain full-width lines at 138 characters, and .stat.hot — the 76x
   multiplier, and the "0 of 7" that is the whole build-vs-buy argument — was
   visually identical to the other two. Three columns also brings the measure
   back to roughly 45 characters.
   ------------------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 860px) {
  .stats { grid-template-columns: 1fr; }
}
.stat {
  padding: 14px 16px;
  background: var(--sheet);
  border: 2.67px solid var(--ink);
  border-radius: 10px;
}
.stat > b {
  display: block;
  font: 800 clamp(22px, 2.4vw, 30px)/1 var(--sans);
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.stat > span {
  display: block;
  margin-top: 7px;
  font: 500 13px/1.45 var(--sans);
  color: var(--muted);
}
.stat.hot {
  background: var(--amber);
  box-shadow: 4px 4px 0 0 var(--ink);
}
.stat.hot > span { color: #3A2E08; }

/* --- The calculator verdict -----------------------------------------------
   A visitor types a number and gets one sentence back. It is the reason the
   page exists and it was set at 16px body, indistinguishable from the
   paragraph above it.
   ------------------------------------------------------------------------- */
.verdict {
  margin: 0 0 18px;
  font: 500 clamp(16px, 1.7vw, 19px)/1.45 var(--sans);
  color: var(--ink-2);
  max-width: 62ch;
  text-wrap: balance;
}
.verdict b {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.verdict .was { color: var(--bad); }
.verdict .now { color: var(--good); }

/* --- Comparison table rows ------------------------------------------------
   These were inline styles duplicated in the markup AND in the template
   literal that vs-metered.script.js uses to rebuild #v-tbody. Any CSS fix was
   overridden the moment a visitor touched the calculator. Now classes, so the
   rule lives in one place.

   Specificity matters here: jump.css sets `table.grid td` (0,1,2) and
   `table.grid td b` (0,1,3), both of which outrank a bare `td.price-bad`
   (0,1,1). The price cells must be qualified by the table, and the <b> inside
   the JUMP price named explicitly, or the colour silently reverts to ink.
   ------------------------------------------------------------------------- */
tr.row-jump  { background: var(--good-row); }
tr.row-rival { color: var(--muted); }

table.grid td.price-good,
table.grid td.price-good b { color: var(--good); }

table.grid td.price-bad,
table.grid td.price-bad b  { color: var(--bad); }

/* ==========================================================================
   RESELLER PAGE — design review fixes

   .split, .evgrid and .evcard are used only on /reseller; .tag-live and
   .tag-dark are new. Nothing here reaches another page.

   Context worth keeping: the margin-economics section immediately above these
   was built entirely with inline styles — 13 style attributes reimplementing
   the border, radius and shadow that .bx already provides — while the two
   sections after it were given class names and no CSS at all. That asymmetry
   is what this block closes.
   ========================================================================== */

/* The success/live green was used in three places and tokenised in none. It
   is the same value as the hero's .dot-live, so it earns a name. */
:root { --live: #10B981; }

/* --- Tag modifiers --------------------------------------------------------
   These replace inline overrides on .tag. The reseller "reseller revenue" tag
   set white on --live, which measures 2.54:1 and fails AA badly. Ink on the
   same green is 7.01:1 and keeps the colour.
   ------------------------------------------------------------------------- */
.tag-live { background: var(--live); color: var(--ink); }
.tag-dark { background: var(--ink);  color: var(--sheet); }

/* --- Margin economics -----------------------------------------------------
   Same rendering as the inline version it replaces, expressed once.
   ------------------------------------------------------------------------- */
.margin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}
.margin-card {
  background: var(--sheet);
  border: 2.5px solid var(--ink);
  border-radius: 8px;
  padding: 22px;
  box-shadow: 4px 4px 0 0 var(--ink);
}
.margin-card h3 {
  margin: 12px 0 6px;
  font: 800 24px/1.2 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.margin-card p {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--muted);
}
.margin-card.is-revenue {
  border-color: var(--live);
  box-shadow: 4px 4px 0 0 var(--live);
}
.margin-card.is-revenue h3 { color: var(--good); }
.margin-card.is-profit    { background: var(--amber); }
.margin-card.is-profit p  { color: var(--ink); }

/* --- Clean division of responsibility -------------------------------------
   The section is titled "Clean division of responsibility" and .split had no
   rule, so the two halves stacked full-width and divided nothing. Two columns
   also brings the list items from 101 characters down to about 70.
   ------------------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 26px);
}
@media (max-width: 820px) {
  .split { grid-template-columns: 1fr; }
}
.split > div {
  background: var(--sheet);
  border: 2.67px solid var(--ink);
  border-radius: 10px;
  padding: clamp(18px, 2vw, 26px);
}
.split > div > h3 {
  margin: 10px 0 0;
  font: 800 19px/1.25 var(--sans);
  color: var(--ink);
}
.split > div > p {
  margin: 8px 0 0;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--muted);
}
.split > div > ul {
  display: grid;
  gap: 9px;
  margin: 15px 0 0;
  padding-left: 18px;
  font: 500 14px/1.5 var(--sans);
  color: var(--ink-2);
}
.split > div > ul > li::marker { color: var(--faint); }
.split > div > ul b { color: var(--ink); }

/* --- Automated billing events ---------------------------------------------
   .evgrid and .evcard both had no rule: four "cards" rendered as flat
   1100x25 lines with the event name and its description running together.
   The names are webhook identifiers, so they take --data like every other
   identifier on the site rather than the body sans they inherited.
   ------------------------------------------------------------------------- */
.evgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px;
}
.evcard {
  background: var(--sheet);
  border: 2.67px solid var(--ink);
  border-radius: 10px;
  padding: 16px 18px;
}
.evcard > b {
  display: block;
  font: 700 13px/1.2 var(--data);
  letter-spacing: .01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.evcard > span {
  display: block;
  margin-top: 9px;
  font: 500 13px/1.5 var(--sans);
  color: var(--muted);
}

/* ==========================================================================
   REMAINING PAGES — design review fixes

   A site-wide audit of class tokens against both stylesheets found 15 classes
   with no rule anywhere. Most were harmless (modifiers whose base class does
   the work, or elements already fully styled inline). The ones below were not.
   ========================================================================== */

/* --- .center: 24 cells that were never centred ----------------------------
   build-vs-buy marks its three verdict columns class="center" on every th and
   td. The class has no rule, so all 24 render left-aligned — the comparison
   columns read as ragged text instead of a scannable grid. Missed in the
   first pass over that page.
   ------------------------------------------------------------------------- */
table.grid th.center,
table.grid td.center { text-align: center; }

/* --- Security: the comparative telemetry bar ------------------------------
   Eight metric tiles across two blocks, built from .telemetry-metric with a
   .metric-label / .metric-val / .metric-sub triplet. None of the four classes
   had a rule, so all eight rendered as three plain stacked lines with no card,
   no scale, and no distinction between the label and the value it labels.
   ------------------------------------------------------------------------- */
.telemetry-metric {
  padding: 14px 16px;
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 8px;
}
.metric-label {
  font: 800 10px/1 var(--data);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
}
.metric-val {
  margin-top: 9px;
  font: 800 17px/1.2 var(--sans);
  letter-spacing: -.01em;
  color: var(--ink);
}
.metric-sub {
  margin-top: 6px;
  font: 500 12.5px/1.4 var(--sans);
  color: var(--muted);
}

/* The status dots in the architecture tabs were empty spans with no rule, so
   they occupied no space and showed nothing at all. */
.tab-indicator-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

/* A 990px paragraph at 13px runs to about 152 characters. Everything else on
   the page is already bounded; this one banner was not. */
.arch-banner-desc { max-width: 82ch; }

/* --- Callout boxes (privacy, terms) ---------------------------------------
   One per page, holding the registered contact address. With no rule it read
   as an ordinary paragraph, so the one block a reader goes looking for had
   nothing to distinguish it.
   ------------------------------------------------------------------------- */
.callout-box {
  padding: 16px 18px;
  background: var(--tint);
  border-left: 4px solid var(--ink);
  border-radius: 0 8px 8px 0;
}
.callout-box p:last-child { margin-bottom: 0; }

/* --- Careers redirect notice ----------------------------------------------
   The whole page sits in .redirect-card, which had no rule — so a page whose
   only job is to be a notice did not read as one.
   ------------------------------------------------------------------------- */
.redirect-card {
  max-width: 60ch;
  padding: clamp(22px, 3vw, 34px);
  background: var(--sheet);
  border: 3px solid var(--ink);
  border-radius: 12px;
  box-shadow: 6px 6px 0 0 var(--ink);
}

/* --- Cloud: the two-way comparison table ----------------------------------
   .grid.pair had no rule. Unlike build-vs-buy's .own table, both columns here
   are ADUMU products, so neither gets claimed — they get equal width and a
   dividing rule, which is what "pair" was asking for.
   ------------------------------------------------------------------------- */
table.grid.pair th:first-child,
table.grid.pair td:first-child { width: 26%; }

table.grid.pair th:nth-child(2),
table.grid.pair td:nth-child(2) { border-left: 2.67px solid var(--ink); }

/* docs' main architecture card was titled with an <h3> while the page had no
   <h2> at all until much later — the largest block carried the smallest
   heading. Promoted to <h2>; jump.css styles `.arch-card-head h3` only, so the
   same treatment has to be named for h2 or the promotion silently changes the
   type size. */
.arch-card-head h2 {
  font: 800 clamp(18px, 2.2vw, 22px)/1.2 var(--sans);
  color: var(--ink);
  margin: 0;
}

/* --- Two contrast failures a hex-pair audit could not see -----------------
   Both were found only by walking every text node against its RESOLVED
   background, because neither is a hardcoded pair in the markup — the colours
   come from the stylesheet and the ground comes from an ancestor.
   ------------------------------------------------------------------------- */

/* The palette has --amber for BACKGROUNDS, where ink on amber measures 9.53.
   It has no amber for TEXT on a light ground, so the homepage's 01/02/03 step
   numerals used --amber directly and landed at 1.86:1 on white — below even
   the 3.0 large-text threshold. This is the amber for text; #B87A0A holds the
   hue and clears 3.6 on white. */
:root { --amber-text: #B87A0A; }
.stealth-num { color: var(--amber-text); }

/* Legal page meta labels sat at #6C727A on the cream ground: 4.30, just under
   AA at 11px. --faint is the token for exactly this job and clears 5.39. */
.legal-meta-label { color: var(--faint); }

/* The contact form's primary submit button — the single most important control
   on the site — set cream on #059669, which measures 3.34:1 at 16px/800 and
   fails AA. --good is the same green family one step darker and clears 4.85.
   The rule lives in jump.css, which is synced from source, so the override
   belongs here. */
.submit-btn.purchase-style { background: var(--good); }
.submit-btn.purchase-style:hover { background: #0B5C56; }

/* ==========================================================================
   HEADING OUTLINE FIXES
   Three pages went H1 -> H3 with no H2 between. Each needed a different fix,
   and each was checked for tag-keyed styling first — jump.css styles some
   headings by TAG, so changing a tag can silently drop its type.
   ========================================================================== */

/* The site had no visually-hidden utility, so there was nowhere to put a
   heading that exists for the outline rather than the page. Security's
   compliance section is four framework cards with no title of its own; this
   gives it one without changing anything on screen.

   Not display:none — that hides it from screen readers too, which is the
   opposite of the point. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* marketplace's hero spec card is aria-hidden="true" — decorative, and already
   invisible to assistive tech. Its title was an <h3>, so it sat in the DOM
   outline as a skipped level while being unreachable to the readers that level
   was for. It is now a div; jump.css keys that card's title styling to the TAG
   (`.itemcard h3`), so the same treatment is carried here. */
.itemcard .spec-title {
  font-size: 18px;
  font-weight: 800;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}

/* Card titles nested under a section H2 were marked <h4>, skipping H3. Both
   rules below mirror jump.css's tag-keyed originals (`.cat h4`,
   `.arch-step-card h4`) so the promotion does not change how they look. */
.cat h3 {
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 3px;
}
.arch-step-card h3 {
  font: 800 15px/1.3 var(--sans);
  color: var(--ink);
  margin: 0 0 8px;
}

/* The three-meter note under both capacity estimators. Explains what the
   competitor range covers — difficulty dial, bandwidth, and the session cap
   that is a capability limit rather than a price. */
.calc-meters {
  margin: 14px 0 0;
  padding: 12px 14px;
  background: var(--tint);
  border-left: 3px solid var(--ink);
  border-radius: 0 6px 6px 0;
  font: 500 12.5px/1.55 var(--sans);
  color: var(--ink-2);
  max-width: none;
}
.calc-meters b { color: var(--ink); font-weight: 700; }

/* Shown under the vs-metered calculator when the sized plan is less than
   three-quarters full. Capacity is sold in whole slots and whole machines, so
   a volume landing mid-tier pays for headroom — which moves the per-1,000
   figure without the monthly price changing. Amber rather than red: it is a
   sizing note, not an error. */
.util-note {
  margin: 16px 0 0;
  padding: 13px 16px;
  background: var(--good-bg, #E2F1EE);
  border-left: 4px solid var(--good);
  border-radius: 0 7px 7px 0;
  font: 500 13.5px/1.55 var(--sans);
  color: var(--ink-2);
  max-width: 84ch;
}
.util-note b { color: var(--ink); font-weight: 800; }
.util-note[hidden] { display: none; }

/* Utilisation column. Under 100% is headroom the buyer already owns and can
   grow into without the price moving — so it reads as good news, not as a
   warning. A metered vendor's 100% is not the better number; it means every
   request they run is one they billed for. */
table.grid td .under { color: var(--good); }

/* ==========================================================================
   Estimator — volume entry, the cloud/dedicated choice, and the parity panel
   ========================================================================== */

/* Buyers arrive with a figure in mind, so the volume is typed as well as
   dragged. The field carries the same weight as the number it replaced. */
.calc-vol-entry { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.calc-vol-input {
  width: 11ch;
  padding: 2px 6px;
  font: 800 clamp(24px, 3vw, 32px)/1.1 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: var(--ink);
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 6px;
  box-shadow: 2px 2px 0 0 var(--ink);
}
.calc-vol-input:focus { outline: none; border-color: var(--amber); box-shadow: 2px 2px 0 0 var(--amber); }

/* --- Cloud or dedicated ---------------------------------------------------
   Shown only from Reserved upward, where both can carry the volume or where
   cloud has run out. Below that a dedicated instance is not a real option and
   showing one would be noise.
   ------------------------------------------------------------------------- */
.calc-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 16px;
}
.calc-choice[hidden] { display: none; }

.calc-opt {
  padding: 14px 16px;
  background: var(--sheet);
  border: 2px solid var(--rule);
  border-radius: 9px;
}
.calc-opt.is-pick { border-color: var(--ink); box-shadow: 3px 3px 0 0 var(--ink); }

.calc-opt-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.calc-opt-head b { font: 800 14.5px/1.25 var(--sans); color: var(--ink); }
.calc-opt-flag {
  font: 800 9.5px/1 var(--data);
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 4px 7px;
  border-radius: 999px;
  background: var(--good-bg);
  color: var(--good);
  box-shadow: inset 0 0 0 1px currentColor;
}
.calc-opt-price {
  margin-top: 9px;
  font: 800 24px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: var(--ink);
}
.calc-opt-price span { font: 700 12px/1 var(--data); color: var(--muted); }
.calc-opt-spec {
  margin-top: 7px;
  font: 700 11px/1.35 var(--data);
  color: var(--muted);
}
.calc-opt-why {
  margin: 9px 0 0;
  font: 500 12.5px/1.5 var(--sans);
  color: var(--ink-2);
  max-width: none;
}

.calc-choice-note {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  padding: 12px 14px;
  background: var(--good-bg);
  border-left: 4px solid var(--good);
  border-radius: 0 7px 7px 0;
  font: 500 12.5px/1.55 var(--sans);
  color: var(--ink-2);
  max-width: none;
}
.calc-choice-note b { color: var(--ink); font-weight: 800; }

/* --- What the price comparison cannot show --------------------------------
   A per-result API returns a page. The things it has no way to sell are not
   a number, so they sit outside the priced comparison rather than inside it.
   ------------------------------------------------------------------------- */
.calc-parity {
  padding: clamp(16px, 2vw, 22px);
  background: var(--tint);
  border-top: 3px solid var(--ink);
}
.calc-parity-head b {
  display: block;
  font: 800 15px/1.3 var(--sans);
  color: var(--ink);
}
.calc-parity-head span {
  display: block;
  margin-top: 4px;
  font: 500 13px/1.5 var(--sans);
  color: var(--muted);
  max-width: 74ch;
}
.calc-parity-list {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 12px;
}
.calc-parity-list li {
  padding: 11px 13px;
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 8px;
}
.calc-parity-list b {
  display: block;
  font: 800 13px/1.3 var(--sans);
  color: var(--ink);
}
.calc-parity-list span {
  display: block;
  margin-top: 5px;
  font: 500 12px/1.45 var(--sans);
  color: var(--muted);
}

/* ==========================================================================
   Estimator — readability pass

   The panels below replace prose that took a minute to read. The audience is
   not in this market and will not read a paragraph to find out whether a
   price is good, so each block is a scannable list with the number on the
   right. Nothing here adds a fact; it removes sentences.
   ========================================================================== */

/* The metered figure is a RANGE, and a buyer cannot tell where in it they
   land until the invoice arrives. Saying so is the point of the tile. */
.calc-rate-tile.is-variable em {
  font-style: normal;
  color: var(--warn);
  font-weight: 800;
}
.calc-rate-tile.is-variable small {
  display: block;
  margin-top: 6px;
  font: 500 11.5px/1.4 var(--sans);
  color: var(--muted);
}

/* --- Why their price moves ------------------------------------------------ */
.calc-meters {
  margin-top: 18px;
  padding: 14px 16px;
  background: var(--tint);
  border-left: 4px solid var(--ink);
  border-radius: 0 8px 8px 0;
}
.calc-meters-lead {
  display: block;
  font: 800 13.5px/1.3 var(--sans);
  color: var(--ink);
  margin-bottom: 10px;
}
.calc-meters-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.calc-meters-list li {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  align-items: baseline;
  gap: 8px;
  font: 500 13px/1.4 var(--sans);
  color: var(--ink-2);
}
.calc-meters-list b { font-weight: 700; color: var(--ink-2); }
.calc-meters-list i {
  font-style: normal;
  font: 700 12px/1.4 var(--data);
  color: var(--muted);
  text-align: right;
}
.m-x    { color: var(--bad);  font-weight: 800; }
.m-tick { color: var(--good); font-weight: 800; }
.calc-meters-list li.m-ok {
  margin-top: 3px;
  padding-top: 8px;
  border-top: 2px solid var(--rule);
}
.calc-meters-list li.m-ok b { color: var(--ink); font-weight: 800; }
.calc-meters-list li.m-ok i { color: var(--good); font-weight: 800; }

/* --- What they cannot do at any price ------------------------------------- */
.calc-parity {
  padding: clamp(16px, 2vw, 22px);
  background: var(--tint);
  border-top: 3px solid var(--ink);
}
.calc-parity-head {
  display: block;
  font: 800 14px/1.3 var(--sans);
  color: var(--ink);
  margin-bottom: 12px;
}
.calc-parity-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px 16px;
}
.calc-parity-list li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font: 600 13px/1.45 var(--sans);
  color: var(--ink-2);
}
.calc-parity-list li::before {
  content: "✓";
  color: var(--good);
  font-weight: 800;
  flex: 0 0 auto;
}
.calc-parity-foot {
  display: block;
  margin-top: 12px;
  font: 500 12.5px/1.5 var(--sans);
  color: var(--muted);
}

/* jump.css lays the right pane out with justify-content:space-between and no
   gap, so once the content grows tall enough to fill the column the meters
   block and the savings box butt straight into each other. A gap is a floor on
   the separation that space-between cannot eat. */
.calc-pane-right { gap: 22px; }

/* --good is sized against white and measures only 4.35 on the tint ground the
   meters panel sits on. This is the same green one step darker, and clears AA
   at 6.24 — used only where green sits on tint. */
:root { --good-on-tint: #0B5C56; }
.calc-meters-list .m-tick,
.calc-meters-list li.m-ok i { color: var(--good-on-tint); }

/* ==========================================================================
   Comparison figures — width, alignment and polish
   ========================================================================== */

/* <figure> carries a browser default of `margin: 1em 40px`, and nothing ever
   reset it — so every .chart sat 80px narrower than the estimator card beside
   it and 40px off its left edge. The sections own the vertical rhythm, so the
   margin goes entirely. */
.chart {
  margin: 0;
}

/* A right-aligned column under a left-aligned heading reads as two columns.
   jump.css right-aligns .num cells but leaves every th left, so the numbers
   and their labels never lined up. */
table.grid th.num { text-align: right; }

/* Quiet banding. Full-strength rules on every row read as a grid; a tint on
   alternate rows separates them without drawing a box around each one. */
table.grid tbody tr:nth-child(even):not(.row-jump):not(.row-rival) { background: rgba(22, 24, 28, .022); }
table.grid tbody tr.row-rival:nth-child(even) { background: rgba(22, 24, 28, .022); }
table.grid tbody tr:hover:not(.row-jump) { background: rgba(240, 180, 41, .10); }

/* The JUMP row is the answer the table exists to give, so it carries an ink
   edge as well as its tint — visible even when the eye is scanning numbers. */
table.grid tbody tr.row-jump > td:first-child {
  box-shadow: inset 3px 0 0 0 var(--ink);
}

/* Figures and the estimator now share a width, so they should share a frame. */
.chart.bx {
  border-width: 3px;
  box-shadow: 6px 6px 0 0 var(--ink);
}

/* --- The parity panel -----------------------------------------------------
   Six items in an auto-fit grid wrapped 5 + 1, which reads as a mistake. Three
   columns divide six evenly at every breakpoint that fits them, and the items
   stretch to a common height so the rows are level.
   ------------------------------------------------------------------------- */
.calc-parity-list {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  align-items: stretch;
}
@media (max-width: 780px) { .calc-parity-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .calc-parity-list { grid-template-columns: 1fr; } }

.calc-parity-list li {
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  background: var(--sheet);
  border: 2px solid var(--ink);
  border-radius: 8px;
  font-weight: 700;
  color: var(--ink);
}
/* The tick becomes a filled chip rather than a loose glyph, so the six line up
   on a common left edge no matter how long the label wraps. */
.calc-parity-list li::before {
  content: "✓";
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--good-bg);
  color: var(--good);
  font-size: 12px;
  font-weight: 800;
}

/* ==========================================================================
   build-vs-buy — "When you should build it yourself"

   The section where the vendor says when not to buy, which makes it the most
   persuasive thing on the page. It was a plain bulleted list inside a box and
   read as a footnote. Each condition now ends in a concrete alternative,
   because advice against yourself is only credible if it is actionable.
   ========================================================================== */

.honest {
  padding: 0;
  overflow: hidden;
}

.honest-head {
  padding: clamp(20px, 2.4vw, 28px);
  background: var(--tint);
  border-bottom: 3px solid var(--ink);
}
.honest-kick {
  display: inline-block;
  margin-bottom: 10px;
  padding: 5px 9px;
  background: var(--ink);
  color: var(--cream);
  font: 800 10px/1 var(--data);
  letter-spacing: .1em;
  text-transform: uppercase;
  border-radius: 4px;
}
.honest-head h2 {
  font: 800 clamp(20px, 2.2vw, 27px)/1.2 var(--sans);
  letter-spacing: -.015em;
  color: var(--ink);
  margin: 0;
}
.honest-head p {
  margin: 9px 0 0;
  font: 400 15px/1.6 var(--sans);
  color: var(--ink-2);
  max-width: 62ch;
}

.honest-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
}
@media (max-width: 820px) { .honest-grid { grid-template-columns: 1fr; } }

/* Dividers between the three rather than around each: they are one list of
   conditions, not three separate offers. */
.honest-card {
  display: flex;
  flex-direction: column;
  padding: clamp(18px, 2vw, 24px);
  border-right: 2px solid var(--rule);
}
.honest-card:last-child { border-right: 0; }
@media (max-width: 820px) {
  .honest-card { border-right: 0; border-bottom: 2px solid var(--rule); }
  .honest-card:last-child { border-bottom: 0; }
}

.honest-card > b {
  font: 800 16px/1.3 var(--sans);
  color: var(--ink);
}
.honest-card > p {
  margin: 8px 0 0;
  font: 400 14px/1.6 var(--sans);
  color: var(--muted);
  flex: 1 1 auto;
  max-width: none;
}

/* The alternative. Pushed to the foot of the card so the three line up even
   when the paragraphs above them differ in length. */
.honest-then {
  display: block;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 2px solid var(--rule);
  font: 600 13px/1.55 var(--sans);
  color: var(--ink-2);
}
.honest-then::before {
  content: "→ ";
  color: var(--good);
  font-weight: 800;
}
.honest-then a {
  color: var(--ink);
  font-weight: 700;
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
}

.honest-foot {
  margin: 0;
  padding: 16px clamp(20px, 2.4vw, 28px);
  background: var(--amber);
  border-top: 3px solid var(--ink);
  font: 500 15px/1.55 var(--sans);
  color: var(--ink);
  max-width: none;
}
.honest-foot b { font-weight: 800; }

/* The three "use this instead" rules sat 20px out of line with each other,
   because each card sized its own paragraph. Subgrid makes the three cards
   share row tracks, so title, body and footer line up across all of them and
   the dividers read as one rule rather than three.

   Wrapped in @supports: without subgrid the flex rules above still stack
   correctly, they just leave the ragged edge. */
@supports (grid-template-rows: subgrid) {
  .honest-grid { grid-template-rows: auto 1fr auto; }

  .honest-card {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 3;
  }
  @media (max-width: 820px) {
    .honest-card { grid-row: auto; grid-template-rows: none; }
  }
}

/* The four "after" telemetry tiles set jump.css's `.arch-telemetry-tile
   b.success` = #059669, which measures 3.77:1 on the white card. The tile text
   is 800 weight but clamps to 18px — just under the 18.66px large-text floor —
   so it needs 4.5, not 3.0. --good is teal and would split these tiles from
   their #DC2626 `.danger` siblings, so this stays in the emerald family and
   goes one step darker: #047857 clears 5.48 against the same white. */
.arch-telemetry-tile b.success { color: #047857; }

/* ==========================================================================
   designer — "How the Designer works"

   Was <ol class="steps">: five stacked rows 1100px wide whose copy stopped at
   644px, so 456px of every row was empty. The numbers were also printed twice
   — the CSS counter drew one in the amber square and each h3 began "1. " as
   well — which is what happens when a numbered component meets pre-numbered
   copy. The counter is now the only source, so the list renumbers itself.

   A spine joins the squares so the five read as one sequence rather than five
   cards, and the recovered width carries an actor chip. Two of the five rows
   need the reader; that is the answer to "how much of this is my job?", and
   it is the page's own thesis ("Say it once. It keeps running.") stated as a
   countable fact. .steps is used on no other page, so nothing else moves.

   Named .stepflow, not .flow: jump.css:156 already owns .flow for the node
   simulation (display:flex, align-items:flex-start). That rule has no markup
   left, so it looked free — but it still cascaded, and this panel inherited
   its flex-start, which shrank the list to 675px inside a 1100px box while
   the foot bar spanned the full width. Nothing in the spine geometry noticed:
   centering, joints and edges are all measured RELATIVE to the list, so they
   stay correct while the whole thing is squeezed. "Ladder" was the other
   candidate and is rejected too — it already means the pricing ladder here.
   ========================================================================== */

/* The spine has to line up with squares it cannot measure, so every dimension
   it depends on is declared once here and derived from. Written literally in
   each rule instead, the narrow breakpoint would have to restate the left and
   top offsets to match a 44px square — three places to change a padding, two
   of which fail silently by leaving the line a few pixels off the numbers.

   Declared on the panel, not on the list: the foot bar is the list's SIBLING,
   and custom properties inherit down rather than across. Held one level lower
   the foot would quietly fall back to its own literal, and its text would
   stop sharing an edge with the rows above it the moment either changed. */
.stepflow {
  --sf-y: clamp(15px, 1.7vw, 19px);   /* row padding, block */
  --sf-x: clamp(18px, 2.2vw, 26px);   /* row padding, inline */
  --sf-sq: 48px;                        /* the numbered square */
  padding: 0;
  overflow: hidden;
}

.stepflow-list {
  counter-reset: f;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stepflow-step {
  position: relative;
  display: grid;
  grid-template-columns: var(--sf-sq) minmax(0, 1fr) auto;
  align-items: start;
  gap: 0 18px;
  padding: var(--sf-y) var(--sf-x);
  border-bottom: 2px solid var(--rule);
}
.stepflow-step:last-child { border-bottom: 0; }

/* The two rows that need the reader carry the recessed ground as well as the
   amber chip. Colour alone would read on screen, but the tint survives a
   greyscale print and does not rely on the chip being noticed. */
.stepflow-step.is-you { background: var(--tint); }

.stepflow-step::before {
  counter-increment: f;
  content: counter(f);
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--sf-sq);
  height: var(--sf-sq);
  background: var(--amber);
  border: 3px solid var(--ink);
  border-radius: 8px;
  font: 800 19px/1 var(--sans);
  color: var(--ink);
}

/* The spine, drawn per row rather than once down the <ol>: only the row knows
   where its own square ends, and a single line on the list cannot find the
   last square to stop at. Reaches through this row's border and the next
   row's padding so it lands exactly on the following square. Hidden on the
   last step so the sequence terminates rather than trailing off. */
.stepflow-step::after {
  content: "";
  position: absolute;
  left: calc(var(--sf-x) + (var(--sf-sq) / 2) - 1.5px);
  top: calc(var(--sf-y) + var(--sf-sq) + 6px);
  bottom: calc(-2px - var(--sf-y));
  width: 3px;
  background: var(--rule);
}
.stepflow-step:last-child::after { display: none; }

.stepflow-body { grid-column: 2; grid-row: 1; }
.stepflow-body h3 {
  margin: 12px 0 5px;
  font: 800 clamp(16px, 1.7vw, 18px)/1.3 var(--sans);
  color: var(--ink);
}
.stepflow-body p {
  margin: 0;
  font: 400 15px/1.6 var(--sans);
  color: var(--muted);
  max-width: 60ch;
}

/* Sits on the optical line of the square and the title, so number, step and
   actor read as one row left to right. */
.stepflow-actor {
  grid-column: 3;
  grid-row: 1;
  align-self: start;
  justify-self: end;
  margin-top: 12px;
  padding: 6px 11px;
  background: var(--ink);
  color: var(--cream);
  border: 2px solid var(--ink);
  border-radius: 999px;
  font: 800 10px/1 var(--data);
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
}
.stepflow-step.is-you .stepflow-actor { background: var(--amber); color: var(--ink); }

.stepflow-foot {
  margin: 0;
  padding: 15px var(--sf-x);
  background: var(--amber);
  border-top: 3px solid var(--ink);
  font: 500 15px/1.55 var(--sans);
  color: var(--ink);
  max-width: none;
}
.stepflow-foot b { font-weight: 800; }

/* Narrow: the actor rail has nowhere to sit beside the copy, so it drops
   under it and left-aligns. Only the square shrinks — the spine follows it. */
@media (max-width: 720px) {
  .stepflow { --sf-sq: 44px; }
  .stepflow-step { grid-template-columns: var(--sf-sq) minmax(0, 1fr); }
  .stepflow-step::before { font-size: 17px; }
  .stepflow-body h3 { margin-top: 10px; }
  .stepflow-actor {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
    margin: 10px 0 0;
  }
}

/* ==========================================================================
   contact — aligning the intent tabs with the body partition

   The tabs merge into the panel below, the same move as .arch-tab-btn. That
   works there because .arch-visualizer-body is one uniform surface. Here the
   body is a two-column grid — white left pane, #F1F3F5 right pane, 3px ink
   divider — and the tabs were laid out by a SEPARATE flex container in the
   header. Two independent layout systems, so the strip only ever came close
   to the divider by coincidence: the active tab ran x 679-933 against a
   divider at 707, overlapping it by 28px. That put the tab's fill over both
   panes at once and dropped the divider out of its underside.

   The fix is to stop the two systems guessing at each other. The header now
   uses the SAME grid template as the body, so its column boundary IS the
   divider, and the toggle sits in the second column — over the right pane,
   whose #F1F3F5 is exactly what the active tab fills with. The active tab's
   3px ink left border then lands on the divider and reads as a continuation
   of it rather than a collision. Change one template and both must move.
   ========================================================================== */

.contact-card-head {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.95fr);
  align-items: end;
  gap: 0;
  /* No inline padding: the column boundary has to sit exactly where the body's
     divider does, and padding here would offset it. The title carries its own
     inset instead. */
  padding: 16px 0 0 0;
}
.contact-card-head h2 {
  padding: 0 24px;
  margin: 0 0 14px;
}

.intent-toggle {
  justify-self: start;
  align-self: end;
}

/* Below the body's own breakpoint the grid collapses to a single column and
   the pane under the tabs becomes the WHITE left pane, so the active tab has
   to fill white to keep merging into what is actually beneath it. */
@media (max-width: 960px) {
  .contact-card-head {
    grid-template-columns: minmax(0, 1fr);
  }
  .intent-toggle {
    padding-left: 24px;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .intent-toggle::-webkit-scrollbar { display: none; }
  .intent-btn.active,
  .intent-btn.active.purchase {
    background: var(--sheet);
    border-bottom-color: var(--sheet);
  }
}

@media (max-width: 560px) {
  .intent-btn { font-size: 12px; padding: 9px 12px 7px; }
  .intent-btn.active,
  .intent-btn.active.purchase { padding: 11px 13px 8px; }
}

.intent-btn:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: -6px;
}

/* ==========================================================================
   home — "Three ways to get your data"

   This sat directly under "Three Ways We Keep Your Data Flowing" and used the
   same component: a heading, then three white .bx cards carrying a tag, a
   title and a paragraph. Two identical-looking blocks in a row, so the second
   read as a repeat of the first and the eye skated over it — which is bad,
   because the first block explains and this one is where the visitor chooses
   something.

   So the two are made to differ in the way they actually differ. The block
   above is a SEQUENCE: three checks a site runs, numbered 01-03. This one is
   a CHOICE between three alternatives, so it carries no numbers — numbering
   here would have implied an order that does not exist — and it inverts onto
   the panel ground, which is the only dark surface in the page's run of cream
   sections. Each column is one <a>, so the whole door is the target rather
   than a text link at the bottom of a card.
   ========================================================================== */

.paths {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  background: var(--panel);
  padding: 0;
  overflow: hidden;
}
@media (max-width: 860px) {
  .paths { grid-template-columns: minmax(0, 1fr); }
}

.path {
  display: flex;
  flex-direction: column;
  padding: clamp(20px, 2.4vw, 28px);
  border-right: 3px solid var(--ink);
  text-decoration: none;
  color: #E8EAED;
  transition: background .15s ease;
}
.path:last-child { border-right: 0; }
@media (max-width: 860px) {
  .path { border-right: 0; border-bottom: 3px solid var(--ink); }
  .path:last-child { border-bottom: 0; }
}

.path:hover,
.path:focus-visible { background: #343C46; }
.path:focus-visible { outline: 3px solid var(--amber); outline-offset: -6px; }

.path-eyebrow {
  font: 800 10px/1 var(--data);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--amber);
}
.path-name {
  display: block;
  margin: 11px 0 8px;
  font: 800 clamp(19px, 2vw, 23px)/1.15 var(--sans);
  letter-spacing: -.02em;
  color: #F4F1EA;
}
.path p {
  margin: 0;
  font: 400 14.5px/1.6 var(--sans);
  color: #C9CDD3;
  /* Pushes every door's link to the foot of its column, so the three line up
     even though the paragraphs differ in length. */
  flex: 1 1 auto;
}

/* Amber, because this navigates rather than acts. Not .textlink: that class
   appends its own arrow via ::after, and three links here already carried a
   hardcoded &rarr; as well, which is why they rendered as "→ →". */
.path-go {
  align-self: flex-start;
  margin-top: 16px;
  padding-bottom: 3px;
  border-bottom: 3px solid var(--amber);
  font: 800 13.5px/1.2 var(--sans);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--amber);
}
.path-go::after { content: " \2192"; }
.path:hover .path-go { color: var(--amber-hi); border-bottom-color: var(--amber-hi); }
