/* omakase web UI — deliberately minimal: control row + stacked result panes. */

:root {
  /* Final W16 revision — the tone system: the configurable color moved OUT
     of the page background into the surfaces. --bg is now the neutral
     tint-less app background; --tint (tone-1, JS-set from the settings
     picker) colors the input, the command buttons and the even-depth (0, 2,
     …) result panes; --tint-soft (tone-2, the pane-head shade) colors every
     pane head, the stroke strip and the odd-depth nested panes, so nested
     results alternate between the two tones. */
  --bg: #f7f7f5;
  --bg-pane: #ffffff;
  /* Tone-1: the configurable background color — overridden inline by
     applyTheme from the settings picker; this value is only the pre-JS
     default. */
  --tint: #A6DDCF;
  /* Tone-2: the softer shade (the pane-head color) — the tint blended
     toward the neutral card surface. */
  --tint-soft: color-mix(in srgb, var(--bg-pane) 60%, var(--tint));
  --ink: #1a1d21;
  --muted: #6b7280;
  --line: #e3e3de;
  /* One configurable accent shared by both themes (the old light-mode red
     is gone): --accent is overridden inline by applyTheme from the
     settings picker; the stylesheet value is only the pre-JS default. */
  --accent: #ef6a5e;
  --progress: 0%;
  /* Softened wood-block (post-W16 revision): the mono chrome identity
     stays, but the double-frame inset ring was dropped in favor of a single
     border + modest radii (8px cards, 6px controls, 4px chips) so the
     tinted background reads calm instead of stamped. Shared type stacks:
     the UI chrome — headings, buttons, badges, pane heads, footer, settings
     labels — is monospace; prose (the credits panel body) keeps the sans
     stack. The JP fonts at the end let Japanese queries inside mono chrome
     render properly. */
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
          "Noto Sans JP", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", Meiryo, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Noto Sans JP",
          "Hiragino Kaku Gothic ProN", Meiryo, monospace;
}

/* Dark theme is driven by data-theme on <html> (set by main.ts applyTheme
   from the settings pane), not by the media query — the system preference is
   only the JS first-boot default. The JS-applied inline --bg / --accent
   (configurable colors) override these stylesheet values; the rest of the
   palette stays theme-derived. */
:root[data-theme="dark"] {
  --bg: #111418;
  --bg-pane: #181c21;
  /* Pre-JS dark tint: the aqua at 45% strength (#4B635D) — matches
     effectiveTint("dark", ...) so there is no flash before the JS applies
     the capped picker color (dark mode caps the tint so the light ink stays
     readable on tinted surfaces). */
  --tint: #4B635D;
  --tint-soft: color-mix(in srgb, var(--bg-pane) 60%, var(--tint));
  --ink: #e8e6e3; /* soft warm white — the harsh ivory + fuzzy text-shadow
                     were dropped in the softened revision */
  --muted: #9aa0a6;
  --line: #2a2f36;
  --accent: #ef6a5e;
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* W17-width: clip (NOT hidden) horizontal overflow so a horizontal
     scrollbar can never appear. hidden would turn html/body into a scroll
     container and break the sticky control row; clip never creates one. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.45 var(--sans);
  overflow-x: clip; /* same hardening as html (W17-width) */
  /* W15: flex column so #panes can grow and the footer pins to the screen
     bottom on short pages (including zero panes); when content is taller the
     page scrolls and the footer stays in flow after the last pane. min-height
     is 100vh with the dvh override below (iOS Safari < 15.4 lacks dvh). */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
@supports (height: 1dvh) {
  body { min-height: 100dvh; }
}

/* ---- header + status ------------------------------------------------------ */
header {
  padding: 10px 14px 8px;
  max-width: 860px;
  margin: 0 auto;
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-height: 44px;
}

h1 {
  margin: 0;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: 0.02em;
  font-family: var(--mono); /* chrome: mono (W16) */
}

/* The app stamp + full build provenance live in the About dialog (W17c) —
   there is no version badge in the header anymore. */

/* Status line: the message ellipsizes before the boot % readout, which is a
   flex-none sibling pinned at the line's end so a long message can never
   clip it. */
#status {
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
  flex: 1;
}

#status .status-msg {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Live boot progress readout — “x%” next to the status message while the
   engine starts / dictionary imports; hidden once ready. The gauge it
   mirrors is the progress bar at the TOP edge of the controls
   (#lookup::after). */
#status .pct {
  flex: none;
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Header trashbin: deletes every result pane (clears the persisted history). */
#clear {
  flex: none;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: 6px;
  background: none;
  color: var(--accent);
}

#clear:hover:not(:disabled),
#clear:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

#clear:disabled {
  color: var(--muted);
  border-color: var(--line);
  cursor: default;
}

#status.error {
  color: var(--accent);
  justify-content: flex-start;
  white-space: normal;
}

#status.error .status-msg {
  white-space: normal;
  overflow: visible;
}

/* ---- control row ----------------------------------------------------------- */
#lookup {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  /* W17b: the progress bar moved to the row's TOP edge, so the bottom
     corners can round — the row reads as a card with the bar as its top
     edge; the top stays square so the bar spans it cleanly. Top padding is
     6px so the input (with its 2px+2px focus outline) clears the 2px bar. */
  border-radius: 0 0 8px 8px;
  padding: 6px 14px 10px;
  max-width: 860px;
  margin: 0 auto;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03);
  /* W17f: the whole row slides away on scroll-down (see .scrolled-down). */
  transition: transform 0.25s ease;
}

/* W17f: scrolled down past the page top — the control row slides fully
   above the viewport (sticky keeps it at top:0, the transform lifts it
   entirely off-screen); scrolling up (or returning to the top) restores it.
   translateY(-100%) is relative to the row's own height, so it never
   matters how tall the row is. */
#lookup.scrolled-down {
  transform: translateY(calc(-100% - 1px));
}

#lookup::after { /* boot progress: the bar at the TOP edge of the controls
                     doubles as the startup gauge — a tiny dot at 0% that
                     fills to the full line (100%) once the engine reports
                     ready (W17b) */
  content: "";
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  height: 2px;
  min-width: 2px; /* even at 0% the gauge's start reads as a dot */
  border-radius: 1px;
  width: var(--progress);
  background: var(--accent);
  transition: width 0.3s ease;
}

#inputrow {
  display: flex;
  gap: 8px;
  align-items: stretch;
}

#query {
  flex: 1 1 auto;
  min-width: 0; /* let the max box keep its 10% without pushing the row */
  font: inherit;
  font-size: 18px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--tint); /* tone-1: the input is a tinted control */
  color: var(--ink);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}

#query:disabled, #settings-max:disabled { opacity: 0.55; }

#buttons {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
  margin-top: 8px;
  position: relative; /* anchor for the absolutely-positioned #cancel-op */
}

button {
  font: inherit;
  font-family: var(--mono); /* chrome: mono (W16) */
  font-size: 16px;
  font-weight: 600;
  padding: 11px 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--tint); /* tone-1: the command buttons are tinted controls */
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

button:active { transform: translateY(1px); }
button.primary { border-color: var(--accent); color: var(--accent); }
/* Disabled buttons dim through color, not opacity — the per-command pending
   badge (.btn-n, below) must stay fully legible on a disabled button, and
   whole-button opacity would wash it out along with the label. */
button:disabled {
  color: var(--muted);
  /* a dimmed tint: clearly inactive, but stays in the tone family */
  background: color-mix(in srgb, var(--tint) 55%, var(--bg-pane));
  cursor: default;
}

/* Spinner while a lookup is in flight (replaces the pressed button's label). */
button.busy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid rgba(127, 127, 127, 0.35);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Cancel control: a sibling of the command buttons that overlays the busy
   button while a lookup runs (its position is set from the busy button's
   box in syncBusyUi). A button nested inside the command button was invalid
   HTML and clobbered the command button's label. */
#cancel-op {
  position: absolute;
  font: inherit;
  font-family: var(--mono);
  font-size: 18px;
  font-weight: 600;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--muted);
  cursor: pointer;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

#cancel-op[hidden] {
  display: none;
}

#cancel-op:hover,
#cancel-op:focus-visible {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

#cancel-op:active {
  transform: scale(0.9);
}

/* Per-command pending badges: how many panes of that command are still queued
   behind the in-flight lookup (the spinner marks the one running). One badge
   per button, counting down as its panes land — the disabled state above must
   never wash these out. */
.btn-n {
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  padding: 3px 5px;
  margin-left: 6px;
  border-radius: 4px;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  color: var(--accent);
  background: var(--bg-pane);
}

/* ---- result panes ---------------------------------------------------------- */
#panes {
  max-width: 860px;
  margin: 0 auto;
  /* W15: the bottom spacing moved to the footer (it owns the screen-bottom
     padding and its safe-area inset) — #panes only keeps the 10px gutter. */
  padding: 10px 14px 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 0 auto; /* grow to pin the footer to the viewport bottom (W15) */
}

.pane {
  background: var(--tint); /* tone-1: top-level + even-depth panes */
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); /* soft lift off the neutral bg */
  overflow: hidden;
}

/* tone-2: odd-depth nested panes — the same shade as the pane heads, so
   nested results alternate (tint, soft, tint, …) down the tree. The class
   is set by main.ts from each pane's nesting depth. */
.pane.tone-soft {
  background: var(--tint-soft);
}

.pane-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  font-family: var(--mono); /* chrome: mono (W16) */
  color: var(--muted);
  background: var(--tint-soft); /* tone-2: every pane head shares this shade */
}

.badge {
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.08em;
  font-weight: 700;
  font-family: var(--mono); /* chrome: mono (W16) */
  padding: 1px 6px;
  border-radius: 4px;
  /* The accent's new role in the tree: every pane's command tag carries the
     accent (like the pending-count badges on the command buttons), tying the
     panes to the accent-driven chrome. Error/cancelled panes read through
     their accent body text instead of a special badge. */
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  color: var(--accent);
}

.pane.cancelled pre { color: var(--accent); }

.pane-query { font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }

/* Per-pane trashbin: deletes that result from the list and the persisted
   history. Sits at the pane's top-right corner (flex end of the head row). */
.pane-del {
  flex: none;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--accent);
}

.pane-del:hover,
.pane-del:focus-visible {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.pane pre {
  margin: 0;
  padding: 12px 14px;
  font: 14px/1.5 var(--mono); /* W17h: a slightly larger result font */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: auto;
  /* Collapse animation, mirroring .pane-children.hidden below: the pane's
     own body tucks away with the nested children when the pane is
     collapsed, leaving only the head banner. */
  max-height: 5000px;
  transition: max-height 0.3s ease-out, opacity 0.2s ease, padding 0.3s ease;
}

.pane.collapsed pre {
  max-height: 0 !important;
  opacity: 0;
  padding-top: 0;
  padding-bottom: 0;
  pointer-events: none;
}

/* A collapsed pane keeps only its head banner: the stroke strip (the kanji
   character + animation widgets) hides with the body. The strip is a sibling
   of the pane's <pre>, so the collapsed-pre rule above never reached it. */
.pane.collapsed .stroke-strip {
  display: none;
}

/* Skeleton rows of a streaming pane: shimmering bars the worker's streamed
   sections progressively replace. Kept short (result-line height) so the
   pane reads as text loading in, not a placeholder block. */
.skel-line {
  display: block;
  height: 11px;
  margin: 5px 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--bg-pane) 50%, var(--line)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: skel-shimmer 1.2s linear infinite;
}

@keyframes skel-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .skel-line { animation: none; }
  .stroke-skeleton { animation: none; }
}

/* ---- clickable result tokens ---------------------------------------------- */
/* Kanji characters and word-lookup icons rendered inside result panes. */
.pane pre button.tok {
  font: inherit;
  font-weight: inherit;
  color: inherit;
  background: none;
  border: none;
  box-shadow: none; /* inline text token — no frame */
  padding: 0;
  margin: 0;
  border-radius: 0;
  cursor: pointer;
  vertical-align: baseline;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.pane pre button.tok:active { transform: none; }

.tok-kanji {
  border-bottom: 1px dotted color-mix(in srgb, var(--muted) 70%, transparent);
  padding: 0 1px;
  margin: 0 -1px;
}

.tok-kanji:hover,
.tok-kanji:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.tok-word {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-right: 3px;
  vertical-align: -2px;
  color: var(--accent);
}

/* The magnifier is an inline SVG glyph, so font-weight cannot embolden it.
   Hover/focus gives it the same “weight” cue every other control gets —
   as a heavier stroke — instead of the old translucency. */
.tok-word svg { stroke-width: 2.2; }

.tok-word:hover svg,
.tok-word:focus svg,
.tok-word:focus-visible svg { stroke-width: 3.6; }

/* ---- load-more paging buttons (W17i) -------------------------------------- */
/* Small inline continuation buttons after a pane's “… and N more” note: a
   framed cousin of the .tok tokens (1px border, 4px radius, pane surface)
   so it reads as a control, not as result text. Disabled (request in
   flight, or the engine is down) dims through the accent color. */
.pane pre button.load-more {
  font: 600 12px/1 var(--mono);
  display: inline-block;
  padding: 3px 8px;
  margin: 4px 0 2px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--accent);
  box-shadow: none;
  vertical-align: baseline;
  cursor: pointer;
}

.pane pre button.load-more:hover,
.pane pre button.load-more:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}

.pane pre button.load-more:active { transform: none; }

.pane pre button.load-more:disabled {
  color: color-mix(in srgb, var(--accent) 45%, var(--muted));
  background: var(--bg-pane);
  cursor: default;
}

.pane.error pre { color: var(--accent); }

/* ---- stroke-order widgets -------------------------------------------------- */
/* One figure per kanji page character, between the pane header and the text:
   a two-cell row — the font-rendered kanji (`.stroke-char`) beside the square
   svg the strokes draw themselves into — over a small bar with the stroke
   count and ‹ / ↻ / › controls. The svg and the character share one light
   surface and one dark ink, so the two boxes read identically in both color
   schemes. */
.stroke-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px 14px 2px;
  border-bottom: 1px solid var(--line);
  background: var(--tint-soft); /* tone-2: reads as the pane head's banner */
}

.stroke-widget {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
}

.stroke-row {
  display: flex;
  gap: 10px;
  align-items: stretch;
}

/* The character and the animation are exactly the same square size. */
.stroke-char,
.stroke-cell {
  width: clamp(96px, 28vw, 150px);
  aspect-ratio: 1 / 1;
}

/* Font-rendered twin of the animation: the kanji in a box the same size as
   the svg, on the same light surface with the same dark ink. Always present
   — including while the svg loads and when the diagram is missing (then the
   animation box is removed and only this character remains). */
.stroke-char {
  display: grid;
  place-items: center;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: clamp(80px, 24vw, 128px);
  line-height: 1;
  color: #26282c;
  font-weight: 600;
  user-select: none;
}

.stroke-cell {
  position: relative;
}

.stroke-svg {
  display: block;
  width: 100%;
  height: 100%;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* Loading skeleton: a shimmer over the animation box (same treatment as the
   pane's skeleton rows) until the stroke paths have been parsed and mounted.
   inset 1px keeps the svg's border visible around it. */
.stroke-skeleton {
  position: absolute;
  inset: 1px;
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--bg-pane) 50%, var(--line)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: skel-shimmer 1.2s linear infinite;
  pointer-events: none;
}

.stroke-draw path {
  fill: none;
  stroke: #26282c;
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  paint-order: stroke;
}

.stroke-bar {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 5px;
  font-size: 12px;
  font-family: var(--mono); /* chrome: mono (W16) */
  color: var(--muted);
}

/* The label flexes to fill the row, pushing the control buttons to the end. */
.stroke-label {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--ink);
}

.stroke-first,
.stroke-prev,
.stroke-replay,
.stroke-next {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 15px;
  font-weight: 400;
  border-radius: 6px;
  color: var(--ink);
  background: var(--bg-pane);
}

.stroke-first:hover:not(:disabled),
.stroke-first:focus-visible,
.stroke-prev:hover:not(:disabled),
.stroke-prev:focus-visible,
.stroke-replay:hover:not(:disabled),
.stroke-replay:focus-visible,
.stroke-next:hover:not(:disabled),
.stroke-next:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

/* ---- footer: credits & attribution ---------------------------------------- */
footer {
  max-width: 860px;
  margin: 0 auto;
  padding: 4px 14px calc(28px + env(safe-area-inset-bottom));
  display: flex;
  align-items: flex-start;
  gap: 10px;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--mono); /* chrome: mono (W16) */
  line-height: 1.5;
}

.footer-credit {
  margin: 0;
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

.footer-credit a {
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-credit a:hover,
.footer-credit a:focus-visible {
  color: var(--accent);
}

/* The "i" button: opens the About dialog (W17c — the old credits <details>
   became a native modal like Settings). */
.info-btn {
  flex: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--bg-pane);
  color: var(--muted);
  font: italic 700 14px/1 var(--mono);
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.info-btn:hover,
.info-btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

/* The About dialog (W17c): same anatomy as Settings — tone-2 head over a
   tone-1 body — holding the version facts block then the credits prose.
   Same closed-state discipline as #settings: an author display rule on a
   <dialog> overrides the UA's closed hiding, so the flex layout is scoped
   to the open state and the closed state is hidden explicitly. */
#about {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--tint);
  color: var(--ink);
  padding: 0;
  width: min(560px, calc(100vw - 32px));
  /* dvh → vh fallback first (iOS Safari < 15.4 lacks dvh; the @supports
     block below overrides), mirroring the W15 body rule and #settings. */
  max-height: min(80vh, 640px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
@supports (height: 1dvh) {
  #about { max-height: min(80dvh, 640px); }
}
#about:not([open]) { display: none; }
#about[open] {
  display: flex;
  flex-direction: column;
}

#about::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.about-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--tint-soft); /* tone-2, matching the pane heads */
  flex: none;
}

.about-head h2 {
  margin: 0;
  font-size: 16px;
  font-family: var(--mono); /* chrome: mono (W16) */
}

#about-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 18px;
  border-radius: 6px;
  background: none;
  color: var(--muted);
}

#about-close:hover,
#about-close:focus-visible {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.about-body {
  padding: 12px 14px 16px;
  overflow-y: auto;
  /* prose keeps the sans stack — mono body text is unreadable (W16) */
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1.55;
}

.about-facts {
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-pane);
  margin-bottom: 14px;
  font-family: var(--mono); /* the facts block is chrome, not prose */
}

.about-facts p { margin: 0 0 6px; }
.about-facts p:last-child { margin-bottom: 0; }

.about-version {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}

.about-muted {
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.about-body p { margin: 0 0 10px; }
.about-body p:last-child { margin-bottom: 0; }
.about-body ul { margin: 0 0 10px; padding-left: 18px; }
.about-body li { margin-bottom: 6px; }
.about-body a {
  color: var(--accent);
  overflow-wrap: anywhere; /* long URLs wrap instead of overflowing (W15) */
}

/* ---- interactive hover / keyboard focus ----------------------------------- */
/* Every clickable element responds the same way when hovered (fine pointers)
   or focused via the keyboard: its label is emboldened, on top of the accent
   color / underline cues above. Disabled controls are excluded — they are not
   interactive. The hover half is scoped to pointers that can hover so a touch
   tap never leaves sticky emphasis behind; :focus-visible gives keyboard
   users the same cue. Text inputs are the one exception: their “label” is the
   user's own typed query, and emboldening it on hover or focus would make the
   text jump every time — so #query and the settings' text inputs never change
   weight, and their active state reads purely through the accent ring below. */
@media (hover: hover) {
  button:not(:disabled):hover,
  a:hover,
  summary:hover,
  .pane pre button.tok:hover {
    font-weight: 700;
  }
}

button:not(:disabled):focus-visible,
a:focus-visible,
summary:focus-visible {
  font-weight: 700;
}

/* Keyboard focus draws one consistent accent ring around every control.
   The weight cue above cannot act on icon-only buttons (their label is an
   svg or a glyph, not text), so the ring is what actually marks their
   focused state — and it replaces the browser's per-element default outline
   so a tabbed-to element reads the same whether it holds text or an icon.
   Text inputs skip the weight cue, so the ring is their whole active state:
   they report :focus-visible on any focus, which shows the ring rather than
   the old border-color change. The outline applies on :focus-visible only, so a
   mouse click that merely focuses a button does not leave a stray ring
   behind. The text inputs keep an outline:none at rest, so their ring
   selectors must be id-scoped to outrank that base rule. */
button:not(:disabled):focus-visible,
a:focus-visible,
summary:focus-visible,
#query:focus-visible,
#settings-max:focus-visible,
#bg-color:focus-visible,
#accent-color:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Result tokens embolden whenever they hold focus — keyboard (:focus-visible)
   and pointer clicks (:focus) alike. Unlike the persistent controls above,
   a token is a transient inline button: clicking one immediately runs its
   lookup, so there is no sticky “selected” weight to worry about. */
.pane pre button.tok:focus,
.pane pre button.tok:focus-visible {
  font-weight: 700;
}

/* ==========================================================================
   COMPOSABLE UI STYLES
   ========================================================================== */

/* ---- Nested result panes -------------------------------------------------- */
/* W17g: nested panes are centered with an equal ~10px gap on BOTH sides
   instead of being thrown right with a big left margin + left border — the
   depth cue is the tone alternation (even = tint, odd = soft), not an edge. */
.pane.nested {
  margin: 0 10px;
  border-left: none;
  padding-left: 0;
}

.pane-children {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow: hidden;
  max-height: 5000px;
  opacity: 1;
  transition: max-height 0.3s ease-out, opacity 0.2s ease, margin-top 0.3s ease;
  will-change: max-height, opacity;
}

.pane-children.hidden {
  max-height: 0 !important;
  opacity: 0;
  margin-top: 0;
  pointer-events: none;
  overflow: hidden;
}

/* ---- Collapse toggle ------------------------------------------------------- */
.pane-collapse {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  width: 32px;
  padding: 0;
  margin-right: 8px;
  border: none;
  border-radius: 6px;
  background: var(--bg-pane);
  color: var(--muted);
  cursor: pointer;
  position: relative;
  z-index: 1;
  -webkit-appearance: none;
  appearance: none;
  user-select: none;
}

.pane-collapse:hover:not(:disabled),
.pane-collapse:focus-visible {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}

.pane-collapse:active {
  transform: scale(0.95);
}

.pane-collapse svg {
  width: 16px;
  height: 16px;
  stroke-width: 2.5;
}

/* ---- Settings pane -------------------------------------------------------- */
/* The gear lives at the right end of the header (after the trashbin) and
   opens the settings dialog. */
#settings-gear {
  flex: none;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: none;
  color: var(--muted);
}

#settings-gear:hover:not(:disabled),
#settings-gear:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* The dialog: centered, viewport-constrained, scrollable body; closed with
   the ✕ button or ESC (no backdrop-click close — avoids accidental
   dismissal on phones). */
#settings {
  border: 1px solid var(--line);
  border-radius: 8px;
  /* The settings box follows the pane anatomy: a tone-2 head over a tone-1
     body (the original intent — the box is a shade of the picked color),
     with the form controls keeping their neutral card surfaces so they pop. */
  background: var(--tint);
  color: var(--ink);
  padding: 0;
  width: min(440px, calc(100vw - 32px));
  /* W17-settings-dvh: max-height falls back to vh first (iOS Safari < 15.4
     lacks dvh — there the whole min(80dvh, 560px) is invalid and the dialog
     becomes unbounded); the @supports block below overrides with dvh where
     supported, exactly like the W15 body rule. */
  max-height: min(80vh, 560px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
@supports (height: 1dvh) {
  #settings { max-height: min(80dvh, 560px); }
}
/* A closed dialog must stay hidden. The UA hides it via
   dialog:not([open]) { display: none }, but an author `display` rule would
   override that (author origin beats UA origin) and leave the settings pane
   permanently visible in the page flow — a styled window whose ✕ cannot
   close it (close() on a never-modal dialog is a no-op). So the flex column
   layout is scoped to the open state only, and the closed state is hidden
   explicitly. */
#settings:not([open]) { display: none; }
#settings[open] {
  display: flex;
  flex-direction: column;
}

#settings::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--tint-soft); /* tone-2, matching the pane heads */
  flex: none;
}

.settings-head h2 {
  margin: 0;
  font-size: 16px;
  font-family: var(--mono); /* chrome: mono (W16) */
}

#settings-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 18px;
  border-radius: 6px;
  background: none;
  color: var(--muted);
}

#settings-close:hover,
#settings-close:focus-visible {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.settings-body {
  padding: 12px 14px 16px;
  overflow-y: auto;
}

.setting {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}

.setting:last-child {
  margin-bottom: 0;
}

.setting-check {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.setting-check input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

.setting-label {
  font-size: 13px;
  font-weight: 600;
  font-family: var(--mono); /* chrome: mono (W16) */
  color: var(--muted);
}

#settings-max {
  font: inherit;
  font-family: var(--mono);
  font-size: 16px;
  padding: 8px 10px;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-pane);
  color: var(--ink);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  -moz-appearance: textfield;
}

#settings-max::-webkit-outer-spin-button,
#settings-max::-webkit-inner-spin-button {
  -webkit-appearance: inner-spin-button;
  opacity: 1;
}

/* Theme segmented control: two equal buttons, the active one in accent. */
.segmented {
  display: inline-flex;
  align-self: flex-start;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
}

.segmented button {
  border: none;
  border-radius: 0;
  padding: 8px 18px;
  font-size: 14px;
  background: var(--bg-pane);
  color: var(--muted);
}

.segmented button + button {
  border-left: 1px solid var(--line);
}

.segmented button.active {
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-pane));
  color: var(--accent);
}

/* Color pickers: hex text input + preview square (and, for the background,
   the 0–100% intensity slider underneath). */
.color-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.color-preview {
  flex: none;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: transparent;
}

.color-row input[type="text"] {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 15px;
  font-family: var(--mono);
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-pane);
  color: var(--ink);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}

.color-row input[type="text"].invalid {
  border-color: var(--accent);
}

#bg-mix {
  width: 100%;
  accent-color: var(--accent);
}

.setting-hint {
  font-size: 12px;
  color: var(--muted);
}

/* ---- Danger zone: erase all data (W17j) ----------------------------------- */
.setting-danger {
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

#settings-wipe {
  align-self: flex-start;
  font: inherit;
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 700;
  padding: 8px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 60%, var(--line));
  border-radius: 6px;
  background: none;
  color: var(--accent);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

#settings-wipe:hover,
#settings-wipe:focus-visible {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
