/* learn.45b.io/pages - the knowledgebase's own stylesheet.
 *
 * The 45B design system, as docsify-themeable variables. The tokens are the
 * authority in notes/45b-design.md - declared here as the same six values the
 * PHP side declares in public/assets/app.css. Two copies of six hex values is
 * the cost of the two stacks being genuinely separate; changing one means
 * changing the other, and there is no third place.
 *
 * It also replaces docsify-plugin-toc's own light.css, which positions the
 * heading navigator with fixed offsets that overlapped the content by 22px at
 * 1600px. Measured with portal/bin/measure-width.py.
 */

@font-face {
  font-family: "Handel Gothic D";
  src: url("/assets/Handel%20Gothic%20D%20Bold.otf") format("opentype");
  font-weight: 700;
  font-display: swap;
}

:root {
  --45b-bg: #121314;
  --45b-surface: #2a2a2a;
  --45b-accent: #4455BB;
  --45b-alt-accent: #aaddaa;
  --45b-alert: #ec6476;
  --45b-text: #dddddd;
  --45b-text-rgb: 221 221 221;

  /* The masthead's height, in one place: the sidebar, the toggle and the
     content all offset by it. It was three hand-tuned margin-top values, and
     the toggle landed on top of the search box. --kbbar-h is the nav bar
     alone; --kbtop-h adds the experimental-notice banner fixed below it
     (2026-09-21) and is what everything downstream now offsets by, so the
     banner's height still lives in exactly one place. */
  --kbbar-h: 56px;
  --kbbanner-h: 34px;
  --kbtop-h: calc(var(--kbbar-h) + var(--kbbanner-h));

  --theme-color: var(--45b-accent);
  --base-background-color: var(--45b-bg);
  --base-color: var(--45b-text);
  --base-font-family: Chivo, system-ui, -apple-system, "Segoe UI", sans-serif;
  --base-font-size: 18px;
  --base-line-height: 1.65;

  --heading-font-family: "Handel Gothic D", Chivo, system-ui, sans-serif;
  --heading-font-weight: 700;
  --heading-color: var(--45b-text);
  --heading-h1-font-size: 2.1rem;
  --heading-h2-font-size: 1.45rem;
  --heading-h3-font-size: 1.12rem;
  /* Simple Dark rules every h2. With an outline of four headings that reads as
     a stack of dividers rather than as a page. */
  --heading-h2-border-style: none;
  --heading-h2-margin: 2.4rem 0 1rem;
  --heading-h3-margin: 1.8rem 0 .7rem;

  --sidebar-background: var(--45b-bg);
  --sidebar-border-color: rgb(var(--45b-text-rgb) / .12);
  --sidebar-width: 17rem;
  --sidebar-nav-link-color--active: var(--45b-accent);
  --sidebar-nav-link-border-color--active: var(--45b-accent);
  --sidebar-nav-pagelink-transform: none;

  /* learn.45b.io is a laptop-wide portal, not a narrow column
     (notes/45b-design.md). Simple Dark's default centres a ~45em measure inside
     the viewport, which left a 306px dead gutter on the left at 1600px. */
  --content-max-width: 62em;

  --link-color: var(--45b-alt-accent);
  --link-color--hover: var(--45b-accent);
  --code-theme-background: var(--45b-surface);
  --code-inline-background: var(--45b-surface);
  --blockquote-border-color: var(--45b-accent);
  --blockquote-background: color-mix(in srgb, var(--45b-accent) 10%, transparent);
  --blockquote-font-style: normal;
  --table-cell-border-color: rgb(var(--45b-text-rgb) / .12);
  --button-border-radius: 30px;
}

html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
body { background: var(--45b-bg); margin: 0; }

/* Docsify core sets `main { width: 100vw }`, and 100vw INCLUDES the scrollbar.
   On every viewport wide enough to show the sidebar that is 15px of horizontal
   scroll on every page - the one thing notes/45b-org.md says must never happen.
   Caught by bin/measure-width.py at 768, 991, 1180 and 1440; invisible at 320
   because the mobile layout has no vertical overflow to reserve for. */
main { width: 100%; }

/* ---------------------------------------------------------------- masthead */

.kbbar {
  position: fixed; inset: 0 0 auto 0; height: var(--kbbar-h); z-index: 40;
  display: flex; align-items: center; gap: 1.1rem;
  padding: 0 1rem;
  background: var(--45b-bg);
  border-bottom: 1px solid rgb(var(--45b-text-rgb) / .12);
  font-family: Chivo, system-ui, sans-serif; font-size: .88rem;
}
.kbbrand {
  font-family: "Handel Gothic D", Chivo, sans-serif; font-weight: 700;
  color: var(--45b-text); text-decoration: none; white-space: nowrap;
}
.kbbrand span { color: rgb(var(--45b-text-rgb) / .55); }
.kbnav { display: flex; gap: 1.1rem; margin-right: auto; min-width: 0; }
.kbnav a { color: rgb(var(--45b-text-rgb) / .8); text-decoration: none; white-space: nowrap; }
.kbnav a:hover { color: var(--45b-accent); }

.kblang { position: relative; flex: none; }
.kblang > summary {
  list-style: none; cursor: pointer; white-space: nowrap;
  padding: .3em .9em; border-radius: var(--button-border-radius);
  border: 1px solid rgb(var(--45b-text-rgb) / .3);
}
.kblang > summary::-webkit-details-marker { display: none; }
.kblang[open] > summary { border-color: var(--45b-accent); }
.kblang ul {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 50;
  margin: 0; padding: 6px; list-style: none; min-width: 11rem;
  background: var(--45b-surface); border-radius: 10px;
  border: 1px solid rgb(var(--45b-text-rgb) / .15);
  box-shadow: 0 8px 24px rgb(0 0 0 / .45);
  max-height: 70vh; overflow-y: auto;
}
.kblang li a {
  display: block; padding: .38em .7em; border-radius: 6px;
  color: var(--45b-text); text-decoration: none;
}
.kblang li a:hover { background: color-mix(in srgb, var(--45b-accent) 55%, transparent); }
.kblang li a[aria-current="true"] { color: var(--45b-alt-accent); font-weight: 700; }

/* The experimental-status notice, fixed directly under .kbbar - outside #app so
   Docsify's per-route re-render never touches it. Same wording and token as
   the portal's own .experimental-banner (public/assets/app.css); this stack
   keeps its own copy for the same reason the six design-system colours are
   duplicated above. */
.kbbanner {
  position: fixed; top: var(--kbbar-h); left: 0; right: 0; z-index: 39;
  height: var(--kbbanner-h);
  display: flex; align-items: center; justify-content: center;
  gap: .3em; flex-wrap: wrap; text-align: center;
  padding-inline: 1rem;
  background: var(--45b-alt-accent); color: var(--45b-bg);
  font-family: Chivo, system-ui, sans-serif; font-size: .78rem; line-height: 1.3;
}
.kbbanner a { color: var(--45b-bg); font-weight: 700; text-decoration: underline; }

/* Docsify positions the sidebar, its toggle and the content from the top of the
   viewport. All three offset by the bar (+ banner) in ONE place, from one
   variable. The toggle used to land on the search box because each carried
   its own guess. */
/* THE CONTENT IS IN NORMAL FLOW, so `top` does nothing to it.
   Two wrong turns before measuring settled it: docsify core positions .content
   absolutely, but docsify-themeable lays it out statically - computed
   `position: static`, and a `top` of 56px silently ignored, which is why the
   page's own <h1> sat UNDER the fixed bar at y=36 with the bar covering 0-56.
   Padding on <main> is independent of which of the two is in charge. */
.sidebar             { top: var(--kbtop-h); padding-top: .8rem; }
.sidebar-toggle      { top: calc(var(--kbtop-h) + 8px); bottom: auto; background: transparent; }
body.close .sidebar-toggle { top: calc(var(--kbtop-h) + 8px); }
main                 { padding-top: var(--kbtop-h); }

/* Wide enough for the heading navigator: RESERVE the column rather than
   floating the navigator over the prose. The first attempt put it in the margin
   a centred measure was expected to leave, and at 1600px there was no such
   margin - it sat 171px on top of the text. The content is narrowed instead, so
   the two cannot collide at any width by construction. */
@media (min-width: 1400px) {
  main { padding-right: 15rem; }
}

@media (max-width: 768px) {
  :root { --kbbar-h: 96px; --kbbanner-h: 48px; } /* bar wraps to two rows; banner to two lines */
  .kbbar { flex-wrap: wrap; align-content: center; padding-block: .4rem; gap: .5rem 1rem; }
  .kbnav { order: 3; width: 100%; overflow-x: auto; gap: .9rem; }
  /* The sidebar toggle is fixed at the left edge on mobile; keep the first nav
     item clear of it. */
  .kbnav { padding-left: 2.2rem; }
  .kbbanner { font-size: .72rem; padding-block: .3rem; }
}

/* ------------------------------------------------------- heading navigator */

/* Replaces docsify-plugin-toc/dist/light.css. Laid out in the right-hand
   margin the CONTENT leaves, rather than at a fixed offset from the viewport -
   which is what made it sit 22px on top of the text at 1600px. */
.page_toc {
  position: fixed; z-index: 10;
  top: calc(var(--kbtop-h) + 2.4rem);
  right: 1rem; width: 13rem;
  max-height: calc(100vh - var(--kbtop-h) - 5rem);
  overflow-y: auto; padding-left: .9rem;
  border-left: 1px solid rgb(var(--45b-text-rgb) / .12);
  font-size: .8rem; line-height: 1.5;
}
.page_toc p { margin: 0 0 .4em; color: rgb(var(--45b-text-rgb) / .45);
  text-transform: uppercase; letter-spacing: .06em; font-size: .68rem; }
.page_toc a { display: block; padding: .18em 0; color: rgb(var(--45b-text-rgb) / .62);
  text-decoration: none; overflow-wrap: anywhere; }
.page_toc a:hover, .page_toc .active { color: var(--45b-accent); }
/* Below this width there is no margin to put it in, and a floating panel over
   the text is worse than no panel. */
@media (max-width: 1400px) { .page_toc { display: none; } }

/* ------------------------------------------------------------- draft banner */

/* `> [!WARNING]` is GitHub's syntax and Docsify renders it as literal text.
   These are blockquotes whose first line is a **Draft** / **Rascunho** strong,
   tagged by kb.js so every locale gets the same treatment without each
   translation having to carry markup. */
.markdown-section blockquote.kb-draft {
  border-left-color: var(--45b-alert);
  background: color-mix(in srgb, var(--45b-alert) 12%, transparent);
}
.markdown-section blockquote.kb-draft::before {
  content: "⚠"; float: left; margin-right: .6em; color: var(--45b-alert);
}
.markdown-section blockquote { border-radius: 0 8px 8px 0; }

/* ------------------------------------------------------------------ quizzes */

.kbquiz {
  margin: 1.8rem 0; padding: 1.1rem 1.2rem;
  background: var(--45b-surface); border-radius: 12px;
  border: 1px solid rgb(var(--45b-text-rgb) / .1);
}
.kbquiz-kind {
  font-size: .68rem; letter-spacing: .08em; text-transform: uppercase;
  color: rgb(var(--45b-text-rgb) / .45);
  display: flex; gap: .6em; align-items: center; flex-wrap: wrap;
}
.kbquiz-mock {
  border: 1px solid var(--45b-alert); color: var(--45b-alert);
  border-radius: 30px; padding: .05em .7em; letter-spacing: .04em;
}
.kbquiz-q { font-weight: 700; margin: .5em 0 .9em; }
.kbquiz-opts { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.kbquiz-opts button {
  font: inherit; text-align: left; width: 100%; cursor: pointer;
  color: var(--45b-text); background: transparent;
  border: 1px solid rgb(var(--45b-text-rgb) / .25); border-radius: 10px;
  padding: .55em .9em; transition: border-color .15s ease, background .15s ease;
}
.kbquiz-opts button:hover:not(:disabled) { border-color: var(--45b-accent); }
.kbquiz-opts button:disabled { cursor: default; }
.kbquiz-opts button.is-right {
  border-color: var(--45b-alt-accent);
  background: color-mix(in srgb, var(--45b-alt-accent) 16%, transparent);
}
.kbquiz-opts button.is-wrong {
  border-color: var(--45b-alert);
  background: color-mix(in srgb, var(--45b-alert) 14%, transparent);
}
.kbquiz-why {
  margin-top: .9rem; font-size: .88rem; padding-left: .9em;
  border-left: 2px solid var(--45b-accent); color: rgb(var(--45b-text-rgb) / .8);
}
.kbquiz-again {
  margin-top: .9rem; font: inherit; font-size: .8rem; cursor: pointer;
  background: transparent; color: rgb(var(--45b-text-rgb) / .6);
  border: 1px solid rgb(var(--45b-text-rgb) / .25);
  border-radius: 30px; padding: .25em 1em;
}
.kbquiz-again:hover { border-color: var(--45b-accent); color: var(--45b-text); }

/* Matching pairs: two columns that collapse to one on a phone. */
.kbmatch { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.kbmatch-col { display: grid; gap: .5rem; align-content: start; }
.kbmatch button {
  font: inherit; cursor: pointer; text-align: left; color: var(--45b-text);
  background: transparent; border: 1px solid rgb(var(--45b-text-rgb) / .25);
  border-radius: 10px; padding: .5em .8em;
}
.kbmatch button.is-picked { border-color: var(--45b-accent); background: color-mix(in srgb, var(--45b-accent) 20%, transparent); }
.kbmatch button.is-done { border-color: var(--45b-alt-accent); color: rgb(var(--45b-text-rgb) / .5); cursor: default; }
.kbmatch button.is-miss { border-color: var(--45b-alert); }

/* ----------------------------------------------------------------- diagrams */

.kbdiagram {
  margin: 1.8rem 0; padding: 1rem 1.1rem 1.2rem;
  background: var(--45b-surface); border-radius: 12px;
  border: 1px solid rgb(var(--45b-text-rgb) / .1);
}
.kbdiagram-title { font-weight: 700; margin-bottom: .2em; }
.kbdiagram-hint { font-size: .78rem; color: rgb(var(--45b-text-rgb) / .5); margin-bottom: .8em; }
/* width:100% and a viewBox: an SVG without one takes its intrinsic size, which
   is how a picture takes a phone page sideways. */
.kbdiagram svg { width: 100%; height: auto; display: block; overflow: visible; }
.kbnode { cursor: pointer; }
.kbnode rect, .kbnode circle {
  fill: var(--45b-bg); stroke: rgb(var(--45b-text-rgb) / .35); stroke-width: 1.5;
  transition: stroke .15s ease, fill .15s ease;
}
.kbnode:hover rect, .kbnode:hover circle { stroke: var(--45b-accent); }
.kbnode.is-on rect, .kbnode.is-on circle {
  stroke: var(--45b-accent); fill: color-mix(in srgb, var(--45b-accent) 28%, var(--45b-bg));
}
.kbnode text { fill: var(--45b-text); font-family: Chivo, sans-serif; font-size: 13px; }
.kbnode.is-on text { font-weight: 700; }
.kbedge { stroke: rgb(var(--45b-text-rgb) / .3); stroke-width: 1.5; fill: none; }
.kbedge.is-on { stroke: var(--45b-alt-accent); stroke-width: 2.5; }
.kbedge-label { fill: rgb(var(--45b-text-rgb) / .5); font-family: Chivo, sans-serif; font-size: 11px; }
.kbdiagram-detail {
  margin-top: .9rem; min-height: 3.4em; font-size: .9rem;
  padding-left: .9em; border-left: 2px solid var(--45b-accent);
  color: rgb(var(--45b-text-rgb) / .82);
}
.kbdiagram-detail b { color: var(--45b-text); }
.kbdiagram-detail.is-empty { border-left-color: rgb(var(--45b-text-rgb) / .2);
  color: rgb(var(--45b-text-rgb) / .45); }

/* --------------------------------------------------------- right-to-left? */

/* None of the nine locales is RTL today - Turkish, Russian and Hindi all read
   left to right. The hook is here and empty on purpose: the day Arabic is
   added, this is the one place that changes. */
[dir="rtl"] .page_toc { border-left: 0; border-right: 1px solid rgb(var(--45b-text-rgb) / .12); }

/* Devanagari and CJK need a little more leading than Chivo's Latin default, or
   the diacritics and the ascenders collide between lines. */
:lang(hi) .markdown-section { line-height: 1.85; }
:lang(ja) .markdown-section,
:lang(zh-Hant-TW) .markdown-section { line-height: 1.9; letter-spacing: .01em; }

/* ------------------------------------------- anchored headings clear the bar */

/* The masthead and the banner are FIXED, so the top var(--kbtop-h) of the
   viewport is covered. A jump to a heading that lands the heading at y=0 lands
   it UNDER them: the page looks scrolled too far and the heading you clicked is
   the one thing you cannot see. `main`'s padding-top does not help - it shifts
   the whole document down, and the jump target moves with it.

   Two mechanisms, because two things do the scrolling: this handles the
   browser's own :target/anchor jump, and kb.js sets Docsify's `topMargin` for
   its animated scroll (Docsify moves the window itself and never consults
   scroll-margin). Keep the extra breathing room the same in both. */
:root { --kbanchor-gap: 12px; }
.markdown-section h1,
.markdown-section h2,
.markdown-section h3,
.markdown-section h4,
.markdown-section h5,
.markdown-section h6 {
  scroll-margin-top: calc(var(--kbtop-h) + var(--kbanchor-gap));
}
