/* tAle — "Precision Wort Instrument" design system.
   Industrial minimalism: charcoal tonal layers, hairline borders, amber for
   active/primary, acid lime for live status only. Hierarchy comes from tonal
   steps and 1px outlines, NOT from shadows, glows or gradients — if you find
   yourself reaching for a box-shadow to separate two things, use a tonal step
   or a hairline instead. */
@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,300..800;1,300..800&family=Geist+Mono:wght@300..700&display=swap');

:root {
  /* --- Tonal layers. Level 0 is the canvas, each step up sits "on" the last. --- */
  --surface-base: #0B0B0B;        /* L0 page canvas */
  --surface-elevated: #121212;    /* L1 cards, primary containers */
  --surface-interface: #1A1A1A;   /* L2 inputs, secondary buttons, nested rows */
  --surface-hover: #201f1f;
  --surface-variant: #353534;

  /* Legacy aliases: the whole app already reads these names, so re-pointing
     them here is what repaints most of the UI. Keep them in sync. */
  --bg-main: var(--surface-base);
  --bg-sidebar: #0e0e0e;
  --bg-card: var(--surface-elevated);
  --bg-card-hover: var(--surface-hover);

  --border-color: rgba(255, 255, 255, 0.08);   /* the hairline */
  --border-accent: rgba(255, 184, 0, 0.35);    /* hairline, but active */

  /* --- Amber: active brewing state, primary actions, live timers. --- */
  --amber-primary: #ffdca1;       /* amber text/accent on dark */
  --amber-hover: #ffe9c4;
  --primary-container: #ffb800;   /* solid amber fill (buttons) */
  --on-primary-container: #3d2c00;/* text ON solid amber — near-black, not pure */
  --amber-dim: #ffba20;
  --amber-glow: rgba(255, 184, 0, 0.18);

  --copper-secondary: #ffba20;

  --text-main: #e5e2e1;
  --text-muted: #d5c4ab;          /* warm dim — reads as "on-surface-variant" */
  --text-dim: #888888;
  --text-vibrant: #ffffff;

  /* --- Acid lime: functional status ONLY ("running", "ready", "active").
         Never decorative — if it isn't reporting a live state, it isn't lime. --- */
  --success: #caf300;
  --success-dim: #b0d500;

  --danger: #ffb4ab;
  --info: var(--amber-primary);

  /* Shapes: soft-industrial. Deliberate, geometric, never pill-shaped. */
  --radius-lg: 16px;              /* large containers, modals */
  --radius-md: 12px;              /* standard cards */
  --radius-sm: 8px;               /* buttons, inputs, chips */

  /* Kept so existing rules referencing them stay valid. Depth is tonal now:
     the only legitimate shadow is modal separation, and it is sharp, not soft. */
  --glass-blur: none;
  --shadow-lg: 0 24px 48px rgba(0, 0, 0, 0.75);
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--surface-variant) var(--surface-base);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: var(--surface-base);
}

::-webkit-scrollbar-thumb {
  background: var(--surface-variant);
  border-radius: 0;
  border: 2px solid var(--surface-base);
  transition: background 0.15s ease;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--outline, #9e8f78);
}

body {
  font-family: 'Hanken Grotesk', system-ui, -apple-system, sans-serif;
  /* Flat. The old build had two radial gradients here; the whole point of this
     system is that depth is tonal, so the canvas is one honest colour. */
  background: var(--surface-base);
  color: var(--text-main);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6, .card-title, .modal-title {
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* Technical Monospaced Metrics.
   Geist Mono on every number so columns align and a running countdown does not
   make the layout jitter as digits change width. */
.metric-value, .timer-display, table td, .mono-data, .srm-swatch, .stage-dur-input, .alarm-time-input {
  font-family: 'Geist Mono', monospace;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- Type scale (from the design system) --------------------------------- */
.t-metric-label {
  font-size: 10px;
  font-weight: 800;
  line-height: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.t-label-caps {
  font-size: 12px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.t-metric-value {
  font-family: 'Geist Mono', monospace;
  font-size: 18px;
  font-weight: 600;
  line-height: 24px;
  font-variant-numeric: tabular-nums;
}

/* One hairline, used everywhere a boundary is needed. */
.hairline { border: 1px solid var(--border-color); }

/* --- Icons ---------------------------------------------------------------
   Outline by default, solid when the thing the icon labels is active/live.
   Fill carries meaning here; it is not decoration.

   fill/stroke MUST be set in CSS rather than as SVG presentation attributes:
   a CSS `fill` rule beats a `fill="none"` attribute, so a blanket
   `.icon { fill: currentColor }` would silently flood every outline icon into
   a solid blob. Hence two explicit variant classes and no blanket fill. */
.icon {
  display: inline-block;
  vertical-align: -0.14em;
  flex-shrink: 0;
}

.icon--outline {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon--solid {
  fill: currentColor;
  stroke: none;
}

/* Star used for BJCP scores — always amber and always solid: a score is a
   value that has been awarded, which is precisely the "active" case. */
.icon-star { color: var(--primary-container); }

/* Column-sort affordance in reference tables: a dim outline until that column
   is the one being sorted by, then a solid amber caret pair. */
.icon-sort { color: var(--text-dim); opacity: 0.55; margin-left: 4px; }
th:hover .icon-sort { opacity: 1; }
th .icon-sort.is-active { opacity: 1; color: var(--primary-container); }

/* Section heading icon (ingredient categories, panel titles). */
.icon-heading { color: var(--text-muted); margin-right: 0.5rem; }

/* --- Outline/solid swap for static markup --------------------------------
   The sidebar and the grid/list switcher are static HTML, so their active
   state is a CSS class, not a re-render. Each button carries BOTH variants and
   CSS shows exactly one. */
.icon-swap > .icon--solid { display: none; }
.nav-tab.active .icon-swap > .icon--solid,
.btn-view-mode.active .icon-swap > .icon--solid { display: inline-block; }
.nav-tab.active .icon-swap > .icon--outline,
.btn-view-mode.active .icon-swap > .icon--outline { display: none; }

.icon-swap { display: inline-flex; flex-shrink: 0; }

/* SIDEBAR NAVIGATION
   Flat, one tonal step below the canvas, separated by a hairline. No blur, no
   drop shadow — a shadow here would be the loudest thing on the page. */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 256px;
  height: 100vh;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border-color);
  padding: 2rem 0;
  display: flex;
  flex-direction: column;
  z-index: 100;
  transition: transform 0.25s ease;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0 1.5rem;
  margin-bottom: 2.5rem;
}

.brand-logo {
  height: 40px;
  width: auto;
  display: block;
}

/* Shown only if the logo SVG fails to load. A wordmark, not an emoji — the
   brand should still read as tAle rather than as a beer glass. */
.brand-fallback {
  font-size: 1.6rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--primary-container);
  line-height: 40px;
}

.nav-tabs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex-grow: 1;
}

/* Active state = amber text + amber left rule + one tonal step. This is the
   treatment on 3 of the 4 Stitch desktop screens; the lime-filled variant on
   the Ingredients screen was a one-off and is not used — lime means "live". */
.nav-tab {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 0.95rem;
  font-weight: 500;
  padding: 0.85rem 1.5rem;
  border-radius: 0;
  border-left: 2px solid transparent;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  text-align: left;
}

/* The flex child is now the .icon-swap wrapper holding both variants. */
.nav-tab .icon-swap { flex: 0 0 20px; width: 20px; height: 20px; }
.nav-tab .nav-icon { width: 20px; height: 20px; }

.nav-tab:hover {
  color: var(--text-main);
  background: var(--surface-interface);
}

.nav-tab.active {
  color: var(--primary-container);
  background: #1c1b1b;
  border-left: 2px solid var(--primary-container);
  font-weight: 700;
}

/* TOP HEADER */
.top-header {
  position: fixed;
  top: 0;
  left: 256px;
  right: 0;
  height: 64px;
  background: var(--surface-base);
  border-bottom: 1px solid var(--border-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 32px;
  z-index: 90;
}

.header-title {
  font-size: 24px;
  line-height: 32px;
  font-weight: 700;
  color: var(--text-main);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Left side of the header: wordmark + view title. `min-width: 0` so the title
   can shrink and ellipsise instead of pushing the account button off-screen —
   the header used to overlap itself for exactly this reason. */
.header-lead {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-width: 0;
  overflow: hidden;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex: 0 0 auto;
}

/* Wordmark in the top bar: mobile only, where the sidebar (and its logo) has
   become a bottom tab bar. */
.header-logo { display: none; height: 26px; width: auto; flex: 0 0 auto; }
@media (max-width: 992px) {
  .header-logo { display: block; }
}

/* LAYOUT CONTAINERS */
.main-wrapper {
  margin-left: 256px;
  padding-top: 64px;
  min-height: 100vh;
}

.container {
  max-width: 1440px;
  margin: 0 auto;
  /* 32px desktop margin / 16px mobile, per the spacing scale. */
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Responsive Tablet & Mobile Layout Scaling.
   The design system calls for the sidebar to collapse into a bottom navigation
   on mobile — thumb-reachable, and it frees the vertical space that a drawer
   header would eat. The drawer behaviour is kept for tablets (993-1200px is
   still sidebar; below 992 it becomes the bottom bar). */
@media (max-width: 992px) {
  .sidebar {
    /* Bottom bar: fixed, full width, above the content. */
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: auto;
    transform: none;
    flex-direction: row;
    padding: 0;
    border-right: none;
    border-top: 1px solid var(--border-color);
    background: var(--bg-sidebar);
    /* Sits above the home indicator on iOS. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* The wordmark belongs in the top bar on mobile, not the nav. */
  .sidebar .brand { display: none; }

  .nav-tabs {
    flex-direction: row;
    width: 100%;
    gap: 0;
  }

  .nav-tab {
    flex: 1 1 0;
    flex-direction: column;
    gap: 4px;
    justify-content: center;
    text-align: center;
    padding: 0.6rem 0.25rem;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border-left: none;
    border-top: 2px solid transparent;
    min-width: 0;
  }

  /* Six tabs share ~390px on a phone (Shopping was added 2026-08-12), so a
     label like "Recipe Library" no longer fits on one line. Wrapping to two
     short lines keeps it readable; ellipsising it to "RECIPE LI…" does not.
     Capped at two lines so one long label cannot make the bar taller than the
     space reserved for it in .main-wrapper's padding-bottom. */
  .nav-tab span:not(.icon-swap) {
    max-width: 100%;
    white-space: normal;
    overflow-wrap: break-word;
    line-height: 1.1;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .nav-tab { padding: 0.55rem 0.15rem; letter-spacing: 0.02em; }

  .nav-tab.active {
    border-left: none;
    border-top: 2px solid var(--primary-container);
    background: #1c1b1b;
  }

  .main-wrapper {
    margin-left: 0 !important;
    /* Room for the bottom bar so the last card is never trapped under it. */
    /* 76px, not 68px: nav labels may wrap to two lines now that there are six
       tabs. Too small here and the last card is trapped under the bar. */
    padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px));
  }
  .top-header {
    left: 0 !important;
    padding: 0 16px;
  }
  .header-title { font-size: 18px; }
  .header-actions { gap: 0.75rem; }
  .header-lead { gap: 0.6rem; }
  .container {
    padding: 16px;
    gap: 16px;
  }
  .recipes-grid, .batch-grid {
    grid-template-columns: 1fr !important;
    gap: 1rem !important;
  }
  .card, .batch-card {
    padding: 1rem !important;
  }
  .mobile-hide {
    display: none !important;
  }
  .modal {
    padding: 1rem !important;
    width: 95vw !important;
  }
  .instruction-step-item {
    flex-wrap: wrap;
  }
  .toolbar-panel { padding: 0.75rem; }
  .toolbar-select { flex: 1 1 auto; }
}

/* Very narrow: four metrics across is unreadable, so go two-up. */
@media (max-width: 420px) {
  .metric-strip, .metrics-row { grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
  /* The bottom tab bar already names the current view, and it highlights it —
     so the header title is pure duplication at the width where space runs out. */
  .header-title { display: none; }
}

/* UI Elements */
/* This is a <button> now, not a <span> — a touch device has no hover, so the
   old `title` attribute showed nothing at all on a phone. It therefore has to
   reset the UA button styling (padding, font, background) back to the flat
   circle it has always looked like. */
.metric-tooltip-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border-color);
  background: transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.65rem;
  font-weight: bold;
  line-height: 1;
  cursor: pointer;
  margin-left: 4px;
  vertical-align: middle;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.metric-tooltip-icon:hover,
.metric-tooltip-icon:focus-visible {
  color: var(--primary-container);
  border-color: var(--border-accent);
}

/* A 14px circle is a fine hover target and a poor thumb target. On a touch
   screen it grows; on a mouse it stays exactly as it was. */
@media (pointer: coarse) {
  .metric-tooltip-icon {
    width: 22px;
    height: 22px;
    font-size: 0.75rem;
    margin-left: 6px;
  }
}

/* The popover itself. Appended to <body> and positioned in script, because
   these icons live inside overflow-scrolling tables and inside <dialog>
   modals — anything positioned relative to the icon gets clipped by one or
   stacked under the other. Above the modal (1000), below the toasts (2000)
   so a save confirmation is never hidden behind an explanation. */
.field-tooltip {
  position: fixed;
  z-index: 1500;
  max-width: min(280px, calc(100vw - 16px));
  padding: 0.6rem 0.75rem;
  background: var(--surface-elevated);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8);
  color: var(--text-main);
  font-size: 0.8rem;
  line-height: 1.45;
  /* Hidden by default and non-interactive: it is a label, and swallowing a tap
     meant for the control underneath would be worse than the problem it fixes. */
  display: none;
  pointer-events: none;
}
.field-tooltip.visible { display: block; }

/* Segmented control. Square-cornered on purpose: the system forbids pills. */
.unit-toggle {
  display: flex;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 2px;
}

.unit-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.35rem 0.8rem;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.unit-btn:hover { color: var(--text-main); }

.unit-btn.active {
  background: var(--surface-variant);
  color: var(--primary-container);
  font-weight: 700;
}

.btn {
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.7rem 1.4rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  white-space: nowrap;
}

.btn:focus-visible {
  outline: 2px solid var(--primary-container);
  outline-offset: 2px;
}

/* Solid amber, near-black label. No gradient, no glow, no lift on hover —
   the button is a control, not a landing-page CTA. */
.btn-primary {
  background: var(--primary-container);
  color: var(--on-primary-container);
  font-weight: 800;
}

.btn-primary:hover { opacity: 0.88; }

.btn-secondary {
  background: var(--surface-interface);
  border-color: var(--border-color);
  color: var(--text-main);
}

.btn-secondary:hover {
  background: var(--surface-variant);
  border-color: rgba(255, 255, 255, 0.16);
}

.btn-danger {
  background: transparent;
  border-color: rgba(255, 180, 171, 0.3);
  color: var(--danger);
}

.btn-danger:hover {
  background: rgba(255, 180, 171, 0.1);
  border-color: var(--danger);
}

/* Square icon-only button (overflow menus, close, view switcher). */
.btn-icon {
  width: 40px;
  height: 40px;
  padding: 0;
  flex: 0 0 40px;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s ease, background 0.15s ease;
}

.btn-icon:hover { color: var(--text-main); background: var(--surface-variant); }

.view-section {
  display: none;
  animation: fadeIn 0.3s ease;
}

/* The 24px gutter lives HERE, not on .container.
   .container's children are the view sections and only one is ever visible, so
   a gap there spaced nothing. The blocks that actually need air — action row,
   toolbar, grid — are siblings inside the section. Anything added to a view
   from now on is spaced correctly by default; don't reach for a margin. */
.view-section.active {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Cards Grid */
.recipes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 1.5rem;
}

/* Card = one tonal step + a hairline. That is the entire treatment.
   The old build had a four-colour rainbow bar across the top of every card and
   a lift-on-hover; both are gone. Hover moves the border to amber instead. */
.card {
  background: var(--surface-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  transition: border-color 0.15s ease, background 0.15s ease;
  position: relative;
}

.card:hover {
  background: #151515;
  border-color: var(--border-accent);
}

.clickable-card {
  cursor: pointer;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
}

.card-header > div:first-child {
  flex: 1;
  min-width: 0;
}

.card-title {
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
  color: var(--text-main);
  word-break: break-word;
  margin-bottom: 0.5rem;
  transition: color 0.15s ease;
}

.card:hover .card-title { color: var(--primary-container); }

/* Style chip. Rectangular — the system explicitly forbids pill shapes. */
.badge {
  display: inline-block;
  font-size: 10px;
  line-height: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface-interface);
  color: var(--text-muted);
  border: 1px solid var(--border-color);
  flex-shrink: 0;
  white-space: nowrap;
}

/* --- Fermentation gravity chart ------------------------------------------
   preserveAspectRatio="none" lets the SVG stretch to the panel width while
   keeping a fixed height; stroke widths are given vector-effect so the line
   doesn't smear horizontally as it stretches. */
/* ⚠️ NO fixed height and NO width:100% stretch here.
   The viewBox is generated at the container's measured pixel width (see
   mountGravityCharts in app.js), so the SVG is already the right size and any
   CSS sizing would scale it — which is exactly the bug this replaced: a fixed
   640 viewBox stretched to a fluid width smeared the text sideways and turned
   the data dots into ellipses. `height: auto` keeps the intrinsic ratio. */
/* Chart and readings sit side by side on a wide card. Capping the chart alone
   fixed its prominence but left the space beside it empty — the table is what
   wants that width. */
.gravity-section {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}
/* A definite basis, not content-sized: .gravity-chart-wrap measures its own
   width to decide what to draw, and a shrink-to-fit parent would make that
   circular (chart sizes column, column sizes chart). */
.gravity-chart-col {
  flex: 0 0 460px;
  max-width: 100%;
}
.gravity-readings-col {
  flex: 1 1 0;
  /* Without this a wide notes column refuses to shrink and pushes the row
     sideways instead of wrapping. */
  min-width: 0;
  overflow-x: auto;
}

/* Sharing the row with the chart makes the table narrower, and the columns that
   must NOT absorb that are the fixed-shape ones: a date broke as "2026-07-" /
   "29", and the Edit/Delete pair stacked. Pin both to one line and let Notes —
   the only genuinely elastic column — take the slack.
   ⚠️ Scoped to the side-by-side layout ONLY. Stacked, the table already has the
   full width and does not need this; applying it there would widen the table
   past a phone screen and put Edit/Delete behind a horizontal scroll. */
@media (min-width: 1001px) {
  .gravity-readings-col td:first-child,
  .gravity-readings-col th:first-child,
  .gravity-readings-col td:last-child,
  .gravity-readings-col th:last-child {
    white-space: nowrap;
  }
  .gravity-readings-col td:last-child,
  .gravity-readings-col th:last-child {
    width: 1%;
  }
}

/* Below this the table is squeezed into uselessness, so stack instead.
   ⚠️ BOTH columns must be reset to `flex: 1 1 auto`. In a column-direction flex
   container `flex-basis` sizes the MAIN axis, which is now the height — so the
   `flex: 1 1 0` that makes the readings column share the width horizontally
   collapses it to zero HEIGHT when stacked, and the whole readings table
   disappears. It did exactly that until a narrow render caught it. */
@media (max-width: 1000px) {
  .gravity-section { flex-direction: column; gap: 0; }
  .gravity-chart-col,
  .gravity-readings-col { flex: 1 1 auto; width: 100%; }
}

.gravity-chart-wrap {
  margin: 0.75rem 0 1rem;
  /* The measuring element. It must be the full available width and must not be
     the thing that scales. */
  width: 100%;
}
.gravity-chart {
  display: block;
  max-width: 100%;
  height: auto;
  overflow: visible;
}

.gc-grid { stroke: var(--border-color); stroke-width: 1; vector-effect: non-scaling-stroke; }

/* Lighter weight than a headline chart: this sits above the readings table and
   should read as a supporting sketch, not compete with the numbers. */
.gc-line {
  fill: none;
  stroke: var(--primary-container);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.gc-dot {
  fill: var(--surface-base);
  stroke: var(--primary-container);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

/* Target FG: a reference, so dashed and dim — it must not compete with the
   measured line, which is the actual data. */
.gc-target {
  stroke: var(--text-dim);
  stroke-width: 1;
  stroke-dasharray: 4 4;
  vector-effect: non-scaling-stroke;
}

.gc-axis {
  fill: var(--text-dim);
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  font-variant-numeric: tabular-nums;
}

/* Painted halo so the label stays readable where it sits on the target line —
   a finished fermentation ends ON that line, so the dots you most want to read
   are right under this text. paint-order draws the stroke first, behind the
   glyphs. */
.gc-target-label {
  letter-spacing: 0.06em;
  paint-order: stroke;
  stroke: var(--surface-interface);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* No mobile height override: the height is derived from the measured width in
   gravityChartHeight(), so a phone already gets a shorter chart. */

/* Batch status chip. Colour says where the batch IS — amber while it's still
   working, lime once it's drinkable, neutral once archived. Every status used
   to render the same emerald chip, so "Fermenting" and "Bottled" were
   indistinguishable and the colour carried no information at all. */
.status-chip {
  display: inline-block;
  font-size: 10px;
  line-height: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  white-space: nowrap;
  flex-shrink: 0;
}

.status-active {
  background: rgba(255, 184, 0, 0.1);
  color: var(--primary-container);
  border-color: rgba(255, 184, 0, 0.35);
}

/* Lime is reserved for live/ready states — this is one of the two places in
   the app that earns it (the other is the running brew-day timer). */
.status-ready {
  background: rgba(202, 243, 0, 0.1);
  color: var(--success);
  border-color: rgba(202, 243, 0, 0.35);
}

.status-done {
  background: var(--surface-interface);
  color: var(--text-dim);
  border-color: var(--border-color);
}

/* Score chip — only rendered when the recipe actually has a scored batch. */
.score-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  background: rgba(255, 184, 0, 0.1);
  border: 1px solid rgba(255, 184, 0, 0.3);
  color: var(--primary-container);
  font-family: 'Geist Mono', monospace;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}
.score-chip .score-max {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}

/* Metric stack: tiny caps label directly above a mono value, so the label is
   visibly secondary to the data. Delimited top and bottom by hairlines. */
.metric-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  padding: 1.15rem 0;
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
}

.metric-stack {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.metric-stack > .metric-label {
  font-size: 10px;
  line-height: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.metric-stack > .metric-value {
  font-family: 'Geist Mono', monospace;
  font-size: 18px;
  line-height: 24px;
  font-weight: 600;
  color: var(--text-main);
  font-variant-numeric: tabular-nums;
}

/* The unit rides small and dim next to the value, never the same weight. */
.metric-unit {
  font-size: 11px;
  color: var(--text-dim);
  margin-left: 3px;
  font-weight: 500;
}

/* Batch size is editable in place — live recipe scaling. Styled to read as a
   metric value, not as a form field, until you touch it. */
.metric-scale-input {
  font-family: 'Geist Mono', monospace;
  font-size: 18px;
  line-height: 24px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--primary-container);
  background: transparent;
  border: none;
  border-bottom: 1px dashed rgba(255, 184, 0, 0.4);
  width: 4.5ch;
  padding: 0;
  cursor: text;
}

.metric-scale-input:focus {
  outline: none;
  border-bottom-color: var(--primary-container);
  background: rgba(255, 184, 0, 0.08);
}

/* Recipe View Modes (Grid, Compact, Table List) */
.recipes-grid.view-compact {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1rem;
}
.recipes-grid.view-compact .card {
  padding: 1rem;
}
.recipes-grid.view-compact .card-desc {
  display: none;
}

.recipes-table-container {
  overflow-x: auto;
  background: var(--surface-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
}
.recipes-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}
.recipes-table th, .recipes-table td {
  padding: 0.75rem 1rem;
  text-align: left;
  border-bottom: 1px solid var(--border-color);
}
.recipes-table th {
  background: transparent;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
.recipes-table tr:last-child td { border-bottom: none; }
.recipes-table tbody tr:hover {
  background: var(--surface-interface);
}

.view-btn-group {
  display: inline-flex;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 2px;
  gap: 2px;
}

.view-btn-group .btn-view-mode {
  padding: 0.45rem 0.7rem;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 6px;
  transition: background 0.15s ease, color 0.15s ease;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  user-select: none;
}

.view-btn-group .btn-view-mode:hover {
  color: var(--text-main);
}

.view-btn-group .btn-view-mode.active {
  background: var(--surface-variant);
  color: var(--primary-container);
}

.card-desc {
  font-size: 14px;
  line-height: 20px;
  color: var(--text-muted);
  /* Two lines, then ellipsis — nine cards should scan at a glance, and a long
     description must not make one card three times the height of its neighbour. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Legacy 4-up metric block, still used by the batch and detail views.
   Restyled to the same label-over-value language as .metric-strip. */
.metrics-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  background: transparent;
  border-radius: 0;
  padding: 1rem 0;
  margin-bottom: 1.25rem;
  border: none;
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
}

.metric-pill {
  text-align: left;
  min-width: 0;
}

.metric-label {
  font-size: 10px;
  line-height: 12px;
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 800;
  letter-spacing: 0.1em;
}

.metric-value {
  font-size: 18px;
  line-height: 24px;
  font-weight: 600;
  color: var(--text-main);
  margin-top: 4px;
}

.card-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: auto;
}

/* Overflow menu — the card's secondary actions (edit / clone / export / print
   / delete) live behind this instead of six buttons competing with the data. */
.overflow-wrap { position: relative; margin-left: auto; }

.overflow-menu {
  position: absolute;
  right: 0;
  bottom: calc(100% + 6px);
  min-width: 190px;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 4px;
  z-index: 60;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.7);
  display: flex;
  flex-direction: column;
}

.overflow-menu button {
  background: transparent;
  border: none;
  color: var(--text-main);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  padding: 0.55rem 0.7rem;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  white-space: nowrap;
}

.overflow-menu button:hover { background: var(--surface-variant); }
.overflow-menu button.danger { color: var(--danger); }
.overflow-menu .menu-sep { height: 1px; background: var(--border-color); margin: 4px 0; }

/* Scaled ingredient preview — a key/value list, mono for the weights so the
   numbers line up down the card. */
.card-preview {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 13px;
  line-height: 18px;
}

.preview-row {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 0.75rem;
  align-items: baseline;
}

.preview-key {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.preview-val {
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.preview-val b {
  font-family: 'Geist Mono', monospace;
  font-weight: 600;
  color: var(--text-main);
  font-variant-numeric: tabular-nums;
}

.preview-more { color: var(--text-dim); font-size: 11px; }
.preview-none { color: var(--text-dim); font-style: italic; }

/* Empty state. Spans the whole grid; dashed border says "this is a container
   awaiting content", not "this is a card". */
.empty-state {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.75rem;
  padding: 4rem 2rem;
  border: 1px dashed var(--border-color);
  border-radius: var(--radius-md);
  color: var(--text-dim);
}

.empty-state h3 {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-main);
}

.empty-state p {
  font-size: 14px;
  line-height: 20px;
  color: var(--text-muted);
  max-width: 42ch;
}

.empty-state-actions { display: flex; gap: 0.75rem; margin-top: 0.5rem; flex-wrap: wrap; justify-content: center; }

/* Recipe note. Amber left rule, not lime: a note is not a live state. */
.card-note {
  border-left: 2px solid var(--border-accent);
  padding-left: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.card-note p {
  font-size: 13px;
  line-height: 18px;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Form Controls & Inputs */
.form-group {
  margin-bottom: 1rem;
}

.form-group label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 0.4rem;
}

/* Inputs sit DARKER than the card they're on (L0 inside L1) and glow amber on
   focus via the border only — no halo. */
.form-control {
  width: 100%;
  padding: 0.6rem 0.8rem;
  background: var(--surface-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-main);
  font-family: inherit;
  font-size: 14px;
  line-height: 20px;
  transition: border-color 0.15s ease;
}

.form-control::placeholder { color: var(--text-dim); }

.form-control:focus {
  border-color: var(--primary-container);
  outline: none;
}

select.form-control {
  appearance: none;
  padding-right: 2rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
                    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) center, calc(100% - 10px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

/* Toolbar panel: the filter row sits in its own L1 container with a hairline,
   which is what separates "controls" from "content" without a heading. */
.toolbar-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  background: var(--surface-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
}

.toolbar-search {
  flex: 1;
  min-width: 240px;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0 1rem;
  transition: border-color 0.15s ease;
}

.toolbar-search:focus-within { border-color: var(--primary-container); }

.toolbar-search svg { flex: 0 0 18px; color: var(--text-muted); }

.toolbar-search input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text-main);
  font-family: inherit;
  font-size: 14px;
  padding: 0.6rem 0;
  min-width: 0;
}

.toolbar-search input::placeholder { color: var(--text-dim); }

/* Filter selects in the toolbar read as controls, not form fields. */
.toolbar-select {
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.65rem 2.25rem 0.65rem 1rem;
  cursor: pointer;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
                    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) center, calc(100% - 10px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.toolbar-select:hover { color: var(--text-main); }
.toolbar-select:focus { outline: none; border-color: var(--primary-container); }

/* Page action row above the toolbar. */
.page-actions {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}

/* Reference sub-tabs (Ingredients / Beer Styles / Tasting Notes). */
.subtab-row {
  display: flex;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 0.75rem;
  flex-wrap: wrap;
}

/* --- Control height ------------------------------------------------------
   Buttons, search fields, selects and icon buttons that share a row must share
   a height, or the row looks accidental.

   Deliberately scoped to the page-level rows rather than set on `.btn`:
   compact buttons inside table rows and modals carry inline padding overrides
   (`padding: 0.25rem 0.5rem`), and a blanket min-height would inflate every
   dense table row by ~20px. */
.page-actions > .btn,
.page-actions .btn-icon,
.toolbar-panel .btn,
.toolbar-select,
.toolbar-search,
.card-actions > .btn,
.card-actions .btn-icon {
  min-height: 42px;
}

.page-actions .btn-icon,
.card-actions .btn-icon { width: 42px; height: 42px; flex: 0 0 42px; }

/* Hide number spinner up/down arrows for clean text entry */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type=number] {
  -moz-appearance: textfield;
}

/* Scaled Output & Form Input Tables */
/* ==========================================================================
   COLLAPSIBLE RECIPE SECTIONS
   A recipe modal is long — three ingredient tables sit between the top and the
   brew steps. Every section folds so the steps are one tap away, and the choice
   is remembered (see rememberRecipeSection in app.js).
   Native <details>/<summary>, so keyboard and screen-reader behaviour is free.
   ========================================================================== */
.recipe-sections { display: flex; flex-direction: column; gap: 0.6rem; }

.recipe-sections-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  font-size: 0.75rem;
  color: var(--text-dim);
}
.recipe-sections-sep { color: var(--border-color); }

.btn-linkish {
  background: transparent;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.btn-linkish:hover { color: var(--primary-container); }

.recipe-section {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--surface-base);
}

.recipe-section-summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.85rem;
  cursor: pointer;
  /* Remove the default disclosure triangle; the chevron is ours. */
  list-style: none;
  /* A 44px row is the minimum comfortable tap target with wet hands. */
  min-height: 44px;
  box-sizing: border-box;
}
.recipe-section-summary::-webkit-details-marker { display: none; }
.recipe-section-summary:hover { background: var(--surface-interface); }

.recipe-section-title {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--primary-container);
  flex: 1 1 auto;
  min-width: 0;
}

/* A folded section still says what is inside it. */
.recipe-section-meta {
  font-family: 'Geist Mono', monospace;
  font-size: 0.72rem;
  color: var(--text-dim);
  flex: 0 0 auto;
}

.recipe-section-chevron {
  display: inline-flex;
  color: var(--text-muted);
  transition: transform 0.15s ease;
  flex: 0 0 auto;
}
.recipe-section[open] > .recipe-section-summary .recipe-section-chevron {
  transform: rotate(180deg);
}

.recipe-section-body { padding: 0 0.85rem 0.85rem; }

.section-hint {
  font-size: 0.78rem;
  color: var(--text-dim);
  line-height: 1.45;
  margin: 0 0 0.5rem;
}

.instruction-list {
  margin: 0;
  padding-left: 1.25rem;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-main);
}
.instruction-list li { margin-bottom: 0.5rem; }

.recipe-notes-body {
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.recipe-detail-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
}
.recipe-detail-actions .btn { flex: 1 1 auto; }

.yeast-panel {
  background: var(--surface-interface);
  padding: 0.8rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
}
.yeast-panel-name { color: var(--text-main); font-size: 0.95rem; }
.yeast-panel-strain { color: var(--text-muted); font-size: 0.85rem; margin-left: 8px; }
.yeast-panel-att { font-size: 0.8rem; color: var(--primary-container); margin-top: 4px; }

.water-line { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.4rem; }

/* Static brew-day schedule (the countdown timer was removed 2026-08-12). */
.stage-ref-list { display: flex; flex-direction: column; gap: 0.5rem; }

.stage-ref-card {
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
}

.stage-ref-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.9rem;
}
.stage-ref-dur {
  font-family: 'Geist Mono', monospace;
  font-size: 0.78rem;
  color: var(--text-dim);
  flex: 0 0 auto;
}

.stage-event-list { list-style: none; margin: 0.4rem 0 0; padding: 0; }
.stage-event-list li {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.2rem 0;
  font-size: 0.82rem;
}
/* Monospaced so the times form a column you can read down mid-boil. */
.stage-event-time {
  font-family: 'Geist Mono', monospace;
  color: var(--primary-container);
  flex: 0 0 3.6rem;
}
.stage-event-label { color: var(--text-muted); min-width: 0; }
.stage-no-events { font-size: 0.78rem; color: var(--text-dim); margin-top: 0.3rem; }

/* ==========================================================================
   SHOPPING LISTS
   Read in a shop, one-handed, so: big tap targets, the item NAME leading (it is
   what you scan the shelf for), and a ticked line stays legible rather than
   disappearing — you still want to see what is already in the basket.
   Lists are shared between accounts, so rows also say who ticked what.
   ========================================================================== */
.shopping-sub {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 3px;
}

.shopping-shared-hint {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-left: auto;
  text-align: right;
}

/* Add-by-search bar: the search grows, the target-list picker does not. */
.shopping-add-panel { align-items: flex-end; gap: 1rem; }
.shopping-add-search { flex: 1 1 320px; min-width: 0; margin-bottom: 0; }
.shopping-add-target { flex: 0 1 240px; min-width: 0; margin-bottom: 0; }
.shopping-add-hint { margin-top: -0.35rem; }

/* The list card reuses the batch card's collapse behaviour and markup, so the
   two read the same way. */
.shopping-list-card { border-left: 2px solid transparent; }
.shopping-list-card.is-active-list { border-left-color: var(--primary-container); }

/* ⚠️ The list card must NOT clip its own actions dropdown.
   `.batch-card` (which this card reuses) sets `overflow: hidden`, and the menu is
   absolutely positioned inside the card, so "Print list / Rename / Delete" got
   cut off at the card edge with the lower entries unreachable.

   ⚠️ The selector is `.batch-card.shopping-list-card` — TWO classes — on purpose.
   A plain `.shopping-list-card { overflow: visible }` has the SAME specificity as
   `.batch-card`, so the winner is decided by source order, and `.batch-card` is
   defined ~390 lines further down this file. That is exactly how the first
   attempt at this fix silently did nothing. Specificity, not source order.

   The clip is kept on the expanded BODY, where it is what stops a wide table
   spilling past the rounded corner. Don't "tidy" this into one rule on the card.
   test_shopping_and_ranges.js resolves the cascade and fails if `hidden` wins. */
.batch-card.shopping-list-card { overflow: visible; }
.shopping-list-card .batch-collapsible-content.expanded { overflow-x: auto; }

/* Above neighbouring cards, which are position: relative with z-index auto. */
.shopping-list-card .overflow-menu { z-index: 80; }

.shopping-list-titlerow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  min-width: 0;
}
.shopping-list-name { font-size: 1.15rem; color: var(--text-main); }

.shopping-active-chip {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--primary-container);
  border: 1px solid var(--border-accent);
  border-radius: 3px;
  padding: 1px 5px;
  white-space: nowrap;
}

.shopping-list-meta {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 4px;
}

.shopping-progress {
  margin-top: 8px;
  height: 4px;
  width: 100%;
  max-width: 320px;
  background: var(--surface-interface);
  border-radius: 2px;
  overflow: hidden;
}
/* Lime = live progress, per the palette rule that acid lime is for live state. */
.shopping-progress-fill {
  height: 100%;
  background: var(--success);
  transition: width 0.2s ease;
}

.shopping-list-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
}
.shopping-mini-btn { padding: 0.3rem 0.6rem; font-size: 0.78rem; }

.shopping-groups { display: flex; flex-direction: column; gap: 1rem; }

.shopping-group + .shopping-group { margin-top: 1rem; }

.shopping-group-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 0.35rem;
  margin-bottom: 0.4rem;
}
.shopping-group-title {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--primary-container);
}
.shopping-group-count {
  font-family: 'Geist Mono', monospace;
  font-size: 0.72rem;
  color: var(--text-dim);
}

.shopping-items { list-style: none; margin: 0; padding: 0; }

.shopping-item { border-bottom: 1px solid var(--border-color); }
.shopping-item:last-child { border-bottom: none; }

/* name | qty | unit | remove, with the checkbox leading.
   The name takes the slack so quantity controls stay a fixed, predictable size. */
.shopping-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  /* 44px minimum: this gets tapped with a trolley in the other hand. */
  min-height: 44px;
  padding: 0.35rem 0.2rem;
}

.shopping-check {
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
  accent-color: var(--primary-container);
  cursor: pointer;
}

.shopping-item-name {
  font-size: 0.92rem;
  color: var(--text-main);
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Monospaced and right-aligned so the quantities form a column you can read
   straight down. */
.shopping-qty {
  flex: 0 0 4.5rem;
  width: 4.5rem;
  font-family: 'Geist Mono', monospace;
  text-align: right;
  padding: 0.3rem 0.4rem;
  font-size: 0.85rem;
}
.shopping-unit {
  flex: 0 0 4.6rem;
  width: 4.6rem;
  padding: 0.3rem 0.3rem;
  font-size: 0.8rem;
}

.shopping-remove { flex: 0 0 32px; width: 32px; height: 32px; }
.shopping-remove:hover { color: var(--danger); border-color: rgba(255, 180, 171, 0.4); }

.shopping-item-detail {
  font-size: 0.75rem;
  color: var(--text-dim);
  padding: 0 0.2rem 0.5rem 2.9rem;
  line-height: 1.45;
}

/* Struck through but not hidden: you still want to check what is in the basket. */
.shopping-item.is-checked .shopping-item-name {
  text-decoration: line-through;
  opacity: 0.45;
}
.shopping-item.is-checked .shopping-qty,
.shopping-item.is-checked .shopping-unit { opacity: 0.5; }

/* Item picker inside the "add to list" modal: every line is a checkbox so you
   can take two hops and a yeast instead of the whole bill. Starts all-ticked, so
   "add the lot" is still one tap. */
.shopping-pick-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 1rem 0 0.25rem;
}
.shopping-pick-label {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--primary-container);
}
.shopping-pick-actions { display: flex; align-items: baseline; gap: 0.5rem; }
.shopping-group-toggle { margin-left: auto; }

/* The whole row is the label, so tapping the name toggles the tick. */
.shopping-row.is-pick { cursor: pointer; }
.shopping-row.is-pick:hover { background: var(--surface-interface); }

/* Read-only preview inside the "add to list" modal. */
.shopping-row.is-preview { min-height: 34px; }

/* "Which list?" chooser — one tap per list, no confirm step. */
.shopping-chooser { display: flex; flex-direction: column; gap: 0.5rem; margin: 0.5rem 0 1rem; }

.shopping-chooser-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  text-align: left;
  padding: 0.7rem 0.85rem;
  min-height: 56px;
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-main);
  font: inherit;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.shopping-chooser-btn:hover {
  border-color: var(--border-accent);
  background: var(--surface-variant);
}
.shopping-chooser-name { font-size: 0.95rem; font-weight: 700; }
.shopping-chooser-meta { font-size: 0.75rem; color: var(--text-dim); }
.shopping-item-qty {
  font-family: 'Geist Mono', monospace;
  font-weight: 700;
  font-size: 0.88rem;
  color: var(--primary-container);
  flex: 0 0 5.5rem;
  text-align: right;
}
.shopping-preview { max-height: 40vh; overflow-y: auto; margin: 0.5rem 0 1rem; }

/* Category badge in the cross-category shopping search dropdown. */
.combobox-cat {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  border: 1px solid var(--border-color);
  border-radius: 3px;
  padding: 0 4px;
  margin-right: 6px;
  vertical-align: middle;
}

/* The "add exactly what I typed" row at the top of the shopping search.
   Amber-tinted so it reads as an action rather than as another library hit,
   and sticky so it stays reachable once the list below it scrolls. The
   ordinary .combobox-item rows carry their padding inline (they are built in
   app.js), so this rule restates it rather than inheriting. */
.combobox-item--custom {
  position: sticky;
  top: 0;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--primary-container);
  background: var(--surface-elevated);
  border-bottom: 1px solid var(--border-accent);
}
.combobox-item--custom .combobox-cat {
  color: var(--primary-container);
  border-color: var(--border-accent);
}
.combobox-item--custom:hover { background: var(--surface-variant); }

/* Ingredients-page action cell: add-to-list for everyone, Delete for admins. */
.ing-actions-col { width: 1%; white-space: nowrap; }
.ing-actions-cell { white-space: nowrap; display: flex; gap: 0.35rem; align-items: center; }
.shop-add-btn { width: 30px; height: 30px; flex: 0 0 30px; }
.shop-add-btn:hover { color: var(--primary-container); border-color: var(--border-accent); }
.ing-del-btn { padding: 0.15rem 0.4rem; font-size: 0.7rem; }

@media (max-width: 640px) {
  .shopping-shared-hint { display: none; }
  .shopping-add-panel { flex-direction: column; align-items: stretch; }
  .shopping-add-search, .shopping-add-target { flex: 1 1 auto; }
  /* The unit picker is the least-needed control on a phone; the row must stay
     one line so the name and the tick are always reachable. */
  .shopping-qty { flex: 0 0 3.8rem; width: 3.8rem; }
  .shopping-unit { flex: 0 0 4rem; width: 4rem; }
  .shopping-item-detail { padding-left: 2.2rem; }
  .shopping-list-name { font-size: 1rem; }
}

/* Published range shown beside the single figure a recipe calculates with. */
.range-context {
  font-size: 0.72rem;
  color: var(--text-dim);
  white-space: nowrap;
  cursor: help;
}

/* "assumed" marker for a number nobody looked up — see isAssumedAttenuation().
   Deliberately quiet: it should read as a caveat, not an error. */
.assumed-flag {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  border: 1px dashed var(--border-color);
  border-radius: 3px;
  padding: 0 4px;
  vertical-align: middle;
  cursor: help;
  white-space: nowrap;
}

.ingredient-table {
  width: 100%;
  max-width: 100%;
  border-collapse: collapse;
  margin-top: 0.5rem;
  box-sizing: border-box;
}

.ingredient-table th, .ingredient-table td {
  padding: 0.4rem 0.5rem;
  text-align: left;
  border-bottom: 1px solid var(--border-color);
  box-sizing: border-box;
}

.ingredient-table th {
  background: var(--surface-interface);
  font-size: 0.75rem;
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 700;
}

.ingredient-table td {
  font-size: 0.85rem;
}

.ingredient-table td .form-control {
  padding: 0.35rem 0.4rem;
  font-size: 0.85rem;
  box-sizing: border-box;
}

.ingredient-table .ferm-name,
.ingredient-table .hop-name,
.ingredient-table .other-name {
  width: 100%;
  min-width: 110px;
}

.ingredient-table .ferm-weight,
.ingredient-table .ferm-color,
.ingredient-table .hop-weight,
.ingredient-table .hop-alpha,
.ingredient-table .hop-time {
  width: 60px;
  max-width: 65px;
  text-align: center;
}

.ingredient-table .ferm-type,
.ingredient-table .hop-use {
  width: 95px;
  padding: 0.35rem 0.2rem;
  font-size: 0.8rem;
}

/* Custom Brew Day Stage Builder Cards */
.stage-builder-card {
  background: var(--surface-interface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.9rem 1.1rem;
  margin-bottom: 1rem;
  box-sizing: border-box;
  width: 100%;
}

.stage-header-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
  flex-wrap: wrap;
}

.stage-header-row .stage-name-input {
  flex: 1 1 220px;
  min-width: 200px;
}

.stage-header-row .stage-dur-input {
  width: 75px;
  max-width: 75px;
  text-align: center;
  font-weight: 700;
}

.alarm-builder-list {
  background: rgba(0, 0, 0, 0.25);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  margin-top: 0.5rem;
}

.alarm-item-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.4rem;
  flex-wrap: wrap;
}

.alarm-item-row .alarm-label-input {
  flex: 1 1 180px;
  min-width: 160px;
}

.alarm-item-row .alarm-time-input {
  width: 70px;
  max-width: 70px;
  text-align: center;
  font-weight: 700;
}

/* Collapsible Batches Cards */
/* Completed batches: kept on the page, folded away below the live ones.
   Deliberately quieter than a card — it is a divider you can open, not another
   batch competing for attention. The body supplies its own gap because
   .batch-card carries no margin (the grid used to space them). */
.completed-batches {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  border-top: 1px solid var(--border-color);
  padding-top: 1.25rem;
}

.completed-batches-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  /* Comfortably past the 44px touch target: this is the control you hit on a
     phone to get at an old batch. */
  min-height: 44px;
  padding: 0.5rem 0.25rem;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 0.15s ease;
}
.completed-batches-toggle:hover { color: var(--primary-container); }

.completed-batches-count {
  font-family: 'Geist Mono', monospace;
  font-size: 0.75rem;
  letter-spacing: 0;
  color: var(--text-dim);
  border: 1px solid var(--border-color);
  border-radius: 3px;
  padding: 0 6px;
}

.completed-batches-body {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.batch-card {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  /* No margin-bottom: batch cards sit in a grid whose gap already spaces them,
     and both together gave 44px between rows. */
  transition: border-color 0.15s ease, background 0.15s ease;
  position: relative;
  overflow: hidden;
}

.batch-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 4px;
  background: transparent;
  transition: background 0.3s ease;
}

.batch-card:hover::before {
  background: var(--amber-primary);
}

.batch-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
}

.batch-collapsible-content {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
  display: none;
  animation: fadeIn 0.3s ease;
}

.batch-collapsible-content.expanded {
  display: block;
}

/* Lovibond SRM Color Swatch */
.srm-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.3);
  vertical-align: middle;
  margin-right: 4px;
}

/* Modals & Overlays */
dialog.modal-dialog {
  padding: 0;
  border: none;
  background: transparent;
  max-width: 850px;
  width: 95vw;
  max-height: 90vh;
  margin: auto;
  outline: none;
  color: var(--text-main);
  color-scheme: dark;
}

dialog.modal-dialog:not([open]) {
  display: none !important;
  pointer-events: none !important;
}

dialog.modal-dialog::backdrop {
  background: rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(8px);
}

.modal-inner {
  background: var(--bg-card);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.75rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
}

.modal-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(8px);
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

.modal-overlay.active {
  display: flex;
}

.modal {
  background: var(--bg-card);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  max-width: 850px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.75rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1.25rem;
}

.modal-title {
  font-size: 1.5rem;
  font-weight: 700;
  color: #FFF;
}

.modal-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 1.2rem;
  cursor: pointer;
}

.btn-icon-share {
  background: var(--surface-interface);
  border: 1px solid var(--border-accent);
  color: var(--amber-primary);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* Toast Container */
.toast-container {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.toast-item {
  background: rgba(23, 31, 51, 0.95);
  border: 1px solid var(--amber-primary);
  color: var(--text-main);
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius-md);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  animation: fadeIn 0.3s ease;
}

/* Print Area Container (Hidden on Screen, Visible on Print) */
#printable-recipe-area {
  display: none !important;
}

/* Print Media Stylesheet

   Hide everything at the top level, then re-show only the brew sheet.

   This used to enumerate the chrome to hide (.sidebar, .top-header,
   .modal-overlay, …) and that list rotted: the redesign renamed the modal from
   `.modal-overlay` to `<dialog class="modal-dialog">`, so the hide rule silently
   stopped matching anything. Print is invoked from a button INSIDE that modal,
   and the UA stylesheet gives <dialog> `position: fixed` — which repeats on
   every printed page. The result was N identical pages of the on-screen modal
   instead of the recipe. An allow-list of one id cannot rot the same way.

   Note also: the sheet is NOT positioned absolutely. It was, and absolutely
   positioned content paginates badly; with everything else hidden it can simply
   flow, which is what makes a multi-page recipe break across pages correctly. */
@media print {
  body {
    background: #ffffff !important;
    color: #000000 !important;
    min-height: 0 !important;
  }
  body > *:not(#printable-recipe-area) {
    display: none !important;
  }
  #printable-recipe-area {
    display: block !important;
    width: 100% !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  /* Keep tables and list items from being split mid-row across a page break. */
  #printable-recipe-area tr,
  #printable-recipe-area li {
    break-inside: avoid;
    page-break-inside: avoid;
  }
  #printable-recipe-area h3 {
    break-after: avoid;
    page-break-after: avoid;
  }
}

/* ==========================================================================
   AUTHENTICATION GATE + USER ADMINISTRATION
   Added 2026-08-11 with real logins. Uses the existing palette variables so it
   inherits any future theme change.
   ========================================================================== */

.auth-gate {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background:
    /* Flat, like every other surface. The lavender wash that used to sit here
       was the last survivor of the old palette — written as decimal rgba, so a
       search for "8083ff" missed it. */
    var(--bg-main);
  overflow-y: auto;
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--bg-card);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 2rem 1.75rem;
  text-align: center;
}

.auth-logo { height: 56px; width: auto; margin-bottom: 0.75rem; }

.auth-title {
  font-size: 1.6rem;
  font-weight: 800;
  color: var(--amber-primary);
  margin-bottom: 0.25rem;
  letter-spacing: 0.02em;
}

.auth-subtitle {
  font-size: 0.875rem;
  color: var(--text-muted);
  margin-bottom: 1.5rem;
  line-height: 1.5;
}

.auth-label {
  display: block;
  text-align: left;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 0.9rem 0 0.35rem;
}

.auth-error {
  margin-top: 1rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: rgba(255, 180, 171, 0.12);
  border: 1px solid rgba(255, 180, 171, 0.35);
  color: var(--danger);
  font-size: 0.85rem;
  text-align: left;
}

.auth-submit { width: 100%; margin-top: 1.25rem; justify-content: center; }

/* Signed-in identity chip in the header — now a button that opens the account
   menu (units + sign out). It is the ONLY control in the right-hand side of the
   header; see the comment on the <header> in index.html for why. */
.account-wrap { position: relative; }

.user-chip {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: rgba(0, 0, 0, 0.4);
  padding: 0.3rem 0.5rem 0.3rem 0.35rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  cursor: pointer;
  font: inherit;
  color: inherit;
  max-width: 100%;
}

.user-chip:hover { border-color: var(--border-accent); }
.user-chip-caret { flex: 0 0 14px; color: var(--text-dim); }

/* The account dropdown. Right-aligned to the chip so it cannot push the layout
   sideways on a narrow screen. */
.account-menu {
  right: 0;
  left: auto;
  bottom: auto;
  top: calc(100% + 6px);
  min-width: 210px;
}

.account-menu-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0.5rem 0.65rem;
  border-bottom: 1px solid var(--border-color);
}
.account-menu-name { font-size: 0.85rem; font-weight: 700; color: var(--text-main); }
.account-menu-role {
  font-size: 0.68rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.account-menu-section {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.6rem 0.65rem;
}
.account-menu-label {
  font-size: 0.68rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
}
/* Inside the menu the segmented control spans the full width so both options
   are comfortable thumb targets. */
.account-menu .unit-toggle { width: 100%; }
.account-menu .unit-btn { flex: 1 1 50%; text-align: center; padding: 0.45rem 0.5rem; }

.user-chip-avatar {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--amber-primary), var(--copper-secondary));
  color: #10142a;
  font-weight: 800;
  font-size: 0.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  text-transform: uppercase;
}

.user-chip-meta { display: flex; flex-direction: column; line-height: 1.15; text-align: left; min-width: 0; }
.user-chip-name {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-main);
  /* A long display name must ellipsise, not shove the caret out of the header. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 12ch;
}
.user-chip-role { font-size: 0.68rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }

/* Admin page */
.admin-panel {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  margin-bottom: 1.5rem;
}

.admin-panel-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.admin-panel-title { color: var(--amber-primary); font-size: 1.05rem; font-weight: 700; margin-bottom: 0.2rem; }
.admin-panel-sub { font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; }

.users-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.users-table th {
  text-align: left;
  padding: 0.5rem 0.6rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border-color);
  white-space: nowrap;
}
.users-table td { padding: 0.6rem; border-bottom: 1px solid var(--border-color); vertical-align: middle; }
.users-table tr:last-child td { border-bottom: none; }
.users-table .row-inactive { opacity: 0.5; }

.user-cell { display: flex; align-items: center; gap: 0.6rem; }
.user-cell-name { font-weight: 700; color: var(--text-main); }
.user-cell-username { font-size: 0.75rem; color: var(--text-dim); }

/* Role chips are `class="badge badge-admin"` etc., so they inherit the base
   .badge defined with the recipe cards and only override colour here.
   There used to be a SECOND generic `.badge` block at this point in the file
   with `border-radius: 999px`. Being later, it won the cascade and quietly
   turned every chip in the app back into a pill — which this design system
   explicitly forbids. Don't reintroduce a second .badge rule; add a modifier. */
.badge-admin  { background: rgba(255, 184, 0, 0.12);  color: var(--primary-container); border-color: var(--border-accent); }
.badge-brewer { background: rgba(202, 243, 0, 0.10);  color: var(--success);          border-color: rgba(202, 243, 0, 0.3); }
.badge-viewer { background: rgba(255, 255, 255, 0.07); color: var(--text-muted);    border-color: var(--border-color); }
.badge-warn   { background: rgba(255, 180, 171, 0.12); color: var(--danger);        border-color: rgba(255, 180, 171, 0.3); }

.user-actions { display: flex; gap: 0.35rem; flex-wrap: wrap; justify-content: flex-end; }
.user-actions .btn { padding: 0.25rem 0.55rem; font-size: 0.75rem; }

.role-legend { display: grid; gap: 0.75rem; }
.role-legend-item {
  display: grid;
  grid-template-columns: 90px 1fr;
  gap: 0.75rem;
  align-items: start;
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.token-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin-top: 0.75rem; }
.token-value {
  flex: 1;
  min-width: 220px;
  font-family: monospace;
  font-size: 0.85rem;
  color: var(--amber-primary);
  background: rgba(0, 0, 0, 0.4);
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  overflow-x: auto;
  white-space: nowrap;
}

.code-block {
  margin-top: 0.4rem;
  padding: 0.75rem;
  background: rgba(0, 0, 0, 0.4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  font-family: monospace;
  font-size: 0.78rem;
  color: var(--text-main);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* Authorship shown instead of the old free-text "who are you?" boxes */
.authorship-field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.7rem;
  background: rgba(0, 0, 0, 0.25);
  border: 1px dashed var(--border-color);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  color: var(--text-muted);
}
.authorship-field strong { color: var(--text-main); }

@media (max-width: 640px) {
  /* Avatar + caret only. The name and role are in the dropdown head. */
  .user-chip-meta { display: none; }
  .users-table th:nth-child(3), .users-table td:nth-child(3) { display: none; }
  .role-legend-item { grid-template-columns: 1fr; gap: 0.15rem; }
}
