@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url(/fonts/geist-mono-400.woff2) format("woff2"); }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url(/fonts/geist-mono-500.woff2) format("woff2"); }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url(/fonts/geist-mono-700.woff2) format("woff2"); }
@font-face { font-family: "Ripper Docs Inter"; font-style: normal; font-weight: 100 900; font-display: optional; src: url(/fonts/inter-latin-wght-normal.woff2) format("woff2-variations"); 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: "Ripper Docs JetBrains Mono"; font-style: normal; font-weight: 100 800; font-display: swap; src: url(/fonts/jetbrains-mono-latin-wght-normal.woff2) format("woff2-variations"); 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; }
/* The docs site — a calm reading column with the nav beside it; the runtime never renders here.
   APPEARANCE: two treatments from the design system, Terminal (the default) and Light. Every colour below is a
   token vendored from @ripper-ai/design-tokens into src/appearance-tokens.json, which records the source commit
   and the sha256 of the file the values were read from. tests/unit/appearance.test.ts holds this file to that
   record, computes AA contrast for every token in both appearances, and refuses a colour written directly. */
:root { --docs-bg: #090909; --docs-code-bg: #0f0f0f; --docs-text: #ececec; --docs-muted: #9a9a9e; --docs-border: #1b1b1d; --docs-accent: #3ffc7e; --docs-syn-string: #f59e0b; --docs-syn-key: #2dd4bf; --docs-syn-number: #c084fc; --docs-syn-keyword: #f472b6; --docs-syn-title: #3ffc7e; --docs-syn-comment: #9a9a9e; }
:root[data-appearance="light"] { --docs-bg: #ffffff; --docs-code-bg: #f4f6f8; --docs-text: #1f2933; --docs-muted: #5b6675; --docs-border: #d8dde4; --docs-accent: #15803d; --docs-syn-string: #b45309; --docs-syn-key: #0f766e; --docs-syn-number: #7e22ce; --docs-syn-keyword: #be185d; --docs-syn-title: #15803d; --docs-syn-comment: #5b6675; }
/* Prose in Inter; the system sans behind it is what a first visit shows if Inter is not ready in time (font-display:
   optional), so it is never a reflow. The faces themselves are generated into docs.css by scripts/build-site.mjs. */
html, body { margin: 0; background: var(--docs-bg); color: var(--docs-text); font-family: "Ripper Docs Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.55; }
/* WRAPS. The header holds three things since the Appearance control arrived, and on a phone they do not fit one
   row. Without wrapping they overlapped instead of shrinking: at 390px "Open the console" sat on top of the Light
   radio and took its taps, so the control was visible and could not be used (caught by the e2e on chromium-mobile). */
.docs-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; row-gap: 8px; column-gap: 16px; padding: 12px 24px; border-bottom: 1px solid var(--docs-border); }
.docs-brand { font-weight: 700; color: var(--docs-text); text-decoration: none; }
.docs-sign-in { color: var(--docs-accent); }
/* The Appearance control, in the header so it is findable on every page. `:not([hidden])` is load-bearing: the
   HTML ships it hidden so that with scripting off there is no radio that changes nothing, and an unconditional
   display rule here would override the hidden attribute and show that dead control anyway. */
.docs-appearance:not([hidden]) { display: flex; align-items: center; gap: 10px; }
.docs-appearance { margin: 0; padding: 0; border: 0; min-width: 0; font-size: 0.85rem; }
.docs-appearance legend { float: left; padding: 0; margin-right: 2px; color: var(--docs-muted); }
.docs-appearance label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; color: var(--docs-text); }
.docs-appearance input { margin: 0; accent-color: var(--docs-accent); }
/* :focus, not :focus-visible, for the same WebKit reason as the picker below: it drops :focus-visible from a radio
   the arrow keys move focus to. The accent outline passes the 3:1 a focus indicator needs in both appearances. */
.docs-appearance label:has(input:focus) { outline: 3px solid var(--docs-accent); outline-offset: 2px; border-radius: 4px; }
.docs-shell { display: grid; grid-template-columns: 220px minmax(0, 760px); gap: 40px; max-width: 1080px; margin: 0 auto; padding: 24px; }
.docs-nav ul { list-style: none; margin: 0; padding: 0; }
/* the IA skeleton: section and group labels (text, not headings — the page's own h1 is the first heading) */
.docs-nav__section { margin: 20px 0 4px; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--docs-muted); }
.docs-nav__section:first-child { margin-top: 0; }
.docs-nav__group { margin: 10px 0 2px; font-size: 0.9rem; font-weight: 600; color: var(--docs-text); }
/* the skip link: off-screen until it has focus, then the first thing on the page */
.docs-skip { position: absolute; left: -9999px; top: 8px; }
.docs-skip:focus { left: 8px; z-index: 10; padding: 8px 12px; background: var(--docs-bg); color: var(--docs-text); border: 2px solid var(--docs-accent); border-radius: 6px; }
.docs-main:focus { outline: none; }
/* a heading's link to itself: shown on hover or when it has focus, never removed from the tab order */
/* UNDERLINED, not merely a different colour. axe reported link-in-text-block on /js/collect: the anchor's
   muted colour against the heading text is 2.53:1 in Light and 2.37:1 in Terminal, where 3:1 is the minimum
   for telling a link apart by colour alone (WCAG 1.4.1). It fails in BOTH appearances, so the remedy has to
   work in both: raising the contrast would mean a link colour that fights the heading it sits in, and an
   underline distinguishes it by something that is not colour at all, which is what the rule asks for. */
.docs-anchor { margin-left: 6px; color: var(--docs-muted); text-decoration: underline; opacity: 0; }
.docs-main h2:hover .docs-anchor, .docs-main h3:hover .docs-anchor, .docs-anchor:focus { opacity: 1; }
/* THE MEASURE: prose is held to 55ch, and that number was measured rather than assumed. The column is 760px, which
   set continuous prose at a median of 97 characters a line. `ch` is the width of "0", which in Inter is wider than
   an average character, so 70ch still gave about 90. Laying out each page's own prose at each width: 55ch gives a
   median of about 70 characters per full line across /js/collect, /how-it-works and /js/hold. Prose only — tables,
   code blocks and the field reference keep the full column, which is what makes them readable. */
.docs-main p, .docs-main li, .docs-main blockquote { max-width: 55ch; }
.docs-next { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--docs-border); }
/* Prose links: coloured AND underlined. They had no colour at all and rendered in the browser's default #0000ee,
   which passed on white and measured 2.11:1 on the Terminal ground. The underline is not decoration: the accent
   against body text is 1.15:1 in Terminal and 2.94:1 in Light, under the 3:1 needed to tell a link apart by
   colour alone. .docs-anchor is excluded because it has its own muted colour and hover behaviour. */
.docs-main a:not(.docs-anchor), .docs-next a { color: var(--docs-accent); text-decoration: underline; }
/* docs.js's Copy button, added above each code block when the script runs (the page never depends on it) */
.docs-copy { display: flex; justify-content: flex-end; margin: 0 0 4px; }
.docs-copy__button { font: inherit; font-size: 0.85rem; padding: 2px 10px; color: var(--docs-text); background: var(--docs-bg); border: 1px solid var(--docs-border); border-radius: 6px; cursor: pointer; }
.docs-copy__button:hover { border-color: var(--docs-muted); }
.docs-copy__button:focus-visible { outline: 3px solid var(--docs-accent); outline-offset: 2px; }
.docs-copy__status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* build-time highlighting (highlight.js classes; no script). The same hue carries the same meaning in both
   appearances — teal keys, amber strings, purple numbers, pink keywords, green names — so switching appearance
   never changes what a colour means. Every token is AA on the code background; the test computes it. */
.hljs-keyword, .hljs-selector-tag, .hljs-doctag, .hljs-built_in, .hljs-type, .hljs-symbol { color: var(--docs-syn-keyword); }
.hljs-string, .hljs-regexp { color: var(--docs-syn-string); }
.hljs-title, .hljs-title.function_, .hljs-section { color: var(--docs-syn-title); }
.hljs-attr, .hljs-attribute, .hljs-property, .hljs-tag, .hljs-name { color: var(--docs-syn-key); }
.hljs-number, .hljs-literal { color: var(--docs-syn-number); }
.hljs-comment, .hljs-meta { color: var(--docs-syn-comment); font-style: italic; }
/* Variables ($VAR in bash and PHP, names in JavaScript) are identifiers, so they take the key colour. */
.hljs-variable { color: var(--docs-syn-key); }
/* Interpolation inside a string — `${id}`, f"{id}", $"{id}" — is CODE, so it resets to the text colour. Unstyled it
   inherited the string's colour and read as part of the string. */
.hljs-subst { color: var(--docs-text); }
/* PLAIN BY DESIGN, named so the choice is visible rather than an omission. These inherit the text colour:
   punctuation (braces, colons, commas), parameter lists, and the wrapper span around a function whose name and
   keyword are coloured inside it. tests/unit/appearance.test.ts lists exactly these as intentional. */
.hljs-function, .hljs-params, .hljs-punctuation { color: inherit; }
.docs-nav a { display: block; padding: 4px 0; color: var(--docs-muted); text-decoration: none; }
.docs-nav a[aria-current="page"] { color: var(--docs-text); font-weight: 600; }
.docs-main pre { background: var(--docs-code-bg); border: 1px solid var(--docs-border); border-radius: 6px; padding: 12px 16px; overflow-x: auto; }
/* The canon's order (RIPPER-DESIGN-SYSTEM.md, Font Stack): Geist Mono, then JetBrains Mono, then Fira Code, then the
   system monospace. Monospace all the way down, so a face swap never reflows a code block. */
.docs-main code { font-family: "Geist Mono", "Ripper Docs JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
/* The scroll box around every table: the TABLE scrolls, the page does not. Measured at 320px, where the
   errors table overflowed the body by 183px. max-width keeps the box inside the column even when the
   table inside it is far wider. */
.docs-main .docs-table { overflow-x: auto; max-width: 100%; }
/* The accent, not a separate focus colour: --docs-focus was never defined, so this always fell back to a blue
   that belonged to neither appearance. The accent passes the 3:1 a focus indicator needs in both. */
.docs-main .docs-table:focus-visible { outline: 2px solid var(--docs-accent); outline-offset: 2px; }
.docs-main table { border-collapse: collapse; } .docs-main td, .docs-main th { border: 1px solid var(--docs-border); padding: 4px 8px; text-align: left; }
/* The Quickstart's platform picker: native radios in a fieldset, NO script. Only the checked option's panel shows,
   through :has(); a browser without :has() (or without CSS) shows every panel in sequence. Rules by position, 1-8:
   scripts/build-site.mjs refuses a picker with more options. The checked option is bolder and ringed, not only
   coloured, and the native radio dot stays visible. */
.docs-picker__choices { border: 0; margin: 0 0 12px; padding: 0; min-width: 0; }
.docs-picker__choices legend { padding: 0; }
.docs-picker__choices legend h2 { margin: 0 0 12px; }
.docs-picker__options { display: flex; flex-wrap: wrap; gap: 8px; }
.docs-picker__option { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 6px 12px; border: 1px solid var(--docs-border); border-radius: 6px; cursor: pointer; }
.docs-picker__option input { margin: 0; accent-color: var(--docs-accent); }
.docs-picker__option:has(input:checked) { border-color: var(--docs-accent); box-shadow: inset 0 0 0 1px var(--docs-accent); font-weight: 700; }
/* :focus, not :focus-visible: WebKit drops :focus-visible from a radio the arrow keys move focus to (measured 2026-09-16) */
.docs-picker__option:has(input:focus) { outline: 3px solid var(--docs-accent); outline-offset: 2px; }
.docs-picker__panel > h3:first-child { margin-top: 20px; }
.docs-picker:has(.docs-picker__option:nth-child(1) input:checked) .docs-picker__panel:not(:nth-child(1)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(2) input:checked) .docs-picker__panel:not(:nth-child(2)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(3) input:checked) .docs-picker__panel:not(:nth-child(3)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(4) input:checked) .docs-picker__panel:not(:nth-child(4)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(5) input:checked) .docs-picker__panel:not(:nth-child(5)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(6) input:checked) .docs-picker__panel:not(:nth-child(6)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(7) input:checked) .docs-picker__panel:not(:nth-child(7)) { display: none; }
.docs-picker:has(.docs-picker__option:nth-child(8) input:checked) .docs-picker__panel:not(:nth-child(8)) { display: none; }
/* NARROW: one column that can shrink below its widest child. A bare 1fr is minmax(auto, 1fr), so the longest code
   line set the column's minimum and the whole page, nav included, scrolled sideways at 320px (measured on every page,
   2026-09-16). minmax(0, 1fr) lets the column narrow; a <pre> scrolls inside itself, and long inline code and table
   cells wrap. */
@media (max-width: 720px) {
  .docs-shell { grid-template-columns: minmax(0, 1fr); }
  .docs-main { min-width: 0; }
  /* ONLY when narrow: on the desktop column a table sizes its columns from min-content, and anywhere-wrapping let
     the errors table squeeze its code column until codes broke mid-word (the visual lane caught it, 2026-09-16) */
  .docs-main p code, .docs-main li code, .docs-main td code, .docs-main td { overflow-wrap: anywhere; }
  /* The generated reference headings are a METHOD and a ROUTE — "POST /v1/checkout/sessions/{session_id}/…"
     — one long token with no break opportunity after the method. Measured at 320px: an h3 reported
     scrollWidth 433 in a 272px column and the whole page panned by 137px, nav and all. Narrow-only, for the
     same reason as the rule above: on the desktop column a route should never be broken mid-path. */
  .docs-main h2, .docs-main h3, .docs-main h4 { overflow-wrap: anywhere; }
}
@media (forced-colors: active) { .docs-main pre, .docs-picker__option { border-color: CanvasText; } .docs-picker__option:has(input:checked) { border-width: 3px; } }
