:root {
  --ink: #203027;
  --ink-soft: #5d6a61;
  --forest: #315c3a;
  --forest-dark: #23462b;
  --sage: #8da27f;
  --sage-soft: #edf2e9;
  --cream: #f7f5ef;
  --paper: #fffefb;
  --line: #dfe4dc;
  --danger: #a63f3f;
  --danger-soft: #fff2f1;
  --shadow-sm: 0 1px 2px rgba(31, 48, 39, 0.05);
  --shadow-md: 0 12px 32px rgba(31, 48, 39, 0.08);
  --radius-sm: 0.65rem;
  --radius-md: 0.9rem;
  --radius-lg: 1.25rem;
  --page-max: 1180px;
}

/* Foundation */
* {
  box-sizing: border-box;
}

html {
  color-scheme: light;
}

/* One keyboard focus ring for the whole site, copied from the one
   .entry-method already had. Before this, most of the app fell back to the
   browser's default ring - which differs per browser, and which several rules
   here had already turned off - so tabbing through the library moved focus
   between things that looked identical.

   :focus-visible rather than :focus, so a mouse press does not leave a ring
   behind on a button somebody has already finished with. Text fields are
   deliberately not in this list: they have their own focus treatment (a green
   border and a soft glow, further down) and would otherwise get both.

   The 2px offset is what keeps it legible on a filled button - a forest
   outline drawn on a forest background is not an outline, so it is drawn just
   outside the button instead of on it. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

body {
  margin: 0;
  color: var(--ink);
  background:
    radial-gradient(circle at 12% 0%, rgba(141, 162, 127, 0.1), transparent 26rem),
    var(--cream);
  font-family: Inter, Aptos, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 1rem;
  line-height: 1.5;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1,
h2,
h3 {
  color: var(--ink);
  line-height: 1.15;
  letter-spacing: -0.025em;
}

h1 {
  margin-bottom: 0.75rem;
  font-size: clamp(2rem, 4vw, 3rem);
}

h2 {
  font-size: 1.35rem;
}

a {
  color: var(--forest);
  text-underline-offset: 0.18em;
}

a:hover {
  color: var(--forest-dark);
}

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

.eyebrow {
  margin-bottom: 0.35rem;
  color: var(--forest);
  font-size: 0.72rem;
  font-weight: 750;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

/* Shared header / navigation. /Display intentionally does not use this layout. */
.site-header {
  position: sticky;
  z-index: 20;
  top: 0;
  background: rgba(255, 254, 251, 0.94);
  border-bottom: 1px solid rgba(49, 92, 58, 0.12);
  box-shadow: var(--shadow-sm);
  backdrop-filter: blur(12px);
}

/* Header and main read the same token, so the brand always starts on the same
   x-position as the page heading. It used to be pinned to 1180px while a wide
   page ran to 1560px, which is why the logo sat inset from the content. */
.site-nav {
  display: flex;
  align-items: center;
  width: min(100% - 2rem, var(--page-max));
  min-height: 4.75rem;
  margin: 0 auto;
  gap: 1.75rem;
}

/* Pushes the account action to the far right; nav links stay beside the brand
   rather than being spread across the whole width. */
.site-nav-account {
  margin-left: auto;
  max-width: none;
}

.site-brand {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  color: var(--ink);
  text-decoration: none;
}

.site-brand picture {
  display: flex;
  align-items: center;
}

.site-brand-mascot {
  width: 6.5rem;
  height: auto;
  margin-right: -0.25rem;
}

.site-brand-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.site-brand-name {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.55rem;
  font-weight: 700;
  letter-spacing: -0.045em;
  line-height: 1;
}

.site-brand-tagline {
  margin-top: 0.22rem;
  color: var(--ink-soft);
  font-size: 0.72rem;
  letter-spacing: 0.035em;
}

.site-nav-links {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.site-nav .nav-link {
  position: relative;
  padding: 0.55rem 0.8rem;
  border-radius: 999px;
  color: var(--ink-soft);
  font-size: 0.93rem;
  font-weight: 650;
  text-decoration: none;
}

/* The pills measure 33px tall, and 30px at the narrowest breakpoint where the
   padding tightens - both under the 44px a thumb wants. Growing the pill
   itself would change the nav's whole proportion, so the hit area is grown
   instead: an invisible 44px-tall overlay, centred on the pill.

   Vertical only, deliberately. The nav bar is 68px tall, so there is nothing
   above or below to collide with, whereas the pills sit 0.4rem apart (0.1rem
   on a phone) and a wider overlay would swallow its neighbour's edge. */
.site-nav .nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

.site-nav .nav-link:hover {
  color: var(--forest);
  background: var(--sage-soft);
}

.site-nav .nav-sign-in {
  margin-left: 0.25rem;
}

/* The account menu: a <details>/<summary> disclosure, the same zero-JS
   pattern the folder sidebar's "+ Add folder" control uses, rather than a
   new JS dependency for a dropdown. */
.account-menu {
  position: relative;
}

.account-menu > summary {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  color: var(--forest-dark);
  background: var(--sage-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 750;
  letter-spacing: 0.02em;
  list-style: none;
}

.account-menu > summary::-webkit-details-marker {
  display: none;
}

.account-menu > summary:hover,
.account-menu[open] > summary {
  border-color: var(--sage);
}

.account-menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 30;
  display: flex;
  flex-direction: column;
  width: 13rem;
  padding: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

/* .account-menu-heading and .account-menu-divider are styled further down,
   grouped with the .menu-heading / .menu-divider the recipe action bar's
   menus use - they were byte-identical copies, and a divider that drifts
   between two menus on the same page is the kind of thing nobody notices
   until both are open in one screenshot. */

.account-menu-link,
.account-menu-logout {
  display: block;
  width: 100%;
  padding: 0.5rem 0.6rem;
  color: var(--ink);
  border-radius: 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
}

.account-menu-logout {
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
}

.account-menu-link:hover,
.account-menu-logout:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

/* Menus whose trigger is a button in a row of buttons rather than the nav
   bar's avatar. Same <details>/<summary> disclosure as the account menu above
   and the same ".flyout-menu" outside-click script; what these add is a
   summary that already looks like a .btn, so the shared parts live here
   rather than being written out once per menu.

   This replaced ".view-menu", the per-recipe "View" flyout, on 2026-09-05:
   choosing between exactly two readings is a segmented control's job (see
   .segmented below), and what is left needing a menu is the display picker
   and the "More" group. */
.display-menu,
.more-menu {
  position: relative;
}

.display-menu > summary,
.more-menu > summary {
  list-style: none;
  gap: 0.35rem;
}

.display-menu > summary::-webkit-details-marker,
.more-menu > summary::-webkit-details-marker {
  display: none;
}

/* Sized in em so it tracks whatever the button's own type is, and rotated
   rather than swapped for a second glyph so open/closed is one element. */
.menu-caret {
  font-size: 0.7em;
  transition: transform 140ms ease;
}

.display-menu[open] .menu-caret,
.more-menu[open] .menu-caret {
  transform: rotate(180deg);
}

/* The square that stands for a wall panel. Decorative - the trigger says
   what it means in words - so it is aria-hidden in the markup. */
.display-menu-icon {
  font-size: 0.95em;
  line-height: 1;
}

/* The trigger reports state, so its label is as long as a display's name.
   Ellipsis rather than wrap, so a household that calls a screen "The one in
   the back kitchen" cannot make this button two lines tall. */
.display-menu-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 14rem;
}

.display-menu-panel,
.more-menu-panel {
  position: absolute;
  left: 0;
  top: calc(100% + 0.5rem);
  z-index: 30;
  display: flex;
  flex-direction: column;
  padding: 0.4rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.display-menu-panel {
  width: 20rem;
}

/* Anchored to the trigger's right edge rather than its left: More is the last
   control in the action bar, so a left-anchored panel hangs off the right of
   the reading pane. It flips back to the left once .recipes-layout collapses
   to one column, where the bar wraps and puts More at the left margin
   instead - see that media query. */
.more-menu-panel {
  width: 12.5rem;
  left: auto;
  right: 0;
}

.menu-heading,
.account-menu-heading {
  padding: 0.4rem 0.6rem 0.15rem;
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.menu-divider,
.account-menu-divider {
  margin: 0.35rem 0.3rem;
  border-top: 1px solid var(--line);
}

/* One row of a menu, whether it is a link or a form's submit button. The
   min-height is the point: these are tapped on a phone, and a 0.9rem line of
   text in 0.5rem of padding is a 33px target. */
.menu-link {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.5rem 0.6rem;
  color: var(--ink);
  border-radius: 0.5rem;
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
}

.menu-button {
  background: none;
  border: none;
  font-family: inherit;
}

.menu-link:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

.menu-button-danger {
  color: var(--danger);
}

.menu-button-danger:hover {
  color: #fff;
  background: var(--danger);
}

/* One display, as a whole-row button: the row IS the action, so there is no
   selection to make before the thing happens. Three cells rather than two
   lines of running text, so the name and what that screen is doing read as
   separate facts at a glance. */
.display-choice {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.1rem 0.75rem;
  width: 100%;
  min-height: 3.25rem;
  padding: 0.55rem 0.6rem;
  background: none;
  border: none;
  border-radius: 0.6rem;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}

.display-choice-name {
  grid-column: 1;
  color: var(--ink);
  font-size: 0.95rem;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.display-choice-state {
  grid-column: 1;
  color: var(--ink-soft);
  font-size: 0.75rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.display-choice-action {
  grid-column: 2;
  grid-row: 1 / span 2;
  color: var(--forest);
  font-size: 0.85rem;
  font-weight: 700;
  white-space: nowrap;
}

/* A hairline between screens, so a name and the line under it read as one
   row rather than two loose lines. Every row after the first: the heading is
   the panel's first child and the first screen its second, so the rule starts
   at the third. Each row is a <form> around its button, except the one
   already showing this recipe, which is a bare <div> - hence both halves. */
.display-menu-panel > :nth-child(n + 3) > .display-choice,
.display-menu-panel > .display-choice:nth-child(n + 3) {
  border-top: 1px solid var(--line);
}

button.display-choice:hover {
  background: var(--sage-soft);
}

button.display-choice:hover .display-choice-action {
  color: var(--forest-dark);
}

/* The screen already showing this recipe. Still a button because its action
   is returning that named wall to DAKboard; the fill reports the current
   state before the action text says what pressing it will do. */
.display-choice-current {
  background: var(--sage-soft);
}

/* Two options, both legible at rest, with the current one filled - the same
   argument and the same shape as .sort-group in the sidebar. The difference
   is that these are <label>s for a radio pair rather than links, because the
   choice changes what is visible on this page rather than navigating. */
.segmented {
  display: inline-flex;
  padding: 3px;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: 0.55rem;
}

.segmented-option {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Matches .btn-compact's height, so this control sits on the same baseline
     as Edit and More instead of floating in the middle of the row. */
  min-height: 2.35rem;
  margin: 0;
  padding: 0.3rem 0.85rem;
  color: var(--ink-soft);
  border-radius: 0.4rem;
  cursor: pointer;
  font-size: 0.86rem;
  font-weight: 650;
  white-space: nowrap;
}

.segmented-option:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

/* The selected option. One rule per choice rather than a loop, the same way
   .entry-method does it - CSS has no loop, and the alternative is a class
   written by script, which is the JavaScript this pattern exists to avoid. */
#recipe-view-normal:checked ~ .recipe-reader-bar .segmented-option[for="recipe-view-normal"],
#recipe-view-notecard:checked ~ .recipe-reader-bar .segmented-option[for="recipe-view-notecard"] {
  color: #fff;
  background: var(--forest);
}

#recipe-view-normal:checked ~ .recipe-reader-bar .segmented-option[for="recipe-view-normal"]:hover,
#recipe-view-notecard:checked ~ .recipe-reader-bar .segmented-option[for="recipe-view-notecard"]:hover {
  color: #fff;
  background: var(--forest-dark);
}

/* The radios themselves are off-screen, so the keyboard ring has to be drawn
   on the label that stands for them - without this, tabbing into the control
   moves focus somewhere invisible and the page looks like it stopped
   responding. Same fix .entry-method needed. */
#recipe-view-normal:focus-visible ~ .recipe-reader-bar .segmented-option[for="recipe-view-normal"],
#recipe-view-notecard:focus-visible ~ .recipe-reader-bar .segmented-option[for="recipe-view-notecard"] {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

.site-main {
  width: min(100% - 2rem, var(--page-max));
  margin: 0 auto;
  padding: clamp(1.75rem, 4vw, 3.5rem) 0 4rem;
}

/* 1180px is a reading measure for the marketing pages. The library is a
   two-pane workspace, so it opts into more room rather than squeezing a
   recipe into a column beside a folder tree. */
body.page-wide {
  --page-max: 1560px;

  /* The Recipe Library has no horizontal gesture: folders and recipes are
     tapped, and both the page and the folder tree scroll vertically. So
     sideways travel here is never anything but a layout accident leaking out
     as a loose-feeling drag under a thumb. clip rather than hidden, because
     hidden would make this a scroll container and the folder sidebar and the
     reading pane's header are both sticky. This is a floor, not the fix - the
     measured cause was .folder-tree-scroll, below. */
  overflow-x: clip;
}

.site-footer {
  color: var(--ink-soft);
  border-top: 1px solid rgba(49, 92, 58, 0.12);
  font-size: 0.82rem;
}

.site-footer > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(100% - 2rem, 1180px);
  min-height: 4.5rem;
  margin: 0 auto;
  gap: 1rem;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.35rem 1.1rem;
}

/* Forms */
form {
  display: flex;
  flex-direction: column;
  max-width: 34rem;
  gap: 0.8rem;
}

form > div {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

label {
  font-size: 0.92rem;
  font-weight: 700;
}

/* Every text-entry control the app uses, and the list is exhaustive on
   purpose: a type left off this rule does not fall back to something close,
   it falls back to the browser's own control. "url" was missing until
   2026-09-05, so the Recipe link box on /Recipes/Create rendered 21px tall
   with a 2px system border next to a 47px styled Title field - and, because
   the browser default is 13.33px, tapping it made iOS Safari zoom the page in
   to bring the text up to 16px. That zoom is the visible half of the bug and
   the one that gets reported; it is not a separate problem to solve, since
   `font: inherit` below already puts every listed control at the body's 16px
   and iOS only zooms below that. "number" was missing too, which left the
   fifteen nutrition boxes on /Recipes/Edit as system controls (Create's review
   step carries the same values as hidden inputs, so it never showed this).
   Add new types here rather than styling them where they are used. */
input[type="text"],
input[type="search"],
input[type="password"],
input[type="email"],
input[type="url"],
input[type="number"],
select,
textarea {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.65rem 0.8rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid #cbd3c9;
  border-radius: var(--radius-sm);
  outline: none;
  font: inherit;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

input[type="text"]:focus,
input[type="search"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="url"]:focus,
input[type="number"]:focus,
select:focus,
textarea:focus {
  border-color: var(--forest);
  box-shadow: 0 0 0 3px rgba(49, 92, 58, 0.13);
}

/* iOS Safari zooms the page in when it focuses a text control set below 16px,
   and it does not zoom back out afterwards - so one tap on a small box leaves
   somebody scrolled sideways through a form. Pinning every control to 16px on
   a touch screen is the only reliable way out, because the sizes that get
   there are mostly not typed on the control at all: `font: inherit` above
   means an input nested inside its <label> picks up that element's 0.92rem,
   which is how both boxes on /Recipes/Screens ended up at 14.72px. A rule set
   directly on the control beats any of that.

   (pointer: coarse) rather than a width breakpoint, for the reason the
   touch-target block already gives: this is about what is pointing at the
   screen, not how wide it is.

   This block cannot reach a control whose own rule sets a size: .ask-tool's
   box is a class plus an attribute selector, so it outranks these type
   selectors wherever they sit. Such a control needs its own coarse override
   written beside its rule - there is one, next to .ask-tool input. */
@media (pointer: coarse) {
  input[type="text"],
  input[type="search"],
  input[type="password"],
  input[type="email"],
  input[type="url"],
  input[type="number"],
  select,
  textarea {
    font-size: 1rem;
  }
}

input[type="file"] {
  max-width: 100%;
  padding: 0.7rem;
  background: var(--paper);
  border: 1px dashed #b9c4b5;
  border-radius: var(--radius-sm);
}

textarea {
  min-height: 9rem;
  resize: vertical;
}

textarea.textarea-large {
  min-height: 17rem;
}

.form-inline {
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  max-width: none;
  gap: 0.5rem;
}

.form-inline input[type="text"] {
  width: auto;
  min-width: 11rem;
}

/* The base `select` rule below sets width: 100%, which inside a row-flex
   form fights the button next to it for space and forces a wrap - the
   dropdown was landing on its own line above "Show on" instead of beside
   it. Capped rather than left to its content width, since a long display
   name (or several) shouldn't be able to reopen the same problem. */
.form-inline select {
  width: auto;
  min-width: 8rem;
  max-width: 12rem;
}

.field-hint {
  margin: 0.2rem 0 0;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  min-height: 2.75rem;
  padding: 0.62rem 1rem;
  color: #fff;
  background: var(--forest);
  border: 1px solid var(--forest);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 1px rgba(28, 55, 34, 0.08);
  cursor: pointer;
  font: inherit;
  font-size: 0.94rem;
  font-weight: 700;
  line-height: 1.1;
  text-decoration: none;
  transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

.btn:hover {
  color: #fff;
  background: var(--forest-dark);
  border-color: var(--forest-dark);
  box-shadow: 0 5px 14px rgba(35, 70, 43, 0.16);
  transform: translateY(-1px);
}

.btn-secondary {
  color: var(--forest);
  background: var(--paper);
  border-color: #aebcab;
}

.btn-secondary:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
  border-color: var(--sage);
}

.btn-quiet {
  color: var(--forest);
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

.btn-quiet:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
  border-color: transparent;
  box-shadow: none;
}

.btn-danger,
.btn-danger-quiet {
  color: var(--danger);
  background: var(--danger-soft);
  border-color: #e7b8b4;
}

.btn-danger:hover,
.btn-danger-quiet:hover {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}

.btn-compact {
  min-height: 2.35rem;
  padding: 0.48rem 0.75rem;
  font-size: 0.86rem;
}

.btn-large {
  min-height: 3.15rem;
  padding: 0.8rem 1.2rem;
  border-radius: 0.75rem;
}

.validation-summary-errors,
.field-validation-error {
  color: var(--danger);
}

.alert-warning {
  max-width: none;
  margin-bottom: 1.25rem;
  padding: 0.9rem 1rem;
  color: #6c5513;
  background: #fff8dc;
  border: 1px solid #ead88d;
  border-radius: var(--radius-md);
}

.alert-warning > :last-child {
  margin-bottom: 0;
}

/* Recipes page */
/* Workspace proportions, not marketing ones. The shared h1 runs to 48px
   against an 11.5px eyebrow - a display pairing that left "Your collection"
   looking stranded above a dense two-pane tool, and spent ~90px of vertical
   space before the panes even start. Scaled to roughly 30px, the eyebrow
   reads as its kicker instead of an orphan, and the sidebar gets the height
   back. Scoped to this page so the marketing pages keep their big type. */
.recipes-page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 1.35rem;
  gap: 2rem;
}

.recipes-page-header h1 {
  margin-bottom: 0.3rem;
  font-size: clamp(1.7rem, 2.6vw, 1.95rem);
  letter-spacing: -0.02em;
}

.recipes-page-header .eyebrow {
  margin-bottom: 0.3rem;
}

.recipes-page-header .page-intro {
  font-size: 0.95rem;
}

.page-intro {
  max-width: 42rem;
  margin-bottom: 0;
  color: var(--ink-soft);
  font-size: 1.04rem;
}

.page-actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.65rem;
}

#antiforgery-token-holder {
  display: none;
}

.recipes-layout {
  display: grid;
  grid-template-columns: minmax(13.5rem, 15.5rem) minmax(0, 1fr);
  align-items: start;
  gap: 1.5rem;
}

/* Two panes: folders-with-their-recipes, and the recipe being read. A third
   column for the recipe list was tried and removed - it left the list pane
   mostly empty while crushing recipe titles to three wrapped lines. Folding
   recipes into the tree gives the whole remaining width to the recipe. */
.recipes-layout {
  grid-template-columns: minmax(17.5rem, 21rem) minmax(0, 1fr);
}

@media (max-width: 820px) {
  .recipes-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The More panel hangs off the right of its trigger, which is right while
     the panes are side by side: the action bar is one row there and More sits
     well into the page. Once the panes stack, the reading pane starts at the
     left margin and the bar wraps, putting Edit and More on a second row at
     x=68 - and a 200px panel anchored to the trigger's right edge then starts
     at -58px and loses its first word. Measured at 390px; anchoring it left
     instead puts it at 68-268px.

     This rides on the same breakpoint as the collapse above rather than
     carrying its own number, because it is the collapse that causes it. */
  .more-menu-panel {
    left: 0;
    right: auto;
  }
}

.folder-sidebar {
  position: sticky;
  top: 6.25rem;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 7.75rem);
  padding: 1.1rem;
  overflow: hidden;
  background: rgba(255, 254, 251, 0.9);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

/* Sits directly on top of the tree, so it reads as that tree's root rather
   than as a section label. Search is above it; nothing separates it from the
   folders it heads.
   
   padding-right matches the folder rows' own, so the "+" here lands in the
   same column as every "+" in the tree below rather than a few pixels off. */
.folder-sidebar-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1rem;
  padding-right: 3px;
}

/* What you do to the tree - order it, or manage it - on its own line under
   its name, with the rule that separates the header from the tree. */
.folder-sidebar-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.45rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}

/* A segmented pair rather than one toggle that hides the alternative: both
   orders are legible at rest, and which one is active is a filled chip rather
   than something you infer from the label. */
.sort-group {
  display: inline-flex;
  padding: 2px;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: 0.55rem;
}

.sort-option {
  position: relative;
  padding: 0.16rem 0.5rem;
  color: var(--ink-soft);
  border-radius: 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
}

/* 22px chips, and the smallest targets on the page. Same overlay as the nav
   pills, and vertical-only for the same reason: the two sit side by side
   inside one 2px-padded group, so a wider box would cover its neighbour. */
.sort-option::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

.sort-option:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

.sort-option.selected {
  color: #fff;
  background: var(--forest);
}

.sort-option.selected:hover {
  color: #fff;
  background: var(--forest-dark);
}

.folder-sidebar-heading h2 {
  margin-bottom: 0;
}

.folder-manage-link {
  padding-top: 0.15rem;
  font-size: 0.82rem;
  font-weight: 700;
}

/* overflow-y on its own is what made this pan sideways on a phone: with the
   other axis left at visible, CSS computes overflow-x to auto, so the tree
   quietly became a horizontal scroller as well as a vertical one. It had
   somewhere to go, too - a closed "+ Add folder" disclosure still contributes
   its text box and button to the scroll width (measured 347 against a 344
   client width at 390px, and 277 against 274 at 320px), so a drag across the
   folder list slid it three pixels and rubber-banded at the end of them on
   iOS. Nothing in the tree is reachable by panning - folder names ellipsise
   and the counts are pinned to the right edge - so the axis is clipped rather
   than left with three pixels of accidental travel. Cost: the outer few
   pixels of the "+" control's invisible 44px touch overlay stop taking taps,
   which is the part that hangs off the row's right edge anyway. */
.folder-tree-scroll {
  min-height: 0;
  margin: 0.45rem -0.35rem 0;
  padding: 0 0.35rem 0.4rem;
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-color: #bdc9ba transparent;
  scrollbar-width: thin;
}

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

.folder-tree .folder-tree {
  margin-top: 0.12rem;
  padding-left: 0.8rem;
  border-left: 1px solid var(--line);
}

.folder-tree li + li {
  margin-top: 0.12rem;
}

.folder-tree-link {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.48rem 0.62rem;
  overflow: hidden;
  color: #405048;
  border-radius: 0.55rem;
  font-size: 0.9rem;
  line-height: 1.25;
  text-decoration: none;
  text-overflow: ellipsis;
}

/* Both spans are transparent to the pointer, which matters more than it looks.
   A folder is a drag-and-drop target, and dragleave fires - and bubbles - when
   the pointer crosses into a CHILD element. Before these spans existed the
   folder link held a bare text node, so dragleave only fired on genuinely
   leaving the folder. Adding children broke that: moving onto the folder's own
   name counted as leaving it, the highlight died, and the drop landed on a
   span instead of the link. pointer-events: none puts the link back to being
   the only thing the drag ever sees. The link itself still takes clicks. */
.folder-tree-link > .folder-name,
.folder-tree-link > .folder-count {
  pointer-events: none;
}

/* The name takes up the slack, so every count in the tree lines up against the
   right edge however short the folder names are. min-width: 0 is what lets a
   flex item shrink below its content and ellipsise - without it a long folder
   name would push the count out of the sidebar instead. */
.folder-tree-link > .folder-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* How many recipes are filed directly in a folder. A pale circle rather than a
   bare number so it reads as a tally attached to the folder rather than part
   of its name, in the same sage as a selected recipe so the sidebar keeps one
   accent colour. Folders holding none are left unbadged: the badge exists to
   show where recipes are, and a column of noughts works against that.
   min-width matching the height keeps one and two digits round; three or more
   widen it into a pill rather than clipping. */
.folder-count {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.35rem;
  height: 1.35rem;
  padding: 0 0.36rem;
  color: var(--forest-dark);
  background: var(--sage-soft);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 650;
  line-height: 1;
  vertical-align: middle;
}

/* Hovering a folder paints the row itself sage, which would swallow a sage
   badge. Paper keeps the circle legible without introducing a second colour. */
.folder-tree-link:hover .folder-count {
  background: var(--paper);
}

.folder-tree-link:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

.folder-tree-link.selected {
  color: #fff;
  background: var(--forest);
  font-weight: 700;
}

.folder-tree-link.drop-target-active {
  outline: 2px dashed var(--forest);
  outline-offset: -3px;
}

/* A folder row is the link plus its "+". The link keeps flex: 1 so the whole
   width stays clickable and stays a drag-and-drop target; the "+" appears on
   hover or keyboard focus so the tree isn't a column of buttons. */
.folder-row {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.folder-row .folder-tree-link {
  flex: 1;
  min-width: 0;
}

.folder-add-child {
  flex: none;
}

.folder-add-child > summary {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  color: var(--forest);
  border-radius: 0.45rem;
  cursor: pointer;
  font-size: 1.05rem;
  line-height: 1;
  list-style: none;

  /* Always visible, just quiet. This was hover-only at first and nobody found
     it - a control that does not exist until you happen to hover the right row
     is not discoverable, and this app has users who will never go looking. */
  color: #9aa89a;
}

.folder-add-child > summary::-webkit-details-marker {
  display: none;
}

/* The chip is 27px square. Same treatment as the nav pills above - the glyph
   keeps its size and an invisible 44px square is centred on it - except that
   this one grows in both directions, because the sidebar has room to its
   right and the control is square rather than a wide pill.

   A 44px box round a 27px chip has to take 17px from somewhere, and both
   directions were checked rather than assumed. Sideways it reaches about 9px
   into the folder link beside it - the far trailing edge of a left-aligned,
   ellipsised name, which is blank on every row and is not where anyone aims
   at a folder. Vertically it reaches about 5px into the rows above and below,
   and only into their own "+" column, never their link. On a touch screen it
   does not overlap vertically at all: the (pointer: coarse) rule below raises
   the rows to 44px there, which is where it would matter. */
.folder-add-child > summary::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* On a touch screen the tree's rows themselves become the target: a 33px
   folder link and a 32px recipe row are both tappable and both a little
   short. Raising them here rather than everywhere is deliberate - a mouse is
   precise, and 44px rows would cost the desktop sidebar a third of its
   density for nothing.

   (pointer: coarse) rather than a width breakpoint, because this is about
   what is pointing at the screen and not how wide it is - a tablet in
   landscape is wide and still a thumb. The tree's own scroll box grows to
   match further down, so the same number of folders stay in view. */
@media (pointer: coarse) {
  .folder-tree-link,
  .tree-recipe {
    min-height: 2.75rem;
  }

  .tree-recipe {
    display: flex;
    align-items: center;
  }

  /* .btn-compact is 38px, which is the whole recipe action bar and both of
     its menu triggers. It is the site's compact size and looks right on a
     desktop, so it grows only where a thumb is doing the pressing. The
     segmented control comes with it, since its own comment ties its height
     to this one. */
  .btn-compact,
  .segmented-option {
    min-height: 2.75rem;
  }

  /* The account menu's rows are 33px for the same reason - a 0.9rem line in
     0.5rem of padding. .menu-link, which the action bar's menus use, is
     already 44px everywhere. */
  .account-menu-link,
  .account-menu-logout {
    display: flex;
    align-items: center;
    min-height: 2.75rem;
  }
}

.folder-row:hover .folder-add-child > summary,
.folder-add-child > summary:focus-visible,
.folder-add-child[open] > summary {
  color: var(--forest);
  background: var(--sage-soft);
}

.folder-add-child-form {
  display: flex;
  gap: 0.35rem;
  margin: 0.35rem 0 0.5rem 0.6rem;
  max-width: none;
}

/* At the root the form has no tree row to sit under. When its disclosure is
   open the tools take a line of their own, so the field gets the sidebar's
   full width instead of being squeezed against the Manage link. */
.folder-sidebar-heading .folder-add-child[open] {
  flex: 1 0 100%;
}

.folder-sidebar-heading .folder-add-child-form {
  margin: 0.5rem 0 0;
}

.folder-add-child-form input[type="text"] {
  min-width: 0;
  flex: 1;
}

/* Recipes nested inside their open folder. Indented past the folder name so
   the hierarchy reads, and left-bordered so a long list still lines up under
   the folder it belongs to. */
.folder-recipes {
  list-style: none;
  margin: 0.15rem 0 0.5rem;
  padding: 0 0 0 0.55rem;
  border-left: 1px solid var(--line);
  margin-left: 0.62rem;
}

.tree-recipe {
  display: block;
  padding: 0.42rem 0.55rem;
  color: #4a5a50;
  border-radius: 0.5rem;
  font-size: 0.88rem;
  line-height: 1.3;
  text-decoration: none;
  cursor: grab;
}

.tree-recipe:hover {
  color: var(--forest-dark);
  background: var(--sage-soft);
}

.tree-recipe.selected {
  color: var(--forest-dark);
  background: var(--sage-soft);

  /* Both edges, so the selected recipe reads as a contained pill rather than
     as something with a left marker. The inset shadow follows the border
     radius, which is where the curved ends come from. */
  box-shadow:
    inset 2px 0 0 var(--forest),
    inset -2px 0 0 var(--forest);
  font-weight: 650;
}

.folder-empty {
  margin: 0.15rem 0 0.5rem 1.2rem;
  color: var(--ink-soft);
  font-size: 0.82rem;
}

.sidebar-search {
  display: flex;

  /* flex-direction is the load-bearing line. The global `form` rule sets
     `flex-direction: column`, and declaring `display: flex` here does not
     override a property this rule never mentions - so Find stacked under the
     field regardless of how wrapping was configured. */
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  max-width: none;
}

.sidebar-search input {
  min-width: 0;
  flex: 1 1 auto;
}

/* flex: none plus nowrap keeps the button on the same row as the field at
   every pane width - it was wrapping underneath, which is what made the
   search look like two stacked controls. */
.sidebar-search .btn {
  flex: none;
  white-space: nowrap;
}

.search-results {
  margin-top: 0.9rem;
}

.search-results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.search-results-head .eyebrow {
  margin-bottom: 0;
}

/* Reading pane. Actions live here, with the recipe they act on, which is what
   lets the tree rows be titles and nothing else. */
.recipe-reader {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.recipe-reader .display-page-embedded > h2:first-child {
  margin-top: 0;
}

/* The title gets its own full-width line, with a toolbar of actions
   underneath rather than squeezed to its right - that row now holds enough
   controls (a display picker, the view menu, Edit, Delete) that sharing a
   line with the title started wrapping unpredictably. */
.recipe-reader-bar {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.reader-title {
  margin: 0;
  font-size: clamp(1.9rem, 3.4vw, 2.6rem);
  line-height: 1.1;
}

.recipe-reader-meta {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  min-width: 0;
}

.recipe-reader-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}

.recipe-reader-actions form {
  max-width: none;
}

/* Read-aloud player - Pages/Recipes/_ReadAloud.cshtml, shared by the reading
   pane here and by /Recipes/View. Its own banded row rather than another
   button in .recipe-reader-actions above: everything in that bar is
   .btn-compact, and these are deliberately the opposite - big targets for
   someone reaching over a mixing bowl. */
.read-aloud {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.75rem 0.9rem;
  background: var(--sage-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

/* A fixed floor on the width so the label swapping between "Read aloud",
   "Pause" and "Resume" doesn't resize the button under a finger that is
   already on its way down to tap it again. */
.read-aloud-button {
  min-width: 10rem;
}

/* .btn sets display:inline-flex, and an author rule beats the browser's own
   [hidden] { display: none } no matter how specific that one looks - so
   without this, "Start over" is visible from the start and the JS toggling
   .hidden does nothing at all. */
.read-aloud [hidden] {
  display: none;
}

/* Scoped to this player rather than added to .btn globally: a disabled button
   is a state only this feature currently reaches, and a site-wide rule would
   change how every other button looks the moment one is disabled. */
.read-aloud .btn:disabled {
  color: var(--ink-soft);
  background: var(--paper);
  border-color: var(--line);
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

.read-aloud-icon {
  margin-right: 0.5rem;
  font-size: 1.05em;
  line-height: 1;
}

.read-aloud-status {
  margin: 0;
  min-width: 0;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

/* On a phone - the device this feature is for - each button takes its own
   full-width row rather than sharing one and shrinking to a thumbnail. */
@media (max-width: 700px) {
  .read-aloud-button {
    flex: 1 1 100%;
  }
}

/* The Normal/Notecard switch: two visually-hidden radios (in the markup just
   above these panels in Index.cshtml) and one rule showing whichever panel
   matches the checked one - see the comment there for why this is radios
   rather than navigation. The [data-view] attribute is part of the hide
   selector, not just ".recipe-view-panel", so this rule outweighs
   ".display-page-embedded { display: block; }" below - two same-specificity
   class selectors would otherwise be decided by file order, and that rule
   was winning, which is why the normal panel used to stay visible under the
   notecard instead of being replaced by it. */
.recipe-view-panel[data-view] {
  display: none;
}

#recipe-view-normal:checked ~ .recipe-view-panel[data-view="normal"],
#recipe-view-notecard:checked ~ .recipe-view-panel[data-view="notecard"] {
  display: block;
}

/* Lets .print-card-columns below size itself off the reading pane's actual
   width (via cqw) rather than the full browser viewport, which would
   overshoot on desktop where the folder sidebar takes real space. */
.recipe-view-panel[data-view="notecard"] {
  container-type: inline-size;
}

.notecard-preview-actions {
  max-width: clamp(15rem, 30cqw, 26rem);
  margin: 0.9rem auto 0;
  text-align: center;
}

.reader-empty {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  padding: 2.5rem 2rem;
  background: rgba(255, 254, 251, 0.7);
  border: 1px dashed var(--sage);
  border-radius: var(--radius-lg);
}

.reader-empty img {
  display: block;
  width: 5.5rem;
  height: auto;
}

.reader-empty h2 {
  margin-bottom: 0.35rem;
}

.reader-empty p {
  margin-bottom: 0.9rem;
  color: var(--ink-soft);
  max-width: 44ch;
}

.sidebar-account {
  margin: 0.7rem -0.2rem -0.25rem;
  padding: 0.8rem 0.2rem 0;
  border-top: 1px solid var(--line);
}

.sidebar-account form {
  max-width: none;
}

.recipes-main {
  min-width: 0;
}

.recipe-toolbar {
  display: grid;
  grid-template-columns: minmax(13rem, 1fr) minmax(9.5rem, auto) auto auto;
  align-items: center;
  max-width: none;
  margin-bottom: 1.6rem;
  padding: 0.75rem;
  background: rgba(255, 254, 251, 0.84);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  gap: 0.55rem;
}

.recipe-toolbar input,
.recipe-toolbar select {
  min-height: 2.55rem;
}

.recipe-list-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 0.85rem;
  padding: 0 0.15rem;
  gap: 1rem;
}

.recipe-list-heading h2 {
  margin-bottom: 0;
  font-size: 1.45rem;
}

.recipe-count {
  flex: 0 0 auto;
  padding: 0.35rem 0.65rem;
  color: var(--ink-soft);
  background: rgba(255, 254, 251, 0.7);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
}

.recipe-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  gap: 0.7rem;
}

.recipe-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 4.65rem;
  padding: 0.85rem 0.9rem 0.85rem 1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  gap: 1rem;
  transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

.recipe-row:hover {
  border-color: #cbd5c8;
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.recipe-row.is-active {
  background: linear-gradient(90deg, #f1f6ed, var(--paper) 56%);
  border-color: #9bb394;
  box-shadow: inset 4px 0 0 var(--forest), var(--shadow-sm);
}

.recipe-row-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 0.35rem;
}

.recipe-row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.recipe-row-actions form {
  max-width: none;
}

.tag-active {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  color: var(--forest);
  font-size: 0.76rem;
  font-weight: 750;
  gap: 0.38rem;
}

.tag-active span {
  width: 0.48rem;
  height: 0.48rem;
  background: #5c965c;
  border-radius: 50%;
  box-shadow: 0 0 0 3px rgba(92, 150, 92, 0.14);
}

.tag-variation {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  padding: 0.1rem 0.5rem;
  color: var(--forest-dark);
  background: var(--sage-soft);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 750;
  letter-spacing: 0.02em;
}

/* The story/provenance block: whose recipe it is, the occasion, the memory.
   Styled as a quiet aside rather than another headed section, so it reads
   as a note from Sue instead of a fifth field to work through. The left rule
   and italic face are doing that job - it is the only italic body text in
   the app, which is what makes it register as a different kind of writing. */
.recipe-story {
  margin: 0 0 1.5rem;
  padding: 0.85rem 0 0.85rem 1.1rem;
  border-left: 3px solid var(--sage);
  background: linear-gradient(90deg, var(--sage-soft), transparent 80%);
}

.recipe-story p:last-child {
  margin-bottom: 0;
  color: var(--ink);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.05rem;
  font-style: italic;
  line-height: 1.6;
  /* The textarea keeps her line breaks; honouring them is the difference
     between a paragraph and a note. */
  white-space: pre-line;
}

.recipe-story-label {
  margin: 0 0 0.3rem;
  color: var(--forest);
  font-size: 0.72rem;
  font-weight: 750;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* "Based on X" / "Variations of this recipe", on the reading pane and on
   /Recipes/View. Navigation between recipes, so it sits visibly apart from
   the recipe body above it. */
.recipe-lineage-panel {
  margin-top: 1.5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}

.recipe-lineage {
  margin: 0 0 0.4rem;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.recipe-lineage-list {
  margin: 0;
  padding-left: 1.2rem;
}

.recipe-lineage-list li {
  margin-bottom: 0.25rem;
}

.recipe-row[draggable="true"] {
  cursor: grab;
}

.recipe-row[draggable="true"]:active {
  cursor: grabbing;
}

.recipe-empty-state {
  display: flex;
  align-items: center;
  min-height: 12rem;
  padding: 1.5rem;
  background: rgba(255, 254, 251, 0.75);
  border: 1px dashed #bdc8ba;
  border-radius: var(--radius-lg);
  gap: 1.25rem;
}

.recipe-empty-state picture {
  flex: 0 0 auto;
  display: flex;
}

.recipe-empty-state img {
  width: 7rem;
  height: 7rem;
  object-fit: contain;
}

.recipe-empty-state h3 {
  margin-bottom: 0.45rem;
  font-size: 1.15rem;
}

.recipe-empty-state p {
  max-width: 30rem;
  margin-bottom: 0;
  color: var(--ink-soft);
}

/* Public landing page */
.landing-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(22rem, 0.92fr);
  align-items: center;
  min-height: 35rem;
  padding: 1.5rem 0 4.5rem;
  gap: clamp(2rem, 6vw, 5rem);
}

.landing-hero-copy h1 {
  max-width: 12ch;
  margin-bottom: 1.2rem;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(3.15rem, 7vw, 5.4rem);
  font-weight: 700;
  letter-spacing: -0.055em;
}

.landing-lede {
  max-width: 41rem;
  margin-bottom: 1.5rem;
  color: var(--ink-soft);
  font-size: clamp(1.08rem, 2vw, 1.28rem);
  line-height: 1.65;
}

.landing-actions {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
  gap: 0.7rem;
}

.landing-highlights {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  color: #4d5d53;
  font-size: 0.84rem;
  font-weight: 650;
  list-style: none;
  gap: 0.65rem 1.15rem;
}

.landing-highlights li {
  display: inline-flex;
  align-items: center;
  gap: 0.42rem;
}

.landing-highlights li::before {
  width: 0.5rem;
  height: 0.5rem;
  background: var(--sage);
  border-radius: 50%;
  box-shadow: 0 0 0 3px rgba(141, 162, 127, 0.15);
  content: "";
}

.landing-portrait {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 31rem;
  padding: 1.75rem 1.5rem 0;
  overflow: hidden;
  background:
    linear-gradient(145deg, rgba(255, 254, 251, 0.94), rgba(230, 238, 225, 0.95)),
    var(--sage-soft);
  border: 1px solid #d6dfd2;
  border-radius: 3rem 3rem 1.4rem 1.4rem;
  box-shadow: 0 28px 70px rgba(36, 70, 43, 0.14);
}

.landing-portrait::before {
  position: absolute;
  z-index: -1;
  top: 2rem;
  right: 1.5rem;
  width: 9rem;
  height: 9rem;
  border: 1px solid rgba(49, 92, 58, 0.13);
  border-radius: 50%;
  content: "";
}

.landing-portrait-glow {
  position: absolute;
  z-index: -1;
  top: 7rem;
  left: 50%;
  width: 22rem;
  height: 22rem;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 50%;
  filter: blur(1px);
  transform: translateX(-50%);
}

.landing-portrait picture {
  display: flex;
  align-items: flex-end;
  width: min(100%, 31rem);
}

.landing-portrait img {
  display: block;
  width: 100%;
  height: auto;
}

.landing-portrait-note {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  left: 1rem;
  display: flex;
  flex-direction: column;
  padding: 0.9rem 1rem;
  color: var(--ink);
  background: rgba(255, 254, 251, 0.91);
  border: 1px solid rgba(49, 92, 58, 0.14);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(9px);
  gap: 0.12rem;
}

.landing-portrait-note strong {
  font-size: 0.92rem;
}

.landing-portrait-note span {
  color: var(--ink-soft);
  font-size: 0.78rem;
}

.landing-section {
  padding: clamp(4rem, 8vw, 6.5rem) 0;
  border-top: 1px solid rgba(49, 92, 58, 0.12);
}

.landing-section-heading {
  max-width: 43rem;
  margin-bottom: 2rem;
}

.landing-section-heading h2,
.landing-feature-card-primary h2,
.landing-guide-callout h2 {
  margin-bottom: 0.75rem;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2rem, 4vw, 3.2rem);
  letter-spacing: -0.045em;
}

.landing-section-heading > p:last-child,
.landing-feature-card p,
.landing-guide-callout p {
  color: var(--ink-soft);
}

.how-it-works-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
  gap: 1rem;
}

.how-it-works-grid li {
  min-height: 15rem;
  padding: 1.4rem;
  background: rgba(255, 254, 251, 0.76);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: 2.5rem;
  color: var(--forest);
  background: var(--sage-soft);
  border: 1px solid #cfdbca;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 800;
}

.how-it-works-grid h3 {
  margin-bottom: 0.55rem;
  font-size: 1.15rem;
}

.how-it-works-grid p {
  margin-bottom: 0;
  color: var(--ink-soft);
}

.landing-feature-grid {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  padding-bottom: clamp(4rem, 8vw, 6.5rem);
  gap: 1rem;
}

.landing-feature-card {
  min-height: 14rem;
  padding: clamp(1.5rem, 3vw, 2.4rem);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.landing-feature-card-primary {
  grid-row: span 2;
  display: flex;
  justify-content: center;
  flex-direction: column;
  min-height: 29rem;
  color: #fff;
  background:
    radial-gradient(circle at 85% 10%, rgba(255, 255, 255, 0.16), transparent 14rem),
    var(--forest);
  border-color: var(--forest);
}

.landing-feature-card-primary h2,
.landing-feature-card-primary .eyebrow {
  color: #fff;
}

.landing-feature-card-primary p {
  color: rgba(255, 255, 255, 0.78);
}

.landing-feature-card-primary a {
  width: fit-content;
  margin-top: 0.6rem;
  color: #fff;
  font-weight: 750;
}

.landing-feature-card h3 {
  margin-bottom: 0.6rem;
}

.landing-feature-card p:last-child {
  margin-bottom: 0;
}

.landing-guide-callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 2rem;
  padding: clamp(1.5rem, 4vw, 2.75rem);
  background: #ece7dc;
  border: 1px solid #ddd5c6;
  border-radius: var(--radius-lg);
  gap: 2rem;
}

.landing-guide-callout h2 {
  margin-bottom: 0.45rem;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
}

.landing-guide-callout p:last-child {
  margin-bottom: 0;
}

/* Public guide */
.guide-hero {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 1.5rem 0 3rem;
  border-bottom: 1px solid var(--line);
  gap: 2rem;
}

.guide-hero h1 {
  max-width: 14ch;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  letter-spacing: -0.05em;
}

.guide-layout {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  align-items: start;
  padding: 3rem 0;
  gap: clamp(2rem, 6vw, 5rem);
}

.guide-nav {
  position: sticky;
  top: 6.5rem;
  display: flex;
  flex-direction: column;
  padding: 1rem;
  background: rgba(255, 254, 251, 0.78);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  gap: 0.2rem;
}

.guide-nav a {
  padding: 0.45rem 0.55rem;
  color: var(--ink-soft);
  border-radius: 0.5rem;
  font-size: 0.86rem;
  font-weight: 650;
  text-decoration: none;
}

.guide-nav a:hover {
  color: var(--forest);
  background: var(--sage-soft);
}

.guide-content {
  max-width: 48rem;
}

.guide-section {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  padding: 0 0 3rem;
  scroll-margin-top: 7rem;
  gap: 1rem;
}

.guide-section + .guide-section {
  padding-top: 3rem;
  border-top: 1px solid var(--line);
}

.guide-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  color: var(--forest);
  background: var(--sage-soft);
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 800;
}

.guide-section h2 {
  margin: 0.45rem 0 0.8rem;
}

.guide-section p {
  color: var(--ink-soft);
}

.guide-note {
  /* Explicit, because this note lives inside a <form> on /Recipes/BulkImport
     and "form > div" turns every direct child into a flex column - which
     broke its one sentence into three lines, one of them just the link. */
  display: block;
  padding: 0.9rem 1rem;
  color: #5d512f;
  background: #f8f1dd;
  border: 1px solid #eadba8;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}

.guide-note strong {
  color: #493f25;
}

/* Privacy / Terms / Contact. One readable column - these are documents to be
   read top to bottom, not pages with a layout. */
.legal-hero {
  max-width: 44rem;
  padding: 1.5rem 0 2rem;
  border-bottom: 1px solid var(--line);
}

.legal-hero h1 {
  font-family: Georgia, "Times New Roman", serif;
  letter-spacing: -0.04em;
}

.legal-updated {
  margin: 1rem 0 0;
  color: var(--ink-soft);
  font-size: 0.82rem;
}

.legal-page {
  max-width: 44rem;
  padding: 2.5rem 0 1rem;
}

.legal-page section + section {
  padding-top: 2rem;
  border-top: 1px solid var(--line);
  margin-top: 2rem;
}

.legal-page section {
  scroll-margin-top: 7rem;
}

.legal-page h2 {
  margin-bottom: 0.8rem;
  font-size: 1.25rem;
}

.legal-page p,
.legal-page li {
  color: var(--ink-soft);
}

.legal-page ul {
  margin: 0;
  padding-left: 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.legal-page strong {
  color: var(--ink);
}

/* A paragraph closing out a list needs its own breathing room - .legal-page ul
   has no bottom margin, and p's top margin is zeroed globally, so without this
   the two sit flush against each other. */
.legal-page ul + p {
  margin-top: 1rem;
}

.contact-address {
  font-size: 1.2rem;
  font-weight: 650;
}

/* Focused recipe-creation pages */
.task-page-header {
  max-width: 48rem;
  margin: 0 auto 1.5rem;
}

.back-link {
  display: inline-flex;
  margin-bottom: 1.7rem;
  color: var(--ink-soft);
  font-size: 0.86rem;
  font-weight: 700;
  text-decoration: none;
  gap: 0.35rem;
}

.back-link:hover {
  color: var(--forest);
}

/* "How are you adding it?" - the three-way chooser at the top of
   /Recipes/Create, and the one input it reveals.

   Three visually-hidden radios drive it, with one rule per panel: the same
   zero-JavaScript pattern as the Normal/Notecard switch further up this file.
   See Create.cshtml for why the server never reads the radios.

   This replaced .create-mode-switch, a two-tab nav between Create and
   BulkImport that sat directly above the form. Two rows of choices asking two
   different questions - which kind of source, and how many recipes - in the
   same visual language is how a page ends up offering four options for adding
   one recipe. Bulk import is .entry-alt at the bottom now. */
.entry-method {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding: 0.35rem;
  background: #e9ede6;
  border: 1px solid #d7ddd4;
  border-radius: var(--radius-md);
  gap: 0.35rem;
}

.entry-method label {
  display: flex;
  flex-direction: column;
  /* min-height, not padding alone: these are the primary control on the page
     and are tapped on a phone, so the hit area is held above the 44px target
     even though the type inside them is small. */
  justify-content: center;
  min-height: 3.1rem;
  margin: 0;
  padding: 0.6rem 0.8rem;
  color: var(--ink-soft);
  font-weight: 400;
  border: 1px solid transparent;
  border-radius: 0.65rem;
  cursor: pointer;
}

.entry-method label:hover {
  color: var(--forest);
  background: rgba(255, 254, 251, 0.55);
}

.entry-method strong {
  font-size: 0.9rem;
}

.entry-method span {
  margin-top: 0.08rem;
  color: var(--ink-soft);
  font-size: 0.76rem;
}

/* The selected card. One rule per choice rather than a loop - there are three
   of them and CSS has no loop; the alternative is a class written by script,
   which is the JavaScript this pattern exists to avoid. */
#entry-photos:checked ~ .entry-method label[for="entry-photos"],
#entry-text:checked ~ .entry-method label[for="entry-text"],
#entry-link:checked ~ .entry-method label[for="entry-link"] {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
}

/* The radio itself is off-screen, so the keyboard ring has to be drawn on the
   card it stands for - without this, tabbing to the chooser moves focus
   somewhere invisible and the page looks like it stopped responding. */
#entry-photos:focus-visible ~ .entry-method label[for="entry-photos"],
#entry-text:focus-visible ~ .entry-method label[for="entry-text"],
#entry-link:focus-visible ~ .entry-method label[for="entry-link"] {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

.entry-panel {
  display: none;
}

/* flex column, not block: this rule outranks the "form > div" that normally
   stacks a label over its input, so it has to restate it or the label sits on
   the same line as the field. */
#entry-photos:checked ~ .entry-panel[data-method="photos"],
#entry-text:checked ~ .entry-panel[data-method="text"],
#entry-link:checked ~ .entry-panel[data-method="link"] {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* The optional title, folded away. Quieter than .original-scan's disclosure
   because this one is genuinely skippable - the parse fills it in, and the
   review screen is where a title actually gets corrected. */
.entry-optional > summary {
  color: var(--ink-soft);
  font-size: 0.88rem;
  cursor: pointer;
}

.entry-optional > summary:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

.entry-optional > div {
  margin-top: 0.75rem;
}

/* Bulk import, as a footnote to the form rather than a tab beside it: it is
   the rarer job, and it is not a different kind of recipe - only a different
   number of them. */
.entry-alt {
  max-width: 48rem;
  margin: 1rem auto 0;
  color: var(--ink-soft);
  font-size: 0.88rem;
  text-align: center;
}

.task-card {
  max-width: 48rem;
  margin: 0 auto;
  padding: clamp(1.25rem, 4vw, 2.25rem);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.recipe-entry-form {
  max-width: none;
  gap: 1.25rem;
}

.label-optional {
  margin-left: 0.3rem;
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 600;
}

.form-divider {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.5rem;
  margin: 0.2rem 0;
  color: var(--ink-soft);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
}

.form-divider::before {
  position: absolute;
  right: 0;
  left: 0;
  height: 1px;
  background: var(--line);
  content: "";
}

.form-divider span {
  position: relative;
  padding: 0 0.75rem;
  background: var(--paper);
}

.form-field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.9rem;
}

.form-actions {
  display: flex;
  align-items: center;
  flex-direction: row;
  flex-wrap: wrap;
  padding-top: 0.5rem;
  gap: 0.55rem;
}

/* Passphrase sign-in */
.auth-page {
  min-height: 100vh;
  padding: clamp(1rem, 4vw, 3rem);
}

.auth-shell {
  display: grid;
  grid-template-columns: minmax(20rem, 0.9fr) minmax(24rem, 1.1fr);
  width: min(100%, 66rem);
  min-height: min(46rem, calc(100vh - 6rem));
  margin: 0 auto;
  overflow: hidden;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2rem;
  box-shadow: 0 30px 80px rgba(31, 48, 39, 0.14);
}

.auth-illustration {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 38rem;
  padding: 1.5rem 1.5rem 0;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 32%, rgba(255, 255, 255, 0.82), transparent 13rem),
    #e3ebdf;
}

.auth-illustration picture {
  display: flex;
  align-items: flex-end;
  width: min(100%, 31rem);
}

.auth-illustration img {
  display: block;
  width: 100%;
  height: auto;
}

.auth-illustration > div {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  left: 1rem;
  padding: 1rem;
  background: rgba(255, 254, 251, 0.9);
  border: 1px solid rgba(49, 92, 58, 0.13);
  border-radius: var(--radius-md);
  backdrop-filter: blur(9px);
}

.auth-illustration h2 {
  max-width: 18ch;
  margin-bottom: 0;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.6rem;
}

.auth-card {
  display: flex;
  justify-content: center;
  flex-direction: column;
  padding: clamp(2rem, 7vw, 5rem);
}

.auth-brand {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  margin-bottom: auto;
  color: var(--ink);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.4rem;
  font-weight: 700;
  text-decoration: none;
}

.auth-brand picture {
  display: flex;
}

.auth-brand img {
  width: 4.5rem;
  height: auto;
}

.auth-heading {
  margin: 4rem 0 1.5rem;
}

.auth-heading h1 {
  margin-bottom: 0.6rem;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2.4rem, 5vw, 3.6rem);
}

.auth-heading > p:last-child {
  max-width: 27rem;
  margin-bottom: 0;
  color: var(--ink-soft);
}

.auth-form {
  max-width: 27rem;
}

.auth-form .btn {
  width: 100%;
  margin-top: 0.25rem;
}

/* The "back to sign in" line under Forgot Password's form, which otherwise
   sits hard against a full-width button and reads as part of it. */
.auth-form + p {
  max-width: 27rem;
  margin-top: 1.1rem;
}

.auth-error {
  max-width: 27rem;
  padding: 0.7rem 0.8rem;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid #e7b8b4;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-weight: 650;
}

/* Sits directly under the sign-in button, not down beside the "back to home"
   link - somebody reads this at the moment the password they just typed was
   refused, and a reset flow nobody finds from that moment is not a reset
   flow. */
.auth-forgot-link {
  width: fit-content;
  margin-top: 1rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 650;
}

.auth-home-link {
  width: fit-content;
  margin-top: auto;
  padding-top: 3rem;
  color: var(--ink-soft);
  font-size: 0.82rem;
  font-weight: 650;
  text-decoration: none;
}

/* Display page — portrait, high-contrast, readable from a distance.

   ONE knob sizes this whole page: --display-base is the body type, and every
   length below is an em of it, so the wall scales as a unit rather than as a
   dozen clamps that drift apart. Two things feed that knob.

   The panel's SHORT edge, not its width. A wall TV turned on its side is 1080
   CSS pixels across inside a 1920-wide viewport - rotation swaps the axes but
   not the units - so every vw in here used to measure the long edge. That is
   why a 40 inch panel and a phone both landed on the same 1.45rem ceiling: the
   clamp had already been maxed out at both ends. min() of the two viewport
   units is the width of the page whichever way round the screen is hung, and
   it tracks pixel density too, so a 4K panel gets type twice as tall in pixels
   and therefore the same size on the wall.

   --display-fit is the governor, set by wwwroot/js/display-fit.js after the
   page lays out: it grows the type to fill a screen with room to spare and
   shrinks it when a long recipe would otherwise run off the bottom. A wall has
   no scrollbar and nobody to use one, so until this a recipe that did not fit
   was silently cut off mid-instruction - measured at 2330px of content in
   1840px of screen on the kitchen panel, a fifth of the recipe missing with
   nothing on screen to say so. It defaults to 1, so the page is still correct
   with the script blocked, still loading, or failing. */
.display-page {
  --display-fit: 1;
  /* max(), not clamp(): a ceiling in rem is a ceiling in DEVICE pixels, and
     that is exactly what breaks density independence. On a 4K panel the
     proportional term wants 67px and a 3.2rem cap held it to 51.2px, so the
     type came out at 1.97% of the panel against 2.17% on 1080p - the fit
     governor clawed most of it back, but only by accident. There is no
     runaway case to guard against here because the term is already a fraction
     of the SHORT edge. */
  --display-base: max(1.2rem, calc(min(100vw, 100vh) * 0.031));

  display: flex;
  justify-content: center;
  min-height: 100vh;
  padding: 0.9em;
  color: #17241b;
  background:
    radial-gradient(circle at 8% 0%, rgba(141, 162, 127, 0.22), transparent 28rem),
    linear-gradient(180deg, #f8f7f1 0%, #f1f3ec 100%);
  font-size: calc(var(--display-base) * var(--display-fit));
  line-height: 1.5;
}

/* em, so the measure grows with the type instead of stranding a 3.2rem page
   in a 58rem column. A rotated panel overrides this to 100% further down. */
.display-recipe {
  width: min(100%, 40em);
}

.display-recipe-header {
  position: relative;
  padding: 1.3em 1.4em;
  overflow: hidden;
  color: #fff;
  background:
    radial-gradient(circle at 92% 10%, rgba(255, 255, 255, 0.14), transparent 13rem),
    #315c3a;
  border: 1px solid rgba(28, 69, 37, 0.35);
  border-radius: clamp(1.2rem, 3vw, 2rem);
  box-shadow: 0 18px 50px rgba(31, 48, 39, 0.16);
}

.display-eyebrow {
  display: flex;
  align-items: center;
  margin-bottom: 0.75rem;
  color: inherit;
  font-size: 0.72em;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  gap: 0.6rem;
}

.display-eyebrow span {
  width: 0.55rem;
  height: 0.55rem;
  background: #d8e6cf;
  border-radius: 50%;
  box-shadow: 0 0 0 0.25rem rgba(216, 230, 207, 0.16);
}

.display-recipe-header h1 {
  max-width: 18ch;
  margin: 0;
  color: #fff;
  font-family: Georgia, "Times New Roman", serif;
  font-size: max(2.1rem, 2.6em);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.045em;
}

.display-recipe-sections {
  display: grid;
  margin-top: 0.7em;
  gap: 0.7em;
}

/* Pages, for a recipe too long for the panel.

   Inert by default, and that is the whole design: with no JavaScript, or on a
   recipe that fits, this is a plain block of automatic height and the page
   renders exactly as it did before pages existed. wwwroot/js/display-fit.js
   adds .is-paged and a pixel height only once it has established that the
   fitted recipe still overruns the screen, then slides the inner block between
   page tops.

   The negative margin and matching padding are there so the section cards keep
   their drop shadows: overflow:hidden clips them otherwise, and a row of cards
   with the shadow sliced off their left and right edges reads as a rendering
   fault from across a room. */
.display-pages.is-paged {
  margin-inline: -0.6em;
  padding-inline: 0.6em;
  overflow: hidden;
}

/* No transition. A page turn has to be instantaneous to read as a page turn
   rather than as a screen that is scrolling on its own, and the same content
   is often being read aloud at the same moment. */
.display-pages-inner {
  position: relative;
}

/* Previous / page / Next, under the pages and outside them, so it stays put as
   the content slides. Rendered hidden and unhidden by the script only when
   there is more than one page. */
.display-page-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 0.7em;
  gap: 0.7em;
}

.display-page-count {
  margin: 0;
  color: #4a5c4d;
  font-size: 0.85em;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Sized in em of the display's own type, like everything else on this page, so
   a 4K panel gets a physically identical button rather than a rem-pinned one
   at half the size - the rule the scaling review landed on. Big enough to hit
   with a knuckle, which is how a kitchen display is actually touched. */
.display-page-button {
  padding: 0.5em 1.1em;
  color: #fff;
  font-family: inherit;
  font-size: 0.9em;
  font-weight: 700;
  background: #315c3a;
  border: 1px solid rgba(28, 69, 37, 0.35);
  border-radius: 999px;
  box-shadow: 0 8px 26px rgba(31, 48, 39, 0.12);
  cursor: pointer;
}

.display-recipe-section {
  padding: 1em 1.15em;
  background: rgba(255, 254, 251, 0.96);
  border: 1px solid #d9e0d5;
  border-radius: clamp(1rem, 2.5vw, 1.5rem);
  box-shadow: 0 8px 26px rgba(31, 48, 39, 0.07);
}

.display-section-heading {
  display: flex;
  align-items: center;
  margin-bottom: 0.6em;
  padding-bottom: 0.4em;
  border-bottom: 2px solid #dfe7da;
  gap: 0.5em;
}

.display-section-heading > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  /* em against this element's own 0.56em type, so the disc keeps its
     proportion to the heading beside it at every panel size. */
  width: 2.5em;
  height: 2.5em;
  color: #315c3a;
  background: #e8f0e3;
  border-radius: 50%;
  font-size: 0.56em;
  font-weight: 850;
  letter-spacing: 0.04em;
}

.display-section-heading h2 {
  margin: 0;
  color: #203027;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.35em;
  letter-spacing: -0.025em;
}

.display-recipe-section ul,
.display-recipe-section ol {
  margin: 0;
}

/* Two columns on a wall panel, one on a phone - and the breakpoint is a
   column width rather than a media query, because the width that matters here
   is the element's, which on a rotated display is the viewport's SHORT edge.
   Ingredient lines are short and the list is the tallest thing on the page
   after the instructions; stacking eight of them down a 1000px column spent
   about a fifth of the screen on empty space to the right of "2 eggs". */
.display-ingredients ul {
  padding-left: 1.25em;
  columns: 13em;
  column-gap: 2.5em;
}

.display-ingredients li {
  margin-bottom: 0.35em;
  padding-left: 0.3em;
  break-inside: avoid;
}

.display-ingredients li::marker {
  color: #5f805e;
  font-size: 1.15em;
}

.display-instructions ol {
  counter-reset: display-step;
  padding: 0;
  list-style: none;
}

.display-instructions li {
  position: relative;
  min-height: 1.9em;
  margin-bottom: 0.5em;
  padding: 0 0 0.5em 2.5em;
  border-bottom: 1px solid #e3e7df;
  counter-increment: display-step;
}

.display-instructions li:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}

.display-instructions li::before {
  content: counter(display-step);
  position: absolute;
  top: -0.1rem;
  left: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* em against this element's own 0.72em type - see the heading disc. */
  width: 2.15em;
  height: 2.15em;
  color: #fff;
  background: #315c3a;
  border-radius: 50%;
  font-size: 0.72em;
  font-weight: 800;
}

.display-recipe-aside {
  background: #eef3ea;
  border-color: #d5dfd0;
}

.display-recipe-aside p {
  margin-bottom: 0;
  white-space: pre-line;
}

/* The story on the wall. Warmer than .display-recipe-aside (Notes/Nutrition,
   which are reference material read up close) and set in the same serif the
   display headings use, because across a kitchen this is the line that says
   whose recipe it is. */
.display-recipe-story {
  background: #f6f1e4;
  border-color: #e2d8c0;
}

.display-recipe-story p {
  margin-bottom: 0;
  color: #3c3323;
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  white-space: pre-line;
}

.display-empty-state {
  display: flex;
  align-items: center;
  flex-direction: column;
  margin-top: clamp(2rem, 10vh, 7rem);
  padding: clamp(2rem, 8vw, 4rem);
  text-align: center;
  background: rgba(255, 254, 251, 0.94);
  border: 1px solid #d9e0d5;
  border-radius: 2rem;
  box-shadow: 0 18px 50px rgba(31, 48, 39, 0.12);
}

.display-empty-state picture {
  display: flex;
}

.display-empty-state img {
  width: clamp(8rem, 32vw, 13rem);
  height: clamp(8rem, 32vw, 13rem);
  margin-bottom: 1.25rem;
  object-fit: contain;
}

.display-empty-state .display-eyebrow {
  color: #315c3a;
}

.display-empty-state h1 {
  margin-bottom: 0.75rem;
  color: #203027;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(2.4rem, 8vw, 4rem);
}

.display-empty-state > p:last-child {
  max-width: 31rem;
  margin-bottom: 0;
  color: #5d6a61;
}

/* The same markup read on a phone or in the library's reading pane, where the
   two rules above do not apply: nothing here is rotated, nothing is read from
   across a room, and display-fit.js never runs. Pinning the type back to what
   it was before the wall started scaling keeps that page exactly as it was -
   without this it would inherit --display-base and jump to 3.2rem on a
   desktop monitor. */
.display-page-embedded {
  display: block;
  min-height: 0;
  padding: 0;
  background: transparent;
  font-size: clamp(1.2rem, 2.4vw, 1.45rem);
  line-height: 1.55;
}

.display-page-embedded h1 {
  font-size: 2.6rem;
}

.display-page-embedded h2 {
  margin-top: 2rem;
  padding-bottom: 0.25rem;
  border-bottom: 3px solid currentColor;
  font-size: 1.8rem;
}

.display-page-embedded .recipe-metadata {
  display: flex;
  flex-wrap: wrap;
  color: var(--ink-soft);
  font-size: 1.1rem;
  gap: 0.25rem 1.5rem;
}

.display-page-embedded li {
  margin-bottom: 0.5rem;
}


/* Cooking mode: one instruction at a time, ingredients still underneath.

   The size relationship between the two is the whole design. The step is set
   far larger than anything else this app renders because it is read from
   wherever somebody happens to be standing, mid-task, at a glance; the
   ingredient list keeps the ordinary display size because it is read
   deliberately, usually from closer, and shrinking the step to make room for
   it would have given up the only thing this mode buys. */
.display-cooking {
  display: grid;
  gap: max(1rem, calc(min(100vw, 100vh) * 0.0222));
}

.display-cooking-header h1 {
  /* Smaller than the ordinary display heading. In this mode the recipe's name
     is orientation rather than the subject - the instruction is the subject -
     and the two competing at the same weight is what made the first version
     read as a title card with some text under it. */
  max-width: 24ch;
  font-size: max(1.6rem, calc(min(100vw, 100vh) * 0.0356));
}

.display-cooking-progress {
  display: flex;
  align-items: center;
  margin-top: max(0.9rem, calc(min(100vw, 100vh) * 0.0185));
  gap: max(0.75rem, calc(min(100vw, 100vh) * 0.0185));
  flex-wrap: wrap;
}

.display-cooking-dots {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* The glanceable half. Filled dots read as a proportion from further away
   than "Step 3 of 5" can be read as words. */
.display-cooking-dots span {
  width: max(0.7rem, calc(min(100vw, 100vh) * 0.0148));
  height: max(0.7rem, calc(min(100vw, 100vh) * 0.0148));
  background: rgba(255, 255, 255, 0.24);
  border-radius: 50%;
}

.display-cooking-dots span.is-done {
  background: #d8e6cf;
}

.display-cooking-count {
  margin: 0;
  color: #d8e6cf;
  font-size: 0.78em;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.display-cooking-step {
  display: flex;
  align-items: center;

  /* A floor, not a height. The panel does not collapse around a three-word
     step ("Serve over ice") and then jump to full height on the next one -
     a layout that moves under somebody's eyes every time they are told to do
     something is worse than one that wastes a little glass. */
  /* 26vh measured the LONG edge on a rotated panel, which is the same axis
     bug the base scale had. As a fraction of the short edge this is the same
     281px on the kitchen screen and now doubles on a 4K one. */
  min-height: calc(min(100vw, 100vh) * 0.26);
  padding: max(1.75rem, calc(min(100vw, 100vh) * 0.0444));
}

.display-cooking-text {
  margin: 0;

  /* The largest type in the app, and sized off the viewport rather than the
     root font so it grows with a 4K panel on a wall instead of staying at
     phone size on it. The cap keeps a long step from overflowing a portrait
     screen, where there is no scrollbar anybody is going to reach for. */
  font-size: max(2rem, calc(min(100vw, 100vh) * 0.063));
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.02em;
}

/* What is coming next, deliberately quiet: it is there to be found when
   somebody goes looking ("can I start chopping?"), not to compete with the
   step for the same glance. */
.display-cooking-next {
  display: flex;
  align-items: baseline;
  margin: 0;
  padding: 0 max(1.75rem, calc(min(100vw, 100vh) * 0.0444));
  color: #5a6f5d;
  font-size: max(1rem, calc(min(100vw, 100vh) * 0.02));
  line-height: 1.4;
  gap: 0.75rem;
}

.display-cooking-next-label {
  flex: 0 0 auto;
  color: #3f6146;
  font-size: 0.78em;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Pinned below the step for the whole of the mode. Recipes need their
   ingredients DURING the steps, not only before them, so this is the one
   thing cooking mode does not take away. */
.display-cooking-ingredients {
  background: #eef3ea;
  border-color: #d5dfd0;

  /* Sized here rather than inherited, because display-fit.js deliberately does
     not run in this mode - the step changes between polls without a reload, so
     a size fitted to step 1 would be wrong by step 4 and there would be
     nothing to re-run it on. That makes this the one list on the wall with no
     governor, so the number has to be safe by construction rather than by
     measurement of one recipe.

     It was pinned at the old 1.45rem ceiling on 2026-09-03 out of caution and
     that was too cautious by half: measured on the worst recipe in the sample
     set - sixteen ingredients, twelve steps - cooking mode used 1372px of an
     1860px panel, leaving 488px of glass empty while the list sat at 23.2px.
     0.035 of the panel's short edge puts it at 37.8px there and still lands
     inside the budget with room over.

     min() of the two viewport units rather than vw, for the reason the base
     scale gives: a quarter turn swaps the page's axes but not the units.

     The clamp is the FLOOR and the no-JavaScript fallback, not the answer.
     0.028 of the short edge is 30px on the kitchen panel, which is the largest
     fixed number that fits the worst recipe in the sample set - and measuring
     the same panel showed a plain eight-ingredient recipe could carry 56px, so
     a fixed number throws away most of the size on every ordinary recipe.
     display-fit.js overrides --cooking-ingredients with the largest value that
     actually fits THIS recipe. With the script blocked the clamp still applies
     and is still safe. */
  font-size: var(--cooking-ingredients, max(1.5rem, calc(min(100vw, 100vh) * 0.028)));
}


/* Kitchen timers, pinned above whatever else the wall is showing.

   Top of the page rather than beside the step, because this is the one thing on
   the display that is about to demand attention, and a portrait panel is read
   from the top. Hidden entirely when there are none - an empty Timers panel is
   a permanent reminder of a thing that is not happening. */
.display-timers {
  margin-bottom: clamp(0.8rem, 2vw, 1.25rem);
}

.display-timer-list {
  display: flex;
  margin: 0;
  padding: 0;
  gap: clamp(0.6rem, 1.6vw, 1rem);
  list-style: none;
  flex-wrap: wrap;
}

.display-timer {
  display: flex;
  align-items: baseline;
  padding: clamp(0.5rem, 1.4vw, 0.8rem) clamp(0.9rem, 2.4vw, 1.4rem);
  color: #17241b;
  background: rgba(255, 254, 251, 0.96);
  border: 1px solid #d9e0d5;
  border-radius: 999px;
  box-shadow: 0 8px 26px rgba(31, 48, 39, 0.07);
  gap: 0.6rem;
}

/* Tabular figures so the seconds do not shove the label sideways every tick -
   a countdown that jitters is read as broken. */
.display-timer-clock {
  font-size: clamp(1.3rem, 3vw, 2rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.display-timer-label {
  color: #4a5c4d;
  font-size: clamp(0.9rem, 1.8vw, 1.2rem);
}

/* Past its moment and waiting on the speakerphone. Colour only - no flashing:
   this is a kitchen wall, not an alarm panel, and the sound is what carries
   the message. */
.display-timer.is-due {
  color: #fff;
  background: #a8443a;
  border-color: #8d3830;
}

.display-timer.is-due .display-timer-label {
  color: rgba(255, 255, 255, 0.85);
}

@media (max-width: 520px) {
  .display-page {
    padding: 0.7rem;
  }

  .display-recipe-header,
  .display-recipe-section {
    border-radius: 1rem;
  }

  .display-instructions li {
    padding-left: 3rem;
  }
}

/* Responsive behavior */
@media (max-width: 920px) {
  .landing-hero {
    grid-template-columns: minmax(0, 1fr) minmax(19rem, 0.82fr);
    gap: 2rem;
  }

  .landing-hero-copy h1 {
    font-size: clamp(2.8rem, 6.5vw, 4.5rem);
  }

  .recipe-row {
    align-items: flex-start;
    flex-direction: column;
  }

  .recipe-row-actions {
    justify-content: flex-start;
    width: 100%;
  }
}

@media (max-width: 760px) {
  .site-nav {
    min-height: 4.25rem;
  }

  .site-brand-mascot {
    width: 5.25rem;
  }

  .site-brand-tagline {
    display: none;
  }

  .site-nav-links {
    gap: 0.1rem;
  }

  .site-nav .nav-link {
    padding: 0.5rem 0.55rem;
    font-size: 0.84rem;
  }

  .recipes-page-header {
    align-items: flex-start;
    flex-direction: column;
    gap: 1.1rem;
  }

  .page-actions {
    justify-content: flex-start;
  }

  .recipes-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .folder-sidebar {
    position: static;
    max-height: none;
  }

  .folder-tree-scroll {
    max-height: 10.5rem;
  }

  /* Rows are 44px on a touch screen (see the (pointer: coarse) block by
     .folder-add-child), so the old 10.5rem box would show three and a half
     folders instead of five. This buys those rows back rather than trading
     reachable taps for a shorter list. */
  @media (pointer: coarse) {
    .folder-tree-scroll {
      max-height: 14rem;
    }
  }

  .sidebar-account {
    display: none;
  }

  .landing-hero {
    grid-template-columns: minmax(0, 1fr);
    padding-top: 0.5rem;
  }

  .landing-hero-copy h1 {
    max-width: 13ch;
  }

  .landing-portrait {
    width: min(100%, 34rem);
    min-height: 28rem;
    margin: 0 auto;
  }

  .how-it-works-grid,
  .landing-feature-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .how-it-works-grid li {
    min-height: 0;
  }

  .step-number {
    margin-bottom: 1.5rem;
  }

  .landing-feature-card-primary {
    grid-row: auto;
    min-height: 22rem;
  }

  .landing-guide-callout,
  .guide-hero {
    align-items: flex-start;
    flex-direction: column;
  }

  .guide-layout {
    grid-template-columns: minmax(0, 1fr);
    padding-top: 1.5rem;
  }

  .guide-nav {
    position: static;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    width: 100%;
  }

  .guide-nav .eyebrow {
    grid-column: 1 / -1;
  }

  .auth-page {
    padding: 1rem;
  }

  .auth-shell {
    grid-template-columns: minmax(0, 1fr);
    min-height: calc(100vh - 2rem);
    border-radius: 1.4rem;
  }

  .auth-illustration {
    display: none;
  }

  .auth-card {
    min-height: calc(100vh - 2rem);
    padding: clamp(1.5rem, 8vw, 3rem);
  }
}

@media (max-width: 600px) {
  .site-nav {
    width: min(100% - 1rem, 1180px);
    gap: 0.5rem;
  }

  .site-brand-mascot {
    width: 3.5rem;
  }

  /* The mascot alone is the home link below 600px. Only the PUBLIC nav links
     were hidden at this breakpoint, so a signed-in phone still had to fit the
     brand, three pills and the avatar across 344px - and because .site-brand
     carries min-width: 0 the layout solved that by crushing the wordmark
     rather than overflowing. Measured at 360px: the brand shrank from 76px to
     46px and "SueChef" was clipped. The <a> keeps its aria-label, so the home
     link is still named for a screen reader. */
  .site-brand-copy {
    display: none;
  }

  /* Without this the flex layout answers a too-narrow header by SHRINKING the
     brand - .site-brand carries min-width: 0 - so the mascot was rendering at
     46px against its own 56px and the wordmark was clipped rather than hidden.
     The brand is now a fixed size and the pills give up padding instead. */
  .site-brand {
    flex: 0 0 auto;
  }

  .site-nav .nav-link-public {
    display: none;
  }

  .site-main {
    width: min(100% - 1.25rem, 1180px);
    padding-top: 1.5rem;
  }

  .page-actions {
    width: 100%;
  }

  .page-actions .btn {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    padding-inline: 0.6rem;
  }

  .recipe-toolbar {
    grid-template-columns: 1fr auto;
  }

  .recipe-toolbar .recipe-search {
    grid-column: 1 / -1;
  }

  .recipe-toolbar select {
    min-width: 0;
  }

  .recipe-list-heading {
    align-items: flex-start;
  }

  .recipe-row-actions {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .recipe-row-actions form,
  .recipe-row-actions .btn {
    width: 100%;
  }

  .recipe-row-actions form:first-child {
    grid-column: 1 / -1;
  }

  .recipe-empty-state {
    align-items: flex-start;
    flex-direction: column;
  }

  .site-footer > div {
    align-items: flex-start;
    flex-direction: column;
    justify-content: center;
    min-height: 5.5rem;
  }

  .landing-hero {
    padding-bottom: 3rem;
  }

  .landing-hero-copy h1 {
    font-size: clamp(2.65rem, 13vw, 3.7rem);
  }

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

  .landing-actions .btn {
    width: 100%;
  }

  .landing-highlights {
    align-items: flex-start;
    flex-direction: column;
  }

  .landing-portrait {
    min-height: 23rem;
    padding-inline: 0.75rem;
    border-radius: 2rem 2rem 1.2rem 1.2rem;
  }

  .landing-portrait-note {
    right: 0.65rem;
    bottom: 0.65rem;
    left: 0.65rem;
  }

  .landing-section {
    padding: 3.5rem 0;
  }

  .landing-guide-callout .btn {
    width: 100%;
  }

  .guide-nav {
    grid-template-columns: minmax(0, 1fr);
  }

  .guide-nav .eyebrow {
    grid-column: auto;
  }

  .guide-section {
    grid-template-columns: minmax(0, 1fr);
  }

  .guide-section h2 {
    margin-top: 0;
  }

  /* One choice per row below 600px. The three cards carry a name and a line
     of description each, which is unreadable at a third of a phone's width. */
  .entry-method {
    grid-template-columns: minmax(0, 1fr);
  }

  .form-field-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .task-card {
    padding: 1.1rem;
  }
}

/* Kitchen displays
   ---------------------------------------------------------------------------
   One card per DAKboard. Each carries more state than a typical list row -
   what it's showing, whether an Echo is linked, and the poll URL its Pi needs -
   so it's laid out as a stacked card rather than squeezed onto one line. */

.warning {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid #f0d5d3;
  border-radius: var(--radius-sm);
}

.empty-state {
  padding: 1.5rem;
  color: var(--ink-soft);
  text-align: center;
  background: var(--sage-soft);
  border-radius: var(--radius-md);
}

.screen-list {
  display: grid;
  gap: 1rem;
  margin: 0 0 1.5rem;
  padding: 0;
  list-style: none;
}

.screen-item {
  padding: 1.1rem 1.2rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.screen-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: baseline;
  justify-content: space-between;
}

.screen-name {
  font-size: 1.15rem;
  font-weight: 600;
}

/* The one thing you look for when glancing at this page, so it reads as a
   badge rather than another line of prose. */
.screen-state {
  padding: 0.2rem 0.6rem;
  font-size: 0.82rem;
  font-weight: 600;
  border-radius: 999px;
}

.screen-state.on {
  color: var(--forest-dark);
  background: var(--sage-soft);
  border: 1px solid var(--sage);
}

.screen-state.off {
  color: var(--ink-soft);
  background: var(--cream);
  border: 1px solid var(--line);
}

.screen-meta {
  margin-top: 0.25rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.screen-url-label {
  display: block;
  margin-top: 0.9rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* Monospace and selectable: this gets copied into a Pi's config, and a
   proportional font makes a 32-character key hard to check by eye. */
.screen-url {
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.45rem 0.6rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

/* The device key row: whether this screen's Pi has registered itself, and the
   one control for it. Inherited the rule shape from the Echo-linking row that
   used to sit beside it, which was removed when Alexa was retired the same
   day - it is a paired-with status on a screen card, and that is what those
   read like. */
.screen-device-key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  font-size: 0.88rem;
  border-top: 1px solid var(--line);
}

.screen-device-key .linked {
  color: var(--forest);
  font-weight: 600;
}

.screen-device-key .unlinked {
  color: var(--ink-soft);
}

.screen-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.9rem;
}

.inline-form {
  display: inline;
}

.add-screen {
  padding: 0.9rem 1.1rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.add-screen > summary {
  font-weight: 600;
  cursor: pointer;
}

.add-screen-form {
  display: grid;
  gap: 0.9rem;
  margin-top: 1rem;
  max-width: 34rem;
}

/* Kitchen display rotation
   ---------------------------------------------------------------------------
   A DAKboard panel is frequently mounted portrait while the OS still reports
   it landscape - the Pi here says 3840x2160 "normal" with no rotation set
   anywhere, because DAKboard rotates its own content in software. That
   rotation is invisible to us, so our page arrives sideways.

   Rotation is therefore a property of the screen record, delivered to this
   page as a whitelisted query parameter and applied as a class. Nothing from
   the query string reaches CSS.

   The mechanics: rotating 90 or 270 degrees swaps the viewport's axes, so the
   element must first be sized in the *opposite* units (100vh wide, 100vw
   tall) and then turned. Centring via position + translate rather than
   margins keeps it correct at any aspect ratio, which matters when the same
   markup runs on a 4K panel and a phone. */

.display-rotate-90,
.display-rotate-180,
.display-rotate-270 {
  position: absolute;
  top: 50%;
  left: 50%;
  overflow: hidden;
}

/* Axes swapped: width and height take each other's viewport unit. */
.display-rotate-90,
.display-rotate-270 {
  width: 100vh;
  height: 100vw;
}

/* 180 is the same shape, just inverted - no swap needed. */
.display-rotate-180 {
  width: 100vw;
  height: 100vh;
}

.display-rotate-90 {
  transform: translate(-50%, -50%) rotate(90deg);
}

.display-rotate-180 {
  transform: translate(-50%, -50%) rotate(180deg);
}

.display-rotate-270 {
  transform: translate(-50%, -50%) rotate(270deg);
}

/* Scrolling inside a rotated container is unusable on a wall-mounted screen
   with no input device, so the content is allowed to be tall and the page
   simply clips. Anything that needs to fit should be made to fit by type
   scale, not by asking someone with floury hands to scroll a rotated frame. */
.display-rotate-90 > .display-recipe,
.display-rotate-180 > .display-recipe,
.display-rotate-270 > .display-recipe {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.screen-rotate {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.9rem;
  color: var(--ink-soft);
  font-size: 0.88rem;
}

.screen-rotate select {
  padding: 0.35rem 0.5rem;
  font-size: 0.88rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

/* ---- Suggestions ---- */

.suggestions-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(17rem, 22rem);
  align-items: start;
  gap: 1.5rem;
}

@media (max-width: 900px) {
  .suggestions-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.suggestion-card,
.budget-card {
  padding: 1.35rem 1.5rem 1.5rem;
  background: rgba(255, 254, 251, 0.9);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.suggestion-side {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.suggestion-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.suggestion-head .eyebrow {
  margin-bottom: 0;
}

.suggestion-stamp {
  color: var(--ink-soft);
  font-size: 0.78rem;
}

.suggestion-about {
  display: flex;
  flex-direction: column;
  margin: 0.9rem 0 1rem;
  padding: 0.7rem 0.9rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  text-decoration: none;
}

.suggestion-about:hover {
  border-color: var(--sage);
  background: #fbfdf8;
}

.suggestion-about-k {
  color: var(--forest);
  font-size: 0.66rem;
  font-weight: 750;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.suggestion-about-v {
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.suggestion-body {
  margin-bottom: 0.75rem;
  font-size: 1.12rem;
  line-height: 1.62;
}

/* The "why this recipe" half, set apart because it is the evidence that the
   suggestion read the library rather than guessing. */
.suggestion-why {
  margin-bottom: 0;
  padding-left: 0.85rem;
  border-left: 2px solid var(--sage);
  color: var(--ink-soft);
  font-size: 0.92rem;
}

.suggestion-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 1.25rem;
}

.suggestion-actions form {
  max-width: none;
}

.suggestion-cost {
  margin-left: auto;
  color: var(--ink-soft);
  font-size: 0.78rem;
}

.budget-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.35rem;
}

.budget-spend {
  font-size: 1.7rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.budget-cap {
  color: var(--ink-soft);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.budget-bar {
  height: 0.55rem;
  margin: 0.8rem 0 0.6rem;
  background: #e7eae3;
  border-radius: 999px;
  overflow: hidden;
}

.budget-bar > span {
  display: block;
  height: 100%;
  background: var(--forest);
  border-radius: 999px;
}

.budget-note {
  margin-bottom: 0;
  color: var(--ink-soft);
  font-size: 0.8rem;
}

.budget-rows {
  margin: 0.9rem 0 0;
}

.budget-rows > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
}

.budget-rows dt {
  color: var(--ink-soft);
  text-transform: capitalize;
}

.budget-rows dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.budget-warn {
  margin: 1rem 0 0;
  padding: 0.7rem 0.85rem;
  background: #fdf7e8;
  border: 1px solid #e8d9ae;
  border-radius: var(--radius-sm);
  color: #6b5518;
  font-size: 0.82rem;
}

.budget-warn strong {
  color: #4d3d0f;
}

.budget-warn-stop {
  background: var(--danger-soft);
  border-color: #e7b8b4;
  color: #7d3230;
}

.budget-warn-stop strong {
  color: var(--danger);
}

.how-list {
  margin: 0.6rem 0 0;
  padding-left: 1.1rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.how-list li + li {
  margin-top: 0.6rem;
}

.how-list strong {
  color: var(--ink);
}

/* --- Display: the recipe library ---------------------------------------- */
/* Read from several feet away, often on a portrait panel, and driven mostly
   by voice. So: fewer things on screen than the /Recipes sidebar shows, much
   larger targets, and numbers big enough to say out loud. */

.display-library {
  display: grid;
  margin-top: 0.6em;
  gap: 0.6em;
}

/* The library heading is a LABEL, not the subject. On a recipe the title is
   what you came to read, so it gets 2.6em; here the folder names are what you
   came to read and "Recipe Library" only says which screen you are looking at.
   Sizing it like a recipe title spent about a quarter of the panel on the one
   line nobody needs, which on a wall is the whole complaint. */
/* A ceiling as well as a floor. The library page can be very sparse - a folder
   holding three recipes - and the fit governor answers sparseness by growing
   everything until it fills the glass, which put "Uncategorized" on the wall at
   127px. em keeps it in proportion on a full page; the rem cap stops a nearly
   empty one turning into a poster. */
.display-library-header h1 {
  max-width: none;
  font-size: clamp(1.9rem, 2em, 4.25rem);
}

.display-library-header {
  padding: 1em 1.2em;
}

.display-breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 0.6em;
  font-size: 0.72em;
  font-weight: 700;
  letter-spacing: 0.04em;
  gap: 0.5em;
}

.display-breadcrumb a {
  color: #d8e6cf;
  text-decoration: none;
  border-bottom: 1px solid rgba(216, 230, 207, 0.4);
}

.display-breadcrumb strong {
  color: #fff;
}

.display-breadcrumb span {
  color: rgba(216, 230, 207, 0.55);
}

.display-folder-list,
.display-recipe-list {
  display: grid;
  margin: 0;
  padding: 0;
  gap: 0.4em;
}

.display-folder-list {
  list-style: none;
}

/* A folder card carries how many recipes are in it, so someone standing at the
   screen can tell a full folder from an empty one before walking over to it -
   the whole reason for browsing by voice from across the room.
   Sized in em rather than rem so the count tracks the card's own clamped font
   size instead of staying phone-sized on a wall panel. */
/* The -static variants are the same rows rendered for a request that may not
   navigate - a copy of a display URL, or a display whose Pi has not enrolled a
   device key. They keep the layout and lose the link, which is the honest
   rendering: there is nothing here to tap. See Pages/Display.cshtml. */
.display-folder-list a,
.display-folder-list .display-folder-static {
  display: flex;
  align-items: center;
  gap: 0.6em;
}

.display-folder-name {
  flex: 1;
  min-width: 0;
}

.display-folder-count {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6em;
  height: 1.6em;
  padding: 0 0.4em;
  color: #23462b;

  /* Deeper than the sidebar's sage, which would vanish against the pale card
     this sits on rather than the white sidebar. */
  background: #dae6d1;
  border-radius: 999px;
  font-family: Inter, Aptos, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 0.62em;
  font-weight: 700;
  line-height: 1;
}

/* em, not a vw clamp. This was the last of the old scheme left in the file and
   it was the visible one: 3.4vw against a rotated 1920-wide viewport resolves
   past the 1.9rem ceiling, so every folder name on the kitchen panel was pinned
   at 30.4px while the heading above it grew with --display-fit. The row is the
   thing you are actually reading on this screen, so it is sized ABOVE the page
   base rather than below it, and now scales with everything else. */
.display-folder-list a,
.display-folder-list .display-folder-static,
.display-recipe-list a,
.display-recipe-list .display-recipe-static {
  display: block;
  padding: 0.5em 0.7em;
  color: #203027;
  text-decoration: none;
  background: #f4f7f1;
  border: 1px solid #dfe7da;
  border-radius: 0.5em;
  font-family: Georgia, "Times New Roman", serif;
  /* Ceiling for the same reason as the heading above: past about 46px the
     longest real folder name ("Sauces, Condiments & Preserves") stops fitting
     the column and wraps, which costs more legibility than the extra size
     buys. */
  font-size: clamp(1.35rem, 1.1em, 2.9rem);
  line-height: 1.25;
}

.display-folder-list a::before,
.display-folder-list .display-folder-static::before {
  content: "\25B8";
  margin-right: 0.5em;
  color: #6f8a63;
}

/* The counter is drawn by hand rather than left to the default list marker,
   because the marker sits outside the box and is the one thing here that has
   to be legible from across the room - it is what "the second one" refers to. */
.display-recipe-list {
  counter-reset: display-recipe;
  list-style: none;
}

.display-recipe-list li {
  counter-increment: display-recipe;
}

.display-recipe-list a,
.display-recipe-list .display-recipe-static {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
}

.display-recipe-list a::before,
.display-recipe-list .display-recipe-static::before {
  content: counter(display-recipe);
  flex: 0 0 auto;
  min-width: 1.6em;
  color: #315c3a;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 0.72em;
  font-weight: 850;
  font-variant-numeric: tabular-nums;
}

.display-library-empty {
  margin: 0;
  color: #55665b;
  font-size: 1.1em;
}

/* --- Ask about this recipe ----------------------------------------------- */
/* Sits under the recipe in the reading pane. Reached partway through cooking,
   so it stays quiet until used rather than competing with the recipe itself. */

.ask-panel {
  margin-top: 1.5rem;
  padding: 1.25rem 1.4rem 1.4rem;
  background: var(--sage-soft);
  border: 1px solid var(--line);
  border-radius: 1rem;
}

.ask-panel-head h2 {
  margin: 0;
  color: var(--ink);
  font-size: 1.15rem;
}

.ask-panel-head p {
  margin: 0.2rem 0 1rem;
  color: #5d6b60;
  font-size: 0.85rem;
}

.ask-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.6rem;
}

.ask-tool {
  flex: 0 1 auto;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 0.7rem;
}

.ask-tool > summary {
  padding: 0.5rem 0.9rem;
  color: var(--forest);
  font-size: 0.9rem;
  font-weight: 650;
  list-style: none;
  cursor: pointer;
}

.ask-tool > summary::-webkit-details-marker {
  display: none;
}

.ask-tool[open] > summary {
  border-bottom: 1px solid var(--line);
}

/* The global `form` rule sets flex-direction: column, so a row layout has to
   say so explicitly - leaving it out is what once stacked the sidebar's Find
   button under its search box. */
.ask-tool form {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  max-width: none;
  padding: 0.75rem 0.9rem;
  gap: 0.5rem;
}

.ask-tool label {
  color: #5d6b60;
  font-size: 0.85rem;
}

.ask-tool input[type="text"] {
  width: 10rem;
  margin: 0;
  padding: 0.35rem 0.55rem;
  font-size: 0.9rem;
}

/* The one control on the site deliberately set below the body size, so it is
   the one the touch floor beside the base form rules cannot reach - see the
   comment there. Without this the page zooms in just as somebody starts
   typing what they are missing, and a box sized down for a thumb would be
   charging exactly the thumb it was sized for. */
@media (pointer: coarse) {
  .ask-tool input[type="text"] {
    font-size: 1rem;
  }
}

/* No disclosure to open, so the button is the whole control and needs no
   padding of its own. */
.ask-tool-single {
  display: block;
  padding: 0;
  background: none;
  border: 0;
}

.ask-answer {
  margin-bottom: 1rem;
  padding: 1rem 1.1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 3px solid var(--forest);
  border-radius: 0.7rem;
}

.ask-answer-question {
  margin: 0 0 0.6rem;
  color: var(--forest);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.ask-answer p {
  margin: 0 0 0.5rem;
}

.ask-caution {
  margin-top: 0.75rem !important;
  padding: 0.6rem 0.75rem;
  color: #6b4a1f;
  background: #fdf6e7;
  border-radius: 0.5rem;
  font-size: 0.92rem;
}

.ask-answer form {
  max-width: none;
  margin-top: 0.75rem;
}

/* While a tool is waiting on Claude. ask-panel.js answers in place, so the
   browser's own "something is loading" - the spinner in the tab, the page
   going white - never happens and the panel has to say so itself. The tools
   fade rather than disappear so nothing below them moves while somebody is
   still looking at it. */
.ask-panel[data-busy] .ask-tools {
  opacity: 0.55;
}

.ask-panel[data-busy] button {
  cursor: progress;
}

/* Why there is no answer, said next to where the question was asked. The page
   has a warning banner for this, but it sits up beside the Library heading and
   the whole point of answering in place is that you are no longer taken
   there. */
.ask-problem {
  margin: 0 0 1rem;
  padding: 0.75rem 0.9rem;
  color: #6b4a1f;
  background: #fdf6e7;
  border: 1px solid #e6d4ad;
  border-radius: 0.7rem;
  font-size: 0.92rem;
}

/* --- What can I make ----------------------------------------------------- */

.pantry-card {
  margin-top: 1.25rem;
}

.pantry-intro {
  margin: 0 0 0.9rem;
  color: #5d6b60;
}

/* Row, explicitly - the global form rule stacks children in a column. */
.pantry-form {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: nowrap;
  max-width: none;
  gap: 0.5rem;
}

.pantry-form input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}

.pantry-hits {
  margin: 0.75rem 0 0;
  padding-left: 1.1rem;
}

.pantry-hits li {
  margin-bottom: 0.25rem;
}

.pantry-hits a {
  color: var(--forest);
  font-weight: 600;
}

/* --- Print recipe card ---------------------------------------------------
   Sized for a standard 3x5 notecard in the orientation people actually
   write on them - 5in wide, 3in tall (landscape), not 3 wide by 5 tall.
   A real recipe routinely runs longer than one 3x5 card holds - more so at
   only ~2.6in of usable height per card - and that's fine: printing spills
   onto additional physical cards through ordinary @page pagination, with
   the break-inside/break-after rules below keeping that from landing
   mid-ingredient or leaving a heading stranded at the bottom of a card. See
   docs/project-ideas.md's "Printing recipes on an index card".

   There is no way to trigger real @page layout without printing, so the
   on-screen preview is a *different* rendering of the same recipe: two
   copies of the content sit in the page (see Print.cshtml /
   Index.cshtml's notecard panel), one inside a <template> (inert - not
   part of the rendered page) that wwwroot/js/notecard-paginate.js reads to
   build actual stacked .print-card elements sized off --card-w/--card-h
   below, and one plain .print-card-print-source copy that stays
   display:none until @media print, at which point it's what's shown and
   the JS-built stack is hidden - see the media query at the bottom.
   Splitting these was necessary once the preview needed to scale with the
   screen: a card scaled down for a phone can't share the same pixel budget
   as a card printed at true 5in size, so the same element can't correctly
   serve both. notecard-paginate.js's own comment covers why this is
   JS-driven measurement rather than a second database table of
   pre-chunked text - the short version is that pagination depends on real
   font metrics, which only the browser actually knows. */
.print-card-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  padding: 2rem 1rem;
  background: #e8e6df;
  container-type: inline-size;
}

.print-hint {
  display: flex;
  align-items: center;
  width: min(5in, 100%);
  margin: 0 0 1rem;
  gap: 0.6rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* Holds the JS-built cards as a plain vertical stack - normal block flow,
   so the pane just grows to fit them the same way the Normal view's text
   does, with no scrollbar of its own. --card-w scales with the available
   width (the reading pane on the library page; the whole page on the
   standalone Print page) via a container query, rather than being locked
   to a literal 5in on screen, per the "some multiple of 3x5 that makes
   sense for the reading pane" brief - only the print output has to be
   physically exact. --card-gap is deliberately close to two lines of the
   card's own text, so cards read as a stack of distinct cards rather than
   one continuous column of content. */
.print-card-columns {
  --card-w: clamp(16rem, 20cqw, 40rem);
  --card-h: calc(var(--card-w) * 3 / 5);

  /* A notecard doesn't need to be legible from across a kitchen the way
     Display's text does - only a genuinely TV-wide reading pane should
     reach anything like that size. As of 2026-08-28, Andrew's second pass
     at this: the previous curve (0.8cqw + 0.4rem, floor 0.65rem) still read
     too large at normal desktop/phone container widths (~9-14px there).
     Floor dropped to 0.5rem and the slope's own constant dropped from
     0.4rem to 0.2rem, so a typical desktop/tablet pane (container roughly
     700-1200px) now sits closer to 0.55-0.65rem (~9-10px) and phone-width
     containers sit on the floor. The 2rem ceiling is untouched - Andrew was
     explicit that a TV-sized reading pane still needs 32px - it now just
     takes a wider container to reach it than before, which is fine since
     nothing below TV width was supposed to get there anyway. */
  --card-font: clamp(0.5rem, 0.6cqw + 0.2rem, 2rem);

  /* Roughly two lines of the card's own current text, so it scales down
     with --card-font instead of staying a fixed-size gap that reads as
     oversized once the font shrinks. */
  --card-gap: calc(var(--card-font) * 2.5);

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--card-gap);
}

/* One JS-built card. Every size below is in em, relative to this element's
   own font-size (or, inside a child rule, that child's own font-size)
   rather than in/pt, so the whole card - text, headings, whitespace -
   scales together with --card-font. Padding is intentionally tight (a
   notecard's margin, not a document's) so more of the card's height goes
   to actual content rather than blank border.

   The ruled lines use the `lh` unit (1lh = this element's actual computed
   line-height, always - see danburzo.ro/line-height-lh and the "Lined
   Paper with CSS" writeups this was cribbed from) rather than a hand-typed
   "1.32em" that has to be kept in sync with line-height by hand.

   As of 2026-08-28 (third pass, final iteration): the first two attempts
   within this pass tried to keep the title/metadata/headings at their own
   larger, differently-proportioned font sizes and still land on the ruled
   grid - either by hand-tuning every margin to an exact fraction of 1lh
   (fragile - the same failure mode as the two earlier attempts documented
   in the "ruled-paper look" section of docs/recipe-notecards.md), or by
   moving the ruled background off the card entirely onto just the lists
   (which then left all the card's *blank* leftover space unruled, when
   Andrew's actual ask was a card that's ruled edge-to-edge like real
   notecard stock). Andrew's own fix, once asked: give up on differently-
   sized headings and just put everything - title included - at the same
   font-size as the body text, one ruled line per piece of content. At a
   uniform font-size, every element's own line-height (all still 1.32,
   inherited, nothing to override per element) produces exactly one 1lh
   line automatically - no fractional margins to solve for, because there's
   no size mismatch left to cause one. Only margins that are whole
   multiples of `var(--card-line)` are used below, so nothing can drift. */
.print-card-columns > .print-card {
  width: var(--card-w);

  /* min-height, not height: a card always shows its full physical size -
     including whatever blank ruled space is left over - so it's visible
     when the sizing/packing needs adjustment, rather than shrinking to fit
     whatever content happened to land on it. min- rather than a hard height
     means the rare card whose content doesn't fit within the budget still
     shows all of it instead of being clipped. */
  min-height: var(--card-h);

  /* One card-body-line, in absolute terms, usable by children (the title
     below) regardless of their own font-size - a child's own `1em`/`1lh`
     is relative to *its* font-size, which isn't what "push this down by
     one ruled line" means. */
  --card-line: calc(var(--card-font) * 1.32);

  position: relative;
  padding: 0.7em;
  color: #17241b;
  background:
    /* the red rule near the top of a ruled index card */
    linear-gradient(to bottom, transparent 0 0.4em, #c0413a 0.4em 0.48em, transparent 0.48em 100%) padding-box,
    /* faint blue writing lines, one per ruled line, the full height of the
       card (including any blank space below the last line of content) -
       real card stock is ruled edge-to-edge regardless of how much of it
       is written on. */
    repeating-linear-gradient(to bottom, transparent 0 calc(1lh - 1px), #c9d7ec calc(1lh - 1px) 1lh) content-box,
    #fffef8;
  border: 1px solid #d5d0c2;
  box-shadow: 0 10px 22px rgba(31, 48, 39, 0.14);
  font-family: Georgia, "Times New Roman", serif;
  font-size: var(--card-font);
  line-height: 1.32;
}

/* Preview-only overrides (the shared `.print-card h1`/`h2`/`.print-meta`/
   `.print-notes`/`li` rules below still govern the print output
   unchanged - print pagination and its physically-exact sizing are out of
   scope here). Title is now the *same* font-size as the body text, just
   bold - Andrew's 2026-08-28 (third pass) call: rather than chase a
   distinct larger title size onto the ruled grid, put everything at one
   common size so every line is trivially exactly 1lh, no exceptions.
   margin-top skips the title past the first (blank) ruled line, the way a
   real notecard leaves that top line blank rather than writing on it
   immediately under the header rule; margin-bottom is a whole ruled line
   too so nothing after it can drift off-grid.

   line-height/color/letter-spacing are all explicit overrides, not
   oversights: a page-wide `h1, h2, h3 { line-height: 1.15; color:
   var(--ink); letter-spacing: -0.025em; }` rule (top of this file) still
   applies to any heading that doesn't set its own value for that specific
   property, .print-card's own heading rules included - discovered when h1
   measured correctly (it already set its own line-height) but h2 didn't
   and measured 0.87 of a ruled line instead of exactly 1. Without these,
   headings would silently use the site's chrome color/spacing instead of
   the notecard's own ink color and plain letter-spacing. */
.print-card-columns > .print-card h1 {
  margin: var(--card-line) 0 var(--card-line);
  font-size: 1em;
  line-height: 1.32;
  color: inherit;
  letter-spacing: normal;
}

/* Same treatment as h1 above, including the page-wide line-height/color/
   letter-spacing override explained there - h2 needs it here too, it just
   wasn't obvious until measurement caught the drift. */
.print-card-columns > .print-card h2 {
  margin: var(--card-line) 0 0;
  line-height: 1.32;
  color: inherit;
  letter-spacing: normal;
}

/* Metadata's 2-column-by-2-row grid layout is unchanged (and still what
   print uses) - only its font-size (now matching the body text, per the
   "one common size" call above) and row-gap (0, since two rows at the
   card's own line-height are already exactly 2x1lh with nothing extra
   needed between them) are preview-only overrides. */
.print-card-columns > .print-card .print-meta {
  font-size: 1em;
  gap: 0 1em;
}

/* Same "match the body text" override as metadata above, and for the same
   reason - at the card's own font-size, `.print-notes`'s inherited 1.32
   line-height already gives exactly 1lh per line (wrapped onto however
   many lines), with nothing left to override. */
.print-card-columns > .print-card .print-notes {
  font-size: 1em;
}

/* The shared `.print-card li` rule's margin-bottom exists to give printed
   items breathing room; on screen it's exactly what breaks the ruled-line
   alignment above (a gap that isn't a multiple of 1lh desyncs every line
   after it). Zeroing it here, preview-only, means each item - and each
   wrapped line within one item - occupies exactly one ruled line with
   nothing to drift. */
.print-card-columns > .print-card li {
  margin-bottom: 0;
}

/* The print-only copy: hidden until @media print below swaps it in for the
   JS-built stack, at which point it's plain block flow at true physical
   size, using the same .print-card rules the JS-built cards use but with
   font-size pinned back to 8.5pt - see the top-of-section comment. */
.print-card-print-source {
  display: none;
}

.print-card-print-source .print-card {
  position: relative;
  font-family: Georgia, "Times New Roman", serif;
  color: #17241b;
  line-height: 1.32;
}

.print-card h1 {
  margin: 0 0 0.5em;
  font-size: 1.65em;
  font-weight: 700;
  line-height: 1.1;
}

/* As of 2026-08-28: dropped the border-bottom/uppercase/letter-spacing
   treatment and the separate Arial family - Andrew said it read like a
   formatted document heading rather than plain notecard handwriting.
   Same serif as the body copy now, just bold, since that's the only
   distinction a heading needs here. */
.print-card h2 {
  margin: 1.1em 0 0.35em;
  font-size: 1em;
  font-weight: 700;

  /* Keeps a heading from being the last thing on one card while its list
     starts the next - break-after is the modern property, page-break-after
     the fallback older engines still look for. */
  break-after: avoid;
  page-break-after: avoid;
}

/* A fixed 2-column grid rather than flex-wrap: flex-wrap decides per line
   whether two items fit based on actual rendered width, which - at some
   combination of card width and font size - can come out just barely
   false and wrap every item onto its own line instead of two per row.
   That quietly doubled the title card's height relative to every other
   card, since title+metadata are placed together as one atomic group (see
   notecard-paginate.js) with no fallback if it doesn't fit the budget. A
   grid always lays out 2 up regardless of content width, so this can't
   happen at any scale. */
.print-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin: 0;
  gap: 0.5em 1em;
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 0.88em;
  break-inside: avoid;
  page-break-inside: avoid;
}

.print-card ul,
.print-card ol {
  margin: 0;
  padding-left: 1.1em;
}

.print-card li {
  margin-bottom: 0.34em;

  /* One ingredient or step should never straddle two cards. */
  break-inside: avoid;
  page-break-inside: avoid;
}

.print-notes {
  margin: 0;
  color: #444;
  font-size: 0.88em;
}

@media print {
  @page {
    size: 5in 3in;
    margin: 0.2in;
  }

  .print-card-page {
    min-height: 0;
    padding: 0;
    background: #fff;
  }

  .print-hint {
    display: none;
  }

  /* The JS-built stack was sized for on-screen preview, not true 5in
     cards - hide it and print the plain, always-true-size copy instead. */
  .print-card-columns {
    display: none;
  }

  .print-card-print-source {
    display: block;
  }

  .print-card-print-source .print-card {
    font-size: 8.5pt;
  }
}

/* Nutrition card (Pages/Shared/_NutritionCard.cshtml).

   A deliberate imitation of the FDA's DUAL-COLUMN Nutrition Facts panel -
   per serving beside per container, which for a recipe means per serving
   beside the whole dish. Checked against the FDA's 2016 reference label.

   What makes it read as a food label rather than a table of numbers:
   Helvetica/Arial, black on white, nothing rounded and nothing tinted, against
   a site that is otherwise warm green; the heavy bars; the type jump from
   "Calories" to its number; nutrient name bold with the amount beside it and
   the % Daily Value bold and hard right; and children indented under their
   parent, two levels deep.

   The markup is a real <table> because this is real tabular data. An earlier
   version built rows out of flex, which cannot line four value columns up
   without hard-coded widths that one "4680mg" would break. */
.nf {
  max-width: 56rem;
  padding: 0.5rem 0.7rem 0.6rem;
  color: #000;
  background: #fff;
  border: 1px solid #000;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* rem, not em: a real label is a fixed-size strip, and inheriting the host
     page's type scale is what made the title wrap on /Recipes/View, whose
     reading pane runs at about 23px. Pages that want a bigger label (i.e.
     /Display, read from across a kitchen) override this one value. */
  font-size: 1rem;
  line-height: 1.2;
  text-align: left;
}

.nf-head {
  padding-bottom: 0.2rem;
  border-bottom: 0.6rem solid #000;
}

/* 1.95em against the 1rem base above measures ~255px in plain Arial, inside
   the narrowest the card ever gets. Sized rather than set to nowrap on
   purpose: if a future change does squeeze it, wrapping is survivable and
   overflowing the black border is not. */
.nf-title {
  margin: 0;
  font-size: 1.95em;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
}

.nf-servings {
  margin: 0.15rem 0 0;
  font-size: 0.85em;
}

.nf-serving-size {
  display: flex;
  justify-content: space-between;
  margin: 0.05rem 0 0;
  font-size: 0.9em;
  font-weight: 700;
  gap: 1rem;
}

/* Directly under the title rather than in the footnote: /Display is read from
   across a kitchen, and a footnote is exactly what gets missed there. It says
   only THAT some values are guessed - the degree marks say which. */
.nf-estimate-note {
  margin: 0;
  padding: 0.25rem 0 0.2rem;
  border-bottom: 1px solid #000;
  font-size: 0.72em;
  font-weight: 700;
}

.nf-table {
  width: 100%;
  border-collapse: collapse;
}

.nf-table th,
.nf-table td {
  padding: 0.16rem 0;
  font-weight: 400;
  text-align: left;
  vertical-align: baseline;
}

/* The nutrient name carries the weight, the amount sits beside it in regular,
   and the percentage is bold and hard right - the panel's own hierarchy. */
.nf-table tbody th[scope="row"] {
  font-weight: 700;
}

.nf-table tbody tr {
  border-bottom: 1px solid #000;
}

.nf-table .nf-amount {
  padding-left: 1.1rem;
  text-align: right;
  white-space: nowrap;
}

.nf-table .nf-dv {
  padding-left: 0.5rem;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
}

/* "Per serving" / "Per recipe", set over the Calories band. */
.nf-column-heads th {
  font-size: 0.7em;
  font-weight: 700;
  text-align: right;
}

.nf-calories-table {
  border-bottom: 0.5rem solid #000;
}

.nf-calories-table tbody tr {
  border-bottom: 0;
}

.nf-calories-table tbody th[scope="row"] {
  font-size: 1.5em;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* The number is set to the same size as the word beside it. The printed FDA
   label makes the figure much larger, but at the sizes this card is actually
   read at - a phone column, a wall panel - the 1.85em it used to carry was
   only a little over the 1.5em of "Calories", which reads as two headings that
   failed to line up rather than as one deliberately dominant number. */
.nf-calories-table tbody td {
  font-size: 1.5em;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  text-align: right;
  white-space: nowrap;
}

/* The small "Amount / % DV" pair above each nutrient block. */
.nf-unit-heads th {
  padding-left: 0.5rem;
  font-size: 0.66em;
  font-weight: 700;
  text-align: right;
}

/* Macros and vitamins side by side on anything wide enough, stacked when not.
   flex-wrap rather than a media query for the same reason _RecipeFacts uses
   it: on /Display the width that matters is a DAKboard iframe's, which a
   viewport query cannot see. */
.nf-columns {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: 1.2rem;
}

.nf-columns > .nf-macros {
  flex: 1 1 19rem;
}

.nf-columns > .nf-micros {
  flex: 1 1 15rem;
}

/* The vitamin block keeps the heavy rule the FDA panel puts above it. The
   macro block doesn't need one: the Calories band above already carries a
   continuous rule the full width of the card, and giving each column its own
   would leave the flex gap showing as a break through the middle of it. */
.nf-columns > .nf-micros {
  border-top: 0.5rem solid #000;
}

.nf-table tbody tr:last-child {
  border-bottom: 0.25rem solid #000;
}

/* Saturated fat, trans fat, fiber, sugars and added sugars are indented under
   their parent and set in regular weight, as the panel prints them. */
.nf-indent-1 th[scope="row"],
.nf-indent-2 th[scope="row"] {
  font-weight: 400;
}

.nf-indent-1 th[scope="row"] {
  padding-left: 0.9rem;
}

.nf-indent-2 th[scope="row"] {
  padding-left: 1.8rem;
}

/* The degree mark on an estimated figure. Deliberately quiet - it qualifies a
   number without competing with it - but carries a title so hovering says what
   it means, and an <abbr> so a screen reader can too. */
.nf-est {
  border: 0;
  font-size: 0.85em;
  text-decoration: none;
  vertical-align: super;
  line-height: 0;
  cursor: help;
}

.nf-footnote {
  margin: 0.4rem 0 0;
  font-size: 0.66em;
  line-height: 1.25;
}

/* Basic vs detailed. One label, two states: .nf-detail is everything only the
   full panel shows, .nf-basic is the short macro list that stands in for it.
   The checkbox is rendered by the card partial as its own previous sibling,
   which is what these sibling selectors hang off - see the comment there for
   why it lives in the partial rather than in each page. */
.nf .nf-detail {
  display: none;
}

#nutrition-detail:checked ~ .nf .nf-basic {
  display: none;
}

#nutrition-detail:checked ~ .nf tr.nf-detail {
  display: table-row;
}

#nutrition-detail:checked ~ .nf table.nf-detail {
  display: table;
}

#nutrition-detail:checked ~ .nf p.nf-detail {
  display: block;
}

#nutrition-detail:checked ~ .nf span.nf-detail {
  display: inline;
}

/* The card's own toggle, under the panel rather than inside its border: it is
   a control this app added, not part of the label, and putting it inside the
   black box would read as a nutrient. */
.nf-toggle {
  display: block;
  margin: 0.5rem 0 0;
  color: #2f5d3f;
  font-family: inherit;
  font-size: 0.75em;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

.nf-toggle-less {
  display: none;
}

#nutrition-detail:checked ~ .nf .nf-toggle-more {
  display: none;
}

#nutrition-detail:checked ~ .nf .nf-toggle-less {
  display: block;
}

/* The free-text fallback carries a sentence, not a panel, so it takes its
   natural height instead of stretching to match the timings column beside it -
   stretching left a tall empty box under one line of text. */
.nf-legacy {
  align-self: flex-start;
  max-width: 24rem;
}

.nf-legacy .nf-title {
  padding-bottom: 0.2rem;
  border-bottom: 0.3rem solid #000;
  font-size: 1.6em;
}

.nf-freetext {
  margin: 0.4rem 0 0;
  font-size: 0.85em;
  white-space: pre-line;
}

/* What the AI assumed an unmeasured ingredient was, printed beside the line it
   was assumed for. Quiet and italic because it is an annotation over somebody's
   recipe, not part of what they wrote - see Models/NutritionProvenance.cs for
   why it is never written into the text itself. */
.ingredient-assumption {
  margin-left: 0.4rem;
  color: #5d6a61;
  font-size: 0.82em;
  font-style: italic;
  white-space: nowrap;
}

/* Timings beside the label (Pages/Shared/_RecipeFacts.cshtml).

   flex-wrap rather than a media query, deliberately. The question is whether
   THIS BLOCK is wide enough for both, and on /Display that is the width of a
   DAKboard iframe, which a viewport-based media query cannot see. With a
   flex-basis on each child the browser wraps the label underneath the timings
   at whatever width it actually has - on a 40 inch portrait panel and in a
   phone-width reading pane alike, with no breakpoint to keep in sync. */
.recipe-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  max-width: 78rem;
  margin: 1.25rem 0;
  gap: 1rem;
}

/* The label may wrap onto its own line, but it must never be SQUEEZED: below
   about 17.5 times its own font size the title no longer fits on one line, and
   a two-line "Nutrition / Facts" is the one thing that stops this reading as a
   real label. em rather than rem so the floor tracks whatever size the host
   page sets the card to - /Display runs it at 1.35rem and therefore needs a
   proportionally wider floor. min(100%, ...) keeps it from overflowing a phone
   narrower than the floor itself, where wrapping the title is unavoidable. */
.recipe-facts > .nf {
  flex: 1 1 40rem;
  min-width: min(100%, 17.5em);
}

/* Heights match against the BASIC card, which is what "the timings line up
   with the label" means in practice. The detailed card is three times taller,
   and stretching four timing rows down beside it leaves each one floating in
   its own paragraph of empty space - so the column stops stretching as soon
   as the label expands.

   :has() rather than a sibling selector because the checkbox is a child of
   this element, and the timings column is rendered before it - there is no
   sibling combinator that reaches backwards. */
.recipe-facts:has(#nutrition-detail:checked) {
  align-items: flex-start;
}

/* align-items: stretch above makes this column as tall as the label; flex: 1
   on each row then shares that height out evenly, so the two line up top and
   bottom without either one being given a fixed height. */
.recipe-facts-meta {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  min-width: 12rem;
  max-width: 17rem;
  margin: 0;
  border: 1px solid #2f5d3f;
  border-radius: 0.4rem;
  overflow: hidden;
}

.recipe-facts-row {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.85rem;
  gap: 1rem;
}

.recipe-facts-row + .recipe-facts-row {
  border-top: 1px solid #cddbcf;
}

.recipe-facts-row dt {
  color: #4a5a4e;
  font-size: 0.8em;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.recipe-facts-row dd {
  margin: 0;
  font-size: 1.15em;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
}

/* A <label> that acts as a link in a row of links (/Recipes/View's top bar).
   Styled to match the anchors beside it rather than the .btn buttons, since
   that row is links. */
.link-button {
  color: #2f5d3f;
  font-weight: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* The nutrition boxes on Edit. Two or three across rather than nine stacked
   rows - they're short numeric fields, and a column of nine full-width boxes
   turns a correction into a scroll. */
.nutrition-fields {
  margin-bottom: 1rem;
}

.nutrition-fields > summary {
  cursor: pointer;
  font-weight: 700;
}

.nutrition-fields-heading {
  margin: 1.1rem 0 0;
  font-size: 0.85em;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.nutrition-fields-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin-top: 0.75rem;
  gap: 0.6rem 1rem;
}

/* Label above input in every cell. The site's default label styling leaves
   short labels sitting inline beside their box, which across a nine-cell grid
   makes each row a different height and the whole panel look broken. */
.nutrition-fields-grid > div {
  display: flex;
  flex-direction: column;
}

.nutrition-fields-grid label {
  margin-bottom: 0.2rem;
}

.nutrition-fields-grid input {
  width: 100%;
  margin: 0;
}

.nutrition-estimated-toggle {
  display: flex;
  align-items: center;
  margin-top: 0.75rem;
  gap: 0.5rem;
}

.nutrition-estimated-toggle input {
  width: auto;
  margin: 0;
}

.nutrition-estimated-toggle label {
  margin: 0;
  font-weight: 400;
}

/* Inside the display's own panel the cards supply all the structure, so the
   panel around them collapses to a plain frame rather than doubling the
   padding and the border radius.

   Only when there IS a card, though. A recipe with no nutrition falls back to
   the plain timings run in _RecipeFacts, which brings no frame of its own, and
   collapsing the panel unconditionally left "Servings: 4  Prep: 5 min" adrift
   on the background between the title and the ingredients. */
.display-nutrition:has(.recipe-facts) {
  padding: 0;
  background: transparent;
  border: none;
}

/* The fallback run, when it is the whole section: spaced for a wall rather
   than for a paragraph of body text. */
.display-nutrition .recipe-metadata {
  margin: 0;
  color: #3d4d42;
  font-size: 0.95em;
  font-weight: 650;
  gap: 0.3em 1.6em;
}

/* The label is a strip, and on the wall it had stopped being one: given a
   1000px row it grew to 712px wide and 340px tall, which stretched four short
   rows across two thirds of the screen and put the % Daily Value column a hand
   span away from the nutrient it belonged to. A real Nutrition Facts panel is
   taller than it is wide, and the reason this one is not is that /Display
   never shows the detailed rows (nothing on a wall can click the toggle), so
   there are only ever three or four of them.

   em rather than rem throughout, so the card and the timings beside it hold
   their proportions as --display-base scales - the whole point of the row is
   that the two columns line up, and one of them sized in rem would drift out
   of the other as the panel grew. */
/* flex-grow is 1, and the basis is well under what the label wants, because
   with flex-wrap on the row a basis that does not fit wraps BEFORE anything
   shrinks - the browser fills a line at the basis widths and only shrinks what
   is already on it. At 26em against a 12.5em timings column that came to
   1197px of basis in the 1016px a 1080-wide portrait panel actually has, so
   the label dropped underneath and both kept their max-widths: measured 2026-
   09-06 on Sue's panel as a 709px bar over a 738px label with 278px of dead
   space down the right. Under the basis, shrink does the work instead and the
   two stay side by side; max-width still stops the label sprawling on a wide
   screen. */
.display-nutrition .nf {
  flex: 1 1 20em;
  max-width: 26em;
  font-size: 0.8em;
}

.display-nutrition .recipe-facts {
  max-width: none;
  margin: 0;
}

/* Wide enough for "SERVINGS  8 wedges" on one line - the label and the value
   sit at opposite ends of the row, so this column's floor is set by the
   longest serving count rather than by the timings, and 11em clipped the s. */
.display-nutrition .recipe-facts-meta {
  flex: 1 1 11em;
  min-width: 9em;
  max-width: 18em;
}

/* Matching heights is right for a recipe with four timings beside the label
   and wrong for one with a single Serves row: stretch then gives that row the
   label's whole height and floats two words in the middle of a 493px box.
   Same argument the :has(#nutrition-detail:checked) rule above already makes
   about the expanded label, so it takes the same shape. Three rows is where
   the column has enough in it for the alignment to read as alignment. With
   :has() unsupported the row falls back to flex-start, which is the safe half
   of this. */
.display-nutrition .recipe-facts {
  align-items: flex-start;
}

.display-nutrition .recipe-facts:has(.recipe-facts-row:nth-child(3)) {
  align-items: stretch;
}

/* The timings column keeps the light palette it has everywhere else: this
   section is one of /Display's pale panels, not the dark green header the
   metadata line used to sit in. The label itself stays black on white because
   a real one always is - it should read as a genuine label pasted onto the
   page rather than a themed panel.

   The card's toggle is hidden here because nothing on a wall panel can click
   it - /Display renders no "Detailed nutrition" button either, so the basic
   card is the only state this page ever shows. */
.display-nutrition .recipe-facts-row dt {
  font-size: 0.85em;
  letter-spacing: 0.1em;
}

.display-nutrition .recipe-facts-row dd {
  font-size: 1.25em;
}

.display-nutrition .nf-toggle {
  display: none;
}

/* Same complaint, different page: in the reading pane the card is free to
   grow to its 56rem ceiling and consume whatever the timings leave, which is
   how a four-row panel ends up wider than the recipe beside it. 42rem is the
   width the label was drawn at; see docs/ui-review/2026-09-nutrition-display. */
.display-page-embedded .recipe-facts > .nf {
  flex: 0 1 42rem;
  max-width: 42rem;
}


.export-section {
  max-width: 46rem;
  margin-top: 2rem;
}

.export-paste {
  width: 100%;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.95rem;
}

/* The "View the original" disclosure - the photograph a recipe was parsed
   from, on /Recipes/View and Edit. Styled to match the read-aloud panel above
   it: same soft ground, same border, same radius, so the two controls that sit
   between the page heading and the recipe read as one row of affordances
   rather than two unrelated widgets. */
.original-scan {
  margin: 1rem 0;
  padding: 0.75rem 0.9rem;
  background: var(--sage-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.original-scan > summary {
  cursor: pointer;
  font-weight: 600;
}

.original-scan-pages {
  margin-top: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.original-scan-pages figure {
  margin: 0;
}

/* A photographed recipe card is often taller than the screen, so it is capped
   by viewport height as well as width - the useful view is the whole card at
   once, not a life-size crop of its top third. Tapping it opens the image on
   its own, which is where zooming in on one line belongs. */
.original-scan-pages img {
  display: block;
  max-width: 100%;
  max-height: 80vh;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
}

.original-scan-pages figcaption {
  margin-top: 0.35rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

/* The TikTok post a recipe was imported from, played in TikTok's own iframe.
   Shared by /Recipes/View and /Display, so it has to read at arm's length on
   a phone and from across a kitchen on a portrait wall panel. */
.video-embed {
  margin-top: 2rem;
}

/* TikTok's player is 9:16, which is the one aspect ratio that suits a portrait
   wall display - so the frame is capped by height as well as width. A
   full-width 9:16 box on a 1080x1920 panel is taller than the screen and would
   push everything below it off the bottom. min() takes whichever limit bites
   first, so the same rule works on a phone without a breakpoint. */
.video-embed-frame {
  position: relative;
  width: min(100%, 26rem, 45vh);
  aspect-ratio: 9 / 16;
  margin: 0 auto;
  border-radius: 0.75rem;
  overflow: hidden;
  background: #000;
}

.video-embed-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.video-embed-credit {
  margin-top: 0.5rem;
  text-align: center;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

/* Voice Phrases (admin). The page is a list of decisions rather than of
   things, so each row carries a control instead of just a link - the select
   and its button belong together and must not wrap apart from each other. */

.phrase-assign {
  display: flex;
  align-items: center;
  /* Explicit, because a broader `form` rule in this file sets column and would
     otherwise stack the select above its own button. */
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 0.4rem;
}

.phrase-assign button {
  /* "Teach it" is two words and the row is tight; let it keep its own line
     rather than breaking in the middle of a label. */
  white-space: nowrap;
}

.phrase-assign select {
  min-width: 12rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.92rem;
}

/* Sections on this page follow a list, and .page-intro sets margin-bottom to
   0, so without this the headings sit directly on top of what precedes them. */
.phrase-section {
  margin-top: 2rem;
}

.phrase-intro + .phrase-intro {
  margin-top: 0.6rem;
}

@media (max-width: 640px) {
  /* The library's compact Nutrition Facts card only needs the per-serving
     figures at a glance. Hiding the whole-recipe columns buys enough width
     to keep it beside the timing card; expanding Detailed nutrition restores
     the full label below the timings. This is scoped to the embedded recipe
     view, so the kitchen display keeps its larger dual-column treatment. */
  .display-page-embedded .recipe-facts {
    flex-wrap: nowrap;
    gap: 0.55rem;
  }

  .display-page-embedded .recipe-facts-meta {
    flex: 0 0 9.25rem;
    min-width: 0;
    max-width: none;
  }

  /* flex-wrap is the safety valve, not the normal state. The column is a fixed
     9.25rem here and neither half of a row can shrink - the label is one
     uppercase word and the value carries white-space: nowrap - so an
     overlong pair used to overflow into the border and read as cut off
     ("Servings 12 servings" measured 138px of content in 130px). Wrapping the
     value under its label costs one line and never cuts a number, which is the
     right way round: a clipped serving count is wrong where a taller row is
     only bigger. */
  .display-page-embedded .recipe-facts-row {
    flex-wrap: wrap;
    padding: 0.4rem;
    gap: 0.25rem;
  }

  /* The label gives up the size the value cannot. Everywhere else in this card
     label and value are the same size, using weight, tracking and alignment
     for hierarchy instead of a size jump - but at this width that pairing has
     nowhere left to go, and the label is the half that can afford it: it is
     uppercase, bold and tracked, and it is the same four words on every
     recipe, while the value is the thing you are reading the row for. Dropping
     the label to 0.7rem and its tracking to 0.02em turns 8px of overflow into
     7px of slack, measured on "12 servings" at 390px and 375px.

     Seven pixels turned out not to be enough on a real iPhone, which is why
     the label reads "Serves" rather than "Servings" now - see
     _RecipeFacts.cshtml. That takes it to 22px, and this rule is what keeps
     the four labels agreeing with each other rather than only the shortest
     one fitting. */
  .display-page-embedded .recipe-facts-row dt {
    font-size: 0.7rem;
    letter-spacing: 0.02em;
  }

  /* margin-left: auto rather than leaning on the row's space-between, which
     only reaches the far edge while both halves share a line. A wrapped value
     is alone on its line and would otherwise jump to the left, breaking the
     right-hand column the other rows line up on. */
  .display-page-embedded .recipe-facts-row dd {
    margin-left: auto;
    font-size: 0.78rem;
  }

  .display-page-embedded .recipe-facts:not(:has(#nutrition-detail:checked)) > .nf {
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
    font-size: 0.76rem;
  }

  .display-page-embedded .recipe-facts:not(:has(#nutrition-detail:checked)) .nf-title {
    font-size: 1.65em;
  }

  .display-page-embedded .recipe-facts:not(:has(#nutrition-detail:checked)) .nf-per-recipe {
    display: none;
  }

  /* Detailed nutrition is intentionally the spacious state: it returns the
     per-recipe figures and takes a full row instead of squeezing five table
     columns into half of a phone. */
  .display-page-embedded .recipe-facts:has(#nutrition-detail:checked) {
    flex-wrap: wrap;
  }

  .display-page-embedded .recipe-facts:has(#nutrition-detail:checked) .recipe-facts-meta {
    flex: 1 1 100%;
    max-width: 17rem;
  }

  .display-page-embedded .recipe-facts:has(#nutrition-detail:checked) > .nf {
    flex: 1 1 100%;
    min-width: min(100%, 17.5em);
    max-width: 42rem;
  }

  /* A phone has no room for a 14rem select beside a button on the same line;
     let the whole control drop under the phrase rather than squeezing it. */
  .phrase-assign {
    flex-wrap: wrap;
  }

  .phrase-assign select {
    min-width: 100%;
  }
}

/* The narrowest phones, and deliberately the last rules in this file.

   These lost silently the first time they were written, sitting a few hundred
   lines above the max-width: 600px block: same specificity, and source order
   decides a tie between media queries, so the wider breakpoint quietly won and
   the mascot stayed at 3.5rem. Anything that has to beat an earlier breakpoint
   belongs down here rather than next to the rules it is refining. */
@media (max-width: 430px) {
  /* Four predictable columns stop More being stranded on a second line.
     The Display trigger owns the flexible column and may ellipsize its live
     state; the other three controls keep their full labels and 44px coarse-
     pointer height. */
  .recipe-reader-actions {
    display: grid;
    grid-template-columns: minmax(0, max-content) auto auto auto;
    justify-content: start;
    gap: 0.35rem;
  }

  .recipe-reader-actions > * {
    min-width: 0;
  }

  .recipe-reader-actions .btn-compact,
  .recipe-reader-actions .segmented-option {
    padding-inline: 0.5rem;
    font-size: 0.8rem;
  }

  .recipe-reader-actions .display-menu > summary {
    width: 100%;
  }

  .recipe-reader-actions .display-menu-icon {
    display: none;
  }

  .recipe-reader-actions .display-menu-label {
    max-width: none;
  }

  /* More is back at the right edge now that it no longer wraps. Anchor its
     panel to that edge, while keeping the wider Display panel inside even a
     320px viewport. */
  .recipe-reader-actions .more-menu-panel {
    left: auto;
    right: 0;
  }

  .recipe-reader-actions .display-menu-panel {
    width: min(20rem, calc(100vw - 1.25rem));
  }

  .site-brand-mascot {
    width: 3rem;
  }

  /* nowrap, or "Add Recipes" breaks over two lines and the sticky header grows
     a row on the narrowest screens. Giving the pills less padding buys enough
     width for all three to stay on one line down to 320px. */
  .site-nav .nav-link {
    padding: 0.45rem 0.4rem;
    font-size: 0.78rem;
    white-space: nowrap;
  }
}

/* Below about 340px - an old SE, and about the narrowest phone still in use -
   the three pills plus the mascot plus the avatar stop fitting even tightened,
   and the page started scrolling sideways. Nothing is dropped; everything just
   gives up a little more. */
@media (max-width: 340px) {
  /* Preserve the one-row toolbar on old 320px-wide phones by trimming only
     horizontal padding; coarse pointers still receive 44px-high targets. */
  .recipe-reader-actions {
    gap: 0.25rem;
  }

  .recipe-reader-actions .btn-compact,
  .recipe-reader-actions .segmented-option {
    padding-inline: 0.32rem;
    font-size: 0.75rem;
  }

  /* At this width the two summary cards no longer have a readable half-width
     layout. Let them stack instead of shrinking Nutrition Facts into a logo. */
  .display-page-embedded .recipe-facts {
    flex-wrap: wrap;
  }

  .display-page-embedded .recipe-facts-meta,
  .display-page-embedded .recipe-facts:not(:has(#nutrition-detail:checked)) > .nf {
    flex: 1 1 100%;
  }

  .site-nav {
    gap: 0.3rem;
  }

  .site-brand-mascot {
    width: 2.5rem;
  }

  .site-nav .nav-link {
    padding: 0.4rem 0.28rem;
    font-size: 0.72rem;
  }
}
