:root {
  --bg: #fbf7f0;
  --bg2: #f3ecdf;
  --card: #ffffff;
  --card2: #fdf6ec;
  --text: #2b2521;
  --muted: #7d7267;
  --line: #e6dccb;
  --accent: #d97b3a;      /* the orange on the boards */
  --accent2: #3f7a4f;     /* herb green */
  --sun: #f4b02e;         /* sunny yellow */
  --berry: #d1477a;       /* berry */
  --danger: #d64027;
  --shadow: 0 6px 20px rgba(60, 40, 20, 0.10);
  --sp: 16px;
  /* spacing scale — section rhythm comes from these, not ad-hoc numbers */
  --s1: 8px; --s2: 14px; --s3: 22px; --s4: 34px; --s5: 52px;
  --radius: 14px;
  color-scheme: light;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: "Nunito Sans", system-ui, sans-serif;
  font-size: 16px;
}
h1, h2, h3, .serif { font-family: "Lora", Georgia, serif; }
a { color: inherit; }
button { font-family: inherit; cursor: pointer; }
.loading, .empty { color: var(--muted); text-align: center; padding: 48px 16px; font-style: italic; }

/* ---- buttons ---- */
.btn {
  border: 1px solid var(--line); background: var(--card); color: var(--text);
  border-radius: 999px; padding: 9px 16px; font-size: 14px; font-weight: 600; text-decoration: none;
  transition: background .15s, border-color .15s, transform .05s;
}
.btn:hover { border-color: #c9b8a0; background: var(--card2); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--accent); color: #ffffff; border-color: transparent; font-weight: 600; }
.btn.ghost { background: var(--card); }
.btn.danger { color: var(--danger); border-color: rgba(224,106,90,.4); background: transparent; }
/* On, for the review flag — the same amber as the badge on the card, so the
   button and the mark it controls are visibly the same thing. It was a plain
   .btn against .btn.ghost before: cream on a cream page, next to transparent
   on a cream page, which is no difference at all. */
.btn.review-on { background: rgba(176,116,10,.94); color: #fff; border-color: transparent; font-weight: 600; }
.btn.review-on:hover { border-color: transparent; background: rgba(150,98,8,.96); }
.btn.sm { padding: 6px 12px; font-size: 13px; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn:disabled:hover { border-color: var(--line); }
.icon-btn { background: transparent; border: none; color: var(--muted); font-size: 20px; padding: 4px 8px; border-radius: 8px; }
.icon-btn:hover { color: var(--text); background: rgba(0,0,0,.05); }

/* ---- rail ---- */
#app { display: flex; min-height: 100vh; }
.page { flex: 1; min-width: 0; }
.rail {
  position: sticky; top: 0; height: 100vh; width: 76px; flex: none;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 18px 8px; background: var(--bg2); border-right: 1px solid var(--line);
}
.rail-brand { margin-bottom: 14px; }
.rail-brand img { width: 30px; height: 34px; display: block; }
.rail-item {
  position: relative; width: 60px; padding: 9px 2px; border-radius: 10px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  color: var(--text); text-decoration: none; font-size: 10px; font-weight: 600; text-align: center;
}
.rail-item svg, .tab svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.rail-item:hover { background: rgba(0,0,0,.05); }
.rail-item.on { background: var(--accent); color: #fff; font-weight: 700; }
.rail-item.me { margin-top: auto; }
.avatar {
  width: 24px; height: 24px; border-radius: 50%; background: var(--accent); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; font-style: normal;
}
.rail-item.on .avatar { background: #fff; color: var(--accent); }
.tab-badge {
  position: absolute; top: 4px; right: 6px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--accent); color: #fff; font-style: normal; font-weight: 700;
  font-size: 10px; line-height: 16px; text-align: center; pointer-events: none;
}
.rail-item.on .tab-badge { background: #fff; color: var(--accent); }

/* Rows for the More, You and Backups pages. */
.room-list { display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; margin-bottom: 22px; }
.room-row {
  display: flex; align-items: center; gap: 14px; padding: 14px 16px; width: 100%; text-align: left;
  border: 0; border-top: 1px solid var(--line); background: none; color: var(--text); font: inherit; text-decoration: none;
}
.room-row:first-child { border-top: 0; }
.room-row:hover { background: var(--bg2); }
.room-row svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; color: var(--muted); }
.room-row span { display: flex; flex-direction: column; }
.room-row small { color: var(--muted); font-size: 13px; }
.room-row .avatar { width: 40px; height: 40px; font-size: 17px; }

.name-row { display: flex; gap: 10px; max-width: 420px; }
.name-row input { flex: 1; }

/* The phone's top bar: the collection's name, nothing else. */
.topbar {
  display: none; position: sticky; top: 0; z-index: 20; backdrop-filter: blur(10px);
  background: rgba(251,246,236,.82); border-bottom: 1px solid var(--line);
  align-items: center; gap: 12px; padding: 12px 18px;
}
.topbar .brand {
  display: inline-flex; align-items: center; gap: 8px; font-family: "Lora", Georgia, serif;
  font-weight: 700; font-size: 20px; text-decoration: none; color: inherit;
}
.topbar .brand { flex: 1; min-width: 0; }
.topbar .brand .mark { width: 22px; height: 26px; }
:root { --rail: 76px; }
@media (max-width: 640px) {
  :root { --rail: 0px; }
  .rail { display: none; }
  .topbar { display: flex; }
}

.wrap { max-width: 1040px; margin: 0 auto; padding: 20px 18px 80px; }

/* ---- reading split: the list in a column, the recipe in a pane beside it ---- */
.page.split { display: flex; align-items: flex-start; }
.page.split > .wrap { width: 380px; flex: none; max-width: none; padding: 20px 16px 80px; }
.page.split .pane { flex: 1; min-width: 0; position: sticky; top: 0; height: 100vh; overflow: auto; border-left: 1px solid var(--line); }
.pane-wrap { max-width: 1040px; margin: 0 auto; padding: 20px 40px 80px; }
.page.split .controls > :not(.search):not(h1), .page.split .sticky-chips, .page.split .count { display: none; }
.page.split .list-head h1 { font-size: 24px; flex-basis: 100%; }
.page.split .sticky-controls { margin: 0 -16px; padding-left: 16px; padding-right: 16px; }
.detail-top { display: flex; align-items: center; gap: 10px; }
.pager { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 13px; }
.pager .btn { padding: 4px 10px; font-size: 16px; line-height: 1; }
.pager [aria-disabled] { opacity: .35; }

/* Cards as rows: the list column and the phone. */
.grid.rows { grid-template-columns: 1fr; gap: 8px; }
.rows .rcard { flex-direction: row; align-items: center; box-shadow: none; border-radius: 12px; }
.rows .rcard:hover { transform: none; box-shadow: none; background: var(--card2); }
.rows .rcard.current { border-color: var(--accent); background: var(--card2); }
.rows .cover-box, .rows .rcard .cover { width: 64px; height: 64px; aspect-ratio: 1; flex: none; }
.rows .rcard .cover { transform: none !important; }   /* the saved zoom is for the 4:3 card */
.rows .rcard .cover.placeholder span { font-size: 28px; }
.rows .rcard .body { padding: 8px 12px; min-width: 0; }
.rows .rcard .title { font-size: 16px; margin: 0 0 2px; }
.rows .rcard .tags, .rows .card-tags { display: none; }
.rows .rcard .fav { top: 50%; transform: translateY(-50%); }
/* 1040 is a reading width — a recipe, a form, the shopping list all want a
   short line. A grid of photographs does not: at 1040 four cards need 1014px
   of the 1004 available, so every monitor showed three and a field of
   background. The grid pages take the window instead, to a ceiling that keeps
   a card a card rather than a thumbnail. */
.wrap:has(.grid) { max-width: 1600px; }

/* ---- controls row ---- */
/* Search and the filters stay put while the grid moves under them. Working
   through several hundred recipes is a lot of scrolling and a lot of changing
   your mind, and a search box that has left the screen means a trip back to
   the top before every one of them.

   Two blocks rather than one, because a phone cannot afford the whole thing:
   locked together they take 38% of that screen and leave a card and a half of
   grid, which is worse than the scrolling it saves. So the chips let go below
   640px and the search box and buttons — the parts you keep going back to —
   stay. On a desktop there is room for both and both stay.

   Each sits under what is above it, at a measured height: --bar is the topbar,
   --ctl the controls. The negative margin lets the background reach the edges
   of the column, so cards pass behind these rather than beside them. */
.sticky-controls, .sticky-chips {
  margin: 0 -18px; padding-left: 18px; padding-right: 18px;
  background: rgba(251,246,236,.9); backdrop-filter: blur(10px);
}
.sticky-controls { position: sticky; top: var(--bar, 52px); z-index: 15; padding-top: 14px; padding-bottom: 10px; }
.sticky-chips { position: sticky; top: calc(var(--bar, 52px) + var(--ctl, 0px)); z-index: 14; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
/* The children's own bottom margins would collapse out through a padding-less
   edge and open a gap in the middle of the background, with cards showing
   through it. The spacing lives on the blocks instead. */
.sticky-controls .controls, .sticky-chips .chip-row { margin-bottom: 0; }

@media (max-width: 640px) {
  .sticky-controls { border-bottom: 1px solid var(--line); }
  .sticky-chips {
    position: static; background: none; backdrop-filter: none; border-bottom: 0;
    margin: 0; padding: 12px 0 6px;
  }
}

.controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 20px; }
.list-head h1 { margin: 0 8px 0 0; font-size: 34px; }
.list-head .search { flex: 0 1 420px; }
.list-head .spacer { flex: 1; }
.list-tools { margin-top: 2px; }
select.chip { appearance: auto; padding-right: 8px; }
@media (max-width: 640px) { .list-head h1, #importBtn { display: none; } .list-head .search { flex: 1 1 100%; } }
.controls .search {
  flex: 1; min-width: 200px; background: var(--card); border: 1px solid var(--line);
  color: var(--text); border-radius: 999px; padding: 10px 16px; font-size: 15px;
}
.controls select {
  background: var(--card); border: 1px solid var(--line); color: var(--text);
  border-radius: 999px; padding: 10px 14px; font-size: 14px;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.chip {
  border: 1px solid var(--line); background: var(--card); color: var(--text);
  border-radius: 999px; padding: 6px 13px; font-size: 13px; font-weight: 600;
}
.chip.active { background: var(--accent); color: #ffffff; border-color: transparent; font-weight: 700; }

.room-blurb { color: var(--muted); max-width: 60ch; margin: -6px 0 20px; }

/* ---- recipe grid ---- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
.rcard {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; text-decoration: none; color: inherit; display: flex; flex-direction: column;
  transition: box-shadow .14s; position: relative;
}
.rcard:hover { box-shadow: 0 8px 24px rgba(122,74,46,.12); }
/* The cover's own clipping box. Without it a zoomed picture — zoom being a
   transform, which takes up no space — paints over the title and tags below,
   because the card only clips at its outer edge. */
.cover-box { aspect-ratio: 4/3; overflow: hidden; flex: none; background: linear-gradient(135deg, #ffe6c2, #ffd0b0); }
.cover-box .cover { width: 100%; height: 100%; aspect-ratio: auto; }
.rcard .cover { aspect-ratio: 4/3; background: linear-gradient(135deg, #ffe6c2, #ffd0b0); object-fit: cover; width: 100%; display: block; }
.rcard .cover.placeholder { display: flex; align-items: center; justify-content: center; }
.rcard .cover.placeholder span {
  font-family: "Lora", Georgia, serif; font-weight: 700; font-size: 72px;
  line-height: 1; opacity: .78; user-select: none; letter-spacing: -.02em;
}
.rcard .body { padding: 14px 16px 16px; }
.rcard .title { font-family: "Lora", Georgia, serif; font-weight: 600; font-size: 18px; margin: 0 0 6px; }
.rcard .meta { color: var(--muted); font-size: 13px; }
.rcard .tags { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px; }
.tag { font-size: 11px; font-weight: 600; color: var(--muted); background: var(--bg2); border-radius: 999px; padding: 3px 9px; }
.tag.cat-breakfast { background: var(--sun); color: #402d05; }
.tag.cat-mains { background: var(--accent); color: #fff; }
.tag.cat-sides { background: var(--accent2); color: #fff; }
.tag.cat-dessert { background: var(--berry); color: #fff; }
.tag.cat-drinks { background: #37a3b6; color: #fff; }
.tag.cat-baking { background: #e0912f; color: #fff; }
.kitchen-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.kitchen-actions .btn { text-decoration: none; }
/* Sat at top:8/right:8 with no padding, this was a 18px target in the corner
   of a card that is itself a link — a thumb that missed opened the recipe. The
   inset moved into the padding instead, so the glyph lands where it always did
   and the tap area reaches the corner. */
.rcard .fav {
  position: absolute; top: 0; right: 0; padding: 10px; font-size: 18px; line-height: 1;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.6));
}
button.fav {
  background: none; border: 0; cursor: pointer; color: rgba(255,255,255,.85);
  -webkit-tap-highlight-color: transparent; transition: transform .12s, color .12s;
}
/* Gold reads as "on" at a glance, but gold on a pale placeholder or on a
   photograph of roast potatoes is gold on gold. The second shadow is a dark
   edge rather than a soft drop, so the star keeps its outline on any ground. */
button.fav.on { color: var(--sun); filter: drop-shadow(0 1px 2px rgba(0,0,0,.55)) drop-shadow(0 0 1px rgba(0,0,0,.9)); }
button.fav:hover { transform: scale(1.15); }
button.fav:disabled { cursor: default; opacity: .6; }

/* ---- detail ---- */
/* The hero is clipped by its wrapper rather than by itself, because a zoomed
   picture is bigger than its box and an <img> does not clip its own overflow.
   Cards need no equivalent — .rcard already hides what leaves it. */
/* The hero is the card's shape. It used to be a fixed 1004x360 letterbox on a
   desktop — nearly 3:1 — so a photograph framed to look right in the grid lost
   most of itself on the recipe page, and one framing could not serve both.
   Width is capped by height so a 4:3 hero cannot fill the screen on its own. */
.hero-wrap {
  margin: 14px 0 0; width: 100%; height: clamp(200px, 30vh, 320px);
  border-radius: var(--radius); overflow: hidden; background: var(--card);
}
.detail-hero { display: block; width: 100%; height: 100%; object-fit: cover; background: var(--card); }
.detail h1 { font-size: clamp(26px, 4vw, 34px); font-weight: 600; margin: var(--s3) 0 var(--s1); line-height: 1.15; }
.detail .sub { color: var(--muted); font-size: 14px; margin-bottom: var(--s2); }
.detail .cols { display: grid; grid-template-columns: 1fr 1.4fr; gap: var(--s5); margin-top: var(--s4); }
@media (max-width: 720px) { .detail .cols { grid-template-columns: 1fr; } }
.detail h2 { font-size: 20px; font-weight: 600; border-bottom: 1px solid var(--line); padding-bottom: var(--s1); margin: 0 0 var(--s2); }
.ingredients { list-style: none; padding: 0; margin: 0; }
.ingredients li { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px dashed var(--line); align-items: baseline; }
.ingredients li input { margin-top: 2px; }
.ingredients .amt { color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Reading a recipe: the quantities in a column of their own, right up against
   the things they measure, so the numbers line up and can be read down while
   shopping or laying out. The tick boxes are gone from here — they do nothing
   on this page. In cook mode they do, so that list keeps them and its layout.
   A group heading is not a measurement and spans both columns. */
.ingredients.aligned li { display: grid; grid-template-columns: 84px 1fr; gap: 14px; }
.ingredients.aligned .amt { text-align: right; }
.ingredients.aligned li.group-head { display: block; }
.steps { list-style: none; counter-reset: step; padding: 0; margin: 0; }
.steps li { counter-increment: step; display: flex; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.steps li::before { content: counter(step); flex: none; width: 30px; height: 30px; border-radius: 50%; background: var(--accent2); color: #ffffff; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.serving-ctl { display: inline-flex; align-items: center; gap: 8px; background: var(--card); border: 1px solid var(--line); border-radius: 999px; padding: 4px 6px; }
.serving-ctl button { width: 28px; height: 28px; border-radius: 50%; border: none; background: var(--card2); color: var(--text); font-size: 18px; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: var(--s3) 0 var(--s2); }
.menu button.danger-item { color: var(--danger); }
/* Delete is visible but carries no button chrome, and margin-left:auto keeps
   it away from the rest — the styling has to do the work, because on a narrow
   screen the row wraps and position alone can't be relied on. */
.btn.danger.bare { border-color: transparent; margin-left: auto; }
.btn.danger.bare:hover { border-color: rgba(224,106,90,.4); background: rgba(214,64,39,.06); }
.notes-box { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s2) var(--sp); margin-top: var(--s4); white-space: pre-wrap; color: var(--muted); }

/* ---- fields ----
   App-wide, not scoped to .form: a textarea outside it inherits the UA's
   monospace default, which is how the notes box ended up in Courier. */
input[type=text], input[type=number], input[type=password], input:not([type]), textarea, select {
  background: var(--card); border: 1px solid var(--line); color: var(--text);
  border-radius: 10px; padding: 10px 12px; font-size: 15px; font-family: inherit;
}
/* Auto-grown to fit their content (autoGrow in app.js), so the scrollbar
   would only ever be a stripe down a box that never scrolls. */
textarea { resize: vertical; overflow-y: hidden; }

/* ---- form ---- */
.form { max-width: 720px; margin: 0 auto; }
.form label { display: block; font-size: 13px; color: var(--muted); margin: 14px 0 6px; }
.form input[type=text], .form input[type=number], .form textarea, .form input:not([type]) { width: 100%; }
.form textarea { min-height: 70px; }
.row-line { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.row-line input.amt { width: 64px; flex: none; }
.row-line select.unit { width: 104px; flex: none; background: var(--card); border: 1px solid var(--line); color: var(--text); border-radius: 10px; padding: 10px 8px; font-family: inherit; font-size: 15px; }
.row-line input.item { flex: 1; }
.row-line textarea { flex: 1; min-height: 44px; }
/* The camera path leads: photographing a card is the thing you can only do
   on a phone, and it's the reason the scan exists. */
.start-from { display: flex; flex-direction: column; gap: 10px; margin-bottom: 8px; }
.scan-cta { padding: 13px 18px; font-size: 15px; }
.scan-hint { color: var(--muted); font-size: 12.5px; margin: -2px 0 2px; line-height: 1.45; }
.scan-hint a { color: var(--accent2); }
.import-box { display: flex; gap: 8px; margin-bottom: 8px; }
.import-box input { flex: 1; }
.import-msg { background: rgba(224,122,95,.10); border: 1px solid rgba(224,122,95,.4); border-radius: 12px; padding: 14px 16px; margin-bottom: 8px; }
.import-msg-lead { font-weight: 600; }
.import-msg-sub { color: var(--muted); font-size: 14px; margin-top: 4px; }
.import-msg-actions { display: flex; gap: 8px; margin-top: 12px; }

/* tag chip input (add tags one at a time, like trips) */
.tag-input { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 8px; }
.tag-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--accent2); color: #ffffff; border-radius: 999px; padding: 4px 6px 4px 11px; font-size: 13px; font-weight: 600; }
.tag-chip button { border: none; background: transparent; color: inherit; cursor: pointer; font-size: 16px; line-height: 1; padding: 0 2px; }
.tag-input input { flex: 1; min-width: 130px; border: none !important; background: transparent !important; color: var(--text); font-size: 15px; padding: 6px !important; outline: none; }
.tag-field { position: relative; }
.tag-menu { position: absolute; left: 0; right: 0; top: 100%; margin-top: 4px; z-index: 60; background: var(--card); border: 1px solid var(--line); border-radius: 10px; max-height: 260px; overflow: auto; overscroll-behavior: contain; box-shadow: var(--shadow); }
.tag-menu .opt { display: block; width: 100%; text-align: left; background: transparent; border: none; color: var(--text); padding: 10px 14px; font-size: 14px; cursor: pointer; }
.tag-menu .opt.hl, .tag-menu .opt:hover { background: rgba(0,0,0,.05); }
.tag-menu .opt.create { color: var(--accent); font-weight: 600; }

/* ---- shopping ---- */
.shop-layout { display: grid; grid-template-columns: 1fr 300px; gap: 32px; align-items: start; }
.shop-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 4px 18px 10px; }
.shop-aside { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; }
.shop-aside h2 { font-size: 18px; margin: 0 0 4px; }
.shop-aside .sub { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
@media (max-width: 900px) { .shop-layout { grid-template-columns: 1fr; } .shop-aside { order: -1; } }
.shop-list { list-style: none; padding: 0; margin: 0; }
.shop-list li { display: flex; align-items: center; gap: 12px; padding: 12px 4px; border-bottom: 1px solid var(--line); }
.shop-list li.checked { opacity: .45; text-decoration: line-through; }
.shop-list .amt { color: var(--accent); font-variant-numeric: tabular-nums; }
.shop-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 4px 0 12px; }
/* Recipe filters on a line of their own, above the toggles rather than
   fighting them for width. A title is a sentence, not a word: without a limit
   "Buttermilk-Brown Sugar Waffles" wrapped inside its own pill and became a
   green circle three lines tall. */
.shop-chips { margin: 4px 0 8px; }
.shop-chips .chip {
  max-width: min(220px, 60vw);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.aisle-h { font-family: "Lora", Georgia, serif; font-size: 15px; font-weight: 600; color: #7a4a2e; margin: 18px 0 2px; }
.aisle-h:first-of-type { margin-top: 8px; }
/* a chip that's off means that recipe is hidden, not gone */
.shop-chips .chip { opacity: .5; text-decoration: line-through; }
.shop-chips .chip.active { opacity: 1; text-decoration: none; }
.src-toggle { white-space: nowrap; font-size: 12px; }
.src-toggle.on { border-color: var(--accent2); color: var(--accent2); }
.shop-src.shared { font-style: italic; }

.shop-item { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.shop-src { color: var(--muted); font-size: 12px; }
.shop-list .drag { color: var(--muted); cursor: grab; touch-action: none; user-select: none; padding: 0 2px; }
.shop-list .grip-sp { width: 11px; }
.shop-list li.dragging { opacity: .6; background: var(--bg2); border-radius: 8px; cursor: grabbing; }

/* the ingredient row's optional preparation note */
.row-line input.note { flex: 1; max-width: 190px; }

/* ---- cook mode ---- */
.cook { position: fixed; inset: 0; z-index: 200; background: var(--bg); display: flex; flex-direction: column; }
.cook-top { display: flex; align-items: center; gap: 12px; padding: 14px 20px; border-bottom: 1px solid var(--line); background: var(--card); }
/* The title yields to the timer rather than pushing it off the end. */
.cook-top b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cook-timers { display: flex; gap: 8px; align-items: center; }
/* Read from across the kitchen, not from arm's length. */
.cook-timers .timer-chip { font-size: 17px; padding: 9px 18px; max-width: none; }
.cook-timers .timer-chip b { font-size: 26px; margin-right: 8px; }
@media (max-width: 640px) {
  /* The label gives way — it is the recipe and the step number, and you are
     looking at both already — but not to nothing: the ✕ rides on the end of it
     and is how you clear a timer that has gone off. */
  .cook-timers .timer-chip { font-size: 12px; padding: 8px 12px; max-width: 44vw; }
  .cook-timers .timer-chip b { font-size: 23px; margin-right: 6px; }
}
.cook-main { flex: 1; display: flex; overflow: hidden; }
.cook-side { width: 38%; max-width: 440px; overflow: auto; padding: 24px; border-right: 1px solid var(--line); background: var(--bg2); }
.cook-hero { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: var(--radius); margin-bottom: 20px; box-shadow: var(--shadow); }
.cook-side-h { font-family: "Lora", Georgia, serif; font-size: 20px; margin: 0 0 8px; display: flex; align-items: center; justify-content: space-between; }
.cook-ings-x, .cook-ings-bar { display: none; }
.cook-step-tools { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
.cook-step-tools .timer-btn { margin-top: 0; }
.cook-snap { font-size: 22px; border: 1px solid var(--line); border-radius: 50%; width: 44px; height: 44px; padding: 0; background: var(--card); }
.cook-right { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.cook-step { flex: 1; overflow: auto; padding: 32px 48px; font-size: clamp(22px, 2.8vw, 38px); line-height: 1.4; font-family: "Lora", Georgia, serif; }
.cook-step .n { color: var(--accent); display: block; font-size: 16px; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 14px; }
.ingredients li.used { opacity: .45; text-decoration: line-through; }
.cook-nav { display: flex; gap: 12px; padding: 16px 20px; border-top: 1px solid var(--line); }
/* A quarter bigger than a button anywhere else. These two are pressed with a
   knuckle, the back of a wrist, or a finger with flour on it, from further
   away than you read anything else on the screen. */
.cook-nav .btn {
  flex: 1; justify-content: center; text-align: center;
  padding: 11.25px 20px; font-size: 17.5px;
}
.timer-btn { display: inline-block; margin-top: 16px; }

/* Everything past cooking, listing and favouriting is filing; it waits behind ⋯. */
.actions .more-wrap { margin-left: auto; }

@media (max-width: 560px) {
  .topbar { gap: 8px; padding: 10px 12px; }
  .topbar .brand { font-size: 17px; }
}

@media (max-width: 720px) {
  /* One step per screen; the ingredients pull up as a sheet over it. */
  .cook-main { position: relative; }
  .cook-side {
    position: absolute; left: 0; right: 0; bottom: 0; top: 120px; width: auto; max-width: none; z-index: 2;
    border-right: none; border-radius: 22px 22px 0 0; box-shadow: 0 -10px 30px rgba(0,0,0,.12);
    transform: translateY(105%); transition: transform .25s ease;
  }
  .cook.ings-open .cook-side { transform: none; }
  .cook-hero { display: none; }
  .cook-side.no-ings { display: none; }
  .cook-ings-x { display: inline-block; }
  .cook-ings-bar {
    display: flex; align-items: center; gap: 8px; margin: 0 16px 10px; padding: 12px 14px; border: 0;
    border-radius: 14px; background: var(--bg2); color: var(--text); font-weight: 600; font-size: 15px; text-align: left;
  }
  .cook-ings-bar #cookUsed { color: var(--muted); font-weight: 500; }
  .cook-step { padding: 22px; }
}

/* ---- login / name picker ---- */
.center-screen { flex: 1; min-height: 90vh; display: grid; place-items: center; padding: 24px; }
.panel { background: var(--card); border: 1px solid var(--line); border-radius: 18px; padding: 32px; max-width: 380px; width: 100%; text-align: center; }
.panel .mark { font-size: 40px; }
.panel h1 { font-size: 28px; margin: 12px 0 4px; }
.panel p { color: var(--muted); margin: 0 0 20px; }
.panel input { width: 100%; background: var(--bg2); border: 1px solid var(--line); color: var(--text); border-radius: 10px; padding: 12px 14px; font-size: 16px; text-align: center; margin-bottom: 12px; }
.name-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.name-grid .btn { padding: 16px; font-size: 16px; }

/* ---- bottom sheet ---- */
.backdrop.sheet-back { align-items: end; padding: 0; }
.sheet { width: 100%; max-width: 520px; background: var(--card); border-radius: 22px 22px 0 0; padding: 10px 16px calc(24px + env(safe-area-inset-bottom, 0)); }
.sheet-grip { width: 40px; height: 4px; border-radius: 2px; background: var(--line); margin: 0 auto 12px; }
.sheet h3 { margin: 0 0 8px; font-size: 18px; }
.sheet .room-row { padding: 12px 0; }
.sheet .import-box { margin-top: 8px; }

/* ---- modal ---- */
.backdrop { position: fixed; inset: 0; z-index: 300; background: rgba(0,0,0,.6); display: grid; place-items: center; padding: 20px; }
/* Capped against the viewport as well as the design width: the backdrop is a
   centred grid, so its track sizes to the widest thing inside and a wide
   child pushes the whole dialog off a phone screen. */
.modal { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 24px; max-width: min(420px, calc(100vw - 40px)); width: 100%; }
.modal h3 { margin: 0 0 12px; }
.modal .modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }

/* ---- toast ---- */
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: #222; color: #fff; padding: 12px 20px; border-radius: 999px; z-index: 500; font-size: 14px; box-shadow: 0 6px 24px rgba(0,0,0,.4); }


/* ---- test kitchen ----
   Graph paper: this is a working notebook, not the finished collection. */
body.kitchen {
  background:
    linear-gradient(rgba(79,157,93,.07) 1px, transparent 1px) 0 0 / 100% 28px,
    linear-gradient(90deg, rgba(79,157,93,.07) 1px, transparent 1px) 0 0 / 28px 100%,
    radial-gradient(60% 40% at 88% -5%, rgba(79,157,93,.10) 0%, transparent 60%),
    #f7f4ea;
  background-attachment: fixed;
}
body.kitchen .topbar, body.kitchen .rail { background: rgba(247,244,234,.86); }
body.kitchen .rcard { box-shadow: 0 4px 14px rgba(60,60,30,.09); }
/* Opens like any other card. It used to be inert because its buttons were
   the only way in; they are now shortcuts past the entry, not the entry. */
.kitchen-card { cursor: pointer; }

/* A test-kitchen entry sitting in the main collection, because it was asked
   for. Two marks rather than one: the border says it at a distance, in the
   grid, and the badge says what it is once you are looking at the card. A
   single quiet cue here would be worse than not showing them at all — the one
   thing this collection must never do is let a guess read as a recipe. */
.rcard.idea {
  border-color: rgba(79,157,93,.55);
  box-shadow: 0 0 0 1px rgba(79,157,93,.35), var(--shadow);
}
.rcard.idea:hover { box-shadow: 0 0 0 1px rgba(79,157,93,.45), 0 14px 34px rgba(60,80,50,.18); }
.rcard.idea .body {
  background:
    linear-gradient(rgba(79,157,93,.06) 1px, transparent 1px) 0 0 / 100% 22px,
    linear-gradient(90deg, rgba(79,157,93,.06) 1px, transparent 1px) 0 0 / 22px 100%;
}
.rcard .idea-tag {
  background: rgba(47,107,61,.92); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 4px 9px; border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
}

/* Why the switch above it is worth flipping. */
.filt-hint { margin: 6px 0 0; font-size: 12px; color: var(--muted); }

/* A third way in, worded as part of the sentence offering it rather than as a
   third button competing with the two above it. */
.linkish {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent2); font-weight: 600; text-decoration: underline;
  text-underline-offset: 2px;
}

/* What a card says about itself, stacked in one corner. Three marks each
   claiming the top-left independently is how two of them end up on top of
   each other the first time a drink also needs a look. */
.card-tags {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  max-width: calc(100% - 44px);   /* clear of the favourite star */
}
.rcard .review-tag {
  background: rgba(176,116,10,.94); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 4px 9px; border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
}

/* The four drink fields, two to a row on a phone and four across on a desk. */
.drink-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 14px; }
.drink-fields select, .drink-fields input { width: 100%; }

/* How it is made, served and finished — above the byline, because on a drink
   it is the first thing you want and there is no cooking time to want. */
.drink-line {
  margin: 0 0 10px; font-size: 13.5px; color: var(--muted);
  background: rgba(209,71,122,.07); border-radius: 999px;
  padding: 6px 13px; display: inline-block;
}
.drink-line b { color: #a83660; font-weight: 600; }

/* A drink sitting in the recipes list, because it was asked for. The same two
   marks the test kitchen uses — a border that reads across the grid and a
   badge on the photograph — in berry rather than green, so a glance never
   confuses "still being worked out" with "this one is a drink". */
/* Put aside. Amber rather than the kitchen's green or the bar's berry: three
   marks that never read alike, because the whole point of showing these in
   the collection is that you can tell at a glance which is which. */
.rcard.parked {
  border-color: rgba(176,116,10,.5);
  box-shadow: 0 0 0 1px rgba(176,116,10,.3), var(--shadow);
}
.rcard.parked:hover { box-shadow: 0 0 0 1px rgba(176,116,10,.42), 0 14px 34px rgba(80,60,20,.18); }
.rcard .parked-tag {
  background: rgba(150,98,8,.94); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 4px 9px; border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
}

.rcard.drink {
  border-color: rgba(209,71,122,.55);
  box-shadow: 0 0 0 1px rgba(209,71,122,.35), var(--shadow);
}
.rcard.drink:hover { box-shadow: 0 0 0 1px rgba(209,71,122,.45), 0 14px 34px rgba(80,40,60,.18); }
.rcard .drink-tag {
  background: rgba(168,54,96,.92); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 4px 9px; border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
}

/* Adding later pages to a recipe already started. Sits quietly above the
   fields rather than competing with them: it is occasional, and the form is
   what someone came here for. */
.add-pages { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 2px 0 14px; }
.add-pages .scan-hint { margin: 0; font-size: 12.5px; color: var(--muted); }

/* Source, with the button that refetches it. The button sits with the field
   rather than under it: it acts on what is in the box, and a gap would make
   it look like it acts on the form. */
.src-field { display: flex; gap: 8px; align-items: center; }
.src-field input { flex: 1; min-width: 0; }
.src-field .btn { white-space: nowrap; flex: none; }
.src-field .btn:disabled { opacity: .45; cursor: default; }

/* The thing being asked about, quoted back rather than buried in the sentence
   asking about it — an address read off a photograph is worth a proper look
   before anyone fetches it. */
.confirm-note {
  margin: 4px 0 10px; padding: 9px 12px;
  background: var(--bg2); border-radius: 10px;
  font-size: 13.5px; color: var(--text);
  overflow-wrap: anywhere;
}
/* An address gets read character by character before anyone fetches it, so it
   is set in mono. A sentence is not, and looks like a error message when it
   is. */
.confirm-note.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }

/* ---- ideas ----
   The test kitchen is graph paper because it is a working notebook, and the
   bar is dim because the light is behind the bottles. Ideas is the board things
   get pinned to and left on: kraft paper, a fine weave, and cards that sit at
   the angle a hand leaves them at.

   The tilt is under a degree. It should read as "put here" rather than as a
   scrapbook — these are recipes you will come back to, and a page that makes
   you turn your head is a page you do not come back to. */
body.ideas {
  background:
    radial-gradient(60% 40% at 88% -6%, rgba(244,176,46,.16) 0%, transparent 62%),
    repeating-linear-gradient(45deg, rgba(150,110,60,.045) 0 2px, transparent 2px 5px),
    repeating-linear-gradient(-45deg, rgba(150,110,60,.045) 0 2px, transparent 2px 5px),
    #f3e9d8;
  background-attachment: fixed;
}
body.ideas .topbar, body.ideas .rail { background: rgba(243,233,216,.88); }
body.ideas .tabbar, body.ideas .save-row { background: rgba(243,233,216,.94); }
body.ideas h1.serif { color: #7a5518; }

/* Pinned, and left at the angle a hand leaves them. */
body.ideas .grid .rcard {
  box-shadow: 0 6px 18px rgba(90,65,25,.16);
  transition: transform .16s, box-shadow .16s;
}
body.ideas .grid .rcard:nth-child(odd)  { transform: rotate(-.5deg); }
body.ideas .grid .rcard:nth-child(even) { transform: rotate(.55deg); }
body.ideas .grid .rcard:hover { transform: rotate(0) translateY(-3px); }
/* The pin itself, above the card, so a stack of them reads as a board. */
body.ideas .grid .rcard::before {
  content: ""; position: absolute; top: -5px; left: 50%; margin-left: -5px;
  width: 10px; height: 10px; border-radius: 50%; z-index: 3;
  background: radial-gradient(circle at 35% 32%, #f0b34a, #b9761a 70%);
  box-shadow: 0 2px 4px rgba(60,40,10,.45);
}
body.ideas .idea-badge { background: rgba(186,117,23,.16); color: #7a5518; }

.kind-seg { margin: 0 0 6px; }
.drink-fields[hidden], .food-fields[hidden] { display: none !important; }

/* ---- running timers ----
   Lives outside #app so it survives re-renders, and above cook mode so it's
   visible while you're actually cooking. */
.timer-bar {
  position: fixed; right: 14px; bottom: 14px; z-index: 250;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
}
.timer-chip {
  border: 1px solid var(--line); background: rgba(255,255,255,.96); color: var(--text);
  border-radius: 999px; padding: 8px 14px; font-size: 13px; box-shadow: var(--shadow);
  backdrop-filter: blur(8px); font-variant-numeric: tabular-nums; max-width: 78vw;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.timer-chip b { color: var(--accent2); margin-right: 4px; }
.timer-chip.done { background: var(--accent); color: #fff; border-color: transparent; font-weight: 600; }
.timer-chip.done b { color: #fff; }
@media (max-width: 640px) { .timer-bar { bottom: calc(70px + env(safe-area-inset-bottom, 0)); left: 14px; align-items: stretch; } }

/* ---- mobile bottom nav ----
   Hidden on desktop, where the topbar has room. On a phone the two things you
   do away from a desk sat at the very top of the screen next to everything
   else; these belong under a thumb. */
.save-row { margin-top: 24px; display: flex; gap: 10px; }

/* Save stays on the screen the whole time a recipe is open, at every width.
   A recipe form is long everywhere — it is longer on a desk, where there is
   room to show twelve ingredients at once — and the one button you want was
   always at the bottom of the scroll.

   A band rather than a floating circle: a floating button has to sit in a
   corner, and both corners at the bottom of this app are already spoken for by
   the timer and the freshness notice. */
body.editing .save-row {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 95; margin: 0;
  left: var(--rail, 0px);
  padding: 10px 18px calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(251,246,236,.94); backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
}
/* Lined up with the form above it rather than stretched across a wide screen,
   so the button stays where the eye already is. */
@media (min-width: 641px) {
  body.editing .save-row { justify-content: flex-start; padding-left: max(18px, calc((100vw - var(--rail, 0px) - 1040px) / 2 + 18px)); }
  body.editing .save-row .btn { min-width: 220px; padding-top: 12px; padding-bottom: 12px; }
  /* A running timer would otherwise sit inside the band. */
  body.editing .timer-bar { bottom: 78px; }
  /* And the last field needs the room the band takes. */
  body.editing .wrap { padding-bottom: 96px; }
}
body.editing .topbar .btn { display: none; }

.tabbar { display: none; }
@media (max-width: 640px) {
  /* Writing a recipe is a long form on a small screen, and the one button you
     want is at the bottom of it. So while the form is open Save sits on the
     screen instead of at the end of the scroll — in the tab bar's place rather
     than stacked over it, because two bars is most of a phone.

     The same element either way: one Save button that changes where it lives,
     not a second one to keep in step with the first. While a field is focused
     the keyboard covers it, which is fine — the moment you want Save is the
     moment you have stopped typing. */
  body.editing .tabbar { display: none; }
  /* On a phone it spans the width, in the tab bar's place. */
  body.editing .save-row .btn { flex: 1; padding-top: 13px; padding-bottom: 13px; }

  .tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
    background: rgba(251,246,236,.94); backdrop-filter: blur(10px);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .tab {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 9px 4px 8px; font-size: 11px; color: var(--muted);
    text-decoration: none; position: relative;
  }
  .tab.on { color: var(--accent); font-weight: 600; }
  .tab .tab-badge { top: 4px; left: 50%; right: auto; margin-left: 6px; }
  /* room for the bar, plus the home indicator on newer iPhones */
  .wrap { padding-bottom: calc(86px + env(safe-area-inset-bottom, 0)); }

  .rcard .meta { font-size: 12px; }

  /* search gets the full width; the rest tucks onto one compact line */
  .controls { gap: 8px; }
  .controls .search { flex: 1 1 100%; }
  .controls select { flex: 1 1 auto; }
  #addBtn { display: none; }   /* Add sits in the top bar */
}

/* ---- photo tiles (form strip + step rows) ---- */
/* Big enough to actually recognise the photo: at 76px a scanned card and a
   plate of food looked the same. */

/* The picture as the recipe page shows it — same width, same 360px cap, same
   object-fit. It was built at the card's 4:3, which previewed a crop nobody
   was looking at: the big one is the one you notice.

   No aspect-ratio of its own. The frame takes the height the photograph
   actually gets, so a short wide picture shows uncropped and there is
   correctly nothing to drag. */
.cover-edit { margin-bottom: 14px; }
.cover-frame {
  position: relative; display: block; width: 100%; max-width: 420px;
  border-radius: var(--radius); overflow: hidden;
  background: var(--card); cursor: grab; touch-action: none;
}
.cover-frame.dragging { cursor: grabbing; }
.cover-zoom { display: flex; align-items: center; gap: 10px; margin-top: 8px; max-width: 340px; }
.cover-zoom label { font-size: 13px; color: var(--muted); }
.cover-zoom input[type=range] { flex: 1; }
.cover-zoom b { font-size: 13px; color: var(--muted); min-width: 42px; text-align: right; font-variant-numeric: tabular-nums; }
/* The frame's shape is set from the recipe page's geometry (drawCover), and
   the picture fills it — rather than the picture deciding the height here,
   which is the shape of this column and not of that page. */
.cover-frame img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  user-select: none; -webkit-user-drag: none; pointer-events: none;
}
/* A hint that it moves, without a control sitting on top of the picture. */
.cover-frame::after {
  content: "⤧"; position: absolute; right: 10px; bottom: 8px;
  color: #fff; font-size: 16px; opacity: .85;
  text-shadow: 0 1px 3px rgba(0,0,0,.7); pointer-events: none;
}

/* Big enough to judge. At 104px these were thumbnails of thumbnails — you
   could tell a photo was there and not what was in it, which is no use on the
   page where you decide which one is the cover and what needs trimming. */
/* 4:3, like the card, the cover frame and the hero. A square tile cropped
   differently from everything else, which made choosing a cover a guess about
   what the grid would do with it. */
/* overflow:hidden because the cover's tile now carries the cover's zoom, and a
   scaled picture is bigger than its tile — without this it spills out over the
   tiles beside it. */
.photo-tile { position: relative; width: 176px; aspect-ratio: 4 / 3; flex: none; overflow: hidden; border-radius: 8px; }
@media (max-width: 640px) { .photo-tile { width: 148px; } }
/* Whole, not cropped. The strip is where you tell photographs apart and pick
   one — a 4:3 tile cropping a portrait photograph shows a band through the
   middle of it, which is neither the picture you imported nor enough to
   recognise it by. The cover frame above is where cropping is decided. */
.photo-tile img {
  width: 100%; height: 100%; object-fit: contain; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg2); cursor: pointer;
}
.photo-tile:not(.staged) img:hover { border-color: var(--accent2); }
.photo-tile.hero img { border: 2px solid var(--accent); cursor: default; }
.photo-tile.staged img { opacity: .85; cursor: default; }

/* Three controls share each tile, so they're sized here rather than inheriting
   the toolbar icon button — and they sit inside the tile, not hanging off it,
   which is what made one tile's ✕ land on its neighbour's cover button. */
.photo-tile .icon-btn {
  position: absolute; width: 24px; height: 24px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; border: none;
  background: rgba(0, 0, 0, .62); color: #fff; border-radius: 50%;
  backdrop-filter: blur(2px);
}
.photo-tile .icon-btn:hover { background: #000; }
.photo-tile .rmPhoto, .photo-tile .rmPending { top: 3px; right: 3px; }
.photo-tile .markCard, .photo-tile .markPending { top: 3px; left: 3px; }
.photo-tile .rotPhoto, .photo-tile .rotPending { bottom: 3px; right: 3px; }
/* A step's photo has its own two, and they had no corners named — so they
   inherited position:absolute with no offsets and sat wherever they fell,
   then vanished entirely once the tiles started clipping. */
.photo-tile .rmStepPhoto { top: 3px; right: 3px; }
/* Small tiles, small controls, or the buttons are the tile. */
.photo-tile.sm .icon-btn { width: 20px; height: 20px; font-size: 11px; }
.photo-tile .cropPhoto { bottom: 3px; left: 3px; }
.photo-tile.sm { width: 92px; }
/* Centred along the top. Four controls want the four corners, and a word that
   cannot be pressed reads better in the middle than wedged beside one that
   can — which is what it was, sharing the bottom-left with the scissors. */
.card-badge {
  position: absolute; left: 50%; transform: translateX(-50%); top: 4px;
  background: var(--berry); color: #fff;
  font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 999px; pointer-events: none;
}
.photo-tile.is-card img { border: 2px solid var(--berry); cursor: default; }
.card-note { color: var(--muted); font-size: 13px; margin: -8px 0 12px; }
.gallery.cards { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.hero-badge {
  position: absolute; left: 50%; transform: translateX(-50%); top: 4px;
  background: var(--accent); color: #fff;
  font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 999px; pointer-events: none;
}
/* Air between a step's photograph and the button that deletes the step, so
   the two are not one target. */
.step-line .step-photo { flex: none; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; max-width: 50%; margin-right: 10px; }

/* step photos where they're actually used */
/* the step text is a flex item; without flex:1 it sizes to content and a
   percentage-width image inside it collapses */
.steps li > span { flex: 1; min-width: 0; }
.step-shots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.steps .step-shot {
  display: block; max-width: min(260px, 100%); height: auto;
  border-radius: 10px; border: 1px solid var(--line); cursor: zoom-in;
}
.cook-shots { display: flex; gap: 10px; margin: 20px 0 0; overflow-x: auto; }
.cook-shot {
  display: block; flex: none; max-width: 100%; max-height: 34vh;
  object-fit: contain; border-radius: 12px; border: 1px solid var(--line);
}

/* whose-list picker sits against the add button as one control */
.add-to { display: inline-flex; align-items: center; }
.add-to .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.add-to select {
  border-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0;
  padding: 9px 8px; font-size: 13px; color: var(--muted); background: var(--card2);
}

.unit-ctl { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; margin-bottom: 14px; }
.unit-ctl button { border: 0; background: transparent; color: var(--muted); font-size: 12px; padding: 6px 12px; }
.unit-ctl button + button { border-left: 1px solid var(--line); }
.unit-ctl button.on { background: var(--accent2); color: #fff; font-weight: 600; }

/* ---- per-person notes ---- */
#notesSec { margin-top: var(--s5); }
#notesSec h2 { margin-bottom: var(--s3); }
.detail .gallery + h2, .detail h2 + .gallery { margin-top: 18px; }
.made-line { color: var(--muted); font-size: 13px; margin: -10px 0 var(--s3); }
.note-add { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin-bottom: 14px; }
.note-add textarea { width: 100%; min-height: 52px; margin-bottom: 8px; }
.note-add-row { display: flex; align-items: center; gap: 10px; }
.note-add-row .spacer { flex: 1; }
.note-priv { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 13px; cursor: pointer; }
.note-list { list-style: none; margin: 0; padding: 0; }
.note-list li { position: relative; padding: 12px 34px 12px 0; border-bottom: 1px solid var(--line); }
.note-list li:last-child { border-bottom: 0; }
.note-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 3px; }
.note-meta b { font-size: 14px; }
.note-meta span { color: var(--muted); font-size: 12px; }
.note-lock { color: var(--berry) !important; font-weight: 600; }
.note-text { white-space: pre-wrap; }
.note-empty { color: var(--muted); font-style: italic; margin: 0 0 8px; }
.note-rm { position: absolute; top: 8px; right: 0; }
.menu-check { display: flex; align-items: center; gap: 8px; padding: 9px 12px; color: var(--muted); font-size: 13px; cursor: pointer; }
.mi-photo { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.filt-group { margin-top: 14px; }
.filt-group label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.seg { display: flex; flex-wrap: wrap; gap: 6px; }
.seg button {
  border: 1px solid var(--line); background: var(--card); color: var(--text);
  border-radius: 999px; padding: 7px 13px; font-size: 13px;
}
.seg button.on { background: var(--accent2); color: #fff; border-color: transparent; font-weight: 600; }
.pdf-opts { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
.pdf-opts label { display: flex; align-items: center; gap: 8px; cursor: pointer; }

/* ---- photo gallery + lightbox ---- */
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; margin: 4px 0 8px; }
.gallery img {
  width: 100%; aspect-ratio: 1; object-fit: cover; cursor: zoom-in;
  border-radius: 12px; border: 1px solid var(--line); background: var(--bg2);
  transition: transform .12s;
}
.gallery img:hover { transform: scale(1.02); }
.backdrop.lightbox { cursor: zoom-out; }
.backdrop.lightbox img {
  max-width: min(94vw, 1200px); max-height: 90vh;
  object-fit: contain; border-radius: 12px; cursor: default;
}
.lb-x { position: fixed; top: 16px; right: 16px; color: #fff; font-size: 20px; }

/* ---- popup menus ---- */
.more-wrap { position: relative; }
.menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 200;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow); padding: 6px; min-width: 190px;
}
.menu button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  color: var(--text); font-size: 14px; padding: 9px 12px; border-radius: 8px;
}
.menu button:hover { background: var(--bg2); }
.menu button small { color: var(--muted); font-size: 12px; margin-left: 4px; }
.menu-sep { height: 1px; background: var(--line); margin: 6px 4px; }

/* ---- history ---- */
.modal.wide { max-width: min(560px, calc(100vw - 40px)); }
.hist-list { list-style: none; margin: 0; padding: 0; max-height: 55vh; overflow-y: auto; }
.hist-list li {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 4px; border-bottom: 1px solid var(--line);
}
.hist-list li:last-child { border-bottom: 0; }
.hist-meta { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.hist-meta b { font-size: 15px; }
.hist-meta span { color: var(--muted); font-size: 13px; }
.hist-detail { font-size: 12px !important; }

/* ---- trash ---- */
.trash-list { list-style: none; margin: 0; padding: 0; }
.trash-list li {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px; margin-bottom: 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
}
.trash-list img, .trash-list .thumb-ph {
  width: 56px; height: 56px; border-radius: 10px; object-fit: cover; flex: none;
  display: grid; place-items: center; background: var(--bg2); font-size: 22px;
}
.trash-meta { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 140px; }
.trash-meta span { color: var(--muted); font-size: 13px; }

@media print {
  .topbar, .controls, .chip-row, .actions, .icon-btn, .serving-ctl button, .unit-ctl, .tabbar, .save-row,
  .gallery, .backdrop, .note-add, .note-rm, .timer-bar { display: none !important; }
  body { background: #fff; color: #000; }
  .detail .cols { grid-template-columns: 1fr 1fr; }
  .detail-hero { max-height: 240px; }
  a { color: #000; }
}

/* A file input styled as a button: the bare control reads as browser chrome,
   so people looked at the edit form and concluded they couldn't add a photo.
   It is a <label> wrapping a hidden input, so it needs rescuing from the
   form's block-level label styling. */
.form label.file-btn {
  display: inline-flex; align-items: center; width: auto; cursor: pointer;
  margin: 0 0 4px; font-size: 14px; color: var(--text); letter-spacing: 0;
}

/* choosing a step photo from the ones already on the recipe */
.modal.wide { max-width: min(560px, calc(100vw - 40px)); }
.pick-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 8px; margin: 14px 0 4px; max-height: 46vh; overflow-y: auto;
}
.pick { padding: 0; border: 2px solid transparent; border-radius: 8px; background: none; cursor: pointer; }
.pick img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 6px; display: block; }
.pick:hover { border-color: var(--accent); }

/* group headings inside the ingredient and step editors */
.row-section { display: flex; gap: 8px; align-items: center; margin: 14px 0 6px; }
.row-section .secname {
  flex: 1; font-family: var(--serif); font-size: 15px; font-weight: 600;
  padding: 8px 10px; border: 1px dashed var(--line); border-radius: 8px;
  background: var(--bg2); color: var(--text);
}
.row-section .secname:focus { border-style: solid; border-color: var(--accent2); }
.row-section:first-child { margin-top: 0; }

/* a named group inside an ingredient or step list */
li.group-head {
  list-style: none; font-family: var(--serif); font-weight: 600;
  color: var(--text); margin: 16px 0 6px; padding: 0;
}
li.group-head:first-child { margin-top: 0; }

/* A heading is not a step: it must not take a number, or the numbering runs
   ahead of the recipe by one per heading. */
.steps li.group-head {
  counter-increment: none; display: block; padding: 0; border-bottom: none;
  font-size: 17px; margin: 22px 0 2px;
}
.steps li.group-head::before { content: none; }
.steps li.group-head:first-child { margin-top: 0; }

/* "this is going to take a moment" — importing, scanning, fetching photos */
.backdrop.working { cursor: progress; }
.working-card {
  background: var(--card); border-radius: 16px; padding: 30px 34px;
  max-width: 380px; text-align: center; box-shadow: 0 18px 50px rgba(0,0,0,.22);
}
.working-title { font-family: var(--serif); font-size: 19px; font-weight: 600; margin: 16px 0 6px; }
.working-note { color: var(--muted); font-size: 14px; margin: 0; min-height: 20px; }
.spinner {
  width: 34px; height: 34px; margin: 0 auto; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* cookbook options */
.modal-note { color: var(--muted); font-size: 13px; margin: 8px 0 14px; }
.field-label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.modal select#cbSize {
  width: 100%; padding: 10px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--card); color: var(--text); font-family: inherit; font-size: 15px;
}

/* the form's heading, with its whole-recipe actions beside it */
.form-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 4px 0 12px; }
.form-head h1 { margin: 0; flex: 1 1 auto; font-size: 28px; }

/* paste-the-page fallback, for a site that refuses the server */
#pasteBox { margin-top: 10px; }
#pasteText {
  width: 100%; padding: 10px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--card); color: var(--text); font-family: inherit; font-size: 14px;
  resize: vertical;
}

/* "you already have this" — has to read as a warning, not as another notice */
.dupe-msg {
  border: 2px solid var(--accent); background: #fdecea;
}
.dupe-msg .import-msg-lead { color: var(--accent); }
.dupe-msg a { color: var(--accent); font-weight: 600; }

/* how many recipes there are, above the grid */
.count { color: var(--muted); font-size: 13px; margin: 16px 0 12px; }

/* tag filters: the ones you picked, plus a menu to add another */
.chip-row { flex-wrap: wrap; align-items: center; }
.chip-row .more-wrap { display: inline-flex; position: relative; }
.chip-more { font-weight: 600; }
.chip-x { margin-left: 6px; opacity: .7; font-size: 15px; line-height: 1; }
/* overscroll-behavior: a menu that has run out of list should stop, not hand
   the rest of the gesture to the page behind it. On a phone that made picking
   a tag a fight between two things scrolling at once. */
/* max-height here is a floor for the first paint; fitMenu() replaces it with
   what is actually on screen once it can measure. */
.tag-menu { max-height: 60vh; overflow-y: auto; overscroll-behavior: contain; min-width: 180px; }
/* Opened upwards, when the keyboard has taken the room below the field. */
.tag-menu.up { top: auto; bottom: 100%; margin: 0 0 4px; }

/* What you are being asked to keep, above the question. Several pages scroll
   sideways rather than growing the row: three of them centred was wide enough
   to hang out over both edges of the dialog. */
.confirm-thumbs {
  display: flex; gap: 8px; margin: 0 0 14px;
  overflow-x: auto; padding-bottom: 2px;
}
.confirm-thumbs img {
  height: 150px; width: auto; flex: 0 0 auto;
  border-radius: 8px; background: var(--line, #eee);
}
.confirm-thumbs img:only-child { margin: 0 auto; }

/* trimming a photo on the way back from the camera */
.crop-modal .crop-of { color: var(--muted); font-weight: 400; font-size: 14px; }
.crop-stage {
  /* shrink-wrapped to the picture: the box is positioned as a percentage of
     this element, so a stage wider than the image puts the frame off the
     photo it is supposed to be trimming. */
  position: relative; width: fit-content; margin: 0 auto 4px;
  max-height: 58vh; overflow: hidden; touch-action: none; line-height: 0;
}
/* A fixed box around the picture, so turning a landscape photo into a
   portrait one does not resize the dialog under the hands holding it. The
   stage still shrink-wraps the image inside — the crop box is a percentage of
   it — but the dialog stays exactly where it was. */
.crop-frame { height: 56vh; display: flex; align-items: center; justify-content: center; }
.crop-stage img { display: block; max-width: 100%; max-height: 56vh; margin: 0 auto; }
.crop-box {
  position: absolute; box-sizing: border-box;
  border: 2px solid #fff; cursor: move;
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.45);
}
.crop-box i {
  position: absolute; width: 26px; height: 26px; background: #fff;
  border-radius: 50%; box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
/* Inside the corners, not straddling them: the box starts at the full frame,
   and handles hung outside it would be clipped away by the stage. */
.crop-box i[data-h="nw"] { left: 3px; top: 3px; cursor: nwse-resize; }
.crop-box i[data-h="ne"] { right: 3px; top: 3px; cursor: nesw-resize; }
.crop-box i[data-h="sw"] { left: 3px; bottom: 3px; cursor: nesw-resize; }
.crop-box i[data-h="se"] { right: 3px; bottom: 3px; cursor: nwse-resize; }

/* ---- selecting cards, and the menu on them ---- */
/* No mode, so nothing changes until a card is actually chosen. */
.rcard.chosen { outline: 3px solid var(--accent); outline-offset: -1px; }
.rcard.chosen::after {
  content: "✓"; position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: #fff; font-size: 15px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 5px rgba(0,0,0,.35);
}

.ctx-menu {
  position: fixed; z-index: 300; min-width: 208px; max-width: 280px; padding: 6px;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 14px 40px rgba(60,40,20,.26);
}
.ctx-menu button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  color: var(--text); font-size: 14px; padding: 8px 11px; border-radius: 8px; cursor: pointer;
}
.ctx-menu button:hover:not(:disabled) { background: var(--bg2); }
.ctx-menu button:disabled { color: var(--muted); cursor: default; }
.ctx-danger { color: var(--danger); }
.ctx-head {
  padding: 7px 11px 8px; font-size: 12px; font-weight: 700; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ctx-sep { height: 1px; margin: 5px 8px; background: var(--line); }
.ctx-hint { padding: 8px 11px 5px; font-size: 11.5px; color: var(--muted); }
.ctx-back { color: var(--muted); font-size: 13px !important; }
.ctx-create { color: var(--accent2); font-weight: 600; }
.ctx-q {
  display: block; width: 100%; margin: 2px 0 6px; font-size: 14px;
  background: var(--bg2); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 7px 10px;
}
/* The list scrolls; the filter box above it does not go with it. */
.ctx-scroll { max-height: 44vh; overflow: auto; }
/* A fixed column for the mark, so the names line up whether or not the
   selection carries them. */
.bulk-mark { display: inline-block; width: 16px; color: var(--accent2); font-weight: 700; }

/* the marker that pulls in the next page of recipes */
.grid-end { text-align: center; color: var(--muted); font-size: 13px; padding: 18px 0 28px; }

/* Editing against the original. Correcting a transcription means reading one
   thing and typing another, so the source sits beside the fields and stays
   put while they scroll — rather than as a thumbnail in a strip below. */
/* Only where there is room to put it beside something. On a phone a second
   panel is just the same page twice as long, and it pushed Cancel and the
   heading below the fold; the original is already a tap away in the photo
   strip, which is the right answer at that width. */
/* Details on the left, ingredients and steps on the right, where there is room.
   Not beside a scanned card: that page is already two columns. */
@media (min-width: 1100px) {
  .wrap:has(.form-cols) { max-width: 1320px; }
  .form:has(.form-cols) { max-width: none; }
  .form-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 40px; align-items: start; }
  .form-split .form-cols { display: block; }
  body.editing .save-row { padding-left: max(18px, calc((100vw - var(--rail, 0px) - 1320px) / 2 + 18px)); }
}
.form-split { display: block; }
.card-aside { display: none; }
.card-aside .card-big {
  display: block; width: 100%; max-height: 46vh; object-fit: contain;
  border-radius: 10px; border: 1px solid var(--line); background: var(--bg2);
  cursor: zoom-in;
}
.card-aside .scan-hint { margin: 6px 0 0; }
/* The other pages of a scan. Numbered, because two photographs of the same
   sheet of paper look identical at 56px and the number is what you are
   actually choosing between. */
.card-pages { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 8px; }
.card-page {
  position: relative; padding: 0; line-height: 0; cursor: pointer; overflow: hidden;
  background: none; border: 2px solid transparent; border-radius: 9px;
}
.card-page img { display: block; width: 56px; height: 56px; object-fit: cover; border-radius: 7px; }
.card-page span {
  position: absolute; left: 0; bottom: 0; padding: 1px 6px; line-height: 1.5;
  font-size: 11px; font-weight: 600; color: #fff; background: rgba(0,0,0,.6);
  border-top-right-radius: 7px;
}
.card-page.on { border-color: var(--accent); }
.card-page:hover { border-color: var(--accent2); }

@media (min-width: 1180px) {
  /* Wider than the 1040px the rest of the site sits in: two columns of a
     usable width need the room, and this is the one page where you are
     reading and typing at the same time. */
  .form-split {
    display: grid; align-items: start; gap: 34px;
    grid-template-columns: minmax(0, 1fr) minmax(400px, 640px);
    width: min(1500px, calc(100vw - 40px));
    margin-left: calc((1004px - min(1500px, 100vw - 40px)) / 2);
  }
  .form-split .form { max-width: none; margin: 0; }
  .card-aside { display: block; position: sticky; top: 14px; margin: 0; text-align: center; }
  .card-aside .card-big { max-height: calc(100vh - 80px); }
  /* Only when there is a row of pages to leave room for; a single card should
     still get the whole height. */
  .card-aside:has(.card-pages) .card-big { max-height: calc(100vh - 156px); }
}

/* The grip. touch-action:none so a finger on it drags the row rather than
   scrolling the page — everywhere else on the row still scrolls normally. */
.grip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; align-self: stretch; cursor: grab;
  color: var(--muted); font-size: 15px; line-height: 1;
  touch-action: none; user-select: none; flex: none;
}
.grip:hover { color: var(--text); }
/* Lifted off the page: tilted, shadowed and outlined, so at a glance it is
   obvious which row is in your hand and which are getting out of its way. */
.row-line.dragging, .row-section.dragging {
  cursor: grabbing; position: relative; z-index: 5;
  border-radius: 10px; background: var(--card);
  outline: 2px solid var(--accent2); outline-offset: 1px;
  box-shadow: 0 12px 28px rgba(60,40,20,.28);
  transform: rotate(-.35deg) scale(1.015);
}
/* While a row is in the air, nothing else on the page should be selectable or
   hoverable — a drag that highlights text as it passes reads as broken. */
body.row-dragging { user-select: none; cursor: grabbing; }
body.row-dragging .grip { cursor: grabbing; }

/* The step's number and its drag handle, at the top-left of the row. The
   number is written from the list on every change, so it always says where
   the step actually is rather than where it was typed. */
.step-line { align-items: flex-start; }
.step-handle {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding-top: 8px; min-width: 20px; user-select: none;
}
.step-n { font-size: 13px; font-weight: 700; color: var(--muted); line-height: 1; font-variant-numeric: tabular-nums; }
.row-line.dragging { opacity: .55; }

/* An ingredient row is amount, unit, name, note and a delete — five things
   plus a drag handle, which does not fit across a phone. It wraps instead:
   the measure on one line, what it is on the next. */
@media (max-width: 700px) {
  .row-line { flex-wrap: wrap; }
  .row-line input.item { flex: 1 1 55%; }
  .row-line input.note { flex: 1 1 28%; max-width: none; }
  .step-line { flex-wrap: nowrap; }
}

/* Up and down beat a drag handle: a drag is a gesture you have to discover
   and then perform accurately, and these say what they do and land on the
   same target every time. */
.mover { display: flex; flex-direction: column; gap: 1px; flex: none; }
.mv {
  border: none; background: none; cursor: pointer; padding: 3px 7px;
  color: var(--muted); font-size: 11px; line-height: 1; border-radius: 5px;
}
.mv:hover:not(:disabled) { background: var(--bg2); color: var(--text); }
.mv:disabled { opacity: .22; cursor: default; }
.step-handle .mover { margin-top: 2px; }

/* Dragging a picture onto the test kitchen. macOS has no "choose from Photos"
   dialog, so dropping or pasting is how a picture in Photos gets here at all. */
.wrap.drop-here { outline: 2px dashed var(--accent); outline-offset: -10px; border-radius: 12px; }
.backdrop.drop-here .modal { outline: 2px dashed var(--accent); outline-offset: 6px; }

/* An entry still being worked out, opened from the test kitchen. It otherwise
   reads exactly like a finished recipe, which is the one thing this
   collection is careful never to allow. */
/* Solid, and the size of a statement rather than a footnote. A pale tint at
   12.5px sat under a full-width photograph and a serif title and lost every
   time; this is the one line on the page that says the recipe is not finished,
   and it is the same green as the badge on the card it came from. */
.idea-badge {
  display: inline-block; margin: 0 0 14px;
  background: rgba(47,107,61,.94); color: #fff;
  font-size: 15px; font-weight: 700; letter-spacing: .01em;
  padding: 9px 17px; border-radius: 999px;
  box-shadow: 0 2px 8px rgba(47,107,61,.3);
}

/* "You are looking at something that is no longer true." Offers the fix
   rather than performing it: reloading the list under someone would throw
   away however far they had scrolled. */
.fresh-bar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(78px + env(safe-area-inset-bottom, 0));
  display: flex; align-items: center; gap: 10px;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 8px 26px rgba(60,40,20,.18);
  border-radius: 999px; padding: 8px 10px 8px 16px;
  font-size: 13.5px; z-index: 300; max-width: calc(100vw - 24px);
}
.fresh-bar span { color: var(--muted); white-space: nowrap; }
.fresh-bar .btn { flex: none; }
@media (min-width: 720px) { .fresh-bar { bottom: 24px; } }

/* The courses get a line to themselves, and only as many as fit on it. Not a
   sideways-scrolling strip: that hides whichever filters are off the right
   edge, so three active filters can look like none, which is worse than not
   showing a button at all. What does not fit is in the tag menu instead. */
.course-row { display: flex; gap: 8px; margin-bottom: 8px; overflow: hidden; }
.course-row .chip { flex: none; font-weight: 600; }
.course-row .chip.hide { display: none; }


/* Where the photograph was taken. A coordinate is not an answer, so it opens
   a map — the question is "which place was that", not "what latitude". */
.where-taken {
  display: inline-block; margin: 0 0 10px 8px;
  font-size: 12.5px; font-weight: 600; color: var(--accent2);
  text-decoration: none; border-bottom: 1px solid currentColor;
}
.where-taken.none { color: var(--muted); border-bottom: 0; font-weight: 400; }
/* a heading carries the same movers as a row, and the same left gutter */
.row-section { display: flex; align-items: center; gap: 8px; }
.row-section .secname { flex: 1; }

/* Rotating is something you do to the picture; keeping or trimming is the
   decision. On its own line so four buttons stop wrapping across three. */
.crop-tools { display: flex; margin: 8px 0 2px; }

/* ---------- sign-in and admin (Cookie) ---------- */
.signin h1 { display:flex; align-items:center; justify-content:center; gap:14px; font-size:56px; margin:0 0 22px; }
.signin h1 img { height:52px; width:auto; }
.signin form { display:flex; flex-direction:column; gap:10px; }
.btn-apple, .btn-google { display:flex; align-items:center; justify-content:center; gap:8px; width:100%; padding:13px; border-radius:11px; font-size:15px; font-weight:600; cursor:pointer; }
.btn-apple { border:0; background:#000; color:#fff; }
.btn-google { border:1px solid #dadce0; background:#fff; color:#1f1f1f; }
.btn-apple svg, .btn-google svg { width:18px; height:18px; }
.signin .link { background:none; border:0; color:var(--muted); font-size:13px; text-decoration:underline; cursor:pointer; padding:8px; }
.signin .err { background:#fde8e1; border:1px solid #f2c2b0; color:#8a3b1e; font-size:13px; border-radius:10px; padding:10px 12px; }
.signin input { text-align:center; }
.admin-list .row { display:flex; align-items:center; gap:10px; padding:9px 0; border-bottom:1px solid var(--line); font-size:14px; }
.admin-list .row span { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; }
.admin-list .row small { color:var(--muted); }
.admin-list .add { display:flex; gap:8px; margin-top:12px; }
.admin-list .add input { flex:1; }
.brand img.mark { height:22px; width:auto; vertical-align:middle; }
[hidden] { display:none !important; }
