/* Polytope v0.7.2. Generated; edit css/src instead. */
@layer ply.reset, ply.base, ply.layout, ply.components, ply.utilities;
/* Self-hosted faces. Ultra: Apache 2.0. Libre Franklin and JetBrains Mono: SIL OFL 1.1. License texts: ./fonts/LICENSE-*.txt */
@font-face { font-family: "Ultra"; font-style: normal; font-weight: 400; font-display: swap; src: url("./fonts/ultra-latin-400.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* Fallback faces: installed fonts scaled to the metrics of the self-hosted ones, so text keeps its
   size and line breaks while the real faces load, and nothing moves when they arrive. Each one is a
   single named face, so a browser resolves it once; a list of system family names would be looked
   up again at every size and weight, which is slow where many fonts are installed.
   size-adjust is the width of the docs' titles, ledes, and code in the real face over the same text
   in the fallback, measured in Chromium. The overrides are the real face's ascent, descent, and
   line gap over its em, divided by size-adjust.
   Ultra (ascent 2066, descent 561, gap 0 on 2048): Georgia Bold, on macOS and Windows, at 122.34%;
   where Georgia is missing, Liberation Serif Bold or Times New Roman Bold, which share metrics, at
   147.12%. Linux has Liberation wherever Chrome does. */
@font-face { font-family: "Ultra Fallback"; font-style: normal; font-weight: 400; src: local("Georgia Bold"), local("Georgia-Bold"); size-adjust: 122.34%; ascent-override: 82.46%; descent-override: 22.39%; line-gap-override: 0%; }
@font-face { font-family: "Ultra Fallback Serif"; font-style: normal; font-weight: 400; src: local("Liberation Serif Bold"), local("LiberationSerif-Bold"), local("Times New Roman Bold"), local("TimesNewRomanPS-BoldMT"); size-adjust: 147.12%; ascent-override: 68.57%; descent-override: 18.62%; line-gap-override: 0%; }
/* Libre Franklin (ascent 966, descent 246, gap 0 on 1000): Arial, or Liberation Sans, which shares
   its metrics. Regular and medium text measured 102.9% and 104.0% of Arial; semibold and bold,
   98.0% and 99.1% of Arial Bold. Each face takes a value near the weight most text uses. */
@font-face { font-family: "Libre Franklin Fallback"; font-style: normal; font-weight: 100 549; src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans"); size-adjust: 103%; ascent-override: 93.79%; descent-override: 23.88%; line-gap-override: 0%; }
@font-face { font-family: "Libre Franklin Fallback"; font-style: normal; font-weight: 550 900; src: local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"), local("LiberationSans-Bold"); size-adjust: 98.5%; ascent-override: 98.07%; descent-override: 24.97%; line-gap-override: 0%; }
/* JetBrains Mono (ascent 1020, descent 300, gap 0 on 1000): Menlo, Liberation Mono, or Courier New,
   all 0.6em wide like it. */
@font-face { font-family: "JetBrains Mono Fallback"; font-style: normal; font-weight: 100 800; src: local("Menlo Regular"), local("Menlo-Regular"), local("Liberation Mono"), local("LiberationMono"), local("Courier New"), local("CourierNewPSMT"); ascent-override: 102%; descent-override: 30%; line-gap-override: 0%; }
@font-face { font-family: "Libre Franklin Variable"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("./fonts/libre-franklin-latin-wght.woff2") format("woff2-variations"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Libre Franklin Variable"; font-style: italic; font-weight: 100 900; font-display: swap; src: url("./fonts/libre-franklin-latin-wght-italic.woff2") format("woff2-variations"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "JetBrains Mono Variable"; font-style: normal; font-weight: 100 800; font-display: swap; src: url("./fonts/jetbrains-mono-latin-wght.woff2") format("woff2-variations"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* Generated from tokens/tokens.json. Do not edit by hand. */
:root {
  color-scheme: light dark;
  --ply-white: #FFFFFF;
  --ply-ink-50: #F6F7F6;
  --ply-ink-100: #EEF0EF;
  --ply-ink-200: #D9DAD6;
  --ply-ink-300: #B9BAB4;
  --ply-ink-400: #8F8F89;
  --ply-ink-500: #6B6B66;
  --ply-ink-600: #4A4A47;
  --ply-ink-700: #3A3A37;
  --ply-ink-800: #262624;
  --ply-ink-900: #1C1C1A;
  --ply-ink-950: #141414;
  --ply-tomato-100: #FBE3DE;
  --ply-tomato-400: #F0654F;
  --ply-tomato-500: #D9412B;
  --ply-tomato-600: #B8321F;
  --ply-mustard-100: #FBEFD2;
  --ply-mustard-400: #F2B83A;
  --ply-mustard-500: #E9A81C;
  --ply-mustard-700: #7A5300;
  --ply-teal-100: #DDF1F0;
  --ply-teal-300: #5CC2C7;
  --ply-teal-700: #0F6E73;
  --ply-cobalt-100: #E3EAF8;
  --ply-cobalt-200: #C2D1F0;
  --ply-cobalt-300: #95AEE3;
  --ply-cobalt-400: #6A8BD4;
  --ply-cobalt-500: #3A62C4;
  --ply-cobalt-600: #2B4A99;
  --ply-cobalt-700: #1D336B;
  --ply-font-display: "Ultra", "Ultra Fallback", "Ultra Fallback Serif", Georgia, serif;
  --ply-font-text: "Libre Franklin Variable", "Libre Franklin Fallback", Arial, sans-serif;
  --ply-font-mono: "JetBrains Mono Variable", "JetBrains Mono Fallback", Menlo, monospace;
  --ply-size-xs: 0.8125rem;
  --ply-size-sm: 0.875rem;
  --ply-size-ui: 0.9375rem;
  --ply-size-md: 1rem;
  --ply-size-body: 1.0625rem;
  --ply-size-body-wide: 1.125rem;
  --ply-size-lg: 1.25rem;
  --ply-size-xl: 1.5rem;
  --ply-size-2xl: 2rem;
  --ply-size-3xl: 2.75rem;
  --ply-size-4xl: 4rem;
  --ply-size-display: clamp(3rem, 1.6rem + 4.4vw, 5.75rem);
  --ply-weight-regular: 400;
  --ply-weight-medium: 500;
  --ply-weight-semibold: 600;
  --ply-weight-bold: 700;
  --ply-leading-display: 0.98;
  --ply-leading-tight: 1.2;
  --ply-leading-normal: 1.45;
  --ply-leading-relaxed: 1.6;
  --ply-space-0: 0;
  --ply-space-1: 0.25rem;
  --ply-space-2: 0.5rem;
  --ply-space-3: 0.75rem;
  --ply-space-4: 1rem;
  --ply-space-5: 1.5rem;
  --ply-space-6: 2rem;
  --ply-space-7: 3rem;
  --ply-space-8: 4rem;
  --ply-space-9: 6rem;
  --ply-space-10: 8rem;
  --ply-border-hair: 1px;
  --ply-border-control: 2px;
  --ply-border-rule: 3px;
  --ply-radius-none: 0;
  --ply-radius-ball: 999px;
  --ply-duration-fast: 120ms;
  --ply-duration-base: 200ms;
  --ply-duration-slow: 320ms;
  --ply-duration-drop: 900ms;
  --ply-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ply-ease-gravity: cubic-bezier(0.55, 0, 1, 0.45);
  --ply-layout-measure: 36rem;
  --ply-layout-measure-wide: 38rem;
  --ply-layout-margin: 14rem;
  --ply-layout-gutter: clamp(1rem, 0.2rem + 4vw, 4.5rem);
  --ply-layout-sidebar: 17.5rem;
  --ply-layout-toc: 15rem;
  --ply-layout-max: 90rem;
  --ply-viz-seq-1: #E3EAF8;
  --ply-viz-seq-2: #C2D1F0;
  --ply-viz-seq-3: #95AEE3;
  --ply-viz-seq-4: #6A8BD4;
  --ply-viz-seq-5: #3A62C4;
  --ply-viz-seq-6: #2B4A99;
  --ply-viz-seq-7: #1D336B;
  --ply-viz-div-1: #2B4A99;
  --ply-viz-div-2: #6A8BD4;
  --ply-viz-div-3: #C2D1F0;
  --ply-viz-div-4: #D9DAD6;
  --ply-viz-div-5: #F6C3B9;
  --ply-viz-div-6: #E8765F;
  --ply-viz-div-7: #B8321F;
  --ply-status-good: #1E7A3C;
  --ply-status-warning: #F2B71F;
  --ply-status-serious: #E8743B;
  --ply-status-critical: #B3261E;
}
:root, :root[data-theme="light"] {
  color-scheme: light;
  --ply-color-bg: var(--ply-white);
  --ply-color-surface-sunken: var(--ply-ink-100);
  --ply-color-panel: var(--ply-ink-950);
  --ply-color-panel-text: var(--ply-ink-50);
  --ply-color-panel-muted: var(--ply-ink-300);
  --ply-color-panel-rule: var(--ply-ink-600);
  --ply-color-text: var(--ply-ink-950);
  --ply-color-text-muted: var(--ply-ink-600);
  --ply-color-text-subtle: var(--ply-ink-500);
  --ply-color-rule-strong: var(--ply-ink-950);
  --ply-color-rule: var(--ply-ink-200);
  --ply-color-accent: var(--ply-tomato-500);
  --ply-color-accent-text: var(--ply-tomato-600);
  --ply-color-on-accent: var(--ply-white);
  --ply-color-link: var(--ply-teal-700);
  --ply-color-link-hover: var(--ply-ink-950);
  --ply-color-note: var(--ply-mustard-500);
  --ply-color-note-soft: var(--ply-mustard-100);
  --ply-color-flag: var(--ply-mustard-100);
  --ply-color-flag-text: var(--ply-ink-950);
  --ply-color-flag-edge: var(--ply-mustard-500);
  --ply-color-focus: var(--ply-teal-700);
  --ply-color-focus-on-panel: var(--ply-mustard-400);
  --ply-color-selection: var(--ply-mustard-500);
  --ply-color-code-comment: var(--ply-mustard-400);
  --ply-color-code-keyword: var(--ply-tomato-400);
  --ply-color-code-string: var(--ply-teal-300);
  --ply-color-code-number: var(--ply-cobalt-300);
  --ply-viz-1: #D9412B;
  --ply-viz-2: #009B93;
  --ply-viz-3: #3A62C4;
  --ply-viz-4: #4C8C2B;
  --ply-viz-5: #2E8FD0;
  --ply-viz-6: #C28A0A;
  --ply-viz-7: #9A3E96;
  --ply-viz-8: #E06C91;
  --ply-viz-surface: #FFFFFF;
}
@media screen and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ply-color-bg: #161615;
    --ply-color-surface-sunken: var(--ply-ink-900);
    --ply-color-panel: var(--ply-ink-800);
    --ply-color-panel-text: var(--ply-ink-50);
    --ply-color-panel-muted: var(--ply-ink-300);
    --ply-color-panel-rule: var(--ply-ink-600);
    --ply-color-text: #F2F2EE;
    --ply-color-text-muted: var(--ply-ink-300);
    --ply-color-text-subtle: var(--ply-ink-400);
    --ply-color-rule-strong: #F2F2EE;
    --ply-color-rule: var(--ply-ink-700);
    --ply-color-accent: var(--ply-tomato-400);
    --ply-color-accent-text: var(--ply-tomato-400);
    --ply-color-on-accent: var(--ply-ink-950);
    --ply-color-link: var(--ply-teal-300);
    --ply-color-link-hover: #F2F2EE;
    --ply-color-note: var(--ply-mustard-500);
    --ply-color-note-soft: #3A2F14;
    --ply-color-flag: var(--ply-mustard-100);
    --ply-color-flag-text: var(--ply-ink-950);
    --ply-color-flag-edge: var(--ply-mustard-500);
    --ply-color-focus: var(--ply-mustard-400);
    --ply-color-focus-on-panel: var(--ply-mustard-400);
    --ply-color-selection: var(--ply-mustard-500);
    --ply-color-code-comment: var(--ply-mustard-400);
    --ply-color-code-keyword: var(--ply-tomato-400);
    --ply-color-code-string: var(--ply-teal-300);
    --ply-color-code-number: var(--ply-cobalt-300);
    --ply-viz-1: #E04B34;
    --ply-viz-2: #00A39A;
    --ply-viz-3: #5A7FDB;
    --ply-viz-4: #5A9A35;
    --ply-viz-5: #2E8FD0;
    --ply-viz-6: #B98200;
    --ply-viz-7: #9A52C8;
    --ply-viz-8: #D9658B;
    --ply-viz-surface: #1C1C1A;
  }
}
@media screen {
  :root[data-theme="dark"] {
    color-scheme: dark;
    --ply-color-bg: #161615;
    --ply-color-surface-sunken: var(--ply-ink-900);
    --ply-color-panel: var(--ply-ink-800);
    --ply-color-panel-text: var(--ply-ink-50);
    --ply-color-panel-muted: var(--ply-ink-300);
    --ply-color-panel-rule: var(--ply-ink-600);
    --ply-color-text: #F2F2EE;
    --ply-color-text-muted: var(--ply-ink-300);
    --ply-color-text-subtle: var(--ply-ink-400);
    --ply-color-rule-strong: #F2F2EE;
    --ply-color-rule: var(--ply-ink-700);
    --ply-color-accent: var(--ply-tomato-400);
    --ply-color-accent-text: var(--ply-tomato-400);
    --ply-color-on-accent: var(--ply-ink-950);
    --ply-color-link: var(--ply-teal-300);
    --ply-color-link-hover: #F2F2EE;
    --ply-color-note: var(--ply-mustard-500);
    --ply-color-note-soft: #3A2F14;
    --ply-color-flag: var(--ply-mustard-100);
    --ply-color-flag-text: var(--ply-ink-950);
    --ply-color-flag-edge: var(--ply-mustard-500);
    --ply-color-focus: var(--ply-mustard-400);
    --ply-color-focus-on-panel: var(--ply-mustard-400);
    --ply-color-selection: var(--ply-mustard-500);
    --ply-color-code-comment: var(--ply-mustard-400);
    --ply-color-code-keyword: var(--ply-tomato-400);
    --ply-color-code-string: var(--ply-teal-300);
    --ply-color-code-number: var(--ply-cobalt-300);
    --ply-viz-1: #E04B34;
    --ply-viz-2: #00A39A;
    --ply-viz-3: #5A7FDB;
    --ply-viz-4: #5A9A35;
    --ply-viz-5: #2E8FD0;
    --ply-viz-6: #B98200;
    --ply-viz-7: #9A52C8;
    --ply-viz-8: #D9658B;
    --ply-viz-surface: #1C1C1A;
  }
}
/* Paper is always the light theme, and black panels print as ink on white. */
@media print {
  :root {
    --ply-color-panel: var(--ply-white);
    --ply-color-panel-text: var(--ply-ink-950);
    --ply-color-panel-muted: var(--ply-ink-600);
    --ply-color-panel-rule: var(--ply-ink-200);
    --ply-color-focus-on-panel: var(--ply-teal-700);
    --ply-color-code-comment: var(--ply-ink-600);
    --ply-color-code-keyword: var(--ply-tomato-600);
    --ply-color-code-string: var(--ply-teal-700);
    --ply-color-code-number: var(--ply-cobalt-600);
  }
}

/* ---- reset.css ---- */
@layer ply.reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { min-height: 100dvh; }
  img, picture, video, canvas, svg { display: block; max-width: 100%; }
  input, button, textarea, select { font: inherit; color: inherit; }
  p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
  h1, h2, h3, h4 { text-wrap: balance; }
  p { text-wrap: pretty; }
  ul[role="list"], ol[role="list"] { list-style: none; padding: 0; }
  /* hidden always hides, even on an element whose class sets a display, such as a button. */
  [hidden]:not([hidden="until-found"]) { display: none !important; }
  /* Reduced motion: everything jumps to its end state. A subtree marked
     data-ply-motion="keep" opts out, for motion the reader starts and needs. */
  @media (prefers-reduced-motion: reduce) {
    :not([data-ply-motion="keep"], [data-ply-motion="keep"] *),
    :not([data-ply-motion="keep"], [data-ply-motion="keep"] *)::before,
    :not([data-ply-motion="keep"], [data-ply-motion="keep"] *)::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
  }
}

/* ---- base.css ---- */
@layer ply.base {
  html {
    background: var(--ply-color-bg);
    color: var(--ply-color-text);
    font-family: var(--ply-font-text);
    font-size: 100%;
    line-height: var(--ply-leading-relaxed);
    accent-color: var(--ply-color-accent);
    caret-color: var(--ply-color-accent);
    scrollbar-color: var(--ply-color-text-subtle) var(--ply-color-bg);
    scroll-padding-top: var(--ply-space-8);
    overflow-wrap: break-word;
  }
  body {
    font-size: var(--ply-size-body);
    font-optical-sizing: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
  ::selection { background: var(--ply-color-selection); color: var(--ply-ink-950); }

  h1, h2 { font-family: var(--ply-font-display); font-weight: 400; line-height: var(--ply-leading-display); letter-spacing: -0.01em; }
  h1 { font-size: var(--ply-size-display); }
  h2 { font-size: var(--ply-size-2xl); line-height: 1.1; }
  h3 { font-size: var(--ply-size-xl); font-weight: var(--ply-weight-bold); line-height: var(--ply-leading-tight); letter-spacing: -0.01em; }
  h4 { font-size: var(--ply-size-lg); font-weight: var(--ply-weight-bold); line-height: var(--ply-leading-tight); }
  h5, h6 { font-size: var(--ply-size-md); font-weight: var(--ply-weight-bold); }

  a { color: var(--ply-color-link); font-weight: var(--ply-weight-semibold); text-decoration-line: underline; text-decoration-thickness: 2px; text-underline-offset: 0.2em; text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); transition: color var(--ply-duration-fast) var(--ply-ease-out), text-decoration-color var(--ply-duration-fast) var(--ply-ease-out); }
  a:hover { color: var(--ply-color-link-hover); text-decoration-color: var(--ply-color-accent); }

  :focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: 2px; }

  /* Surfaces. Inside a black panel or a code block the quiet text tokens and the focus ring
     switch to their panel values, so utilities and controls read correctly there.
     A panel also takes over text, ground, and rules, so a checkbox, switch, ghost button,
     or table inside it draws in chalk on black instead of black on black.
     On mustard the ring is black. Mark other surfaces with data-surface="panel" or "note". */
  .ply-panel, .ply-code, [data-surface="panel"] {
    --ply-color-text-muted: var(--ply-color-panel-muted);
    --ply-color-text-subtle: var(--ply-color-panel-muted);
    --ply-color-focus: var(--ply-color-focus-on-panel);
  }
  .ply-panel, [data-surface="panel"] {
    --ply-color-text: var(--ply-color-panel-text);
    --ply-color-bg: var(--ply-color-panel);
    --ply-color-rule-strong: var(--ply-color-panel-text);
    --ply-color-rule: var(--ply-color-panel-rule);
    --ply-color-surface-sunken: color-mix(in srgb, var(--ply-color-panel-text) 14%, var(--ply-color-panel));
  }
  .ply-note, mark, [data-surface="note"] { --ply-color-focus: var(--ply-ink-950); }
  .ply-note:is(.ply-note--soft, .ply-note--info, .ply-note--success) { --ply-color-focus: inherit; }
  .ply-note--danger { --ply-color-focus: var(--ply-color-focus-on-panel); }

  strong, b { font-weight: var(--ply-weight-bold); }
  small { font-size: var(--ply-size-sm); }
  hr { border: 0; border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); margin-block: var(--ply-space-7); }
  abbr[title] { text-decoration: underline dotted; text-underline-offset: 0.2em; cursor: help; }
  mark { background: var(--ply-color-note); color: var(--ply-ink-950); padding-inline: 0.15em; }

  code, kbd, samp, pre { font-family: var(--ply-font-mono); font-size: 0.875em; font-variant-ligatures: none; }
  :not(pre) > code { background: var(--ply-color-surface-sunken); padding: 0.1em 0.35em; }
  table { font-variant-numeric: tabular-nums; }
  /* Element defaults that match .ply-kbd and .ply-quote; in the base layer so any class wins. */
  kbd { display: inline-block; font-family: var(--ply-font-mono); font-size: 0.8em; line-height: 1.4; padding: 0 0.4em; border: 1.5px solid currentColor; border-bottom-width: 3px; }
  blockquote { margin: var(--ply-space-5) 0; padding-inline-start: var(--ply-space-5); border-inline-start: 1px solid var(--ply-color-rule-strong); font-size: var(--ply-size-lg); line-height: var(--ply-leading-normal); max-width: var(--ply-layout-measure); }
  ::placeholder { color: var(--ply-color-text-subtle); opacity: 1; }
}

/* ---- layout.css ---- */
@layer ply.layout {
  /* Page frame: top bar, mast, then the docs grid or an article. */
  .ply-container { width: 100%; max-width: var(--ply-layout-max); margin-inline: auto; padding-inline: var(--ply-layout-gutter); }

  .ply-rule { border: 0; border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); margin: 0; }

  .ply-mast { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ply-space-6); padding-block: var(--ply-space-7) var(--ply-space-7); align-items: start; }
  .ply-mast > :first-child { container-type: inline-size; }
  /* The title sizes to its longest word: --_em is that word's width in em (MastTitle writes
     it; --_chars, a count of characters, is the older estimate). Short of 24px a word too
     long even for that breaks where it must. */
  .ply-mast__title { font-family: var(--ply-font-display); font-size: min(var(--ply-size-display), max(var(--ply-size-xl), 100cqi / var(--_em, calc(var(--_chars, 8) * 0.76)))); line-height: var(--ply-leading-display); letter-spacing: -0.01em; font-weight: 400; }
  .ply-mast--compact { padding-block: var(--ply-space-7) var(--ply-space-6); }
  .ply-mast--compact .ply-mast__title { --ply-size-display: clamp(var(--ply-size-3xl), 1.4rem + 3.6vw, 4.5rem); }
  .ply-mast__stop { color: var(--ply-color-accent); }
  .ply-mast__lede { font-size: var(--ply-size-lg); line-height: var(--ply-leading-normal); max-width: 36rem; margin-top: var(--ply-space-5); }
  /* The exhibit column opens only when there is an exhibit; a mast without one (a list page)
     keeps the whole width for its title. */
  @media (min-width: 64rem) {
    .ply-mast { gap: var(--ply-space-7); padding-block: var(--ply-space-7) var(--ply-space-8); }
    .ply-mast:has(> .ply-mast__exhibit:not(:empty)) { grid-template-columns: minmax(0, 1fr) minmax(0, 35rem); }
    .ply-mast--compact { padding-block: var(--ply-space-7) var(--ply-space-6); }
  }

  /* Reading frames: the docs grid and the article. Both put the text at the centre of the
     page when there is room, with navigation at the page's start edge. Their main column
     (container ply-main) runs from the text's edge to the page's end edge, and the space
     after the text holds sidenotes and margin figures; on a docs page, wide blocks run on
     into it as well. Each side track is
     at least what it holds; past that they split the rest evenly, which centres the text. */

  /* Docs grid: the chapter sidebar, with the on-this-page list under the current page. */
  .ply-docs { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
  .ply-docs__nav { display: none; }
  .ply-docs__toc { display: none; }
  .ply-docs__main { min-width: 0; padding-block: var(--ply-space-6) var(--ply-space-9); container: ply-main / inline-size; }
  @media (min-width: 56rem) {
    .ply-docs { grid-template-columns: minmax(calc(var(--ply-layout-sidebar) + var(--ply-space-7)), 1fr) minmax(0, var(--ply-layout-measure)) minmax(0, 1fr); }
    .ply-docs__nav { display: block; grid-column: 1; grid-row: 1; width: var(--ply-layout-sidebar); padding-block: var(--ply-space-6); position: sticky; top: 0; align-self: start; max-height: 100dvh; overflow-y: auto; }
    .ply-docs__main { grid-column: 2 / -1; grid-row: 1; }
  }
  /* Deprecated in 0.6.0: an on-this-page column after the main column. A grid that still
     has one keeps the old three columns from 80rem; new pages put the list in the sidebar. */
  @media (min-width: 80rem) {
    .ply-docs:has(> .ply-docs__toc) { grid-template-columns: var(--ply-layout-sidebar) minmax(0, 1fr) var(--ply-layout-toc); }
    .ply-docs:has(> .ply-docs__toc) > .ply-docs__nav { width: auto; }
    .ply-docs:has(> .ply-docs__toc) > .ply-docs__main { grid-column: 2; padding-inline-start: var(--ply-space-7); }
    .ply-docs__toc { display: block; grid-column: 3; grid-row: 1; padding-block: var(--ply-space-6) 0; padding-inline-start: var(--ply-space-6); position: sticky; top: 0; align-self: start; }
  }

  /* Article: a paper, a post, a page of notes. The on-this-page list hangs in a rail at the
     page's start edge from 80rem. Below 60rem it is one column. A header set in a
     .ply-article--head of its own lines up with the text under it. */
  .ply-article { display: grid; grid-template-columns: minmax(0, 1fr); padding-block: var(--ply-space-6) var(--ply-space-8); }
  .ply-article--head { padding-block: 0; }
  .ply-article__rail { display: none; }
  .ply-article__main { grid-column: 1 / -1; grid-row: 1; min-width: 0; container: ply-main / inline-size; }
  /* Where the rail is closed, the same list waits in a disclosure above the text. */
  .ply-article__contents { margin-bottom: var(--ply-space-6); border-bottom: var(--ply-border-hair) solid var(--ply-color-rule); }
  .ply-article__contents > summary { font-size: var(--ply-size-ui); }
  .ply-article__contents .ply-toc { padding-block-end: var(--ply-space-3); font-size: var(--ply-size-ui); }
  @media (min-width: 60rem) {
    .ply-article { grid-template-columns: minmax(var(--_lead, 0px), 1fr) minmax(0, var(--ply-layout-measure)) minmax(calc(var(--ply-space-6) + var(--ply-layout-margin)), 1fr); }
    .ply-article__main { grid-column: 2 / -1; }
  }
  @media (min-width: 80rem) {
    .ply-article { --_lead: calc(var(--ply-layout-toc) + var(--ply-space-7)); }
    .ply-article__rail { display: block; grid-column: 1; grid-row: 1; width: var(--ply-layout-toc); position: sticky; top: var(--ply-space-5); align-self: start; max-height: calc(100dvh - 2 * var(--ply-space-5)); overflow-y: auto; padding: var(--ply-space-1) var(--ply-space-2) var(--ply-space-1) 0; margin-block-start: calc(-1 * var(--ply-space-1)); }
    .ply-article__contents { display: none; }
  }

  /* The widest screens set the text a step larger, in a column that grows with it. */
  @media (min-width: 100rem) {
    :is(.ply-docs, .ply-article) { --ply-layout-measure: var(--ply-layout-measure-wide); }
    :is(.ply-docs, .ply-article) .ply-prose { font-size: var(--ply-size-body-wide); }
  }

  /* Reading column: everything inside keeps a readable measure; wide blocks opt out. */
  .ply-prose { max-width: var(--ply-layout-measure); }
  .ply-prose > * + * { margin-top: var(--ply-space-4); }
  .ply-prose > :is(h2, h3, h4) { margin-top: var(--ply-space-7); }
  .ply-prose > :is(h2, h3, h4) + * { margin-top: var(--ply-space-3); }
  .ply-prose > :first-child { margin-top: 0; }
  .ply-prose > h2 { scroll-margin-top: var(--ply-space-6); }
  .ply-prose :is(ul, ol) { padding-inline-start: 1.25em; }
  .ply-prose li + li { margin-top: var(--ply-space-2); }
  .ply-prose ul > li::marker { color: var(--ply-color-accent); }
  .ply-wide { max-width: none; }

  /* Scroll shadows: a soft edge appears on whichever side has more to scroll to.
     Used by charts, display equations, wide tables, and code. Each scroller is also the
     containing block, so an absolute child such as .ply-visually-hidden stays inside it
     instead of widening the page. */
  .ply-scroll-cue, .ply-chart__plot, .ply-equation__body, .ply-table-wrap, .ply-code pre {
    position: relative;
    --_cue:
      linear-gradient(to right, var(--_cue-bg, var(--ply-color-bg)) 30%, transparent) left / 2.5rem 100% no-repeat local,
      linear-gradient(to left, var(--_cue-bg, var(--ply-color-bg)) 30%, transparent) right / 2.5rem 100% no-repeat local,
      linear-gradient(to right, color-mix(in srgb, var(--_cue-shade, var(--ply-color-text)) 22%, transparent), transparent) left / 0.75rem 100% no-repeat scroll,
      linear-gradient(to left, color-mix(in srgb, var(--_cue-shade, var(--ply-color-text)) 22%, transparent), transparent) right / 0.75rem 100% no-repeat scroll;
    background: var(--_cue);
    background-color: var(--_cue-bg, var(--ply-color-bg));
  }
  .ply-chart__plot { --_cue-bg: var(--ply-viz-surface); }

  .ply-stack { display: flex; flex-direction: column; gap: var(--ply-stack-gap, var(--ply-space-4)); }
  .ply-cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ply-cluster-gap, var(--ply-space-3)); }
  .ply-grid { display: grid; gap: var(--ply-grid-gap, var(--ply-space-5)); grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--ply-grid-min, 16rem)), 1fr)); }
}

/* ---- components/button.css ---- */
@layer ply.components {
  /* Buttons are square exhibit labels: black by default, outlined or bare when quieter. */
  .ply-button {
    --_bg: var(--ply-color-text); --_fg: var(--ply-color-bg); --_bd: var(--ply-color-text);
    display: inline-flex; align-items: center; justify-content: center; gap: var(--ply-space-2);
    min-height: 2.75rem; padding: 0 var(--ply-space-5);
    background: var(--_bg); color: var(--_fg); border: var(--ply-border-control) solid var(--_bd); border-radius: var(--ply-radius-none);
    font-family: var(--ply-font-text); font-size: var(--ply-size-md); font-weight: var(--ply-weight-semibold); line-height: 1.2; text-decoration: none; text-align: center; text-wrap: balance; max-width: 100%; padding-block: 0.4rem;
    cursor: pointer; user-select: none;
    transition: background var(--ply-duration-fast) var(--ply-ease-out), color var(--ply-duration-fast) var(--ply-ease-out), border-color var(--ply-duration-fast) var(--ply-ease-out), transform var(--ply-duration-fast) var(--ply-ease-out);
  }
  /* Hover fills tomato. The fill is accent-text so the label keeps 4.5:1 in both themes. */
  .ply-button:hover:not(:disabled, [aria-disabled="true"]) { --_bg: var(--ply-color-accent-text); --_bd: var(--ply-color-accent-text); --_fg: var(--ply-color-on-accent); background: var(--_bg); color: var(--_fg); }
  .ply-button:active { transform: translateY(1px); }
  .ply-button svg { width: 1.125em; height: 1.125em; flex: none; }

  .ply-button--secondary { --_bg: transparent; --_fg: var(--ply-color-text); --_bd: var(--ply-color-text); }
  .ply-button--secondary:hover:not(:disabled, [aria-disabled="true"]) { --_bg: var(--ply-color-text); --_fg: var(--ply-color-bg); --_bd: var(--ply-color-text); background: var(--ply-color-text); color: var(--ply-color-bg); }
  .ply-button--ghost { --_bg: transparent; --_fg: var(--ply-color-text); --_bd: transparent; padding-inline: var(--ply-space-3); }
  /* In a row of buttons a ghost button's word lines up with the row's edge when it wraps to a
     new line, as a filled button's box does: it draws its padding into the gap before it. */
  .ply-cluster > .ply-button--ghost:not(.ply-button--icon) { margin-inline-start: calc(-1 * var(--ply-space-3)); }
  .ply-button--ghost:hover:not(:disabled, [aria-disabled="true"]) { --_bg: var(--ply-color-surface-sunken); --_fg: var(--ply-color-text); --_bd: transparent; background: var(--ply-color-surface-sunken); color: var(--ply-color-text); }
  .ply-button--sm { min-height: 2.25rem; padding-inline: var(--ply-space-4); font-size: var(--ply-size-sm); }
  .ply-button--icon { padding: 0; width: 2.75rem; }
  .ply-button--icon.ply-button--sm { width: 2.25rem; }

  .ply-button:disabled, .ply-button[aria-disabled="true"] { --_bg: var(--ply-color-surface-sunken); --_fg: var(--ply-color-text-subtle); --_bd: var(--ply-color-surface-sunken); cursor: not-allowed; transform: none; }
  .ply-button[aria-busy="true"] { cursor: progress; }
  .ply-button[aria-busy="true"] .ply-loading { --ply-loading-color: currentColor; }

  /* Inside a black panel the primary button turns white. */
  .ply-panel .ply-button:not(.ply-button--secondary, .ply-button--ghost, :disabled, [aria-disabled="true"]) { --_bg: var(--ply-color-panel-text); --_fg: var(--ply-color-panel); --_bd: var(--ply-color-panel-text); }
  .ply-panel .ply-button--secondary { --_fg: var(--ply-color-panel-text); --_bd: var(--ply-color-panel-text); }
  .ply-panel .ply-button:hover:not(:disabled, [aria-disabled="true"]) { --_bg: var(--ply-color-accent-text); --_bd: var(--ply-color-accent-text); --_fg: var(--ply-color-on-accent); }
  .ply-panel .ply-button:is(:disabled, [aria-disabled="true"]) { --_bg: transparent; --_fg: var(--ply-color-panel-muted); --_bd: var(--ply-color-panel-rule); }
}

/* ---- components/nav.css ---- */
@layer ply.components {
  /* Top bar: wordmark, chapter links with a black wall-label tab on the current one, tools. */
  .ply-topbar { border-bottom: var(--ply-border-rule) solid var(--ply-color-rule-strong); background: var(--ply-color-bg); }
  .ply-topbar__inner { display: flex; align-items: center; gap: var(--ply-space-5); min-height: 4.5rem; padding-block: var(--ply-space-3); }
  .ply-wordmark { font-family: var(--ply-font-display); font-size: var(--ply-size-xl); line-height: 1; color: var(--ply-color-text); text-decoration: none; white-space: nowrap; flex: none; font-weight: 400; }
  .ply-wordmark:hover { color: var(--ply-color-text); }
  .ply-wordmark__stop { color: var(--ply-color-accent); }
  .ply-topbar__links { display: none; }
  .ply-topbar__tools { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ply-space-2); flex: none; }
  /* Narrow screens keep the tool names for screen readers and show only the icons. */
  @media (max-width: 40rem) {
    .ply-topbar__inner { gap: var(--ply-space-3); }
    .ply-topbar__tools { gap: var(--ply-space-1); }
    .ply-search-trigger { padding: 0; width: 2.5rem; justify-content: center; }
    .ply-search-trigger > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  }
  @media (max-width: 25rem) {
    .ply-menu--chapters > summary { padding: 0; min-width: 2.5rem; }
    .ply-menu__word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  }
  /* From 72rem the chapter tabs show. Tabs that do not fit scroll in their row, which keeps
     5px around them so the focus ring is not cut off. With js/topbar.js, the tabs show at any
     width where they fit, and the menu replaces them where they do not. */
  @media (min-width: 72rem) {
    .ply-topbar__links { display: flex; align-items: center; gap: var(--ply-space-1); min-width: 0; overflow-x: auto; scrollbar-width: thin; padding: 5px; margin: -5px -5px -5px calc(var(--ply-space-5) - 5px); }
  }
  .ply-topbar:is([data-ply-tabs="tabs"], [data-ply-tabs="snug"], [data-ply-tabs="tight"]) .ply-topbar__links { display: flex; align-items: center; gap: var(--ply-space-1); min-width: 0; overflow: visible; padding: 5px; margin: -5px -5px -5px calc(var(--ply-space-5) - 5px); }
  .ply-topbar[data-ply-tabs="menu"] .ply-topbar__links { display: none; }
  /* Before giving up the tabs, the bar tightens in two steps: first the tabs set closer (snug),
     then the search field keeps its icon and key but not its word, which is still read out
     (tight). */
  .ply-topbar:is([data-ply-tabs="snug"], [data-ply-tabs="tight"]) .ply-topbar__inner { gap: var(--ply-space-3); }
  .ply-topbar:is([data-ply-tabs="snug"], [data-ply-tabs="tight"]) .ply-topbar__links { gap: 0; margin-inline-start: calc(var(--ply-space-3) - 5px); }
  .ply-topbar:is([data-ply-tabs="snug"], [data-ply-tabs="tight"]) .ply-tab { padding-inline: var(--ply-space-2); }
  .ply-topbar[data-ply-tabs="tight"] .ply-search-trigger { gap: var(--ply-space-2); }
  .ply-topbar[data-ply-tabs="tight"] .ply-search-trigger > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

  .ply-tab { display: inline-flex; align-items: center; min-height: 2.25rem; padding: 0 var(--ply-space-3); color: var(--ply-color-text-muted); font-size: var(--ply-size-ui); font-weight: var(--ply-weight-medium); text-decoration: none; white-space: nowrap; transition: color var(--ply-duration-fast) var(--ply-ease-out), background var(--ply-duration-fast) var(--ply-ease-out); }
  .ply-tab:not([aria-current]):hover { color: var(--ply-color-text); background: var(--ply-color-surface-sunken); }
  .ply-tab[aria-current="page"], .ply-tab[aria-current="true"] { background: var(--ply-color-text); color: var(--ply-color-bg); font-weight: var(--ply-weight-semibold); }

  /* Search trigger shaped like an input. */
  .ply-search-trigger { text-decoration: none; font-weight: var(--ply-weight-medium); display: inline-flex; align-items: center; gap: var(--ply-space-4); min-height: 2.5rem; padding-block: 0; padding-inline: var(--ply-space-3) var(--ply-space-2); border: var(--ply-border-control) solid var(--ply-color-rule-strong); background: transparent; color: var(--ply-color-text-muted); font-size: var(--ply-size-ui); cursor: pointer; }
  .ply-search-trigger:hover { color: var(--ply-color-text); }

  /* Chapter sidebar. The current page gets the tomato point. */
  .ply-sidenav { font-size: var(--ply-size-ui); }
  .ply-sidenav__group + .ply-sidenav__group { margin-top: var(--ply-space-5); }
  .ply-sidenav__heading { font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); color: var(--ply-color-text); margin-bottom: var(--ply-space-2); }
  .ply-sidenav ul { list-style: none; padding: 0; padding-inline-start: var(--ply-space-5); }
  .ply-sidenav a { display: flex; align-items: center; padding-block: 0.4rem; color: var(--ply-color-text-muted); font-weight: var(--ply-weight-regular); text-decoration: none; position: relative; }
  .ply-sidenav a:hover { color: var(--ply-color-text); }
  .ply-sidenav a[aria-current="page"] { color: var(--ply-color-text); font-weight: var(--ply-weight-bold); }
  .ply-sidenav a[aria-current="page"]::before { content: ""; position: absolute; inset-inline-start: calc(-1 * var(--ply-space-5)); top: 50%; width: 0.6875rem; height: 0.6875rem; margin-top: -0.34375rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); }

  /* On this page. */
  .ply-toc { font-size: var(--ply-size-sm); }
  .ply-toc__heading { font-weight: var(--ply-weight-bold); margin-bottom: var(--ply-space-2); font-size: var(--ply-size-sm); }
  .ply-toc ul { list-style: none; padding: 0; }
  .ply-toc a { display: block; padding-block: 0.3rem; color: var(--ply-color-text-muted); font-weight: var(--ply-weight-regular); text-decoration: none; }
  .ply-toc a:hover, .ply-toc a[aria-current="true"] { color: var(--ply-color-text); }
  .ply-toc a[aria-current="true"] { font-weight: var(--ply-weight-semibold); }
  .ply-toc .ply-toc__sub a { padding-inline-start: var(--ply-space-3); }
  /* An auto list stays out of sight until the script fills it, so no label hangs over nothing. */
  .ply-toc[data-auto]:not(:has(li)), .ply-article__contents:not(:has(.ply-toc li)) { display: none; }
  /* In an article's rail, the current section gets the tomato point, as the sidebar's page does. */
  .ply-article__rail .ply-toc ul { padding-inline-start: var(--ply-space-5); }
  .ply-article__rail .ply-toc a { position: relative; }
  .ply-article__rail .ply-toc a[aria-current="true"]::before { content: ""; position: absolute; inset-inline-start: calc(-1 * var(--ply-space-5)); top: 50%; width: 0.5625rem; height: 0.5625rem; margin-top: -0.28125rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); }
  /* Inside a sidebar, the list hangs under the current page, on a hairline that ties it there. */
  .ply-sidenav .ply-toc { margin-block: var(--ply-space-1) var(--ply-space-2); }
  .ply-sidenav .ply-toc ul { padding-inline-start: var(--ply-space-3); border-inline-start: var(--ply-border-hair) solid var(--ply-color-rule); }
  .ply-sidenav .ply-toc a { display: block; padding-block: 0.3rem; }

  .ply-breadcrumb { display: flex; flex-wrap: wrap; gap: var(--ply-space-2); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); list-style: none; padding: 0; }
  .ply-breadcrumb li + li::before { content: "/"; margin-inline-end: var(--ply-space-2); color: var(--ply-color-text-subtle); }
  .ply-breadcrumb a { color: inherit; font-weight: var(--ply-weight-regular); }

  /* Previous and next at the end of a page. */
  .ply-pager { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ply-space-4); margin-top: var(--ply-space-8); border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); padding-top: var(--ply-space-5); }
  .ply-pager a { position: relative; display: block; text-decoration: none; color: var(--ply-color-text); padding: var(--ply-space-3) 0; }
  .ply-pager a > span { display: block; font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); font-weight: var(--ply-weight-medium); }
  .ply-pager a strong { display: block; font-family: var(--ply-font-display); font-weight: 400; font-size: var(--ply-size-xl); line-height: 1.1; overflow-wrap: break-word; text-wrap: balance; }
  .ply-pager a:hover strong { color: var(--ply-color-accent-text); }
  .ply-pager__next { text-align: end; grid-column: 2; }
  @media (max-width: 30rem) {
    .ply-pager { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .ply-pager__next { grid-column: 1; }
  }

  /* Mobile chapter menu (a native details element, no script needed). */
  .ply-menu { position: relative; }
  /* The menu button matches the search field's 40px height when they sit side by side. */
  .ply-menu > summary { list-style: none; cursor: pointer; }
  .ply-menu--chapters > summary { min-height: 2.5rem; }
  .ply-menu > summary::-webkit-details-marker { display: none; }
  .ply-menu__panel { position: absolute; inset-inline-end: 0; top: calc(100% + var(--ply-space-2)); z-index: 100; min-width: 16rem; background: var(--ply-color-bg); border: var(--ply-border-rule) solid var(--ply-color-rule-strong); padding: var(--ply-space-3); display: grid; gap: var(--ply-space-1); }
  @media (min-width: 72rem) { .ply-menu--chapters { display: none; } }
  .ply-topbar:is([data-ply-tabs="tabs"], [data-ply-tabs="snug"], [data-ply-tabs="tight"]) .ply-menu--chapters { display: none; }
  .ply-topbar[data-ply-tabs="menu"] .ply-menu--chapters { display: block; }
}

/* ---- components/search.css ---- */
@layer ply.components {
  /* Search: the top-bar trigger opens a panel over the page. Results come from the site's own
     Pagefind index (css/js/search.js). Square sheet, a 3px rule, the soft shadow that floating
     layers get; the matched words are mustard marks and the active result has the tomato point. */
  .ply-search { display: contents; }
  .ply-search-trigger__key { font-family: var(--ply-font-mono); font-size: var(--ply-size-xs); line-height: 1.4; padding: 0 0.4em; border: 1.5px solid currentColor; border-bottom-width: 3px; color: var(--ply-color-text-muted); }
  @media (max-width: 40rem) { .ply-search-trigger__key { display: none; } }

  .ply-search__dialog {
    width: min(44rem, calc(100% - 2rem)); max-width: none; max-height: min(38rem, calc(100% - 7rem));
    margin: 5rem auto auto; padding: 0; border: var(--ply-border-rule) solid var(--ply-color-rule-strong);
    background: var(--ply-color-bg); color: var(--ply-color-text); box-shadow: 0 1rem 2.5rem rgb(0 0 0 / 0.25);
    overflow: hidden; opacity: 1; transform: none;
    transition: opacity var(--ply-duration-base) var(--ply-ease-out), transform var(--ply-duration-base) var(--ply-ease-out), display var(--ply-duration-base) allow-discrete, overlay var(--ply-duration-base) allow-discrete;
  }
  .ply-search__dialog[open] { display: grid; grid-template-rows: auto minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); }
  @starting-style { .ply-search__dialog[open] { opacity: 0; transform: translateY(-0.5rem); } }
  .ply-search__dialog::backdrop { background: rgb(20 20 20 / 0.45); }
  html:has(.ply-search__dialog[open]) { overflow: hidden; }

  .ply-search__bar { display: flex; align-items: center; gap: var(--ply-space-3); padding-block: var(--ply-space-2); padding-inline: var(--ply-space-4) var(--ply-space-3); border-bottom: var(--ply-border-control) solid var(--ply-color-rule-strong); }
  .ply-search__bar > svg { flex: none; width: 1.25rem; height: 1.25rem; color: var(--ply-color-text-muted); }
  .ply-search__bar:has(.ply-search__input:focus-visible) { box-shadow: inset 0 -3px 0 var(--ply-color-focus); }
  .ply-search__input { flex: 1; min-width: 0; min-height: 2.75rem; border: 0; background: transparent; color: var(--ply-color-text); font: inherit; font-size: var(--ply-size-lg); }
  .ply-search__input:focus-visible { outline: none; }
  .ply-search__input::-webkit-search-cancel-button { display: none; }
  .ply-search__close { flex: none; appearance: none; min-height: 2rem; padding: 0 var(--ply-space-2); border: 1.5px solid var(--ply-color-rule-strong); border-bottom-width: 3px; background: transparent; color: var(--ply-color-text-muted); font-family: var(--ply-font-mono); font-size: var(--ply-size-xs); cursor: pointer; }
  .ply-search__close:hover { color: var(--ply-color-text); }

  .ply-search__body { overflow-y: auto; overscroll-behavior: contain; padding-block: var(--ply-space-2) var(--ply-space-4); }
  .ply-search__status { display: flex; align-items: center; gap: var(--ply-space-3); padding: var(--ply-space-2) var(--ply-space-4); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); }
  .ply-search__status:empty { display: none; }
  .ply-search__idle { padding: var(--ply-space-2) var(--ply-space-4); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); }
  .ply-search__idle ul { display: flex; flex-wrap: wrap; gap: var(--ply-space-2); list-style: none; padding: 0; margin-top: var(--ply-space-3); }
  .ply-search__idle a { display: inline-flex; align-items: center; min-height: 2rem; padding-inline: var(--ply-space-3); border: var(--ply-border-hair) solid var(--ply-color-rule); color: var(--ply-color-text); font-weight: var(--ply-weight-medium); text-decoration: none; }
  .ply-search__idle a:hover { border-color: var(--ply-color-rule-strong); }

  .ply-search__results { list-style: none; margin: 0; padding: 0; }
  .ply-search__hit--page:not(:first-child) { border-top: var(--ply-border-hair) solid var(--ply-color-rule); }
  .ply-search__hit a { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 0.15rem var(--ply-space-3); position: relative; padding-block: var(--ply-space-3); padding-inline: var(--ply-space-6) var(--ply-space-4); color: var(--ply-color-text); font-weight: var(--ply-weight-regular); text-decoration: none; }
  .ply-search__hit--section a { padding-block: var(--ply-space-2); padding-inline-start: calc(var(--ply-space-6) + var(--ply-space-4)); }
  .ply-search__hit a:hover, .ply-search__hit[aria-selected="true"] a { background: var(--ply-color-surface-sunken); }
  .ply-search__hit[aria-selected="true"] a::before { content: ""; position: absolute; inset-inline-start: var(--ply-space-3); top: calc(var(--ply-space-3) + 0.3rem); width: 0.6875rem; height: 0.6875rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); }
  .ply-search__hit--section[aria-selected="true"] a::before { inset-inline-start: calc(var(--ply-space-3) + var(--ply-space-4)); top: calc(var(--ply-space-2) + 0.3rem); }
  .ply-search__title { display: flex; align-items: center; gap: var(--ply-space-2); min-width: 0; font-size: var(--ply-size-ui); font-weight: var(--ply-weight-bold); overflow-wrap: anywhere; }
  .ply-search__hit--section .ply-search__title { font-weight: var(--ply-weight-semibold); }
  .ply-search__title .ply-swatch { margin: 0; }
  .ply-search__excerpt { grid-column: 1 / -1; font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); overflow-wrap: anywhere; }
  /* Matches in pale mustard, so a dense excerpt stays readable. */
  .ply-search__excerpt mark { padding-inline: 0.1em; background: var(--ply-color-note-soft); color: var(--ply-color-text); font-weight: var(--ply-weight-semibold); }
  .ply-search__more { margin: var(--ply-space-3) var(--ply-space-4) 0; }

  /* Phones: the panel takes the screen, field at the top. */
  @media (max-width: 40rem) {
    .ply-search__dialog { width: 100%; height: 100%; max-height: none; margin: 0; border: 0; box-shadow: none; }
  }
}

/* ---- components/panel.css ---- */
@layer ply.components {
  /* The exhibit panel: a black field that holds a working model, code, or a demo. */
  .ply-panel { background: var(--ply-color-panel); color: var(--ply-color-panel-text); padding: var(--ply-space-5); }
  .ply-panel a { color: var(--ply-color-code-string); }
  .ply-panel a:hover { color: var(--ply-color-panel-text); }
  .ply-panel--flush { padding: 0; }

  /* Figure with an exhibit caption underneath, outside the black. */
  .ply-exhibit { margin: 0; }
  .ply-exhibit > figcaption { margin-top: var(--ply-space-3); font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); max-width: 40rem; }
  .ply-exhibit > figcaption b, .ply-exhibit > figcaption strong { color: var(--ply-color-text); }

  /* A wall-label: small black tag naming an exhibit. */
  .ply-label { display: inline-block; background: var(--ply-color-text); color: var(--ply-color-bg); font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); padding: 0.2rem 0.5rem; line-height: 1.3; }
  /* A label hung over a figure (a card's Preprint): its own pale ground and edge, so it reads
     on a white screenshot and a black panel alike, in both themes. */
  .ply-label--flag { background: var(--ply-color-flag); color: var(--ply-color-flag-text); outline: 1.5px solid var(--ply-color-flag-edge); outline-offset: -1.5px; }
}

/* ---- components/note.css ---- */
@layer ply.components {
  /* Notes. Kind is carried by a shape and a word, never by color alone:
     note = ball, warning = triangle, success = check square, danger = cross square. */
  .ply-note { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--ply-space-3); align-items: start; padding: var(--ply-space-4) var(--ply-space-5); background: var(--ply-color-note); color: var(--ply-ink-950); font-size: var(--ply-size-ui); line-height: var(--ply-leading-normal); max-width: var(--ply-layout-measure); }
  .ply-note__mark { width: 1rem; height: 1rem; margin-top: 0.2rem; flex: none; }
  .ply-note__mark--ball { border-radius: var(--ply-radius-ball); background: var(--ply-ink-950); width: 0.875rem; height: 0.875rem; margin-top: 0.3rem; }
  .ply-note__title { font-weight: var(--ply-weight-bold); }
  .ply-note a { color: var(--ply-ink-950); }
  .ply-note code { background: color-mix(in srgb, var(--ply-ink-950) 10%, transparent); }

  .ply-note--soft { background: var(--ply-color-note-soft); color: var(--ply-color-text); }
  .ply-note--soft a { color: var(--ply-color-link); }
  .ply-note--soft .ply-note__mark--ball { background: var(--ply-color-text); }
  .ply-note--info { background: var(--ply-color-surface-sunken); color: var(--ply-color-text); }
  .ply-note--info a { color: var(--ply-color-link); }
  .ply-note--info .ply-note__mark--ball { background: var(--ply-color-text); }
  .ply-note--danger { background: var(--ply-ink-950); color: var(--ply-ink-50); }
  .ply-note--danger a { color: var(--ply-mustard-400); }
  .ply-note--danger .ply-note__mark { color: var(--ply-tomato-400); }
  .ply-note--success { background: var(--ply-color-surface-sunken); color: var(--ply-color-text); }
  .ply-note--success .ply-note__mark { color: var(--ply-status-good); }
}

/* ---- components/table.css ---- */
@layer ply.components {
  .ply-table-wrap { overflow-x: auto; max-width: 100%; border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); container-type: inline-size; }
  .ply-table { width: 100%; border-collapse: collapse; font-size: var(--ply-size-ui); line-height: var(--ply-leading-normal); font-variant-numeric: tabular-nums; }
  .ply-table th { text-align: start; font-weight: var(--ply-weight-bold); font-size: var(--ply-size-sm); padding-block: var(--ply-space-3) var(--ply-space-2); padding-inline: 0 var(--ply-space-4); border-bottom: var(--ply-border-control) solid var(--ply-color-rule-strong); vertical-align: bottom; white-space: nowrap; }
  .ply-table td { padding-block: var(--ply-space-3); padding-inline: 0 var(--ply-space-4); border-bottom: var(--ply-border-hair) solid var(--ply-color-rule); vertical-align: top; }
  .ply-table :is(th, td):last-child { padding-inline-end: 0; }
  .ply-table .ply-num { text-align: end; }
  /* The first column names things. Names stay whole; a wide table scrolls in its wrap. */
  .ply-table :is(th, td):first-child code { white-space: nowrap; }
  /* A caption keeps to the measure and to the visible part of a table that scrolls, so it
     reads whole on a phone while the table moves under it. */
  .ply-table caption { caption-side: bottom; text-align: start; padding-top: var(--ply-space-3); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); max-width: min(var(--ply-layout-measure), 100cqi); position: sticky; inset-inline-start: 0; }
  .ply-table--striped tbody tr:nth-child(even) { background: var(--ply-color-surface-sunken); }
  .ply-table--striped td:first-child, .ply-table--striped th:first-child { padding-inline-start: var(--ply-space-3); }
  .ply-table tbody tr:hover { background: color-mix(in srgb, var(--ply-color-note) 18%, transparent); }
  .ply-swatch { display: inline-block; width: 1.25rem; height: 1.25rem; vertical-align: -0.3em; border: var(--ply-border-hair) solid var(--ply-color-rule); margin-inline-end: var(--ply-space-2); }
}

/* ---- components/form.css ---- */
@layer ply.components {
  .ply-field { display: grid; gap: var(--ply-space-2); max-width: 32rem; }
  .ply-field__label { font-size: var(--ply-size-sm); font-weight: var(--ply-weight-bold); }
  .ply-field__hint { font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); line-height: var(--ply-leading-normal); }
  .ply-field__error { display: flex; gap: var(--ply-space-2); align-items: baseline; font-size: var(--ply-size-sm); color: var(--ply-color-accent-text); font-weight: var(--ply-weight-semibold); }
  .ply-field__error::before { content: ""; flex: none; width: 0.625rem; height: 0.625rem; background: var(--ply-color-accent); transform: rotate(45deg) translateY(-1px); }

  .ply-input, .ply-select, .ply-textarea {
    width: 100%; min-height: 2.75rem; padding: 0 var(--ply-space-3);
    background: var(--ply-color-bg); color: var(--ply-color-text);
    border: var(--ply-border-control) solid var(--ply-color-rule-strong); border-radius: var(--ply-radius-none);
    font-size: var(--ply-size-md); line-height: 1.4;
    transition: border-color var(--ply-duration-fast) var(--ply-ease-out), box-shadow var(--ply-duration-fast) var(--ply-ease-out);
  }
  .ply-textarea { padding-block: var(--ply-space-3); min-height: 7rem; resize: vertical; }
  .ply-input:hover, .ply-select:hover, .ply-textarea:hover { box-shadow: inset 0 0 0 1px var(--ply-color-rule-strong); }
  .ply-input:focus-visible, .ply-select:focus-visible, .ply-textarea:focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: 2px; }
  .ply-input[aria-invalid="true"], .ply-select[aria-invalid="true"], .ply-textarea[aria-invalid="true"] { border-color: var(--ply-color-accent); box-shadow: inset 0 0 0 1px var(--ply-color-accent); }
  .ply-input:disabled, .ply-select:disabled, .ply-textarea:disabled { background: var(--ply-color-surface-sunken); color: var(--ply-color-text-subtle); border-color: var(--ply-color-rule); cursor: not-allowed; }
  .ply-select { appearance: none; padding-inline-end: 2.5rem; background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); background-position: calc(100% - 1.15rem) 50%, calc(100% - 0.8rem) 50%; background-size: 0.35rem 0.35rem; background-repeat: no-repeat; cursor: pointer; }
  .ply-select:dir(rtl) { background-position: 0.45rem 50%, 0.8rem 50%; }

  /* Checkbox and radio: square box, round ball. The ball is tomato when picked. */
  .ply-check { display: inline-flex; align-items: flex-start; gap: var(--ply-space-3); min-height: 1.5rem; font-size: var(--ply-size-md); line-height: var(--ply-leading-normal); cursor: pointer; }
  .ply-check input { appearance: none; flex: none; width: 1.25rem; height: 1.25rem; margin-top: 0.15rem; border: var(--ply-border-control) solid var(--ply-color-rule-strong); background: var(--ply-color-bg); display: grid; place-content: center; cursor: pointer; }
  .ply-check input[type="radio"] { border-radius: var(--ply-radius-ball); }
  .ply-check input::before { content: ""; width: 0.625rem; height: 0.625rem; transform: scale(0); transition: transform var(--ply-duration-fast) var(--ply-ease-out); background: var(--ply-color-text); }
  .ply-check input[type="radio"]::before { border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); }
  .ply-check input:checked::before { transform: scale(1); }
  .ply-check input[type="checkbox"]:checked { background: var(--ply-color-text); }
  .ply-check input[type="checkbox"]::before { background: var(--ply-color-bg); clip-path: polygon(14% 52%, 0 66%, 38% 100%, 100% 22%, 86% 8%, 38% 72%); width: 0.75rem; height: 0.75rem; }
  .ply-check input:disabled { border-color: var(--ply-color-rule); background: var(--ply-color-surface-sunken); cursor: not-allowed; }
  .ply-check:has(input:disabled) { color: var(--ply-color-text-subtle); cursor: not-allowed; }

  /* Switch: a ball that rolls along a track. */
  .ply-switch { display: inline-flex; align-items: center; gap: var(--ply-space-3); cursor: pointer; font-size: var(--ply-size-md); }
  .ply-switch input { appearance: none; flex: none; width: 2.75rem; height: 1.5rem; border: var(--ply-border-control) solid var(--ply-color-rule-strong); background: var(--ply-color-bg); position: relative; cursor: pointer; transition: background var(--ply-duration-base) var(--ply-ease-out); }
  .ply-switch input::before { content: ""; position: absolute; top: 50%; inset-inline-start: 0.1875rem; width: 1rem; height: 1rem; margin-top: -0.5rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-text); transition: transform var(--ply-duration-base) var(--ply-ease-out), background var(--ply-duration-base) var(--ply-ease-out); }
  .ply-switch input:checked { background: var(--ply-color-text); }
  .ply-switch input:checked::before { transform: translateX(1.25rem); background: var(--ply-color-accent); }
  .ply-switch input:checked:dir(rtl)::before { transform: translateX(-1.25rem); }

  /* Range slider: a rail and a ball, with the value printed beside it. */
  .ply-range { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--ply-space-3); }
  /* The value is set in the text face with tabular figures, so a page with a slider and no
     code loads no mono face. */
  .ply-range output { font-size: var(--ply-size-sm); font-weight: var(--ply-weight-semibold); font-variant-numeric: tabular-nums; min-width: 3.5ch; text-align: end; }
  .ply-slider { appearance: none; width: 100%; height: 1.5rem; background: transparent; cursor: pointer; }
  .ply-slider::-webkit-slider-runnable-track { height: 3px; background: var(--ply-color-rule-strong); }
  .ply-slider::-moz-range-track { height: 3px; background: var(--ply-color-rule-strong); }
  .ply-slider::-webkit-slider-thumb { appearance: none; width: 1.125rem; height: 1.125rem; margin-top: -0.47rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); border: 0; }
  .ply-slider::-moz-range-thumb { width: 1.125rem; height: 1.125rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); border: 0; }
  .ply-slider:focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: 4px; }

  /* Segmented control: wall-label tabs in a row. */
  .ply-segmented { display: inline-flex; align-self: flex-start; max-width: 100%; overflow-x: auto; scrollbar-width: thin; border: var(--ply-border-control) solid var(--ply-color-rule-strong); }
  .ply-segmented button, .ply-segmented label { white-space: nowrap; appearance: none; border: 0; background: transparent; padding: 0 var(--ply-space-3); min-height: 2.25rem; display: inline-flex; align-items: center; font-size: var(--ply-size-sm); font-weight: var(--ply-weight-medium); color: var(--ply-color-text); cursor: pointer; }
  .ply-segmented :is(button, label) + :is(button, label) { border-inline-start: var(--ply-border-control) solid var(--ply-color-rule-strong); }
  .ply-segmented [aria-pressed="true"], .ply-segmented label:has(input:checked) { background: var(--ply-color-text); color: var(--ply-color-bg); font-weight: var(--ply-weight-semibold); }
  .ply-segmented input { position: absolute; opacity: 0; pointer-events: none; }
  /* On a phone the segments set closer, so a row of four short labels fits without scrolling. */
  @media (max-width: 30rem) { .ply-segmented button, .ply-segmented label { padding-inline: var(--ply-space-2); } }
  .ply-segmented label:has(input:focus-visible), .ply-segmented button:focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: -5px; }

  .ply-panel :is(.ply-input, .ply-select, .ply-textarea) { background: transparent; color: var(--ply-color-panel-text); border-color: var(--ply-color-panel-text); }
  .ply-panel .ply-slider::-webkit-slider-runnable-track { background: var(--ply-color-panel-text); }
  .ply-panel .ply-slider::-moz-range-track { background: var(--ply-color-panel-text); }
  .ply-panel .ply-segmented { border-color: var(--ply-color-panel-text); }
  .ply-panel .ply-segmented :is(button, label) { color: var(--ply-color-panel-text); }
  .ply-panel .ply-segmented :is(button, label) + :is(button, label) { border-color: var(--ply-color-panel-text); }
  .ply-panel .ply-segmented [aria-pressed="true"], .ply-panel .ply-segmented label:has(input:checked) { background: var(--ply-color-panel-text); color: var(--ply-color-panel); }
}

/* ---- components/code.css ---- */
@layer ply.components {
  /* Code sits in a black exhibit panel. Comments are mustard so the explanation reads first. */
  .ply-code { position: relative; background: var(--ply-color-panel); color: var(--ply-color-panel-text); max-width: var(--ply-layout-measure); }
  /* A code block below the screen skips its layout and paint until the reader nears it: its
     hundreds of highlighted spans are the costliest layout on most pages. The block scrolls
     already, so it clips nothing new. A block with long lines starts focusable, and
     js/scroll.js measures it once it is laid out; tables and equations keep their layout,
     since scroll.js has to measure their overflow to make them focusable. */
  .ply-code pre { content-visibility: auto; contain-intrinsic-size: auto 10rem; }
  .ply-code pre { position: relative; direction: ltr; margin: 0; padding: var(--ply-space-4) var(--ply-space-5); overflow-x: auto; font-size: var(--ply-size-sm); line-height: 1.7; tab-size: 2; scrollbar-color: var(--ply-color-panel-rule) var(--ply-color-panel); --_cue-bg: var(--ply-color-panel); --_cue-shade: var(--ply-color-panel-text); background: var(--_cue) !important; color: var(--ply-color-panel-text) !important; }
  .ply-code pre code { background: none; padding: 0; font-size: inherit; }
  /* Wrapped code (wrap): each line wraps under its own indent, or under the value of a
     "key = value" line (--_hang, in characters, from Code.astro). Words break only when one
     is wider than the whole line, so a cite key stays whole. */
  .ply-code--wrap pre { white-space: pre-wrap; overflow-wrap: break-word; container-type: inline-size; }
  /* A hang never takes more than 40% of the line, so on a phone a long field name still
     leaves its value room. */
  .ply-code--wrap .line { --_h: min(var(--_hang, 0ch), 40cqi); display: inline-block; max-width: 100%; padding-inline-start: var(--_h); text-indent: calc(-1 * var(--_h)); }
  .ply-code__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--ply-space-3); padding-block: var(--ply-space-2); padding-inline: var(--ply-space-5) var(--ply-space-2); border-bottom: var(--ply-border-hair) solid var(--ply-color-panel-rule); font-size: var(--ply-size-xs); color: var(--ply-color-panel-muted); }
  .ply-code__lang { font-family: var(--ply-font-mono); min-width: 0; overflow-wrap: anywhere; }
  .ply-code__copy { appearance: none; border: var(--ply-border-hair) solid var(--ply-color-panel-rule); background: transparent; color: var(--ply-color-panel-text); font-size: var(--ply-size-xs); font-weight: var(--ply-weight-semibold); padding: 0.3rem 0.6rem; cursor: pointer; }
  .ply-code__copy:hover { border-color: var(--ply-color-panel-text); }
  .ply-code__copy[data-copied] { background: var(--ply-color-note); color: var(--ply-ink-950); border-color: var(--ply-color-note); }
  .ply-code .c, .ply-code .tok-comment { color: var(--ply-color-code-comment); }
  .ply-code .k, .ply-code .tok-keyword { color: var(--ply-color-code-keyword); }
  .ply-code .s, .ply-code .tok-string { color: var(--ply-color-code-string); }
  .ply-code .n, .ply-code .tok-number { color: var(--ply-color-code-number); }
  .ply-code mark { background: color-mix(in srgb, var(--ply-color-note) 30%, transparent); color: inherit; padding: 0; }

  .ply-kbd { display: inline-block; font-family: var(--ply-font-mono); font-size: 0.8em; line-height: 1.4; padding: 0 0.4em; border: 1.5px solid currentColor; border-bottom-width: 3px; }
}

/* ---- components/tag.css ---- */
@layer ply.components {
  /* Tags and status labels. Status always carries a mark and a word. */
  .ply-tag { display: inline-flex; align-items: center; gap: 0.4em; font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); line-height: 1.3; padding: 0.15rem 0.5rem; border: 1.5px solid currentColor; color: var(--ply-color-text); max-width: 100%; overflow-wrap: anywhere; }
  /* In a table a tag keeps its words whole, so it never narrows a column. */
  .ply-table .ply-tag { overflow-wrap: normal; }
  .ply-tag--solid { background: var(--ply-color-text); color: var(--ply-color-bg); border-color: var(--ply-color-text); }
  .ply-tag--note { background: var(--ply-color-note); color: var(--ply-ink-950); border-color: var(--ply-color-note); }

  .ply-status { display: inline-flex; align-items: center; gap: var(--ply-space-2); font-size: var(--ply-size-sm); font-weight: var(--ply-weight-semibold); }
  .ply-status::before { content: ""; width: 0.6875rem; height: 0.6875rem; flex: none; }
  .ply-status--idle::before { border: 2px solid currentColor; border-radius: var(--ply-radius-ball); }
  /* impeccable-disable-next-line pulsing-dot: a running state, not decoration; reduced motion stops it */
  .ply-status--running::before { border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); animation: ply-pulse 1.2s var(--ply-ease-out) infinite; }
  .ply-status--done::before { background: var(--ply-status-good); clip-path: polygon(14% 52%, 0 66%, 38% 100%, 100% 22%, 86% 8%, 38% 72%); width: 0.875rem; }
  .ply-status--warning::before { background: var(--ply-status-warning); clip-path: polygon(50% 0, 100% 100%, 0 100%); width: 0.8125rem; }
  .ply-status--error::before { background: var(--ply-status-critical); clip-path: polygon(20% 0, 50% 30%, 80% 0, 100% 20%, 70% 50%, 100% 80%, 80% 100%, 50% 70%, 20% 100%, 0 80%, 30% 50%, 0 20%); }
  @keyframes ply-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(0.6); } }

  /* Stability of a component or API: stable is solid, experimental is outlined, deprecated is struck. */
  .ply-stability { font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); padding: 0.1rem 0.45rem; border: 1.5px solid var(--ply-color-text); }
  .ply-stability--stable { background: var(--ply-color-text); color: var(--ply-color-bg); }
  .ply-stability--experimental { border-style: dashed; }
  .ply-stability--deprecated { text-decoration: line-through; color: var(--ply-color-text-muted); border-color: var(--ply-color-text-muted); }
}

/* ---- components/machine.css ---- */
@layer ply.components {
  /* The probability machine (a Galton board). Without JavaScript it shows the settled
     bell curve from --n counts on each bin. css/js/machine.js upgrades it to a live run. */
  .ply-machine {
    --rows: 8; --bins: 9; --max: 9;
    --_peg: var(--ply-color-panel-text); --_wall: var(--ply-color-panel-rule); --_ball: var(--ply-color-accent); --_funnel: var(--ply-color-note);
    display: block; position: relative; container-type: inline-size;
    aspect-ratio: 28 / 15; width: 100%;
    background: var(--ply-color-panel); overflow: hidden;
  }
  .ply-machine canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
  .ply-machine__funnel { position: absolute; left: 41.5%; top: 5%; width: 17%; height: 8.5%; background: var(--_funnel); clip-path: polygon(0 0, 100% 0, 60% 100%, 40% 100%); }
  .ply-machine__pegs {
    position: absolute; left: 18%; right: 18%; top: 17%; height: 36%;
    /* impeccable-disable-next-line radial-halo: hard-edged pegs, not a glow */
    background-image: radial-gradient(circle, var(--_peg) 0.55cqw, transparent 0.62cqw);
    background-size: calc(100% / var(--bins)) calc(100% / var(--rows));
    background-position: calc(50% / var(--bins)) 0;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
  }
  .ply-machine__bins { position: absolute; left: 18%; right: 18%; bottom: 4.5%; height: 37%; display: flex; align-items: flex-end; border-bottom: 3px solid var(--_peg); }
  .ply-machine__bins > span { flex: 1; height: 100%; position: relative; border-inline-start: 2px solid var(--_wall); }
  .ply-machine__bins > span:last-child { border-inline-end: 2px solid var(--_wall); }
  .ply-machine__bins > span::after {
    /* Ball diameter is 30% of a bin's width; --n balls stack from the floor. */
    --_d: calc(19.2cqw / var(--bins));
    content: ""; position: absolute; inset-inline: 0; bottom: 0;
    height: calc(var(--n, 0) * var(--_d)); max-height: 100%;
    /* impeccable-disable-next-line radial-halo: hard-edged balls of the static machine, not a glow */
    background-image: radial-gradient(circle at center, var(--_ball) calc(var(--_d) * 0.44), transparent calc(var(--_d) * 0.48));
    background-size: 100% var(--_d); background-position: center bottom; background-repeat: repeat-y;
  }
  .ply-machine[data-live] > :not(canvas) { display: none; }

  /* Loading indicator: a ball dropping through three rows of pegs, on repeat.
     --ply-loading-count caps the repeats, for specimens that sit on a page. */
  .ply-loading { --ply-loading-color: var(--ply-color-accent); display: inline-block; position: relative; width: 1.5em; height: 1.5em; vertical-align: -0.3em; }
  .ply-loading::before {
    content: ""; position: absolute; inset: 22% 8% 18%;
    /* impeccable-disable-next-line radial-halo: pegs of the loading indicator */
    background-image: radial-gradient(circle, currentColor 0.07em, transparent 0.085em);
    background-size: 33.33% 33.33%;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
    opacity: 0.55;
  }
  /* impeccable-disable-next-line pulsing-dot: the loading indicator is a ball falling through pegs; reduced motion shows it at rest */
  .ply-loading > i, .ply-loading::after { content: ""; position: absolute; top: 0; left: 50%; width: 0.36em; height: 0.36em; margin-left: -0.18em; border-radius: 50%; background: var(--ply-loading-color); animation: ply-drop 1.1s var(--ply-ease-gravity) var(--ply-loading-count, infinite); }
  .ply-loading > i { animation-name: ply-drop-alt; animation-delay: 0.55s; }
  @keyframes ply-drop {
    0% { transform: translate(0, 0); opacity: 0; }
    10% { opacity: 1; }
    35% { transform: translate(-0.22em, 0.42em); }
    60% { transform: translate(0.02em, 0.76em); }
    85% { transform: translate(-0.2em, 1.1em); opacity: 1; }
    100% { transform: translate(-0.2em, 1.14em); opacity: 0; }
  }
  @keyframes ply-drop-alt {
    0% { transform: translate(0, 0); opacity: 0; }
    10% { opacity: 1; }
    35% { transform: translate(0.22em, 0.42em); }
    60% { transform: translate(0.44em, 0.76em); }
    85% { transform: translate(0.24em, 1.1em); opacity: 1; }
    100% { transform: translate(0.24em, 1.14em); opacity: 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    .ply-loading::after, .ply-loading > i { animation: none; opacity: 1; transform: translate(0, 1.1em); }
    .ply-loading > i { display: none; }
  }
}

@layer ply.components {
  /* The complex: points, edges, and filled triangles of a Vietoris-Rips complex.
     css/js/complex.js makes the radius live; the server-rendered SVG is the fallback. */
  .ply-complex { display: block; position: relative; background: var(--ply-color-panel); aspect-ratio: 100 / 56; width: 100%; }
  .ply-complex svg { position: absolute; inset: 0; width: 100%; height: 100%; }
  .ply-complex[data-live] .ply-complex__static { display: none; }
  /* A complex that grows on first view waits as bare points from the first paint, so the
     server's finished drawing does not show and then vanish when the script starts the growth.
     If no script takes over within five seconds, the finished drawing comes back. */
  @media screen and (scripting: enabled) and (prefers-reduced-motion: no-preference) {
    .ply-complex[autoplay="visible"]:not([data-live]) :is(.ply-complex__tri, .ply-complex__edge) { visibility: hidden; animation: ply-complex-wait 0s 5s forwards; }
  }
  @keyframes ply-complex-wait { to { visibility: visible; } }
  /* Its slider and counts start at 0 to match. Without script they cannot move, so they hide. */
  @media (scripting: none) { .ply-complex-controls[data-grows] { display: none; } }
  .ply-complex__tri { fill: var(--ply-color-note); fill-opacity: 0.5; }
  .ply-complex__tri--dense { fill-opacity: 0.85; }
  .ply-complex__edge { stroke: var(--ply-color-panel-text); stroke-opacity: 0.7; stroke-width: 0.28; }
  .ply-complex__pt { fill: var(--ply-color-panel-text); }
  .ply-complex__ring { fill: none; stroke: var(--ply-color-accent); stroke-width: 0.35; stroke-dasharray: 1 1; }
  .ply-complex__hot { fill: var(--ply-color-accent); }
  .ply-complex-controls { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ply-space-3); margin-top: var(--ply-space-3); }
  @media (min-width: 40rem) { .ply-complex-controls { grid-template-columns: minmax(0, 1fr) auto; align-items: end; } }
  .ply-complex-counts { display: flex; gap: var(--ply-space-4); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); font-variant-numeric: tabular-nums; }
  .ply-complex-counts b { color: var(--ply-color-text); }
}

/* ---- components/atlas.css ---- */
@layer ply.components {
  /* Atlas: a globe of the places in a CV, beside the entries that happened there
     (css/js/atlas.js). The globe is a working model in a black panel, drawn like the complex:
     places are chalk points, the route between them chalk edges, and the current place is the
     tomato ball with its dashed reach. The legs into and out of the current place keep the
     full chalk, and the rest of the route fades with distance in time (css/js/atlas-core.js,
     heat). Countries that hold a place fill mustard, like the
     complex's triangles. In a wide container the entries run on the left and the globe stays in
     view on the right; in a narrow one it stays pinned above the entries. */
  .ply-atlas { display: block; container: ply-atlas / inline-size; }
  .ply-atlas__frame { display: grid; grid-template-columns: minmax(0, 1fr); }

  /* The stage pins the globe. Stacked, its ground hides the entries scrolling under it. */
  .ply-atlas__stage { position: sticky; top: 0; z-index: 2; padding-block: var(--ply-space-3); background: var(--ply-color-bg); border-bottom: var(--ply-border-rule) solid var(--ply-color-rule-strong); }
  .ply-atlas__map {
    /* Layer colors for the canvas, each with its strength. */
    --_sea: var(--ply-color-panel-text); --_sea-a: 0.05;
    --_grid: var(--ply-color-panel-text); --_grid-a: 0.1;
    --_land: var(--ply-color-panel-text); --_land-a: 0.13;
    --_coast: var(--ply-color-panel-text); --_coast-a: 0.6;
    --_visited: var(--ply-color-note); --_visited-a: 0.5;
    --_border: var(--ply-color-panel-text); --_border-a: 0.28;
    --_limb: var(--ply-color-panel-text); --_limb-a: 0.3;
    --_route: var(--ply-color-panel-text); --_route-a: 0.8;
    --_trail: var(--ply-color-accent); --_trail-a: 1;
    position: relative; height: clamp(12rem, 34svh, 22rem); overflow: hidden; container-type: size;
    background: var(--ply-color-panel); color: var(--ply-color-panel-text);
    user-select: none; -webkit-user-select: none;
  }
  .ply-atlas[data-live] .ply-atlas__map { cursor: grab; touch-action: pan-y; }
  .ply-atlas__map[data-dragging] { cursor: grabbing; }
  .ply-atlas__map:focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: -6px; }
  .ply-atlas__static, .ply-atlas__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
  .ply-atlas[data-live] .ply-atlas__static { display: none; }
  /* The canvas shows once its first frame is whole; until then the server's globe does. */
  .ply-atlas:not([data-live]) .ply-atlas__canvas { visibility: hidden; }
  .ply-atlas__static path { fill: none; vector-effect: non-scaling-stroke; }
  .ply-atlas__static [data-layer="sea"] { fill: var(--_sea); fill-opacity: var(--_sea-a); stroke: var(--_limb); stroke-opacity: var(--_limb-a); stroke-width: 1.5; }
  .ply-atlas__static [data-layer="grid"] { stroke: var(--_grid); stroke-opacity: var(--_grid-a); stroke-width: 1; }
  .ply-atlas__static [data-layer="land"] { fill: var(--_land); fill-opacity: var(--_land-a); stroke: var(--_coast); stroke-opacity: var(--_coast-a); stroke-width: 1; }
  .ply-atlas__static [data-layer="visited"] { fill: var(--_visited); fill-opacity: var(--_visited-a); }
  .ply-atlas__static [data-layer="route"] { stroke: var(--_route); stroke-opacity: var(--_route-a); stroke-width: 1.5; }
  .ply-atlas__static circle { fill: var(--ply-color-panel-text); stroke: var(--ply-color-panel); stroke-width: 2; vector-effect: non-scaling-stroke; }
  /* The server's names sit in the square the static globe is drawn into. */
  .ply-atlas__static-names { position: absolute; inset: 0; pointer-events: none; }
  .ply-atlas__static-names .ply-atlas__name { --_s: min(100cqw, 100cqh); left: calc(50% + (var(--x) - 0.5) * var(--_s) + 0.6rem); top: calc(50% + (var(--y) - 0.5) * var(--_s)); translate: 0 -50%; }
  .ply-atlas__static-names .ply-atlas__name[data-side="left"] { left: auto; right: calc(50% - (var(--x) - 0.5) * var(--_s) + 0.6rem); }
  .ply-atlas__static-names [data-wide] { display: none; }
  @container (min-width: 45rem) and (min-height: 20rem) { .ply-atlas__static-names [data-wide] { display: block; } }
  .ply-atlas[data-live] .ply-atlas__static-names { display: none; }
  .ply-atlas__ink { position: absolute; width: 0; height: 0; overflow: hidden; forced-color-adjust: none; }

  /* Pins, names, the ball, and the tag ride over the canvas. */
  .ply-atlas__pins { position: absolute; inset: 0; pointer-events: none; }
  .ply-atlas__pin, .ply-atlas__here, .ply-atlas__tag, .ply-atlas__name { position: absolute; left: 0; top: 0; }
  .ply-atlas__pin { width: 1.75rem; height: 1.75rem; margin: -0.875rem 0 0 -0.875rem; padding: 0; border: 0; background: none; cursor: pointer; pointer-events: auto; }
  .ply-atlas__pin::before { content: ""; position: absolute; inset: 0.4375rem; border: 2px solid var(--ply-color-panel); border-radius: var(--ply-radius-ball); background: var(--ply-color-panel-text); transition: scale var(--ply-duration-fast) var(--ply-ease-out); }
  .ply-atlas__pin:hover::before { scale: 1.3; }
  .ply-atlas__pin[data-out] { opacity: 0.3; }
  .ply-atlas__here { width: 0; height: 0; pointer-events: none; }
  .ply-atlas__here::before, .ply-atlas__here::after { content: ""; position: absolute; border-radius: var(--ply-radius-ball); translate: -50% -50%; }
  /* The reach: a dashed tomato ring, as on the complex's hovered vertex. */
  .ply-atlas__here::before { width: 2.75rem; height: 2.75rem; border: 2px dashed var(--ply-color-accent); transition: opacity var(--ply-duration-base) var(--ply-ease-out), scale var(--ply-duration-slow) var(--ply-ease-out); }
  .ply-atlas__here[data-moving]::before { opacity: 0; scale: 0.5; }
  .ply-atlas__here::after { width: 1.125rem; height: 1.125rem; border: 2px solid var(--ply-color-panel); background: var(--ply-color-accent); }
  /* The current place's name is a chalk tag; other places are black wall labels. */
  .ply-atlas__tag, .ply-atlas__name { padding: 0.15rem 0.45rem; font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); line-height: 1.3; white-space: nowrap; pointer-events: none; }
  .ply-atlas__tag { background: var(--ply-color-panel-text); color: var(--ply-color-panel); }
  .ply-atlas__name { background: var(--ply-color-panel); color: var(--ply-color-panel-text); font-weight: var(--ply-weight-semibold); }

  /* A wall-label caption under the globe, as under every exhibit. */
  .ply-atlas__caption { margin: var(--ply-space-2) 0 0; font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); }
  .ply-atlas__caption b { color: var(--ply-color-text); }
  /* Stacked, the caption is part of the pinned stage. It keeps two lines, so a longer name
     does not push the entries down and change which one is current under the reader. */
  .ply-atlas__caption { min-block-size: calc(2em * var(--ply-leading-normal)); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
  /* On the panel's own ground, so it reads over land and mustard alike. */
  .ply-atlas__readout { position: absolute; inset-inline-start: var(--ply-space-3); inset-block-end: var(--ply-space-3); max-width: calc(100% - 10rem); margin: 0; padding: 0.15rem 0.45rem; background: var(--ply-color-panel); font-family: var(--ply-font-mono); font-size: var(--ply-size-xs); line-height: 1.4; color: var(--ply-color-panel-muted); font-variant-numeric: tabular-nums; pointer-events: none; }
  .ply-atlas__readout[data-hint] { color: var(--ply-color-panel-text); }
  .ply-atlas__readout:empty { display: none; }
  /* A row under a short, stacked globe; a column beside the tall one. */
  .ply-atlas__controls { position: absolute; inset-inline-end: var(--ply-space-3); inset-block-end: var(--ply-space-3); display: flex; border: var(--ply-border-control) solid var(--ply-color-panel-text); background: var(--ply-color-panel); }
  .ply-atlas__controls button { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; padding: 0; border: 0; background: transparent; color: var(--ply-color-panel-text); cursor: pointer; }
  .ply-atlas__controls button + button { border-inline-start: var(--ply-border-control) solid var(--ply-color-panel-text); }
  .ply-atlas__controls button:hover { background: var(--ply-color-surface-sunken); }
  .ply-atlas__controls button:focus-visible { outline: 3px solid var(--ply-color-focus); outline-offset: -5px; }
  .ply-atlas__controls svg { width: 1.125rem; height: 1.125rem; fill: none; stroke: currentColor; stroke-width: 2.25; stroke-linecap: square; stroke-linejoin: miter; }
  .ply-atlas:not([data-live]) :is(.ply-atlas__controls, .ply-atlas__readout) { display: none; }

  /* An optional head above the entries, such as the page title. Mast titles size to it. */
  .ply-atlas__head { container-type: inline-size; padding-block-end: var(--ply-space-5); }

  /* The entries: a history wall, each one dated in wood type. */
  .ply-atlas__list { display: grid; align-content: start; gap: var(--ply-space-4); padding-block: var(--ply-space-4) 20svh; }
  .ply-atlas__filter { justify-self: start; }
  @media (scripting: none) { .ply-atlas__filter { display: none; } }
  .ply-atlas__stops { list-style: none; margin: 0; padding: 0; border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); }
  .ply-atlas__stop { display: flex; flex-direction: column; gap: var(--ply-space-1); padding-block: var(--ply-space-5) var(--ply-space-6); border-bottom: var(--ply-border-hair) solid var(--ply-color-rule); scroll-margin-block: var(--ply-space-6); }
  .ply-atlas__when { order: -1; margin: 0 0 var(--ply-space-2); font-family: var(--ply-font-display); font-size: var(--ply-size-xl); line-height: 1.1; font-variant-numeric: tabular-nums; text-wrap: balance; }
  .ply-atlas__title { margin: 0; font-family: var(--ply-font-text); font-size: var(--ply-size-lg); font-weight: var(--ply-weight-bold); line-height: 1.3; letter-spacing: 0; }
  .ply-atlas__org { margin: 0; font-size: var(--ply-size-ui); color: var(--ply-color-text-muted); }
  .ply-atlas__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ply-space-2) var(--ply-space-3); margin: var(--ply-space-2) 0 0; font-size: var(--ply-size-sm); }
  .ply-atlas__place { display: inline-flex; margin: 0; }
  .ply-atlas__place, .ply-atlas__go { align-items: center; gap: var(--ply-space-2); font-weight: var(--ply-weight-semibold); }
  .ply-atlas__go { display: inline-flex; min-height: 1.75rem; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer; text-decoration: underline var(--ply-border-control) var(--ply-color-rule); text-underline-offset: 0.25em; }
  .ply-atlas__go:hover { text-decoration-color: var(--ply-color-accent); }
  .ply-atlas__dot { flex: none; width: 0.6875rem; height: 0.6875rem; border-radius: var(--ply-radius-ball); background: var(--ply-color-text); transition: background-color var(--ply-duration-base) var(--ply-ease-out); }
  .ply-atlas__stop[data-current] .ply-atlas__dot { background: var(--ply-color-accent); }
  .ply-atlas__body { margin-top: var(--ply-space-3); max-width: var(--ply-layout-measure); }

  /* Wide: entries on the left, the globe in view on the right for the whole list. */
  @container ply-atlas (min-width: 52rem) {
    .ply-atlas__frame { grid-template-columns: minmax(17rem, 25rem) minmax(0, 1fr); grid-template-rows: auto 1fr; column-gap: var(--ply-space-7); align-items: start; }
    .ply-atlas__head { grid-column: 1; grid-row: 1; }
    .ply-atlas__caption { min-block-size: 0; display: block; -webkit-line-clamp: none; line-clamp: none; overflow: visible; }
    .ply-atlas__stage { grid-column: 2; grid-row: 1 / span 2; top: var(--ply-space-5); padding: 0; background: none; border: 0; }
    /* Room for the caption under the stuck globe. The controls and readout sit at the top,
       which is on screen from the first paint, before the globe has stuck. */
    .ply-atlas__map { height: min(calc(100svh - 2 * var(--ply-space-5) - 2.5rem), 52rem); }
    .ply-atlas__controls, .ply-atlas__readout { inset-block: var(--ply-space-3) auto; }
    .ply-atlas__list { grid-column: 1; grid-row: 2; padding-block: 0 40svh; }
    .ply-atlas__controls { flex-direction: column; }
    .ply-atlas__readout { max-width: calc(100% - 6rem); }
    .ply-atlas__controls button + button { border-inline-start: 0; border-top: var(--ply-border-control) solid var(--ply-color-panel-text); }
  }
  @media (max-width: 22rem) { .ply-atlas__readout { display: none; } }
  @media (pointer: coarse) {
    /* A finger needs 44px; the drawn point stays the same size. */
    .ply-atlas__pin { width: 2.75rem; height: 2.75rem; margin: -1.375rem 0 0 -1.375rem; }
    .ply-atlas__pin::before { inset: 0.9375rem; }
    .ply-atlas__controls button { width: 2.75rem; height: 2.75rem; }
    .ply-atlas__go { min-height: 2.75rem; }
  }
  @media (prefers-reduced-motion: reduce) {
    .ply-atlas__pin::before, .ply-atlas__here::before, .ply-atlas__dot { transition: none; }
  }
}

/* ---- components/research.css ---- */
@layer ply.components {
  /* Paper header: title, authors with affiliation marks, venue, and the link row. A header
     measures its own width to size its title (container-type), so it takes its width from
     where it sits, never from its text: in a box that shrinks to fit, give it a width. */
  .ply-paper { display: grid; gap: var(--ply-space-5); padding-block: var(--ply-space-7); container-type: inline-size; width: 100%; }
  .ply-paper__venue { font-size: var(--ply-size-sm); font-weight: var(--ply-weight-bold); text-wrap: balance; }
  /* A wood-type title (WoodTitle, and the paper and post headers' own) sizes to its column,
     not the window, so it sets the same in a narrow column beside an exhibit as across the
     page. About 22 characters to a line, more for a long title (--_len, its length) so it
     keeps to about three lines, and never so big that its longest word (--_em, its width in
     em) breaks, short of 24px: a word too long even for that breaks where it must. On a phone it keeps to 32px, or nearer 24px for a long title. The cap is in
     em, so a title breaks the same way at every width it reaches its full size, with the
     fallback face as with Ultra. */
  :is(.ply-title, .ply-paper__title, .ply-post__title) { --_cpl: max(22, var(--_len, 0) / 2.7); font-family: var(--ply-font-display); font-weight: 400; font-size: min(var(--ply-size-4xl), max(var(--ply-size-xl), 100cqi / var(--_em, calc(var(--_chars, 8) * 0.76))), max(var(--ply-size-xl), var(--ply-size-2xl) - max(0, var(--_len, 0) - 40) * 0.0075rem, 100cqi / (var(--_cpl) * 0.66))); line-height: 1.02; letter-spacing: -0.01em; max-width: calc(var(--_cpl) * 0.66em); text-wrap: balance; }
  /* A subtitle (what follows the colon) is read, so it is in the text face, under the name
     and sized from it. */
  :is(.ply-title__sub, .ply-paper__subtitle) { display: block; margin-top: var(--ply-space-3); font-family: var(--ply-font-text); font-size: clamp(var(--ply-size-lg), 0.42em, var(--ply-size-2xl)); font-weight: var(--ply-weight-semibold); line-height: var(--ply-leading-tight); max-width: 32em; }
  .ply-paper__links { display: flex; flex-wrap: wrap; gap: var(--ply-space-2); }

  /* Post header: title, lede, the date line, tags. The same title and rhythm as a paper's. */
  .ply-post { display: grid; gap: var(--ply-space-4); padding-block: var(--ply-space-7); container-type: inline-size; width: 100%; }
  .ply-post__lede { font-size: var(--ply-size-lg); line-height: var(--ply-leading-normal); max-width: var(--ply-layout-measure); }
  .ply-post__meta { display: flex; flex-wrap: wrap; gap: var(--ply-space-1) var(--ply-space-4); font-size: var(--ply-size-sm); font-weight: var(--ply-weight-bold); font-variant-numeric: tabular-nums; }
  .ply-post__meta > * + *::before { content: ""; display: inline-block; width: 0.375rem; height: 0.375rem; margin-inline-end: var(--ply-space-4); border-radius: var(--ply-radius-ball); background: var(--ply-color-accent); vertical-align: 0.15em; }
  .ply-post__tags { display: flex; flex-wrap: wrap; gap: var(--ply-space-2); list-style: none; padding: 0; }

  .ply-authors { display: flex; flex-wrap: wrap; gap: var(--ply-space-1) var(--ply-space-4); list-style: none; padding: 0; font-size: var(--ply-size-lg); font-weight: var(--ply-weight-semibold); }
  .ply-authors sup, .ply-affiliations sup { font-size: 0.65em; font-weight: var(--ply-weight-bold); color: var(--ply-color-accent-text); margin-inline-start: 0.1em; }
  .ply-authors a { color: inherit; }
  /* The owner's name (.ply-me) is bold, and the other names drop to regular so it shows. */
  .ply-authors:has(.ply-me) { font-weight: var(--ply-weight-regular); }
  .ply-authors .ply-me { font-weight: var(--ply-weight-bold); }
  /* The rows keep one baseline inside .ply-prose, which spaces list items apart. */
  :is(.ply-authors, .ply-affiliations, .ply-post__tags) > li { margin: 0; }
  .ply-affiliations { display: flex; flex-wrap: wrap; gap: var(--ply-space-1) var(--ply-space-4); list-style: none; padding: 0; font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); }
  /* A folded list (PaperHeader fold): the names past the fold hide and a gap mark stands in
     for each run of them, only while script can unfold the list. Without script (or where a
     browser cannot tell), and on paper, the list shows whole. */
  .ply-authors__gap { display: none; color: var(--ply-color-text-muted); }
  .ply-authors__toggle { display: none; }
  @media (scripting: enabled) {
    .ply-authors[data-folded] .ply-authors__more { display: none; }
    .ply-authors[data-folded] .ply-authors__gap, .ply-authors__toggle { display: list-item; }
  }
  .ply-authors__toggle button { display: inline-flex; align-items: center; min-height: 1.75rem; padding: 0; border: 0; background: none; color: inherit; font: inherit; font-size: var(--ply-size-ui); font-weight: var(--ply-weight-semibold); cursor: pointer; text-decoration: underline var(--ply-border-control) var(--ply-color-rule); text-underline-offset: 0.25em; }
  .ply-authors__toggle button:hover { text-decoration-color: var(--ply-color-accent); }
  @media print {
    .ply-authors[data-folded] .ply-authors__more { display: revert; }
    :is(.ply-authors__gap, .ply-authors__toggle) { display: none; }
  }
  .ply-authors__note { font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); }

  .ply-abstract { max-width: var(--ply-layout-measure); }
  /* The label is the section's heading, set as the page's other h2s are. */
  .ply-abstract__label { font-family: var(--ply-font-display); font-size: var(--ply-size-2xl); line-height: 1.1; margin-bottom: var(--ply-space-3); }
  .ply-abstract p { font-size: var(--ply-size-lg); line-height: var(--ply-leading-relaxed); }

  /* Numbered figures. Margin figures sit in the margin after the text (.ply-margin). Wide
     ones (.ply-wide) run on past the text on a docs page and keep to it in an article. */
  .ply-figure { margin: var(--ply-space-6) 0; }
  .ply-figure--wide { max-width: none; }
  .ply-figure img, .ply-figure svg, .ply-figure video { width: 100%; height: auto; }
  .ply-figure figcaption { margin-top: var(--ply-space-3); font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); max-width: var(--ply-layout-measure); }
  .ply-figure__num { font-weight: var(--ply-weight-bold); color: var(--ply-color-text); margin-inline-end: 0.35em; }

  /* Display equation with its number flush right. */
  .ply-equation { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--ply-space-4); margin: var(--ply-space-5) 0; max-width: var(--ply-layout-measure); }
  .ply-equation__body { overflow-x: auto; overflow-y: hidden; text-align: center; font-size: 1.1em; padding-block: var(--ply-space-1); }
  @media (max-width: 40rem) { .ply-equation__body { font-size: 0.95em; } }
  .ply-equation__num { font-variant-numeric: tabular-nums; color: var(--ply-color-text-muted); font-size: var(--ply-size-sm); }
  .ply-math { font-size: 1.05em; }
  .ply-math .katex { line-height: inherit; }

  /* Inline citation with a preview card on hover or focus. */
  .ply-cite { position: relative; display: inline; }
  .ply-cite > a { font-weight: var(--ply-weight-semibold); font-variant-numeric: tabular-nums; text-decoration: none; color: var(--ply-color-link); }
  .ply-cite > a:hover { text-decoration: underline; }
  /* Closed cards are display: none so they never widen the page. The ::before strip bridges
     the gap to the link, so the pointer can move onto the card. js/cite.js adds Escape to
     close and keeps an open card inside the viewport (--_dx). */
  .ply-cite__card { display: none; position: absolute; left: 50%; bottom: calc(100% + 0.5rem); z-index: 50; width: min(22rem, calc(100vw - 2rem)); transform: translate(calc(-50% + var(--_dx, 0px)), 0.25rem); opacity: 0; background: var(--ply-color-panel); color: var(--ply-color-panel-text); padding: var(--ply-space-3) var(--ply-space-4); font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); font-weight: var(--ply-weight-regular); box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.18); outline: 1px solid var(--ply-color-panel-rule); outline-offset: -1px; transition: opacity var(--ply-duration-base) var(--ply-ease-out), transform var(--ply-duration-base) var(--ply-ease-out), display var(--ply-duration-base) allow-discrete; }
  .ply-cite__card::before { content: ""; position: absolute; inset-inline: 0; top: 100%; height: 0.75rem; }
  .ply-cite__card b { color: var(--ply-color-panel-text); }
  .ply-cite:is(:hover, :focus-within):not([data-dismissed]) .ply-cite__card { display: block; opacity: 1; transform: translate(calc(-50% + var(--_dx, 0px)), 0); }
  @starting-style { .ply-cite:is(:hover, :focus-within):not([data-dismissed]) .ply-cite__card { opacity: 0; transform: translate(calc(-50% + var(--_dx, 0px)), 0.25rem); } }

  /* BibTeX block: a code panel with a copy button, set narrower than prose code. */
  .ply-bibtex .ply-code { max-width: var(--ply-layout-measure); }

  /* History wall: entries grouped by year with the year as a big wood-type numeral.
     Used for publication lists and the changelog. */
  .ply-wall { display: grid; gap: 0; }
  .ply-wall__year { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ply-space-3); padding-block: var(--ply-space-5); border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); }
  .ply-wall__label { font-family: var(--ply-font-display); font-size: var(--ply-size-3xl); line-height: 1; font-variant-numeric: tabular-nums; }
  .ply-wall__entries { list-style: none; padding: 0; display: grid; gap: var(--ply-space-4); }
  @media (min-width: 48rem) { .ply-wall__year { grid-template-columns: 9rem minmax(0, 1fr); gap: var(--ply-space-6); } }

  .ply-pub { display: grid; gap: var(--ply-space-1); }
  .ply-pub__title { font-weight: var(--ply-weight-bold); font-size: var(--ply-size-lg); line-height: 1.3; }
  .ply-pub__title a { color: inherit; font-weight: inherit; }
  .ply-pub__authors { font-size: var(--ply-size-ui); color: var(--ply-color-text-muted); }
  .ply-pub__authors .ply-me { color: var(--ply-color-text); font-weight: var(--ply-weight-semibold); }
  .ply-pub__venue { font-size: var(--ply-size-sm); font-weight: var(--ply-weight-semibold); }
  .ply-pub__links { display: flex; flex-wrap: wrap; gap: var(--ply-space-3); font-size: var(--ply-size-sm); margin-top: var(--ply-space-1); }
  .ply-pub__links a { display: inline-flex; align-items: center; min-height: 1.5rem; }
}

/* ---- components/card.css ---- */
@layer ply.components {
  /* Cards: entries (papers, posts, projects) hung as exhibits on a list page. A black panel
     holds the entry's figure, or a complex of its own; under it, the title (the one link a
     keyboard reaches), a meta line, and a summary. The panel is a second, pointer-only way in.
     The grid takes up to three columns of at least 16rem, and a grid with fewer cards than
     columns shares the row between them, so it never leaves an empty column. */
  .ply-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, max(16rem, (100% - 2 * var(--ply-space-6)) / 3)), 1fr)); gap: var(--ply-space-7) var(--ply-space-6); container-type: inline-size; }
  /* The columns are as many as the cards besides the lead, up to three, so a lead with two
     cards under it sets them half and half. */
  .ply-cards:not(:has(> :nth-child(3 of :not(.ply-card--lead)))) { grid-template-columns: repeat(auto-fit, minmax(min(100%, max(16rem, (100% - var(--ply-space-6)) / 2)), 1fr)); }
  .ply-cards:not(:has(> :nth-child(2 of :not(.ply-card--lead)))) { grid-template-columns: minmax(0, 1fr); }
  .ply-card { display: grid; grid-template-rows: auto 1fr; gap: var(--ply-space-4); align-content: start; min-width: 0; }
  .ply-card__panel { position: relative; display: block; background: var(--ply-color-panel); aspect-ratio: 100 / 56; overflow: hidden; text-decoration: none; }
  .ply-card__panel :is(img, video) { display: block; width: 100%; height: 100%; object-fit: cover; }
  .ply-card__panel :is(.ply-complex-wrap, .ply-complex) { height: 100%; }
  /* A hairline frame, so a light screenshot keeps its edge on the white wall. */
  .ply-card__panel:has(img, video)::after { content: ""; position: absolute; inset: 0; border: var(--ply-border-hair) solid var(--ply-color-rule); pointer-events: none; }
  .ply-card__labels { position: absolute; inset-block-start: var(--ply-space-3); inset-inline-start: var(--ply-space-3); display: flex; flex-wrap: wrap; gap: var(--ply-space-2); pointer-events: none; }
  .ply-card__body { display: grid; gap: var(--ply-space-2); align-content: start; }
  .ply-card__title { margin: 0; font-family: var(--ply-font-text); font-size: var(--ply-size-lg); font-weight: var(--ply-weight-bold); line-height: 1.25; letter-spacing: -0.01em; text-wrap: balance; overflow-wrap: break-word; }
  .ply-card__title a { color: var(--ply-color-text); font-weight: inherit; text-decoration-color: var(--ply-color-rule); }
  .ply-card:hover .ply-card__title a { text-decoration-color: var(--ply-color-accent); }
  .ply-card__meta { display: flex; flex-wrap: wrap; gap: 0 var(--ply-space-3); font-size: var(--ply-size-sm); font-weight: var(--ply-weight-semibold); color: var(--ply-color-text-muted); font-variant-numeric: tabular-nums; }
  .ply-card__meta > :first-child { color: var(--ply-color-text); }
  .ply-card__summary { font-size: var(--ply-size-ui); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); max-width: 40rem; }
  .ply-card__tags { display: flex; flex-wrap: wrap; gap: var(--ply-space-2); list-style: none; padding: 0; margin-top: var(--ply-space-1); }
  .ply-card__tags > li { margin: 0; }

  /* The lead card, or a card alone in its grid or under the lead, spans the row; with room it
     sets its panel beside the text and its title in wood type, the name over the subtitle. */
  :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) { grid-column: 1 / -1; }
  .ply-card__sub-mark { display: inline; }
  @container (min-width: 40rem) {
    :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) { grid-template-rows: none; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: var(--ply-space-6); align-items: start; }
    :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) .ply-card__title { font-family: var(--ply-font-display); font-weight: 400; font-size: var(--ply-size-2xl); line-height: 1.1; }
    :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) .ply-card__sub { display: block; margin-top: var(--ply-space-2); font-family: var(--ply-font-text); font-size: var(--ply-size-lg); font-weight: var(--ply-weight-semibold); line-height: var(--ply-leading-tight); }
    :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) .ply-card__sub-mark { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
    :is(.ply-card--lead, .ply-cards > .ply-card:only-child, .ply-card--lead + .ply-card:last-child) .ply-card__summary { font-size: var(--ply-size-md); }
  }
}

/* ---- components/writing.css ---- */
@layer ply.components {
  /* Sidenotes, and margin blocks (.ply-margin: a small figure, table, or code block placed
     before the paragraph it goes with), sit in the margin after the text when there is room
     for it, and under their paragraph when there is not. A main column that names itself
     ply-main, as the docs and article frames do, decides by its own width: room for the
     36rem measure, the 2rem gap, and the 14rem margin. There the margin also widens with the
     page, up to half again, so notes end at the page's end edge. Without one, the
     margin opens at 90rem, 14rem wide. A section heading starts below anything still in the
     margin, so a note never sits beside the next section. */
  .ply-sidenote-ref { font-size: 0.72em; font-weight: var(--ply-weight-bold); color: var(--ply-color-accent-text); vertical-align: super; line-height: 0; margin-inline-start: 0.08em; }
  .ply-sidenote { display: block; margin: var(--ply-space-3) 0; padding-inline-start: var(--ply-space-4); font-size: var(--ply-size-sm); line-height: var(--ply-leading-normal); color: var(--ply-color-text-muted); position: relative; }
  /* The number is already read out from the reference mark just before the note. */
  .ply-sidenote::before { content: attr(data-n); content: attr(data-n) / ""; position: absolute; inset-inline-start: 0; font-weight: var(--ply-weight-bold); color: var(--ply-color-accent-text); }
  .ply-margin { max-width: var(--ply-layout-measure); }
  @media (min-width: 90rem) {
    .ply-has-sidenotes { position: relative; }
    :is(.ply-sidenote, .ply-prose > .ply-margin) { --_side: var(--ply-layout-margin); float: inline-end; clear: inline-end; width: var(--_side); margin-block: 0.2rem var(--ply-space-3); margin-inline: 0 calc(-1 * (var(--_side) + var(--ply-space-6))); }
    .ply-prose > h2 { clear: inline-end; }
  }
  @container ply-main (width >= 52rem) {
    :is(.ply-sidenote, .ply-prose > .ply-margin) { --_side: clamp(var(--ply-layout-margin), 100cqi - var(--ply-layout-measure) - var(--ply-space-6), var(--ply-layout-margin) * 1.5); float: inline-end; clear: inline-end; width: var(--_side); margin-block: 0.2rem var(--ply-space-3); margin-inline: 0 calc(-1 * (var(--_side) + var(--ply-space-6))); }
    .ply-prose > h2 { clear: inline-end; }
  }
  @container ply-main (width < 52rem) {
    .ply-sidenote { float: none; width: auto; margin: var(--ply-space-3) 0; }
    .ply-prose > .ply-margin { float: none; width: auto; margin-block: var(--ply-space-5) 0; margin-inline: 0; }
    .ply-prose > h2 { clear: none; }
  }

  .ply-footnotes { margin-top: var(--ply-space-8); padding-top: var(--ply-space-4); border-top: var(--ply-border-control) solid var(--ply-color-rule-strong); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); max-width: var(--ply-layout-measure); }
  .ply-footnotes ol { padding-inline-start: 1.5em; }
  .ply-footnotes li + li { margin-top: var(--ply-space-2); }

  /* Theorem-like environments. The kind is set in bold small text before the body,
     the way a printed paper does it. Proofs end with a tomato box. */
  .ply-env { margin: var(--ply-space-5) 0; padding-block: var(--ply-space-4); padding-inline: var(--ply-space-5) 0; border-top: var(--ply-border-control) solid var(--ply-color-rule-strong); border-bottom: var(--ply-border-hair) solid var(--ply-color-rule); max-width: var(--ply-layout-measure); }
  .ply-env__head { font-weight: var(--ply-weight-bold); margin-inline-end: 0.4em; }
  .ply-env__name { font-weight: var(--ply-weight-regular); color: var(--ply-color-text-muted); }
  .ply-env--theorem, .ply-env--lemma, .ply-env--proposition, .ply-env--corollary { font-style: italic; }
  .ply-env--theorem .ply-env__head, .ply-env--lemma .ply-env__head { font-style: normal; }
  .ply-env--definition { background: var(--ply-color-surface-sunken); border-top-color: var(--ply-color-rule-strong); padding-inline-end: var(--ply-space-5); }
  .ply-env--proof { border-top: 0; border-bottom: 0; padding-block: 0; }
  .ply-env--proof::after { content: ""; display: block; margin-inline-start: auto; width: 0.7em; height: 0.7em; background: var(--ply-color-accent); margin-top: -1em; }
  .ply-env--proof .ply-env__head { font-style: italic; }

  .ply-details { border-top: var(--ply-border-control) solid var(--ply-color-rule-strong); max-width: var(--ply-layout-measure); }
  .ply-details > summary { display: flex; align-items: center; gap: var(--ply-space-3); padding-block: var(--ply-space-3); font-weight: var(--ply-weight-bold); cursor: pointer; list-style: none; }
  .ply-details > summary::-webkit-details-marker { display: none; }
  .ply-details > summary::before { content: ""; flex: none; width: 0.7rem; height: 0.7rem; border-radius: var(--ply-radius-ball); border: 2px solid currentColor; transition: background var(--ply-duration-fast) var(--ply-ease-out); }
  .ply-details[open] > summary::before { background: var(--ply-color-accent); border-color: var(--ply-color-accent); }
  .ply-details > :not(summary) { padding-bottom: var(--ply-space-4); }

  .ply-quote { margin: var(--ply-space-5) 0; padding-inline-start: var(--ply-space-5); border-inline-start: 1px solid var(--ply-color-rule-strong); font-size: var(--ply-size-lg); line-height: var(--ply-leading-normal); max-width: var(--ply-layout-measure); }
  .ply-quote cite { display: block; margin-top: var(--ply-space-2); font-size: var(--ply-size-sm); font-style: normal; color: var(--ply-color-text-muted); }
}

/* ---- components/demo.css ---- */
@layer ply.components {
  /* Demo and tool chrome: a header, a control panel, a black stage, a status line.
     Built for interactive figures and small research tools. */
  .ply-app { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "head" "stage" "controls" "status"; border: var(--ply-border-rule) solid var(--ply-color-rule-strong); background: var(--ply-color-bg); }
  @media (min-width: 56rem) {
    .ply-app { grid-template-columns: 18rem minmax(0, 1fr); grid-template-areas: "head head" "controls stage" "status status"; }
  }
  .ply-app__head { grid-area: head; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ply-space-4); justify-content: space-between; padding: var(--ply-space-3) var(--ply-space-4); border-bottom: var(--ply-border-rule) solid var(--ply-color-rule-strong); }
  .ply-app__title { font-family: var(--ply-font-display); font-size: var(--ply-size-xl); line-height: 1.1; font-weight: 400; }
  .ply-app__controls { grid-area: controls; padding: var(--ply-space-4); display: grid; gap: var(--ply-space-5); align-content: start; border-top: var(--ply-border-rule) solid var(--ply-color-rule-strong); }
  @media (min-width: 56rem) { .ply-app__stage { min-height: 20rem; } }
  @media (min-width: 56rem) { .ply-app__controls { border-top: 0; border-inline-end: var(--ply-border-rule) solid var(--ply-color-rule-strong); } }
  .ply-app__stage { grid-area: stage; background: var(--ply-color-panel); color: var(--ply-color-panel-text); min-height: 12rem; position: relative; display: grid; place-items: center; overflow: hidden; }
  .ply-app__status { grid-area: status; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ply-space-2) var(--ply-space-5); padding: var(--ply-space-2) var(--ply-space-4); border-top: var(--ply-border-control) solid var(--ply-color-rule-strong); font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); }

  .ply-control { display: grid; gap: var(--ply-space-2); }
  .ply-control__label { display: flex; justify-content: space-between; align-items: baseline; gap: var(--ply-space-3); font-size: var(--ply-size-sm); font-weight: var(--ply-weight-bold); }
  .ply-control__hint { font-size: var(--ply-size-xs); color: var(--ply-color-text-muted); line-height: var(--ply-leading-normal); }
  .ply-control-group { display: grid; gap: var(--ply-space-4); padding-top: var(--ply-space-4); border-top: var(--ply-border-hair) solid var(--ply-color-rule); }
  .ply-control-group > legend, .ply-control-group__title { font-size: var(--ply-size-xs); font-weight: var(--ply-weight-bold); color: var(--ply-color-text-muted); padding: 0; }
  fieldset.ply-control-group { border: 0; border-top: var(--ply-border-hair) solid var(--ply-color-rule); margin: 0; padding-inline: 0; }

  /* Key-value readout of parameters or results. */
  .ply-readout { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--ply-space-1) var(--ply-space-4); font-size: var(--ply-size-sm); margin: 0; }
  .ply-readout dt { color: var(--ply-color-text-muted); }
  .ply-readout dd { margin: 0; font-family: var(--ply-font-mono); font-variant-numeric: tabular-nums; text-align: end; }

  /* Empty state: what is missing and what to do about it. */
  .ply-empty { display: grid; justify-items: center; gap: var(--ply-space-3); text-align: center; padding: var(--ply-space-7) var(--ply-space-5); max-width: 28rem; margin-inline: auto; }
  /* The mark is a complex with no edges yet: five loose points, which no one takes for a
     spinner. */
  .ply-empty__mark { --_dot: radial-gradient(circle, currentColor 0 0.1875rem, transparent 0.21875rem); width: 3.5rem; height: 2rem; background: var(--_dot) 8% 30% / 0.5rem 0.5rem no-repeat, var(--_dot) 38% 85% / 0.5rem 0.5rem no-repeat, var(--_dot) 55% 10% / 0.5rem 0.5rem no-repeat, var(--_dot) 78% 60% / 0.5rem 0.5rem no-repeat, var(--_dot) 97% 20% / 0.5rem 0.5rem no-repeat; opacity: 0.7; }
  .ply-empty__title { margin: 0; font-family: var(--ply-font-display); font-size: var(--ply-size-xl); font-weight: 400; line-height: 1.1; }
  .ply-empty p:not(.ply-empty__title) { color: inherit; opacity: 0.85; font-size: var(--ply-size-md); }

  /* Toast for short confirmations. */
  .ply-toast { position: fixed; inset-inline: var(--ply-space-4); bottom: var(--ply-space-4); z-index: 200; margin-inline: auto; max-width: 26rem; display: flex; gap: var(--ply-space-3); align-items: center; background: var(--ply-color-panel); color: var(--ply-color-panel-text); padding: var(--ply-space-3) var(--ply-space-4); font-size: var(--ply-size-sm); box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 0.25); outline: 1px solid var(--ply-color-panel-rule); outline-offset: -1px; animation: ply-toast-in var(--ply-duration-slow) var(--ply-ease-out); }
  @keyframes ply-toast-in { from { transform: translateY(0.75rem); opacity: 0; } }
}

/* ---- components/viz.css ---- */
@layer ply.components {
  /* Chart frame for SVG or canvas charts drawn by D3, Observable Plot, or by hand.
     Series colors come from --ply-viz-1 ... --ply-viz-8 in that order. */
  .ply-chart { display: grid; gap: var(--ply-space-3); max-width: 100%; }
  .ply-chart__title { font-weight: var(--ply-weight-bold); font-size: var(--ply-size-md); line-height: 1.3; margin: 0; }
  .ply-chart__subtitle { font-size: var(--ply-size-sm); color: var(--ply-color-text-muted); margin-top: var(--ply-space-1); }
  .ply-chart__plot { position: relative; overflow-x: auto; }
  /* The data table toggle spans the chart, also when a wide chart runs past the measure. */
  .ply-chart > .ply-details { max-width: none; }
  .ply-chart__hint { display: none; font-size: var(--ply-size-xs); color: var(--ply-color-text-muted); }
  @media (max-width: 40rem) { .ply-chart__hint { display: block; } }
  /* Charts keep a readable size on phones and scroll sideways; the data table holds the numbers. */
  .ply-chart__plot > svg { min-width: 32rem; }
  .ply-chart svg { overflow: visible; font-family: var(--ply-font-text); font-size: var(--ply-size-xs); font-variant-numeric: tabular-nums; }
  .ply-chart .grid line, .ply-chart .ply-grid-line { stroke: var(--ply-color-rule); stroke-width: 1; }
  .ply-chart .axis path, .ply-chart .axis line, .ply-chart .ply-axis-line { stroke: var(--ply-color-text-muted); }
  .ply-chart .axis text, .ply-chart .ply-axis-text { fill: var(--ply-color-text-muted); }
  .ply-chart .ply-baseline { stroke: var(--ply-color-text); stroke-width: 1.5; }
  .ply-chart .ply-direct-label { fill: var(--ply-color-text); font-weight: 600; }
  .ply-chart__source { font-size: var(--ply-size-xs); color: var(--ply-color-text-muted); }

  .ply-legend { display: flex; flex-wrap: wrap; gap: var(--ply-space-1) var(--ply-space-4); list-style: none; padding: 0; font-size: var(--ply-size-sm); }
  .ply-legend li { display: inline-flex; align-items: center; gap: var(--ply-space-2); }
  .ply-legend__key { width: 0.75rem; height: 0.75rem; border-radius: var(--ply-radius-ball); background: var(--c, var(--ply-viz-1)); flex: none; }
  .ply-legend__key--line { width: 1.25rem; height: 2px; border-radius: 0; }
  .ply-legend__key--bar { border-radius: 0; }

  .ply-tooltip { position: absolute; pointer-events: none; z-index: 60; background: var(--ply-color-panel); color: var(--ply-color-panel-text); padding: var(--ply-space-2) var(--ply-space-3); font-size: var(--ply-size-xs); line-height: var(--ply-leading-normal); font-variant-numeric: tabular-nums; white-space: nowrap; box-shadow: 0 0.5rem 1.25rem rgb(0 0 0 / 0.2); outline: 1px solid var(--ply-color-panel-rule); outline-offset: -1px; }
  .ply-tooltip b { font-weight: var(--ply-weight-bold); }

  /* Texture fill for print, forced colors, and color vision deficiency. */
  /* impeccable-disable-next-line repeating-stripes-gradient: chart texture fill for print, forced colors, and color vision deficiency */
  .ply-texture { background-image: repeating-linear-gradient(45deg, currentColor 0 1.5px, transparent 1.5px 6px); }

}

/* ---- utilities.css ---- */
@layer ply.utilities {
  .ply-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .ply-skip { position: absolute; inset-inline-start: var(--ply-space-4); top: var(--ply-space-3); z-index: 300; transform: translateY(-200%); background: var(--ply-color-text); color: var(--ply-color-bg); padding: var(--ply-space-2) var(--ply-space-4); font-weight: var(--ply-weight-bold); text-decoration: none; }
  .ply-skip:hover, .ply-skip:focus { transform: none; color: var(--ply-color-bg); background: var(--ply-color-text); }
  .ply-display { font-family: var(--ply-font-display); font-weight: 400; line-height: var(--ply-leading-display); }
  .ply-mono { font-family: var(--ply-font-mono); }
  .ply-muted { color: var(--ply-color-text-muted); }
  .ply-small { font-size: var(--ply-size-sm); }
  .ply-num { font-variant-numeric: tabular-nums; }
  .ply-nowrap { white-space: nowrap; }
  .ply-stop { color: var(--ply-color-accent); }
  /* A link or button of your own that stands alone grows to a 44px target under a finger,
     as Polytope's own controls do. */
  @media (pointer: coarse) { .ply-target { display: inline-flex; align-items: center; min-height: 2.75rem; min-width: 2.75rem; } }
  .ply-flow > * + * { margin-top: var(--ply-flow, var(--ply-space-4)); }
  .ply-mt-0 { margin-top: 0 !important; }
  @media (max-width: 40rem) { .ply-hide-narrow { display: none !important; } }
  /* A wide block inside the reading column runs to the edge of a main column that names
     itself ply-main: on a docs page, from the text's edge to the page's end edge. It starts
     below any sidenote or margin figure still beside the text, so it never runs over one.
     An article keeps its margin for notes and margin figures, so a block there never
     straddles the edge between text and margin: a wide one keeps to the text, and a long
     table or line of code scrolls in its own frame. In this layer so it also beats a
     component's own max-width. */
  @container ply-main (width > 0px) { .ply-prose > .ply-wide { width: 100cqi; max-width: none; clear: inline-end; } }
  .ply-article .ply-prose > .ply-wide { width: auto; clear: none; }
}

/* ---- modes.css ---- */
@layer ply.utilities {
  /* Forced colors (Windows contrast themes). The system repaints text, borders, and
     backgrounds, and drops gradient images, so marks drawn as fills would vanish.
     They keep their shapes here in system colors; picked states use Highlight. */
  @media (forced-colors: active) {
    .ply-check input::before, .ply-switch input::before, .ply-field__error::before,
    .ply-status:not(.ply-status--idle)::before, .ply-note__mark--ball, .ply-env--proof::after,
    .ply-sidenav a[aria-current="page"]::before, .ply-details[open] > summary::before {
      forced-color-adjust: none; background: CanvasText;
    }
    .ply-check input:checked, .ply-switch input:checked { forced-color-adjust: none; background: Highlight; border-color: Highlight; }
    .ply-check input:checked::before, .ply-switch input:checked::before { background: HighlightText; }
    .ply-check:has(input:disabled), .ply-check input:disabled, .ply-switch:has(input:disabled), .ply-switch input:disabled { forced-color-adjust: none; color: GrayText; border-color: GrayText; background: Canvas; }

    .ply-tab[aria-current="page"], .ply-tab[aria-current="true"],
    .ply-segmented [aria-pressed="true"], .ply-segmented label:has(input:checked) {
      forced-color-adjust: none; background: Highlight; color: HighlightText;
    }

    .ply-slider { forced-color-adjust: none; }
    .ply-slider::-webkit-slider-runnable-track { background: CanvasText; }
    .ply-slider::-moz-range-track { background: CanvasText; }
    .ply-slider::-webkit-slider-thumb { background: Highlight; }
    .ply-slider::-moz-range-thumb { background: Highlight; }

    .ply-select { appearance: auto; background-image: none; padding-inline-end: var(--ply-space-3); }
    :is(.ply-input, .ply-select, .ply-textarea)[aria-invalid="true"] { border-style: dashed; }
    .ply-label { outline: 1px solid CanvasText; outline-offset: -1px; }

    /* Models, swatches, and legend keys keep their own colors: the color is the content. */
    .ply-machine, .ply-complex, .ply-atlas__map, .ply-swatch, .ply-legend__key { forced-color-adjust: none; }
    .ply-loading { forced-color-adjust: none; color: CanvasText; --ply-loading-color: CanvasText; }
    .ply-texture { forced-color-adjust: none; color: CanvasText; }
  }

  /* Touch. Fingers need 44px targets; the compact 36px sizes are for mouse and trackpad. */
  @media (pointer: coarse) {
    .ply-tab, .ply-button--sm, .ply-segmented :is(button, label), .ply-search-trigger, .ply-code__copy { min-height: 2.75rem; }
    .ply-button--icon.ply-button--sm, .ply-search-trigger { min-width: 2.75rem; }
    .ply-menu--chapters > summary, .ply-code__copy { min-width: 2.75rem; }
    .ply-sidenav a { padding-block: 0.625rem; }
    .ply-toc a { padding-block: 0.6875rem; }
    .ply-slider { height: 2.75rem; }
    .ply-check { padding-block: 0.625rem; }
    .ply-switch { min-height: 2.75rem; }
    :is(.ply-pub__links, .ply-paper__links) a { display: inline-flex; align-items: center; min-height: 2.75rem; }
    .ply-wordmark { display: inline-flex; align-items: center; min-height: 2.75rem; }
    .ply-authors__toggle button { min-height: 2.75rem; }
    /* A publication's title runs as text, so it grows its target with padding, which leaves
       the lines where they are. */
    .ply-pub__title a { padding-block: 0.5rem; }
  }

  /* Print. tokens.css switches paper to the light theme with ink-on-white panels;
     this hides the site chrome and keeps figures, code, and notes whole. */
  @media print {
    .ply-skip, .ply-topbar, .ply-docs__nav, .ply-docs__toc, .ply-article__rail, .ply-pager, .ply-menu,
    .ply-theme-toggle, .ply-code__copy, .ply-chart__hint, .ply-cite__card, .ply-toast,
    .ply-atlas__controls, .ply-atlas__readout, .ply-atlas__filter { display: none !important; }
    /* The globe prints once at the top of its entries, as it stands. */
    .ply-atlas__stage { position: static; }
    .ply-atlas__map { height: 18rem; break-inside: avoid; }
    .ply-atlas__list { padding-block-end: 0; }
    .ply-docs, .ply-article { display: block; }
    .ply-panel, .ply-code { border: var(--ply-border-control) solid var(--ply-color-rule-strong); }
    .ply-code pre { white-space: pre-wrap; overflow: visible; }
    .ply-chart__plot { overflow: visible; }
    .ply-chart__plot > svg { min-width: 0; }
    .ply-exhibit, .ply-figure, .ply-note, .ply-env, .ply-equation, tr { break-inside: avoid; }
    /* A code block may run on to the next page, so a long one (a BibTeX entry) never leaves a
       page nearly blank before it; a short one stays whole, and the bar stays with its code. */
    .ply-code__bar { break-after: avoid; }
    .ply-code pre { orphans: 4; widows: 4; }
    /* The highlighting colors are for a black panel; on paper the code prints in ink. */
    .ply-code pre span { color: inherit !important; }
    /* On paper a link cannot be followed, so a link to another site prints its address. The
       paper header's buttons print as lines of text. */
    :is(.ply-prose, .ply-pub__links, .ply-paper__links, .ply-footnotes) a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; font-weight: var(--ply-weight-regular); overflow-wrap: anywhere; }
    .ply-paper__links { display: grid; gap: var(--ply-space-1); }
    .ply-paper__links a { display: block; min-height: 0; padding: 0; border: 0; background: none; color: var(--ply-color-text); font-weight: var(--ply-weight-semibold); text-align: start; }
    .ply-paper__links .ply-icon { display: none; }
    h2, h3, h4 { break-after: avoid; }
    /* Browsers drop backgrounds when printing. These fills carry a label or a state, so they
       keep their ink: without it a primary button or pressed segment prints white on white
       and a checked box prints empty. */
    .ply-swatch, .ply-legend__key, .ply-note, .ply-tag, mark, .ply-chart svg, .ply-machine, .ply-complex, .ply-atlas__map,
    .ply-button, .ply-segmented, .ply-tab, .ply-label, .ply-stability, .ply-status, .ply-check, .ply-switch,
    .ply-slider, .ply-field__error, .ply-sidenav, .ply-details, .ply-env--proof {
      print-color-adjust: exact; -webkit-print-color-adjust: exact;
    }
  }
}
