/* pages-full.css - everything above the shared base: the webfonts and the
   app's whole component and view vocabulary. Loaded by the app shell
   (index.html, and by gs-upgrade.js at page boot) and linked by every
   pre-rendered page for the classes the two surfaces share (.chip, .card,
   .markdown, …), so a generated page renders the app's own treatment by
   construction. The design tokens, theme gates, reset and body/anchor base
   live in pages-core.css — this sheet only consumes those vars and declares
   none of its own. */

  /* Webfonts (SIL OFL 1.1), shipped in the shell under fonts/. The ONLY
     url() consumers in either sheet, and the reason they live here: this file
     is always fetched from the prefix root, while pages-core.css is inlined
     into pages at arbitrary depths where a relative URL would break. EB
     Garamond is one variable file; IBM Plex Mono is four static weights, and
     a browser only downloads the weights a page actually renders. */
  @font-face {
    font-family: 'EB Garamond';
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url('fonts/eb-garamond.woff2') format('woff2');
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/ibm-plex-mono.woff2') format('woff2');
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-medium.woff2') format('woff2');
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-semibold.woff2') format('woff2');
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/ibm-plex-mono-bold.woff2') format('woff2');
  }

  /* The tokens, theme gates, global reset and body/anchor base all live in
     pages-core.css (shared with the pre-rendered pages, which inline it).
     Only the app shell's flex frame is body-level styling of this sheet's
     own. */
  body { min-height: 100vh; display: flex; flex-direction: column; }
  /* One shared keyboard-focus ring for native controls. The component :focus
     rules keep their border-color shifts but no longer suppress the outline.
     (Browsers treat a focused text input as :focus-visible, so inputs show the
     ring on click-focus too — accepted.) */
  :is(button, input):focus-visible { outline: 2px solid color-mix(in srgb, var(--link) 60%, transparent); outline-offset: 1px; }
  /* Button system: three bases — bordered control, pill, and bare text — each
     stated once; a component's own later rule carries only its deviations
     (layout, a link color, an intentional size). */
  .diff-btn, .version-diff-btn, .gran-btn, .pref-btn, .share-link, .fs-btn {
    background: var(--btn); border: 1px solid var(--line); border-radius: var(--r-ctl);
    padding: var(--pad-ctl); cursor: pointer; font-family: var(--mono);
    font-size: var(--fs-ui); color: var(--muted); line-height: 1.2;
  }
  .diff-btn:hover, .version-diff-btn:hover, .gran-btn:hover, .pref-btn:hover,
  .share-link:hover, .fs-btn:hover { color: var(--text); background: var(--chip); }
  .pref-btn:hover, .share-link:hover { border-color: var(--link); }
  /* .facet-chip rides the pill base for the shared recipe (border, radius,
     cursor, mono voice) and keeps only its deviations at its own rule below
     (flex layout for the count, chip fill, tighter padding, text color). */
  .filter-chip, .load-more, .board-lane-jump, .facet-chip {
    background: var(--btn); border: 1px solid var(--line); border-radius: var(--r-pill);
    padding: 0.15rem 0.7rem; cursor: pointer; font-family: var(--mono);
    font-size: var(--fs-ui); color: var(--muted); line-height: 1.2;
  }
  .filter-chip:hover, .load-more:hover, .board-lane-jump:hover { color: var(--text); background: var(--chip); }
  .load-more:hover, .board-lane-jump:hover { border-color: var(--link); }
  .view-toggle, .board-col-toggle, .board-col-hide, .show-more, .body-clamp-toggle,
  .board-more, .facet-more {
    background: transparent; border: none; cursor: pointer;
    font-family: var(--mono); font-size: var(--fs-ui); color: var(--muted);
  }
  .view-toggle:hover, .board-col-toggle:hover, .board-col-hide:hover,
  .show-more:hover, .body-clamp-toggle:hover { color: var(--text); }
  /* One input recipe (in-page search, tree filter, contributor filter): mono at
     the UI scale on the panel surface, one shared link-tinted focus shift. */
  .search-input, .tree-search, .contrib-filter {
    border: 1px solid var(--line); border-radius: var(--r-ctl); background: var(--panel);
    color: var(--text); font-family: var(--mono); font-size: var(--fs-ui);
  }
  .search-input::placeholder, .tree-search::placeholder, .contrib-filter::placeholder { color: var(--muted); }
  .search-input:focus, .tree-search:focus, .contrib-filter:focus { border-color: color-mix(in srgb, var(--link) 50%, var(--line)); }
  /* Callout base: one bordered, rounded message box. .err is the danger
     variant; .notice is neutral-informational (muted) — a notice that really
     warns takes .notice-warn explicitly. */
  .err, .notice { border: 1px solid var(--line); border-radius: var(--r-panel); }
  /* Two-panel shell echoing the TUI nav + content split. No header bar: the
     theme control and the brand credit both live in the sidebar. */
  .shell {
    flex: 1; width: 100%; max-width: var(--shell-max); margin: 0 auto;
    display: grid; grid-template-columns: var(--nav-w) 1fr; gap: var(--nav-gap);
    padding: var(--shell-pad) var(--shell-pad) 2rem;
  }
  /* Sidebar column: sticky to the viewport and independently scrollable while the
     footer is pinned to the viewport bottom (position:fixed, below). The nav's own
     bottom padding reserves the footer's height so the last links scroll fully
     into view above it, never hidden behind it. 100dvh (with a 100vh fallback)
     tracks mobile browser chrome. */
  .nav {
    min-width: 0; display: flex; flex-direction: column;
    position: sticky; top: 1.25rem; align-self: start;
    height: calc(100vh - 2.5rem); height: calc(100dvh - 2.5rem);
    overflow-y: auto; padding-bottom: 3.25rem;
  }
  /* Sidebar header row: repo title, theme toggle (icon-only, before collapse),
     and the collapse control all on one line. */
  .nav-header {
    display: flex; align-items: center; gap: 0.4rem;
    padding-bottom: 0.6rem; margin-bottom: 0.6rem; border-bottom: 1px solid var(--line);
  }
  .repo-title {
    flex: 1 1 auto; min-width: 0;
    font-family: var(--mono); font-weight: 600; font-size: var(--fs-ui);
    color: var(--muted); word-break: break-word;
  }
  .theme-icon, .nav-collapse {
    flex: 0 0 auto; background: transparent; border: none; cursor: pointer;
    color: var(--muted); padding: 0; line-height: 1; display: inline-flex;
    align-items: center; justify-content: center;
    width: 18px; height: 18px;
  }
  .theme-icon:hover, .nav-collapse:hover { color: var(--text); }
  .theme-icon svg { width: 13px; height: 13px; display: block; }
  .nav-collapse { font-family: var(--mono); font-size: var(--fs-md); }
  .nav-handle {
    display: none; position: fixed; top: 0.75rem; left: 0.75rem; z-index: 6;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-ctl);
    color: var(--muted); font-family: var(--mono); font-size: var(--fs-md);
    padding: 0.25rem 0.5rem; cursor: pointer;
  }
  .nav-handle:hover { color: var(--text); background: var(--chip); }
  body.nav-collapsed .nav { display: none; }
  body.nav-collapsed .shell { grid-template-columns: 1fr; }
  body.nav-collapsed .nav-handle { display: block; }
  /* Layout width: fixed (max-width shell, the reading default) or full width
     (code/diff-friendly), toggled in the sidebar header and persisted. */
  body.wide .shell { max-width: none; }
  #nav { display: flex; flex-direction: column; gap: 0.15rem; }
  /* Grouped nav (mirrors the TUI nav registry): a section is a non-collapsible
     muted label followed by its links; the group is itself a flex column so the
     0.15rem rhythm carries inside it. Child links take a slight left indent,
     GitHub settings-sidebar style. Reuses --muted / --fs-dense; theming rides
     the CSS variables, so light/dark need no extra rules. */
  #nav .nav-group { display: flex; flex-direction: column; gap: 0.15rem; }
  #nav .nav-section, .nav-footer {
    font-family: var(--mono); font-size: var(--fs-dense); text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--muted); opacity: 0.7;
    padding: 0.4rem 0.5rem 0.1rem;
  }
  #nav .nav-group a { margin-left: 1.5rem; }
  /* Nav items reuse the tree-row hover feel: a soft rounded chip tint. The
     active item is signaled by a stronger link-tinted fill + bolder text (the
     trees.software list style), NOT a left-border indicator. The whole sidebar
     shares one voice: IBM Plex Mono at 0.8rem (repo title, nav links, glyphs),
     with the file tree and footer kept denser. */
  #nav a, #nav .nav-disabled {
    display: flex; align-items: center; padding: 0.26rem 0.5rem; border-radius: var(--r-ctl);
    font-family: var(--mono); font-size: var(--fs-ui); color: var(--muted);
  }
  #nav a:hover { color: var(--text); text-decoration: none; background: var(--chip); }
  #nav a.active {
    color: var(--text); font-weight: 600;
    background: var(--link-t2);
  }
  /* Nav item icons mirror the TUI nav panel's per-item glyphs. Muted like the
     file-type icons, inheriting the item's hover/active color; a fixed box so
     labels align regardless of glyph width. */
  .nav-icon {
    flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    width: 1rem; margin-right: 0.5rem; color: inherit;
    font-family: var(--mono); font-size: var(--fs-ui); line-height: 1;
  }
  /* Code nav search magnifier: pinned to the item's right edge, muted like the
     glyphs, brightening on hover. Clicking navigates to Code (if elsewhere) and
     focuses the tree search input — no second search box. */
  .nav-search {
    flex: 0 0 auto; margin-left: auto; display: inline-flex; align-items: center;
    justify-content: center; width: 1rem; height: 1rem; color: var(--muted); cursor: pointer;
  }
  #nav a:hover .nav-search, .nav-search:hover { color: var(--text); }
  .nav-search .gs-icon { width: 0.85rem; height: 0.85rem; color: inherit; }
  #nav .nav-disabled { color: var(--muted); opacity: 0.4; cursor: default; }
  #nav .nav-disabled::after {
    content: "M1"; font-family: var(--mono); font-size: var(--fs-dense);
    margin-left: 0.4rem; opacity: 0.8;
  }
  .content { min-width: 0; }
  /* Panel surface: the one bordered, rounded, panel-filled box every list-like
     component shares; a component's own rule below carries only its deviations
     (overflow mode, padding, margins). */
  .tree-list, .blob, .blob-img, .diff-file, .asset-list, .contrib-list, .version-node {
    border: 1px solid var(--line); border-radius: var(--r-panel);
    overflow: hidden; background: var(--panel);
  }
  /* Cards / items. */
  .card {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-panel);
    padding: var(--pad-panel); margin: 0 0 0.7rem;
  }
  /* Whole-card navigation: the card links to its item detail, so it takes a
     pointer cursor and a tree-row-like hover (chip tint + link-tinted border).
     Inner anchors and text selection keep their own behavior in JS. */
  .card.clickable { cursor: pointer; transition: border-color 0.12s ease, background 0.12s ease; }
  .card.clickable:hover {
    border-color: color-mix(in srgb, var(--link) 45%, var(--line));
    background: var(--chip);
  }
  /* Subject scale: one semantic element, always weight 600, sized by context —
     card = body size, detail = --fs-h1 (it is the page title), diff-head =
     --fs-h4, board-card = --fs-ui. The size tiers live on the context rules
     (.detail/.card here, diff-head and board-card at their own rules below);
     cards nest inside .detail views, so the card tier is stated after the
     detail tier to win the body size back. */
  .subject { font-weight: 600; }
  /* Card head: glyph + state chip + subject + trailing chips on one baseline row,
     with a small gap so chips never touch (and the chip row aligns to the subject
     column). The leading glyph drops its own inline margin — the flex gap spaces
     it — so subject text and any wrapped chips share one left edge. */
  .card-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.4rem; }
  .card-head .subject { flex: 1 1 0; min-width: 0; }
  .card-head .type-glyph { margin-right: 0; }
  .card .body { white-space: pre-wrap; word-break: break-word; margin-top: 0.3rem; }
  .card .body.dim { color: var(--muted); }
  /* List-card body clamp: long messages cap at ~10 rendered lines (--lh-body
     em-steps, matching the base font shorthand) with a theme-independent
     mask fade over the last ~2 lines; fixed-em gradient stops keep bodies
     shorter than the cap fully opaque. JS removes the class when the text fits
     and attaches the Show more toggle only when it overflows. */
  .body-clamp {
    max-height: calc(10 * var(--lh-body) * 1em); overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(8 * var(--lh-body) * 1em), transparent calc(10 * var(--lh-body) * 1em));
    mask-image: linear-gradient(to bottom, #000 calc(8 * var(--lh-body) * 1em), transparent calc(10 * var(--lh-body) * 1em));
  }
  .body-clamp-toggle { display: block; margin-top: 0.2rem; padding: 0.15rem 0; }
  .meta { color: var(--muted); font-size: var(--fs-ui); display: inline-flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; margin-left: 1.75rem; }
  .hash { font-family: var(--mono); font-size: var(--fs-ui); color: var(--muted); }
  /* Type glyphs carry one consistent semantic scheme across every view (timeline,
     search, lists, board, milestones, detail): a state-bearing glyph (issue,
     pull-request) is colored by state (open/closed/merged), and every neutral TYPE
     glyph (release, post, milestone, sprint, memo, …) shares the single muted
     color. This mirrors the TUI, which dims card glyphs and conveys state through
     the state chip. */
  .type-glyph { display: inline-block; width: 1.1em; margin-right: 0.3em; text-align: center; line-height: 1; vertical-align: middle; font-family: var(--mono); color: var(--muted); }
  .type-glyph.tg-open { color: var(--open); }
  .type-glyph.tg-closed { color: var(--closed); }
  .type-glyph.tg-merged { color: var(--merged); }
  .chip {
    display: inline-block; background: var(--chip); border-radius: var(--r-pill);
    padding: 0.05rem 0.5rem; font-size: var(--fs-ui); color: var(--muted);
    font-family: var(--mono); font-weight: 500;
  }
  .chip.state { color: #fff; }
  .chip.open { background: var(--open); }
  .chip.closed { background: var(--closed); }
  .chip.merged { background: var(--merged); }
  .chip.pre { background: var(--warn); }
  /* Milestone/sprint lifecycle state chips. Every state carries a solid fill so
     the white .chip.state text stays legible (an unmapped state would fall back
     to the translucent --chip background and vanish). Colors are chosen for
     WCAG-AA contrast with white in both themes: active reuses the link blue,
     completed the done green, planned a solid neutral grey, canceled/unknown a
     muted slate. */
  .chip.active { background: #1f6feb; }
  .chip.completed { background: var(--open); }
  .chip.planned { background: #6b7280; }
  .chip.canceled, .chip.unknown { background: #6e7681; }
  .empty, .loading { color: var(--muted); padding: 2.5rem 0; text-align: center; }
  /* Page-entry boot, second half (gs-upgrade.js). The boot reveals the chrome as
     soon as THIS sheet governs the page, well before the first view's data has
     been read, so the visitor gets the site's frame for most of the wait instead
     of a blank page. `gs-loading` on <html> is what makes that safe: it holds the
     content slot on the same loading treatment as `.loading` above and hides
     whatever the app has rendered into it, so the slot goes from loading to the
     finished view exactly once rather than assembling itself in view. The boot
     removes the class at that transition; the plain shell never sets it. */
  html.gs-loading #view > * { display: none; }
  html.gs-loading #view::before {
    content: "Loading…"; display: block;
    color: var(--muted); padding: 2.5rem 0; text-align: center;
  }
  .err {
    color: var(--danger); border-color: var(--danger);
    padding: var(--pad-panel); background: var(--danger-t1);
  }
  .detail dl { margin: 0.6rem 0 0; display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 0.9rem; }
  .detail dt { color: var(--muted); font-size: var(--fs-ui); font-family: var(--mono); }
  .detail dd { margin: 0; font-size: var(--fs-ui); word-break: break-word; }
  .detail .subject { font-size: var(--fs-h1); margin-bottom: 0.3rem; }
  .detail .card .subject, .card .subject { font-size: var(--fs-body); }
  .back { display: inline-block; margin-bottom: 0.8rem; color: var(--muted); font-size: var(--fs-ui); }
  /* Detail top bar: the back link on the left, the copy-link/share control on the
     right, on one row so the share affordance never disturbs the meta line. */
  .detail-topbar { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
  .detail-topbar .back { margin-bottom: 0.8rem; }
  .share-link {
    flex: 0 0 auto; max-width: 100%; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
  }
  a.repo-title { color: var(--muted); }
  a.repo-title:hover { color: var(--text); text-decoration: none; }
  .mono { font-family: var(--mono); }
  /* File browser: breadcrumb, tree listing, blob view. */
  .breadcrumb { font-size: var(--fs-ui); margin-bottom: 0.8rem; word-break: break-all; }
  .breadcrumb a { color: var(--link); }
  /* Rows use the code-block mono at the blob scale, no inter-row separators
     (the outer .tree-list border stays), so the browser reads like the blob.
     Compact vertical rhythm (trees.software density): ~1.6em line boxes. */
  .tree-row {
    display: flex; align-items: center; gap: 0.5rem; line-height: 1.6;
    padding: 0.12rem 0.7rem; font-family: var(--mono); font-size: var(--fs-code); color: var(--text);
  }
  .tree-row:hover { background: var(--chip); text-decoration: none; }
  /* Home root listing: collapsed to HOME_FILE_LIMIT rows with a gradient fade
     over the last visible row and a centered chevron toggle that flips to
     "Show less" once expanded. The fade uses the page-background token so it
     reads on both themes, and takes no pointer events so rows stay clickable. */
  .home-list { position: relative; }
  .tree-fade {
    position: absolute; left: 0; right: 0; bottom: 0; height: 2.75rem;
    pointer-events: none;
    background: linear-gradient(to bottom, transparent, var(--bg));
  }
  .show-more {
    display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
    width: 100%; margin-top: 0.5rem; padding: 0.35rem;
  }
  .show-more .gs-icon { width: 1.15rem; height: 1.15rem; color: inherit; }
  .show-more-label { font-size: var(--fs-ui); }
  /* Home's recent-activity heading, above the metadata-only item rows that close
     the landing (the static front page carries the same section). */
  .home-activity-head {
    font-size: var(--fs-h2); margin: 1.5rem 0 0.5rem;
    border-bottom: 1px solid var(--line); padding-bottom: 0.25rem;
  }
  .tree-icon { width: 1.1rem; text-align: center; opacity: 0.85; }
  /* File-type icons (vendored @pierre/vscode-icons, monochrome currentColor, so
     they read on both themes via the inherited --muted color). Cloned from
     cached templates; the render layer falls back to the text glyphs when
     icons.js is absent. */
  .gs-icon { display: inline-flex; align-items: center; justify-content: center; width: 1rem; height: 1rem; flex: 0 0 auto; color: var(--muted); vertical-align: -0.15em; }
  .gs-icon svg { width: 100%; height: 100%; display: block; }
  .tree-icon.gs-icon { width: 1.1rem; height: 1.1rem; opacity: 1; }
  /* Per-filetype icon color: the monochrome Pierre SVGs inherit currentColor, so
     tinting is just a color on the wrapper, switched per theme by the --i-* hue
     tokens. Compound selectors keep specificity above the base .gs-icon --muted.
     Uncolored keys (folders, unknown, symlink, and the monochrome types) carry
     no i-* class and stay --muted. */
  .gs-icon.i-cyan { color: var(--i-cyan); }
  .gs-icon.i-yellow { color: var(--i-yellow); }
  .gs-icon.i-orange { color: var(--i-orange); }
  .gs-icon.i-indigo { color: var(--i-indigo); }
  .gs-icon.i-green { color: var(--i-green); }
  .gs-icon.i-blue { color: var(--i-blue); }
  .gs-icon.i-red { color: var(--i-red); }
  .gs-icon.i-vermilion { color: var(--i-vermilion); }
  /* In-place hierarchical tree (Code view + directory routes): directory rows
     expand children inline. Nesting reads from plain indentation alone (a
     rail-free spacer sized by depth), no vertical guides. The chevron toggles
     expansion; the directory name is a real anchor that navigates. */
  .tree-dir { cursor: pointer; }
  .tree-indent { flex: 0 0 auto; align-self: stretch; }
  .tree-chevron-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 0.85rem; flex: 0 0 auto; color: var(--muted); cursor: pointer;
  }
  .tree-chevron-btn:hover { color: var(--text); }
  .tree-name { color: inherit; }
  .tree-name:hover { text-decoration: none; }
  .tree-chevron { width: 0.85rem; height: 0.85rem; flex: 0 0 auto; opacity: 0.7; transform: rotate(-90deg); transition: transform 0.12s ease; }
  .tree-chevron.open { transform: rotate(0); }
  .tree-chevron-spacer { width: 0.85rem; flex: 0 0 auto; }
  .tree-dir:focus-visible { outline: 1px solid var(--link); outline-offset: -1px; }
  .tree-more { color: var(--muted); font-style: italic; }
  /* In-place file/dir search (Code view): a subtle mono input above the tree,
     matching the visual language. Matches highlight with the mark tint; a
     truncation notice reuses the .notice look. */
  .tree-search-wrap { margin-bottom: 0.6rem; }
  .tree-search { width: 100%; padding: 0.35rem 0.6rem; }
  .tree-row mark.tree-mark {
    background: var(--warn-t3);
    color: inherit; border-radius: 2px; padding: 0 1px;
  }
  /* Active row tint (current file/dir in the sidebar tree), matching the nav
     active fill — a link-tinted background, no left border. */
  .tree-active, .tree-active:hover { background: var(--link-t2); }
  /* Code-context sidebar file tree: a compact repo tree nested under the Code
     nav item so files stay navigable while the content pane shows a blob.
     Smaller than the content tree to fit the 220px column; long names ellipsize
     with the full name in a title attr. Hidden when empty, when the sidebar is
     collapsed (the whole nav is hidden then), and on the narrow top-nav layout. */
  .nav-tree-slot { margin: 0.1rem 0 0.2rem 0.35rem; overflow-x: hidden; }
  .nav-tree-slot:empty { display: none; }
  .nav-tree-list { border: none; background: transparent; }
  .nav-tree-list .tree-row { padding: 0.06rem 0.3rem; line-height: 1.4; font-size: var(--fs-ui); border-radius: var(--r-ctl); }
  .nav-tree-list .tree-row .mono { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bc-icon { margin-left: 0.3rem; width: 0.95rem; height: 0.95rem; }
  .diff-file-icon { width: 0.95rem; height: 0.95rem; }
  .blob-meta { margin-bottom: 0.6rem; }
  .blob-head { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
  .blob-head .breadcrumb { flex: 1 1 auto; margin-bottom: 0; }
  .blob-head .view-modes, .blob-head .blob-meta { margin-bottom: 0; flex: 0 0 auto; }
  .notice {
    color: var(--muted);
    padding: 0.4rem 0.7rem; margin-bottom: 0.6rem; font-size: var(--fs-ui);
  }
  .notice.notice-warn { color: var(--warn); }
  /* Code leading, three deliberate tiers: fenced blocks, .raw-body and the
     diff surfaces share the diff standard 1.45; the blob viewer reads at 1.5
     (a whole screen of code earns the extra air); .tree-row sits at 1.6 (its
     line box IS the row rhythm). Anything else is a bug, not a fourth tier. */
  .blob {
    overflow: auto;
    font-family: var(--mono); font-size: var(--fs-code); line-height: 1.5;
  }
  .blob-row { display: flex; }
  .blob-row.hl { background: var(--warn-t1); }
  .blob-row .ln {
    flex: 0 0 auto; min-width: 3rem; text-align: right; padding: 0 0.7rem;
    color: var(--muted); user-select: none; border-right: 1px solid var(--line);
  }
  .blob-row .ln:hover { color: var(--text); text-decoration: none; }
  .blob-row .lc { white-space: pre; padding: 0 0.7rem; }
  .blob-img { padding: 0.9rem; text-align: center; }
  .blob-img img { max-width: 100%; height: auto; }
  /* README / Markdown. */
  .markdown { margin-top: 1rem; word-break: break-word; }
  .markdown h1, .markdown h2, .markdown h3, .markdown h4 { margin: 1rem 0 0.5rem; line-height: 1.2; }
  .markdown h1 { font-size: var(--fs-h1); border-bottom: 1px solid var(--line); padding-bottom: 0.3rem; }
  .markdown h2 { font-size: var(--fs-h2); border-bottom: 1px solid var(--line); padding-bottom: 0.25rem; }
  .markdown p { margin: 1rem 0; }
  .markdown ul, .markdown ol { margin: 0.6rem 0 0.6rem 1.4rem; }
  .markdown li { margin: 0.2rem 0; }
  .markdown code {
    font-family: var(--mono); font-size: 1rem;
    background: var(--code-bg); border: 1px solid var(--line);
    padding: 0 0.3rem; border-radius: 4px;
  }
  .markdown pre.codeblock {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-panel);
    /* Tighter than --pad-panel on purpose (flush top): code-only, so cards and
       the other panel surfaces keep the shared pair. */
    padding: 0 0.9rem 0.5rem; overflow: auto; margin: 0.7rem 0; line-height: 0.8;
  }
  .markdown pre.codeblock code { background: none; border: none; padding: 0; font-size: var(--fs-code); }
  .markdown img { max-width: 100%; height: auto; }
  .markdown div[align="center"], .markdown center, .markdown [align="center"] { text-align: center; }
  .markdown blockquote {
    margin: 0.7rem 0; padding: 0.2rem 0.9rem; color: var(--muted);
    border-left: 3px solid var(--line);
  }
  .markdown del, .markdown s, .markdown strike { text-decoration: line-through; opacity: 0.75; }
  .markdown kbd {
    font-family: var(--mono); font-size: 0.78em; padding: 0.05rem 0.35rem;
    border: 1px solid var(--line); border-radius: 4px; background: var(--chip);
  }
  .markdown table { border-collapse: collapse; margin: 0.8rem 0; display: block; overflow: auto; max-width: 100%; }
  .markdown th, .markdown td { border: 1px solid var(--line); padding: 0.3rem 0.6rem; text-align: left; }
  .markdown th { background: var(--chip); font-weight: 600; }
  .markdown li.task { list-style: none; margin-left: -1.1rem; }
  .markdown li.task input { margin-right: 0.4rem; }
  .markdown ul ul, .markdown ol ol, .markdown ul ol, .markdown ol ul { margin: 0.2rem 0 0.2rem 1.2rem; }
  /* Rendered|Raw toggle for .md blob views, reusing the diff-mode toggle look. */
  .view-modes { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-ctl); overflow: hidden; margin-bottom: 0.6rem; }
  .body-modes { margin: 0.6rem 0 0.2rem; }
  /* Raw commit-message pane: the verbatim message (subject + body + GitMsg
     trailer block) reads as a code block — mono at the code scale, panel
     background, bordered, rounded. Kept pre-wrap (not pre + horizontal scroll):
     the message is mostly prose that wants to wrap, and word-break folds the
     long GitMsg trailer refs rather than forcing the whole pane to scroll
     sideways (overflow-x stays as a safety valve for anything unbreakable). */
  .raw-body {
    white-space: pre-wrap; word-break: break-word; overflow-x: auto;
    font-family: var(--mono); font-size: var(--fs-code); line-height: 1.45;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-panel);
    padding: var(--pad-panel);
  }
  .view-toggle { padding: 0.2rem 0.7rem; }
  .view-toggle:hover { background: var(--chip); }
  .view-toggle.active { color: var(--text); background: var(--chip); font-weight: 600; }
  /* Fullscreen affordance: expand button on code/diff/fenced blocks, and the
     full-viewport overlay it opens. */
  .fs-btn { padding: 0.15rem 0.35rem; line-height: 1; }
  .fs-wrap { position: relative; }
  .fs-wrap > .fs-btn { position: absolute; top: 0.35rem; right: 0.35rem; z-index: 2; }
  .fs-overlay { position: fixed; inset: 0; z-index: 50; background: var(--bg); }
  /* Floating close: a semi-transparent circled X whose center sits on the
     top-right corner of the content block's border (content padding 1.25rem,
     button 2rem, so a 0.25rem offset centers it on the corner). The text-mixed
     backing and currentColor glyph read over code on both themes; dimmed while
     idle, full opacity on hover. */
  .fs-close.circle {
    position: fixed; top: 0.25rem; right: 0.25rem; z-index: 51;
    width: 2rem; height: 2rem; border-radius: 50%; border: none; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--text) 12%, transparent);
    color: var(--text); cursor: pointer; font-size: var(--fs-md); line-height: 1;
    opacity: 0.6; backdrop-filter: blur(2px);
    transition: opacity 0.12s ease;
  }
  .fs-close.circle:hover { opacity: 1; }
  .fs-content { position: absolute; inset: 0; overflow: auto; padding: 1.25rem; }
  .fs-content .blob, .fs-content .diff-file-body, .fs-content pre.codeblock { max-width: 100%; }
  .meta-strip { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
  /* Diff views: changed-file list, unified + split hunks. */
  .diff-section { margin-top: 1.2rem; }
  .diff-head {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
    padding-bottom: 0.5rem; margin-bottom: 0.6rem; border-bottom: 1px solid var(--line);
  }
  .diff-head .subject { font-size: var(--fs-h4); }
  .chip.caveat { background: var(--warn-t2); color: var(--text); }
  /* Diff bulk-control row: the expand/collapse toggle, the unified|split mode
     toggle (one button each, label = the action a click performs), and
     fullscreen cluster at the row's right edge. */
  .diff-controls { margin-left: auto; display: inline-flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
  .diff-file { margin-bottom: 0.7rem; }
  .diff-file-head {
    display: flex; align-items: center; gap: 0.6rem; padding: var(--pad-row);
    cursor: pointer; font-size: var(--fs-ui);
  }
  .diff-file-head:hover { background: var(--chip); }
  .diff-status {
    flex: 0 0 auto; width: 1.1rem; text-align: center; font-family: var(--mono);
    font-size: var(--fs-ui); font-weight: 700; border-radius: 4px; color: #fff;
  }
  .diff-status.s-added { background: var(--open); }
  .diff-status.s-deleted { background: var(--danger); }
  .diff-status.s-modified { background: var(--warn); }
  .diff-path { flex: 1 1 auto; word-break: break-all; font-family: var(--mono); font-size: var(--fs-code); }
  .diff-counts { flex: 0 0 auto; display: inline-flex; gap: 0.5rem; font-size: var(--fs-ui); }
  .cnt-add { color: var(--open); }
  .cnt-del { color: var(--danger); }
  .diff-file-body { border-top: 1px solid var(--line); overflow: auto; }
  .diff-hunk-head {
    font-size: var(--fs-ui); color: var(--muted);
    background: var(--link-t1);
    padding: 0.15rem 0.7rem;
  }
  .diff-line { display: flex; font-family: var(--mono); font-size: var(--fs-ui); line-height: 1.45; }
  .diff-line.add { background: var(--open-t1); }
  .diff-line.del { background: var(--danger-t1); }
  .dl-num {
    flex: 0 0 auto; min-width: 2.6rem; text-align: right; padding: 0 0.5rem;
    color: var(--muted); user-select: none; font-size: var(--fs-dense);
  }
  .dl-sign { flex: 0 0 auto; width: 1rem; text-align: center; user-select: none; color: var(--muted); }
  .dl-text { white-space: pre-wrap; word-break: break-word; padding-right: 0.6rem; flex: 1 1 auto; }
  /* Split view is one CSS grid (num/code/num/code) so the four columns align
     and each row's two sides stay height-locked when one wraps. .ds-contents
     groups a row's four cells yet lets them be direct grid items. */
  .diff-body.split { display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr); }
  .ds-contents { display: contents; }
  .ds-full { grid-column: 1 / -1; }
  .ds-num {
    text-align: right; padding: 0 0.5rem; color: var(--muted); user-select: none;
    font-family: var(--mono); font-size: var(--fs-dense); line-height: 1.45;
  }
  .ds-code {
    white-space: pre-wrap; word-break: break-word; padding: 0 0.6rem;
    font-family: var(--mono); font-size: var(--fs-ui); line-height: 1.45;
  }
  .ds-code.ds-left { border-right: 1px solid var(--line); }
  .ds-num.add, .ds-code.add { background: var(--open-t1); }
  .ds-num.del, .ds-code.del { background: var(--danger-t1); }
  .ds-empty { background: color-mix(in srgb, var(--text) 4%, transparent); }
  /* Word-level intra-line marks: stronger tint over the row's base add/del tint. */
  mark.dw, mark.aw { color: inherit; border-radius: 2px; padding: 0 1px; }
  mark.dw { background: var(--danger-t3); }
  mark.aw { background: var(--open-t3); }
  /* Collapsed-context divider between hunks (spans all split columns via grid). */
  .diff-expand {
    display: flex; align-items: center; gap: 0.5rem; cursor: pointer;
    grid-column: 1 / -1; font-size: var(--fs-ui); color: var(--link);
    background: var(--link-t1); padding: 0.15rem 0.7rem;
  }
  .diff-expand:hover { background: var(--link-t2); }
  .diff-expand-icon { opacity: 0.8; }
  /* Prism tokens mapped onto the existing palette (subtle; this is a read
     surface). Both themes inherit through the --link/--warn/--text tokens. */
  .token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--muted); font-style: italic; }
  .token.punctuation, .token.operator { color: color-mix(in srgb, var(--text) 75%, var(--bg)); }
  .token.keyword, .token.tag, .token.selector, .token.atrule, .token.important, .token.builtin { color: var(--link); }
  .token.function, .token.class-name, .token.attr-name, .token.property { color: color-mix(in srgb, var(--link) 78%, var(--text)); }
  .token.string, .token.char, .token.attr-value, .token.regex, .token.url { color: var(--warn); }
  .token.number, .token.boolean, .token.constant, .token.symbol { color: color-mix(in srgb, var(--warn) 65%, var(--text)); }
  .token.deleted { color: var(--danger); }
  .token.inserted { color: var(--open); }
  .token.bold { font-weight: 600; }
  .token.italic { font-style: italic; }
  .token.namespace { opacity: 0.7; }
  /* M3: release assets, filters, threads, embedded context, pm groups, contributors. */
  .selectable { user-select: all; }
  .assets { margin-top: 1rem; }
  /* Section-label voice, stated once for every uppercase in-content label; the
     margins stay contextual on each label's own rule (heads take the 0.9/0.5
     rhythm, flex-row labels take none). The sidebar's #nav .nav-section keeps
     its own denser voice. */
  .assets-head, .thread-head, .contrib-head, .version-history-head,
  .review-summary-head, .fb-offscreen-head, .pm-members-head, .pm-subissues-head,
  .pm-rel-label, .board-col-head, .board-groupby-label, .board-lane-head,
  .facet-label, .search-group-head, .config-head, .view-count {
    font-size: var(--fs-ui); color: var(--muted); text-transform: uppercase;
    letter-spacing: 0.05em;
  }
  /* Page total-count line (branches/tags/releases); hidden while a count that
     fills in asynchronously (releases) is not yet known. */
  .view-count { margin: 0.1rem 0 0.7rem; }
  .view-count:empty { display: none; }
  .assets-head, .thread-head, .contrib-head,
  .version-history-head, .review-summary-head, .fb-offscreen-head {
    margin: 0.9rem 0 0.5rem;
  }
  .asset-list { margin-bottom: 0.6rem; }
  .asset-row {
    display: flex; align-items: center; gap: 0.5rem; padding: var(--pad-row);
    font-size: var(--fs-ui); color: var(--text); border-top: 1px solid var(--line); word-break: break-all;
  }
  .asset-list .asset-row:first-child { border-top: none; }
  a.asset-row:hover { background: var(--chip); text-decoration: none; }
  .asset-signed { font-size: var(--fs-ui); margin-top: 0.3rem; }
  .filter-bar { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
  .filter-chip.active { color: var(--text); background: var(--chip); border-color: var(--link); font-weight: 600; }
  /* Load-more continuation control for walk-backed lists (timeline/issues/PRs/
     releases/memos/branch log): a centered pill that walks the next history
     window and appends its resolved items. */
  .load-more-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; margin: 1rem 0 0.4rem; }
  .search-tier-note { max-width: 34rem; margin: 0 auto; text-align: center; line-height: 1.5; color: var(--muted); font-size: var(--fs-ui); }
  /* The list continuation keeps a deliberately larger hit area than the base pill. */
  .load-more { padding: 0.3rem 1.1rem; }
  .load-more:disabled { cursor: default; opacity: 0.6; }
  .thread { margin-top: 1.2rem; }
  .card.comment { background: var(--panel); }
  /* Reply-context ("In reply to") block: the parent chain shown above a reply's
     own post. De-emphasized so the anchor post stays visually primary — smaller
     text, tighter padding, and a slight fade on the context cards. */
  .reply-context { margin-top: 0; margin-bottom: 1rem; font-size: var(--fs-ui); opacity: 0.82; }
  .reply-context .card.comment { padding: 0.5rem 0.7rem; }
  /* Thread hierarchy: a comment-row lays `depth` rail guides beside the card;
     each rail-guide is a vertical connector line (one per reply level; indent is
     capped in JS at THREAD_MAX_DEPTH). align-items:stretch runs the guides the
     card's full height so they read as continuous connectors, like the TUI. */
  .comment-row { display: flex; align-items: stretch; }
  .thread-rail { display: flex; flex: 0 0 auto; }
  .rail-guide { width: 1.25rem; border-left: 2px solid var(--line); }
  .comment-row > .card.comment { flex: 1 1 auto; min-width: 0; }
  /* Detail meta line: author/time on the left, the small Rendered|Raw toggle
     right-aligned on the same line (the blob-head pattern). */
  .detail-meta { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; margin: 0.1rem 0; }
  /* The global .meta indent (1.75rem) exists to clear a card's glyph column;
     no glyph precedes the detail meta line, so the author name aligns flush
     with the content edge here. */
  .detail-meta .meta { flex: 1 1 auto; margin-left: 0; }
  .detail-meta .view-modes { margin: 0; flex: 0 0 auto; }
  .embedded {
    margin-top: 0.5rem; padding: 0.4rem 0.6rem; border-left: 2px solid var(--line);
    background: var(--chip); border-radius: 0 var(--r-ctl) var(--r-ctl) 0;
  }
  .embedded-from { font-style: italic; }
  .embedded-quote { white-space: pre-wrap; word-break: break-word; color: var(--muted); margin: 0.25rem 0; font-size: var(--fs-ui); }
  /* PM detail extras (milestone/sprint members, sub-issues, relations),
     progress bars, the kanban board, and the in-bucket item search. */
  /* Member rows: flex so the leading state chip sits in a fixed-width slot and
     the subjects line up vertically no matter the chip label's width. */
  .pm-member { margin-top: 0.35rem; font-size: var(--fs-md); padding-left: 0.2rem; display: flex; align-items: baseline; gap: 0.5rem; }
  .pm-member > .chip.state { flex: 0 0 auto; min-width: 4.2rem; text-align: center; }
  .pm-members-head, .pm-subissues-head { margin: 0.9rem 0 0.4rem; }
  .pm-members, .pm-subissues { margin-top: 0.6rem; }
  /* Progress bar: a slim track with a link-tinted fill + "n closed of m" label. */
  .pm-progress { display: flex; align-items: center; gap: 0.5rem; margin: 0.3rem 0 0.5rem; }
  .pm-bar { flex: 1 1 auto; height: 0.5rem; max-width: 16rem; background: var(--chip); border-radius: var(--r-pill); overflow: hidden; }
  .pm-bar-fill { height: 100%; background: var(--open); border-radius: var(--r-pill); }
  .pm-progress-label { font-size: var(--fs-ui); color: var(--muted); flex: 0 0 auto; }
  /* Issue detail relationship links (parent / milestone / sprint). */
  .pm-rels { margin: 0.6rem 0; display: flex; flex-direction: column; gap: 0.3rem; }
  .pm-rel { display: inline-flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; font-size: var(--fs-ui); }
  .pm-rel-label { min-width: 4.5rem; }
  .chip.pm-sub-chip { background: var(--link-t2); color: var(--text); }
  /* Card enrichment chips (labels/assignees/priority/retracted) on issue/PR list
     cards: a wrapping row below the meta, mirroring the TUI card stats. */
  .card-chips { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: 0.35rem; }
  .chip.chip-label { background: var(--chip); color: var(--muted); }
  .chip.chip-assignee { background: var(--link-t2); color: var(--text); }
  .chip.chip-priority { color: var(--text); background: var(--chip); }
  .chip.chip-priority.prio-critical { background: var(--danger-t3); }
  .chip.chip-priority.prio-high { background: var(--danger-t2); }
  .chip.chip-priority.prio-medium { background: var(--warn-t2); }
  .chip.chip-priority.prio-low { background: var(--chip); }
  .chip.chip-retracted { background: var(--danger-t2); color: var(--text); }
  .chip.chip-origin { background: var(--link-t2); color: var(--text); }
  .chip.chip-due { background: var(--warn-t2); color: var(--text); }
  .chip.chip-count { background: var(--chip); color: var(--muted); }
  .chip.chip-review { background: var(--chip); color: var(--text); }
  .chip.chip-review .review-ok { color: var(--open); font-weight: 600; }
  .chip.chip-review .review-no { color: var(--danger); font-weight: 600; }
  /* Kanban board: horizontally scrolling columns, each a header + issue cards. */
  .board { display: flex; gap: 0.8rem; overflow-x: auto; padding-bottom: 0.5rem; align-items: flex-start; }
  .board-col { flex: 0 0 15rem; min-width: 15rem; background: var(--chip); border-radius: var(--r-panel); padding: 0.5rem; }
  /* A collapsed column shows only its slim header, so it stops taking a full lane
     of horizontal space while remaining a visible, re-expandable stub. */
  .board-col.board-col-collapsed { flex: 0 0 auto; min-width: 0; }
  .board-col-head { margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.4rem; }
  .board-col-name { flex: 1 1 auto; white-space: nowrap; }
  .board-col-toggle, .board-col-hide { flex: 0 0 auto; line-height: 1; padding: 0 0.1rem; }
  .board-empty { color: var(--muted); text-align: center; padding: 0.6rem 0; font-size: var(--fs-ui); }
  .card.board-card { margin-bottom: 0.5rem; padding: 0.5rem 0.6rem; }
  .card.board-card .subject { font-size: var(--fs-ui); }
  /* Board card title line: glyph + subject as ONE flex child so a long subject
     wraps its text under itself without the glyph splitting onto its own row in a
     narrow column (G8). The glyph hugs the first line; the subject text flows. */
  .board-card-title { display: inline; }
  .board-card-title .type-glyph { margin-right: 0.3em; }
  .board-more {
    display: inline-block; margin-top: 0.15rem; font-size: var(--fs-dense);
    color: var(--link); padding: 0.1rem 0.2rem;
  }
  .board-card-labels { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: 0.3rem; }
  .chip.board-wip-over { background: var(--danger-t2); color: var(--text); }
  /* Board group-by (swimlane) control + lane bands. The control is a labeled
     chip row; each lane band carries a header and its own columns×issues grid. */
  .board-controls { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.7rem; }
  .board-col-restore { border-style: dashed; }
  .board-groupby-indicator { font-size: var(--fs-ui); color: var(--muted); margin: 0.2rem 0 0.5rem; }
  /* Lane index: a compact wrap of clickable lane labels that scroll to a lane, so
     long lanes don't bury the ones after them. */
  .board-lane-index { display: flex; flex-wrap: wrap; gap: 0.3rem; margin: 0 0 0.7rem; }
  .board-lane { margin-bottom: 1rem; scroll-margin-top: 0.5rem; }
  .board-lane-head {
    padding: 0.25rem 0.1rem; margin-bottom: 0.4rem; border-bottom: 1px solid var(--line);
    cursor: pointer; display: flex; align-items: center; gap: 0.4rem;
    position: sticky; top: 0; z-index: 2; background: var(--bg);
  }
  .board-lane-caret { flex: 0 0 auto; font-family: var(--mono); }
  /* In-bucket item search (#/search): input, grouped results, highlighted marks. */
  .search-input { width: 100%; padding: 0.5rem 0.8rem; margin-bottom: 0.5rem; }
  .search-status { margin-bottom: 0.6rem; }
  .search-facets { display: flex; flex-direction: column; gap: 0.35rem; margin: 0.1rem 0 0.9rem; }
  .facet-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem; }
  .search-sort { margin: 0 0 0.7rem; }
  /* Freshness pill: fixed and unobtrusive; a click reloads for the new push. */
  .update-pill { position: fixed; right: 1rem; bottom: 1rem; z-index: 50; font-family: var(--mono); font-size: var(--fs-ui); color: var(--bg); background: var(--link); border: none; border-radius: var(--r-pill); padding: 0.45rem 0.9rem; cursor: pointer; box-shadow: 0 2px 10px color-mix(in srgb, var(--link) 40%, transparent); }
  .facet-label { flex: 0 0 auto; min-width: 4.2rem; }
  .facet-chip { display: inline-flex; align-items: baseline; gap: 0.35rem; color: var(--text); background: var(--chip); padding: 0.1rem 0.55rem; }
  .facet-chip:hover { border-color: var(--link); }
  .facet-chip.selected { background: var(--link-t3); border-color: var(--link); }
  .facet-count { color: var(--muted); font-variant-numeric: tabular-nums; }
  .facet-chip.selected .facet-count { color: var(--text); }
  .facet-more { color: var(--link); padding: 0.1rem 0.3rem; }
  .search-group-head { margin: 0.9rem 0 0.5rem; }
  .search-snippet { margin-top: 0.3rem; white-space: normal; }
  .search-help { text-align: left; padding: 1.5rem 0; }
  .search-help-scope { margin-top: 0.6rem; font-size: var(--fs-ui); color: var(--muted); }
  mark.search-mark { background: var(--warn-t3); color: var(--text); border-radius: 2px; padding: 0 0.1em; }
  .contrib { margin: 0.6rem 0 1rem; }
  .contrib-row {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--pad-row); font-size: var(--fs-ui); border-top: 1px solid var(--line);
  }
  .contrib-list .contrib-row:first-child { border-top: none; }
  .contrib-name { word-break: break-word; }
  .contrib-meta { display: inline-flex; gap: 0.5rem; align-items: center; }
  .contrib-pct { color: var(--muted); font-size: var(--fs-ui); font-family: var(--mono); }
  /* Top-authors heading row: label on the left, a live filter input on the same
     line; the list scrolls once it grows past a few rows and reveals more on
     scroll (infinite autoscroll window). */
  .contrib-head {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap;
  }
  .contrib-filter { padding: 0.2rem 0.5rem; min-width: 8rem; max-width: 14rem; }
  .contrib-scroll { max-height: 22rem; overflow-y: auto; }
  /* Item version history picker (detail pages, N>1 versions). A compact list of
     version rows; selecting one repaints the body pane, and a per-row "diff to
     previous" toggle reveals a unified message-body diff. */
  .version-history { margin: 1rem 0; }
  .version-node { margin-bottom: 0.4rem; }
  .version-row {
    display: flex; align-items: center; gap: 0.5rem; padding: var(--pad-row);
    cursor: pointer; font-size: var(--fs-ui);
  }
  .version-row:hover { background: var(--chip); }
  /* Selection follows the active-state doctrine (#nav): link-tinted fill +
     bolder text, no left-border indicator (which also shifted the row 3px). */
  .version-row.active { background: var(--link-t2); font-weight: 600; }
  .version-row-meta { display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; color: var(--muted); flex: 1 1 auto; }
  .chip.version-label { background: var(--chip); color: var(--text); font-family: var(--mono); }
  .version-diff-ctl { margin-left: auto; }
  .version-diff-pane:not(:empty) { border-top: 1px solid var(--line); }
  .version-diff { overflow: auto; }
  /* PR review summary strip: count line + status chip, per-reviewer chips, and
     verdict feedback bodies. */
  .review-summary { margin-top: 1.2rem; }
  .review-summary-line { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
  .review-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
  .chip.reviewer-chip { color: var(--text); }
  .chip.reviewer-chip.fb-approved { background: var(--open-t2); }
  .chip.reviewer-chip.fb-changes-requested { background: var(--danger-t2); }
  .chip.reviewer-chip.fb-commented { background: var(--chip); }
  /* The signed-tag chip is a positive verdict: the approved reviewer-chip
     treatment (open t2 tint, tinted text), not the bare base chip. */
  .chip.chip-signed { background: var(--open-t2); color: var(--text); }
  /* Inline PR feedback cards on the diff (and offscreen block). */
  .diff-feedback { grid-column: 1 / -1; padding: 0.3rem 0.6rem 0.3rem 2.4rem; background: var(--link-t1); border-top: 1px dashed var(--line); }
  /* The left border here (and on .embedded) is a KIND marker — the feedback's
     verdict, a quote's rail — never selection, which signals by fill + weight
     per the active-state doctrine. */
  .fb-card {
    border: 1px solid var(--line); border-left: 3px solid var(--muted); border-radius: var(--r-ctl);
    background: var(--panel); padding: 0.4rem 0.6rem; font-size: var(--fs-ui); margin: 0.2rem 0;
  }
  .fb-card.fb-approved { border-left-color: var(--open); }
  .fb-card.fb-changes-requested { border-left-color: var(--danger); }
  .fb-head { display: flex; align-items: center; gap: 0.3rem; margin-bottom: 0.2rem; }
  .fb-icon { font-family: var(--mono); font-weight: 700; }
  .fb-author { font-weight: 600; }
  .fb-card.fb-approved .fb-icon { color: var(--open); }
  .fb-card.fb-changes-requested .fb-icon { color: var(--danger); }
  .chip.fb-count { background: var(--link-t2); color: var(--text); }
  .suggestion { margin-top: 0.3rem; }
  .suggestion-head { font-size: var(--fs-ui); color: var(--muted); margin-bottom: 0.2rem; }
  pre.suggestion-code { background: var(--code-bg); border: 1px solid var(--line); border-radius: var(--r-ctl); padding: 0.5rem 0.7rem; overflow: auto; }
  pre.suggestion-code code { font-family: var(--mono); font-size: var(--fs-code); }
  .fb-offscreen { border-top: 1px solid var(--line); padding: 0.4rem 0.7rem; }
  .fb-offscreen-item { display: flex; align-items: flex-start; gap: 0.4rem; margin-top: 0.3rem; }
  .fb-offscreen-item > .meta { flex: 0 0 auto; padding-top: 0.5rem; }
  .fb-offscreen-item > .fb-card { flex: 1 1 auto; min-width: 0; }
  /* Sidebar footer: fixed to the viewport bottom, spanning exactly the sidebar's
     width (--nav-w) at the sidebar's horizontal position. The shell is centered
     (max-width --shell-max, margin auto) with --shell-pad inset, so the sidebar's
     left edge from the viewport is the centering margin plus the pad; max() clamps
     the margin to 0 when the viewport is narrower than the shell. Opaque bg over
     the scrolling nav; the nav reserves matching bottom padding so links clear it.
     Sits above the content (z-index) but the content column never underlaps it.
     The mono uppercase treatment comes from the shared #nav .nav-section rule;
     the 0.5rem here overrides its --fs-dense so the credit stays tiny. */
  .nav-footer {
    position: fixed; bottom: 0; z-index: 6;
    left: calc(max(0px, (100vw - var(--shell-max)) / 2) + var(--shell-pad));
    width: var(--nav-w);
    padding: 0.5rem; background: var(--bg);
    border-top: 1px solid var(--line);
    font-size: 0.5rem;
  }
  /* Full-width layout drops the shell max-width, so the sidebar's left edge is just
     the shell pad from the viewport (no centering margin) — track it so the fixed
     footer stays aligned under the sidebar in wide mode too, not shifted right. */
  body.wide .nav-footer { left: var(--shell-pad); }
  /* A collapsed desktop sidebar hides the nav, so its fixed footer must hide too. */
  body.nav-collapsed .nav-footer { display: none; }
  .foot-brand { display: inline-flex; align-items: center; gap: 0.7rem; color: var(--muted); }
  .foot-brand:hover { text-decoration: none; color: var(--text); }
  .logo-small { width: 18px; height: 18px; color: inherit; display: block; }
  .nav-footer .logo-small { width: 14px; height: 14px; }
  /* Analytics: per-extension item counts (stat grid) and the commits-per-month
     activity bar row (CSS-only, no chart library). */
  .analytics-section { margin: 1.2rem 0; }
  .stat-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
  .stat-cell {
    flex: 1 1 6rem; min-width: 6rem; background: var(--panel); border: 1px solid var(--line);
    border-radius: var(--r-panel); padding: 0.5rem 0.6rem; text-align: center;
  }
  .stat-value { font-size: var(--fs-h3); font-weight: 600; }
  .stat-value-text { font-size: var(--fs-ui); font-weight: 600; padding-top: 0.35rem; }
  .stat-label { font-size: var(--fs-ui); color: var(--muted); margin-top: 0.1rem; }
  .activity-plot { display: flex; align-items: flex-start; gap: 0.35rem; }
  .activity-plot .activity-chart { flex: 1 1 0; min-width: 0; }
  .activity-yaxis {
    flex: 0 0 auto; height: 192px; box-sizing: border-box; min-width: 1.6rem;
    display: flex; flex-direction: column; justify-content: space-between;
    padding: 0.5rem 0 1.4rem; text-align: right; font-size: var(--fs-dense); color: var(--muted);
  }
  .activity-ytick { line-height: 1; }
  .activity-chart {
    display: flex; align-items: flex-end; gap: 3px; height: 192px;
    border: 1px solid var(--line); border-radius: var(--r-panel); background: var(--panel);
    padding: 0.5rem 0.6rem 0.3rem; overflow-x: auto;
  }
  /* Fixed ~12-periods-per-view: columns grow to fill when few, but never shrink
     below 1/12 of the visible width, so a long history stays legible and scrolls
     horizontally (the chart's overflow-x) instead of squishing. 33px = 11 * 3px gap. */
  .activity-col { flex: 1 0 calc((100% - 33px) / 12); min-width: 0; display: flex; flex-direction: column; align-items: center; height: 100%; }
  .activity-bar-wrap { flex: 1 1 auto; width: 100%; display: flex; align-items: flex-end; }
  .activity-bar { width: 100%; min-height: 2px; border-radius: 2px 2px 0 0; background: color-mix(in srgb, var(--link) 55%, transparent); }
  .activity-label { width: 100%; text-align: center; font-size: var(--fs-dense); color: var(--muted); margin-top: 0.2rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Stacked activity chart: a full-height bar per period, split into per-kind
     color segments (bottom-up), with a granularity toggle and color legend. */
  .analytics-chart-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
  .activity-chart.stacked { align-items: flex-end; }
  .activity-stack { width: 100%; min-height: 2px; display: flex; flex-direction: column-reverse; border-radius: 2px 2px 0 0; overflow: hidden; }
  .activity-seg { width: 100%; min-height: 1px; }
  .akind-posts { background: var(--link); }
  .akind-issues { background: var(--open); }
  .akind-prs { background: var(--merged); }
  .akind-releases { background: var(--warn); }
  .akind-memos { background: color-mix(in srgb, var(--link) 45%, var(--warn)); }
  .akind-commits { background: color-mix(in srgb, var(--text) 45%, var(--bg)); }
  .chart-filter { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.5rem 0 0.6rem; }
  .chart-filter .filter-chip { display: inline-flex; align-items: center; gap: 0.35rem; }
  .legend-swatch { width: 0.7rem; height: 0.7rem; border-radius: 2px; display: inline-block; }
  .gran-toggle { display: inline-flex; gap: 0.25rem; }
  .gran-btn { text-transform: capitalize; }
  .gran-btn.active { color: var(--text); background: var(--chip); border-color: var(--link); }
  /* Author names carry an email tooltip on hover (title attr). */
  .author[title] { cursor: help; }
  /* Configuration page: reader-preference control rows + read-only ext configs. */
  .config-section { margin: 1.2rem 0; }
  .config-head { margin-bottom: 0.6rem; }
  .pref-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.8rem;
    padding: var(--pad-row); border: 1px solid var(--line); border-radius: var(--r-panel);
    background: var(--panel); margin-bottom: 0.4rem;
  }
  .pref-label { font-size: var(--fs-ui); }
  .pref-btn { color: var(--text); min-width: 6rem; }
  .config-ext { margin-bottom: 0.6rem; }
  .config-ext-head { font-weight: 600; margin-bottom: 0.3rem; }
  /* Site config section: an accent color swatch and the favicon preview. */
  .config-swatch { display: inline-flex; align-items: center; gap: 0.4rem; }
  .config-swatch-dot { width: 0.85rem; height: 0.85rem; border-radius: 3px; border: 1px solid var(--line); }
  .config-favicon { width: 1.1rem; height: 1.1rem; vertical-align: middle; border-radius: 3px; }
  /* Mobile hamburger + slide-over drawer (base = desktop, hidden). The bar and
     scrim only appear at <=720px; the drawer CSS lives in the media block. */
  .mobile-bar { display: none; }
  .nav-scrim { display: none; }
  /* Inline SVG icon (not a ☰ text glyph, whose ink sits high in the em box and
     defeats box-centering): the icon box is the ink, so flex centering is exact. */
  .nav-hamburger {
    background: transparent; border: none; color: var(--text); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    padding: 0.1rem 0.3rem; flex: 0 0 auto;
  }
  .nav-hamburger svg { display: block; }
  /* Mobile title links home (same route as the desktop repo-title) and centers on
     the bar's cross axis so it sits level with the hamburger despite the smaller
     font — display:flex + align-items:center pins the text box to the row center. */
  .mobile-title {
    display: flex; align-items: center; min-width: 0;
    font-family: var(--mono); font-size: var(--fs-ui); line-height: 1; color: var(--muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mobile-title:hover { color: var(--text); text-decoration: none; }
  @media (max-width: 720px) {
    /* Compact top bar with the hamburger + repo title, sticky at the top. */
    .mobile-bar {
      display: flex; align-items: center; gap: 0.6rem;
      position: sticky; top: 0; z-index: 7;
      background: var(--bg); border-bottom: 1px solid var(--line);
      padding: 0.5rem 0.75rem;
    }
    .shell { grid-template-columns: 1fr; gap: 0.75rem; padding-top: 0.75rem; }
    /* The nav becomes an off-canvas drawer. display:flex !important overrides the
       desktop body.nav-collapsed hide so a persisted desktop-collapse never traps
       a mobile user; visibility is driven by the transform + body.nav-open. */
    .nav {
      position: fixed; top: 0; left: 0; bottom: 0; z-index: 9;
      width: 82%; max-width: 300px; max-height: none; overflow-y: auto;
      background: var(--bg); border-right: 1px solid var(--line);
      padding: 1rem; transform: translateX(-100%);
      transition: transform 0.2s ease; display: flex !important;
    }
    /* In the drawer the footer is a normal block (position:static) so it moves
       with the drawer's transform (a fixed footer would escape the off-canvas
       transform and stay on screen while the drawer is closed). margin-top:auto
       keeps it at the drawer's bottom; the nav's desktop bottom-padding reservation
       is unneeded here. */
    .nav-footer {
      position: static; left: auto; width: auto; margin-top: auto; margin-bottom: -1rem;
      padding: 0.7rem 0 0.5rem; z-index: auto;
    }
    .nav { padding-bottom: 1rem; }
    body.nav-collapsed .nav-footer { display: flex; }
    body.nav-open .nav { transform: translateX(0); }
    body.nav-open .nav-scrim { display: block; position: fixed; inset: 0; z-index: 8; background: color-mix(in srgb, #000 45%, transparent); }
    body.nav-collapsed .shell { grid-template-columns: 1fr; }
    /* The desktop floating handle is never the mobile mechanism. */
    .nav-handle { display: none !important; }
    #nav { flex-direction: column; }
  }

  /* Page-level actions above a list (compare links, "browse files"). */
  .page-actions { display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem; align-items: baseline; margin: 0.2rem 0 0.8rem; }
  .action-link { font-size: var(--fs-ui); color: var(--link); }
  .compare-link { margin-left: 0.5rem; font-size: var(--fs-ui); }

  /* Compare page: base/head pickers, then a PR-style commits + files diff. */
  .compare-pickers { display: flex; flex-wrap: wrap; align-items: end; gap: 0.5rem 0.8rem; margin: 0.4rem 0 1rem; }
  .compare-field { display: flex; flex-direction: column; gap: 0.15rem; }
  .compare-select { padding: 0.25rem 0.4rem; background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: var(--r-ctl); }
  .compare-dots { align-self: center; padding-bottom: 0.3rem; }
  .compare-commits { margin: 1rem 0; }

  /* Commit graph: SVG lane gutter beside a fixed-row-height text column. */
  .graph-scroll { overflow-x: auto; }
  .graph-body { display: flex; align-items: flex-start; gap: 0.6rem; }
  .graph-gutter-wrap { flex: 0 0 auto; }
  .graph-gutter { display: block; }
  /* The text column keeps a readable floor: a branchy window widens the gutter,
     and the row must overflow into .graph-scroll instead of crushing the text. */
  .graph-text-col { flex: 1 1 auto; min-width: 22rem; }
  .graph-line { display: flex; align-items: center; overflow: hidden; border-bottom: 1px solid color-mix(in srgb, var(--line) 40%, transparent); }
  .graph-row-text { display: flex; align-items: baseline; flex-wrap: nowrap; gap: 0.5rem; min-width: 0; width: 100%; }
  .graph-subject { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* One fixed-height row must stay one line: a long branch name (dependabot/…)
     gets a single-line ellipsis cap instead of wrapping over neighboring rows,
     and chips shrink to a readable stub before they crush the subject (the
     full name rides the title tooltip; a crowded row ends in a +N chip). */
  .graph-row-text .chip { flex: 0 1 auto; min-width: 3.5rem; max-width: 14rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Tag chips are exempt from the stub cap: tag names are short version labels
     (and links to their tag page), so they render in full. */
  .graph-row-text .chip.tag-tip { flex: 0 0 auto; min-width: 0; max-width: none; }
  .graph-meta { flex: 0 0 auto; }
  .chip.branch-tip { background: var(--link-t3); color: var(--link); }
  .chip.branch-tip.default { background: var(--link); color: var(--bg); }
  /* Tags get a solid border, no fill: filled chips are reserved for branches.
     Rendered as links to the tag page; no underline, like .chip.merged-branch. */
  .chip.tag-tip { background: transparent; border: 1px solid color-mix(in srgb, var(--open) 60%, transparent); color: var(--open); text-decoration: none; }
  /* A merged-PR head branch is historical (the ref may be deleted): dashed
     outline + dimmed, so it never reads as a live ref. */
  .chip.merged-branch {
    background: transparent; border: 1px dashed color-mix(in srgb, var(--merged) 60%, transparent);
    color: color-mix(in srgb, var(--merged) 85%, var(--text)); text-decoration: none;
  }
