@import url("/assets/bootstrap-icons-36ab3983.css");

/* ============================================
   Culinari Design Tokens
   Brand: Culinari (codebase internal: Tastebite)
   Source of truth for colors, type, spacing,
   radii, shadows, and motion. Consume via
   CSS vars in components and utilities.
============================================ */

:root {
  /* ---- Brand (terracotta) -------------------- */
  --culinari-primary:        #D97757;
  --culinari-primary-hover:  #C96351;
  --culinari-primary-active: #B84E42;
  --culinari-primary-deep:   #A84430;
  --culinari-primary-tint:   rgba(217, 119, 87, 0.12);
  --culinari-primary-wash:   #fbf7f3;
  --culinari-primary-cream:  #fffaf6;
  --culinari-primary-grad-1: #D97757;
  --culinari-primary-grad-2: #b85c3f;

  /* ---- Neutrals ------------------------------ */
  --culinari-ink:         #1a1a1a;
  --culinari-fg:          #333333;
  --culinari-fg-muted:    #555555;
  --culinari-muted:       #7F7F7F;
  --culinari-silver:      #B0B0B0;
  --culinari-placeholder: #C0C0C0;
  --culinari-hairline:    #E8E8E8;
  --culinari-line:        #f0f0f0;
  --culinari-line-warm:   #f0ece8;
  --culinari-bg-soft:     #f5f5f5;
  --culinari-bg-app:      #f8f9fa;
  --culinari-bg-panel:    #f9f9f9;
  --culinari-surface:     #ffffff;

  /* Admin (dark) */
  --culinari-admin-1: #1f2937;
  --culinari-admin-2: #111827;

  /* ---- Semantic ------------------------------ */
  --culinari-success: #198754;
  --culinari-warning: #ffc107;
  --culinari-danger:  #dc3545;
  --culinari-info:    #0dcaf0;
  /* Deep variants for icons/text on tinted backgrounds — needed wherever
     the base hue can't clear WCAG AA 4.5:1 on cream/wash surfaces. */
  --culinari-success-deep: #136B43;
  --culinari-warning-deep: #7A5300;
  --culinari-danger-deep:  #B02A37;

  /* ---- Semantic aliases ---------------------- */
  --fg-1: var(--culinari-ink);
  --fg-2: var(--culinari-fg);
  --fg-3: var(--culinari-muted);
  --fg-placeholder: var(--culinari-placeholder);

  --bg-page:  var(--culinari-surface);
  --bg-card:  var(--culinari-surface);
  --bg-soft:  var(--culinari-bg-soft);
  --bg-app:   var(--culinari-bg-app);
  --bg-cream: var(--culinari-primary-cream);
  --bg-wash:  var(--culinari-primary-wash);

  --border-hairline: var(--culinari-hairline);
  --border-line:     var(--culinari-line);
  --border-warm:     var(--culinari-line-warm);

  --accent:        var(--culinari-primary);
  --accent-hover:  var(--culinari-primary-hover);
  --accent-active: var(--culinari-primary-active);
  --accent-tint:   var(--culinari-primary-tint);
  /* Terracotta as TEXT/icon on light surfaces — needs deeper hue
     to clear WCAG AA 4.5:1 contrast. Use --accent for backgrounds,
     --accent-text for body text, links, icons, hover/active text. */
  --accent-text:   var(--culinari-primary-deep);

  /* ---- Type families ------------------------- */
  --font-sans:    'Inter', system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: 'Nunito Sans', 'Inter', system-ui, sans-serif;
  --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ---- Type scale ---------------------------- */
  --fs-12: 12px;
  --fs-13: 13px;
  --fs-14: 14px;
  --fs-15: 15px;
  --fs-16: 16px;
  --fs-18: 18px;
  --fs-20: 20px;
  --fs-24: 24px;
  --fs-h3: 1.5rem;
  --fs-h2: 2rem;
  --fs-h2-lg: 2.5rem;
  --fs-h1: 2.5rem;
  --fs-hero: 2.25rem;
  --fs-hero-lg: 3.25rem;

  --lh-tight: 1.1;
  --lh-snug:  1.2;
  --lh-body:  1.6;

  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;
  --fw-heavy:    800;

  --tracking-label:   0.02em;
  --tracking-eyebrow: 0.12em;

  /* ---- Radii --------------------------------- */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-2xl: 20px;
  --radius-3xl: 24px;
  --radius-pill: 999px;
  --radius-circle: 50%;

  /* ---- Elevation (warm-tinted) --------------- */
  --shadow-xs:   0 1px 2px rgba(0,0,0,0.04);
  --shadow-sm:   0 2px 10px rgba(0,0,0,0.05);
  --shadow-md:   0 4px 12px rgba(0,0,0,0.08);
  --shadow-lg:   0 10px 25px rgba(0,0,0,0.10);
  --shadow-xl:   0 18px 40px rgba(217, 119, 87, 0.12);
  --shadow-hero: 0 30px 60px rgba(217, 119, 87, 0.22);
  --shadow-cta:  0 24px 50px rgba(217, 119, 87, 0.25);
  --shadow-drag: 0 8px 20px rgba(0, 0, 0, 0.15);
  --shadow-chip: 0 12px 30px rgba(0, 0, 0, 0.08);

  /* ---- Spacing (4px base) -------------------- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;

  /* ---- Motion -------------------------------- */
  --ease-standard: ease;
  --ease-soft: cubic-bezier(0.2, 0.8, 0.2, 1);
  --dur-fast: 150ms;
  --dur-base: 200ms;
  --dur-slow: 400ms;

  /* ---- Bootstrap bridges --------------------- */
  --bs-primary: var(--culinari-primary);
  --bs-primary-rgb: 217, 119, 87;
  --bs-link-color: var(--accent-text);
  --bs-link-color-rgb: 168, 68, 48;
  --bs-link-hover-color: var(--culinari-primary-active);
}

/* ============================================
   Skip link (accessibility): first focusable
   element, hidden until keyboard focus, jumps
   to <main id="main-content">. Uses transform
   (not a layout prop) so motion rules hold.
============================================ */

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: var(--space-2);
  z-index: 1090;
  padding: var(--space-2) var(--space-4);
  background: var(--accent);
  color: var(--culinari-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  text-decoration: none;
  transform: translateY(-150%);
  transition: transform var(--dur-fast) var(--ease-soft);
}

.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: none;
}

#main-content:focus {
  outline: none;
}

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

/* ============================================
   Layout utility classes
   (replacements for one-off inline style attributes — keeps templates
   free of style="" so the CSP unsafe-inline surface stays minimal)
============================================ */

/* Anti-spam honeypot: parked far offscreen but still form-submittable. */
.offscreen-honeypot {
  position: absolute;
  left: -5000px;
}

/* Minimum comfortable tap target for icon-only buttons. */
.min-touch-36 {
  min-width: 36px;
  min-height: 36px;
}

/* 1px probe observed by the bulk-select bar's IntersectionObserver. */
.h-sentinel {
  height: 1px;
}

/* Fixed-width checkbox/icon table column. */
.w-36px {
  width: 36px;
}

/* Clamp wide table cells (raw LLM output) so rows stay scannable. */
.mw-320px {
  max-width: 320px;
}

/* ============================================
   Type utility classes
============================================ */

.type-eyebrow {
  display: inline-block;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  color: var(--accent-text);
}

.type-label {
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  font-weight: var(--fw-regular);
}

.type-meta {
  font-size: var(--fs-12);
  color: var(--culinari-muted);
}

/* Reading-measure cap for standalone prose (intros, hints, help text) so body
   copy stays within the 65–75ch DESIGN.md measure on wide admin pages. */
.prose-measure {
  max-width: 68ch;
}

.type-hero-title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-hero);
  line-height: var(--lh-tight);
  color: var(--fg-1);
}

@media (min-width: 768px) {
  .type-hero-title { font-size: var(--fs-hero-lg); }
}

.type-h2 {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  color: var(--fg-1);
}

@media (min-width: 768px) {
  .type-h2 { font-size: var(--fs-h2-lg); }
}

.type-card-title {
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-20);
  color: var(--culinari-ink);
  line-height: var(--lh-snug);
}

.btn-primary {
  --bs-btn-color: var(--culinari-surface);
  --bs-btn-bg: var(--culinari-primary);
  --bs-btn-border-color: var(--culinari-primary);
  --bs-btn-hover-color: var(--culinari-surface);
  --bs-btn-hover-bg: var(--culinari-primary-hover);
  --bs-btn-hover-border-color: var(--culinari-primary-active);
  --bs-btn-focus-shadow-rgb: 217, 119, 87;
  --bs-btn-active-color: var(--culinari-surface);
  --bs-btn-active-bg: var(--culinari-primary-active);
  --bs-btn-active-border-color: var(--culinari-primary-deep);
  --bs-btn-disabled-color: var(--culinari-surface);
  --bs-btn-disabled-bg: var(--culinari-primary);
  --bs-btn-disabled-border-color: var(--culinari-primary);
}

.btn-outline-primary {
  --bs-btn-color: var(--culinari-primary);
  --bs-btn-border-color: var(--culinari-primary);
  --bs-btn-hover-color: var(--culinari-surface);
  --bs-btn-hover-bg: var(--culinari-primary);
  --bs-btn-hover-border-color: var(--culinari-primary);
  --bs-btn-focus-shadow-rgb: 217, 119, 87;
  --bs-btn-active-color: var(--culinari-surface);
  --bs-btn-active-bg: var(--culinari-primary);
  --bs-btn-active-border-color: var(--culinari-primary);
  --bs-btn-disabled-color: var(--culinari-primary);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--culinari-primary);
}

/* PT-safe button utility. Use on locale-bearing actions where the PT
   string runs 20–40% longer than EN. Provides side padding and
   min-width slack so "Adicionar ao Plano de Refeição" doesn't truncate
   a button sized to "Add to Meal Plan". See DESIGN.md §6. */
.btn--pt-safe {
  min-width: auto;
  padding-inline: 1.25rem;
  white-space: nowrap;
}

/* Touch-floor utility for detail-page header / toolbar actions, where btn-sm
   would fall under the 44×44 target. inline-flex centers the label so the
   added min-height doesn't top-align text. See DESIGN.md §6 (≥44px actions). */
.btn--touch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
}

/* Modal close: lift Bootstrap's small btn-close to the 44px touch floor and
   swap its default blue focus ring for the terracotta halo used elsewhere.
   See DESIGN.md §6 (≥44px actions) and the focus-halo pattern. */
.modal-header .btn-close {
  width: 2.75rem;
  height: 2.75rem;
  background-position: center;
  border-radius: var(--radius-md);
}

.modal-header .btn-close:focus,
.modal-header .btn-close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

/* Radius utility classes mapped to Culinari tokens.
   Bootstrap only ships rounded-0..5 — these bridge the numeric-px naming
   used across views (rounded-6, rounded-8, rounded-12) to the design scale. */
.rounded-6  { border-radius: var(--radius-sm)  !important; }
.rounded-8  { border-radius: var(--radius-md)  !important; }
.rounded-12 { border-radius: var(--radius-lg)  !important; }
.rounded-16 { border-radius: var(--radius-xl)  !important; }
.rounded-20 { border-radius: var(--radius-2xl) !important; }
.rounded-24 { border-radius: var(--radius-3xl) !important; }

.pagination {
  --bs-pagination-color: var(--culinari-primary);
  --bs-pagination-hover-color: var(--culinari-primary-deep);
  --bs-pagination-focus-color: var(--culinari-primary-deep);
  --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(217, 119, 87, 0.25);
  --bs-pagination-active-color: var(--culinari-surface);
  --bs-pagination-active-bg: var(--culinari-primary);
  --bs-pagination-active-border-color: var(--culinari-primary);
  /* Wrap the bar between buttons when it can't fit, instead of letting
     flex shrink the items and break words inside a button. */
  flex-wrap: wrap;
  row-gap: var(--space-2);
}

/* Keep multi-word controls ("‹‹ Primeiro", "Próximo ›", …) on one line so
   every cell stays the same height as the number buttons. */
.page-link {
  white-space: nowrap;
}

/* ============================================
   Book Index Page Styles
============================================ */

.resource-card {
  padding: var(--space-2);
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-soft),
              box-shadow var(--dur-base) var(--ease-soft);
}

.resource-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.resource-card:hover .resource-title,
.resource-row-card:hover .resource-title {
  color: var(--accent-text);
}

.resource-image {
  height: 200px;
  object-fit: cover;
  aspect-ratio: 16/9;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.resource-image--portrait {
  height: auto;
  max-height: 320px;
  aspect-ratio: 2 / 3;
  object-fit: contain;
  background-color: var(--bg-soft);
}

.resource-image--empty {
  height: 200px;
  background-color: var(--bg-soft);
  border-radius: var(--radius-lg);
  font-size: 2.5rem;
}

.resource-image--portrait.resource-image--empty {
  height: auto;
}

/* Resource chip — design-system tag pill used across the resource-card family.
   Replaces ad-hoc `badge bg-light text-dark` Bootstrap defaults. */
.resource-chip {
  display: inline-flex;
  align-items: center;
  background: var(--bg-wash);
  color: var(--culinari-fg);
  font-size: 12px;
  font-weight: 400;
  line-height: 1;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  white-space: nowrap;
}

.resource-chip--muted {
  color: var(--culinari-fg-muted);
}

.resource-chip--draft {
  background: var(--bg-soft);
  color: var(--culinari-fg-muted);
}

/* Caption chip laid over an image (carousel slide label). Stays on the warm
   cream palette (admin is the only dark region) and lifts off the photo with a
   warm hairline + soft shadow; the opaque fill keeps the label legible on any
   image. */
.resource-chip--overlay {
  border-color: var(--border-warm);
  box-shadow: var(--shadow-sm);
}

.resource-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--culinari-fg);
  text-decoration: none;
  transition: color 0.15s ease;
}

/* Bookmark + small action icons (edit, remove, etc.) — ≥44×44 hit target
   so the cook's fingertip lands the tap with oily fingers and a propped phone. */
.bookmark-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: var(--culinari-fg);
  text-decoration: none;
  border-radius: var(--radius-pill);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.bookmark-icon a,
a.bookmark-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-pill);
}

.bookmark-icon:hover,
.bookmark-icon a:hover {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.bookmark-icon:focus-visible,
.bookmark-icon a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

/* Library icon — twin of .bookmark-icon for the second toggle next to it
   on every book card and show header. Same 44×44 hit target, same neutral
   resting color, same hover/focus chrome. The two icons differentiate by
   glyph silhouette (collection stack vs bookmark tab), never by chrome. */
.library-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: var(--culinari-fg);
  text-decoration: none;
  border-radius: var(--radius-pill);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.library-icon:hover {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.library-icon:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

/* The book-actions wrapper holds both icons side by side on cards.
   Listed here so any future shared rules ride on the role-neutral class
   instead of overloading .bookmark-icon. */
.book-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* The saved thumb-tab moment: when the cook has saved a recipe / book,
   the saved-state glyph carries the brand's single voice. Applies to
   both icons (bookmark = wishlist; collection = library) so saved is
   symmetric across the two affordances. */
.bookmark-icon .bi-bookmark-fill,
.bookmark-icon .bi-bookmark-star-fill,
.bookmark-icon .bi-heart-fill,
.bookmark-container .bi-bookmark-fill,
.bookmark-container .bi-bookmark-star-fill,
.bookmark-container .bi-heart-fill,
.library-icon .bi-collection-fill,
.bookmark-container .bi-collection-fill,
.book-actions .bi-collection-fill,
.book-actions .bi-bookmark-fill,
.book-actions .bi-bookmark-star-fill {
  color: var(--accent);
}

/* Drag handle — invisible-chrome icon button that's still focusable
   and keyboard-reachable. Subordinate to content: nearly invisible at
   rest so it doesn't visually fence the row's left edge, brightens to
   muted ink on row hover/focus, accent on direct hover. Keyboard-only
   users get the focus ring regardless of the dimming. */
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--culinari-line-warm);
  cursor: grab;
  border-radius: var(--radius-md);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.recipe-row:hover .drag-handle,
.recipe-row:focus-within .drag-handle {
  color: var(--culinari-fg-muted);
}

.drag-handle:hover {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.drag-handle:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.drag-handle:active {
  cursor: grabbing;
}

/* Recipe-form sortable rows — ingredients, alternatives, steps, side dishes.
   Cookbook-warm hairlines instead of Bootstrap border-bottom, ghost-tertiary
   action buttons instead of btn-light. */
.recipe-row {
  border-bottom: 1px solid var(--culinari-hairline);
}

.recipe-row--alternative {
  background-color: var(--bg-cream);
  border-bottom-color: var(--culinari-line-warm);
}

/* Counter shown next to the "+ Add alternative" action when a parent
   already has at least one alternative. Surfaces the 3-cap inline so
   it is not a surprise once the limit is hit. */
.alternatives-counter {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  font-size: var(--fs-12);
  color: var(--culinari-fg-muted);
  background-color: var(--culinari-line-warm);
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

.row-action-btn.is-disabled {
  cursor: default;
  color: var(--culinari-fg-muted);
}

.row-action-btn.is-disabled:hover,
.row-action-btn.is-disabled:focus-visible {
  background-color: transparent;
  color: var(--culinari-fg-muted);
}

.row-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  flex-wrap: nowrap;
  gap: 4px;
}

/* Quiet supporting tag — used for "Optional", future labels.
   Italic, warm-thread bg, muted ink. Never accent-coloured. */
.ingredient-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 10px;
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  font-style: italic;
  letter-spacing: 0.02em;
  color: var(--culinari-fg-muted);
  background-color: var(--culinari-line-warm);
  border-radius: 999px;
  vertical-align: middle;
}

/* Linked-ingredient mark on saved rows — the persistent companion to the
   autocomplete's typing-time "recognized" check, so linked vs free-text rows
   scan at a glance across a long list. Glyph + visually-hidden text in the
   partial; muted, never accent-coloured (metadata, not an earned status). */
.ingredient-linked-mark {
  margin-left: 0.375rem;
  font-size: var(--fs-12);
  color: var(--culinari-fg-muted);
  vertical-align: middle;
}

/* Always visible at low contrast; brightens on row hover/focus. We
   intentionally do not gate visibility behind a hover media query —
   hiding editor controls until the user "knows where to look" is the
   SaaS reflex Culinari avoids. Calm beats invisible. */
.row-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  background-color: transparent;
  color: var(--culinari-fg-muted);
  border-radius: var(--radius-md);
  text-decoration: none;
  font-size: var(--fs-16);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.recipe-row:hover .row-action-btn,
.recipe-row:focus-within .row-action-btn {
  color: var(--culinari-fg);
}

.row-action-btn:hover,
.row-action-btn:focus-visible {
  background-color: var(--accent-tint);
  color: var(--accent-text);
  outline: none;
}

.row-action-btn--danger,
.recipe-row:hover .row-action-btn--danger,
.recipe-row:focus-within .row-action-btn--danger {
  color: var(--culinari-danger);
}

.row-action-btn--danger:hover,
.row-action-btn--danger:focus-visible {
  background-color: rgba(220, 53, 69, 0.08);
  color: var(--culinari-danger);
}

/* Labeled variant of row-action-btn: keeps the 40px height and accent
   hover for affordance, but grows to fit a visible text label next to
   the icon. Used for "+ alternative" so the action announces itself
   without relying on hover tooltips. Defined after the base rule so the
   width/padding/font overrides win at equal specificity. */
.row-action-btn--labeled {
  width: auto;
  padding: 0 10px;
  gap: 6px;
}

.row-action-btn--labeled .row-action-label {
  white-space: nowrap;
  font-size: var(--fs-14);
}

@media (max-width: 575.98px) {
  .row-action-btn { width: 44px; height: 44px; }
  .row-action-btn--labeled { width: 44px; padding: 0; }
  .row-action-btn--labeled .row-action-label { display: none; }

  /* Reflow recipe rows to two lines on phones — name on line 1,
     qty/unit/actions on line 2 — so the action column gains breathing
     room and 44px touch targets fit comfortably. The Bootstrap col
     breakpoints in markup do most of the work; we only need vertical
     rhythm and right-aligned actions here. */
  .recipe-row {
    row-gap: 4px;
  }

  .recipe-row .row-actions {
    justify-content: flex-end;
  }
}

/* ============================================
   Dashboard — Kitchen Notebook Layout
   Single column, cream-page surface, no card chrome,
   hairlines (warm-thread) and Headline type carry
   the architecture. See DESIGN.md §2 (Cream-First),
   §3 (Headline / Eyebrow), §4 (Flat-By-Default).
============================================ */

.dashboard-page {
  max-width: 880px;
  margin-inline: auto;
  padding-block: var(--space-8) var(--space-16);
  background-color: var(--bg-cream);
}

.dashboard-greeting {
  margin-block-end: var(--space-10);
}

.dashboard-greeting__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.5vw, 2.5rem);
  font-weight: var(--fw-heavy);
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  color: var(--fg-1);
  margin: 0;
}

.dashboard-greeting__subtitle {
  margin-block-start: var(--space-2);
  margin-block-end: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  color: var(--fg-3);
}

.dashboard-band {
  padding-block-start: var(--space-6);
}

.dashboard-band + .dashboard-band {
  margin-block-start: var(--space-12);
  padding-block-start: var(--space-10);
  border-block-start: 1px solid var(--border-warm);
}

.dashboard-eyebrow {
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--accent-text);
  margin-block-end: var(--space-6);
}

.dashboard-section + .dashboard-section {
  margin-block-start: var(--space-10);
}

.dashboard-section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--border-warm);
}

.dashboard-section__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--fg-1);
  margin: 0;
}

.dashboard-section__meta {
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  color: var(--fg-3);
  margin: 0;
}

.dashboard-section__meta-dot {
  margin-inline: var(--space-2);
  color: var(--culinari-silver);
}

.dashboard-meta-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-inline-end: calc(-1 * var(--space-2));
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--accent-text);
  text-decoration: none;
  white-space: nowrap;
  border-radius: var(--radius-sm);
  transition: color 200ms var(--ease-soft),
              background-color 200ms var(--ease-soft);
}

.dashboard-meta-link:hover,
.dashboard-meta-link:focus-visible {
  color: var(--accent-active);
  background-color: var(--accent-tint);
  text-decoration: none;
  outline: none;
}

.dashboard-meta-link .bi {
  margin-inline-start: var(--space-1);
  font-size: 0.95em;
}

/* Recipe list — quieter than the index variant of recipe-row-card. */
.dashboard-recipe-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.dashboard-recipe-list__item + .dashboard-recipe-list__item {
  border-block-start: 1px solid var(--border-warm);
}

/* Flatten the inherited row-card on the dashboard so the recipe list reads
   as type-led entries on cream, not as stacked white cards. The hairline
   on .dashboard-recipe-list__item + .dashboard-recipe-list__item already
   gives the rhythm. */
.dashboard-recipe-list__item .resource-row-card {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: var(--space-4) 0;
  transition: background-color 200ms var(--ease-soft);
}

.dashboard-recipe-list__item .resource-row-card:hover,
.dashboard-recipe-list__item .resource-row-card:focus-within {
  transform: none;
  box-shadow: none;
  background-color: var(--bg-wash);
}

/* Cookbook shelf — horizontal scroll of jacket-led covers. */
.dashboard-shelf {
  display: flex;
  gap: var(--space-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-block-end: var(--space-3);
  margin-inline: calc(-1 * var(--space-4));
  padding-inline: var(--space-4);
  scrollbar-width: thin;
}

.dashboard-shelf__item {
  flex: 0 0 auto;
  scroll-snap-align: start;
  width: 140px;
}

@media (max-width: 575.98px) {
  .dashboard-shelf__item { width: 108px; }
}

.book-shelf-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--fg-1);
  transition: transform 200ms var(--ease-soft);
}

.book-shelf-card:hover,
.book-shelf-card:focus-visible {
  transform: translateY(-2px);
  text-decoration: none;
  color: var(--fg-1);
  outline: none;
}

.book-shelf-card:focus-visible .book-shelf-card__cover {
  box-shadow: 0 0 0 3px var(--accent);
}

.book-shelf-card__cover {
  position: relative;
  aspect-ratio: 2 / 3;
  width: 100%;
  border-radius: var(--radius-sm);
  background-color: var(--bg-wash);
  overflow: hidden;
  border: 1px solid var(--border-warm);
  transition: box-shadow 200ms var(--ease-soft);
}

.book-shelf-card:hover .book-shelf-card__cover {
  box-shadow: var(--shadow-md);
}

.book-shelf-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.book-shelf-card__fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-3);
  background-color: var(--bg-wash);
  font-family: var(--font-display);
}

.book-shelf-card__fallback-title {
  font-size: var(--fs-14);
  font-weight: var(--fw-bold);
  color: var(--fg-1);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.book-shelf-card__fallback-publisher {
  margin-block-start: auto;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-regular);
  color: var(--fg-3);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.book-shelf-card__title {
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--fg-1);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.book-shelf-card__subtitle {
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  color: var(--fg-3);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Empty state — three method options, no single mystery button. */
.dashboard-empty {
  padding-block: var(--space-8);
}

/* Inline variant for quieter section nudges (books / meal plan / wishlist
   when absent). One-line shape, no body, no button cluster. */
.dashboard-empty--inline {
  padding-block: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
}

.dashboard-empty__line {
  font-family: var(--font-sans);
  font-size: var(--fs-16);
  color: var(--fg-3);
  margin: 0;
}

.dashboard-empty__heading {
  font-family: var(--font-display);
  font-size: var(--fs-20);
  font-weight: var(--fw-bold);
  color: var(--fg-1);
  margin: 0 0 var(--space-2);
}

.dashboard-empty__body {
  font-family: var(--font-sans);
  font-size: var(--fs-16);
  color: var(--fg-2);
  margin: 0 0 var(--space-5);
  max-width: 52ch;
}

.dashboard-empty__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.dashboard-empty__action {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--fg-2);
  background-color: var(--bg-cream);
  text-decoration: none;
  transition: border-color 200ms var(--ease-soft),
              color 200ms var(--ease-soft),
              background-color 200ms var(--ease-soft);
}

.dashboard-empty__action:hover,
.dashboard-empty__action:focus-visible {
  border-color: var(--accent);
  color: var(--accent-text);
  background-color: var(--bg-cream);
  outline: none;
}

.dashboard-empty__action--primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--culinari-surface);
}

.dashboard-empty__action--primary:hover,
.dashboard-empty__action--primary:focus-visible {
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--culinari-surface);
}

/* Wishlist nudge — one quiet row. */
.dashboard-wishlist-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  text-decoration: none;
  color: var(--fg-1);
  transition: color 200ms var(--ease-soft);
}

.dashboard-wishlist-item:hover,
.dashboard-wishlist-item:focus-visible {
  color: var(--accent-text);
  text-decoration: none;
  outline: none;
}

.dashboard-wishlist-item:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-tint);
  border-radius: var(--radius-sm);
}

.dashboard-wishlist-item__cover {
  flex: 0 0 auto;
  width: 56px;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-warm);
  background-color: var(--bg-wash);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 575.98px) {
  .dashboard-wishlist-item__cover { width: 48px; }
}

.dashboard-wishlist-item__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.dashboard-wishlist-item__fallback {
  font-size: 1.25rem;
  color: var(--accent-text);
}

.dashboard-wishlist-item__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.dashboard-wishlist-item__title {
  font-family: var(--font-sans);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  color: inherit;
  line-height: var(--lh-snug);
}

.dashboard-wishlist-item__publisher {
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  color: var(--fg-3);
}

@media (max-width: 575.98px) {
  .dashboard-page {
    padding-block: var(--space-6) var(--space-10);
    padding-inline: var(--space-3);
  }

  .dashboard-section__head {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }

  .dashboard-shelf__item {
    width: 120px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .book-shelf-card,
  .book-shelf-card__cover,
  .dashboard-meta-link,
  .dashboard-wishlist-item,
  .dashboard-empty__action,
  .dashboard-recipe-list__item .resource-row-card {
    transition: none;
  }

  .book-shelf-card:hover,
  .book-shelf-card:focus-visible {
    transform: none;
  }
}

@media (max-width: 767.98px) {
  .resource-image {
    height: 180px;
  }

  /* Stack recipe cards vertically on small screens */
  .recipes-grid .row > div {
    width: 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
}

@media (min-width: 576px) and (max-width: 991.98px) {
  /* Two columns on medium screens */
  .recipes-grid .row > div {
    flex: 0 0 50%;
    max-width: 50%;
  }
}

/* Tag Cloud Styling */
.tag-cloud .badge {
  font-size: 0.9rem;
  padding: 6px 12px;
  transition: background-color var(--dur-base) var(--ease-soft),
              color var(--dur-base) var(--ease-soft),
              transform var(--dur-base) var(--ease-soft);
}

.tag-cloud .badge:hover {
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
  transform: translateY(-2px);
}

/* Search Form Styling — inherits Tastebite form-control bottom-border style */

/* Lists Styling */
.list-group-item {
  transition: background-color var(--dur-base) var(--ease-soft);
}

.list-group-item:hover {
  background-color: var(--bg-app);
}

/* Dashboard Sidebar Links */
.dashboard-sidebar-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--culinari-fg);
  font-size: 0.9rem;
  transition: background 0.2s ease, color 0.2s ease;
}

.dashboard-sidebar-link i {
  color: var(--accent-text);
  font-size: 1.1rem;
  width: 20px;
  text-align: center;
}

.dashboard-sidebar-link:hover {
  background: var(--accent-tint);
  color: var(--accent-text);
}

.dashboard-sidebar-link.active {
  background: var(--accent-tint);
  color: var(--accent-text);
  font-weight: 600;
}

/* Sidebar Styling */
.recipes-sidebar {
  position: sticky;
  top: 20px;
}

.sidebar-section {
  background: var(--culinari-surface);
  border-radius: var(--radius-lg);
  padding: 15px;
  box-shadow: var(--shadow-sm);
}

.sidebar-title {
  font-size: 1.1rem;
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg);
  border-bottom: 2px solid var(--border-line);
  padding-bottom: 10px;
}

@media (max-width: 991.98px) {
  .recipes-sidebar {
    position: static;
    margin-top: 2rem;
  }

  /* The index disclosure sits flush under its own toggle, not trailing a grid,
     so it drops the 2rem gap the shared sidebar uses elsewhere. */
  #indexFilters.recipes-sidebar {
    margin-top: 0;
  }
}

/* The mobile filters disclosure collapses #indexFilters below lg. At lg+ it is
   the always-visible sticky sidebar, so the Bootstrap collapse must not hide it. */
@media (min-width: 992px) {
  #indexFilters.collapse {
    display: block !important;
  }
}

.filters-toggle__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.4rem;
  border-radius: var(--radius-pill);
  background: var(--culinari-primary);
  color: var(--culinari-surface);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  line-height: 1;
}

/* Keep the badge legible when the outline button inverts to a terracotta fill. */
.filters-toggle:hover .filters-toggle__count,
.filters-toggle:focus-visible .filters-toggle__count {
  background: var(--culinari-surface);
  color: var(--accent-text);
}

/* ============================================
   Meal Plan Board Drag-and-Drop Styles
============================================ */

.drag-over-highlight {
  border: 2px dashed var(--bs-primary) !important;
  border-radius: 0.375rem;
}

.recipe-draggable {
  cursor: grab;
}

.recipe-draggable:active,
.sortable-chosen {
  cursor: grabbing;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
  transform: scale(1.03);
  position: relative;
  z-index: 10;
}

/* Recipe card inner wrapper — terracotta chip to match design system */
.recipe-card-inner {
  background-color: var(--accent-tint);
  color: var(--culinari-ink);
  font-size: 13px;
  font-weight: var(--fw-medium);
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.recipe-card-inner:hover {
  background-color: rgba(217, 119, 87, 0.22);
  color: var(--culinari-ink);
}

.recipe-card-inner-thumb {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: 4px;
}

/* Row-action buttons — visible only on hover */
.recipe-remove-btn,
.recipe-move-btn,
.recipe-preview-btn {
  opacity: 0;
  transition: opacity 0.15s ease;
  font-size: 0.75rem;
}

.recipe-draggable:hover .recipe-remove-btn,
.recipe-draggable:hover .recipe-move-btn,
.recipe-draggable:hover .recipe-preview-btn,
.meal-plan-cell-list > div:hover .recipe-preview-btn {
  opacity: 0.5;
}

.recipe-remove-btn:hover,
.recipe-move-btn:hover,
.recipe-preview-btn:hover {
  opacity: 1 !important;
}

/* Add button on filled cells — visible only on cell hover */
.cell-add-btn {
  opacity: 0;
  transition: opacity 0.15s ease;
}

[data-controller="meal-plan-cell"]:hover .cell-add-btn {
  opacity: 0.5;
}

.cell-add-btn:hover {
  opacity: 1 !important;
}

/* Touch devices have no hover, so these hover-revealed cell controls (add,
   remove, move, and the inline servings display) would never surface. Keep
   them quietly visible so they stay reachable on a phone or tablet. */
@media (pointer: coarse) {
  .recipe-remove-btn,
  .recipe-move-btn,
  .recipe-preview-btn,
  .cell-add-btn,
  [data-controller="meal-plan-cell"] .cell-add-btn {
    opacity: 0.6;
  }
}

/* Empty cell "+" trigger — fills cell, icon visible only on hover */
.cell-empty-trigger {
  height: 100%;
  min-height: 40px;
}

.cell-empty-trigger .bi-plus-circle {
  opacity: 0;
  transition: opacity 0.15s ease;
}

.cell-empty-trigger:hover .bi-plus-circle {
  opacity: 0.6;
}

/* Ghost placeholder in source cell during drag */
.sortable-ghost {
  opacity: 0.45;
}

/* Do NOT add transition to .sortable-ghost — must disappear instantly on cancel */

/* Meal plan board — card shell */
.meal-plan-board {
  border: 1px solid var(--culinari-line-warm);
  border-radius: 12px;
  overflow: hidden;
  background: var(--culinari-surface);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

.week-selector {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
}

/* When the selector sits inside a card (meal-plan board), it needs a tinted
   surface and a dividing line below. Standalone usage (shopping list)
   inherits the surrounding cream; the surrounding rhythm provides the break. */
.week-selector--in-card {
  background: var(--bg-card);
  border-bottom: 1px solid var(--border-warm);
}

.week-selector__nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.week-selector__label {
  min-width: 14ch;
  padding: 0 var(--space-2);
  font-size: var(--fs-15);
  font-weight: var(--fw-semibold);
  color: var(--fg-1);
  text-align: center;
}

.week-selector__today {
  margin-left: auto;
}

.meal-plan-board__body {
  padding: 0;
}

/* Board table — tinted header, soft dividers, terracotta accents */
.meal-plan-board .table {
  margin: 0;
  border: 0;
  font-size: 13px;
}

.meal-plan-board .table > :not(caption) > * > * {
  border-color: var(--culinari-line);
  padding: 10px 8px;
  vertical-align: top;
}

.meal-plan-board .table thead th {
  background: var(--culinari-primary-wash);
  border-bottom: 1px solid var(--culinari-line-warm);
  color: var(--culinari-ink);
  font-weight: 600;
  font-size: 13px;
  padding: 14px 8px;
}

.meal-plan-board .table thead th.table-active {
  background: rgba(217, 119, 87, 0.10);
  color: var(--culinari-primary);
}

.meal-plan-board .table thead th .text-muted {
  color: var(--culinari-muted) !important;
  font-weight: 400;
  font-size: var(--fs-12);
  margin-top: 2px;
}

.meal-plan-board .table tbody td:first-child {
  background: var(--culinari-primary-wash);
  color: var(--culinari-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--fs-12);
  font-weight: 600;
  vertical-align: middle;
  width: 120px;
}

.meal-plan-board .table tbody td {
  min-height: 96px;
  height: 96px;
}

/* Ensure today column in body has subtle tint too */
.meal-plan-board .table tbody tr td.table-active {
  background: rgba(217, 119, 87, 0.04);
}

/* Sticky first column so meal-type label stays visible when scrolling horizontally */
.meal-plan-board .table-responsive {
  position: relative;
}

.meal-plan-board .table thead th:first-child,
.meal-plan-board .table tbody td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
}

.meal-plan-board .table thead th:first-child {
  background: var(--culinari-primary-wash);
  box-shadow: 1px 0 0 var(--culinari-line-warm);
}

.meal-plan-board .table tbody td:first-child {
  box-shadow: 1px 0 0 var(--culinari-line-warm);
}

/* Subtle right-edge fade hints that the grid scrolls horizontally on narrow viewports */
@media (max-width: 767.98px) {
  .meal-plan-board .table-responsive::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 24px;
    pointer-events: none;
    background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.9));
  }

  /* Each day gets enough room to read at a glance, and a gentle horizontal
     snap lands a swipe cleanly on a day. scroll-padding clears the sticky
     meal-type column so the snapped day isn't hidden behind it. proximity
     (not mandatory) keeps the snap assistive, never fighting the user. */
  .meal-plan-board .table-responsive {
    scroll-snap-type: x proximity;
    scroll-padding-left: 120px;
  }

  .meal-plan-board .meal-plan-week-day-col,
  .meal-plan-board .meal-plan-week-cell {
    min-width: 8.5rem;
    scroll-snap-align: start;
  }
}

/* Keep the week selector inside a phone: drop the wide label floor, tighten the
   inset, and allow the Today pill to wrap rather than overflow. */
@media (max-width: 575.98px) {
  .week-selector {
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-inline: var(--space-4);
  }

  .week-selector__label {
    min-width: 0;
  }
}

/* ============================================
   Diff View Styles (Phase 8)
============================================ */

.diff-table td.cell-majority  { background-color: rgba(255, 193, 7, 0.35) !important; }
.diff-table td.cell-conflict  { background-color: rgba(220, 53, 69, 0.30) !important; }

.diff-table {
  font-size: 0.875rem;
}

.diff-table th {
  position: sticky;
  top: 0;
  background: var(--culinari-surface);
  z-index: 2;
}

.diff-table td {
  vertical-align: middle;
}

.diff-table .model-cell {
  cursor: pointer;
}

.diff-table .model-cell:hover {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

.diff-table .merged-cell {
  cursor: pointer;
  min-width: 120px;
}

.diff-table .merged-cell:hover {
  outline: 2px dashed var(--bs-secondary);
  outline-offset: -2px;
}

.cell-flash--ok {
  background-color: rgba(25, 135, 84, 0.15);
  transition: background-color 300ms var(--ease-soft);
}

.cell-flash--err {
  background-color: rgba(220, 53, 69, 0.15);
  transition: background-color 300ms var(--ease-soft);
}

@media (prefers-reduced-motion: reduce) {
  .cell-flash--ok,
  .cell-flash--err {
    transition: none;
  }
}

.page-thumbnail-sidebar {
  max-width: 140px;
  position: sticky;
  top: 60px;
}

/* ============================================
   Search Recipe Frame Loading Spinner
============================================ */

turbo-frame#search_recipes {
  position: relative;
  display: block;
  min-height: 2rem;
}

turbo-frame#search_recipes[aria-busy="true"]::after {
  content: "";
  position: absolute;
  top: 0.75rem;
  left: 50%;
  margin-left: -0.75rem;
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid var(--culinari-hairline);
  border-top-color: var(--culinari-primary);
  border-radius: 50%;
  animation: search-frame-spin 0.6s linear infinite;
  display: block;
  z-index: 10;
}

@keyframes search-frame-spin {
  to { transform: rotate(360deg); }
}

/* Unit conversion quantity highlight. Tinted from --culinari-success
   to keep the flash warm and on-system instead of Bootstrap-green. */
@keyframes unit-converted-flash {
  0%   { background-color: rgba(25, 135, 84, 0.18); }
  100% { background-color: transparent; }
}

.unit-converted {
  animation: unit-converted-flash var(--dur-slow) var(--ease-soft);
}

/* Reduced-motion: the search spinner and the unit-conversion flash are
   movement/feedback, so they collapse to instant per the end-to-end pledge.
   aria-busy still conveys the loading state to assistive tech. */
@media (prefers-reduced-motion: reduce) {
  turbo-frame#search_recipes[aria-busy="true"]::after,
  .unit-converted {
    animation: none;
  }
}

/* Inline servings edit */
.inline-servings-display {
  cursor: pointer;
  display: inline-block;
  border-radius: var(--radius-sm);
}

.inline-servings-display:hover,
.inline-servings-display:focus-visible {
  color: var(--accent-text) !important;
  text-decoration: underline;
}

.inline-servings-display:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.inline-servings-input {
  width: 4rem;
  text-align: center;
  margin: 0 auto;
}

input.form-control.servings-input-narrow {
  width: 4rem;
  font-size: 0.75rem;
  text-align: center;
}

/* Cook-mode stepper buttons stay tappable with messy fingers — overrides Bootstrap btn-sm. */
.btn-stepper-icon {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  line-height: 1;
  border: 1px solid var(--culinari-hairline);
  background-color: transparent;
  color: var(--culinari-fg-muted);
  transition: background-color var(--dur-fast) var(--ease-soft),
              border-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.btn-stepper-icon:hover,
.btn-stepper-icon:focus-visible {
  background-color: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-text);
}

.btn-stepper-icon:disabled,
.btn-stepper-icon[disabled] {
  opacity: 0.45;
  background-color: transparent;
  border-color: var(--culinari-hairline);
  color: var(--culinari-muted);
}

/* Narrow numeric inputs in inline-edit forms (ingredient quantity, range max). */
.input-quantity {
  width: 6rem;
}

/* Servings number that toggles to an editable input on click. Used in both
   the static cook view and its turbo-stream replacement, so the affordance
   stays identical when re-rendered. */
.servings-display-input {
  display: inline-block;
  min-width: 2rem;
  text-align: center;
  cursor: pointer;
}

/* Reinforce the summary affordance — Safari's default cursor on <summary> is the
   text caret, which reads as "selectable" rather than "clickable". */
.summary-affordance {
  cursor: pointer;
}

/* ============================================
   Star Rating Widget Styles
============================================ */
.star-rating-star {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: var(--radius-md);
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.star-rating-star .bi {
  font-size: 1.25rem;
  transition: color var(--dur-fast) var(--ease-soft);
}

.star-rating-star:hover {
  background-color: var(--accent-tint);
}

.star-rating-star:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
  background-color: var(--accent-tint);
}

.star-rating-star:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.star-rating-star.star-clicked .bi {
  animation: star-flash var(--dur-base) var(--ease-soft);
}

@keyframes star-flash {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
  .star-rating-star,
  .star-rating-star .bi {
    transition: none;
  }
  .star-rating-star.star-clicked .bi {
    animation: none;
  }
}

.star-rating-average:not(.star-rating-average--compact) .bi {
  font-size: 1.25rem;
}

/* Row-card variant: caption-aligned ink, single icon + value + count.
   Keeps terracotta scarce for the show-page rating where it earns the airtime. */
.star-rating-average--compact {
  gap: 0.3rem;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--culinari-fg-muted);
}

.star-rating-average--compact .bi {
  font-size: 0.875rem;
  line-height: 1;
  color: var(--accent-text);
}

/* Compact trigger that opens the rating picker dropdown */
.rating-trigger {
  background: none;
  border: 0;
  padding: 0.15rem 0.4rem;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: inherit;
  border-radius: var(--radius-pill);
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.rating-trigger::after {
  display: none;
}

.rating-trigger:hover,
.rating-trigger[aria-expanded="true"] {
  background-color: var(--accent-tint);
}

.rating-trigger:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

.rating-trigger__cta {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--accent-text);
  font-weight: var(--fw-semibold);
}

.rating-trigger__you {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  font-size: 0.85rem;
  color: var(--fg-3);
  background-color: var(--accent-tint);
  border-radius: var(--radius-pill);
  padding: 0.05rem 0.55rem;
  font-weight: var(--fw-semibold);
  line-height: 1.4;
}

.rating-trigger__you i.bi.bi-star-fill {
  font-size: 0.85rem;
}

.rating-dropdown__menu {
  min-width: auto;
  border-color: var(--border-warm);
  box-shadow: var(--shadow-md);
}

.user-stars__caption {
  font-size: 0.75rem;
}

.star-rating-widget .remove-rating-btn {
  border: 0;
  background: none;
  color: var(--fg-3);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: var(--radius-md);
  transition: background-color var(--dur-fast) var(--ease-soft), color var(--dur-fast) var(--ease-soft);
}

.star-rating-widget .remove-rating-btn .bi {
  font-size: 1rem;
}

.star-rating-widget .remove-rating-btn:hover,
.star-rating-widget .remove-rating-btn:focus-visible {
  color: var(--bs-danger);
  background-color: rgba(var(--bs-danger-rgb, 220, 53, 69), 0.08);
  outline: 0;
}

.star-rating-widget .remove-rating-btn:focus-visible {
  outline: 2px solid var(--bs-danger);
  outline-offset: -2px;
}

.star-rating-widget .remove-rating-btn:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

@media (prefers-reduced-motion: reduce) {
  .rating-trigger,
  .star-rating-widget .remove-rating-btn {
    transition: none;
  }
}

/* ============================================
   Global Typography & Show Page Styles
============================================ */

body {
  font-family: 'Inter', sans-serif;
  /* Cream-First Rule: paper-warm baseline. Pure white is reserved for
     inputs and reading-critical content blocks. Defaulting cream gives
     Culinari its cookbook temperature instead of SaaS-default white. */
  background-color: var(--bg-cream);
  color: var(--culinari-fg);
}

h1, h2, h3, h4, h5, h6 {
  font-family: 'Nunito Sans', sans-serif;
}

/* ============================================
   Recipe show — header, actions, footer CTA
   "Recipe is the hero" — chrome shrinks, content is loud.
   Source attribution lives as an eyebrow above the title;
   secondary actions collapse into a single overflow menu.
============================================ */

.resource-header {
  padding-top: var(--space-4);
}

.recipe-source-eyebrow {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--culinari-muted);
}

.recipe-source-eyebrow__from {
  color: var(--culinari-muted);
}

.recipe-source-eyebrow__name {
  color: var(--accent-text);
  letter-spacing: var(--tracking-eyebrow);
}

.recipe-source-eyebrow__link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease-soft);
}

.recipe-source-eyebrow__link:hover,
.recipe-source-eyebrow__link:focus-visible {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
  outline: none;
}

.recipe-source-eyebrow__link:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-tint);
  border-radius: var(--radius-xs);
}

.resource-header__title-row {
  margin-bottom: var(--space-3);
}

.resource-header .resource-title {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: var(--fw-heavy);
  line-height: var(--lh-tight);
  color: var(--culinari-ink);
  letter-spacing: -0.01em;
  word-break: break-word;
}

@media (min-width: 768px) {
  .resource-header .resource-title {
    font-size: 2.75rem;
  }
}

@media (min-width: 1200px) {
  .resource-header .resource-title {
    font-size: 3.25rem;
  }
}

.resource-header__back {
  margin-top: 0.4rem;
}

.resource-actions {
  margin-top: 0.25rem;
  row-gap: var(--space-2);
}

/* Start cooking sits in the title row as the user's primary intent.
   On narrow screens the icon stays visible and the label wraps on a
   second line if the title is long. */
.resource-actions__cta {
  font-weight: var(--fw-semibold);
  padding: 0.55rem 1.1rem;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 575.98px) {
  .resource-actions__cta span {
    display: none;
  }
  .resource-actions__cta i {
    margin-inline-end: 0 !important;
  }
  .resource-actions__cta {
    width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
  }
}

.resource-actions .bookmark-container a {
  font-size: 20px;
  color: var(--culinari-fg);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-circle);
  transition: color var(--dur-fast) var(--ease-soft),
              background-color var(--dur-fast) var(--ease-soft);
}

.resource-actions .bookmark-container a:hover,
.resource-actions .bookmark-container a:focus-visible {
  color: var(--accent-text);
  background-color: var(--accent-tint);
  outline: none;
}

.resource-actions-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: transparent;
  border: none;
  border-radius: var(--radius-circle);
  color: var(--culinari-fg);
  font-size: 20px;
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-soft),
              background-color var(--dur-fast) var(--ease-soft);
}

.resource-actions-toggle.disabled,
.resource-actions-toggle:disabled {
  color: var(--culinari-silver);
  pointer-events: none;
  cursor: not-allowed;
}

.resource-actions-toggle:hover,
.resource-actions-toggle:focus-visible,
.resource-actions-toggle[aria-expanded="true"] {
  color: var(--accent-text);
  background-color: var(--accent-tint);
  outline: none;
}

/* Unified "add" ghost-button affordance for inline secondary actions on
   the recipe show page (add-to-list, add-note, servings stepper +/-,
   read-book CTA on the book card). Replaces three separate Bootstrap
   btn-outline-secondary instances so every secondary action speaks the
   same terracotta-ghost vocabulary. The hit area extends to 44×44 via
   pseudo-element while the visible glyph stays compact for dense rows. */
.btn.recipe-action-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 36px;
  padding: 6px 14px;
  position: relative;
  background-color: transparent;
  color: var(--accent-text);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.btn.recipe-action-add::before {
  content: "";
  position: absolute;
  inset: -4px;
}

.btn.recipe-action-add:hover,
.btn.recipe-action-add:focus-visible {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.btn.recipe-action-add--circle {
  width: 36px;
  height: 36px;
  min-width: 36px;
  padding: 0;
  border-radius: var(--radius-circle);
}

.resource-meta-row {
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-3);
}

.resource-meta-row__item {
  display: inline-flex;
  align-items: center;
}

.resource-meta-row small {
  font-size: var(--fs-12);
  color: var(--culinari-fg);
}

.resource-meta-row i.bi {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

/* The rule above styles the row's decorative meta icons (e.g. the clock). It must
   not leak into buttons sitting in the same row: a button's glyph takes the
   button's own colour and size (e.g. white on the terracotta Start timer CTA),
   not muted-grey at 14px. */
.resource-meta-row .btn i.bi {
  color: inherit;
  font-size: inherit;
}

/* Autosave status pill — persistent indicator next to the recipe status badge.
   Reflects the current save state (saving / saved / error) via [data-state],
   never auto-hides on success so the user always knows their work is safe.
   The resting "saved" state is type-only (no fill) so it doesn't compete with
   the published-status badge sitting beside it; saving and error keep fills. */
.autosave-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 7rem;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--fs-12);
  font-weight: 500;
  line-height: 1;
  border: 1px solid transparent;
  transition: background-color var(--dur-base) var(--ease-soft),
              color var(--dur-base) var(--ease-soft),
              border-color var(--dur-base) var(--ease-soft);
}

.autosave-pill[data-state="idle"] {
  display: none;
}

.autosave-pill__icon {
  font-size: var(--fs-14);
  line-height: 1;
}

.autosave-pill[data-state="saving"] {
  background: var(--culinari-primary-wash);
  color: var(--culinari-muted);
  border-color: var(--culinari-line-warm);
}

.autosave-pill[data-state="saving"] .autosave-pill__icon {
  animation: autosave-spin 900ms linear infinite;
}

/* Saved is the resting state next to the status badge; recede so the two
   pills don't both shout. Type + icon carry the meaning; no fill. */
.autosave-pill[data-state="saved"] {
  background: transparent;
  color: var(--culinari-success);
  border-color: transparent;
}

.autosave-pill[data-state="error"] {
  background: rgba(220, 53, 69, 0.10);
  color: var(--culinari-danger);
  border-color: rgba(220, 53, 69, 0.25);
  animation: autosave-pulse var(--dur-slow) var(--ease-soft) 1;
}

@keyframes autosave-spin {
  to { transform: rotate(360deg); }
}

@keyframes autosave-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .autosave-pill[data-state="saving"] .autosave-pill__icon,
  .autosave-pill[data-state="error"] {
    animation: none;
  }
}

/* Footer CTA: Start cooking is the primary action of the entire page,
   given dignity by being the only large affordance below the content. */
.recipe-footer-cta {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-3);
  align-items: stretch;
  padding-top: var(--space-6);
  border-top: 1px solid var(--border-warm);
}

@media (min-width: 576px) {
  .recipe-footer-cta {
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-5);
  }
}

.recipe-footer-cta__primary {
  padding: 0.875rem 2rem;
  min-width: 220px;
  font-weight: var(--fw-semibold);
}

.resource-author-avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-circle);
  background: var(--culinari-primary);
  color: var(--culinari-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-12);
  flex-shrink: 0;
}

.resource-author-avatar-img {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

/* Metadata component-list: vertical rows */
.component-list {
  margin: 0;
  padding: 0;
}

.component-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-hairline);
}

.component-list li:first-child {
  border-top: 1px solid var(--border-hairline);
}

.component-list li small {
  font-size: var(--fs-12);
  color: var(--culinari-muted);
  text-transform: uppercase;
  line-height: var(--lh-snug);
}

.component-list li span,
.component-list li > div {
  font-size: var(--fs-14);
  color: var(--culinari-ink);
  font-weight: var(--fw-semibold);
}

/* Description-list variant (book detail metadata): label/value rows as a
   two-column grid. Scoped to dl.component-list so the ul/li recipe usages
   above are untouched. */
dl.component-list {
  margin: 0;
}

dl.component-list > div {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  padding: 10px 0;
  border-bottom: 1px solid var(--border-hairline);
}

dl.component-list > div:first-child {
  border-top: 1px solid var(--border-hairline);
}

dl.component-list dt {
  margin: 0;
  font-size: var(--fs-12);
  color: var(--culinari-muted);
  text-transform: uppercase;
  line-height: var(--lh-snug);
  font-weight: var(--fw-regular);
}

dl.component-list dd {
  margin: 0;
  font-size: var(--fs-14);
  color: var(--culinari-ink);
  font-weight: var(--fw-semibold);
  text-align: right;
}

/* Book cover on the detail page: flat at rest (hairline, no gray Bootstrap
   shadow); warm lift is reserved for interactive cards. */
.book-cover-image {
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-warm);
}

/* Labeled variant of the save toggles, used in the book detail header where
   there is room for text. Index/row cards keep the icon-only base. */
.bookmark-icon--labeled,
.library-icon--labeled {
  width: auto;
  gap: var(--space-2);
  padding-inline: var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
}

/* Below md the label is hidden (d-none d-md-inline); collapse to a clean
   44x44 icon target so phones (portrait or landscape) never crowd the title. */
@media (max-width: 767.98px) {
  .bookmark-icon--labeled,
  .library-icon--labeled {
    width: 44px;
    padding-inline: 0;
    border-radius: var(--radius-pill);
  }
}

/* Section spacing. Reading surfaces cap body measure at 70ch so a cook
   glancing back to find a line never has to skim 100+ char lines. The
   65–75ch band from DESIGN.md is the project's named reading rule. */
.recipe-description {
  max-width: 70ch;
  margin-bottom: 1.5rem;
}

.resource-metadata {
  margin-top: 1rem;
  margin-bottom: 1.5rem;
}

/* Circular ingredient checkboxes */
.recipe-checkbox.form-check {
  padding-left: 40px;
}

.recipe-checkbox .form-check-input {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid var(--culinari-ink);
  margin-top: 0;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-color: transparent;
}

.recipe-checkbox .form-check-input:checked {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNC4wMDEiIHZpZXdCb3g9IjAgMCAyNCAyNC4wMDEiPg0KICA8ZyBpZD0iRm9ybV9FbGVtZW50c19DaGVja2JveF9EZWZhdWx0IiBkYXRhLW5hbWU9IkZvcm0gRWxlbWVudHMvQ2hlY2tib3gvRGVmYXVsdCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCkiPg0KICAgIDxwYXRoIGlkPSJGb3JtX0VsZW1lbnRzX0NoZWNrYm94X0NoZWNrZWQiIGRhdGEtbmFtZT0iRm9ybSBFbGVtZW50cy9DaGVja2JveC9DaGVja2VkIiBkPSJNMS45MzMsMTguNTMyQTEyLDEyLDAsMCwxLDE2Ljg4NCwxLjAzOSwxLjA5MSwxLjA5MSwwLDEsMSwxNiwzLjAzMmE5LjgxOCw5LjgxOCwwLDEsMCw1LjgxOCw5LjI3bDAtLjNWMTFhMS4wOTEsMS4wOTEsMCwwLDEsMi4xNzUtLjEyOEwyNCwxMXYxQTEyLDEyLDAsMCwxLDEuOTMzLDE4LjUzMlptOS40LTMuNDctLjEtLjA5MUw3Ljk1NiwxMS43QTEuMDkxLDEuMDkxLDAsMCwxLDkuNCwxMC4wNjVsLjEuMDkxLDIuNSwyLjVMMjIuMTM3LDIuNTA4YTEuMDkxLDEuMDkxLDAsMCwxLDEuNjM0LDEuNDM5bC0uMDkxLjFMMTIuNzcyLDE0Ljk3MWExLjA5MSwxLjA5MSwwLDAsMS0xLjQ0LjA5MVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMCkiIGZpbGw9IiNEOTc3NTciLz4NCiAgPC9nPg0KPC9zdmc+DQo=");
  background-color: transparent;
  border-color: var(--culinari-ink);
  background-size: 24px 24px;
  background-position: center;
  background-repeat: no-repeat;
}

.recipe-checkbox .form-check-label {
  line-height: 24px;
  cursor: pointer;
}

/* Bold ingredient section headers */
.checklist strong {
  font-weight: 600;
  font-size: 18px;
  display: block;
  margin: 10px 0 5px;
}

/* Cookbook-printed step numerals. Replaces a SaaS-disc badge with a
   display-weight terracotta numeral that announces the step on its own
   typographic weight, the way a printed cookbook does. The list is
   capped at 70ch so reading-while-cooking never loses its place on a
   wide viewport (DESIGN.md "65–75ch Rule"). */
ul.instruction-list {
  max-width: 70ch;
  padding-left: 0;
}

ul.instruction-list li {
  position: relative;
  padding-left: 2.75rem;
  margin-bottom: 1.25rem;
}

/* Direct child only: the step text now contains its own spans
   (.step-ingredient / .step-amount) that must not catch the number styling. */
ul.instruction-list li > span:first-child {
  position: absolute;
  left: 0;
  top: -0.05em;
  width: auto;
  height: auto;
  background: transparent;
  font-family: var(--font-display);
  font-size: var(--fs-20);
  font-weight: var(--fw-heavy);
  color: var(--accent-text);
  line-height: var(--lh-snug);
  letter-spacing: 0;
}

/* Per-step total-time sub-line (shared by the public step list and the
   manage/edit row via recipes/steps/_duration). Block-level so it always
   sits on its own line under the step text in both surfaces; muted slate
   reads clearly at arm's length without competing with the instruction. */
.step-duration {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.25rem;
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
  font-variant-numeric: tabular-nums;
}

.step-duration .bi {
  font-size: 0.9em;
}

/* Inlined ingredient amounts (#444): the quantity rides along inside the step
   text so the cook never bounces back to the ingredient list mid-step. The
   mention stays body-weight; the amount is a quiet muted aside that must not
   compete with the instruction itself. */
.step-ingredient {
  font-weight: 500;
}

.step-amount {
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* On-screen opt-out (inline_amounts Stimulus controller): the spans stay in
   the DOM so servings rescaling keeps them current while hidden. */
.inline-amounts-off .step-amount {
  display: none;
}

/* Cook-mode steps are tap-to-log. Give them a real interactive affordance so a
   cook glancing from the stove can tell they're tappable and which one is
   current. Full background tint, never a side-stripe (banned accent). The tap
   affordance (pointer + hover tint) is gated behind .is-cooking: before Start
   the steps are inert, so they read as a plain reference list. */
.cook-step-clickable {
  display: flex;
  align-items: baseline;
  column-gap: 0.625rem;
  border-radius: var(--radius-md);
  min-height: 44px;
  border: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease-soft),
              border-color var(--dur-fast) var(--ease-soft),
              opacity var(--dur-fast) var(--ease-soft);
}

/* Once cooking starts, steps are tap-to-log. A persistent hairline (not a
   hover-only tint, which never fires on touch) makes each step read as its own
   tap target at arm's length. Full border, never a side-stripe accent. */
.is-cooking .cook-step-clickable {
  cursor: pointer;
  border-color: var(--culinari-hairline);
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  padding-right: 0.75rem;
}

/* The leading number rides in flow as a flex item, sharing the text's first
   baseline at any step size. (The absolute number from .instruction-list drifts
   above the line once the active step grows to display size.) Higher specificity
   than the .instruction-list base so only cook steps switch to in-flow. */
ul.instruction-list li.cook-step-clickable {
  padding-left: 0;
}

ul.instruction-list li.cook-step-clickable > span:first-child {
  position: static;
  flex: none;
}

ul.instruction-list li.cook-step-clickable > div {
  flex: 1 1 auto;
  min-width: 0;
}

/* Once the card border appears, the in-flow number needs the card's own left
   padding to clear it. */
.is-cooking ul.instruction-list li.cook-step-clickable {
  padding-left: 0.75rem;
}

.is-cooking .cook-step-clickable:hover {
  background-color: var(--accent-tint);
}

/* While paused, steps go inert: the header's "Paused" marker is the single
   source of truth, so the list drops its tap affordance until the cook resumes. */
.is-cooking.is-paused .cook-step-clickable {
  cursor: default;
}

.is-cooking.is-paused .cook-step-clickable:hover {
  background-color: transparent;
}

.cook-step-clickable:focus-visible {
  outline: none;
  background-color: var(--accent-tint);
  box-shadow: 0 0 0 4px var(--accent-tint);
}

/* The current (first not-yet-completed) step keeps the tint and a terracotta
   edge so it stays found without a hover, the way a thumb holds a place in a
   cookbook. */
.is-cooking .cook-step-clickable.is-active {
  background-color: var(--accent-tint);
  border-color: var(--accent);
}

/* The current step is the one thing a cook reads from across the kitchen, so it
   alone grows to display size; done and upcoming steps stay at body size as quiet
   reference. Pairs with the controller scrolling it into a comfortable band. */
.is-cooking .cook-step-clickable.is-active .cook-step__text {
  font-size: var(--fs-20);
  line-height: var(--lh-body);
}

/* Done is a state class, not an inline strikethrough: legible at distance and
   restored on resume. Shared shape with the ingredient row below. */
.cook-step-clickable.is-done {
  text-decoration: line-through;
  color: var(--culinari-muted);
  opacity: 0.65;
}

/* A one-line nudge that steps are tappable, shown only while cooking. */
.cook-steps-hint {
  display: none;
}

.is-cooking .cook-steps-hint {
  display: block;
}

/* Its mirror: a one-line model of the flow shown before Start (the moment a
   first-timer needs it), then swapped out for the tap hint once cooking begins. */
.cook-prestart-hint {
  display: block;
}

.is-cooking .cook-prestart-hint {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .cook-step-clickable {
    transition: none;
  }
}

/* Ingredient "done" — struck and muted, but the terracotta check stays bright. */
.ingredient-item.is-done .ingredient-item__label {
  text-decoration: line-through;
  color: var(--culinari-muted);
}

/* Live elapsed clock: a clear header figure with tabular numerals, but pitched
   below the active step's display size so the instruction the cook squints at
   stays the loudest thing on screen (static prep/cook/total stay quieter still). */
.cook-elapsed__icon {
  color: var(--culinari-muted);
}

.cook-elapsed__label {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

.cook-elapsed__value {
  font-family: var(--font-display);
  font-size: var(--fs-20);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--culinari-ink);
  font-variant-numeric: tabular-nums;
}

/* The header timer toggles parts (clock, paused marker, Start/Pause/Resume/Reset)
   with the [hidden] attribute. Bootstrap's d-flex / .btn carry !important display,
   which would otherwise win, so enforce hidden inside the timer scope. */
.cook-timer [hidden] {
  display: none !important;
}

/* Paused marker beside the frozen clock: a muted status (icon + word, never
   hue-only) so a glance tells running from paused without reading the digits. */
.cook-elapsed__paused {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
}

/* Keep the pause glyph the same slate as its word (the meta-row icon rule would
   otherwise leave it a lighter grey). */
.cook-elapsed__paused i.bi {
  color: inherit;
}

/* Reset stays a quiet ghost: destructive intent lives in the confirm, not in a
   loud red button. The rest label is slate (clears 4.5:1 on cream); it warms to
   danger-deep on hover/focus/press. The active vars carry that warmth through the
   tap, since a bare .btn's :active would otherwise revert to ink-on-transparent. */
.cook-timer__reset {
  background: transparent;
  border: 1px solid transparent;
  color: var(--culinari-fg-muted);
  --bs-btn-active-color: var(--culinari-danger-deep);
  --bs-btn-active-bg: rgba(220, 53, 69, 0.08);
  --bs-btn-active-border-color: transparent;
}

.cook-timer__reset:hover,
.cook-timer__reset:focus-visible {
  color: var(--culinari-danger-deep);
  background-color: rgba(220, 53, 69, 0.08);
  outline: none;
}

.cook-timer__reset:focus-visible {
  box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.12);
}

/* Step-progress count appended to the Finish button, e.g. "(3 of 8 done)". */
.cook-finish__count {
  font-weight: var(--fw-regular);
  opacity: 0.85;
}

/* Cook-mode finish summary: a calm peak-end note that replaces the finish CTA
   in place. Type-led, terracotta only on the elapsed figure. */
.cook-summary {
  text-align: center;
}

.cook-summary__eyebrow {
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--accent-text);
  margin-bottom: var(--space-2);
}

.cook-summary__total {
  font-family: var(--font-display);
  font-size: var(--fs-20);
  color: var(--culinari-ink);
  margin-bottom: var(--space-1);
}

.cook-summary__detail {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  margin-bottom: var(--space-2);
}

/* Cook mode: prep/cook/total + servings is reference data, not the task. Lay it
   out as one quiet inline row instead of four stacked full-width rows, so the
   first step sits near the top of the content rather than below a tall band.
   Scoped to .cook-metadata so the shared .component-list usages are untouched. */
.cook-metadata .component-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-6);
}

.cook-metadata .component-list li,
.cook-metadata .component-list li:first-child {
  border: 0;
  padding: 0;
  justify-content: flex-start;
  gap: var(--space-2);
}

/* Ingredients are gathered up front, then rarely revisited mid-cook. On phones
   the panel is a real disclosure (folded once cooking starts by the controller,
   re-openable to re-check a quantity); from md up it's a plain always-open panel
   beside the steps, never collapsible. The <details> renders [open], so md+ just
   neutralises the summary's toggle affordance to keep it open. */
.cook-ingredients > summary {
  cursor: pointer;
  margin-bottom: var(--space-3);
}

.cook-ingredients__count {
  margin-left: var(--space-1);
  font-weight: var(--fw-regular);
}

.cook-ingredients__count::before { content: "("; }
.cook-ingredients__count::after { content: ")"; }

@media (min-width: 768px) {
  .cook-ingredients > summary {
    cursor: default;
    pointer-events: none;
    list-style: none;
  }

  .cook-ingredients > summary::-webkit-details-marker { display: none; }
  .cook-ingredients > summary::marker { content: ""; }
}

/* ============================================
   Notes & Lists Shared Styles
============================================ */

/* List pills */
.recipe-list-pill {
  display: inline-block;
  padding: 6px 14px;
  background-color: var(--bg-soft);
  color: var(--culinari-fg);
  border-radius: var(--radius-pill);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.recipe-list-pill:hover,
.recipe-list-pill:focus-visible {
  background-color: var(--accent);
  color: var(--culinari-surface);
}

/* ============================================
   Book-sourced recipe card
============================================ */

/* Book-sourced recipe card. Reconciled to documented tokens so the
   recipe show page no longer carries a third warm-cream temperature
   fighting cream-page / cream-flash. */
.recipe-book-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  background-color: var(--bg-cream);
}

.recipe-book-card__cover {
  flex-shrink: 0;
  display: block;
  width: 72px;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 18px rgba(168, 68, 48, 0.18);
  background-color: var(--culinari-surface);
}

.recipe-book-card__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.recipe-book-card__cover-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bg-wash);
  color: var(--accent-text);
  font-size: 26px;
}

.recipe-book-card__body {
  flex: 1;
  min-width: 0;
}

.recipe-book-card__title {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: var(--fw-bold);
  color: var(--culinari-ink);
  text-decoration: none;
  line-height: 1.25;
}

.recipe-book-card__title:hover,
.recipe-book-card__title:focus-visible {
  color: var(--accent-text);
}

.recipe-book-card__authors {
  font-size: var(--fs-13);
  color: var(--culinari-fg-muted);
  margin-top: 2px;
}

.recipe-book-card__message {
  font-size: var(--fs-13);
  color: var(--culinari-fg);
  margin-top: 8px;
  line-height: 1.4;
}

.recipe-book-card__page {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  padding: 3px 12px;
  background-color: var(--culinari-surface);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg);
}

.recipe-book-card__page i {
  color: var(--accent-text);
  font-size: 11px;
}

.recipe-book-card__action {
  flex-shrink: 0;
  white-space: nowrap;
}

@media (max-width: 575.98px) {
  .recipe-book-card {
    flex-wrap: wrap;
  }
  .recipe-book-card__action {
    width: 100%;
    text-align: center;
  }
}

/* Note items */
.note-item {
  padding: 16px 0;
  border-bottom: 1px solid var(--border-line);
}

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

.note-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.note-author {
  display: flex;
  align-items: center;
  gap: 10px;
}

.note-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--accent);
  color: var(--culinari-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-14);
  flex-shrink: 0;
}

.note-meta {
  display: flex;
  flex-direction: column;
}

.note-meta strong {
  font-size: var(--fs-14);
  line-height: var(--lh-snug);
}

.note-meta time {
  font-size: var(--fs-12);
  color: var(--culinari-muted);
}

.note-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

.note-actions > i {
  font-size: var(--fs-14);
  margin-right: 4px;
}

.note-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background-color: transparent;
  color: var(--culinari-muted);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
  padding: 0;
  text-decoration: none;
  font-size: var(--fs-14);
}

.note-action-btn:hover,
.note-action-btn:focus-visible {
  background-color: var(--border-line);
  color: var(--culinari-fg);
}

.note-action-btn.note-action-delete:hover,
.note-action-btn.note-action-delete:focus-visible {
  background-color: rgba(220, 53, 69, 0.08);
  color: var(--culinari-danger);
}

/* Note bodies are reading surfaces too — cap at 70ch like recipe
   description and instructions (DESIGN.md "65–75ch Rule"). */
.note-content {
  max-width: 70ch;
  padding-left: 42px;
}

.note-content p {
  margin-bottom: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--culinari-fg);
}

/* Note form */
.note-form-compact {
  padding-bottom: 16px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--culinari-line);
}

.note-textarea {
  resize: none;
  font-size: 14px;
}

.note-form-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
}

/* ============================================
   Index Layout Styles
============================================ */

/* ----------------------------------------
   Recipe Row Card
---------------------------------------- */

.resource-row-card {
  background: var(--culinari-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-soft),
              box-shadow var(--dur-base) var(--ease-soft),
              border-color var(--dur-base) var(--ease-soft);
}

.resource-row-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--border-warm);
}

.resource-row-thumb {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: var(--radius-md);
}

.resource-row-card__thumb .resource-row-thumb {
  display: block;
}

/* Placeholder div when no image */
div.resource-row-thumb {
  width: 80px;
  height: 80px;
  border-radius: var(--radius-md);
}

.resource-row-thumb--empty {
  background-color: var(--bg-soft);
  font-size: 1.5rem;
}

/* ----------------------------------------
   Book Row Card — cover only; outer card uses .resource-row-card
---------------------------------------- */

.book-row-card__cover {
  width: 60px;
  height: 90px;
  border-radius: var(--radius-md);
}

.book-row-card__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ----------------------------------------
   Responsive — mobile stacking
---------------------------------------- */

@media (max-width: 575.98px) {
  .resource-row-card {
    flex-wrap: wrap;
  }

  .resource-row-thumb,
  div.resource-row-thumb {
    width: 100%;
    height: 160px;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }
}

/* ============================================
   Main Navbar (Culinari Design System)
   Brand wordmark left, centered links,
   circular search button + avatar on right.
============================================ */

.culinari-header {
  padding: 14px 0;
}

.culinari-header .navbar-brand,
.culinari-header .navbar-brand:hover,
.culinari-header .navbar-brand:focus {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 22px;
  line-height: 1;
  color: var(--culinari-ink);
}

.culinari-header .navbar-brand span {
  font: inherit;
  color: inherit;
}

.culinari-header .navbar-nav {
  gap: var(--space-2);
  align-items: center;
}

.culinari-header .nav-link {
  padding: 6px 12px;
  font-size: var(--fs-15);
  color: var(--culinari-fg);
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}

.culinari-header .nav-link:hover,
.culinari-header .nav-link:focus-visible {
  color: var(--accent-text);
}

.culinari-header .nav-link.active,
.culinari-header .nav-link.show {
  color: var(--accent-text);
  font-weight: var(--fw-semibold);
}

.culinari-header .culinari-header-links {
  gap: var(--space-3);
}

/* User avatar (circular 44px touch target, terracotta).
   The visible disk is 32px via inset positioning of the inner glyph;
   the outer link is 44x44 to clear WCAG 2.5.5 minimum target size. */
.nav-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-circle);
  background: var(--culinari-primary);
  color: var(--culinari-surface);
  text-decoration: none;
  overflow: hidden;
  transition: box-shadow var(--dur-fast) var(--ease-standard);
}

.nav-avatar:hover,
.nav-avatar:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-tint);
  color: var(--culinari-surface);
}

.nav-avatar::after {
  display: none;
}

.nav-avatar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.nav-avatar__initial {
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-13);
  line-height: 1;
  text-transform: uppercase;
}

/* ============================================
   Navbar locale switcher (in user dropdown)
   — restores the one-click EN/PT toggle PRODUCT.md
   describes as "a daily reality, not an edge case"
   without re-adding navbar weight. The two items
   sit under a Language header at the bottom of
   the avatar dropdown. Active locale is marked
   with bi-check2 + terracotta-deep text so the
   state survives color blindness (Hue-Plus-Mark).
============================================ */

.navbar-locale-header {
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--culinari-muted);
  margin: 0;
  padding: var(--space-2) var(--space-4);
}

.navbar-locale-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* Override Bootstrap's .dropdown-item.active (which would paint the
   row in --bs-primary blue) so the active locale reads in the brand
   vocabulary. Background stays neutral — the bi-check2 + terracotta
   text carry the active signal. */
.dropdown-menu .navbar-locale-item.active,
.dropdown-menu .navbar-locale-item.active:hover,
.dropdown-menu .navbar-locale-item.active:focus {
  background: transparent;
  color: var(--accent-text);
  font-weight: var(--fw-semibold);
}

.navbar-locale-check {
  font-size: var(--fs-14);
  color: var(--accent-text);
  flex-shrink: 0;
}

/* ============================================
   Navbar Split Dropdown (Recipes / Books)
   — clickable label + caret that opens dropdown
============================================ */

.nav-split-dropdown {
  display: flex !important;
  flex-wrap: wrap;
  align-items: center;
  position: relative;
}

.nav-split-dropdown > .nav-link:first-child {
  padding-right: 0;
}

.nav-split-dropdown > .dropdown-toggle-split {
  padding-left: 0.25rem;
  padding-right: 0.25rem;
  color: var(--culinari-muted);
}

.nav-split-dropdown > .dropdown-toggle-split:hover,
.nav-split-dropdown > .dropdown-toggle-split:focus-visible {
  color: var(--accent-text);
}

.nav-split-dropdown > .dropdown-toggle-split::after {
  margin-left: 0;
}

/* ============================================
   Form Elements Restyle (Tastebite Design System)
   Reference: docs/html_templates/elements.html
============================================ */

.form-label {
  text-transform: uppercase;
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  font-weight: var(--fw-regular);
  margin-bottom: 0.5rem;
  letter-spacing: var(--tracking-label);
}

.form-control:not([type="file"]),
.form-select {
  border-radius: 0;
  border: none;
  border-bottom: 2px solid var(--border-hairline);
  font-size: var(--fs-14);
  color: var(--culinari-ink);
  font-weight: var(--fw-regular);
  box-shadow: none;
}

.form-control:not([type="file"]):focus,
.form-select:focus {
  box-shadow: 0 2px 0 0 var(--accent-tint);
  border-color: var(--culinari-primary);
  color: var(--culinari-ink);
  background-color: var(--culinari-surface);
}

.form-control:not([type="file"])::placeholder,
.form-select::placeholder {
  color: var(--culinari-placeholder);
}

.form-control:not([type="file"]):disabled,
.form-control:not([type="file"])[readonly],
.form-select:disabled,
.form-select[readonly] {
  background-color: var(--culinari-bg-soft);
  color: var(--culinari-silver);
}

input.form-control:not([type="file"]),
select.form-select {
  height: 48px;
}

/* Restore natural height for small/compact inputs */
input.form-control.form-control-sm,
select.form-select.form-select-sm {
  height: auto;
}

textarea.form-control {
  height: auto;
}

/* Recipe ingredient & step forms: match the 1rem display-row font size
   so the inline edit form doesn't shrink the text mid-page. */
#recipe_ingredients .form-control:not([type="file"]),
#recipe_ingredients .form-select,
#recipe_steps .form-control:not([type="file"]),
#recipe_steps .form-select {
  font-size: 1rem;
}

/* ============================================
   Meal-Plan Generate Form
============================================ */

.generate-form-card {
  border: 1px solid var(--border-hairline);
  box-shadow: none;
}

.generate-form__section {
  border: 0;
  margin: 0 0 1.5rem;
  padding: 0;
  min-width: 0;
}

.generate-form__section-title {
  text-transform: uppercase;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  color: var(--culinari-primary-deep);
  margin-bottom: 0.75rem;
}

.generate-form__advanced {
  border-top: 1px solid var(--border-hairline);
  margin-top: 1rem;
  padding-top: 1rem;
}

.generate-form__advanced > summary {
  cursor: pointer;
  margin-bottom: 0.75rem;
}

.generate-form__choices .form-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  margin: 0;
  padding-left: 0;
}

.generate-form__choices .form-check-input {
  width: 22px;
  height: 22px;
  margin: 0;
  float: none;
  flex-shrink: 0;
}

/* Bootstrap 5.2 hard-codes #0d6efd into the compiled .form-check-input:checked
   rule, so the --bs-primary override does not reach it. Repaint to terracotta. */
.generate-form__choices .form-check-input:checked {
  background-color: var(--culinari-primary);
  border-color: var(--culinari-primary);
}

.generate-form__choices .form-check-input:focus {
  border-color: var(--culinari-primary);
  box-shadow: 0 0 0 0.25rem var(--accent-tint);
}

.generate-form__choices .form-check-label {
  cursor: pointer;
  padding: 0.5rem 0;
}

/* ============================================
   Meal-Plan Image Import — Review Screen
============================================ */

/* Reference photo: sticky beside the grid on desktop. Below lg the columns
   stack and the inner div loses its scroll room, so the COLUMN itself sticks
   as a compressed strip — the cook can still check the source while editing
   day 3 on a phone. */
@media (min-width: 992px) {
  .import-review__photo {
    position: sticky;
    top: 8px;
  }
}

@media (max-width: 991.98px) {
  .import-review__photo-col {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--bg-cream);
    border-bottom: 1px solid var(--border-warm);
    padding-block: 8px;
    text-align: center;
  }

  .import-review__photo img {
    max-height: 130px;
    width: auto;
    max-width: 100%;
  }
}

/* Meal slots: no nested boxes inside the day card — warm-thread dividers and
   an uppercase label carry the structure (same shape as .generate-form__section). */
.import-review__slot {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.import-review__slot + .import-review__slot {
  border-top: 1px solid var(--border-warm);
  padding-top: 0.75rem;
}

.import-review__slot-legend {
  float: none;
  width: auto;
  text-transform: uppercase;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  color: var(--fg-3);
  margin-bottom: 0.5rem;
}

/* Matched-recipe chip: linked = terracotta tint (the recipe is in the cook's
   library); unlinked = muted AND struck through so "off" never rides on hue
   alone. white-space normal: PT titles overflow a phone column as a nowrap
   Bootstrap badge. */
.import-review__match {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25rem;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--accent-tint);
  color: var(--accent-text);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  text-align: start;
  white-space: normal;
}

.import-review__match--off {
  background: var(--bg-soft);
  color: var(--culinari-fg-muted);
  text-decoration: line-through;
}

/* Skip warnings are hue-plus-mark: always icon + text. Deep mustard clears
   AA on cream. */
.import-review__skip-note,
.import-review__skip-summary {
  color: var(--culinari-warning-deep);
}

/* Bootstrap 5.2 hard-codes #0d6efd into the compiled :checked rule (same
   repaint the generate form needs). */
.import-review .form-check-input:checked {
  background-color: var(--culinari-primary);
  border-color: var(--culinari-primary);
}

.import-review .form-check-input:focus {
  border-color: var(--culinari-primary);
  box-shadow: 0 0 0 0.25rem var(--accent-tint);
}

.import-review__include {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  min-width: 32px;
  margin: 0;
  padding: 0;
  cursor: pointer;
}

.import-review__include .form-check-input {
  float: none;
  margin: 0;
}

/* Touch ergonomics below lg: the review is phone-first work. Checkboxes get a
   44px hit area; inputs regain full height (-sm density is for desktop). */
@media (max-width: 991.98px) {
  .import-review__include {
    min-width: 44px;
    min-height: 44px;
    padding-top: 6px;
  }

  .import-review__unlink {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .import-review__include .form-check-input,
  .import-review__unlink .form-check-input {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
  }

  .import-review .form-control-sm,
  .import-review .form-select-sm {
    min-height: 44px;
    font-size: var(--fs-16);
    padding-top: 0.375rem;
    padding-bottom: 0.375rem;
  }
}

/* Extraction-wait skeleton: sketches the day grid that will replace it.
   Static bars under prefers-reduced-motion — the status text alone carries
   the progress meaning. */
.import-skeleton {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.import-skeleton__bar {
  height: 12px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--bg-soft) 25%, var(--border-line) 50%, var(--bg-soft) 75%);
  background-size: 200% 100%;
  animation: import-skeleton-shimmer 1.4s var(--ease-standard) infinite;
}

.import-skeleton__bar:nth-child(1) { width: 36%; }
.import-skeleton__bar:nth-child(2) { width: 92%; }
.import-skeleton__bar:nth-child(3) { width: 74%; }
.import-skeleton__bar:nth-child(4) { width: 83%; }

@keyframes import-skeleton-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

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

/* ============================================
   Navbar Search Field
============================================ */

/* Inline pill search field (Culinari Design System) */
.nav-search-field {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  width: 200px;
  padding: 10px 18px;
  margin: 0;
  background: var(--bg-soft);
  border: 0;
  border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}

.nav-search-field:focus-within {
  background: var(--culinari-surface);
  box-shadow: 0 0 0 1px var(--culinari-primary), 0 0 0 4px var(--accent-tint);
}

.nav-search-field__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--culinari-muted);
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  flex-shrink: 0;
  cursor: pointer;
}

.nav-search-field__icon i {
  font-size: 18px;
  line-height: 1;
  opacity: .6;
}

.nav-search-field:focus-within .nav-search-field__icon i {
  opacity: 1;
  color: var(--accent-text);
}

.nav-search-field__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 0;
  font-family: inherit;
  font-size: var(--fs-14);
  color: var(--culinari-fg);
  outline: none;
  box-shadow: none;
  height: auto;
}

.nav-search-field__input::placeholder {
  color: var(--culinari-muted);
}

/* Collapse to a circular icon-only disk on narrow viewports.
   44x44 minimum to clear WCAG 2.5.5 touch target size. The disk is a toggle:
   tapping it expands a usable full-width field over the header row (nav_search
   controller adds .is-expanded). The header nav is the positioning anchor. */
.culinari-header .navbar.has-header-inner {
  position: relative;
}

@media (max-width: 991.98px) {
  .nav-search-field {
    width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
    border-radius: var(--radius-circle);
  }

  /* The icon button fills the disk so the whole 44px is the tap target. */
  .nav-search-field__icon {
    width: 100%;
    height: 100%;
  }

  .nav-search-field__input {
    display: none;
  }

  /* Expanded: the field overlays the header row as a full-width search bar.
     Only opacity/transform animate (never width/position) so the brand curve
     stays compliant; reduced-motion drops the reveal below. */
  .nav-search-field.is-expanded {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    padding: 10px 18px;
    justify-content: flex-start;
    border-radius: var(--radius-pill);
    background: var(--culinari-surface);
    box-shadow: 0 0 0 1px var(--culinari-primary), 0 0 0 4px var(--accent-tint);
    z-index: 20;
    animation: nav-search-reveal var(--dur-base) var(--ease-soft);
  }

  .nav-search-field.is-expanded .nav-search-field__icon {
    width: auto;
    height: auto;
    color: var(--accent-text);
  }

  .nav-search-field.is-expanded .nav-search-field__input {
    display: block;
  }
}

@keyframes nav-search-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .nav-search-field.is-expanded {
    animation: none;
  }
}

.back-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--culinari-muted);
  text-decoration: none;
  font-size: 1.5rem;
  line-height: 1;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  margin-left: -0.5rem;
}

.back-link:hover,
.back-link:focus-visible {
  color: var(--culinari-ink);
  background-color: var(--bg-soft);
  border-radius: var(--radius-circle);
}

/* ============================================
   Landing Page — Cookbook Frontispiece

   The page reads like the front pages of a printed cookbook:
   - Hero: a recipe-page mock as the visual proof. Show, don't tell.
   - Pull-quote: an epigraph between hero and contents.
   - Contents: a numbered table-of-contents (NOT a 4-up tile grid).
   - Method: how it works, three steps.
   - Pricing teaser, final CTA, footer.
============================================ */

.landing-eyebrow {
  display: inline-block;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  color: var(--accent-text);
  margin-bottom: 14px;
}

.landing-section-head {
  max-width: 640px;
  margin: 0 auto;
}

.landing-section-title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 2rem;
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  margin-bottom: 0;
  letter-spacing: -0.01em;
}

@media (min-width: 768px) {
  .landing-section-title {
    font-size: 2.5rem;
  }
}

/* Hero */

.landing-hero {
  padding: 64px 0 48px;
  background-color: var(--bg-cream);
}

@media (min-width: 768px) {
  .landing-hero {
    padding: 96px 0 80px;
  }
}

.landing-hero__title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 2.25rem;
  line-height: var(--lh-tight);
  color: var(--culinari-ink);
  margin-bottom: 20px;
  letter-spacing: -0.015em;
}

@media (min-width: 768px) {
  .landing-hero__title {
    font-size: 3.25rem;
  }
}

@media (min-width: 1200px) {
  .landing-hero__title {
    font-size: 3.75rem;
  }
}

.landing-hero__subtitle {
  font-size: 1.1rem;
  line-height: var(--lh-body);
  color: var(--culinari-fg);
  max-width: 52ch;
  margin-bottom: 28px;
}

.landing-hero__ctas .btn-lg {
  padding: 12px 28px;
  font-weight: var(--fw-semibold);
}

/* Centered hero variant (waitlist signup). The shared subtitle caps its
   measure at 52ch with no auto margins — correct for the landing's left
   copy column, but it pins left under a centered heading. Re-center it here
   only, leaving the landing rule untouched. */
.landing-hero--centered .landing-hero__subtitle {
  margin-inline: auto;
}

/* Waitlist signup -------------------------------------------------------- */

/* Cap the form (not the whole panel) to a comfortable measure so the
   bottom-border inputs and full-width CTA stay anchored under the hero text
   instead of stretching the full half-column. */
.waitlist-signup {
  max-width: 26rem;
  margin-inline: auto;
}

.waitlist-form__note {
  margin-top: var(--space-3);
  margin-bottom: 0;
  font-size: var(--fs-13);
  line-height: 1.5;
  color: var(--culinari-muted);
  text-align: center;
}

/* Success confirmation — the peak moment of the flow. Modeled on
   .pricing-success, but herb-tinted so it separates from the cream hero,
   with a leading check icon so meaning never rests on hue alone
   (DESIGN.md Hue-Plus-Mark rule). Replaces the default Bootstrap alert. */
.waitlist-success {
  max-width: 30rem;
  margin-inline: auto;
  padding: var(--space-8);
  text-align: center;
  background: rgba(25, 135, 84, 0.08);
  border: 1px solid rgba(25, 135, 84, 0.30);
  border-radius: var(--radius-lg);
}

.waitlist-success:focus {
  outline: none;
}

.waitlist-success__icon {
  display: block;
  margin-bottom: var(--space-3);
  font-size: 2.5rem;
  line-height: 1;
  color: var(--culinari-success-deep);
}

.waitlist-success__title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  color: var(--fg-1);
  margin-bottom: var(--space-3);
}

.waitlist-success__body {
  max-width: 40ch;
  margin-inline: auto;
  margin-bottom: var(--space-4);
  color: var(--fg-2);
}

/* Recipe-page mock — the hero "art" is the product itself. */

.landing-hero__art {
  position: relative;
}

.landing-recipe-mock {
  background-color: var(--culinari-surface);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-xl);
  padding: 40px 36px;
  box-shadow: var(--shadow-xl);
  max-width: 480px;
  margin: 0 auto;
  font-family: var(--font-sans);
  position: relative;
}

/* A subtle "page" left edge — like a book spine. */
.landing-recipe-mock::before {
  content: "";
  position: absolute;
  inset: 8px auto 8px 0;
  width: 1px;
  background-color: var(--border-warm);
}

.landing-recipe-mock__head {
  margin-bottom: 28px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border-warm);
}

.landing-recipe-mock__eyebrow {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 12px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--culinari-muted);
}

.landing-recipe-mock__eyebrow-name {
  color: var(--accent-text);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-13);
  font-weight: var(--fw-regular);
}

.landing-recipe-mock__title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 1.875rem;
  line-height: var(--lh-tight);
  color: var(--culinari-ink);
  margin: 0 0 10px;
  letter-spacing: -0.01em;
}

.landing-recipe-mock__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: var(--fs-13);
  color: var(--culinari-muted);
}

.landing-recipe-mock__meta-dot {
  width: 3px;
  height: 3px;
  background-color: var(--culinari-silver);
  border-radius: var(--radius-circle);
}

.landing-recipe-mock__columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: 28px;
}

.landing-recipe-mock__heading {
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--culinari-muted);
  margin: 0 0 12px;
}

.landing-recipe-mock__ingredients,
.landing-recipe-mock__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--fs-14);
  line-height: 1.55;
  color: var(--culinari-ink);
}

.landing-recipe-mock__ingredients li {
  padding: 6px 0;
  border-bottom: 1px solid var(--border-warm);
}

.landing-recipe-mock__ingredients li:last-child {
  border-bottom: 0;
}

.landing-recipe-mock__steps li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 6px 0;
}

.landing-recipe-mock__step-num {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-circle);
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  line-height: 1;
}

/* Cookbook epigraph: pull-quote between hero and contents. */

.landing-quote {
  padding: 64px 0 24px;
}

.landing-quote__inner {
  max-width: 56ch;
  margin: 0 auto;
  text-align: center;
  border-top: 1px solid var(--border-warm);
  border-bottom: 1px solid var(--border-warm);
  padding: 36px 16px;
}

.landing-quote__text {
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.4;
  font-weight: var(--fw-medium);
  font-style: italic;
  color: var(--culinari-ink);
  margin: 0 0 12px;
  letter-spacing: -0.005em;
}

@media (min-width: 768px) {
  .landing-quote__text {
    font-size: 1.875rem;
  }
}

.landing-quote__cite {
  display: block;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--culinari-muted);
}

/* Table of contents — replaces the 4-up icon-tile grid. */

.landing-toc {
  padding: 48px 0 72px;
}

.landing-toc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: toc;
}

.landing-toc__entry {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 24px;
  align-items: baseline;
  padding: 22px 0;
  border-bottom: 1px solid var(--border-warm);
}

.landing-toc__entry:first-child {
  border-top: 1px solid var(--border-warm);
}

.landing-toc__index {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 1.5rem;
  color: var(--accent-text);
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.landing-toc__copy {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.landing-toc__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.25rem;
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  margin: 0;
  letter-spacing: -0.005em;
}

.landing-toc__body {
  color: var(--culinari-fg);
  font-size: 0.95rem;
  line-height: var(--lh-body);
  margin: 0;
  max-width: 60ch;
}

/* How it works */

.landing-how {
  padding: 72px 0;
  background-color: var(--bg-wash);
}

.landing-step {
  background: var(--culinari-surface);
  border-radius: var(--radius-lg);
  padding: 28px 24px;
  border: 1px solid var(--border-warm);
  position: relative;
}

.landing-step__number {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-circle);
  background: var(--culinari-primary);
  color: var(--culinari-surface);
  font-weight: var(--fw-bold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 16px;
}

.landing-step__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  color: var(--culinari-ink);
  margin-bottom: 8px;
}

.landing-step__body {
  color: var(--culinari-fg-muted);
  font-size: 0.95rem;
  line-height: var(--lh-body);
  margin-bottom: 0;
}

/* Pricing teaser — quieter, no decorative gradient */

.landing-pricing-teaser {
  padding: 56px 0;
}

.landing-pricing-teaser__inner {
  background-color: var(--bg-cream);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-xl);
  padding: 48px 32px;
  text-align: center;
  box-shadow: var(--shadow-sm);
}

.landing-pricing-teaser__subtitle {
  font-size: 1.05rem;
  line-height: var(--lh-body);
  color: var(--culinari-fg);
  max-width: 60ch;
  margin: 14px auto 24px;
}

.landing-pricing-teaser .btn-lg {
  font-weight: var(--fw-semibold);
  padding: 12px 32px;
}

/* Final CTA */

.landing-final-cta {
  padding: 80px 0;
  text-align: center;
}

.landing-final-cta__body {
  color: var(--culinari-fg-muted);
  font-size: 1.1rem;
  line-height: 1.6;
  max-width: 540px;
  margin: 16px auto 28px;
}

/* Footer */

.landing-footer {
  padding: 24px 0 40px;
  border-top: 1px solid var(--culinari-line-warm);
}

.landing-footer__brand {
  font-family: 'Nunito Sans', sans-serif;
  font-weight: 800;
  color: var(--culinari-ink);
}

.landing-footer__links {
  font-size: 0.95rem;
}

.landing-footer__link {
  color: var(--culinari-fg-muted);
  text-decoration: none;
}

.landing-footer__link:hover,
.landing-footer__link:focus {
  color: var(--culinari-primary);
  text-decoration: underline;
}

/* ============================================
   Legal / static content pages (Privacy, Terms)
   A single, readable measure of long-form prose.
============================================ */

.legal-page {
  max-width: 68ch;
  margin: 0 auto;
  padding: 56px 0 72px;
  color: var(--culinari-fg);
  line-height: 1.7;
}

.legal-page__title {
  font-family: 'Nunito Sans', sans-serif;
  font-weight: 800;
  color: var(--culinari-ink);
  margin-bottom: 4px;
}

.legal-page__meta {
  color: var(--culinari-fg-muted);
  font-size: 0.95rem;
  margin-bottom: 24px;
}

.legal-page h2 {
  font-family: 'Nunito Sans', sans-serif;
  font-weight: 700;
  font-size: 1.3rem;
  color: var(--culinari-ink);
  margin-top: 36px;
  margin-bottom: 12px;
}

.legal-page ul {
  padding-left: 1.25rem;
}

.legal-page li {
  margin-bottom: 8px;
}

.legal-page a {
  color: var(--culinari-primary);
}

.legal-page a:hover,
.legal-page a:focus {
  color: var(--culinari-primary-hover);
}

/* Responsive tweaks */

@media (max-width: 767.98px) {
  .landing-hero {
    padding: 48px 0 32px;
  }

  .landing-toc,
  .landing-how {
    padding: 48px 0;
  }

  .landing-final-cta {
    padding: 56px 0;
  }

  .landing-pricing-teaser__inner {
    padding: 36px 24px;
    border-radius: var(--radius-lg);
  }
}

/* ============================================
   Admin Navbar
   Shown below the main navbar on admin pages.
   Distinct dark styling signals admin mode.
============================================ */

.admin-navbar {
  background: linear-gradient(180deg, var(--culinari-admin-1) 0%, var(--culinari-admin-2) 100%);
  color: #f9fafb;
  border-bottom: 3px solid var(--culinari-primary);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}

.admin-navbar .admin-navbar-brand {
  color: var(--culinari-primary);
  padding: 0.5rem 0;
  letter-spacing: 0.06em;
}

.admin-navbar .admin-navbar-brand .bi {
  font-size: 1.1rem;
}

.admin-navbar .nav-link {
  color: rgba(249, 250, 251, 0.85);
  font-weight: 500;
  padding: 0.5rem 0.9rem;
}

.admin-navbar .nav-link:hover,
.admin-navbar .nav-link:focus-visible,
.admin-navbar .nav-link.show {
  color: var(--culinari-surface);
}

.admin-navbar .navbar-toggler {
  border-color: rgba(249, 250, 251, 0.3);
  padding: 0.25rem 0.5rem;
}

.admin-navbar .admin-navbar-exit {
  color: rgba(249, 250, 251, 0.7);
  font-size: 0.875rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

.admin-navbar .admin-navbar-exit:hover {
  color: var(--culinari-surface);
}

.admin-navbar .admin-navbar-icon {
  color: rgba(249, 250, 251, 0.7);
  font-size: 1rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

.admin-navbar .admin-navbar-icon:hover,
.admin-navbar .admin-navbar-icon:focus-visible {
  color: var(--culinari-surface);
}

.admin-layout .culinari-header {
  display: none;
}

/* ============================================
   Shopping List
============================================ */

.shopping-list-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

.shopping-list-meta i.bi {
  color: var(--accent);
  font-size: var(--fs-16);
}

.shopping-list-meta__sep {
  color: var(--culinari-silver);
}

.shopping-list-card {
  background: var(--bg-card);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

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

.shopping-list__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-line);
  transition: background-color var(--dur-fast) var(--ease-standard);
}

.shopping-list__row:last-child {
  border-bottom: 0;
}

.shopping-list__row:hover {
  background-color: var(--bg-wash);
}

.shopping-list__item {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: break-word;
  font-size: var(--fs-15);
  font-weight: var(--fw-semibold);
  color: var(--fg-1);
}

.shopping-list__quantity {
  flex-shrink: 0;
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.shopping-list-empty {
  padding: var(--space-12) var(--space-5);
  text-align: center;
  color: var(--culinari-muted);
}

.shopping-list-empty i.bi {
  font-size: 2.5rem;
  color: var(--culinari-silver);
}

.shopping-list-empty__message {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-14);
}

.shopping-list-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  background: var(--bg-cream);
  border-top: 1px solid var(--border-warm);
}

.shopping-list-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.shopping-list-action i.bi {
  font-size: var(--fs-16);
}

@media (max-width: 575.98px) {
  .shopping-list-actions .shopping-list-action {
    flex: 1 1 calc(50% - var(--space-2));
    justify-content: center;
  }
}

/* Pill-toggle family — shared chrome for switch / tab / filter
   controls. Variant choice is by IA band, not by behavior:

   --ghost: no wrapper chrome, just a row of pills. Use in the
   page-header band — the controls that sit alongside (or under) the
   H1 and switch the dataset the page is reading (e.g. recipes index
   All / Mine / Saved). Letting the band breathe keeps visual weight
   on the title.

   --surface: capsule wrapper with cream background and hairline
   border. Use in the grid-control band — the controls that sit
   directly above the grid and tune how the same dataset is presented
   (sort, layout, in-place tabs, mode switches). The capsule groups
   them as a tool cluster and separates them from the content below.

   Inner buttons use .pill-toggle__btn; --icon-only tightens horizontal
   padding for icon-only controls. */

.pill-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  /* Allow long-label rows (Portuguese sort labels can run 20-40% longer
     than English) to wrap onto a second line rather than overflow. The
     --surface variant uses short labels and rarely triggers this. */
  flex-wrap: wrap;
  row-gap: 2px;
}

.pill-toggle--surface {
  padding: 4px;
  background: var(--bg-cream);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-pill, 999px);
}

/* The ghost variant intentionally has no wrapper chrome — base .pill-toggle
   already provides the flex row. This rule exists so the modifier is
   discoverable in the stylesheet, not to add styles. */
.pill-toggle--ghost { /* no chrome by design */ }

.pill-toggle__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* Wider side padding gives PT labels (typically 1.5× longer than EN)
     breathing room inside the pill. The active state's white fill stops
     rendering ragged when labels wrap or run long. */
  padding: var(--space-2) var(--space-5);
  font-family: inherit;
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--culinari-muted);
  text-decoration: none;
  background: transparent;
  border: 0;
  border-radius: var(--radius-pill, 999px);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}

.pill-toggle__btn--icon-only {
  padding: var(--space-2) var(--space-3);
}

.pill-toggle__btn:hover {
  color: var(--fg-1);
  text-decoration: none;
}

.pill-toggle__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.pill-toggle__btn:active {
  background: var(--bg-cream);
  color: var(--fg-1);
}

.pill-toggle__btn:disabled,
.pill-toggle__btn[aria-disabled="true"] {
  color: var(--culinari-silver);
  cursor: not-allowed;
  pointer-events: none;
}

.pill-toggle__btn.is-active {
  background: var(--bg-card);
  color: var(--fg-1);
  /* Inset hairline keeps the active pill legible whether it sits on
     cream-page, cream-wash (--surface variant), or a white sidebar card,
     where --shadow-sm alone reads as white-on-white. */
  box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--culinari-hairline);
}

.pill-toggle__btn.is-active:focus-visible {
  box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--culinari-hairline), 0 0 0 4px var(--accent-tint);
}

.pill-toggle__btn i.bi {
  font-size: var(--fs-15);
}

/* Touch floor: the toggle is the first interactive thing a phone user
   reaches at the top of an index/show page. Keep the chip compact on
   desktop, but enforce 44px on touch. */
@media (max-width: 575.98px) {
  .pill-toggle__btn {
    min-height: 44px;
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }
}

.shopping-list-day {
  border-bottom: 1px solid var(--border-warm);
}

.shopping-list-day:last-child {
  border-bottom: 0;
}

.shopping-list-day__header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  background: var(--bg-cream);
  border-bottom: 1px solid var(--border-line);
}

.shopping-list-day__weekday {
  font-size: var(--fs-15);
  font-weight: var(--fw-bold);
  color: var(--fg-1);
  text-transform: capitalize;
}

.shopping-list-day__date {
  font-size: var(--fs-13);
  color: var(--culinari-muted);
  font-variant-numeric: tabular-nums;
}

.shopping-list-day__count {
  margin-left: auto;
  font-size: var(--fs-13);
  color: var(--culinari-muted);
}

.shopping-list-day__empty {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  font-style: italic;
}

/* ============================================
   Thumbnails — fixed square images used in
   search results, list rows, settings avatars.
============================================ */

.img-thumb-60,
.img-thumb-80 {
  object-fit: cover;
  border-radius: var(--radius-md);
  display: block;
}

.img-thumb-60 { width: 60px; height: 60px; }
.img-thumb-80 { width: 80px; height: 80px; }

.img-thumb-placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-soft);
  color: var(--culinari-muted);
  font-size: 1.5rem;
}

/* ============================================
   Layout utilities
============================================ */

.container-narrow { max-width: 900px; }
.container-xnarrow { max-width: 640px; }

.min-w-0 { min-width: 0; }

.progress-thin { height: 4px; }

.usage-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: var(--fs-13);
  font-weight: var(--fw-medium, 500);
  line-height: 1.4;
  border: 1px solid transparent;
  white-space: nowrap;
}
.usage-pill--ok   { background: rgba(25, 135, 84, 0.10);  color: var(--culinari-success-deep); border-color: rgba(25, 135, 84, 0.20); }
.usage-pill--warn { background: rgba(255, 193, 7, 0.18);  color: var(--culinari-warning-deep); border-color: rgba(255, 193, 7, 0.35); }
.usage-pill--full { background: rgba(220, 53, 69, 0.12);  color: var(--culinari-danger-deep);  border-color: rgba(220, 53, 69, 0.30); }


.toast-layer { z-index: 1080; }

/* ============================================
   Admin table column widths
============================================ */

.table-col-label  { width: 40%; }
.table-col-meta   { width: 35%; }
.table-col-narrow { width: 15%; }
.table-col-tight  { width: 10%; }

.table-scroller {
  max-height: 500px;
  overflow-y: auto;
}

.admin-notes-content-cell { max-width: 320px; }

/* `.resource-image--cookbook-tile` was the hash-derived placeholder color
   for cookbook covers without an uploaded image. Replaced by
   `.cookbook-index__cover--empty`, which renders the cookbook title on a
   cream-wash swatch — keeps the index chromatically calm per the
   One Voice Rule and avoids hue-only identity. */

.book-header-cover { max-height: 300px; }

/* ============================================
   Book reading page
============================================ */

.book-read-cover {
  width: 140px;
  max-width: 100%;
}

@media (min-width: 768px) {
  .book-read-cover {
    width: 180px;
  }
}

.book-read-cover-img {
  width: 100%;
  height: auto;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
  display: block;
}

.book-read-subtitle {
  margin-left: 2.75rem;
  font-size: 0.95rem;
}

.book-read-meta {
  margin-top: 0.25rem;
}

.book-index-search {
  position: relative;
}

.book-index-search-icon {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translateY(-50%);
  color: var(--culinari-muted);
  font-size: 15px;
  pointer-events: none;
}

.book-index-search-input {
  padding: 0.625rem 1rem 0.625rem 42px;
  min-height: 46px;
  border-radius: var(--radius-md);
  background-color: var(--culinari-bg-app);
  border: 1px solid var(--culinari-hairline);
  font-size: 15px;
}

.book-index-search-input::placeholder {
  color: var(--culinari-silver);
}

.book-index-search-input:focus {
  background-color: var(--culinari-surface);
  border-color: var(--culinari-primary);
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

.book-index-list {
  border-top: 1px solid var(--culinari-hairline);
}

.book-index-item {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--culinari-hairline);
  transition: background-color 0.15s ease;
}

.book-index-item:hover {
  background-color: var(--culinari-primary-wash);
}

.book-index-item--static:hover {
  background-color: transparent;
}

.book-index-item-link {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 12px 14px 4px;
  text-decoration: none;
  color: inherit;
  min-height: 60px;
}

.book-index-page {
  min-width: 64px;
  font-family: 'Inter', monospace;
  font-weight: 700;
  color: var(--culinari-primary);
  font-size: 14px;
  text-align: center;
  letter-spacing: 0.5px;
  flex-shrink: 0;
  padding: 4px 10px;
}

.book-index-item--static .book-index-page {
  color: var(--culinari-silver);
}

.book-index-item-title {
  font-weight: 500;
  color: var(--culinari-ink);
  font-size: 15px;
  line-height: 1.4;
}

.book-index-item-title--muted {
  color: var(--culinari-silver);
  font-weight: 400;
}

a.book-index-item-link:hover .book-index-item-title,
a.book-index-item-link:focus-visible .book-index-item-title {
  color: var(--culinari-primary);
}

.book-index-item-action {
  display: flex;
  align-items: center;
  padding-right: 4px;
}

.book-index-item-action a {
  color: var(--culinari-silver);
  text-decoration: none;
  font-size: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.book-index-item-action a:hover {
  color: var(--culinari-primary);
  background-color: var(--culinari-primary-tint);
}

.book-index-item-action .bi-bookmark-fill {
  color: var(--culinari-primary);
}

.book-read-empty-icon {
  font-size: 2.5rem;
  color: var(--culinari-placeholder);
}

/* Toolbar: search + filter chip */
.book-index-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.book-index-toolbar .book-index-search {
  flex: 1;
  min-width: 220px;
}

/* 44px tap target via 12px vertical padding (PRODUCT.md AA commitment,
   kitchen-context one-handed use). max-width + inner-span ellipsis lets
   long EN/PT translations and user-entered tag names truncate gracefully
   without overflowing the sidebar gutter or pushing the grid. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 12px 16px;
  max-width: 240px;
  background: transparent;
  border: 1px solid var(--culinari-hairline);
  border-radius: 999px;
  color: var(--culinari-fg-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, font-weight 0s;
}

.filter-chip > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.filter-chip .bi {
  font-size: 14px;
  flex-shrink: 0;
}

.filter-chip:hover {
  border-color: var(--culinari-primary);
  color: var(--culinari-primary);
}

/* Pressed state (sidebar toggles): primary fill + weight 600.
   --applied variant: same visual treatment for the applied-filters strip,
   where chips are one-way remove actions (not toggles) and don't carry
   aria-pressed — the leading bi-x icon supplies the meaning instead. */
.filter-chip[aria-pressed="true"],
.filter-chip--applied {
  background-color: var(--culinari-primary);
  border-color: var(--culinari-primary);
  color: var(--culinari-surface);
  font-weight: 600;
}

.filter-chip--applied:hover {
  background-color: var(--accent-hover, var(--culinari-primary));
  color: var(--culinari-surface);
}

.filter-chip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

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

/* Three kind-variants form a small line-and-tint vocabulary so users
   can distinguish ownership at a glance:
     --app   tinted bg, no border    → public, admin-curated library
     --user  dotted border           → personal label (scoped by tagger_id)
     --reset dashed border + bi-x    → dismissible escape, not a category
   Default (no kind) is solid hairline outline for plain taxonomy
   (dish_types, cuisines). Active (pressed) state via [aria-pressed="true"]
   above overrides all four with the primary fill + bi-check + weight 600. */
.filter-chip--app {
  background-color: var(--accent-tint);
  border-color: transparent;
  color: var(--accent-text);
}

.filter-chip--app:hover {
  background-color: var(--accent-tint);
  border-color: var(--culinari-primary);
}

.filter-chip--user {
  border-style: dotted;
}

.filter-chip--user:hover {
  border-style: solid;
}

.filter-chip--reset {
  border-style: dashed;
  color: var(--culinari-muted);
}

.filter-chip--reset:hover {
  border-style: solid;
  color: var(--culinari-primary);
  border-color: var(--culinari-primary);
}

/* Optional trailing count badge ("Italian 12"). Tabular-nums so columns
   of counts align; muted so the label reads first. flex-shrink: 0 keeps
   the count visible even when the label ellipses. */
.filter-chip__count {
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  opacity: 0.7;
  flex-shrink: 0;
}

/* Thin band rendered above the index grid when any facet is active.
   Lives at the same band as the H1/scope pills that created the state,
   so the escape is reachable without scrolling past the grid (this is
   the load-bearing part on phones, where the sidebar drops below). */
.active-filters-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 1rem;
}

/* ============================================
   Book Index — Empty / Processing / Locked / Submissions
   Editorial states for the public book show page when
   the index has not yet been extracted.
============================================ */
.book-index-state {
  display: grid;
  gap: 28px;
  align-items: center;
  padding: 28px 4px;
  border-top: 1px solid var(--border-warm);
  border-bottom: 1px solid var(--border-warm);
  background-color: var(--culinari-primary-cream);
}

@media (min-width: 768px) {
  .book-index-state {
    grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
    gap: 36px;
    padding: 36px 28px;
  }
}

.book-index-state__copy {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.book-index-state__eyebrow {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--culinari-primary-deep);
  margin: 0;
}

.book-index-state__headline {
  font-family: 'Nunito Sans', Inter, system-ui, sans-serif;
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--culinari-ink);
  margin: 0;
  max-width: 32ch;
}

.book-index-state__lead {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  margin: 0;
  max-width: 60ch;
  text-wrap: pretty;
}

.book-index-state__cta {
  align-self: flex-start;
  margin-top: 4px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
  border: 1px solid var(--culinari-primary);
  border-radius: 8px;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  min-height: 44px;
  min-width: 12ch;
  transition: background-color 0.15s var(--ease-soft),
              border-color 0.15s var(--ease-soft);
}

.book-index-state__cta:hover,
.book-index-state__cta:focus-visible {
  background-color: var(--culinari-primary-hover);
  border-color: var(--culinari-primary-hover);
  color: var(--culinari-surface);
}

.book-index-state__cta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

.book-index-state__cta:active {
  background-color: var(--culinari-primary-active);
  border-color: var(--culinari-primary-active);
}

.book-index-state__cta i.bi {
  font-size: 17px;
}

/* Locked state is the quietest: a one-line invitation, no big headline,
   on a flat warm-thread row instead of a cream block. */
.book-index-state--locked {
  grid-template-columns: 1fr;
  background-color: transparent;
  padding: 18px 4px;
  border-radius: 0;
  gap: 14px;
}

.book-index-state--locked .book-index-state__copy {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}

.book-index-state--locked .book-index-state__lead {
  margin: 0;
  flex: 1 1 240px;
}

.book-index-state--locked .book-index-state__cta {
  padding: 8px 16px;
  font-size: 14px;
  background-color: transparent;
  color: var(--culinari-primary-deep);
  border: 1px solid var(--culinari-primary);
  min-height: 0;
}

.book-index-state--locked .book-index-state__cta:hover,
.book-index-state--locked .book-index-state__cta:focus-visible {
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
}

/* Visual sample: a tiny static "page · title" preview that hints at
   the populated index. No image asset, just type — keeps the page light. */
.book-index-state__visual {
  display: none;
  border: 1px solid var(--border-warm);
  background-color: var(--culinari-surface);
  border-radius: 8px;
  padding: 8px 0;
  font-family: 'Inter', system-ui, sans-serif;
  position: relative;
  overflow: hidden;
}

@media (min-width: 768px) {
  .book-index-state__visual {
    display: block;
  }
}

.book-index-state__visual::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 88px;
  width: 1px;
  background-color: var(--border-warm);
  pointer-events: none;
}

.book-index-state__visual-row {
  display: grid;
  grid-template-columns: 88px 1fr;
  align-items: center;
  padding: 10px 14px;
  gap: 14px;
  border-bottom: 1px solid var(--border-warm);
  font-size: 14px;
}

.book-index-state__visual-row:last-child {
  border-bottom: 0;
}

.book-index-state__visual-page {
  font-family: 'Inter', monospace;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
  color: var(--culinari-primary);
  text-align: right;
  padding-right: 14px;
}

.book-index-state__visual-title {
  font-weight: 500;
  color: var(--culinari-ink);
}

.book-index-state__visual-row--muted .book-index-state__visual-page {
  color: var(--culinari-silver);
}

.book-index-state__visual-row--muted .book-index-state__visual-title {
  color: var(--culinari-silver);
  font-weight: 400;
}

/* Processing state: the cream block with a quiet pulsing dot next to
   the headline. The dot stays put under reduced motion. */
.book-index-state--processing .book-index-state__headline {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}

.book-index-state__pulse {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--culinari-primary);
  flex-shrink: 0;
  animation: book-index-pulse 1800ms var(--ease-soft) infinite;
}

@keyframes book-index-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 0.85; transform: scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .book-index-state__pulse { animation: none; opacity: 0.7; }
}

/* Submissions list: rebuild "existing uploads" with the same hairline-row
   rhythm as .book-index-list, so the empty + submitted page reads as one
   visual language with the populated index above. */
.book-index-submissions {
  margin-top: 28px;
}

.book-index-submissions__title {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--culinari-muted);
  margin: 0 0 12px;
}

.book-index-submissions__list {
  border-top: 1px solid var(--culinari-hairline);
}

.book-index-submission {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--culinari-hairline);
  text-decoration: none;
  color: inherit;
  min-height: 56px;
  transition: background-color 0.15s ease;
}

.book-index-submission:hover {
  background-color: var(--culinari-primary-wash);
  color: inherit;
}

.book-index-submission__date {
  flex: 1;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  color: var(--culinari-fg-muted);
}

.book-index-submission__status {
  flex-shrink: 0;
}

.book-index-submissions__view-all {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 16px;
  padding: 6px 0;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  color: var(--culinari-primary-deep);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s ease;
}

.book-index-submissions__view-all:hover,
.book-index-submissions__view-all:focus-visible {
  border-bottom-color: var(--culinari-primary-deep);
  color: var(--culinari-primary-deep);
}

/* Recipe-badge variants used by the upload status chip on the public side.
   --pending pairs warm cream + primary-deep so the in-flight state reads
   warm rather than alarming. --rejected uses chili semantic; AA contrast
   is preserved by pairing with an icon at the call site. */
.recipe-badge--pending {
  background-color: var(--culinari-primary-cream);
  color: var(--culinari-primary-deep);
  border: 1px dashed var(--culinari-primary);
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--fw-medium);
}

.recipe-badge--rejected {
  background-color: rgba(220, 53, 69, 0.10);
  color: var(--culinari-danger-deep);
  border: 1px solid rgba(220, 53, 69, 0.20);
}

/* ============================================
   Book Index — Public Upload Form (editorial)
   The Pro user lands here from the calm empty
   state on the book show. The form has to hold
   that brand temperature: paper-warm panel,
   bottom-border-only labels, terracotta CTA.
============================================ */
.book-index-form {
  display: block;
}

.book-index-form__intro {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  margin: 0 0 28px;
}

/* ---- Tile-grid uploaders (one per field) ---- */

.book-index-photos {
  margin-bottom: 32px;
}

.book-index-photos__head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}

.book-index-photos__label {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--culinari-muted);
  margin: 0;
  cursor: pointer;
}

.book-index-photos__tag {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--culinari-hairline);
  color: var(--culinari-muted);
  background-color: transparent;
}

.book-index-photos__tag--required {
  color: var(--culinari-primary-deep);
  border-color: var(--culinari-primary);
  background-color: var(--culinari-primary-tint);
}

.book-index-photos__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.75rem;
  padding: 0.5rem;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-soft);
}

/* Native input is hidden but stays in the DOM so the form still posts files. */
.book-index-photos__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.book-index-photos__hint {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--culinari-fg-muted);
  margin: 10px 0 0;
}

/* Tile shared shape (both --current and --preview) */
.book-index-photos__tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--culinari-surface);
  border: 1px solid var(--border-hairline);
  margin: 0;
}

.book-index-photos__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.book-index-photos__badge {
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  background: var(--culinari-primary-tint);
  color: var(--culinari-primary-deep);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  pointer-events: none;
}

.book-index-photos__tile--current .book-index-photos__badge {
  /* Already-uploaded tiles: warm tinted badge as above. */
}

/* Soft warning when a freshly-selected photo looks too small or too light
   to OCR well. Full hairline tint (no side-stripe accent per design rules). */
.book-index-photos__tile--warn {
  border-color: var(--culinari-warning-deep);
}

.book-index-photos__badge--warn {
  background: rgba(255, 193, 7, 0.18);
  color: var(--culinari-warning-deep);
}

.book-index-photos__remove {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-circle, 50%);
  border: 0;
  background: rgba(26, 26, 26, 0.72);
  color: var(--culinari-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.book-index-photos__remove:hover,
.book-index-photos__remove:focus-visible {
  background: var(--culinari-primary-active);
  outline: none;
}

.book-index-photos__remove i {
  pointer-events: none;
}

/* Add tile: dashed prompt, full keyboard target, hover warms toward terracotta. */
.book-index-photos__add {
  aspect-ratio: 1 / 1;
  border: 2px dashed var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--culinari-surface);
  color: var(--culinari-muted);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  cursor: pointer;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 0.85rem;
  font-weight: 500;
  text-align: center;
  padding: 6px;
  transition: border-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft),
              background-color var(--dur-fast) var(--ease-soft);
}

.book-index-photos__add i {
  font-size: 1.5rem;
}

.book-index-photos__add:hover,
.book-index-photos__add:focus-visible {
  border-color: var(--culinari-primary);
  color: var(--culinari-primary-deep);
  background: var(--culinari-primary-tint);
  outline: none;
}

.book-index-photos__add-label {
  display: block;
  line-height: 1.2;
  max-width: 11ch;
  word-wrap: break-word;
}

/* Form actions */
.book-index-form__actions {
  display: flex;
  justify-content: flex-start;
  margin-top: 32px;
}

.book-index-form__submit {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 26px;
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
  border: 1px solid var(--culinari-primary);
  border-radius: 8px;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  min-height: 44px;
  min-width: 14ch;
  cursor: pointer;
  transition: background-color 0.15s var(--ease-soft),
              border-color 0.15s var(--ease-soft);
}

.book-index-form__submit:hover,
.book-index-form__submit:focus-visible {
  background-color: var(--culinari-primary-hover);
  border-color: var(--culinari-primary-hover);
  color: var(--culinari-surface);
}

.book-index-form__submit:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

.book-index-form__submit:active {
  background-color: var(--culinari-primary-active);
  border-color: var(--culinari-primary-active);
}

@media (max-width: 575.98px) {
  .book-index-form__actions { justify-content: stretch; }
  .book-index-form__submit { width: 100%; justify-content: center; }
}

/* Review-before-send disclosure: a friction layer that asks the cook to
   pause and re-read their selection before committing to a multi-day OCR job. */
.book-index-form__review {
  margin-top: 32px;
  background-color: var(--culinari-primary-wash);
  border: 1px solid var(--border-warm);
  border-radius: 12px;
  padding: 18px 20px;
}

.book-index-form__review[open] {
  padding-bottom: 22px;
}

.book-index-form__review-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;
  list-style: none;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--culinari-ink);
  min-height: 28px;
}

.book-index-form__review-summary::-webkit-details-marker { display: none; }

.book-index-form__review-summary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
  border-radius: 6px;
}

.book-index-form__review-summary-chevron {
  font-size: 16px;
  color: var(--culinari-fg-muted);
  transition: transform 0.2s var(--ease-soft);
}

.book-index-form__review[open] .book-index-form__review-summary-chevron {
  transform: rotate(180deg);
}

.book-index-form__review-body {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.book-index-form__review-intro {
  margin: 0;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  max-width: 60ch;
}

.book-index-form__review-list {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: var(--culinari-fg);
  max-width: 60ch;
}

.book-index-form__review .book-index-form__actions {
  margin-top: 6px;
}

/* ============================================
   Book Index — Upload Receipt (post-submit show)
   No card chrome. Type-led layout with hairline
   section heads and a calm thumbnail grid.
============================================ */
/* The .book-index-receipt root has no width rule of its own — it inherits
   the surrounding Bootstrap .container so it matches the new/edit form.
   Internal elements self-cap their text widths (60ch on state-lead,
   32ch on state-headline). */
/* Header chrome (back-link, title row, H1) comes from .resource-header so the
   show page lines up with the new/edit form. We only add the hairline rule
   beneath, and the meta row sitting between header and body. */
.book-index-receipt__header {
  padding-bottom: 20px;
  border-bottom: 1px solid var(--culinari-hairline);
}

.book-index-receipt__eyebrow {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--culinari-primary-deep);
  margin: 0;
}

.book-index-receipt__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  color: var(--culinari-fg-muted);
}

.book-index-receipt__timestamp {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.book-index-receipt__timestamp time {
  color: var(--culinari-ink);
  font-weight: 500;
}

.book-index-receipt__section {
  margin-top: 32px;
}

.book-index-receipt__section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 12px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border-warm);
}

.book-index-receipt__section-head .book-index-receipt__eyebrow {
  color: var(--culinari-muted);
}

.book-index-receipt__count {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--culinari-muted);
  font-variant-numeric: tabular-nums;
}

.book-index-receipt__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 14px;
}

@media (min-width: 768px) {
  .book-index-receipt__grid {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 16px;
  }
}

.book-index-receipt__grid[data-count="1"] {
  grid-template-columns: minmax(0, 360px);
}

.book-index-receipt__photo {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.book-index-receipt__photo-link {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--culinari-primary-cream);
  border: 1px solid var(--border-warm);
  border-radius: 8px;
  overflow: hidden;
  padding: 6px;
  min-height: 200px;
  text-decoration: none;
  transition: border-color 0.15s var(--ease-soft),
              box-shadow 0.15s var(--ease-soft);
}

.book-index-receipt__photo-link:hover {
  border-color: var(--culinari-primary);
}

.book-index-receipt__photo-link:focus-visible {
  outline: none;
  border-color: var(--culinari-primary);
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

.book-index-receipt__photo-link img {
  display: block;
  max-width: 100%;
  max-height: 360px;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 4px;
}

.book-index-receipt__photo-caption {
  font-family: 'Inter', monospace;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--culinari-muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
  margin: 0;
}

/* Author line directly under the H1. Quiet, single-source attribution; gives
   Pro cooks with multiple editions of the same title a fast disambiguator. */
.book-index-receipt__authors {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  color: var(--culinari-fg-muted);
  margin: 4px 0 0;
}

/* Total-photos line lives in the meta row next to the timestamp. Tabular nums
   so the count never shifts width as photos add. */
.book-index-receipt__total {
  font-variant-numeric: tabular-nums;
}

/* State block: the body's "what's happening now / what to do next" surface.
   Three variants share the structure; only --processing carries the cream
   block, sample preview and pulse to match the book page's processing UI. */
.book-index-receipt__state {
  margin-top: 4px;
  margin-bottom: 8px;
  padding: 22px 4px;
  display: grid;
  gap: 18px;
  align-items: center;
}

@media (min-width: 768px) {
  .book-index-receipt__state--processing {
    grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
    gap: 36px;
    padding: 32px 28px;
  }
}

.book-index-receipt__state--processing {
  background-color: var(--culinari-primary-cream);
  border-top: 1px solid var(--border-warm);
  border-bottom: 1px solid var(--border-warm);
  padding: 28px 18px;
}

.book-index-receipt__state-copy {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.book-index-receipt__state-headline {
  font-family: 'Nunito Sans', Inter, system-ui, sans-serif;
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--culinari-ink);
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  max-width: 32ch;
}

.book-index-receipt__state-icon {
  font-size: 22px;
  flex-shrink: 0;
}

.book-index-receipt__state--accepted .book-index-receipt__state-icon {
  color: var(--culinari-primary-deep);
}

.book-index-receipt__state--rejected .book-index-receipt__state-icon {
  color: var(--culinari-danger-deep);
}

.book-index-receipt__state--accepted-empty .book-index-receipt__state-icon {
  color: var(--culinari-fg-muted);
}

.book-index-receipt__state-lead {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  margin: 0;
  max-width: 60ch;
  text-wrap: pretty;
}

/* Liveness line under the processing lead. Quiet, kitchen-notebook register;
   updates client-side every few seconds so the cook can see the page is alive. */
.book-index-receipt__state-status {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 12px;
  line-height: 1.4;
  color: var(--culinari-muted);
  margin: 0;
  margin-top: 4px;
}

.book-index-receipt__state-cta {
  align-self: flex-start;
  margin-top: 8px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  background-color: var(--culinari-primary);
  color: var(--culinari-surface);
  border: 1px solid var(--culinari-primary);
  border-radius: 8px;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  min-height: 44px;
  transition: background-color 0.15s var(--ease-soft),
              border-color 0.15s var(--ease-soft);
}

.book-index-receipt__state-cta:hover,
.book-index-receipt__state-cta:focus-visible {
  background-color: var(--culinari-primary-hover);
  border-color: var(--culinari-primary-hover);
  color: var(--culinari-surface);
}

.book-index-receipt__state-cta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

.book-index-receipt__state-cta:active {
  background-color: var(--culinari-primary-active);
  border-color: var(--culinari-primary-active);
}

.book-index-receipt__state-cta i.bi {
  font-size: 17px;
}

/* Quiet inline link inside the submitted state — users can still edit
   photos while we're reading; we just don't want this to look like a CTA. */
.book-index-receipt__state-link {
  align-self: flex-start;
  margin-top: 4px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
  color: var(--culinari-primary-deep);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s var(--ease-soft);
}

.book-index-receipt__state-link:hover,
.book-index-receipt__state-link:focus-visible {
  border-bottom-color: var(--culinari-primary-deep);
  color: var(--culinari-primary-deep);
  outline: none;
}

.book-index-receipt__state-link:focus-visible {
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
  border-radius: 2px;
}

.book-index-receipt__state-link--destructive {
  color: var(--culinari-danger-deep);
}

.book-index-receipt__state-link--destructive:hover,
.book-index-receipt__state-link--destructive:focus-visible {
  color: var(--culinari-danger-deep);
  border-bottom-color: var(--culinari-danger-deep);
}

.book-index-receipt__state-link--destructive:focus-visible {
  box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.18);
}

/* Slow pulse to make the chip subtly alive without going noisy. Reuses the
   same keyframe as the book-page processing dot. */
.book-index-receipt__pulse {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--culinari-primary);
  flex-shrink: 0;
  animation: book-index-pulse 1800ms var(--ease-soft) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .book-index-receipt__pulse { animation: none; opacity: 0.7; }
}

/* Sample preview reuses the type rhythm of .book-index-state__visual but
   scoped to the receipt so spacing tweaks here don't leak to the book page. */
.book-index-receipt__state-visual {
  display: none;
  border: 1px solid var(--border-warm);
  background-color: var(--culinari-surface);
  border-radius: 8px;
  padding: 8px 0;
  font-family: 'Inter', system-ui, sans-serif;
  position: relative;
  overflow: hidden;
}

@media (min-width: 768px) {
  .book-index-receipt__state-visual {
    display: block;
  }
}

.book-index-receipt__state-visual::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 88px;
  width: 1px;
  background-color: var(--border-warm);
  pointer-events: none;
}

.book-index-receipt__state-visual-row {
  display: grid;
  grid-template-columns: 88px 1fr;
  align-items: center;
  padding: 10px 14px;
  gap: 14px;
  border-bottom: 1px solid var(--border-warm);
  font-size: 14px;
}

.book-index-receipt__state-visual-row:last-child {
  border-bottom: 0;
}

.book-index-receipt__state-visual-page {
  font-family: 'Inter', monospace;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
  color: var(--culinari-primary);
  text-align: right;
  padding-right: 14px;
}

.book-index-receipt__state-visual-title {
  font-weight: 500;
  color: var(--culinari-ink);
}

.book-index-receipt__state-visual-row--muted .book-index-receipt__state-visual-page {
  color: var(--culinari-silver);
}

.book-index-receipt__state-visual-row--muted .book-index-receipt__state-visual-title {
  color: var(--culinari-silver);
  font-weight: 400;
}

/* Submitted status chip spins its arrow-repeat icon so the chip alone reads
   as "actively working" — supplements the body-block pulse for users who
   only glance at the meta row. */
.recipe-badge--pending i.bi-arrow-repeat {
  animation: book-index-spin 1600ms linear infinite;
  transform-origin: center;
}

@keyframes book-index-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .recipe-badge--pending i.bi-arrow-repeat { animation: none; }
}

/* ============================================
   Culinari Flash Messages
   Cookbook-warm replacement for Bootstrap alerts.
   Three roles (notice / success / alert) all sit on
   tinted neutrals with hairline borders and a
   leading icon, so meaning never depends on hue
   alone (DESIGN.md hue-plus-mark rule).
============================================ */
.culinari-flash-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 16px 0 24px;
}

.culinari-flash-stack:not(:has(> *)) {
  display: none;
}

.culinari-flash {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: 8px;
  border: 1px solid var(--culinari-hairline);
  background-color: var(--culinari-primary-cream);
  color: var(--culinari-ink);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

.culinari-flash__icon {
  font-size: 18px;
  flex-shrink: 0;
  line-height: 1;
  color: var(--culinari-muted);
}

.culinari-flash__body {
  flex: 1;
  min-width: 0;
}

.culinari-flash__dismiss {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  margin: -6px -8px -6px 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--culinari-muted);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.culinari-flash__dismiss:hover,
.culinari-flash__dismiss:focus-visible {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.culinari-flash__dismiss:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.culinari-flash__dismiss .bi {
  font-size: 16px;
  line-height: 1;
}

.culinari-flash--notice {
  background-color: var(--culinari-primary-wash);
  border-color: var(--border-warm);
}

.culinari-flash--notice .culinari-flash__icon {
  color: var(--culinari-primary-deep);
}

.culinari-flash--success {
  background-color: rgba(25, 135, 84, 0.08);
  border-color: rgba(25, 135, 84, 0.30);
  color: var(--culinari-ink);
}

.culinari-flash--success .culinari-flash__icon {
  color: var(--culinari-success-deep);
}

.culinari-flash--warning {
  background-color: rgba(255, 193, 7, 0.16);
  border-color: rgba(255, 193, 7, 0.45);
  color: var(--culinari-ink);
}

.culinari-flash--warning .culinari-flash__icon {
  color: var(--culinari-warning-deep);
}

.culinari-flash--alert {
  background-color: rgba(220, 53, 69, 0.08);
  border-color: rgba(220, 53, 69, 0.30);
  color: var(--culinari-ink);
}

.culinari-flash--alert .culinari-flash__icon {
  color: var(--culinari-danger-deep);
}

/* Cookie notice — fixed, bottom-centered card. Sits below modals (1050+) and
   the app toast (1080) so it never blocks them. Revealed by cookie_consent_controller. */
.culinari-cookie-banner {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 16px;
  z-index: 1040;
  max-width: 720px;
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-warm);
  background-color: var(--culinari-primary-cream);
  color: var(--culinari-ink);
  box-shadow: var(--shadow-lg);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

.culinari-cookie-banner .btn {
  flex-shrink: 0;
}

.culinari-cookie-banner__icon {
  font-size: 20px;
  flex-shrink: 0;
  line-height: 1;
  color: var(--culinari-primary-deep);
}

.culinari-cookie-banner__body {
  flex: 1;
  min-width: 0;
  margin: 0;
}

.culinari-cookie-banner__link {
  color: var(--culinari-primary-deep);
  text-decoration: underline;
  font-weight: 600;
}

.culinari-cookie-banner__link:hover,
.culinari-cookie-banner__link:focus-visible {
  color: var(--culinari-primary-deep);
}

@media (max-width: 575.98px) {
  .culinari-cookie-banner {
    flex-direction: column;
    align-items: stretch;
    text-align: left;
  }

  .culinari-cookie-banner .btn {
    width: 100%;
  }
}

/* Sticky compact header */
.book-read-compact-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1020;
  background-color: var(--culinari-surface);
  border-bottom: 1px solid var(--border-hairline);
  padding: 10px 0;
  opacity: 0;
  transform: translateY(-100%);
  transition: opacity var(--dur-base) var(--ease-soft), transform var(--dur-base) var(--ease-soft);
  pointer-events: none;
}

.book-read-compact-header.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.book-read-compact-header-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 40px;
}

.book-read-compact-title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1rem;
  color: var(--culinari-ink);
  margin: 0;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.book-read-compact-header .bookmark-container a {
  font-size: 18px;
  color: var(--culinari-fg);
  text-decoration: none;
}

.book-read-compact-header .bookmark-container a:hover {
  color: var(--accent-text);
}

/* ============================================
   Similar Recipes Section
   Slim grid on >=md, horizontal scroll-snap on
   mobile. Used by recipes/_similar.html.erb.
============================================ */
.recipe-similar {
  --similar-gap: 1rem;
  --similar-card-min: 160px;
}

.recipe-similar__heading {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--culinari-ink);
}

.recipe-similar__strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--similar-card-min), 1fr));
  gap: var(--similar-gap);
}

.recipe-similar__card {
  display: block;
  color: inherit;
  text-decoration: none;
  transition: transform 0.2s ease;
}

.recipe-similar__card:hover {
  color: inherit;
  text-decoration: none;
  transform: translateY(-2px);
}

.recipe-similar__card:hover .recipe-similar__title {
  color: var(--culinari-primary);
}

.recipe-similar__media {
  border-radius: var(--radius-sm);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--culinari-bg-soft);
}

.recipe-similar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.recipe-similar__img--empty {
  width: 100%;
  height: 100%;
  font-size: 2rem;
}

.recipe-similar__body {
  padding-top: 0.5rem;
}

.recipe-similar__title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--culinari-fg);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.3;
  min-height: calc(0.95rem * 1.3 * 2);
  transition: color 0.15s ease;
}

/* Mobile: horizontal scroll-snap strip */
@media (max-width: 767.98px) {
  .recipe-similar__strip {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 0.5rem;
    margin-inline: -0.75rem;
    padding-inline: 0.75rem;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }

  .recipe-similar__card {
    flex: 0 0 60%;
    max-width: 220px;
    scroll-snap-align: start;
  }
}

/* Skeleton placeholders (turbo-frame lazy-loading state). Warm-thread
   tones keep the loading state inside the cookbook palette so the
   placeholder doesn't read as a foreign cool body on a cream surface. */
.recipe-similar__skeleton {
  background: linear-gradient(90deg, var(--border-warm) 0%, var(--bg-cream) 50%, var(--border-warm) 100%);
  background-size: 200% 100%;
  animation: recipe-similar-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm, 6px);
}

.recipe-similar__skeleton--heading {
  height: 1.5rem;
  width: 30%;
  max-width: 220px;
}

.recipe-similar__skeleton--media {
  aspect-ratio: 4 / 3;
}

.recipe-similar__skeleton--text {
  height: 0.9rem;
  margin-top: 0.5rem;
}

.recipe-similar__skeleton--text-short {
  width: 60%;
}

.recipe-similar__card--placeholder {
  pointer-events: none;
}

@keyframes recipe-similar-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Honor the user's reduced-motion preference end-to-end (PRODUCT.md
   commitment). Hover lifts, fades, sticky-header transforms, skeleton
   shimmer all collapse to instant. Single :hover transforms must also
   be flattened — keep 0.01ms instead of 0 to preserve transitionend
   events some controllers listen for. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .tag-cloud .badge:hover,
  .resource-card:hover,
  .resource-row-card:hover,
  .recipe-similar__card:hover {
    transform: none !important;
  }

  .recipe-similar__skeleton {
    animation: none;
  }
}

/* ============================================
   Drink Pairings (recipe show — issue #418)
   Type-led list that belongs to the cookbook
   system: eyebrow kind labels, ink drink
   styles, cream axis chips, and a calm-but-
   tappable alcohol reveal. Flat: no card, no
   shadow, no divider at rest.
============================================ */
/* Section heading ("Drink pairings") keeps the global h2 styling; the
   alcoholic sub-band is a quiet eyebrow so it never competes with it. */
.recipe-pairings__subheading {
  margin-bottom: var(--space-3);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--culinari-muted);
}

/* Suggestion list — the drink style is the hero of each row. */
.recipe-pairings__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.recipe-pairings__item {
  margin: 0;
}
.recipe-pairings__kind {
  margin: 0 0 0.15rem;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--culinari-muted);
}
.recipe-pairings__headline {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.recipe-pairings__style {
  font-size: var(--fs-18);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
}
.recipe-pairings__reason {
  margin: var(--space-1) 0 0;
  max-width: 68ch;
  font-size: var(--fs-14);
  line-height: var(--lh-body);
  color: var(--culinari-fg-muted);
}
.recipe-pairings__note {
  font-size: var(--fs-12);
  color: var(--culinari-muted);
}

/* Alcohol reveal — collapsed <details> by default (holds the future Hotwire
   Native shell at Apple's 13+ tier). Understated on purpose, but a real
   ≥44px tap target with a legible label and a custom caret, not the bare
   browser triangle. Mirrors the photo-critique breakdown summary pattern. */
.recipe-pairings__reveal {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) 0;
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-15);
  font-weight: var(--fw-medium);
  color: var(--culinari-fg-muted);
  transition: color var(--dur-fast) var(--ease-soft);
}
.recipe-pairings__reveal::-webkit-details-marker { display: none; }
.recipe-pairings__reveal::marker { content: ""; }
.recipe-pairings__reveal:hover { color: var(--accent-text); }
.recipe-pairings__reveal:focus-visible {
  outline: none;
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 4px var(--accent-tint);
}
.recipe-pairings__reveal-caret {
  flex-shrink: 0;
  font-size: 0.85rem;
  color: var(--culinari-muted);
  transition: transform var(--dur-fast) var(--ease-soft);
}
.recipe-pairings__reveal:hover .recipe-pairings__reveal-caret { color: var(--accent-text); }
.recipe-pairings__alcohol[open] .recipe-pairings__reveal-caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .recipe-pairings__reveal,
  .recipe-pairings__reveal-caret { transition: none; }
}

/* Pairing-shaped loading skeleton — echoes the real list (eyebrow + style +
   reasoning per row) rather than the similar-strip prose shape. Reuses the
   .recipe-similar__skeleton shimmer engine and its reduced-motion rule. */
.recipe-pairings__skel { height: 0.85rem; }
.recipe-pairings__skel--heading { height: 1.4rem; width: 32%; max-width: 200px; }
.recipe-pairings__skel-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.recipe-pairings__skel-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.recipe-pairings__skel--eyebrow { width: 18%; max-width: 90px; height: 0.7rem; }
.recipe-pairings__skel--style { width: 42%; max-width: 240px; height: 1.05rem; }
.recipe-pairings__skel--reason { width: 72%; max-width: 480px; height: 0.8rem; }

/* Step-flow-diagram loading skeleton: a heading bar plus one tall block where
   the flowchart lands. Reuses the .recipe-similar__skeleton shimmer engine.
   The block height tracks a typical 4-level graph so the content below
   doesn't jump when the real diagram (~420-450px) replaces it. */
.recipe-step-flow__skel--heading { height: 1.4rem; width: 32%; max-width: 200px; }
.recipe-step-flow__skel--diagram { height: 420px; width: 100%; max-width: 640px; }

/* Diagram nodes double as links into the numbered step list (wired up by
   step_graph_controller when the list is on the page). */
.step-graph-node--link { cursor: pointer; }
.step-graph-node--link:hover rect,
.step-graph-node--link:focus-visible rect {
  stroke: var(--culinari-primary-deep);
  stroke-width: 2px;
}
.step-graph-node--link:focus-visible { outline: none; }

/* Anchor targets for those links: keep the step clear of the sticky header. */
.instruction-list li { scroll-margin-top: 96px; }

.recipe-photos__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.75rem;
  padding: 0.5rem;
  border: 2px dashed var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-soft);
  transition: background-color var(--dur-fast) var(--ease-soft), border-color var(--dur-fast) var(--ease-soft);
}

.recipe-photos__grid--drag {
  background: var(--accent-tint);
  border-color: var(--culinari-primary);
}

.recipe-photos__tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--culinari-surface);
  border: 1px solid var(--border-hairline);
}

.recipe-photos__tile.sortable-ghost {
  opacity: 0.4;
}

.recipe-photos__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.recipe-photos__cover-badge {
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  background: rgba(0, 0, 0, 0.72);
  color: var(--culinari-surface);
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.15rem 0.45rem;
  border-radius: 4px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  pointer-events: none;
}

.recipe-photos__remove {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-circle);
  border: 0;
  background: rgba(0, 0, 0, 0.65);
  color: var(--culinari-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.recipe-photos__remove:hover,
.recipe-photos__remove:focus-visible {
  background: var(--culinari-danger);
  outline: none;
}

.recipe-photos__remove i,
.recipe-photos__add i {
  pointer-events: none;
}

.recipe-photos__drag {
  position: absolute;
  bottom: 0.3rem;
  right: 0.3rem;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.65);
  color: var(--culinari-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  line-height: 1;
  cursor: grab;
  user-select: none;
  transition: background-color 120ms ease;
}

.recipe-photos__drag i {
  pointer-events: none;
}

.recipe-photos__drag:hover {
  background: rgba(0, 0, 0, 0.85);
}

.recipe-photos__drag:active {
  cursor: grabbing;
}

/* The critique trigger is the feature's front door, so it reads as a labeled
   content pill (cream + terracotta-deep), deliberately distinct from the two
   dark utility circles (remove/drag) it shares the tile with. */
.recipe-photos__critique {
  position: absolute;
  bottom: 0.3rem;
  left: 0.3rem;
  /* leave room for the bottom-right drag handle (26px + its 0.3rem inset) */
  max-width: calc(100% - 0.6rem - 32px);
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.55rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--culinari-primary-cream);
  color: var(--culinari-primary-deep);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(26, 26, 26, 0.28);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.recipe-photos__critique:hover,
.recipe-photos__critique:focus-visible {
  background: var(--culinari-primary);
  color: var(--culinari-surface);
  outline: none;
}

.recipe-photos__critique i {
  pointer-events: none;
  font-size: 0.85rem;
}

.recipe-photos__critique-label {
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Touch devices: meet the 44x44 target on the tile's corner controls
   (mirrors the documented row-action-btn touch exception; desktop stays compact). */
@media (pointer: coarse) {
  .recipe-photos__remove,
  .recipe-photos__drag {
    width: 44px;
    height: 44px;
    font-size: 1.1rem;
  }
  /* The labeled pill keeps its shape but clears the 44px touch floor and the
     larger (44px) drag handle to its right. */
  .recipe-photos__critique {
    min-height: 44px;
    padding: 0.45rem 0.7rem;
    font-size: 0.8125rem;
    max-width: calc(100% - 0.6rem - 50px);
  }
}

/* ---- Photo critique drawer + result ------------------------------ */
.recipe-photo-critique-drawer .offcanvas-body {
  background: var(--culinari-primary-cream);
}
.recipe-photo-critique-drawer .offcanvas-title {
  color: var(--culinari-primary-deep);
}
.recipe-photo-critique-drawer .offcanvas-header {
  border-bottom: 1px solid var(--culinari-hairline);
}
.recipe-photo-critique__thumb {
  width: 44px;
  height: 44px;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 1px solid var(--culinari-line-warm);
  flex: 0 0 auto;
}

.recipe-photo-critique__summary {
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--culinari-ink);
  margin: 0 0 0.25rem;
}
.recipe-photo-critique__overall {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.875rem;
  color: var(--culinari-fg-muted);
  margin: 0 0 1rem;
}
.recipe-photo-critique__overall-label {
  font-weight: 600;
  color: var(--culinari-ink);
}

.recipe-photo-critique__dim {
  padding: 0.75rem 0;
  border-top: 1px solid var(--culinari-line-warm);
}
.recipe-photo-critique__dim:first-of-type {
  border-top: 0;
}
.recipe-photo-critique__dim-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.recipe-photo-critique__dim-label {
  font-weight: 600;
  color: var(--culinari-ink);
}
.recipe-photo-critique__dim-tip {
  margin: 0.15rem 0 0;
  font-size: 0.875rem;
  color: var(--culinari-fg-muted);
}
.recipe-photo-critique__updated {
  margin-top: 1rem;
  font-size: 0.75rem;
  color: var(--culinari-muted);
}

/* Per-criterion breakdown disclosure: a quiet label-styled toggle (no SaaS
   chevron-button), native <details> so it is keyboard-operable with no JS. */
.recipe-photo-critique__breakdown { margin-top: 0.5rem; }
.recipe-photo-critique__breakdown-summary {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  list-style: none;
  padding: 0.25rem 0;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--culinari-fg-muted);
}
.recipe-photo-critique__breakdown-summary::-webkit-details-marker { display: none; }
.recipe-photo-critique__breakdown-summary::marker { content: ""; }
.recipe-photo-critique__breakdown-summary:hover { color: var(--culinari-ink); }
.recipe-photo-critique__breakdown-summary:focus-visible {
  outline: none;
  border-radius: var(--radius-sm, 6px);
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}
.recipe-photo-critique__breakdown-caret {
  font-size: 0.75rem;
  transition: transform var(--dur-fast) var(--ease-soft);
}
.recipe-photo-critique__breakdown[open] .recipe-photo-critique__breakdown-caret {
  transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
  .recipe-photo-critique__breakdown-caret { transition: none; }
}

.recipe-photo-critique__failed {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.75rem;
  border-radius: var(--radius-md);
  background: rgba(220, 53, 69, 0.08);
  color: var(--culinari-danger-deep);
  font-size: 0.9375rem;
}

/* Loading skeleton: opacity pulse only; collapses under reduced-motion. */
.recipe-photo-critique__skeleton {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0.5rem 0;
}
.recipe-photo-critique__loading {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--culinari-primary-deep);
}
.recipe-photo-critique__loading i {
  font-size: 1rem;
}
.recipe-photo-critique__loading-hint {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--culinari-muted);
}
.recipe-photo-critique__skeleton-bars {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.recipe-photo-critique__skeleton-bar {
  height: 0.85rem;
  border-radius: var(--radius-pill);
  background: var(--culinari-line);
  animation: critique-skeleton-pulse 1.4s var(--ease-soft) infinite;
}
.recipe-photo-critique__skeleton-bar:nth-child(1) { width: 90%; }
.recipe-photo-critique__skeleton-bar:nth-child(2) { width: 70%; }
.recipe-photo-critique__skeleton-bar:nth-child(3) { width: 82%; }
@keyframes critique-skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .recipe-photo-critique__skeleton-bar { animation: none; opacity: 0.7; }
}

/* The drawer's run/refresh action is the primary tap target; clear the 44px
   floor on touch (Bootstrap's btn height is ~38px). */
@media (pointer: coarse) {
  .recipe-photo-critique__action {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* ---- Compare page: side-by-side critique columns ----- */
/* Each photo's critique is a discrete, flat-bordered unit (border, not shadow,
   at rest) so the side-by-side comparison reads as separable columns rather
   than one undivided scroll. A bounded unit per item is the right affordance
   for comparison; dimension rows inside use warm-thread dividers, not sub-cards
   (no nesting). */
.recipe-photo-critique-column {
  padding: var(--space-5);
  background: var(--culinari-surface);
  border: 1px solid var(--culinari-line-warm);
  border-radius: var(--radius-lg);
}
.recipe-photo-critique-column__title {
  margin: 0 0 0.75rem;
  color: var(--culinari-ink);
}
.recipe-photo-critique-column__img {
  display: block;
  width: 100%;
  max-height: 320px;
  object-fit: cover;
}

/* The highest-scoring photo, marked so the comparison resolves to a decision rather
   than leaving the cook to eyeball N columns. The terracotta border + trophy eyebrow
   carry the signal (border + icon + text, never color alone); one column of several
   keeps the One Voice terracotta scarce. */
.recipe-photo-critique-column--top {
  border-color: var(--culinari-primary);
}
.recipe-photo-critique-column__pick {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0 0 0.5rem;
}

/* Spent-quota affordance, now shown once (the run-all region) instead of per column:
   a muted, factual line, not a loud banner. The upgrade link carries the lone accent. */
.recipe-photo-critique__limit {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
}
.recipe-photo-critique__limit .bi-lock-fill {
  color: var(--culinari-muted);
}
.recipe-photo-critique__limit-cta {
  color: var(--accent-text);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}
.recipe-photo-critique__limit-cta:hover {
  text-decoration: underline;
}

/* Compare-page entry links (recipe show + photos field). They inherit the
   terracotta-deep link color but drop the underline, so give them an explicit
   interactive treatment: underline on hover, a visible focus ring. */
.recipe-photo-critiques__entry {
  color: var(--accent-text);
  text-decoration: none;
  border-radius: var(--radius-sm);
}
.recipe-photo-critiques__entry:hover {
  text-decoration: underline;
}
.recipe-photo-critiques__entry:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--culinari-primary-tint);
}

/* Calm the resting thumbnail without hiding the affordance. The tile already
   carries remove + drag + cover badge; on hover-capable pointers the feedback
   pill rests at a quiet 0.7 opacity (still legible, so mouse users discover it)
   and rises to full on tile hover or when any tile control is focused (keyboard
   reveal). On touch (no hover) it stays fully visible, since the 44px pill IS
   the tap target there. */
@media (hover: hover) and (pointer: fine) {
  .recipe-photos__critique {
    opacity: 0.7;
    transition: opacity var(--dur-fast) var(--ease-soft),
                background-color var(--dur-fast) var(--ease-soft),
                color var(--dur-fast) var(--ease-soft);
  }
  .recipe-photos__tile:hover .recipe-photos__critique,
  .recipe-photos__tile:focus-within .recipe-photos__critique {
    opacity: 1;
  }
}

.recipe-photos__add {
  aspect-ratio: 1 / 1;
  border: 2px dashed var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--culinari-surface);
  color: var(--culinari-muted);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  cursor: pointer;
  font-size: 0.85rem;
  transition: border-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft),
              background-color var(--dur-fast) var(--ease-soft);
}

.recipe-photos__add i {
  font-size: 1.5rem;
}

.recipe-photos__add:hover,
.recipe-photos__add:focus-visible {
  border-color: var(--culinari-primary);
  color: var(--accent-text);
  background: var(--accent-tint);
  outline: none;
}

.recipe-photos__add--disabled,
.recipe-photos__add[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.recipe-photos__add-label {
  font-weight: 500;
}

.recipe-photos__tile--uploading .recipe-photos__img {
  filter: brightness(0.55);
}

.recipe-photos__spinner {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

/* List-row toggle icon — slightly larger than body text so it reads
   as a tappable affordance on the lists/manage table rows. */
.list-row__toggle-icon {
  font-size: var(--fs-20);
  line-height: 1;
}

/* ============================================
   Lists index + show — type-led row vocabulary.
   Replaces the previous card.shadow-sm + table-light
   reflex on /lists and /lists/<id> with hairline-warm
   rows on cream-page, ghost row-action-btns, and a
   wishlist chip that earns its place instead of
   whispering in italic. See DESIGN.md §4
   (Flat-By-Default), §5 (row-action-btn, recipe-badge).
============================================ */

.lists-page__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 24px 0 20px;
  flex-wrap: wrap;
}

.lists-page__title {
  margin: 0;
}

.lists-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border-warm);
  margin-bottom: 32px;
}

.lists-list--system {
  margin-top: 8px;
  border-top: 0;
}

.lists-list__group-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 8px;
}

.lists-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto 96px;
  align-items: center;
  gap: 12px;
  padding: 12px 8px;
  position: relative;
  border-bottom: 1px solid var(--border-warm);
  transition: background-color var(--dur-fast) var(--ease-soft);
  min-height: 56px;
}

.lists-row:hover,
.lists-row:focus-within {
  background-color: var(--bg-wash);
}

.lists-row__type {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  color: var(--culinari-fg-muted);
  font-size: var(--fs-20);
  flex-shrink: 0;
  transition: color var(--dur-fast) var(--ease-soft);
}

.lists-row:hover .lists-row__type,
.lists-row:focus-within .lists-row__type {
  color: var(--accent-text);
}

/* System-managed rows (wishlist) carry the saved-bookmark thumb-tab in
   their type slot. The accent fill replaces the previous chip + hint
   stack so a single, confident signal stands for "this list is yours
   automatically". */
.lists-row__type--accent {
  color: var(--accent);
}

.lists-row:hover .lists-row__type--accent,
.lists-row:focus-within .lists-row__type--accent {
  color: var(--accent-text);
}

.lists-row__title {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex-wrap: wrap;
}

.lists-row__name {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--culinari-fg);
  line-height: 1.35;
  word-break: break-word;
}

.lists-row__link {
  color: inherit;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-soft);
}

.lists-row__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.lists-row__link:focus-visible {
  outline: none;
}

.lists-row__link:focus-visible::after {
  box-shadow: inset 0 0 0 2px var(--accent-text);
}

.lists-row:hover .lists-row__name,
.lists-row:focus-within .lists-row__name {
  color: var(--accent-text);
}

.lists-row__badge {
  pointer-events: none;
}

.lists-row__count {
  font-variant-numeric: tabular-nums;
  color: var(--culinari-fg-muted);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  text-align: right;
  min-width: max(5em, 6ch);
}

.lists-row__actions {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  position: relative;
  z-index: 2;
  min-width: 96px;
}

.lists-row__action {
  position: relative;
  z-index: 2;
}

.lists-row__system-hint {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--culinari-fg-muted);
}

/* Mobile: stack count + actions so the row stays scannable and the
   44×44 hit targets fit comfortably below 576px. */
@media (max-width: 575.98px) {
  .lists-row {
    grid-template-columns: 40px minmax(0, 1fr) auto;
    grid-template-areas:
      "type title actions"
      "type count actions";
    row-gap: 2px;
    padding: 10px 4px;
  }

  .lists-row__type { grid-area: type; align-self: center; }
  .lists-row__title { grid-area: title; align-self: end; }
  .lists-row__count { grid-area: count; text-align: left; align-self: start; }
  .lists-row__actions { grid-area: actions; align-self: center; }
}

/* Teaching empty state — PRODUCT.md: empty states teach the interface,
   not 'nothing here.' Type-led block, single terracotta CTA, no
   illustration. */
.lists-empty {
  padding: 40px 0 24px;
  max-width: 56ch;
}

.lists-empty__eyebrow {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 12px;
}

.lists-empty__title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 1.875rem);
  font-weight: 700;
  color: var(--culinari-fg);
  margin: 0 0 12px;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.lists-empty__body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  margin: 0 0 24px;
  max-width: 52ch;
}

.lists-empty__cta {
  margin-bottom: 8px;
}

/* Show-page empty state — same teaching block as the index empty,
   but framed for "this list is empty" rather than "you have no lists."
   Single primary CTA, no illustration filler. */
.lists-show__empty {
  padding: 40px 0 24px;
  max-width: 56ch;
}

.lists-show__empty__eyebrow {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 12px;
}

.lists-show__empty__title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 1.875rem);
  font-weight: 700;
  color: var(--culinari-fg);
  margin: 0 0 12px;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.lists-show__empty__body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--culinari-fg-muted);
  margin: 0 0 24px;
  max-width: 52ch;
}

.lists-show__empty__cta {
  margin-bottom: 8px;
}

/* Reorder hint sits below the form's items heading. Quiet line; says
   "the list saves itself" without taking the band's attention. */
.lists-form__reorder-hint {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--culinari-fg-muted);
  margin: -4px 0 12px;
}

/* List show — flatten the resource-row-card chrome so the items read
   as type-led entries on cream, mirroring the dashboard pattern. */
.lists-show__items {
  border-top: 1px solid var(--border-warm);
  margin-bottom: 32px;
  list-style: none;
  padding: 0;
}

.lists-show__item + .lists-show__item {
  border-top: 1px solid var(--border-warm);
}

.lists-show__item .resource-row-card {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: var(--space-3, 12px) 0;
}

.lists-show__item .resource-row-card:hover,
.lists-show__item .resource-row-card:focus-within {
  transform: none;
  box-shadow: none;
  background-color: var(--bg-wash);
}

/* List show — grid layout: CSS Grid container so each sortable child
   sits as a direct grid item. Auto-fill keeps columns flexible without
   relying on Bootstrap row chrome inside the sortable container.
   Actions live in the figcaption (always visible, muted at rest) so
   touch users never depend on hover to manage the list. */
.lists-show__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-6, 24px) var(--space-4, 16px);
  margin-bottom: 32px;
  list-style: none;
  padding-left: 0;
}

.lists-show__grid-item {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.lists-show__grid-thumb {
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.lists-show__grid-caption {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 12px;
  flex: 1 1 auto;
}

.lists-show__grid-title {
  font-size: 1.05rem;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.lists-show__grid-meta {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--culinari-fg-muted);
  margin: 0;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Empty-cover state in the grid: cream-on-cream is invisible against
   the page. Strengthen with a hairline and bump the glyph contrast so
   "no image" reads as a deliberate state, not loading. */
.lists-show__grid-item .resource-image--empty {
  border: 1px solid var(--border-warm);
  color: var(--culinari-fg-muted);
}

.lists-show__grid-item .resource-image--empty i.bi {
  color: var(--culinari-fg-muted);
  font-size: 2rem;
}

.lists-show__grid-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: auto;
  padding-top: 8px;
}

/* Lists pages — index rows (.lists-row__actions), show grid cells
   (.lists-show__grid-actions), and show row cards (.lists-show__items)
   all sit in the same visual family as recipe/book cards, where the
   canonical icon affordance (.bookmark-icon, .library-icon) renders as
   a pill on hover. Override .row-action-btn + .drag-handle's default
   rounded-md radius across the lists surfaces so edit/trash/drag share
   the same pill chrome. Scoped to lists containers so the same buttons
   keep rounded-md hover when row cards appear elsewhere (search
   results, recipe show, ingredient rows, etc.). */
.lists-row__actions .row-action-btn,
.lists-show__grid-actions .row-action-btn,
.lists-show__items .row-action-btn,
.lists-show__items .drag-handle {
  border-radius: var(--radius-pill);
}

@media (prefers-reduced-motion: reduce) {
  .lists-row,
  .lists-row__type,
  .lists-row__name,
  .lists-row__link {
    transition: none;
  }

  /* Grid items inherit .resource-card, which carries its own
     transition + hover transform at lines 280-287. Specificity hands
     the win to .resource-card unless we explicitly match the same
     compound selector here. */
  .lists-show__grid-item.resource-card,
  .lists-show__grid-item.resource-card:hover {
    transition: none;
    transform: none;
    box-shadow: none;
  }
}

/* ============================================
   Lists form — /lists/new and /lists/:id/edit.
   Flat surface on cream-page (no card chrome),
   bottom-border inputs from simple_form already,
   readonly type row when the list has items,
   ghost cancel. The .is-valid suppression below
   is scoped so the rest of the app keeps its
   Bootstrap valid-state painting.
============================================ */

.lists-form {
  max-width: 56ch;
}

.lists-form__fields {
  margin-bottom: 32px;
}

/* Suppress Bootstrap's at-rest valid-state painting (green check icon,
   green underline, padded-right space for the icon) on these forms.
   Bootstrap's own .form-select.is-valid selector is chained with two
   :not() filters, so we match the same shape to win specificity.
   Validation feedback should appear only when the user has done
   something actionable, not because the record is persisted or a
   default value is set. is-invalid painting is preserved. */
.lists-form .form-control.is-valid,
.lists-form .form-control.is-valid:focus {
  border-color: var(--border-hairline);
  background-image: none;
  padding-right: 0;
  box-shadow: none;
}

.lists-form .form-select.is-valid:not([multiple]):not([size]),
.lists-form .form-select.is-valid:not([multiple])[size="1"],
.lists-form .form-select.is-valid:focus:not([multiple]):not([size]) {
  border-color: var(--border-hairline);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23555555' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 16px 12px;
  padding-right: 2.25rem;
  box-shadow: none;
}

.lists-form .form-control:focus {
  border-color: var(--accent-text);
  box-shadow: 0 2px 0 0 var(--accent-tint);
}

.lists-form .form-select:focus,
.lists-form .form-select.is-valid:focus:not([multiple]):not([size]) {
  border-color: var(--accent-text);
  box-shadow: 0 0 0 1px var(--accent-text), 0 0 0 4px var(--accent-tint);
}

/* Read-only "Type: Recipes" row that replaces the disabled select
   when the list has items. Same vertical rhythm as a labeled input. */
.lists-form__readonly-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 0 16px;
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: 16px;
}

.lists-form__readonly-label {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--culinari-fg-muted);
}

.lists-form__readonly-value {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 500;
  color: var(--culinari-fg);
}

.lists-form__readonly-value i.bi {
  color: var(--culinari-fg-muted);
  font-size: var(--fs-20);
}

.lists-form__readonly-hint {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--culinari-fg-muted);
  margin-top: 4px;
}

.lists-form__items-heading {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--culinari-fg);
  margin: 32px 0 12px;
}

.lists-form__actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 24px;
}

.lists-form__cancel {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--culinari-fg-muted);
  text-decoration: none;
  padding: 10px 16px;
  border-radius: var(--radius-md);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.lists-form__cancel:hover,
.lists-form__cancel:focus-visible {
  background-color: var(--accent-tint);
  color: var(--accent-text);
  outline: none;
}

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

/* Recipe status badges — branded chip vocabulary (DESIGN.md Components).
   Quiet by default, terracotta-tint on the active states. Pair the hue
   with a glyph or label so meaning survives color blindness. */
.recipe-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  line-height: 1.4;
  text-transform: uppercase;
  white-space: nowrap;
}

.recipe-badge--draft {
  background-color: var(--bg-soft);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-hairline);
}

.recipe-badge--published {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

/* Private/"just me": metadata treatment (cream + muted + warm hairline), the
   same calm lane as --system/--translated so terracotta stays reserved for the
   public state the user reached through the gate. */
.recipe-badge--personal {
  background-color: var(--bg-cream);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-warm);
}

/* System-managed labels (wishlist, auto-generated lists). Metadata
   treatment, not earned-status: cream surface, muted text, warm hairline,
   normal case. Mirrors the --translated lane so the brand's terracotta
   voice stays reserved for things the user acted on. */
.recipe-badge--system {
  background-color: var(--bg-cream);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-warm);
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--fw-medium);
}

.recipe-badge--translated {
  background-color: var(--bg-cream);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-warm);
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--fw-medium);
}

.recipe-badge i.bi {
  font-size: var(--fs-14);
  color: inherit;
}

/* ============================================
   Publish drawer — audience choice + readiness
   checklist. Reuses the photo-critique drawer's
   calm, scrollable, no-backdrop temperature.
============================================ */
.recipe-publish-drawer .offcanvas-body {
  padding: 1.25rem;
  /* Cream, so the publish drawer reads as warm as its sibling critique drawer
     (Cream-First Rule); a white body would read SaaS-cold next to it. */
  background: var(--culinari-primary-cream);
}

/* Quiet escape hatch out of the pending_review one-way door: a ghost action,
   not a CTA — withdrawing is a step back, terracotta stays the forward voice. */
.recipe-publish__withdraw {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  padding: 6px 14px;
  color: var(--culinari-fg-muted);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.recipe-publish__withdraw:hover,
.recipe-publish__withdraw:focus-visible {
  background: var(--culinari-primary-tint);
  color: var(--culinari-primary-deep);
}

.recipe-publish__state-line {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--culinari-fg-muted);
  font-size: var(--fs-14);
  margin-bottom: 1rem;
}

.recipe-publish__checklist-title {
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--culinari-fg-muted);
  margin-bottom: 0.5rem;
}

.recipe-publish__checks { display: flex; flex-direction: column; gap: 6px; }

.recipe-publish__check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--fs-14);
  margin: 0;
}

.recipe-publish__check--todo { color: var(--culinari-fg-muted); }

.recipe-publish__pending {
  text-align: center;
  padding: 1.5rem 0.5rem;
}

.recipe-publish__pending-title {
  font-weight: var(--fw-semibold);
  margin-bottom: 0.35rem;
}

.recipe-publish__state {
  text-align: center;
  padding: 1rem 0.5rem 1.25rem;
}

.recipe-publish__state i.bi { font-size: var(--fs-28, 1.75rem); }

.recipe-publish__hint { margin-top: -0.25rem; margin-bottom: 0.75rem; }

/* Food-match verdict line inside the photo-critique result. */
.recipe-photo-critique__match {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-14);
  margin-top: 0.25rem;
}

.recipe-photo-critique__review-facts {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
}
.recipe-photo-critique__review-sep {
  color: var(--culinari-line-warm);
}

/* ============================================
   Admin publish-review queue

   Kitchen-notebook treatment for the moderation queue: hairline cards on the
   linen admin shell (no SaaS drop-shadow), the recipe-badge advisory lane
   (icon + label, never hue alone), terracotta as the single affirmative voice
   (Approve), chili reserved for the destructive path (Reject), and the
   documented bottom-border field for the reason. No Bootstrap badge/button
   color utilities — one saturated voice, the rest carried by structure.
   ============================================ */
.publish-reviews__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-6) 0 var(--space-5);
}

.publish-reviews__count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
}

.publish-reviews__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.publish-reviews__empty {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  background: var(--culinari-surface);
  border: 1px solid var(--culinari-hairline);
  border-radius: var(--radius-lg);
  color: var(--culinari-fg-muted);
}

.publish-reviews__empty i.bi {
  display: block;
  font-size: 2rem;
  margin-bottom: var(--space-2);
}

/* Only the queue's "all caught up" state earns the success green; History's
   empty state ("nothing reviewed yet") is neutral, not a success. */
.publish-reviews__empty--ok i.bi { color: var(--culinari-success-deep); }

/* Queue / History toggle sits in the page-header band, under the H1. */
.publish-reviews__scope {
  margin-bottom: var(--space-5);
}

/* History tab: a scannable log of decided recipes. Same kitchen-notebook
   treatment as the queue — hairline on linen, no SaaS shadow; the decision is an
   icon+label tag (never hue alone), the success voice for Approve and a quiet
   structural grey for the past Reject (chili stays reserved for the live
   destructive action, not a historical record). */
.publish-reviews__history {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--culinari-surface);
  border: 1px solid var(--culinari-hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.publish-reviews__history th,
.publish-reviews__history td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: middle;
  font-size: var(--fs-14);
  border-bottom: 1px solid var(--culinari-hairline);
}

.publish-reviews__history thead th {
  font-size: var(--fs-12);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--culinari-fg-muted);
}

.publish-reviews__history tbody tr:last-child td {
  border-bottom: 0;
}

.publish-reviews__history-recipe {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
  color: var(--culinari-fg);
  text-decoration: none;
}

.publish-reviews__history-recipe:hover span,
.publish-reviews__history-recipe:focus-visible span {
  text-decoration: underline;
}

.publish-reviews__history-thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
}

.publish-reviews__history-thumb--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--culinari-hairline);
  color: var(--culinari-fg-muted);
}

.publish-reviews__verdict {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  white-space: nowrap;
}

.publish-reviews__verdict i.bi { font-size: var(--fs-14); }
.publish-reviews__verdict--approved { color: var(--culinari-success-deep); }
.publish-reviews__verdict--rejected { color: var(--culinari-fg-muted); }

.publish-reviews__history-reason {
  max-width: 28rem;
  color: var(--culinari-fg-muted);
}

.publish-review {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  background: var(--culinari-surface);
  border: 1px solid var(--culinari-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

/* Bulk-select gutter: a 44x44 tap target on the card's left edge, so ticking a
   recipe never competes with the cover image or the decision footer. */
.publish-review__select {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: 0;
  cursor: pointer;
}

.publish-review__select .form-check-input {
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  cursor: pointer;
}

/* min-width:0 lets the Bootstrap .row inside shrink/wrap inside the flex item
   instead of forcing the card wider than its container. */
.publish-review__main {
  flex: 1 1 auto;
  min-width: 0;
}

.publish-review__cover {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 1px solid var(--culinari-line-warm);
}

.publish-review__cover--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-soft);
  color: var(--culinari-muted);
  font-size: 1.5rem;
}

.publish-review__title {
  font-size: var(--fs-20);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  color: var(--culinari-ink);
  margin: 0 0 var(--space-2);
}

.publish-review__meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-12);
  color: var(--culinari-muted);
  margin: 0 0 var(--space-1);
}

.publish-review__counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
  margin: var(--space-2) 0 var(--space-3);
}

.publish-review__counts span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* The deliberate route into the full recipe: a neutral outline button (real hit
   target), terracotta only on hover so Approve stays the single fill voice. */
.publish-review__view {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--space-2);
  padding: 0.625rem 1rem;
  border: 1px solid var(--culinari-hairline);
  border-radius: var(--radius-md);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  color: var(--culinari-ink);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.publish-review__view:hover,
.publish-review__view:focus-visible {
  border-color: var(--culinari-primary);
  color: var(--culinari-primary-deep);
  text-decoration: none;
}

.publish-review__advisory-heading {
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--culinari-muted);
  margin: 0 0 var(--space-2);
}

.publish-review__advisory {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.publish-review__advisory i.bi { font-size: var(--fs-14); }

.publish-review__advisory--pass {
  background: rgba(25, 135, 84, 0.12);
  color: var(--culinari-success-deep);
}

/* warning-deep text, not white-on-mustard: the latter fails contrast. */
.publish-review__advisory--fail {
  background: rgba(255, 193, 7, 0.18);
  color: var(--culinari-warning-deep);
}

.publish-review__advisory--pending {
  background: var(--bg-cream);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--culinari-line-warm);
}

.publish-review__critique { margin: var(--space-3) 0; }

/* Advisory re-run: a quiet utility beneath the critique — muted ink on a hairline,
   deliberately not competing with the Approve (terracotta) / Reject (chili) voices. */
.publish-review__recritique {
  margin-top: var(--space-2);
  background: transparent;
  color: var(--culinari-muted);
  border: 1px solid var(--culinari-hairline);
  font-weight: var(--fw-semibold);
}

.publish-review__recritique:hover,
.publish-review__recritique:focus-visible {
  background: var(--culinari-surface);
  color: var(--culinari-ink);
  border-color: var(--culinari-primary);
}

/* Decision footer: a hairline-separated band under the assessment. Reason field
   grows; the Approve/Reject pair stays a fixed cluster, the two opposing actions
   adjacent (the reject submit reaches its form via the form= attribute). */
.publish-review__decision {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--culinari-line-warm);
}

.publish-review__decision-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.publish-review__approve-form { display: flex; }
.publish-review__approve-btn { white-space: nowrap; }

/* Reject reason: the documented bottom-border field, not a boxed control. */
.publish-review__field-label {
  display: block;
  font-size: var(--fs-12);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--culinari-muted);
  margin-bottom: var(--space-1);
}

.publish-review__field {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--culinari-hairline);
  border-radius: 0;
  padding: var(--space-2) 0;
  font-size: var(--fs-14);
  color: var(--culinari-ink);
  resize: vertical;
  transition: border-color var(--dur-fast) var(--ease-soft);
}

.publish-review__field:focus {
  outline: none;
  border-bottom-color: var(--culinari-primary);
  box-shadow: 0 2px 0 0 var(--culinari-primary-tint);
  background: var(--culinari-surface);
}

.publish-review__field::placeholder { color: var(--culinari-placeholder); }

.publish-review__reject {
  flex: 1 1 18rem;
  margin: 0;
}

/* Reject = destructive-tertiary: chili text on a hairline outline, chili tint on
   hover. Terracotta stays the single affirmative voice (Approve). */
.publish-review__reject-btn {
  background: transparent;
  color: var(--culinari-danger-deep);
  border: 1px solid var(--culinari-hairline);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.publish-review__reject-btn:hover,
.publish-review__reject-btn:focus-visible {
  background: rgba(220, 53, 69, 0.08);
  color: var(--culinari-danger-deep);
  border-color: var(--culinari-danger);
}

/* Bulk-decision bar: tick recipes, then approve or reject the batch together.
   Builds on the shared .bulk-select-bar (sticky + surface bg + --stuck shadow);
   the kitchen-notebook frame (hairline card, no SaaS shadow) and one-voice color
   match the rest of the queue. Row 1 = selection summary, the decision controls
   wrap to their own full-width row so the shared reason has room to breathe. */
.publish-reviews__bulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: 1px solid var(--culinari-hairline);
  border-radius: var(--radius-lg);
}

.publish-reviews__bulk-all {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
  cursor: pointer;
  white-space: nowrap;
}

/* Count sits at the right of the summary row; the decision controls drop below. */
.publish-reviews__bulk .bulk-select-count { margin-left: auto; }

.publish-reviews__bulk-actions {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* Shared reject reason: the documented bottom-border field, growing to fill the
   row; it only feeds Reject (ignored by Approve). */
.publish-reviews__bulk-reason {
  flex: 1 1 16rem;
  min-width: 0;
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--culinari-hairline);
  border-radius: 0;
  padding: var(--space-2) 0;
  font-size: var(--fs-14);
  color: var(--culinari-ink);
  transition: border-color var(--dur-fast) var(--ease-soft);
}

.publish-reviews__bulk-reason:focus {
  outline: none;
  border-bottom-color: var(--culinari-primary);
  box-shadow: 0 2px 0 0 var(--culinari-primary-tint);
}

.publish-reviews__bulk-reason::placeholder { color: var(--culinari-placeholder); }

/* Keep author/reviewer emails from blowing out the history columns: wrap rather
   than force the table wider (no display name exists, so the email is the id). */
.publish-reviews__history-email {
  max-width: 16rem;
  overflow-wrap: anywhere;
}

.admin-shell-container {
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: 1rem;
}

@media (min-width: 1200px) {
  .admin-shell-container {
    padding-inline: 1.75rem;
  }
}

.admin-shell-sidebar {
  position: sticky;
  top: 1rem;
  align-self: flex-start;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
}

/* ============================================
   Print-files modal — template choice cards

   Replaces the name-only <select> so the user previews each template's
   look before a slow, print-bound build. The two specimen palettes
   mirror the PDF templates — Heritage (claret #6F2C2F on cream #FAF4E4)
   and Editorial (near-black #14120E on white) — and stay literal: they
   are PDF-fidelity, not design-system drift, the same exception the
   removed template-picker cards carried.
============================================ */

.print-template-choice {
  border: none;
  margin: 0;
  padding: 0;
}

.print-template-choice__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2);
}

@media (min-width: 480px) {
  .print-template-choice__grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* The whole card is the label; the radio is visually hidden but focusable. */
.print-template-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-2);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  background-color: var(--culinari-surface);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-soft),
              box-shadow var(--dur-fast) var(--ease-soft);
}

.print-template-card:hover {
  box-shadow: var(--shadow-sm);
}

.print-template-card:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.print-template-card:has(input:focus-visible) {
  box-shadow: 0 0 0 4px var(--accent-tint);
}

/* Keyboarding onto the already-selected card: keep the 1px accent ring that
   marks selection AND add the focus halo, instead of letting the later
   focus-visible rule replace the ring and lose the "this one is chosen" cue. */
.print-template-card:has(input:checked):has(input:focus-visible) {
  box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-tint);
}

.print-template-card input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.print-template-card__specimen {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-md);
  font-size: 0.95rem;
  line-height: 1.1;
  text-align: center;
}

.print-template-card__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.print-template-card__name {
  font-weight: var(--fw-semibold);
  color: var(--culinari-ink);
}

.print-template-card__desc {
  font-size: var(--fs-12);
  line-height: 1.4;
  color: var(--culinari-fg-muted);
}

/* Heritage ("classic") — serif claret on warm cream */
.print-template-card--classic .print-template-card__specimen {
  background-color: #FAF4E4;
  color: #6F2C2F;
  font-family: Georgia, 'Times New Roman', serif;
}

/* Editorial ("modern") — bold sans, near-black on white */
.print-template-card--modern .print-template-card__specimen {
  background-color: #FFFFFF;
  border: 1px solid var(--border-warm);
  color: #14120E;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* ============================================
   Cookbook Picture Page — image preview on edit

   Bootstrap's .img-thumbnail uses 1px gray border + 4px radius + 4px
   padding + white background. We mirror that intent with Culinari
   tokens so the preview matches the rest of the system.
============================================ */

.cookbook-picture-preview {
  display: block;
  max-height: 200px;
  width: auto;
  padding: var(--space-1);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  background-color: var(--culinari-surface);
}

/* ============================================
   Cookbook Page Forms — shared action row

   The four cookbook-page-form partials (recipe / section / text / picture)
   render a shared _form_actions partial with Submit + Cancel side by side.
   PT inflation is ~25–35%; min-width keeps both buttons readable without
   forcing a fixed width. On phones the buttons share the row equally.
============================================ */

.cookbook-page-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.cookbook-page-form__submit,
.cookbook-page-form__cancel {
  min-width: 132px;
}

@media (max-width: 575.98px) {
  .cookbook-page-form__submit,
  .cookbook-page-form__cancel {
    flex: 1 1 0;
    min-width: 0;
  }
}

/* ============================================
   Cookbook Index — editorial empty state

   When the cook has zero cookbooks, the page is not a centered SaaS card.
   It is a left-aligned reading column: display-heavy headline, one body
   paragraph that teaches the model, one eyebrow inventory line showing
   what fits inside (Recipes · Sections · Text · Pictures), one quiet
   primary CTA. No icons, no card, no exclamation marks.
============================================ */

.cookbooks-empty {
  padding: var(--space-10) 0 var(--space-16);
}

.cookbooks-empty__inner {
  max-width: 60ch;
}

.cookbooks-empty__headline {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: clamp(1.75rem, 3.5vw, 2.5rem);
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  color: var(--culinari-ink);
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}

.cookbooks-empty__body {
  font-size: var(--fs-16);
  line-height: var(--lh-body);
  color: var(--culinari-fg);
  margin: 0 0 var(--space-8);
}

.cookbooks-empty__cta {
  /* btn-primary carries the visual; just nudge the touch target on
     small viewports so the editorial column doesn't squish it. */
  min-width: 180px;
}

@media (max-width: 575.98px) {
  .cookbooks-empty {
    padding-block: var(--space-8) var(--space-12);
  }
}

@media (max-width: 319.98px) {
  /* Defensive: the 180px min-width can dominate sub-320px viewports
     (Apple Watch, fold-half states). Let the CTA fill the column there. */
  .cookbooks-empty__cta { width: 100%; }
}

/* ============================================
   Cookbook Show — header + numbered TOC

   The printed-cookbook-table-of-contents idiom from the landing page
   (see .landing-toc) brought inside the app: numbered rows, eyebrow page
   type, display-bold title, warm-thread dividers. The page numbers are
   the single saturated terracotta moment per row. Row controls live in
   .row-action-btn / .drag-handle (already styled); the rules here only
   compose the layout and the add-a-page popover.

   The header commits one terracotta CTA (Export PDF) and one ghost
   overflow icon (.bookmark-icon). The "Edit" / "Preview" / "Delete"
   actions live inside the overflow menu, never as competing buttons.
============================================ */

.cookbook-show__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--border-warm);
  margin-bottom: var(--space-10);
}

.cookbook-show__title-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
  flex: 1 1 auto;
}

.cookbook-show__title-block {
  min-width: 0;
}

.cookbook-show__title {
  margin: 0;
  word-break: break-word;
}

.cookbook-show__subtitle {
  margin: var(--space-1) 0 0;
  color: var(--culinari-muted);
  font-size: var(--fs-14);
  line-height: var(--lh-body);
  max-width: 60ch;
}

.cookbook-show__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.cookbook-show__cta {
  min-width: 132px; /* PT-length safe: "Ficheiros para impressão" */
  white-space: nowrap;
}

/* Preview ghost button. The in-progress verb a pro author hits many
   times per session, surfaced in the header so it is one click away
   instead of two-clicks-behind-overflow. Ghost styling so Export keeps
   the outlined-CTA visual weight. */
.cookbook-show__preview {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  background-color: transparent;
  color: var(--culinari-fg);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.cookbook-show__preview:hover,
.cookbook-show__preview:focus-visible {
  background-color: var(--accent-tint);
  color: var(--accent-text);
  text-decoration: none;
  outline: none;
}

.cookbook-show__preview:focus-visible {
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.cookbook-show__overflow {
  position: relative;
}

.cookbook-show__overflow-menu {
  min-width: 220px;
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-2);
  margin-top: var(--space-2);
}

.cookbook-show__overflow-menu .dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--culinari-fg);
  font-size: var(--fs-14);
  white-space: normal;
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.cookbook-show__overflow-menu .dropdown-item i.bi {
  font-size: var(--fs-16);
  width: 20px;
  flex-shrink: 0;
}

.cookbook-show__overflow-menu .dropdown-item:hover,
.cookbook-show__overflow-menu .dropdown-item:focus {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.cookbook-show__overflow-menu .dropdown-item--danger {
  color: var(--culinari-danger);
}

.cookbook-show__overflow-menu .dropdown-item--danger:hover,
.cookbook-show__overflow-menu .dropdown-item--danger:focus {
  background-color: rgba(220, 53, 69, 0.08);
  color: var(--culinari-danger);
}

.cookbook-show__overflow-menu .dropdown-divider {
  border-top-color: var(--border-warm);
  margin: var(--space-2) 0;
}

/* Contents section ---------------------------------------------------- */

.cookbook-show__contents-heading {
  display: block;
  margin: 0 0 var(--space-3);
}

/* Numbered TOC -------------------------------------------------------- */

.cookbook-toc {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: cookbook-toc;
  border-top: 1px solid var(--border-warm);
}

.cookbook-toc__entry {
  display: grid;
  /* Four-column row: drag-handle, counter numeral, copy, trailing
     actions. Drag is the leftmost element so the grip lands first for
     thumb and eye, matching how recipe-row reorders work elsewhere in
     the codebase. The numeral track is sized to fit a 2-digit page
     count tabular-num (≈ 22px) so the numeral hugs the title instead
     of floating in a wide empty cell. Picture-page rows expand to a
     5th column for the thumb (see :has() override below) so all row
     heights stay uniform. position: relative anchors the
     visible-on-focus keyboard hint chip. */
  position: relative;
  grid-template-columns: 36px 28px minmax(0, 1fr) auto;
  column-gap: var(--space-3);
  align-items: center;
  padding: var(--space-4) var(--space-3) var(--space-4) var(--space-4);
  border-bottom: 1px solid var(--border-warm);
  counter-increment: cookbook-toc;
  outline: none;
  transition: background-color var(--dur-fast) var(--ease-soft);
}

/* Picture-page rows get a dedicated thumb track so the thumbnail does
   not stretch the copy block vertically. Browsers without :has() fall
   back to the 4-column layout, which still works (the thumb sits in
   the copy column visually, just less tidy). */
.cookbook-toc__entry:has(.cookbook-toc__preview-thumb) {
  grid-template-columns: 36px 28px minmax(0, 1fr) 48px auto;
}

.cookbook-toc__entry:hover {
  background-color: var(--bg-wash);
}

.cookbook-toc__entry:focus-visible {
  /* Halo only — no border-radius. The row keeps its full-width hairline
     bottom, so rounding the corners on focus would leave a half-rounded
     shape sitting against a straight border. */
  background-color: var(--bg-wash);
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.cookbook-toc__handle {
  /* Override .drag-handle's 44×44 minimums for the dense TOC. Both axes
     shrink to 36px so the pill border-radius (set in the scoped block
     below) renders as a circle, matching .bookmark-icon's chrome. The
     hit area extends via the row's vertical padding (≈ 56px row
     height) so the touch target stays usable even though the visible
     glyph fits a 36 × 36 box. Color is inherited from .drag-handle
     base: faint warm-thread at rest, brightening to muted-fg on row
     hover/focus and accent on direct hover, same staircase as
     recipe-row. */
  min-width: 36px;
  width: 36px;
  min-height: 36px;
  height: 36px;
}

/* Mirror the recipe-row hover/focus staircase: the row brightening tells
   the cook which controls belong to the row their cursor is on without
   the handles and action buttons fighting for attention at rest. */
.cookbook-toc__entry:hover .cookbook-toc__handle,
.cookbook-toc__entry:focus-within .cookbook-toc__handle {
  color: var(--culinari-fg-muted);
}

.cookbook-toc__entry:hover .row-action-btn,
.cookbook-toc__entry:focus-within .row-action-btn {
  color: var(--culinari-fg);
}

.cookbook-toc__entry:hover .row-action-btn--danger,
.cookbook-toc__entry:focus-within .row-action-btn--danger {
  /* Danger keeps chili color even when the row brightens, so destruction
     stays visually distinct from the neutral edit button next to it. */
  color: var(--culinari-danger);
}

.cookbook-toc__index {
  font-family: var(--font-display);
  /* Match the row title's weight (700) and size (1.15rem) so the
     numeral reads as a quiet row index rather than a focal moment
     competing with the handle's hover pill and the title itself.
     Terracotta-deep is preserved as the cookbook-print brand cue.
     Left-aligned so the numeral hugs the handle column and reads as
     a row prefix; right-align would strand the digit mid-track with
     dead space on both sides. */
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  color: var(--accent-text);
  line-height: 1;
  text-align: left;
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.cookbook-toc__index::before {
  content: counter(cookbook-toc);
}

.cookbook-toc__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.cookbook-toc__eyebrow {
  /* .type-eyebrow handles font, color, spacing, weight */
  margin: 0;
}

.cookbook-toc__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  letter-spacing: -0.005em;
  word-break: break-word;
}

.cookbook-toc__preview {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  line-height: var(--lh-body);
  max-width: 60ch;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cookbook-toc__preview-thumb {
  /* Sibling of .cookbook-toc__copy now, sitting in its own grid track.
     The row's align-items: center vertically centers it against the
     title block; no margin-top needed. */
  width: 48px;
  height: 36px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  border: 1px solid var(--border-warm);
}

/* Visible-on-focus keyboard hint. aria-hidden, so screen-reader users
   only hear the row aria-label (which names the Arrow-key reorder).
   This chip is the sighted-keyboard cue: tab onto a row and a small
   "↑↓ Reorder" pill fades in at the row's trailing edge. Hidden on
   hover so it does not fight the action buttons that share the slot. */
.cookbook-toc__kbd-hint {
  position: absolute;
  top: 50%;
  right: var(--space-3);
  transform: translate(8px, -50%);
  z-index: 2;
  pointer-events: none;

  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background-color: var(--accent-tint);
  color: var(--accent-text);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  white-space: nowrap;

  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-soft),
              transform var(--dur-base) var(--ease-soft);
}

.cookbook-toc__kbd-keys {
  font-family: var(--font-mono);
  font-weight: var(--fw-bold);
  font-size: var(--fs-12);
  letter-spacing: 0;
}

.cookbook-toc__entry:focus-visible .cookbook-toc__kbd-hint {
  opacity: 1;
  transform: translate(0, -50%);
}

.cookbook-toc__entry:hover .cookbook-toc__kbd-hint {
  opacity: 0;
}

.cookbook-toc__actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Cookbook-show TOC entries sit in the same visual family as recipe/book
   cards and the lists surfaces, where the canonical icon affordance
   (.bookmark-icon, .library-icon) renders as a pill on hover. Override
   .row-action-btn + .drag-handle's default rounded-md radius inside the
   cookbook-toc so edit / trash / drag share the same pill chrome.
   Scoped to the cookbook-toc container so the same buttons keep
   rounded-md hover elsewhere in the app (recipe-row, search results,
   ingredient rows, etc.). Mirrors the lists scoped override above. */
.cookbook-toc__actions .row-action-btn,
.cookbook-toc__handle {
  border-radius: var(--radius-pill);
}

/* Lifted state — applied by cookbook_toc_controller while the row is
   in keyboard pickup mode, and by Sortable.js (.sortable-chosen) during
   pointer drag. Same visual treatment so keyboard and mouse users see
   the row "held" in the same way. */
.cookbook-toc__entry.sortable-chosen,
.cookbook-toc__entry--lifted {
  background-color: var(--bg-cream);
  box-shadow: var(--shadow-drag);
  border-radius: var(--radius-md);
  position: relative;
  z-index: 1;
}

.cookbook-toc__entry.sortable-ghost {
  opacity: 0.4;
}

/* Empty state — legacy class kept for any future surfaces that want a
   one-line empty paragraph. The cookbook-show empty branch uses the
   richer .cookbook-show__empty panel below. */
.cookbook-toc__empty {
  margin: 0;
  padding: var(--space-5) var(--space-3);
  color: var(--culinari-muted);
  font-style: italic;
  font-size: var(--fs-16);
  border-top: 1px solid var(--border-warm);
  border-bottom: 1px solid var(--border-warm);
  max-width: 60ch;
}

/* Cookbook-show empty panel. A single warm surface where the cookbook
   would have begun: heading + poetic subtitle + one primary CTA (Add a
   recipe, the statistical default) + a discreet dropdown for the other
   three page types. First-time authors get one obvious next step
   instead of an italic line and a small "+" row to find on their own. */
.cookbook-show__empty {
  background-color: var(--bg-cream);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  padding: var(--space-8) var(--space-6);
  max-width: 560px;
  margin: var(--space-3) 0 var(--space-10);
}

.cookbook-show__empty-heading {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: 1.5rem;
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  letter-spacing: -0.01em;
}

.cookbook-show__empty-copy {
  margin: 0 0 var(--space-6);
  color: var(--culinari-muted);
  font-size: var(--fs-16);
  line-height: var(--lh-body);
  max-width: 52ch;
}

/* Add-a-page row + popover ------------------------------------------- */

.cookbook-toc__add-wrap {
  position: relative;
}

.cookbook-toc__add-trigger {
  display: grid;
  /* Mirror the entry's [drag] [counter] [copy] layout so the "+" lines
     up with the numeric counters above. The first slot is an empty
     spacer — the add row is never draggable. */
  grid-template-columns: 36px 28px minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: center;
  width: 100%;
  padding: var(--space-4) var(--space-3) var(--space-4) var(--space-4);
  border: none;
  border-bottom: 1px solid var(--border-warm);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.cookbook-toc__add-handle-spacer {
  /* Reserves the drag-handle column on the add row without rendering a
     button there. The width is set by the grid track. */
}

.cookbook-toc__add-trigger:hover {
  background-color: var(--bg-wash);
}

.cookbook-toc__add-trigger:focus-visible {
  /* Halo only — see .cookbook-toc__entry:focus-visible. */
  outline: none;
  background-color: var(--bg-wash);
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.cookbook-toc__add-index {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  color: var(--accent-text);
  line-height: 1;
  text-align: left;
}

.cookbook-toc__add-label {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  color: var(--culinari-fg);
  letter-spacing: -0.005em;
}

.cookbook-toc__add-menu {
  min-width: 280px;
  max-width: 360px;
  padding: var(--space-2);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  margin-top: var(--space-1);
}

.cookbook-toc__add-option {
  display: flex !important;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: var(--space-3) !important;
  border-radius: var(--radius-md);
  white-space: normal !important;
  color: var(--culinari-fg);
  transition: background-color var(--dur-fast) var(--ease-soft),
              color var(--dur-fast) var(--ease-soft);
}

.cookbook-toc__add-option:hover,
.cookbook-toc__add-option:focus {
  background-color: var(--accent-tint);
  color: var(--accent-text);
}

.cookbook-toc__add-option-name {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-16);
  color: inherit;
  line-height: var(--lh-snug);
}

.cookbook-toc__add-option-description {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  line-height: var(--lh-body);
}

.cookbook-toc__add-option:hover .cookbook-toc__add-option-description,
.cookbook-toc__add-option:focus .cookbook-toc__add-option-description {
  color: var(--accent-text);
}

.cookbook-toc__add-menu .dropdown-divider {
  border-top-color: var(--border-warm);
  margin: var(--space-2) 0;
}

/* Mobile (< 576px) — denser numbers, always-visible actions ----------- */
@media (max-width: 575.98px) {
  .cookbook-show__header {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }

  /* Group the action controls at the start of the row instead of
     spreading them edge-to-edge. Combined with letting Export size to
     its content (no flex stretch), this keeps the cookbook title and
     contents as the dominant reading surface on mobile, where Export
     is the wrong primary verb. */
  .cookbook-show__actions {
    justify-content: flex-start;
    flex-wrap: wrap;
  }

  .cookbook-show__cta {
    /* No stretch on mobile: let Export size to its label so it stops
       dominating the small-screen header. */
    min-width: 0;
  }

  .cookbook-show__preview {
    height: 36px;
    padding: 0 var(--space-3);
  }

  .cookbook-toc__entry {
    grid-template-columns: 32px 28px minmax(0, 1fr) auto;
    column-gap: var(--space-2);
    padding-block: var(--space-3);
  }

  /* Picture-row thumb track on mobile: smaller thumb, narrower track. */
  .cookbook-toc__entry:has(.cookbook-toc__preview-thumb) {
    grid-template-columns: 32px 28px minmax(0, 1fr) 40px auto;
  }

  .cookbook-toc__preview-thumb {
    width: 40px;
    height: 30px;
  }

  .cookbook-toc__add-trigger {
    grid-template-columns: 32px 28px minmax(0, 1fr);
    column-gap: var(--space-2);
    padding-block: var(--space-3);
  }

  .cookbook-toc__handle {
    min-width: 32px;
    width: 32px;
  }

  .cookbook-toc__index,
  .cookbook-toc__add-index {
    font-size: 1.05rem;
  }

  .cookbook-toc__title,
  .cookbook-toc__add-label {
    font-size: 1rem;
  }

  .cookbook-show__empty {
    padding: var(--space-6) var(--space-4);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cookbook-toc__entry,
  .cookbook-toc__add-trigger,
  .cookbook-show__overflow-menu .dropdown-item,
  .cookbook-toc__add-option,
  .cookbook-toc__kbd-hint,
  .cookbook-show__preview {
    transition: none;
  }
}

/* ============================================
   Cookbooks Index — TOC-style list

   The /cookbooks index is a contents-page-of-cookbooks: the same
   numbered-TOC pattern that powers the show-page TOC, one level up.
   Replaces the 3-up tile grid that violated DESIGN.md's
   identical-card-grids ban and the One Voice Rule.

   Each row is a single `<a>` so the entire row is the tap target — no
   nested pencil/edit icon competing with the title. Cover swatch on the
   left in a 2:3 portrait crop (cookbook-spine shape), title in display
   weight, meta row underneath. Edit/delete reach via the cookbook show
   page's overflow menu.
============================================ */

.cookbooks-index__new-cta {
  /* PT inflation: "Novo livro de receitas" runs ~3× the English label;
     min-width keeps the button readable without forcing fixed width. */
  min-width: 144px;
  white-space: nowrap;
}

.cookbooks-index__sort {
  /* Wraps cleanly when PT pill labels exceed the row width on mobile. */
  flex-wrap: wrap;
  row-gap: var(--space-2);
  margin-bottom: var(--space-6);
}

.cookbook-index {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border-warm);
}

.cookbook-index > li {
  display: block;
}

.cookbook-index__entry {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto;
  column-gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-3) var(--space-4) 0;
  border-bottom: 1px solid var(--border-warm);
  color: inherit;
  text-decoration: none;
  outline: none;
  transition: background-color var(--dur-fast) var(--ease-soft);
}

.cookbook-index__entry:hover {
  background-color: var(--bg-wash);
  text-decoration: none;
  color: inherit;
}

.cookbook-index__entry:focus-visible {
  background-color: var(--bg-wash);
  box-shadow: 0 0 0 4px var(--accent-tint);
}

.cookbook-index__cover {
  display: block;
  width: 48px;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background-color: var(--bg-wash);
  border: 1px solid var(--border-warm);
  /* Cookbook-spine shadow — terracotta-tinted lift on the small cover so
     it reads as a printed spine on a shelf, not a flat thumbnail. */
  box-shadow: 0 2px 6px rgba(168, 68, 48, 0.10);
}

.cookbook-index__cover-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cookbook-index__cover--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  text-align: center;
}

.cookbook-index__cover-empty-title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 9px;
  line-height: 1.1;
  letter-spacing: -0.005em;
  color: var(--accent-text);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.cookbook-index__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.cookbook-index__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.15rem;
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  letter-spacing: -0.005em;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cookbook-index__entry:hover .cookbook-index__title,
.cookbook-index__entry:focus-visible .cookbook-index__title {
  color: var(--accent-text);
}

.cookbook-index__subtitle {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
  line-height: var(--lh-body);
  max-width: 60ch;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cookbook-index__meta {
  font-size: var(--fs-13);
  color: var(--culinari-muted);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  align-items: baseline;
}

.cookbook-index__meta-sep {
  color: var(--culinari-silver);
}

.cookbook-index__chevron {
  color: var(--culinari-silver);
  font-size: var(--fs-16);
  transition: color var(--dur-fast) var(--ease-soft),
              transform var(--dur-fast) var(--ease-soft);
}

.cookbook-index__entry:hover .cookbook-index__chevron,
.cookbook-index__entry:focus-visible .cookbook-index__chevron {
  color: var(--accent-text);
  transform: translateX(2px);
}

@media (max-width: 575.98px) {
  .cookbook-index__entry {
    grid-template-columns: 40px minmax(0, 1fr) auto;
    column-gap: var(--space-3);
    padding-block: var(--space-3);
  }
  .cookbook-index__cover {
    width: 40px;
  }
  .cookbook-index__title {
    font-size: 1rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cookbook-index__entry,
  .cookbook-index__chevron {
    transition: none;
  }
  .cookbook-index__entry:hover .cookbook-index__chevron,
  .cookbook-index__entry:focus-visible .cookbook-index__chevron {
    transform: none;
  }
}

/* ============================================
   Cookbook cover-image hint (new / edit forms)

   Quiet aspect-ratio guidance under the cover-image input so cooks
   know what shape to upload. Keeps the cover decision in their hands
   without nagging.
============================================ */

.cookbook-cover-hint {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3);
  background-color: var(--bg-wash);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-md);
}

.cookbook-cover-hint__mock {
  width: 32px;
  aspect-ratio: 2 / 3;
  background-color: var(--culinari-surface);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.cookbook-cover-hint__text {
  font-size: var(--fs-13);
  color: var(--culinari-fg-muted);
  line-height: var(--lh-body);
  margin: 0;
}

/* ============================================
   Danger zone — destructive-action framing

   Used on the cookbook edit page to separate the destroy action from
   the form. Eyebrow + chili hairline + minimal padding so the section
   reads as a warning region without becoming a banner. The button text
   itself stays unchanged (still uses t(:destroy) so existing tests find
   the link).
============================================ */

.danger-zone {
  margin-top: var(--space-10);
  padding-top: var(--space-5);
  border-top: 1px solid rgba(220, 53, 69, 0.25);
}

.danger-zone__eyebrow {
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--culinari-danger);
  margin: 0 0 var(--space-2);
}

.danger-zone__body {
  font-size: var(--fs-14);
  color: var(--culinari-fg-muted);
  line-height: var(--lh-body);
  margin: 0 0 var(--space-3);
  max-width: 60ch;
}

/* ============================================
   Pipeline status badge — admin recipe export/import + similar async flows.

   Replaces Bootstrap default bg-success / bg-warning / bg-danger badges so
   pipeline statuses (pending / building / processing / ready / completed /
   failed / succeeded / skipped) read in Culinari's status palette
   (herb / mustard / chili) with the Hue-Plus-Mark rule satisfied via icon
   + uppercase label.
============================================ */

.pipeline-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  line-height: 1.4;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
}

.pipeline-status__icon {
  display: inline-flex;
  align-items: center;
  font-size: 1em;
  line-height: 1;
}

.pipeline-status--neutral {
  background: var(--bg-soft);
  color: var(--culinari-fg-muted);
  border-color: var(--border-hairline);
}

.pipeline-status--inflight {
  background: rgba(255, 193, 7, 0.18);
  color: var(--culinari-warning-deep);
  border-color: rgba(255, 193, 7, 0.35);
}

.pipeline-status--success {
  background: rgba(25, 135, 84, 0.10);
  color: var(--culinari-success-deep);
  border-color: rgba(25, 135, 84, 0.20);
}

.pipeline-status--failure {
  background: rgba(220, 53, 69, 0.12);
  color: var(--culinari-danger-deep);
  border-color: rgba(220, 53, 69, 0.30);
}

.pipeline-status--lg {
  padding: 6px 14px;
  font-size: var(--fs-13);
}

/* ============================================
   Pipeline surfaces — flat-by-default cards for the admin pipeline UIs.

   Replaces `card border-0 shadow-sm` on cream-page so pipeline tables and
   action heroes read as paper-on-paper instead of floating SaaS cards.
   Shadow arrives only on hover/drag/focus elsewhere in the system.
============================================ */

.pipeline-surface {
  background: var(--culinari-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
}

.pipeline-surface--wash {
  background: var(--bg-wash);
  border-color: var(--border-warm);
}

.pipeline-surface--success-wash {
  background: rgba(25, 135, 84, 0.06);
  border-color: rgba(25, 135, 84, 0.22);
}

.pipeline-surface--failure {
  background: var(--culinari-surface);
  border-color: rgba(220, 53, 69, 0.35);
}

/* Clip variant — rounds inner full-bleed content (e.g. a flush data table) to
   the surface corners. Safe with .table-scroller, which owns its own vertical
   scroll, so hiding the surface overflow does not disable scrolling. */
.pipeline-surface--clip {
  overflow: hidden;
}

/* ============================================
   Admin detail list — grouped key/value pairs for show-page sidebars.

   Replaces the <p><strong>label:</strong> value</p> wall with a real <dl>:
   quiet uppercase pencil labels above ink values, chunked into groups split
   by a warm-thread divider. The label stays light; the value carries weight.
============================================ */
.admin-detail-list {
  margin: 0;
}

.admin-detail-list__group {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.admin-detail-list__group + .admin-detail-list__group {
  margin-top: 0.875rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--border-warm);
}

.admin-detail-list dt {
  margin-bottom: 0.125rem;
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* These dt terms carry data keys an admin scans, not decorative labels, so
     they use fg-muted (#555, ~7:1 on white) instead of muted (#7F7F7F, ~4.0:1)
     to clear WCAG AA 4.5:1. */
  color: var(--culinari-fg-muted);
}

.admin-detail-list dd {
  margin: 0;
  color: var(--culinari-fg);
  line-height: 1.4;
}

/* ============================================
   Pipeline action band — replaces the redundant status hero on show pages.

   Ready: anchors the download CTA + filename + size.
   Failed: anchors retry + re-upload buttons + a one-line cause.
   In-flight: anchors a spinner + the live "N of M processed" progress.
============================================ */

.pipeline-action {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

@media (min-width: 768px) {
  .pipeline-action {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.pipeline-action__copy {
  min-width: 0;
}

.pipeline-action__heading {
  margin: 0 0 var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--culinari-muted);
}

.pipeline-action__title {
  margin: 0;
  font-size: var(--fs-18, 1.125rem);
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg);
  line-height: 1.3;
  word-break: break-word;
}

.pipeline-action__meta {
  margin: var(--space-1) 0 0;
  color: var(--culinari-muted);
  font-size: var(--fs-13);
}

.pipeline-action__buttons {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  flex-shrink: 0;
}

.pipeline-action__inflight {
  display: flex;
  /* Spinner sits beside its label at every breakpoint. Without these, the
     parent .pipeline-action's row-mode justify-content: space-between (>=768px)
     flings the spinner to the far edge, marooning it from the text it belongs
     to. flex-start keeps the pair together; row holds the layout below 768px,
     where the base .pipeline-action would otherwise stack them. */
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
  gap: var(--space-3);
}

/* Calm, on-brand spinner instead of Bootstrap's cool text-secondary gray. */
.pipeline-action__inflight .spinner-border {
  color: var(--culinari-muted);
}

/* Pre-order warnings (overflow / low-DPI) are full sentences, not labels. Cap
   the measure so they read at ~70ch instead of stretching the full surface
   width (~140ch), and give the items a little air between lines. */
.pipeline-warnings {
  max-width: 72ch;
}

.pipeline-warnings li + li {
  margin-top: var(--space-1);
}

.pipeline-progress {
  margin: 6px 0 0;
  font-variant-numeric: tabular-nums;
  color: var(--culinari-fg);
  font-weight: var(--fw-medium, 500);
}

.pipeline-progress__bar {
  margin-top: 6px;
  width: 100%;
  max-width: 320px;
  height: 4px;
  background: var(--border-hairline);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.pipeline-progress__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-pill);
  transition: width 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* ============================================
   Mustard alert — Hue-Plus-Mark warning for high-stakes admin inline notices
   (e.g. the recipe-import overwrite consequence). Replaces Bootstrap's
   default yellow alert-warning so the moment reads in the mustard palette.
============================================ */

.alert-mustard {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  background: rgba(255, 193, 7, 0.14);
  color: var(--culinari-warning-deep);
  border: 1px solid rgba(255, 193, 7, 0.30);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-14);
  line-height: 1.5;
}

.alert-mustard__icon {
  flex-shrink: 0;
  margin-top: 2px;
  font-size: 1.1em;
  color: var(--culinari-warning-deep);
}

/* ============================================
   Bulk-select count chip — always rendered so the disabled-submit precondition
   is visible. Pairs with aria-live="polite" so screen readers hear the count
   change as rows are ticked.
============================================ */

.bulk-select-count {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  background: var(--bg-wash);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium, 500);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.bulk-select-count--armed {
  background: var(--accent-tint);
  color: var(--accent-text);
  border-color: rgba(217, 119, 87, 0.20);
}

/* Sticky bulk-action bar — keeps the submit reachable on long admin tables. */
.bulk-select-bar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--culinari-surface);
}

/* ============================================
   Strategy radio group — recipe-import "If a recipe already exists" choice.
   Renders three options vertically with a one-line consequence under each
   label. The overwrite option carries a permanent mustard note so admins
   see the destructive consequence before they pick.
============================================ */

.strategy-radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.strategy-radio {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--culinari-surface);
  cursor: pointer;
  transition: border-color 150ms var(--ease-soft, ease), background-color 150ms var(--ease-soft, ease);
}

.strategy-radio:hover {
  border-color: var(--accent);
  background: var(--bg-wash);
}

.strategy-radio:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.strategy-radio input[type="radio"] {
  margin-top: 4px;
  accent-color: var(--accent);
  flex-shrink: 0;
}

.strategy-radio__body {
  flex: 1;
  min-width: 0;
}

.strategy-radio__label {
  display: block;
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg);
  margin-bottom: 2px;
}

.strategy-radio__desc {
  display: block;
  font-size: var(--fs-13);
  color: var(--culinari-fg-muted);
  line-height: 1.5;
}

/* Hover changes only the border so the unselected option doesn't compete
   with the selected one's surface for visual weight. */
.strategy-radio:hover {
  background: var(--culinari-surface);
}

/* Divider between the drawing's "iterate" (re-render) and "start fresh /
   discard" actions. Warm-thread line on the cream/success-wash surface, full
   opacity (Bootstrap's <hr> defaults to 0.25 and a cool color). */
.drawing-actions-divider {
  margin: var(--space-4) 0 var(--space-3);
  border: 0;
  border-top: 1px solid var(--border-warm);
  opacity: 1;
}

/* ============================================
   Pipeline action band — additions.

   __message is the headline-without-eyebrow variant. Use when the action
   band carries a single load-bearing sentence (e.g. "Imported 5 recipes
   cleanly", "Bundle couldn't be processed") instead of the eyebrow+title
   pair the ready-state uses. Color modifiers use Culinari semantic tokens
   so the message reads in herb/chili rather than Bootstrap success/danger.
============================================ */

.pipeline-action__message {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-18, 1.125rem);
  font-weight: var(--fw-semibold);
  color: var(--culinari-fg);
  line-height: 1.3;
  word-break: break-word;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.pipeline-action__message--success { color: var(--culinari-success-deep); }
.pipeline-action__message--danger  { color: var(--culinari-danger-deep); }

.pipeline-action__message-icon {
  flex-shrink: 0;
  font-size: 1.1em;
  line-height: 1;
}

/* ============================================
   Pipeline stacktrace — replaces `bg-light` on the <pre> error blocks so
   the failure surface keeps its temperature. Hairline border picks up
   the same line treatment as pipeline-surface and the strategy radios.
============================================ */

.pipeline-stacktrace {
  background: var(--bg-soft);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  margin: var(--space-2) 0 0;
  max-height: 240px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  color: var(--culinari-fg);
  line-height: 1.5;
}

/* ============================================
   Pipeline filter — pill-toggle (--surface variant) for entry-status
   filters on the imports show page. Lives in the grid-control band beside
   the entries table, so per the pill-toggle convention it uses the
   --surface treatment (lift + hairline) rather than the page-header ghost.

   Active-state color follows the same token map as pipeline-status so a
   filter chip and the badges it filters read in the same color family.
============================================ */

.pipeline-filter {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--bg-wash);
  border: 1px solid var(--border-warm);
  border-radius: var(--radius-pill);
}

.pipeline-filter__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  color: var(--culinari-fg-muted);
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: background-color 150ms var(--ease-soft),
              border-color 150ms var(--ease-soft),
              color 150ms var(--ease-soft);
}

.pipeline-filter__chip:hover {
  color: var(--culinari-fg);
}

.pipeline-filter__chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pipeline-filter__chip.is-active {
  background: var(--culinari-surface);
  border-color: var(--border-hairline);
  color: var(--culinari-fg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.pipeline-filter__chip--success.is-active { color: var(--culinari-success-deep); }
.pipeline-filter__chip--inflight.is-active { color: var(--culinari-warning-deep); }
.pipeline-filter__chip--failure.is-active { color: var(--culinari-danger-deep); }

/* ============================================
   Usage pill — glyph slot so the role (ok / warn / full) isn't carried
   by color alone. Keeps Hue-Plus-Mark consistent with pipeline-status
   badges sitting on the same row.
============================================ */

.usage-pill__icon {
  font-size: 0.85em;
  line-height: 1;
  margin-right: 2px;
}

/* ============================================
   Bulk-select bar — scroll shadow toggled by the IntersectionObserver
   sentinel above the bar (added by bulk_select_controller). Lets the
   operator notice the bar has pinned to the top.
============================================ */

.bulk-select-bar--stuck {
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.06);
}

/* ============================================
   Pricing — cookbook editions, editorial single column.
   Replaces the SaaS card-grid pricing template. The voice is
   "two editions, one shelf" — frame each tier as a named edition,
   not a feature checklist.
============================================ */

.pricing-page {
  max-width: 880px;
  margin-inline: auto;
  padding-block: var(--space-12) var(--space-16);
}

.pricing-hero {
  margin-bottom: var(--space-12);
}

.pricing-hero .type-eyebrow {
  margin-bottom: var(--space-3);
}

.pricing-hero__title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-hero);
  line-height: var(--lh-tight);
  color: var(--fg-1);
  margin-bottom: var(--space-4);
}

@media (min-width: 768px) {
  .pricing-hero__title {
    font-size: var(--fs-hero-lg);
  }
}

.pricing-hero__intro {
  font-size: var(--fs-18);
  line-height: var(--lh-body);
  color: var(--fg-2);
  max-width: 52ch;
  margin: 0;
}

.pricing-edition {
  padding-block: var(--space-10);
}

.pricing-edition__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}

.pricing-edition__name {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  color: var(--fg-1);
  margin: 0;
}

.pricing-edition__price {
  margin: 0;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  color: var(--fg-2);
}

.pricing-edition__price-amount {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-24);
  color: var(--fg-1);
  line-height: 1;
}

.pricing-edition__price-period {
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

.pricing-edition__pitch {
  font-size: var(--fs-16);
  line-height: var(--lh-body);
  color: var(--fg-2);
  margin-bottom: var(--space-6);
  max-width: 56ch;
}

/* Leading line on the Collector's edition that frames the capability
   list as "Basics + delta," so the upgrade math is on the page instead
   of in the reader's head. Quieter than the pitch (smaller) but still
   in the body color family. */
.pricing-edition__includes {
  font-size: var(--fs-14);
  line-height: var(--lh-body);
  color: var(--culinari-muted);
  margin: 0 0 var(--space-3) 0;
}

.pricing-edition__capabilities {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-6) 0;
  border-top: 1px solid var(--border-warm);
}

.pricing-edition__capabilities li {
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--border-warm);
  font-size: var(--fs-15);
  line-height: var(--lh-body);
  color: var(--fg-2);
}

.pricing-edition__status {
  display: inline-block;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0;
  padding: 8px 14px;
  background: var(--accent-tint);
  border-radius: var(--radius-pill);
}

.pricing-edition__billing {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
}

.pricing-edition__billing-label {
  font-size: var(--fs-13);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--culinari-muted);
}

/* Shown to anonymous / premium / no-Paddle visitors in place of the
   interactive toggle — preserves the information that annual billing
   exists without rendering a control that would do nothing on click. */
.pricing-edition__billing-static {
  font-size: var(--fs-15);
  line-height: var(--lh-body);
  color: var(--fg-2);
  margin-bottom: var(--space-6);
  max-width: 56ch;
}

/* Annual-discount chip inside the billing pill-toggle. The toggle itself
   uses the shared .pill-toggle--surface vocabulary; only the discount
   badge is pricing-specific. */
.pricing-billing-discount {
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--accent-text);
  background: var(--accent-tint);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  letter-spacing: var(--tracking-label);
}

.pricing-edition__cta {
  margin-top: var(--space-2);
}

.pricing-edition__cta-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 14px 28px;
  font-family: inherit;
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  color: var(--culinari-surface);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-soft),
              transform var(--dur-fast) var(--ease-soft),
              box-shadow var(--dur-base) var(--ease-soft);
  box-shadow: var(--shadow-md);
}

.pricing-edition__cta-button:hover,
.pricing-edition__cta-button:focus-visible {
  background: var(--accent-hover);
  color: var(--culinari-surface);
  box-shadow: var(--shadow-cta);
  outline: none;
}

.pricing-edition__cta-button:active {
  background: var(--accent-active);
  transform: translateY(1px);
}

.pricing-edition__cta-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  box-shadow: none;
}

.pricing-edition__item-summary {
  margin-bottom: var(--space-3);
}

.pricing-divider {
  border: 0;
  border-top: 1px solid var(--border-warm);
  margin: 0;
}

.pricing-checkout {
  margin-top: var(--space-12);
  padding-top: var(--space-10);
  border-top: 1px solid var(--border-warm);
}

.pricing-checkout__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-20);
  color: var(--fg-1);
  margin-bottom: var(--space-5);
}

.pricing-success {
  margin-top: var(--space-12);
  padding: var(--space-10);
  background: var(--bg-cream);
  border-radius: var(--radius-xl);
  text-align: center;
}

.pricing-success__icon {
  font-size: 2.5rem;
  color: var(--culinari-success-deep);
  display: block;
  margin-bottom: var(--space-3);
}

.pricing-success__title {
  font-family: var(--font-display);
  font-weight: var(--fw-heavy);
  font-size: var(--fs-h2);
  color: var(--fg-1);
  margin-bottom: var(--space-3);
}

.pricing-success__body {
  color: var(--fg-2);
  margin-bottom: var(--space-6);
}

.pricing-success__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 24px;
  font-weight: var(--fw-semibold);
  color: var(--culinari-surface);
  background: var(--accent);
  border-radius: var(--radius-pill);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-soft);
}

.pricing-success__cta:hover,
.pricing-success__cta:focus-visible {
  background: var(--accent-hover);
  color: var(--culinari-surface);
}

/* Honest error surface for the Paddle flow — sits at the top of the
   checkout band as a chili-color note so a silent CDN block, a thrown
   Checkout.open, or a checkout.error event doesn't leave the cook
   staring at a dead button. Paired with bi-x-circle in copy + role=alert
   so the meaning survives color blindness (Hue-Plus-Mark Rule). */
.pricing-checkout__error {
  margin: var(--space-6) 0 0;
  padding: var(--space-3) var(--space-4);
  /* Inline rgba follows the convention already in use at .usage-pill--full
     (no --culinari-danger-tint token exists in :root). */
  background: rgba(220, 53, 69, 0.08);
  border: 1px solid var(--culinari-danger);
  border-radius: var(--radius-md);
  color: var(--culinari-danger-deep);
  font-size: var(--fs-14);
  line-height: var(--lh-body);
}

/* ── Collector's edition as the destination — typographic only ──────────
   The side-stripe and tinted-card routes are off the table (the side-stripe
   accent ban, and the brand's anti-pricing-card stance), so the paid tier is
   set apart the way a book marks a featured passage: a quiet descriptive
   kicker in the brand hue, and a price scaled to LEAD the head row. No fill,
   border, side-stripe, shadow, or badge — the asymmetry against the basics
   tier (small "Free", no kicker, no CTA) carries the hierarchy. */
.pricing-edition__kicker {
  display: block;            /* composes .type-eyebrow (font / tracking / weight /
                                colour live there) like the cookbook eyebrows;
                                only the block layout + spacing is new here */
  margin: 0 0 var(--space-2);
}

/* P1 — on the destination tier the PRICE leads, not the name: 24px to 40px,
   one step past the --fs-h2 (32px) name it currently sits beneath. Kept in
   --fg-1 ink at --fw-heavy (both inherited); never accent-tinted, since a
   terracotta price reads as a sale tag and over-spends the one hue (reserved
   for the kicker and the CTA). The free tier's "Free" stays 24px; that
   big-number / small-word asymmetry is itself the value signal. */
.pricing-edition--premium .pricing-edition__price-amount {
  font-size: var(--fs-h2-lg);
}

/* P2 — the absolute annual figure, on the page at rest (no toggle click).
   Last child of the flex-wrap billing row; flex-basis:100% drops it onto its
   own line under the pills via the row's existing gap. The euros live here;
   the "save 33%" chip on the Annual pill keeps the percent. */
.pricing-edition__billing-note {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-13);
  line-height: var(--lh-body);
  /* --culinari-fg-muted (#555), not --culinari-muted (#7F7F7F): the lighter
     muted is ~3.86:1 on cream, under AA for 13px body text. This line carries
     the literal annual figure, so it must clear 4.5:1. */
  color: var(--culinari-fg-muted);
}

/* P2 — quiet reassurance under Subscribe, mirroring the free tier's "No card
   required". Plain muted footnote, deliberately icon-less: a shield/lock glyph
   beside a pay button is the trust-theater the calm brand avoids. */
.pricing-edition__trust {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-13);
  line-height: var(--lh-body);
  /* AA-legible muted (#555, ~7.2:1 on cream); the reassurance must be readable. */
  color: var(--culinari-fg-muted);
}

/* P2 — the signed-in free user's own meters, SHOWN where their plan lives,
   so the collector's "Unlimited" lands against a real ceiling. Reuses the
   .usage-pill shape but NEUTRALISES the dashboard's red/amber/green tone
   scale: on this Restrained one-accent sales surface those semantic hues
   read as paywall pressure, which the brand avoids. The bare "Books 3/3"
   beside "Unlimited" persuades on its own; the chips stay a calm warm
   outline with AA-legible muted ink. (The shared component and the
   dashboard usage bar keep their tones; this overrides only here.) */
.pricing-edition__usage { margin-top: var(--space-4); }
.pricing-edition__usage-meters {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.pricing-edition__usage-meters .usage-pill {
  border-color: var(--border-warm);
  color: var(--culinari-fg-muted);
}

@media (prefers-reduced-motion: reduce) {
  .pill-toggle__btn,
  .pricing-edition__cta-button,
  .pricing-success__cta { transition: none; }
}

/* Ingredient FOODON autocomplete -------------------------------------------
   The results list overlays the quantity/unit fields below the name input, so
   it must clear the Bootstrap modal layer (z-index 1055) when the ingredient
   form is shown in a modal. Chrome follows the system's dropdown/popover
   vocabulary (radius-lg + shadow-md + hairline) rather than Bootstrap's
   list-group defaults; overflow clipping rounds the inner rows for free. */
.ingredient-autocomplete__results {
  z-index: 1056;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

/* Muted explanatory text inside the dropdown (alias hits, the no-match row).
   fg-muted, not pencil: this is semantic content (the alias IS the text that
   selection inserts) at small sizes, and pencil (#7F7F7F) only reaches 4.0:1
   on the dropdown's white — below AA. */
.ingredient-autocomplete__hint {
  color: var(--culinari-fg-muted);
}

/* Visible companion to the canonical check: the icon never carries the meaning
   alone, and `title` tooltips don't exist on touch. fg-muted for AA at 12px. */
.ingredient-autocomplete__note {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.75rem;
  color: var(--culinari-fg-muted);
}

/* Reserve room at the right edge for the loading spinner / canonical check. */
.ingredient-autocomplete__field > .form-control {
  padding-right: 2.25rem;
}

.ingredient-autocomplete__adornment {
  position: absolute;
  top: 50%;
  right: 0.625rem;
  transform: translateY(-50%);
  display: inline-flex;
  line-height: 1;
  pointer-events: none;
}

/* Comfortable touch targets for one-handed, messy-fingers kitchen use. */
.ingredient-autocomplete__option {
  display: flex;
  align-items: center;
  min-height: 44px;
}

/* Hover belongs only to real options, in the system's cream-wash — not the
   cool gray the global .list-group-item:hover would paint, and never on the
   role=presentation no-match/error rows, which aren't interactive. */
.ingredient-autocomplete__option[role="option"]:hover {
  background-color: var(--bg-wash);
}

.ingredient-autocomplete__option:not([role="option"]):hover {
  background-color: transparent;
}

/* Keyboard-selected option (stimulus-autocomplete's selected class, set via
   data-autocomplete-selected-class — its default `active` would otherwise hit
   Bootstrap's .list-group-item.active, whose blue is baked into the CDN CSS as
   a literal and ignores the --bs-primary re-theme). Accent tint + ink keeps
   One Voice and stays readable, hint included. */
.ingredient-autocomplete__option.is-selected {
  background-color: var(--accent-tint);
  color: var(--culinari-ink);
}

/* Calm, cookbook-warm match highlight instead of the default highlighter yellow.
   accent_insensitive_highlight emits every <mark> in the app (ingredient
   autocomplete + recipe-search rows), so the style rides on its class. */
mark.match-highlight {
  /* zero out Bootstrap's default `mark` padding (.1875em) so the tint hugs the
     matched letters; any horizontal padding would split mid-word matches. */
  padding: 0;
  border-radius: 0.2rem;
  color: inherit;
  /* weight, not color alone, carries the match (WCAG 1.4.1) */
  font-weight: 600;
  background-color: rgba(var(--bs-primary-rgb), 0.16);
}

@media (max-width: 575.98px) {
  .ingredient-autocomplete__field > .form-control {
    min-height: 44px;
  }

  /* Same 44px-on-touch rule the row-action icons follow: on phones the
     icon-only save/cancel buttons are the primary tap targets of the row. */
  .ingredient-form-actions .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
  }
}

/* ============================================
   RAG Answer Panel — "Ask the kitchen"
   Grounded Q&A over the library. A quiet hairline-bound
   section: the answer reads as body prose, citation chips
   sit like margin notes beside their source cards, and the
   busy state lives in the panel, not just the button.
============================================ */

.rag-answer {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}

.rag-answer__heading {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--culinari-ink);
  margin-bottom: var(--space-4);
}

.rag-answer__form .form-label {
  display: block;
}

.rag-answer__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.rag-answer__controls .form-control {
  flex: 1 1 240px;
}

/* Busy state: Turbo sets aria-busy on the form for the length of the
   submission. The panel-level "Thinking…" line and the dimmed previous
   answer are what make the multi-second LLM call feel attended. */
.rag-answer__status {
  display: none;
  margin: var(--space-3) 0 0;
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

.rag-answer__form[aria-busy="true"] ~ .rag-answer__status {
  display: block;
}

.rag-answer__result {
  transition: opacity var(--dur-base) var(--ease-soft);
}

.rag-answer__form[aria-busy="true"] ~ .rag-answer__result {
  opacity: 0.5;
}

.rag-answer__text {
  margin-top: var(--space-4);
  max-width: 70ch;
  font-size: var(--fs-16);
  line-height: var(--lh-body);
  color: var(--culinari-fg);
}

.rag-answer__citation-link {
  color: var(--accent-text);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}

.rag-answer__citation-link:hover,
.rag-answer__citation-link:focus-visible {
  text-decoration: underline;
}

.rag-answer__disclaimer {
  max-width: 70ch;
  font-size: var(--fs-12);
  color: var(--culinari-muted);
}

.rag-answer__empty {
  margin: var(--space-4) 0 0;
  color: var(--culinari-fg-muted);
}

.rag-answer__error {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  font-size: var(--fs-14);
  color: var(--culinari-danger-deep);
}

.rag-answer__sources-label {
  margin: var(--space-4) 0 var(--space-2);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-size: var(--fs-14);
  color: var(--culinari-muted);
}

.rag-answer__sources {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Citation chip sits beside its card like a printed margin note; the
   shared id anchors the inline [R1] links in the answer prose. */
.rag-answer__source {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--space-2) var(--space-3);
  border-radius: var(--radius-lg);
  scroll-margin-top: var(--space-16);
}

.rag-answer__source:target {
  background-color: var(--accent-tint);
}

.rag-answer__source:target .resource-row-card {
  border-color: var(--accent);
}

.rag-answer__source-tag {
  display: inline-flex;
  align-items: center;
  margin-top: var(--space-2);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background-color: var(--accent-tint);
  color: var(--accent-text);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  line-height: 1.4;
  white-space: nowrap;
}

/* Citations are evidence, not browse cards — keep them still. */
.rag-answer__source .resource-row-card:hover,
.rag-answer__source .resource-row-card:focus-within {
  transform: none;
  box-shadow: none;
  background-color: var(--bg-wash);
}

/* ---------------------------------------------------------------------------
   Admin dashboard — flat cards, KPI tiles, section scaffolding

   Admin is part of the Culinari system, so cards obey the Flat-By-Default Rule:
   a hairline border at rest, no shadow. Elevation appears only on hover of the
   linkable stat tiles. Terracotta stays the one saturated voice.
   --------------------------------------------------------------------------- */
.admin-dashboard-section {
  scroll-margin-top: 1.5rem;
}
.admin-dashboard-section__title {
  font-family: var(--font-display, inherit);
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--culinari-ink);
  margin-bottom: 1rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--border-hairline);
}

/* Flat by default: hairline border, no resting shadow. Overrides Bootstrap's
   card treatment so admin cards read as notebook surfaces, not SaaS floats. */
.admin-dashboard .card {
  border: 1px solid var(--border-hairline);
  box-shadow: none;
}

/* Darken Bootstrap's muted grey (~#6c757d, borderline on white) to slate so the
   small uppercase labels and timestamps clear AA. The two-class selector beats
   Bootstrap's single-class .text-muted !important on specificity. */
.admin-dashboard .text-muted {
  color: var(--culinari-fg-muted) !important;
}

.admin-stat {
  color: var(--fg-1);
  transition: box-shadow .15s ease, transform .15s ease;
}
a.admin-stat:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.admin-stat__label {
  letter-spacing: .03em;
}
.admin-stat__value {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.15;
  color: var(--culinari-ink);
}
.admin-stat__icon {
  font-size: 1.15rem;
  color: var(--culinari-primary);
}
.admin-stat--warning .admin-stat__icon { color: var(--culinari-warning-deep); }
.admin-stat--danger  .admin-stat__icon { color: var(--culinari-danger-deep); }
.admin-stat--success .admin-stat__icon { color: var(--culinari-success-deep); }
.admin-stat--warning .admin-stat__value { color: var(--culinari-warning-deep); }
.admin-stat--danger  .admin-stat__value { color: var(--culinari-danger-deep); }

/* Compact secondary metric: label + value on one hairline-divided row, no card
   and no hero number. Used for lower-signal clusters (DAU/WAU/MAU) so the page
   isn't a wall of giant figures. */
.admin-stat-inline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  padding: .5rem .25rem;
  border-bottom: 1px solid var(--border-hairline);
}
.admin-stat-inline__label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--culinari-fg-muted);
}
.admin-stat-inline__value {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--culinari-ink);
  white-space: nowrap;
}

/* Needs-attention strip: rendered only when something is actionable. Each item
   pairs a tone color with an icon + label + count (Hue-Plus-Mark), never colour
   alone. */
.admin-attention {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: 1.5rem;
}
.admin-attention__item {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .875rem;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-cream);
  color: var(--culinari-ink);
  text-decoration: none;
  font-size: 14px;
  transition: background-color .15s ease, border-color .15s ease;
}
.admin-attention__item:hover {
  background: var(--bg-wash);
  border-color: var(--culinari-primary);
}
.admin-attention__count {
  font-weight: 700;
}
.admin-attention__item--danger  { color: var(--culinari-danger-deep); }
.admin-attention__item--warning { color: var(--culinari-warning-deep); }

/* Neutral admin status chip (mirrors the system recipe-badge calm lane): pill,
   uppercase, hairline border. Replaces raw Bootstrap bg-light badges. */
.admin-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--bg-soft);
  color: var(--culinari-fg-muted);
  border: 1px solid var(--border-hairline);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  line-height: 1.4;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Generation-health rows: hairline-divided, not boxed mini-cards inside a card
   (no-nesting rule). */
.admin-gen-health__row {
  padding: .5rem 0;
  border-bottom: 1px solid var(--border-hairline);
}
.admin-gen-health__row:last-child {
  border-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
  .admin-stat,
  a.admin-stat:hover,
  .admin-attention__item {
    transition: none;
    transform: none;
  }
}

/* =========================================================
   Admin observability (AI calls / RAG retrievals)
   ========================================================= */

/* Bootstrap ships the `code` element in magenta (#d63384), a saturated hue
   outside the palette that becomes the loudest color on data-dense admin
   tables (model names repeat per row). One Voice Rule: charcoal mono. */
code {
  color: var(--culinari-fg);
}

/* Deep-variant text utilities: the base semantic hues can't clear WCAG AA
   4.5:1 as small text on cream (mustard ~1.6:1, chili ~4.4:1). Icons may
   keep the base hue (3:1 graphics threshold); text takes the deep variant. */
.text-warning-deep { color: var(--culinari-warning-deep) !important; }
.text-danger-deep  { color: var(--culinari-danger-deep) !important; }

/* Flat-by-default cards: hairline border, no resting shadow. */
.obs-card {
  border: 1px solid var(--border-hairline);
  box-shadow: none;
}

/* LLM prompts/responses live in metadata jsonb and can run thousands of
   lines; cap the pre and let it scroll instead of swallowing the page. */
.obs-metadata-pre {
  max-height: 24rem;
  overflow: auto;
}
