/* Site chrome: nav, browse grid, forms, upload page — same palette/type as
   the recipe card (Bodoni Moda serif, Karla sans, cream bg, sage accent). */

:root {
  --cream: #f6f5f2;
  --ink: #17191a;
  --ink-soft: #2c3031;
  --ink-muted: #4b5051;
  --grey: #7f8683;
  --grey-light: #9aa09d;
  --rule: #d8d6cf;
  --sage: #4a6b56;
  --sage-dark: #35503f;
  --card-bg: #ffffff;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: Karla, Helvetica, sans-serif;
  font-weight: 300;
  min-height: 100vh;
}

a { color: var(--sage); text-decoration: none; }
a:hover { color: var(--sage-dark); }

h1, h2, h3 { font-family: 'Bodoni Moda', Didot, serif; font-weight: 400; }

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 40px;
  border-bottom: 1px solid var(--rule);
  background: var(--cream);
}
.site-header .brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: 'Fraunces', 'Bodoni Moda', Didot, serif;
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: 24px;
  color: var(--ink);
}
.site-header .brand .accent { color: var(--sage); font-style: italic; }
.brand-glyph { width: 60px; height: 60px; display: block; border-radius: 13px; }
/* The nav (1.15.0): a house icon for Browse, then the links, separated by
   hyphens, none of them ever broken across two rows.

   The hyphen is each item's ::before, hung in the gap to its left. The list
   is pulled left by exactly one gap (--nav-gap) inside a nav that clips
   horizontally, so the first item on EVERY row -- not just the first item
   overall -- has its hyphen outside the clip and out of sight. That is what
   keeps a wrapped row on a phone from starting with "- Settings", with no
   script measuring rows. Rows start on the left for the same reason (a
   right-aligned row's first item wouldn't sit at the clip edge). */
.site-nav {
  --nav-gap: 28px;
  overflow: hidden;
  padding: 4px 0;
}
.site-nav ul {
  list-style: none;
  margin: 0 0 0 calc(-1 * var(--nav-gap));
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  row-gap: 10px;
}
.site-nav li {
  position: relative;
  margin-left: var(--nav-gap);
  white-space: nowrap;   /* "Cook with Me" is one name: never split it */
  flex: none;
}
.site-nav li::before {
  content: "-";          /* fallback for a browser without alt text in content */
  content: "-" / "";     /* decoration only: screen readers skip it */
  position: absolute;
  left: calc(-1 * var(--nav-gap));
  width: var(--nav-gap);
  top: 50%;
  transform: translateY(-50%);
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-muted);
  opacity: .7;
  pointer-events: none;
}
/* In the iPhone app (html.ios-app, set by base.html from the app's user
   agent) the rows are centred (1.16.0). The left-shift-and-clip above can't
   hide a centred row's leading hyphen, so here the gap is a column-gap, the
   list isn't shifted, and a small script in base.html marks the first item
   of each row (.nav-row-start), whose hyphen is hidden. The web is
   unchanged. */
.ios-app .site-nav ul { justify-content: center; margin-left: 0; column-gap: var(--nav-gap); }
.ios-app .site-nav li { margin-left: 0; }
.ios-app .site-nav li.nav-row-start::before { visibility: hidden; }
.site-nav .nav-home { display: inline-flex; align-items: center; vertical-align: middle; padding: 2px; border-radius: 6px; }
.site-nav .nav-home svg { display: block; }
.site-nav a:focus-visible { outline: 2px solid var(--sage); outline-offset: 2px; border-radius: 4px; }
.site-header nav a {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.site-header nav a:hover { color: var(--sage); }

/* Local-only marker so a dev instance is never mistaken for prod -- only
   rendered at all when SHOULDER_CHEF_DEV_BANNER is set in the environment
   (see app/services/dev_watermark.py); .site-header's only other two
   children (.brand, nav) are pinned to the edges by justify-content:
   space-between, so this fills the empty middle stretch between them.
   flex:1 + text-align:center centers it in whatever width is left over,
   which grows/shrinks with the nav links and viewport automatically. */
.dev-banner {
  flex: 1;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: #fff;
  background: var(--bar-red, #B04A3C);
  border: 1.5px solid var(--bar-red, #B04A3C);
  border-radius: 6px;
  padding: 5px 10px;
  margin: 0 24px;
  pointer-events: none;
  user-select: none;
}

.container { max-width: 1180px; margin: 0 auto; padding: 32px 40px 60px; }

/* ---------- Browse page ---------- */

.browse-toolbar {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 22px;
}
.search-box {
  flex: 1;
  min-width: 220px;
  padding: 11px 16px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font: inherit;
  font-size: 13.5px;
  background: #fff;
}
.search-box:focus { outline: none; border-color: var(--sage); }

.display-mode-toggle {
  flex: none;
  display: inline-flex;
  border: 1px solid var(--rule);
  border-radius: 999px;
  overflow: hidden;
}
.display-mode-btn {
  font: inherit;
  font-size: 12px;
  letter-spacing: .02em;
  background: none;
  border: none;
  border-left: 1px solid var(--rule);
  padding: 8px 15px;
  color: var(--grey);
  cursor: pointer;
}
.display-mode-btn:first-child { border-left: none; }
.display-mode-btn:hover { background: var(--cream); color: var(--sage-dark); }
.display-mode-btn.active { background: var(--sage); color: #fff; }

/* The three filters are siblings on one centred row, so a chip and a
   dropdown trigger line up with each other. They have to be the same
   height for that to hold -- see .chip and .filter-dropdown-trigger,
   which share their padding and font size deliberately. */
.filter-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 26px;
  flex-wrap: wrap;
}
/* Both controls are line-height:normal by default, which means their
   height comes from the FONT METRICS OF WHATEVER TEXT IS IN THEM -- and
   the Favourites chip contains a star, whose glyph box is taller than the
   button's plain Latin, so it sat a pixel proud of the two beside it.
   Pinning line-height makes the height a function of the CSS instead, so
   the row stays aligned whatever anyone puts in a label later.
   Scoped to the filter bar: .chip is also used by the cook and plan
   pickers, which have no buttons to line up with. */
.filter-bar .chip,
.filter-bar .filter-dropdown-trigger {
  display: inline-flex;
  align-items: center;
  line-height: 14px;
}
.chip {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: none;
  color: var(--ink-muted);
  background: #fff;
  cursor: pointer;
}
.chip.active { background: var(--sage); border-color: var(--sage); color: #fff; }
.chip:hover:not(.active) { border-color: var(--sage); color: var(--sage); }
.chip-count {
  display: inline-block;
  margin-left: 3px;
  font-weight: 700;
  color: var(--grey-light);
  font-variant-numeric: tabular-nums;
}
.chip.active .chip-count { color: rgba(255,255,255,.75); }

/* ---------- Browse filter dropdowns (Category, Tags) ---------- */

/* Replaced two rows of chips. Chips are the right control for a handful of
   values and the wrong one for a tag vocabulary: the row wrapped to three
   lines, pushed the grid below the fold, and could still only hold one
   selection at a time. See _filter_dropdown.html. */
.filter-dropdown { position: relative; }

.filter-dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: #fff;
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: none;
  color: var(--ink-muted);
  cursor: pointer;
}
.filter-dropdown-trigger:hover { border-color: var(--sage); color: var(--sage); }
.fd-has-selection .filter-dropdown-trigger {
  background: var(--sage);
  border-color: var(--sage);
  color: #fff;
}
.fd-trigger-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  font-size: 9.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.fd-trigger-count[hidden] { display: none; }
.fd-trigger-caret { font-size: 8px; opacity: .7; }

.filter-dropdown-panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  width: 270px;
  max-width: calc(100vw - 32px);
  padding: 10px;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 14px 36px rgba(0, 0, 0, .15);
  text-transform: none;
  letter-spacing: normal;
}
.filter-dropdown-panel[hidden] { display: none; }

.filter-dropdown-search {
  width: 100%;
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
}
.filter-dropdown-search:focus { outline: none; border-color: var(--sage); }

.filter-dropdown-list {
  margin-top: 8px;
  max-height: 260px;
  overflow-y: auto;
}

.filter-dropdown-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 7px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}
.filter-dropdown-option:hover { background: var(--cream); }
.filter-dropdown-option input { margin: 0; flex-shrink: 0; accent-color: var(--sage); }
.fd-option-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The two ways an option leaves the screen. Both are display:none rather
   than `disabled`, because a hidden checkbox still submits and a disabled
   one does not -- a filter you searched past must stay in force. */
.fd-filtered { display: none; }
.filter-dropdown:not(.fd-expanded):not(.fd-searching) .fd-extra { display: none; }

.filter-dropdown-none { margin: 8px 7px; font-size: 12.5px; color: var(--grey-light); }
.filter-dropdown-none[hidden] { display: none; }

.filter-dropdown-all,
.filter-dropdown-clear {
  font: inherit;
  font-size: 12px;
  background: none;
  border: 0;
  padding: 0;
  color: var(--sage);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
}
.filter-dropdown-all {
  display: block;
  margin: 8px 0 0 7px;
  font-weight: 500;
}
.filter-dropdown-all[hidden] { display: none; }
.filter-dropdown-all:hover,
.filter-dropdown-clear:hover { color: var(--sage-dark); text-decoration: underline; }

.filter-dropdown-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
}
.filter-dropdown-clear { color: var(--grey); }

@media (max-width: 480px) {
  /* Anchored to the right edge instead, so the Tags panel does not hang off
     the side of a phone. */
  .filter-dropdown:last-child .filter-dropdown-panel { left: auto; right: 0; }
}

.recipe-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 22px;
}

.recipe-tile {
  background: var(--card-bg);
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: box-shadow .15s ease, transform .15s ease;
}
.recipe-tile:hover { box-shadow: 0 6px 20px rgba(23,25,26,.08); transform: translateY(-2px); }
.recipe-tile a { color: inherit; display: flex; flex-direction: column; height: 100%; }
.recipe-tile .thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  background: #e6e4de url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40'%3E%3C/svg%3E");
  overflow: hidden;
}
.favorite-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(23,25,26,.55);
  color: #f0c419;
  border-radius: 50%;
  font-size: 13px;
  line-height: 1;
}
.cooked-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(23,25,26,.55);
  color: #a9d4b0;
  border-radius: 50%;
  font-size: 12px;
  line-height: 1;
}
.recipe-tile .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.recipe-tile .thumb.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--grey-light);
  font-family: 'Bodoni Moda', serif;
  font-size: 13px;
}
.recipe-tile .info { padding: 14px 16px 16px; flex: 1; display: flex; flex-direction: column; }
.recipe-tile .eyebrow-sm {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sage);
  margin-bottom: 6px;
}
.recipe-tile h3 {
  margin: 0 0 6px;
  font-size: 18px;
  line-height: 1.2;
  color: var(--ink);
}
.tile-rating { font-size: 11px; letter-spacing: 1px; margin-bottom: 2px; }
.tile-rating .star-filled { color: #e0b93e; }
.tile-rating .star-empty { color: var(--rule); }
.recipe-tile .tags { margin-top: auto; padding-top: 8px; display: flex; gap: 5px; flex-wrap: wrap; }
.recipe-tile .tag-pill {
  font-size: 9.5px;
  font-weight: 500;
  color: var(--grey);
  background: var(--cream);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 2px 9px;
}

/* Compact display mode: same markup as the tile grid (browse.html toggles
   this class onto #recipe-grid via JS -- see browse.html's extra_scripts)
   -- the photo and tag pills are just hidden rather than removed from the
   DOM, so switching back to Tiles needs no re-fetch. Multi-column list
   rows rather than a single column, since the whole point is fitting more
   recipes on screen at once. */
.recipe-grid.compact {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0 28px;
}
.recipe-grid.compact .recipe-tile {
  background: none;
  border: none;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  transition: background .1s ease;
}
.recipe-grid.compact .recipe-tile:hover {
  background: var(--cream);
  box-shadow: none;
  transform: none;
}
.recipe-grid.compact .thumb,
.recipe-grid.compact .favorite-badge,
.recipe-grid.compact .cooked-badge,
.recipe-grid.compact .tile-rating,
.recipe-grid.compact .tags {
  display: none;
}
.recipe-grid.compact .info {
  padding: 11px 6px;
  flex-direction: row;
  align-items: baseline;
  gap: 12px;
}
.recipe-grid.compact .eyebrow-sm {
  flex: none;
  width: 100px;
  margin-bottom: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recipe-grid.compact h3 {
  margin: 0;
  flex: 1;
  min-width: 0;
  font-size: 14.5px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--grey);
}
.empty-state a { font-weight: 600; }

/* ---------- Upload page ---------- */

.upload-card {
  max-width: 640px;
  margin: 0 auto;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 36px;
}
.dropzone {
  display: block;
  border: 2px dashed var(--rule);
  border-radius: 10px;
  padding: 48px 24px;
  text-align: center;
  color: var(--grey);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.dropzone.dragover { border-color: var(--sage); background: rgba(74,107,86,.05); }
.dropzone input[type=file] { display: none; }
.dropzone .hint { font-size: 12px; margin-top: 6px; }
.file-chosen { margin-top: 14px; font-size: 13px; color: var(--ink-soft); }

/* The two ways into the library, offered side by side at the top of Add
   Recipe. The "you're here" half is deliberately flat and quiet and the
   Bulk Import half is the one that looks clickable, so the pair reads as
   "there is another door" rather than as two buttons of equal weight. */
.add-routes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin: 18px 0 26px;
}
.add-route {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 16px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  text-decoration: none;
}
.add-route-eyebrow {
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--grey-light);
}
.add-route-title {
  font-family: 'Bodoni Moda', Didot, serif;
  font-size: 17px;
  color: var(--ink);
}
.add-route-body { font-size: 12.5px; line-height: 1.45; color: var(--ink-muted); }

.add-route-here { background: var(--cream); }
.add-route-here .add-route-eyebrow { color: var(--sage); }

.add-route-other { background: #fff; transition: border-color .15s ease, box-shadow .15s ease; }
.add-route-other .add-route-title { color: var(--sage-dark); }
.add-route-other:hover,
.add-route-other:focus-visible {
  border-color: var(--sage);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .07);
}
/* The title carries the arrow rather than a separate CTA line: the whole
   card is the target, so a second "click here" inside it is noise. */
.add-route-other .add-route-title::after { content: " \2192"; }

@media (max-width: 560px) {
  .add-routes { grid-template-columns: 1fr; }
}

.divider-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 26px 0;
  color: var(--grey-light);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.divider-or::before, .divider-or::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

textarea, input[type=text], input[type=url], input[type=search], input[type=email], input[type=password], select {
  width: 100%;
  font: inherit;
  padding: 10px 13px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: #fff;
  color: var(--ink);
}
textarea:focus, input:focus, select:focus { outline: none; border-color: var(--sage); }
textarea { min-height: 140px; resize: vertical; font-family: Karla, sans-serif; }

.btn {
  display: inline-block;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  padding: 11px 22px;
  border-radius: 999px;
  border: 1px solid var(--sage);
  background: var(--sage);
  color: #fff;
  cursor: pointer;
  text-align: center;
}
.btn:hover { background: var(--sage-dark); border-color: var(--sage-dark); }
.btn.secondary { background: #fff; color: var(--sage); }
.btn.secondary:hover { background: rgba(74,107,86,.06); }
.btn.danger { background: #fff; color: #a1503a; border-color: #d8c6bd; }
.btn.danger:hover { background: rgba(161,80,58,.08); border-color: #a1503a; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-row { display: flex; gap: 10px; margin-top: 18px; flex-wrap: wrap; }

.loading-state {
  display: none;
  text-align: center;
  padding: 40px 0;
  color: var(--grey);
}
.loading-state.active { display: block; }
.spinner {
  width: 32px; height: 32px;
  border: 3px solid var(--rule);
  border-top-color: var(--sage);
  border-radius: 50%;
  margin: 0 auto 14px;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.error-box {
  background: #fdf1ee;
  border: 1px solid #e8b7a8;
  color: #8a3a24;
  padding: 12px 16px;
  border-radius: 8px;
  font-size: 13px;
  margin-bottom: 18px;
}

.chef-review-banner {
  max-width: 900px;
  margin: 0 auto 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  background: #fdf6e3;
  border: 1px solid #e0b93e;
  color: #7a5c0a;
  padding: 12px 18px;
  border-radius: 10px;
  font-size: 13.5px;
  font-weight: 500;
}

.chef-review-banner-error {
  background: #fdecea;
  border-color: #e0a39a;
  color: #8a3a24;
}

/* ---------- Edit / form page ---------- */

.form-page { max-width: 820px; margin: 0 auto; }
.edit-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.edit-header-row h1 { margin: 0; }
.edit-header-row .btn-row { margin-top: 0; }
.form-section {
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 24px 28px;
  margin-bottom: 20px;
}
.form-section h2 {
  margin: 0 0 16px;
  font-size: 15px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--sage);
  font-family: Karla, sans-serif;
  font-weight: 600;
}

/* ---------- Help page: two columns, Downloads pinned to the right ---------- */
/* Wider than the shared .form-page max-width since there are now two columns
   side by side, not just one -- scoped to .help-page rather than raising
   .form-page's own max-width, which settings/upload/edit also use. */
.help-page { max-width: 1100px; }
.help-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 24px;
  align-items: start;
}
.help-aside { position: sticky; top: 20px; }
.help-aside .form-section { margin-bottom: 0; }
/* Two panels stack here (About, Downloads). The aside is sticky, so once the
   release notes are opened it can be taller than the window -- let it scroll
   itself rather than hiding its bottom edge off-screen. */
.help-aside .form-section + .form-section { margin-top: 20px; }
.help-aside { max-height: calc(100vh - 40px); overflow-y: auto; }
/* Three download links stacked in a narrow column read better than the
   default flex-wrap row, which would squeeze each button down to a couple
   words wide at 300px. */
.help-downloads-row { flex-direction: column; align-items: stretch; }
.help-downloads-row .btn { text-align: center; }
/* Help sections collapse (2026-10-16): each card is a <details>, closed on
   load, so the page reads as a list of headings. Child selectors only (>),
   so the glossary and release-notes <details> nested inside keep their own
   look. */
.help-section > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  list-style: none;
}
.help-section > summary::-webkit-details-marker { display: none; }
.help-section > summary::before {
  content: "\25B8";
  display: inline-block; /* a transform needs a box, or the open chevron never turns */
  color: var(--grey);
  transition: transform .15s ease;
}
.help-section[open] > summary::before { transform: rotate(90deg); }
.help-section > summary h2 { margin: 0; flex: 1; }
.help-section[open] > summary { margin-bottom: 16px; }
.help-section:not([open]) { padding-top: 16px; padding-bottom: 16px; }
.help-main .help-section:not([open]) { margin-bottom: 10px; }
.help-section > summary:focus-visible { outline: 2px solid var(--sage); outline-offset: 4px; border-radius: 4px; }
.help-summary-meta { font-size: 13px; color: var(--grey); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .help-section > summary::before { transition: none; }
}
@media print {
  .help-section > summary::before, #help-toggle-all { display: none; }
}
@media (max-width: 860px) {
  .help-layout { grid-template-columns: 1fr; }
  .help-aside { position: static; max-height: none; overflow-y: visible; }
}

.field-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 14px; }
/* Settings > Cook with Me: Tone, Volume, Test sound (1.17.0). The dropdown
   never gets narrower than its longest name ("Clear Bell", "Music Box") plus
   its padding and arrow; on a phone it takes the whole first row and Volume
   and Test sound share the second. */
.field-row.alert-tone-row { grid-template-columns: minmax(10rem, 1fr) minmax(8rem, 1fr) auto; align-items: end; }
.alert-tone-row select { min-width: 10rem; }
/* Two fields side by side on a wide screen, one above the other on a phone
   (Settings' new / confirm password). */
.field-row.field-row-pair { grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) {
  .field-row.field-row-pair { grid-template-columns: 1fr; }
  .field-row.alert-tone-row { grid-template-columns: 1fr auto; }
  .alert-tone-row .alert-tone-field { grid-column: 1 / -1; }
}
.field { margin-bottom: 14px; }
.field label {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  margin-bottom: 5px;
}
.ingredient-group, .step-row {
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 14px;
  margin-bottom: 12px;
  background: var(--cream);
}
.ingredient-item, .step-row .field-row { display: grid; grid-template-columns: 1fr 1fr 2fr auto; gap: 8px; margin-bottom: 8px; align-items: end; }
.step-row .field-row { grid-template-columns: 1fr 3fr auto; }
.remove-btn {
  background: none;
  border: 1px solid var(--rule);
  border-radius: 6px;
  color: #a24;
  cursor: pointer;
  padding: 8px 10px;
  font-size: 12px;
}
.remove-btn:hover { border-color: #a24; }
.add-btn {
  background: none;
  border: 1px dashed var(--rule);
  border-radius: 6px;
  color: var(--sage);
  cursor: pointer;
  padding: 8px 14px;
  font-size: 12px;
  font-weight: 600;
}
.add-btn:hover { border-color: var(--sage); }

.current-photo { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.current-photo img { width: 90px; height: 90px; object-fit: cover; border-radius: 6px; }

/* "Generate photo (AI)" button on the edit page: htmx adds .htmx-request to
   the element while its request is in flight, which we use to swap the
   label for a "generating" message and disable further clicks. */
#generate-photo-btn .btn-loading,
#regenerate-photo-btn .btn-loading { display: none; }
#generate-photo-btn.htmx-request,
#regenerate-photo-btn.htmx-request { pointer-events: none; opacity: .7; }
#generate-photo-btn.htmx-request .btn-label,
#regenerate-photo-btn.htmx-request .btn-label { display: none; }
#generate-photo-btn.htmx-request .btn-loading,
#regenerate-photo-btn.htmx-request .btn-loading { display: inline; }

[title] { position: relative; }

.tag-input-hint { font-size: 11px; color: var(--grey); margin-top: 4px; }

/* ---------- Chip editor: tags and categories on the edit page ---------- */

/* Search on top, what the recipe already has underneath. See
   _chip_editor.html for why that order rather than one combined field. */
.chip-editor { margin-bottom: 14px; }
.chip-editor label {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  margin-bottom: 5px;
}
/* The suggestion menu is absolutely positioned against this, so it opens
   directly under the input wherever the input happens to sit. */
.chip-editor-search { position: relative; margin-bottom: 6px; }

.chip-editor-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 7px 9px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--cream);
}
/* Not a field any more -- it is a read-out of what is assigned. The cream
   fill and the lighter border say so, against the white search box above. */
.chip-editor-pills { display: contents; }
.chip-editor-empty { font-size: 12px; color: var(--grey-light); }
.chip-editor-empty[hidden] { display: none; }

.chip-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 5px 3px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--cream);
  font-size: 12px;
  color: var(--ink-soft);
  max-width: 100%;
}
.chip-pill-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-pill-remove {
  appearance: none;
  border: 0;
  background: none;
  padding: 0 3px;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--grey);
  cursor: pointer;
}
.chip-pill-remove:hover { color: var(--ink); }

/* Inherits the app's standard text-input look from the global rule above;
   only the size is pinned here, to sit with the other fields in the row. */
.chip-editor-input { font-size: 13px; padding: 9px 11px; }

.chip-suggestions {
  position: absolute;
  z-index: 30;
  top: 100%;
  left: 0;
  right: 0;
  margin: 4px 0 0;
  padding: 4px;
  list-style: none;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .12);
  max-height: 232px;
  overflow-y: auto;
}
.chip-suggestions[hidden] { display: none; }
.chip-suggestion {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 6px;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
}
.chip-suggestion:hover,
.chip-suggestion.active { background: var(--cream); }
.chip-suggestion-count { font-size: 10.5px; color: var(--grey-light); flex-shrink: 0; }
.chip-suggestion-new .chip-suggestion-count { color: var(--sage); }

/* ---------- Oven temperature picker ---------- */

.oven-field { max-width: 320px; }
.oven-custom { display: flex; gap: 10px; margin-top: 8px; }
/* Required, not belt-and-braces: `display: flex` above is an AUTHOR rule and
   the browser's own `[hidden] { display: none }` is a USER-AGENT rule, so
   without this the hidden attribute is simply ignored and the custom fields
   are always on screen. */
.oven-custom[hidden] { display: none; }
.oven-unit {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 1 1 0;
}
.oven-unit input[type=number] {
  width: 100%;
  min-width: 0;
  font: inherit;
  font-size: 13px;
  padding: 9px 10px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
}
.oven-unit span { font-size: 13px; color: var(--ink-muted); flex-shrink: 0; }

/* Help page's tile descriptions -- larger and more readable than the base
   .tag-input-hint size (which is tuned for a small inline field hint, not
   a paragraph of prose). Scoped to help.html only via this extra class,
   so every OTHER .tag-input-hint (Settings, Edit, Cook with Me picker,
   etc.) is unaffected. */
.help-desc { font-size: 14.5px; line-height: 1.6; }

/* ---------- Recipe detail action bar ---------- */
/* Three zones by intent, left to right: engagement (favorite/rating/made-
   this), utility cooking tools (a segmented control), and manage/
   destructive actions (one unified pill). Colors below are dedicated
   --bar-* tokens rather than the site's existing --sage/--rule/etc: this
   redesign's palette is close to but not identical to those (e.g. the
   hairline here is #E4E0D4 vs the site's --rule #d8d6cf), and reusing the
   old names would either drift this bar's exact spec colors or silently
   change every other button on the site that reads --sage/--rule. */
:root {
  --bar-sage: #6C8F79;
  --bar-sage-hover: #5C7C68;
  --bar-charcoal: #33403A;
  --bar-hairline: #E4E0D4;
  --bar-muted: #9A9284;
  --bar-red: #B04A3C;
  --bar-red-bg: #FBEDEA;
  --bar-border-neutral: #D8D2C0;
  --bar-hover-fill: #F1EEE4;
}

.action-bar {
  max-width: 900px;
  margin: 0 auto 14px;
  background: #fff;
  border: 1px solid var(--bar-hairline);
  border-radius: 16px;
  padding: 14px 18px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* Tighter than the handoff's own 16px: at this app's existing 900px
     card width (matched here so the bar and the card below it align),
     all 3 zones at the handoff's literal padding/gap values needed
     ~998px and wrapped onto 2 rows even on a wide desktop viewport --
     confirmed live on the deployed site and dialed back against the
     real rendered widths (not just estimated) until one row fits with
     a little room to spare. See the matching trims on each zone's own
     buttons below. */
  gap: 10px;
  flex-wrap: wrap;
  /* Every pill/icon button below uses inline-flex + align-items:center to
     center its icon and label -- unlike the site's older .btn (plain
     inline-block + text-align:center), flex-centered text can land on a
     fractional pixel row, and Windows Chrome's ClearType subpixel
     antialiasing renders fractionally-positioned text with visible
     red/blue fringing at its edges. Forcing grayscale antialiasing here
     sidesteps that regardless of the exact fractional offset. */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
.action-icon { display: block; flex: none; }

/* Zone 1: Engagement -- favorite, rating, separated by a thin divider
   rather than a gap alone so the two unrelated controls read as distinct
   rather than one run-on group. ("I've made this" was removed from this
   zone to reduce header-bar compaction.) */
.action-zone-engage { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.action-divider { width: 1px; height: 20px; background: var(--bar-hairline); flex: none; }

/* Favorite: a single heart icon, no label -- icon-only round button
   (matches the sizing/shape of the manage zone's .action-icon-btn) rather
   than a text pill, per request not to use a star for this. */
.action-favorite-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 29px;
  height: 29px;
  border-radius: 50%;
  border: 1px solid var(--bar-border-neutral);
  background: transparent;
  color: var(--bar-charcoal);
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.action-favorite-btn:hover { background: var(--bar-hover-fill); }
.action-favorite-btn.active {
  background: var(--bar-sage);
  border-color: var(--bar-sage);
  color: #fff;
}
.action-favorite-btn.active:hover { background: var(--bar-sage-hover); border-color: var(--bar-sage-hover); }

/* Star rating: 5 plain buttons, filled cumulatively (both the SVG's own
   fill and its color) up to the stored rating -- no JS hover-preview (a
   partial CSS-only preview would look inconsistent against an
   already-set rating, since :hover on one star can't un-highlight a
   sibling that's actually saved). Idle color is the handoff's muted
   gray; filled keeps the app's existing star-gold so it still matches
   the rating stars shown on the browse grid tiles. */
.action-rating { display: inline-flex; align-items: center; gap: 1px; }
.action-rating-star {
  display: inline-flex;
  background: none;
  border: none;
  padding: 1px;
  line-height: 0;
  color: #C9C2AC;
  cursor: pointer;
}
.action-rating-star:hover { color: var(--bar-sage); }
.action-rating-star.filled { color: #e0b93e; }

/* Zone 2: cooking tools, as a segmented control -- cream group background
   (reuses the site's existing --cream, which is already effectively
   identical to the handoff's #F7F5EF) with borderless pills that turn
   white on hover. */
.action-seg {
  display: inline-flex;
  align-items: center;
  gap: 0px;
  background: var(--cream);
  border-radius: 999px;
  padding: 3px;
  flex-wrap: wrap;
}
.action-seg-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 9px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--bar-charcoal);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.action-seg-btn:hover { background: #fff; }
.action-seg-btn:disabled { opacity: .5; cursor: not-allowed; }
.action-seg-btn:disabled:hover { background: transparent; }
/* Available for a genuinely "currently engaged" tool, but nothing in this
   group triggers it by default: a button here only stays visible while
   its own pending state (recipe.pending_scale / recipe.chef_review) is
   false, at which point there's nothing left to mark as active -- kept
   for design-system completeness rather than wired to a real state. */
.action-seg-btn.active { background: #fff; box-shadow: 0 1px 3px rgba(23,25,26,.12); }

/* Zone 3: manage/destructive, as one unified pill -- Edit/PDF as icon-only
   circles (document-management actions), Delete set apart by a divider
   and muted until hovered so it doesn't compete visually with the rest
   of the bar. */
.action-manage {
  display: inline-flex;
  align-items: center;
  gap: 0px;
  background: #fff;
  border: 1px solid var(--bar-hairline);
  border-radius: 999px;
  padding: 3px 4px;
  box-shadow: 0 1px 2px rgba(23,25,26,.05);
}
.action-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 29px;
  height: 29px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--bar-charcoal);
  cursor: pointer;
}
.action-icon-btn:hover { background: var(--bar-hover-fill); }
.action-manage-divider { width: 1px; height: 18px; background: var(--bar-hairline); flex: none; margin: 0 6px; }
.action-delete-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 9px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--bar-muted);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.action-delete-btn:hover { background: var(--bar-red-bg); color: var(--bar-red); }

@media (max-width: 700px) {
  .site-header { padding: 14px 18px; flex-direction: column; gap: 10px; }
  .site-nav {
    --nav-gap: 20px;
  }
  .site-header nav a { letter-spacing: .05em; }
  .container { padding: 20px 16px 40px; }
  .field-row { grid-template-columns: 1fr 1fr; }

  /* The three zones already wrap onto their own rows at this width (the
     bar itself is flex-wrap); just tighten padding/gaps so each zone's
     own contents don't also need to wrap internally as often. */
  .action-bar { padding: 12px 14px; gap: 10px; }
  .action-zone-engage { gap: 8px; }
}

/* ---------- Chef review page ---------- */

.chef-review-page { max-width: 820px; margin: 0 auto; }

.cr-header { margin-bottom: 24px; }
.cr-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sage);
  margin-bottom: 6px;
}
.cr-header h1 { margin: 0 0 10px; font-size: 28px; }
.cr-summary {
  margin: 0;
  padding: 14px 18px;
  background: #fff;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--sage);
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-soft);
}

.cr-findings { margin-bottom: 30px; display: flex; flex-direction: column; gap: 10px; }
.cr-finding {
  background: #fff;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--grey-light);
  border-radius: 8px;
  padding: 12px 16px;
}
.cr-finding.cr-severity-important { border-left-color: #b5482f; }
.cr-finding.cr-severity-suggestion { border-left-color: #d8a83e; }
.cr-finding.cr-severity-note { border-left-color: var(--grey-light); }
.cr-finding-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.cr-badge {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--cream);
  color: var(--grey);
  border: 1px solid var(--rule);
}
.cr-severity-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--grey-light);
}
.cr-finding-summary { margin: 0 0 4px; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.cr-finding-rationale { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-muted); }

.cr-diffs-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}
.cr-diffs-header h2 { margin: 0; font-size: 18px; }
.cr-select-buttons { display: flex; gap: 8px; }
.cr-select-btn {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--sage);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
}
.cr-select-btn:hover { border-color: var(--sage); }

.cr-diff-card {
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 16px 18px;
  margin-bottom: 14px;
}
.cr-diff-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
  margin-bottom: 12px;
}
.cr-diff-toggle input { width: 17px; height: 17px; accent-color: var(--sage); flex-shrink: 0; }
/* A field edited after the chef's review (UAT-005): starts unticked, says why. */
.cr-diff-edited { border-color: #e0a39a; }
.cr-edited-note { margin: -4px 0 10px; font-size: 13px; color: #8a3a24; }
.cr-diff-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}
.cr-diff-col-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--grey-light);
  margin-bottom: 6px;
}
.cr-diff-col-new .cr-diff-col-label { color: var(--sage); }
.cr-diff-col-body {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-soft);
}
.cr-diff-col-new .cr-diff-col-body { color: var(--ink); }
.cr-empty { color: var(--grey-light); font-style: italic; }
.cr-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.cr-chip {
  font-size: 11px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--cream);
  border: 1px solid var(--rule);
}
.cr-group-heading {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  margin: 8px 0 4px;
}
.cr-group-heading:first-child { margin-top: 0; }
.cr-ingredients, .cr-steps { margin: 0 0 4px; padding-left: 18px; }
.cr-ingredients li, .cr-steps li { margin-bottom: 3px; }

.cr-no-changes {
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 24px;
  text-align: center;
}
.cr-no-changes p { margin: 0 0 4px; color: var(--ink-muted); font-size: 14px; }
.cr-no-changes .btn-row { justify-content: center; margin-top: 14px; }

@media (max-width: 640px) {
  .cr-diff-columns { grid-template-columns: 1fr; }
}

/* ---------- Settings page ---------- */

.settings-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
}
.settings-checkbox input[type="checkbox"] { width: 16px; height: 16px; }
.settings-checkbox-small { margin-top: 8px; font-size: 12px; color: var(--grey); }
.settings-subfields {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
}
.integration { border: 1px solid var(--rule); border-radius: 8px; margin-bottom: 12px; }
.integration:last-child { margin-bottom: 0; }
.integration summary,
.collapsible-section summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  color: var(--ink);
}
.integration summary::-webkit-details-marker,
.collapsible-section summary::-webkit-details-marker { display: none; }
.integration summary::before,
.collapsible-section summary::before {
  content: "\25B8";
  display: inline-block;
  margin-right: 10px;
  color: var(--grey);
  transition: transform .15s ease;
}
.integration[open] summary::before,
.collapsible-section[open] summary::before { transform: rotate(90deg); }
.integration-name { flex: 1; }
.integration-status {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--grey);
  background: var(--cream);
  border-radius: 999px;
  padding: 3px 10px;
}
.integration-status.on { color: var(--sage-dark); background: rgba(74,107,86,.12); }
.integration-body { padding: 16px 18px 18px; border-top: 1px solid var(--rule); }
.settings-saved-banner {
  background: rgba(74,107,86,.1);
  border: 1px solid var(--sage);
  color: var(--sage-dark);
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 18px;
}

/* ---------- Chef-wait modal (shown while a chef-LLM call is in flight) ---------- */

.chef-wait-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(23, 25, 26, .45);
  padding: 20px;
}
.chef-wait-overlay[hidden] { display: none; }

/* Cancel sits under the status line. Quiet on purpose: it is the way out of a
   long wait, not the thing the person is meant to press. */
.chef-wait-cancel { margin-top: 22px; }
.chef-wait-cancel:disabled { opacity: .6; cursor: default; }
#upload-cancel { margin-top: 18px; }

/* A passing message at the bottom of the page ("Cancelled -- nothing was
   changed"). Above the chef-wait overlay (1000) so it is readable the moment
   the overlay is dismissed. */
.chef-toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 1100;
  max-width: min(92vw, 420px);
  padding: 12px 18px;
  border-radius: 10px;
  background: var(--ink, #1f2a24);
  color: var(--card-bg, #fff);
  font-size: .95rem;
  box-shadow: 0 8px 28px rgba(0,0,0,.25);
}
.chef-toast[hidden] { display: none; }

.chef-wait-modal {
  background: var(--card-bg);
  border-radius: 14px;
  padding: 36px 40px;
  max-width: 360px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,.25);
}

.chef-wait-spinner {
  width: 40px;
  height: 40px;
  margin: 0 auto 18px;
  border-radius: 50%;
  border: 3px solid var(--rule);
  border-top-color: var(--sage);
  animation: chef-wait-spin 0.9s linear infinite;
}
@keyframes chef-wait-spin { to { transform: rotate(360deg); } }

.chef-wait-title {
  font-family: 'Bodoni Moda', serif;
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 8px;
}

.chef-wait-status {
  font-size: 13.5px;
  color: var(--ink-muted);
  line-height: 1.6;
}

/* Print text-size picker, shown before a PDF download (see
   recipe_detail.html) -- same overlay/modal shape as .chef-wait-overlay
   above, but interactive rather than a spinner. */
.pdf-size-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(23, 25, 26, .45);
  padding: 20px;
}
.pdf-size-overlay[hidden] { display: none; }

.pdf-size-modal {
  position: relative;
  background: var(--card-bg);
  border-radius: 14px;
  padding: 32px 34px 26px;
  max-width: 380px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0,0,0,.25);
}
.pdf-size-modal h2 { margin: 0 0 8px; font-size: 20px; }

.pdf-size-close {
  position: absolute;
  top: 10px;
  right: 14px;
  background: none;
  border: none;
  font-size: 22px;
  line-height: 1;
  color: var(--grey);
  cursor: pointer;
}
.pdf-size-close:hover { color: var(--sage); }

.pdf-size-toggle { display: flex; flex-direction: column; gap: 8px; }
.pdf-size-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  font-size: 13.5px;
  cursor: pointer;
}
.pdf-size-pill:has(input:checked) { border-color: var(--sage); background: rgba(74,107,86,.06); }
.pdf-size-pill input { accent-color: var(--sage); width: 16px; height: 16px; }


/* ---------- Signed-out pages: login and first-run setup ---------- */
/* Their own shell (auth_base.html), so none of the site-chrome rules above
   apply -- the header, nav and container aren't on the page at all. */
.auth-body {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 20px;
  min-height: 100vh;
}
.auth-page { width: 100%; max-width: 420px; }
.auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-bottom: 22px;
}
.auth-glyph { width: 52px; height: 52px; border-radius: 12px; display: block; }
.auth-wordmark {
  font-family: 'Fraunces', 'Bodoni Moda', Didot, serif;
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: 26px;
  color: var(--ink);
}
.auth-wordmark .accent { color: var(--sage); font-style: italic; }
.auth-card {
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px 30px 30px;
  box-shadow: 0 10px 30px rgba(0,0,0,.06);
}
.auth-title { margin: 0 0 6px; font-size: 26px; }
.auth-lede {
  margin: 0 0 18px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-muted);
}
.auth-title + .auth-form, .auth-title + .error-box { margin-top: 18px; }
.auth-hint { margin: 6px 0 0; font-size: 11.5px; color: var(--grey); }
/* Full width rather than .btn's inline sizing: there is exactly one action
   on these pages, and a small button floating left reads as optional. */
.auth-submit { width: 100%; margin-top: 8px; justify-content: center; }


/* ---------- Household members (/household) ---------- */
.member-list { list-style: none; margin: 0; padding: 0; }
.member-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.member-row:last-child { border-bottom: 0; padding-bottom: 0; }
.member-identity { min-width: 0; }
.member-name {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.member-email { font-size: 13px; color: var(--ink-muted); margin-top: 2px; word-break: break-all; }
.member-meta { font-size: 11.5px; color: var(--grey); margin-top: 4px; }
.member-badge {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 2px 8px;
}
.member-badge-admin { color: var(--sage); border-color: var(--sage); }
.member-remove-form,
.member-role-form { margin: 0; flex-shrink: 0; }
.member-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.member-note { font-size: 11.5px; color: var(--grey-light); flex-shrink: 0; }

/* Cost page: the average-per-call figure that sits beside each row's total.
   A fixed minimum width so the figures line up down the list like a column
   instead of drifting with the length of each number. */
.cost-avg { font-size: 12.5px; color: var(--grey-light); min-width: 118px; text-align: right; font-variant-numeric: tabular-nums; }
.cost-avg-unit { font-size: 11px; }
.member-actions strong { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------- Recycle bin ---------- */

/* Same row shape as the member list above -- identity on the left,
   buttons on the right, stacking on a phone. Reusing the shape rather
   than the class so the two can drift without surprising each other. */
.bin-list { list-style: none; margin: 0; padding: 0; }
.bin-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.bin-row:last-child { border-bottom: 0; padding-bottom: 0; }
.bin-identity { min-width: 0; }
.bin-title {
  font-family: 'Bodoni Moda', Didot, serif;
  font-size: 17px;
  color: var(--ink);
}
.bin-meta { font-size: 12px; color: var(--ink-muted); margin-top: 3px; }
.bin-countdown { font-size: 11.5px; color: var(--grey); margin-top: 4px; }
.bin-countdown-soon { color: #a1503a; font-weight: 500; } /* same rust as .btn.danger */
.bin-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.bin-empty { font-size: 13px; color: var(--ink-muted); margin: 0; }

/* The one-shot banner over the Browse grid straight after a delete. */
.bin-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.bin-banner-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.bin-banner-actions form { margin: 0; }

@media (max-width: 520px) {
  .bin-row { flex-direction: column; align-items: flex-start; gap: 10px; }
}
@media (max-width: 520px) {
  /* The buttons drop below the name rather than squeezing the email into a
     two-character column. */
  .member-row { flex-direction: column; align-items: flex-start; gap: 10px; }
}

/* ── Nav links for work already in flight ────────────────────────────────
   Cook with Me and Meal Plan are the only two features that keep running
   when you navigate away. Until they said so, the way back to a cook you
   had wandered off from was remembering it existed -- a poor property for
   a feature whose whole premise is that you're busy doing something else.

   A filled pill rather than a dot or a colour change: the nav is 12.5px
   uppercase letter-spaced text in a muted grey, where "slightly different
   grey" reads as nothing at all from across a kitchen. */
.site-header nav a.nav-active-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--sage);
  color: #fff;
}
.site-header nav a.nav-active-pill:hover {
  background: var(--sage-dark);
  color: #fff;
}
/* "28 min", "2 left" -- the number is the point, so it gets the weight, but
   at reduced opacity so the feature's name still reads first. */
.nav-active-detail {
  font-weight: 700;
  opacity: .82;
  letter-spacing: .06em;
}
.nav-active-detail::before {
  content: "·";
  margin-right: 6px;
  opacity: .7;
  font-weight: 600;
}
@media (max-width: 760px) {
  /* The header already stacks to a column down here and the nav wraps, so
     the pills cost a row rather than an overflow. Trimmed anyway: two of
     them plus five plain links is a lot of width to find. */
  .site-header nav a.nav-active-pill { padding: 4px 9px; }
}

/* ── Settings: three groups (1.17.0) ────────────────────────────────────
   You / Recipes / Kitchen, each a small heading over its cards. Card titles
   are h3 here (the group is the h2) but look like every other card title. */
.settings-group { margin-top: 34px; }
.settings-page .edit-header-row { margin-bottom: 18px; }
.settings-group:first-of-type { margin-top: 0; }
.settings-page .form-section > .field:last-child { margin-bottom: 0; }
.settings-group-title {
  font-family: 'Fraunces', 'Bodoni Moda', Didot, serif;
  font-size: 24px;
  font-weight: 500;
  color: var(--ink);
  margin: 0 0 4px;
  padding-bottom: 8px;
  border-bottom: 2px solid var(--sage);
}
.settings-group-note { margin: 8px 0 14px; font-size: 13px; color: var(--ink-muted); }
.settings-group-title + .form-section,
.settings-group-title + form { margin-top: 14px; }
.settings-page .form-section h3 {
  margin: 0 0 16px;
  font-size: 15px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--sage);
  font-family: Karla, sans-serif;
  font-weight: 600;
}
.settings-subhead { margin: 0 0 10px; font-family: Karla, sans-serif; font-size: 14px; font-weight: 700; color: var(--ink); }
.settings-divider { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--rule); }
.settings-save-row { margin: -6px 0 20px; }
.form-section .settings-save-row { margin: 18px 0 0; }
/* The model rates sit inside the Costs card, under its link. */
.settings-nested { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule); }
.settings-nested > summary .integration-name { font-family: Karla, sans-serif; font-size: 14px; font-weight: 700; }

/* ── Settings: the "Elsewhere" links ─────────────────────────────────────
   Tag review, Manage access and Bulk import were three full sections at the
   TOP of Settings, each a heading, a three-line paragraph and a button --
   a screenful of navigation standing between you and the settings you came
   to change. They are not settings; they are links to other pages, and one
   list of them says the same thing in a fifth of the height. */
.settings-links {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}
.settings-links li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 13px 0;
  border-bottom: 1px solid var(--rule);
}
.settings-link-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.settings-link-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
}
/* One line, and it is allowed to be the only explanation: the long version
   of each of these lives on the page the link goes to, and on Help. */
.settings-link-note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--grey);
}
.settings-links .btn { flex: none; }

@media (max-width: 560px) {
  /* Stacked, with the button under its own description rather than squeezed
     against it. */
  .settings-links li {
    flex-direction: column;
    align-items: flex-start;
    gap: 9px;
  }
}

/* ── The chef's rates, on the Settings page ──────────────────────────────
   Four small fields per model, on one line on a desktop and wrapping on a
   phone. Deliberately plain inputs rather than a table: a table of four
   numeric columns is unreadable at 375px, and this list already has the
   member-row shape the rest of the page uses. */
.llm-price-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-top: 8px;
}
.llm-price-fields label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: 'Space Mono', monospace;
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.llm-price-fields input {
  /* Wide enough for the word "unknown", which is the placeholder that tells
     you a rate is not set. 8ch was sized for "15.00" and clipped it to
     "unknow" -- truncating the one word on the row that carries the meaning.
     A px width rather than ch because ch is the width of a ZERO, and every
     character in "unknown" is wider than that. */
  width: 96px;
  padding: 6px 8px;
  font: inherit;
  font-size: 13.5px;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  background: var(--cream);
  border: 1px solid var(--rule);
  border-radius: 6px;
}
.llm-price-fields .llm-price-note { flex: 1 1 240px; }
.llm-price-fields .llm-price-note input { width: 100%; }
.llm-price-row { align-items: flex-start; }
@media (max-width: 560px) {
  .llm-price-fields input { width: 84px; }
  .llm-price-fields .llm-price-note { flex-basis: 100%; }
}


/* A whole page section that collapses, as opposed to .integration, which is a
   card INSIDE one. Shares the summary chevron and the status pill above, but
   deliberately NOT the .integration class itself: half the e2e suite reaches
   the chef key via `details.integration` .first(), and quietly making that
   selector match a third element is how a dozen specs start failing somewhere
   far from the cause. */
.collapsible-section > summary { padding: 0 0 2px; }
.collapsible-section > summary .integration-name {
  font-family: 'Bodoni Moda', Georgia, serif;
  font-size: 19px;
  font-weight: 600;
}
.collapsible-section[open] > summary { margin-bottom: 14px; }

/* ---------------------------------------------------------------------------
   iOS 26's scroll edge effect, in an installed web app
   ---------------------------------------------------------------------------
   Added to the home screen and opened from there, iOS 26 paints a progressive
   blur across roughly the top 40pt of the viewport -- part of Liquid Glass,
   the same treatment native apps get under a navigation bar. It is system
   chrome drawn ABOVE the web view, so nothing in the page can switch it off:
   there is no meta tag, no manifest key and no CSS property for it. The only
   thing a page can do is keep content out of the band, which is what this
   does. Reported as the logo looking half-faded at the top of the screen.

   env(safe-area-inset-top) resolves to 0 here, because the viewport is not
   edge-to-edge -- there is no viewport-fit=cover and the status bar style is
   `default`, so iOS already insets the web view below the status bar. It is
   kept in the calc so the rule stays correct if the viewport ever does go
   edge-to-edge, at which point the inset becomes real and has to be added.

   Scoped twice on purpose. `display-mode: standalone` excludes a normal
   Safari tab, which has its own toolbar and no edge effect, and the
   -webkit-touch-callout support test excludes Chrome, Firefox and installed
   Android apps -- none of which do this, and all of which would just get a
   header 40px taller for nothing.

   MUST STAY AFTER the max-width:700px block above, which sets `padding` in
   full and would otherwise reset this back.
   ------------------------------------------------------------------------ */
@supports (-webkit-touch-callout: none) {
  @media (display-mode: standalone) {
    .site-header { padding-top: calc(env(safe-area-inset-top) + 40px); }
  }
}

/* ---------- Back button (1.8.0) ----------
   Floats bottom-left on every page but Browse. Translucent so it never hides
   what's under it; frosted so it stays readable over a photo or a list. */
.page-back {
  position: fixed;
  left: calc(16px + env(safe-area-inset-left, 0px));
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  /* Below the floating panels (Ask the Chef, timers: 40) so an open panel
     covers it rather than the other way round. */
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px 10px 12px;
  border: 1px solid rgba(23, 25, 26, .14);
  border-radius: 999px;
  background: rgba(255, 255, 255, .55);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--ink);
  font: 500 14px Karla, Helvetica, sans-serif;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .08);
  cursor: pointer;
  opacity: .85;
  transition: opacity .15s ease, background .15s ease;
}
.page-back span { font-size: 20px; line-height: 1; margin-top: -2px; }
.page-back:hover, .page-back:focus-visible { opacity: 1; background: rgba(255, 255, 255, .85); }
/* Out of the way of what already lives bottom-left: the pickers' selection
   bar, and the Timers chip on the recipe page, Kitchen Mode helpers and the
   live cook. */
body:has(.cook-selection-bar:not([hidden])) .page-back { bottom: calc(96px + env(safe-area-inset-bottom, 0px)); }
body:has(.cook-timers-chip:not([hidden])) .page-back { bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
@media print { .page-back { display: none; } }

/* Settings > Ingredient tags: each flagged tag is a checkbox (1.8.2), so a
   tag worth keeping can be unticked before anything is removed. */
.ingredient-tags-list { padding-left: 18px; }
.ingredient-tags-list li { margin-bottom: 8px; }
.ingredient-tag-pick {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 2px 4px 2px 0;
  padding: 3px 10px 3px 6px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
}
.ingredient-tag-pick input { accent-color: var(--sage); }
.ingredient-tag-pick:has(input:not(:checked)) { opacity: .6; text-decoration: line-through; }

/* 1.13.0: the PDF dialog shows only the actions this device can use. .btn
   sets its own display, which beats the browser's [hidden] rule, so say it
   here. */
.pdf-actions [hidden] { display: none; }
.pdf-action-status:empty { display: none; }
