/* ==========================================================================
   Kanga’s Kitchen — screen styles

   Blue-and-white gingham, wicker, wildflowers, and a lot of sun. Somewhere
   between an Amalfi terrace and an English kitchen garden: bright, airy, and
   unfussy. Type runs large on purpose — this gets read at arm's length on a
   counter with flour on your hands.
   ========================================================================== */

/*
 * Every colour is one `light-dark(daytime, evening)` pair — evening being the
 * same terrace after dark, deep sea-blue rather than brown — and which half of
 * each pair wins is decided by `color-scheme`, never by `prefers-color-scheme`.
 *
 * That distinction is the entire reason the Light/Dark/Auto switch in the top
 * bar can work. `prefers-color-scheme` reports the device and nothing else, so
 * a rule keyed on it can't be overruled: on a dark iPad it would go on
 * insisting on dark however many times she pressed Light. `color-scheme` is a
 * property, so the `data-theme` rules below can simply set it, and every pair
 * re-resolves against whatever it now says.
 */
:root {
  color-scheme: light dark;

  --paper:      light-dark(#F7FAFD, #101924);   /* sunlit white with a breath of sky   */
  --paper-deep: light-dark(#E8F0F8, #172433);   /* the pale square of a gingham check  */
  --card:       light-dark(#FFFFFF, #18242F);
  --ink:        light-dark(#1F3A5F, #E8F0F8);
  --ink-soft:   light-dark(#4E6B8A, #A8BFD4);
  --ink-faint:  light-dark(#8AA2BC, #7793AD);
  --rule:       light-dark(#D5E3F0, #2A3B4D);

  --accent:     light-dark(#3E7CB1, #7DB0DC);   /* cornflower — the blue of the check  */
  --accent-deep:light-dark(#2B5A85, #A3CBEB);
  --accent-wash:light-dark(#E4EFF9, #1B2E40);

  --wicker:     #C9A063;                        /* basket weave, warm and dry          */
  --wicker-wash:light-dark(#F7EFE2, #2A2318);
  --sun:        #F2C14E;                        /* lemons on the terrace               */
  --meadow:     light-dark(#6B9E5E, #8DBE80);   /* stems and leaves                    */
  --bloom:      light-dark(#E08B9B, #E8A3B0);   /* a wildflower in the jug             */

  --sage:       light-dark(#6B9E5E, #8DBE80);
  --danger:     light-dark(#C1554D, #E89189);
  --danger-wash:light-dark(#FBEAE8, #3A2320);

  /*
   * Course. Seven, and the values live in public/vocabulary.js too — that file
   * is the one the editor and the server read, these are the same numbers for
   * the stylesheet. Chosen to GROUP a shelf rather than to be memorised: warm
   * through the savoury run, green for the leaf course, rose for sweet, blue
   * for liquid, slate for the things that aren't a course at all.
   */
  --co-breakfast: light-dark(#E0A23C, #EFBE69);
  --co-appetizer: light-dark(#D2795C, #E79878);
  --co-soupsalad: light-dark(#7FA34C, #A3C473);
  --co-main:      light-dark(#B0503F, #D4796A);
  --co-side:      light-dark(#6B8E4E, #97BE79);
  --co-dessert:   light-dark(#C77B92, #E3A5B7);
  --co-drink:     light-dark(#4A7BA7, #7DB0DC);
  --co-howto:     light-dark(#5A6B7D, #93A6BA);
  --course-none:  light-dark(#7B8FA3, #9DB0C4);

  --radius:   14px;
  --radius-sm: 9px;
  /* Daylight shadows are the ink colour thinned right down; after dark they go
     to plain black, because a blue shadow on a blue page just looks like haze. */
  --shadow:   0 1px 2px light-dark(rgba(31,58,95,.05), rgba(0,0,0,.35)),
              0 6px 18px light-dark(rgba(31,58,95,.07), rgba(0,0,0,.3));
  --shadow-lg:0 2px 6px light-dark(rgba(31,58,95,.08), rgba(0,0,0,.4)),
              0 20px 44px light-dark(rgba(31,58,95,.14), rgba(0,0,0,.45));

  /* One thread of the weave, so the narrow band under the top bar can be woven
     at its own scale instead of rescaling --gingham (see .topbar::after). */
  --check: light-dark(rgba(62,124,177,.30), rgba(125,176,220,.24));

  /* The gingham itself: two translucent blue washes crossed over white, so the
     overlaps darken naturally the way woven cloth does. */
  --gingham: repeating-linear-gradient(90deg, light-dark(rgba(62,124,177,.28), rgba(125,176,220,.20)) 0 10px, transparent 10px 20px),
             repeating-linear-gradient(0deg,  light-dark(rgba(62,124,177,.28), rgba(125,176,220,.20)) 0 10px, transparent 10px 20px);
  /* A quieter weave for large areas sitting behind cards. */
  --gingham-soft: repeating-linear-gradient(90deg, light-dark(rgba(62,124,177,.13), rgba(125,176,220,.09)) 0 10px, transparent 10px 20px),
                  repeating-linear-gradient(0deg,  light-dark(rgba(62,124,177,.13), rgba(125,176,220,.09)) 0 10px, transparent 10px 20px);

  --serif: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
  --sans:  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --wrap: 1120px;
}

/* The override, in both directions. `theme.js` writes and clears this one
   attribute; with it absent we are back to `light dark` and the device decides. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/*
 * The safety net, for a browser too old to know what light-dark() means.
 *
 * Every token above would be invalid at computed-value time there — not one
 * colour wrong, but every colour gone, on a page nobody could then read. That
 * is far too total a failure to leave to a version check, so both halves of
 * every pair are restated here the old way. What is lost is only the manual
 * switch: the device setting decides, which is where this app started.
 *
 * Generated from the pairs above. If you add a token up there, add it here.
 */
@supports not (color: light-dark(#000, #fff)) {
  :root {
    --paper: #F7FAFD;
    --paper-deep: #E8F0F8;
    --card: #FFFFFF;
    --ink: #1F3A5F;
    --ink-soft: #4E6B8A;
    --ink-faint: #8AA2BC;
    --rule: #D5E3F0;
    --accent: #3E7CB1;
    --accent-deep: #2B5A85;
    --accent-wash: #E4EFF9;
    --wicker-wash: #F7EFE2;
    --meadow: #6B9E5E;
    --bloom: #E08B9B;
    --sage: #6B9E5E;
    --danger: #C1554D;
    --danger-wash: #FBEAE8;
    --shadow: 0 1px 2px rgba(31,58,95,.05), 0 6px 18px rgba(31,58,95,.07);
    --shadow-lg: 0 2px 6px rgba(31,58,95,.08), 0 20px 44px rgba(31,58,95,.14);
    --co-breakfast: #E0A23C;
    --co-appetizer: #D2795C;
    --co-soupsalad: #7FA34C;
    --co-main:      #B0503F;
    --co-side:      #6B8E4E;
    --co-dessert:   #C77B92;
    --co-drink:     #4A7BA7;
    --co-howto:     #5A6B7D;
    --course-none:  #7B8FA3;
    --check: rgba(62,124,177,.30);
    --gingham: repeating-linear-gradient(90deg, rgba(62,124,177,.28) 0 10px, transparent 10px 20px), repeating-linear-gradient(0deg, rgba(62,124,177,.28) 0 10px, transparent 10px 20px);
    --gingham-soft: repeating-linear-gradient(90deg, rgba(62,124,177,.13) 0 10px, transparent 10px 20px), repeating-linear-gradient(0deg, rgba(62,124,177,.13) 0 10px, transparent 10px 20px);
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --paper: #101924;
      --paper-deep: #172433;
      --card: #18242F;
      --ink: #E8F0F8;
      --ink-soft: #A8BFD4;
      --ink-faint: #7793AD;
      --rule: #2A3B4D;
      --accent: #7DB0DC;
      --accent-deep: #A3CBEB;
      --accent-wash: #1B2E40;
      --wicker-wash: #2A2318;
      --meadow: #8DBE80;
      --bloom: #E8A3B0;
      --sage: #8DBE80;
      --danger: #E89189;
      --danger-wash: #3A2320;
      --shadow: 0 1px 2px rgba(0,0,0,.35), 0 6px 18px rgba(0,0,0,.3);
      --shadow-lg: 0 2px 6px rgba(0,0,0,.4), 0 20px 44px rgba(0,0,0,.45);
      --co-breakfast: #EFBE69;
      --co-appetizer: #E79878;
      --co-soupsalad: #A3C473;
      --co-main:      #D4796A;
      --co-side:      #97BE79;
      --co-dessert:   #E3A5B7;
      --co-drink:     #7DB0DC;
      --co-howto:     #93A6BA;
      --course-none:  #9DB0C4;
      --check: rgba(125,176,220,.24);
      --gingham: repeating-linear-gradient(90deg, rgba(125,176,220,.20) 0 10px, transparent 10px 20px), repeating-linear-gradient(0deg, rgba(125,176,220,.20) 0 10px, transparent 10px 20px);
      --gingham-soft: repeating-linear-gradient(90deg, rgba(125,176,220,.09) 0 10px, transparent 10px 20px), repeating-linear-gradient(0deg, rgba(125,176,220,.09) 0 10px, transparent 10px 20px);
    }
  }
}

*, *::before, *::after { box-sizing: border-box; }

/* Several components below set an explicit `display`, which would otherwise
   beat the `hidden` attribute's UA rule and leave overlays stuck on screen. */
[hidden] { display: none !important; }

/*
 * The canvas — the strip behind the status bar, and whatever shows when a
 * scroll rubber-bands past the end of a short list.
 *
 * This has to be the CLOTH, not the paper. A background on <html> stops the
 * body's from propagating to the canvas, so setting a flat colour here left the
 * tablecloth ending abruptly at the bottom of a short page. Declaring the same
 * cloth on <html> restores it, and gives theme.js a settled colour to read back
 * for the status-bar meta — `--paper` on its own still computes to both halves
 * of its pair, so it can't be read directly.
 */
html {
  background-color: var(--paper-deep);
  background-image: var(--gingham-soft);
  background-size: 34px 34px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 17px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, legend { line-height: 1.2; }
.display { font-family: var(--serif); font-weight: 600; letter-spacing: -0.01em; }
h1.display { font-size: clamp(1.9rem, 4vw, 2.6rem); margin: 0 0 .2em; }

.lede { color: var(--ink-soft); margin: 0 0 1.5rem; font-size: 1.05rem; }
.hint { color: var(--ink-faint); font-size: .875rem; margin: .4rem 0 0; }
.opt  { color: var(--ink-faint); font-weight: 400; font-size: .8em; }

a { color: var(--accent-deep); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 2rem 1.25rem 5rem; }
.wrap--narrow { max-width: 720px; }
.center { text-align: center; }
.stack > * + * { margin-top: 1.25rem; }

.ico { width: 1.1em; height: 1.1em; fill: none; stroke: currentColor; stroke-width: 2;
       stroke-linecap: round; stroke-linejoin: round; flex: none; }
.ico--lg { width: 2rem; height: 2rem; stroke-width: 1.5; }

.screen--center { min-height: 100dvh; display: grid; place-items: center; padding: 1.5rem; }

/* ── Buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: .5rem; justify-content: center;
  font: inherit; font-weight: 600;
  padding: .65rem 1.1rem; border-radius: var(--radius-sm);
  border: 1px solid var(--rule); background: var(--card); color: var(--ink);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .15s, border-color .15s, transform .05s;
}
.btn:hover  { border-color: var(--ink-faint); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* After dark the accent lightens, so white letters on it stop being legible —
   the label goes near-black instead. Same trick everywhere a filled control
   appears below. */
.btn--primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn--primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

.btn--quiet { background: transparent; border-color: transparent; color: var(--ink-soft); }
.btn--quiet:hover { background: var(--paper-deep); border-color: transparent; color: var(--ink); }

.btn--danger { color: var(--danger); border-color: transparent; background: transparent; }
.btn--danger:hover { background: var(--danger-wash); border-color: transparent; }

.btn--block  { width: 100%; }
.btn--large  { padding: .85rem 1.6rem; font-size: 1.05rem; }
.btn--small  { padding: .4rem .7rem; font-size: .875rem; }

.btn--toggle[aria-pressed="true"] {
  background: var(--accent-wash); border-color: var(--accent); color: var(--accent-deep);
}
.btn--toggle[aria-pressed="true"] .ico { fill: currentColor; }

/* ── Forms ─────────────────────────────────────────────────────────────── */

.field { margin-bottom: 1.35rem; }
fieldset.field { border: 0; padding: 0; margin-inline: 0; }

label, legend {
  display: block; font-weight: 600; font-size: .95rem;
  margin-bottom: .4rem; padding: 0;
}
legend { font-family: var(--serif); font-size: 1.3rem; margin-bottom: .25rem; }

input, select, textarea {
  width: 100%; font: inherit; color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: .65rem .8rem;
}
textarea { resize: vertical; line-height: 1.7; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash);
}
.input--display { font-family: var(--serif); font-size: 1.35rem; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 .8rem; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 .6rem; }

.check { display: flex; align-items: center; gap: .6rem; font-weight: 500; margin: .6rem 0; }
.check input { width: auto; }

.row-form { display: flex; gap: .6rem; flex-wrap: wrap; }
.row-form input { flex: 1 1 260px; }

/* ── Alerts ────────────────────────────────────────────────────────────── */

.alert { border-radius: var(--radius-sm); padding: .75rem 1rem; margin: 0 0 1rem; font-size: .95rem; }
.alert--error { background: var(--danger-wash); color: var(--danger); }
.alert--info  { background: var(--accent-wash); color: var(--accent-deep); }

/* ── Sign in ───────────────────────────────────────────────────────────── */

.login-card {
  width: min(440px, 100%); background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 2.25rem;
  text-align: center;
}
.login-card form { text-align: left; }
.login-mark { font-size: 2.75rem; line-height: 1; }
.chips { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: .6rem; }
.chip {
  font: inherit; font-weight: 600; font-size: .9rem; cursor: pointer;
  padding: .35rem .8rem; border-radius: 999px;
  border: 1px solid var(--rule); background: var(--paper); color: var(--ink);
}
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ── Top bar ───────────────────────────────────────────────────────────── */

.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .75rem 1.25rem;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}
.brand {
  font-family: var(--serif); font-weight: 600; font-size: 1.2rem;
  color: var(--ink); text-decoration: none;
}
.topbar__right { display: flex; align-items: center; gap: .5rem; }

/* ── Light / dark / auto ───────────────────────────────────────────────── */

/* Three buttons welded into one switch, because this is a single setting with
   three positions — not three things she could turn on at the same time. */
.themepick { display: inline-flex; }
.themepick .btn {
  border-radius: 0; margin-left: -1px;
  padding-inline: .8rem; font-size: .92rem;
}
.themepick .btn:first-child {
  margin-left: 0;
  border-start-start-radius: var(--radius-sm); border-end-start-radius: var(--radius-sm);
}
.themepick .btn:last-child {
  border-start-end-radius: var(--radius-sm); border-end-end-radius: var(--radius-sm);
}
/* Lift the chosen segment so its own border draws the seam, not its neighbour's. */
.themepick .btn[aria-pressed="true"] {
  position: relative; z-index: 1;
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.themepick .btn:focus-visible { position: relative; z-index: 2; }

/* Welded segments leave no gap between targets, so each one has to be wide
   enough that a thumb landing on a seam still gets what she was aiming at. */
@media (any-pointer: coarse) { .themepick .btn { min-width: 4.5rem; } }

/* Phone: keep the bar on one line. The brand shortens to just the mark. */
@media (max-width: 540px) {
  .topbar { padding: .6rem .8rem; gap: .5rem; }
  .brand { font-size: 1.05rem; white-space: nowrap; }
  .brand .brand__long { display: none; }
  .topbar .btn { padding: .55rem .75rem; font-size: .92rem; }
  .topbar__right { gap: .15rem; }
  .themepick .btn { padding-inline: .5rem; font-size: .85rem; min-width: 0; }
}

.backlink {
  display: inline-flex; align-items: center; gap: .4rem;
  color: var(--ink-soft); text-decoration: none; font-weight: 600; font-size: .95rem;
  margin-bottom: 1.25rem;
}
.backlink:hover { color: var(--ink); }

/* ── Shelf ─────────────────────────────────────────────────────────────── */

.shelf-head { margin-bottom: 1.25rem; }

.toolbar {
  display: flex; gap: .75rem; flex-wrap: wrap; align-items: center;
  margin-bottom: 2rem;
}
.search { position: relative; flex: 1 1 280px; display: flex; align-items: center; }
.search .ico { position: absolute; left: .8rem; color: var(--ink-faint); }
.search input { padding-left: 2.5rem; }
.toolbar__filters { display: flex; gap: .5rem; flex-wrap: wrap; }
.toolbar__filters select { width: auto; padding-block: .6rem; }

.grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  /*
   * Every row the same height as every other, not just every card the same
   * height as the ones beside it — which a grid does on its own and which was
   * hiding the problem. The shelf was ragged BETWEEN rows, and a row of tall
   * cards next to a row of short ones is what makes a grid stop reading as a
   * grid.
   *
   * This only behaves because the card below is bounded: the title and the
   * description are clamped, so the tallest card in the shelf is a known
   * height rather than however long one recipe's name happens to be. Take the
   * clamps off and every row inherits the worst case.
   */
  grid-auto-rows: 1fr;
}

.rcard {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--rule); border-radius: 7px;
  overflow: hidden; text-decoration: none; color: inherit;
  box-shadow: var(--shadow); transition: transform .12s, box-shadow .12s;
}
.rcard:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
/*
 * The photograph's slot, and it has to be the SLOT's shape rather than the
 * photograph's.
 *
 * `aspect-ratio` alone did not do it. This is a flex item, and a flex item's
 * automatic minimum height is its content — so a square photo made a square
 * tile and a portrait one made a tile half again as tall, and `object-fit:
 * cover` never got the chance to crop because the box had already grown to fit.
 * The same 4:3 slot rendered at 170px on one card and 341px on the card beside
 * it, which is most of why the shelf looked ragged.
 *
 * `min-height: 0` gives up that automatic minimum so the ratio wins; `flex:
 * none` stops the tile trading height with the body around it.
 */
.rcard__img {
  aspect-ratio: 4/3; background: var(--paper-deep); position: relative;
  flex: none; min-height: 0; overflow: hidden;
}
.rcard__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/*
 * A recipe with no photograph. The mark comes from the dish itself (see
 * dish-icon.js), so a shelf of un-photographed recipes is still scannable.
 *
 * Set on the tablecloth rather than a flat grey, so a missing photo reads as a
 * card waiting for one rather than an image that failed to load — and the mark
 * keeps its own colour instead of being dimmed to a smudge.
 */
.rcard__img--blank {
  /* Sized against the tile rather than the root, so the mark stays the same
     fraction of the card whether three fit across an iPad or one fits a phone.
     `cqh` needs the container declared, and the tile already has a fixed
     aspect-ratio, so declaring it costs nothing. */
  container-type: size;
  display: grid; place-items: center;
  font-size: clamp(2.4rem, 42cqh, 5rem);
  background-color: var(--paper-deep);
  /*
   * Woven at half strength and twice the scale it used to be.
   *
   * While the whole page was gingham this tile was quieter than its
   * surroundings. With the ground gone to plain paper it became the loudest
   * pattern on the shelf — a full checked square, the largest single area of a
   * card, shouting over the course bar that is supposed to be doing the
   * grouping. Same cloth, turned down until it reads as texture behind the
   * mark rather than as the point of the tile.
   */
  background-image: var(--gingham-soft);
  background-size: 30px 30px;
  opacity: 1;
}
.rcard__img--blank { position: relative; isolation: isolate; }
.rcard__img--blank::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--card); opacity: .55;
}
.rcard__img--blank .rcard__fav { font-size: 1rem; }
.rcard__fav {
  position: absolute; top: .6rem; right: .6rem; width: 1.9rem; height: 1.9rem;
  display: grid; place-items: center; border-radius: 999px;
  background: rgba(255,255,255,.92); color: var(--accent);
}
.rcard__fav .ico { fill: currentColor; }
.rcard__body { padding: .9rem 1rem 1.1rem; display: flex; flex-direction: column; gap: .35rem; flex: 1; }
/* Sans, not serif: the shelf is scanned. A long title also holds two lines
   here where the serif took three. The serif still owns the recipe page and
   the printed sheet, which are read rather than scanned. */
.rcard__title {
  font-family: var(--sans); font-size: 1.08rem; font-weight: 700; margin: 0;
  letter-spacing: -.005em; line-height: 1.25;
  /* Two lines, always: clamped so a long name cannot push the shelf around,
     and reserved so a one-line name still leaves the card the same shape as
     its neighbours. Names longer than this are read on the recipe's own page,
     which is one tap away. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-height: calc(2 * 1.25em);
}
/* The description, where there is one. Two lines is enough to tell two
   chocolate cakes apart, which is all this line is for. */
.rcard__body > .hint {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Two lines. Unclamped it ran to four and dragged the shelf out of shape; held
   to one it cut yields like "serves Two braided loaves" in half, which is the
   thing you are squinting at the card FOR. Two costs twenty pixels a card and
   takes the number of cards with something cut from ten to two. */
.rcard__meta {
  color: var(--ink-faint); font-size: .82rem; margin-top: auto; padding-top: .5rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.empty {
  text-align: center; padding: 4rem 1rem; color: var(--ink-soft);
  border: 2px dashed var(--rule); border-radius: var(--radius);
}
.empty strong { display: block; font-family: var(--serif); font-size: 1.4rem; color: var(--ink); margin-bottom: .4rem; }

/* ── Add: method chooser ───────────────────────────────────────────────── */

.methods { display: grid; gap: 1rem; margin-bottom: 1.75rem; }
.method {
  display: flex; gap: 1.1rem; align-items: flex-start; text-align: left;
  padding: 1.25rem; font: inherit; cursor: pointer;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  color: inherit; box-shadow: var(--shadow); transition: border-color .12s, transform .12s;
}
.method:hover { border-color: var(--accent); transform: translateY(-2px); }
.method[aria-expanded="true"] { border-color: var(--accent); background: var(--accent-wash); }
.method__icon {
  display: grid; place-items: center; flex: none;
  width: 2.9rem; height: 2.9rem; border-radius: 999px;
  background: var(--accent-wash); color: var(--accent-deep);
}
.method[aria-expanded="true"] .method__icon { background: var(--card); }
.method__body { display: flex; flex-direction: column; gap: .25rem; }
.method__body strong { font-family: var(--serif); font-size: 1.15rem; }
.method__body span { color: var(--ink-soft); font-size: .93rem; line-height: 1.5; }

.panel {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 1.5rem; box-shadow: var(--shadow); margin-bottom: 2rem;
}
.panel h2 { font-family: var(--serif); margin: 0 0 .75rem; font-size: 1.3rem; }
.steps-help { color: var(--ink-soft); font-size: .95rem; padding-left: 1.2rem; margin: 0 0 1.25rem; }
.steps-help li { margin-bottom: .3rem; }
.steps-help em { color: var(--ink); font-style: normal; font-weight: 600; }

.dropzone {
  width: 100%; display: flex; flex-direction: column; align-items: center; gap: .4rem;
  padding: 2.5rem 1rem; font: inherit; cursor: pointer; color: var(--ink-soft);
  background: var(--paper); border: 2px dashed var(--rule); border-radius: var(--radius);
}
.dropzone:hover, .dropzone.is-over { border-color: var(--accent); background: var(--accent-wash); color: var(--accent-deep); }
.dropzone strong { font-size: 1.05rem; color: var(--ink); }

/* ── Thumbnails / image picker ─────────────────────────────────────────── */

.thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: .75rem; margin-top: 1rem; }
.thumb { position: relative; border-radius: var(--radius-sm); overflow: hidden; border: 1px solid var(--rule); background: var(--paper-deep); }
.thumb img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }

.pick {
  position: relative; padding: 0; border: 2px solid var(--rule); background: var(--paper-deep);
  border-radius: var(--radius-sm); overflow: hidden; cursor: pointer;
}
.pick img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; transition: opacity .12s; }
.pick:not(.is-kept) img { opacity: .45; }
.pick.is-kept { border-color: var(--sage); }
.pick.is-hero { border-color: var(--accent); }
.pick__flag {
  position: absolute; top: .35rem; left: .35rem;
  font-size: .7rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: .18rem .45rem; border-radius: 999px; color: #fff; background: var(--sage);
}
.pick.is-hero .pick__flag { background: var(--accent); }
/*
 * Both of these sit ON the photograph, so their white pill stays white in both
 * themes — a dark pill over a dark holiday snap would vanish. A fixed ground
 * needs a fixed label to go with it: plain `var(--ink)` lightens after dark and
 * left near-white letters on a near-white pill, at about 1.1:1.
 */
.pick__hero-btn {
  position: absolute; bottom: 0; left: 0; right: 0;
  font: inherit; font-size: .78rem; font-weight: 600; padding: .3rem;
  border: 0; cursor: pointer;
  background: rgba(255,255,255,.94); color: var(--ink);
}
.pick__hero-btn:hover { background: var(--accent); color: #fff; }
.pick__del {
  position: absolute; top: .3rem; right: .3rem; width: 1.6rem; height: 1.6rem;
  display: grid; place-items: center; border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(255,255,255,.94); color: var(--danger);
}

/* ── Editor ────────────────────────────────────────────────────────────── */

.editor { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 2.5rem; align-items: start; }
.editor__actions {
  grid-column: 1 / -1; display: flex; gap: .75rem; align-items: center; flex-wrap: wrap;
  border-top: 1px solid var(--rule); padding-top: 1.5rem;
}
.editor__actions .alert { flex-basis: 100%; margin: 0; }

.card {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 1.25rem; box-shadow: var(--shadow); margin-bottom: 1rem;
}
.card .field:last-child { margin-bottom: 0; }
details.card > summary {
  cursor: pointer; font-weight: 600; font-family: var(--serif); font-size: 1.1rem;
  list-style-position: inside;
}
details.card[open] > summary { margin-bottom: 1rem; }

.groups { display: grid; gap: 1rem; }
.group { border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--card); padding: .9rem; }
.group__head { display: flex; gap: .5rem; align-items: center; margin-bottom: .5rem; }
.group__head input { font-weight: 600; }
.group textarea { font-variant-numeric: tabular-nums; }
.group__foot { display: flex; justify-content: space-between; align-items: center; margin-top: .35rem; }

@media (max-width: 900px) {
  .editor { grid-template-columns: 1fr; gap: 1.5rem; }
}

/* ── Recipe view ───────────────────────────────────────────────────────── */

/* The back-link sits in its own .wrap above this one; drop the doubled gap. */
#screen-recipe > .wrap { padding-bottom: 0; }
#recipe-view { padding-top: 0; }

.rv__hero { width: 100%; max-height: 420px; object-fit: cover; border-radius: var(--radius); margin-bottom: 1.75rem; }
.rv__title { font-size: clamp(2rem, 5vw, 3rem); margin: 0 0 .35rem; }
.rv__by { color: var(--ink-soft); margin: 0 0 1.25rem; }
.rv__actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 2rem; }

.rv__facts {
  display: flex; flex-wrap: wrap; gap: 0 2.5rem;
  border-block: 1px solid var(--rule); padding: 1rem 0; margin-bottom: 2rem;
}
.rv__fact { display: flex; flex-direction: column; }
.rv__fact dt { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); }
.rv__fact dd { margin: 0; font-weight: 600; font-size: 1.05rem; }

.rv__cols { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: 3rem; align-items: start; }
@media (max-width: 820px) { .rv__cols { grid-template-columns: 1fr; gap: 2rem; } }

.rv h2 { font-family: var(--serif); font-size: 1.5rem; margin: 0 0 .75rem; }
.rv h3 { font-family: var(--serif); font-size: 1.1rem; margin: 1.25rem 0 .5rem; color: var(--accent-deep); }

.ing-list { list-style: none; padding: 0; margin: 0; }
.ing-list li { padding: .5rem 0 .5rem 1.6rem; border-bottom: 1px solid var(--rule); position: relative; }
.ing-list li::before {
  content: ""; position: absolute; left: .35rem; top: 1.1rem;
  width: .4rem; height: .4rem; border-radius: 999px; background: var(--accent);
}
.step-list { padding-left: 0; margin: 0; list-style: none; counter-reset: step; }
.step-list li { counter-increment: step; display: flex; gap: 1rem; margin-bottom: 1.25rem; }
.step-list li::before {
  content: counter(step); flex: none;
  width: 1.9rem; height: 1.9rem; border-radius: 999px;
  display: grid; place-items: center; font-weight: 700; font-size: .9rem;
  background: var(--accent-wash); color: var(--accent-deep);
}

.scaler { display: flex; gap: .35rem; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; }
.scaler span { font-size: .85rem; color: var(--ink-faint); margin-right: .25rem; }
.scaler button {
  font: inherit; font-weight: 600; font-size: .85rem; cursor: pointer;
  padding: .3rem .7rem; border-radius: 999px;
  border: 1px solid var(--rule); background: var(--card); color: var(--ink);
}
.scaler button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }

.nut-table { width: 100%; border-collapse: collapse; margin-top: .5rem; font-size: .93rem; }
.nut-table th, .nut-table td { text-align: left; padding: .4rem 0; border-bottom: 1px solid var(--rule); }
.nut-table th { font-weight: 500; color: var(--ink-soft); }
.nut-table td { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }

.rv__notes { background: var(--paper-deep); border-radius: var(--radius); padding: 1.25rem 1.5rem; margin-top: 2rem; }
.rv__notes p { white-space: pre-wrap; margin: 0; }
.rv__gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .75rem; margin-top: 2rem; }
.rv__gallery img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: var(--radius-sm); }

.tag-row { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: 1rem; }
.tag { font-size: .8rem; padding: .2rem .65rem; border-radius: 999px; background: var(--paper-deep); color: var(--ink-soft); }

/* ── Overlays ──────────────────────────────────────────────────────────── */

.busy, .modal {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center; padding: 1.5rem;
  background: color-mix(in srgb, var(--paper) 78%, transparent);
  backdrop-filter: blur(3px);
}
.busy__card, .modal__card {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 2rem; text-align: center; max-width: 420px; width: 100%;
}
.modal__card { text-align: left; }
.modal__card h2 { font-family: var(--serif); margin: 0 0 .35rem; }
.modal__actions { display: flex; gap: .6rem; justify-content: flex-end; margin-top: 1.5rem; }
.busy__card p { margin: .75rem 0 0; font-weight: 600; }

.spinner {
  width: 2.5rem; height: 2.5rem; margin: 0 auto; border-radius: 999px;
  border: 3px solid var(--rule); 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; } }

.toast {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
  z-index: 200; padding: .75rem 1.25rem; border-radius: 999px;
  background: var(--ink); color: var(--paper); font-weight: 600; box-shadow: var(--shadow-lg);
}

/* The print sheet is built in the DOM but never shown on screen. */
#print-sheet { display: none; }

/* ==========================================================================
   Cookbook production — binder status, selection, page editor, batch print
   ========================================================================== */

/* ── Binder status bar ─────────────────────────────────────────────────── */

.binderbar {
  display: flex; gap: .75rem; align-items: center; justify-content: space-between;
  flex-wrap: wrap; margin-bottom: 1.5rem;
}
.binderbar__filters { display: flex; gap: .4rem; flex-wrap: wrap; }
.chip--filter { display: inline-flex; align-items: center; gap: .4rem; }
/* This is the headline number on the shelf — how many pages are waiting to be
   printed — so it has to survive both themes. */
.chip__n {
  font-size: .75rem; font-weight: 700; padding: .05rem .4rem; border-radius: 999px;
  background: var(--accent); color: #fff; min-width: 1.3em; text-align: center;
}
/* On a chosen chip the badge is the chip itself under a white veil, which comes
   out pale in either theme — so unlike the badge above, this count is dark in
   both. White digits on it were 2.8:1 by day and 1.8:1 after dark. */
.chip--filter[aria-pressed="true"] .chip__n { background: rgba(255,255,255,.28); color: #1B1917; }
.chip__n:empty, .chip__n[data-zero="1"] { display: none; }

/* ── Print state on a card ─────────────────────────────────────────────── */

.pstate {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .75rem; font-weight: 600; padding: .15rem .5rem; border-radius: 999px;
}
.pstate::before { content: ""; width: .45rem; height: .45rem; border-radius: 999px; background: currentColor; }
.pstate--unprinted { background: var(--paper-deep); color: var(--ink-faint); }
.pstate--printed   { background: #E8F0E8; color: var(--sage); }
.pstate--stale     { background: var(--accent-wash); color: var(--accent-deep); }

.rcard { position: relative; }
.rcard__pick {
  position: absolute; top: .6rem; left: .6rem; z-index: 2;
  width: 1.75rem; height: 1.75rem; border-radius: 999px;
  border: 2px solid #fff; background: rgba(255,255,255,.9);
  display: grid; place-items: center; cursor: pointer; color: transparent;
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
/* The white ring stays white in both themes — it is what lifts the circle off
   the photograph — but the tick inside follows the fill it sits on. */
.rcard--picked .rcard__pick { background: var(--accent); border-color: #fff; color: #fff; }
.rcard--picked { outline: 3px solid var(--accent); outline-offset: 2px; }
body.is-selecting .rcard { cursor: pointer; }

/* ── Selection bar ─────────────────────────────────────────────────────── */

.selectbar {
  position: fixed; left: 50%; bottom: 1.25rem; transform: translateX(-50%);
  z-index: 60; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;
  padding: .7rem 1.1rem; border-radius: 999px;
  background: var(--card); border: 1px solid var(--rule); box-shadow: var(--shadow-lg);
  font-weight: 600;
}
.selectbar__actions { display: flex; gap: .4rem; flex-wrap: wrap; }

/* ── Page editor ───────────────────────────────────────────────────────── */

/* ==========================================================================
   The editor's two tabs

   These were two separate screens behind two buttons that both said "Edit".
   As tabs they read as one job with two sides to it, which is what they are.
   ========================================================================== */

.tabs {
  display: grid; grid-template-columns: 1fr 1fr; gap: .5rem;
  margin: 1.25rem 0 1.5rem;
}
.tab {
  display: flex; flex-direction: column; align-items: flex-start; gap: .1rem;
  min-height: 60px; padding: .7rem .95rem;
  font: inherit; font-weight: 600; text-align: left; cursor: pointer; color: var(--ink-soft);
  background: var(--paper-deep); border: 1px solid var(--rule);
  border-radius: var(--radius); transition: background .12s, border-color .12s, color .12s;
}
.tab .ico { width: 18px; height: 18px; margin-bottom: .1rem; }
.tab span { font-weight: 400; font-size: .82rem; color: var(--ink-faint); }
.tab[aria-selected="true"] {
  background: var(--card); border-color: var(--accent); color: var(--ink);
  box-shadow: inset 0 -3px 0 var(--accent);
}
.tab[aria-selected="true"] span { color: var(--ink-soft); }
@media (hover: hover) { .tab:hover { border-color: var(--accent); } }

/* Sitting inside the editor now rather than owning the whole viewport. */
.pe { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 1.25rem; }
.pe__side {
  padding: 1.25rem; border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--card); align-self: start;
}
.pe__stage {
  padding: 1.25rem; background: var(--paper-deep);
  border-radius: var(--radius);
  /* The sheet keeps a true 8.5in layout box and is scaled to fit, so what is
     drawn always fits — but the box it was scaled FROM still hangs over both
     edges, and `auto` offered a scrollbar for the part of it that isn't there.
     Clipped on this axis only, so the column still grows down as it should. */
  overflow-x: clip;
}

/* One save, reachable from either tab without scrolling a long form. */
.editbar {
  position: sticky; bottom: 0; z-index: 15;
  margin: 2rem -1rem 0; padding: .85rem 1rem;
  padding-bottom: max(.85rem, env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--rule);
}
.editbar__row { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
.editbar .alert { margin: 0 0 .6rem; }
.editbar #editor-cancel { margin-left: auto; }

/* ==========================================================================
   How-to guides on the shelf and on a recipe

   A guide is the same kind of thing as a recipe — a page in the same binder —
   so its card is the recipe card with one badge. Only the badge distinguishes
   "How to fold a napkin" from a recipe with a similar name.
   ========================================================================== */

.rcard--guide { border-color: color-mix(in srgb, var(--wicker) 45%, var(--rule)); }
.rcard__kind {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--wicker); margin-bottom: .1rem;
}
.rcard__kind .ico { width: 14px; height: 14px; }

.rv__guides { margin-top: 2.5rem; }
.guide-strip { display: flex; flex-wrap: wrap; gap: .75rem; }
.guide-chip {
  display: flex; align-items: center; gap: .7rem; min-height: 56px;
  padding: .5rem .9rem .5rem .5rem; text-decoration: none; color: inherit;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.guide-chip img, .guide-chip__blank {
  width: 46px; height: 46px; border-radius: var(--radius-sm); object-fit: cover;
  display: grid; place-items: center; background: var(--wicker-wash); font-size: 1.3rem;
}
.guide-chip strong { display: block; font-size: .98rem; }
.guide-chip small { color: var(--ink-faint); font-size: .8rem; }
@media (hover: hover) { .guide-chip:hover { border-color: var(--wicker); } }

/*
 * A feature that exists but isn't ready. Deliberately not `disabled`: a disabled
 * button gives no feedback at all when tapped, and "why is this greyed out" is a
 * worse question than a sentence that answers it. It stays tappable and says so.
 */
.btn--soon {
  color: var(--ink-faint); border-style: dashed; background: transparent;
  cursor: default;
}
.btn--soon .ico { opacity: .6; }
.btn--soon span {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding: .1rem .4rem; border-radius: 999px; background: var(--paper-deep); color: var(--ink-faint);
}
.btn--soon:hover { border-color: var(--rule); }

/* ── Settings: appearance and the reading voice ─────────────────────────── */

.modal__card--tall { max-height: min(84dvh, 40rem); overflow-y: auto; }

.themepick { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.themepick .btn { width: 100%; }
.themepick .btn[aria-pressed="true"] {
  background: var(--accent-wash); border-color: var(--accent); color: var(--accent-deep);
}

/*
 * A voice's name says nothing about how it sounds, so the list is really a row
 * of play buttons — tapping one both chooses it and plays it.
 */
/* ── Choosing the reading voice ─────────────────────────────────────────────
   Shut until you ask for it. A Mac lists around forty voices; open, that was
   one field taller than the rest of Settings put together, and it read as a
   wall you had to scroll past to reach anything below — so the list is behind a
   row that just says which voice is in use.
   ────────────────────────────────────────────────────────────────────────── */
.vpick__current {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  min-height: 48px; padding: .55rem .8rem; margin-top: .5rem;
  font: inherit; text-align: left; cursor: pointer;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
}
.vpick__current:hover { border-color: var(--ink-faint); }
.vpick__current:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }

/* Parked, exactly as Cook along is — dashed and faint, with the same chip. The
   two shut doors in this app should look like the same kind of door. */
.vpick__current--soon {
  color: var(--ink-faint); border-style: dashed; background: transparent; cursor: default;
}
.vpick__current--soon:hover { border-color: var(--rule); }
.vpick__current--soon .vpick__now { font-weight: 600; }
.vpick__soon {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding: .1rem .4rem; border-radius: 999px; background: var(--paper-deep); color: var(--ink-faint);
}
.vpick__now { flex: 1; font-weight: 600; }
.vpick__open { font-size: .82rem; color: var(--accent-deep); }
.vpick__chev { transition: transform .18s ease; color: var(--ink-faint); }
.vpick__current[aria-expanded="true"] .vpick__chev { transform: rotate(180deg); }

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

/* Capped and scrolled inside itself, so even open it can't run away with the
   sheet. `contain` stops a flick that reaches the end of the list from carrying
   on into the dialog behind it. */
.vpick {
  list-style: none; padding: .3rem; margin: .5rem 0 0; display: grid; gap: .3rem;
  max-height: 15rem; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--paper-deep);
}
.vpick__count { margin: .4rem 0 0; font-size: .82rem; color: var(--ink-faint); }
.vpick__row {
  display: flex; align-items: center; gap: .6rem; width: 100%; min-height: 48px;
  padding: .55rem .8rem; font: inherit; text-align: left; cursor: pointer;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
}
.vpick__row[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-wash); }
.vpick__name { font-weight: 600; flex: 1; }
.vpick__lang { color: var(--ink-faint); font-size: .8rem; }
.vpick__tag {
  font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding: .15rem .45rem; border-radius: 999px;
  background: var(--paper-deep); color: var(--ink-faint);
}
.vpick__tag--good { background: color-mix(in srgb, var(--meadow) 22%, transparent); color: var(--meadow); }
.vpick__more { margin-top: 1rem; font-size: .9rem; color: var(--ink-soft); }
.vpick__more summary { cursor: pointer; font-weight: 600; color: var(--accent-deep); min-height: 44px;
  display: flex; align-items: center; }
.vpick__more p { margin: .5rem 0; }

/* A way in that isn't available on this install — visibly shut, still tappable
   so it can say why, rather than silently missing or inertly greyed. */
.method--off { opacity: .62; }
.method--off .method__icon { opacity: .7; }
.method__off {
  display: block; margin-top: .35rem; font-weight: 600; font-size: .85rem;
  color: var(--wicker);
}

/* Which kind of page a batch row is, when the batch holds both. */
.batch-kind {
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--wicker); margin-right: .3rem;
}

/* What this recipe costs in paper. A "page" is a recipe and a recipe can be
   three sheets, so the count she loads the tray with is this one. */
.batch-sheets {
  font-size: .75rem; color: var(--ink-faint); margin-left: .4rem;
  font-variant-numeric: tabular-nums;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 900px) {
  /* minmax(0,…), not 1fr: a bare `1fr` track has a min-content floor, and the
     sheet inside is 816px wide and does not wrap — so the column grew to the
     sheet and pushed the whole PAGE sideways instead of the sheet being
     scaled to the column. */
  .pe { grid-template-columns: minmax(0, 1fr); }
  .pe__side { border-right: 0; border-bottom: 1px solid var(--rule); }
}

.templates { display: grid; gap: .4rem; }
.tpl {
  text-align: left; font: inherit; cursor: pointer; padding: .6rem .75rem;
  border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--paper);
  color: inherit;
}
.tpl strong { display: block; font-size: .95rem; }
.tpl span { font-size: .8rem; color: var(--ink-faint); line-height: 1.35; display: block; }
.tpl[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-wash); }
.tpl--suggested::after {
  content: "fits best"; float: right; font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--sage);
}

/* What the chosen style costs in paper.
   Deliberately quiet — it is a price, not a warning. Four sheets of big type
   is a perfectly good trade for someone who wants to read it from across the
   kitchen; she just gets to know it is four. */
.pe-cost {
  margin: .5rem 0 0; font-size: .8rem; line-height: 1.4; color: var(--ink-faint);
}
.pe-cost .linkish {
  font: inherit; color: var(--accent); background: none; border: 0; padding: 0;
  cursor: pointer; text-decoration: underline; text-underline-offset: .15em;
}

/* A real letter sheet on screen, so the preview is literally the page. */
.page-stage {
  width: 8.5in; min-height: 11in; margin: 0 auto;
  padding: 0.55in 0.6in 0.5in 1.15in;
  background: #fff; color: #000;
  box-shadow: 0 4px 24px rgba(0,0,0,.22);
  position: relative;

  /*
   * Eight and a half inches wide, always — and scaled down when the column is
   * narrower rather than reflowed to fit it.
   *
   * This screen's own words are "what you see here is exactly what prints", so
   * the sheet cannot be allowed to change shape to suit the window. Narrowing
   * it re-breaks every line, which is the one thing a page preview must not
   * do. Scaling renders the identical layout smaller: same line breaks, same
   * page, fewer pixels.
   *
   * --stage-fit is set by fitStage() in app.js, which measures the column. It
   * cannot be done here: the ratio is a length divided by a length, and CSS
   * has no way to spell that.
   *
   * Scaled from the top LEFT, not the centre. `margin: 0 auto` cannot centre a
   * box wider than the space it is in — it gives up and left-aligns — so the
   * box's middle sits right of the column's middle, and scaling about that
   * middle carried the sheet off the right-hand edge by exactly half what it
   * was overflowing by. From the left corner it shrinks into the room it has.
   * When there is room to spare the scale is 1 and `margin: 0 auto` centres it
   * in the ordinary way.
   */
  transform: scale(var(--stage-fit, 1));
  transform-origin: top left;

  /* The sheet is a piece of paper: white in both themes, black ink, always. So
     the tokens are pinned to their daytime values for everything inside it —
     otherwise the after-dark accent (a pale sky blue meant for a navy page)
     lands on white and fades the marks that matter, and "second page starts
     here" — the one warning that the page won't fit — drops to 1.7:1. */
  --accent: #3E7CB1;
  --accent-deep: #2B5A85;
  --ink: #1F3A5F;
  --ink-soft: #4E6B8A;
  --ink-faint: #8AA2BC;
  --rule: #D5E3F0;
}
/* The dotted line that used to run down here marked where a 3-hole punch would
   land, so the margin could be seen to be clear of it. The pages go into
   sleeves, so nothing is punched and the line was answering a question nobody
   was asking. (The wide left margin it pointed at is still there — that is a
   separate decision, and changing it changes every sheet count in the app.) */
.page-stage .ps { max-width: none; }
.page-stage [contenteditable]:hover { background: #FFF6D8; }
.page-stage [contenteditable]:focus { background: #FFF2C4; outline: 2px solid var(--accent); outline-offset: 2px; }

/* Where the second sheet would begin. */
.page-stage--over::after {
  content: "second page starts here";
  position: absolute; left: 0; right: 0; top: calc(11in - 0.5in);
  border-top: 2px dashed var(--accent);
  font: 600 9pt/1 Helvetica, Arial, sans-serif; color: var(--accent-deep);
  padding-top: .06in; text-align: right;
}

/*
 * Below this the sheet gives up and becomes a plain fluid block.
 *
 * It used to happen at 1100px of VIEWPORT, which is the wrong thing to measure
 * — the sheet lives in a column beside a 300px sidebar, so a 1280px window put
 * an 816px sheet in a 720px column, and the preview scrolled sideways while the
 * rule that was meant to prevent it sat waiting for a window nobody had. Now
 * the sheet is scaled to its column instead, at any width, and this is only
 * about a phone: at 375px a true-scale letter sheet renders at 42%, and 11pt
 * type at 42% is not something you can tap a word in to correct.
 *
 * So the phone trades the promise for legibility, and it is the only thing
 * that does. Every screen this is actually used on — the iPad, the Mac — keeps
 * a sheet that breaks its lines exactly where the paper will.
 */
@media (max-width: 620px) {
  .page-stage {
    width: 100%; min-height: 0; padding: 1.25rem 1rem 1.25rem 1.6rem;
    transform: none;
  }
  .page-stage--over::after { display: none; }
}

/* ── Batch print ───────────────────────────────────────────────────────── */

.batch-list { margin: 1.5rem 0; padding-left: 1.4rem; }
.batch-list li { padding: .4rem 0; border-bottom: 1px solid var(--rule); }
.batch-list .pstate { margin-left: .5rem; }

.or-rule { display: flex; align-items: center; gap: .75rem; margin: 1.25rem 0; color: var(--ink-faint); font-size: .85rem; }
.or-rule::before, .or-rule::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

/* ── Recipe view additions ─────────────────────────────────────────────── */

/* A column now: the status line, and under it what changed since the sheet was
   printed. The head keeps the row it always was. */
.rv__binder {
  padding: .75rem 1rem; border-radius: var(--radius-sm);
  background: var(--paper-deep); margin-bottom: 1.5rem; font-size: .9rem;
}
.rv__binder-head { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }

/* Sits with the sentence it answers, not shoved to the far edge — it was
   `margin-left: auto`, which pushed it onto its own line away from the tag it
   belongs to. The binder button is the one that goes right. */
.rv__whatchanged { text-decoration: underline; text-underline-offset: 3px; }
.rv__binder-head > .btn:last-child { margin-left: auto; }

/* Evidence for "needs a fresh page", so the decision can be made rather than
   guessed — but only once asked for. Shut, the bar is one line. */
.rv__changepanel {
  margin-top: .6rem; padding-top: .6rem; border-top: 1px solid var(--rule);
  display: flex; flex-direction: column; align-items: flex-start; gap: .6rem;
}
/* First thing under the rule, so which view you are in is settled before you
   start reading — and the same segmented control the scaler and the unit switch
   use, because it is doing the same job. Its own margin is dropped; the panel's
   gap owns the spacing. */
.rv__changeview { margin-bottom: 0; }
.rv__changes {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .25rem;
}
.rv__changes li { display: flex; gap: .45rem; flex-wrap: wrap; }
.rv__changes li::before { content: "·"; color: var(--ink-faint); }
.rv__changes span { color: var(--ink-faint); }

/* ── Word for word ──────────────────────────────────────────────────────────
   What the sheet in the binder says, beside what the recipe says now. Two
   columns on anything wide enough to read them side by side; stacked below
   that, where a 40-character column is worse than no column at all. The labels
   stay in the same order stacked, so "Previous" is always the one above.
   ────────────────────────────────────────────────────────────────────────── */
.chg { width: 100%; font-size: .875rem; }
.chg__row { display: grid; grid-template-columns: 1fr; gap: .5rem 1.25rem; }
.chg__row--head {
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-faint); padding-bottom: .3rem; display: none;
}
.chg__field + .chg__field { margin-top: .9rem; }
.chg__field h3 {
  font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-soft); margin: 0 0 .35rem;
}
.chg__was, .chg__now {
  padding: .5rem .65rem; border-radius: var(--radius-sm);
  background: var(--card); border: 1px solid var(--rule);
}
/* The old side reads as the past: quieter, and struck through only on a whole
   value that is gone — never on lines, where it would make the words she may
   need to retype harder to read, which is the one thing this view is for. */
.chg__was { color: var(--ink-soft); }
.chg__lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .25rem; }
.chg__lines li { padding-left: .8rem; text-indent: -.8rem; }
.chg__lines li::before { content: "· "; color: var(--ink-faint); }
.chg p { margin: 0; }
.chg__none { color: var(--ink-faint); font-style: italic; }

/* Stacked, each side labelled, once there isn't room for two columns. */
.chg__was::before, .chg__now::before {
  display: block; font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint); margin-bottom: .25rem;
}
.chg__was::before { content: "Previous"; }
.chg__now::before { content: "Now"; }

@media (min-width: 620px) {
  .chg__row { grid-template-columns: 1fr 1fr; }
  .chg__row--head { display: grid; }
  .chg__was::before, .chg__now::before { display: none; }
}
.gallery-tile { position: relative; }
.gallery-tile__tools {
  position: absolute; inset: auto 0 0 0; display: flex; gap: .25rem; padding: .25rem;
  background: linear-gradient(transparent, rgba(0,0,0,.6));
  opacity: 0; transition: opacity .12s;
}
.gallery-tile:hover .gallery-tile__tools, .gallery-tile:focus-within .gallery-tile__tools { opacity: 1; }
.gallery-tile__tools button {
  font: inherit; font-size: .72rem; font-weight: 600; cursor: pointer;
  border: 0; border-radius: 4px; padding: .2rem .45rem;
  /* White chips laid over the photograph, so the labels are pinned dark for the
     same reason the picker's are — see .pick__hero-btn. */
  background: rgba(255,255,255,.94); color: var(--ink);
}
.gallery-tile__badge {
  position: absolute; top: .35rem; left: .35rem; font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  background: var(--accent); color: #fff; padding: .15rem .4rem; border-radius: 999px;
}

/* ==========================================================================
   Gingham, wicker and wildflowers
   Texture is used sparingly and always as an edge or a band — never behind
   text, which has to stay quietly readable with flour on your hands.
   ========================================================================== */

/* A woven ribbon along the top of the app, like the trim on a picnic basket. */
.topbar {
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--rule);
}
/* Tall enough to show two rows of squares — at one row it just reads as a
   dashed rule, which is not the same thing at all. */
/*
 * The band of cloth under the top bar.
 *
 * It has to be woven at its own scale rather than reusing --gingham: that
 * pattern has a 20px period, and squeezing it into a shorter tile with
 * background-size cuts every square mid-stride, which is what turned this into
 * a row of vertical stripes. Two things fix it — a 8px thread that divides the
 * height exactly, and a band tall enough for TWO rows, since a single row of
 * checks just reads as a dashed rule.
 */
.topbar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -16px; height: 16px;
  background-color: var(--card);
  background-image:
    repeating-linear-gradient(90deg, var(--check) 0 8px, transparent 8px 16px),
    repeating-linear-gradient(0deg,  var(--check) 0 8px, transparent 8px 16px);
}
.topbar { position: sticky; }

.brand { white-space: nowrap; }
.brand > span[aria-hidden] { margin-right: .35rem; }

/*
 * The tablecloth. EVERY screen sits on this — previously only the page editor
 * had any texture, which made it the one screen that looked themed while the
 * rest were just a blue tint of the old design.
 *
 * Sunlight falls across it from the top-left. Reading surfaces are laid on top
 * as opaque white "paper" (.sheet), so no body text is ever set over a pattern.
 */
/*
 * Plain paper.
 *
 * The gingham used to be here, behind every screen. It meant nothing on any
 * page had a quiet ground to sit on: every card, every sheet, every dialog was
 * a patch of white fighting a pattern for attention, and the pattern was at a
 * scale too small to read as cloth anyway — closer to a moire.
 *
 * It survives where it is at a size you can actually see it: the login card's
 * band, the page-editor stage, the printed sheet. Everywhere else the ground is
 * paper, and the colour on a card is the course it belongs to.
 */
body {
  background-color: var(--paper);
  background-image:
    radial-gradient(1200px 700px at 10% -10%, rgba(242,193,78,.10), transparent 62%),
    radial-gradient(900px 520px at 94% 2%, rgba(62,124,177,.07), transparent 64%);
  background-attachment: fixed, fixed;
}

/*
 * Paper on cloth. Anything with sustained reading goes on one of these so the
 * weave never sits behind a sentence.
 */
.sheet {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 2rem 2.25rem 2.5rem;
}
@media (max-width: 640px) { .sheet { padding: 1.25rem 1.1rem 1.75rem; border-radius: 0; } }

/* The login card as a checked tablecloth with a white plate on top. */
.login-card { position: relative; overflow: hidden; }
.login-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 18px;
  background-image: var(--gingham); background-size: 12px 12px;
}
.login-card { padding-top: 3rem; }

/*
 * The course bar: full width, top edge, five pixels.
 *
 * Enough to read across a kitchen, small enough that a shelf of thirty doesn't
 * shout. It replaces a gingham flash that only appeared on hover — which on an
 * iPad is never.
 *
 * Seven courses, because eleven could not be told apart at this width: Salad
 * beside Side, Breakfast beside Bread. Two colours that look like information
 * and aren't are worse than no colour. Anything unrecognised — an older Bread
 * or Sauce, or a blank — takes the neutral, and the word under it always says
 * which.
 */
.rcard::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 5px; z-index: 1;
  background: var(--course, var(--course-none));
}
.rcard__course {
  margin: 0; font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--course, var(--ink-faint));
}

/* The binder pill on the left, whose recipe it is on the right — one line,
   using room the pill was leaving empty. */
.rcard__foot {
  margin: 0; display: flex; align-items: center; gap: .5rem;
  min-width: 0;   /* so the name gives way rather than widening the card */
}
.rcard__who {
  margin-left: auto; color: var(--ink-faint); font-size: .82rem;
  display: inline-flex; align-items: center; gap: .3rem;
  min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}

/* One slot, one height. The course sits here on a recipe and "How-to" sits
   here on a guide, and they were set at .68rem and .72rem with only one of
   them carrying a bottom margin — which nobody decided, and which left guide
   cards a couple of pixels taller than the recipe cards beside them. */
.rcard__course, .rcard__kind { min-height: 1.2rem; margin: 0 0 .1rem; }

.rcard--co-breakfast-brunch { --course: var(--co-breakfast); }
.rcard--co-appetizers       { --course: var(--co-appetizer); }
.rcard--co-soup-salad       { --course: var(--co-soupsalad); }
.rcard--co-main             { --course: var(--co-main); }
.rcard--co-side             { --course: var(--co-side); }
.rcard--co-dessert          { --course: var(--co-dessert); }
.rcard--co-drink            { --course: var(--co-drink); }
/* The catch-all takes the same slate as an unset course, on purpose: "filed
   under Other" and "not filed" carry the same amount of information. */
.rcard--co-other-uncategorized { --course: var(--course-none); }
/* A how-to is not a course, and gets a colour deliberately outside the set. */
.rcard--co-howto            { --course: var(--co-howto); }

/*
 * The occasions, as icons. Emoji rather than an icon set: there is no build
 * step and no CDN here, so a line-icon library would mean vendoring hundreds of
 * SVGs to get a Christmas tree that is still the wrong shape.
 */
.rcard__occ { margin: .1rem 0 0; display: flex; flex-wrap: wrap; gap: .25rem; }
.occ-dot {
  display: inline-grid; place-items: center;
  width: 1.4rem; height: 1.4rem; border-radius: 5px;
  background: var(--paper-deep);
  font-size: .82rem; line-height: 1;
}

/* Section headings get a small wildflower stem rather than a rule. */
.shelf-head h1.display::after,
#screen-add h1.display::after,
#screen-editor h1.display::after,
.rv__title::after,
#screen-batch h1.display::after {
  content: ""; display: block; width: 3.2rem; height: 3px; margin-top: .5rem;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--sun), var(--bloom) 55%, var(--meadow));
}

/* Empty state: a basket, not a void. Straw goes muddy against a dark page, so
   after dark it is simply a slightly deeper square of the same cloth. */
.empty { border-color: var(--rule); background: var(--wicker-wash); }

/* Method chooser tiles pick up the three flower colours in turn. */
.method:nth-child(1) .method__icon { background: var(--accent-wash); color: var(--accent-deep); }
.method:nth-child(2) .method__icon { background: #FDF3DC; color: #9A7420; }
.method:nth-child(3) .method__icon { background: #E9F3E6; color: #4A7340; }

/* Favourite hearts bloom rather than rust. */
.rcard__fav { color: var(--bloom); }
.btn--toggle[aria-pressed="true"] {
  border-color: var(--bloom); color: var(--bloom); background: #FCEFF2;
}

/* Binder states in the garden palette: straw, meadow, wildflower. The washes
   invert to the darkest reading of the same three flowers. */
.pstate--unprinted { background: var(--wicker-wash); color: #8A6B3C; }
.pstate--printed   { background: #E9F3E6; color: #4A7340; }
.pstate--stale     { background: #FCEFF2; color: #B5566B; }

/* The page-editor stage sits on a checked cloth. */
.pe__stage {
  background-color: var(--paper-deep);
  background-image: var(--gingham);
  background-size: 26px 26px;
}
.page-stage { box-shadow: 0 6px 30px rgba(31,58,95,.28); }

/* Notes panel as a scrap of paper tucked in the basket. */
.rv__notes { background: var(--wicker-wash); border: 1px solid var(--rule); }

/* Step numbers as little sunlit discs. The disc stays lemon in both themes, so
   only the digit on it has to change — and it goes darker, not lighter. */
.step-list li::before { background: var(--sun); color: #4A3A10; }

.ing-list li::before { background: var(--meadow); }

/* ── Paper surfaces: which screens are "a sheet on the cloth" ───────────── */

/* The recipe itself is the longest read in the app — it gets real paper. */
#recipe-view.rv {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 2rem 2.25rem 3rem;
  margin-bottom: 4rem;
}
#recipe-view .rv__hero { margin-inline: 0; }

/* The long entry form, likewise. */
.editor__main {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem 1.75rem .5rem;
}

/* The add-a-recipe helper panels already read as cards; give the intro air. */
#screen-add .methods { margin-top: 1.75rem; }

@media (max-width: 640px) {
  #recipe-view.rv { padding: 1.5rem 1.1rem 2.5rem; border-radius: 0; border-inline: 0; }
  .editor__main { padding: 1.25rem 1.1rem .25rem; }
}

/* The wildflower stem under a recipe title sits closer than on a page heading. */
.rv__title::after { margin-top: .4rem !important; }

/* Cards read as plates set down on the cloth. */
.rcard, .card, .panel, .method, .login-card { background: var(--card); }
.rcard { box-shadow: var(--shadow); }
.rcard:hover { box-shadow: var(--shadow-lg); }

/* The topbar is a painted strip above the cloth, not part of it. */
.topbar {
  background: color-mix(in srgb, #fff 92%, var(--accent-wash));
  backdrop-filter: blur(10px);
}

/* Shelf toolbar controls sit on the cloth, so they need solid fills. */
.toolbar input, .toolbar select, .binderbar .chip, .btn { background-color: var(--card); }
.chip--filter[aria-pressed="true"] { background-color: var(--accent); }
.btn--primary { background-color: var(--accent); }
.btn--quiet { background-color: transparent; }
/* A wash of white lifts a quiet button off the cloth in daylight; after dark
   the same wash at full strength would be a glare, so it drops to a whisper. */
.btn--quiet:hover { background-color: rgba(255,255,255,.6); }

/* ── Saved PDF booklets ────────────────────────────────────────────────── */

.pdf-result { margin-top: 1.5rem; border-color: var(--accent); }
.pdf-result h2 { font-family: var(--serif); font-size: 1.25rem; margin: 0 0 .25rem; }
.pdf-result input { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9rem; }
.pdf-list { list-style: none; padding: 0; margin: 0; }
.pdf-list li {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .6rem 0; border-bottom: 1px solid var(--rule);
}
.pdf-list li:last-child { border-bottom: 0; }
.pdf-list strong { flex: 1 1 12rem; font-weight: 600; }
.pdf-list .hint { margin: 0; }

/* ==========================================================================
   Touch first: iPad, then iPhone, then desktop.

   The app is used standing at a counter with a tablet propped up, so the
   defaults here are the touch defaults and the mouse gets the refinements —
   not the other way round.
   ========================================================================== */

/* ── 1. Real targets ───────────────────────────────────────────────────── */

/* Apple's floor is 44pt. Several controls sat just under it. */
.btn, .chip, .tpl, select, input[type="text"], input[type="url"],
input[type="search"], input[type="password"] {
  min-height: 44px;
}
.btn--small { min-height: 38px; padding: .5rem .85rem; }
/* On touch even the "small" buttons are real targets; 38px is a mouse-only
   affordance where pointing is precise. Keyed on any-pointer, so an iPad with a
   keyboard case still gets finger-sized buttons — see the note below. */
@media (any-pointer: coarse) { .btn--small { min-height: 44px; padding: .6rem 1rem; } }
.check { min-height: 44px; align-items: center; }
.check input[type="checkbox"] { width: 22px; height: 22px; }

/* Filter and toolbar rows need room between targets, not just inside them. */
.toolbar, .toolbar__filters, .binderbar__filters { gap: .6rem; }

/* Anything that opens a menu gets the native control, which iOS renders as a
   proper wheel picker — do not restyle these into something smaller. */
select { font-size: 1rem; }

/* ── 2. Hover is not a thing on touch ──────────────────────────────────── */

/*
 * The gallery's Make main / Remove buttons were revealed on :hover. On an iPad
 * that means they never appear at all — the controls were simply unreachable.
 * On touch they are always visible; the reveal is a mouse-only refinement.
 */
/*
 * `any-pointer: coarse`, not `hover: none`.
 *
 * An iPad in a Magic Keyboard — or with any trackpad or mouse paired — reports
 * `hover: hover`, so every rule below used to switch itself off on the exact
 * device this app is built for, reverting to mouse-sized targets under a finger.
 * `any-pointer: coarse` stays true whenever a finger is one of the ways in,
 * which is the question these rules are actually asking.
 */
@media (any-pointer: coarse) {
  .gallery-tile__tools { opacity: 1; position: static; background: none; padding: .4rem 0 0; }
  .gallery-tile__tools button {
    background: var(--card); border: 1px solid var(--rule); color: var(--ink);
    min-height: 44px; padding: .5rem .7rem; flex: 1;
  }
  .gallery-tile { display: flex; flex-direction: column; }
  .rcard::after { display: none; }           /* hover flash is meaningless */
  .pick__hero-btn { position: static; padding: .5rem; }
  /* 25.6px under a fingertip, and a near-miss silently drops the photo from the
     recipe — or puts a discarded one back. Worth the extra corner. */
  .pick__del { width: 44px; height: 44px; top: .15rem; right: .15rem; }
  .pick img { opacity: 1 !important; }        /* dimming reads as "broken" */
  .pick:not(.is-kept) { opacity: .5; }
}

/* Tapping a card shouldn't flash a grey box or trigger the callout menu. */
.rcard, .btn, .chip, .method, .tpl, .pick {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

/* ── 3. Safe areas (iPhone notch / home indicator, iPad rounded corners) ── */

.topbar { padding-left: max(1.25rem, env(safe-area-inset-left));
          padding-right: max(1.25rem, env(safe-area-inset-right)); }
.wrap   { padding-left: max(1.25rem, env(safe-area-inset-left));
          padding-right: max(1.25rem, env(safe-area-inset-right)); }
.selectbar { bottom: max(1.25rem, calc(env(safe-area-inset-bottom) + .5rem)); }
.pe__side, .pe__stage { padding-bottom: max(2rem, calc(env(safe-area-inset-bottom) + 1rem)); }

/* ── 4. iPad portrait (810–1024pt): the primary target ─────────────────── */

@media (min-width: 700px) and (max-width: 1080px) {
  /* Three cards across reads well on a 10.9–11" screen held upright. */
  .grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1.25rem; }

  /* The recipe's two columns are too tight at this width — stack them, which
     also matches how the printed page now works. */
  .rv__cols { grid-template-columns: 1fr; gap: 1.75rem; }

  /* Editor sidebar below the form rather than a cramped 320px rail. */
  .editor { grid-template-columns: 1fr; }
  .editor__side { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }
  .editor__side .card:first-child { grid-column: 1 / -1; }
}

/* ── 5. The page editor on a tablet ────────────────────────────────────── */

/*
 * A letter sheet is 816px wide. On iPad portrait that leaves nothing for the
 * controls, so the sheet stacks above them and is SCALED to the width there
 * is.
 *
 * It used to be set to `width: 100%` here, under a comment saying the
 * proportions stayed true. They did not: a narrowed sheet re-breaks every
 * line, so the preview quietly stopped being the page on every screen under
 * 1180px — which is most of them, including the iPad this is for. Scaling is
 * done in one place now, against the column rather than the window, and
 * `.page-stage` keeps its real dimensions at every size.
 */
@media (max-width: 1180px) {
  /* Stacked, the sheet goes first: this tab exists to show her the page, and
     burying it under four fields of controls means scrolling to find the thing
     she came to look at. */
  /* minmax(0,…), not 1fr: a bare `1fr` track has a min-content floor, and the
     sheet inside is 816px wide and does not wrap — so the column grew to the
     sheet and pushed the whole PAGE sideways instead of the sheet being
     scaled to the column. */
  .pe { grid-template-columns: minmax(0, 1fr); }
  .pe__stage { order: -1; }
  .pe__side .field { margin-bottom: 1rem; }
  /* The show/hide boxes are the one control worth keeping within reach of the
     sheet, so they run across rather than down. */
  .pe__side .field .check { display: inline-flex; margin-right: 1.25rem; }
  /* Page styles side by side rather than a tall stack that pushes the sheet
     off the bottom of the screen. */
  .templates { grid-template-columns: 1fr 1fr; }
  .pe__actions { grid-template-columns: 1fr 1fr; }
  .pe__stage { padding: 1.25rem .75rem 2rem; }
}

/* ── 6. iPhone ─────────────────────────────────────────────────────────── */

@media (max-width: 560px) {
  .grid { grid-template-columns: 1fr; }
  .toolbar__filters { display: grid; grid-template-columns: 1fr 1fr; }
  .toolbar__filters select, .toolbar__filters .btn { width: 100%; }
  .templates, .pe__actions, .editor__side { grid-template-columns: 1fr; }
  /* Two tabs side by side get too cramped for their subtitles on a phone. */
  .tabs { grid-template-columns: 1fr; }
  .tab { min-height: 52px; flex-direction: row; align-items: center; gap: .5rem; }
  .tab span { display: none; }
  .editbar__row > .btn { flex: 1 1 auto; }
  .editbar #editor-cancel { margin-left: 0; }

  /* A fixed bar of five buttons doesn't fit — let it wrap and stay reachable. */
  .selectbar {
    left: .75rem; right: .75rem; transform: none;
    border-radius: var(--radius); justify-content: space-between;
  }
  .selectbar__actions { flex: 1 1 100%; }
  .selectbar__actions .btn { flex: 1; }

  .rv__actions .btn, .editor__actions .btn { flex: 1 1 auto; }
  .modal__card { padding: 1.25rem; }
}

/* ── 7. Desktop refinements (mouse only) ───────────────────────────────── */

@media (hover: hover) and (min-width: 1181px) {
  .rcard:hover { transform: translateY(-2px); }
  .method:hover { transform: translateY(-2px); }
}

/*
 * The same colours again, as light-dark() pairs.
 *
 * The rules above now carry only the daytime value, which every browser
 * understands; this block adds the evening half where light-dark() is
 * supported. Without the guard these declarations were simply invalid on an
 * older Safari, and an invalid `color` inherits — which on a filled blue
 * button meant near-white text on light blue.
 */
@supports (color: light-dark(#000, #fff)) {
  /* After dark the accent lightens, so white letters on it stop being legible —
   the label goes near-black instead. Same trick everywhere a filled control
   appears below. */
.btn--primary { color: light-dark(#fff, #1B1917); }
  .chip[aria-pressed="true"] { color: light-dark(#fff, #1B1917); }
  /* Lift the chosen segment so its own border draws the seam, not its neighbour's. */
.themepick .btn[aria-pressed="true"] { color: light-dark(#fff, #1B1917); }
  .pick__flag { color: light-dark(#fff, #1B1917); }
  /*
 * Both of these sit ON the photograph, so their white pill stays white in both
 * themes — a dark pill over a dark holiday snap would vanish. A fixed ground
 * needs a fixed label to go with it: plain `var(--ink)` lightens after dark and
 * left near-white letters on a near-white pill, at about 1.1:1.
 */
.pick__hero-btn { color: light-dark(var(--ink), #1B1917); }
  .pick__hero-btn:hover { color: light-dark(#fff, #1B1917); }
  .pick__del { color: light-dark(var(--danger), #C1554D); }
  .scaler button[aria-pressed="true"] { color: light-dark(#fff, #1B1917); }
  /* This is the headline number on the shelf — how many pages are waiting to be
   printed — so it has to survive both themes. */
.chip__n { color: light-dark(#fff, #1B1917); }
  .pstate--printed { background: light-dark(#E8F0E8, #23301F); }
  /* The white ring stays white in both themes — it is what lifts the circle off
   the photograph — but the tick inside follows the fill it sits on. */
.rcard--picked .rcard__pick { color: light-dark(#fff, #1B1917); }
  .gallery-tile__tools button { color: light-dark(var(--ink), #1B1917); }
  .gallery-tile__badge { color: light-dark(#fff, #1B1917); }
  /* Empty state: a basket, not a void. Straw goes muddy against a dark page, so
   after dark it is simply a slightly deeper square of the same cloth. */
.empty { background: light-dark(var(--wicker-wash), var(--paper-deep)); }
  .method:nth-child(2) .method__icon { background: light-dark(#FDF3DC, #2E2718);color: light-dark(#9A7420, var(--sun)); }
  .method:nth-child(3) .method__icon { background: light-dark(#E9F3E6, #1D2A1B);color: light-dark(#4A7340, var(--meadow)); }
  .btn--toggle[aria-pressed="true"] { background: light-dark(#FCEFF2, #332226); }
  /* Binder states in the garden palette: straw, meadow, wildflower. The washes
   invert to the darkest reading of the same three flowers. */
.pstate--unprinted { color: light-dark(#8A6B3C, var(--wicker)); }
  .pstate--printed { background: light-dark(#E9F3E6, #1D2A1B);color: light-dark(#4A7340, var(--meadow)); }
  .pstate--stale { background: light-dark(#FCEFF2, #332226);color: light-dark(#B5566B, var(--bloom)); }
  /* Notes panel as a scrap of paper tucked in the basket. */
.rv__notes { background: light-dark(var(--wicker-wash), var(--paper-deep)); }
  /* Step numbers as little sunlit discs. The disc stays lemon in both themes, so
   only the digit on it has to change — and it goes darker, not lighter. */
.step-list li::before { color: light-dark(#4A3A10, #2A2110); }
  /* The topbar is a painted strip above the cloth, not part of it. */
.topbar { background: light-dark(
    color-mix(in srgb, #fff 92%, var(--accent-wash)),
    color-mix(in srgb, var(--card) 92%, transparent)); }
  /* A wash of white lifts a quiet button off the cloth in daylight; after dark
   the same wash at full strength would be a glare, so it drops to a whisper. */
.btn--quiet:hover { background-color: light-dark(rgba(255,255,255,.6), rgba(255,255,255,.06)); }
}

/* ── Occasions ──────────────────────────────────────────────────────────────
   Everything is a toggle, and every toggle carries its word. The icon alone
   would be a quiz: 🍂 is Thanksgiving to somebody who already knows and autumn
   to everybody else. The card shows icons alone because a card is a glance;
   this is the place the choice is actually made, so it spells it out.
   ────────────────────────────────────────────────────────────────────────── */

.field__legend { display: block; font-weight: 600; margin-bottom: .15rem; }
.field__legend + .hint { margin-top: 0; }

.occpick__list { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }

.occ {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .7rem .4rem .5rem;
  border: 1px solid var(--rule); border-radius: 999px;
  background: var(--card); color: var(--ink-soft);
  font: inherit; font-size: .875rem; cursor: pointer;
  /* Comfortably past 44px with the padding above, on a control there may be
     twenty of. */
  min-height: 44px;
}
.occ:hover { border-color: var(--accent); color: var(--ink); }
.occ:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }
.occ__icon { font-size: 1.05rem; line-height: 1; }

/* Chosen reads as filled-in, not merely outlined — at arm's length on a bright
   kitchen counter a border change alone is not a state change. */
.occ.is-on {
  background: var(--accent-wash); border-color: var(--accent);
  color: var(--accent-deep); font-weight: 600;
}

.occ--add { border-style: dashed; }

.occnew {
  margin-top: .6rem; padding: .7rem;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--paper-deep);
}
.occnew__row { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; }
.occnew__label {
  flex: 1 1 12rem; min-width: 0; min-height: 44px;
  padding: .45rem .6rem; font: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--card); color: var(--ink);
}
.occnew__icon {
  width: 44px; height: 44px; flex: none; cursor: pointer;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--card); font-size: 1.3rem; line-height: 1;
}
.occnew__icons {
  display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .6rem;
  max-height: 8.5rem; overflow-y: auto;
}
.occnew__pick {
  width: 40px; height: 40px; cursor: pointer; font-size: 1.15rem; line-height: 1;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: var(--card);
}
.occnew__pick:hover { border-color: var(--rule); }
.occnew__pick.is-on { border-color: var(--accent); background: var(--accent-wash); }
.occnew__pick:focus-visible, .occnew__icon:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 1px;
}

/* On the recipe page: the icon AND the word, because here there is room. */
.rv__occ { display: flex; flex-wrap: wrap; gap: .4rem; margin: .5rem 0 0; }
.rv__occ .occ { cursor: default; min-height: 0; padding: .25rem .6rem .25rem .45rem; }
.rv__occ .occ:hover { border-color: var(--rule); color: var(--ink-soft); }

/* A note on a single ingredient or step, on screen. Quieter than the line it
   belongs to and on its own row, so it reads as an aside rather than as more
   instruction. The printed equivalent is .ps__linenote in print.css. */
.line-note {
  display: block; margin-top: .15rem;
  font-size: .85rem; line-height: 1.45; color: var(--ink-faint);
  font-style: italic;
}

/* ── Whose favourite ────────────────────────────────────────────────────────
   A star belongs to a person now, so it is drawn as that person: their initial
   in the colour their profile has always carried and nothing has ever used.
   ────────────────────────────────────────────────────────────────────────── */

/* `width: max-content`, and no gap.
   Absolutely positioned, this box was shrink-to-fitting to less than the faces
   inside it — two overlapped avatars span 43px and the box measured 30px — so
   the last face hung 3px past the card and was clipped by its rounded corner.
   max-content makes the box measure what it actually holds.
   And the gap was fighting the negative margin that creates the overlap: two
   mechanisms arguing about the same spacing, which is why it looked off even
   where it wasn't clipped. The overlap alone does it. */
.rcard__fav {
  position: absolute; top: .6rem; right: .6rem;
  display: flex; width: max-content;
}
/* ── One person, as a circle ────────────────────────────────────────────────
   A photo, an emoji, or initials on their own colour. One class, three fills,
   so a face is the same size and shape wherever it appears.
   ────────────────────────────────────────────────────────────────────────── */
.av {
  width: 1.5rem; height: 1.5rem; border-radius: 999px; flex: none;
  display: grid; place-items: center; overflow: hidden;
  font-style: normal; font-size: .72rem; font-weight: 700; color: #fff;
  background: var(--ink-faint);
}
.av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av--emoji { background: var(--paper-deep); font-size: .95rem; }
.av--lg { width: 1.9rem; height: 1.9rem; font-size: .8rem; }
.av--lg.av--emoji { font-size: 1.15rem; }
.av--xl { width: 3.5rem; height: 3.5rem; font-size: 1.15rem; }
.av--xl.av--emoji { font-size: 2rem; }
/* Byline size — small enough to sit in a line of running text without becoming
   punctuation you have to read around. Fixed in rem like the others: an em
   width would resolve against this element's own reduced font-size and come out
   two thirds of what it looks like it asks for. */
.av--xs { width: 1.2rem; height: 1.2rem; font-size: .55rem; }
.av--xs.av--emoji { font-size: .8rem; }
.av.is-more { background: var(--ink-faint); font-size: .62rem; }

/* A name with its face, in a byline or on a note. Drawn only for somebody in
   the family — see personChip() in people.js.

   NOT an inline-flex box. It was, and the name inside it sat about 3px below
   the words either side: an inline-flex takes its baseline from its first flex
   item, which here is the circle, and the circle's baseline is the middle of
   two tiny capitals — so the browser dropped the whole chip to put "MO" on the
   line. Left as ordinary inline text, the name shares the line's own baseline
   and cannot drift; only the circle is nudged, which is what wants nudging. */
.person { white-space: nowrap; }
/* `middle` rather than a length, because a length shifts the element's own
   baseline — and an emoji face and an initials face have different baselines,
   so the two kinds of avatar would sit at different heights. `middle` works off
   the box instead. The nudge then centres it on the text rather than on
   baseline-plus-half-an-x-height, which leaves it sitting low. */
.person .av {
  display: inline-grid; vertical-align: middle;
  margin-right: .3em; position: relative; top: -.15em;
}

/* Overlapped, the way a row of faces reads as one group rather than a list of
   separate marks. */
.rcard__fav .av { border: 2px solid var(--card); margin-left: -.45rem; }
.rcard__fav .av:first-child { margin-left: 0; }

/* The picker. Anchored to the button, because on an iPad a panel that opens
   somewhere else is a panel you have to go and find. */
/* The anchor. Without a positioned ancestor the panel escapes to the page
   corner, which on an iPad means hunting for it. */
.favwrap { position: relative; }

.favpick {
  position: absolute; top: 100%; left: 0; z-index: 20; margin-top: .35rem;
  display: flex; flex-direction: column; gap: .1rem;
  min-width: 13rem; padding: .3rem;
  background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.favpick button {
  display: flex; align-items: center; gap: .5rem;
  text-align: left; font: inherit; font-size: .9rem;
  min-height: 44px; padding: .4rem .55rem;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--ink); cursor: pointer;
}
.favpick button:hover { background: var(--paper-deep); }
.favpick button[aria-pressed="true"] {
  background: var(--accent-wash); color: var(--accent-deep); font-weight: 600;
}
.favpick__dot {
  width: 1.5rem; height: 1.5rem; border-radius: 999px; flex: none;
  display: grid; place-items: center;
  font-size: .72rem; font-weight: 700; color: #fff;
}
.favpick__dot--any { background: var(--bloom); }

#settings-email-note.is-bad { color: var(--danger); }


/* ── The favourite control ──────────────────────────────────────────────────
   One outline, two buttons, a hairline between them: the heart toggles your own
   star, the wider half discloses whose stars are already on it. They can't be
   one button — the heart REMOVES your star, so a tap meant to find out who
   likes a recipe would take yours off.

   The heart is sized to its content and the who-half takes the rest, which at
   the unit's minimum width leaves the heart at roughly a quarter. Not a literal
   1fr/3fr: that would stretch the heart on a wide screen and squeeze it under
   the 44px touch target on a narrow one.
   ────────────────────────────────────────────────────────────────────────── */
/* The expanded list hangs below the control instead of sitting in the actions
   row, for two reasons. Left in the flow it stretched every sibling button to
   its height — the row is a flex row, so "Cook along" grew into a tall slab —
   and it shoved the whole recipe down and back up each time you peeked. Same
   shape as the favourites filter's panel, which is the other list-under-a-
   button in the app. */
.favsplit-wrap { position: relative; display: inline-flex; }
.favsplit-wrap .likedby__list {
  position: absolute; top: 100%; left: 0; z-index: 20;
  min-width: 100%; box-shadow: var(--shadow-lg);
}

.favsplit {
  display: inline-flex; align-items: stretch; min-width: 13.5rem; max-width: 100%;
  border: 1px solid var(--rule); border-radius: 999px;
  background: var(--card); overflow: hidden;
}
/* Nothing starred yet, so there is no second half and no minimum to hold. */
.favsplit--solo { min-width: 0; }

/* No `border: 0` shorthand here. It used to live on `.favsplit button`, which at
   (0,1,1) outranked `.favsplit__who` at (0,1,0) and quietly erased the divider
   the whole control is built around — the line simply never drew. Each half now
   states its own border, so there is nothing to win. */
.favsplit button {
  font: inherit; color: var(--ink); background: transparent;
  min-height: 44px; cursor: pointer;
  display: inline-flex; align-items: center; gap: .45rem;
}
.favsplit button:hover { background: var(--paper-deep); }
/* Inset, because an outline on a child of an `overflow: hidden` pill is clipped
   on the curve and reads as a broken edge. */
.favsplit button:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }

.favsplit__heart {
  flex: 0 0 auto; justify-content: center; padding: 0 1.05rem;
  border: 0;
}
.favsplit__heart[aria-pressed="true"] { background: var(--accent-wash); color: var(--bloom); }
.favsplit__heart[aria-pressed="true"] .ico { fill: currentColor; }
.favsplit__heart:disabled { opacity: .55; cursor: default; }

.favsplit__who {
  flex: 1 1 auto; min-width: 0; padding: 0 .85rem;
  border: 0; border-left: 1px solid var(--rule);
  font-size: .875rem; color: var(--ink-soft); text-align: left;
}
.favsplit__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Overlapped, so a row of faces reads as one group rather than a list of marks.
   The ring is the pill's own colour, which is what separates each face from the
   one behind it — but on a light theme that ring is white on a white pill, so a
   pale face (an emoji sits on --paper-deep) dissolved into the background and
   the cluster came out a smudge. The hairline gives every face an edge on any
   ground it lands on. */
.likedby__faces { display: flex; }
.likedby__faces .av {
  border: 2px solid var(--card); margin-left: -.45rem;
  box-shadow: 0 0 0 1px var(--rule);
}
.likedby__faces .av:first-child { margin-left: 0; }

.likedby__list {
  list-style: none; margin: .45rem 0 0; padding: .4rem;
  display: flex; flex-direction: column; gap: .1rem;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--card); max-width: 18rem;
}
.likedby__list li { display: flex; align-items: center; gap: .5rem; padding: .3rem .35rem; font-size: .9rem; }

/* ── Choosing a face ────────────────────────────────────────────────────── */
.avpick { display: flex; align-items: center; gap: .9rem; margin-top: .5rem; }
.avpick__acts { display: flex; flex-wrap: wrap; gap: .4rem; }
.avpick__grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: .3rem; margin-top: .7rem;
  max-height: 11rem; overflow-y: auto;
}
.avpick__one {
  width: 100%; aspect-ratio: 1; min-height: 44px;
  font-size: 1.3rem; line-height: 1; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: var(--paper-deep);
}
.avpick__one:hover { border-color: var(--rule); }
.avpick__one[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-wash); }
.avpick__one:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }

/* None of this is binder business. Who likes a dish is a fact about the family,
   not about the recipe, and the printed sheet says only what to cook. */
@media print { .favsplit, .favsplit-wrap, .likedby__list, .person .av { display: none !important; } }
