/* ===== DESIGN TOKENS =====

   Everything visual comes from here. Three rules keep it that way:

   - No raw colour outside this block. A value that appears twice is a token.
   - No raw length for padding, gap, margin or font-size, no raw transition
     duration and no raw z-index. Every scale below is a closed set — if a
     component needs a step that isn't here, the step is missing from the
     scale, not from the component.
   - The app is deliberately light-only, and `color-scheme` is NOT declared.
     Naming a dark scheme without shipping dark rules is what makes a browser
     paint inputs, selects and scrollbars dark against a permanently light
     page. A real dark theme means redefining the tokens below under
     `prefers-color-scheme`, and only then re-declaring `color-scheme`.

   The palette is a kitchen's: a herb-green sidebar, a terracotta topbar that
   stays put and says at a glance which app this is, warm paper rather than
   cold white, and saffron for the one thing on a page that is currently
   selected. Warm greys, not neutral ones — a 0%-saturation grey next to
   terracotta reads as blue.
*/
:root {
  /* ---- palette ---- */
  --herb: #24402F;
  --herb-deep: #1A3023;
  --herb-light: #3C6349;
  --terracotta: #B94F2C;
  --terracotta-hover: #9C401F;
  --terracotta-light: #D9805B;
  --saffron: #E3A02B;
  --saffron-deep: #B87B12;

  --cream: #FAF6F0;
  --white: #FFFFFF;
  --paper: #FFFDFA;

  --text-dark: #2B2521;
  --text-mid: #675C53;
  --text-light: #97897D;
  --text-muted: #BCB0A5;
  --border: #E8DFD3;
  --border-light: #F2ECE3;

  --herb-bg: #EBF1EC;
  --terracotta-bg: #FBEDE5;
  --saffron-bg: #FDF3DF;

  /* Success — saved, done, confirmed. */
  --success: #2F8B54;
  --success-strong: #226B3F;
  --success-bg: #E8F4EC;

  /* Amber — something to look at, not something broken. */
  --amber: #8A5A12;
  --amber-bg: #FBF0DA;

  /* Danger — destructive actions and refusals. */
  --danger: #B3261E;
  --danger-bg: #FBE9E7;

  --shadow-sm: 0 1px 3px rgba(43,37,33,0.05), 0 2px 8px rgba(43,37,33,0.04);
  --shadow-md: 0 4px 12px rgba(43,37,33,0.08), 0 2px 6px rgba(43,37,33,0.05);
  --shadow-lg: 0 12px 32px rgba(43,37,33,0.12);

  /* ---- spacing scale (4px grid) ----
     Use for padding, gap and margin. Nothing in between these steps. */
  --space-1: 0.125rem;  /*  2px */
  --space-2: 0.25rem;   /*  4px */
  --space-3: 0.375rem;  /*  6px */
  --space-4: 0.5rem;    /*  8px */
  --space-5: 0.75rem;   /* 12px */
  --space-6: 1rem;      /* 16px */
  --space-7: 1.25rem;   /* 20px */
  --space-8: 1.5rem;    /* 24px */
  --space-9: 2rem;      /* 32px */
  --space-10: 3rem;     /* 48px */

  /* ---- type scale ----
     `2xs` and `xs` are the uppercase micro-labels; `base` is body text. */
  --text-2xs: 0.6875rem;  /* 11px */
  --text-xs: 0.75rem;     /* 12px */
  --text-sm: 0.8125rem;   /* 13px */
  --text-base: 0.875rem;  /* 14px */
  --text-md: 0.9375rem;   /* 15px */
  --text-lg: 1rem;        /* 16px */
  --text-xl: 1.125rem;    /* 18px */
  --text-2xl: 1.3125rem;  /* 21px */
  --text-3xl: 1.5rem;     /* 24px */
  --text-4xl: 2rem;       /* 32px */

  /* ---- radii ---- */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* ---- type ----
     An amount, a unit, a duration: things read as quantities rather than as
     prose, and things that have to line up in a column. */
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* ---- motion ---- a closed set, like the scales above. */
  --dur-1: 0.06s;  /* a press: as near instant as still reads as a change */
  --dur-2: 0.12s;
  --dur-3: 0.15s;  /* hover and focus, the app's default */
  --dur-4: 0.2s;   /* something that moves rather than recolours */
  --dur-5: 0.6s;   /* a panel sliding in */

  /* ---- stacking order ----
     Named steps, bottom of the page to the top of it. Nothing sets a raw
     z-index, and no two things share a step — which is what stops "whichever
     of these two fixed elements comes later in the document wins". */
  --z-sticky-heading: 1;
  --z-topbar: 10;
  --z-popover: 20;
  --z-menu: 40;
  /* The running-timer card. Above the page and its menus, below the scrim —
     when the sidebar is open on a phone, the app is asking for an answer and a
     timer floating over the dimming is a second thing shouting. */
  --z-timer: 45;
  --z-scrim: 50;
  --z-sidebar: 60;
  --z-modal: 80;
  --z-skip-link: 90;

  /* ---- chrome dimensions ----
     Each of these is a number two rules have to agree on: the sidebar's width
     and the room the main column leaves for it; the topbar's height and how
     far down anything sticky below it starts. */
  --sidebar-w: 240px;
  --topbar-h: 60px;

  /* ---- breakpoints ----
     Not tokens, because `@media` cannot read a custom property — the values
     are written out at each rule. Listed here so the set stays closed like the
     scales above, and so adding a fourth is a decision rather than a habit:

       900px  the sidebar goes off-canvas behind the hamburger
       700px  page padding steps down, the recipe detail folds to one column
       520px  the recipe grid becomes a single column
  */

  /* ---- content width ---- one measure for every page. */
  --content-max: 1100px;
}

* { box-sizing: border-box; }

/* The `hidden` attribute must always win, even over components that set their
   own `display` (e.g. flex rows toggled by JS). */
[hidden] { display: none !important; }

html { min-height: 100%; }

body {
  font-family: 'Inter', -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: var(--cream);
  color: var(--text-mid);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: 'Manrope', sans-serif;
  color: var(--text-dark);
  letter-spacing: -0.3px;
}

h1 { font-size: var(--text-3xl); font-weight: 800; margin: 0 0 var(--space-7); }
h2 { font-size: var(--text-xl); font-weight: 800; margin: 0 0 var(--space-5); }
h3 { font-size: var(--text-lg); font-weight: 700; margin: 0 0 var(--space-4); }

p { line-height: 1.6; }

a { color: var(--terracotta); text-decoration: none; transition: color var(--dur-3); }
a:hover { color: var(--terracotta-hover); }

/* ===== SKIP LINK / SCREEN-READER TEXT ===== */
.skip-link {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  z-index: var(--z-skip-link);
  padding: var(--space-4) var(--space-5);
  background: var(--herb);
  color: var(--white);
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.skip-link:not(:focus) {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
/* `clip` rather than display:none or visibility:hidden — both of those take the
   text out of the accessibility tree along with the pixels. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ===== SHELL LAYOUT ===== */
.shell { display: flex; min-height: 100vh; }

/* ===== SIDEBAR ===== */
.shell-sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--herb);
  color: var(--white);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; bottom: 0; left: 0;
  z-index: var(--z-sidebar);
  overflow-y: auto;
  transition: transform var(--dur-4) ease;
}

.shell-brand {
  padding: var(--space-6) var(--space-7);
  border-bottom: 1px solid rgba(255,255,255,0.1);
  min-height: var(--topbar-h);
  display: flex;
  align-items: center;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
  font-family: 'Manrope', sans-serif;
  font-weight: 800;
  font-size: var(--text-xl);
  color: var(--white);
}
.brand:hover { color: var(--white); }
/* The mark: a lidded pot over a flame. Sized in `em` so it scales with the
   wordmark rather than needing its own step on the type scale. */
.brand-mark {
  flex: none;
  width: 1.6em;
  height: 1.6em;
  color: var(--saffron);
}
.brand-word { letter-spacing: -0.4px; }

.shell-nav { padding: var(--space-6) 0; flex: 1; }
.shell-nav-item {
  display: block;
  padding: var(--space-5) var(--space-7);
  color: rgba(255,255,255,0.75);
  font-size: var(--text-base);
  font-weight: 500;
  border-left: 3px solid transparent;
  transition: color var(--dur-3), background var(--dur-3), border-color var(--dur-3);
}
.shell-nav-item:hover { color: var(--white); background: rgba(255,255,255,0.06); }
.shell-nav-item.active {
  color: var(--white);
  font-weight: 700;
  background: rgba(255,255,255,0.09);
  border-left-color: var(--saffron);
}
.shell-nav-parent { font-weight: 700; }

.shell-nav-sub { display: flex; flex-direction: column; padding: var(--space-1) 0 var(--space-3); }
.shell-nav-subitem {
  display: block;
  padding: var(--space-4) var(--space-7) var(--space-4) var(--space-9);
  color: rgba(255,255,255,0.6);
  font-size: var(--text-base);
  font-weight: 500;
  border-left: 3px solid transparent;
  transition: color var(--dur-3), background var(--dur-3), border-color var(--dur-3);
}
.shell-nav-subitem:hover { color: var(--white); background: rgba(255,255,255,0.06); }
.shell-nav-subitem.active {
  color: var(--white);
  font-weight: 600;
  background: rgba(255,255,255,0.09);
  border-left-color: var(--saffron);
}

.shell-sidebar-footer {
  padding: var(--space-6) var(--space-7);
  border-top: 1px solid rgba(255,255,255,0.1);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
/* The running build. Monospaced and quiet: it is read once, after an update,
   to answer "did that take" — and the rest of the time it should be furniture. */
.shell-version {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: rgba(255,255,255,0.4);
  letter-spacing: 0.04em;
}
/* The Settings disclosure in the sidebar footer: the pages that are about the
   app rather than about the food. A <details> rather than a scripted menu — it
   opens with the keyboard, is announced as a disclosure without any ARIA, and
   works before any script has run. shell.js only remembers the state. */
.shell-settings { margin-bottom: var(--space-5); }
.shell-settings-summary {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  color: rgba(255,255,255,0.65);
  cursor: pointer;
  list-style: none;
  transition: background var(--dur-3), color var(--dur-3);
}
/* The default marker is a black triangle that reads as a smudge on the herb
   ground; ::before below is the same affordance in the sidebar's own colour. */
.shell-settings-summary::-webkit-details-marker { display: none; }
.shell-settings-summary::before {
  content: "›";
  display: inline-block;
  font-size: var(--text-lg);
  line-height: 1;
  transition: transform var(--dur-4);
}
.shell-settings[open] > .shell-settings-summary::before { transform: rotate(90deg); }
.shell-settings-summary:hover { background: rgba(255,255,255,0.06); color: var(--white); }
.shell-settings-items { padding-bottom: var(--space-3); }

.shell-user { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); }
.shell-user-name {
  font-size: var(--text-sm);
  color: rgba(255,255,255,0.85);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.shell-logout-form { margin: 0; }
.shell-logout {
  border: 1px solid rgba(255,255,255,0.25);
  background: transparent;
  color: rgba(255,255,255,0.8);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--dur-3), color var(--dur-3), border-color var(--dur-3);
}
.shell-logout:hover { background: rgba(255,255,255,0.1); color: var(--white); border-color: rgba(255,255,255,0.5); }

.shell-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(26,48,35,0.45);
  z-index: var(--z-scrim);
}

/* ===== MAIN ===== */
.shell-main {
  flex: 1;
  margin-left: var(--sidebar-w);
  min-width: 0;
  display: flex;
  flex-direction: column;
  transition: margin-left var(--dur-4) ease;
}
/* Desktop: the hamburger collapses the sidebar out of the way, and the choice
   is remembered (static/js/shell.js) because it is a preference about the app
   rather than about one page. */
.shell.sidebar-collapsed .shell-sidebar { transform: translateX(-100%); }
.shell.sidebar-collapsed .shell-main { margin-left: 0; }

/* ===== TOPBAR =====
   Terracotta and always visible. It is the one element on screen at all times,
   so it carries the identity: on a phone, where the green sidebar is off
   canvas, this bar is the entire answer to "which app am I in". */
.shell-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-4) var(--space-7);
  background: var(--terracotta);
  color: var(--white);
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
  min-height: var(--topbar-h);
  box-shadow: 0 1px 0 rgba(43,37,33,0.08), 0 2px 10px rgba(43,37,33,0.06);
}

.topbar-titles { display: flex; align-items: baseline; gap: var(--space-5); min-width: 0; }
.topbar-page {
  font-family: 'Manrope', sans-serif;
  font-size: var(--text-xl);
  font-weight: 800;
  color: var(--white);
  letter-spacing: -0.3px;
  line-height: 1.15;
  white-space: nowrap;
  flex-shrink: 0;
}
.topbar-sub {
  font-size: var(--text-sm);
  color: rgba(255,255,255,0.75);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-4); flex-shrink: 0; }
/* The round chrome shared by the topbar's icon buttons. Outlined in the bar's
   own white rather than filled: a filled button here would compete with the
   page's primary action, which is also terracotta on white. */
.topbar-help {
  width: 2rem; height: 2rem; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1.5px solid rgba(255,255,255,0.45);
  border-radius: 50%;
  background: transparent;
  color: var(--white);
  font-family: 'Manrope', sans-serif; font-weight: 800; font-size: var(--text-lg);
  cursor: pointer;
  transition: background var(--dur-3), border-color var(--dur-3);
}
.topbar-help:hover { background: rgba(255,255,255,0.16); border-color: var(--white); }

/* A `.button` in the topbar cannot wear the page's primary colour, because the
   bar already is that colour — terracotta on terracotta is a button-shaped
   shadow. So the pair is inverted here: the primary action goes solid white,
   the secondary takes the outlined chrome the help button and the hamburger
   already share. This only became visible when the bar grew a second action;
   with one there was nothing for it to be less prominent than. */
.shell-topbar .button {
  background: var(--white);
  color: var(--terracotta);
  box-shadow: none;
}
.shell-topbar .button:hover {
  background: var(--cream);
  color: var(--terracotta-hover);
  transform: none;
  box-shadow: none;
}
.shell-topbar .button--secondary {
  background: transparent;
  color: var(--white);
  border: 1.5px solid rgba(255,255,255,0.45);
}
.shell-topbar .button--secondary:hover {
  background: rgba(255,255,255,0.16);
  border-color: var(--white);
  color: var(--white);
}

.shell-hamburger {
  background: transparent;
  border: 1.5px solid rgba(255,255,255,0.45);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.shell-hamburger:hover { background: rgba(255,255,255,0.16); }
.shell-hamburger-icon { position: relative; display: inline-block; width: 20px; height: 14px; }
.shell-hamburger-icon span {
  position: absolute; left: 0;
  width: 100%; height: 2px;
  background: var(--white);
  border-radius: var(--radius-xs);
  transition: transform var(--dur-4) ease, opacity var(--dur-3) ease, top var(--dur-4) ease;
}
.shell-hamburger-icon span:nth-child(1) { top: 0; }
.shell-hamburger-icon span:nth-child(2) { top: 6px; }
.shell-hamburger-icon span:nth-child(3) { top: 12px; }
.shell-hamburger[aria-expanded="true"] .shell-hamburger-icon span:nth-child(1) { top: 6px; transform: rotate(45deg); }
.shell-hamburger[aria-expanded="true"] .shell-hamburger-icon span:nth-child(2) { opacity: 0; }
.shell-hamburger[aria-expanded="true"] .shell-hamburger-icon span:nth-child(3) { top: 6px; transform: rotate(-45deg); }

/* ===== LANGUAGE SWITCHER ===== */
.lang-switcher { position: relative; flex-shrink: 0; }
.lang-toggle[aria-expanded="true"] { background: rgba(255,255,255,0.2); border-color: var(--white); }
.lang-menu {
  position: absolute; top: calc(100% + var(--space-4)); right: 0;
  z-index: var(--z-menu);
  min-width: 9rem;
  display: flex; flex-direction: column;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: var(--space-2);
  gap: var(--space-1);
}
.lang-menu form { margin: 0; }
.lang-option {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-5);
  padding: var(--space-4);
  border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-mid);
  cursor: pointer;
  font-family: 'Inter', sans-serif; font-size: var(--text-base); font-weight: 500; text-align: left;
  transition: background var(--dur-2), color var(--dur-2);
}
.lang-option:hover { background: var(--cream); color: var(--text-dark); }
.lang-option--active { color: var(--text-dark); font-weight: 600; }
.lang-option-code { text-transform: uppercase; font-size: var(--text-2xs); letter-spacing: 0.05em; color: var(--text-light); }
.lang-option--active .lang-option-code { color: var(--terracotta); }

/* ===== CONTENT ===== */
.content { padding: var(--space-9); max-width: var(--content-max); margin: 0 auto; width: 100%; }

.page-lead { color: var(--text-mid); margin: calc(-1 * var(--space-5)) 0 var(--space-8); max-width: 46rem; }
.hint { color: var(--text-light); font-size: var(--text-base); }
.back-link { display: inline-block; font-size: var(--text-base); font-weight: 600; color: var(--text-mid); margin-bottom: var(--space-5); }
.back-link:hover { color: var(--terracotta); }

/* ===== CARDS ===== */
.card {
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-8);
}

.table-card {
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
  margin-top: var(--space-6);
}
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--border-light); }
/* Uppercase micro-caps are for things that are not field labels — a column
   heading, a stat caption, a pill. A label on a field somebody fills in is
   sentence case; see the FORMS section. */
th {
  color: var(--text-light);
  font-weight: 600;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: var(--cream);
  border-bottom: 1px solid var(--border);
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: rgba(43,37,33,0.015); }

/* ===== BUTTONS ===== */
.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  background: var(--terracotta);
  color: var(--white);
  border: none;
  border-radius: var(--radius-sm);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: var(--text-base);
  box-shadow: 0 4px 12px rgba(185,79,44,0.22);
  transition: background var(--dur-3), transform var(--dur-1), box-shadow var(--dur-3);
  cursor: pointer;
}
.button:hover {
  background: var(--terracotta-hover);
  color: var(--white);
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(185,79,44,0.28);
}
.button:disabled, .button[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
  pointer-events: none;
}
.button--secondary {
  background: transparent;
  color: var(--herb);
  border: 1.5px solid var(--border);
  box-shadow: none;
  font-weight: 600;
}
.button--secondary:hover {
  background: var(--white);
  border-color: var(--herb);
  color: var(--herb);
  transform: none;
  box-shadow: none;
}
.button--danger { background: var(--danger); box-shadow: none; }
.button--danger:hover { background: var(--danger); box-shadow: none; filter: brightness(0.92); }
.button--small { padding: var(--space-3) var(--space-5); font-size: var(--text-sm); }
.button--big { padding: var(--space-5) var(--space-8); font-size: var(--text-md); width: 100%; justify-content: center; }

.link-button {
  background: none; border: none; padding: 0;
  color: var(--terracotta);
  text-decoration: underline;
  font-size: inherit; font-family: inherit;
  cursor: pointer;
}
.link-button:hover { color: var(--terracotta-hover); }

/* ===== LIST TOOLBAR (search left, primary action right) ===== */
.list-toolbar { display: flex; align-items: center; gap: var(--space-5); margin-bottom: var(--space-6); flex-wrap: wrap; }
.list-toolbar .search-form { margin-bottom: 0; flex: 1 1 16rem; }
.list-toolbar > .button { margin-left: auto; flex-shrink: 0; }

.search-form { display: flex; gap: var(--space-4); margin-bottom: var(--space-6); }
.search-input-wrap { position: relative; display: flex; flex: 1 1 auto; }
.search-form input[type="text"] {
  padding: var(--space-4) var(--space-9) var(--space-4) var(--space-5);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  font-family: inherit;
  font-size: var(--text-md);
  color: var(--text-dark);
  width: 100%;
  transition: border-color var(--dur-3), box-shadow var(--dur-3);
}
.search-form input[type="text"]:focus {
  border-color: var(--herb);
  box-shadow: 0 0 0 3px rgba(36,64,47,0.1);
}
/* The soft ring is the *pointer* focus style, and it must not remove the
   outline: `form input:focus` outranks `input:focus-visible` on specificity,
   so an `outline: none` here silently strips the keyboard indicator from the
   whole app. `:not(:focus-visible)` confines it to the pointer. */
.search-form input[type="text"]:focus:not(:focus-visible) { outline: none; }
.search-clear {
  position: absolute;
  right: var(--space-4);
  top: 50%;
  transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 1.35rem; height: 1.35rem;
  padding: 0; border: none; border-radius: 50%;
  background: transparent;
  color: var(--text-light);
  font-size: var(--text-xl); line-height: 1;
  cursor: pointer;
  transition: background var(--dur-3), color var(--dur-3);
}
.search-clear:hover { background: var(--border-light); color: var(--text-dark); }

/* ===== PILLS AND TAGS ===== */
.pill {
  display: inline-block;
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 600;
  background: var(--border-light);
  color: var(--text-mid);
}
.pill--herb { background: var(--herb-bg); color: var(--herb); }
.pill--saffron { background: var(--saffron-bg); color: var(--saffron-deep); }

/* A tag as a link. `a.tag` inherits the pill shape but has to say it is
   clickable, which it does by moving on hover rather than by being underlined —
   a row of underlined pills is a row of underlines. */
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.tag {
  display: inline-block;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--herb-bg);
  color: var(--herb);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background var(--dur-3), color var(--dur-3);
}
a.tag:hover { background: var(--herb); color: var(--white); }
.tag--active { background: var(--herb); color: var(--white); }
.tag-count { color: inherit; opacity: 0.6; margin-left: var(--space-2); font-weight: 500; }

/* ===== FORMS ===== */
form p, form .field { margin: var(--space-6) 0; display: flex; flex-direction: column; gap: var(--space-3); }
/* ...but a hint inside a form is prose, not a label+field row: the column flex
   would put every inline child of it on its own line. */
form p.hint, form p.help { display: block; }
/* A button in one of those rows is not a field, so it must not be stretched to
   the field width — `align-items: stretch` is the flex default and it turned
   "+ Another line" into a 1100px bar. */
form p > .button, form p > .link-button { align-self: flex-start; }

/* The label convention, and it is the only one: a label on a field somebody
   fills in is **sentence case**, in this size and weight, wherever that field
   lives. Uppercase is for things that are not labels (see `th` above) — and
   all-caps is worst exactly where German puts its longest compounds. */
form label, .field-label {
  font-family: 'Manrope', sans-serif;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-dark);
  text-transform: none;
  letter-spacing: normal;
}
form input, form select, form textarea {
  padding: var(--space-4) var(--space-5);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  font-size: var(--text-md);
  font-family: inherit;
  color: var(--text-dark);
  transition: border-color var(--dur-3), box-shadow var(--dur-3);
}
form textarea { line-height: 1.6; resize: vertical; }
form input:focus, form select:focus, form textarea:focus {
  border-color: var(--herb);
  box-shadow: 0 0 0 3px rgba(36,64,47,0.1);
}
form input:focus:not(:focus-visible),
form select:focus:not(:focus-visible),
form textarea:focus:not(:focus-visible) { outline: none; }
form input[type="checkbox"] { width: auto; }

.help { color: var(--text-light); font-size: var(--text-sm); margin: 0; }
.field-error { color: var(--danger); font-size: var(--text-sm); }
.field-error:empty { display: none; }
.required-marker { color: var(--terracotta); }

/* A section eyebrow, not a field label — so it takes the uppercase treatment
   (see the note above `form label`). */
.form-section {
  font-family: 'Manrope', sans-serif;
  font-size: var(--text-xs);
  font-weight: 800;
  color: var(--text-mid);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: var(--space-9) 0 var(--space-5);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}
.form-actions { display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap; margin-top: var(--space-9); }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--space-6); }
.form-grid > p { margin: 0; }

/* ===== MODAL ===== */
body.modal-open { overflow: hidden; }
.modal-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-8);
  background: rgba(26,48,35,0.5);
}
.modal-overlay[hidden] { display: none; }
.modal {
  background: var(--white);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-8) var(--space-8) var(--space-7);
  max-width: 420px;
  width: 100%;
  /* Never taller than the screen, and the *body* is what scrolls.
     `body.modal-open` stops the page behind from moving, so a panel taller than
     the viewport had its foot below the bottom edge with nothing able to reach
     it — which is how the help on the recipe form became unreadable from about
     halfway down. The title and the buttons stay put; only the prose moves. */
  display: flex;
  flex-direction: column;
  max-height: 100%;
}
.modal-title { font-family: 'Manrope', sans-serif; font-size: var(--text-xl); font-weight: 800; color: var(--text-dark); margin: 0 0 var(--space-4); }
.modal-body {
  color: var(--text-mid);
  font-size: var(--text-md);
  margin: 0 0 var(--space-7);
  overflow-y: auto;
  /* The default is `min-height: auto` — "never shrink below my content" — which
     would make the cap above do nothing at all. The same rule as the diagram
     column, in a flex column rather than a grid. */
  min-height: 0;
  /* Reaching the end of the help must not then start scrolling the page
     underneath it. */
  overscroll-behavior: contain;
}
.modal-body p { margin: 0 0 var(--space-4); }
.modal-body p:last-child { margin-bottom: 0; }
.modal-body ul { margin: var(--space-3) 0 var(--space-5); padding-left: var(--space-7); }
.modal-body li { margin: var(--space-2) 0; }
.modal-body h3 { font-size: var(--text-base); margin: var(--space-6) 0 var(--space-3); }
.modal-actions { display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap; }
.help-panel { max-width: 32rem; position: relative; }
.help-panel .modal-title { padding-right: var(--space-8); }
.help-panel .modal-body { margin-bottom: 0; }
.modal-close {
  position: absolute; top: var(--space-5); right: var(--space-6);
  border: none; background: transparent; cursor: pointer;
  font-size: var(--text-2xl); line-height: 1; color: var(--text-light);
}
.modal-close:hover { color: var(--text-dark); }

/* ===== NOTICES ===== */
.messages { list-style: none; padding: 0; margin: 0 0 var(--space-7); }
.notice {
  position: relative;
  border-radius: var(--radius-md);
  padding: var(--space-6);
  padding-right: var(--space-9);
  margin-bottom: var(--space-6);
  font-size: var(--text-base);
  line-height: 1.5;
  background: var(--herb-bg);
  border-left: 4px solid var(--herb);
  color: var(--text-dark);
}
.notice--success { background: var(--success-bg); border-left-color: var(--success); }
.notice--warning { background: var(--amber-bg); border-left-color: var(--amber); }
.notice--error { background: var(--danger-bg); border-left-color: var(--danger); }
.notice-dismiss {
  position: absolute; top: var(--space-3); right: var(--space-4);
  border: 0; background: none; padding: var(--space-2);
  line-height: 1; font-size: var(--text-xl); color: var(--text-light);
  cursor: pointer; border-radius: var(--radius-xs);
}
.notice-dismiss:hover { color: var(--text-dark); }

/* "Nothing here yet" is a heading and a centred card, never a bare paragraph on
   a blank page — which reads as a page that failed to load rather than as an
   empty collection. */
.empty-state {
  padding: var(--space-10) var(--space-7);
  text-align: center;
  color: var(--text-light);
  background: var(--paper);
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
}
.empty-state h2 { color: var(--text-dark); }
.empty-state > p { max-width: 34rem; margin-inline: auto; }
.empty-state .button { margin-top: var(--space-6); }

/* ===== HOME ===== */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-6); margin-bottom: var(--space-9); }
.stat {
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-7);
}
.stat-value { font-family: 'Manrope', sans-serif; font-size: var(--text-4xl); font-weight: 800; color: var(--text-dark); line-height: 1; }
.stat-label {
  display: block;
  margin-top: var(--space-4);
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-light);
}
.section-heading {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-6);
  margin: var(--space-9) 0 var(--space-6);
}
.section-heading h2 { margin: 0; }

/* ===== RECIPE GRID ===== */
.recipe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--space-7); }
.recipe-card {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  color: inherit;
  transition: transform var(--dur-3), box-shadow var(--dur-3);
}
.recipe-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); color: inherit; }
/* A fixed aspect ratio, so a portrait phone photograph and a landscape one do
   not make two cards in the same row different heights. */
.recipe-card-image { aspect-ratio: 4 / 3; background: var(--herb-bg); overflow: hidden; }
.recipe-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The placeholder for a recipe with no photograph: the app's own mark, quiet,
   rather than a broken-image icon or a grey rectangle. */
.recipe-card-image--empty { display: flex; align-items: center; justify-content: center; color: var(--herb); opacity: 0.25; }
.recipe-card-image--empty svg { width: 3.5rem; height: 3.5rem; }
.recipe-card-body { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-4); flex: 1; }
.recipe-card-title { font-family: 'Manrope', sans-serif; font-size: var(--text-lg); font-weight: 700; color: var(--text-dark); line-height: 1.3; margin: 0; }
.recipe-card-desc {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-mid);
  line-height: 1.5;
  /* Two lines, then ellipsis: a card is a glance, and a description that runs
     to six lines makes the grid ragged. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.recipe-card-meta {
  margin-top: auto;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-5);
  font-size: var(--text-xs);
  color: var(--text-light);
}
.recipe-card-meta strong { color: var(--text-mid); font-weight: 600; }

/* ===== RECIPE DETAIL ===== */
.recipe-hero { border-radius: var(--radius-lg); overflow: hidden; margin-bottom: var(--space-8); background: var(--herb-bg); }
.recipe-hero img { width: 100%; max-height: 26rem; object-fit: cover; display: block; }

.recipe-meta-row {
  display: flex; flex-wrap: wrap; gap: var(--space-8);
  padding: var(--space-6) 0;
  margin-bottom: var(--space-8);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.recipe-meta { display: flex; flex-direction: column; gap: var(--space-2); }
.recipe-meta-label { font-size: var(--text-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-light); }
.recipe-meta-value { font-family: 'Manrope', sans-serif; font-size: var(--text-lg); font-weight: 700; color: var(--text-dark); }

/* Ingredients beside the method, because that is how a recipe is read: the
   list stays in view while you work down the steps. It sticks for the same
   reason, and lets go below 700px where the two columns stack. */
/* Not a two-column grid any more. It was one while the ingredient list and the
   method sat side by side; now Preparing and Cooking are exclusive panels, and
   a grid whose first track is 20rem put whichever panel was showing into that
   track — so the diagram was squeezed into 318px, and scrolled, with 1100px of
   page sitting empty beside it. */
.recipe-body { display: block; }
.recipe-ingredients {
  /* Wide enough for "1.000 g festkochende Kartoffeln", not so wide that a list
     of six words becomes a line the eye has to track across the page. */
  max-width: 34rem;
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-7);
}
.ingredient-list { list-style: none; margin: 0; padding: 0; }
.ingredient-list li {
  display: flex;
  gap: var(--space-5);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-light);
  font-size: var(--text-md);
}
.ingredient-list li:last-child { border-bottom: none; }
/* The quantity column is fixed and tabular so the numbers line up down the
   list; the name takes what is left. A ragged left edge on a column of amounts
   is the thing that makes an ingredient list hard to scan. */
.ingredient-amount {
  flex: 0 0 5.5rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--text-dark);
  font-weight: 600;
  text-align: right;
}
.ingredient-name { color: var(--text-dark); }
.ingredient-note { display: block; font-size: var(--text-sm); color: var(--text-light); }

/* The servings stepper. It rewrites the amounts in place
   (static/js/recipe_scale.js) and is `hidden` until that script runs, so a page
   without JavaScript shows the recipe's own amounts rather than a control that
   does nothing. */
.serving-scaler {
  display: flex; align-items: center; gap: var(--space-5);
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--border);
}
.serving-stepper { display: inline-flex; align-items: center; gap: var(--space-2); }
.serving-step {
  width: 1.9rem; height: 1.9rem;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--border);
  border-radius: 50%;
  background: var(--white);
  color: var(--text-dark);
  font-family: 'Manrope', sans-serif; font-size: var(--text-lg); font-weight: 700; line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-3), background var(--dur-3);
}
.serving-step:hover:not(:disabled) { border-color: var(--herb); background: var(--herb-bg); }
.serving-step:disabled { opacity: 0.35; cursor: not-allowed; }
.serving-count {
  min-width: 1.6rem;
  text-align: center;
  font-family: 'Manrope', sans-serif;
  font-size: var(--text-lg);
  font-weight: 800;
  color: var(--text-dark);
  font-variant-numeric: tabular-nums;
}
.serving-label { font-size: var(--text-sm); color: var(--text-mid); }
/* Said only while the shown amounts are not the ones that were typed in — a
   permanent "scaled" note would be noise on the nine visits out of ten where
   nothing has been scaled. */
.serving-scaled-note { font-size: var(--text-xs); color: var(--saffron-deep); font-weight: 600; }

.recipe-instructions { font-size: var(--text-md); line-height: 1.75; color: var(--text-dark); }
/* Instructions are stored as plain text and rendered with `linebreaks`, which
   is deliberate: a rich-text editor is a second sanitiser to get right, and a
   recipe method is paragraphs. */
.recipe-instructions p { margin: 0 0 var(--space-6); }
.recipe-notes {
  margin-top: var(--space-9);
  padding: var(--space-6) var(--space-7);
  background: var(--saffron-bg);
  border-left: 4px solid var(--saffron);
  border-radius: var(--radius-sm);
}
.recipe-notes h3 { margin-top: 0; }
.recipe-notes p:last-child { margin-bottom: 0; }
.recipe-source { margin-top: var(--space-8); font-size: var(--text-sm); color: var(--text-light); }

/* ===== THE RECIPE CANVAS =====
   The recipe form's editor: the diagram itself, with the formset rows living
   inside its cells rather than in a list below it. Geometry from
   static/js/recipe_diagram.js, which sets grid-row and grid-column on each cell
   — everything here is what a cell looks like, not where it goes. */
.builder {
  padding: var(--space-7);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.builder-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-5);
  margin-bottom: var(--space-7);
}
.builder-keys { font-size: var(--text-xs); color: var(--text-light); }
/* A recipe five steps deep does not fit a laptop, and the alternative to
   scrolling it is reflowing it — which destroys the one thing the layout says.
   The scroll is here rather than on the page: a grid column holding something
   wide defaults to `min-width: auto`, keeps its full content width, and takes
   the topbar and the sidebar sideways with it. */
/* `overflow-x: auto` makes the *other* axis `auto` too — that is what the spec
   says, not a browser quirk — so the boundary arrows, which sit half outside
   the top and bottom edges of a step cell, were clipped by this box. The
   padding gives them the room; without it the top strip is a sliver. */
.builder-scroll { overflow-x: auto; padding: var(--space-4) 0; }
/* No min-width of its own: the blocks set their own minimum through their
   column tracks, and a second figure here would be a third place that has to
   agree with them. */
.builder-canvas { display: flex; flex-direction: column; }
.builder-block {
  display: grid;
  gap: var(--space-4);
  align-items: stretch;
}
/* A step with nothing going into it: "heat the oven to 180 °C". It spans every
   column, which is what says it belongs to the whole recipe rather than to one
   branch of it — and it sits wherever its position leaves it, so it can come
   after a few other steps rather than only at the top. */
.builder-block--standing .step-card {
  background: var(--saffron-bg);
  border-color: var(--saffron);
}

/* The space between two blocks. Nearly invisible until a step is being
   dragged, when it is the target that means "a step of your own, here". */
.builder-gap {
  height: var(--space-6);
  border-radius: var(--radius-xs);
  transition: background var(--dur-3), box-shadow var(--dur-3);
}
.builder.is-dragging .builder-gap { background: var(--border-light); }
.builder-gap.is-drop { background: var(--saffron-bg); box-shadow: inset 0 0 0 2px var(--saffron); }

.builder-cell {
  position: relative;
  display: flex; flex-direction: column; justify-content: center;
  min-width: 0;
  border-radius: var(--radius-sm);
  transition: box-shadow var(--dur-3);
}
.builder-cell--step { align-items: stretch; }
.builder-cell.is-drop { box-shadow: 0 0 0 2px var(--saffron); }
/* Where a dragged line will land. A pseudo-element rather than a real one: an
   inserted marker changes the grid's row count mid-drag, so every cell below
   the pointer moves and the thing being aimed at is no longer under it. */
.builder-cell.is-drop-before::before,
.builder-cell.is-drop-after::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--saffron);
}
.builder-cell.is-drop-before::before { top: calc(-1 * var(--space-3)); }
.builder-cell.is-drop-after::after { bottom: calc(-1 * var(--space-3)); }

/* The lines that are in no step. Not a section — just the last cards in the
   column, which is what the household asked for: a heading and a paragraph
   around them made an ordinary ingredient look like an unfinished one.

   It is still the drop target for "take this out of its step", so it becomes a
   *place* while a drag is happening and goes back to being nothing afterwards.
   Without that, taking the first line out of a step would have nowhere to aim
   at. */
.builder-tray { margin-top: var(--space-5); }
.builder-tray-slots { display: flex; flex-direction: column; gap: var(--space-4); }
.builder-tray-hint {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-light);
  /* `display: none` rather than hidden: it has to leave no room at all when
     nothing is being dragged, or the canvas ends in a blank line. */
  display: none;
}
.builder.is-dragging .builder-tray {
  padding: var(--space-5);
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  transition: background var(--dur-3), border-color var(--dur-3);
}
.builder.is-dragging .builder-tray-slots:not(:empty) { margin-bottom: var(--space-4); }
.builder.is-dragging .builder-tray-hint { display: block; }
.builder-tray.is-drop { background: var(--saffron-bg); border-color: var(--saffron); }
/* Where the cards are rendered and where they go home to. With JavaScript it
   holds only the rows somebody has removed, which are hidden; without it, it
   is the whole form as a plain list, which still saves. */
.builder-pool { display: flex; flex-direction: column; gap: var(--space-4); }
.builder-pool:not(:empty) { margin-top: var(--space-5); }

/* ---- the cards ---- */
.line-card, .step-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  transition: border-color var(--dur-3), box-shadow var(--dur-3), opacity var(--dur-3);
}
.step-card {
  border-color: var(--herb-light);
  background: var(--herb-bg);
  height: 100%;
  justify-content: center;
}
.line-card > p, .step-card > p { margin: 0; }
.card-head { display: flex; align-items: center; gap: var(--space-3); }
.card-head > p { margin: 0; flex: 1 1 auto; min-width: 0; }
.card-foot { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4); }
.card-foot > p { margin: 0; }
/* The inputs are in a cell now, not in a labelled column, so they are quieter
   and tighter than a form field elsewhere in the app. */
.line-card input, .line-card textarea, .line-card select,
.step-card input, .step-card textarea {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  width: 100%;
}
/* Amount and unit are *bounded*, not proportional. They were a fixed 4.5rem —
   narrower than the word "Amount" in the amount field's own placeholder, which
   read as "Amo…" — and then briefly `1fr 1fr`, which is what a proportional
   split does on a card that is 900px wide in the Steps list: a number box 442px
   across for a three-digit number.
   `justify-content: start` is the load-bearing half. Without it the tracks
   absorb the leftover width however they are sized, and any cap here is
   decorative. */
.card-foot {
  display: grid;
  grid-template-columns: minmax(0, 7rem) minmax(0, 8rem);
  justify-content: start;
}
.line-amount, .line-unit { min-width: 0; }
/* The same argument for the name: it is the widest thing on the card and wants
   room, but a single ingredient name does not want 800px of it. */
.line-name input { max-width: 26rem; }
.line-note input { max-width: 22rem; }
.line-note { flex: 1 1 8rem; min-width: 0; }
/* "no fixed amount" sits on the face of the card, not inside "More": the form
   refuses a line with neither an amount nor this, so it is the answer to an
   error rather than a rarity. */
.line-noamount { font-size: var(--text-2xs); color: var(--text-mid); }
.step-detail { flex: 1 1 8rem; min-width: 0; }
/* The box the diagram is read by: it wraps rather than scrolling, so a step
   whose name is longer than its column is still legible at a glance. */
.step-card .step-text textarea {
  font-weight: 600;
  line-height: 1.35;
  /* Sized to its content by fitStepText() in static/js/recipe_diagram.js, so
     it neither scrolls nor keeps empty rows — and is not draggable by a corner
     that would fight the script for the height. */
  resize: none;
  overflow: hidden;
}

/* The questions asked once a recipe in twenty — a note, "optional", a
   substitute, a duration, a detail — folded away. Five of them open at once in
   five cells side by side is not a diagram, it is a form again. */
.card-more { font-size: var(--text-2xs); }
.card-more-summary {
  display: inline-block;
  color: var(--text-light);
  font-weight: 600;
  cursor: pointer;
  list-style-position: inside;
}
.card-more-summary:hover { color: var(--text-mid); }
.card-more-body {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4);
  padding-top: var(--space-3);
}
.card-more-body > p { margin: 0; }
/* The two ways of joining a step to the rest of the recipe without dragging.
   Wrapping, because in a step column about eleven rems wide the two of them
   side by side would each show about four characters. */
.step-adds { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
.step-add {
  align-self: flex-start;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--herb-light);
  cursor: pointer;
  text-align: left;
}
.step-add:hover { color: var(--herb); text-decoration: underline; }


/* ---- resizing a tile by dragging its edge ----
   One handle per boundary, owned by the tile above it. The version this
   replaced put a control on the top *and* the bottom of every step, so every
   boundary had two of them eight pixels apart doing the same thing from
   opposite sides. */
.builder-cell--step { position: relative; }
.cell-resize {
  position: absolute;
  border-radius: var(--radius-pill);
  background: transparent;
  transition: background var(--dur-3), opacity var(--dur-3);
  opacity: 0;
  z-index: var(--z-popover);
}
/* The bottom edge of a step's own lines: a wide, thin bar. Taller than it
   looks — the visible line is 4px, the grab area is 14px, because a 4px target
   is one nobody hits on the first go and impossible with a finger. */
.cell-resize--rows {
  /* Starts clear of the "+" that adds a step beside this one, which sits at the
     left end of the same edge. */
  left: 2.5rem;
  right: var(--space-4);
  height: 14px;
  transform: translateY(-50%);
  cursor: ns-resize;
  touch-action: none;
}
.cell-resize--rows::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 4px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
  background: var(--herb-light);
}
/* The two ends of a standing instruction's band. */
.cell-resize--cols {
  top: var(--space-4);
  bottom: var(--space-4);
  width: 14px;
  cursor: ew-resize;
  touch-action: none;
}
.cell-resize--start { left: 0; transform: translateX(-50%); }
.cell-resize--end { right: 0; transform: translateX(50%); }
.cell-resize--cols::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 4px;
  transform: translateX(-50%);
  border-radius: var(--radius-pill);
  background: var(--herb-light);
}
/* Shown on hover of the tile, and always while one is being dragged or has
   focus — a handle that vanishes when the pointer leaves the tile is one you
   cannot drag away from it. */
.builder-cell--step:hover .cell-resize,
.builder-cell--step:focus-within .cell-resize,
.builder.is-resizing .cell-resize { opacity: 1; }
.cell-resize:hover, .cell-resize:focus-visible { opacity: 1; }
.cell-resize:hover::after, .cell-resize:focus-visible::after { background: var(--herb); }

/* Where the edge would land if it were released now. Drawn instead of moving
   the cards, because moving them re-lays the canvas out and destroys the very
   handle being dragged. */
.resize-guide {
  position: absolute;
  z-index: var(--z-menu);
  background: var(--saffron);
  border-radius: var(--radius-pill);
  pointer-events: none;
}
.resize-guide[hidden] { display: none; }
.resize-guide--rows { height: 3px; transform: translateY(-50%); }
.resize-guide--cols { width: 3px; transform: translateX(-50%); }
.builder.is-resizing { cursor: grabbing; }

/* A substitute is drawn under the line it replaces and inside its cell, which
   is where the recipe page draws it too. */
.line-group { display: flex; flex-direction: column; gap: var(--space-3); }
.line-alt { padding-left: var(--space-7); }
.line-alt .line-card { background: var(--cream); border-style: dashed; }

/* ---- the handle ---- */
.card-grip {
  flex: 0 0 auto;
  width: 1.5rem; height: 1.75rem;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--text-muted);
  cursor: grab;
  /* Without this a finger on the handle scrolls the page instead of dragging
     the card, and the drag never starts at all. */
  touch-action: none;
}
.card-grip:hover { background: var(--border-light); color: var(--text-mid); }
.card-grip-mark {
  width: 0.5rem; height: 0.875rem;
  background-image: radial-gradient(currentColor 40%, transparent 45%);
  background-size: 0.25rem 0.25rem;
}
.card-remove {
  flex: 0 0 auto;
  border: 1.5px solid var(--border);
  background: var(--white);
  color: var(--text-light);
  border-radius: var(--radius-sm);
  width: 1.75rem; height: 1.75rem;
  font-size: var(--text-base); line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-3), color var(--dur-3), background var(--dur-3);
}
.card-remove:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-bg); }
/* A removed row is hidden, never taken out of the DOM. A formset is an index
   range, not a list: a form missing from the POST is a hole, and Django reads
   the absent fields against that form's defaults, concludes it changed and
   validates it — which is how a deleted row comes back carrying "this field is
   required". Ticking DELETE and hiding is what the formset actually supports.
   One class for both formsets on this page — see .row--deleted below. */
.row-delete-box { display: none; }

/* ---- what a drag looks like ---- */
.line-card.is-dragging, .step-card.is-dragging { opacity: 0.4; }
.card-grip:active { cursor: grabbing; }
.drag-ghost {
  position: fixed;
  top: 0; left: 0;
  z-index: var(--z-popover);
  /* The page under the pointer has to be findable, and a ghost that answers
     `elementFromPoint` is the only thing ever found. */
  pointer-events: none;
  box-shadow: var(--shadow-lg);
  opacity: 0.9;
}

.image-preview { margin-top: var(--space-5); }
.image-preview img { max-width: 14rem; border-radius: var(--radius-sm); border: 1px solid var(--border); display: block; }

/* ===== THE DIAGRAM =====
   The Cooking-for-Engineers table: ingredients down the left, operations
   merging left to right. Geometry (rowspan/colspan) comes from
   apps/recipes/diagram.py; this only draws it.

   A real <table> with `border-collapse: collapse`, which is what makes a cell
   spanning four rows show one continuous outline instead of four stacked
   boxes — the whole visual point of the diagram. */
/* Grid and flex items default to `min-width: auto` — "never shrink below my
   content". For a column holding a wide table or a text input that is not a
   soft preference: the item keeps its full content width, the grid grows past
   the viewport, and the *page* scrolls sideways, taking the topbar and the
   sidebar with it. Every column here that can hold something wide says
   `min-width: 0`, so the overflow happens inside the element that actually has
   a scrollbar for it.

   Measured, not guessed: at a 390px viewport the recipe page was 508px wide
   before this rule and is exactly 390px after it, with the diagram scrolling
   inside its own box. */
.recipe-body > *,
.builder-block > *,
.form-grid > *,
.builder-scroll,
.diagram-scroll { min-width: 0; }

.diagram-scroll {
  /* A diagram with five columns does not fit a phone, and the alternative to
     scrolling it is reflowing it — which destroys the one thing it says. The
     scroll is on this wrapper rather than on the page, so the rest of the
     recipe still behaves.

     A recipe with a lot of steps makes this the normal case rather than the
     phone case, so the bar is always visible rather than the overlay kind that
     fades out — on a long diagram, a scrollbar you have to discover is a
     diagram that looks truncated. `scroll-padding` keeps a cell the cooking
     view scrolls to off the very edge. */
  scrollbar-width: thin;
  scrollbar-gutter: stable;
  scroll-padding-inline: var(--space-6);
  overscroll-behavior-x: contain;
  overflow-x: auto;
  margin-bottom: var(--space-6);
  border-radius: var(--radius-md);
  background: var(--paper);
  border: 1px solid var(--border-light);
  box-shadow: var(--shadow-sm);
}
.diagram { width: 100%; min-width: 28rem; border-collapse: collapse; }
.diagram-cell {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--herb-light);
  vertical-align: middle;
  font-size: var(--text-sm);
  background: var(--white);
  transition: background var(--dur-3), border-color var(--dur-3);
}
/* The global table rules strip the last row's border and tint rows on hover.
   Both are right for a list of records and wrong for a drawing. */
.diagram tbody tr:last-child .diagram-cell { border-bottom: 1px solid var(--herb-light); }
.diagram tbody tr:hover .diagram-cell { background: var(--white); }
.diagram tbody + tbody .diagram-cell { border-top: 1px solid var(--herb-light); }

.diagram-cell--ingredient { text-align: left; color: var(--text-dark); }
.diagram-cell--ingredient .ingredient-amount {
  flex: none;
  display: inline;
  margin-right: var(--space-2);
}
.diagram-unit { color: var(--text-mid); }
.diagram-name { color: var(--text-dark); font-weight: 600; }
.diagram-note { display: block; font-size: var(--text-xs); color: var(--text-light); }
.diagram-flag {
  margin-left: var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--saffron-deep);
}
.diagram-alt {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-mid);
  font-style: italic;
}
/* Optional lines are quieter but not hidden: the recipe works without them and
   somebody standing at a cupboard needs to see that at a glance. */
.diagram-cell--ingredient.is-optional .diagram-name { font-weight: 500; color: var(--text-mid); }

.diagram-cell--step {
  text-align: center;
  background: var(--herb-bg);
  color: var(--herb);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
}
.diagram tbody tr:hover .diagram-cell--step { background: var(--herb-bg); }
.diagram-step-text { display: block; }
.diagram-step-time {
  display: block;
  margin-top: var(--space-2);
  /* Not the mono face. That is the amounts' font, and it is theirs because a
     column of numbers wants to line up; a duration under a step name is not in
     a column with anything, and set in mono it read as a different kind of
     thing from the countdown that later runs against it. Tabular figures stay,
     for the same reason the countdown keeps them. */
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--herb-light);
}
/* The gap between an ingredient and the operation it feeds is a cell, because a
   row that simply omits it has silently shifted everything after it one column
   left. It has to look like nothing, which means no border of its own. */
.diagram-cell--filler { border: none; background: transparent; }
.diagram tbody tr:hover .diagram-cell--filler { background: transparent; }
.diagram-hint { margin-bottom: var(--space-8); }

/* Where the cooking view is standing right now, and where it has been. */
.diagram-cell.is-current {
  background: var(--saffron-bg);
  border-color: var(--saffron-deep);
  box-shadow: inset 0 0 0 1px var(--saffron-deep);
}
.diagram tbody tr:hover .diagram-cell.is-current { background: var(--saffron-bg); }
.diagram-cell.is-done { background: var(--success-bg); color: var(--success-strong); }
.diagram tbody tr:hover .diagram-cell.is-done { background: var(--success-bg); }

/* ===== COOKING VIEW ===== */
.cook-bar {
  position: sticky;
  top: var(--topbar-h);
  z-index: var(--z-sticky-heading);
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-6);
  padding: var(--space-5) var(--space-6);
  margin-bottom: var(--space-7);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
.cook-timer { display: flex; align-items: center; gap: var(--space-5); }
.cook-clock {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--text-dark);
  line-height: 1;
}
.cook-timer-buttons { display: flex; align-items: center; gap: var(--space-4); }
/* Inside the sticky bar the scaler is one control among several, not the head
   of a panel — so it drops the rule underneath it. */
.cook-servings { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.cook-progress {
  margin-left: auto;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-mid);
}

.cook-step {
  padding: var(--space-8);
  background: var(--white);
  border: 1px solid var(--border);
  border-left: 4px solid var(--terracotta);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
.cook-step-count {
  margin: 0 0 var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-light);
}
/* Deliberately large. This is read from two steps away, with wet hands, on a
   phone lying on a worktop. */
.cook-step-text { font-size: var(--text-4xl); line-height: 1.15; margin: 0 0 var(--space-5); }
.cook-step-detail { font-size: var(--text-md); color: var(--text-mid); margin: 0 0 var(--space-6); }
.cook-step-from { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-sm); color: var(--text-mid); }
.cook-step-ingredients { list-style: none; margin: var(--space-6) 0 0; padding: 0; }
.cook-step-ingredients li {
  display: flex; gap: var(--space-5);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-light);
  font-size: var(--text-lg);
}
.cook-step-ingredients li:last-child { border-bottom: none; }
.cook-step.is-ready { border-left-color: var(--success); background: var(--success-bg); }

.cook-nav { display: flex; align-items: center; gap: var(--space-5); margin: var(--space-7) 0 var(--space-9); }
.cook-nav .button { flex: 1 1 auto; justify-content: center; }
.cook-plain { margin-bottom: var(--space-9); }

.cook-finish {
  margin-top: var(--space-9);
  padding: var(--space-8);
  background: var(--saffron-bg);
  border: 1px solid var(--saffron);
  border-radius: var(--radius-md);
}
.cook-finish h3 { margin-top: var(--space-7); }
/* One column per portion size, wrapping rather than scrolling: five short
   number fields side by side on a tablet, two or three on a phone. The columns
   stretch to fill the row but the inputs do not — a 12rem box for a number
   that is never more than two digits reads as a text field somebody has
   mislabelled. */
.portion-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--space-5); }
.portion-field { margin: 0; }
.portion-field input { width: 100%; max-width: 5rem; }

/* ===== COOKING HISTORY ===== */
.cook-history { margin-top: var(--space-10); }
.cook-log-list { list-style: none; margin: 0; padding: 0; }
/* On the history page an entry names its recipe; on a recipe page it does not,
   because the reader is already looking at it. */
.cook-log-recipe { font-weight: 600; color: var(--text-dark); }
.cook-log-recipe:hover { color: var(--terracotta); }
.cook-log {
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--border-light);
}
.cook-log:last-child { border-bottom: none; }
.cook-log-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4); }
.cook-log-date { font-family: 'Manrope', sans-serif; font-weight: 700; color: var(--text-dark); }
.cook-log-who { font-size: var(--text-sm); color: var(--text-light); }
.cook-log-portions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-4); }
.cook-log-note { margin: var(--space-4) 0 0; font-size: var(--text-sm); color: var(--text-mid); }
.cook-log-remove { display: inline-block; margin-top: var(--space-3); }
/* The measured time sits beside the one somebody typed in when they added the
   recipe, and says which is which. The two disagreeing is the interesting part. */
.recipe-meta--measured .recipe-meta-value { color: var(--success-strong); }

/* Optional lines and substitutes, in the plain ingredient list. */
.ingredient-flag {
  margin-left: var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--saffron-deep);
}
.ingredient-alt { display: block; font-size: var(--text-sm); color: var(--text-mid); font-style: italic; }
.ingredient-alt .ingredient-amount { flex: none; display: inline; text-align: left; font-style: normal; }
.ingredient-list li.is-optional .ingredient-name { color: var(--text-mid); }

/* ===== RECIPE FORM (the small controls on a card) ===== */
/* One class for both formsets: a removed row is hidden, never taken out of the
   DOM. A formset is an index range, not a list — see static/js/recipe_form.js. */
.row--deleted { display: none; }

.row-extra { display: inline-flex; align-items: center; gap: var(--space-3); }
.row-extra[hidden] { display: none; }
.row-extra-label, .checkbox-inline span {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-light);
}
.row-extra-select, .row-extra-input {
  padding: var(--space-2) var(--space-4);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--white);
  font-family: inherit;
  font-size: var(--text-xs);
  color: var(--text-dark);
  max-width: 12rem;
}
.checkbox-inline { display: inline-flex; align-items: center; gap: var(--space-3); }
/* `form p` is a column of label-above-field; a checkbox and its label are a
   row. Written as `form p.checkbox-row` rather than with !important — the
   column rule is `form p`, so one extra class outranks it on specificity, and
   an !important here would have to be answered with another one by whatever
   needs to override it next. */
form p.checkbox-row { flex-direction: row; align-items: center; gap: var(--space-4); }

/* ===== TWO VIEWS OF ONE THING =====
   The Steps/Diagram switch on the recipe form and on the recipe page. A pair of
   buttons rather than tabs: there is one panel and it is rebuilt in place, so
   role="tab" would promise a tabpanel relationship that does not exist.
   aria-pressed is what these actually are. */
.view-switch {
  display: inline-flex;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--cream);
}
.view-switch-option {
  padding: var(--space-3) var(--space-6);
  border: none;
  border-radius: var(--radius-pill);
  background: none;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-mid);
  cursor: pointer;
  transition: background var(--dur-3), color var(--dur-3);
}
.view-switch-option:hover { color: var(--text-dark); }
.view-switch-option.is-on {
  background: var(--white);
  color: var(--herb);
  box-shadow: var(--shadow-sm);
}

.pantry-filter { margin-bottom: var(--space-5); }
.recipe-card-pantry { align-self: flex-start; margin-top: var(--space-4); }

/* The Preparing/Cooking switch on a recipe page, above the two panels it
   chooses between. */
.view-switch-bar { display: flex; justify-content: flex-start; margin-bottom: var(--space-7); }

/* ===== THE METHOD AS NUMBERED STEPS =====
   The same rows the diagram draws, read as a sequence instead of as a shape.
   Each step carries the lines that go into it, which is what ended the
   duplication: the ingredients used to be listed down the left *and* inside the
   diagram beside it. */
.method-steps { list-style: none; margin: 0 0 var(--space-8); padding: 0; display: flex; flex-direction: column; gap: var(--space-7); }
.method-step { display: flex; flex-direction: column; gap: var(--space-4); }
.method-step-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-4); }
.method-step-number {
  flex: 0 0 auto;
  width: 1.5rem; height: 1.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--herb);
  color: var(--cream);
  font-size: var(--text-2xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.method-step-text { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--text-dark); }
.method-step-from { margin: 0; font-size: var(--text-sm); color: var(--text-mid); padding-left: var(--space-8); }
.method-step-from em { color: var(--text-dark); font-style: normal; font-weight: 600; }
.method-step-lines { list-style: none; margin: 0; padding-left: var(--space-8); display: flex; flex-direction: column; gap: var(--space-3); }
.method-step-lines li { display: flex; gap: var(--space-5); font-size: var(--text-base); }
.method-step-detail { margin: 0; padding-left: var(--space-8); font-size: var(--text-sm); color: var(--text-mid); }
.method-unplaced { margin-bottom: var(--space-8); }

/* ===== WHAT THE CUPBOARD SAYS =====
   Three states, and "cannot tell" is deliberately not folded into "missing":
   200 g of butter against two packets of butter is genuinely unanswerable, and
   "you can make this" is a promise somebody acts on by not going to the shop. */
.pantry-check { margin-top: var(--space-8); }
.pantry-check h3 { font-size: var(--text-base); margin-bottom: var(--space-4); }
.pantry-check-list, .shopping-list { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.pantry-check-line, .shopping-line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-5);
  font-size: var(--text-base);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-xs);
  background: var(--cream);
}
.pantry-check-line--missing { background: var(--danger-bg); }
.pantry-check-line--short { background: var(--amber-bg); }
.pantry-check-line--unknown { background: var(--border-light); }
.pantry-check-name, .shopping-name { font-weight: 600; color: var(--text-dark); }
.pantry-check-note { font-size: var(--text-xs); color: var(--text-mid); text-align: right; }
.shopping-line { background: var(--herb-bg); }
.shopping-amount { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-mid); }

/* Everything about the recipe rather than about making it — the notes, the
   history, where it came from. Below both panels, because hiding them behind
   "Cooking" would make them findable only by somebody already after the
   method. */
.recipe-aside { margin-top: var(--space-9); }
.cook-log-actions { display: flex; align-items: center; gap: var(--space-5); }
.cook-log-actions form { margin: 0; }

/* ===== THE STEP LIST =====
   The recipe as numbered steps, which is the same rows the canvas lays out —
   moved, not copied. See static/js/recipe_diagram.js. */
.builder-canvas--list { overflow: visible; }
.step-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-6); }
.step-list-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--paper);
}
.step-list-item.is-drop { border-color: var(--saffron); background: var(--saffron-bg); }
.step-list-number {
  flex: 0 0 auto;
  width: 1.75rem; height: 1.75rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--herb);
  color: var(--cream);
  font-size: var(--text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* min-width so a long step name cannot push the row wider than the page — the
   default is min-width:auto, which means "never shrink below my content". */
.step-list-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-5); }
/* In the list a step card is a row, not a cell of a grid, so it drops the
   height and the centring the canvas gives it. */
.step-list-body .step-card { height: auto; justify-content: flex-start; }
.step-list-from { margin: 0; font-size: var(--text-xs); color: var(--text-mid); }
.step-list-lines { list-style: none; margin: 0; padding-left: var(--space-7); display: flex; flex-direction: column; gap: var(--space-4); }

/* ===== INGREDIENT SUGGESTIONS =====
   Parented to <body> and positioned against the input, because on the recipe
   form these fields sit in cells that scroll sideways inside .builder-scroll —
   a popup inside that is clipped by it. static/js/ingredient_suggest.js. */
.suggest-popup {
  position: absolute;
  z-index: var(--z-popover);
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  min-width: 12rem;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  box-shadow: var(--shadow-md);
}
.suggest-popup[hidden] { display: none; }
.suggest-option {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-xs);
  font-size: var(--text-sm);
  cursor: pointer;
}
.suggest-option.is-active { background: var(--herb-bg); }
.suggest-name { flex: 1 1 auto; min-width: 0; color: var(--text-dark); }
.suggest-via { font-size: var(--text-2xs); color: var(--text-light); font-style: italic; }
.suggest-unit {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-light);
}

/* ===== PANTRY ===== */
.pantry-add {
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--cream);
  margin-bottom: var(--space-8);
}
.pantry-add-fields { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-5); }
.pantry-add-fields > p { margin: 0; }
.pantry-add-name { flex: 1 1 14rem; min-width: 0; }
.pantry-add-amount { flex: 0 0 7rem; }
.pantry-add-unit { flex: 0 0 8rem; }
.pantry-group { margin-bottom: var(--space-8); }
.pantry-group-title {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-light);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-light);
  margin-bottom: var(--space-5);
}
.pantry-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.pantry-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-sm);
  background: var(--white);
}
/* Some, not counted — the honest state for salt, and one the matching treats
   as enough. Marked so it does not read as a row somebody forgot to fill in. */
.pantry-row.is-unmeasured { background: var(--cream); }
.pantry-name { flex: 1 1 12rem; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.pantry-sold { font-size: var(--text-2xs); color: var(--text-light); }
.pantry-amount { flex: 0 0 6rem; }
.pantry-unit { flex: 0 0 7rem; }
.pantry-remove { flex: 0 0 auto; }
.pantry-amount input, .pantry-unit select {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--white);
  font-family: inherit;
  font-size: var(--text-sm);
}

/* ===== SIMPLE FORMSET ROWS =====
   The alias and purchase-size rows on the catalogue page. Not the canvas: these
   are a short list of like things with no geometry to them. */
.simple-rows { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.simple-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-5); }
.simple-row-field { flex: 1 1 8rem; min-width: 0; }
.simple-row-field input, .simple-row-field select { width: 100%; }
.simple-row--size .simple-row-field { flex: 0 0 7rem; }
.simple-row--size .simple-row-field:last-of-type { flex: 1 1 8rem; }
.simple-row-delete { flex: 0 0 auto; font-size: var(--text-2xs); }
/* An "×" per row instead of a "remove" tick-box, and a "+" under the list. The
   DELETE box is still there and still ticked — the formset needs it — it is
   just not what anybody presses. */
.simple-row[hidden] { display: none; }
.row-drop {
  flex: 0 0 auto;
  width: 1.75rem; height: 1.75rem;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text-light);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-3), color var(--dur-3);
}
.row-drop:hover { background: var(--danger-bg); color: var(--danger); }

/* ===== DATA TABLES ===== */
/* Wide content scrolls inside its own box. Without this the table widens the
   page and takes the topbar and sidebar sideways with it. */
.table-scroll { overflow-x: auto; }
/* A unit is at most a few characters. Left to the form grid it stretched to
   half the page on the ingredient form — a dropdown the width of a paragraph
   for the word "g". */
select[data-unit-select] { max-width: 11rem; }
.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-base); }
.data-table th, .data-table td { padding: var(--space-5); text-align: left; border-bottom: 1px solid var(--border-light); }
.data-table thead th {
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-light);
  border-bottom: 1px solid var(--border);
}
/* The global `th` rule uppercases and letter-spaces headings, which is right
   for a column heading and wrong for a row one: it turned every ingredient in
   the catalogue into "ERDNUSSBUTTER". The same override the People table
   needs, for the same reason. */
.data-table th[scope="row"] {
  font-weight: 600;
  color: var(--text-dark);
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--text-base);
  background: transparent;
}
.catalogue-aliases { display: block; font-size: var(--text-2xs); color: var(--text-light); font-weight: 400; }
.plain-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); margin-bottom: var(--space-7); }
/* Bounded, not greedy. `1 1 14rem` on its own let the box eat the whole row —
   a search field the width of the page for a word like "Butter". */
.filter-bar input[type="search"] { flex: 1 1 14rem; min-width: 0; max-width: 26rem; }

/* ===== ADDING WHERE IT IS WANTED =====
   A "+" between the tiles, shown on hover. It replaces "+ Zutat" at the top of
   the page, which added at the end of the formset — so a new ingredient landed
   in the tray at the bottom and had to be dragged back to where it was meant to
   go. */
.cell-plus {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-3);
  z-index: var(--z-popover);
}
.cell-plus--row { left: 0; right: 0; height: var(--space-8); }
.cell-plus--after { bottom: calc(-1 * var(--space-4)); }
.cell-plus--before { top: calc(-1 * var(--space-4)); }
.cell-plus--col {
  top: 0; bottom: 0;
  right: calc(-1 * var(--space-5));
  width: var(--space-8);
}
/* A step *beside* this one, in the same column. Left-aligned on the bottom edge
   so it does not sit on top of the resize bar, which starts further right for
   the same reason. */
.cell-plus--sibling {
  left: var(--space-4);
  bottom: calc(-1 * var(--space-5));
  width: var(--space-8);
  height: var(--space-8);
}
.builder-cell:hover > .cell-plus,
.builder-cell:focus-within > .cell-plus,
.cell-plus:hover { opacity: 1; pointer-events: auto; }
.cell-plus-button {
  width: 1.5rem; height: 1.5rem;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--white);
  box-shadow: var(--shadow-sm);
  color: var(--herb);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-3), color var(--dur-3);
}
.cell-plus-button:hover { background: var(--herb); color: var(--cream); }

/* ===== WHAT A STEP IS SET TO =====
   The oven and the timer, on one row under the step's text. They are the same
   kind of thing — a number the step carries rather than a word somebody wrote —
   and they read as one row of settings instead of a dropdown pair at the left
   and a lone box adrift in the middle of the card, which is how they landed the
   first time.

   `:empty` on the row itself, not only on the oven: a step with neither shows
   nothing at all, and most steps have neither. */
.step-settings {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* The timer, built to look like the controls it stands next to. A pale pill
   was the first attempt and it read as decoration: the oven beside it is two
   bordered selects, and a borderless tinted lozenge between them looks like
   something the page is telling you rather than something you can type in.

   So it takes the same border, radius and white fill as every other field, and
   wraps the clock and the word "min" *inside* that border — one control that
   happens to be labelled at both ends, rather than an icon, a box and a caption
   sitting near each other. */
.step-minutes {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  color: var(--text-light);
  cursor: text;
  transition: border-color var(--dur-3), box-shadow var(--dur-3);
}
.step-minutes-icon { width: 0.95rem; height: 0.95rem; flex: 0 0 auto; }
/* The border belongs to the wrapper, so the box inside carries none of its
   own — otherwise a focused timer is a rounded rectangle inside a rounded
   rectangle. The focus ring moves to the wrapper for the same reason, which is
   the sanctioned way to quieten one rather than remove it. */
.step-minutes input,
.step-card .step-minutes input {
  width: 2.4rem;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-dark);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* The wrapper carries the ring, so the global `form input:focus` shadow is
   cancelled here — it would otherwise draw a second one inside the border.
   The outline itself is only *quietened*, and only for pointer focus: an
   unconditional `outline: none` here strips the keyboard indicator, which is
   what config/tests.py::test_a_focus_ring_is_never_removed exists to catch. */
.step-minutes input:focus { box-shadow: none; }
.step-minutes input:focus:not(:focus-visible) { outline: none; }
.step-minutes:focus-within {
  border-color: var(--herb);
  box-shadow: 0 0 0 3px rgba(36,64,47,0.1);
}
.step-minutes-unit { font-size: var(--text-xs); color: var(--text-light); }
/* The colon between the two boxes, which is what makes them read as one
   duration rather than as two unrelated numbers. No gap either side of it: the
   wrapper's own `gap` would otherwise put "12 : 30" on the card, which is a
   ratio and not a time. */
.step-minutes-colon {
  margin: 0 calc(-1 * var(--space-2));
  color: var(--text-light);
  font-size: var(--text-sm);
}
/* Left-aligned, unlike the minutes: they are the tail of a clock reading, so
   they line up under the colon rather than against the word after them. */
.step-minutes input[name$="-seconds"] { width: 1.9rem; text-align: left; }
/* Chrome's spinner steals a third of a box this narrow and is no use for a
   duration somebody types in whole. */
.step-minutes input::-webkit-outer-spin-button,
.step-minutes input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.step-minutes input { -moz-appearance: textfield; appearance: textfield; }

/* ===== A STEP THAT IS SEVERAL THINGS TO DO =====
   "- Topf in Ofen stellen / - Ofen vorheizen" is one box of the diagram and two
   actions. A single-line step stays a plain line — turning every step in the
   recipe into a one-item bullet list is worse than the problem. */
.diagram-step-parts, .cook-step-parts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: left;
}
.diagram-step-parts li, .cook-step-parts li { position: relative; padding-left: var(--space-6); }
.diagram-step-parts li::before, .cook-step-parts li::before {
  content: "–";
  position: absolute;
  left: 0;
  color: var(--text-light);
}
.cook-step-parts li { font-size: var(--text-2xl); font-weight: 600; line-height: 1.25; }
.cook-step-parts li::before { color: var(--terracotta-light); }

/* ===== THE OVEN =====
   A temperature and a mode, with an icon, wherever a step sets the oven. It is
   the number somebody is looking for when they walk up to it. */
.oven-setting {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--terracotta-bg);
  color: var(--terracotta);
  font-size: var(--text-xs);
  font-weight: 600;
}
.oven-icon { width: 1rem; height: 1rem; flex: 0 0 auto; }
.oven-temp { font-variant-numeric: tabular-nums; }
.oven-mode { color: var(--text-mid); font-weight: 400; }
.diagram-cell--oven { background: var(--terracotta-bg); }
.cook-step .oven-setting { font-size: var(--text-base); }
.step-oven { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.step-oven:empty { display: none; }
.oven-select { max-width: 9rem; }
/* The temperature on the step card. A box rather than the dropdown it was:
   a closed list of the settings a domestic oven has detents for is wrong the
   first time a recipe says a number that is not on it, and then there is
   nowhere to put it at all. Narrow, because it holds three digits. */
/* Scoped to the card as well, for the same reason `.step-minutes input` is:
   `.step-card input` sets `width: 100%` and outranks a single class, so a plain
   `.oven-temp-box` rule loses and the box comes out three digits too wide. */
.oven-temp-box,
.step-card .oven-temp-box {
  width: 4.5rem;
  font-variant-numeric: tabular-nums;
}
/* Chrome's spinner takes a third of a box this narrow and is no use for a
   temperature somebody types whole — the same argument as the step timer. */
.oven-temp-box::-webkit-outer-spin-button,
.oven-temp-box::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.oven-temp-box { -moz-appearance: textfield; appearance: textfield; }
.oven-temp-unit {
  font-size: var(--text-xs);
  color: var(--text-light);
  /* Against its own number, not a word-space away: the row's gap would read as
     "180   °C". */
  margin-left: calc(-1 * var(--space-2));
}
/* Said the moment it is typed, not on the next save. A temperature out of range
   used to be written through as nothing and disappear quietly when the recipe
   was saved, with no way to tell it had. */
.oven-temp-box.is-invalid,
.step-card .oven-temp-box.is-invalid { border-color: var(--danger); }
.oven-error {
  /* Its own line inside the wrapping flex row, or the message pushes the mode
     select off the end of a step card three inches wide. */
  flex: 1 0 100%;
  font-size: var(--text-xs);
}

/* ===== THE COOKING WALK =====
   Back and Next are pinned rather than sitting under the step: a step box
   changes height with every step, and a button below it is never twice in the
   same place.

   They hang off the *content column*, not the window edges. Fixed to the
   viewport they ended up half a screen away from the recipe on a wide monitor —
   the household's words were "too small and too far from the main content" —
   so the strip below is the content column's own width, centred, with an arrow
   at each end. It is `pointer-events: none` so it does not swallow clicks on
   the page behind it; the arrows themselves take theirs back. */
.cook-nav {
  position: fixed;
  top: 50%;
  left: calc(50% + (var(--sidebar-w) / 2));
  transform: translate(-50%, -50%);
  /* Wider than the content column by one arrow plus a gap at each side, so the
     arrows sit *beside* the recipe rather than on top of it. Pinned to the
     column's own width they overlapped the diagram — which on this page is the
     thing being read. Clamped to what is actually there, so on a narrow window
     they close in rather than disappearing off the edge. */
  width: min(100vw - var(--sidebar-w) - var(--space-6),
             calc(var(--content-max) + 11rem));
  display: flex;
  justify-content: space-between;
  pointer-events: none;
  z-index: var(--z-popover);
}
.cook-nav[hidden] { display: none; }
.cook-arrow {
  pointer-events: auto;
  /* Big. This is pressed with a floury hand, at arm's length, on a worktop —
     the 3rem it started at was a desk-sized target for a kitchen-sized job. */
  width: 4.5rem; height: 4.5rem;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--white);
  box-shadow: var(--shadow-lg);
  color: var(--herb);
  cursor: pointer;
  transition: background var(--dur-3), opacity var(--dur-3), transform var(--dur-3);
}
/* Sized here rather than by font-size: the mark is a path in a square viewBox,
   so its box *is* its ink and the flex centring above lands it dead centre. */
.cook-arrow-mark { width: 2rem; height: 2rem; display: block; }
.cook-arrow:hover:not(:disabled) { background: var(--herb-bg); transform: scale(1.06); }
.cook-arrow:disabled { opacity: 0.25; cursor: default; }
.cook-finish-row { display: flex; justify-content: center; margin-top: var(--space-8); }

/* ===== THE TIMER, WHILE COOKING =====
   Its own band at the foot of the step rather than a number and a button left
   loose under the ingredient list — which is where two competing declarations
   of this class had put it, the second quietly dropping the first's margin.

   It is separated by a rule and tinted, because it is the only part of the step
   that *changes on its own*: everything above it is the recipe, and this is the
   clock running. The countdown is set large on purpose — it is read from the
   other side of the kitchen, which is also why it keeps the tabular figures, so
   the digits do not jostle as the seconds fall. */
.cook-step-timer {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  margin-top: var(--space-8);
  padding-top: var(--space-7);
  border-top: 1px solid var(--border);
  color: var(--text-light);
}
.cook-step-timer-icon { width: 1.5rem; height: 1.5rem; flex: 0 0 auto; }
.cook-step-remaining {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--text-dark);
  /* Nudged off the icon it stands beside; the gap token would separate them by
     a word-space, and a clock wants to sit against its own number. */
  margin-right: var(--space-2);
}
/* Running and finished. The whole band takes the colour, not only the digits,
   so a glance at the bottom of the card answers "is it going?" without
   reading. */
.cook-step.is-running .cook-step-timer { color: var(--terracotta); }
.cook-step.is-running .cook-step-remaining { color: var(--terracotta); }
.cook-step.is-ready .cook-step-timer { color: var(--success-strong); }
.cook-step.is-ready .cook-step-remaining { color: var(--success-strong); }

/* ===== THE TIMER, EVERYWHERE ELSE =====
   The card static/js/timer_watch.js puts in the corner of every page that is
   not the cooking view a timer belongs to. Small on purpose: it is a reminder
   that something is running, not a second copy of the cooking view — the whole
   of it is a name, a clock and the one button anybody wants when it goes off.

   Bottom right rather than top: the topbar is already the app's own row of
   controls and the sidebar owns the left, so the bottom right corner is the
   only one where a persistent box covers nothing. `position: fixed`, so it
   stays put while the page scrolls under it. */
.timer-dock {
  position: fixed;
  right: var(--space-6);
  bottom: var(--space-6);
  z-index: var(--z-timer);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Wide enough for "8 - 12 Stunden abgedeckt…" to wrap to two lines and no
     wider — past this it stops reading as a note in the corner. */
  max-width: min(20rem, calc(100vw - var(--space-9)));
}
.timer-card {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--paper);
  box-shadow: var(--shadow-lg);
}
.timer-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  /* The wide thing in a flex row says so, or a long step name pushes the Stop
     button off the end of the card instead of wrapping. */
  min-width: 0;
}
.timer-card-recipe {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-mid);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.timer-card-step {
  font-size: var(--text-base);
  color: var(--text-dark);
  /* Two lines, then an ellipsis. A step is sometimes three sentences, and the
     card is a reminder rather than the recipe. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.timer-card-clock {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl);
  font-weight: 700;
  line-height: 1.1;
  color: var(--terracotta);
}
/* Finished, and waiting to be answered. The whole card takes the colour for the
   same reason the cooking view's band does: it has to be readable as "that one
   has gone off" from across the room, without reading anything. */
.timer-card.is-ready {
  border-color: var(--success);
  background: var(--success-bg);
}
.timer-card.is-ready .timer-card-clock,
.timer-card.is-ready .timer-card-recipe { color: var(--success-strong); }
.timer-card-stop {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-5);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  color: var(--text-dark);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-3), border-color var(--dur-3);
}
.timer-card-stop:hover { background: var(--cream); border-color: var(--text-muted); }
.timer-card.is-ready .timer-card-stop { border-color: var(--success); }

/* On a phone the card spans the width, minus the same margin either side — a
   20rem box pinned to one corner of a 390px screen leaves a stripe of page
   beside it that reads as a mistake. */
@media (max-width: 640px) {
  .timer-dock {
    left: var(--space-6);
    max-width: none;
  }
}

/* ===== THE TIMER SOUNDS ===== */
.sound-choices { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.sound-choice { display: flex; align-items: center; gap: var(--space-5); }
.sound-choice label { margin: 0; flex: 1 1 auto; }

/* ===== PEOPLE ===== */
.people-table th[scope="row"] { font-weight: 600; text-transform: none; letter-spacing: normal; font-size: var(--text-base); background: transparent; color: var(--text-dark); }
.people-sub { display: block; font-size: var(--text-xs); color: var(--text-light); font-weight: 400; }
/* Four actions on one line push the table past the content width on a laptop,
   and the fourth — Delete — is the one that falls off the right-hand edge. So
   they wrap: the row is a little taller and every action is reachable without
   discovering that the table scrolls sideways. */
.people-actions { width: 1%; }
.people-actions-group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); }
.people-actions form { margin: 0; }
.people-table td, .people-table th { padding: var(--space-5); }
.people-table tr.is-inactive td, .people-table tr.is-inactive th { opacity: 0.55; }
.numeric { text-align: right; font-variant-numeric: tabular-nums; }
.pill--off { background: var(--danger-bg); color: var(--danger); }
.link-button--danger { color: var(--danger); }
.link-button--danger:hover { color: var(--danger); }

/* ===== SSO SETTINGS ===== */
/* The redirect URI, which is a value people come to this page to copy into DSM
   and which fails silently if it is mistyped. Given its own block for that
   reason rather than being one field among fifteen. */
.callback-box {
  padding: var(--space-6) var(--space-7);
  margin-bottom: var(--space-8);
  background: var(--herb-bg);
  border-left: 4px solid var(--herb);
  border-radius: var(--radius-sm);
}
.callback-label {
  display: block;
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--herb);
}
.callback-url {
  display: block;
  margin: var(--space-3) 0;
  font-family: var(--font-mono);
  font-size: var(--text-md);
  color: var(--text-dark);
  word-break: break-all;
}

.check-results {
  padding: var(--space-7);
  margin-bottom: var(--space-8);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.check-list { list-style: none; margin: 0; padding: 0; }
/* Two columns, not three. The version with the outcome in a third `auto`
   column collapsed the middle one to a single character wide the first time a
   real failure came back: the detail was a `requests` exception several hundred
   characters long with no spaces to break at, so it took the whole row and left
   the URL wrapping one letter per line. Anything holding text of unpredictable
   length gets its own row and is allowed to break anywhere. */
.check {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--space-2) var(--space-5);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border-light);
}
.check:last-child { border-bottom: none; }
.check > * { min-width: 0; }
.check-mark { font-weight: 800; }
.check--ok .check-mark { color: var(--success); }
.check--bad .check-mark { color: var(--danger); }
.check-label { display: block; font-weight: 600; color: var(--text-dark); font-size: var(--text-sm); }
.check-url {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-light);
  overflow-wrap: anywhere;
}
.check-detail {
  grid-column: 2;
  font-size: var(--text-sm);
  color: var(--text-mid);
  overflow-wrap: anywhere;
}
.check--bad .check-detail { color: var(--danger); }

.sso-actions {
  display: flex; flex-wrap: wrap; gap: var(--space-5);
  margin-top: var(--space-9);
  padding-top: var(--space-7);
  border-top: 1px solid var(--border);
}
.sso-actions form { margin: 0; }

/* ===== TAG LIST ===== */
.tag-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--space-6); }
.tag-card {
  background: var(--paper);
  border: 1px solid var(--border-light);
  border-left: 4px solid var(--herb);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6) var(--space-7);
}
.tag-card-title { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-5); margin-bottom: var(--space-4); }
.tag-card-title a { font-family: 'Manrope', sans-serif; font-weight: 700; font-size: var(--text-lg); color: var(--text-dark); }
.tag-card-title a:hover { color: var(--terracotta); }
.tag-card-list { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); }
.tag-card-list li { padding: var(--space-2) 0; }

/* ===== LOGIN =====
   Its own page, outside the shell: there is no navigation to offer somebody
   who is not signed in yet. */
.login-body {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-8);
  background: var(--herb);
}
.login-card {
  width: 100%;
  max-width: 24rem;
  background: var(--white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-9);
}
.login-brand { display: flex; justify-content: center; margin-bottom: var(--space-7); }
.login-brand .brand { color: var(--herb); font-size: var(--text-2xl); }
.login-brand .brand-mark { color: var(--terracotta); }
.login-title { font-size: var(--text-xl); text-align: center; margin-bottom: var(--space-8); }
.login-form { margin: 0; }
/* The SSO button is the way in, so it is the filled one and it is first. The
   local form below is the way *back* in when SSO is broken — present, and
   visibly secondary. */
.login-sso { margin-bottom: var(--space-7); }
.login-sso .button { width: 100%; justify-content: center; }
.login-divider {
  display: flex; align-items: center; gap: var(--space-5);
  margin: var(--space-7) 0;
  color: var(--text-light);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.login-divider::before, .login-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.login-local-toggle { display: block; margin: 0 auto; font-size: var(--text-sm); color: var(--text-light); }
.login-local-toggle:hover { color: var(--text-dark); }
.login-lang {
  display: flex;
  justify-content: center;
  gap: var(--space-6);
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border-light);
}
.login-lang form { margin: 0; }
.login-lang .link-button { color: var(--text-light); text-decoration: none; font-size: var(--text-sm); }
.login-lang .link-button:hover { color: var(--text-dark); }
/* The current language is stated by weight rather than by being unclickable:
   a disabled control here would look like the choice had failed. */
.login-lang--active { color: var(--text-dark); font-weight: 700; }

.pw-wrap { position: relative; display: flex; }
.pw-wrap input { width: 100%; padding-right: var(--space-9); }
.pw-reveal {
  position: absolute; right: var(--space-4); top: 50%; transform: translateY(-50%);
  border: none; background: transparent; color: var(--text-light);
  cursor: pointer; padding: var(--space-2); display: flex;
}
.pw-reveal:hover { color: var(--text-dark); }

/* ===== ACCESSIBILITY ===== */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--terracotta);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
/* Inside the terracotta topbar, terracotta is invisible. */
.shell-topbar a:focus-visible,
.shell-topbar button:focus-visible { outline-color: var(--white); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
  /* The sidebar is off-canvas by default; the hamburger slides it in. */
  .shell-sidebar { transform: translateX(-100%); }
  .shell-main { margin-left: 0; }
  .shell.sidebar-open .shell-sidebar { transform: translateX(0); }
  .shell.sidebar-open .shell-overlay { display: block; }
}

@media (max-width: 700px) {
  .content { padding: var(--space-7); }
  th, td { padding: var(--space-4) var(--space-5); }
  /* .recipe-body needs no override any more — it is a plain block at every
     width, because the two panels are never on screen together. */
  .topbar-sub { display: none; }
  /* Hidden on a phone: a big target pinned over the content is in the way when
     the content is the whole screen, the thumb is already there, and a swipe
     does the same thing. The sidebar is off-canvas at this width too, so the
     strip's offset from it would be wrong regardless. */
  .cook-nav { display: none; }
  /* The canvas keeps its shape and scrolls: reflowing it to one column is
     exactly the thing it exists not to do. Only the padding around it goes, so
     the cards themselves get the width instead. */
  .builder { padding: var(--space-5); }
  /* A note and a detail are both prose, and beside a 4.5rem number field they
     are two words wide. On their own line the card stays typeable. */
  .line-note, .step-detail { flex: 1 1 100%; }
  /* Read from arm's length rather than from a desk — but --text-4xl is a
     headline on a 6-inch screen, and a two-word instruction wrapping over three
     lines is harder to take in, not easier. */
  .cook-step-text { font-size: var(--text-2xl); }
  .cook-step { padding: var(--space-6); }
  .cook-clock { font-size: var(--text-2xl); }
  .diagram-cell { padding: var(--space-3) var(--space-4); font-size: var(--text-xs); }
}

@media (max-width: 520px) {
  .recipe-grid { grid-template-columns: 1fr; }
  .recipe-meta-row { gap: var(--space-6); }
  /* The progress counter loses its push to the right once the bar wraps: on
     its own line, "Step 3 of 7" flush right reads as a stray fragment. */
  .cook-progress { margin-left: 0; }
  .cook-nav { flex-wrap: wrap; }
  .row-extra-select { max-width: 9rem; }
}
