/*
 * Brand tokens, sampled directly from docs/images/logo.png rather than
 * invented: navy #2B3990, cyan #0BC1F9, magenta #F40B75. Held separately from
 * Material's own --md-* chrome variables (theme.palette.primary is now
 * "white"/"black" — see CLAUDE.md §7) so the two never collide.
 *
 * Contrast, not preference, decides which raw token is safe where: navy on
 * a light surface is ~10:1, but ~1.7:1 on a dark one; cyan is the reverse
 * (~8:1 dark, ~2:1 light). Neither works unconditionally, so --on-accent is
 * a scheme-aware alias — navy in the default (light) scheme, cyan in slate
 * (dark) — for anything (badge numerals, hub border, hover states) that
 * needs to read as "brand-colored and legible" regardless of which palette
 * the visitor is using. Magenta is rationed to exactly one job everywhere
 * on the site: the regulatory/status marker. It is used as a border/bar
 * accent (3:1 UI-component contrast, which it clears on both schemes), not
 * as body text color (it falls short of 4.5:1 for normal text on a light
 * background).
 */

:root {
  --on-navy: #2b3990;
  --on-cyan: #0bc1f9;
  --on-magenta: #f40b75;
  --on-accent: var(--on-navy);
}

[data-md-color-scheme="slate"] {
  --on-accent: var(--on-cyan);
}

/*
 * Header logo (2026-07-30). docs/images/logo.png is the full wordmark, not a
 * standalone mark — "OpenNerve" is already drawn into the artwork (see
 * docs/images/CREDITS.md). Material's default header also renders the site
 * name as separate text next to it (from `site_name` in mkdocs.yml), which
 * doubled the word up in the header. Two changes, both scoped to the header
 * only — the identical site-name text inside the mobile nav drawer
 * (`.md-nav__title` in the sidebar) and the browser tab title are untouched:
 *
 * 1. Logo grows from Material's default 1.2rem to 2.4rem — the header row
 *    isn't a fixed height (`.md-header__inner` is a plain flex row), so it
 *    grows to fit the taller image automatically, no extra rule needed.
 * 2. The whole `.md-header__title` block is hidden, not just the site-name
 *    topic. Tried hiding only the first (site-name) topic first, keeping
 *    Material's second, sibling topic that's meant to fade in the current
 *    page's own heading once you scroll past it (a `.md-header__title--active`
 *    class toggled in JS). Verified via a real browser (CDP computed-style
 *    check, not just a screenshot — see CLAUDE.md §9) that on this site's
 *    landing page specifically, that swap is already active at rest, before
 *    any scrolling — a pre-existing quirk, not caused by this change, most
 *    likely from the custom hero markup confusing Material's scroll-position
 *    tracking. Since the landing page's title also happens to equal the site
 *    name ("OpenNerve"), the redundant text would have kept showing there
 *    regardless. Hiding the whole block sidesteps that quirk and is
 *    consistent on every page, at the cost of losing the scroll-linked
 *    page-title affordance everywhere, not just the landing page.
 */
.md-header__button.md-logo img {
  height: 2.4rem;
}

.md-header__title {
  display: none;
}

/*
 * Header layout fix (2026-07-30) — the header was only ever filling about
 * half the available width (reported in Firefox, but confirmed with a
 * headless-Chrome screenshot too, so it's a real cross-browser bug, not a
 * Firefox quirk). Root cause: Material's `.md-header__inner` is a plain flex
 * row (logo, title, palette toggle, search, repo-source link), and exactly
 * ONE item in that row carries `flex-grow` in Material's own compiled CSS —
 * `.md-header__title`. The rule directly above this comment removes that
 * element from the flex layout entirely (`display: none`), so nothing is
 * left to absorb the freed space: every remaining item just sits flush
 * against the logo, and the right half of the header renders empty.
 *
 * Fix: hand that role to the search widget instead of restoring a spacer.
 * `.md-search` grows to fill the freed space (fixing the emptiness) and its
 * inner input is stretched to fill it in turn (making it a genuinely bigger
 * search bar, not just a repositioned one) — one change, two of this
 * round's asks solved together. Scoped to the same `min-width: 60em`
 * breakpoint Material itself uses for the desktop search layout, so mobile
 * (where search is a full-screen overlay opened from an icon button, not an
 * inline bar) is untouched.
 */
@media screen and (min-width: 60em) {
  .md-header__inner .md-search {
    flex-grow: 1;
    max-width: 30rem;
    margin: 0 1.25rem;
  }

  .md-search__inner {
    float: none;
    width: 100%;
  }

  .md-search__form {
    height: 2.3rem;
  }

  .md-search__input {
    font-size: 0.9rem;
  }
}

/*
 * Header external links (2026-07-30). Replaces the header's repo-source
 * link (this site's own GitHub repo — see the `extra.social` entry in
 * mkdocs.yml, which now surfaces that same link in the footer instead) with
 * four icon links relevant to visitors rather than contributors to this
 * docs site specifically: OpenNerve's GitHub org, the community message
 * board, LinkedIn, and email. Built and appended by
 * docs/javascripts/header-icons.js — same template-free approach as
 * nav-menu.js (CLAUDE.md §7), rather than editing Material's header.html
 * partial.
 */
.md-header__source {
  display: none;
}

.on-header-icons {
  display: none;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
  margin-left: 0.5rem;
}

@media screen and (min-width: 60em) {
  .on-header-icons {
    display: flex;
  }
}

.on-header-icons__link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.2rem;
  height: 1.2rem;
  color: currentcolor;
  opacity: 0.85;
}

.on-header-icons__link:hover,
.on-header-icons__link:focus {
  opacity: 1;
}

.on-header-icons__link svg {
  width: 100%;
  height: 100%;
  fill: currentcolor;
}

/*
 * Header tabs (2026-07-30) — bigger, more evenly spaced text, now that the
 * fix above gives the row room to breathe instead of clustering against the
 * logo. Material's defaults are `font-size: .7rem` and `.6rem` of side
 * padding per item.
 */
.md-tabs__link {
  font-size: 0.8rem;
}

.md-tabs__item {
  padding: 0 1rem;
}

/*
 * Maturity badge component (CLAUDE.md §5).
 *
 * Deliberate exception to the "images/assets live alongside their page"
 * convention in CLAUDE.md §8: this stylesheet is shared across every
 * component page, so it lives once under docs/stylesheets/ instead of being
 * duplicated per page. Registered via `extra_css:` in mkdocs.yml — plain CSS,
 * no plugin, no new dependency.
 *
 * Three axes (Documentation / Evidence / Manufacturing) render as separate
 * cards, deliberately never merged into one score. Availability renders
 * underneath as its own strip — CLAUDE.md §5 is explicit that it is a
 * separate field, not a fourth maturity level.
 */

.on-maturity {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem;
  margin: 1.25rem 0;
}

.on-badge {
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.25rem;
  padding: 0.75rem 0.9rem;
  background: var(--md-code-bg-color);
}

.on-badge__axis {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--md-default-fg-color--light);
  margin-bottom: 0.3rem;
}

.on-badge__level {
  display: block;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--on-accent);
  font-family: var(--md-code-font);
}

.on-badge__label {
  display: block;
  font-size: 0.78rem;
  color: var(--md-default-fg-color--light);
  margin-top: 0.15rem;
}

/* Unassigned: level not yet set by maintainers. Muted, no illustrative tint. */
.on-badge--unassigned {
  background: transparent;
  border-style: dashed;
}

.on-badge--unassigned .on-badge__level {
  color: var(--md-default-fg-color--light);
}

/* Illustrative: placeholder values used only for layout review (see the
 * ILLUSTRATIVE ONLY banner that must accompany any use of this modifier).
 * Dashed border + tint so the badge alone still reads as provisional even if
 * screenshotted apart from the banner text. Currently unused — every
 * component page that had placeholder levels now carries real,
 * maintainer-assigned ones (2026-08-28) — kept for the next page-design
 * review that needs it. */
.on-badge--illustrative {
  border-style: dashed;
  border-color: var(--on-accent);
  background: color-mix(in srgb, var(--on-accent) 8%, var(--md-code-bg-color));
}

.on-availability {
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.25rem;
  padding: 0.6rem 0.9rem;
  margin: 0 0 1.25rem;
  font-size: 0.85rem;
}

.on-availability__label {
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  color: var(--md-default-fg-color--light);
  margin-right: 0.5rem;
}

.on-availability--unassigned,
.on-availability--illustrative {
  border-style: dashed;
}

/*
 * Platform block diagram.
 *
 * Used only on the landing page (docs/index.md) since start/architecture.md
 * was removed 2026-08-31 — per the split rule documented in CLAUDE.md §7,
 * this now technically qualifies for home.css rather than extra.css, but it
 * was left here to avoid an unrelated file-move as part of that page's
 * removal; flagged in CLAUDE.md §9.
 *
 * The IPG is the hub; every other block is a link to its component page, so
 * this diagram doubles as navigation. Signal paths are distinguished by line
 * style, not color (solid = wired, dashed = wireless/inductive) — this is
 * how real block diagrams do it, and it's colorblind-safe.
 *
 * Rebuilt (round 3, 2026-07-30) around a body boundary rather than the
 * previous cross layout (software above, sensors below, leads left, charger
 * right) — see CLAUDE.md §7's dated entry for the full reasoning. The
 * previous layout was spatially balanced but arbitrary: nothing about it
 * explained why two of the four links were drawn dashed. This version splits
 * the diagram into "outside the body" / "inside the body", with a dashed
 * `.on-diagram--skin` divider between them — the two dashed (wireless)
 * links, BLE and the inductive charger link, are exactly the two links that
 * cross it, so the line style stops needing a legend to decode. The other
 * two links (stim/record, I2C) are wired and entirely on the implanted side.
 *
 * This rebuild also fixes a real bug in the previous hub markup on
 * docs/index.md: a single-underscore class (`on-diagram_node-media`, so it
 * matched no rule and the photo rendered unstyled), "IPG" printed twice, and
 * an unmatched closing `</span>`. The hub now carries a proper
 * `.on-diagram__node-media` (the same hero-ipg.jpg used elsewhere on this
 * page) so that bug has nowhere to recur.
 *
 * Round 4 (2026-07-30, at the site owner's request after reviewing round 3):
 * every node now carries a photo, not just leads/sensors. Control software
 * and the charger have no real photo in the asset library (per
 * docs/images/CREDITS.md), so they use `docs/images/placeholder.jpg` — a
 * plain generated graphic (diagonal hatch + a generic image glyph), not a
 * stand-in meant to look like a real product photo, so a reader can't
 * mistake it for one. Two knock-on changes this round, both aimed at
 * shrinking how much vertical space the photos take up:
 *   - `.on-diagram__node-media`'s `aspect-ratio` goes from 4/3 to 5/2 —
 *     "longer and thinner," per the request — and every node/group gets a
 *     `max-width` instead of stretching to fill its grid column, so cards
 *     render smaller outright rather than just proportionally shorter.
 *   - The hub no longer stretches to fill its two-row span (round 3's
 *     `align-self: stretch` plus the `.on-diagram--hub > p` height fix are
 *     both gone). A stretched hub reads fine as a text-only pillar, but
 *     looks wrong once it has a photo in it — a tall, distorted crop instead
 *     of a normal card. It's centered within its span instead, the same way
 *     the leads/sensors groups are centered within theirs.
 *
 * Grid structure: 6 columns × 5 rows, named via `grid-template-areas`
 * (keeping the self-documenting single-source-of-truth the previous rebuild
 * introduced, instead of hand-kept `grid-column`/`grid-row` line numbers).
 * `align-items: center` applies to every item, including `--hub` (spans
 * both device rows) — only `--skin` overrides it to `stretch`, since its
 * dashed line needs to run continuously top to bottom to read as a wall.
 *
 * Connector anatomy — three layered pieces per connector, so a single line
 * + label + arrowheads work with only one extra span in the markup:
 *   - `.on-diagram__connector-rule`: the drawn line itself, absolutely
 *     positioned edge-to-edge of the connector's own grid cell (not inset
 *     by the cell's padding — an absolutely positioned child is positioned
 *     against its containing block's *padding* edge, so this always meets
 *     the flanking node/hub box, which itself carries zero outer
 *     margin/padding for the same reason). Solid or dashed via the
 *     `--wireless` modifier.
 *   - The connector's own `::before`/`::after`: arrowheads (CSS border
 *     triangles, `currentColor`-free — they use the same fg-color token as
 *     the rule, so no separate dark-mode rule is needed). Both render by
 *     default (every link here is bidirectional except one); the
 *     `--one-way` modifier suppresses the left (outbound) arrowhead for the
 *     charger's link, which only ever pushes power into the IPG.
 *   - `.on-diagram__connector-label`: an opaque bordered chip sitting on top
 *     of the rule (not a background knockout), so it reads correctly
 *     regardless of what's behind it. The charger's connector reads
 *     "inductive," not a frequency figure — 300 kHz (still cited correctly
 *     elsewhere on this site) is a nominal figure; the site owner noted the
 *     real operating frequency varies with a feedback loop, so a single
 *     number on the diagram overstates precision the link doesn't have.
 *     See CLAUDE.md §9 for the other places "300 kHz" still appears, which
 *     this change deliberately left alone (a sourced prose claim, not part
 *     of the diagram).
 *
 * Markup order is deliberately the mobile reading order (outside-the-body
 * region → software → BLE → charger → inductive → skin → inside-the-body
 * region → IPG [+ development board, nested in the same cell] → stim/record
 * → leads → I2C → sensors), not the desktop visual order — grid placement is
 * by named area regardless of source order, but the mobile fallback below is
 * a plain flex column with no `order` property, so DOM order is what
 * visitors tab through and what a screen reader encounters. One resulting,
 * accepted tradeoff: on desktop, keyboard tab order reaches the charger link
 * before the IPG link, because the hub spans two grid rows and DOM order is
 * optimized for the mobile stack instead.
 */

.on-diagram {
  display: grid;
  grid-template-columns:
    minmax(7rem, 1fr) minmax(3rem, auto) 1.5rem
    minmax(8rem, 1.1fr) minmax(3rem, auto) minmax(7rem, 1fr);
  /* 2026-08-10: a fifth row (`byolc`/`byol`, "Bring your own lead") was added
   * between `stim` and `i2c` rather than appended after `sensors`. The hub
   * spans every device row and centers within that span, so the row it sits
   * beside matters: a short row inserted between the two existing device
   * rows keeps `byolc` roughly centered on the hub the same way `stim` and
   * `i2c` already are (see the align-self comment below), instead of adding
   * a fourth connector that would need its own alignment special-case.
   * `skin` and `hub` both extend their span by one row to match; the
   * trailing empty row is unchanged and still exists only so `skin`'s rule
   * can run past the last content row.
   *
   * 2026-08-11: the development board box was added inside the `hub` cell
   * itself (a second stacked node under the IPG photo, see `.on-diagram--hub`
   * below) rather than as its own grid row. A standalone row was tried first
   * and rejected: since a CSS grid row's height is shared by every column in
   * it, the development-board row only started once the *tallest* column
   * finished (the leads/sensors photo columns spanning rows 2-4), leaving it
   * visually stranded well below the IPG box instead of directly beneath it.
   * Nesting it in the hub cell ties its position to the IPG photo's own
   * height regardless of what the flanking columns are doing. No new grid
   * row or area was needed for it. */
  grid-template-areas:
    "lab-out lab-out .    lab-in lab-in  lab-in "
    "sw      ble      skin hub    stim    leads  "
    ".       .        skin hub    byolc   byol   "
    "chg     pwr      skin hub    i2c     sensors"
    ".       .        skin .      .       .      ";
  align-items: center;
  gap: 0.6rem 0;
  margin: 1.75rem 0;
}

.on-diagram--skin {
  align-self: stretch;
}

.on-diagram--lab-out { grid-area: lab-out; }
.on-diagram--lab-in { grid-area: lab-in; }
.on-diagram--sw { grid-area: sw; }
.on-diagram--chg { grid-area: chg; }
.on-diagram--ble { grid-area: ble; }
.on-diagram--pwr { grid-area: pwr; }
.on-diagram--skin { grid-area: skin; }
.on-diagram--hub { grid-area: hub; }
.on-diagram--stim { grid-area: stim; }
.on-diagram--byolc { grid-area: byolc; }
.on-diagram--byol { grid-area: byol; }
.on-diagram--i2c { grid-area: i2c; }
.on-diagram--leads { grid-area: leads; }
.on-diagram--sensors { grid-area: sensors; }

/* The hub spans rows 2 and 3 and centers within that combined span (its
 * center therefore sits exactly on the row 2/3 seam) — but every other item
 * in row 2 centers within row 2's OWN height, and every item in row 3
 * centers within row 3's own height, so their natural center is offset from
 * the hub's by roughly half a row. Left alone, that gap grows with however
 * much taller the leads/sensors photos make each row than the software/
 * charger content sharing it, and the connectors end up pointing at empty
 * space above/below the hub instead of at it. Pulling row 2's items to the
 * bottom of their row and row 3's items to the top of theirs clusters both
 * rows' content toward that same seam — hub included — so all four
 * connectors converge on roughly the hub's actual center, the way lines in
 * an ordinary hub-and-spoke diagram do. */
.on-diagram--sw,
.on-diagram--ble,
.on-diagram--stim,
.on-diagram--leads {
  align-self: end;
}

.on-diagram--chg,
.on-diagram--pwr,
.on-diagram--i2c,
.on-diagram--sensors {
  align-self: start;
}

.on-diagram__region {
  align-self: end;
  padding-bottom: 0.35rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--md-default-fg-color--light);
  text-align: center;
}

.on-diagram__group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

/* margin-inline is a safety margin, not decoration: on a narrower content
 * column, the leads/sensors columns can shrink close to their own content
 * width, leaving little to no centering slack — without this, the label can
 * end up sitting close enough to the neighboring connector's arrowhead that
 * they visually touch. Originally motivated by start/architecture.md's
 * sidebar+table-of-contents layout (removed 2026-08-31); kept as a cheap
 * defensive default even though the diagram's only remaining page
 * (docs/index.md) has no sidebar eating into its width. */
.on-diagram__group-label {
  margin-inline: 0.4rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--md-default-fg-color--light);
  margin-bottom: 0.1rem;
}

.on-diagram__node {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  width: 100%;
  max-width: 9rem;
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.35rem;
  background: var(--md-code-bg-color);
  font-size: 0.85rem;
  text-decoration: none !important;
  color: var(--md-typeset-color) !important;
}

.on-diagram__node:hover {
  border-color: var(--on-accent);
}

/* 5/2 (was 4/3) and a max-width on the card itself (above) are both aimed at
 * the same problem: the leads/sensors photos were driving rows 2/3 much
 * taller than the rest of the diagram needed, since every node/group in a
 * row shares that row's height. A longer, thinner, smaller-overall photo
 * shrinks that inflation directly, rather than working around it. */
.on-diagram__node-media {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 2;
  object-fit: cover;
}

/* The hub's photo (hero-ipg.jpg, 992×1000 — see docs/images/CREDITS.md) is
 * the one exception to the shared 5/2 ratio above: it's a full product
 * shot, not a cropped detail like the leads/sensors photos, so cropping it
 * to a wide banner would cut off real content. Same width as every other
 * card, but its own aspect ratio, tall enough to show the whole photo with
 * no cropping at all (cover vs. contain is moot once the ratio matches
 * exactly). */
.on-diagram__node--hub .on-diagram__node-media {
  aspect-ratio: 992 / 1000;
}

/* The dev board photo (dev-board.jpg, 1553×1255 — see docs/images/CREDITS.md)
 * gets the same own-ratio treatment as the hub's, for the same reason: it's
 * a full product shot, not a cropped detail, so the shared 5/2 banner crop
 * would slice off the top and bottom of the board. */
.on-diagram__node--dev .on-diagram__node-media {
  aspect-ratio: 1553 / 1255;
}

.on-diagram__node-body {
  display: block;
  padding: 0.55rem 0.75rem;
}

.on-diagram__node--hub {
  text-align: center;
  font-family: var(--md-code-font);
  font-weight: 700;
  font-size: 1rem;
  border-width: 2px;
  border-color: var(--on-accent);
}

.on-diagram__node--hub .on-diagram__node-body {
  padding: 1.1rem 0.75rem;
}

.on-diagram__node-tag {
  display: block;
  font-family: var(--md-code-font);
  font-size: 0.7rem;
  color: var(--md-default-fg-color--light);
  margin-top: 0.15rem;
}

/* .on-diagram--chg and .on-diagram--byol wrap a single node each (unlike the
 * group areas above, which hold two) — the wrapper itself must be the grid
 * item, not the <a> inside it: markdown wraps a bare link that isn't already
 * inside a block-level <div> in its own <p>, and `grid-area` only has any
 * effect on a direct child of the grid container. `justify-content: center`
 * is what actually centers the card: the grid's own `justify-items:
 * stretch` default makes this wrapper fill its full column width, so
 * without it the single child would hug the left edge instead of centering
 * — the same job `.on-diagram__group`'s `align-items: center` does for the
 * two-node groups. Zero margin/padding on both, deliberately — see the
 * connector-rule comment above for why that matters. */
.on-diagram--chg,
.on-diagram--byol {
  display: flex;
  justify-content: center;
}

/* .on-diagram--hub holds two stacked nodes (IPG, then the development
 * board directly beneath it, added 2026-08-11) rather than the single node
 * every other wrapper above holds — so it needs `flex-direction: column`
 * plus `align-items: center` (which centers each child horizontally, the
 * cross axis once the main axis is vertical) instead of the single-child
 * `justify-content: center` pattern above. `gap` separates the two cards.
 * The development board deliberately sits inside the hub's own cell instead
 * of getting its own grid row: a standalone row's height is shared across
 * every column, so it only started once the tallest column (leads/sensors)
 * finished, landing well below the IPG box instead of directly under it —
 * see the grid-template-areas comment above `.on-diagram`. */
.on-diagram--hub {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
}

/* The skin boundary: a dashed vertical rule (`::before`, flex-grow so it
 * fills whatever height `align-self: stretch` gives this column) with a
 * small caption below it, mirroring how a connector's label sits on its
 * line. Deliberately not a connector itself — it carries no arrows or
 * direction, just marks where the two crossing links (BLE, inductive
 * charging) leave the body. */
.on-diagram--skin {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.on-diagram--skin::before {
  content: "";
  flex: 1;
  width: 0;
  border-left: 2px dashed var(--md-default-fg-color--lighter);
}

.on-diagram__skin-label {
  margin-top: 0.3rem;
  font-family: var(--md-code-font);
  font-size: 0.65rem;
  letter-spacing: 0.03em;
  color: var(--md-default-fg-color--light);
}

.on-diagram__connector {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0 0.35rem;
}

.on-diagram__connector-rule {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 0;
  border-top: 2px solid var(--md-default-fg-color--light);
  transform: translateY(-50%);
}

.on-diagram__connector--wireless .on-diagram__connector-rule {
  border-top-style: dashed;
}

/* Arrowheads: plain CSS border triangles on the connector's own
 * pseudo-elements, so no extra markup is needed for them (only the rule
 * above needs a real element, since a pseudo-element is already spent on
 * each arrowhead). Both render by default — every link here is
 * bidirectional except the charger's, which `--one-way` suppresses the
 * left (outbound) arrowhead for. */
.on-diagram__connector::before,
.on-diagram__connector::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  transform: translateY(-50%);
}

.on-diagram__connector::before {
  left: 0;
  border-right: 7px solid var(--md-default-fg-color--light);
}

.on-diagram__connector::after {
  right: 0;
  border-left: 7px solid var(--md-default-fg-color--light);
}

.on-diagram__connector--one-way::before {
  content: none;
}

/* Opaque chip, not a background knockout — reads correctly regardless of
 * what's behind it (the rule, or page background at the diagram's edges). */
.on-diagram__connector-label {
  position: relative;
  z-index: 1;
  display: inline-block;
  white-space: nowrap;
  background: var(--md-code-bg-color);
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.2rem;
  padding: 0.15rem 0.45rem;
  font-family: var(--md-code-font);
  font-size: 0.65rem;
  letter-spacing: 0.03em;
  color: var(--md-default-fg-color--light);
}

/*
 * In-page PDF viewer.
 *
 * Used on any component page that links to an upstream PDF (Parylene Cuff,
 * IPG Gen1/Gen2 test protocols, etc.) — shared here rather than in home.css
 * per the same split rule as the block diagram above.
 *
 * Markup contract, per instance:
 *
 *   <div class="on-pdf" markdown>
 *   <div class="on-pdf__embed"
 *        data-pdf-src="https://raw.githubusercontent.com/ORG/REPO/main/File.pdf"
 *        data-pdf-title="File title"></div>
 *   <a class="on-pdf__fallback"
 *      href="https://raw.githubusercontent.com/ORG/REPO/main/File.pdf">
 *     Open "File title" directly (PDF) &#8599;
 *   </a>
 *   </div>
 *
 * docs/javascripts/pdf-embed.js finds .on-pdf__embed[data-pdf-src] at page
 * load and injects an <iframe> pointing at the vendored pdf.js viewer
 * (docs/javascripts/pdfjs/), passing data-pdf-src as the file to fetch and
 * render. The .on-pdf__fallback link is authored directly in markdown, not
 * generated by JS, so it still works with JavaScript disabled or if the
 * script fails to load. See CLAUDE.md §7 "PDF viewer" for why this needs a
 * vendored viewer rather than a plain iframe onto the upstream URL directly
 * (raw.githubusercontent.com sends X-Frame-Options: deny), and for the
 * live-fetch-over-commit-pinning tradeoff this implies.
 *
 * For a dense list of PDFs (e.g. the IPG Gen1/Gen2 test-protocol documents),
 * wrap each .on-pdf in a plain <details>/<summary> instead of showing it
 * expanded by default, so the list stays scannable:
 *
 *   <details class="on-pdf-details">
 *   <summary>Accelerated aging DVT protocol</summary>
 *   <div class="on-pdf" markdown>
 *   ...
 *   </div>
 *   </details>
 *
 * pdf-embed.js only creates the iframe (and triggers the PDF fetch) the
 * first time a <details> is opened, not on page load — with ~15 of these
 * on one page, eagerly embedding all of them would mean fetching every PDF
 * on every page view.
 */

.on-pdf {
  margin: 1.5rem 0;
}

.on-pdf-details {
  border-bottom: 1px solid var(--md-default-fg-color--lightest);
  padding: 0.5rem 0;
}

.on-pdf-details summary {
  cursor: pointer;
  font-size: 0.9rem;
  padding: 0.15rem 0;
}

.on-pdf-details summary:hover {
  color: var(--on-accent);
}

.on-pdf-details[open] summary {
  margin-bottom: 0.5rem;
}

.on-pdf-details .on-pdf {
  margin: 0;
}

.on-pdf__frame {
  display: block;
  width: 100%;
  height: 70vh;
  max-height: 50rem;
  min-height: 24rem;
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.25rem;
}

.on-pdf__fallback {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--md-default-fg-color--light);
}

/*
 * .on-video — a plain, aspect-ratio-locked YouTube embed. Unlike .on-pdf,
 * this needs no wrapper script: YouTube's own embed iframe is designed to be
 * framed cross-origin (no X-Frame-Options block, unlike raw.githubusercontent.com
 * PDFs), so a plain <iframe src="https://www.youtube.com/embed/VIDEO_ID">
 * works directly. `aspect-ratio: 16 / 9` plus `width: 100%` keeps it
 * responsive without a padding-hack wrapper. Introduced 2026-08-10 for
 * docs/data/educational-resources.md.
 *
 * Markup:
 *   <div class="on-video" markdown>
 *   <iframe class="on-video__frame" src="https://www.youtube.com/embed/VIDEO_ID"
 *     title="..." loading="lazy" allowfullscreen></iframe>
 *   </div>
 */
.on-video {
  margin: 1.5rem 0;
}

.on-video__frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.25rem;
}

@media (max-width: 600px) {
  /* No drawn grid connectors at this width — a ruled line at 5-6rem of
   * available width reads as noise, not signal. The diagram becomes a plain
   * top-to-bottom flex stack in DOM order (outside-the-body label, software,
   * BLE, charger, inductive, skin, inside-the-body label, IPG hub [with the
   * development board stacked directly beneath it], stim/record, leads,
   * I2C, sensors) — already the correct reading order, per the markup-order
   * comment above `.on-diagram`. No `order` property is used here, so
   * visual order and tab/reading order always match. */
  .on-diagram {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
  }

  /* Each group's two nodes sit side by side rather than stacked, so related
   * items (the two lead types, the two sensor types) read as a pair. */
  .on-diagram__group {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
  }

  .on-diagram__group-label {
    grid-column: 1 / -1;
  }

  /* `align-self: end`/`start` (base rules, on .on-diagram__region and on the
   * seam-clustering group above it) mean "bottom/top of the grid row" on
   * desktop — but `align-self` means something else entirely once the
   * container is a flex column instead of a grid (cross-axis, i.e.
   * horizontal, alignment), which would otherwise shrink these to their own
   * content width and shove them against an edge instead of spanning full
   * width. Reset all of it back to filling the width, and drop the
   * row-bottom padding .on-diagram__region doesn't need in a flex stack. */
  .on-diagram__region,
  .on-diagram--sw,
  .on-diagram--ble,
  .on-diagram--stim,
  .on-diagram--leads,
  .on-diagram--chg,
  .on-diagram--pwr,
  .on-diagram--i2c,
  .on-diagram--sensors,
  .on-diagram--byolc,
  .on-diagram--byol {
    align-self: stretch;
  }

  .on-diagram__region {
    padding-bottom: 0;
  }

  /* The connector keeps its label chip as a plain caption between blocks,
   * but drops the rule and arrowheads — there's no room for a meaningful
   * line at this width. */
  .on-diagram__connector {
    min-height: 0;
    padding: 0.3rem 0;
  }

  .on-diagram__connector-rule,
  .on-diagram__connector::before,
  .on-diagram__connector::after {
    content: none;
    display: none;
  }

  /* The skin boundary rotates from a vertical rule with a caption below it
   * to a full-width horizontal rule with the caption beside it. */
  .on-diagram--skin {
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    padding: 0.2rem 0;
  }

  .on-diagram--skin::before {
    width: auto;
    height: 0;
    border-left: none;
    border-top: 2px dashed var(--md-default-fg-color--lighter);
  }

  .on-diagram__skin-label {
    margin-top: 0;
  }
}

/*
 * Header dropdown menus (CLAUDE.md §7 "Header dropdown menus").
 *
 * Enables theme.features: navigation.tabs in mkdocs.yml, which renders each
 * top-level nav: section as a button in the header. docs/javascripts/nav-menu.js
 * adds a dropdown of that section's immediate sub-pages beneath each button,
 * built from the sidebar nav tree that Material already renders into every
 * page — see that script's doc comment for why, and why this is CSS/JS
 * rather than a Jinja template override (custom_dir).
 *
 * Used on every page (the header is site-wide), so this lives in extra.css
 * rather than home.css per the split rule at the top of this file.
 *
 * `.on-navmenu` is appended to <body> and positioned `fixed` by the script,
 * not nested under the tab item — Material sets `overflow: auto` on
 * `.md-tabs`/`.md-tabs__list` (so the tab row can scroll horizontally), which
 * clips any descendant that visually extends past it regardless of that
 * descendant's own `position`. Visibility is toggled via the
 * `on-navmenu--visible` class from JS (mouse/focus/touch), not CSS
 * `:hover`/`:focus-within`, since the menu is no longer a DOM descendant of
 * the tab item.
 */

.md-tabs__item--has-menu > .md-tabs__link::after {
  content: "";
  display: inline-block;
  width: 0.4em;
  height: 0.4em;
  margin: 0 0 0.1em 0.4em;
  border-right: 1px solid currentcolor;
  border-bottom: 1px solid currentcolor;
  transform: rotate(45deg);
  opacity: 0.7;
}

.on-navmenu {
  display: none;
  position: fixed;
  min-width: 12rem;
  z-index: 100;
  background: var(--md-default-bg-color);
  border: 1px solid var(--md-default-fg-color--lightest);
  border-radius: 0.25rem;
  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.15);
  padding: 0.35rem 0;
}

.on-navmenu.on-navmenu--visible {
  display: block;
}

.on-navmenu__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.on-navmenu__link {
  display: block;
  padding: 0.5rem 1rem;
  font-size: 0.7rem;
  color: var(--md-default-fg-color);
  text-decoration: none !important;
  white-space: nowrap;
}

.on-navmenu__link:hover,
.on-navmenu__link:focus {
  background: var(--md-code-bg-color);
  color: var(--on-accent) !important;
}

/*
 * The landing page (and any page that sets `hide: navigation`) suppresses
 * the left sidebar for a full-width layout. On desktop that sidebar is
 * redundant with the new header tabs above, but below the breakpoint where
 * Material collapses the header tabs back into the sidebar's off-canvas
 * drawer (the same 76.234375em breakpoint Material itself uses), suppressing
 * it would leave mobile visitors on that page with no navigation at all.
 * Restoring it only below that width keeps the desktop hero full-width while
 * giving mobile the drawer back.
 */
@media screen and (max-width: 76.234375em) {
  .md-sidebar--primary[hidden] {
    display: block;
  }
}

/*
 * Left-sidebar section headers (e.g. "Implantable Pulse Generator", the
 * category above "Gen2 PCBA"/"Gen1 PCBA") vs. the plain page links under
 * them. Material already bolds these via its own
 * `.md-nav__item--section > .md-nav__link` rule, but both header and page
 * links otherwise share the same muted --md-default-fg-color--light grey,
 * so at the sidebar's small (0.7rem) font size the weight difference alone
 * doesn't read as "this is a category" at a glance. Recolored to --on-accent
 * (the same scheme-aware brand navy/cyan used for the diagram hub border
 * and badge numerals) instead, so headers are unambiguously distinct from
 * the grey child links regardless of theme.
 *
 * Two DOM shapes need covering: a section with no index page renders as
 * `<label class="md-nav__link" for="...">` (matched by the `[for]` variant
 * below — needed to out-specificity Material's own `[for]` rule, which is
 * otherwise more specific than a plain `.md-nav__link` selector and would
 * win); a section that IS also a page (e.g. Implantable Pulse Generator,
 * via navigation.indexes) renders as
 * `<div class="md-nav__link md-nav__container">` wrapping the real `<a>` —
 * matched by the first, plain selector, with the accent color inherited
 * down into the anchor since the anchor has no explicit resting-state color
 * of its own.
 *
 * A third case, found after the first pass looked inconsistent across tabs
 * (Hardware/Community read blue, Software/Resources/Start here
 * stayed grey): Material has a *third*, higher-specificity rule reserved
 * for the top-level tab labels specifically —
 * `.md-nav--lifted>.md-nav__list>.md-nav__item>[for]` — which beats the
 * `[for]` selector above (one more class in the chain) and wins back to
 * grey for any tab with no `index.md` child of its own. Hardware and
 * Community only looked fixed by coincidence: `components/index.md` and
 * `community/index.md` are literally named `index.md`, so those two tabs
 * take the `.md-nav__container` branch instead and never hit this rule.
 * Matching Material's own selector (rather than trying to out-specify it
 * with `!important`) fixes every tab uniformly regardless of whether it
 * happens to have an index page.
 */
.md-nav__item--section > .md-nav__link,
.md-nav__item--section > .md-nav__link[for],
.md-nav--lifted > .md-nav__list > .md-nav__item > [for] {
  color: var(--on-accent);
  font-weight: 700;
}

/*
 * Development board, Standard Leads, and Sub-mm Parylene Cuff, under
 * Hardware: major components that are currently single flat pages (no
 * synced sub-pages yet, unlike Charger/Mechanical Sensors/Chemical Sensors,
 * which already have children and so already pick up the section-header
 * treatment above for free). Given the same bold/accent treatment as the
 * section headers so they read as peers of those, ahead of getting their
 * own sub-pages later.
 *
 * A flat leaf page has no nav_item.children, so Material's template never
 * gives it a distinguishing class to hook (see nav-item.html) — there's no
 * ".md-nav__item--section" here to reuse. Scoped instead by each page's own
 * URL, via an ends-with attribute selector on the rendered href, which
 * stays stable across every depth these links get rendered at (confirmed:
 * "dev-board/", "standard-leads/", "parylene-cuff/" against both root-level
 * and nested pages in the built site) without depending on relative-path
 * prefixes. Deliberately by exact page, not by "every childless Components
 * link" — Overview & maturity table is also childless and should stay a
 * plain link, and this must not spread to same-shaped pages under other
 * tabs (e.g. About's own leaf pages), which this selector already can't
 * reach since none of them share these URLs.
 *
 * Bring your own lead joined this list 2026-08-11, same treatment, same
 * reasoning (flat leaf page under Hardware, no children of its own yet).
 */
.md-nav__link[href$="dev-board/"],
.md-nav__link[href$="standard-leads/"],
.md-nav__link[href$="parylene-cuff/"],
.md-nav__link[href$="byo-lead/"] {
  color: var(--on-accent);
  font-weight: 700;
}
