/* MKT-01 — the public site.
 *
 * Its own stylesheet, not the app's: these pages must render before any
 * JavaScript and on a poor connection, and the app's tokens file carries a lot
 * they do not need.
 *
 * MKT-04 — it now looks like the product. The first version used system-ui and
 * flat panels, so somebody arriving from a search result and then signing in saw
 * two different products. The fonts were already self-hosted for the app at
 * /fonts/*.woff2 and served from the same origin; the site simply never asked
 * for them. Nothing new is downloaded that the app did not already ship.
 *
 * The typographic rule, which is easy to get wrong in the flattering direction:
 * Orbitron and Share Tech Mono are display faces. They are used for headings,
 * the brand, eyebrows, figures and labels — never for paragraphs. A marketing
 * page is mostly prose, and prose set in a monospace face looks correct in a
 * screenshot and is punishing to actually read.
 */

@font-face {
  font-family: 'Orbitron';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/orbitron.woff2') format('woff2');
}
@font-face {
  font-family: 'Share Tech Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/share-tech-mono.woff2') format('woff2');
}

:root {
  --bg: #050913;
  --bg-raised: #0b1220;
  --panel: rgba(11, 18, 32, 0.72);
  --line: rgba(16, 242, 178, 0.15);
  --line-strong: rgba(16, 242, 178, 0.35);
  --grid: rgba(16, 242, 178, 0.045);
  --text: #c8fff0;
  --bright: #ffffff;
  --dim: rgba(200, 255, 240, 0.68);
  --faint: rgba(200, 255, 240, 0.45);
  --accent: #10f2b2;
  --accent-glow: rgba(16, 242, 178, 0.45);
  --cyan: #4df3ff;
  --danger: #ff4d6d;
  --warn: #ffc857;
  --display: 'Orbitron', 'Segoe UI', system-ui, sans-serif;
  --mono: 'Share Tech Mono', 'Consolas', ui-monospace, monospace;
  --max: 1120px;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.7 system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  overflow-x: hidden;
}

/* The backdrop from the application, at the same weight. Fixed and inert:
   `contain: strict` keeps a repaint anywhere else on the page from dragging this
   full-viewport layer through with it. */
body::before {
  content: "";
  position: fixed; inset: 0;
  background-image:
    radial-gradient(circle at 12% 8%, rgba(16, 242, 178, 0.05), transparent 42%),
    radial-gradient(circle at 88% 78%, rgba(77, 243, 255, 0.045), transparent 45%),
    repeating-linear-gradient(0deg, var(--grid) 0 1px, transparent 1px 80px),
    repeating-linear-gradient(90deg, var(--grid) 0 1px, transparent 1px 80px);
  pointer-events: none;
  contain: strict;
  z-index: 0;
}
body > * { position: relative; z-index: 1; }

a { color: var(--accent); }
a:hover { color: var(--cyan); }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: var(--bg);
  padding: 12px 16px; z-index: 10;
}
.skip:focus { left: 0; top: 0; }

/* ---- header --------------------------------------------------------------- */
/* MKT-28 — the header stays on screen, and it sits above the page.
 *
 * Sticky, not because every site does it, but because the two things it holds
 * are the only two a visitor ever wants mid-page: the way to another page, and
 * the way to sign up. Scrolling back to the top to reach either is the tax this
 * removes.
 *
 * It needs its own background now. Transparent was fine while it sat at the top
 * of the document and nothing ever passed behind it; sticky, the hero text
 * scrolls straight through the letters. The blur is the same treatment the
 * bottom CTA bar already uses, so the two edges of the screen match.
 *
 * `position: sticky` rather than `fixed`: sticky keeps the header in the flow,
 * so nothing below it needs a margin the height of a box that changes height
 * between one row and two.
 */
.site-head {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  max-width: var(--max); margin: 0 auto; padding: 18px 20px;
  position: sticky;
  top: 0;
  z-index: 50;
}

/* The band behind it, full width.
 *
 * The header is centred at 1120px, so a background on the element itself stops
 * there and the page scrolls past in the gutters either side. Painted by a
 * pseudo-element that ignores the max-width instead — and as a pseudo-element
 * rather than a `clip-path` trick on the header, because clipping the header
 * would clip the menu panel that hangs below it, which is the one thing here
 * that has to escape the box. */
.site-head::before {
  content: '';
  position: absolute;
  inset: 0 50%;
  width: 100vw;
  transform: translateX(-50%);
  z-index: -1;
  background: rgba(5, 9, 19, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.brand {
  font-family: var(--display);
  font-weight: 700; letter-spacing: 3px; color: var(--accent);
  text-decoration: none; font-size: 17px;
  text-shadow: 0 0 18px var(--accent-glow);
}
.site-head nav { display: flex; gap: 20px; flex: 1 1 auto; flex-wrap: wrap; }
.site-head nav a {
  font-family: var(--mono); font-size: 14px; letter-spacing: 0.5px;
  color: var(--dim); text-decoration: none; text-transform: uppercase;
}
.site-head nav a:hover, .site-head nav a[aria-current] { color: var(--accent); }

/* ---- buttons -------------------------------------------------------------- */
.btn {
  display: inline-block; padding: 11px 22px; margin: 0 8px 8px 0;
  border: 1px solid var(--line-strong); border-radius: 4px;
  color: var(--text); text-decoration: none;
  font-family: var(--mono); font-size: 15px; letter-spacing: 1px; text-transform: uppercase;
  transition: border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.btn:hover { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 18px rgba(16, 242, 178, 0.18); }
.btn--primary {
  background: var(--accent); border-color: var(--accent); color: #04120d; font-weight: 700;
}
.btn--primary:hover { background: var(--cyan); border-color: var(--cyan); color: #04120d; }

/* ---- content -------------------------------------------------------------- */
main { max-width: var(--max); margin: 0 auto; padding: 0 20px 40px; }

h1, h2, h3 { font-family: var(--display); font-weight: 700; }

.hero { padding: 56px 0 28px; }
.hero h1 {
  margin: 0 0 18px;
  /* clamp rather than breakpoints: one rule covers a 320px phone and a wide
     monitor, and there is no size in between that gets it wrong. */
  font-size: clamp(1.7rem, 4.4vw, 2.9rem);
  line-height: 1.18; letter-spacing: 0.5px;
  color: var(--bright);
}
.lede { font-size: clamp(1.05rem, 2.2vw, 1.25rem); color: var(--dim); max-width: 60ch; }
.ctas { margin-top: 26px; }
.note { color: var(--faint); font-size: 0.9rem; font-family: var(--mono); }

/* The small uppercase line above a heading. Mono, because it reads as a label
   from the machine rather than as prose. */
.eyebrow {
  font-family: var(--mono); font-size: 13px; letter-spacing: 2px;
  text-transform: uppercase; color: var(--accent); margin: 0 0 10px;
}

.band { padding: 34px 0; border-top: 1px solid var(--line); }
.band h2 { margin: 0 0 14px; font-size: clamp(1.2rem, 2.6vw, 1.55rem); color: var(--bright); letter-spacing: 0.5px; }
.band p, .band ul { max-width: 68ch; color: var(--dim); }

.prose h2 { margin-top: 30px; font-size: 1.15rem; }

/* ---- panels with corner brackets ------------------------------------------ */
/* The bracket is the app's HUD idiom. Drawn with two gradients on a pseudo
   element rather than four absolutely positioned divs, so it costs no markup and
   cannot be half-applied by a page that forgets one corner. */
.panel {
  position: relative;
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: 4px;
  padding: 24px;
}
.panel::before, .panel::after {
  content: ""; position: absolute; width: 14px; height: 14px;
  border-color: var(--accent); border-style: solid; pointer-events: none;
}
.panel::before { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
.panel::after  { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; }

/* ---- feature grid --------------------------------------------------------- */
.grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin-top: 8px;
}
.grid .panel h3 {
  margin: 0 0 8px; font-size: 1rem; color: var(--accent);
  letter-spacing: 1px; display: flex; align-items: center; gap: 10px;
}
.grid .panel p { margin: 0; color: var(--dim); font-size: 0.95rem; max-width: none; }
.grid .panel svg { flex: none; }

/* A figure quoted as data: mono, accent, large. */
.stat { font-family: var(--mono); color: var(--accent); font-size: 1.6rem; line-height: 1; }

/* ---- the hero map --------------------------------------------------------- */
/* Inline SVG, no library and no script. Cytoscape is 546 KB and belongs to the
   application; this is a drawing of what the application shows, and a marketing
   page that ships a graph engine to render a picture has lost the argument it is
   making about weight. */
.mapwrap {
  margin: 30px 0 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background:
    linear-gradient(180deg, rgba(16, 242, 178, 0.04), transparent 55%),
    var(--bg-raised);
  padding: 10px;
  overflow: hidden;
}
.mapwrap svg { display: block; width: 100%; height: auto; }

.node-label { font-family: var(--mono); font-size: 11px; fill: var(--dim); }
.edge { stroke: rgba(16, 242, 178, 0.28); stroke-width: 1.4; fill: none; }
.edge--bad { stroke: rgba(255, 77, 109, 0.55); stroke-dasharray: 5 4; }
.node-ring { fill: none; stroke-width: 1.5; }
.node--up   { fill: #10f2b2; }
.node--warn { fill: #ffc857; }
.node--down { fill: #ff4d6d; }

/* A short dash travelling each link — the one thing a still picture of a network
   cannot say: that it is being measured right now.
   stroke-dashoffset rather than a motion path, because it animates in every
   browser that draws SVG at all and, with animation switched off, settles into a
   stationary dash instead of a dot parked at the origin. */
.flow {
  stroke: var(--cyan); stroke-width: 2; fill: none;
  stroke-linecap: round;
  stroke-dasharray: 10 260;
  animation: flow 3.4s linear infinite;
}
@keyframes flow { from { stroke-dashoffset: 270; } to { stroke-dashoffset: 0; } }
@keyframes breathe { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
.node--down { animation: breathe 1.7s ease-in-out infinite; }

/* Legend swatches. */
.key { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin: 0 5px 0 14px; vertical-align: middle; }
.key--up   { background: var(--accent); }
.key--warn { background: var(--warn); }
.key--down { background: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .flow { animation: none; stroke-dashoffset: 135; }
  .node--down { animation: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* ---- pricing -------------------------------------------------------------- */
.plans {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.plan { border: 1px solid var(--line); border-radius: 6px; padding: 24px; background: var(--bg-raised); }
.plan--featured { border-color: var(--line-strong); box-shadow: 0 0 30px rgba(16, 242, 178, 0.08); }
.plan h3 { margin: 0 0 6px; color: var(--accent); letter-spacing: 2px; font-size: 1rem; text-transform: uppercase; }
.plan__price { font-family: var(--display); font-size: 1.5rem; margin: 0 0 16px; color: var(--bright); }
.plan__price .tbd { font-size: 0.9rem; color: var(--faint); font-family: var(--mono); }
.plan ul { list-style: none; padding: 0; margin: 0 0 20px; }
.plan li { padding: 7px 0 7px 18px; border-bottom: 1px solid var(--line); font-size: 0.93rem; position: relative; color: var(--dim); }
.plan li::before { content: "▸"; position: absolute; left: 0; color: var(--accent); }
.plan li:last-child { border-bottom: 0; }
/* MKT-32 - what the plan does not have, said rather than left out. A row that
   is simply missing reads as an oversight; a dim row with a different marker is
   the reason the next column costs more. */
.plan li.plan__off { color: var(--faint); }
.plan li.plan__off::before { content: "·"; color: var(--faint); }
.plan li.plan__limit { color: var(--faint); font-style: italic; }
.plan li.plan__limit::before { content: "!"; color: var(--faint); }
.plan li strong { color: var(--text); font-weight: 600; }

/* ---- faq ------------------------------------------------------------------ */
.faq { max-width: 68ch; }
.faq dt { font-family: var(--mono); color: var(--text); margin-top: 22px; font-size: 1rem; }
.faq dd { margin: 6px 0 0; color: var(--dim); }

/* ---- breadcrumbs ---------------------------------------------------------- */
/* Marked up as a BreadcrumbList as well, which is what puts the trail in a
   search result instead of a bare URL. Visible and structured have to agree:
   markup describing a trail the page does not show is the kind of mismatch that
   gets structured data ignored site-wide. */
.crumbs {
  max-width: var(--max); margin: 0 auto; padding: 18px 20px 0;
  font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.04em;
  color: var(--faint);
}
.crumbs a { color: var(--faint); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: underline; }
.crumbs i { font-style: normal; margin: 0 8px; color: rgba(16, 242, 178, 0.35); }
.crumbs [aria-current] { color: var(--dim); }

/* ---- the sticky call to action -------------------------------------------- */
/* Phones only. On a narrow screen the header CTA scrolls away in the first
   swipe and never comes back, so the page that converts best on a desktop
   converts on nothing on a phone. Hidden on wide screens, where the header is
   still there and a bar across the bottom is just something in the way. */
.stickycta { display: none; }
@media (max-width: 780px) {
  .stickycta {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
    background: rgba(5, 9, 19, 0.94);
    border-top: 1px solid var(--line-strong);
    backdrop-filter: blur(8px);
  }
  .stickycta span {
    font-family: var(--mono); font-size: 0.8rem; color: var(--dim);
  }
  .stickycta .btn { padding: 10px 16px; white-space: nowrap; }
  /* Room for it, so the bar never covers the last line of the footer. */
  .site-foot { padding-bottom: 96px; }
}

/* MKT-26 / MKT-27 — the header on a phone.
 *
 * Measured rather than guessed: the five nav links need **866px** side by side.
 * With the brand and the sign-in button that is 1152px of content in a 390px
 * viewport, and `flex-wrap: wrap` did what it was told — three rows of links,
 * each its own tap target, so the header alone was about 230px before the page
 * started.
 *
 * There are two layouts below, and which one you get depends on whether the
 * script ran:
 *
 *   without `js`  — the links as one strip that scrolls sideways. Not lovely,
 *                   but every page is reachable, which is the only thing that
 *                   matters when an enhancement fails.
 *   with `js`     — a button, and the links as a panel under the bar.
 *
 * That order is deliberate. Collapsing the nav in CSS and revealing it with a
 * script means a visitor whose script is blocked gets a burger that does
 * nothing and no way to reach a single page — on the pages we are asking them
 * to read.
 */
.navburger { display: none; }

@media (max-width: 780px) {
  .site-head {
    gap: 12px 16px;
    padding: 14px 16px 0;
  }
  .brand { font-size: 16px; letter-spacing: 2px; }

  /* MKT-30 - the sign-in button was taller than the bar it sits in.
   *
   * `.btn` is 49px: 11px of padding either side of a 25.5px line box, plus the
   * border. The burger beside it is 44px, and the header's mobile padding is
   * `14px 16px 0` - nothing at the bottom, because the scrolling nav strip
   * below carries its own 10px and was expected to.
   *
   * With the script running there is no strip. So the tallest child was the
   * button, the button therefore *set* the header's height, and it ended up
   * with 14px of air above it and none at all below: its bottom border sat on
   * the header's bottom border. Which reads exactly as reported, a button
   * bigger than the bar.
   *
   * Two corrections, and the second is the one that lasts. The button matches
   * the burger at 44px, so the two tap targets beside each other are the same
   * size and neither can decide the bar's height on its own. And the header
   * gets its bottom padding back whenever the strip is not there to supply it.
   */
  .site-head > .btn {
    margin-left: auto;
    margin-bottom: 0;
    display: inline-flex;
    align-items: center;
    height: 44px;
    padding: 0 16px;
    /* The label is what it is; the box around it is what changed. */
    line-height: 1;
  }
  .js .site-head { padding-bottom: 14px; }

  /* --- the fallback: one scrolling strip ---------------------------------- */
  .site-head nav {
    order: 3;
    flex: 1 0 100%;
    flex-wrap: nowrap;
    gap: 18px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* Edge to edge, so the first and last items are not clipped against the
       padding the header sets for everything else. */
    margin: 0 -16px;
    padding: 0 16px 10px;
    scrollbar-width: none;
  }
  .site-head nav::-webkit-scrollbar { display: none; }
  .site-head nav a { white-space: nowrap; padding: 6px 0; }

  /* --- the enhancement: a button and a panel ------------------------------ */
  .js .navburger {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    order: 2;
    /* 44px, the size this stylesheet already decided a tap target should be. */
    width: 44px;
    height: 44px;
    margin-left: 8px;
    padding: 0;
    background: none;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
    color: var(--text);
    cursor: pointer;
  }
  /* Three bars, drawn rather than typed: the ☰ character renders at a different
     weight and baseline in every font, and this header loads none for it. */
  .js .navburger span,
  .js .navburger span::before,
  .js .navburger span::after {
    display: block;
    width: 18px;
    height: 2px;
    background: currentColor;
    border-radius: 2px;
    transition: transform 160ms ease, opacity 120ms ease, background 120ms ease;
  }
  .js .navburger span::before,
  .js .navburger span::after { content: ''; position: absolute; }
  .js .navburger span::before { transform: translateY(-6px); }
  .js .navburger span::after  { transform: translateY(6px); }

  /* Open: the bars become a cross. Four lines, and it is the difference between
     a button that toggles and a button you press twice hoping. */
  .js .site-head.is-navopen .navburger span { background: transparent; }
  .js .site-head.is-navopen .navburger span::before { transform: rotate(45deg); }
  .js .site-head.is-navopen .navburger span::after  { transform: rotate(-45deg); }

  .js .site-head > .btn { order: 1; margin-left: auto; }

  /* MKT-28 — the panel hangs off the header instead of being a row of it.
     As a row it pushed the page down as it opened, so the thing you were
     reading moved while you were looking for a link. Absolute against the
     sticky header, so it follows the bar and overlays whatever is under it. */
  .js .site-head nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 2;
    order: 3;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0;
    overflow: hidden;
    margin: 0;
    padding: 0 16px;
    background: rgba(5, 9, 19, 0.98);
    border-bottom: 1px solid var(--line);
    /* Height rather than display, so it can be animated. `visibility` keeps the
       links out of the tab order while the panel is shut, and it is the one
       property that can be delayed until the collapse has finished. */
    max-height: 0;
    opacity: 0;
    visibility: hidden;
    transition: max-height 200ms ease, opacity 140ms ease, visibility 0s linear 200ms;
  }
  /* The rest of the page, dimmed and out of reach. A menu you can see the page
     through is one people tap past by accident, and the tap lands on whatever
     was behind it. */
  .js .site-head.is-navopen::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: 100dvh;
    z-index: 1;
    background: rgba(2, 5, 10, 0.55);
  }

  .js .site-head.is-navopen nav {
    max-height: 60dvh;
    opacity: 1;
    visibility: visible;
    overflow-y: auto;
    padding-bottom: 10px;
    transition: max-height 220ms ease, opacity 140ms ease, visibility 0s;
  }
  .js .site-head nav a {
    display: block;
    white-space: nowrap;
    padding: 13px 0;
    border-bottom: 1px solid var(--line);
  }
  .js .site-head nav a:last-child { border-bottom: none; }

  /* Air under a header that was already too tall. */
  .hero { padding: 28px 0 20px; }
  .ctas { margin-top: 20px; }
  /* Two CTAs stacked read as two choices; side by side and wrapping mid-word
     they read as one broken button. */
  .ctas .btn { display: block; text-align: center; margin: 0 0 10px; }
  .mapwrap { margin: 20px 0 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .js .site-head nav, .js .navburger span,
  .js .navburger span::before, .js .navburger span::after { transition: none; }
}


/* ---- footer --------------------------------------------------------------- */
.site-foot {
  border-top: 1px solid var(--line);
  max-width: var(--max); margin: 0 auto; padding: 34px 20px 48px;
  color: var(--faint); font-size: 0.9rem;
}
.foot-grid { display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.foot-grid h2 {
  font-family: var(--mono); font-size: 0.75rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--accent); margin: 0 0 10px; font-weight: 400;
}
.foot-grid ul { list-style: none; margin: 0; padding: 0; }
.foot-grid a { color: var(--faint); text-decoration: none; }
.foot-grid a:hover { color: var(--accent); text-decoration: underline; }
.foot-legal { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--line); font-family: var(--mono); }

/* Touch targets. These links sit in tight columns and are the smallest things
   on the page, which is the combination that produces mis-taps. */
@media (pointer: coarse) {
  .foot-grid a { display: inline-block; min-height: 44px; line-height: 44px; }
  .foot-grid li { line-height: 1; }
}

/* MKT-07 — the live map, laid over the static drawing that is its placeholder.
   The SVG keeps the box the right shape before the frame paints, and stays
   visible for anyone whose browser blocks the frame or runs without script. */
.mapwrap { position: relative; }
.mapframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: transparent;
  /* The frame owns the pointer so dragging works; the SVG underneath is inert
     anyway, so nothing is lost by covering it. */
}

/* ===========================================================================
 * MKT-09 — screenshots, feature rows, figures, the fleet map and the diagram.
 *
 * The page had one picture on it (the hero map) and everything else was prose.
 * Every claim below is now next to the screen that carries it out, captured
 * from the running application rather than drawn — a marketing page that
 * illustrates its own idea of the product is promising an interface nobody
 * will find after signing up.
 * ======================================================================== */

:root { --max: 1200px; }

/* ---- a screenshot --------------------------------------------------------- */
/* MKT-12 — cropped to the thing being talked about, and framed.
 *
 * These were full-page captures: a navigation bar, an account email and half a
 * screen of empty grid around the one panel the section was about. Each is now
 * a crop, and the frame supplies the padding the crop no longer has, so the
 * picture reads as a specimen rather than as a browser window somebody
 * forgot to close. */
.shot { margin: 0; }
.shot img {
  display: block; width: 100%; height: auto;
  border-radius: 6px;
  /* Inside the frame, not against its edge. */
  padding: 14px;
  background:
    linear-gradient(180deg, rgba(16, 242, 178, 0.05), transparent 60%),
    var(--bg-raised);
  border: 1px solid var(--line);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
@media (min-width: 900px) { .shot img { padding: 20px; } }
.shot--wide { grid-column: 1 / -1; }

/* ---- alternating rows ----------------------------------------------------- */
.row {
  display: grid; gap: 28px;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  padding: 46px 0;
  border-top: 1px solid var(--line);
}
.row__text h2 { margin: 0 0 14px; font-size: clamp(1.3rem, 2.8vw, 1.75rem); color: var(--bright); letter-spacing: 0.4px; }
.row__text p { color: var(--dim); max-width: 56ch; }
.row__media { min-width: 0; }

@media (min-width: 900px) {
  /* Alternating sides, so the eye is not asked to travel the same path six
     times running. One component renders both directions — a second markup
     shape would be a second thing to keep in step.
     The columns swap along with the order: with `order` alone the flipped rows
     put the screenshot in the 5fr column, so every other product shot rendered
     noticeably smaller than the one above it. */
  .row { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 48px; }
  .row--flip { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
  .row--flip .row__text { order: 2; }
  .row--flip .row__media { order: 1; }
}

.ticks { list-style: none; padding: 0; margin: 18px 0 0; }
.ticks li {
  position: relative; padding: 6px 0 6px 26px;
  color: var(--dim); font-size: 0.95rem;
}
.ticks li::before {
  content: "✓"; position: absolute; left: 0; top: 6px;
  color: var(--accent); font-family: var(--mono);
}
.more {
  font-family: var(--mono); font-size: 0.9rem; letter-spacing: 0.06em;
  text-transform: uppercase; text-decoration: none;
}
.more:hover { text-decoration: underline; }

/* ---- the fleet map -------------------------------------------------------- */
.fleetmap {
  margin: 24px 0 18px;
  border: 1px solid var(--line); border-radius: 6px;
  background: linear-gradient(180deg, rgba(77, 243, 255, 0.03), transparent 60%), var(--bg-raised);
  padding: 8px;
  overflow: hidden;
}
.fleetmap svg { display: block; width: 100%; height: auto; }
.grat { stroke: rgba(16, 242, 178, 0.07); stroke-width: 1; }
/* The coastlines are simplified to the point of being a diagram; the probe
   coordinates on top of them are exact. Land is drawn dim so that the arcs and
   the probes stay the brightest things in the frame. */
.land {
  fill: rgba(16, 242, 178, 0.09);
  stroke: rgba(16, 242, 178, 0.45);
  stroke-width: 1.1;
  stroke-linejoin: round;
}
.route { fill: none; stroke: rgba(77, 243, 255, 0.22); stroke-width: 1; }
.target { fill: rgba(77, 243, 255, 0.45); }
/* A dash travelling the arc. stroke-dashoffset rather than a motion path: it
   animates in every browser that renders SVG at all, and with animation
   switched off it settles into a stationary dash rather than a dot parked in
   a corner. */
.route-flow {
  fill: none; stroke: var(--accent); stroke-width: 1.6; stroke-linecap: round;
  stroke-dasharray: 14 460; stroke-dashoffset: 474;
  animation: routeflow 5.5s linear infinite;
}
@keyframes routeflow { to { stroke-dashoffset: 0; } }
.probe__pulse {
  fill: none; stroke: var(--accent); stroke-width: 1;
  transform-box: fill-box; transform-origin: center;
  animation: probepulse 3.2s ease-out infinite;
}
@keyframes probepulse {
  0%   { transform: scale(0.7); opacity: 0.9; }
  70%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .route-flow { animation: none; stroke-dashoffset: 240; }
  .probe__pulse { animation: none; opacity: 0.35; }
}
.probe__halo { fill: rgba(16, 242, 178, 0.10); stroke: rgba(16, 242, 178, 0.35); stroke-width: 1; }
.probe__dot { fill: var(--accent); }
.probe__name { font-family: var(--display); font-size: 14px; fill: var(--bright); letter-spacing: 1px; }
.probe__meta { font-family: var(--mono); font-size: 11px; fill: var(--faint); }

/* ---- the consensus diagram ------------------------------------------------ */
.diagram {
  margin: 22px 0 6px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg-raised); padding: 14px;
  overflow-x: auto;
}
.diagram svg { display: block; width: 100%; min-width: 640px; height: auto; }
.dg-box { fill: rgba(11, 18, 32, 0.9); stroke: var(--line-strong); stroke-width: 1; }
.dg-box--key { fill: rgba(16, 242, 178, 0.07); stroke: var(--accent); }
.dg-box--up { stroke: rgba(16, 242, 178, 0.45); }
.dg-box--warn { stroke: rgba(255, 200, 87, 0.5); }
.dg-box--down { stroke: rgba(255, 77, 109, 0.55); }
.dg-line { stroke: rgba(16, 242, 178, 0.32); stroke-width: 1.4; fill: none; }
.dg-t { font-family: var(--mono); font-size: 13px; fill: var(--text); }
.dg-t--key { font-family: var(--display); fill: var(--accent); letter-spacing: 1px; }
.dg-s { font-family: var(--mono); font-size: 11px; fill: var(--faint); }

/* ---- the footer, now that it is a site map -------------------------------- */
.foot-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

/* ===========================================================================
 * MKT-11 — the cascade.
 *
 * A database stops answering and the services behind it change colour in the
 * same second. That is the product's whole argument, and it is the one claim on
 * the page that a paragraph genuinely cannot make.
 *
 * A nine-second loop, in two beats: the database goes red, and a moment later
 * the two services touching it turn amber, then the two behind those. The gap
 * between the beats is the point — it is what a person watching the real map
 * sees, and it is why the map exists.
 *
 * SVG and keyframes rather than the GIF this replaced: 3 KB against half a
 * megabyte, crisp at any width, and it can be turned off by somebody who has
 * asked the web to stop moving. A GIF cannot be.
 * ======================================================================== */
.cascade {
  border: 1px solid var(--line); border-radius: 6px;
  background: linear-gradient(180deg, rgba(16, 242, 178, 0.04), transparent 55%), var(--bg-raised);
  padding: 12px;
  overflow-x: auto;
}
/* Full width, because it is the demonstration rather than an illustration
   beside a paragraph — in a half-width column the database that everything
   else depends on fell off the right-hand edge. */
.cascade svg { display: block; width: 100%; min-width: 620px; height: auto; }
.cascade + .note { margin-top: 12px; }

.cs-node { fill: rgba(11, 18, 32, 0.92); stroke-width: 1.5; stroke: var(--accent); }
.cs-label { font-family: var(--mono); font-size: 13px; fill: var(--text); }
.cs-edge { stroke: rgba(16, 242, 178, 0.30); stroke-width: 1.6; }

/* The database. Everything else in the picture is timed off this. */
.cs-node--bad { animation: cs-down 9s ease-in-out infinite; }
@keyframes cs-down {
  0%, 20%   { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
  26%, 82%  { stroke: var(--danger); fill: rgba(255, 77, 109, 0.16); }
  90%, 100% { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
}

/* Tier one: the services that talk to it directly. */
.cs-node--dep1 { animation: cs-warn1 9s ease-in-out infinite; }
@keyframes cs-warn1 {
  0%, 30%   { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
  36%, 80%  { stroke: var(--warn); fill: rgba(255, 200, 87, 0.13); }
  88%, 100% { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
}

/* Tier two: the ones behind those. A beat later, which is the whole idea. */
.cs-node--dep2 { animation: cs-warn2 9s ease-in-out infinite; }
@keyframes cs-warn2 {
  0%, 40%   { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
  46%, 78%  { stroke: var(--warn); fill: rgba(255, 200, 87, 0.13); }
  86%, 100% { stroke: var(--accent); fill: rgba(11, 18, 32, 0.92); }
}

.cs-edge--bad { animation: cs-edge 9s ease-in-out infinite; }
@keyframes cs-edge {
  0%, 22%   { stroke: rgba(16, 242, 178, 0.30); stroke-dasharray: 0; }
  28%, 82%  { stroke: rgba(255, 77, 109, 0.55); stroke-dasharray: 6 5; }
  90%, 100% { stroke: rgba(16, 242, 178, 0.30); stroke-dasharray: 0; }
}

/* Asked to stop moving: show the moment that matters, and hold it. */
@media (prefers-reduced-motion: reduce) {
  .cs-node--bad  { animation: none; stroke: var(--danger); fill: rgba(255, 77, 109, 0.16); }
  .cs-node--dep1,
  .cs-node--dep2 { animation: none; stroke: var(--warn); fill: rgba(255, 200, 87, 0.13); }
  .cs-edge--bad  { animation: none; stroke: rgba(255, 77, 109, 0.55); stroke-dasharray: 6 5; }
}

/* MKT-10 — the free tools.
   Deliberately plain. A tool page is somebody arriving from a search with one
   question, and every pixel spent on decoration is a pixel between them and the
   answer. The controls borrow the site's existing .btn and colours so the page
   still looks like ours without a second design to maintain. */
.tool {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 18px;
  background: rgba(11, 18, 32, 0.45);
  margin-top: 18px;
}
.tool__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.tool__row label { color: var(--dim); font-size: 13px; }
.tool select,
.tool input[type="text"] {
  background: rgba(5, 10, 20, 0.8);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  /* 16px, because anything smaller makes iOS zoom the page on focus and the
     tool jumps out from under the thumb — the same trap FE-17 was. */
  font: inherit;
  font-size: 16px;
  padding: 8px 10px;
}
.tool select:focus,
.tool input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.tool__out {
  background: rgba(5, 10, 20, 0.9);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 14px;
  /* The one thing a generated rule list must do on a phone: scroll sideways
     rather than wrap, because a wrapped firewall rule is a broken one when it
     is pasted. */
  overflow-x: auto;
  white-space: pre;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 13px;
  line-height: 1.7;
  color: var(--text);
  margin: 0 0 14px;
  max-height: 60vh;
}
.tool__table { width: 100%; border-collapse: collapse; margin-top: 6px; }
.tool__table th,
.tool__table td {
  text-align: left;
  padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.tool__table th { color: var(--dim); font-weight: 500; }
.tool__table td:last-child { color: var(--accent); font-family: var(--mono, ui-monospace, monospace); }
.chip {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--dim);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  margin-left: 6px;
  padding: 5px 11px;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }

/* TOOL-01 — the two tools that answer from the fleet.
 *
 * Three states, not two, and the middle one is the whole point: every location
 * reaching a host is up, none of them is down, and some of them is degraded.
 * The colours are the product's own (FE-63), so a visitor who later opens a
 * dashboard reads amber the same way in both places. Getting that wrong would
 * teach somebody one meaning on a free page and a different one once they pay.
 */
.verdict {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 15px;
  margin: 4px 0 14px;
  min-height: 1.4em;
  color: var(--dim);
}
.verdict--up { color: var(--accent); }
.verdict--degraded { color: var(--warn); }
.verdict--down { color: var(--danger); }

/* The result cells. `td:last-child` above is already accented, so these have to
 * come after it to win, and they are what carry the per-location verdict. */
.tool__table td.ok { color: var(--accent); }
.tool__table td.bad { color: var(--danger); }
/* A blocked ping is normal and is not a fault. Colouring it red would report a
 * firewall doing its job as an outage. */
.tool__table td.muted { color: var(--faint); }
.tool__table td.pending { color: var(--faint); font-style: italic; }

/* ===========================================================================
 * MKT-24 — /community.
 *
 * Built from the tokens at the top of this file, so the section reads as the
 * same product rather than as a blog that happens to share a domain.
 *
 * One deliberate departure, stated because it otherwise looks like an
 * oversight. The rule above is that Orbitron sets headings, and that is right
 * for the furniture here: the eyebrow, the hub title, the band headings. It is
 * wrong for the thing this section is full of. Article titles are whole
 * sentences of seventy or eighty characters, and Orbitron is a geometric
 * display face with almost no width variation between letters; at that length
 * it stops being read and starts being decoded. So titles and prose take the
 * body face, and the display face keeps everything that stays short.
 * ======================================================================== */

/* ---- the filter chips ---------------------------------------------------- */
.cfilter {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: 0 0 22px;
}
.cchip {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em;
  text-transform: uppercase;
  background: transparent; color: var(--dim);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 14px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
}
.cchip:hover { color: var(--accent); border-color: var(--line-strong); }
.cchip.is-on {
  color: var(--accent); border-color: var(--accent);
  background: rgba(16, 242, 178, 0.08);
  box-shadow: 0 0 14px rgba(16, 242, 178, 0.14);
}
.cchip__n { color: var(--faint); font-size: 11px; }
.cchip.is-on .cchip__n { color: var(--accent); }

/* ---- the card grid ------------------------------------------------------- */
/* One column on a phone, where the lead article is simply the first card. The
   split only appears once there is width for a readable measure inside it. */
.cgrid { display: grid; gap: 14px; grid-template-columns: 1fr; }
.cband--lead { padding: 8px 0 34px; }

@media (min-width: 720px) {
  .cgrid { grid-template-columns: repeat(2, 1fr); gap: 16px; }
}
/* The lead is one card across the full width. It used to span two rows of a
   four-column grid, which left a tall empty panel under a two-line title.
   Two class names rather than one: the column rules above live inside media
   queries that come later in the file, and at equal specificity later wins. */
.cgrid.cgrid--lead { grid-template-columns: 1fr; }
@media (min-width: 1000px) {
  .cgrid { grid-template-columns: repeat(3, 1fr); }
}

.ccard {
  position: relative;
  display: flex; flex-direction: column; justify-content: space-between;
  gap: 14px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel);
  padding: 18px;
  transition: border-color 140ms ease, transform 140ms ease;
}
/* The corner bracket is this page's own idiom, borrowed from .panel rather than
   reinvented, so a card reads as part of the same interface. */
.ccard::before {
  content: ""; position: absolute; top: -1px; left: -1px;
  width: 13px; height: 13px;
  border: 2px solid var(--accent); border-width: 2px 0 0 2px;
  pointer-events: none; opacity: 0.55;
  transition: opacity 140ms ease;
}
.ccard:hover { border-color: var(--line-strong); transform: translateY(-2px); }
.ccard:hover::before { opacity: 1; }
.ccard:focus-within { border-color: var(--accent); }

.ccard__link { text-decoration: none; color: inherit; display: block; }
/* The whole card is the target, not only the words. A narrow link inside a wide
   card is the reason people tap twice on a phone. */
.ccard__link::after { content: ""; position: absolute; inset: 0; }

.ccard__kind {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--accent);
  margin: 0 0 10px;
}
.ccard__title {
  font-family: var(--body);
  font-size: 1.05rem; line-height: 1.35; font-weight: 600;
  letter-spacing: 0; color: var(--bright);
  margin: 0 0 8px;
}
.ccard__hook {
  margin: 0; color: var(--dim); font-size: 0.94rem; line-height: 1.55;
}
.ccard__meta {
  margin: 0; font-family: var(--mono); font-size: 11px;
  color: var(--faint); letter-spacing: 0.04em;
  display: flex; align-items: center; gap: 9px;
}
.ccard__dot {
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--faint); flex: none;
}

.ccard--lead { padding: 24px; }
.ccard--lead .ccard__title { font-size: 1.5rem; }
.ccard--lead .ccard__hook { font-size: 1rem; }
@media (min-width: 1000px) {
  .ccard--lead .ccard__title { font-size: 1.8rem; }
}

/* The filter hides cards and whole bands. `hidden` has to beat the flex and
   grid displays above, which is the one place the override earns itself. */
.ccard[hidden], .cband[hidden] { display: none !important; }

.cband > h2 {
  font-family: var(--body); font-weight: 400;
  font-size: 1rem; line-height: 1.6; color: var(--dim);
  max-width: 62ch; letter-spacing: 0;
  margin: 0 0 20px;
}

/* ---- the article page ---------------------------------------------------- */
.cart { padding: 26px 0 8px; }
.cart__head { border-bottom: 1px solid var(--line); padding-bottom: 22px; margin-bottom: 26px; }
.cart__head .eyebrow { margin-bottom: 14px; }
.cart__head .eyebrow a { color: var(--accent); text-decoration: none; }
.cart__head .eyebrow a:hover { color: var(--cyan); }
.cart__head h1 {
  font-family: var(--body);
  font-size: clamp(1.55rem, 4vw, 2.35rem);
  line-height: 1.2; font-weight: 700; letter-spacing: -0.01em;
  color: var(--bright);
  margin: 0 0 16px;
  max-width: 22ch;
}
@media (min-width: 720px) { .cart__head h1 { max-width: 26ch; } }
.cart__head .lede { max-width: 62ch; margin: 0 0 18px; }
.cart__meta {
  margin: 0; font-family: var(--mono); font-size: 12px;
  color: var(--faint); letter-spacing: 0.04em;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}


/* ---- what this covers ---------------------------------------------------- */
.ckey {
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg-raised);
  padding: 20px; margin: 0 0 30px;
}
.ckey h2 {
  font-family: var(--display); font-size: 0.78rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 14px;
}
/* Margins rather than a grid gap. A gap is applied between every child, so it
   pushed each term as far from its own definition as from the next pair, and
   the list stopped reading as pairs at all. */
.ckey dl { margin: 0; }
.ckey dt {
  font-family: var(--body); font-weight: 600; color: var(--bright);
  font-size: 0.95rem; line-height: 1.4;
  margin-top: 18px;
}
.ckey dt:first-of-type { margin-top: 0; }
.ckey dd {
  margin: 4px 0 0; color: var(--dim);
  font-size: 0.92rem; line-height: 1.6; max-width: 68ch;
}
@media (min-width: 860px) { .ckey { padding: 26px 28px; } }

/* ---- the prose ----------------------------------------------------------- */
/* Sixty-eight characters. Wider and the eye loses the start of the next line;
   it is the one typographic number that decides whether long pieces get
   finished rather than merely opened. */
.cprose { max-width: 68ch; }
.cprose > * { max-width: 68ch; }
.cprose p { margin: 0 0 20px; color: var(--text); line-height: 1.75; overflow-wrap: break-word; }
.cprose a { color: var(--accent); text-underline-offset: 3px; }
.cprose a:hover { color: var(--cyan); }
.cprose strong { color: var(--bright); font-weight: 600; }

.cprose h2 {
  font-family: var(--body);
  font-size: 1.35rem; line-height: 1.3; font-weight: 700;
  letter-spacing: -0.01em; color: var(--bright);
  margin: 42px 0 16px; padding-top: 4px;
}
.cprose h3 {
  font-family: var(--body);
  font-size: 1.08rem; font-weight: 600; letter-spacing: 0;
  color: var(--bright); margin: 32px 0 12px;
}
.cprose h2 + p, .cprose h3 + p { margin-top: 0; }

.cprose ul, .cprose ol { margin: 0 0 22px; padding-left: 22px; color: var(--text); }
.cprose li { margin-bottom: 9px; line-height: 1.7; }
.cprose li::marker { color: var(--accent); }

.cprose blockquote {
  margin: 0 0 22px; padding: 2px 0 2px 18px;
  border-left: 2px solid var(--line-strong);
  color: var(--dim);
}

/* Inline code and blocks. A command that reflows is a command that breaks when
   it is pasted, so blocks scroll inside their own box rather than wrapping, and
   inside their own box rather than taking the page sideways with them. */
.cprose code {
  font-family: var(--mono); font-size: 0.92em;
  background: rgba(16, 242, 178, 0.08);
  border: 1px solid rgba(16, 242, 178, 0.16);
  border-radius: 3px; padding: 1px 5px;
  color: var(--accent);
  overflow-wrap: break-word;
}
.cprose pre {
  margin: 0 0 24px; padding: 16px 18px;
  background: #040d13;
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: 4px;
  overflow-x: auto;
  max-width: 100%;
}
.cprose pre code {
  background: none; border: 0; padding: 0;
  color: var(--text); font-size: 0.86rem; line-height: 1.65;
  white-space: pre;
}
@media (min-width: 720px) {
  .cprose pre { padding: 18px 22px; }
  .cprose pre code { font-size: 0.9rem; }
}

/* The wrapper site/community.js puts round every table from markdown. */
.cprose .ctable { overflow-x: auto; max-width: 100%; margin: 0 0 24px; }
.cprose table {
  width: 100%; border-collapse: collapse; margin: 0;
  font-size: 0.92rem;
}
.cprose th, .cprose td {
  text-align: left; padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--line);
}
.cprose th {
  color: var(--faint); font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.1em; text-transform: uppercase; font-weight: 400;
}
.cprose td { color: var(--dim); }

/* ---- figures ------------------------------------------------------------- */
.cfig { margin: 0 0 26px; }
.cfig img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg-raised);
}
.cfig figcaption {
  margin-top: 10px; color: var(--faint);
  font-size: 0.85rem; line-height: 1.55; font-family: var(--mono);
}

/* ---- sources ------------------------------------------------------------- */
.csources { max-width: 68ch; }
.csources h2 {
  font-family: var(--display); font-size: 0.78rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 14px;
}
.csources ol { margin: 0; padding-left: 20px; }
.csources li { margin-bottom: 9px; color: var(--dim); font-size: 0.92rem; line-height: 1.6; }
.csources li::marker { color: var(--faint); }
.csources a { color: var(--accent); overflow-wrap: anywhere; }

.cmore { display: flex; flex-wrap: wrap; gap: 10px; }

/* ---- the article body, beside its summary -------------------------------- */
/* The prose is held at 68 characters because that is what gets read. On a wide
   screen that left two thirds of the page empty and a full-width summary panel
   stranded above it, so the summary moves into the gap.
   Source order is summary first: on a phone it is a summary, and a summary
   after the article it summarises is a table of contents nobody needed. The
   grid puts it back on the right where there is room for both. */
.cart__cols { display: block; }

/* An article page is narrower than the rest of the site.
 *
 * The shared container is 1200px, which is right for a marketing page of
 * three-column grids and wrong here: prose held at 68 characters plus a 300px
 * summary comes to about 950, so the two sat as separate islands with 250px of
 * empty grid between them and the whole page read as shoved left.
 *
 * Narrowing the container rather than widening the measure, because 68
 * characters is the number that decides whether a long piece gets finished.
 * The :has() selector is on every current browser; where it is not, the layout
 * is simply the wider one, which is worse rather than broken. */
main:has(.cart) { max-width: 1000px; }

@media (min-width: 1080px) {
  .cart__cols {
    display: grid;
    /* The first column takes the slack, so the summary sits against the right
       edge of the container instead of stopping 250px short of it and leaving
       the whole page looking shoved into the middle. The prose keeps its own
       measure inside that column rather than stretching to fill it. */
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 0 64px;
    align-items: start;
  }
  .cart__cols .cprose { grid-column: 1; grid-row: 1; }
  .cart__cols .ckey {
    grid-column: 2; grid-row: 1;
    position: sticky; top: 22px;
    margin: 0;
    /* Its own scroll once it is taller than the screen, so a long summary
       cannot trap the page against the bottom of a sticky box. */
    max-height: calc(100vh - 44px);
    overflow-y: auto;
  }
  .cart__cols .ckey dd { font-size: 0.88rem; }
}

/* MKT-29 — the identification block in the terms.
 *
 * A definition list rather than a paragraph, because these are seven separate
 * facts a customer, a tax authority or a payment provider each come looking for
 * one of. Buried in prose, every one of them has to be hunted for. */
.legal-id {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 20px;
  margin: 16px 0;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-raised);
}
.legal-id dt { color: var(--faint); font-family: var(--mono); font-size: 0.85rem; }
.legal-id dd { margin: 0; color: var(--text); }
@media (max-width: 560px) {
  /* One column on a phone: a max-content label column and a long address in the
     other is a two-word column and a six-line one. */
  .legal-id { grid-template-columns: 1fr; gap: 2px 0; }
  .legal-id dd { margin-bottom: 10px; }
}
