:root{
  --bg: #0b0d10;
  --panel: #10141a;
  --panel2: #0f1318;
  --text: #e6e6e6;
  --muted: #a8b3c2;
  --border: #222b36;
  --accent: #66a3ff;
  --row-hover: rgba(255,255,255,0.04);
  --row-selected: rgba(102,163,255,0.18);
  --row-shared-highlight: rgba(255,196,102,0.18);
  --row-shared-highlight-strong: rgba(255,196,102,0.4);
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --surface-uncovered: #222b36;
  /* Only used where the topbar is pinned over scrolling content (phone widths):
     a stronger edge than --border plus a shadow, so the bar reads as a layer
     above the page. Both are theme-specific -- a shadow tuned for dark mode
     turns into a grey smear on white. */
  --border-strong: #3a4757;
  --topbar-shadow: 0 4px 16px rgb(0 0 0 / 0.75), 0 1px 3px rgb(0 0 0 / 0.6);
}

:root[data-theme="light"]{
  --bg: #ffffff;
  --panel: #f4f6f8;
  --panel2: #eef1f4;
  --text: #16191d;
  --muted: #55606e;
  --border: #d7dde3;
  --accent: #2464c7;
  --row-hover: rgba(0,0,0,0.04);
  --row-selected: rgba(36,100,199,0.14);
  --row-shared-highlight: rgba(199,130,36,0.16);
  --row-shared-highlight-strong: rgba(199,130,36,0.4);
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --surface-uncovered: #d7dde3;
  --border-strong: #b3bdc7;
  --topbar-shadow: 0 4px 16px rgb(0 0 0 / 0.22), 0 1px 3px rgb(0 0 0 / 0.16);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body{
  margin:0;
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji", "Segoe UI Emoji";
  font-size: 18px;
  background: var(--bg);
  color: var(--text);
}

.topbar{
  height: 48px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  padding: 0 14px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.brand{ font-weight: 600; display: flex; align-items: center; }
.brand-title{ white-space: nowrap; }
.brand-meta{ display: contents; }

/* Subtle: same color/weight as plain text, just a pointer cursor -- unlike
   .topbar-link (About), which is meant to visually announce itself as a link. */
.brand-title-link{ cursor: pointer; color: inherit; text-decoration: none; }

.sidebar-toggle{
  display: none;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 34px;
  height: 34px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  margin-right: 12px;
  flex: 0 0 auto;
}
.sidebar-toggle:hover{ border-color: var(--accent); }
.topbar-link {
  font-weight: normal;
  font-size: 16px;
  margin-left: 25px;
  color: var(--accent);
  text-decoration: none;
  vertical-align: middle;
}
a.topbar-link:hover { text-decoration: underline; }

/* about.html's topbar has 3 direct children (.brand, .topnav, .about-back-link)
   instead of index.html's 2 -- order restores the original visual arrangement
   (title, back-link, then dropdown pushed right) on desktop; see the narrow
   breakpoint below for the mobile swap. */
.about-topbar{ flex-wrap: nowrap; }
.about-topbar .brand{ order: 1; }
.about-topbar .about-back-link{ order: 2; margin-right: auto; }
.about-topbar .topnav{ order: 3; }

.topnav{ display:flex; align-items:center; gap:10px; color: var(--muted); }
.navlink{ color: var(--accent); text-decoration:none; }
.navlink:hover{ text-decoration:underline; }
.sep{ opacity:.6; }

/* The axis switch. Three peers, styled as a segmented control rather than a
   dropdown, so none reads as the default with the others as filters. Matches
   the sibling atlas's .axisSwitch. */
.axisSwitch{ display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.axisButton{
  font-size: 13px;
  padding: 3px 10px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.axisButton + .axisButton{ border-left: 1px solid var(--border); }
.axisButton:hover{ color: var(--accent); }
.axisButton.active{ background: var(--accent); color: #fff; }

.navlabel{ font-size: 15px; }
#schemeSelect{
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 8px;
  border-radius: 6px;
}

.search-wrap{
  position: relative;
  display: inline-flex;
  align-items: center;
}
.search-wrap-icon{
  position: absolute;
  left: 8px;
  width: 13px;
  height: 13px;
  opacity: 0.5;
  pointer-events: none;
}
.search-input {
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 8px 4px 84px;
  border-radius: 6px;
  width: 320px;
  font-family: inherit;
}
.search-input:focus {
  outline: 1px solid var(--accent);
  border-color: var(--accent);
}
.search-wrap:focus-within .search-wrap-icon{
  opacity: 0.8;
}
.exactToggle{
  position: absolute;
  left: 27px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: var(--panel2);
}
.exactToggle:hover{
  border-color: var(--accent);
}
.exactToggle:has(input:checked){
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}
/* Visually hidden but still focusable/keyboard-toggleable -- the pill itself
   (the <label>) is the whole visible control, not a checkbox-plus-text. */
.exactToggle input{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.theme-toggle{
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1.3;
  white-space: nowrap;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.theme-toggle:hover{ border-color: var(--accent); }

.layout{
  height: calc(100% - 48px);
  display:grid;
  grid-template-columns: var(--sidebar-width, 400px) 6px 1fr;
}

.sidenav{
  border-right: 1px solid var(--border);
  background: var(--panel2);
  padding: 12px;
  overflow:auto;
  /* Lets .allRow respond to the sidebar's own width rather than the viewport's --
     the sidebar is user-draggable (--sidebar-width, see app.js), so it can get
     narrow enough to wrap that row at any viewport size. See the container query
     below .includeOrphansToggle. */
  container-type: inline-size;
  container-name: sidenav;
}

.sidebar-resizer{
  cursor: col-resize;
  background: transparent;
  position: relative;
}
.sidebar-resizer::after{
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2px;
  width: 2px;
  background: var(--border);
}
.sidebar-resizer:hover::after,
.sidebar-resizer.dragging::after{
  background: var(--accent);
}

.sidebar-backdrop{
  display: none;
}

.panelTitle{ font-weight: 600; margin-bottom: 6px; }
.panelTitleLink{ cursor: pointer; }
/* ---- Category filter (sidebar) ----
   Categories were a browse axis until 2026-08-13; as a filter they narrow
   whichever axis is active rather than forming a hierarchy of their own. */
.filterSection{ margin: 4px 0 10px; }
.filterHead{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 6px;
}
.filterTitle{ font-size: 14px; color: var(--muted); }
.filterClear{
  font-size: 12px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
}
.filterClear:hover{ color: var(--accent); }

.filterRows{ display: flex; flex-direction: column; gap: 4px; }
.filterRow{
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel2);
}
/* The tag itself takes the slack and ellipsizes; the count and × stay put. */
.filterRowValue{
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filterRowCount{ color: var(--muted); font-size: 12px; flex: none; white-space: nowrap; }
.filterRemove{
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
  flex: none;
}
.filterRemove:hover{ color: var(--accent); }

.filterAdd{
  margin-top: 6px;
  font-size: 13px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.filterAdd:hover{ color: var(--accent); border-color: var(--accent); }

.filterEditor{ position: relative; }
.filterInput{
  width: 100%;
  box-sizing: border-box;
  font-size: 13px;
  padding: 4px 8px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--panel2);
  color: var(--text);
}
/* Anchored to the editor and scrollable: the list can run to hundreds of tags,
   and the sidebar itself must not grow to fit them. */
.filterMenu{
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 50;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  box-shadow: 0 6px 18px rgba(0,0,0,0.35);
}
.filterMenuItem{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 8px;
  font-size: 13px;
  cursor: pointer;
}
.filterMenuItem:hover{ background: var(--row-hover); }
.filterMenuVal{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filterMenuCount{ color: var(--muted); font-size: 12px; flex: none; white-space: nowrap; }
.filterMenuEmpty{ padding: 6px 8px; font-size: 13px; color: var(--muted); }

.expandAllButton{
  font-size: 13px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
}
.expandAllButton:hover{ border-color: var(--accent); color: var(--accent); }
.loadingHint{ font-size: 14px; color: var(--muted); padding: 10px 4px; }
.allRow{
  display:flex;
  flex-wrap: wrap;
  align-items:center;
  justify-content: space-between;
  gap: 6px;
  padding: 3px 2px;
  border-radius: 6px;
  cursor:pointer;
}
.allRow:hover{ background: var(--row-hover); }
.allRow.selected{ background: var(--row-selected); }
/* "All" itself must never shrink/truncate -- unlike nested category rows,
   which legitimately compete with a long title for space, this row's title
   is a fixed 3-character word and the thing actually short on space is
   everything else (toggle pill, stats). DOM order is All / stats / toggle
   (so the toggle wraps to its own line last on mobile, see the narrow
   breakpoint below), but visually the toggle sits right before stats on
   desktop, both pushed together to the row's right edge -- `order` plus
   the toggle's own margin-left:auto (below) decouples visual placement
   from DOM position. */
.allRow .title.topLevel{ flex: 0 0 auto; order: 1; }
.allStats{
  order: 3;
  padding-left: 10px;
  opacity: 0.7;
}
.allDivider{ border: none; border-top: 1px solid var(--border); margin: 6px 0 10px; }
.includeOrphansToggle{
  display: inline-flex;
  align-items: center;
  gap: 5px;
  order: 2;
  margin-left: auto;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 13px;
  font-weight: normal;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: var(--panel2);
  flex: 0 0 auto;
}
.includeOrphansToggle input{
  margin: 0;
  width: 13px;
  height: 13px;
}
.includeOrphansToggle:hover{
  border-color: var(--accent);
}
.includeOrphansToggle:has(input:checked){
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

/* Once the sidebar is too narrow to fit All + stats + toggle on one line, use the
   same arrangement the phone breakpoint uses: All + stats stay together on line 1
   and the toggle drops to its own line below, right-aligned. Keyed to the
   sidebar's inline size, not the viewport's, because the sidebar is
   drag-resizable -- at a wide viewport with a dragged-in sidebar a viewport-only
   rule leaves desktop's order in place, wrapping the *stats* to line 2 and
   stranding them under the pill instead.

   Threshold is 450px of *content*: a container query resolves against the
   container's content box, and .sidenav is border-box with 12px side padding, so
   this corresponds to a 474px sidebar as measured while dragging -- just under the
   475px at which the desktop order fits on one line.

   Between the wrap point and the threshold (~460-470px outer) all three still fit
   on one line, in the swapped order All < stats < toggle. Left as-is: it reads
   fine, and the alternatives (forcing the pill onto its own row with
   flex-basis:100%) stretch the pill across the full row instead of letting it hug
   its text. */
@container sidenav (max-width: 450px){
  .allStats{ order: 2; }
  .includeOrphansToggle{ order: 3; margin-left: auto; }
}

.mainpane{
  overflow:auto;
}

.content{
  padding: 14px;
}

/* Both browse axes are flat, so rows carry no disclosure arrow and the text
   starts the row. The left padding is what the 28px arrow column used to
   supply -- without it the titles sit flush against the pane edge. */
.tree .row{
  display:flex;
  align-items:center;
  gap: 6px;
  padding: 3px 2px 3px 8px;
  border-radius: 6px;
}
.tree .row:hover{ background: var(--row-hover); }
.tree .row.selected{ background: var(--row-selected); }
.tree .row.shared-highlight{ background: var(--row-shared-highlight); }

.toggleArrow{
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  user-select:none;
  color: var(--muted);
  font-size: 1.6rem;
}
.title{
  cursor:pointer;
  user-select:none;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.title.topLevel, .small.topLevel{
  font-weight: bold;
}
.tree .row .small{
  flex-shrink: 0;
  white-space: nowrap;
}
.indent{ margin-left: 18px; }

.content .block{
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
}

.content .block.collapsible{ padding: 10px 12px; }
.content .block.collapsible > summary{
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
}
.content .block.collapsible > summary::-webkit-details-marker{ display: none; }

/* Sticky section headers (about.html): while a top-level section is open, pin its
   own summary to the top of the scroll area so the reader always knows which
   section they're in and can close it by clicking the same bar again -- the same
   affordance sticky category headers give on index.html (see positionStickyHeaders
   in app.js).

   Only *open* summaries stick, and only top-level ones (:not(.block.collapsible
   .block.collapsible) > summary): a closed summary is already in normal flow, and
   pinning nested disclosures too -- the audit tree runs several deep -- would
   stack a pile of bars down the viewport.

   Negative margins cancel .block's 10px 12px padding so the bar spans the full
   block width and fully masks the prose scrolling beneath it; the matching
   positive padding puts the text back where it was. z-index keeps it above
   sibling content but below index.html's sticky topbar (1000). */
.content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--panel);
  margin: -10px -12px 0;
  padding: 10px 12px;
  border-radius: 10px 10px 0 0;
}

/* Audit findings (about.html "Data Quality" section, generated by
   pipeline/audit.py): every finding row -- whether it has results (a
   <summary>) or not (a plain <div>) -- shares the same .audit-summary
   element type/box model, so the text always lines up regardless of
   whether a triangle is drawn. A finding with results shows a custom
   disclosure triangle (same construction as .block.collapsible above) in
   place of a bullet; a zero-count finding's ::before is left transparent,
   acting as a same-size invisible spacer instead. */
.audit-item{ list-style: none; }
.audit-summary{
  display: flex;
  align-items: center;
}
.audit-summary:not(.audit-summary-empty){ cursor: pointer; }
.audit-summary::-webkit-details-marker{ display: none; }
.audit-summary::before{
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 0.6em;
  height: 0.6em;
  margin-right: 10px;
  border-right: 2px solid transparent;
  border-bottom: 2px solid transparent;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.audit-summary:not(.audit-summary-empty)::before{
  border-right-color: var(--muted);
  border-bottom-color: var(--muted);
}
details[open] > .audit-summary::before{ transform: rotate(45deg); }
.content .block.collapsible > summary h2,
.content .block.collapsible > summary h3{ margin: 0; }
.content .block.collapsible > summary::before{
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 0.6em;
  height: 0.6em;
  margin-right: 10px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.content .block.collapsible[open] > summary::before{ transform: rotate(45deg); }
.content .block.collapsible h3{ margin: 14px 0 4px; }
.content .block.collapsible h3:first-of-type{ margin-top: 14px; }
.content .block.collapsible p{ margin: 4px 0 10px; }

.content .block.collapsible .block.collapsible{
  background: none;
  border: none;
  padding: 10px 0 0;
  margin-bottom: 0;
}

.sticky-header{
  position: sticky;
  background: var(--panel);
  padding: 6px 4px;
  margin: 0 -4px;
}

.content a{ color: var(--accent); text-decoration:none; }
.content a:hover{ text-decoration:underline; }

/* === External link icon === */
a[target="_blank"]::after{
  content: "";
  display: inline-block;
  width: 0.65em;
  height: 0.65em;
  margin-left: 0.15em;
  vertical-align: middle;
  position: relative;
  top: -0.05em;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 9 L9 1 M5 1 L9 1 L9 5' stroke='black' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 9 L9 1 M5 1 L9 1 L9 5' stroke='black' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.catLinkArrow{
  display: inline-block;
  margin-left: 4px;
  opacity: 0.6;
  vertical-align: middle;
}
.catLinkArrow:hover{ opacity: 1; }

.content ul {
  list-style: none;
  padding: 0;
  margin: 8px 0 0 12px;
}
.content li {
  margin-bottom: 4px;
}

.subpageList {
  margin: 4px 0 4px 18px;
  padding-left: 10px;
  border-left: 1px solid var(--border);
}

.pageRow{
  display: flex;
  align-items: center;
  gap: 4px;
  border-radius: 6px;
}
.pageRow.shared-highlight{ background: var(--row-shared-highlight); }
.pageRowMain{
  min-width: 0;
  /* The link text/meta reads as sitting slightly below the magnifying-glass
     icon's true visual center (font line-box asymmetry) -- nudge it and the
     disclosure triangle (below) up together to line up with the icon. */
  transform: translateY(-1px);
}
.subpageToggleGroup{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0;
  cursor: pointer;
}
.subpageToggle{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  transform: translateY(1px);
}
/* Filled-triangle SVG mask (rather than the Unicode ▸/▾ glyphs, which
   carry a lot of built-in whitespace inside their own em-box at any
   font-size) -- fills its box tightly, so a bigger visible glyph doesn't
   also mean a bigger (row-height-inflating) box. Points right by default,
   rotates to point down when expanded -- one shape, no separate asset. */
.triangleIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  opacity: 0.7;
  transition: transform 0.1s ease;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 1 L8 5 L2.5 9 Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 1 L8 5 L2.5 9 Z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.triangleIcon.expanded{
  transform: rotate(90deg);
}

.small{ font-size: 14px; color: var(--muted); }

/* Elements with a `title` attribute get a long-press popup instead (see
   initLongPressTooltips in app.js) -- suppress iOS/Safari's native
   long-press text-selection callout and copy/search context menu so it
   doesn't show up alongside our own popup. .selectable-title opts an
   element back into normal text selection (e.g. #dataUpdated, whose text
   is itself useful to copy) while still getting the long-press popup. */
[title]:not(.selectable-title){
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.touch-tooltip{
  position: fixed;
  z-index: 3000;
  max-width: min(80vw, 320px);
  transform: translate(-50%, -100%) translateY(-10px);
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 13px;
  line-height: 1.4;
  white-space: pre-line;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  pointer-events: none;
}

.ocrBadge, .indexBadge{
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #a87a2b;
  background: rgba(199,130,36,0.16);
  border: 1px solid rgba(199,130,36,0.35);
  border-radius: 4px;
  padding: 1px 6px;
  margin-right: 6px;
  white-space: nowrap;
}
:root[data-theme="light"] .ocrBadge, :root[data-theme="light"] .indexBadge{
  color: #8a5a10;
}

.multiCatBtn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  color: inherit;
  opacity: 0.5;
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
.multiCatBtn:hover{
  opacity: 1;
}
.pageRow.shared-highlight .multiCatBtn{
  opacity: 1;
  background: var(--row-shared-highlight-strong);
}
.searchIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4.2' cy='4.2' r='3.2' stroke='black' stroke-width='1.2' fill='none'/%3E%3Cpath d='M6.6 6.6 L9.2 9.2' stroke='black' stroke-width='1.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4.2' cy='4.2' r='3.2' stroke='black' stroke-width='1.2' fill='none'/%3E%3Cpath d='M6.6 6.6 L9.2 9.2' stroke='black' stroke-width='1.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.sourceIndexLinks{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
}
.sourceIndexLink{
  display: inline-flex;
  align-items: center;
  gap: 3px;
  opacity: 0.5;
}
.sourceIndexLink:hover{
  opacity: 1;
}
.scanIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='0.5' width='7' height='9' rx='0.6' stroke='black' stroke-width='1' fill='none'/%3E%3Cpath d='M3.2 3.2 H6.8 M3.2 5 H6.8 M3.2 6.8 H5.4' stroke='black' stroke-width='0.8' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='0.5' width='7' height='9' rx='0.6' stroke='black' stroke-width='1' fill='none'/%3E%3Cpath d='M3.2 3.2 H6.8 M3.2 5 H6.8 M3.2 6.8 H5.4' stroke='black' stroke-width='0.8' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Mid-size: topbar wraps to 2 rows, but sidebar stays visible in the grid
   (no hamburger/off-canvas -- that's reserved for the narrower tier below). */
@media (max-width: 1100px){
  .topbar{ flex-wrap: wrap; height: auto; row-gap: 6px; padding: 8px 10px; }
  .brand{ flex: 1 1 100%; align-items: baseline; column-gap: 10px; }

  /* about.html only: title + dropdown share row 1 (title shrinks to fit,
     dropdown pushed to the row's far right); "Back to tree" moves to its
     own row 2 below, left-aligned -- see the desktop-order rules above
     .topnav for why this page's topbar has 3 direct children instead of
     index.html's 2. */
  .about-topbar{ flex-wrap: wrap; }
  /* The brand shrinks (flex-shrink:1) while .topnav is pushed right by its
     margin-left:auto, so without a floor between them the ellipsized title runs
     straight into the select with no gap at all. column-gap alone won't do it --
     the auto margin absorbs the free space first -- so the title needs its own
     right margin. */
  .about-topbar .brand{ flex: 0 1 auto; margin-right: 14px; min-width: 0; }
  .about-topbar .topnav{ flex: 0 0 auto; margin-left: auto; order: 2; }
  .about-topbar .about-back-link{ order: 3; flex: 1 1 100%; margin-right: 0; }
  .brand-title{ flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; font-size: 17px; }
  /* Wrap rather than nowrap: below ~370px the four controls need 366px on one
     line, the search box is already at its floor, and something has to give --
     nowrap sent the theme button off the right edge instead of onto its own
     line. */
  .topnav{ flex: 1 1 100%; display: flex; align-items: center; column-gap: 6px; flex-wrap: wrap; row-gap: 6px; }
  .search-wrap{ flex: 1 1 auto; min-width: 0; order: 1; }
  .search-input{ width: auto; flex: 1 1 auto; min-width: 0; }
  .topnav .sep-search{ order: 2; }
  /* Scoped to the topbar: this hides the "अ→a" caption to buy horizontal space in
     a cramped nav, but .navlabel is also used for the changelog's
     "include असम्बद्धवर्गीकृतम्" checkbox down in about.html's Data Quantity
     section, which has a full-width row to itself and no reason to disappear. An
     unscoped rule here hid that toggle -- and with it the only way to include the
     orphan bucket in the trends -- at every width below 1100px. */
  .topnav .navlabel{ display: none; }
  #schemeSelect{ order: 4; flex: 0 1 auto; min-width: 0; max-width: 160px; }

  /* A <select> sizes itself to its *longest* option, not the selected one, so
     the box stayed wide while showing a 4-character "IAST". Shortening the two
     worst labels ("Harvard-Kyoto" -> "HK", "Devanagari" -> "देवनागरी", which is
     both narrower and self-demonstrating) shrinks it at the source, leaving
     "ISO 15919" as the widest -- no max-width clamp needed. */
  .about-topbar #schemeSelect{
    font-size: 14px;
    padding: 4px 6px;
  }
  .topnav .sep:last-of-type{ order: 5; }
  .theme-toggle{
    order: 6;
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .theme-toggle .toggle-label{ display: none; }

  /* about.html's nav is just the scheme dropdown + this button (no search box),
     so the toggle sits directly beside #schemeSelect rather than after a
     separator. A fixed 34px box next to the dropdown reads as oversized and
     empty on a phone, so match the dropdown's own height instead and let the
     icon center itself. */
  .about-topbar .theme-toggle{
    width: auto;
    height: auto;
    aspect-ratio: 1;
    padding: 4px 8px;
  }

  .layout{ height: calc(100% - 76px); }
}

/* Narrow (phone): hide version/date, switch sidebar to off-canvas hamburger. */
@media (max-width: 800px){
  .sidebar-toggle{ display: inline-flex; align-items: center; justify-content: center; }
  .brand-meta{ display: none; }

  /* Phone widths: the nav row must be free to shrink, or it runs off the side.
     Measured before this block existed: 65px of horizontal document overflow at
     500px wide, 135px at 430px, 175px at 390px. */
  /* The input carries a fixed 320px width in the base rule, which floors the
     wrap at ~240px whatever flex says. Both the wrap and the input it contains
     need min-width:0 before flex can shrink either one. */
  .topnav .search-wrap{ flex: 1 1 60px; min-width: 0; }
  /* The 84px left padding reserves room for the icon and the "exact" chip that
     sit absolutely inside the field. It is an incompressible floor: no width or
     flex value shrinks the input below it, which is the rest of what pushed the
     row off-screen. The chip stays, so the padding only tightens to what the
     icon and chip actually occupy. */
  .topnav .search-input{ width: 100%; min-width: 0; flex: 1 1 auto; padding-left: 72px; }
  .topnav #schemeSelect{ flex: 0 1 auto; min-width: 0; max-width: none; }
  .topnav .theme-toggle{ flex: 0 0 auto; }
  /* Every divider goes: at this width the pipes cost horizontal room the search
     box needs, and the groups are already legible without them. */
  .topnav .sep{ display: none; }

  /* Body (not .mainpane) scrolls at this width (see .layout below), so the
     topbar needs its own sticky pin here -- above sticky category headers
     (max z-index ~999, see renderCategoryBlock in app.js) but below the
     off-canvas sidebar/backdrop (1900/2000) so an open sidebar still overlays it. */
  /* Content scrolls *under* this bar at phone widths, so the 1px --border alone
     wasn't enough separation -- --panel and --bg differ by only a few points of
     luminance, leaving text appearing to bleed through the boundary. Give the
     bar its own slightly-lifted surface plus a drop shadow so it reads as a
     layer above the page rather than part of it. */
  .topbar{
    position: sticky;
    top: 0;
    z-index: 1000;
    background: var(--panel);
    border-bottom-color: var(--border-strong);
    box-shadow: var(--topbar-shadow);
  }

  /* The document (not .mainpane) scrolls here and the topbar is pinned over it,
     so sticky section headers must come to rest *below* that bar rather than at
     top:0, where they'd slide underneath it. --topbar-h is measured and set on
     :root by about.html, since this topbar wraps to a variable height. */
  .content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
    top: var(--topbar-h, 48px);
  }

  /* ...and .mainpane must stop being an overflow container at this width, or the
     sticky headers above have nothing to stick to. A position:sticky element
     anchors to its nearest *scrolling* ancestor, and an overflow:auto box counts
     as one even when it has nothing to scroll -- which is the case here, since
     about.html's inline `height: calc(100% - 48px)` resolves against an auto-height
     body and lets .mainpane grow to full content height. The header would then
     stick relative to .mainpane and scroll out of view along with it, landing
     underneath the topbar. Handing the scroll to the document instead makes the
     viewport the anchor. `height: auto` also overrides that inline height, which a
     media query otherwise loses to. */
  .mainpane{
    overflow: visible;
    height: auto !important;
  }

  /* With the off-canvas sidebar open, prevent the background (mainpane/body) from
     scrolling underneath it -- html, not just body, since body's own box isn't
     what's scrolling here (see .layout below: document/viewport scroll is used). */
  html:has(body.sidebar-open-lock){ overflow: hidden; }
  body.sidebar-open-lock{ overflow: hidden; }

  body{ height: auto; min-height: 100%; }
  .layout{
    height: auto;
    min-height: calc(100vh - 48px);
    display: block;
    position: relative;
  }

  .sidebar-resizer{ display: none; }

  .sidenav{
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    width: min(85vw, 340px);
    /* Sticky category headers in the main pane use z-index up to ~999
       (1000 - depth, see renderCategoryBlock in app.js) -- the mobile sidebar
       and its backdrop must stay above that ceiling or a deep-scrolled sticky
       header punches through the open sidebar. */
    z-index: 2000;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 2px 0 12px rgba(0,0,0,0.3);
  }
  .sidenav.open{
    transform: translateX(0);
  }

  .sidebar-backdrop{
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.45);
    z-index: 1900;
  }
  .sidebar-backdrop.open{
    display: block;
  }

  .mainpane{
    height: 100%;
  }

  /* The All-row stats/toggle reflow that used to live here is now handled by the
     @container sidenav rule up by .includeOrphansToggle: the off-canvas sidebar is
     min(85vw, 340px) at this width, comfortably under that query's threshold, so
     phones get the same arrangement without a second copy of the rule. */

  /* about.html readability at phone widths. Section <h2>s inherit the UA's 1.5em,
     which at this width wraps a title like "Data Quality: Structural Errors and
     Provenance Hints" to three lines and costs most of the viewport before any
     content shows. Scale the headings down and tighten their leading; the sticky
     bar they sit in shrinks with them. */
  .content .block.collapsible > summary h2{
    font-size: 19px;
    line-height: 1.25;
  }
  .content .block.collapsible > summary h3{
    font-size: 16px;
    line-height: 1.3;
  }
  .content .block.collapsible{ padding: 8px 10px; }
  .content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
    /* Keep the negative margins matched to the reduced padding above, or the
       pinned bar stops spanning the block's full width. */
    margin: -8px -10px 0;
    padding: 8px 10px;
  }
  /* The disclosure triangle is sized in em, so it shrank with the heading -- give
     it back a little breathing room from the now-tighter title. */
  .content .block.collapsible > summary::before{ margin-right: 8px; }

  /* Granularity + orphan-bucket toggle: on one cramped line the label wraps mid-
     phrase, stranding "asambaddhavargīkṛtam" on its own row away from its
     checkbox. Give each control its own full-width row instead, and keep each
     checkbox next to its own text. */
  #changelogGranularity{ margin-left: 6px; }
  label[for="changelogIncludeOrphans"]{
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 0 !important;
    margin-top: 8px;
  }
  label[for="changelogIncludeOrphans"] input{ flex: 0 0 auto; }

  .chart-card h4{ font-size: 12px; }
}

/* === Changelog trend charts === */
.chart-card{
  margin: 10px 0 16px;
}
.chart-card h4{
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}
.chart-svg{
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
.chart-svg .chart-grid{
  stroke: var(--border);
  stroke-width: 1px;
  shape-rendering: crispEdges;
}
.chart-svg .chart-axis-text{
  fill: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* Chart text is authored in the SVG's 720px user-space coordinates and the SVG is
   then scaled to fit (.chart-svg is width:100%), so these font sizes are NOT
   on-screen pixels -- they shrink with the chart. On a 390px phone the chart
   renders ~341px wide, a 0.47x scale, which turns the 11px above into ~5px on
   screen against 18px body text: unreadable.
   Counter-scale so the labels land near 12px actual. Must be declared after the
   base rule above -- a media query adds no specificity, so an identical selector
   later in the file would otherwise win. */
@media (max-width: 800px){
  .chart-svg .chart-axis-text{ font-size: 24px; }
}
.chart-svg .chart-line{
  fill: none;
  stroke: var(--accent);
  stroke-width: 2px;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.chart-svg .chart-area{
  fill: var(--accent);
  opacity: 0.1;
}
.chart-svg .chart-dot{
  fill: var(--accent);
  stroke: var(--panel);
  stroke-width: 2px;
}
.chart-svg .chart-crosshair{
  stroke: var(--muted);
  stroke-width: 1px;
  opacity: 0;
  pointer-events: none;
}
.chart-svg .chart-hover-dot{
  fill: var(--accent);
  stroke: var(--panel);
  stroke-width: 2px;
  opacity: 0;
  pointer-events: none;
}
.chart-svg .chart-hit{
  fill: transparent;
  cursor: crosshair;
}
.chart-tooltip{
  position: absolute;
  pointer-events: none;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.1s ease;
  z-index: 10;
}
.chart-tooltip .chart-tooltip-date{
  color: var(--muted);
  margin-bottom: 2px;
}
.chart-tooltip .chart-tooltip-value{
  font-weight: 600;
}
.chart-wrap{
  position: relative;
}
.sourceTimeline{
  position: relative;
  margin: 14px 0 10px;
  max-width: 100%;
}
.sourceTimelineRow{
  display: flex;
  align-items: stretch;
}
.sourceTimelinePre{
  flex: none;
  width: 28px;
  height: 28px;
  margin-right: 2px; /* surface gap, same as between two real segments */
  border-radius: 4px 0 0 4px;
  background: var(--surface-uncovered);
  cursor: default;
}
.sourceTimelineBar{
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  height: 28px;
  border-radius: 0 4px 4px 0;
  overflow: hidden;
  background: var(--panel2);
}
.sourceTimelineSeg{
  position: relative;
  height: 100%;
  margin-right: 2px; /* surface gap between touching segments */
  cursor: default;
}
.sourceTimelineSeg:last-child{ margin-right: 0; }
.sourceTimelineAxis{
  position: relative;
  height: 16px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--muted);
}
.sourceTimelineAxis span{
  position: absolute;
  transform: translateX(-50%);
  white-space: nowrap;
}
.sourceTimelineAxis span:first-child{ transform: translateX(0); }
.sourceTimelineAxis span:last-child{ transform: translateX(-100%); }
.sourceTimelineLegend{
  display: flex;
  /* Right-aligned so the legend's end lines up with the bar's present-day
     end. Item order (materialized -> legacy-live -> current-live, matching
     the bar's oldest-to-newest layout) comes from the markup, not from
     flex-direction -- reversing via row-reverse looked right on one line but
     flipped back to newest-first as soon as the legend wrapped. */
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 10px;
  font-size: 14px;
  color: var(--text);
}
.legendItem{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.legendSwatch{
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: none;
}
.sourceTimelineTooltip{
  position: absolute;
  z-index: 20;
  pointer-events: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 13px;
  color: var(--text);
  box-shadow: 0 4px 16px rgba(0,0,0,0.25);
  white-space: normal;
  width: max-content;
  max-width: min(360px, calc(100vw - 48px));
}
.sourceTimelineTooltip .ttLabel{ color: var(--muted); }

/* Computed figures. Every `data-stat` number is rewritten by pipeline/audit.py,
   so bolding them marks what is measured rather than authored. Scoped to
   `span` deliberately: the attribute is also legal on an SVG `text` node, which
   carries its own type hierarchy for a blanket rule to flatten. */
span[data-stat] { font-weight: 700; }


/* --- changelog (about.html) ------------------------------------------- */
/* Bars are a plain flex row rather than a charting library: the data is one
   value per year, and about.html loads no dependencies. */
/* Two columns, two rows: the y-axis gutter beside the plot, an empty cell
   beside the x labels. The gutter sizes itself to its widest tick, and the
   empty cell inherits that width, which is what keeps the first x label over
   the first bar without a hard-coded indent anywhere. */
.cl-wrap {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 8px;
  margin: 8px 0 4px;
}
/* The gutter is the plot's height exactly, so a tick positioned by `bottom`
   is in the same coordinate space as a bar's height percentage.

   The ticks inside are absolutely positioned, so they contribute NOTHING to
   this track's width -- the `auto` column collapses to zero and every label
   hangs off its left edge, out past the panel's border. The min-width is what
   reserves room for them; it fits the widest label the axis can produce
   ("1,500", "150 MB") with a little air on the left. */
.cl-yaxis { position: relative; height: 160px; min-width: 52px; }
.cl-tick {
  position: absolute; right: 0;
  /* Centred on its own gridline rather than sitting above it. */
  transform: translateY(50%);
  font-size: 10px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.cl-tick-zero { bottom: 0; }
/* The top tick sits at bottom:100%, where the shared 50% translate would leave
   half the label above the plot and outside the panel's border. Drop it by its
   full height instead, so it hangs just inside the top gridline. */
.cl-tick-top { transform: translateY(100%); }
/* min-width:0 is load-bearing: at month resolution the bar row has 300-odd
   flex items, whose combined minimum content width exceeds the column. Without
   it the `1fr` track refuses to shrink and the plot overruns the panel. */
.cl-plot { position: relative; height: 160px; min-width: 0; }
.cl-grid { position: absolute; inset: 0; pointer-events: none; }
.cl-gridline {
  position: absolute; left: 0; right: 0; height: 1px;
  background: var(--border);
}
/* The zero line is the baseline the bars stand on, so it is drawn on the plot
   itself rather than as one more gridline -- it belongs to the axis. */
.cl-plot::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--border);
}
.cl-chart {
  position: relative;
  display: flex; align-items: flex-end; gap: 3px;
  height: 100%;
}
.cl-col {
  flex: 1 1 0; min-width: 0; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
}
/* Same column geometry as the bars above it, so label i sits under bar i. */
.cl-xaxis {
  display: flex; gap: 3px; margin-top: 3px; min-width: 0;
}
.cl-xaxis.cl-dense { gap: 1px; }
.cl-xaxis > .cl-year { flex: 1 1 0; min-width: 0; }
/* At month resolution there are 300+ columns in the width of the page, so the
   gap and the per-bar label both have to go -- a 1px bar beside a 3px gap is
   mostly gap, and every twelfth label is what the axis can actually carry.
   Set by the JS, which knows how many columns it just drew. */
.cl-chart.cl-dense { gap: 1px; }
.cl-chart.cl-dense .cl-bar { border-radius: 0; }
.cl-bar {
  background: var(--accent, #6ea8fe); border-radius: 2px 2px 0 0; min-height: 2px;
}
.cl-col-partial .cl-bar {
  /* Documents seen in the live catalogue but never measured -- hatched so the
     bar does not read as a like-for-like byte comparison. */
  background: repeating-linear-gradient(
    45deg, var(--accent, #6ea8fe), var(--accent, #6ea8fe) 3px,
    transparent 3px, transparent 6px);
}
.cl-year {
  font-size: 10px; opacity: 0.65; text-align: center;
  font-variant-numeric: tabular-nums;
  /* One line only: a label wider than its column would otherwise wrap and
     shove the bars up, which is what 300 monthly labels did. */
  white-space: nowrap;
}
/* A column whose label is suppressed still reserves the row, so the bars all
   end at the same baseline. */
.cl-year:empty::after { content: "\00a0"; }

/* Changelog toggles: three independent axes (metric, granularity, mode),
   each a segmented group of radio-like buttons. */
.cl-controls {
  display: flex; flex-wrap: wrap; align-items: center;
  /* The row gap has to clear the space between groups, or a wrapped group
     sits closer to the row above than to its own neighbours. */
  gap: 6px 22px; margin: 10px 0 2px;
}
.cl-ctl-group { display: inline-flex; align-items: center; gap: 7px; }
.cl-ctl-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  opacity: 0.55;
  /* A two-word label breaking mid-group makes that group taller than its
     neighbours and pushes the row out of alignment. Wrap between groups
     instead, which the row already does. */
  white-space: nowrap;
}
.cl-controls button {
  font: inherit; font-size: 12px; padding: 3px 10px; cursor: pointer;
  background: transparent; color: inherit;
  border: 1px solid var(--border, #2a2f3a);
}
/* Scoped to the segment, not the row. As `.cl-controls button:first-of-type`
   these matched once per toolbar, so every button between the two ends kept
   square corners and read as a loose box instead of a joined segment. The
   `.cl-seg` wrapper holds only the buttons -- the group also holds the label,
   so scoping to the group makes the label the "first child" and the radii
   land on the wrong edges. */
.cl-seg { display: inline-flex; }
.cl-seg button:first-child { border-radius: 4px 0 0 4px; }
.cl-seg button:last-child { border-radius: 0 4px 4px 0; }
.cl-seg button + button { border-left: none; }
.cl-controls button:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
.cl-controls button.cl-on {
  background: var(--accent, #6ea8fe);
  border-color: var(--accent, #6ea8fe);
  color: #fff;
}
/* The segment is a set of radio-like choices, and `.cl-on` already says which
   is current. A focus ring left standing after a CLICK drew a second, louder
   box around a button the user had just answered -- so the ring is reserved
   for keyboard focus, where it is the only cue there is. */
.cl-controls button:focus { outline: none; }
.cl-controls button:focus-visible {
  outline: 2px solid var(--accent, #6ea8fe); outline-offset: 2px;
}
.cl-hint { font-size: 12px; opacity: 0.7; margin-top: 8px; }

/* A callout for a sentence that states the conclusion a section has been
   building toward (about.html: the folders/topics/menus summary). Indented and
   boxed so it reads as a resting point rather than another paragraph.

   --panel2 rather than --panel because these sit INSIDE .content .block, which
   is already --panel; a callout the same color as its container is not a
   callout. The accent left rule carries the emphasis, so the background can
   stay quiet and the text keeps normal weight -- bolding a full sentence is
   what this exists to avoid.

   The doubled selector is for specificity: `.content .block.collapsible p`
   (0,3,1) sets a margin and these callouts are always such a <p>, so a plainer
   `.content .callout` (0,2,0) loses the indent. Matched rather than
   !important'd. */
.content .block.collapsible p.callout,
.content .callout{
  background: var(--panel2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  padding: 10px 14px;
  margin: 12px 0 12px 1.6em;
}

/* The local-only plain-text link, shown by `serve_docs.py --fulltext`.
   Deliberately quiet: a dev affordance, not part of the published UI, and it
   never renders on the deployed site. */
.localTxtLink {
  margin-left: 6px;
  font-size: 0.8em;
  opacity: 0.65;
  text-decoration: none;
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 0 4px;
}
.localTxtLink:hover { opacity: 1; }
