@import url('https://fonts.googleapis.com/css2?family=VT323&family=Inter:wght@400;700&display=swap');

/* Terminal/BBS redesign (2026-07-08), replacing the earlier arcade-cabinet-
   bezel theme. Confirmed direction: a genuine command-line system, not a
   video-game skin — VT323 only (no Press Start 2P, no JetBrains Mono),
   hierarchy carried by size/case/letter-spacing/glow instead of a second
   typeface, box-drawing-style double-line frames (native CSS border
   mitering, not clip-path or font-dependent glyphs), bracket-style reverse-
   video buttons, and real CRT phosphor glow via layered text-shadow. See
   DESIGN.md for the full rationale and named rules.

   Inter is the one deliberate, scoped exception (2026-07-13): the Staff
   Manual page's body paragraphs/lists use it instead of VT323, chosen from
   a mockup the user picked (see MEMORY.md) — long-form reading text in a
   pixel font was hard to read for the manual's non-technical, non-gamer
   pilot audience. Headings, the sidebar, and every other page keep VT323
   unchanged; this is the second such exception after the manual's own
   print stylesheet further down this file. */
:root {
  --ql-bg: #04120b;
  --ql-panel: #071f14;
  --ql-accent: #00b97a;
  --ql-accent-cyan: #36ffff;
  --ql-text: #d8f5e8;
  --ql-muted: #6b9c86;
  /* Scoped to Food Badge icons only (DESIGN.md "Food Badge Icon Colors") —
     never used as a border, text, or button color anywhere else. */
  --ql-food-fruit: #ff5a5a;
  --ql-food-protein: #ff9b52;
  --ql-food-grain: #e0c56b;
}

* {
  box-sizing: border-box;
}

/* General-purpose hide utility. Don't delete this thinking it's redundant
   with .ql-modal-overlay.ql-hidden or .ql-breakdown-item__detail.ql-hidden
   further down — those compound selectors exist *in addition to* this
   one, deliberately, so they out-specificity that element's own
   `display` value regardless of source order (see the comment at each
   rule). Deleting this generic rule instead of adding the compound one
   is exactly what broke every OTHER .ql-hidden usage in the app
   previously — the fix must keep both. Any future element that sets its
   own `display` and also needs `.ql-hidden` support needs this same
   compound-selector treatment, not just the generic rule alone. */
.ql-hidden {
  display: none;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--ql-bg);
  color: var(--ql-text);
  font-family: 'VT323', 'Courier New', monospace;
  font-size: 20px;
  padding: 3rem 1rem;
}

/* Whole-viewport CRT scanline, not per-panel — a single overlay reads as
   "looking at the screen through glass," rather than needing the z-index
   arbitration the old per-panel version required (every .ql-panel child
   needed its own explicit position/z-index to paint above it). Kept
   restrained (mix-blend-mode + low opacity) per the brief: enough texture
   to read as a real CRT, not so much it becomes the dominant visual. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 999;
  background: repeating-linear-gradient(
    0deg,
    rgba(0, 0, 0, 0.25) 0px,
    rgba(0, 0, 0, 0.25) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: multiply;
}

/* Data Export opts out of the page-wide scanline (explicit staff-tool
   readability request — the horizontal lines were making body text
   harder to read) but keeps it in the nav bar, so the topbar still
   matches every other page. Scoped to this one page via a body class,
   not a global change — every other page keeps the full-viewport
   version above untouched. */
body.ql-no-page-scanline::before {
  content: none;
}

body.ql-no-page-scanline .ql-topbar::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    0deg,
    rgba(0, 0, 0, 0.25) 0px,
    rgba(0, 0, 0, 0.25) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: multiply;
}

.ql-page {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Single terminal-window frame — replaces the old 3-nested-div arcade
   bezel (outer black ring / green ring / panel). A plain CSS double
   border already miters correctly at all 4 corners with zero extra
   markup; the earlier clip-path/offset-shadow approach was exactly the
   "flat CSS shapes faking pixel art" the redesign was asked to fix. */
.ql-panel {
  width: 90%;
  max-width: 420px;
  border: 4px double var(--ql-accent);
  background: var(--ql-panel);
  box-shadow:
    0 0 40px rgba(0, 185, 122, 0.12),
    inset 0 0 60px rgba(0, 185, 122, 0.04);
  padding: 0 0 1.75rem;
  text-align: center;
}

/* Wider variant for the two-column main console (login/forgot/reset stay
   at the narrow default above — they only ever have one column). */
.ql-panel--wide {
  max-width: 1320px;
}

.ql-titlebar {
  padding: 0.9rem 1.5rem;
  margin: 0 0 1.5rem;
  border-bottom: 4px double var(--ql-accent);
  text-align: left;
}

/* Login's titlebar moved entirely outside .ql-panel in the markup (direct
   request: the logo/subtitle should sit free of any frame, not just lose
   the internal divider — the double-border bottom edge combined with
   .ql-panel's own double-border sides was reading as a second frame boxing
   them in). As a sibling of .ql-panel instead of a child, it no longer
   inherits the panel's width, so it gets its own matching width/max-width
   here to keep the same left edge as the panel below it; border-bottom and
   the default bottom margin are dropped in favor of a slightly larger gap
   before the now-visually-separate panel. Scoped to this one page via
   body.ql-login-page — every other .ql-panel page (Today's Quest, Profile,
   Boss Battle) keeps the titlebar inside the panel, framed, unchanged. */
body.ql-login-page .ql-titlebar {
  width: 90%;
  max-width: 420px;
  margin: 0 0 1.75rem;
  border-bottom: none;
}

/* .ql-panel's base padding is `0 0 1.75rem` (top: 0) — fine when
   .ql-titlebar used to be its first child, since the titlebar's own
   padding provided the visual top gap. Now that the "QuestLog Login"
   heading is the panel's first child (titlebar moved out, above), it sat
   right up against the top border with nothing but the border's own 4px
   for spacing — direct request for more room. */
body.ql-login-page .ql-panel {
  padding-top: 1.75rem;
}

.ql-titlebar__name {
  font-size: 1.4rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 10px rgba(0, 185, 122, 0.7);
}

.ql-titlebar__meta {
  margin: 0.35rem 0 0;
  font-size: 1.05rem;
  color: var(--ql-muted);
}

/* Hard on/off blink (steps(1)), not a fade — a fade reads as a soft UI
   transition; a real terminal cursor snaps. */
/* Empty span, no explicit height — with nothing inside it and no height
   set, an inline-block can render at effectively zero visible area
   regardless of background color or animation state. 0.85em/vertical-align
   makes it read as a proper block cursor sitting on the text baseline,
   matching the surrounding title text's proportions. */
.ql-cursor {
  display: inline-block;
  width: 0.55em;
  height: 0.85em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: var(--ql-accent);
  color: var(--ql-bg);
  animation: ql-blink 1.1s steps(1) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .ql-cursor {
    animation: none;
  }
}

@keyframes ql-blink {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

h1,
.ql-heading {
  font-family: 'VT323', monospace;
  font-size: 1.5rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.6);
  margin: 0 0 1.25rem;
  padding: 0 1.5rem;
}

/* Two-column main console layout (Today's Quest left, logging sections
   right). */
.ql-layout {
  display: grid;
  grid-template-columns: 0.85fr 2.15fr;
  padding: 0 1.75rem;
}

.ql-column--quest {
  text-align: center;
  padding-right: 1.75rem;
}

.ql-column--log {
  text-align: left;
  padding-left: 1.75rem;
  border-left: 1px solid rgba(0, 185, 122, 0.35);
}

/* 3 log sections side-by-side. */
.ql-log-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

/* Profile Page's grid: the first column (avatar + character name) is much
   narrower than an equal third, which left a wide dead gap before the
   divider into Food Badges. `auto` sizes the column to its actual content
   (the 192px avatar, or a longer typed name) instead, so the divider sits
   right up against it. Compound selector (not source-order-dependent)
   since it must outrank the equal-specificity .ql-log-grid rule above. */
.ql-log-grid.ql-profile-grid {
  grid-template-columns: auto 1fr 1fr;
}

/* Grid stretches all 3 sections to match the tallest (Meals & Snacks has
   more fields than Power-Ups or Obstacles) — flex column + margin-top:auto
   on each section's final action button anchors it to the bottom instead
   of leaving dead space trailing after a short section's last field.
   align-items:flex-start keeps every button left-aligned consistently
   (the earlier version only special-cased the *last* button, so the first
   "Search" button in Meals & Snacks fell back to the container's default
   stretch behavior and misaligned) — form fields/checkbox groups need an
   explicit align-self:stretch to stay full-width under that same rule. */
.ql-log-grid .ql-section {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.ql-log-grid .ql-section .ql-button:last-of-type {
  margin-top: auto;
}

.ql-log-grid .ql-section:first-child {
  padding-left: 0;
}

.ql-log-grid .ql-section:last-child {
  padding-right: 0;
}

.ql-log-grid .ql-section:not(:first-child) {
  border-left: 1px solid rgba(0, 185, 122, 0.35);
}

/* .ql-section--center (the Boss Page's boss-figure section, the only
   :first-child that's centered rather than left-aligned) needs two
   overrides against the left-aligned-by-default rules above:
   (1) symmetric padding — the :first-child rule zeroing only the left
   side pushed its content off-true, shifted toward the removed edge;
   (2) align-items:center — .ql-log-grid .ql-section's own
   align-items:flex-start controls flex-item cross-axis position and wins
   over text-align:center (they're unrelated properties), so the section's
   own children (title, avatar, health bar) were hugging the left edge of
   the column regardless of text-align. Compound selector to at least
   match :first-child's specificity, placed after it so source order also
   favors this override. */
.ql-log-grid .ql-section.ql-section--center {
  align-items: center;
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}

.ql-field {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-bottom: 1rem;
  text-align: left;
}

.ql-field label {
  margin-bottom: 0.25rem;
  font-size: 1.05rem;
  color: var(--ql-muted);
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.ql-field input,
.ql-field textarea {
  width: 100%;
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
}

.ql-field textarea {
  resize: vertical;
}

/* Login's email/password fields inset from the panel's edges (direct
   request: they read as too wide, spanning nearly the full panel). Padding
   on the form (rather than each .ql-field) narrows the label and input
   together in one place, and lines up with the heading's/titlebar's own
   1.5rem horizontal padding above it. Scoped to this one form only — every
   other .ql-field on the site (Settings, meal search, etc.) stays full
   width. */
#ql-login-form {
  padding: 0 1.5rem;
}

/* Scanline removed for just the Email/Password fields, not the whole page
   (revised from an earlier page-wide `ql-no-page-scanline` opt-out, which
   the user found removed too much — the rest of the login page, titlebar
   and panel border included, should keep the CRT texture). Same
   z-index-lift technique used for the How This Works section: body::before
   is `position: fixed; z-index: 999`, and neither .ql-page nor .ql-panel
   establish their own stacking context, so giving these two fields
   `position: relative; z-index: 1000` is enough to paint them above the
   overlay without touching anything else on the page. Login has no
   .ql-topbar, so unlike How This Works this doesn't risk the same
   content-painting-through-the-nav bug — see the fix on .ql-topbar itself
   if a fixed nav is ever added to this page. */
#ql-login-form .ql-field {
  position: relative;
  z-index: 1000;
}

/* Bracketed command style — `[ LOG POWER-UPS ]` — instead of a filled
   rectangle with an offset "pixel" shadow. Reverse-video on hover/focus
   (the container's own green becomes the button's background) is the
   direct Norton Commander/htop reference from the confirmed brief; the
   old hard-offset-shadow press mechanic is dropped along with it. */
/* Padding tried at 0.85rem 0 to clear the 44px touch-target minimum
   (`/impeccable audit` P1) — reverted to the original 0.5rem per direct
   feedback: the taller buttons read as visually wrong, and that verdict
   wins over the audit's numeric target. The touch-target gap is
   documented as a known, deliberately-accepted tradeoff below. */
.ql-button {
  display: inline-block;
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: transparent;
  color: var(--ql-accent);
  border: none;
  border-radius: 0;
  padding: 0.5rem 0;
  cursor: pointer;
  text-shadow: 0 0 2px rgba(0, 185, 122, 0.6);
}

.ql-button::before {
  content: '[ ';
}

.ql-button::after {
  content: ' ]';
}

.ql-button:hover {
  background: var(--ql-accent);
  color: var(--ql-bg);
  text-shadow: none;
}

/* Current-page nav button (.ql-nav's [ TODAY'S QUEST ]/[ PROFILE ]/
   [ BOSS BATTLE ] trio) stays in the reverse-video fill permanently,
   instead of only ever showing it on hover — direct request, so it's
   clear at a glance which of the 3 pages you're already on. Marked via
   `aria-current="page"` on that page's own link (one per page, in
   index.html/profile.html/boss.html) rather than a new class — it's the
   semantically correct attribute for "this is the current page in a set
   of nav links" and gets announced by screen readers for free. */
.ql-button[aria-current='page'] {
  background: var(--ql-accent);
  color: var(--ql-bg);
  text-shadow: none;
}

.ql-button:active {
  filter: brightness(0.85);
}

.ql-button:disabled {
  color: var(--ql-muted);
  text-shadow: none;
  cursor: not-allowed;
}

.ql-button:disabled:hover {
  background: transparent;
  color: var(--ql-muted);
}

/* Signal Cyan, not the browser-default blue outline — on-brand per the
   Two-Accent Rule (cyan = informational/status) and better contrast
   against this dark palette than the default. */
.ql-button:focus-visible,
.ql-field input:focus-visible,
.ql-field textarea:focus-visible,
.ql-select:focus-visible {
  outline: 3px solid var(--ql-accent-cyan);
  outline-offset: 2px;
}

.ql-checkbox input:focus-visible + .ql-checkbox-box {
  outline: 3px solid var(--ql-accent-cyan);
  outline-offset: 2px;
}

.ql-error {
  color: var(--ql-accent-cyan);
  font-family: 'VT323', monospace;
  margin-top: 1rem;
  min-height: 1.5rem;
}

.ql-success {
  color: var(--ql-accent);
  font-family: 'VT323', monospace;
  margin-top: 1rem;
}

.ql-success a {
  color: var(--ql-accent);
}

.ql-text-link {
  color: var(--ql-accent-cyan);
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
}

.ql-text-link:hover {
  text-decoration: none;
}

.ql-avatar-wrap {
  margin-bottom: 1.5rem;
  filter: drop-shadow(0 0 6px rgba(0, 185, 122, 0.55));
}

/* Single static avatar/boss figure, same look regardless of stat levels
   (PRD Section 3 — no avatar states yet). */
.ql-avatar {
  width: 128px;
  height: 128px;
  image-rendering: pixelated;
}

/* Profile Page shows the avatar larger than Today's Quest/Boss Page — still
   a multiple of the source art's 64px so pixel edges stay crisp at scale. */
.ql-avatar--large {
  width: 192px;
  height: 192px;
}

/* Editable in-game character name, shown under the avatar on the Profile
   Page (distinct from the client's real `name`, never shown/edited here). */
.ql-character-name {
  margin-top: 1rem;
}

.ql-character-name__button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  letter-spacing: 0.05em;
  background: transparent;
  border: none;
  color: var(--ql-text);
  cursor: pointer;
  padding: 0.25rem 0.4rem;
}

/* Out-specificities .ql-character-name__button's own `display` — same
   .ql-hidden-vs-component-display issue as .ql-character-name__form below. */
.ql-character-name__button.ql-hidden {
  display: none;
}

.ql-character-name__text {
  color: var(--ql-accent);
  text-shadow: 0 0 4px rgba(0, 185, 122, 0.5);
}

.ql-character-name__text--placeholder {
  color: var(--ql-muted);
  text-shadow: none;
  font-style: italic;
}

.ql-character-name__pencil {
  font-size: 1rem;
  color: var(--ql-muted);
}

.ql-character-name__button:hover .ql-character-name__text,
.ql-character-name__button:focus-visible .ql-character-name__text {
  color: var(--ql-accent-cyan);
}

.ql-character-name__button:hover .ql-character-name__pencil,
.ql-character-name__button:focus-visible .ql-character-name__pencil {
  color: var(--ql-accent-cyan);
}

.ql-character-name__button:focus-visible {
  outline: 3px solid var(--ql-accent-cyan);
  outline-offset: 2px;
}

.ql-character-name__form {
  display: inline-block;
}

/* Out-specificities .ql-character-name__form's own `display` so toggling
   .ql-hidden on this element actually hides it — see the .ql-hidden comment
   above for why the generic utility alone isn't enough once a component
   sets its own `display`. */
.ql-character-name__form.ql-hidden {
  display: none;
}

.ql-character-name__input {
  width: 14rem;
  max-width: 60vw;
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  text-align: center;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
}

.ql-character-name__input:focus-visible {
  outline: 3px solid var(--ql-accent-cyan);
  outline-offset: 2px;
}

.ql-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.stat-bar {
  text-align: left;
  margin-bottom: 1.25rem;
}

.stat-bar__label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.stat-bar__label {
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Numeric readout next to each label — a stat sitting at 0 previously had
   no legible value anywhere (just an empty track), unreadable for anyone
   glancing at the bar and invisible to screen readers. Cyan, per the
   Two-Accent Rule: this is informational readout, not a primary action. */
.stat-bar__value {
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  color: var(--ql-accent-cyan);
  text-shadow: 0 0 4px rgba(54, 255, 255, 0.6);
}

.stat-bar__track {
  border: 1px solid var(--ql-accent);
  background: var(--ql-bg);
  height: 1.3rem;
}

/* transform: scaleX(), not width — animating width triggers layout on
   every frame; scaleX is compositor-only. transform-origin keeps growth
   anchored to the left edge, matching how width: 0% -> N% used to read. */
.stat-bar__fill {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .stat-bar__fill {
    transition: none;
  }
}

.stat-bar--stamina .stat-bar__fill {
  background: var(--ql-accent);
  box-shadow: 0 0 8px rgba(0, 185, 122, 0.8);
}

.stat-bar--focus .stat-bar__fill {
  background: var(--ql-accent-cyan);
  box-shadow: 0 0 8px rgba(54, 255, 255, 0.8);
}

.stat-bar--health .stat-bar__fill {
  background: #f2c14e;
  box-shadow: 0 0 8px rgba(242, 193, 78, 0.7);
}

.ql-section {
  text-align: left;
  padding: 0 1.25rem;
}

/* `>` command-prompt prefix via CSS, not markup — keeps strings.js as the
   single source of truth for the actual text. Scoped to section-level
   headings only; the page/panel-level .ql-heading (e.g. "Today's Quest",
   "QuestLog Login") stays a centered title with no prefix — one tier
   quieter reads as "inside a running session," not "a new command." */
.ql-section-heading {
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow: 0 0 2px rgba(0, 185, 122, 0.5);
  margin: 0 0 0.75rem;
}

.ql-section-heading::before {
  content: '> ';
}

.ql-section-placeholder {
  color: var(--ql-muted);
  font-size: 1.05rem;
  margin: 0;
}

.ql-checkbox-group {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin-bottom: 1rem;
}

/* Vertical padding grows the clickable label area to the 44px touch-target
   minimum (measured 25px tall without it) without enlarging the checkbox
   glyph itself, which would read as oversized. Staff log power-ups on
   tablets during meal times — a stated speed/accuracy requirement, not a
   nice-to-have. Hover reverse-video-tints, echoing the TUI row-selection
   language used elsewhere (buttons, Daily Records rows). */
.ql-checkbox {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.3rem;
  cursor: pointer;
}

.ql-checkbox:hover {
  background: rgba(0, 185, 122, 0.15);
}

/* Visually hidden, not display:none — stays focusable/tabbable and
   readable by screen readers; .ql-checkbox-box below is the on-brand
   glyph a sighted user actually sees and clicks (native checkboxes can't
   be restyled past accent-color, which left a raw OS box in an otherwise
   fully bespoke UI). */
.ql-checkbox input[type='checkbox'] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.ql-checkbox-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  border: 2px solid var(--ql-accent);
  background: var(--ql-panel);
  color: var(--ql-bg);
  font-family: 'VT323', monospace;
  font-size: 0.8rem;
  line-height: 1;
}

.ql-checkbox input:checked + .ql-checkbox-box {
  background: var(--ql-accent);
}

.ql-checkbox input:checked + .ql-checkbox-box::after {
  content: 'X';
}

.ql-select {
  align-self: stretch;
  display: block;
  width: 100%;
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  padding: 0.4rem 0.5rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
}

.ql-meal-results {
  align-self: stretch;
  list-style: none;
  margin: 1rem 0;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--ql-accent);
}

.ql-meal-results:empty {
  border: none;
  margin: 0;
}

.ql-meal-result {
  border-bottom: 1px dotted rgba(0, 185, 122, 0.3);
}

.ql-meal-result:last-child {
  border-bottom: none;
}

.ql-meal-result label {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.5rem;
  cursor: pointer;
}

.ql-meal-result label:hover {
  background: rgba(0, 185, 122, 0.15);
}

.ql-meal-result input[type='radio'] {
  accent-color: var(--ql-accent);
  margin-top: 0.3rem;
  cursor: pointer;
}

.ql-meal-result__name {
  display: block;
}

.ql-meal-result__description {
  display: block;
  font-size: 0.9rem;
  color: var(--ql-muted);
}

/* Daily Records — a standalone terminal window below the main console
   (kept as a separate frame by explicit request, not merged into one
   continuous window), matching width so the two blocks line up. Monthly
   Review (added 2026-07-10) lives inside this same window as a second
   .ql-records-subsection, rather than getting its own frame — revised
   2026-07-10 from an earlier version with two stacked boxes, for one
   cleaner combined panel instead of two visually separate ones. */
.ql-history {
  text-align: left;
  width: 90%;
  max-width: 1320px;
  margin: 2rem auto 0;
  background: var(--ql-panel);
  border: 4px double var(--ql-accent);
  box-shadow: 0 0 40px rgba(0, 185, 122, 0.1);
  padding: 1.5rem 1.75rem;
}

/* Daily Records and Monthly Review titles use .ql-manual-heading (below),
   which supplies its own divider + top spacing (padding-top: 2rem) as part
   of the component — no extra subsection-level divider/margin needed here
   (revised 2026-07-10: this used to carry its own border-top + margin
   before both titles switched to .ql-manual-heading, which would have
   doubled the line above Monthly Review). */
.ql-records-subsection + .ql-records-subsection {
  margin-top: 0;
}

/* How This Works — plain text, no box. Same width/alignment as the rest
   of the page so it still lines up, but no border/panel fill/glow.
   Reverted 2026-07-10 back to this origin terminal style after a brief
   "Quest Briefing" poster departure read as disharmonious with the rest
   of the page. */
/* Lifted above the whole-viewport CRT scanline (body::before, z-index 999,
   see Elevation) via its own stacking context — the scanline's horizontal
   lines were making this section's dense body text hard to read. Scoped to
   just this section (not a page-wide opt-out like Data Export/Settings/
   Manual use) since the game panels above it and Contact below it keep the
   scanline unchanged. Real bug, fixed same day: this z-index also used to
   beat .ql-topbar's, so once this section scrolled up under the fixed nav
   its text painted through it instead of underneath — .ql-topbar is now
   1001, above this. */
#ql-how-it-works {
  position: relative;
  z-index: 1000;
  text-align: left;
  width: 90%;
  max-width: 1320px;
  margin: 2rem auto 0;
  padding: 0 1.75rem;
}

/* Contact — solid Terminal Green panel (revised 2026-07-10), reverse-video
   at the section scale: the same "fill flips to the accent color, text
   flips to Void Black" language as buttons/Daily-Records-row hover, just
   applied to a whole section instead of a hover state. Still the same
   width/alignment as the rest of the page (not full-bleed), just filled
   instead of transparent. */
#ql-contact {
  text-align: left;
  width: 90%;
  max-width: 1320px;
  margin: 2rem auto 0;
  padding: 2.5rem 1.75rem;
  background: var(--ql-accent);
}

/* Contact row layout, revised 2026-07-10 — title and body side by side
   instead of stacked, matching How This Works' "adjust the layout, keep
   the terminal style" treatment. No divider/prompt line here (unlike
   How This Works/Daily Records/Monthly Review's .ql-manual-heading) —
   the reference layout for this one is a clean two-up row, title as the
   visual anchor rather than a "section start" marker. Flexbox with
   space-between (revised again 2026-07-11, was a grid `auto 1fr` column
   — that left the body text floating in a wide column with a lot of dead
   green space after it) so the body block sits flush against the right
   edge instead of just starting somewhere left-of-center. */
.ql-contact-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 3.5rem;
}

.ql-contact-row__title {
  font-family: 'VT323', monospace;
  font-size: 3rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ql-bg);
  margin: 0;
}

.ql-contact-row__body p {
  font-size: 1.3rem;
  line-height: 1.6;
  color: var(--ql-bg);
  margin: 0;
  max-width: 60ch;
}

/* How This Works' title, bigger than the standard .ql-manual-heading size
   used by Contact/Daily Records/Monthly Review — this is the page's most
   important explainer, so it gets more weight than the others. */
#ql-how-it-works-heading.ql-manual-heading {
  font-size: 2.6rem;
}

/* Two-column layout kept from the "Quest Briefing" exploration (pull-quote
   + supporting text on the left, a paragraph stack with inline-highlighted
   phrases on the right) but recolored into the terminal system: Terminal
   Green instead of coral, VT323 (inherited from body) instead of a system
   sans, reverse-video highlight (Terminal Green fill / Void Black text)
   matching the same emphasis language as buttons and Daily Records rows,
   rather than a one-off accent color. */
.ql-how-it-works-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 3rem;
  margin-top: 1.5rem;
}

.ql-how-it-works__pull {
  font-size: 1.8rem;
  line-height: 1.3;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.5);
  margin: 0 0 1.2rem;
}

.ql-how-it-works__support {
  font-size: 1.3rem;
  line-height: 1.6;
  color: var(--ql-text);
  margin: 0 0 1rem;
  max-width: 52ch;
}

.ql-how-it-works__stack p {
  font-size: 1.3rem;
  line-height: 1.6;
  color: var(--ql-text);
  margin: 0 0 1.1rem;
}

.ql-how-it-works__highlight {
  color: var(--ql-bg);
  background: var(--ql-accent);
  font-weight: 700;
  padding: 0 0.25rem;
  box-decoration-break: clone;
}

.ql-how-it-works__header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1.5rem;
}

.ql-how-it-works__manual-link {
  flex-shrink: 0;
  margin-bottom: 0.75rem;
  font-size: 1.2rem;
}

/* "Manual" title format: a plain divider rule marking the start of the
   section, a dim `$ man ...` shell-prompt line, then the heading itself —
   no `>` prefix here (unlike .ql-section-heading elsewhere), since the
   prompt line above it already carries that "command" read. The divider
   is absolutely positioned (top:0 of the heading's own padding box) so it
   can render visually above the ::before prompt line despite ::after
   normally painting after an element's real content. */
.ql-manual-heading {
  position: relative;
  padding-top: 2rem;
  margin: 0 0 0.75rem;
  font-family: 'VT323', monospace;
  font-size: 1.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.6);
}

.ql-manual-heading::before {
  content: '$ man blip-up/how-this-works';
  display: block;
  margin: 0 0 0.5rem;
  font-size: 1rem;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--ql-muted);
  text-shadow: none;
}

#ql-history-heading.ql-manual-heading::before {
  content: '$ man blip-up/daily-records';
}

#ql-monthly-review-heading.ql-manual-heading::before {
  content: '$ man blip-up/monthly-review';
}

.ql-manual-heading::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid rgba(0, 185, 122, 0.35);
}

.ql-history input[type='date'] {
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
}

/* Chrome's native calendar icon is drawn dark, meant for light input
   backgrounds — invisible against this now-dark field without inverting. */
.ql-history input[type='date']::-webkit-calendar-picker-indicator {
  filter: invert(1);
}

.ql-history-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--ql-accent);
}

.ql-history-list:empty {
  border: none;
  margin: 0;
}

/* Reverse-video row hover, matching the button/checkbox language — a real
   TUI file-list selection, not a border/underline highlight. */
.ql-history-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px dotted rgba(0, 185, 122, 0.3);
}

.ql-history-item:last-child {
  border-bottom: none;
}

.ql-history-item:hover {
  background: var(--ql-accent);
  color: var(--ql-bg);
}

.ql-history-item:hover .ql-history-item__type,
.ql-history-item:hover .ql-history-item__points,
.ql-history-item:hover .ql-history-item__time {
  color: var(--ql-bg);
}

.ql-history-item__type {
  display: block;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
}

.ql-history-item__detail {
  display: block;
}

.ql-history-item__meta {
  text-align: right;
  flex-shrink: 0;
}

.ql-history-item__points {
  display: block;
  color: var(--ql-accent);
}

.ql-history-item__time {
  display: block;
  font-size: 0.9rem;
  color: var(--ql-muted);
}

/* Monthly Review reuses the .ql-history panel/box styling as-is (same
   double-border frame, same spacing) — only the month input and table
   below are new. */
.ql-history input[type='month'] {
  font-family: 'VT323', monospace;
  font-size: 1.1rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
}

.ql-history input[type='month']::-webkit-calendar-picker-indicator {
  filter: invert(1);
}

.ql-monthly-review-table {
  width: 100%;
  margin: 1rem 0 0;
  border-collapse: collapse;
}

.ql-monthly-review-table th,
.ql-monthly-review-table td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  border-bottom: 1px dotted rgba(0, 185, 122, 0.3);
}

.ql-monthly-review-table th {
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
  border-bottom: 1px solid var(--ql-accent);
}

/* Stat columns line up on their digits, matching the stat bars' own
   numeric display elsewhere on the page. */
.ql-monthly-review-table td:not(:first-child) {
  font-variant-numeric: tabular-nums;
}

.ql-monthly-review-table tbody tr:last-child td {
  border-bottom: none;
}

/* Same reverse-video row hover as .ql-history-item, for one consistent
   "selectable row" language across both tables. */
.ql-monthly-review-table tbody tr:hover {
  background: var(--ql-accent);
  color: var(--ql-bg);
}

/* Totals row — set apart from the daily rows with a solid top border (vs.
   the dotted row separators above) and Terminal Green bold text, the same
   "this number matters more" language as the stat bar values. */
.ql-monthly-review-table tfoot th,
.ql-monthly-review-table tfoot td {
  border-top: 2px solid var(--ql-accent);
  border-bottom: none;
  font-weight: 700;
  color: var(--ql-accent);
  text-shadow: 0 0 4px rgba(0, 185, 122, 0.5);
}

.ql-monthly-review-table tfoot th {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.85rem;
}

/* Table (+ totals) on the left, trend chart on the right — same two-column
   console layout as the main Today's Quest/Log split (.ql-layout), reusing
   flexbox instead of grid so the table column naturally reclaims the full
   width when the chart column is hidden (empty month, no data to plot). */
.ql-monthly-review-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 1.75rem;
  margin-top: 1rem;
}

.ql-monthly-review-table-col {
  flex: 1 1 380px;
  min-width: 0;
}

.ql-monthly-review-chart-col {
  flex: 1 1 320px;
  min-width: 0;
  padding-left: 1.75rem;
  border-left: 1px solid rgba(0, 185, 122, 0.35);
}

.ql-monthly-review-table-col .ql-monthly-review-table {
  margin-top: 0;
}

/* Positioning context for the HTML tooltip overlay, which sits as a
   sibling of the <svg> rather than inside it (SVG can't host arbitrary
   HTML/CSS-styled boxes the way a plain div can). */
.ql-monthly-review-chart-wrap {
  position: relative;
}

.ql-monthly-review-chart {
  display: block;
  width: 100%;
  height: auto;
}

.ql-monthly-review-chart .ql-chart-hit {
  fill: transparent;
  cursor: pointer;
}

/* Lines/dots never intercept the mouse, so hovering exactly on a marker or
   a stroked line segment still falls through to the day-band .ql-chart-hit
   rect underneath (see app.js) — otherwise a solid-painted dot/line would
   grab the pointer event and the tooltip would drop out right where you'd
   expect it to be most precise. */
.ql-monthly-review-chart .ql-chart-line,
.ql-monthly-review-chart .ql-chart-dot {
  pointer-events: none;
}

/* Follows the cursor over a day's column, showing that day's date plus
   whichever stat(s) are closest to the cursor's height (app.js groups by
   actual value, so more than one stat only ever appears together when
   they truly share a value/y position that day). No border — still
   unboxed in spirit — but a mostly-opaque backing wash keeps the
   left-aligned text legible over the chart's own lines/gridlines. */
.ql-chart-tooltip {
  position: absolute;
  z-index: 10;
  transform: translate(-50%, calc(-100% - 10px));
  padding: 0.35rem 0.6rem;
  background: rgba(4, 18, 11, 0.82);
  text-align: left;
  font-family: 'VT323', monospace;
  font-size: 1.05rem;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
}

.ql-chart-tooltip__date {
  color: var(--ql-muted);
  font-size: 0.9rem;
  margin-bottom: 0.1rem;
}

/* One shared color for all 3 stats here — the tooltip only ever shows
   more than one row when they share a value, so per-stat colors aren't
   needed to tell rows apart the way the chart lines/legend still do. */
.ql-chart-tooltip__row {
  color: var(--ql-text);
}

.ql-monthly-review-chart .ql-chart-grid {
  stroke: rgba(0, 185, 122, 0.25);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.ql-monthly-review-chart .ql-chart-axis {
  stroke: rgba(0, 185, 122, 0.5);
  stroke-width: 1;
}

.ql-monthly-review-chart .ql-chart-axis-label {
  font-family: 'VT323', monospace;
  font-size: 11px;
  fill: var(--ql-muted);
}

.ql-monthly-review-chart .ql-chart-line {
  fill: none;
  stroke-width: 1.5;
}

.ql-monthly-review-chart .ql-chart-line--stamina {
  stroke: var(--ql-accent);
}

.ql-monthly-review-chart .ql-chart-line--focus {
  stroke: var(--ql-accent-cyan);
}

.ql-monthly-review-chart .ql-chart-line--health {
  stroke: #f2c14e;
}

.ql-monthly-review-chart .ql-chart-dot {
  shape-rendering: crispEdges;
}

.ql-monthly-review-chart .ql-chart-dot--stamina {
  fill: var(--ql-accent);
}

.ql-monthly-review-chart .ql-chart-dot--focus {
  fill: var(--ql-accent-cyan);
}

.ql-monthly-review-chart .ql-chart-dot--health {
  fill: #f2c14e;
}

.ql-monthly-review-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  font-size: 0.9rem;
}

.ql-monthly-review-legend__item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.ql-monthly-review-legend__item::before {
  content: '';
  display: inline-block;
  width: 0.65rem;
  height: 0.65rem;
}

.ql-monthly-review-legend__item--stamina::before {
  background: var(--ql-accent);
}

.ql-monthly-review-legend__item--focus::before {
  background: var(--ql-accent-cyan);
}

.ql-monthly-review-legend__item--health::before {
  background: #f2c14e;
}

@media (max-width: 900px) {
  .ql-monthly-review-chart-col {
    padding-left: 0;
    border-left: none;
    flex-basis: 100%;
  }
}

/* Real bug, not just a size tweak: .ql-page is a flex item of `body`
   (align-items: center), so with no explicit width it shrink-wraps to
   its own content's fit-content size instead of filling the viewport.
   .ql-plain-page's `width: 90%` below then resolves against THAT
   shrunk, content-dependent size rather than the page's real available
   width — a fragile pattern .ql-panel/.ql-history also technically rely
   on, but their wide titlebar/nav content happened to keep the
   equilibrium width large enough to look correct. Removing the
   frame/titlebar/nav (this page's own "unframed" revision, same date)
   left only narrower content, so the same 90% rule silently resolved to
   a visibly smaller page. Fix: give .ql-page a definite width via
   `align-self: stretch` (scoped to this page only, via the body class
   already used for the scanline opt-out — every other page keeps
   .ql-page's default shrink-wrap, which their narrower .ql-panel boxes
   depend on for centering). */
body.ql-no-page-scanline .ql-page {
  align-self: stretch;
}

/* Data Export page (revised 2026-07-12): a staff-facing data/reporting
   tool, not a "terminal window" — unframed (no .ql-panel double border,
   no titlebar, no [ Today's Quest ] nav row), plain left-aligned text
   throughout instead of .ql-panel's centered default. Sized the same as
   .ql-panel--wide (90% / max-width 1320px) so it still lines up with the
   rest of the site, just without the frame. */
.ql-plain-page {
  width: 80%;
  max-width: 1320px;
  margin: 1.5rem auto 2rem;
  text-align: left;
}

.ql-heading--left {
  text-align: left;
}

.ql-plain-section {
  margin-top: 2.5rem;
}

.ql-plain-section:first-of-type {
  margin-top: 1.5rem;
}

/* Section title without the "$ man ..." shell-prompt line .ql-manual-heading
   uses elsewhere — that's exactly the "cryptic terminal chrome" this page
   is dropping for a non-technical staff audience. Keeps the same weight
   (VT323 + glow) and a plain top divider for scannability between
   sections, just without the prompt text above it. */
.ql-section-title {
  position: relative;
  padding-top: 1.5rem;
  margin: 0 0 0.75rem;
  font-family: 'VT323', monospace;
  font-size: 1.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.6);
}

.ql-section-title::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid rgba(0, 185, 122, 0.35);
}

/* Data Export page: Historical Trend controls + Activity Breakdown bars.
   The trend chart itself reuses .ql-monthly-review-chart's CSS scope
   directly (see data-export.html) rather than duplicating ~80 lines of
   identical axis/line/dot rules for a second chart that looks the same. */
.ql-trend-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.5rem;
  margin-bottom: 1rem;
}

/* Pushes just the Export button flush to the row's right edge — the
   classic flex "auto margin" trick — while From/To/Daily-Monthly stay
   grouped together on the left, unlike `justify-content: space-between`
   which would spread every item apart evenly. */
#ql-export-button {
  margin-left: auto;
}

.ql-field--inline {
  margin-bottom: 0;
}

.ql-field--inline .ql-select,
.ql-field--inline input {
  width: auto;
  min-width: 10rem;
  margin-bottom: 0;
}

/* Chrome's native calendar icon is drawn dark, meant for light input
   backgrounds — invisible against this dark field without inverting
   (same fix Daily Records/Monthly Review already have, scoped to
   .ql-history — this page isn't inside that container, so it needs its
   own copy). Without this, the picker button is effectively invisible,
   which reads as "you can only type the date." */
.ql-plain-page input[type='date']::-webkit-calendar-picker-indicator,
.ql-plain-page input[type='month']::-webkit-calendar-picker-indicator {
  filter: invert(1);
}

/* Caps the Historical Trend chart's width so it doesn't stretch to fill
   the full section — scoped to this page's own chart by id, not the
   shared .ql-monthly-review-chart-wrap class, so Monthly Review's chart
   on index.html is untouched. */
#ql-trend-chart-wrap {
  max-width: 640px;
}

.ql-toggle-group {
  display: flex;
  border: 1px solid var(--ql-accent);
}

.ql-toggle-button {
  font-family: 'VT323', monospace;
  font-size: 1.05rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.45rem 1rem;
  background: transparent;
  color: var(--ql-text);
  border: none;
  cursor: pointer;
}

.ql-toggle-button + .ql-toggle-button {
  border-left: 1px solid var(--ql-accent);
}

.ql-toggle-button:hover {
  color: var(--ql-accent);
}

.ql-toggle-button--active,
.ql-toggle-button--active:hover {
  background: var(--ql-accent);
  color: var(--ql-bg);
}

/* Fixed 2 columns, not auto-fit — this app is desktop-only (no mobile
   reflow, see AGENTS.md/MEMORY.md), and a fixed column count is also
   what guarantees Obstacles Logged (the 3rd item, auto-placed onto its
   own row by the grid) ends up exactly as wide as Meals Logged above it:
   one grid keeps its column tracks identical across every row it lays
   out, which separate grid containers per row couldn't guarantee. */
.ql-breakdown-groups {
  display: grid;
  grid-template-columns: repeat(2, minmax(240px, 1fr));
  gap: 1.75rem;
  margin-top: 0.5rem;
}

.ql-breakdown-group__heading {
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ql-muted);
  margin: 0 0 0.6rem;
}

.ql-breakdown-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.ql-breakdown-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 2fr auto;
  align-items: center;
  gap: 0.6rem;
}

.ql-breakdown-item__label {
  font-size: 1.15rem;
  color: var(--ql-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ql-breakdown-item__track {
  display: block;
  height: 0.7rem;
  background: var(--ql-bg);
  border: 1px solid rgba(0, 185, 122, 0.35);
}

.ql-breakdown-item__fill {
  display: block;
  height: 100%;
  background: var(--ql-accent);
}

.ql-breakdown-item__value {
  font-variant-numeric: tabular-nums;
  font-size: 1.1rem;
  color: var(--ql-muted);
  min-width: 1.5ch;
  text-align: right;
}

/* Meals Logged rows are clickable (Power-Ups/Obstacles aren't — there's
   no finer detail to drill into for those) — expands to the specific
   foods logged under that meal type. Kept as a plain clickable <li>
   (role="button", not a real <button>) so it stays a grid item lined up
   with the label/track/value columns above it without an extra wrapping
   box breaking the grid; keyboard support (Enter/Space) added in JS. */
.ql-breakdown-item--expandable {
  cursor: pointer;
}

.ql-breakdown-item--expandable:hover .ql-breakdown-item__label {
  color: var(--ql-accent);
}

.ql-breakdown-item__caret {
  display: inline-block;
  width: 1ch;
  margin-right: 0.3rem;
  color: var(--ql-muted);
}

.ql-breakdown-item__detail {
  grid-column: 1 / -1;
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0.5rem 0 0 1.6rem;
  border-top: 1px dotted rgba(0, 185, 122, 0.3);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 1rem;
  color: var(--ql-text);
}

.ql-breakdown-item__detail li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}

/* Same fix as .ql-modal-overlay.ql-hidden (see the comment on .ql-hidden
   at the top of this file): .ql-breakdown-item__detail's own
   `display: flex` ties with .ql-hidden's `display: none` at equal
   specificity, and would win on source order alone since it's declared
   later in the file. This compound selector wins regardless of order. */
.ql-breakdown-item__detail.ql-hidden {
  display: none;
}

/* Settings page (2026-07-12): Facts & Guidance. Same "unframed staff tool"
   treatment as Data Export (.ql-plain-page). Scoring Rules (an editable
   points-per-item admin panel) shipped alongside this originally but was
   pulled from the UI the same day after a conversation with a social
   worker — see MEMORY.md. The backend (scoring_rules table, settings
   routes/service, and scoring-service.js's live-rules wiring) is left in
   place, unused, so re-adding the UI later needs no backend work; only
   the markup/JS/strings/CSS specific to that section were removed. */
.ql-settings-intro {
  color: var(--ql-muted);
  font-size: 1.05rem;
  margin: 0 0 1rem;
}

.ql-settings-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.ql-settings-row__status {
  font-family: 'VT323', monospace;
  font-size: 1rem;
  min-width: 4ch;
}

.ql-settings-row__status--success {
  color: var(--ql-accent);
}

.ql-settings-row__status--error {
  color: var(--ql-accent-cyan);
}

/* Facts & Guidance: one row per fact — name is fixed (see
   settings-service.js), only the guidance wording is editable. */
.ql-settings-list--facts {
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}

.ql-settings-fact-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.75rem;
}

.ql-settings-fact-row__name {
  flex: 0 0 10rem;
  font-size: 1.15rem;
  color: var(--ql-text);
  padding-top: 0.4rem;
}

.ql-settings-fact-row__message {
  flex: 1 1 20rem;
  font-family: 'VT323', monospace;
  font-size: 1.05rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--ql-accent);
  border-radius: 0;
  background: var(--ql-bg);
  color: var(--ql-text);
  resize: vertical;
}

.ql-settings-add-fact {
  padding-top: 1.5rem;
  border-top: 1px solid rgba(0, 185, 122, 0.35);
}

.ql-modal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.75);
  z-index: 1002;
}

/* Combined selector so this always wins over .ql-modal-overlay's own
   `display: flex`, regardless of which rule appears later in the file —
   two equal-specificity rules on the same element would otherwise let
   source order silently decide (bug: modal stayed visible even with this
   class applied, since .ql-modal-overlay was declared after .ql-hidden). */
.ql-modal-overlay.ql-hidden {
  display: none;
}

/* Same double-border terminal-window language as the main frame — the
   earlier clip-path stepped-corner card (plus a separate offset-shadow
   duplicate element to fake depth) was exactly the kind of "flat CSS
   shape faking pixel art" the redesign was asked to fix. */
.ql-modal-wrap {
  width: 85%;
  max-width: 320px;
}

.ql-modal {
  background: var(--ql-panel);
  border: 4px double var(--ql-accent);
  box-shadow: 0 0 40px rgba(0, 185, 122, 0.15);
  padding: 1.75rem;
  text-align: left;
}

.ql-modal-flourish {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 1rem;
}

.ql-modal-flourish span {
  display: inline-block;
}

.ql-modal-flourish span:first-child {
  width: 28px;
  height: 4px;
  background: var(--ql-accent);
}

.ql-modal-flourish span:not(:first-child) {
  width: 4px;
  height: 4px;
  background: var(--ql-accent-cyan);
}

.ql-modal-message {
  color: var(--ql-text);
  font-family: 'VT323', monospace;
  font-size: 1.25rem;
  line-height: 1.4;
  margin: 0 0 1.25rem;
}

.ql-modal-divider {
  border: none;
  border-top: 1px dotted rgba(0, 185, 122, 0.4);
  margin: 0 0 1.25rem;
}

.ql-logout {
  margin-top: 1.75rem;
}

/* Cross-page nav row (index/profile/boss) — same bracket-button language
   as every other action in the app, just anchored under the titlebar. */
.ql-nav {
  display: flex;
  gap: 1rem;
  padding: 0 1.5rem 1.25rem;
}

/* Generic horizontal padding wrapper for page content that isn't already
   inside .ql-layout (which carries its own padding) — used by the
   Profile/Boss pages, which don't have a 2-column quest/log split. */
.ql-page-content {
  padding: 0 1.75rem 0.5rem;
}

.ql-section--center {
  text-align: center;
}

/* Food/Boss Badges (PRD 13.1) — small bracketed tiles, one per earned
   badge. Deliberately plain (single 1px border, no glow on the tile itself)
   so a wall of badges doesn't visually compete with the outer
   double-bordered window. */
.ql-badge-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

/* Boss Badges — text-only (a boss name + level, not a food). */
.ql-badge {
  border: 1px solid var(--ql-accent);
  padding: 0.4rem 0.6rem;
  font-size: 0.95rem;
}

/* Food Badges — icon stacked above the name. Two tiers (see
   public/js/food-icons.js and DESIGN.md "Food Badge Icon Colors"):
   .ql-food-icon--category is the hand-drawn single-color pixel icon,
   colored via a per-category modifier class + CSS currentColor. A bare
   .ql-food-icon (no --category class) is a specific per-food icon sourced
   with its own original colors — those are left alone entirely, no fill
   override, no glow, so their own art renders as-is. Everything else on
   the tile (border, name text) stays the standard Terminal Green/Frost
   Text regardless of tier. */
.ql-food-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  width: 76px;
  border: 1px solid var(--ql-accent);
  padding: 0.5rem 0.35rem 0.4rem;
  text-align: center;
}

.ql-food-badge__name {
  font-size: 0.8rem;
  line-height: 1.15;
  word-break: break-word;
}

.ql-food-icon {
  width: 32px;
  height: 32px;
}

.ql-food-icon svg {
  width: 100%;
  height: 100%;
}

.ql-food-icon--category {
  color: var(--ql-accent);
  filter: drop-shadow(0 0 3px currentColor);
}

.ql-food-icon--category svg {
  fill: currentColor;
}

.ql-food-icon--category.ql-food-icon--vegetable {
  color: var(--ql-accent);
}

.ql-food-icon--category.ql-food-icon--dairy {
  color: var(--ql-accent-cyan);
}

.ql-food-icon--category.ql-food-icon--fruit {
  color: var(--ql-food-fruit);
}

.ql-food-icon--category.ql-food-icon--protein {
  color: var(--ql-food-protein);
}

.ql-food-icon--category.ql-food-icon--grain {
  color: var(--ql-food-grain);
}

.ql-food-icon--category.ql-food-icon--generic {
  color: var(--ql-accent);
}

/* Victory pop-up (defeated-state, 2026-07-15, moved from an inline banner
   to a real modal same day) — a defeated boss previously reused the exact
   same layout/copy as an active fight with only a small text line marking
   the win (`/impeccable critique` P2). Reuses the app's one modal shape
   (`.ql-modal-overlay`/`.ql-modal-wrap`/`.ql-modal`, same as the obstacle
   guidance pop-up, including its dialog semantics/focus-trap/Escape
   pattern — see boss.js) rather than a second bespoke popup treatment.
   Ties the win to the badge the client actually just earned (visible
   later on Profile) — the stamp shows the same boss name/epithet/level as
   the title on the page behind it, tilted like a freshly-applied seal. */
.ql-boss-victory-modal {
  text-align: center;
}

.ql-boss-badge-stamp {
  display: inline-flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 200px;
  border: 3px double var(--ql-accent);
  background: var(--ql-bg);
  padding: 1rem 1.25rem 0.85rem;
  transform: rotate(-1.4deg);
  box-shadow: 0 0 22px rgba(0, 185, 122, 0.25);
}

.ql-boss-badge-stamp__eyebrow {
  font-size: 0.95rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
  text-shadow: 0 0 4px rgba(54, 255, 255, 0.6);
}

.ql-boss-badge-stamp__name {
  font-size: 1.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.6);
  line-height: 1.15;
}

.ql-boss-badge-stamp__level {
  font-size: 1rem;
  letter-spacing: 0.08em;
  color: var(--ql-muted);
}

/* Terminal print-line treatment, not prose — same "> " prompt-glyph
   convention as .ql-section-heading. display:inline-block centers the
   line as a unit inside the centered modal while keeping the text itself
   left-aligned, like a real readout rather than a stretched centered
   sentence. */
.ql-boss-victory-log {
  display: inline-block;
  text-align: left;
  font-size: 1.1rem;
  color: var(--ql-accent);
  margin: 1.1rem auto 0;
}

.ql-boss-victory-log::before {
  content: '> ';
}

.ql-boss-victory-log::after {
  content: '';
  display: inline-block;
  width: 0.55em;
  height: 1em;
  margin-left: 3px;
  background: var(--ql-accent);
  vertical-align: text-bottom;
  animation: ql-cursor-blink 1s steps(1) infinite;
}

@keyframes ql-cursor-blink {
  50% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ql-boss-victory-log::after {
    animation: none;
  }
}

/* Dims + desaturates the same boss art rather than requiring a second,
   defeated-specific illustration asset — a stilled, spent look instead of
   the mid-fight pose, paired with the banner above leading with the win. */
.ql-boss-content--defeated #ql-boss-figure {
  filter: grayscale(1) brightness(0.55);
}

/* Boss title (PRD 13.2/13.3) — highlights the boss's own proper name
   ("Ignis") over its epithet ("the Overheater") and level, rather than
   rendering all three as one flat, equally-weighted string. No `>` prefix
   here (unlike .ql-section-heading elsewhere) — this reads as a title
   card, not a command. */
.ql-boss-title {
  margin: 0 0 1rem;
}

.ql-boss-title__row {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.ql-boss-title__name {
  font-size: 2rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 10px rgba(0, 185, 122, 0.7);
  margin: 0;
}

.ql-boss-title__epithet {
  font-size: 1.4rem;
  color: var(--ql-muted);
}

.ql-boss-title__level {
  display: block;
  font-size: 1.1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ql-muted);
  margin-top: 0.35rem;
}

/* The health label row, health bar, and target text stay left-aligned even
   though the boss title/avatar above them are centered (.ql-section--center
   sets align-items:center on the whole section) — align-self overrides the
   container's alignment for just these 3 flex items. Scoped under
   #ql-boss-content since .stat-bar__label-row is also used, unscoped, by
   Today's Quest's stat bars (align-self would be inert there regardless,
   since that page's layout isn't a flex container, but scoping keeps the
   intent explicit). */
#ql-boss-content .stat-bar__label-row,
#ql-boss-health,
#ql-boss-target {
  align-self: flex-start;
}

/* Boss health bar (PRD 13.2/13.3) — one box per health_bar_size slot.
   Filled boxes glow like the stat-bar fills; a depleted slot reads as an
   empty outline, echoing the paper sheet's "crossed-out slot" language. */
.ql-boss-health {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0.75rem 0 1rem;
}

.ql-boss-health__box {
  width: 1.1rem;
  height: 1.1rem;
  border: 1px solid var(--ql-muted);
}

.ql-boss-health__box--filled {
  border-color: var(--ql-accent);
  background: var(--ql-accent);
  box-shadow: 0 0 6px rgba(0, 185, 122, 0.7);
}

/* Attack & Defence / How to Defeat / Cheat Code copy — real hierarchy bug:
   these four <p>s had no rule of their own at all, so they inherited
   body's raw 20px with the browser's default (tight) line-height. First
   fix shrank them to 1.05rem to land below .ql-section-heading — reverted
   same day per direct feedback: the ask was more hierarchy AND more
   readability, and shrinking the body copy works against readability.
   Body text keeps its natural size here; hierarchy instead comes from
   making the heading above it more assertive (see #ql-attack-heading/
   #ql-howto-heading below) and from the cyan "Attack:"/"Defence:"/
   "Cheat Code:" labels (.ql-boss-text-label) standing out from the
   sentence that follows. line-height 1.6 replaces the inherited "normal"
   (too tight for 2-4 line paragraphs); margin gives each paragraph
   deliberate rhythm instead of relying on the browser's default <p>
   spacing. */
#ql-boss-attack-text,
#ql-boss-defence-text,
#ql-boss-howto-text,
#ql-boss-cheat-text {
  line-height: 1.6;
  color: var(--ql-text);
  margin: 0 0 1rem;
}

#ql-boss-attack-text:last-child,
#ql-boss-defence-text:last-child,
#ql-boss-howto-text:last-child,
#ql-boss-cheat-text:last-child {
  margin-bottom: 0;
}

/* Inline "Attack:"/"Defence:"/"Cheat Code:" label at the start of each
   sentence (built in boss.js as a <strong>, not string-concatenated text,
   so it can be styled independently) — Signal Cyan per the Two-Accent
   Rule (this is an informational readout, same role as .stat-bar__value,
   not a primary action, so it stays green). No font-weight bump: VT323
   only ships a regular weight, so `bold` here would trigger the browser's
   synthetic-bold faux-weight, which looks chunky/blurry on a pixel font —
   color and glow alone carry the emphasis instead, same restraint
   .stat-bar__value already uses. */
.ql-boss-text-label {
  color: var(--ql-accent-cyan);
  text-shadow: 0 0 4px rgba(54, 255, 255, 0.5);
}

/* Attack & Defence / How to Defeat headings bumped up from the shared
   1.1rem .ql-section-heading size — scoped to these two ids, not the
   class itself, since .ql-section-heading is reused site-wide (Today's
   Quest, Profile, Settings) and this bump is specific to restoring
   hierarchy over Boss Battle's own (deliberately full-size) body text.
   1.3rem reuses the same step .stat-bar__label already uses elsewhere on
   this exact page, rather than inventing a new value; still comfortably
   below the 1.5rem page heading and the 2rem boss name above it. */
#ql-attack-heading,
#ql-howto-heading {
  font-size: 1.3rem;
}

/* Top navigation bar — a site-wide utility strip (logo, language toggle,
   How This Works / Data / Contact), separate from .ql-nav (the in-panel
   row for moving between the 3 main app pages). Only present on
   authenticated pages. Fixed to the viewport (not absolute) so it stays
   visible while scrolling — pages using it get extra top clearance via
   .ql-has-topbar below so it never overlaps the panel underneath it. */
/* Panel Surface (not Void Black) so the bar reads as its own layer above
   scrolled content, the same trick every other terminal window already
   uses to stand apart from the page background — plus a bottom border so
   the edge is unambiguous now that content passes underneath it. */
/* Real bug: How This Works' scanline-escape fix (#ql-how-it-works,
   z-index: 1000, see Elevation) sat higher than this bar's old z-index of
   10, so once that section scrolled up underneath the fixed topbar, its
   text painted through the nav instead of under it. Bumped to 1001 —
   above any content that's been deliberately lifted above the scanline
   (currently that section and the login page's Email/Password fields,
   both at 1000) — so the nav always wins against ordinary page content,
   lifted or not. Modals (.ql-modal-overlay, .ql-manual-changes-overlay)
   are bumped to 1002 in turn, so they still cover the topbar too. */
.ql-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 1.75rem;
  background: var(--ql-panel);
  border-bottom: 1px solid var(--ql-accent);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
  z-index: 1001;
}

body.ql-has-topbar {
  padding-top: 5.5rem;
}

.ql-topbar__logo {
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.5);
}

.ql-topbar__links {
  display: flex;
  align-items: center;
  gap: 1.75rem;
}

.ql-topbar__lang {
  font-family: 'VT323', monospace;
  font-size: 1rem;
  letter-spacing: 0.06em;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.35rem 0;
}

.ql-topbar__lang-sep {
  color: rgba(216, 245, 232, 0.5);
}

/* Non-current language stays dim like every other topbar link at rest, and
   brightens on hover to signal it's the click target. The current language
   stays lit permanently — same "you are here" language as the in-panel nav's
   aria-current fill and the Manual sidebar's scrollspy accent. */
.ql-topbar__lang-option {
  color: rgba(216, 245, 232, 0.5);
}

.ql-topbar__lang:hover .ql-topbar__lang-option,
.ql-topbar__lang:focus-visible .ql-topbar__lang-option {
  color: var(--ql-text);
}

.ql-topbar__lang-option.is-current {
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 6px rgba(0, 185, 122, 0.6);
}

.ql-topbar__lang:hover .ql-topbar__lang-option.is-current,
.ql-topbar__lang:focus-visible .ql-topbar__lang-option.is-current {
  color: var(--ql-accent);
}

/* Links share one hover treatment: text flips to Terminal Green with the
   same glow formula used everywhere else (Phosphor Glow Rule), plus a
   green gradient "sweep" behind the text that animates in on hover — a
   CRT-phosphor-igniting read, not a generic underline/tint. */
.ql-topbar__link {
  position: relative;
  display: inline-block;
  font-family: 'VT323', monospace;
  font-size: 1rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  background: transparent;
  border: none;
  cursor: pointer;
  color: rgba(216, 245, 232, 0.5);
  padding: 0.35rem 0;
  transition:
    color 0.25s ease-out,
    text-shadow 0.25s ease-out;
}

.ql-topbar__link::before {
  content: '';
  position: absolute;
  inset: -0.3rem -0.6rem;
  background: linear-gradient(
    100deg,
    transparent 20%,
    rgba(0, 185, 122, 0.35) 50%,
    transparent 80%
  );
  background-size: 220% 100%;
  background-position: 130% 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease-out;
}

.ql-topbar__link:hover,
.ql-topbar__link:focus-visible,
.ql-topbar__dropdown:hover .ql-topbar__link--data,
.ql-topbar__dropdown:focus-within .ql-topbar__link--data {
  color: var(--ql-accent);
  text-shadow:
    0 0 2px var(--ql-accent),
    0 0 8px rgba(0, 185, 122, 0.7);
}

.ql-topbar__link:hover::before,
.ql-topbar__link:focus-visible::before,
.ql-topbar__dropdown:hover .ql-topbar__link--data::before,
.ql-topbar__dropdown:focus-within .ql-topbar__link--data::before {
  opacity: 1;
  animation: ql-topbar-sweep 0.9s ease-out;
}

@keyframes ql-topbar-sweep {
  from {
    background-position: 130% 0;
  }
  to {
    background-position: -30% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ql-topbar__link::before {
    transition: none;
    animation: none !important;
  }
}

.ql-topbar__dropdown {
  position: relative;
}

/* Small single-border terminal window for the Data submenu — one tier
   quieter than the main double-border windows, matching .ql-badge's
   "subordinate to the outer window" logic. */
.ql-topbar__menu {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.5rem;
  min-width: 170px;
  background: var(--ql-panel);
  border: 1px solid var(--ql-accent);
  box-shadow: 0 0 24px rgba(0, 185, 122, 0.15);
  padding: 0.35rem 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition:
    opacity 0.15s ease-out,
    transform 0.15s ease-out,
    visibility 0.15s;
}

.ql-topbar__dropdown:hover .ql-topbar__menu,
.ql-topbar__dropdown:focus-within .ql-topbar__menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .ql-topbar__menu {
    transition: none;
  }
}

.ql-topbar__menu-link {
  display: block;
  font-family: 'VT323', monospace;
  font-size: 1rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ql-text);
  padding: 0.5rem 0.9rem;
}

.ql-topbar__menu-link:hover,
.ql-topbar__menu-link:focus-visible {
  background: var(--ql-accent);
  color: var(--ql-bg);
}

/* Simple placeholder pages (How This Works, Contact, Data Export,
   Settings) — body copy inside the standard .ql-panel frame. */
.ql-page-copy {
  text-align: left;
  padding: 0 1.75rem 0.5rem;
  color: var(--ql-text);
  line-height: 1.5;
}

.ql-page-copy p {
  margin: 0 0 1rem;
}

.ql-page-copy p:last-child {
  margin-bottom: 0;
}

.ql-coming-soon {
  display: inline-block;
  font-family: 'VT323', monospace;
  font-size: 0.9rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
  border: 1px solid var(--ql-accent-cyan);
  padding: 0.25rem 0.6rem;
  margin-bottom: 1rem;
}

/* How This Works / Contact live as sections on Today's Quest, below Daily
   Records — reachable by scrolling or by jumping via the top nav bar
   (index.html#ql-how-it-works / #ql-contact). scroll-margin-top keeps the
   fixed topbar from covering the heading after a jump. #ql-how-it-works,
   #ql-contact, and .ql-history already supply their own horizontal
   padding, so the nested .ql-page-copy (built for direct use inside
   .ql-panel) doesn't need its own here. */
#ql-how-it-works,
#ql-contact {
  scroll-margin-top: 5.5rem;
}

/* Staff Manual page (2026-07-13): same unframed .ql-plain-page treatment
   as Data Export/Settings, plus a fixed left sidebar Table of Contents
   and a printable/PDF-exportable version — see the @media print block
   further down for that half. Sidebar layout and the Inter body font
   below were both picked from a mockup (see MEMORY.md 2026-07-13). */
.ql-manual-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

/* position: fixed (not sticky) per direct request — anchored to the
   viewport's left edge, independent of .ql-page's own centered flex
   layout, so it doesn't need to participate in that flow at all. Starts
   below the topbar (top: 5.5rem matches body.ql-has-topbar's own
   clearance) and scrolls its own contents independently if the list
   ever outgrows the viewport height. */
.ql-manual-sidebar {
  position: fixed;
  top: 5.5rem;
  left: 0;
  bottom: 0;
  width: 220px;
  overflow-y: auto;
  padding: 1.5rem 1.25rem;
  background: var(--ql-panel);
  border-right: 1px solid rgba(0, 185, 122, 0.35);
  z-index: 5;
}

.ql-manual-sidebar .ql-section-title {
  padding-top: 0;
  margin-bottom: 0.9rem;
  font-size: 1.2rem;
}

.ql-manual-sidebar .ql-section-title::before {
  content: none;
}

/* Groups the sidebar's 12 links into 5 clusters (Getting Started/Daily
   Routine/Reviewing & Data/Profile & Settings/Reference) instead of one
   flat list — real vertical rhythm (generous margin-top here vs. .ql-
   manual-sidebar li's tight margin-bottom within a group) does the work
   a divider line would otherwise need to. Reuses --ql-muted rather than a
   new token; small-caps-style sizing/tracking is the same "quiet all-caps
   label" language as .ql-how-it-works__manual-link's own eyebrow-free
   restraint elsewhere on the site. */
.ql-manual-sidebar__group-label {
  font-family: 'Inter', sans-serif;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ql-muted);
  margin: 1.1rem 0 0.5rem;
}

.ql-manual-sidebar__group-label:first-of-type {
  margin-top: 0;
}

.ql-manual-sidebar ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ql-manual-sidebar li {
  margin-bottom: 0.3rem;
}

/* Chevron prefix (`> `) dropped — real hierarchy bug, not a taste call:
   the heading text itself already leads with a number ("4. Daily Routine:
   ..."), so every link was carrying two markers doing the same job. The
   number is the only marker left. Text sits dimmed (a translucent tint of
   --ql-text, the same "quiet at rest" pattern the topbar's own default
   link color already uses) until hover/focus/active brightens it — Signal
   accent color is no longer on by default for all 12 links at once. */
.ql-manual-sidebar a {
  font-family: 'Inter', sans-serif;
  font-size: 0.9rem;
  line-height: 1.4;
  padding: 0.3rem 0.6rem;
  margin: 0 -0.6rem;
  border-left: 2px solid transparent;
  color: rgba(216, 245, 232, 0.7);
  text-decoration: none;
  display: block;
}

.ql-manual-sidebar a:hover,
.ql-manual-sidebar a:focus-visible {
  color: var(--ql-text);
  border-left-color: rgba(0, 185, 122, 0.4);
}

/* Current section, tracked live by an IntersectionObserver in manual.js
   as the reader scrolls — the sidebar becomes a "you are here" map
   instead of a static table of contents. Left accent bar + tinted row +
   full Terminal Green, same reverse-video-adjacent, persistent-state
   language as the [ Today's Quest ]/[ Profile ]/[ Boss Battle ] nav's own
   aria-current fix. */
.ql-manual-sidebar a.is-active {
  color: var(--ql-accent);
  background: rgba(0, 185, 122, 0.1);
  border-left-color: var(--ql-accent);
  text-shadow: 0 0 3px rgba(0, 185, 122, 0.4);
}

/* The sidebar is fixed/out-of-flow, so main content just needs its own
   left clearance instead of .ql-plain-page's usual auto-centering — a
   full override (not just a margin tweak) so it doesn't inherit that
   page's width:80%-against-an-indeterminate-parent chain at all. */
body.ql-manual-page .ql-plain-page {
  width: auto;
  max-width: 900px;
  margin: 1.5rem 2rem 2rem 260px;
}

/* Anchor-jump targets for the sidebar's links above — same
   topbar-clearance fix as #ql-how-it-works/#ql-contact. */
.ql-manual-page section[id] {
  scroll-margin-top: 5.5rem;
}

.ql-plain-section ol,
.ql-plain-section ul {
  margin: 0 0 1rem;
  padding-left: 1.5rem;
}

.ql-plain-section li {
  margin-bottom: 0.5rem;
  line-height: 1.5;
}

/* Body copy on the Manual page reads in Inter, not VT323 — chosen from a
   mockup after the user found long-form VT323 hard to read (see the
   @import comment at the top of this file for the full rationale).
   Headings/sidebar stay VT323; only paragraphs and list items change. */
body.ql-manual-page .ql-plain-section p,
body.ql-manual-page .ql-plain-section li {
  font-family: 'Inter', sans-serif;
  font-size: 1rem;
  line-height: 1.65;
}

/* Real screenshots of the app, one per major step (2026-07-13) — mocked
   realistic data captured via Playwright, not the client's real records.
   --size-* caps width for portrait/tall captures (login form, the stat
   bars, a single checkbox row) that would otherwise stretch awkwardly
   across the full ~900px content column; the default (no size modifier)
   is full width, for wide captures (a form section, a chart, a table).
   --float-* (added when the layout editor's "2 columns" option is picked
   — see further down) floats the figure so surrounding paragraphs/lists
   wrap beside it instead of stacking above/below. Panel Surface
   background + a thin border reads as "framed illustration," not a
   random pasted image. */
.ql-manual-figure {
  margin: 1.25rem 0 1.5rem;
  padding: 0.75rem;
  border: 1px solid rgba(0, 185, 122, 0.35);
  background: var(--ql-panel);
}

/* Float rules carry position/spacing only, not width — a fallback
   max-width covers the (currently unreachable via the editor's own
   presets, which always pair a size) case of a floated figure with no
   size class. The three --size-* rules are declared after these on
   purpose: equal specificity means source order decides a tie, so a
   figure combining e.g. --size-small with --float-left still ends up
   280px wide, not the float's 45% fallback silently winning instead
   (a real bug this order fixes — see MEMORY.md). */
.ql-manual-figure--float-left {
  float: left;
  margin-right: 1.5rem;
  max-width: 45%;
}

.ql-manual-figure--float-right {
  float: right;
  margin-left: 1.5rem;
  max-width: 45%;
}

.ql-manual-figure--size-small {
  max-width: 280px;
}

.ql-manual-figure--size-medium {
  max-width: 420px;
}

.ql-manual-figure--size-large {
  max-width: 640px;
}

.ql-manual-figure img {
  display: block;
  width: 100%;
  height: auto;
}

.ql-manual-figure figcaption {
  margin-top: 0.6rem;
  font-family: 'Inter', sans-serif;
  font-size: 0.85rem;
  color: var(--ql-muted);
  text-align: center;
}

/* Reference tables (currently just the scoring section's point tables) —
   same dotted-row/cyan-header language as .ql-monthly-review-table, but in
   Inter (matches this page's body text) instead of VT323, and no reverse-
   video row hover since these rows aren't clickable/selectable like a
   records list. */
.ql-manual-table {
  width: 100%;
  margin: 1rem 0 1.5rem;
  border-collapse: collapse;
  font-family: 'Inter', sans-serif;
  font-size: 1rem;
}

.ql-manual-table th,
.ql-manual-table td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  border-bottom: 1px dotted rgba(0, 185, 122, 0.3);
}

.ql-manual-table th {
  font-family: 'VT323', monospace;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
  border-bottom: 1px solid var(--ql-accent);
}

.ql-manual-table td:not(:first-child) {
  font-variant-numeric: tabular-nums;
}

.ql-manual-table tbody tr:last-child td {
  border-bottom: none;
}

/* Clearfix so a floated figure's height is contained within its own
   section (never bleeds into the next one), plus every heading clears
   floats unconditionally as a second line of defense. Scoped to the
   Manual page only — every other page's .ql-plain-section never floats
   anything, so this rule has no reason to apply there. */
body.ql-manual-page .ql-plain-section::after {
  content: '';
  display: table;
  clear: both;
}

body.ql-manual-page .ql-plain-section .ql-section-title {
  clear: both;
}

/* Temporary in-browser layout editor (2026-07-13, expanded same day,
   ?edit=1 only — see manual.js). Hidden by default; only ever added to
   the DOM at all when editModeEnabled, and body.ql-manual-editing
   (toggled by the "Hide/Show Edit Controls" button) further hides it
   without discarding the underlying saved edits. Not part of the page
   real staff see. */
.ql-manual-figure__toolbar {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
}

body.ql-manual-editing .ql-manual-figure__toolbar {
  display: flex;
}

.ql-manual-figure__toolbar button,
.ql-manual-figure__toolbar select {
  font-family: 'Inter', sans-serif;
  font-size: 0.85rem;
  padding: 0.2rem 0.55rem;
  background: var(--ql-bg);
  border: 1px solid var(--ql-accent);
  color: var(--ql-text);
  cursor: pointer;
}

.ql-manual-figure__toolbar button:hover {
  background: var(--ql-accent);
  color: var(--ql-bg);
}

/* The file input itself stays invisible — its Replace button (a regular
   toolbar button) triggers it via .click(), same pattern as any custom
   "styled file upload" button. */
.ql-manual-figure__file-input {
  display: none;
}

#ql-manual-edit-toggle,
#ql-manual-edit-reset,
#ql-manual-edit-show {
  font-size: 0.95rem;
}

/* "Show My Changes" overlay — the handoff point for getting edits out of
   this browser's localStorage and into chat, since Claude has no other
   way to read them. Deliberately plain (no glow/animation): its only job
   is to be easy to read and copy from. */
.ql-manual-changes-overlay {
  position: fixed;
  inset: 0;
  z-index: 1002;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.75);
}

.ql-manual-changes-panel {
  width: min(700px, 90vw);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.5rem;
  background: var(--ql-panel);
  border: 1px solid var(--ql-accent);
}

.ql-manual-changes-panel h2 {
  font-family: 'VT323', monospace;
  font-size: 1.3rem;
  color: var(--ql-accent);
  margin: 0;
}

.ql-manual-changes-panel p {
  font-family: 'Inter', sans-serif;
  font-size: 0.95rem;
  color: var(--ql-muted);
  margin: 0;
}

.ql-manual-changes-textarea {
  flex: 1;
  min-height: 260px;
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 0.8rem;
  padding: 0.75rem;
  background: var(--ql-bg);
  color: var(--ql-text);
  border: 1px solid rgba(0, 185, 122, 0.35);
  resize: vertical;
}

.ql-manual-changes-actions {
  display: flex;
  gap: 0.75rem;
}

/* Text editing: every heading/paragraph/list directly inside a
   .ql-plain-section becomes contenteditable in edit mode (figures are
   excluded — they have their own toolbar instead). A dashed outline is
   the only visual cue, appearing on focus so it doesn't clutter a page
   that's otherwise supposed to preview close to the real thing. */
body.ql-manual-editing .ql-manual-editable-text:focus {
  outline: 1px dashed var(--ql-accent-cyan);
  outline-offset: 4px;
}

/* Printable/PDF version — triggered by the Export as PDF button
   (window.print()). A deliberate, scoped exception to the One-Voice Rule
   (VT323-only typography) and the dark terminal palette: a small pixel
   font and glowing dark-mode text are hard to read on paper, so the
   print output switches to a plain system sans-serif on a white
   background instead. This is a print-ergonomics call specific to this
   one page's PDF/handout use case, not a change to the app's on-screen
   visual identity — every other page is untouched, and this page's own
   screen version keeps the terminal look. */
@media print {
  body.ql-manual-page {
    padding-top: 0;
    background: #fff;
    color: #111;
    font-family: -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  }

  body.ql-manual-page .ql-topbar,
  body.ql-manual-page #ql-manual-export,
  body.ql-manual-page .ql-manual-sidebar,
  body.ql-manual-page .ql-manual-figure__toolbar,
  body.ql-manual-page #ql-manual-edit-toggle,
  body.ql-manual-page #ql-manual-edit-reset,
  body.ql-manual-page #ql-manual-edit-show,
  body.ql-manual-page .ql-manual-changes-overlay {
    display: none;
  }

  body.ql-manual-page .ql-plain-page {
    width: 100%;
    max-width: none;
    margin: 0;
  }

  body.ql-manual-page .ql-heading,
  body.ql-manual-page .ql-section-title,
  body.ql-manual-page .ql-plain-section p,
  body.ql-manual-page .ql-plain-section li,
  body.ql-manual-page .ql-manual-table th,
  body.ql-manual-page .ql-manual-table td {
    color: #111;
    text-shadow: none;
    font-family: -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  }

  body.ql-manual-page .ql-manual-table {
    break-inside: avoid-page;
  }

  body.ql-manual-page .ql-manual-table th,
  body.ql-manual-page .ql-manual-table td {
    border-color: #ccc;
  }

  body.ql-manual-page .ql-section-title::before {
    border-top-color: #ccc;
  }

  body.ql-manual-page .ql-plain-section {
    break-inside: avoid-page;
  }

  body.ql-manual-page .ql-manual-figure {
    border-color: #ccc;
    background: transparent;
    break-inside: avoid-page;
  }

  body.ql-manual-page .ql-manual-figure figcaption {
    color: #555;
  }
}

.ql-history .ql-page-copy {
  padding: 0;
}

html {
  scroll-behavior: smooth;
}

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

/* Onboarding Tour — first-login walkthrough on Today's Quest (2026-07-15).
   Reuses the site's documented z-index tiers (see Elevation in DESIGN.md):
   the scrim sits at 999, the same tier as the CRT scanline (body::before)
   — appended later in the DOM, so it paints above the scanline at equal
   z-index without needing a higher number. Each step's spotlighted
   target(s) rise to 1000 via .ql-onboarding-lift ("scanline-escaped
   content", the same tier How This Works and the login fields already
   use) so they stay legible above both the scanline and this scrim.

   The spotlight ring itself is a separate synthetic overlay
   (.ql-onboarding-spotlight-box), not an outline drawn directly on the
   target — a step can spotlight more than one element (Profile + Boss
   Battle, not the whole nav row including Today's Quest) as a single
   continuous box this way, positioned via JS from the union of their
   real bounding rects, with no shared wrapper needed in the real
   markup. */
.ql-onboarding-scrim {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: rgba(3, 13, 8, 0.75);
  pointer-events: none;
}

.ql-onboarding-lift {
  position: relative;
  z-index: 1000;
}

.ql-onboarding-spotlight-box {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  outline: 2px solid var(--ql-accent-cyan);
  outline-offset: 4px;
  box-shadow:
    0 0 0 4px var(--ql-bg),
    0 0 24px rgba(54, 255, 255, 0.35);
}

/* The topbar step's target (.ql-topbar) never receives .ql-onboarding-lift
   — it already sits at z-index:1001 (above every other tier, including
   this scrim) for its own documented reason (2026-07-14 fix), and adding
   a same-specificity z-index:1000 rule would silently lose to source
   order and demote it below the scrim, reopening that exact bug. This
   modifier only changes the box's own outline direction: it draws inward
   since the topbar sits flush against the viewport's top/left/right
   edges with no room to draw outward. */
/* z-index bumped above .ql-topbar's own 1001 — the base box's 1000 loses
   to the topbar's opaque background at equal-ish stacking (real bug,
   caught via user report: the ring rendered fully invisible on this step,
   painted underneath the topbar it was supposed to outline). Nothing else
   on the page needs to win against this while the tour is running. */
.ql-onboarding-spotlight-box--edge {
  z-index: 1002;
  outline-offset: -2px;
  box-shadow: 0 0 24px rgba(54, 255, 255, 0.35);
}

.ql-onboarding-callout {
  position: fixed;
  z-index: 1000;
  width: 300px;
  max-width: calc(100vw - 2rem);
  background: var(--ql-panel);
  border: 1px solid var(--ql-accent-cyan);
  box-shadow: 0 0 20px rgba(54, 255, 255, 0.25);
  padding: 0.9rem 1rem;
  text-align: left;
}

.ql-onboarding-callout__step {
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ql-accent-cyan);
  text-shadow: 0 0 4px rgba(54, 255, 255, 0.6);
  margin: 0 0 0.4rem;
}

.ql-onboarding-callout p {
  margin: 0 0 0.9rem;
  font-size: 1.05rem;
  line-height: 1.4;
  color: var(--ql-text);
}

.ql-onboarding-callout__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* Plain muted text, not a bracket button — Skip is a secondary escape
   hatch, not the primary action, so it doesn't compete with Next/Done. */
.ql-onboarding-callout__skip {
  background: transparent;
  border: none;
  padding: 0;
  font-family: 'VT323', monospace;
  font-size: 1rem;
  color: var(--ql-muted);
  cursor: pointer;
}

.ql-onboarding-callout__skip:hover,
.ql-onboarding-callout__skip:focus-visible {
  color: var(--ql-text);
}

.ql-onboarding-callout__advance {
  margin-top: 0;
}

.ql-onboarding-callout__advance--next::after {
  content: ' →';
}
