:root {
  color-scheme: dark light;
  font-family:
    system-ui,
    -apple-system,
    sans-serif;
}

body {
  max-width: 1280px;
  margin: 2rem auto;
  padding: 0 1rem 4rem;
  line-height: 1.6;
}

h1,
h2,
h3 {
  line-height: 1.25;
}

h2 {
  margin-top: 2.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid #8886;
}

code {
  font-family: ui-monospace, monospace;
  background: #8882;
  padding: 0.1rem 0.3rem;
  border-radius: 4px;
  font-size: 0.9em;
}

pre {
  background: #8882;
  padding: 1rem;
  border-radius: 8px;
  overflow-x: auto;
}

pre code {
  background: none;
  padding: 0;
}

table {
  display: block;
  overflow-x: auto;
  border-collapse: collapse;
  width: 100%;
  margin: 1rem 0;
}

/* Short identifiers/payloads (event names, CSS custom property names,
   `{ ... }` snippets) read badly wrapped mid-token at a hyphen or space —
   opt a table into this on a per-page basis where that's genuinely all a
   column ever holds (events.html's Detail column, theming.html's Property
   column). Left off elsewhere: some tables' code cells hold longer literal
   values that are more readable wrapped across lines than forced into one
   long, horizontally-scrolled row (settings.html's Core options table). */
.nowrap-code code {
  white-space: nowrap;
}

th,
td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid #8884;
  vertical-align: top;
}

th {
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  opacity: 0.7;
}

/* One scrollable row instead of wrapping to two/three — with 12 pages
   listed, `flex-wrap: wrap` pushed real content down by however many
   extra rows the viewport forced, worse the narrower the screen got (a
   real complaint on mobile specifically, and it was already double-row on
   an ordinary desktop width once Architecture landed as the 12th entry).
   A single horizontally-scrolling strip is the same fix at every width —
   desktop and mobile share this one rule, no separate breakpoint/JS
   needed. `scroll-padding-inline` keeps a link from landing flush against
   the edge when scrolled/focused into view.
   A `mask-image` edge fade used to hint "there's more this way" — removed
   (reported directly): it was a static gradient applied regardless of
   actual scroll position, so it dimmed the first/last item even at the
   ends where there was nothing further to scroll to, reading as a bug
   rather than a hint. */
.docs-nav {
  display: flex;
  gap: 0.5rem;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-padding-inline: 0.5rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  margin-bottom: 1.5rem;
  padding-bottom: 0.5rem;
}

.docs-nav a {
  flex-shrink: 0;
  text-decoration: none;
  padding: 0.4rem 0.8rem;
  border: 1px solid #8886;
  border-radius: 6px;
  color: inherit;
}

.docs-nav a[aria-current='page'] {
  background: #4da3ff22;
  border-color: #4da3ff;
}

/* docs.js's auto-built "On this page" — a plain <details> for the
   collapse (no JS needed for that part, only for building the list
   itself). `columns` on the top-level <ul> lets a page with enough
   headings spread across 2-3 columns instead of one tall list, adapting
   to both viewport width and heading count automatically — no JS deciding
   a column count. `break-inside: avoid` on each top-level <li> keeps an h2
   and its own nested h3s together in one column rather than splitting
   mid-group. */
.toc {
  margin: 1.5rem 0;
  border: 1px solid #8886;
  border-radius: 8px;
  padding: 0 1rem;
}

.toc summary {
  cursor: pointer;
  font-weight: 600;
  padding: 0.75rem 0;
}

.toc[open] summary {
  border-bottom: 1px solid #8886;
  margin-bottom: 0.75rem;
}

.toc ul {
  list-style: none;
  margin: 0;
  padding: 0 0 0.75rem;
  columns: 200px;
  column-gap: 1.5rem;
}

.toc li {
  break-inside: avoid;
  margin: 0.3rem 0;
}

.toc li > ul {
  columns: initial;
  margin: 0.3rem 0 0.3rem 1.1rem;
  padding: 0;
}

.toc a {
  text-decoration: none;
  color: inherit;
  opacity: 0.8;
}

.toc a:hover {
  opacity: 1;
  text-decoration: underline;
}

.guide-list {
  display: grid;
  gap: 0.75rem;
  padding: 0;
  list-style: none;
}

.guide-list li {
  border: 1px solid #8886;
  border-radius: 8px;
  padding: 1rem 1.25rem;
}

.guide-list a {
  font-weight: 600;
  text-decoration: none;
}

.guide-list p {
  margin: 0.35rem 0 0;
  opacity: 0.85;
}

.swatch {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  border-radius: 3px;
  vertical-align: -0.1em;
  margin-right: 0.4em;
  border: 1px solid #8886;
}

blockquote {
  margin: 1rem 0;
  padding: 0.5rem 1rem;
  border-left: 3px solid #4da3ff;
  background: #4da3ff11;
}

footer {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 3rem;
  padding-top: 1rem;
  border-top: 1px solid #8886;
  opacity: 0.7;
  font-size: 0.9em;
}

/* Injected by docs.js into every footer. `margin-left: auto` (not
   `justify-content: space-between` on the footer) so it lands on the row's
   own right edge whether there's existing footer content to its left or
   it's the only thing there (a page with no "Next: ..." link) — the auto
   margin still absorbs the row's free space either way. */
.docs-version {
  margin-left: auto;
  white-space: nowrap;
  font-family: ui-monospace, monospace;
}

/* Injected by docs.js on every page — hidden until scrolled past its own
   threshold, so a page too short to need it never shows it in the first
   place. Fixed, not sticky/in-flow, so it stays reachable at a constant
   spot regardless of how far down the page you are. */
.scroll-top-btn {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid #4da3ff;
  border-radius: 999px;
  background: #4da3ff22;
  color: inherit;
  font: inherit;
  font-size: 0.85em;
  cursor: pointer;
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
}

.scroll-top-btn.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  .scroll-top-btn {
    transition: none;
  }
}

/* Shared by docs/architecture/*.html — a bordered box around an inline SVG
   diagram or an ASCII DOM-tree, same treatment `pre`/`table` already get
   elsewhere on this site (a contained, scrollable box, never a page-wide
   horizontal scroll). SVG diagrams use currentColor/no fill on strokes and
   text so they follow :root's light/dark color-scheme for free, same as
   the rest of the page. */
.arch-diagram {
  border: 1px solid #8886;
  border-radius: 8px;
  padding: 1rem;
  margin: 1rem 0;
  overflow-x: auto;
}

/* A real bug, found in dark mode: SVG's own initial `fill` value is black,
   not `currentColor` — every <rect>/<line>/<path> below has an explicit
   `stroke="currentColor"`, but the <text> labels never got a matching
   explicit `fill`, so they silently fell back to literal black. Invisible
   by luck in light mode (black text over a light/transparent box reads
   fine); black-on-near-black in dark mode reads as the whole diagram being
   blank. One rule here, rather than annotating every <text> tag by hand. */
.arch-diagram svg text {
  fill: currentColor;
}

.arch-diagram svg {
  display: block;
  width: 100%;
  height: auto;
  min-width: 480px;
}

.arch-caption {
  margin: 0.75rem 0 0;
  font-size: 0.9em;
  opacity: 0.75;
}

.arch-tree {
  margin: 0;
  background: none;
  padding: 0;
}

/* Shared by docs/examples/*.html — a real, runnable gallery grid + a status
   line showing what just happened, same look across every example page. */
.thumb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.5rem;
  margin: 1rem 0;
}

.thumb-grid a {
  display: block;
}

.thumb-grid img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 6px;
  cursor: pointer;
  display: block;
}

.status-line {
  background: #8882;
  padding: 0.75rem 1rem;
  border-radius: 6px;
  font-family: ui-monospace, monospace;
  margin: 1rem 0;
  /* A real bug, found chasing an unrelated mobile-viewport-widening report
     against view-controls.html: this renders JSON.stringify() output
     directly (e.g. `rotateFlipChange: {"index":0,"flipH":true,...}`) as
     plain text. JSON.stringify's default output has no internal
     whitespace, so the whole payload is one unbreakable "word" under
     normal CSS wrapping — wide enough, on a narrow mobile viewport, to
     overflow this element and widen the page's own scrollable content.
     Confirmed directly: document.documentElement.scrollWidth grew to
     exactly match window.innerWidth's reported widening — this was never
     a Shoji library bug, or even a real browser quirk, just unwrapped
     debug output on the demo page. break-word lets it wrap like the rest
     of the page instead. */
  overflow-wrap: break-word;
}

.event-log {
  background: #8882;
  padding: 0.75rem 1rem;
  border-radius: 6px;
  font-family: ui-monospace, monospace;
  font-size: 0.85em;
  max-height: 12rem;
  overflow-y: auto;
  margin: 1rem 0;
  /* Same JSON-payload-overflow issue as .status-line above. */
  overflow-wrap: break-word;
}

.event-log div + div {
  border-top: 1px solid #8884;
  padding-top: 0.25rem;
  margin-top: 0.25rem;
}
