/* Umbrella — shared design system.
 *
 * Brand kit (docs/umbrella/BRAND.md): Deep Slate + Tech Blue on white, an 8px
 * spacing scale, 4px radius, hairline borders. "Engineered, not decorated" —
 * no gradients, no shadows, no illustration, no rounded pills.
 *
 * Two deliberate departures from the previous version, both taken from the
 * brand doc rather than around it:
 *
 *   Type is Geist, not Inter. BRAND.md names "Geist, fallback Inter" and the
 *   site had been shipping the fallback. Geist Mono replaces JetBrains Mono for
 *   the same reason plus one more: on this site the monospace is carrying
 *   CONTENT — domains, filenames, response headers — not captions, so it should
 *   come from the same family as the text it sits beside.
 *
 *   One dark surface exists, `--source`. It is used for exactly one thing: the
 *   blocks that show real files and real command output. Source is read on a
 *   dark surface in every tool this audience uses, and confining the darkness
 *   to those blocks keeps it a signal rather than a style.
 */

/* ── Type ───────────────────────────────────────────────────────────────────
   Self-hosted, not loaded from Google Fonts.
   Hotlinking fonts.googleapis.com sends every visitor's IP address to a third
   party on every page view — a disclosure this site would then have to make in
   its own privacy policy, and one German courts have already ruled on. Serving
   four files from our own origin removes the disclosure, the two preconnects
   and a render-blocking cross-origin request in a single change.

   Geist and Geist Mono are variable fonts covering 400-600, so one @font-face
   per family per subset is the whole set: three weight-specific rules fetched
   the same bytes three times. Licence: SIL Open Font License 1.1, see
   fonts/LICENSE.md. */

@font-face{font-family:'Geist';font-style:normal;font-weight:400 600;font-display:swap;src:url('/assets/fonts/geist-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist';font-style:normal;font-weight:400 600;font-display:swap;src:url('/assets/fonts/geist-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('/assets/fonts/geist-mono-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('/assets/fonts/geist-mono-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

:root{
  --slate:#182B4D; --blue:#1769FF; --blue-press:#0F52D9;
  --bg:#fff; --neutral:#F5F7FA; --border:#E4E7EC;
  --ink:#182B4D; --muted:#5A6478;
  --source:#0B1524; --source-ink:#C9D5E8; --source-dim:#6B7C99; --source-rule:#1E2C44;
  --ok:#16A34A; --warn:#F59E0B; --bad:#DC2626;
  /* Text-safe variants. --ok/--bad stay for rules, borders and glyphs that sit
     beside a text label carrying the meaning; anything where the COLOURED TEXT
     IS the message uses these. Measured against AA's 4.5:1 body-text floor:
     --ok on white is 3.30:1 and --bad on the dark page is 3.72:1, both short.
     The dark red reuses the salmon already in the source panels rather than
     introducing a second red. */
  --ok-text:#15803D; --bad-text:#DC2626;
  /* Link text, separate from --blue. --blue is the BUTTON colour, where the
     contrast that matters is white-on-blue; as link text on the dark-mode page
     it measures 3.84:1 and on the dark neutral 3.52:1, both under AA's 4.5:1.
     Lighthouse reported three links, but the defect was every prose link on
     every page in dark mode.

     The light value is --blue-press rather than --blue for the same reason one
     step down: #1769FF clears 4.5:1 on white (4.66) but not on --neutral
     (4.35), and links sit on that surface inside every .note and .reassure
     panel. A link colour that passes on one of the two surfaces it is used on
     is not a passing link colour. This one measures 6.50:1 on white and 6.05:1
     on the neutral, and is a value the palette already had. */
  --link:#0F52D9;
  --s1:8px; --s2:16px; --s3:24px; --s4:32px; --s6:48px; --s8:64px; --s10:80px; --s12:96px;
  --sans:Geist, Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:"Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --wrap:1100px;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%; scroll-behavior:smooth}
body{
  margin:0; background:var(--bg); color:var(--ink);
  font:400 17px/1.65 var(--sans);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
.wrap{max-width:var(--wrap); margin:0 auto; padding:0 var(--s3)}
.narrow{max-width:720px}

/* A 1100px column on a 2560px display is a ribbon in the middle of a white
   field, with the window's scrollbar stranded a long way from anything it
   scrolls. Prose still stops at its own readable measure — that is what the
   68ch caps are for — but the structural elements (header, source panels,
   tables, plan cards, the editorial grid) get more of the display, so the page
   reads as laid out for the screen rather than ignoring it. */
@media (min-width:1500px){ :root{ --wrap:1280px } }
@media (min-width:1900px){ :root{ --wrap:1400px } }

a{color:var(--link); text-decoration:none}
a:hover{text-decoration:underline}

/* WCAG 1.4.1: a link inside a block of text must be distinguishable by
   something other than colour. This site had `text-decoration:none` on every
   link and an underline only on hover, so in prose the colour was the only
   signal — which is nothing at all to a reader who cannot separate the two.
   Nav and footer link LISTS are deliberately exempt and stay undecorated: the
   requirement is about links EMBEDDED in text, and a list of links needs no
   underline to be recognised as one.
   Thin, offset, and drawn at 45% so it reads as a considered underline on a
   page with no other decoration rather than a default browser hyperlink. If
   color-mix is unsupported the declaration drops and the underline falls back
   to the link's own colour, which is a weaker result and still a passing one. */
p a:not(.btn), li a:not(.btn), dd a:not(.btn){
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:2px;
  text-decoration-color:color-mix(in srgb, var(--link) 45%, transparent);
}
p a:not(.btn):hover, li a:not(.btn):hover, dd a:not(.btn):hover{
  text-decoration-color:var(--link);
}
/* Link lists, not prose. Also the table of contents, whose items are `li a`. */
header nav a, footer a, .toc a{text-decoration:none}
/* Focus is visible everywhere and never removed. The offset keeps the ring off
   the glyphs on a hairline-bordered page where 1px is meaningful. */
a:focus-visible,button:focus-visible,summary:focus-visible{
  outline:2px solid var(--blue); outline-offset:3px; border-radius:2px;
}

h1,h2,h3{margin:0; font-weight:600; line-height:1.12; letter-spacing:-0.026em}
p{margin:0}
.eyebrow{
  font:500 12px/1 var(--sans); letter-spacing:.15em;
  text-transform:uppercase; color:var(--muted);
}
code,.mono{font-family:var(--mono); font-size:.9em; letter-spacing:-0.01em}

/* ── Header ─────────────────────────────────────────────────────────────── */

header{border-bottom:1px solid var(--border)}
header .wrap{display:flex; align-items:center; justify-content:space-between; height:72px; gap:var(--s3)}
.brand{display:flex; align-items:center; gap:10px; color:inherit}
.brand:hover{text-decoration:none}
.brand svg{display:block; flex:none}
.brand .name{font-weight:600; letter-spacing:-0.03em; color:var(--slate); font-size:20px; display:block}
.brand .sub{color:var(--muted); font-size:11.5px; letter-spacing:.02em; display:block; margin-top:1px}
/* Scoped to the header. Unscoped, this rule reached the table-of-contents
   <nav> on the document pages and laid its label out beside its list, flex-
   centred — a landmark element is not a safe thing to style by tag name once
   the site has more than one of them. */
header nav{display:flex; gap:var(--s3); align-items:center; font-size:15px}
header nav a.plain{color:var(--muted)}
header nav a.plain:hover{color:var(--ink); text-decoration:none}
header nav .short{display:none}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn{
  display:inline-block; background:var(--blue); color:#fff; border-radius:4px;
  padding:12px 20px; font-weight:500; font-size:15px; line-height:1.2;
  transition:background 120ms ease;
}
.btn:hover{text-decoration:none; background:var(--blue-press)}
.btn.ghost{background:transparent; color:var(--ink); border:1px solid var(--border)}
.btn.ghost:hover{background:var(--neutral); border-color:#CFD6E2}
.btn.sm{padding:9px 15px; font-size:14px}

/* ── Page chrome for the secondary pages ────────────────────────────────── */

.page{padding:var(--s10) 0 var(--s6)}
.page h1{font-size:clamp(32px,4.4vw,46px); max-width:22ch}
.page p.lede{margin-top:var(--s3); font-size:19px; line-height:1.55; color:var(--muted); max-width:60ch}
.updated{margin-top:var(--s2); color:var(--muted); font-size:14px}

section{padding:var(--s4) 0}
h2{font-size:clamp(22px,2.8vw,28px); max-width:26ch; margin-top:var(--s4)}
h3{font-size:18px; margin-top:var(--s3)}
section p{margin-top:var(--s2); max-width:68ch; color:var(--ink)}
section p.muted{color:var(--muted)}
ul{margin:var(--s2) 0 0; padding-left:var(--s3); max-width:68ch}
li{margin-top:var(--s1)}

.note{background:var(--neutral); border:1px solid var(--border); border-radius:4px; padding:var(--s3); margin-top:var(--s3)}
.note p{margin-top:0}

/* Unfilled legal values, deliberately loud.
   A placeholder that reads like prose is worse than no placeholder at all: it
   can be published without anyone noticing, and on a terms page that means
   shipping an agreement naming no contracting entity. This has to be
   unmistakable to a skim, so it does not rely on the reader knowing the
   bracket convention. `npm run check:legal` is the actual guard — this is the
   part that catches a human eye before the script catches the deploy. */
mark.legal-todo{
  background:#FFF3CD; color:#5C4400; border:1px dashed #B78103;
  border-radius:3px; padding:0 .3em; font-weight:600; white-space:normal;
}

/* ── Plans ──────────────────────────────────────────────────────────────── */

.plans{display:grid; grid-template-columns:repeat(3,1fr); gap:var(--s3); margin-top:var(--s4)}
.plan{border:1px solid var(--border); border-radius:4px; padding:var(--s3); display:flex; flex-direction:column}
.plan.featured{border-color:var(--blue)}
.plan .tier{font-weight:600; font-size:18px; letter-spacing:-0.02em}
.plan .price{margin-top:var(--s2); font-size:32px; font-weight:600; letter-spacing:-0.03em}
.plan .price span{font-size:15px; font-weight:400; color:var(--muted); letter-spacing:0}
.plan ul{margin-top:var(--s3); padding-left:0; list-style:none}
.plan li{position:relative; padding-left:var(--s2); font-size:15px; color:var(--muted)}
.plan li::before{content:"—"; position:absolute; left:0; color:#C2CBD9}

/* ── Tables ─────────────────────────────────────────────────────────────── */

table{border-collapse:collapse; width:100%; margin-top:var(--s3); font-size:15px}
th,td{text-align:left; padding:11px 12px; border-bottom:1px solid var(--border); vertical-align:top}
th{font-weight:500; color:var(--muted); font-size:12px; letter-spacing:.1em; text-transform:uppercase}
/* The features no plan withholds, stated once beneath the comparison instead of
   as rows of three identical "Yes" cells: repeating the same answer across
   every column makes a plan look padded, and it pushes the rows that carry the
   actual decision out of the reader's first glance.

   Outside the table on purpose. Inside it these were colspan rows, which
   inherited the 620px mobile min-width the comparison columns need and the
   sentences do not — putting a plain line of text behind a sideways scroll. */
.included-cap{
  margin-top:var(--s4); font-size:12px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--muted);
}
/* max-width:none, because the base ul caps at 68ch and these rules have to line
   up with the full-width comparison table directly above them. */
.included{margin:var(--s2) 0 0; padding:0; list-style:none; max-width:none}
.included li{
  margin-top:0; position:relative; padding:11px 0 11px 26px; font-size:15px;
  border-bottom:1px solid var(--border);
}
.included li::before{content:"✓"; position:absolute; left:0; color:var(--ok-text)}

.scroll-x{overflow-x:auto; -webkit-overflow-scrolling:touch}
/* Shown only when the table is actually wider than its container. */
.scroll-hint{display:none; margin-top:var(--s1); font-size:13px; color:var(--muted)}
@media (max-width:700px){
  /* Without a floor, every row label wraps to five words-per-line and a
     four-column comparison becomes unreadable in the name of fitting. It
     scrolls inside .scroll-x either way; the page itself never does. */
  .scroll-x table{min-width:620px}
  .scroll-hint{display:block}
}

/* ── Document layout — the long pages ───────────────────────────────────────
   Legal and reference pages are a narrow column of prose, and a narrow column
   inside a 1100px page leaves half the viewport empty. The contents list fills
   it with the one thing these pages actually need: a way to not read all of it.
   Sticky, because the reason to want it is halfway down. */

.doc{display:grid; grid-template-columns:210px minmax(0,1fr); gap:var(--s8); align-items:start}
.toc{position:sticky; top:var(--s4); font-size:14.5px}
.toc p{margin:0 0 var(--s2); font:500 12px/1 var(--sans); letter-spacing:.15em;
       text-transform:uppercase; color:var(--muted)}
.toc ul{list-style:none; margin:0; padding:0; max-width:none}
.toc li{margin:0}
.toc a{display:block; padding:5px 0 5px 12px; color:var(--muted); border-left:1px solid var(--border)}
.toc a:hover{color:var(--ink); border-left-color:var(--blue); text-decoration:none}
.doc-body > section:first-child{padding-top:0}
.doc-body h2{margin-top:var(--s6)}
.doc-body > section:first-child h2{margin-top:0}
/* Anchored headings must clear the top of the viewport when jumped to. */
.doc-body h2[id]{scroll-margin-top:var(--s4)}

/* ── Steps — used by the docs page, where order is load-bearing ─────────── */

.stepped{counter-reset:step}
.stepped > .st{display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--s3);
               padding:var(--s4) 0; border-top:1px solid var(--border)}
.stepped > .st:first-child{border-top:0; padding-top:var(--s2)}
.stepped > .st::before{
  counter-increment:step; content:counter(step,decimal-leading-zero);
  font-family:var(--mono); font-size:13px; color:var(--link); font-weight:500; padding-top:4px;
}
.stepped h3{margin:0; font-size:19px}
.stepped p{margin-top:var(--s2)}
/* Not scoped to .stepped — the badges label headings elsewhere on the docs
   page too, and scoped they silently rendered as bare words. */
.auto{
  display:inline-block; margin-left:8px; font:500 11px/1 var(--sans); letter-spacing:.08em;
  text-transform:uppercase; color:var(--ok); border:1px solid currentColor;
  border-radius:3px; padding:4px 6px; vertical-align:2px;
}
.auto.manual{color:var(--warn)}

/* A line of code the reader is expected to copy. Light surface, because it is
   an instruction rather than output — the dark panels are reserved for what a
   machine said back. */
.code{
  display:block; margin-top:var(--s2); background:var(--neutral);
  border:1px solid var(--border); border-radius:4px; padding:12px var(--s2);
  font-family:var(--mono); font-size:14px; overflow-x:auto; white-space:pre;
}

/* ── FAQ, shared with the home page ─────────────────────────────────────── */

.faq{border-top:1px solid var(--border); margin-top:var(--s3)}
.faq details{border-bottom:1px solid var(--border)}
.faq summary{cursor:pointer; list-style:none; padding:var(--s3) 0; font-weight:500; font-size:17px;
             display:flex; justify-content:space-between; gap:var(--s3); align-items:baseline}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+"; color:var(--muted); font-weight:400; font-size:20px; line-height:1}
.faq details[open] summary::after{content:"–"}
.faq details p{margin:0 0 var(--s3); color:var(--muted); font-size:16px; max-width:68ch}

/* ── Forms ──────────────────────────────────────────────────────────────── */

.field{margin-top:var(--s3)}
.field label{display:block; font-size:14.5px; font-weight:500; margin-bottom:6px}
.field .hint{display:block; font-weight:400; color:var(--muted); margin-top:2px}
.field input, .field select, .field textarea{
  width:100%; font:400 16px/1.5 var(--sans); color:var(--ink); background:var(--bg);
  border:1px solid var(--border); border-radius:4px; padding:11px 12px;
  transition:border-color 120ms ease;
}
.field textarea{min-height:150px; resize:vertical}
.field input:hover, .field select:hover, .field textarea:hover{border-color:#CFD6E2}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible{
  outline:2px solid var(--blue); outline-offset:1px; border-color:var(--blue);
}
.field [aria-invalid="true"]{border-color:var(--bad)}
.field .err{display:block; margin-top:6px; font-size:14px; color:var(--bad)}
.field .err:empty{display:none}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields that are not rendered — and tabindex/aria-hidden keep it away from
   anyone using a keyboard or a screen reader, who would otherwise be the only
   real people who could fill it in and get silently discarded. */
.trap{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}

form .actions{margin-top:var(--s4); display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap}
form button.btn{border:0; cursor:pointer; font-family:var(--sans)}
form button.btn[disabled]{opacity:.55; cursor:default}
.form-status{margin-top:var(--s3); font-size:15.5px}
.form-status:empty{display:none}
.form-status.ok{color:var(--ok)}
.form-status.bad{color:var(--bad)}
.sent{border:1px solid var(--ok); border-radius:4px; padding:var(--s3); margin-top:var(--s3)}
.sent h3{margin:0 0 var(--s1); font-size:18px}
.sent p{margin:0; color:var(--muted)}

/* ── Skip link ──────────────────────────────────────────────────────────── */

.skip{position:absolute; left:-9999px; top:0; background:var(--blue); color:#fff;
      padding:10px 16px; border-radius:0 0 4px 0; z-index:10}
.skip:focus{left:0; text-decoration:none}

/* ── Footer ─────────────────────────────────────────────────────────────── */

footer{border-top:1px solid var(--border); margin-top:var(--s10); padding:var(--s8) 0}
/* Was repeat(3,1fr) — Product, Support, Legal. A Guides column was split out
   when the guide count reached six and Product had become a list of eleven
   links pretending to be one idea. */
/* minmax(0,1fr), not 1fr. A grid item's default min-width is auto, so the
   longest link label ("The same sitemap on every domain") set the column's
   floor and pushed Legal onto a second row. */
footer .cols{display:grid; grid-template-columns:1.2fr repeat(4,minmax(0,1fr)); gap:var(--s4)}
footer .cols h2{margin:0 0 var(--s2); font:500 12px/1 var(--sans); letter-spacing:.15em;
                text-transform:uppercase; color:var(--muted); max-width:none}
footer .cols ul{list-style:none; margin:0; padding:0; max-width:none; font-size:15px}
footer .cols li{margin:0 0 9px}
footer .cols a{color:var(--muted)}
footer .cols a:hover{color:var(--ink)}
footer .sig .brand{margin-bottom:var(--s2)}
footer .sig p{margin:0; color:var(--muted); font-size:15px; max-width:32ch}
footer .base{margin-top:var(--s8); padding-top:var(--s3); border-top:1px solid var(--border)}
footer .fine{margin:0; color:var(--muted); font-size:13.5px; max-width:78ch}
footer .fine + .fine{margin-top:var(--s1)}
/* Kept so an un-migrated page still renders a sane footer. */
footer .links{display:flex; gap:var(--s3); font-size:15px; flex-wrap:wrap}
footer .links a{color:var(--muted)}
footer .links a:hover{color:var(--ink)}

/* ── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width:900px){
  /* The contents list stops earning its column and becomes a lid on the page.
     minmax(0,1fr), never a bare 1fr: 1fr means minmax(AUTO,1fr), so the track
     refuses to shrink below its content and the longest contents list — the
     privacy policy's — pushed the page 269px wider than the phone. */
  .doc{grid-template-columns:minmax(0,1fr); gap:var(--s4)}
  .toc{position:static; border-bottom:1px solid var(--border); padding-bottom:var(--s3)}
  .toc ul{display:flex; flex-wrap:wrap; gap:0 var(--s3)}
  .toc a{border-left:0; padding:4px 0}
  footer .cols{grid-template-columns:1fr 1fr; gap:var(--s4)}
  footer .sig{grid-column:1 / -1}
}
@media (max-width:820px){
  .plans{grid-template-columns:1fr}
}
@media (max-width:640px){
  body{font-size:16px}
  header .wrap{height:auto; padding-top:var(--s2); padding-bottom:var(--s2); gap:var(--s2)}
  .brand .sub{display:none}
  .brand .name{font-size:18px}
  header nav{gap:var(--s2); font-size:14px}
  header nav .long{display:none}
  header nav .short{display:inline}
  /* Four nav items plus the wordmark overflow 390px and the whole page then
     scrolls sideways. Pricing and the call to action are the two that have a
     job here; Docs and Contact are one thumb away in the footer. */
  header nav a.plain.secondary{display:none}
  .page{padding:var(--s6) 0 var(--s4)}
}

@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  *{transition-duration:0.01ms !important; animation-duration:0.01ms !important}
}

/* The viewer's theme is not ours to assume, but this is a light-mode brand and
   the source blocks are drawn for it. Only the surfaces shift, so text stays
   legible in a dark UA without the palette becoming something else. The source
   blocks are already dark and are deliberately left alone. */
@media (prefers-color-scheme: dark){
  :root{
    --bg:#0E1726; --neutral:#151F33; --border:#243046;
    --ink:#E7ECF4; --muted:#9AA7BD; --slate:#E7ECF4;
    --source:#060D18; --source-rule:#1B2739;
    /* --ok already clears 4.5:1 against this background; --bad does not, so it
       lifts to the salmon the source panels use for the same "wrong" meaning. */
    --ok-text:#16A34A; --bad-text:#FF8F8F;
    /* The same blue the source panels already use for a "correct" value, so
       this is a value the palette had rather than a fifth blue. 8.2:1 here. */
    --link:#7FB0FF;
  }
  .btn.ghost:hover{border-color:#2F3D57}
  /* The mark is two-tone: a blue half that reads on any background, and a dark
     half that disappears on a dark one. The dark half is drawn with
     `currentColor`, so lifting it is a matter of setting a colour rather than
     matching a hex.
     This replaced `.brand svg [fill="#182B4D"]{fill:#E7ECF4}`, which was one
     logo revision away from silently matching nothing — the exact failure
     LOGO-SWAP.md was written to prevent. A selector that has to be kept in step
     with an artwork's fill value will eventually not be. */
  .brand svg{color:#E7ECF4}
}
