:root {
  --bg: #0d1117;
  --panel: #161b22;
  /* Decoration only: panel outlines, list separators, the tuning bar edge.
     Editable controls use --control-border so their boundaries reach 3:1
     against the fields and panels around them. */
  --border: #30363d;
  --control-border: #636c76;
  --focus: #58a6ff;
  --fg: #e6edf3;
  --muted: #8b949e;
  --accent: #3fb950;
  --bad: #f85149;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 1rem;
}

h1 {
  font-size: 1.4rem;
  margin: 0;
}

h2 {
  font-size: 1.05rem;
  margin: 0;
}

h3 {
  font-size: 0.95rem;
  margin: 1rem 0 0.4rem;
}

.top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.session {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.session .facts {
  min-width: 0;
  overflow-wrap: anywhere;
}

.session-error {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.8rem;
}

/* Destination buttons are distinct choices, not one segmented control: a
   consistent gap keeps them from touching at any width and lets them wrap. */
nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

nav button.active {
  color: var(--accent);
  border-color: var(--accent);
  /* The active destination must not read by color alone: weight and underline
     carry it too (and `aria-current` exposes it programmatically). */
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

button,
select,
input {
  font: inherit;
  /* Editable-field text is a comfortable ~16 px: a control inside a .fact
     label would otherwise inherit its 12.8 px label type. */
  font-size: 1rem;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--control-border);
  border-radius: 6px;
  padding: 0.35rem 0.6rem;
  /* Common actions are practical 44 px touch targets: the painted box is the
     hit area, so what looks tappable is tappable. The .pin checkbox opts out
     below (its wrapping label is the target) and the gram steppers keep their
     own 44 px square sizing. */
  min-height: 2.75rem;
  max-width: 100%;
  min-width: 0;
}

/* A checkbox is a mark, not a field: keep its own box. */
input[type="checkbox"] {
  min-height: 0;
}

button {
  cursor: pointer;
  /* A long label wraps inside the button rather than overflowing its row. */
  overflow-wrap: anywhere;
}

/* One clear primary action per task (Save, sign in, create passkey): a filled
   accent control while secondary actions stay outlined. The dark --bg edge
   keeps the filled boundary at least 3:1 against its own fill (UX-10). */
.primary,
#save-ingredient,
.save-recipe {
  background: var(--accent);
  border-color: var(--bg);
  color: var(--bg);
  font-weight: 600;
}

/* A pending/unavailable primary drops the fill and reads as a quiet, legible
   disabled control instead of a live primary action ("saving…"). */
.primary:disabled,
#save-ingredient:disabled,
.save-recipe:disabled {
  opacity: 1;
  background: var(--bg);
  border-color: var(--control-border);
  color: var(--muted);
}

/* Keyboard focus is its own colour channel, so a focused control is
   distinguishable from the invalid (--bad) and pinned/met (--accent) states.
   Text fields match :focus-visible whenever they are focused. */
button:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* A button disabled while it works (a save in flight) reads as unavailable. */
button:disabled {
  opacity: 0.55;
  cursor: default;
}

.layout {
  display: grid;
  gap: 1rem;
  min-width: 0;
}

@media (min-width: 820px) {
  .layout {
    /* The editor gets the larger share: its row controls need the width to
       keep ingredient names identifiable, while the list stays usable
       (UX-16). */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
    align-items: start;
  }
}

/* The ingredient manager is a single list: use the full width. */
.layout.single {
  grid-template-columns: 1fr;
}

/* With a form or confirmation open, a wide screen gets a real detail pane
   beside the list (like the recipe editor), so the ingredient form is at the
   top of the page instead of below the whole list. Phones keep the
   detail-takes-over behavior below. */
@media (min-width: 820px) {
  .layout.single.detail-open {
    /* The list gets the larger share: its rows carry names, facts and two
       actions, while the ingredient form stays comfortable in the rest. */
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    align-items: start;
  }
}

/* On a phone the detail (an opened recipe or form) takes over the screen:
   hide the list so the detail starts at the top. On wider screens both the
   list and the detail are shown side by side, so this never applies. */
@media (max-width: 819px) {
  .layout.detail-open .list-panel {
    display: none;
  }
}

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem;
}

.detail {
  min-width: 0;
  /* The recipe row layout below is chosen by this pane's usable width, not
     the viewport: at 820 px the two-column page leaves the editor narrower
     than its phone-width counterpart, which used to truncate ingredient
     names to about 99 px (UX-16). */
  container-type: inline-size;
}

.panel-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

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

/* Library tools (UX-16): a lightweight text filter and the visible item
   count. The count is deliberately not a live region (UX-07: typing must not
   announce on every keystroke). */
.list-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0.4rem 0;
}

.filter-field {
  display: flex;
  flex: 1 1 12rem;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  color: var(--muted);
  font-size: 0.8rem;
}

.filter-field input {
  flex: 1 1 auto;
  /* A definite width keeps the input's intrinsic control size out of the
     panel's min-content width; without it the search field stretched the
     320 px list panel (UX-16). */
  width: 100%;
  min-width: 0;
}

.item-count {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* A filter that matches nothing is its own state: not the empty library and
   not a failure. */
.list-state.no-matches {
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 0.6rem;
}

.list-state.no-matches p {
  margin: 0 0 0.4rem;
}

.list-state.no-matches button {
  margin-top: 0.2rem;
}

.list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--border);
}

.list li > div:first-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The row's actions keep their size while the (now longer) summary shrinks,
   so "open"/"delete" never wrap letter by letter (UX-16). */
.list li > .row-actions {
  flex-shrink: 0;
}

/* List lifecycle states: loading, genuinely empty, and failed. A failed load
   must read as an error, never as an empty account. */
.list-state {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0.4rem 0;
}

.list-state.failure {
  border: 1px solid var(--bad);
  border-radius: 8px;
  padding: 0.6rem;
  margin: 0.5rem 0 0.6rem;
}

.list-state.failure p {
  margin: 0 0 0.5rem;
  color: var(--bad);
}

.facts {
  display: block;
  color: var(--muted);
  font-size: 0.8rem;
}

.row-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}

.fact {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  color: var(--muted);
  font-size: 0.8rem;
  margin: 0.4rem 0;
}

.fact input {
  width: 100%;
  min-width: 0;
}

/* The recipe row: a narrow detail pane gives the selector its own full row
   so long ingredient names stay identifiable; once the pane is wide enough
   the controls share one row again. The 36 rem container threshold keeps the
   820 px two-column editor wrapped while 1280 px keeps the single row. */
.line {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin: 0.4rem 0;
}

.line select {
  flex: 1 1 100%;
  min-width: 0;
}

.line .grams {
  min-width: 0;
}

@container (min-width: 36rem) {
  .line {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 4.5rem auto auto auto;
  }

  .line select {
    flex: none;
  }
}

/* A text field with a numeric keypad hint: unlike `type="number"` it keeps a
   typed `.` visible while editing, so decimals can round instead of being
   silently dropped and appended to. The visible gram unit sits inside the
   field's own box (UX-20), so the row keeps its pre-unit geometry. */
.grams-field {
  position: relative;
  display: inline-block;
  width: 4.5rem;
  min-width: 0;
}

.line .grams {
  width: 100%;
  text-align: center;
  /* Space for the unit inside the field's right edge. */
  padding-right: 1.1rem;
}

/* The visible gram unit: assistive tech reads the field's “Grams for line N”
   name, so the visual suffix is hidden from the tree. */
.grams-unit {
  position: absolute;
  top: 50%;
  right: 0.45rem;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1;
  pointer-events: none;
}

/* Thumb-friendly step buttons (44 px square). */
.stepper {
  display: flex;
  gap: 0.4rem;
}

.stepper button {
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0;
  font-size: 1.1rem;
  line-height: 1;
  /* A press must not select text or open a long-press callout; the button
     handles the hold itself. */
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.stepper button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Pin: hold this line's amount fixed when optimizing. The row marks itself so
   the state is visible at a glance; the label is a 44 px tap target. */
.line .pin {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
  cursor: pointer;
  min-height: 2.75rem;
  padding: 0 0.25rem;
}

.line .pin input {
  accent-color: var(--accent);
  margin: 0;
  cursor: pointer;
  width: 1.25rem;
  height: 1.25rem;
}

/* A valid pinned amount carries the accent, but validity wins: an invalid
   pinned amount keeps the error boundary (UX-01/UX-10) while the checked pin
   still marks the pinned state. */
.line.pinned select:not([aria-invalid="true"]),
.line.pinned .grams:not([aria-invalid="true"]) {
  border-color: var(--accent);
}

/* Validation belongs beneath the complete control row, so it never steals a
   grid column from the selector, grams, stepper, pin, or remove controls. */
.line > .field-error {
  grid-column: 1 / -1;
  flex: 1 0 100%;
  width: 100%;
}

/* Per-line floors stay optional, with a full-size action beside the amounts. */
.floors-edit {
  min-height: 2.75rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--control-border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.9rem;
}

.floors-edit[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

.floors {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.6rem;
  flex: 1 0 100%;
  min-width: 0;
}

@container (min-width: 36rem) {
  .floors {
    grid-column: 1 / -1;
  }
}

.floors-label {
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
}

.floor-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  color: var(--muted);
  font-size: 0.8rem;
  min-width: 0;
}

.floor-field .floor {
  width: 3.6rem;
  min-width: 0;
  padding-left: 0.35rem;
  padding-right: 0.35rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.floor-field .field-error {
  flex: 1 0 100%;
}

.floor-unit {
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1;
}

/* On a phone the controls do not fit on one row: the base `.line` flex
   layout above already gives the select its own row. */

/* Compact tuning feedback (UX-08): the whole-recipe result and its distance to
   the targets sits next to the amounts being edited and sticks to the top of
   the viewport, so a long recipe never hides the result or the primary
   actions. The scroll margins below keep a focused amount and its validation
   message clear of the bar when the browser scrolls them into view. */
.tuning {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.6rem;
  margin: 0.4rem -0.9rem;
  padding: 0.45rem 0.9rem;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.tuning-readout {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  margin-right: auto;
}

.tuning-facts {
  margin: 0;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}

/* The scope/denominator cue sits under the compact totals so a phone user
   does not have to reach the full Nutrition panel to know what the numbers
   describe. It stays subordinate to the numbers and wraps within the bar. */
.tuning-scope {
  margin: 0;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.2;
}

.tuning-split {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  list-style: none;
  margin: 0;
  padding: 0;
  color: var(--muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

.tuning-split li {
  padding: 0;
  white-space: nowrap;
}

.tuning .note,
.tuning .status,
.tuning .save-error {
  flex-basis: 100%;
  margin: 0;
}

/* A failed save and its retry: the alert region holds the message only
   (UX-06 pattern), the retry control sits outside it, and both stay next to
   the action they belong to so a scrolled editor still shows them (UX-17). */
.save-error {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  flex-basis: 100%;
}

.save-error .error {
  flex: 1 1 14rem;
  margin: 0;
}

.save-error .retry {
  flex: 0 0 auto;
}

/* The editor publishes the sticky bar's measured height as
   `--tuning-bar-height` (see `track_tuning_bar_height`) because the note and
   status lines wrap the bar to several rows on a phone, more than any fixed
   margin can cover. The fallback is a floor: it covers the first paint before
   the measurement lands. The longest optimizer-note bar measured on a phone
   is about 229 px at 320 px; the live measurement raises the margin above
   this floor once it lands, and the measured value is what keeps focused
   amounts clear for taller bars. */
.detail input,
.detail select {
  scroll-margin-top: calc(max(var(--tuning-bar-height, 5rem), 5rem) + 0.5rem);
  scroll-margin-bottom: 4rem;
}

@media (max-width: 819px) {
  .detail input,
  .detail select {
    scroll-margin-top: calc(max(var(--tuning-bar-height, 13rem), 13rem) + 0.5rem);
  }
}

/* Target heading and its optional explanation. */
.target-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 1rem;
  margin: 1rem 0 0.4rem;
}

.target-head h3 {
  margin: 0;
}

.targets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem;
}

.targets .fact {
  margin: 0.3rem 0;
}

/* On a wide editor the five target fields fit three to a row, keeping the
   target section compact enough for the Nutrition panel to stay in view at
   1280x900 while every field keeps its 44 px control. */
@media (min-width: 820px) {
  .targets {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.split {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  font-size: 0.85rem;
}

.split li {
  padding: 0.15rem 0;
}

.delta.ok {
  color: var(--accent);
}

.delta.bad,
.error {
  color: var(--bad);
}

.field-error {
  display: block;
  color: var(--bad);
  font-size: 0.8rem;
  margin-top: 0.2rem;
}

input[aria-invalid="true"] {
  border-color: var(--bad);
}

.error {
  margin-top: 0.6rem;
}

/* Destructive actions read as destructive wherever they appear: list rows
   and the named confirmation that guards them. In a confirmation the red
   choice is pushed clear of the quieter cancel that precedes it. */
button.delete {
  color: var(--bad);
  border-color: var(--bad);
}

.discard .row-actions button.delete {
  margin-left: auto;
}

/* Result of a deliberate action (a completed delete), announced with
   role=status. */
.status {
  color: var(--muted);
  margin-top: 0.6rem;
}

/* Unsaved draft status and the discard decision that guards a replacement. */
.dirty {
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 400;
  padding: 0.05rem 0.45rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

.draft-hint {
  color: var(--muted);
  font-size: 0.8rem;
  margin: 0.2rem 0 0.4rem;
}

.discard {
  border-color: var(--bad);
}

.discard p {
  margin: 0 0 0.4rem;
}

.note {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0.5rem 0 0;
}

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

.note.error {
  color: var(--bad);
}

/* Cooking view: large, uncluttered, readable from a step away. */
.cook .panel-head h2 {
  font-size: 1.4rem;
  overflow-wrap: anywhere;
}

.cook-total {
  color: var(--muted);
  margin: 0.2rem 0 0.8rem;
}

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

.cook-list li {
  display: flex;
  gap: 0.9rem;
  align-items: baseline;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 1.25rem;
  overflow-wrap: anywhere;
}

.cook-grams {
  min-width: 5.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.cook-nutrition {
  margin-top: 0.9rem;
  font-size: 1.02rem;
}

button.big {
  font-size: 1.05rem;
  padding: 0.5rem 0.9rem;
}

.hidden {
  display: none;
}

/* Auth screen: a centered card; first-run register or one-button sign-in. */
.auth-screen {
  min-height: 100vh;
  max-width: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

.auth-card {
  width: 100%;
  max-width: 22rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.auth-card h1 {
  text-align: center;
}

.auth-hint {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0;
  text-align: center;
}

.auth-label {
  color: var(--muted);
  font-size: 0.8rem;
}

.auth-card input,
.auth-card button {
  width: 100%;
}

.form-ok {
  color: var(--accent);
  margin: 0;
  text-align: center;
}

/* Account screen: passkey rows reuse `.list`; the hint reads left. */
.account-hint {
  text-align: left;
  margin: 0.2rem 0 0.6rem;
}

/* A passkey row leads with the user's label (UX-18); the provider and dates
   stay secondary and the credential id sits in optional detail, so the
   technical identity never dominates the row. */
.passkey-main {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.passkey-name {
  overflow-wrap: anywhere;
}

.passkey-tech {
  color: var(--muted);
  font-size: 0.8rem;
  margin-top: 0.15rem;
}

.passkey-tech summary {
  cursor: pointer;
}

.passkey-tech code {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* The inline rename form and remove confirmation take over the row. */
.passkey-rename,
.passkey-confirm {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
}

.passkey-rename input {
  width: 100%;
}

.passkey-confirm p {
  margin: 0;
}

.passkey-confirm .draft-hint {
  margin: 0;
}

/* Issue 9 spike: explanations live in nonmodal native popovers. The top
   layer keeps opening help from moving values or changing the sticky bar. */
.help-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-width: 0;
}

.help-heading h2,
.help-heading h3 {
  margin: 0;
  min-width: 0;
}

.info-button {
  flex: 0 0 2.75rem;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border-radius: 50%;
  color: var(--muted);
  font-family: Georgia, serif;
  font-weight: 700;
  font-size: 1.1rem;
}

.info-popover {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(28rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 2rem);
  overflow: auto;
  padding: 1rem;
  border: 1px solid var(--control-border);
  border-radius: 10px;
  background: var(--panel);
  color: var(--fg);
  box-shadow: 0 0.5rem 2rem #0008;
  font-size: 1rem;
  line-height: 1.5;
}

.info-popover p:last-child {
  margin-bottom: 0;
}

.info-popover .help-heading button {
  flex-shrink: 0;
}

.amount-heading {
  margin-top: 0.6rem;
}

.floor-help-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.ingredient-facts-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.25rem 0.75rem;
  margin-bottom: 0.75rem;
}

/* Nutrition values form one readout. Paragraph defaults add large gaps and
   push the calorie result below the desktop viewport after Optimize. */
#nutrition-total,
#nutrition-delta {
  margin: 0.35rem 0;
}
