:root {
  --surface-2: #ffffff;
  --surface-1: #f5f4f0;
  --surface-0: #fbfaf7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #898781;
  --text-accent: #0a66d6;
  --text-success: #08794b;
  --text-warning: #b0500a;
  --text-danger: #d32f2f;
  --border: #e1e0d9;
  --border-strong: #c3c2b7;
  --border-accent: #5aa9ff;
  --bg-accent: #dcedff;
  --bg-success: #d9f2e3;
  --bg-warning: #fdedde;
  --btn-accent: #0a66d6;
  --btn-accent-hover: #0956b8;
  --radius: 8px;
}
@media (prefers-color-scheme: dark) {
  :root {
    --surface-2: #232320;
    --surface-1: #1a1a19;
    --surface-0: #141413;
    --text-primary: #f0efec;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --text-accent: #5aa9ff;
    --text-success: #4ee6a8;
    --text-warning: #ffb454;
    --text-danger: #ff6b6b;
    --border: #383835;
    --border-strong: #444441;
    --border-accent: #1a7aeb;
    --bg-accent: #0a66d64d;
    --bg-success: #08794b4d;
    --bg-warning: #b0500a4d;
  }
}
* { box-sizing: border-box; }
/* Every link uses the theme's accent color (the browser default blue/purple
   is unreadable on the dark background); components override as needed. */
a, a:visited { color: var(--text-accent); }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--surface-0);
  color: var(--text-primary);
}
.page { max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem; }
.page-header {
  display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 8px;
  position: sticky; top: 0; z-index: 50;
  background: var(--surface-0);
  margin: -2rem -1.5rem 1.5rem;
  padding: 20px 1.5rem;
  border-bottom: 0.5px solid var(--border);
}
/* Without this, clicking an in-page anchor link (e.g. help.html's
   sidebar) scrolls its target heading to the very top of the viewport
   - directly underneath the sticky .page-header above, which hides it
   completely rather than just looking a bit close. scroll-margin-top
   tells the browser's native anchor-scrolling to stop short of the
   header instead. General h2/h3[id] selector (not scoped to one page)
   so this keeps working if another page adds anchor links later.
   Value is a bit more than the header's real rendered height (~83px)
   for breathing room, not just the bare minimum to avoid the overlap. */
h2[id], h3[id] { scroll-margin-top: 100px; }
h1 { font-size: 36px; font-weight: 500; margin: 0; display: flex; align-items: baseline; gap: 10px; }
/* Server-rendered landing pages (webapp/seo_pages.py, webapp/templates/). */
.breadcrumb { font-size: 13px; color: var(--text-muted); margin: 0 0 12px; }
.breadcrumb a { color: var(--text-accent); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { margin: 0 4px; }
.landing .landing-title { font-size: 30px; font-weight: 600; margin: 0 0 0.5rem; display: block; }
.landing .landing-lead { font-size: 18px; color: var(--text-primary); }
.landing-table { border-collapse: collapse; margin: 0.25rem 0 0.75rem; min-width: 280px; max-width: 720px; width: 100%; }
.landing-table th, .landing-table td { text-align: left; padding: 6px 10px; border-bottom: 0.5px solid var(--border); font-size: 15px; }
.landing-table th { color: var(--text-muted); font-weight: 500; font-size: 13px; }
.landing-table td:not(:first-child), .landing-table th:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.landing-list { columns: 2; column-gap: 24px; }
@media (max-width: 560px) { .landing-list { columns: 1; } }
.landing-cta { display: inline-block; margin-top: 4px; padding: 10px 16px; border-radius: 8px; background: var(--text-accent); color: #fff !important; text-decoration: none; font-weight: 600; }
.landing-footer { margin-top: 2rem; padding-top: 1rem; border-top: 0.5px solid var(--border); font-size: 14px; color: var(--text-muted); }
.landing-footer a { color: var(--text-accent); }
.style-water-link { display: inline-block; margin: 6px 0 0; color: var(--text-accent); text-decoration: none; }
.style-water-link:hover { text-decoration: underline; }
/* The header brand used to be each page's <h1>, so every page's only
   heading was "BrewWaterSolver". It's now a home link styled the same,
   and each page has a descriptive <h1> for search engines and screen
   readers (visually hidden, so the header still reads BrewWaterSolver). */
.site-title { font-size: 36px; font-weight: 500; margin: 0; display: flex; align-items: baseline; gap: 10px; color: inherit; text-decoration: none; }
.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; }
h2 { font-size: 26px; font-weight: 500; margin: 1.75rem 0 0.6rem; color: var(--text-accent); }
h2:first-of-type { margin-top: 0; }
h3 { font-size: 24px; font-weight: 600; color: var(--text-primary); margin: 1.5rem 0 0.5rem; }
.prose p { font-size: 16px; line-height: 1.6; color: var(--text-secondary); margin: 0 0 0.75rem; max-width: 720px; }
.prose code { background: var(--surface-1); border-radius: 4px; padding: 1px 5px; font-size: 13px; font-family: monospace; }
.prose ul { margin: 0 0 0.75rem; padding-left: 20px; max-width: 720px; }
.prose li { font-size: 16px; line-height: 1.6; color: var(--text-secondary); margin-bottom: 4px; }
.prose a { color: var(--text-accent); }

/* Each Help/About section gets the same colored left-accent-stripe
   treatment as the results-page cards (see card-accent-* in the results
   section below). One consistent color (not per-section variety) since
   these sections are just topic groupings, not functional categories the
   way salt-additions/optimal-mode/core-data are on the results page. */
.doc-section {
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 12px;
  border-left-width: 4px; border-left-color: var(--text-accent);
  padding: 1.25rem 1.4rem; margin-bottom: 20px;
}
.doc-section h2 { margin-top: 0; }
/* Help page: topic groups. Section titles inside a group are h3 for the
   outline but keep the same look as a top-level section title. */
.doc-section h3 { font-size: 26px; font-weight: 500; color: var(--text-accent); margin-top: 0; }
.help-category { margin-top: 28px; }
.help-category .help-category-title {
  font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--text-secondary); margin: 0 0 10px; padding-left: 2px;
}
.doc-section:last-child { margin-bottom: 0; }
/* About page "Get in Touch" block (p rules scoped under .contact-section
   so they outrank the page's .prose p color/margins) - gradient-topped outer card holding
   two inner cards (featured project, developer links). Colors come from
   theme tokens plus two contact-only accents defined here per theme. */
.contact-section {
  --contact-purple: #7c3aed; --contact-teal: #0e9aa7; --contact-docker: #1d8fe1;
  position: relative; overflow: hidden;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 16px;
  padding: 2rem 2rem 1.75rem; margin-bottom: 20px;
}
@media (prefers-color-scheme: dark) {
  .contact-section { --contact-purple: #b48cff; --contact-teal: #5cc8d6; --contact-docker: #4fb0f0; }
}
.contact-section::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: linear-gradient(90deg, var(--text-accent), var(--contact-purple), var(--contact-teal));
}
.contact-section h2 { margin: 0 0 6px; font-size: 28px; font-weight: 700; color: var(--text-primary); }
.contact-section .contact-subtitle { margin: 0 0 1.5rem; color: var(--text-secondary); font-size: 15px; }
.contact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 700px) { .contact-grid { grid-template-columns: 1fr; } }
.contact-card {
  display: flex; flex-direction: column;
  background: var(--surface-1); border: 0.5px solid var(--border); border-radius: 14px;
  padding: 1.5rem 1.6rem;
}
.contact-section .contact-card-label {
  display: flex; align-items: center; gap: 10px; margin: 0 0 1rem;
  font-size: 15px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
}
.contact-section .contact-card-label-project { color: var(--text-accent); }
.contact-section .contact-card-label-dev { color: var(--contact-purple); }
.featured-foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.featured-foot .contact-launch { margin-top: 0; gap: 8px; }
.featured-nav { display: flex; align-items: center; gap: 4px; }
.featured-nav[hidden] { display: none; }
.featured-nav-btn { width: 30px; height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; }
.featured-nav-btn svg { width: 16px; height: 16px; }
.featured-count { font-size: 12px; color: var(--text-secondary); min-width: 3.2em; text-align: center; font-variant-numeric: tabular-nums; }
.contact-card-label-icon { width: 18px; height: 18px; flex-shrink: 0; }
.contact-card-title { margin: 0 0 8px; font-size: 22px; font-weight: 700; color: var(--text-primary); }
.contact-section .contact-card-body { max-width: none; margin: 0 0 1.5rem; color: var(--text-secondary); line-height: 1.5; }
.contact-section .contact-card-meta { margin: -0.75rem 0 1.5rem; font-size: 13px; color: var(--text-secondary); }
.contact-launch { margin-top: auto; font-weight: 700; flex-direction: row; }
.contact-launch .contact-icon { width: 17px; height: 17px; }
.contact-links { display: flex; flex-direction: column; gap: 16px; }
.contact-link {
  display: inline-flex; align-items: center; gap: 14px;
  color: var(--text-accent); text-decoration: none; width: fit-content;
}
.contact-links .contact-link { color: var(--text-primary); }
.contact-link:hover { text-decoration: underline; }
.contact-icon { width: 20px; height: 20px; flex-shrink: 0; }
.contact-icon-email, .contact-icon-linkedin { color: var(--text-accent); }
.contact-icon-github { color: var(--contact-purple); }
.contact-icon-docker { color: var(--contact-docker); }
.toc { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-bottom: 2rem; }
.toc a { font-size: 13px; color: var(--text-accent); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
.version-badge { font-size: 12px; font-weight: 400; color: var(--text-muted); font-family: monospace; }
.page-nav { display: flex; gap: 4px; align-self: flex-end; }
.page-nav a, .page-nav-current {
  font-size: 13px; padding: 8px 14px; text-decoration: none;
  border-bottom: 2px solid transparent; border-radius: 8px 8px 0 0;
}
.page-nav a { color: var(--text-secondary); }
.page-nav a:hover { color: var(--text-primary); background: var(--surface-1); }
.page-nav-current { color: var(--text-accent); font-weight: 700; font-size: 16px; border-bottom-color: var(--text-accent); background: var(--surface-1); }
.layout { display: grid; grid-template-columns: 280px 1fr; gap: 20px; align-items: start; }
@media (max-width: 800px) { .layout { grid-template-columns: 1fr; } .left-column { position: static !important; max-height: none !important; } }

.left-column {
  display: flex; flex-direction: column; gap: 20px;
  position: sticky; top: 84px; max-height: calc(100vh - 104px); overflow-y: auto; overflow-x: hidden;
}
.panel {
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 12px; padding: 1rem 1.1rem;
}
.compact-tile { cursor: pointer; transition: border-color 0.1s; }
.compact-tile:hover, .compact-tile:focus-visible { border-color: var(--border-accent); outline: none; }
.compact-tile-value { font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0; }

.tile-row { display: flex; gap: 8px; }
.tile-row .compact-tile {
  flex: 1 1 0; min-width: 0; padding: 10px 8px; margin-bottom: 0;
}
.tile-row .compact-tile .label { font-size: 10px; margin: 0 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tile-row .compact-tile .compact-tile-value { font-size: 13px; line-height: 1.25; word-break: break-word; }
.tile-row .compact-tile .hint { font-size: 10px; line-height: 1.3; margin: 3px 0 0 !important; }
.label { font-size: 13px; color: var(--text-secondary); margin: 0 0 8px; }
/* Left-column section headings (Preferences, Target water, Batch size) -
   larger and bold so each section reads as a heading, not a field label. */
.mineral-water-filter { margin-bottom: 8px; }
.label.section-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
.salt-card-title { font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 0 0 8px; }
input, select, button {
  font-family: inherit; font-size: 14px; border-radius: var(--radius);
  border: 0.5px solid var(--border-strong); background: var(--surface-2); color: var(--text-primary);
  height: 34px; padding: 0 8px;
}
select, input[type=number], input[type=search], input[type=text] { width: 100%; margin-bottom: 12px; }
button { cursor: pointer; }
button:hover { background: var(--surface-1); }

.mode-row { display: flex; margin-bottom: 12px; }
.mode-btn { flex: 1; font-size: 12px; padding: 6px 4px; height: auto; }
.mode-btn:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.mode-btn:last-child { border-radius: 0 var(--radius) var(--radius) 0; }
.mode-btn:not(:first-child) { border-left: none; }
.mode-btn.active { background: var(--bg-accent); color: var(--text-accent); border-color: var(--border-accent); }

.advanced-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; }
.advanced-grid label { font-size: 12px; color: var(--text-secondary); }
.advanced-grid input { margin-bottom: 0; margin-top: 2px; }
.saved-profiles-row { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 2px; }
.saved-profiles-row select { flex: 1 1 auto; min-width: 140px; margin: 0; }

.volume-row { display: flex; gap: 8px; margin-bottom: 16px; }
.volume-row input { margin-bottom: 0; flex: 1; min-width: 0; }
.volume-row select { margin-bottom: 0; width: 80px; }

.salt-presets { margin-bottom: 8px; }
.salts-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.salt-row { display: flex; align-items: flex-start; gap: 6px; font-size: 12px; line-height: 1.4; }
.salt-row input { width: auto; height: auto; margin: 3px 0 0; }
/* Tour step 1's mirrored salt checklist - two columns so 9 salts fit in
   the bottom-docked tour modal without pushing its nav off-screen. */
.tour-salts-list { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 12px; margin-bottom: 0; }
.tour-salts-list .salt-row { padding: 3px 0; border-bottom: none; justify-content: flex-start; }
/* The Salts on hand modal's rows were picking up the later results-list
   .salt-row rule (space-between, 12px padding, borders) - right-aligned
   labels and a list tall enough to push the coverage figure off-screen. */
#saltsList .salt-row { padding: 4px 0; border-bottom: none; justify-content: flex-start; }
/* Calcium chloride's form picker, right-aligned in its Salts on hand row. */
#saltsList .salt-form-select { width: auto; margin: 0 0 0 auto; padding: 2px 6px; font-size: 12px; height: auto; }
@media (max-width: 520px) { .tour-salts-list { grid-template-columns: 1fr; } }

/* Grain bill modal: an "Add a grain" block and a separate "Your grain bill" table. */
.srm-readout { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; font-size: 13px; color: var(--text-secondary); }
.srm-readout .compact-tile-value { display: inline; font-size: 18px; }
/* Pint glass filled with the beer's SRM color (--srm-beer, set in app-modals.js). */
.srm-glass { width: 26px; height: 36px; flex: 0 0 auto; overflow: visible; }
.srm-glass-beer { fill: var(--srm-beer, #E58500); }
.srm-glass-foam { fill: #fbf7ec; }
.srm-glass-shine { fill: #ffffff; opacity: 0.25; }
.srm-glass-outline { fill: none; stroke: var(--text-secondary); stroke-width: 1.5; stroke-linejoin: round; }
.grain-section { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px; margin-bottom: 12px; }
.grain-bill-section { background: var(--surface-1); }
.grain-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.grain-clear-btn { height: auto; padding: 2px 10px; font-size: 12px; margin-bottom: 8px; }
.grain-clear-btn.armed { color: var(--text-danger); border-color: var(--text-danger); }
.grain-section-title { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); margin: 0 0 8px; }
.grain-section select, .grain-section .mineral-water-filter { margin-bottom: 8px; }
.grain-row { display: flex; gap: 6px; align-items: center; }
.grain-row input { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.grain-row select { width: auto; margin-bottom: 0; }
.grain-add-btn { flex: 0 0 auto; padding: 0 18px; font-weight: 600; background: var(--text-accent); color: #fff; border-color: var(--text-accent); }
.grain-add-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.grain-empty { margin: 0; font-style: italic; }
.grain-bill-list { margin-bottom: 0; }
.grain-bill-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.grain-bill-table th { font-size: 11px; font-weight: 600; color: var(--text-secondary); text-align: left; padding: 0 6px 6px; border-bottom: 1px solid var(--border-strong); }
.grain-bill-table td { padding: 6px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.grain-bill-table .num { text-align: right; white-space: nowrap; }
.grain-bill-table tfoot td { font-weight: 600; border-bottom: none; border-top: 1px solid var(--border-strong); }
.grain-bill-table input.grain-weight-input { width: 72px; max-width: 72px; height: 28px; margin: 0; padding: 2px 6px; text-align: right; font-size: 13px; }
.grain-remove-btn { height: 26px; width: 26px; padding: 0; font-size: 14px; }
.grain-row-flash td { animation: grain-flash 1.4s ease-out; }
@keyframes grain-flash { from { background: var(--bg-accent); } to { background: transparent; } }
.grain-warning { margin: 10px 0 0; align-items: flex-start; }
/* Grain bill tile tinted with the beer's approximate SRM color. */
.compact-tile.srm-tinted { background: var(--srm-bg); border-color: var(--srm-bg); }
.compact-tile.srm-tinted, .compact-tile.srm-tinted .label, .compact-tile.srm-tinted .hint,
.compact-tile.srm-tinted .compact-tile-value { color: var(--srm-fg); }
.compact-tile.srm-tinted .label, .compact-tile.srm-tinted .hint { opacity: 0.85; }
.compact-tile.srm-tinted:hover, .compact-tile.srm-tinted:focus-visible { border-color: var(--border-accent); }
.hint { font-size: 12px; color: var(--text-muted); margin: 0 0 16px; line-height: 1.4; }

.tooltip { position: relative; border-bottom: 1px dotted var(--text-muted); cursor: help; }
.tooltip .tooltip-bubble {
  position: absolute; bottom: 100%; left: 0; margin-bottom: 6px;
  background: var(--surface-2); border: 0.5px solid var(--border-strong); border-radius: var(--radius);
  padding: 8px 10px; font-size: 12px; color: var(--text-primary); white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  /* Higher than the sticky header's z-index: 50 (see .page-header
     above) - a tooltip opening near the top of the page (e.g. the
     metric tiles row) previously had its upper portion rendered
     underneath the opaque header instead of on top of it, which
     visually looked like clipping even though the bubble's own
     position/size were never actually wrong - confirmed by measuring
     its rect directly (fully within the viewport, no ancestor with
     overflow:hidden either) before finding this was a stacking-order
     problem, not a positioning or clipping one. */
  opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 60;
}
.tooltip:hover .tooltip-bubble, .tooltip:focus .tooltip-bubble { opacity: 1; }

.pref-row { display: grid; grid-template-columns: 1fr 145px; align-items: center; column-gap: 10px; font-size: 12px; margin-bottom: 8px; }
.pref-row input { width: auto; height: auto; margin: 0; justify-self: end; }
.pref-row select { width: 100%; margin-bottom: 0; }
.pref-row .metric-info-tooltip { margin-left: 4px; }
.pref-row .metric-info-tooltip i { font-size: 11px; opacity: 0.8; }
/* Checkbox before its text (Split into Mash / Sparge water). */
.checkbox-left-row { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-bottom: 8px; cursor: pointer; }
.checkbox-left-row input { width: auto; height: auto; margin: 0; }
.checkbox-left-row .metric-info-tooltip { margin-left: 4px; }
.checkbox-left-row .metric-info-tooltip i { font-size: 11px; opacity: 0.8; }
.shop-cards-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; margin-bottom: 20px; }
.shop-collapsible-card { flex: 1 1 320px; max-width: 480px; margin-bottom: 0; }
.my-styles-add-row { display: flex; gap: 8px; margin-top: 10px; }
.my-styles-add-row select { flex: 1 1 auto; margin: 0; }
.my-styles-add-row button { width: auto; height: auto; padding: 8px 14px; margin: 0; flex-shrink: 0; }
.my-styles-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.my-styles-list li { display: flex; align-items: center; justify-content: space-between; background: var(--surface-1); border-radius: var(--radius); padding: 6px 10px; font-size: 13px; }
.my-styles-remove-btn { background: none; border: none; height: auto; width: auto; padding: 0 0 0 10px; color: var(--text-muted); cursor: pointer; font-size: 16px; line-height: 1; }
.my-styles-remove-btn:hover { color: var(--text-warning); background: none; }
.buy-calc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
.buy-calc-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-secondary); }
.buy-calc-field input, .buy-calc-field select { width: 100%; margin-bottom: 0; }
.buy-calc-field .volume-row select { width: 80px; }
.buy-calc-field .volume-row { margin-bottom: 0; }
@media (max-width: 420px) {
  .buy-calc-grid { grid-template-columns: 1fr; }
}
.salt-prices-list { display: flex; flex-direction: column; gap: 4px; }
.salt-price-row { display: flex; align-items: center; justify-content: space-between; font-size: 12px; gap: 8px; }
.salt-price-input-wrap { display: flex; align-items: center; gap: 3px; }
.salt-price-prefix { color: var(--text-muted); }
.salt-price-row input { width: 64px; text-align: right; margin: 0; }

.drawer-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.1rem; border-bottom: 0.5px solid var(--border);
}
.drawer-title { font-size: 15px; font-weight: 500; }
.drawer-close {
  background: none; border: none; width: auto; height: auto; padding: 4px;
  color: var(--text-muted); cursor: pointer;
}
.drawer-close:hover { background: var(--surface-1); border-radius: var(--radius); }
.drawer-body { flex: 1; overflow-y: auto; padding: 1rem 1.1rem; }
.drawer-footer { padding: 1rem 1.1rem; border-top: 0.5px solid var(--border); }

.whatsnew-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
/* Applied only while a tour step has something highlighted (see
   openTutorialModal()) - a real, reported problem: the modal's own
   default centered position directly covered large highlighted areas
   (e.g. step 3's #results, which sits in the upper-middle of the
   page, right where a centered modal also lands), and the backdrop's
   own dimming washed out the highlight's glow underneath it. Docking
   to the bottom keeps the modal clear of content highlighted higher
   up the page instead of sitting on top of it, and a much lighter
   backdrop (down from 0.4 to 0.08) leaves the highlighted card
   reading at its real, undimmed color instead of muddied through a
   dark overlay - the Welcome/What's new popups (this same overlay,
   without this class) keep the original centered, more-dimmed look,
   since they have no highlighted content underneath to preserve. */
.whatsnew-overlay-tour { align-items: flex-end; background: rgba(0,0,0,0.08); }
.whatsnew-modal {
  width: 420px; max-width: 100%; max-height: 85vh; background: var(--surface-2);
  border-radius: 12px; display: flex; flex-direction: column;
  box-shadow: 0 8px 32px rgba(0,0,0,0.3); animation: whatsnew-pop-in 0.15s ease-out;
}
@keyframes whatsnew-pop-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
.whatsnew-modal .drawer-body ul { margin: 0; padding-left: 1.2em; }
.whatsnew-modal .drawer-body li { margin-bottom: 8px; font-size: 13px; line-height: 1.5; }
.whatsnew-modal .drawer-body li:last-child { margin-bottom: 0; }
.whatsnew-modal .drawer-body p { font-size: 13px; line-height: 1.5; color: var(--text-secondary); margin: 0; }
.whatsnew-version-heading { font-weight: 600; color: var(--text-primary); margin: 16px 0 6px !important; }
.whatsnew-version-heading:first-child { margin-top: 0 !important; }

/* The tour highlights whichever card(s) a step is actually talking
   about (openTutorialModal() in app.js) - a glowing outline rather
   than a background fill, so text inside the highlighted card stays
   fully readable. Strengthened after a live report that the original
   version ("not highlighting very well") read as too subtle - wider,
   brighter glow layers plus a slow pulse to actively draw the eye,
   rather than a static outline easy to miss at a glance. Paired with
   .whatsnew-overlay-tour's much lighter backdrop above (was the
   bigger contributor to the highlight reading as weak than the glow's
   own size was, in testing) - together they keep the highlighted card
   close to its real, undimmed brightness instead of both fighting a
   dark overlay on top of it. */
.tutorial-highlight {
  position: relative; z-index: 1;
  outline: 3px solid var(--text-accent);
  outline-offset: 4px;
  box-shadow: 0 0 0 4px var(--text-accent), 0 0 0 8px rgba(42, 120, 214, 0.35), 0 0 32px 8px var(--text-accent);
  border-radius: var(--radius);
  animation: tutorial-highlight-pulse 1.8s ease-in-out infinite;
}
@keyframes tutorial-highlight-pulse {
  0%, 100% { box-shadow: 0 0 0 4px var(--text-accent), 0 0 0 8px rgba(42, 120, 214, 0.35), 0 0 32px 8px var(--text-accent); }
  50% { box-shadow: 0 0 0 4px var(--text-accent), 0 0 0 10px rgba(42, 120, 214, 0.2), 0 0 44px 14px var(--text-accent); }
}

.solve-btn {
  width: 100%; height: 38px; font-weight: 500; margin-bottom: 8px;
  background: var(--btn-accent); color: #ffffff; border-color: var(--btn-accent);
}
.solve-btn:hover { background: var(--btn-accent-hover); border-color: var(--btn-accent-hover); }
.error-msg { font-size: 12px; color: var(--text-warning); margin: 0 0 8px; min-height: 1em; }
/* Batch size panel: the Solve button's own bottom margin plus the
   empty error line's reserved height/margin left ~28px of dead space
   under the button. Only show the error line when it has text. */
#batchSizePanel .solve-btn { margin-bottom: 0; }
#batchSizePanel .error-msg { margin: 8px 0 0; }
#batchSizePanel .error-msg:empty { display: none; }
.cli-toggle {
  background: none; border: none; height: auto; width: auto; padding: 0;
  font-size: 12px; color: var(--text-accent); cursor: pointer; text-decoration: underline;
}
.cli-toggle:hover { background: none; }
.copy-table-btn { font-size: 12px; height: 26px; padding: 0 10px; }
.card-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.card-header-row .label, .card-header-row .salt-card-title { margin-bottom: 0; }

.results { min-width: 0; }
.empty-state { font-size: 14px; color: var(--text-muted); padding: 2rem 0; text-align: center; }

.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 14px; }
.metric-card { background: var(--surface-1); border-radius: var(--radius); padding: 0.6rem 0.85rem; border: 1.5px solid transparent; border-left-width: 4px; border-left-color: var(--border-accent); }
.metric-card.metric-card-alert { border-color: var(--text-danger); }
.metric-card .m-label { font-size: 12px; color: var(--text-secondary); margin: 0 0 2px; }
.metric-card .m-value { font-size: 19px; font-weight: 500; margin: 0; }
.metric-alert-tooltip { margin-left: 5px; border-bottom: none; cursor: help; color: var(--text-danger); display: inline-flex; }
.metric-alert-tooltip i { font-size: 13px; }
.metric-info-tooltip { margin-left: 5px; border-bottom: none; cursor: help; color: var(--text-muted); display: inline-flex; }
.metric-info-tooltip i { font-size: 13px; }
.salt-warning-tooltip { margin-left: 6px; border-bottom: none; cursor: help; color: var(--text-warning); display: inline-flex; }
.salt-warning-tooltip i { font-size: 14px; }
.tooltip .metric-tooltip-bubble { white-space: normal; width: 220px; line-height: 1.4; left: 0; }
/* In the 280px side panel the info icons sit toward the right edge - open
   their bubbles leftward so they stay inside the panel (a left-anchored
   220px bubble made the panel scroll sideways). */
.left-column .tooltip .metric-tooltip-bubble { left: auto; right: -8px; }
/* The last metric tile sits against the page's right edge; a left-anchored
   bubble there widened the page by a pixel even while hidden. */
.metric-card:last-child .tooltip .metric-tooltip-bubble { left: auto; right: 0; }


.flavor-chip { font-size: 11px; font-weight: 500; padding: 2px 7px; border-radius: 10px; margin-left: 6px; display: inline-block; vertical-align: middle; }
.chip-malty { background: var(--bg-accent); color: var(--text-accent); }
.chip-balanced { background: var(--surface-1); color: var(--text-secondary); }
.chip-hoppy { background: var(--bg-warning); color: var(--text-warning); }
/* Mineral water's TDS classification - same color scheme as the beer
   flavor chips above (neutral/accent/warning), but named for what
   they actually mean here rather than reusing malty/hoppy naming in a
   context with no malt or hops. */
.chip-mineral-low { background: var(--surface-1); color: var(--text-secondary); }
.chip-mineral-medium { background: var(--bg-accent); color: var(--text-accent); }
.chip-mineral-high { background: var(--bg-warning); color: var(--text-warning); }

.card { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 12px; padding: 1rem 1.1rem; margin-bottom: 16px; }
/* Preferences specifically (not .pref-row generally - the same class
   is also used by the Batch size panel's Split/Mash volume rows,
   which shouldn't be affected here) reads noticeably taller than the
   compact tiles next to it, both collapsed (just .card's own vertical
   padding) and expanded (each row's own margin-bottom, compounded
   across ~6 rows plus the salt price list). Tightened independently
   of the generic .card/.pref-row rules other cards still rely on. */
#preferencesCard { padding: 0.65rem 1.1rem; }
#preferencesCard .pref-row { margin-bottom: 5px; }
/* Now the first item in the left column: drop .card's own 16px
   margin-bottom (the column's 20px gap already spaces it), and the
   header row's bottom margin, which left dead space under the title
   while collapsed - the body supplies that spacing when expanded. */
#preferencesCard.card { margin-bottom: 0; }
#preferencesCard .card-header-row { margin-bottom: 0; }
#preferencesBody { margin-top: 8px; }
#preferencesCard .salt-prices-list { gap: 3px; }
.card .label { margin-bottom: 8px; }
.row { display: flex; justify-content: space-between; font-size: 13px; padding: 4px 0; border-bottom: 0.5px solid var(--border); }
.row:last-child { border-bottom: none; }
.salt-row { display: flex; justify-content: space-between; align-items: baseline; padding: 12px 0; border-bottom: 0.5px solid var(--border); gap: 10px; }
.salt-row:last-child { border-bottom: none; }
.salt-row-name { font-size: 16px; flex: 1 1 auto; min-width: 0; }
.salt-row-amount { font-size: 20px; font-weight: 600; color: var(--text-warning); flex: 0 0 auto; white-space: nowrap; }

/* Colored left-accent stripe, same technique as .shop-card - applied as
   an opt-in modifier so .ph-card's own accent (dynamic in-range/out-of-range
   color) is untouched. Color choices are deliberate, not arbitrary:
   -salts reuses the same amber as .shop-card-salt (the Shop page),
   threading the two pages together; -data marks the two cards that
   both present/explain the same underlying water-chemistry result. */
.card-accent { border-width: 0.5px 0.5px 0.5px 4px; }
.card-accent-salts { border-left-color: var(--text-warning); }
.card-accent-data { border-left-color: var(--text-accent); }

.banner { border-radius: var(--radius); padding: 10px 12px; font-size: 13px; display: flex; align-items: center; gap: 8px; margin-bottom: 16px; border-left: 4px solid transparent; }
.banner-info { background: var(--bg-accent); color: var(--text-accent); border-left-color: var(--text-accent); }
.target-confidence { font-size: 12px; margin: 6px 0 0; display: flex; align-items: center; gap: 4px; }
.target-confidence.conf-medium { color: var(--text-accent); }
.target-confidence.conf-low { color: var(--text-warning); }
.target-confidence .metric-info-tooltip { margin-left: 2px; }
.banner-warning { background: var(--bg-warning); color: var(--text-warning); border-left-color: var(--text-warning); }
/* The Help page's "Take a quick tour" callout specifically - a call
   to action, not a regular informational aside, so it reads more
   prominently than the base .banner's 13px/10px sizing rather than
   blending in with the page's other banners. */
.tour-banner { font-size: 16px; padding: 16px 18px; }
.tour-banner i { font-size: 20px; }
.shop-warning-tooltip { margin-left: auto; flex-shrink: 0; cursor: help; }
.shop-warning-tooltip .tooltip-bubble { left: auto; right: -8px; white-space: normal; width: 260px; }

.legend { display: flex; gap: 16px; font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
.legend .dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; }
.result-tabs {
  display: flex; gap: 4px; padding: 4px; margin-bottom: 16px;
  background: var(--surface-1); border: 1px solid var(--border-strong); border-radius: var(--radius);
}
.result-tab {
  flex: 1 1 0; font: inherit; font-size: 14px; font-weight: 600; padding: 10px 12px; height: auto;
  background: transparent; border: 1px solid transparent; border-radius: calc(var(--radius) - 2px);
  color: var(--text-secondary); cursor: pointer; white-space: nowrap;
}
.result-tab:hover { color: var(--text-primary); background: var(--surface-2); border-color: var(--border); }
.result-tab.active { color: var(--text-accent); background: var(--bg-accent); border-color: var(--border-accent); }
@media (max-width: 600px) { .result-tab { font-size: 13px; padding: 8px 6px; white-space: normal; } }
.result-tab-panel { display: none; }
.result-tab-panel.active { display: block; }

.compare-table { display: grid; grid-template-columns: 1.2fr 1fr 1fr 1fr 0.9fr; gap: 4px; font-size: 12px; }
.compare-table .head { font-size: 12px; color: var(--text-muted); padding: 2px 0 6px; border-bottom: 0.5px solid var(--border); }
.compare-table .cell { padding: 5px 0; }

.impact-table { display: grid; grid-template-columns: 1fr 0.9fr 2.4fr; gap: 4px 10px; font-size: 12px; }
.impact-table .head { font-size: 12px; color: var(--text-muted); padding: 2px 0 6px; border-bottom: 0.5px solid var(--border); }
.impact-table .cell { padding: 7px 0; border-bottom: 0.5px solid var(--border); }
.impact-table .impact-notes { color: var(--text-secondary); line-height: 1.4; }

.explorer-panel { padding-bottom: 0.5rem; }
@media (max-width: 600px) { .impact-table { grid-template-columns: 1fr; } .impact-table .head:not(:first-child) { display: none; } }

/* Mash pH card. Vocabulary: Range (green band), Target (tick), Predicted
   (ring, +/-0.1 dashed band), Measured (blue diamond / blue card), With
   acid (filled dot at the end of the dose arrow). See renderPhCard(). */
.ph-card { border-width: 0.5px 0.5px 0.5px 4px; }
.ph-card.ph-in-range { border-left-color: var(--text-success); }
.ph-card.ph-out-of-range { border-left-color: var(--text-warning); }
.ph-color-in { color: var(--text-success); }
.ph-color-out { color: var(--text-warning); }

.ph-card-header { display: flex; align-items: center; gap: 6px 16px; flex-wrap: wrap; margin: 0 0 12px; font-size: 13px; color: var(--text-secondary); }
.ph-legend { display: inline-flex; align-items: center; gap: 6px; }
.ph-legend-range { width: 14px; height: 8px; border-radius: 2px; background: var(--text-success); opacity: 0.6; }
.ph-legend-target { width: 2px; height: 14px; border-radius: 1px; background: var(--text-primary); }
.ph-help-link { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; color: var(--text-accent); text-decoration: none; font-weight: 500; }
.ph-help-link:hover { text-decoration: underline; }
.ph-help-icon { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; border-radius: 50%; border: 1.5px solid currentColor; font-size: 10px; font-weight: 700; line-height: 1; }
.ph-info-tip { margin-left: 4px; }
.ph-info-tip i { font-size: 12px; font-style: normal; }
.ph-tip-right .tooltip-bubble { left: auto; right: -8px; }

.ph-hero { display: flex; align-items: stretch; gap: 12px 14px; flex-wrap: wrap; margin-bottom: 14px; }
.ph-source, .ph-after { display: flex; flex-direction: column; justify-content: center; gap: 3px; }
.ph-source-label { display: flex; align-items: center; font-size: 12px; color: var(--text-secondary); }
.ph-headline { display: flex; align-items: baseline; gap: 6px; }
.ph-big-value { font-size: 36px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }
.ph-uncertainty { font-size: 13px; color: var(--text-muted); }
.ph-status { font-size: 13px; font-weight: 500; }
.ph-arrow { align-self: center; font-size: 22px; color: var(--text-muted); }
.ph-after-value { font-size: 30px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }

.ph-measured-card { flex: 1 1 300px; display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: 10px; background: var(--bg-accent); border: 1px solid var(--border-accent); }
.ph-measured-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ph-measured-badge { padding: 2px 8px; border-radius: 999px; background: var(--text-accent); color: var(--surface-2); font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.ph-measured-meta { font-size: 14px; font-weight: 500; color: var(--text-primary); }
.ph-measured-card .ph-actual-row { margin-left: auto; }
.ph-measured-main { display: flex; align-items: baseline; gap: 12px; }
.ph-actual-value { font-size: 44px; font-weight: 600; line-height: 1; color: var(--text-accent); font-variant-numeric: tabular-nums; }
.ph-measured-notes { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.ph-measured-predicted { display: inline-flex; align-items: center; color: var(--text-secondary); }
.ph-actual-row { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ph-actual-label { font-size: 13px; color: var(--text-secondary); }

.ph-gauge-wrap { position: relative; height: 30px; margin: 20px 0 2px; }
.ph-gauge { position: absolute; left: 0; right: 0; top: 10px; height: 10px; border-radius: 5px; overflow: hidden; background: var(--border-strong); }
.ph-gauge-range { position: absolute; top: 0; bottom: 0; background: var(--text-success); opacity: 0.6; }
.ph-uncertainty-band { position: absolute; top: 5px; height: 20px; box-sizing: border-box; border: 1px dashed var(--text-warning); border-radius: 6px; background: var(--bg-warning); opacity: 0.8; }
.ph-dose-arrow { position: absolute; top: 14px; height: 2px; background: var(--text-primary); opacity: 0.7; }
.ph-gauge-wrap .optimal-marker { position: absolute; top: 3px; width: 3px; height: 24px; margin-left: -1.5px; border-radius: 2px; background: var(--text-primary); border-bottom: none; cursor: help; }
.ph-predicted-marker { position: absolute; top: 7px; width: 16px; height: 16px; margin-left: -8px; box-sizing: border-box; border-radius: 50%; border: 3px solid var(--surface-2); }
.ph-predicted-marker.ph-fill-in { background: var(--text-success); }
.ph-predicted-marker.ph-fill-out { background: var(--text-warning); }
.ph-predicted-marker.ph-marker-ghost { top: 9px; width: 12px; height: 12px; margin-left: -6px; border: 2px dashed var(--text-muted); background: transparent; }
.ph-gauge-wrap .actual-ph-marker { position: absolute; top: 5px; width: 20px; height: 20px; margin-left: -10px; border-bottom: none; cursor: help; }
.ph-gauge-wrap .actual-ph-marker::before { content: ""; position: absolute; inset: 3px; background: var(--text-accent); border: 2px solid var(--surface-2); border-radius: 2px; transform: rotate(45deg); }
.ph-after-marker { position: absolute; top: 9px; width: 12px; height: 12px; margin-left: -8px; border-radius: 50%; border: 2px solid var(--surface-2); background: var(--text-success); }
.ph-marker-label { position: absolute; top: -14px; transform: translateX(-50%); font-size: 12px; font-weight: 600; white-space: nowrap; color: var(--text-secondary); }
.ph-marker-label.ph-target-label { font-weight: 400; }
.ph-marker-label.ph-label-measured { color: var(--text-accent); }
.ph-scale { position: relative; height: 16px; margin-bottom: 14px; font-size: 12px; color: var(--text-muted); }
.ph-scale span { position: absolute; top: 0; transform: translateX(-50%); }
.ph-scale span:first-child { transform: none; }
.ph-scale span:last-child { transform: translateX(-100%); }

.ph-dose { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-radius: 10px; background: var(--surface-1); margin-bottom: 12px; }
.ph-dose-row { display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap; }
.ph-dose-name { flex: 1 1 200px; display: flex; flex-direction: column; font-size: 14px; }
.ph-dose-source { display: inline-flex; align-items: center; font-size: 12px; color: var(--text-secondary); }
.ph-dose-source.is-measured { color: var(--text-accent); }
.ph-stepper { display: flex; align-items: center; gap: 10px; }
.ph-step-btn { width: 40px; height: 40px; padding: 0; font-size: 20px; line-height: 1; background: transparent; }
.ph-dose-value { min-width: 84px; text-align: center; font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ph-dose-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.ph-preset { height: 30px; padding: 0 12px; border-radius: 999px; background: transparent; color: var(--text-secondary); font-size: 13px; }
.ph-preset.active { border-color: var(--text-primary); color: var(--text-primary); background: var(--surface-2); font-weight: 500; }

.ph-reading-row { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap; padding: 10px 12px; margin-bottom: 12px; border: 1px dashed var(--border-strong); border-radius: 10px; }
.ph-reading-text { flex: 1 1 220px; display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.ph-reading-hint { font-size: 12px; color: var(--text-secondary); }

/* Deliberately more prominent than the quiet, underlined .cli-toggle
   links used elsewhere (Edit/Clear/Cancel) - a real, bordered button. */
.enter-actual-ph-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-accent); color: var(--text-accent);
  border: 1px solid var(--text-accent); border-radius: var(--radius);
  padding: 6px 12px; font-size: 13px; font-weight: 600; cursor: pointer;
}
.enter-actual-ph-btn:hover { background: var(--text-accent); color: var(--surface-1); }
.enter-actual-ph-btn i { font-style: normal; }
.ra-row { display: flex; align-items: baseline; justify-content: space-between; margin: 4px 0 4px; }
.ra-label { font-size: 13px; color: var(--text-secondary); }
.ra-value { font-size: 20px; font-weight: 600; }

.shop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.shop-card { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 12px; padding: 1rem; display: flex; flex-direction: column; border-left-width: 4px; }
.shop-card-salt { border-left-color: var(--text-warning); }
.shop-card-amendment { border-left-color: var(--text-accent); }
.shop-card-package { border-left-color: var(--text-success); }
.shop-card-equipment { border-left-color: var(--text-danger); }
.shop-card-title { font-size: 14px; font-weight: 500; margin: 0 0 2px; }
.shop-card-formula { font-size: 12px; color: var(--text-accent); font-family: monospace; margin: 0 0 8px; }
.shop-card-desc { font-size: 12px; color: var(--text-secondary); margin: 0 0 12px; flex: 1; line-height: 1.4; }
.shop-card-amount { font-size: 12px; color: var(--text-accent); font-weight: 500; margin: 0 0 10px; padding: 6px 8px; background: var(--bg-accent); border-radius: 6px; }
.shop-card-link { text-decoration: none; text-align: center; font-size: 13px; display: flex; align-items: center; justify-content: center; }
.salt-tier-badge { display: inline-block; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 7px; border-radius: 10px; margin-bottom: 6px; align-self: flex-start; cursor: help; }
.salt-tier-badge.tooltip { border-bottom: none; }
.tier-essential { background: var(--bg-success); color: var(--text-success); }
.tier-recommended { background: var(--bg-warning); color: var(--text-warning); }
.tier-situational { background: var(--border); color: var(--text-muted); }
.shop-card-nolink { font-size: 12px; color: var(--text-muted); text-align: center; margin: 0; font-style: italic; }

.chalk-help-link { margin-left: 6px; font-size: 11px; font-weight: 400; }

/* Print - only meaningfully applies to index.html's layout (.layout/.panel
   are exclusive to that page - checked before adding this), triggered by
   the "Print results" button (id="printBtn") calling window.print().
   Goal: a clean, single-column reference page with everything currently
   computed, regardless of what's toggled open/closed on screen - not a
   literal screenshot of the current view. */
/* On narrow viewports, a tooltip bubble anchored to its trigger (bottom:
   100%, left: 0, fixed 220px width) can both overflow past the edge of
   a narrow grid cell horizontally AND land on top of an entirely
   different row's content vertically (bottom:100% pops it up from
   wherever it's anchored - on a 2-column mobile metric grid, "up" from
   the second row lands squarely on the first row's tiles). Confirmed
   via a real mobile-viewport screenshot, not assumed. Sidesteps both by
   detaching from the grid layout entirely and centering in the
   viewport instead - less precisely "anchored" to the trigger, but
   never overlaps the wrong content or clips off-screen. Kept after every
   other tooltip placement rule (and matching the side panel's more
   specific one) so none of them can pull a bubble off-screen here. */
@media (max-width: 600px) {
  .tooltip .tooltip-bubble,
  .tooltip .metric-tooltip-bubble,
  .left-column .tooltip .metric-tooltip-bubble {
    position: fixed;
    top: 50%; left: 50%; right: auto; bottom: auto;
    transform: translate(-50%, -50%);
    width: auto; max-width: calc(100vw - 40px);
    white-space: normal;
    z-index: 200;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
  }
}

@media print {
  .chalk-help-link { display: none; }
  /* Sets a known, deterministic margin rather than relying on whatever
     the browser/OS print dialog defaults to (which varies) - lets the
     beforeprint auto-scale-to-fit logic in app.js calculate the exact
     usable page height instead of guessing at it. */
  @page { size: letter; margin: 0.4in; }

  /* Constrains the actual rendered width to match the print page's
     usable width (8.5in letter - 2*0.4in margin = 7.7in), regardless of
     the screen viewport the page happens to be sitting at when print is
     triggered. Without this, beforeprint's height measurement in app.js
     reflects the (wider) screen layout - text wraps less at that width
     than it will in the actual narrower printed page, so the measured
     height comes out too low and the auto-scale-to-fit logic doesn't
     kick in when it needs to. Confirmed via an actual generated print
     PDF: content measured as fitting in the browser was still 2 pages
     once really printed at letter width. */
  .page { max-width: 7.7in; margin: 0 auto; }

  /* Force the light theme regardless of the browser/OS's dark-mode
     preference - printing the dark theme's actual colors would waste
     ink and risks near-invisible text if the browser strips
     backgrounds during print by default. Placed after the
     prefers-color-scheme:dark block so it wins the cascade regardless
     of system preference. */
  :root {
    --surface-2: #ffffff;
    --surface-1: #f5f4f0;
    --surface-0: #fbfaf7;
    --text-primary: #0b0b0b;
    --text-secondary: #52514e;
    --text-muted: #898781;
    --text-accent: #0a66d6;
    --text-success: #08794b;
    --text-warning: #b0500a;
    --text-danger: #d32f2f;
    --border: #e1e0d9;
    --border-strong: #c3c2b7;
    --border-accent: #5aa9ff;
    --bg-accent: #dcedff;
    --bg-success: #d9f2e3;
    --bg-warning: #fdedde;
  }

  .print-only { display: block !important; }
  .page-header { display: none !important; }

  /* The Mash pH / Chart & comparison / Mineral impact tabs only show one
     panel at a time on screen - print should still show all of them
     regardless of which was active, matching the rest of this block's
     "everything currently computed, not a screenshot of the current
     view" goal. Tab buttons themselves are meaningless on paper. */
  .result-tabs { display: none !important; }
  .result-tab-panel { display: block !important; }

  /* Interactive chrome that means nothing on paper. */
  .page-nav, #solveBtn, #errorMsg, .cli-toggle,
  .copy-table-btn, .whatsnew-overlay, .tooltip-bubble,
  #shareLinkBtn {
    display: none !important;
  }

  /* Exception to the .tooltip-bubble rule above: the chalk dissolution
     warning lives only in this tooltip now (no standalone banner
     anymore) - without this, it would be silently missing from print
     entirely, contradicting this block's "everything currently
     computed" goal. Shows the short, print-only-inline instruction
     instead of the full explanatory tooltip text (kept on screen,
     hidden here) - the actionable instruction is what matters once
     it's on paper rather than hovered on screen. */
  .salt-warning-tooltip { display: inline !important; }
  .salt-warning-tooltip i { display: none; }
  .salt-warning-tooltip .tooltip-bubble { display: none !important; }
  .print-only-inline { display: inline !important; }
  .chalk-warning-print { color: var(--text-warning); font-size: 12px; }

  /* Target/Volume input controls, plus the Configure-decomposition
     tiles (Source water, Salts on hand, Grain bill, Preferences) and
     the Mash/Sparge split controls - all shown as plain text in
     #printSummary instead, since a live <select>/<input>/checkbox or a
     full interactive card printed as-is is confusing on paper (no way
     to tell what was actually selected without interacting with it,
     browsers render form controls inconsistently when printed, and -
     confirmed by an actual screenshot - the tiles alone took up most
     of a page before any results even appeared). */
  #mode-style, #mode-profile, #mode-mineral_water, #mode-advanced,
  .mode-row, .volume-row,
  #sourceWaterTile, #saltsOnHandTile, #grainBillTile, #preferencesCard,
  #mashSpargeSplitRow, #mashVolumeRow {
    display: none !important;
  }

  /* With all of the above hidden, .left-column still reserved ~48px of
     empty panel/label margins - enough to push the Mash/Sparge split
     case onto a 2nd page (verified via real Chrome print-to-PDF).
     Everything it held is in #printSummary, so drop it entirely. */
  .left-column { display: none !important; }

  /* Single column instead of the sidebar+results grid, since the
     sidebar's only remaining content after the above is "Target"/
     "Configure" labels with nothing under them. */
  .layout { display: block; }
  .panel { border: none; padding: 0; margin-bottom: 12px; }
  .panel .label:first-of-type { display: none; } /* the now-empty "Target" label */

  body { background: var(--surface-0); color: var(--text-primary); }
  .card, .metric-card { break-inside: avoid; border: 1px solid var(--border); }

  /* Fit the whole plan on one printed page. The content easily fits -
     confirmed by generating an actual print PDF and rasterizing it:
     both pages were mostly blank (page 1 ~80% full, page 2 ~55%), the
     ion-comparison table just didn't fit in what remained of page 1 and
     the whole break-inside:avoid card got pushed to page 2 as a unit.
     Screen spacing is comfortable-for-reading; print needs compact-to-
     fit instead, so this tightens padding/margins/font-sizes across the
     board rather than relying on page-break tricks alone. */
  #printHeader .print-title { font-size: 20px; font-weight: 500; }
  #printHeader p { margin-bottom: 2px !important; }
  #printSummary { font-size: 13px; line-height: 1.35; margin-bottom: 6px; }

  .panel { margin-bottom: 8px; }
  .metric-grid { gap: 6px; margin-bottom: 8px; }
  .metric-card { padding: 0.35rem 0.55rem; }
  .metric-card .m-label { margin-bottom: 0; }
  .metric-card .m-value { font-size: 15px; }
  .banner { padding: 5px 8px; margin-bottom: 8px; font-size: 11px; }
  .row { padding: 2px 0; font-size: 12px; }
  .salt-row { padding: 5px 0; }
  .salt-row-name { font-size: 13px; }
  .salt-row-amount { font-size: 15px; }

  /* The three result tabs (Mash pH, Chart & comparison, Mineral
     impact) all show at once in print - together the biggest
     contributor to overflow (measured at ~630px of ~930px total
     layout height via an actual generated PDF, well before this
     tightening). Grown since the auto-scale-to-fit approach below was
     first tried and abandoned in its original form (CSS zoom/transform
     don't reliably affect Chromium's print-to-PDF page-break
     calculation - confirmed by generating real PDFs with both and
     finding the page count unchanged even when the computed scale
     factor was correct, since neither actually reduces the element's
     contribution to document flow the way real box-model properties
     do) - compacting the actual content directly is what gets the
     common case to fit without any JS involved at all. */
  .enter-actual-ph-btn { display: none; } /* prompts to click a button - meaningless on paper */
  /* Interactive-only parts of the pH card - the dose value itself stays. */
  .ph-help-link, .ph-info-tip, .ph-step-btn, .ph-dose-presets, .ph-reading-row, .ph-actual-row .cli-toggle { display: none !important; }
  .ph-hero, .ph-dose { margin-bottom: 4px; }
  .ph-card-header { margin-bottom: 4px; }
  .ph-gauge-wrap { margin-bottom: 2px; }
  .impact-table .cell, .impact-table .head { padding: 3px 0; }
  .impact-table { font-size: 11px; }
  .impact-table .impact-notes { line-height: 1.25; }

  /* Heavier-content cases (Mash/Sparge split - two salt lists instead
     of one - and/or an entered actual pH reading and its extra row)
     still overflow past one page even with the tightening above -
     confirmed via generated PDFs at ~1250px against the ~980px
     printable height. .print-extra-compact (toggled by JS below, only
     when actually needed) tightens further using real padding/margin/
     font-size properties - unlike zoom/transform, these actually
     shrink the element's contribution to document flow, which is what
     Chromium's print pagination measures. */
  .print-extra-compact .card { padding: 0.2rem 0.35rem; margin-bottom: 2px; }
  .print-extra-compact .salt-row { padding: 1px 0; }
  .print-extra-compact .salt-row-amount { font-size: 13px; }
  .print-extra-compact .metric-card { padding: 0.2rem 0.35rem; }
  .print-extra-compact .metric-card .m-value { font-size: 12px; }
  .print-extra-compact .metric-grid { gap: 4px; margin-bottom: 4px; }
  .print-extra-compact .ph-big-value { font-size: 18px; }
  .print-extra-compact .ph-actual-value { font-size: 20px; }
  .print-extra-compact .ph-after-value { font-size: 16px; }
  .print-extra-compact .ph-dose { padding: 2px 6px; }
  .print-extra-compact .banner { padding: 2px 5px; margin-bottom: 2px; font-size: 9px; }
  .print-extra-compact .impact-table .cell, .print-extra-compact .impact-table .head { padding: 1px 0; }
  .print-extra-compact .impact-table .impact-notes { line-height: 1.15; }
  .print-extra-compact .ra-row { margin: 4px 0 2px; }
  .print-extra-compact .banner { line-height: 1.2; }
  .print-extra-compact .result-tab-panel { margin-bottom: 0; }
  .print-extra-compact .result-tab-panel .card { margin-bottom: 2px; }
  .print-extra-compact .panel { margin-bottom: 4px; }
  .print-extra-compact #printHeader .print-title { font-size: 16px; }
  .print-extra-compact .card-header-row { margin-bottom: 2px; }
  .print-extra-compact .salt-card-title { font-size: 13px; }
  .print-extra-compact .salt-row-name { font-size: 12px; }
  .print-extra-compact .salt-row-amount { font-size: 12px; }
  .print-extra-compact .impact-table { font-size: 9px; }
  .impact-table { font-size: 10px; }
  .impact-table .head { padding: 0 0 2px; }
  .impact-table .cell { padding: 2px 0; }
  .impact-table .impact-notes { line-height: 1.15; }
  .compare-table { font-size: 10px; }
  .compare-table .head { padding: 0 0 2px; }
  .compare-table .cell { padding: 1px 0; }
  .card-header-row { margin-bottom: 4px !important; }
  .card { padding: 0.45rem 0.6rem; margin-bottom: 6px; }
  .ph-big-value { font-size: 22px; }
  .card .label { margin-bottom: 4px; }

  /* The bar chart duplicates the comparison table right below it (same
     target-vs-result values, just visualized) and reserves a fixed
     200px+16px whether or not it fits - a big, unnecessary chunk of
     page 1 for print, where the table's exact numbers matter more than
     a bar chart anyway (which also wouldn't render meaningfully on a
     B&W printer). The legend only exists to explain the chart's colors,
     so it goes too. */
  .chart-wrap, .legend { display: none !important; }
}
