/* ==========================================================================
   Field Notes — Pledgivo documentation theme
   A distinct, light-only technical-reference identity for the docs site.
   Reskins Material for MkDocs; does not reuse the Ember product brand.
   ========================================================================== */

@font-face {
  font-family: "Fraunces";
  font-weight: 500 800;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/fraunces.woff2") format("woff2");
}
/* The product wordmark's own face, and nothing else's. docs/brand-identity.md
   sets `Pledgivo` in Bricolage Grotesque 800 with `ivo` in Ember, and the
   masthead is the one place on this site where the product's identity outranks
   the Field Notes type system. Subset to the eight glyphs of the word — 2 KB,
   against 39 KB for the full latin cut — so it costs the page essentially
   nothing and can never be reached for as a general display face. SIL OFL 1.1;
   see assets/fonts/LICENSES.md. */
@font-face {
  font-family: "Bricolage Grotesque";
  font-weight: 800;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/bricolage800.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/plexsans.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/plexmono400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/plexmono500.woff2") format("woff2");
}

:root,
[data-md-color-scheme="default"] {
  /* Swiss. Neutral paper, true black, and exactly one chromatic colour: a
     single printer's red, used sparingly enough that it still means something.
     The second axis is not a second hue but weight — black carries what a
     generator produced (layer tags, domain rules, entry identity), red carries
     what a person decided (active nav, section ticks, the current heading).
     Danger is therefore separated from the accent by depth and border weight
     rather than by hue, which is the only way to keep one red meaningful. */
  --fn-paper: #efefec;
  --fn-paper-dim: #e4e4e0;
  --fn-panel: #ffffff;
  --fn-ink: #0b0b0b;
  --fn-ink-soft: #494949;
  --fn-ink-faint: #8a8a86;
  --fn-line: #d3d3ce;
  --fn-line-soft: #e3e3de;
  --fn-accent: #d0021b;
  --fn-accent-ink: #a80217;
  --fn-accent-soft: #fbe9eb;
  --fn-accent-soft-line: #efb3bb;
  --fn-warn: #8a6a00;
  --fn-warn-soft: #f4eed8;
  --fn-warn-line: #ddcd9c;
  --fn-danger: #6f0411;
  --fn-danger-soft: #f2e2e4;
  --fn-danger-line: #cfa4ab;
  /* The neutral axis — everything a generator emitted rather than a person. */
  --fn-gen: #0b0b0b;
  --fn-gen-soft: #e9e9e5;
  --fn-gen-line: #b8b8b3;
  --fn-radius: 10px;
  --fn-shadow: 0 1px 2px rgba(11, 11, 11, 0.04), 0 10px 24px -14px rgba(11, 11, 11, 0.16);
  --fn-shadow-pop: 0 18px 44px -18px rgba(11, 11, 11, 0.34);
  --fn-font-display: "Fraunces", Georgia, "Iowan Old Style", serif;
  --fn-font-body: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, sans-serif;
  --fn-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Material variable overrides */
  --md-primary-fg-color: var(--fn-panel);
  --md-primary-fg-color--light: var(--fn-panel);
  --md-primary-fg-color--dark: var(--fn-panel);
  --md-primary-bg-color: var(--fn-ink);
  --md-accent-fg-color: var(--fn-accent-ink);
  --md-accent-fg-color--transparent: var(--fn-accent-soft);
  --md-default-bg-color: var(--fn-paper);
  --md-default-fg-color: var(--fn-ink);
  --md-default-fg-color--light: var(--fn-ink-soft);
  --md-default-fg-color--lighter: var(--fn-ink-faint);
  --md-default-fg-color--lightest: var(--fn-line);
  --md-typeset-color: var(--fn-ink);
  --md-typeset-a-color: var(--fn-accent-ink);
  --md-code-bg-color: var(--fn-paper-dim);
  --md-code-fg-color: var(--fn-ink);
  --md-footer-bg-color: var(--fn-ink);
  --md-footer-fg-color: #f2f1ec;
  --md-footer-fg-color--light: #cfd3da;
}

/* ---------- Base typography ---------- */
body,
.md-typeset {
  font-family: var(--fn-font-body);
  font-size: 0.76rem;
  color: var(--fn-ink);
}
.md-typeset h1,
.md-typeset h2,
.md-typeset h3,
.md-typeset h4 {
  font-family: var(--fn-font-display);
  font-weight: 600;
  color: var(--fn-ink);
  text-wrap: balance;
}
.md-typeset h1 { font-size: 2.1rem; margin: 0 0 1rem; letter-spacing: -0.01em; }
.md-typeset h2 { font-size: 1.5rem; margin: 2.2rem 0 0.9rem; }
.md-typeset h3 { font-family: var(--fn-font-body); font-weight: 600; font-size: 1.05rem; margin: 1.6rem 0 0.6rem; }
.md-typeset a { text-decoration: none; }
.md-typeset a:hover { text-decoration: underline; text-decoration-color: var(--fn-accent-soft-line); }
.md-typeset code,
.md-typeset kbd,
.md-typeset pre {
  font-family: var(--fn-font-mono);
}
.md-typeset code { font-size: 0.72em; border-radius: 4px; }
.md-typeset .highlight pre code { font-size: 0.75rem; line-height: 1.65; }

/* ---------- Header ---------- */
.md-header {
  background: var(--fn-panel);
  color: var(--fn-ink);
  border-bottom: 1px solid var(--fn-line);
  box-shadow: none;
}
.md-header__title {
  font-family: var(--fn-font-display);
  font-weight: 600;
}
.md-header__topic:first-child { font-weight: 600; }
.md-search__input {
  background: var(--fn-paper-dim);
  color: var(--fn-ink);
  border-radius: 8px;
}
.md-search__input::placeholder { color: var(--fn-ink-faint); opacity: 1; }
.md-search__form { border-radius: 8px; }
.md-header__option .md-icon,
.md-header__button:not(.md-logo) { color: var(--fn-ink-soft); }

/* ---------- Sidebar navigation ---------- */
.md-nav { font-family: var(--fn-font-body); }
.md-nav__title {
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  font-weight: 500;
}
.md-nav__item .md-nav__link { color: var(--fn-ink-soft); border-left: 2px solid transparent; border-radius: 6px; }
.md-nav__link:hover { color: var(--fn-ink); background: var(--fn-paper-dim); }
.md-nav__link--active,
.md-nav__item .md-nav__link--active {
  color: var(--fn-accent-ink) !important;
  font-weight: 600;
  border-left-color: var(--fn-accent);
  background: var(--fn-accent-soft);
}
.md-nav__icon { color: var(--fn-ink-faint); }
.md-sidebar__scrollwrap { scrollbar-width: thin; }

/* right-hand "on this page" TOC */
.md-nav--secondary .md-nav__title { display: none; }
.md-nav--secondary::before {
  content: "On this page";
  display: block;
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  padding: 0 0 0.6rem 0.6rem;
}
.md-nav--secondary .md-nav__link { color: var(--fn-ink-soft); font-size: 0.72rem; }
.md-nav--secondary .md-nav__link--active { color: var(--fn-accent-ink); font-weight: 600; }

/* ---------- Layout width ----------
   Material's default 61rem grid leaves most of a normal monitor blank;
   reference docs are full of wide tables, code blocks, and the data-model
   diagram, so give the whole shell (header + content) more room. */
.md-grid { max-width: 90rem; }

/* A page that hides both rails (`hide: [navigation, toc]` — the landing page)
   leaves Material's content block spanning the full grid, so its prose runs from
   the window edge at one measure while its headings and card grids run at
   another, and the whole plate hangs off the left. Re-inserting the rails' own
   width as margins puts the article in exactly the column it occupies on every
   other page, at every viewport, rather than a centred width that has to be
   guessed and then drifts from the real one. Scoped to the wide layout: below
   the breakpoint the rails are the drawer and the content is already full width.

   Keyed off `[hidden]` because Material still renders both sidebar elements on
   such a page and merely hides them — there is no per-page body class to hook,
   and `:only-child` would never match. */
@media screen and (min-width: 76.25em) {
  .md-sidebar--primary[hidden] ~ .md-sidebar--secondary[hidden] ~ .md-content {
    margin-inline: 12.1rem;
  }
}

/* ---------- Content shell ---------- */
.md-content__inner { padding-top: 1.6rem; }
.md-content__inner h1:first-child { margin-top: 0.2rem; }

/* eyebrow via attr_list: {: .fn-eyebrow } */
.fn-eyebrow {
  display: block;
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fn-accent-ink);
  margin-bottom: 0.6rem;
}
.fn-dek {
  font-size: 1.02rem;
  color: var(--fn-ink-soft);
  max-width: 58ch;
}

/* ---- The plate: the eyebrow / title / dek / meta run that opens a page ----

   These four elements are one masthead, not four stray paragraphs, and they
   read as one only if something closes them off from the body that follows.
   A hairline at the reading measure does that: it gives the title block a
   bottom edge, so the first h2 starts a section rather than continuing the
   introduction.

   The rule belongs to whichever of them comes last, which varies by page: a
   narrative page ends at the dek, a generated reference page adds a meta
   strip. Rather than tag the last one in every source file (and re-tag it
   whenever a page gains a strip), each claims the rule and gives it up when
   the other follows it. Browsers without :has() draw one extra hairline
   inside the plate — visible, harmless, and not worth a class per page.

   `.fn-status-strip` is deliberately absent: it reads like a plate element
   but every page that has one puts it mid-document, so giving it the plate's
   closing rule would draw a hairline across the middle of three pages. */
.md-content__inner > .fn-dek,
.md-content__inner > .fn-platemeta {
  padding-bottom: 1.6rem;
  margin-bottom: 2.4rem;
  border-bottom: 1px solid var(--fn-line);
}
.md-content__inner > .fn-dek:has(+ .fn-platemeta) {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: 0;
}

/* Provenance line for a generated page — what produced it and how much of it
   there is. Deliberately no "last built" date: it would rewrite itself on
   every regeneration and put a diff in the repo for a page whose content did
   not change. Everything here is a fact the generator already knows. */
.md-content__inner > .fn-platemeta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 2.2rem;
  margin-top: 1.7rem;
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  line-height: 1.5;
  color: var(--fn-ink-faint);
}
.fn-platemeta > span { white-space: nowrap; }
.fn-platemeta b {
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  margin-right: 0.55rem;
}
/* The values are paths, filenames and counts — monospaced already by the strip
   itself, so Material's inline-code chrome (tinted box, border, padding) only
   fragments a line that reads better as one run of type. */
.md-typeset .fn-platemeta code {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  font-size: 1em;
  color: var(--fn-ink);
}

/* status chip strip */
/* The home page's one line about price. It sits between the calls to action and
   the spec chips because that is where the question lands - a reader who has
   just been told to install something wants to know what installing it costs
   before they read another word. Body-copy size, not dek size: it is an answer,
   not a second headline. */
.md-typeset .fn-free-line {
  max-width: 62ch;
  margin: -.35rem 0 1.5rem;
  font-size: .9rem;
  line-height: 1.6;
  color: var(--fn-ink-soft);
}
.md-typeset .fn-free-line b { color: var(--fn-ink); }

.fn-status-strip { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0 1.6rem; }
.fn-chip {
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  color: var(--fn-ink-soft);
  border: 1px solid var(--fn-line);
  border-radius: 20px;
  padding: 0.2rem 0.7rem;
  background: var(--fn-panel);
}

/* badge (e.g. "maps to: Opportunity") */
.fn-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  color: var(--fn-ink-soft);
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line);
  border-radius: 20px;
  padding: 0.15rem 0.7rem 0.15rem 0.55rem;
  vertical-align: middle;
  margin-left: 0.5rem;
}
.fn-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fn-accent); }

/* ---------- Card grids (native Material "grid cards" markdown) ---------- */
.md-typeset .grid.cards > ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.9rem;
  margin: 1.2rem 0 1.8rem;
}
.md-typeset .grid.cards > ul > li {
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  background: var(--fn-panel);
  box-shadow: var(--fn-shadow);
  padding: 1.1rem 1.2rem;
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.md-typeset .grid.cards > ul > li:hover {
  transform: translateY(-2px);
  border-color: var(--fn-accent-soft-line);
}
.md-typeset .grid.cards > ul > li > p:first-child { margin-top: 0; }
.md-typeset .grid.cards > ul > li strong { font-family: var(--fn-font-display); font-size: 1.02rem; }
.md-typeset .grid.cards > ul > li hr { border-color: var(--fn-line-soft); margin: 0.7rem 0; }

/* plain (non-card) grid, e.g. explore tiles */
.md-typeset .grid:not(.cards) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0.6rem;
  margin: 1rem 0 1.6rem;
}
.md-typeset .grid:not(.cards) > p {
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line-soft);
  border-radius: var(--fn-radius);
  padding: 0.9rem 1.05rem;
  margin: 0;
}

/* ---------- Admonitions ----------
   One shape, four temperatures. Material ships a distinct hue per type — teal
   for tip, blue for note, purple for example — which against a one-ink palette
   reads as eight unrelated colours arriving from somewhere else. Every type's
   title bar and icon are therefore flattened back to the box's own colour
   first, then re-assigned to one of four temperatures that mean something
   here: red for anything the reader must act on, neutral black for anything a
   generator or the platform merely reports, gold for a warning, and the deeper
   red for genuine danger. The left stripe carries the temperature so the
   distinction survives at a glance without a second hue. */
.md-typeset .admonition,
.md-typeset details {
  border-radius: 0 var(--fn-radius) var(--fn-radius) 0;
  border: 1px solid var(--fn-accent-soft-line);
  border-left: 3px solid var(--fn-accent);
  background: var(--fn-accent-soft);
  box-shadow: none;
  font-size: 0.74rem;
}
.md-typeset .admonition-title,
.md-typeset summary {
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fn-accent-ink);
}
/* Flatten Material's per-type title bar and icon. Both of its rules are
   `.md-typeset .<type> > .admonition-title`, so the same three-class shape is
   needed here to reach them — a shorter selector loses however late it loads. */
.md-typeset .admonition > .admonition-title,
.md-typeset details > summary {
  background-color: transparent;
  color: var(--fn-accent-ink);
}
.md-typeset .admonition > .admonition-title::before,
.md-typeset details > summary::before { background-color: var(--fn-accent-ink); }

/* Neutral — what the platform or a generator reports, not what you must do. */
.md-typeset .admonition.tip,
.md-typeset .admonition.success,
.md-typeset .admonition.abstract,
.md-typeset .admonition.example,
.md-typeset .admonition.quote,
.md-typeset details.tip,
.md-typeset details.abstract,
.md-typeset details.example {
  border-color: var(--fn-gen-line);
  border-left-color: var(--fn-gen);
  background: var(--fn-gen-soft);
}
.md-typeset .tip > .admonition-title,
.md-typeset .success > .admonition-title,
.md-typeset .abstract > .admonition-title,
.md-typeset .example > .admonition-title,
.md-typeset .quote > .admonition-title,
.md-typeset details.tip > summary,
.md-typeset details.abstract > summary,
.md-typeset details.example > summary { color: var(--fn-gen); }
.md-typeset .tip > .admonition-title::before,
.md-typeset .success > .admonition-title::before,
.md-typeset .abstract > .admonition-title::before,
.md-typeset .example > .admonition-title::before,
.md-typeset .quote > .admonition-title::before,
.md-typeset details.tip > summary::before,
.md-typeset details.abstract > summary::before,
.md-typeset details.example > summary::before { background-color: var(--fn-gen); }

/* `note` and `info` are the two types with no re-assignment above: the intent
   is that they keep the default red set on `.md-typeset .admonition`. They did
   not. Material's own `.md-typeset .note` ties that selector on specificity and
   wins on load order, so every note on the site was outlined in its stock blue
   — a hue this palette does not contain, and the one place the theme leaked.
   One extra class settles it; the rest of the box was already correct. */
.md-typeset .admonition.note,
.md-typeset .admonition.info,
.md-typeset details.note,
.md-typeset details.info {
  border-color: var(--fn-accent-soft-line);
  border-left-color: var(--fn-accent);
}

.md-typeset .admonition.warning,
.md-typeset .admonition.caution,
.md-typeset .admonition.attention,
.md-typeset details.warning {
  border-color: var(--fn-warn-line);
  border-left-color: var(--fn-warn);
  background: var(--fn-warn-soft);
}
.md-typeset .warning > .admonition-title,
.md-typeset .caution > .admonition-title,
.md-typeset .attention > .admonition-title,
.md-typeset details.warning > summary { color: var(--fn-warn); }
.md-typeset .warning > .admonition-title::before,
.md-typeset .caution > .admonition-title::before,
.md-typeset .attention > .admonition-title::before,
.md-typeset details.warning > summary::before { background-color: var(--fn-warn); }

.md-typeset .admonition.danger,
.md-typeset .admonition.failure,
.md-typeset .admonition.bug,
.md-typeset details.danger {
  border-color: var(--fn-danger-line);
  border-left-color: var(--fn-danger);
  background: var(--fn-danger-soft);
}
.md-typeset .danger > .admonition-title,
.md-typeset .failure > .admonition-title,
.md-typeset .bug > .admonition-title,
.md-typeset details.danger > summary { color: var(--fn-danger); }
.md-typeset .danger > .admonition-title::before,
.md-typeset .failure > .admonition-title::before,
.md-typeset .bug > .admonition-title::before,
.md-typeset details.danger > summary::before { background-color: var(--fn-danger); }

/* ---------- Code blocks ---------- */
.md-typeset pre > code,
.md-typeset .highlight {
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
}
.md-typeset .highlight { padding: 0; }
.md-typeset .highlight pre { margin: 0; padding: 0.9rem 1.1rem; }
.md-clipboard { color: var(--fn-ink-faint); }
.md-clipboard:hover { color: var(--fn-accent-ink); }

/* ---------- Tables ---------- */
.md-typeset table:not([class]) {
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  overflow: hidden;
}
.md-typeset table:not([class]) th {
  font-family: var(--fn-font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  background: var(--fn-paper-dim);
  border-bottom: 1px solid var(--fn-line);
}
.md-typeset table:not([class]) td { font-size: 0.74rem; }
.md-typeset table:not([class]) tr:nth-child(2n) { background: transparent; }
.md-typeset table:not([class]) code { color: var(--fn-accent-ink); }

/* ---------- Footer ---------- */
/* The footer is rebuilt in overrides/partials/footer.html and styled in the
   band below; Material's own dark ground is not used. */
.md-footer { background: transparent; }

/* ---------- Prototype/version note ---------- */
.md-typeset .fn-figure {
  border: 1px dashed var(--fn-line);
  border-radius: var(--fn-radius);
  background: var(--fn-paper-dim);
  padding: 2.4rem 1.5rem;
  text-align: center;
  color: var(--fn-ink-faint);
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  margin: 0.8rem 0 0.3rem;
}
.md-typeset .fn-figure-caption {
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  color: var(--fn-ink-faint);
  margin: 0 0 1.6rem;
}
.md-typeset .fn-figure-caption b { color: var(--fn-ink-soft); font-weight: 500; }

.md-typeset .fn-shot {
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  overflow: hidden;
  margin: 0.8rem 0 0.3rem;
  box-shadow: 0 2px 14px rgba(28, 43, 70, 0.08);
  background: var(--fn-paper-dim);
}
.md-typeset .fn-shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* Chapter markers under a film. Each button seeks the <video> above it, and
   without JS they are inert labels, so nothing is lost. */
/* `ul.` and the `:not([hidden])` are load-bearing, not noise. Material sets
   `.md-typeset ul:not([hidden]) { display: flow-root }`, and that `:not()`
   carries an attribute selector's weight — so a plain `.md-typeset
   .fn-chapters` loses the display and the pills stack one per line instead of
   wrapping into a row. Matching the specificity and landing later in the
   cascade is what wins it back. */
.md-typeset ul.fn-chapters:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.7rem 0 0.4rem;
  padding: 0;
  list-style: none;
}
.md-typeset .fn-chapters li { margin: 0; }
.md-typeset .fn-chapters button {
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  color: var(--fn-ink-soft);
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line);
  border-radius: 999px;
  padding: 0.28rem 0.7rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.md-typeset .fn-chapters button:hover {
  border-color: var(--fn-accent);
  color: var(--fn-accent);
}
.md-typeset .fn-chapters button b {
  font-weight: 500;
  color: var(--fn-ink-faint);
}

/* ==========================================================================
   The overview film — landing page only

   The walkthrough videos above keep the browser's own controls: they are
   support material inside a page of prose, and a native bar is the right
   amount of chrome for that. The landing film is the opposite — it is the
   page's one piece of motion, seen by a reader who has not decided anything
   yet, and the native bar arrives as a slab of Chrome-grey with a blue accent
   in a page that is paper, ink and one printer's red. It also cannot show
   chapters, which is the one thing an 89-second overview needs.

   Everything below .fn-vp is inert until assets/js/field-notes-video.js has
   built the controls it styles; the film plays with its native bar until then.
   ========================================================================== */

.md-typeset .fn-film { margin: 1.1rem 0 1.9rem; }
/* The landing film sits between the proof strip and the first numbered
   section, so it needs the section rhythm's air rather than the tighter
   in-body spacing a walkthrough inside a guide wants. */
.md-typeset .fn-film--home {
  margin: 2.6rem 0 3.6rem;
  /* Clears the sticky masthead when the hero's Watch button centres the film. */
  scroll-margin-top: 5.5rem;
}

/* A caption line, not a heading — it names the film and states its cost in
   time before anyone commits to it, in the same mono the rest of the site uses
   for metadata. */
.md-typeset .fn-film__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.55rem;
  margin: 0 0 0.6rem;
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fn-accent);
}
.md-typeset .fn-film__cap span:last-child {
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--fn-ink-faint);
}

.md-typeset .fn-film .fn-chapters { margin: 0.7rem 0 0; }
/* The chapter the film is currently in, set by field-notes-video.js. */
.md-typeset .fn-chapters button[aria-current="true"] {
  border-color: var(--fn-accent);
  background: var(--fn-accent-soft);
  color: var(--fn-accent-ink);
}
.md-typeset .fn-chapters button[aria-current="true"] b { color: var(--fn-accent); }

/* ---------- The frame ---------- */

/* No fixed aspect ratio: the landing film is 16:9 but both screen-capture
   walkthroughs are 1600x1228, and a 16:9 frame would crop a third off the
   Salesforce UI they exist to show. The <video> carries width/height
   attributes instead, so the browser reserves the right box before the file
   loads and nothing reflows underneath the reader. */
.md-typeset .fn-vp {
  position: relative;
  background: var(--fn-ink);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  overflow: hidden;
  /* The accent ring every player wears, landing film and walkthroughs alike.
     An outline rather than a border or a box-shadow: outline-offset leaves a
     genuinely transparent gap, so the ring reads the same over paper, over a
     white panel, and over the sketch background on the landing page — a
     box-shadow ring would have to paint that gap an opaque colour and pick the
     wrong one somewhere. It also sits outside the layout box, so adding it
     moves nothing on the page. */
  outline: 2px solid var(--fn-accent);
  outline-offset: 3px;
  box-shadow: 0 2px 14px rgba(28, 43, 70, 0.08);
  isolation: isolate;
}
.md-typeset .fn-vp video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--fn-ink);
}
/* Fullscreen hands the frame the whole screen, which is the wrong shape for
   every film here — letterbox rather than stretch or crop. */
.md-typeset .fn-vp:fullscreen { display: grid; place-items: center; background: #000; outline: none; }
.md-typeset .fn-vp:fullscreen video { width: auto; max-width: 100vw; max-height: 100vh; }
/* The frame takes focus so the keyboard shortcuts have somewhere to land,
   which means it draws the browser's default ring — a blue this palette does
   not contain. The accent ring above is always on, so keyboard focus has to be
   a visible step beyond it rather than the same line: thicker, and standing
   further off the frame. */
.md-typeset .fn-vp:focus { outline: 2px solid var(--fn-accent); outline-offset: 3px; }
.md-typeset .fn-vp:focus-visible { outline-width: 3px; outline-offset: 6px; }

/* A tight halo behind the play disc, not a full scrim. The film's frames run
   from near-white to near-black; a scrim strong enough to lift the disc off
   the dark ones turns the light ones to mud, so the disc keeps its own
   contrast and this only stops it dissolving into a pale frame. */
.md-typeset .fn-vp::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(74px 74px at 50% 50%,
    rgba(6, 10, 20, 0.2), rgba(6, 10, 20, 0.06) 62%, transparent 78%);
  transition: opacity 0.35s ease;
}
.md-typeset .fn-vp.is-playing::before { opacity: 0; }

/* The flash the hero's Watch button fires, so the eye lands on the thing that
   just scrolled into place rather than hunting for it. */
@keyframes fn-vp-arrive {
  from { box-shadow: 0 0 0 0 rgba(208, 2, 27, 0.55), 0 2px 14px rgba(28, 43, 70, 0.08); }
  to   { box-shadow: 0 0 0 14px rgba(208, 2, 27, 0), 0 2px 14px rgba(28, 43, 70, 0.08); }
}
.md-typeset .fn-vp.is-arriving { animation: fn-vp-arrive 1.1s ease-out; }

/* ---------- Play affordance ---------- */

/* Paper disc on ink, so it reads as belonging to this site rather than to the
   browser. */
.md-typeset .fn-vp__big {
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: 2;
  width: 88px;
  height: 88px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  background: rgba(239, 239, 236, 0.94);
  color: var(--fn-ink);
  box-shadow: 0 10px 40px -8px rgba(0, 0, 0, 0.6);
  transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.3, 1), background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}
.md-typeset .fn-vp__big svg { width: 26px; height: 26px; fill: currentColor; margin-left: 4px; }
.md-typeset .fn-vp__big:hover { transform: scale(1.07); background: var(--fn-accent); color: #fff; }
.md-typeset .fn-vp__big:focus-visible { outline: 3px solid var(--fn-accent); outline-offset: 4px; }
.md-typeset .fn-vp.is-playing .fn-vp__big { opacity: 0; pointer-events: none; transform: scale(0.8); }

/* A ring that ticks round while the film buffers, so a cold start on a 3.7 MB
   file never looks like a dead button. */
.md-typeset .fn-vp__big::after {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 2px solid transparent;
  border-top-color: var(--fn-accent);
  opacity: 0;
}
.md-typeset .fn-vp.is-loading .fn-vp__big::after { opacity: 1; animation: fn-vp-spin 0.8s linear infinite; }
@keyframes fn-vp-spin { to { transform: rotate(360deg); } }

/* ---------- Control bar ---------- */

.md-typeset .fn-vp__bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: 1.5rem 0.8rem 0.55rem;
  /* Short and soft on purpose. A tall, near-opaque scrim is the safe choice for
     dark footage and the wrong one here: the overview film is cream throughout,
     so the original 2.6rem ramp to rgba(0,0,0,.86) read as a grey smudge across
     the bottom fifth of the frame rather than as a control bar. This is the
     least ink that still keeps white glyphs legible over a pale frame. */
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.74) 0%, rgba(0, 0, 0, 0.36) 55%, transparent 100%);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.22s ease, transform 0.22s ease;
  pointer-events: none;
}
.md-typeset .fn-vp:hover .fn-vp__bar,
.md-typeset .fn-vp.is-paused .fn-vp__bar,
.md-typeset .fn-vp:focus-within .fn-vp__bar { opacity: 1; transform: none; pointer-events: auto; }
/* Quiet playback with no pointer: the bar retreats, unless focus is inside it. */
.md-typeset .fn-vp.is-idle:not(:focus-within) .fn-vp__bar { opacity: 0; pointer-events: none; }
/* A player that has never been played shows its poster and nothing else. The
   `is-paused` rule above cannot tell "stopped mid-film" from "not yet started",
   and the second is the state every reader meets first — the poster is a
   designed still, and the big play disc is already the affordance. Hovering or
   tabbing in still brings the bar up, so nothing is hidden from someone
   reaching for it. */
.md-typeset .fn-vp.is-fresh:not(:hover):not(:focus-within) .fn-vp__bar {
  opacity: 0;
  pointer-events: none;
}

.md-typeset .fn-vp__track {
  position: relative;
  height: 3px;
  margin: 0 0 0.5rem;
  border-radius: 2px;
  background: rgba(239, 239, 236, 0.28);
  cursor: pointer;
}
/* A 3px line is honest about how much room the film has, but it is a hopeless
   click target — the padding gives it a 16px one without moving the line. */
.md-typeset .fn-vp__track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  bottom: -7px;
}
.md-typeset .fn-vp__track:focus-visible { outline: 2px solid var(--fn-accent); outline-offset: 6px; }
.md-typeset .fn-vp__buffered,
.md-typeset .fn-vp__played {
  position: absolute;
  left: 0;
  top: 0;
  height: 3px;
  border-radius: 2px;
  width: 0;
}
.md-typeset .fn-vp__buffered { background: rgba(239, 239, 236, 0.3); }
.md-typeset .fn-vp__played { background: var(--fn-accent); }
.md-typeset .fn-vp__knob {
  position: absolute;
  top: 50%;
  left: 0;
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  background: var(--fn-paper);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.md-typeset .fn-vp__track:hover .fn-vp__knob,
.md-typeset .fn-vp__track:focus-visible .fn-vp__knob { opacity: 1; }
.md-typeset .fn-vp__tip {
  position: absolute;
  bottom: 14px;
  transform: translateX(-50%);
  padding: 0.14rem 0.4rem;
  border-radius: 4px;
  background: rgba(11, 11, 11, 0.9);
  color: var(--fn-paper);
  font-family: var(--fn-font-mono);
  font-size: 0.58rem;
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
}
.md-typeset .fn-vp__track:hover .fn-vp__tip { opacity: 1; }
/* Chapter boundaries, drawn onto the track once the duration is known. */
.md-typeset .fn-vp__tick {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 9px;
  border-radius: 1px;
  background: rgba(239, 239, 236, 0.55);
  pointer-events: none;
}

.md-typeset .fn-vp__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--fn-paper);
}
.md-typeset .fn-vp__sp { flex: 1; }
.md-typeset .fn-vp__btn {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.md-typeset .fn-vp__btn svg { width: 1rem; height: 1rem; fill: currentColor; }
.md-typeset .fn-vp__btn:hover { background: rgba(239, 239, 236, 0.16); }
.md-typeset .fn-vp__btn:focus-visible { outline: 2px solid var(--fn-accent); outline-offset: 2px; }
.md-typeset .fn-vp__btn--cc {
  width: auto;
  padding: 0 0.34rem;
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
}
.md-typeset .fn-vp__btn--cc[aria-pressed="true"] { background: var(--fn-accent); color: #fff; }
.md-typeset .fn-vp__time {
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  color: rgba(239, 239, 236, 0.72);
  white-space: nowrap;
}
.md-typeset .fn-vp__time b { font-weight: 500; color: var(--fn-paper); }
/* The chapter the film is currently in, spelled out beside the clock. */
.md-typeset .fn-vp__now {
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  color: rgba(239, 239, 236, 0.68);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media screen and (max-width: 45em) {
  /* At phone width the clock and the chapter name cannot both fit beside three
     buttons; the pills below the film carry the chapter anyway. */
  .md-typeset .fn-vp__now { display: none; }
}

/* ==========================================================================
   Step viewer — a run of screenshots collapsed into one click-through panel.
   Built by assets/js/step-viewer.js. Everything under .fn-steps (no .is-ready)
   is the no-JS fallback: the same figures, stacked, as a plain screenshot run.
   ========================================================================== */
.md-typeset .fn-steps { margin: 1.1rem 0 1.7rem; --fn-step-dwell: 7s; }
.md-typeset .fn-step { margin: 0 0 1.2rem; }
.md-typeset .fn-step img { display: block; width: 100%; height: auto; border: 1px solid var(--fn-line); border-radius: var(--fn-radius); }
.md-typeset .fn-step figcaption,
.md-typeset .fn-steps__cap {
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  line-height: 1.6;
  color: var(--fn-ink-faint);
  margin: 0.5rem 0 0;
}
.md-typeset .fn-step figcaption b,
.md-typeset .fn-steps__cap b { color: var(--fn-ink-soft); font-weight: 500; }

.md-typeset .fn-steps.is-ready {
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  background: var(--fn-panel);
  box-shadow: 0 2px 14px rgba(28, 43, 70, 0.08);
  overflow: hidden;
}
.md-typeset .fn-steps.is-ready .fn-step { margin: 0; }
/* The theme styles `.md-typeset figure` with a display value, which outranks the
   user-agent [hidden] rule — without this the "hidden" steps still take space. */
.md-typeset .fn-steps .fn-step[hidden],
.md-typeset .fn-steps .fn-steps__cap[hidden] { display: none; }
.md-typeset .fn-steps.is-ready .fn-step img { border: 0; border-radius: 0; aspect-ratio: 8 / 5; object-fit: contain; background: var(--fn-paper-dim); }

/* ---- header: counter, step title, transport, progress meter ---- */
.fn-steps__bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.4rem 0.45rem 0.4rem 0.75rem;
  background: var(--fn-paper-dim);
  border-bottom: 1px solid var(--fn-line);
}
.fn-steps__count {
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  color: var(--fn-ink-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fn-steps__count b { color: var(--fn-accent); font-weight: 500; }
.fn-steps__count i { font-style: normal; }
.fn-steps__title {
  flex: 1;
  min-width: 0;
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  color: var(--fn-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fn-steps__nav { display: flex; gap: 0.25rem; }
.fn-steps__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border: 1px solid var(--fn-line);
  border-radius: 6px;
  background: var(--fn-panel);
  color: var(--fn-ink-soft);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.fn-steps__btn svg { width: 0.8rem; height: 0.8rem; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.fn-steps__btn svg.fn-steps__glyph-play,
.fn-steps__btn svg.fn-steps__glyph-pause { fill: currentColor; stroke: none; }
.fn-steps__btn:hover { border-color: var(--fn-accent); color: var(--fn-accent); }
.fn-steps.is-playing .fn-steps__btn--play {
  color: var(--fn-accent);
  border-color: var(--fn-accent-soft-line);
  background: var(--fn-accent-soft);
}
.fn-steps__meter { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; overflow: hidden; }
.fn-steps__meter i { display: block; width: 0; height: 100%; background: var(--fn-accent); }
.fn-steps.is-playing .fn-steps__meter i { animation: fn-step-meter var(--fn-step-dwell) linear forwards; }
.fn-steps.is-suspended .fn-steps__meter i { animation-play-state: paused; }
@keyframes fn-step-meter { from { width: 0; } to { width: 100%; } }

/* ---- stage: the screenshot itself, click anywhere for the next step ---- */
.fn-steps__stage { position: relative; cursor: pointer; background: var(--fn-paper-dim); outline: none; }
.fn-steps__stage:focus-visible { box-shadow: inset 0 0 0 2px var(--fn-accent-soft-line); }
.fn-steps__hint {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  color: #fff;
  background: rgba(22, 31, 53, 0.74);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.fn-steps__stage:hover .fn-steps__hint,
.fn-steps__stage:focus-visible .fn-steps__hint { opacity: 1; }
.fn-steps.is-last .fn-steps__hint::after { content: " (back to 1)"; color: rgba(255, 255, 255, 0.6); }

/* ---- caption: fixed floor so the panel does not jump between steps ---- */
.md-typeset .fn-steps__caps { padding: 0.65rem 0.85rem 0.15rem; min-height: 4.6rem; }
.md-typeset .fn-steps__caps .fn-steps__cap { margin: 0; }

/* ---- rail: every step reachable in one click, progress readable at a glance ---- */
.md-typeset ol.fn-steps__rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.28rem;
  list-style: none;
  margin: 0;
  padding: 0.5rem 0.8rem 0.65rem;
  border-top: 1px solid var(--fn-line-soft);
  background: var(--fn-panel);
}
.md-typeset ol.fn-steps__rail li { margin: 0; }
.md-typeset ol.fn-steps__rail li::marker { content: none; }
.fn-steps__rail button {
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--fn-ink-faint);
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: 5px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.fn-steps__rail button:hover { border-color: var(--fn-accent); color: var(--fn-accent); }
.fn-steps__rail button.is-seen { color: var(--fn-ink-soft); background: var(--fn-paper-dim); }
.fn-steps__rail button.is-on { color: #fff; background: var(--fn-accent); border-color: var(--fn-accent); }

@media (prefers-reduced-motion: reduce) {
  .fn-steps.is-playing .fn-steps__meter i { animation: none; width: 100%; }
  .fn-steps__btn, .fn-steps__rail button, .fn-steps__hint { transition: none; }
}

/* ==========================================================================
   Interactive data-model diagram (Cytoscape.js)
   ========================================================================== */
.dm-app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 0;
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  background: var(--fn-panel);
  overflow: hidden;
  margin: 1rem 0 1.8rem;
  box-shadow: var(--fn-shadow);
}
.dm-canvas-wrap { position: relative; border-right: 1px solid var(--fn-line); }
#dm-cy {
  width: 100%;
  height: 640px;
  background: var(--fn-paper);
  background-image: linear-gradient(var(--fn-line-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--fn-line-soft) 1px, transparent 1px);
  background-size: 28px 28px;
}
.dm-toolbar {
  position: absolute;
  top: 12px;
  left: 12px;
  right: 12px;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  z-index: 5;
  flex-wrap: wrap;
}
.dm-search {
  font-family: var(--fn-font-mono);
  font-size: 0.7rem;
  border: 1px solid var(--fn-line);
  background: var(--fn-panel);
  border-radius: 8px;
  padding: 0.35rem 0.7rem;
  color: var(--fn-ink);
  min-width: 180px;
}
.dm-search:focus { outline: none; border-color: var(--fn-accent-soft-line); }
.dm-domain-filters { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.dm-domain-btn {
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.03em;
  border: 1px solid var(--fn-line);
  background: var(--fn-panel);
  border-radius: 20px;
  padding: 0.28rem 0.65rem;
  color: var(--fn-ink-soft);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.dm-domain-btn .sw { width: 7px; height: 7px; border-radius: 50%; }
.dm-domain-btn.is-off { opacity: 0.4; }
.dm-fullscreen-btn { margin-left: auto; }
.dm-app:fullscreen,
.dm-app:-webkit-full-screen {
  max-width: none;
  height: 100vh;
  margin: 0;
  border-radius: 0;
}
.dm-app:fullscreen .dm-canvas-wrap,
.dm-app:-webkit-full-screen .dm-canvas-wrap,
.dm-app:fullscreen #dm-cy,
.dm-app:-webkit-full-screen #dm-cy,
.dm-app:fullscreen .dm-panel,
.dm-app:-webkit-full-screen .dm-panel {
  height: 100%;
  max-height: none;
}
.dm-zoom-controls {
  position: absolute;
  bottom: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 5;
}
.dm-zoom-btn {
  width: 30px;
  height: 30px;
  border-radius: 7px;
  border: 1px solid var(--fn-line);
  background: var(--fn-panel);
  color: var(--fn-ink);
  font-family: var(--fn-font-mono);
  font-size: 0.85rem;
  cursor: pointer;
  box-shadow: var(--fn-shadow);
}
.dm-zoom-btn:hover { border-color: var(--fn-accent-soft-line); color: var(--fn-accent-ink); }
.dm-hint {
  position: absolute;
  bottom: 12px;
  left: 12px;
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  color: var(--fn-ink-faint);
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: 20px;
  padding: 0.3rem 0.7rem;
  z-index: 5;
}

/* --------------------------------------------------------------------------
   Detail panel
   --------------------------------------------------------------------------
   The panel is a 360px column that has to hold a 39-character API name, a
   type, and up to forty of each. Everything here follows from that: the width
   is the constraint, not the styling.

   It used to lay a field row out as `flex; justify-content: space-between`
   with a `nowrap` type. Once the type carried its reference target the label
   ran to 36 monospace characters — wider than the column — and since a nowrap
   flex item will not shrink, the type ate the row, the name column collapsed
   toward zero, and the name wrapped one character per line. The row overflowed
   and put a horizontal scrollbar under the panel.

   The fix is structural: a two-track grid where the name track is explicitly
   allowed to shrink (`minmax(0, 1fr)` plus `min-width: 0`, since a grid item's
   automatic minimum is its content) and the type track takes only what it
   needs. The target moved to the Relationships rows, which is where a reader
   can click it.
   -------------------------------------------------------------------------- */
.dm-panel {
  border-left: 1px solid var(--fn-line);
  padding: 0 1.05rem 1.3rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  max-height: 640px;
  background: var(--fn-panel);
}
.dm-panel-empty {
  color: var(--fn-ink-faint);
  font-size: 0.72rem;
  padding-top: 2.6rem;
  text-align: center;
}
.dm-panel-empty svg { width: 26px; height: 26px; margin-bottom: 0.6rem; opacity: 0.5; }

/* Pinned: a long field list scrolls for several screens, and a heading that
   scrolls away leaves the reader holding a column of API names with nothing on
   screen saying which object they belong to. */
.dm-panel-head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--fn-panel);
  padding: 1.05rem 0 0.7rem;
  border-bottom: 1px solid var(--fn-line-soft);
  margin-bottom: 0.9rem;
}
.dm-panel-kicker {
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--fn-accent-ink);
  margin-bottom: 0.3rem;
}
.dm-panel-title {
  font-family: var(--fn-font-display);
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.2;
  margin: 0 0 0.25rem;
}
/* `break-all` here would split an identifier anywhere; the markup supplies a
   <wbr> at each underscore instead, so this only has to allow the break. */
.dm-panel-api {
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  color: var(--fn-ink-faint);
  margin-bottom: 0.6rem;
  overflow-wrap: break-word;
}
.dm-panel-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0; }
.dm-meta-chip {
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  color: var(--fn-ink-soft);
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line);
  border-radius: 20px;
  padding: 0.16rem 0.55rem;
  white-space: nowrap;
}
.dm-panel-desc {
  font-size: 0.74rem;
  color: var(--fn-ink-soft);
  margin-bottom: 0.2rem;
  line-height: 1.6;
}

/* Label, a hairline taking up the slack, then the count. The count was an
   unstyled <span> before, so it butted straight onto the uppercased label and
   rendered as "FIELDS4". */
.dm-panel-section-title {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  margin: 1.4rem 0 0.5rem;
}
.dm-panel-section-title .rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--fn-line-soft);
}
.dm-panel-section-title .n {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--fn-ink-soft);
}

.dm-field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 0.7rem;
  padding: 0.42rem 0.4rem;
  margin: 0 -0.4rem;
  border-radius: 5px;
  border-bottom: 1px solid var(--fn-line-soft);
  font-size: 0.7rem;
}
.dm-field-row:last-child { border-bottom: none; }
/* The name and its type can end up a column apart; tinting the whole row on
   hover is what lets the eye carry one across to the other. */
.dm-field-row:hover { background: var(--fn-paper-dim); }
.dm-field-name {
  min-width: 0;
  font-family: var(--fn-font-mono);
  color: var(--fn-accent-ink);
  overflow-wrap: break-word;
  line-height: 1.45;
}
.dm-field-name .req { color: var(--fn-danger); }
.dm-field-type {
  font-family: var(--fn-font-mono);
  color: var(--fn-ink-faint);
  white-space: nowrap;
  text-align: right;
}

/* Relationships are rows rather than pills. A pill holding an object label and
   a 21-character field name wrapped inside its own border, which read as a
   broken chip; a row gives each part a line and makes the whole strip the
   target. */
.dm-rel-list { display: flex; flex-direction: column; gap: 0.1rem; }
.dm-rel-row {
  display: grid;
  grid-template-columns: 1rem minmax(0, 1fr);
  gap: 0 0.5rem;
  align-items: start;
  padding: 0.42rem 0.4rem;
  margin: 0 -0.4rem;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
}
.dm-rel-row:hover,
.dm-rel-row:focus-visible {
  background: var(--fn-accent-soft);
  border-color: var(--fn-accent-soft-line);
  outline: none;
}
.dm-rel-dir {
  font-family: var(--fn-font-mono);
  font-size: 0.72rem;
  color: var(--fn-ink-faint);
  line-height: 1.5;
}
.dm-rel-row:hover .dm-rel-dir { color: var(--fn-accent-ink); }
.dm-rel-main { min-width: 0; }
.dm-rel-obj {
  display: block;
  font-size: 0.72rem;
  color: var(--fn-ink);
  line-height: 1.35;
}
.dm-rel-row:hover .dm-rel-obj { color: var(--fn-accent-ink); }
/* Which field carries the relationship — apparatus, so mono and quieter than
   the object name it hangs under. */
.dm-rel-field {
  display: block;
  font-family: var(--fn-font-mono);
  font-size: 0.64rem;
  color: var(--fn-ink-faint);
  overflow-wrap: break-word;
  line-height: 1.4;
}

.dm-app + .legend-strip {
  margin-top: -1.8rem;
}
.legend-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  align-items: center;
  padding: 0.7rem 1rem;
  border: 1px solid var(--fn-line);
  border-top: none;
  border-radius: 0 0 var(--fn-radius) var(--fn-radius);
  background: var(--fn-paper-dim);
  font-size: 0.66rem;
  color: var(--fn-ink-soft);
  margin: 0 0 1.8rem;
}
.legend-item { display: flex; align-items: center; gap: 0.4rem; }
.legend-swatch { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }

@media (max-width: 76.1875em) {
  .dm-app { grid-template-columns: 1fr; }
  .dm-panel { border-left: none; border-top: 1px solid var(--fn-line); max-height: 420px; }
}

/* ==========================================================================
   Reference prose — reading measure and rhythm
   --------------------------------------------------------------------------
   The generated reference pages (reference/*.md, data-model/*.md) are long-form
   technical prose sitting in a 90rem grid. Before this block nothing capped
   paragraph width, so body text ran the full ~1000px column at roughly 120-140
   characters per line — about double a comfortable measure, which is what made
   the generated pages read as walls.

   The model is the field guide this theme is named for: narrative runs in a
   measured column, while the technical apparatus (tables, code blocks, method
   signatures, diagrams) runs full width as plates do. Only the flowing
   elements are constrained, and only at the top level of the typeset, so
   prose nested inside a table cell, admonition, or card is untouched.
   ========================================================================== */
:root {
  --fn-measure: 74ch;
  --fn-prose-leading: 1.72;
}

.md-typeset > p,
.md-typeset > ul,
.md-typeset > ol,
.md-typeset > blockquote,
.md-typeset > dl {
  max-width: var(--fn-measure);
}

/* Leading and inter-paragraph rhythm. Long technical prose carries long
   identifiers that force the eye back across the line; the extra leading and
   the clear paragraph gap are what let a reader find the next line again. */
.md-typeset > p,
.md-typeset > ul,
.md-typeset > ol {
  line-height: var(--fn-prose-leading);
  margin-top: 0;
  margin-bottom: 1.05em;
}

/* Identifiers are the landmarks in this prose — `CONFIRM_PAYMENT`, `dfForm`,
   `PaymentIntent`. At the inherited 0.72em they read smaller than the text
   around them and disappear into it; at 0.84em with a hairline they become
   scannable anchors, which is most of what makes a dense paragraph navigable. */
.md-typeset > p code,
.md-typeset > ul code,
.md-typeset > ol code {
  font-size: 0.84em;
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line-soft);
  padding: 0.1em 0.34em;
  color: var(--fn-ink);
}

/* Class/bundle entry headings. h4 is the per-symbol heading on the reference
   pages and had no size of its own, so it landed at body size and gave the
   page no scannable rhythm between one symbol and the next. */
.md-typeset h4 {
  font-family: var(--fn-font-body);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  margin: 1.9rem 0 0.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--fn-line-soft);
}

/* ==========================================================================
   Reference entry — the identity gutter
   --------------------------------------------------------------------------
   A generated entry says two different kinds of thing. One is what the symbol
   IS: its name, class or interface, what it extends, which file holds it. The
   other is what it DOES: the prose, the doc note, the method table. Stacked in
   one column those read as a single undifferentiated run, and a reader
   scanning for a name has to read paragraphs to find the next one.

   Splitting them puts every identity at the same left edge, one under another,
   so the page can be scanned down that edge alone — and the body keeps its
   reading measure instead of stretching to whatever width the viewport is.
   The identity column is sticky, so a long entry's prose scrolls past while
   the name it belongs to stays on screen; that is the whole reason the split
   is worth its width.

   Identifiers are set in mono here on purpose. Black-and-mono is what a
   generator produced from source; the body's serif-and-prose is what a person
   wrote. The page is easier to trust when the two are told apart on sight.
   ========================================================================== */
.md-typeset .fn-entry {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 0 2rem;
  margin-top: 1.9rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--fn-line-soft);
}
/* The first entry under a layer/group heading opens the section — a rule
   directly under the heading would read as an underline for it. */
.md-typeset h3 + .fn-entry,
.md-typeset h2 + .fn-entry {
  border-top: none;
  padding-top: 0;
  margin-top: 0.9rem;
}

.md-typeset .fn-entry__id {
  position: sticky;
  top: calc(var(--fn-masthead) + 1.1rem);
  align-self: start;
  min-width: 0;
}
/* The h4 is inside the grid now, so the standalone-heading treatment above
   (its own rule, its own top margin) would draw a second line beside the
   entry's. The heading here is a label on a column, not a divider. */
.md-typeset .fn-entry__id h4 {
  /* A deep link lands on this h4, but what has to clear the masthead is the
     ENTRY — its rule, its top padding, and the first line of prose beside the
     heading. The page-wide `h4 { scroll-margin-top }` measures only to the
     heading, and a sticky target lands lower still than it computes, so both
     together left the entry's first body line hidden under the header. This
     value is measured from the entry's top edge, not the heading's. */
  scroll-margin-top: calc(var(--fn-masthead) + 3rem);
  border-top: none;
  padding-top: 0;
  margin: 0 0 0.5rem;
  font-family: var(--fn-font-mono);
  font-weight: 500;
  font-size: 0.84rem;
  line-height: 1.4;
  letter-spacing: 0;
  overflow-wrap: anywhere;
}
.md-typeset .fn-entry__id > p {
  margin: 0 0 0.35rem;
  max-width: none;
  font-family: var(--fn-font-mono);
  font-size: 0.66rem;
  line-height: 1.6;
  color: var(--fn-ink-faint);
  overflow-wrap: anywhere;
}
/* Every value in this column is already monospaced by the column itself, so
   Material's inline-code chrome only chops a short line into tinted boxes. */
.md-typeset .fn-entry__id code {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  font-size: 1em;
  color: var(--fn-ink-soft);
}
.md-typeset .fn-entry__id a {
  color: var(--fn-ink-soft);
  border-bottom: 1px solid var(--fn-line);
}
.md-typeset .fn-entry__id a:hover { color: var(--fn-accent-ink); }
/* Source path last, and dimmer: it answers "where do I edit this", which is
   the question a reader asks after the ones above it. */
.md-typeset .fn-entry__src {
  margin-top: 0.75rem !important;
  margin-bottom: 0 !important;
}

/* The measure and leading are set globally on `.md-typeset > p` — direct
   children only, so prose inside a table cell or a card is left alone. That
   exclusion now catches this body too, and without re-asserting them here an
   entry's prose would run the full column at Material's default leading:
   exactly the wall this split exists to prevent. */
.md-typeset .fn-entry__body > p,
.md-typeset .fn-entry__body > ul,
.md-typeset .fn-entry__body > ol,
.md-typeset .fn-entry__body > blockquote,
.md-typeset .fn-entry__body > dl {
  max-width: var(--fn-measure);
}
.md-typeset .fn-entry__body > p,
.md-typeset .fn-entry__body > ul,
.md-typeset .fn-entry__body > ol {
  line-height: var(--fn-prose-leading);
  margin-top: 0;
  margin-bottom: 1.05em;
}
.md-typeset .fn-entry__body > p code,
.md-typeset .fn-entry__body > ul code,
.md-typeset .fn-entry__body > ol code {
  font-size: 0.84em;
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line-soft);
  padding: 0.1em 0.34em;
  color: var(--fn-ink);
}
/* Apparatus keeps the body column's full width and scrolls inside it rather
   than pushing the grid wider — the identity column's width is fixed, so an
   overflowing table would otherwise crush it. */
.md-typeset .fn-entry__body > table:not([class]),
.md-typeset .fn-entry__body > .highlight {
  max-width: 100%;
}
.md-typeset .fn-entry__body > :last-child { margin-bottom: 0; }

/* Below the theme's tablet breakpoint the content column is too narrow to give
   a fixed column away, so the identity stacks above the body and stops
   sticking — a sticky element in a single-column flow just covers the text
   under it. */
@media screen and (max-width: 59.9375em) {
  .md-typeset .fn-entry {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
  .md-typeset .fn-entry__id {
    position: static;
    margin-bottom: 0.9rem;
  }
}

/* Wide apparatus. Tables and code blocks are plates, not prose — they keep the
   full column so a method signature or a long SOQL line does not wrap. */
.md-typeset > table:not([class]),
.md-typeset > .highlight,
.md-typeset > .tabbed-set { max-width: none; }

/* ==========================================================================
   Docs note — rationale set apart from summary
   --------------------------------------------------------------------------
   A reference entry carries two kinds of prose that used to look identical:
   the summary (what this class/bundle IS) and the doc note (why it exists,
   what is easy to get wrong). Only the first paragraph of a note ever carried
   the "Docs note." lead-in, so every paragraph after it read as more summary.
   The container is now labelled once and marked with a rule in the margin —
   the field guide's aside, not another paragraph of body copy.
   ========================================================================== */
.md-typeset .fn-docnote {
  margin: 1.1rem 0 1.4rem;
  padding: 0.15rem 0 0.15rem 1.1rem;
  border-left: 2px solid var(--fn-accent-soft-line);
  max-width: calc(var(--fn-measure) + 1.1rem);
}
.md-typeset .fn-docnote > p {
  color: var(--fn-ink-soft);
  line-height: var(--fn-prose-leading);
  margin: 0 0 0.9em;
}
.md-typeset .fn-docnote > p:last-child { margin-bottom: 0; }
.md-typeset .fn-docnote > ul,
.md-typeset .fn-docnote > ol {
  color: var(--fn-ink-soft);
  line-height: var(--fn-prose-leading);
}
.md-typeset .fn-docnote__label {
  font-family: var(--fn-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--fn-accent-ink);
  margin: 0 0 0.45em !important;
}
.md-typeset .fn-docnote code {
  font-size: 0.84em;
  background: var(--fn-paper-dim);
  border: 1px solid var(--fn-line-soft);
  padding: 0.1em 0.34em;
}

/* ==========================================================================
   Atmosphere and motion
   --------------------------------------------------------------------------
   Everything here is decoration and must behave like it: nothing moves the
   text the reader is on, nothing runs on the main thread, and nothing is
   required to understand the page.

   Scroll-driven animations (`animation-timeline`) are used rather than an
   IntersectionObserver because the reference pages carry ~1000 entries —
   observing them all would cost real main-thread work on every scroll, while
   a scroll timeline is composited. They sit inside @supports so a browser
   without them shows the finished state rather than a half-applied one.
   ========================================================================== */

/* ---- Paper. A warm grain plus two very slow colour drifts. The grain is
   static (a moving grain is nausea, not texture); only the wash moves, at a
   speed closer to weather than animation. ---- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(60rem 40rem at 12% -10%, rgba(208, 2, 27, 0.032), transparent 60%),
    radial-gradient(50rem 38rem at 105% 15%, rgba(11, 11, 11, 0.045), transparent 62%);
  animation: fn-wash 72s ease-in-out infinite alternate;
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E");
}
/* Lift the chrome above the animated wash and the grain overlay. Both washes
   are positioned at z-index 0, so unpositioned in-flow content paints under
   them and the page reads through a film; each element that holds text has to
   be lifted past them explicitly.

   Which elements, though, is load-bearing, and two earlier shapes of this
   rule were wrong in ways that took a while to see:

   * `position` is deliberately NOT set on the header or the sidebars.
     Material gives both a position that changes with the breakpoint — sticky
     on the wide layout, fixed for the drawer — and forcing `relative` here
     silently overrode both, which unstuck the masthead and left the phone
     drawer sitting in the flow, taking 242px out of the content column.
   * The lift is on `.md-content`, NOT on `.md-main`. The drawer is a child of
     `.md-main`, and a stacking context there at z-index 1 traps it below the
     dimming overlay's z-index 5 however high the drawer's own z-index goes.
     The visible symptom was a drawer that opened and looked right but whose
     every link was dead: each tap landed on `label.md-overlay` above it, and
     closed the drawer instead of navigating. Lifting only the text container
     leaves Material's own sidebar/overlay stacking intact. */
.md-content, .md-footer { position: relative; z-index: 1; }
.md-header { z-index: 1; }
/* The sidebar's own lift is wide-layout ONLY — below the breakpoint it is the
   drawer, and Material has already stacked it correctly against the overlay. */
@media screen and (min-width: 76.25em) {
  .md-sidebar { z-index: 1; }
}

@keyframes fn-wash {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-2.5%, 1.5%, 0) scale(1.06); }
}

/* ---- Reading progress. A hairline in the accent, tied to document scroll —
   on a 470KB reference page the scrollbar alone is a poor gauge of how much
   is left. ---- */
@supports (animation-timeline: scroll()) {
  .md-header::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -1px;
    height: 2px;
    width: 100%;
    transform-origin: 0 50%;
    background: linear-gradient(90deg, var(--fn-accent), var(--fn-warn));
    animation: fn-progress linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes fn-progress {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

/* ---- Entry reveal. Each reference entry lifts into place as it enters the
   viewport and settles well before it reaches reading height, so the text is
   never moving by the time the eye arrives.

   The range is a fixed scroll distance (`entry 0%` → `entry 360px`), not a
   percentage. Every percentage in a view() range is relative to the element's
   own height, so a block taller than the viewport can never finish its reveal
   while you are reading it: a 2000px docs note deep-linked from the contents
   grid sat at opacity 0.35 with no way to scroll it to 1 except past the point
   of reading it. A length is height-independent — anything that enters the
   viewport is fully opaque 360px later, whether it is a heading or a page. ---- */
@supports (animation-timeline: view()) {
  .md-typeset h2,
  .md-typeset h4,
  .md-typeset > table:not([class]),
  .md-typeset .fn-entry__body > table:not([class]),
  .md-typeset .grid.cards > ul > li {
    animation: fn-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 360px;
  }
  @keyframes fn-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* The docs-note rule draws itself in — a small reward for scrolling that
     costs one composited transform. The note itself never fades: it is body
     prose, often the longest block on the page, and the one thing a deep link
     lands on. Motion here is the rule only, which cannot make text unreadable
     however far it stalls. */
  .md-typeset .fn-docnote {
    border-left-color: transparent;
    background-image: linear-gradient(var(--fn-accent-soft-line), var(--fn-accent-soft-line));
    background-repeat: no-repeat;
    background-size: 2px 100%;
    background-position: 0 0;
    animation: fn-rule linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 300px;
  }
  @keyframes fn-rule {
    from { background-size: 2px 0%; }
    to   { background-size: 2px 100%; }
  }
}

/* ---- Page load. One orchestrated masthead reveal, staggered. Page-load
   motion is spent here and nowhere else. ---- */
.md-content__inner > .fn-eyebrow,
.md-content__inner > h1,
.md-content__inner > .fn-dek,
.md-content__inner > .fn-free-line,
.md-content__inner > .fn-status-strip,
.md-content__inner > .fn-platemeta {
  animation: fn-enter 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
.md-content__inner > .fn-eyebrow      { animation-delay: 0.02s; }
.md-content__inner > h1               { animation-delay: 0.09s; }
.md-content__inner > .fn-dek          { animation-delay: 0.17s; }
.md-content__inner > .fn-free-line    { animation-delay: 0.23s; }
.md-content__inner > .fn-status-strip { animation-delay: 0.30s; }
.md-content__inner > .fn-platemeta    { animation-delay: 0.25s; }

@keyframes fn-enter {
  from { opacity: 0; transform: translateY(10px); filter: blur(3px); }
  to   { opacity: 1; transform: translateY(0); filter: blur(0); }
}

/* ---- Micro-interactions ---- */
.md-typeset h2,
.md-typeset h3,
.md-typeset h4 { scroll-margin-top: 4.2rem; }

.md-typeset .headerlink {
  opacity: 0;
  transition: opacity 0.18s ease, transform 0.18s ease;
  transform: translateX(-3px);
}
.md-typeset h1:hover .headerlink,
.md-typeset h2:hover .headerlink,
.md-typeset h3:hover .headerlink,
.md-typeset h4:hover .headerlink,
.md-typeset .headerlink:focus { opacity: 1; transform: translateX(0); }

/* An identifier in prose is a destination — say so before it is clicked. */
.md-typeset > p a code,
.md-typeset .fn-entry__body > p a code,
.md-typeset .fn-docnote a code {
  transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}
.md-typeset a:hover code {
  background: var(--fn-accent-soft);
  border-color: var(--fn-accent-soft-line);
  color: var(--fn-accent-ink);
}

.md-nav--secondary .md-nav__link {
  transition: color 0.16s ease, padding-left 0.16s ease;
  border-left: 2px solid transparent;
  padding-left: 0.5rem;
}
.md-nav--secondary .md-nav__link--active {
  border-left-color: var(--fn-accent);
  color: var(--fn-accent-ink);
}
.md-nav--secondary .md-nav__link:hover { padding-left: 0.72rem; }

/* ---- The whole of the above is optional. Honour the system setting. ---- */
@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
  .md-content__inner > .fn-eyebrow,
  .md-content__inner > h1,
  .md-content__inner > .fn-dek,
  .md-content__inner > .fn-status-strip { animation: none; }
  @supports (animation-timeline: view()) {
    .md-typeset h2,
    .md-typeset h4,
    .md-typeset .fn-docnote,
    .md-typeset > table:not([class]),
    .md-typeset .fn-entry__body > table:not([class]),
    .md-typeset .grid.cards > ul > li { animation: none; }
    .md-typeset .fn-docnote {
      border-left-color: var(--fn-accent-soft-line);
      background-image: none;
    }
  }
  .md-header::after { animation: none; }
  * { transition-duration: 0.01ms !important; }
}

/* ---------- Contents grid on generated reference pages ----------
   A generated reference page is one very long document on purpose: every
   cross-reference the generators emit, and every URL already published,
   resolves to an anchor within it. The theme's "On this page" rail turns that
   length into a single undifferentiated column, so the page gets its own
   overview at the top instead — the domain groups, their size, and a link
   into each. It reuses the site's existing card grid rather than introducing
   a second card style, and only tightens the parts that differ: the link rows
   inside a card are dense navigation, not prose, so they drop to a smaller
   size and a tighter measure than the reading-measure rules above set. */
.md-typeset .fn-contents__label {
  font-family: var(--fn-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fn-ink-soft);
  margin: 2.2rem 0 0;
}
.md-typeset .grid.cards.fn-contents > ul {
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin-top: 0.7rem;
}
.md-typeset .grid.cards.fn-contents > ul > li {
  padding: 0.95rem 1.05rem;
}
.md-typeset .grid.cards.fn-contents > ul > li > p {
  max-width: none;
  line-height: 1.6;
}
/* The count line — "36 classes" — is a label, not a sentence. */
.md-typeset .grid.cards.fn-contents > ul > li > p:nth-of-type(2) {
  font-family: var(--fn-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--fn-ink-soft);
  margin-bottom: 0.35rem;
}
.md-typeset .grid.cards.fn-contents > ul > li > p:last-child {
  font-size: 0.78rem;
  color: var(--fn-ink-soft);
  margin-bottom: 0;
}
.md-typeset .grid.cards.fn-contents > ul > li > p:last-child a {
  color: var(--fn-ink-soft);
  border-bottom: 1px solid transparent;
}
.md-typeset .grid.cards.fn-contents > ul > li > p:last-child a:hover {
  color: var(--fn-accent-ink);
  border-bottom-color: var(--fn-accent-soft-line);
}

/* ==========================================================================
   Masthead — brand, section tabs, search, repo in ONE row
   --------------------------------------------------------------------------
   The site has 40 pages across six sections. Before this block every one of
   them lived in a single flat sidebar column, which made the sidebar carry the
   whole navigation load and gave a reader no sense of which part of the
   documentation they were in. The six sections are now the primary axis and
   live in the masthead; the sidebar carries only the current section.

   The row itself is composed in `overrides/main.html`, not here. Material
   renders the tabs as a SEPARATE ROW below the header, and CSS cannot move one
   flow element into another — the first attempt overlaid the tab row on the
   header with `position: fixed`, which aligned the two only by coincidence and
   came apart the moment Material's own JS hid the tabs on scroll. With the
   tabs rendered inside `.md-header__inner`, everything below is ordinary
   styling of one flex row.

   The brand block is exactly as wide as the primary sidebar, so the tabs begin
   on the same vertical line as the page content and the hairline under the
   brand continues the sidebar's own right edge. Below Material's layout
   breakpoint the tabs give way to the drawer, so the tab and brand rules that
   assume the wide layout are scoped to it and the mobile header is untouched.
   ========================================================================== */
:root {
  --fn-masthead: 3rem;
  --fn-brand-w: 12.1rem;
}

.md-header {
  /* An earlier rule puts every chrome element into its own stacking context so
     the animated background sits behind them; `position: relative` there also
     silently dropped Material's sticky header, which only showed once the tab
     row stopped being a fixed overlay covering the gap. */
  position: sticky;
  top: 0;
  z-index: 20;
  height: var(--fn-masthead);
  background: rgba(239, 239, 236, 0.88);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--fn-line);
  box-shadow: none;
}
.md-header[data-md-state="shadow"],
.md-header--shadow { box-shadow: none; }
.md-header__inner {
  height: var(--fn-masthead);
  align-items: stretch;
  padding: 0;
  gap: 0;
}

/* ---- brand lockup ---- */
.fn-brand {
  /* Ember, the product's coral — deliberately NOT --fn-accent, which is this
     theme's printer's red and means "active/current". They are close enough on
     screen to be mistaken for one another, so they are kept as separate names:
     if the brand colour ever moves, nothing about the page's own state colours
     moves with it. */
  --fn-brand-ember: #e0533d;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 0 auto;
  height: 100%;
  padding: 0 0.7rem 0 0.2rem;
  color: var(--fn-ink);
}
.fn-brand:hover { color: var(--fn-ink); }
/* The product mark, served from assets/favicon.svg. Its own artwork already
   carries the rounded corner, so the radius here exists only to keep the drop
   shadow from squaring off around it. Ember coral is the one warm colour this
   otherwise black-and-printer's-red theme allows, and it is allowed here
   because a logo is an identity rather than a palette entry — the rule at the
   right edge of the lockup keeps it from ever sitting beside the red of an
   active tab. */
.fn-brand__mark {
  flex: none;
  display: block;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 0.35rem;
  box-shadow: 0 2px 5px -1px rgba(11, 11, 11, 0.35);
}
/* The lockup, not a heading: Bricolage Grotesque 800 with the `ivo` in Ember,
   exactly as the Salesforce app's own top bar sets it. The two surfaces are
   the product's two front doors and they now say the name the same way. The
   colour is Midnight rather than this theme's ink for the same reason — the
   wordmark is brand artwork that happens to be live text, so it keeps the
   brand's values rather than the page's. */
.fn-brand__word {
  font: 800 1.02rem/1 "Bricolage Grotesque", var(--fn-font-body);
  letter-spacing: -0.02em;
  color: #1c2b46;
  white-space: nowrap;
}
.fn-brand__word b { font-weight: 800; color: var(--fn-brand-ember); }
.fn-brand__ver {
  font: 400 0.6rem/1 var(--fn-font-mono);
  color: var(--fn-ink-faint);
  border: 1px solid var(--fn-line);
  border-radius: 20px;
  padding: 0.12rem 0.35rem;
  white-space: nowrap;
}

/* Material's header observer writes the page title into this element as the
   reader scrolls. On the wide layout the brand lockup and the section tabs
   already say where the reader is, so it is kept in the DOM for the observer
   and hidden from view; below the breakpoint the tabs are gone and it is the
   only thing naming the current page, so it stays — and it is what pushes the
   search button to the right edge. */
@media screen and (min-width: 76.25em) {
  .fn-header__title { display: none; }
}
@media screen and (max-width: 76.2344em) {
  .fn-header__title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.8rem;
    line-height: var(--fn-masthead);
    margin-left: 0.35rem;
  }
}

/* ---- section tabs ---- */
.md-tabs {
  background: transparent;
  color: var(--fn-ink-soft);
  overflow: visible;
  height: 100%;
  border: 0;
  flex: 1 1 auto;
  min-width: 0;
}
.md-tabs__list {
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  gap: 2px;
  height: 100%;
}
.md-tabs__item { height: 100%; padding: 0; }
.md-tabs__link {
  display: flex;
  align-items: center;
  height: 100%;
  margin: 0;
  padding: 0 0.65rem;
  font-size: 0.83rem;
  font-weight: 500;
  line-height: 1;
  color: var(--fn-ink-soft);
  opacity: 1;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color 0.16s ease, border-color 0.16s ease;
}
.md-tabs__link:hover { color: var(--fn-ink); }
.md-tabs__link--active,
.md-tabs__item--active .md-tabs__link {
  color: var(--fn-ink);
  font-weight: 600;
  border-bottom-color: var(--fn-accent);
}

/* ---- search pill and repo button ---- */
.md-header__inner > .md-search { flex: 0 0 auto; margin-left: auto; }
.fn-masthead__source {
  flex: 0 0 auto;
  width: auto;
  max-width: none;
  margin: 0 0 0 0.4rem;
  padding: 0;
  display: flex;
  align-items: center;
}

@media screen and (min-width: 76.25em) {
  .fn-brand {
    width: var(--fn-brand-w);
    /* Aligned with the sidebar's own left inset, not with the viewport: the
       brand column and the chapter column are the same 12.1rem column, and a
       mark that starts further left than the section label under it reads as
       a misalignment rather than as a wider gutter. */
    padding-left: 1.5rem;
    border-right: 1px solid var(--fn-line-soft);
  }
  .md-tabs { padding-left: 0.7rem; }
  /* The mirror of that inset on the right, so the search pill stops where the
     table-of-contents text stops instead of running into the window edge. */
  .md-header__inner { padding-right: 1.1rem; }
  .fn-masthead__drawer,
  .fn-masthead__searchicon { display: none; }

  /* A bordered pill on paper rather than Material's tinted field: the header
     is a light band, so the stock translucent-white input reads as a gap in
     it. Fixed width because the tabs, not the search, own the middle.

     The width belongs on `.md-search__inner`, NOT on the form. Material sizes
     the whole search unit by animating the inner from 11.7rem to 34.4rem when
     the toggle is checked, and positions the results panel at `width: 100%` of
     that same inner. Pinning the form instead left the two out of step the
     moment search opened: a 10.5rem input with a 34.4rem panel hanging off its
     right edge. Sizing the inner keeps form, panel and scroll wrapper on one
     box, and Material's `:checked` rule still wins the expansion. */
  .md-search__inner { width: 10.5rem; }
  .md-search__form {
    height: 1.7rem;
    border-radius: 0.4rem;
    background: var(--fn-panel);
    border: 1px solid var(--fn-line);
    box-shadow: none;
    transition: border-color 0.16s ease;
  }
  .md-search__form:hover { background: var(--fn-panel); border-color: var(--fn-accent-soft-line); }
  [dir="ltr"] .md-search__input {
    font-size: 0.75rem;
    color: var(--fn-ink);
    background: transparent;
    padding-left: 1.9rem;
  }
  .md-search__input::placeholder { color: var(--fn-ink-faint); }
  .md-search__icon[for="__search"] {
    top: 0.32rem;
    left: 0.4rem;
    width: 0.9rem;
    height: 0.9rem;
    color: var(--fn-ink-faint);
  }
  .md-search__options { top: 0.2rem; right: 1.9rem; }

  /* The shortcut hint Material does not ship. It is decoration on the form
     rather than an element in the partial, so it cannot intercept the click
     that opens search; it is dropped while search is open, where the same
     corner belongs to the clear button. */
  .md-search__form::after {
    content: "⌘K";
    position: absolute;
    top: 50%;
    right: 0.28rem;
    transform: translateY(-50%);
    font: 500 0.56rem/1 var(--fn-font-mono);
    color: var(--fn-ink-faint);
    background: var(--fn-paper-dim);
    border: 1px solid var(--fn-line);
    border-radius: 4px;
    padding: 0.15rem 0.28rem;
    pointer-events: none;
  }
  [data-md-toggle="search"]:checked ~ .md-header .md-search__form::after { display: none; }

  /* Open state. The pill becomes the head of the results panel, so its bottom
     corners square off and the panel picks the radius up again underneath —
     one shape, not a pill with a card behind it. Material's own `top: 1.9rem`
     assumes its 1.8rem form; ours is 1.7rem inside 0.1rem of inner padding,
     so the panel is pulled up to sit flush on the pill's border. */
  [data-md-toggle="search"]:checked ~ .md-header .md-search__form {
    background: var(--fn-panel);
    border-color: var(--fn-line);
    border-bottom-color: transparent;
    border-radius: 0.4rem 0.4rem 0 0;
    box-shadow: none;
  }
  .md-search__output { top: 1.8rem; }
  [data-md-toggle="search"]:checked ~ .md-header .md-search__output {
    border-radius: 0 0 0.4rem 0.4rem;
    border: 1px solid var(--fn-line);
    box-shadow: var(--fn-shadow-pop, 0 18px 44px -18px rgba(11, 11, 11, 0.34));
  }
  .md-search__scrollwrap { background: var(--fn-panel); }

  /* The section name above the chapter list: a label for the column, not a
     link competing with the chapters under it. */
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {
    font-family: var(--fn-font-mono);
    font-size: 0.62rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--fn-ink-faint);
    background: transparent;
    border-left-color: transparent;
    padding-left: 0.7rem;
    margin-bottom: 0.3rem;
    pointer-events: none;
  }

  /* The repo link becomes a square icon button — the stock lockup spends a
     third of the row on a repository name and two stat lines that no reader
     of a docs page is looking for. */
  .fn-masthead__source .md-source { font-size: 0; }
  .fn-masthead__source .md-source__repository { display: none; }
  .fn-masthead__source .md-source__icon {
    width: 1.7rem;
    height: 1.7rem;
    padding: 0;
    display: grid;
    place-items: center;
    border: 1px solid var(--fn-line);
    border-radius: 0.4rem;
    background: var(--fn-panel);
    color: var(--fn-ink-soft);
    transition: border-color 0.16s ease, color 0.16s ease;
  }
  .fn-masthead__source .md-source__icon svg { width: 0.9rem; height: 0.9rem; margin: 0; }
  .fn-masthead__source:hover .md-source__icon {
    border-color: var(--fn-accent-soft-line);
    color: var(--fn-accent-ink);
  }
}

/* The tab row is the only thing in the masthead that grows with the site, and
   adding Pricing took it past what fits beside a fixed-width search pill: at
   the low end of the wide layout "Code Reference" ran under the pill and was
   clipped. `.md-tabs` is `overflow: visible`, so the overflow is silent — the
   label simply disappears behind the search with no scrollbar and no ellipsis
   to say so. Between the layout switch (76.25em) and the width where the row
   fits comfortably (measured: the default sizing needs ~1500px for eight
   tabs), tighten the tabs and the pill rather than dropping a section from
   the masthead. Two steps, because one setting cannot span the whole range:
   the row is a fixed width while the pill slides left with the viewport, so
   the squeeze at 1220px is ~60px worse than at 1280px. Re-measure both steps
   if a tab is ever added or renamed. */
@media screen and (min-width: 76.25em) and (max-width: 94em) {
  .md-tabs__link { padding: 0 0.3rem; font-size: 0.79rem; }
  .md-search__inner { width: 7.6rem; }
}

@media screen and (min-width: 76.25em) and (max-width: 80em) {
  .md-tabs__link { padding: 0 0.24rem; font-size: 0.75rem; }
  .md-search__inner { width: 6.8rem; }
}


/* Material treats the tab row as transient: once the page heading scrolls
   under the header it sets `hidden` on the tabs and animates the page title
   into the header in their place. That trade makes sense for a second row that
   can be reclaimed; here the tabs ARE the primary navigation and live in the
   header itself, so the hide is pinned open.

   Only on the wide layout, though. Below the breakpoint Material hides the
   tabs for a different and still-correct reason — the six sections belong to
   the drawer, where they have room — and an unscoped pin re-opened them there
   too, running them off the right edge of a phone. */
@media screen and (min-width: 76.25em) {
  .md-tabs[hidden] {
    display: flex;
    pointer-events: auto;
  }
  .md-tabs[hidden] .md-tabs__link {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition: color 0.16s ease, border-color 0.16s ease;
  }
}

/* ==========================================================================
   Sidebar — the current section as a numbered chapter list
   --------------------------------------------------------------------------
   With the sections lifted into the masthead, the sidebar shows one section at
   a time and can afford to say something the flat list could not: how many
   chapters there are and which one you are on. The numbers come from a CSS
   counter over the rendered list rather than from the page titles, so nav
   order in mkdocs.yml stays the single source of truth — reordering a section
   renumbers it with no edit here and no edit to any page.
   ========================================================================== */
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list {
  counter-reset: fn-ch;
  border-left: 1px solid var(--fn-line-soft);
  margin-left: 0.75rem;
  padding-left: 0;
}
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item {
  counter-increment: fn-ch;
  position: relative;
}
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.32rem 0.5rem 0.32rem 0.7rem;
  border-left: 0;
  border-radius: 0 6px 6px 0;
  line-height: 1.42;
}
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link::before {
  content: counter(fn-ch, decimal-leading-zero);
  flex: none;
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  font-weight: 400;
  color: var(--fn-ink-faint);
  padding: 0.12em 0.26em;
  border-radius: 3px;
  transition: color 0.16s ease, background-color 0.16s ease;
}
/* The active chapter is the one thing in the sidebar that is a decision rather
   than an inventory, so it is the one thing that gets the accent. */
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link--active {
  background: transparent;
}
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link--active::before {
  color: var(--fn-panel);
  background: var(--fn-accent);
  font-weight: 500;
}
.md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link--active::after {
  content: "";
  position: absolute;
  left: -1px;
  top: 0.28rem;
  bottom: 0.28rem;
  width: 2px;
  background: var(--fn-accent);
}

/* Material nests a copy of the page's own table of contents under the active
   chapter, fronted by a `<label>` that opens it — and that label carries the
   chapter's own title, so the active `<li>` holds a label and an anchor
   reading the same words. On the wide layout the right-hand rail already
   holds the table of contents, so the label and the nested nav are both
   redundant and both go. */
@media screen and (min-width: 76.25em) {
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link,
  .md-sidebar--primary .md-nav--secondary { display: none; }
}

/* In the drawer there is no rail, so the nested table of contents is the only
   in-page navigation a phone gets and the label has to stay as its opener.
   What it must not stay as is a second numbered chapter: styled like its
   sibling anchor it published as "02 After you install" twice in a row. The
   number and the active chip come off, and the repeated title is replaced by
   what the control actually does. */
@media screen and (max-width: 76.234375em) {
  /* The label precedes the anchor in Material's markup, so the list item is
     made a column to let `order` put the opener under the chapter it opens
     rather than above it. The nested nav is absolutely positioned in the
     drawer and the toggle input is hidden, so neither is affected. */
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item--active {
    display: flex;
    flex-direction: column;
  }
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link {
    font-family: var(--fn-font-mono);
    font-size: 0.62rem;
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--fn-ink-faint);
    order: 1;
  }
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link::before,
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link::after {
    content: none;
  }
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link .md-ellipsis {
    font-size: 0;
  }
  .md-sidebar--primary .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__list > .md-nav__item > label.md-nav__link .md-ellipsis::before {
    content: "On this page";
    font-size: 0.62rem;
  }
}

/* ==========================================================================
   Footer band (overrides/partials/footer.html)
   --------------------------------------------------------------------------
   One ending per page, on paper. Material's stock footer is a black slab
   carrying page nav, a copyright and a "Made with Material for MkDocs" line,
   and the CloudAlgo attribution used to sit below it as a second dark strip —
   so every page finished twice, in a colour the rest of the site never uses.
   This is the replacement: page nav, the help offer, attribution, all in the
   page's own paper and ink, separated by rules rather than by colour.
   ========================================================================== */
.fn-foot {
  background: var(--fn-paper-dim);
  border-top: 1px solid var(--fn-line);
}
.fn-foot__inner { padding: 2.1rem 0.8rem 2.4rem; }

/* ---- previous / next ---- */
/* Two cards on one row, each naming its direction in mono and its destination
   in the display face — the same pairing the section headings use, so the
   footer reads as part of the document rather than as theme furniture. */
.fn-foot__nav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.7rem;
  margin-bottom: 1.8rem;
}
.fn-foot__page {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.85rem 1rem;
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  color: var(--fn-ink);
  text-decoration: none;
  transition: border-color 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease;
}
.fn-foot__page:hover {
  border-color: var(--fn-accent);
  transform: translateY(-1px);
  box-shadow: 0 6px 18px -12px rgba(11, 11, 11, 0.5);
}
/* The next page is the one a reader following the docs in order wants, so it
   is the one that reads as the action: right-aligned, so the pair points
   forward across the row. */
.fn-foot__page--next { text-align: right; }
.fn-foot__page--empty { border: none; background: none; }
.fn-foot__dir {
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fn-accent);
}
.fn-foot__pagetitle {
  font-family: var(--fn-font-display);
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ---- the help offer ---- */
/* Not navigation, and deliberately not styled as another card: a reader who
   has reached the bottom of an install or customisation page and still has a
   question needs a person, and this is where that becomes obvious. */
.fn-foot__help {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.6rem;
  padding: 1.15rem 1.25rem;
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-left: 3px solid var(--fn-accent);
  border-radius: var(--fn-radius);
}
.fn-foot__helptitle {
  margin: 0 0 0.2rem;
  font-family: var(--fn-font-display);
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fn-ink);
}
.fn-foot__helpnote {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--fn-ink-soft);
  max-width: 58ch;
}
.fn-foot__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: none;
  padding: 0.55rem 1.05rem;
  background: var(--fn-accent);
  color: #fff;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  transition: background 0.16s ease, transform 0.16s ease;
}
.fn-foot__cta:hover { background: var(--fn-accent-ink); transform: translateY(-1px); color: #fff; }
.fn-foot__cta svg { display: block; }

/* ---- attribution ---- */
.fn-foot__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.7rem 1.6rem;
  margin-top: 1.6rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--fn-line);
}
.fn-foot__lockup {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  text-decoration: none;
  opacity: 0.85;
  transition: opacity 0.16s ease;
}
.fn-foot__lockup:hover { opacity: 1; }
.fn-foot__lead {
  font-family: var(--fn-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
}
/* CloudAlgo's dark lockup, because the band is paper now — the white cut of
   the same logo was only ever needed by the dark strip this replaces. */
.fn-foot__logo { height: 1.3rem; width: auto; display: block; }
.fn-foot__copy {
  margin: 0;
  font-size: 0.7rem;
  color: var(--fn-ink-faint);
}
.fn-foot__copy a { color: var(--fn-ink-soft); border-bottom: 1px solid var(--fn-line); }
.fn-foot__copy a:hover { color: var(--fn-accent); border-bottom-color: currentColor; }

@media screen and (max-width: 44.9375em) {
  .fn-foot__nav { grid-template-columns: 1fr; }
  .fn-foot__page--next { text-align: left; }
  .fn-foot__page--empty { display: none; }
  .fn-foot__help { flex-direction: column; align-items: flex-start; }
  .fn-foot__meta { flex-direction: column; align-items: flex-start; }
}


/* ==========================================================================
   Home — the marketing landing page
   --------------------------------------------------------------------------
   Everything below runs on `docs/index.md` and nowhere else. It is the one
   page in the site that is not documentation: a reader arriving here has not
   decided to use this product yet, and a table of contents with a headline on
   it does not help them decide. So the page makes a claim, gives evidence for
   the claim, shows the flow the claim is about, and offers one obvious next
   click.

   The background is hand-drawn technical sketches rather than photography or
   gradients. That is not a style preference — this theme is paper, black and
   one printer's red, and both of the usual ways to add warmth would fight it.
   Margin drawings do not: they read as the sketches on an engineer's page,
   which is what this product is. Every sketch is a real thing in the product
   (the donation form, the recurring loop, the goal bar, the org, the object
   graph, the receipt), so the field illustrates the page rather than
   decorating it.

   All motion is CSS, all of it is slow, and all of it is switched off under
   prefers-reduced-motion at the foot of this section. Nothing here loops fast
   enough to catch the eye of somebody reading.

   Note on units: Material sets `html { font-size: 125% }`, so 1rem is 20px
   here and every size below is already expressed against that, not against a
   16px or 17px root.
   ========================================================================== */

/* ---------- The drifting sketch field ---------- */

/* Fixed, so the page moves across the field rather than the field moving with
   the page — that is what keeps 70-120s drift loops from reading as scroll
   judder.

   z-index -1 puts it at the bottom of `.md-content`'s stacking context (that
   element is `position: relative; z-index: 1` further up this file), which is
   exactly the layer wanted: above the body wash and grain at z-index 0, below
   every element that holds text, and far below the masthead at z-index 20. */
.fn-field {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

/* Engineer's grid paper, masked away toward the foot of the viewport so it
   never competes with the prose. Static on purpose: with six sketches adrift,
   one thing has to be nailed down or the whole background swims. */
.fn-field::before {
  content: "";
  position: absolute;
  inset: -10%;
  background-image:
    linear-gradient(to right, rgba(11, 11, 11, 0.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(11, 11, 11, 0.045) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 25%, transparent 75%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000 25%, transparent 75%);
}
/* No grain layer here: `body::after` already lays one over the whole site, and
   a second one on this page only would make the home page visibly coarser. */

.fn-sk { position: absolute; color: var(--fn-ink); opacity: 0.09; }
.fn-sk svg { display: block; width: 100%; height: auto; overflow: visible; }
/* The recurring loop is the one sketch in red — it is the single mechanic that
   most distinguishes this product, so it gets the accent and a little more
   presence than the rest. */
.fn-sk--red { color: var(--fn-accent); opacity: 0.13; }

.fn-sk--form    { top: 6%;  left: -4%;  width: 340px; animation: fn-drift-a 74s ease-in-out infinite; }
.fn-sk--loop    { top: 28%; right: -3%; width: 300px; animation: fn-drift-b 91s ease-in-out infinite; }
.fn-sk--goal    { top: 64%; left: 4%;   width: 250px; animation: fn-drift-c 83s ease-in-out infinite; }
.fn-sk--org     { top: 80%; right: 8%;  width: 270px; animation: fn-drift-a 102s ease-in-out infinite reverse; }
.fn-sk--graph   { top: 44%; left: 46%;  width: 210px; animation: fn-drift-b 118s ease-in-out infinite; }
.fn-sk--receipt { top: 12%; right: 22%; width: 180px; animation: fn-drift-c 67s ease-in-out infinite reverse; }

@keyframes fn-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(-3deg); }
  50%      { transform: translate3d(26px, -34px, 0) rotate(2deg); }
}
@keyframes fn-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(4deg); }
  50%      { transform: translate3d(-30px, 26px, 0) rotate(-2deg); }
}
@keyframes fn-drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
  50%      { transform: translate3d(18px, 22px, 0) rotate(-5deg) scale(1.05); }
}

/* Every stroke draws itself once on load, in reading order, then stays put.
   It is the page's single "this is alive" moment; everything after it is
   slow drift. The dasharray is a flat 1400 rather than a per-path length
   because these are decorative shapes at 9% opacity — measuring each path to
   get a perfectly even draw rate would be precision nobody can see. */
.fn-sk :is(path, rect, circle, line, polyline, ellipse) {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1400;
  stroke-dashoffset: 1400;
  animation: fn-draw 2.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.fn-sk--loop    * { animation-delay: 0.35s; }
.fn-sk--goal    * { animation-delay: 0.70s; }
.fn-sk--org     * { animation-delay: 1.05s; }
.fn-sk--graph   * { animation-delay: 1.40s; }
.fn-sk--receipt * { animation-delay: 1.75s; }
@keyframes fn-draw { to { stroke-dashoffset: 0; } }

/* ---------- Hero ---------- */

.md-typeset .fn-home-h1 {
  font-size: 2.95rem;
  line-height: 1.04;
  letter-spacing: -0.022em;
  max-width: 16ch;
  margin: 0 0 1.1rem;
}
/* A drawn underline rather than a highlight block: the same emphasis, the same
   hand as the background sketches, and it survives being printed. Sitting the
   swash below the baseline (not across it) is load-bearing — an earlier
   version centred it and the phrase read as struck through. */
.md-typeset .fn-home-h1 em {
  font-style: normal;
  position: relative;
  white-space: nowrap;
}
.md-typeset .fn-home-h1 em::after {
  content: "";
  position: absolute;
  left: -0.04em;
  right: -0.04em;
  bottom: -0.16em;
  height: 0.3em;
  background: no-repeat center/100% 100% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 14' preserveAspectRatio='none'%3E%3Cpath d='M2 9c34-5 76-7 116-5 30 1 52 4 80 7' fill='none' stroke='%23d0021b' stroke-width='3.2' stroke-linecap='round'/%3E%3C/svg%3E");
  animation: fn-swipe 1s 0.9s cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes fn-swipe {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* The landing dek is the one place the standard `.fn-dek` is too quiet — it is
   carrying the whole product description for a reader who may read nothing
   else on the page. */
.md-typeset .fn-hero__dek {
  font-size: 1.05rem;
  line-height: 1.55;
  max-width: 56ch;
  margin: 0 0 1.7rem;
}

.fn-cta { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-bottom: 1.8rem; }
.md-typeset .fn-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  height: 2.2rem;
  padding: 0 1rem;
  border-radius: var(--fn-radius);
  font-size: 0.78rem;
  font-weight: 600;
  border: 1px solid transparent;
  transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease, border-color 0.16s ease;
}
.md-typeset .fn-btn:hover { text-decoration: none; }
.md-typeset .fn-btn--primary { background: var(--fn-ink); color: var(--fn-paper); box-shadow: var(--fn-shadow); }
.md-typeset .fn-btn--primary:hover { transform: translateY(-2px); box-shadow: var(--fn-shadow-pop); }
.md-typeset .fn-btn--ghost { background: transparent; color: var(--fn-ink); border-color: var(--fn-line); }
.md-typeset .fn-btn--ghost:hover { border-color: var(--fn-ink); background: var(--fn-panel); }
/* The third call to action: watch the film. It carries a still from the film
   rather than an icon, because the still is the only one of the three buttons
   that can show what is behind it — and it is what makes the row read as
   "read, do, or watch" rather than as two buttons and a link.

   Taller than the other two by design; the runtime under the label is there so
   nobody has to click to find out what they are committing to. */
.md-typeset .fn-btn--watch {
  height: auto;
  padding: 0.42rem 0.9rem 0.42rem 0.42rem;
  gap: 0.6rem;
  background: var(--fn-panel);
  color: var(--fn-ink);
  border-color: var(--fn-line);
  text-align: left;
}
.md-typeset .fn-btn--watch:hover {
  border-color: var(--fn-ink);
  transform: translateY(-2px);
  box-shadow: var(--fn-shadow-pop);
}
.md-typeset .fn-btn--watch .fn-btn__thumb {
  position: relative;
  flex: none;
  width: 64px;
  height: 36px;
  border-radius: 5px;
  overflow: hidden;
  background: var(--fn-ink);
}
/* At 64x36 no frame of this film is legible, so the thumbnail is zoomed into
   the donation card rather than shrunk whole — it reads as a warm crop with
   the Give button's red in it, which is the point, instead of unreadable type. */
.md-typeset .fn-btn--watch .fn-btn__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(2.4);
  transform-origin: 64% 60%;
}
/* A white triangle on a pale frame is invisible, so the thumbnail carries its
   own scrim — which doubles as the thing that makes it read as a film still
   rather than as a stray image inside a button. */
.md-typeset .fn-btn--watch .fn-btn__thumb::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(rgba(11, 11, 11, 0.18), rgba(11, 11, 11, 0.42));
}
.md-typeset .fn-btn--watch .fn-btn__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: 2;
  width: 0;
  height: 0;
  border-left: 10px solid #fff;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}
.md-typeset .fn-btn--watch small {
  display: block;
  margin-top: 0.12rem;
  font-family: var(--fn-font-mono);
  font-size: 0.58rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--fn-ink-faint);
}

.fn-btn svg {
  width: 0.72rem;
  height: 0.72rem;
  stroke: currentColor;
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- Proof strip ---------- */

/* Four numbers, each chosen because it is a consequence of being native rather
   than a claim a competitor could match by writing better marketing. Two of
   them are zero, which is the point. */
.fn-proof {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--fn-ink);
  border-bottom: 1px solid var(--fn-line);
  margin: 0 0 3.4rem;
}
.fn-proof > div { padding: 1.1rem 1rem 1.2rem; border-right: 1px solid var(--fn-line-soft); }
.fn-proof > div:last-child { border-right: 0; }
.fn-proof b {
  display: block;
  font: 600 1.72rem/1 var(--fn-font-display);
  letter-spacing: -0.02em;
  margin-bottom: 0.25rem;
}
.fn-proof span { display: block; font-size: 0.7rem; line-height: 1.42; color: var(--fn-ink-soft); }

/* ---------- Section headers ---------- */

.fn-home-sec { margin-bottom: 3.6rem; }
.fn-sec-head {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  margin-bottom: 1.4rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--fn-line);
}
.fn-sec-head__n { font: 500 0.6rem/1 var(--fn-font-mono); color: var(--fn-accent); letter-spacing: 0.08em; }
.md-typeset .fn-sec-head h2 {
  font-size: 1.6rem;
  line-height: 1.12;
  letter-spacing: -0.018em;
  margin: 0;
}
/* The kicker is a caption for the section, so it is pushed to the far edge and
   right-aligned — it reads as a margin note rather than as a second heading. */
.md-typeset .fn-sec-head p {
  margin: 0 0 0 auto;
  /* Wide enough that every kicker written so far sits on one line. A caption
     that wraps orphans its last word against the right edge, which reads as a
     mistake rather than as a margin note. */
  max-width: 46ch;
  font-size: 0.72rem;
  color: var(--fn-ink-faint);
  text-align: right;
}

/* ---------- Feature cards ---------- */

.fn-home-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.9rem; }
.fn-home-card {
  position: relative;
  overflow: hidden;
  padding: 1.3rem 1.3rem 1.1rem;
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  box-shadow: var(--fn-shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.fn-home-card:hover { transform: translateY(-3px); box-shadow: var(--fn-shadow-pop); border-color: var(--fn-ink); }
.fn-home-card__icon { width: 2.4rem; height: 2.4rem; color: var(--fn-ink); margin-bottom: 0.85rem; transition: color 0.2s ease; }
.fn-home-card:hover .fn-home-card__icon { color: var(--fn-accent); }
.fn-home-card__icon svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.md-typeset .fn-home-card h3 {
  font-family: var(--fn-font-display);
  font-size: 1rem;
  line-height: 1.25;
  margin: 0 0 0.4rem;
}
.md-typeset .fn-home-card p { margin: 0 0 0.85rem; font-size: 0.76rem; line-height: 1.55; color: var(--fn-ink-soft); }
.md-typeset .fn-home-card a,
.md-typeset .fn-home-path a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font: 500 0.68rem/1 var(--fn-font-mono);
  color: var(--fn-accent-ink);
}
.md-typeset .fn-home-card a::after { content: "→"; transition: transform 0.16s ease; }
.fn-home-card:hover a::after { transform: translateX(3px); }

/* ---------- How a gift travels ---------- */

/* The one question a fundraising buyer actually has and no feature grid
   answers: where does the money go, and where does the record land. */
.fn-flow {
  padding: 1.8rem 1.6rem 1.3rem;
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  box-shadow: var(--fn-shadow);
}
.fn-flow__rail { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; align-items: start; }
.fn-flow__step { padding: 0 0.7rem; text-align: center; }
.fn-flow__art { height: 3.4rem; margin: 0 auto 0.75rem; color: var(--fn-ink); }
.fn-flow__art svg {
  height: 100%;
  width: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fn-flow__step b { display: block; font: 600 0.84rem/1.3 var(--fn-font-display); margin-bottom: 0.25rem; }
.fn-flow__step span { display: block; font-size: 0.7rem; line-height: 1.5; color: var(--fn-ink-soft); }
.fn-flow__arrow { width: 2.6rem; padding-top: 1.2rem; color: var(--fn-accent); }
.fn-flow__arrow svg { width: 100%; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* The dashes travel along the connectors. This is the only element on the site
   that moves continuously, and it earns that because it is depicting a flow —
   the motion IS the information. */
.fn-flow__arrow path.fn-dash { stroke-dasharray: 5 6; animation: fn-march 2.4s linear infinite; }
@keyframes fn-march { to { stroke-dashoffset: -22; } }
.md-typeset .fn-flow__note {
  margin: 1.3rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px dashed var(--fn-line);
  font-size: 0.72rem;
  color: var(--fn-ink-soft);
  text-align: center;
  max-width: none;
}
.fn-flow__note b { color: var(--fn-ink); }

/* ---------- Why native matters ---------- */

/* A ledger, not a feature table: two columns answering the same five
   questions, with the product's column tinted on hover so the eye can hold a
   row while comparing across it. */
.md-typeset table.fn-ledger:not([class=""]) {
  width: 100%;
  border-collapse: collapse;
  border-top: 1px solid var(--fn-ink);
  font-size: 0.76rem;
  display: table;
}
.md-typeset .fn-ledger th {
  padding: 0.6rem 0.75rem;
  text-align: left;
  font: 500 0.6rem/1 var(--fn-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  background: none;
  border-bottom: 1px solid var(--fn-line);
}
.md-typeset .fn-ledger th:last-child { color: var(--fn-accent-ink); }
.md-typeset .fn-ledger td {
  padding: 0.72rem 0.75rem;
  vertical-align: top;
  color: var(--fn-ink-soft);
  border-bottom: 1px solid var(--fn-line-soft);
}
.md-typeset .fn-ledger td:first-child { width: 22%; font-weight: 600; color: var(--fn-ink); }
.md-typeset .fn-ledger td:last-child { color: var(--fn-ink); background: rgba(255, 255, 255, 0.55); }
.md-typeset .fn-ledger tr:hover td { background: var(--fn-panel); }
.md-typeset .fn-ledger tr:hover td:last-child { background: var(--fn-accent-soft); }

/* ---------- Where to start ---------- */

.fn-home-paths { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.8rem; }
.fn-home-path {
  padding: 1rem 1rem 0.9rem;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  transition: background 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
}
.fn-home-path:hover { background: var(--fn-panel); border-color: var(--fn-ink); transform: translateY(-2px); }
.fn-home-path b {
  display: block;
  margin-bottom: 0.5rem;
  font: 500 0.58rem/1 var(--fn-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fn-accent);
}
.md-typeset .fn-home-path h3 {
  font-family: var(--fn-font-display);
  font-size: 0.88rem;
  line-height: 1.3;
  margin: 0 0 0.35rem;
}
.md-typeset .fn-home-path p { margin: 0 0 0.7rem; font-size: 0.71rem; line-height: 1.5; color: var(--fn-ink-soft); }

/* ---------- Closing band ---------- */

.fn-closer {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 2.4rem 2rem;
  background: var(--fn-ink);
  color: var(--fn-paper);
  border-radius: var(--fn-radius);
}
.fn-closer__art { position: absolute; right: -2rem; top: -1.5rem; width: 17rem; color: #fff; opacity: 0.13; }
.fn-closer__art svg { width: 100%; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.fn-closer__body { position: relative; }
.md-typeset .fn-closer h2 {
  font-size: 1.68rem;
  line-height: 1.14;
  letter-spacing: -0.018em;
  margin: 0 0 0.5rem;
  color: var(--fn-paper);
}
.md-typeset .fn-closer p { margin: 0; max-width: 46ch; font-size: 0.8rem; color: #c9c9c4; }
.md-typeset .fn-closer .fn-btn {
  position: relative;
  flex: none;
  background: var(--fn-paper);
  color: var(--fn-ink);
}
.md-typeset .fn-closer .fn-btn:hover { transform: translateY(-2px); box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.6); }

/* ---------- Narrow layout ---------- */

@media screen and (max-width: 60em) {
  .md-typeset .fn-home-h1 { font-size: 2rem; }
  .fn-proof { grid-template-columns: repeat(2, 1fr); }
  .fn-home-cards,
  .fn-home-paths { grid-template-columns: 1fr; }
  /* The rail collapses to a stack, so the connectors have nothing to connect. */
  .fn-flow__rail { grid-template-columns: 1fr; }
  .fn-flow__arrow { display: none; }
  /* Rules BETWEEN the steps, not under each one: the note below the rail
     already draws its own top border, so a border under the last step lands a
     second dashed line a few pixels from the first. */
  .fn-flow__step { padding: 1rem 0; }
  .fn-flow__step + .fn-flow__step { border-top: 1px dashed var(--fn-line); }
  .fn-closer { flex-direction: column; align-items: flex-start; }
  .md-typeset .fn-sec-head p { display: none; }
  /* Six drifting sketches behind a single-column layout is clutter, not
     atmosphere: on a narrow screen the text sits over most of them. Keep the
     two that stay clear of the column. */
  .fn-sk--loop,
  .fn-sk--goal,
  .fn-sk--graph,
  .fn-sk--receipt { display: none; }
}

/* ---------- Honour the system setting ---------- */

@media (prefers-reduced-motion: reduce) {
  .fn-sk,
  .fn-sk *,
  .md-typeset .fn-home-h1 em::after,
  .fn-flow__arrow path.fn-dash { animation: none !important; }
  /* Without the draw-on the strokes would otherwise sit permanently hidden. */
  .fn-sk :is(path, rect, circle, line, polyline, ellipse) { stroke-dashoffset: 0; }
  .md-typeset .fn-home-h1 em::after { clip-path: none; }
  .fn-home-card,
  .fn-home-path,
  .md-typeset .fn-btn { transition: none; }
  /* The film's own motion is the reader's choice; the chrome around it is not. */
  .md-typeset .fn-vp.is-arriving { animation: none; }
  .md-typeset .fn-vp.is-loading .fn-vp__big::after { animation: none; }
  .md-typeset .fn-vp__bar,
  .md-typeset .fn-vp__big,
  .md-typeset .fn-vp::before { transition: none; }
}

/* ===========================================================================
   Pricing — the second non-documentation page

   Pairs with docs/pricing.md and is used by nothing else, the same way the
   Home block above pairs with index.md. Rules that have to beat a Material
   default (tables, lists, headings, paragraphs) carry the .md-typeset prefix;
   the purely structural containers do not, matching the Home block.

   The one colour decision worth stating: red appears exactly three times on
   this page — the 0% in the zero band, the recommended tier's top rule, and
   the single differing row of the comparison table. That is the doctrine of
   this theme applied to a pricing page. Black carries what the product does,
   red carries what a person decided, and adding a fourth red would cost the
   other three their meaning.
   =========================================================================== */

.fn-sk--coin { top: 66%; right: 8%; width: 130px; animation: fn-drift-c 74s ease-in-out infinite; }
.fn-sk--coin * { animation-delay: 2.1s; }

/* No width cap of its own. This page used to sit in a 1000px measure, which is
   invisible while the section rail is showing (the content column is narrower
   than the cap anyway) and obvious the moment it isn't: below the rail's
   breakpoint every other page widens to the full column and this one stayed at
   1000px, so the pricing page rendered visibly narrower than the home page at
   the same window size. Same column as everything else. */
.fn-pr { margin: 0 auto; }

/* ---------- Hero ---------- */

.fn-pr-hero { padding: 1.4rem 0 2.6rem; }
.md-typeset .fn-pr-hero .fn-dek { max-width: 58ch; margin-bottom: 1.8rem; }
.fn-pr-chips { display: flex; flex-wrap: wrap; gap: .45rem; }

/* ---------- The zero band ---------- */

.fn-pr-zero {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--fn-ink);
  border-bottom: 1px solid var(--fn-line);
  margin: 0 0 4.6rem;
}
.fn-pr-zero > div { padding: 1.4rem 1.2rem 1.5rem; border-right: 1px solid var(--fn-line-soft); }
.fn-pr-zero > div:last-child { border-right: 0; }
.md-typeset .fn-pr-zero b {
  display: block;
  font: 600 1.72rem/1.08 var(--fn-font-display);
  letter-spacing: -.02em;
  margin-bottom: .3rem;
}
.md-typeset .fn-pr-zero .fn-pr-zero__big {
  font-size: 3.1rem;
  color: var(--fn-accent-ink);
  letter-spacing: -.03em;
}
.md-typeset .fn-pr-zero span {
  display: block;
  font-size: .82rem;
  line-height: 1.45;
  color: var(--fn-ink-soft);
}

/* ---------- Sections ---------- */

.fn-pr-sec { margin-bottom: 4.8rem; }

/* ---------- The rate card ----------
   One ruled grid rather than two floating cards: this theme is hairlines and
   Fraunces numerals, and a pair of shadowed rounded boxes belongs to a
   different design system. The recommended column is marked with an inset top
   rule instead of a fill, because a filled column would out-shout the 0% in
   the band above it, and that figure has to stay the loudest thing on the
   page. */

/* A quiet caveat line under a table — smaller than body copy and set in the
   secondary ink, so it reads as a footnote to the thing above it rather than
   as the next paragraph. Used where a table needs one sentence of context that
   would be noise inside a cell. */

.fn-pr-note {
  margin: 0.9rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--fn-ink-2);
  max-width: 60ch;
}

.fn-pr-rate {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  border: 1px solid var(--fn-line);
  border-top: 2px solid var(--fn-ink);
  border-radius: var(--fn-radius);
  overflow: hidden;
  background: var(--fn-panel);
  box-shadow: var(--fn-shadow);
}
.fn-pr-tier {
  padding: 1.7rem 1.6rem 1.6rem;
  display: flex;
  flex-direction: column;
}
.fn-pr-tier + .fn-pr-tier { border-left: 1px solid var(--fn-line-soft); }
.fn-pr-tier--pick { background: #fdfcfc; box-shadow: inset 0 3px 0 var(--fn-accent); }
.fn-pr-tier__tag {
  min-height: .6rem;
  font: 500 .6rem/1 var(--fn-font-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fn-accent);
  margin-bottom: .7rem;
}
.md-typeset .fn-pr-tier h3 {
  font: 600 1.3rem/1 var(--fn-font-display);
  margin: 0 0 .2rem;
  letter-spacing: -.012em;
}
.md-typeset .fn-pr-tier__for {
  font-size: .8rem;
  line-height: 1.45;
  color: var(--fn-ink-faint);
  margin: 0 0 1.2rem;
  min-height: 2.9em;
}
.fn-pr-tier__price { display: flex; align-items: baseline; gap: .25rem; margin-bottom: .15rem; }
.md-typeset .fn-pr-tier__price b {
  font: 600 2.6rem/1 var(--fn-font-display);
  letter-spacing: -.03em;
}
.md-typeset .fn-pr-tier__price i {
  font-style: normal;
  font: 400 .8rem/1 var(--fn-font-mono);
  color: var(--fn-ink-faint);
}
.md-typeset .fn-pr-tier__unit {
  display: block;
  font: 400 .72rem/1.4 var(--fn-font-mono);
  color: var(--fn-ink-faint);
  margin: 0 0 1.1rem;
}
.fn-pr-tier__cap {
  border-top: 1px solid var(--fn-line-soft);
  border-bottom: 1px solid var(--fn-line-soft);
  padding: .85rem 0;
  margin-bottom: 1.1rem;
}
.md-typeset .fn-pr-tier__cap b {
  display: block;
  font: 600 1.08rem/1.2 var(--fn-font-display);
  margin-bottom: .15rem;
}
.md-typeset .fn-pr-tier__cap span {
  font: 400 .72rem/1.4 var(--fn-font-mono);
  color: var(--fn-ink-faint);
}
.md-typeset .fn-pr-tier ul {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  font-size: .86rem;
  color: var(--fn-ink-soft);
}
.md-typeset .fn-pr-tier li {
  position: relative;
  padding-left: 1.2rem;
  margin-bottom: .5rem;
  line-height: 1.45;
}
.md-typeset .fn-pr-tier li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .45em;
  width: .52rem;
  height: .28rem;
  border-left: 1.6px solid var(--fn-accent);
  border-bottom: 1.6px solid var(--fn-accent);
  transform: rotate(-45deg);
}
/* The one line per card that is NOT a tick. What separates the plans on donor
   pages is a credit line, and a credit line is a limitation on the free plan
   and an absence on the paid one - neither is a feature, and both read as a
   lie with a checkmark beside them. Set below the list, in the faint ink, so
   it reads as a footnote to the card rather than as its fifth benefit. */
.md-typeset .fn-pr-tier__note {
  font-size: .78rem;
  line-height: 1.5;
  color: var(--fn-ink-faint);
  margin: -.9rem 0 1.4rem;
  padding-top: .8rem;
  border-top: 1px dashed var(--fn-line-soft);
}

.md-typeset .fn-pr-tier .fn-btn { margin-top: auto; justify-content: center; }
.fn-pr-rate__foot {
  grid-column: 1 / -1;
  border-top: 1px solid var(--fn-line);
  padding: .95rem 1.6rem;
  background: var(--fn-paper-dim);
  font-size: .83rem;
  color: var(--fn-ink-soft);
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.8rem;
}
.md-typeset .fn-pr-rate__foot b { color: var(--fn-ink); }

/* ---------- The comparison table ----------
   One "All tiers" column rather than one column per plan: the tiers differ by
   annual volume and support level, never by feature, so a three-column grid of
   identical ticks would be the same mark repeated thirty times. If a feature is
   ever restricted to a higher tier this table stops being true, and the "every
   feature, every tier" claim in the hero has to go with it. */

.fn-pr-tablewrap { overflow-x: auto; }
.md-typeset .fn-pr-table {
  width: 100%;
  border-collapse: collapse;
  border-top: 1px solid var(--fn-ink);
  font-size: .9rem;
  display: table;
  box-shadow: none;
  border-radius: 0;
}
.md-typeset .fn-pr-table th {
  text-align: left;
  padding: .7rem .9rem;
  font: 500 .68rem/1 var(--fn-font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  border-bottom: 1px solid var(--fn-line);
  background: transparent;
}
.md-typeset .fn-pr-table th:first-child,
.md-typeset .fn-pr-table td:first-child { width: 24%; }
.md-typeset .fn-pr-table th:last-child,
.md-typeset .fn-pr-table td:last-child { width: 13%; white-space: nowrap; }
.md-typeset .fn-pr-table td {
  padding: .78rem .9rem;
  border-bottom: 1px solid var(--fn-line-soft);
  color: var(--fn-ink-soft);
  vertical-align: top;
  background: transparent;
}
.md-typeset .fn-pr-table td:first-child { color: var(--fn-ink); font-weight: 600; }
.md-typeset .fn-pr-table .tick { color: var(--fn-ink-faint); font-size: .82rem; }
.md-typeset .fn-pr-table tr:hover td { background: var(--fn-panel); }

/* ---------- Money flow ---------- */

.fn-pr-flow {
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  padding: 2.2rem 2rem 1.6rem;
  box-shadow: var(--fn-shadow);
}
.fn-pr-flow__rail { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; align-items: start; }
.fn-pr-flow__step { text-align: center; padding: 0 .8rem; }
.fn-pr-flow__art { height: 74px; margin: 0 auto .9rem; color: var(--fn-ink); }
.fn-pr-flow__art svg {
  height: 100%;
  width: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.md-typeset .fn-pr-flow__step b {
  display: block;
  font: 600 1rem/1.3 var(--fn-font-display);
  margin-bottom: .3rem;
}
.md-typeset .fn-pr-flow__step span {
  display: block;
  font-size: .82rem;
  line-height: 1.5;
  color: var(--fn-ink-soft);
}
.fn-pr-flow__arrow { width: 64px; color: var(--fn-accent); padding-top: 24px; }
.fn-pr-flow__arrow svg {
  width: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fn-pr-flow__arrow path.dash { stroke-dasharray: 5 6; animation: fn-pr-march 2.4s linear infinite; }
@keyframes fn-pr-march { to { stroke-dashoffset: -22; } }

/* The subscription hangs off the rail on a dashed lead rather than sitting in
   it. Moving this box into .fn-pr-flow__rail would make the drawing say the
   subscription is a toll gate on the money path, which is the opposite of what
   the page claims. */
.fn-pr-flow__aside {
  margin: 1.5rem auto 0;
  max-width: 30rem;
  text-align: center;
  padding-top: 1.2rem;
  border-top: 1px dashed var(--fn-accent-soft-line);
  position: relative;
}
.fn-pr-flow__aside::before {
  content: "paid separately";
  position: absolute;
  top: -.52rem;
  left: 50%;
  transform: translateX(-50%);
  background: var(--fn-panel);
  padding: 0 .5rem;
  font: 500 .6rem/1 var(--fn-font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
}
.md-typeset .fn-pr-flow__aside b {
  display: block;
  font: 600 1rem/1.3 var(--fn-font-display);
  margin-bottom: .2rem;
}
.md-typeset .fn-pr-flow__aside span { font-size: .84rem; color: var(--fn-ink-soft); }
.md-typeset .fn-pr-flow__note {
  margin: 1.4rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px dashed var(--fn-line);
  font-size: .84rem;
  color: var(--fn-ink-soft);
  text-align: center;
}
.md-typeset .fn-pr-flow__note b { color: var(--fn-ink); }

/* ---------- Worked example ---------- */

.fn-pr-worked {
  margin-top: 1.4rem;
  background: var(--fn-panel);
  border: 1px solid var(--fn-line);
  border-radius: var(--fn-radius);
  padding: 1.3rem 1.5rem;
}
.md-typeset .fn-pr-worked > b {
  display: block;
  font: 600 1rem/1.3 var(--fn-font-display);
  margin-bottom: .7rem;
}
.md-typeset .fn-pr-worked__t {
  width: 100%;
  border-collapse: collapse;
  font-size: .86rem;
  display: table;
  box-shadow: none;
  border-radius: 0;
}
.md-typeset .fn-pr-worked__t th {
  text-align: left;
  padding: .45rem 0 .6rem;
  border-bottom: 1px solid var(--fn-line);
  font: 500 .66rem/1 var(--fn-font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fn-ink-faint);
  background: transparent;
}
.md-typeset .fn-pr-worked__t th:not(:first-child),
.md-typeset .fn-pr-worked__t td:not(:first-child) { text-align: right; }
.md-typeset .fn-pr-worked__t td {
  padding: .45rem 0;
  border-bottom: 1px solid var(--fn-line-soft);
  color: var(--fn-ink-soft);
  background: transparent;
  font: 400 .82rem/1.5 var(--fn-font-mono);
}
/* Our own figure is the one the section is about, and it is the one that does
   not move down the column. Weight rather than colour: the page's three reds
   are spent. */
.md-typeset .fn-pr-worked__t td:nth-child(2) { color: var(--fn-ink); font-weight: 500; }
.md-typeset .fn-pr-worked__t tbody tr:last-child td { border-bottom: 0; }
.md-typeset .fn-pr-worked__note {
  margin: 1rem 0 0;
  padding-top: .9rem;
  border-top: 1px dashed var(--fn-line);
  font-size: .82rem;
  line-height: 1.55;
  color: var(--fn-ink-soft);
}
.md-typeset .fn-pr-worked__note b { color: var(--fn-ink); }

/* ---------- FAQ ---------- */

.fn-pr-faq { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1rem 2.2rem; }
.fn-pr-faq > div { border-top: 1px solid var(--fn-line); padding-top: .95rem; }
.md-typeset .fn-pr-faq h3 {
  font: 600 1rem/1.35 var(--fn-font-display);
  margin: 0 0 .4rem;
  letter-spacing: -.008em;
}
/* Inline links inside the FAQ answers, styled against Material's default. Material
   paints links in the accent, which here is the single printer's red — and this page
   budgets red to three appearances (the section numbers, the picked tier, the closer).
   Five red passages in one section spends that budget in a place that is not the
   argument. Ink plus a faint rule reads as a reference mark rather than a call to
   action, which is what a link in a supporting answer actually is; red returns on
   hover, where it costs nothing at rest. */
.md-typeset .fn-pr-faq a {
  color: var(--fn-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--fn-line);
  transition: color .18s ease, border-color .18s ease;
}
.md-typeset .fn-pr-faq a:hover,
.md-typeset .fn-pr-faq a:focus-visible {
  color: var(--fn-accent-ink);
  border-bottom-color: var(--fn-accent-ink);
}

.md-typeset .fn-pr-faq p {
  margin: 0;
  font-size: .86rem;
  line-height: 1.58;
  color: var(--fn-ink-soft);
}

/* ---------- Closer ---------- */

.fn-pr-closer { position: relative; overflow: hidden; }
.fn-pr-closer > .fn-pr-closer__art { position: absolute; right: -30px; top: -24px; width: 300px; color: #fff; opacity: .13; }
.fn-pr-closer__art svg {
  width: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fn-pr-closer > div,
.fn-pr-closer > .fn-btn { position: relative; }

/* ---------- Narrow ---------- */

@media screen and (max-width: 76.1875em) {
  .fn-pr-zero { grid-template-columns: repeat(2, 1fr); }
  .fn-pr-flow__rail { grid-template-columns: 1fr; }
  .fn-pr-flow__arrow { display: none; }
  .fn-pr-flow__step { padding: 1.2rem 0; border-bottom: 1px dashed var(--fn-line); }
  .fn-pr-faq { grid-template-columns: 1fr; }
  .fn-sk--coin { display: none; }
}

@media screen and (max-width: 44.9375em) {
  .fn-pr-zero { grid-template-columns: 1fr; }
  .fn-pr-zero > div { border-right: 0; }
  /* Two plans stack here rather than at the section-rail breakpoint. That
     earlier breakpoint was set when this was a three-column grid, where three
     cards genuinely run out of room on a laptop; two cards do not, and stacking
     them at 1219px turned the comparison into two full-width slabs a reader
     had to scroll between to compare. */
  .fn-pr-rate { grid-template-columns: 1fr; }
  .fn-pr-tier + .fn-pr-tier { border-left: 0; border-top: 1px solid var(--fn-line-soft); }
  .md-typeset .fn-pr-tier__for { min-height: 0; }
  .fn-pr-rate__foot { padding: .95rem 1.2rem; }
}

@media (prefers-reduced-motion: reduce) {
  .fn-pr-flow__arrow path.dash { animation: none; }
}

/* ---------- Pricing: motion ----------
   The page had exactly one moving part (the marching dashes on the flow
   arrows), which left the longest page on the site feeling static under the
   scroll. Everything below is either the site's existing load reveal reused,
   or a scroll-linked reveal on the same `view()` timeline the reference pages
   use — no new vocabulary, no new colour, and nothing that animates a value a
   reader might be trying to read.

   Two rules the additions keep to. Only `transform`, `opacity` and colour are
   animated, so every one of these composites and none of them lays out. And
   the red budget is untouched: motion here is carried by position and weight,
   never by turning something accent-coloured on scroll. ---------- */

/* The hero is this page's masthead, so it takes the masthead's staggered load
   reveal — the same `fn-enter` and the same cadence as `.md-content__inner`'s
   on every generated page. It is wrapped in `.fn-pr-hero` rather than sitting
   as a direct child of `.md-content__inner`, which is the only reason the
   existing selector did not already reach it. */
.fn-pr-hero > .fn-eyebrow,
.fn-pr-hero > h1,
.fn-pr-hero > .fn-dek,
.fn-pr-hero > .fn-pr-chips {
  animation: fn-enter 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
.fn-pr-hero > .fn-eyebrow   { animation-delay: 0.02s; }
.fn-pr-hero > h1            { animation-delay: 0.09s; }
.fn-pr-hero > .fn-dek       { animation-delay: 0.17s; }
.fn-pr-hero > .fn-pr-chips  { animation-delay: 0.25s; }

/* The chips land one after another rather than as a block: they are four
   separate claims, and arriving separately is what says so. No blur on these
   — at chip size it reads as a rendering fault rather than as depth. */
.fn-pr-chips .fn-chip {
  animation: fn-chip-in 0.42s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
.fn-pr-chips .fn-chip:nth-child(1) { animation-delay: 0.30s; }
.fn-pr-chips .fn-chip:nth-child(2) { animation-delay: 0.37s; }
.fn-pr-chips .fn-chip:nth-child(3) { animation-delay: 0.44s; }
.fn-pr-chips .fn-chip:nth-child(4) { animation-delay: 0.51s; }
@keyframes fn-chip-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The zero band sits above the fold, so it continues the load cadence instead
   of waiting for a scroll that may never come. Left to right, because the
   figures are an argument in order: no cut, no per-user, then the two ways in
   which the first year costs less than the sticker price. */
.fn-pr-zero > div {
  animation: fn-enter 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
.fn-pr-zero > div:nth-child(1) { animation-delay: 0.46s; }
.fn-pr-zero > div:nth-child(2) { animation-delay: 0.53s; }
.fn-pr-zero > div:nth-child(3) { animation-delay: 0.60s; }
.fn-pr-zero > div:nth-child(4) { animation-delay: 0.67s; }

/* Everything past the fold is scroll-linked. `view()` gives each element its
   own timeline, so a stagger across a row is expressed as a *range*, not a
   delay: the three cards enter the viewport together and settle at 300px,
   340px and 380px of scroll, which reads as left-to-right without any element
   being held invisible longer than the one beside it.

   Ranges are lengths rather than percentages for the reason recorded on the
   reference-page reveal above: a percentage range is relative to the element's
   own height, so a block taller than the viewport can never finish.

   The rate block reveals as ONE unit, not as three staggered cards, and that is
   forced rather than chosen. `view()` resolves against the subject's nearest
   scroll container, and `.fn-pr-rate` is `overflow: hidden` (it clips the grid
   to its rounded corners). That makes it a scrollport which never scrolls, so a
   timeline on anything inside it is frozen at whatever progress the subject's
   static position happens to yield — the tiers froze at 1 and looked fine, but
   `.fn-pr-rate__foot` froze at 0.41 and sat permanently half-faded, at no scroll
   position recoverable by the reader. Animate the container, whose own nearest
   scrollport is the root. Before adding a scroll-linked reveal anywhere else on
   this page, check for a clipped ancestor first. */
@supports (animation-timeline: view()) {
  .fn-pr-rate,
  .fn-pr-flow__step,
  .fn-pr-faq > div,
  .fn-pr-worked__t tbody tr,
  .fn-pr-flow__aside {
    animation: fn-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 340px;
  }
  .fn-pr-flow__step:nth-child(1),
  .fn-pr-worked__t tbody tr:nth-child(1) { animation-range: entry 0% entry 300px; }
  .fn-pr-worked__t tbody tr:nth-child(3) { animation-range: entry 0% entry 380px; }
  .fn-pr-faq > div:nth-child(even) { animation-range: entry 0% entry 390px; }

  /* The section numbers are the page's spine. They draw their own rule as the
     section arrives — the same `fn-rule` trick as the docs-note bar, scaled to
     a short horizontal hairline. */
  .fn-pr-sec .fn-sec-head::after {
    animation: fn-sec-rule linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 260px;
  }
  @keyframes fn-sec-rule {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

/* A hairline under each section head, drawn by the rule above. It is a border
   in every other respect — declared outside the @supports so the line is there
   whether or not the browser can animate it. */
.fn-pr-sec .fn-sec-head { position: relative; }
.fn-pr-sec .fn-sec-head::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -0.55rem;
  width: 100%;
  height: 1px;
  background: var(--fn-line-soft);
  transform-origin: 0 50%;
}

/* Hover. The rate card is the only thing on the page a reader is choosing
   between, so it is the only thing that answers the pointer. The recommended
   column already carries a red rule across its top; the other two borrow the
   same gesture in the neutral line colour on hover, which marks the card
   without spending a fourth red. */
.fn-pr-tier {
  transition: background-color 0.18s ease, box-shadow 0.18s ease;
}
.fn-pr-tier:hover { background: var(--fn-panel); box-shadow: inset 0 3px 0 var(--fn-line); }
.fn-pr-tier--pick:hover { background: #fbf9f9; box-shadow: inset 0 3px 0 var(--fn-accent); }

/* The worked example's whole point is that the middle column does not move, so
   the rows answer the pointer as rows — the comparison is read across, not
   down. */
.md-typeset .fn-pr-worked__t tbody tr { transition: background-color 0.16s ease; }
.md-typeset .fn-pr-worked__t tbody tr:hover td { background: var(--fn-panel); }

/* The closer's watermark drifts, very slowly and very slightly. At 13% opacity
   behind the heading it is texture rather than an object, and texture that is
   perfectly still on an otherwise-scrolling page reads as a printed artefact —
   which, on a page arguing that nothing here is a moving target, is the wrong
   note to end on. 68s, no easing worth naming, never in the reader's way. */
.fn-pr-closer > .fn-pr-closer__art {
  animation: fn-pr-drift 68s ease-in-out infinite alternate;
}
@keyframes fn-pr-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-14px, 8px, 0) scale(1.05); }
}

@media (prefers-reduced-motion: reduce) {
  .fn-pr-hero > .fn-eyebrow,
  .fn-pr-hero > h1,
  .fn-pr-hero > .fn-dek,
  .fn-pr-hero > .fn-pr-chips,
  .fn-pr-chips .fn-chip,
  .fn-pr-zero > div,
  .fn-pr-closer > .fn-pr-closer__art { animation: none; }
  @supports (animation-timeline: view()) {
    .fn-pr-rate,
    .fn-pr-flow__step,
    .fn-pr-faq > div,
    .fn-pr-worked__t tbody tr,
    .fn-pr-flow__aside,
    .fn-pr-sec .fn-sec-head::after { animation: none; }
  }
}
