/* =========================================================================
   ePepper — Lean Editorial
   ========================================================================= */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT@0,9..144,400..700,50;1,9..144,400..700,50&family=Inter:wght@400;500;600;700&display=swap');

/* ----------  Tokens  ---------- */
:root {
  color-scheme: light dark;

  /* Light palette (defaults) */
  --bg:            #FBFAF7;
  --surface:       #FFFFFF;
  --surface-alt:   #F2EFE8;
  --text:          #1A1A1A;
  --text-dim:      #3A3835;
  --muted:         #6E695F;   /* ~5.0:1 on --bg, comfortably AA at 13 px */
  --faint:         #B6B0A4;
  --border:        #E4DFD4;
  --accent:        #B83227;
  --accent-hover:  #962419;
  --accent-soft:   #F7E4E1;
  --on-accent:     #FFFFFF;
  --warn:          #B07A0F;
  --danger:        #9B1C1C;
  --danger-soft:   #FDECEC;

  /* Typography */
  --font-display: 'Fraunces', 'Times New Roman', Georgia, serif;
  --font-body:    'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Layout */
  --page-max:     1140px;
  --gutter:       clamp(20px, 4vw, 56px);
  --vrhythm:      1.65;

  /* Radii / motion */
  --r-sm:  6px;
  --r-md:  10px;
  --ease:  cubic-bezier(.2,.6,.2,1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:            #121110;
    --surface:       #1B1A18;
    --surface-alt:   #23211E;
    --text:          #F1ECE2;
    --text-dim:      #CFC8BC;
    --muted:         #8E867A;
    --faint:         #5A554D;
    --border:        #2C2A26;
    --accent:        #E5614F;
    --accent-hover:  #F1816F;
    --accent-soft:   #3A1C18;
    --on-accent:     #15110F;
    --warn:          #D9A742;
    --danger:        #F0817E;
    --danger-soft:   #3A1B1B;
  }
}

/* ----------  Reset + base  ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: var(--vrhythm);
  font-feature-settings: "ss01", "cv11", "ss03";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }

a {
  color: var(--accent);
  text-decoration: none;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color .15s var(--ease);
}
a:hover { color: var(--accent-hover); text-decoration: underline; }

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

button, input, select, textarea { font: inherit; color: inherit; }

button { cursor: pointer; }

/* Focus */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ----------  Topbar / masthead  ---------- */
.topbar {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: saturate(140%) blur(8px);
}

.topbar-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 22px var(--gutter) 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.brand {
  display: inline-block;
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 26px;
  font-variation-settings: "opsz" 36, "SOFT" 50;
  letter-spacing: -0.01em;
  line-height: 1;
}
.brand:hover { color: var(--text); text-decoration: none; }
.brand .brand-e {
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
  /* Italic e tilts away from the upright P; pull "Pepper" back so the
     wordmark reads as one glyph cluster, not two words separated by air.
     (.brand was inline-flex with gap:10px to position a pepper SVG that
     no longer exists — that gap was the real culprit; this nudge handles
     the residual optical gap from italic slant.) */
  margin-right: -0.06em;
}

.topnav {
  display: flex;
  align-items: center;
  gap: 4px;
}
.topnav .iconbtn { color: var(--text-dim); }

.iconbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--r-sm);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-decoration: none;
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.iconbtn:hover {
  color: var(--text);
  background: var(--surface-alt);
  text-decoration: none;
}
.iconbtn svg { flex: none; }
.iconbtn-label { white-space: nowrap; }

/* ----------  Main canvas  ---------- */
main {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 56px var(--gutter) 96px;
}

/* ----------  Utility  ---------- */
.muted { color: var(--muted); }

/* ----------  Login  ---------- */
body.centered {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}

.login {
  width: 100%;
  max-width: 440px;
  text-align: left;
}
.login h1 {
  font-family: var(--font-display);
  font-size: clamp(48px, 6vw, 72px);
  font-weight: 700;
  font-variation-settings: "opsz" 96, "SOFT" 50;
  letter-spacing: -0.025em;
  line-height: 1;
  margin: 0 0 8px;
  color: var(--text);
}
.login h1 .brand-e {
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
  margin-right: -0.04em;
}
.login > p { margin: 0 0 28px; color: var(--muted); font-size: 17px; }

.login form {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

/* Hairline-underline login field (proposal 8) — drop the rounded
   rectangle in favour of a single bottom rule. The merged paste/submit
   button next to it gets a matching borderless treatment so the two
   elements read as a single editorial input row. */
.login input[type="password"] {
  width: 100%;
  padding: 14px 2px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  border-radius: 0;
  font-size: 20px;
  transition: border-color .15s var(--ease);
}
.login input[type="password"]:focus {
  outline: none;
  border-bottom-color: var(--accent);
  box-shadow: none;
}
.login .input-action-btn {
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 14px 14px;
}
.login .input-action-btn[data-state="submit"] {
  border-color: transparent;
  border-bottom-color: var(--accent);
}
.login .input-action-btn:hover { background: transparent; }

/* (No .login button override — Sign in now lives inside .input-action
   as the merged paste/submit button next to the access-code field. See
   .input-action / .input-action-btn above.) */

/* ----------  List controls (library search) ---------- */
.list-controls {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: stretch;
  /* No border-bottom or padding-bottom — the first recipe row already
     draws a top hairline, so adding one here stacked two lines with a
     wide gap between them. The recipe list's own borders carry the
     separation now. */
  margin: 0 0 16px;
}

/* Tag cloud (library page, outside the form) */
.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 24px;
}

/* Tag pills — shared between the tag cloud, card tags, and recipe detail */
.tag-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  background: var(--surface);
  transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease);
  white-space: nowrap;
}
a.tag-pill:hover {
  border-color: var(--accent);
  color: var(--accent);
  text-decoration: none;
  background: var(--accent-soft);
}
.tag-pill--active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}
.tag-pill--sm {
  padding: 2px 9px;
  font-size: 11px;
  letter-spacing: 0.03em;
}

/* Card-level tag row (inside .card-meta) — inline-flex so pills sit on
   the same baseline row as the cooked-date span */
.card-tags {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Tag popover — <details>-based GitHub-label-style inline editor.
   Lives in #recipe-tags, which carries the section hairline. */
#recipe-tags {
  margin: 0 0 28px;
  padding-bottom: 28px;
  border-bottom: 1px solid var(--border);
}
.tag-popover {
  position: relative;
  display: block;
}
.tag-popover[open] { z-index: 100; }

.tag-summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  padding: 4px 0;
}
.tag-summary::-webkit-details-marker { display: none; }

.tag-summary-pills {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tag-edit-hint {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--faint);
  padding: 3px 8px;
  border-radius: var(--r-sm);
  transition: color .15s var(--ease), background .15s var(--ease);
}
.tag-summary:hover .tag-edit-hint {
  color: var(--accent);
  background: var(--accent-soft);
}

.tag-popup {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 200;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px;
  min-width: 220px;
  max-width: 320px;
  max-height: 300px;
  overflow-y: auto;
}

.tag-chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  background: var(--surface-alt);
  cursor: pointer;
  user-select: none;
  transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease);
}
.tag-chip input[type="checkbox"] { display: none; }
.tag-chip:hover { border-color: var(--accent); color: var(--accent); }
.tag-chip--on {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}
.tag-new-input {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  transition: border-color .15s var(--ease);
}
.tag-new-input:focus { outline: none; border-color: var(--accent); }
.tag-new-input::placeholder { color: var(--faint); }

.list-search,
.list-select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--r-md);
  font-size: 15px;
  font-family: var(--font-body);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.list-search:focus,
.list-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.list-search::placeholder { color: var(--faint); }

.list-select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23807A70' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 14px 14px;
  padding-right: 38px;
}
@media (prefers-color-scheme: dark) {
  .list-select {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238E867A' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6l5 5 5-5'/%3E%3C/svg%3E");
  }
}

/* ----------  Recipe edit page (hidden — reached by URL only)  ---------- */
.edit-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 640px;
}
.edit-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.edit-field label {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}
.edit-field .field-help {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}
.edit-field .field-help code {
  font-size: 12px;
  background: var(--surface);
  border-radius: var(--r-sm);
  padding: 1px 5px;
}
.edit-form textarea.list-search {
  min-height: 120px;
  resize: vertical;
  font-family: var(--font-body);
  line-height: 1.5;
}
.edit-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.edit-row .edit-field { flex: 1 1 160px; }
.edit-actions {
  display: flex;
  justify-content: flex-start;
}

/* ----------  Library list  ---------- */
.library-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 28px;
}
.library-header h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: "opsz" 96, "SOFT" 50;
  font-size: clamp(40px, 5.5vw, 64px);
  line-height: 1;
  letter-spacing: -0.025em;
  margin: 0;
}
.library-header .meta {
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}
/* "clear" reset appended to the meta line when a filter is active — quiet
   paprika link, no filled chrome, matching the app's link vocabulary. */
.clear-filter { color: var(--accent); }
.clear-filter:hover { color: var(--accent-hover); text-decoration: underline; }

.recipes {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: recipe;
}

.recipes > li {
  border-bottom: 1px solid var(--border);
}
/* No top border on the first card of each tier — the .library-tier-h
   above already draws its own hairline. Stacking both produced a
   double-line right under the h2 (and at tier-spanning page boundaries
   where the h2 is suppressed, two adjacent <ul>s' last/first li
   borders stacked into the same visual artefact). The h2's border-top
   carries the section break; tier <ul>s only need bottom rules to
   close out each row. */
/* No bottom border on the last card of each tier either — it'd sit
   ~36 px above the next h2's own border-top, producing two clearly
   spaced hairlines per section break. Tiers end openly; the next
   eyebrow's hairline is the single separator. The very last tier on
   the page ends without a closing line, matching editorial layouts
   that let content trail off rather than fence it in. */
.recipes > li:last-child { border-bottom: 0; }

.card {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-areas: "title meta badge";
  align-items: end;
  gap: 4px 24px;
  padding: 22px 4px 22px 14px;
  color: var(--text);
  text-decoration: none;
  transition: background .2s var(--ease);
  position: relative;
}
.card-title  { grid-area: title; }
.card-meta   { grid-area: meta; padding-bottom: 2px; }
.card-badge  { grid-area: badge; padding-bottom: 2px; }
.card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--accent);
  opacity: 0;
  transition: opacity .2s var(--ease);
}
.card:hover {
  text-decoration: none;
  background: linear-gradient(to right, var(--surface-alt), transparent 60%);
}
.card:hover::before { opacity: 1; }

.card-title {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}
.card-title > span:first-child {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: "opsz" 36, "SOFT" 50;
  font-size: 22px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text);
}
.card-source {
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.card-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13px;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* "On air" indicator on the library card for the recipe currently on
   the e-ink panel. Borderless: paprika label + a pulsing paprika dot
   drawn by ::before, like a recording-light. */
.card-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--accent);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
}
.card-badge::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  /* Pulse the dot itself (scale + opacity) plus an outward ripple via
     box-shadow. The scale gives the eye an unmissable beat; the ripple
     adds the polished "on-air" softness. The ripple colour is fixed
     paprika RGB — close enough for both themes; rgba in keyframes can't
     reference --accent. */
  box-shadow: 0 0 0 0 rgba(184, 50, 39, 0.55);
  animation: ePepper-onair-pulse 1.6s ease-in-out infinite;
}
@keyframes ePepper-onair-pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
    box-shadow: 0 0 0 0 rgba(184, 50, 39, 0.55);
  }
  50% {
    transform: scale(1.35);
    opacity: 0.6;
    box-shadow: 0 0 0 6px rgba(184, 50, 39, 0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .card-badge::before { animation: none; }
}

.loader {
  padding: 32px 4px;
  color: var(--muted);
  font-style: italic;
  text-align: center;
  border-bottom: 0 !important;
}

/* ----------  Add page  ---------- */
.page-header {
  margin-bottom: 40px;
}
.page-header h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: "opsz" 96, "SOFT" 50;
  font-size: clamp(40px, 5.5vw, 64px);
  line-height: 1;
  letter-spacing: -0.025em;
  margin: 0 0 12px;
}
.page-header p {
  font-size: 17px;
  color: var(--muted);
  margin: 0;
  max-width: 56ch;
}

/* Flat list of add methods — same library-list pattern. Single column,
   hairline-separated rows, no rounded-card chrome. */
.add-grid {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.add-tile {
  margin: 0;
  padding: 24px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  position: relative;
  transition: border-color .2s var(--ease);
}
.add-tile:first-child { border-top: 1px solid var(--border); }

/* h2 mirrors .status-card h2: a small-caps section label (.status-card-h-label
   set in the markup) — the LINK / SNAP eyebrow — followed by a quiet
   help line (.add-tile-help) below in muted body type. Reset the
   browser h2 metrics so the only visual weight is the small-caps span. */
.add-tile h2 {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: 1;
}
.add-tile-help {
  margin: 0;
  font-size: 14px;
}

.file-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
  flex-wrap: wrap;
}

/* .input-action — merged Paste/Submit affordance.
   One button next to a text-ish input (login access code, URL add).
   When the field is empty the button is a paste-from-clipboard helper;
   when the field has a value the button submits the parent form.
   The state attribute switches the icon (clipboard ↔ arrow) and the
   colour treatment (quiet outline ↔ accent outline). The button stays
   the same shape and position so flipping state doesn't shift layout.
   See input-action.js for the state machine; the JS degrades to a
   plain submit button if the Clipboard API isn't available. */
.input-action {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.input-action > input { flex: 1; min-width: 0; }
.input-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 48px;
  padding: 0 14px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--muted);
  cursor: pointer;
  transition: color .15s var(--ease), border-color .15s var(--ease),
              background .15s var(--ease);
}
.input-action-btn:hover { color: var(--accent); border-color: var(--accent); }
.input-action-btn[data-state="submit"] {
  /* Filled-paprika outline signals "ready to submit". Same accent
     family as button.link.accent — just sized to match the input. */
  color: var(--accent);
  border-color: var(--accent);
}
.input-action-btn[data-state="submit"]:hover { background: var(--accent-soft); }

/* Icon swap. Both SVGs are in the markup; one is hidden depending on
   state. Block instead of inline-flex so the SVG fills the centered
   button uniformly. */
.input-action-btn .state-paste,
.input-action-btn .state-submit { display: none; }
.input-action-btn[data-state="paste"]  .state-paste  { display: block; }
.input-action-btn[data-state="submit"] .state-submit { display: block; }

.add-tile input[type="url"] {
  flex: 1;
  padding: 14px 16px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  border-radius: var(--r-md);
  font-size: 15px;
}
.add-tile input[type="url"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

/* (.add-tile button.primary / .icon-only rules removed — the URL row
   now uses a paste-btn + a button.link.accent submit, neither needs
   the filled-primary treatment.) */

.file-input {
  flex: 1;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 1px dashed var(--border);
  background: var(--bg);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.file-input:hover { border-color: var(--accent); background: var(--accent-soft); }
.file-input input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.file-input svg { color: var(--accent); }
.file-input-name { font-size: 14px; }

.add-progress {
  display: none;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--muted);
}
.htmx-request .add-progress { display: inline-flex; }
.spinning svg { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ----------  Status  ----------
   Single-column layout: the e-ink preview gets the full width up top
   (capped at the BMP's intrinsic 800 px so it renders at actual size
   on desktop), then the status cards stack below. The 2-col grid the
   redesign initially had squeezed the 800×480 frame into a 590 px
   sidebar, which distorted it visually. */
.status-grid {
  display: flex;
  flex-direction: column;
  gap: 28px;
  align-items: stretch;
}

/* The e-ink frame is 800×480 1-bit BMP. No card chrome — the image is
   the content. max-width: 800 px caps it at native pixel size on
   desktop (so it doesn't get upscaled in a wide column), and width:
   100% lets it scale down proportionally below that. Pixelated keeps
   the dot pattern crisp at any size. min-height is intentionally
   absent — the old 320 px floor stretched landscape frames into
   squares. */
/* The preview is nested inside the Display .status-card, sitting between
   the title row and the Clear control. No border or extra chrome — the
   section's bottom hairline is enough — just margin so the image has
   breathing room above and below. */
.display-preview {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 18px 0;
}
.display-preview img {
  display: block;
  max-width: 800px;
  width: 100%;
  height: auto;
  margin: 0 auto;
  background: #ffffff;
  /* 1 px hairline contains the white BMP in dark mode so it doesn't
     float as a slab of light on a graphite page. Same --border hue
     used everywhere else, so the frame reads as "the panel". */
  border: 1px solid var(--border);
  image-rendering: pixelated;
}

/* Multi-page preview = a swipeable carousel. The track is a native CSS
   scroll-snap row (one full-width frame per page), so a phone swipes
   between pages with no JS. The scrollbar is hidden so it reads as a single
   framed panel rather than a scrollbox; the arrows + counter below are
   progressive enhancement (status.html) for desktop discoverability. */
.carousel {
  max-width: 800px;
  margin: 0 auto;
}
.carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;                 /* Firefox */
  -webkit-overflow-scrolling: touch;
}
.carousel-track::-webkit-scrollbar { display: none; }   /* WebKit */
.carousel-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  width: 100%;
  height: auto;
  background: #ffffff;
  /* Same hairline as the single-page preview so a dark-mode frame doesn't
     float as a slab of light. */
  border: 1px solid var(--border);
  image-rendering: pixelated;
}
/* Centered page-nav strip below the frame (DESIGN.md: page nav lives below
   the preview, centered). Arrows reuse the .iconbtn role. */
.carousel-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 12px;
}
.carousel-counter {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
  min-width: 52px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.iconbtn:disabled {
  opacity: 0.35;
  pointer-events: none;
}

/* Flat list of status sections — same library-list pattern (rule
   dividers, no card chrome). Each card is a row separated by hairlines,
   first card grows a top hairline so the list reads as bounded. */
.status-cards {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.status-card {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 24px 0;
}
.status-card:first-child { border-top: 1px solid var(--border); }

/* Section h2: small-caps label on the left, optional muted meta on the
   right (only the Display card uses the meta slot, for "Updated …"). */
.status-card h2 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 14px;
}
/* Used in the Status page section headers AND in the Add page tile headers
   (LINK / SNAP). Unscoped so any h2 can carry this small-caps eyebrow. */
.status-card-h-label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.status-card-h-label svg { color: var(--accent); }
.status-card h2 .status-card-h-meta {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--muted);
  white-space: nowrap;
}

.display-title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  line-height: 1.25;
  font-variation-settings: "opsz" 36, "SOFT" 50;
  letter-spacing: -0.005em;
}
.display-title strong { font-weight: 600; }
.display-title strong a { color: var(--text); }
.display-title strong a:hover { color: var(--accent); }
.display-title .muted {
  display: block;
  font-family: var(--font-body);
  font-style: normal;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  margin-top: 4px;
}
.status-card > p { margin: 0 0 6px; font-size: 15px; }
.status-card > p:last-child { margin-bottom: 0; }
.status-card .muted { font-size: 14px; }

.status-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.status-line strong { color: var(--text); }
/* Per-line metric icons (battery/wifi/thermo/droplet on the Device card,
   etc.) — muted so they read as "label decoration", not as primary
   attention pulls the way the section-title accent icons do. */
.status-icon { display: inline-flex; color: var(--muted); }

.status-actions {
  margin: 16px 0 0;
  text-align: center;
}
/* Clear button is now a link.danger — inherits everything from
   button.link.danger; no card-specific override needed. */

/* ----------  Recipe page  ---------- */
.back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 32px;
}
.back:hover { color: var(--accent); text-decoration: none; }

.recipe {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}

.recipe > h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variation-settings: "opsz" 144, "SOFT" 50;
  font-size: clamp(44px, 7vw, 84px);
  line-height: 1;
  letter-spacing: -0.03em;
  margin: 0 0 18px;
  max-width: 14ch;
  color: var(--text);
}

.recipe > p.muted {
  font-size: 14px;
  letter-spacing: 0.04em;
  margin: 0 0 16px;
  color: var(--muted);
}
.recipe > p.muted a { color: var(--text); border-bottom: 1px solid var(--border); }
.recipe > p.muted a:hover { color: var(--accent); border-bottom-color: var(--accent); }

.actions {
  margin: 0 0 28px;
  text-align: center;
}

/* Cooking facts (time / servings) — small-caps eyebrow under the title,
   above the provenance meta. Reuses .nb's no-break + bullet-between
   treatment so it can't break mid-phrase. --text-dim (not --muted) so the
   facts read a touch stronger than the provenance line below them. */
.recipe-facts {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 12px;
}
.recipe-facts .nb { display: inline-block; }
.recipe-facts .nb:not(:last-child)::after {
  content: "·";
  display: inline-block;
  margin: 0 0 0 8px;
  opacity: 0.6;
}

/* Recipe meta line below the title — each segment is an inline-block
   .nb so it can't break mid-phrase. The bullet between segments is a
   ::after pseudo *inside* each non-last segment, so when the meta line
   wraps the bullet stays glued to the end of the previous line instead
   of dangling at the start of the new line. The wrap point is the real
   whitespace between adjacent .nb spans. */
.recipe-meta .nb { display: inline-block; }
.recipe-meta .nb:not(:last-child)::after {
  content: "·";
  display: inline-block;
  margin: 0 0 0 8px;
  opacity: 0.6;
}
/* Push to display, login Sign in, etc. all share this quiet link
   treatment — same chrome as "Delete this recipe" in the recipe footer
   but accent-coloured because the action isn't destructive. Lives on
   the generic button.link selector so it works everywhere. */
button.link.accent {
  color: var(--accent);
  font-weight: 500;
}
button.link.accent:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.recipe-body {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 64px;
  align-items: start;
}

.recipe h2 {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 16px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.recipe h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: "opsz" 36, "SOFT" 50;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -0.005em;
  margin: 28px 0 14px;
  color: var(--text);
}
.recipe h3:first-of-type { margin-top: 0; }

.recipe-sidebar {
  position: sticky;
  top: 92px;
}

.ingredients {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 15px;
  line-height: 1.55;
}
.ingredients li {
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: baseline;
  gap: 10px;
}
/* Bullets dropped (proposal 6) — the paprika squares were one of nine
   paprika roles on the recipe page; promoting the step numerals as the
   only paprika in the body lets them carry the brand on their own.
   The hairline indent + leading whitespace does the visual work. */
.ingredients li::before {
  content: "";
  flex: none;
  width: 8px;
  height: 1px;
  background: var(--border);
  transform: translateY(-6px);
}

.steps {
  list-style: none;
  counter-reset: step;
  margin: 0 0 24px;
  padding: 0;
}
.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: 18px;
  padding: 14px 0 18px;
  border-bottom: 1px solid var(--border);
  font-size: 17px;
  line-height: 1.6;
  color: var(--text);
}
.steps li:last-child { border-bottom: 0; }
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-display);
  font-weight: 400;
  font-style: italic;
  font-variation-settings: "opsz" 96, "SOFT" 50;
  font-size: 36px;
  line-height: 1;
  color: var(--accent);
  letter-spacing: -0.02em;
}

/* Single-step section: a plain paragraph, no numeral. Matches the
   `.steps li` body type so a single-step section reads with the same
   weight as one row of a multi-step list, just without the oversize
   italic numeral that doesn't earn its flourish on a sequence-of-one. */
.step-solo {
  margin: 0 0 24px;
  padding: 4px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: var(--text);
}

.recipe-footer {
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid var(--border);
  text-align: center;
}
button.link, a.link {
  background: transparent;
  border: 0;
  font-size: 13px;
  letter-spacing: 0.05em;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: var(--r-sm);
  cursor: pointer;
}
button.link.danger {
  color: var(--danger);
  font-weight: 500;
}
button.link.danger:hover { background: var(--danger-soft); color: var(--danger); }

/* ----------  Status alert banner & inline warning chip  ----------
   Editorial callout for low-battery / overdue alerts on the status page,
   plus the small inline chip rendered next to a Device "last seen" line
   when the heartbeat is overdue. Designed to fit the warn token family
   in both modes (--warn = amber 700 light, amber 400 dark). */
.status-banner {
  /* Sits above the two-column .status-grid; span both columns when
     present so it doesn't get squeezed into a single cell. */
  grid-column: 1 / -1;
  display: grid;
  gap: 6px;
  padding: 14px 18px;
  background: transparent;
  color: var(--text);
  border-left: 3px solid var(--warn);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  font-size: 15px;
  line-height: 1.55;
}
.status-banner strong {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--warn);
  font-weight: 600;
}
.warn-inline {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
  padding: 1px 8px;
  border: 1px solid var(--warn);
  color: var(--warn);
  border-radius: 999px;
  font-size: 0.85em;
  font-weight: 500;
}

/* ----------  Toast  ----------
   Every confirmation injected into #toast wraps itself in `.toast`
   (_toast.html, undo banner, cleared banner). So #toast itself stays
   an unstyled passthrough — styling it as a pill too stacks two fixed
   pills bottom-center, which double-renders the message and the dark
   pill chrome on every push. */
/* Inline editorial banner — sits at the top of <main> (inside #toast)
   instead of floating as a rounded pill. Same accent-left-bar pattern
   as the comments / status banners so the whole confirmation language
   is one design family. Auto-dismisses via the template's own setTimeout. */
.toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  margin: 0 0 24px;
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  color: var(--text);
  font-size: 15px;
  line-height: 1.45;
}
.toast button {
  background: transparent;
  border: 0;
  color: var(--accent);
  padding: 4px 8px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}
.toast button:hover { background: var(--surface); }

/* ----------  Inline add-page error fragment  ----------
   Returned as a 400 HTML fragment into #add-result when URL parsing /
   OCR fails. Sits above the two .add-tile cards. */
.add-error {
  background: var(--danger-soft, #FDECEC);
  border-left: 3px solid var(--danger, #9B1C1C);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  color: var(--text);
  padding: 14px 18px;
  margin: 0 0 22px;
  font-size: 15px;
  line-height: 1.55;
}
.add-error code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.06);
}
@media (prefers-color-scheme: dark) {
  .add-error code { background: rgba(255, 255, 255, 0.08); }
}

/* ----------  htmx indicator helpers ---------- */
.htmx-indicator { opacity: 0; }
.htmx-request .htmx-indicator { opacity: 1; transition: opacity .2s ease-in; }
.htmx-request.htmx-indicator   { opacity: 1; transition: opacity .2s ease-in; }

/* ----------  Masthead nav (proposal 1)  ---------- */
.masthead-nav {
  gap: 0;
  align-items: baseline;
}
.masthead-nav .masthead-link {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  cursor: pointer;
  line-height: 1;
}
.masthead-nav .masthead-link:hover { color: var(--accent); text-decoration: none; }
.masthead-nav .masthead-link + .masthead-link::before {
  content: "·";
  display: inline-block;
  margin: 0 14px;
  color: var(--muted);
  opacity: 0.6;
}
/* ----------  Library tiers  ----------
   Magazine-contents-page tiering: a small-caps date eyebrow with a
   hairline above each time bucket. The lead row that used to promote
   the on-display recipe to the top was removed — the existing on-air
   dot inside the list row carries that signal well enough. */
.library-tier-h {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 36px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.library-tier-h:first-of-type { margin-top: 18px; }
.recipes-tier-this-week,
.recipes-tier-this-month,
.recipes-tier-this-year,
.recipes-tier-older { margin-top: 4px; }

/* ----------  Responsive  ---------- */
@media (max-width: 900px) {
  /* Tighter top padding on mobile — the 56 px desktop breathing room
     plus the page-header's own margin pushed every title nearly off
     the first fold. Same lower padding to keep scroll-room at bottom. */
  main { padding: 18px var(--gutter) 80px; }
  .topbar-inner { padding: 16px var(--gutter); }
  .library-header { margin-bottom: 18px; }
  .page-header    { margin-bottom: 22px; }
  /* Back link on recipe pages — was a 32 px wedge below the chip,
     which dominated the viewport before the recipe title even
     appeared. */
  .back { margin-bottom: 14px; }
  .brand { font-size: 22px; }
  .iconbtn-label { display: none; }
  .iconbtn { padding: 8px 10px; }

  /* Mobile: search takes the full first row (people want to type
     comfortably), and the source select drops to the row below via
     flex-wrap instead of stacking under the full-width search. */
  .list-controls {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    grid-template-columns: none;
  }
  .list-search { flex: 1 1 100%; }
  .list-select { flex: 1 1 auto; min-width: 0; }
  /* Mobile: title spans the row, meta + badge share the second row so
     ON DISPLAY pins to the bottom-right next to the cooked-meta —
     same vocabulary as desktop, just stacked. */
  .card {
    grid-template-columns: 1fr auto;
    grid-template-areas: "title title" "meta badge";
    gap: 8px 12px;
  }
  .card-title > span:first-child { font-size: 19px; }
  .card-meta { justify-content: flex-start; text-align: left; padding-bottom: 0; }
  .card-badge { align-self: center; padding-bottom: 0; }

  .add-grid { grid-template-columns: 1fr; gap: 18px; }
  /* Inherit the desktop "padding: 24px 0" — the section already
     respects the page gutter, so an extra horizontal inset here would
     push the section icon away from the gutter line. */
  .add-tile { padding: 22px 0; }

  .status-grid { gap: 22px; }

  .recipe-body { grid-template-columns: 1fr; gap: 36px; }
  .recipe-sidebar { position: static; }
  .recipe > h1 { font-size: clamp(38px, 11vw, 60px); max-width: 100%; }
  .steps li { grid-template-columns: 44px 1fr; gap: 14px; font-size: 16px; }
  .steps li::before { font-size: 30px; }

  .library-header { flex-direction: column; align-items: flex-start; gap: 6px; }
}

@media (max-width: 540px) {
  :root { --gutter: 16px; }
  /* Topbar tightens hard on phones — the masthead-nav links don't fit
     beside the wordmark at default tracking/separator spacing on a 390 px
     viewport. Every horizontal value shrinks by the smallest amount
     that buys legibility back. */
  .topbar-inner { padding: 12px var(--gutter); gap: 10px; }
  .brand { font-size: 19px; }
  .topnav { gap: 0; }
  .iconbtn { padding: 8px; }
  .masthead-nav .masthead-link { letter-spacing: 0.1em; font-size: 11px; }
  .masthead-nav .masthead-link + .masthead-link::before { margin: 0 6px; }
  .recipe > h1 { font-size: 40px; }
  .steps li { grid-template-columns: 36px 1fr; gap: 12px; }
  .steps li::before { font-size: 26px; }
  .login h1 { font-size: 44px; }
}
