/* MonoLisa 3.000, subject to a EULA: https://www.monolisa.dev/license
   © 2026 FaceType Foundry. All Rights Reserved.

   Folded in from the two generated monolisa.css files, whose src paths are
   site-root absolute and do not resolve where the files actually live. Variable
   faces spanning 1-900, split by unicode range rather than by weight, so one
   file per range covers every weight the app asks for. Of the italic faces
   only basic latin Text is declared, for the guide pages' prose; the rest are
   left out, and an undeclared face is never fetched.

   fonts/README.md says which ranges are needed and why. */

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/0-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+0020-007F;
}

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/2-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+0080-00FF;
}

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/4-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+0370-03FF;
}

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/6-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+2000-206F;
}

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/8-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+2190-21FF;
}

@font-face {
  font-family: MonoLisaText;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/10-MonoLisaText-normal.woff2)
    format("woff2");
  unicode-range: U+2200-22FF;
}

/* Basic latin only. The guide pages are the one place that sets italic, and
   every word they italicise is ASCII; a stray × or em-dash inside an <em>
   would fall to the synthetic slant of its own range's normal face, which on
   one symbol is not worth another file. */
@font-face {
  font-family: MonoLisaText;
  font-style: italic;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-text/woff2/1-MonoLisaText-italic.woff2)
    format("woff2");
  unicode-range: U+0020-007F;
}

@font-face {
  font-family: MonoLisaCode;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-code/woff2/0-MonoLisaCode-normal.woff2)
    format("woff2");
  unicode-range: U+0020-007F;
}

@font-face {
  font-family: MonoLisaCode;
  font-style: normal;
  font-weight: 1 900;
  font-display: swap;
  src: url(../fonts/monolisa-code/woff2/2-MonoLisaCode-normal.woff2)
    format("woff2");
  unicode-range: U+0080-00FF;
}

:root {
  /* Flexoki dark — https://stephango.com/flexoki */
  --bg: #1c1b1a; /* base-950 */
  --panel: #282726; /* base-900 */
  --panel-2: #343331; /* base-850 */
  --border: #403e3c; /* base-800 */
  --grid: #343331; /* base-850 */
  --dead: #282726; /* base-900, for the stretch of an axis nothing can reach */
  --axis: #6f6e69; /* base-600 */
  --text: #fffcf0; /* paper    */
  --text-dim: #878580; /* base-500 */
  --accent: #4385be; /* blue-400 */
  /* Flexoki's eight hues at the 400 step, ordered so the ones reached first
     are the furthest apart. */
  --series-a: #4385be; /* blue-400 */
  --series-b: #da702c; /* orange-400 */
  --series-c: #3aa99f; /* cyan-400 */
  --series-d: #ce5d97; /* magenta-400 */
  --series-e: #879a39; /* green-400 */
  --series-f: #8b7ec8; /* purple-400 */
  --series-g: #d0a215; /* yellow-400 */
  --series-h: #d14d41; /* red-400 */
  --sans:
    MonoLisaText, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    sans-serif;
  --mono:
    MonoLisaCode, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    monospace;
}

* {
  box-sizing: border-box;
}

/* Every inline link starts here: the app's one accent colour, underlined so
   it reads as a link independent of colour, and visited in Flexoki's own
   purple rather than a colour borrowed from outside the palette. A control
   styled as a button or a tab (.tabs a, .page-menu-pop a, .landing-card, …)
   already sets its own color and text-decoration, which this loses to on
   specificity, so those are untouched. */
a {
  color: var(--accent);
  text-decoration-line: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
  text-underline-offset: 2px;
}

a:hover {
  text-decoration-color: currentColor;
}

a:visited {
  color: var(--series-f); /* purple-400 */
}

a:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

body {
  margin: 0;
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.45 var(--sans);
  overflow-x: hidden;
}

/* The document scrolls; this fills exactly one viewport so the explanation
   below sits just off the bottom edge rather than intruding on the chart.
   svh, not dvh: sized to the viewport with the mobile browser chrome showing,
   which is the state the shell has to fit in. */
.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100svh;
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* --- top bar: title, tabs, global options on one line --- */

.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  row-gap: 0.25rem;
  padding: 0.25rem 0.85rem;
  min-height: 2.4rem;
  flex: 0 0 auto;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
}

/* The title and the view tabs read as one unit - what page, what part of it -
   so they sit close together rather than at the topbar's own (wider) gap. */
.nav-cluster {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.tabs {
  display: flex;
  gap: 0.2rem;
}
.tabs[hidden] {
  display: none;
}

/* Two tab groups sitting side by side (a view switcher plus a standalone
   Guide tab) read as one row, so the gap between them matches the gap
   within each rather than the wider one nav-cluster gives every child. */
.tabs + .tabs {
  margin-left: -0.2rem;
}

.tabs button,
.tabs a {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-weight: 500;
  font-size: 0.78rem;
  padding: 0.2rem 0.65rem;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
}

.tabs button:hover,
.tabs a:hover {
  color: var(--text);
  background: var(--panel-2);
}

.tabs button[aria-selected="true"],
.tabs a[aria-selected="true"] {
  color: var(--text);
  background: var(--panel-2);
  border-color: var(--border);
}

/* The page title doubles as the switch to the site's other pages: what used
   to be two tabs of links sitting next to it is now everything that isn't
   this page, one click away instead of always on. */
.page-menu {
  position: relative;
}

.page-menu-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--text);
  font-family: inherit;
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.01em;
  padding: 0.2rem 0.5rem;
  white-space: nowrap;
  cursor: pointer;
}

.page-menu-btn:hover {
  background: var(--panel-2);
}

.page-menu-btn[aria-expanded="true"] {
  background: var(--panel-2);
  border-color: var(--border);
}

.page-menu-caret {
  color: var(--text-dim);
  font-size: 0.6rem;
}

.page-menu-pop[hidden] {
  display: none;
}

.page-menu-pop {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 9rem;
  padding: 0.3rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.page-menu-pop a {
  padding: 0.35rem 0.55rem;
  border-radius: 4px;
  color: var(--text-dim);
  font-family: inherit;
  font-weight: 500;
  font-size: 0.78rem;
  text-decoration: none;
  white-space: nowrap;
}

.page-menu-pop a:hover {
  color: var(--text);
  background: var(--panel);
}

.topbar .spacer {
  flex: 1;
}

/* share and reset as one flex item, so a wrap moves the pair together instead
   of splitting them across two lines. */
.topbar .actions {
  display: inline-flex;
  gap: 1rem;
}
.topbar .actions[hidden] {
  display: none;
}

.topbar .opts[hidden] {
  display: none;
}

.topbar .opts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.85rem;
  align-items: center;
  min-width: 0;
  font-size: 0.75rem;
  color: var(--text-dim);
}

/* Controller strip: pad name, axis picker, invert toggle. flex:1 1 100% wraps
   it onto its own line within whatever flex-wrap row it's dropped into,
   whether that's the calculator's controls column or blackbox's topbar. */
.pad {
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.15rem 0.5rem;
  font-size: 0.75rem;
  color: var(--text-dim);
}

.pad .pad-name {
  font: 0.75rem var(--mono);
  color: var(--text);
}
.pad .hint {
  color: var(--text-dim);
  cursor: help;
  text-decoration: underline dotted var(--border);
  text-underline-offset: 3px;
}

select,
input[type="number"] {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.15rem 0.3rem;
  font: 0.78rem var(--mono);
}

input[type="number"] {
  width: 100%;
  text-align: right;
}
select:focus,
input:focus {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

/* Sliders. Both pages have them: eight rate curves on one, a provisional
   throttle curve on the other, and --slot-color is what lets each carry the
   colour of the thing it moves. */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  height: 1.75rem;
  margin: 0;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 5px;
  background: var(--border);
  border-radius: 3px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  margin-top: -6.5px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--slot-color, var(--accent));
  cursor: pointer;
}
input[type="range"]::-moz-range-track {
  height: 5px;
  background: var(--border);
  border-radius: 3px;
}
input[type="range"]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: var(--slot-color, var(--accent));
  cursor: pointer;
}

/* Touchscreens get no hover or fine-pointer precision, so the thumb needs a
   real fingertip-sized target rather than the mouse-tuned one above. */
@media (pointer: coarse) {
  input[type="range"] {
    height: 2.25rem;
  }
  input[type="range"]::-webkit-slider-runnable-track {
    height: 6px;
  }
  input[type="range"]::-webkit-slider-thumb {
    width: 24px;
    height: 24px;
    margin-top: -9px;
  }
  input[type="range"]::-moz-range-track {
    height: 6px;
  }
  input[type="range"]::-moz-range-thumb {
    width: 24px;
    height: 24px;
  }
}

/* --- views fill the remaining viewport --- */

.view {
  flex: 1 1 auto;
  min-height: 0;
  display: none;
  flex-direction: column;
}
.view.active {
  display: flex;
}

/* A floor rather than the 0 a pure flex layout would allow: eight configs and
   eight readout rows, both at their cap, must still leave a usable chart at the
   shortest viewport this layout applies to. */
.chart-wrap {
  flex: 1 1 auto;
  min-height: 120px;
  position: relative;
}

.chart-wrap canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: crosshair;
  touch-action: pan-y;
}

/* --- readout strip pinned to the bottom --- */

.readout {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0 1.1rem;
  min-height: 3rem;
  max-height: 30vh;
  overflow: hidden;
  padding: 0.25rem 0.7rem;
  border-top: 1px solid var(--border);
  background: var(--panel);
  font: 0.78rem var(--mono);
}

.readout .hint {
  color: var(--text-dim);
}
.readout .k {
  color: var(--text-dim);
}
.readout .v {
  font-weight: 600;
}
.readout .sw {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 2px;
  margin-right: 0.3rem;
}
/* Outlined means reach and filled means hold time, matching how the two
   series are drawn in the lane, so a chip reads the same everywhere. */
.readout-keys {
  border-left: 1px solid var(--border);
  padding-left: 1.1rem;
}
/* display:flex above ties [hidden] on specificity and wins on source order,
   so the empty column kept its rule and padding on every view that has no
   keys to put in it. */
.readout-rows[hidden] {
  display: none;
}

/* Sized by its tallest column, so both start at the same y, and centred as one
   block in the strip. */
.readout-cols {
  display: flex;
  gap: 0 1.1rem;
  min-width: 0;
  max-height: 100%;
}

.readout .sw.hollow {
  background: none;
  border: 1.5px solid currentColor;
}
/* A series' name sits at the chip's colour, not the value's brightness: it
   says which row this is, and the numbers are what gets read. */
.readout .name {
  font-weight: 600;
}
/* The elbow is marked on the curve with a tick across it rather than a change
   in the line, so its key is that shape and not a block of colour. */
.readout .sw.tick {
  width: 2px;
  height: 0.7rem;
  border-radius: 0;
  margin: 0 0.55rem 0 0.25rem;
  vertical-align: -0.1rem;
}

label.check {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  white-space: nowrap;
}

/* inline-flex would otherwise win over the hidden attribute. */
label.check[hidden] {
  display: none;
}
input[type="checkbox"] {
  accent-color: var(--accent);
  margin: 0;
}

.topbar .opts input[type="number"] {
  width: 3.9rem;
}

.topbar .btn-group,
.topbar .btn-group .confirm {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  color: var(--text-dim);
  white-space: nowrap;
}

/* The parts swap by the hidden attribute, which inline-flex would override -
   both a hidden part inside a group and a whole group hidden by itself, such
   as one that only applies to the current view. */
.topbar .btn-group [hidden],
.topbar .btn-group[hidden] {
  display: none;
}

.topbar .btn-group button {
  background: var(--panel-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.15rem 0.5rem;
  font-family: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.topbar .btn-group button:hover {
  color: var(--text);
  border-color: var(--axis);
}

/* The one button here that throws something away. */

.readout-cursor {
  flex: 0 0 8.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.readout-rows {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Top-aligned within the group rather than centred within the readout: two
     columns that each centre on their own row count sit half a line apart.
     The group is what gets centred, so a short readout still looks placed. */
  justify-content: flex-start;
  min-width: 0;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
}

/* The readout clips rather than grows, and macOS gives Chrome an overlay
   scrollbar that stays invisible until scrolled, leaving a half-shown row
   looking like a layout fault rather than a hint that there is more. Chromium
   honours these pseudo-elements only while scrollbar-width and scrollbar-color
   are left alone; Firefox draws a real scrollbar without being asked. */
.readout-rows::-webkit-scrollbar {
  width: 9px;
}

.readout-rows::-webkit-scrollbar-track {
  background: transparent;
}

.readout-rows::-webkit-scrollbar-thumb {
  background: var(--axis);
  background-clip: content-box;
  border: 2px solid transparent;
  border-radius: 5px;
}

.readout-rows > div,
.readout-cursor > div {
  display: flex;
  gap: 0 1.1rem;
  white-space: nowrap;
  line-height: 1.35;
}

.topbar .opts label.check[title] {
  cursor: help;
  text-decoration: underline dotted var(--text-dim);
  text-underline-offset: 3px;
}

.topbar .opts label.check[title] input {
  text-decoration: none;
}

/* Small screens, and short ones: a landscape phone is wide but only ~390px
   tall, so the viewport-filling layout has nowhere to put its own controls and
   clips them with no way to scroll. Become an ordinary scrolling document
   instead, with the chart at a definite height since flex no longer sizes it. */
@media (max-width: 820px), (max-height: 620px) {
  /* The shell grows past the viewport rather than compressing into it, so the
     document is already scrolling and the explanation just follows on. */
  .app {
    height: auto;
    min-height: 100svh;
  }

  .view.active {
    display: block;
  }

  .chart-wrap {
    flex: none;
    height: max(280px, 55vh);
    min-height: 280px;
  }

  /* The readout has to grow and wrap here; clipping it is what cut the text
     off, and the chart is no longer resizing underneath the cursor anyway. */
  .readout {
    flex-direction: column;
    align-items: stretch;
    min-height: 3rem;
    max-height: none;
    overflow: visible;
    padding: 0.4rem 0.7rem;
    gap: 0.15rem 0;
  }

  .readout-cursor {
    flex: 0 0 auto;
    flex-direction: row;
    gap: 0 1rem;
  }

  .readout-rows {
    overflow: visible;
  }

  .readout-rows > div,
  .readout-cursor > div {
    flex-wrap: wrap;
    white-space: normal;
  }
}

/* --- guide pages --- */

.explain {
  padding: 2rem 1.25rem 4rem;
}

.explain-body {
  max-width: 62ch;
  margin: 0 auto;
  font-size: 0.9rem;
  line-height: 1.65;
}

.explain-body h2 {
  margin: 2.75rem 0 0.75rem;
  font-size: 1rem;
  font-weight: 600;
}

.explain-body > :first-child {
  margin-top: 0;
}

.explain-body h3 {
  margin: 2rem 0 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
}

.explain-body p {
  margin: 0 0 0.9rem;
}

.explain-body code {
  font: 0.85em var(--mono);
  color: var(--text);
}

/* A control named in the prose, wearing the app's own button: the same mono,
   panel fill and border, so the term in the sentence and the thing it points
   at on screen read as one object. Firmware parameters stay bare <code>, which
   is what keeps the two kinds of monospace apart. A dotted underline did this
   job before and was too faint to see, which left a lowercase control name
   looking like a sentence missing its capital. */
.explain-body kbd {
  font: 0.82em var(--mono);
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.1em 0.35em;
  white-space: nowrap;
}

/* One closing row: where to go next on the left, whose page this is on the
   right. They wrap onto separate lines when the column gets too narrow for
   both, which is the only reason the attribution carries its own alignment. */
.explain-body .back {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.25rem 1.5rem;
  margin-top: 2rem;
  font-size: 0.8rem;
}

.explain-body .built {
  margin-left: auto;
  color: var(--text-dim);
}
