  :root {
    --bg: #f5f1e8;
    --card: #ffffff;
    --ink: #1a1a1a;
    --muted: #5a6b5e;
    --accent: #1f4d2e;
    --accent-dark: #143420;
    --accent-tint: #e8f0e8;
    --border: #dcdccf;
    --danger: #8a2a2a;
    --ok: #1f4d2e;
  }
  * { box-sizing: border-box; }
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    background: var(--bg);
    color: var(--ink);
    line-height: 1.45;
  }
  header {
    padding: 24px 20px 8px;
    text-align: center;
  }
  header .brand-logo {
    display: block;
    margin: 0 auto 10px;
    max-width: 110px;
    height: auto;
  }
  header .brand-logo--mark { max-width: 60px; }
  header h1 {
    margin: 0;
    font-size: 1.7rem;
    letter-spacing: 1px;
    color: var(--accent);
    font-weight: 600;
  }
  header p {
    margin: 4px 0 0;
    color: var(--muted);
    font-size: 0.95rem;
  }
  .nav-wrapper {
    position: relative;
    max-width: 760px;
    margin: 0 auto;
  }
  nav.tabs {
    position: relative;
    display: flex;
    justify-content: center;
    gap: 4px;
    margin: 16px 0 8px;
    border-bottom: 1px solid var(--border);
  }
  nav.tabs button {
    background: none;
    border: none;
    padding: 10px 18px;
    font-size: 1rem;
    color: var(--muted);
    cursor: pointer;
    border-bottom: 3px solid transparent;
    margin-bottom: -1px;
  }
  nav.tabs button.active {
    color: var(--accent-dark);
    border-bottom-color: var(--accent);
    font-weight: 600;
  }
  .hamburger-btn {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
    padding: 8px 6px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 0;
    border-bottom: 3px solid transparent !important;
  }
  .hamburger-btn span {
    display: block;
    width: 20px;
    height: 2px;
    background: var(--muted);
    border-radius: 2px;
    transition: background 0.15s;
  }
  .hamburger-btn.active span { background: var(--accent-dark); }
  #hamburger-menu {
    position: absolute;
    top: calc(100% - 1px);
    right: 8px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 0 0 8px 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    z-index: 200;
    min-width: 170px;
  }
  #hamburger-menu button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 13px 20px;
    background: none;
    border: none;
    border-bottom: 1px solid var(--border);
    font-size: 1rem;
    color: var(--ink);
    cursor: pointer;
  }
  #hamburger-menu button:last-child { border-bottom: none; }
  #hamburger-menu button:hover { background: var(--bg); }
  #hamburger-menu button.active { color: var(--accent-dark); font-weight: 600; }
  /* Recipes rides the main tab bar on wide screens and folds into the hamburger on mobile,
     so it's one click on a laptop but doesn't crowd a phone. Wide is the default; the mobile
     swap lives in the 640px block below. */
  #hamburger-menu #tab-recipes-btn { display: none; }
  main {
    max-width: 760px;
    margin: 0 auto;
    padding: 12px 16px 64px;
  }
  .card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    margin-bottom: 14px;
  }
  .card h2 {
    margin: 0 0 8px;
    font-size: 1.1rem;
  }
  .row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }
  .row + .row { margin-top: 8px; }
  label { font-size: 0.9rem; color: var(--muted); }
  input[type="text"], input[type="number"], input[type="date"], textarea {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 8px;
    font-size: 0.95rem;
    background: white;
    color: var(--ink);
    font-family: inherit;
  }
  input[type="number"] { width: 90px; text-align: right; }
  input[type="text"], input[type="date"] { flex: 1; min-width: 120px; }
  textarea { flex: 1; resize: vertical; min-height: 56px; }
  /* Selects match the input styling (they previously rendered browser-default:
     square corners, grey chrome, intrinsic widths that blow out rows). The open
     picker is OS-rendered and unstylable; this covers the closed control. */
  select {
    -webkit-appearance: none;
    appearance: none;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 26px 6px 8px;
    font-size: 0.95rem;
    background-color: white;
    color: var(--ink);
    font-family: inherit;
    line-height: inherit;
    cursor: pointer;
    max-width: 100%;
    min-width: 0;              /* let flex rows shrink long option text… */
    overflow: hidden;
    text-overflow: ellipsis;   /* …and ellipsize instead of overflowing */
    white-space: nowrap;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%231f4d2e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
  }
  select:hover { border-color: var(--accent); }
  select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(31, 77, 46, 0.15); }
  select:disabled { opacity: 0.55; cursor: default; }
  button {
    background: var(--accent);
    color: white;
    border: none;
    border-radius: 6px;
    padding: 7px 14px;
    font-size: 0.95rem;
    cursor: pointer;
  }
  button:hover { background: var(--accent-dark); }
  /* ⚠️ A secondary button must look like a button BEFORE it is hovered. This was
     `background: transparent`, so at rest it was text inside a hairline border — on a card
     (--card is white, --bg is cream) that reads as prose, and the affordance only arrived on
     hover, which a touch screen never delivers at all. It now sits on --accent-tint, the
     tint already in the palette. Deliberately NOT solid accent: that is reserved for
     `.active` below, which means "this one is ON", so a resting button painted accent would
     read as selected. Hover deepens from there so it still has somewhere to go. */
  button.secondary {
    background: var(--accent-tint);
    color: var(--accent-dark);
    border: 1px solid var(--border);
  }
  button.secondary:hover { background: #d6e3d6; }
  /* "This one is ON" for a secondary toggle. ⚠️ These exact declarations were already
     written three times over — #temp-unit-*.active, .welcome-chips button.on, and
     #bake-chart-card button.tiny.active — each scoped to its own container. So the app had
     a consistent visual language for selected-state that most toggles could not reach:
     they carried `.active` in the markup (steps-only, the library source tabs) and rendered
     identically to their off state. One unscoped rule is the language; the scoped copies are
     now redundant rather than load-bearing. (The per-batch size buttons paint their own
     paler green inline, which still wins — a third dialect, left alone for now.) */

  button.secondary.active { background: var(--accent); color: #fff; border-color: var(--accent); }
  button.secondary.active:hover { background: var(--accent-dark); color: #fff; }
  button.danger {
    background: transparent;
    color: var(--danger);
    border: 1px solid var(--border);
  }
  button.danger:hover { background: #fbeaea; }
  button.tiny { padding: 3px 8px; font-size: 0.8rem; }
  button.reorder-btn { padding: 0 5px; font-size: 0.7rem; line-height: 1.3; min-height: 0; }
  button.reorder-btn:disabled { opacity: 0.3; cursor: default; }
  #temp-unit-f-btn.active, #temp-unit-c-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
  .muted { color: var(--muted); font-size: 0.9rem; }
  .recipe-summary {
    font-size: 0.85rem;
    color: var(--muted);
    margin-top: 4px;
  }
  .recipe-card-header { cursor: pointer; user-select: none; }
  .recipe-card-header:hover .recipe-toggle-icon { color: var(--accent); }
  .recipe-toggle-icon { font-size: 0.7rem; color: var(--muted); margin-right: 4px; display: inline-block; transition: transform 0.15s; }
  .recipe-toggle-icon.open { transform: rotate(90deg); }
  .drag-grip { cursor: grab; color: var(--muted); font-size: 0.95rem; margin-right: 6px; user-select: none; touch-action: none; }
  .drag-grip:active { cursor: grabbing; }
  .card.dragging { opacity: 0.45; }
  .card.drag-over { outline: 2px dashed var(--accent); outline-offset: -2px; }
  /* Section headers read as a tinted band that clearly outranks the recipe cards
     nested under them — larger bold label, accent left-stripe, card-matching radius. */
  details.recipe-group > summary { list-style: none; cursor: pointer; padding: 10px 14px; margin: 20px 0 12px; background: var(--accent-tint); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 10px; display: flex; align-items: center; gap: 8px; }
  details.recipe-group > summary::-webkit-details-marker { display: none; }
  details.recipe-group > summary:hover { background: #dfeadf; }
  .recipe-group-caret { font-size: 0.8rem; color: var(--accent); transition: transform 0.15s; display: inline-block; }
  details.recipe-group[open] > summary .recipe-group-caret { transform: rotate(90deg); }
  .recipe-group-title { font-weight: 700; font-size: 1.12rem; color: var(--accent-dark); letter-spacing: 0.01em; }
  .recipe-group-count { font-weight: 600; font-size: 0.75rem; color: var(--accent-dark); background: rgba(31,77,46,0.12); border-radius: 999px; padding: 1px 8px; }
  /* Recipe actions sit on their own line under the name, so every card reads the
     same instead of the buttons floating right and wrapping at some widths. */
  .recipe-card-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
  .reorder-arrows { display: inline-flex; flex-direction: column; gap: 1px; }
  /* Attempt records (Development tab). ⚠️ The star buttons carry their own REST surface — a
     control whose only state is :hover cannot be found on a phone, which this project has
     shipped past once already. The glyph says what it is; the border says it can be pressed. */
  .attempt-stars { letter-spacing: 1px; color: #b45309; }
  .attempt-star-btn { font-size: 1.15rem; line-height: 1; padding: 3px 5px; color: #b45309;
    background: #fffbeb; border: 1px solid #fcd34d; border-radius: 5px; cursor: pointer; }
  .attempt-star-btn[aria-pressed="true"] { background: #fde68a; border-color: #d97706; }
  .attempt-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    padding: 6px 0; border-top: 1px solid var(--line, #e5e7eb); font-size: 0.85rem; }
  .attempt-row:first-child { border-top: 0; }
  .attempt-n { font-weight: 600; }
  .attempt-inputs { display: grid; grid-template-columns: auto 1fr; gap: 6px 10px; align-items: center; }
  .attempt-tried { font-size: 0.8rem; color: #3730a3; background: #eef2ff; border-radius: 4px; padding: 1px 6px; }
  .attempt-photo { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; cursor: pointer;
    border: 1px solid var(--line, #e5e7eb); flex: none; }
  .recipe-photo-thumb { width: 34px; height: 34px; object-fit: cover; border-radius: 4px;
    border: 1px solid var(--line, #e5e7eb); vertical-align: middle; margin-right: 6px; }
  /* ============================================================
     THE RECIPE BOX IS A PHOTO-CARD GRID (Jonathan, 2026-08-25: "cuter boxes"
     with the recipe's image). The shape every recipe app shares — cover on
     top, title under it, one compact meta line — so a shelf is scanned by
     picture instead of read as a stack of full-width bars.

     ⚠️ THIS IS LAYOUT, NOT A NEW RENDERER. The DOM skeleton is untouched:
     every card is still .card[data-recipe-id] whose .recipe-card-header is
     immediately followed by .recipe-details, so setRecipeCardOpen's
     header.nextElementSibling still finds the details; and renderRecipes
     still EMITS every pill and summary it always did — the collapsed card
     hides them here, in CSS, rather than the renderer dropping them (the
     offline pin that reads provenance pills out of renderRecipes' innerHTML
     must keep passing).

     🔴 SCOPED TO #recipes-list ON PURPOSE. The Prices tab shares the matcher,
     the grouper and the group markup with this tab (2026-08-24) and must stay
     a list of full-width rows — an unscoped .recipe-grid or .recipe-cover rule
     would leak the grid onto a tab nobody asked to redesign.
     ============================================================ */
  #recipes-list .recipe-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 10px;
    align-items: start;
  }
  /* The card loses its own padding so the cover can run edge to edge; every
     block inside it carries its own instead. */
  #recipes-list .recipe-grid > .card {
    padding: 0;
    margin-bottom: 0;
    overflow: hidden;
  }
  #recipes-list .recipe-grid > .card .recipe-card-body { padding: 9px 11px 0; }
  #recipes-list .recipe-grid > .card .recipe-card-body { padding-bottom: 10px; }
  #recipes-list .recipe-grid > .card .recipe-card-actions { padding: 0 11px 12px; margin-top: 10px; }
  #recipes-list .recipe-grid > .card .recipe-details { padding: 0 11px 12px; }
  /* Every card has a cover — a photo or a tinted placeholder — so the grid reads
     evenly instead of alternating tall picture cards with short text ones. */
  .recipe-cover {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    /* 📐 Measured at a TRUE 320 px (CDP, not --window-size — Chrome floors a headless window to
       500 on macOS): below ~350 px the grid legitimately falls to ONE column, and 4:3 of a
       296 px card is a 221 px hero that pushes the second recipe off the screen. The cap only
       ever bites there — at 390 the cover is 132 px and at 1440 it is 129. */
    max-height: 160px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: #f1efea;
    border-bottom: 1px solid var(--border);
  }
  .recipe-cover-emoji { font-size: 2.4rem; line-height: 1; }
  /* The Development card's thumbnail cover (devCoverHtml): the box card's cover at 96x72, a fixed box that never pushes the row wider. */
  .recipe-cover.dev-cover { width: 96px; flex: none; aspect-ratio: 4 / 3; max-height: 72px; border: 1px solid var(--border); border-radius: 6px; }
  .recipe-cover.dev-cover .recipe-cover-emoji { font-size: 1.6rem; }
  /* The photo covers the placeholder rather than replacing it: a private photo
     renders display:none and is un-hidden by hydrateAttemptPhotos once its signed
     URL resolves, so the emoji is what the baker looks at in the meantime. */
  .recipe-cover .recipe-photo-thumb {
    position: absolute; top: 0; left: 0; width: 100%; height: 100%;
    margin: 0; border: 0; border-radius: 0; object-fit: cover;
  }
  /* The photo credit (PLAN-photo-sharing.md §B4): a small caption over the cover's lower-right
     corner, on the public-copy branch only. A REST surface, not a hover — phones never hover. */
  .recipe-cover .recipe-photo-credit {
    position: absolute; right: 4px; bottom: 4px; max-width: 92%;
    padding: 1px 6px; border-radius: 4px; font-size: 0.62rem; line-height: 1.5;
    background: rgba(20, 20, 20, 0.55); color: #fff;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* A tint per process type, so the placeholder cards are still told apart at a glance. */
  .rc-cover-sourdough-loaf   { background: #f3ead6; }
  .rc-cover-pan-loaf         { background: #efe7d7; }
  .rc-cover-bagel            { background: #e8e6f6; }
  .rc-cover-focaccia         { background: #e9f1e0; }
  .rc-cover-enriched         { background: #fbeedc; }
  .rc-cover-simple           { background: #efece7; }
  .rc-cover-levain           { background: #e3eef0; }
  #recipes-list .recipe-grid .rc-title-row { display: flex; align-items: flex-start; gap: 2px; }
  #recipes-list .recipe-grid .rc-title-row strong {
    font-size: 0.95rem; line-height: 1.25; overflow-wrap: anywhere;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
    overflow: hidden;
  }
  /* IN ROTATION, ON THE COVER. A ring-and-fill check the eye finds while scanning a shelf,
     instead of a word-chip that repeated in every card. ⚠️ The white ring is not decoration:
     the mark sits on an arbitrary PHOTO, so accent-on-photo alone can vanish against a dark
     crust — the ring and the shadow are what keep it legible on any cover. */
  #recipes-list .recipe-grid .rc-rotation-mark {
    position: absolute; top: 6px; right: 6px; z-index: 1;
    width: 22px; height: 22px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--accent); color: #fff;
    font-size: 0.72rem; line-height: 1; font-weight: 700;
    border: 1.5px solid rgba(255, 255, 255, 0.92);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
  }
  #recipes-list .recipe-grid .rc-pills { display: block; margin-top: 6px; }
  /* ⠿ IS THE ONLY CONTROL LEFT ON A SHUT CARD, so it carries a REST surface — a
     glyph with nothing but a hover colour cannot be found on a phone. */
  #recipes-list .recipe-grid .drag-grip {
    background: var(--card); border: 1px solid var(--border); border-radius: 6px;
    padding: 0 3px; margin-right: 3px; line-height: 1.3; flex: none;
  }
  /* SHUT = the cute box: cover, name, one meta line. Everything else waits for a tap — and
     the tap opens an OVERLAY, not the card in place (Jonathan, 2026-09-06: "instead of
     expanding inline, it should go to an overlay box that can be clicked out of"). So a card
     IN THE GRID is never open: openRecipeOverlay moves the card node itself into a modal
     (openFormOverlay, ✕ / backdrop / Escape all close it), where none of these #recipes-list
     rules reach it, and closeRecipeOverlay puts it back here, shut.
     ⚠️ The #id in the selector is load-bearing — the phone override further down sets
     .recipe-card-actions to display:grid, and a bare class here would lose to it. */
  #recipes-list .recipe-grid > .card .rc-pills,
  #recipes-list .recipe-grid > .card .recipe-summary,
  #recipes-list .recipe-grid > .card .recipe-card-actions { display: none; }
  /* THE OVERLAY = the whole recipe as a page: the cover as a banner, the name under it, every
     pill, the actions row, then the ingredients, steps, notes and source. The card sheds its
     padding so the cover runs edge to edge; every block inside carries its own instead. */
  .card.rc-overlay { padding: 0; }
  .card.rc-overlay .recipe-cover { aspect-ratio: auto; height: 200px; max-height: 200px; }
  .card.rc-overlay .recipe-card-header { cursor: default; }
  /* Grid-only controls stand down here: the ▶ (nothing to toggle), the ⠿ grip and ▲▼ arrows
     (nothing to reorder against), and the cover's rotation mark — the ✓ In rotation BUTTON is
     on screen in the actions row, and the mark would be the same fact said twice. */
  .card.rc-overlay .recipe-toggle-icon,
  .card.rc-overlay .drag-grip,
  .card.rc-overlay .reorder-arrows,
  .card.rc-overlay .rc-rotation-mark { display: none; }
  .card.rc-overlay .rc-title-row strong { font-size: 1.25rem; line-height: 1.25; }
  .card.rc-overlay .recipe-card-body { padding: 12px 16px 0; }
  .card.rc-overlay .recipe-card-actions { padding: 0 16px 12px; margin-top: 10px; border-bottom: 1px solid var(--border); }
  .card.rc-overlay .recipe-details { padding: 12px 16px 16px; }
  /* The injected ✕ sits OVER the cover's top-right corner. It is a sticky float (so it follows a
     long recipe down), and the cover is a flex box — a block-formatting root that will not sit
     beside a float, so it dropped below it and left a blank strip above the banner. The negative
     bottom margin zeroes the float's margin box, and the cover lays out as if nothing preceded it. */
  .card.rc-overlay > .overlay-close-x { margin: 8px 8px -40px 0; }
  .dev-editor-banner { margin: 0 0 10px; padding: 7px 10px; border-radius: 6px;
    background: #eef2ff; border: 1px solid #c7d2fe; font-size: 0.85rem; }
  .card.levain-card { background: #eef4ee; border-left: 3px solid var(--accent); }
  .plan-details > summary { list-style: none; cursor: pointer; user-select: none; display: flex; align-items: baseline; gap: 8px; }
  .plan-details > summary::-webkit-details-marker { display: none; }
  .plan-details > summary h2 { margin: 0; }
  .plan-card-toggle { font-size: 0.7rem; color: var(--muted); transition: transform 0.15s; display: inline-block; }
  /* 🔴 A SUMMARY THAT PAINTS ITS OWN ARROW MUST NOT ALSO WEAR THE BROWSER'S (Jonathan,
     2026-08-26: *"why does saved bakes have an extra arrow '▶ Saved bakes 1 saved'??"*).
     📐 Measured on #saved-bakes-card before the fix: its summary computed
     list-style-type: disclosure-closed and display: list-item, so Chrome drew the native
     triangle immediately left of the hand-written span — two arrows, one fold.
     The cause is that the suppression was keyed on the CONTAINER (.plan-details,
     .equip-section) while the glyph is keyed on the toggle span, so any disclosure that
     borrowed the glyph under some other class got both marks and nothing said so. Keyed on
     the TOGGLE, the rule travels with the thing that makes it necessary: a new fold that
     writes its own arrow can never acquire a second one.
     ⚠️ Descendant, not child — the toggle is sometimes wrapped (a heading, a flex row). */
  summary:has(.plan-card-toggle) { list-style: none; }
  summary:has(.plan-card-toggle)::-webkit-details-marker { display: none; }
  /* ...and the SAME keying for what the arrow says. Suppressing the native marker without this
     would leave a fold whose only mark points right while it stands open — the browser's arrow
     turned, and a hand-written one that does not is a control rendering click history instead of
     current state. Both halves of the affordance belong to the toggle, not to a container class. */
  details[open] > summary .plan-card-toggle { transform: rotate(90deg); }
  summary:hover .plan-card-toggle { color: var(--accent); }
  .equip-section > summary { list-style: none; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px; }
  .equip-section > summary::-webkit-details-marker { display: none; }
  .equip-section > summary h3 { margin: 0; font-size: 1rem; }
  table.ingredients {
    width: 100%;
    border-collapse: collapse;
    margin-top: 6px;
  }
  table.ingredients th, table.ingredients td {
    text-align: left;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border);
    font-size: 0.95rem;
  }
  table.ingredients th { color: var(--muted); font-weight: 500; font-size: 0.85rem; }
  /* A gram cell never gains from wrapping: on a 390px phone a long seed label ("Mature starter
     (50% bread flour, 50% whole wheat flour)", PLAN-kitchen-starter A4) squeezed "18 g" onto two
     lines beside it — 📐 CDP 2026-09-03 at innerWidth 390. The bake sheet's own td.num already
     said nowrap; the base rule now does too. */
  table.ingredients td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
  /* Proofing card on phones: the Recipe/Warm/Cold/Fridge table stacks into one labeled
     block per recipe — its three control columns are far wider than a phone card. */
  @media (max-width: 640px) {
    #proofing-output table.ingredients,
    #proofing-output table.ingredients tbody,
    #proofing-output table.ingredients tr,
    #proofing-output table.ingredients td { display: block; width: 100%; }
    #proofing-output table.ingredients thead { display: none; }
    #proofing-output table.ingredients tr { padding: 6px 0; border-bottom: 1px solid var(--border); }
    #proofing-output table.ingredients tr:last-child { border-bottom: none; }
    #proofing-output table.ingredients td { border-bottom: none; padding: 3px 0; text-align: left; }
    #proofing-output table.ingredients td:first-child { font-weight: 600; }
    /* NOTE: must out-rank the `table.ingredients td { display: block }` rule above —
       (1,1,2) — or the flex layout (and its gap) silently loses and the label runs
       into the value ("Cold proofnone · proofs warm"). Hence the full selector. */
    #proofing-output table.ingredients td[data-label] { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
    #proofing-output table.ingredients td[data-label]::before {
      content: attr(data-label);
      color: var(--muted);
      font-size: 0.85rem;
      flex: 0 0 88px;
    }
    #proofing-output td[data-label] select { flex: 1 1 auto; min-width: 0; }
    #proofing-output td[data-label] > span[style*="inline-flex"] { flex: 1 1 auto; min-width: 0; }
  }
  /* Prices "Cost link" column holds a <select> that would otherwise size to its
     widest option and push the table past its card. Fixed layout + a capped
     column (and a shrinkable select) keeps it inside the box. */
  .prices-ing-table { table-layout: fixed; width: 100%; }
  .prices-ing-table th:nth-child(2) { width: 58px; }
  .prices-ing-table th:last-child { width: 38%; }
  .prices-ing-table td { overflow-wrap: anywhere; }
  .prices-ing-table td.num { white-space: nowrap; overflow-wrap: normal; }  /* keep "100%" on one line */
  .prices-ing-table .prices-pantry { width: 100%; min-width: 0; max-width: 100%; }
  .ingredient-edit-row {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 6px;
  }
  /* 🔴 THE "USED IN" COLUMN GREW BECAUSE ITS QUESTION DID. It was 96px while it only had to say
     "in dough" or "tangzhong"; it now also carries the recipe's own STEP LABELS, which are authored
     prose ("Dip in the lye bath"). 📐 Measured by screenshot at 1100px before changing anything:
     96px rendered the chosen destination as "Dip in th…", so the baker could not read the answer
     they had just given. 150px fits it (the phone restack, which flexes to 146px, already did —
     the defect was DESKTOP-only, which is the opposite of the usual way round and is exactly why
     it had to be looked at rather than reasoned about). The width comes out of the name column's
     1fr, which has it to spare at this width. */
  .ingredient-edit-row .ing-main {
    display: grid;
    grid-template-columns: 1fr 72px 28px 116px 150px 116px 32px;
    gap: 8px;
    align-items: center;
  }
  /* 🔴 THE AMOUNT FIELD SAYS ITS OWN UNIT. Reported live 2026-08-26 (Jonathan: "we should have
     the unit represented for the ingredients either the % or the g after the field — it's too
     confusing to have to look at the toggle up top to know what you're looking at"). The mode
     toggle lives at the top of a scrolling editor, so on any recipe with more than a few rows the
     answer to "is this 72 a percentage or 72 grams?" was off screen — and the two readings differ
     by a factor of the flour weight. The placeholder already said it and a placeholder is gone the
     moment there is a value, which is exactly when the question gets asked.
     ⚠️ NO NEW GRID COLUMN: the suffix is painted INSIDE the field's own 72px, so the row keeps its
     density and the phone restack (which flexes on .ing-amt) is unchanged in width. */
  .ingredient-edit-row .ing-amt { position: relative; display: block; min-width: 0; }
  .ingredient-edit-row .ing-amt .ing-pct {
    width: 100%; box-sizing: border-box; padding-right: 20px;
  }
  /* ⚠️ The native spinner paints over exactly this space. Hiding it costs the mouse-only
     click-to-step affordance; ARROW-KEY stepping is untouched (it is a property of the input, not
     of the button), phones never draw one, and a 0.1-point step is not how anyone edits a formula.
     The unit is on screen at all times, which the spinner never was. */
  .ingredient-edit-row .ing-amt .ing-pct::-webkit-outer-spin-button,
  .ingredient-edit-row .ing-amt .ing-pct::-webkit-inner-spin-button {
    -webkit-appearance: none; appearance: none; margin: 0;
  }
  .ingredient-edit-row .ing-amt .ing-pct { -moz-appearance: textfield; }
  /* pointer-events:none so the suffix can never swallow a tap meant for the field it labels. */
  .ingredient-edit-row .ing-amt-unit {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    font-size: 0.75rem; line-height: 1; color: var(--muted);
    pointer-events: none; user-select: none;
  }
  /* Contextual per-ingredient line — shown only when the row needs it (e.g. water temp).
     RIGHT-ALIGNED, hanging off the row above rather than starting at the left margin: flush
     left they began where the next ingredient's NAME begins, so the eye read them as a break in
     the list instead of a continuation of the row they belong to. The 40px right pad is the
     remove-button column (32px + the 8px grid gap), so these line up with the right edge of the
     controls, not with the ✕. */
  .ing-water-temp, .ing-heldback {
    display: none; align-items: center; gap: 6px; font-size: 0.8rem;
    color: var(--muted); justify-content: flex-end; text-align: right;
    padding-right: 40px; margin-top: -2px;
  }
  .ingredient-edit-row.is-water .ing-water-temp,
  .ingredient-edit-row.is-water .ing-heldback { display: flex; }
  /* Egg rows get the same contextual line: a COUNT is what the baker holds, grams are what the
     app stores, so the two sit together rather than the count claiming a grid column that every
     non-egg row would then have to leave empty. */
  .ing-eggcount {
    display: none; align-items: center; gap: 6px; font-size: 0.8rem;
    color: var(--muted); justify-content: flex-end; text-align: right;
    padding-right: 40px; margin-top: -2px;
  }
  .ingredient-edit-row.is-egg .ing-eggcount { display: flex; }
  .ing-eggcount input { width: 64px; }
  /* VOLUME MODE, PER ROW (2026-10-24). A 28px toggle column after the amount (🥄, shown only on a row the ingredient
     database can convert); pressed, the amount cell shows the MEASURE with its unit painted as the suffix, and the detail
     line (unit, a flour's filling convention, the grams it comes to) hangs under the row in the egg line's voice. Rows not
     switched spend nothing: no line, and the toggle is the only addition. */
  .ing-vol-toggle {
    visibility: hidden; width: 28px; height: 28px; padding: 0; border: 1px solid var(--border); border-radius: 6px;
    background: var(--card); color: var(--muted); font-size: 0.85rem; line-height: 1; cursor: pointer;
  }
  /* ⚠️ visibility, not display: the toggle owns a GRID COLUMN, and a display:none item hands its cell to the tag select
     (📐 the first photograph: every unconvertible row's tag squeezed into 28px and the ✕ sitting in the link column).
     Since 2026-10-24 (the same day: "Why can I only add volume to 1 ingredient?") the toggle shows on EVERY row — muted on a
     row the database cannot convert, where a press answers why in the row instead of doing nothing silently. */
  .ingredient-edit-row .ing-vol-toggle { visibility: visible; }
  .ingredient-edit-row:not(.has-volume) .ing-vol-toggle { opacity: 0.45; }
  .ingredient-edit-row.vol-why .ing-vol-toggle { opacity: 1; }
  .ingredient-edit-row.vol-why .ing-volume { display: flex; }
  .ingredient-edit-row.vol-why .ing-vol-unit, .ingredient-edit-row.vol-why .ing-vol-conv { display: none; }
  .ingredient-edit-row.vol-mode .ing-vol-toggle { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); font-weight: 600; }
  .ingredient-edit-row .ing-amt .ing-vol-amt, .ingredient-edit-row .ing-amt .ing-vol-suffix { display: none; }
  .ingredient-edit-row.vol-mode .ing-amt .ing-pct, .ingredient-edit-row.vol-mode .ing-amt .ing-amt-unit { display: none; }
  /* min-width 0: the global text-input rule says 120px, which overflowed the 72px cell over the toggle (📐 photographed). */
  .ingredient-edit-row.vol-mode .ing-amt .ing-vol-amt { display: block; width: 100%; min-width: 0; box-sizing: border-box; padding-right: 30px; }
  .ingredient-edit-row.vol-mode .ing-amt .ing-vol-suffix {
    display: block; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    font-size: 0.7rem; line-height: 1; color: var(--muted); pointer-events: none; user-select: none;
  }
  .ing-volume {
    display: none; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 0.8rem;
    color: var(--muted); justify-content: flex-end; text-align: right;
    padding-right: 40px; margin-top: -2px;
  }
  .ingredient-edit-row.vol-mode .ing-volume { display: flex; }
  .ing-volume select { width: auto; max-width: 15em; font-size: inherit; }
  /* OPTIONAL AS A PILL INSIDE THE NAME FIELD (Jonathan, 2026-10-24: "the optional tick box on every row is super annoying
     … doesn't take up so much real estate"). The checkbox is still the stored state — visually hidden, keyboard-reachable —
     and the pill is its label, painted at the right edge of the name box the way the unit is painted inside the amount. */
  .ing-name-wrap { position: relative; min-width: 0; display: block; }
  .ing-name-wrap .ing-name { width: 100%; box-sizing: border-box; padding-right: 80px; }
  .ing-optional { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); margin: 0; display: inline-flex; align-items: center; }
  .ing-optional .ing-optional-input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
  .ing-optional-chip {
    font-size: 0.68rem; line-height: 1; padding: 3px 8px; border: 1px solid var(--border); border-radius: 999px;
    color: var(--muted); opacity: 0.7; background: var(--card); cursor: pointer; user-select: none; white-space: nowrap;
  }
  .ing-optional-chip:hover, .ing-optional-input:checked + .ing-optional-chip { opacity: 1; }
  .ing-optional-input:checked + .ing-optional-chip { background: var(--accent-dark); color: #fff; border-color: var(--accent-dark); }
  .ing-optional-input:focus-visible + .ing-optional-chip { outline: 2px solid var(--accent); outline-offset: 1px; }
  .ing-water-temp input { width: 64px; }
  .ing-heldback input { width: auto; }
  .ingredient-edit-row select {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 5px 22px 5px 6px;
    font-size: 0.85rem;
    background-color: white;   /* longhand — the shorthand would wipe the select chevron */
    color: var(--ink);
  }
  .ingredient-edit-row select.ing-tag { min-width: 0; }
  .bake-row {
    display: grid;
    grid-template-columns: 1fr 90px auto auto;
    gap: 10px;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px dashed var(--border);
  }
  .bake-row:last-child { border-bottom: none; }
  .bake-row .meta { font-size: 0.8rem; color: var(--muted); }
  .bake-recipe { padding: 10px 0; border-bottom: 1px dashed var(--border); }
  .bake-recipe:last-child { border-bottom: none; }
  .bake-recipe-head .meta { font-size: 0.8rem; color: var(--muted); margin-left: 8px; }
  .bake-recipe-compact { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
  .bake-recipe-compact .bake-recipe-head { margin: 0; }
  /* Recipe name + its first-batch deadline share a line on desktop (deadline to
     the right of the name); they stack on mobile where width is tight. */
  .bake-recipe-firstline { display: flex; align-items: center; justify-content: space-between; gap: 8px 14px; flex-wrap: wrap; }
  .bake-recipe-firstline .bake-batch-row.bake-batch-row-first { padding: 0; }
  .bake-batch-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 4px 0; }
  /* Split / extra-batch rows right-align so their count + deadline sit under Bake 1's
     (which is pushed right by the recipe name in .bake-recipe-firstline). */
  .bake-batch-extra-row { justify-content: flex-end; }
  .bake-batch-label { font-size: 0.78rem; color: var(--muted); min-width: 54px; }
  .bake-batch-row input[type="number"] { width: 72px; }
  .bake-batch-row input[type="datetime-local"] { flex: 0 1 210px; min-width: 156px; max-width: 220px; }
  /* THE DEADLINE PICKER (2026-10-19 — "we want days of the week even on mobile"). The native datetime-local stays in the DOM
     as the value contract and hides once its button exists (enhanceDeadlineInput); the button reads "📅 Sat, Oct 17 · 9:00 AM"
     and opens #dl-picker-modal. Every control here sets its own colour AND background — the base button rule paints white
     text on accent and a hover darkens it, which would turn a day number into an unreadable block. Phones never hover:
     the button carries a border at rest, the day cells are 44px targets, the disabled days are struck through. */
  input.dlp-native { display: none !important; }
  button.dlp-btn {
    display: inline-flex; align-items: center; gap: 6px; min-height: 38px; max-width: 100%;
    padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px;
    background: #fff; color: var(--ink); font: inherit; font-size: 0.95rem; text-align: left; cursor: pointer;
  }
  button.dlp-btn:hover { background: var(--accent-tint); color: var(--ink); }
  button.dlp-btn .dlp-txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  button.dlp-btn.dlp-empty { color: var(--muted); }
  body.steps-only #deadline-default-input-btn { display: none; }
  .bake-batch-row button.dlp-btn { flex: 0 1 210px; min-width: 156px; max-width: 220px; }
  .dlp-box { width: 100%; max-width: 380px; padding: 12px 14px 14px; }
  .dlp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .dlp-head h3 { margin: 0; font-size: 1rem; }
  button.dlp-x, button.dlp-x:hover { background: none; border: none; color: var(--ink); font-size: 1.2rem; min-width: 44px; min-height: 44px; padding: 0; }
  .dlp-preview { margin: 0 0 8px; font-weight: 600; font-size: 1.05rem; }
  .dlp-month { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
  .dlp-month button { min-width: 44px; min-height: 44px; font-size: 1.2rem; padding: 0; }
  .dlp-month-title { font-weight: 600; }
  .dlp-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
  .dlp-wd { text-align: center; font-size: 0.75rem; font-weight: 600; color: var(--muted); padding: 2px 0 4px; }
  button.dlp-day, button.dlp-day:hover {
    min-height: 44px; padding: 0; border: 1px solid transparent; border-radius: 8px;
    background: transparent; color: var(--ink); font: inherit; font-size: 1rem;
  }
  button.dlp-day:not(:disabled):hover { background: var(--accent-tint); }
  button.dlp-day.dlp-today { border-color: var(--accent); font-weight: 700; }
  button.dlp-day.dlp-sel, button.dlp-day.dlp-sel:hover { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 700; }
  button.dlp-day:disabled { color: var(--muted); opacity: 0.45; text-decoration: line-through; cursor: default; }
  /* The day chips and the clock dial (2026-10-20). A number on the dial is a 44 px target that takes NO pointer events: the dial
     reads the angle under a finger or a mouse (any minute on the ring), and the numbers stay buttons for a keyboard. */
  .dlp-days { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; margin-bottom: 10px; }
  button.dlp-chip, button.dlp-chip:hover {
    min-height: 44px; padding: 4px 2px; border: 1px solid var(--border); border-radius: 8px; background: #fff; color: var(--ink);
    font: inherit; font-size: 0.85rem; font-weight: 600; line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  button.dlp-chip:not(.dlp-sel):hover { background: var(--accent-tint); }
  button.dlp-chip.dlp-sel, button.dlp-chip.dlp-sel:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
  button.dlp-chip.dlp-later { white-space: normal; font-size: 0.8rem; }
  button.dlp-chip.dlp-later:not(.dlp-sel) { font-weight: 500; border-style: dashed; color: var(--accent); }
  button.dlp-chip.dlp-later.dlp-open:not(.dlp-sel) { border-style: solid; border-color: var(--accent); }
  button.dlp-link, button.dlp-link:hover { background: none; border: none; color: var(--accent); font: inherit; font-size: 0.9rem; padding: 10px 0 0; min-height: 44px; text-decoration: underline; }
  .dlp-time-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
  input.dlp-tbox { flex: 1; min-width: 0; min-height: 44px; font-size: 1.25rem; font-weight: 600; text-align: center; padding: 4px 8px; }
  input.dlp-tbox.dlp-bad { border-color: var(--danger); }
  button.dlp-kbd, button.dlp-kbd:hover { min-width: 48px; min-height: 44px; padding: 0; font-size: 1.25rem; border: 1px solid var(--border); border-radius: 8px; background: var(--accent-tint); color: var(--ink); }
  .dlp-clock { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; }
  .dlp-clock[hidden] { display: none; }
  .dlp-dial { position: relative; flex: 0 0 auto; border-radius: 50%; background: var(--accent-tint); touch-action: none; user-select: none; -webkit-user-select: none; cursor: pointer; }
  .dlp-face, .dlp-face svg { position: absolute; inset: 0; width: 100%; height: 100%; }
  .dlp-face svg { pointer-events: none; }
  .dlp-face line { stroke: var(--accent); stroke-width: 2; }
  .dlp-face .dlp-hub, .dlp-face .dlp-knob { fill: var(--accent); }
  .dlp-face .dlp-knob-dot { fill: #fff; }
  button.dlp-num, button.dlp-num:hover {
    position: absolute; width: 44px; height: 44px; padding: 0; border: none; border-radius: 50%; background: transparent; color: var(--ink);
    font: inherit; font-size: 1rem; line-height: 44px; text-align: center; pointer-events: none;
  }
  button.dlp-num.dlp-sel { color: #fff; font-weight: 700; }
  button.dlp-num:disabled { color: var(--muted); opacity: 0.4; }
  .dlp-side { display: flex; flex-direction: column; gap: 8px; }
  .dlp-seg { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
  .dlp-seg button, .dlp-seg button:hover { min-width: 58px; min-height: 44px; padding: 0 6px; border: none; border-radius: 0; background: #fff; color: var(--ink); font: inherit; font-size: 0.9rem; font-weight: 600; }
  .dlp-seg button + button { border-top: 1px solid var(--border); }
  .dlp-seg button[aria-pressed="true"], .dlp-seg button[aria-pressed="true"]:hover { background: var(--accent); color: #fff; }
  .dlp-seg button:disabled { color: var(--muted); opacity: 0.45; cursor: default; }
  .dlp-floor-note.dlp-err { color: var(--danger); }
  .dlp-floor-note { margin: 6px 0 0; font-size: 0.8rem; min-height: 1.2em; }
  .dlp-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 12px; }
  .dlp-actions button { min-height: 44px; }
  .dlp-actions .dlp-default { margin-right: auto; }
  .bake-batch-unit { font-size: 0.85rem; color: var(--muted); min-width: 46px; }
  /* ⚠️ 20px cleared the base chevron's 19px band by ONE pixel — true, and a rule nobody could
     read as deliberate. Widened to 22 so the [select-chevron] invariant has real margin here. */
  .bake-oven-select { font-size: 0.8rem; padding: 2px 22px 2px 4px; max-width: 132px; min-width: 0; }
  .bake-add-row { display: flex; flex-wrap: wrap; gap: 6px 16px; }
  .bake-add-btn { background: none; border: none; color: #8a5a2b; cursor: pointer; font-size: 0.82rem; padding: 4px 0; text-align: left; }
  .bake-add-btn:hover { text-decoration: underline; }
  .bake-batch-remove { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 4px; }
  .bake-batch-remove:hover { color: #c0392b; }
  .plan-btn {
    font-size: 0.82rem;
    padding: 5px 12px;
    border-radius: 6px;
    border: 1.5px solid var(--accent);
    background: white;
    color: var(--accent-dark);
    cursor: pointer;
    white-space: nowrap;
    font-weight: 500;
    transition: background 0.12s, color 0.12s;
  }
  .plan-btn:hover { background: var(--accent-light, #e8f0e8); }
  .plan-btn.on {
    background: var(--accent);
    color: white;
    font-weight: 600;
    border-color: var(--accent);
  }
  .plan-btn.on:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
  .plan-btn-x { margin-left: 6px; opacity: 0.5; font-weight: 700; padding: 0 3px; border-radius: 4px; }
  .plan-btn-x:hover { opacity: 1; background: rgba(0,0,0,0.12); }
  button:disabled { opacity: 0.55; cursor: default; }
  /* Empty-rotation starter cards (Bake Plan): tappable recipe cards for building a
     first rotation — .on mirrors the welcome-wizard choice styling. */
  .starter-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; margin-top: 12px; }
  .starter-card { text-align: left; background: var(--card); color: var(--ink); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; font-size: 0.9rem; cursor: pointer; }
  .starter-card:hover { background: #eef3ee; }
  .starter-card.on { border-color: var(--accent); background: #e3ebe3; }
  .starter-card .sc-name { font-weight: 600; color: var(--accent-dark); display: block; }
  .starter-card .sc-meta { display: block; font-size: 0.78rem; color: var(--muted); margin-top: 2px; }
  .plan-picker { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
  .totals {
    background: #c8dcc8;
    border: 1px solid #94b496;
    border-radius: 10px;
    padding: 16px;
  }
  .totals h2 { margin-top: 0; }
  .totals table td.num { font-weight: 600; }
  .overview-group-title { font-size: 1.25rem; margin: 0 0 4px; }
  .overview-item { padding: 12px 0; border-top: 1px solid #94b496; }
  .overview-item:first-of-type { border-top: none; padding-top: 8px; }
  .overview-item:last-child { padding-bottom: 0; }
  /* Today's kitchen conditions card */
  .cond-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 14px; margin: 4px 0 2px; }
  .cond-grid > label { display: flex; flex-direction: column; gap: 4px; font-size: 0.85rem; font-weight: 600; }
  .cond-in { display: flex; align-items: center; gap: 6px; font-weight: normal; }
  .cond-in input, .cond-in select { width: 100%; flex: 1 1 auto; }
  .cond-in input[type="number"] { max-width: 90px; }
  .cond-readout { background: #eef4ee; border: 1px solid #cfe0cf; border-radius: 8px; padding: 12px 14px; }
  .cond-water { display: flex; align-items: center; gap: 12px; }
  .cond-water-temp { font-size: 1.7rem; font-weight: 700; color: var(--accent-dark); line-height: 1; }
  .cond-water-label { font-size: 0.9rem; font-weight: 600; }
  .cond-warn { margin: 8px 0 0; font-size: 0.83rem; color: #8a5a00; background: #fff6e5; border-radius: 6px; padding: 6px 8px; }
  .pill {
    display: inline-block;
    background: #e3ebe3;
    color: var(--accent-dark);
    border-radius: 999px;
    padding: 2px 9px;
    font-size: 0.75rem;
    margin-left: 6px;
  }
  details summary {
    cursor: pointer;
    color: var(--accent-dark);
    font-size: 0.9rem;
    margin-top: 6px;
  }
  /* Admin review folds: signed-off work collapses so the panel doesn't grow every time you
     agree with it. Reads as a header, not a link, since the count is the useful part. */
  details.adm-fold > summary {
    list-style: revert; margin: 0; padding: 6px 0; font-weight: 600; font-size: 0.86rem;
    border-top: 1px solid var(--border); color: var(--text);
  }
  details.adm-fold[open] > summary { color: var(--accent-dark); }
  /* One settled row, collapsed to a single line inside a group fold. Lighter than the group
     header so the nesting reads: group → row → its text and actions. */
  details.adm-fold-sm > summary {
    list-style: revert; margin: 0; font-size: 0.8rem;
    display: list-item;                     /* keep the marker; the flex header lives inside */
  }
  details.adm-fold-sm > summary > strong { overflow-wrap: anywhere; }
  details.adm-fold-sm > summary .pill { margin-left: 4px; }
  /* Lifted wording, in the overlap pill's own colours so the flag and the phrases read as one
     warning. Admin-only: a published summary should have none of these left. */
  mark.rn-sum-lift {
    background: #fde68a; color: #92400e; border-radius: 3px; padding: 0 2px;
    box-decoration-break: clone; -webkit-box-decoration-break: clone;
  }
  /* Recipe-editor section headers (Ingredients / Steps & timing / Capacity / Kitchen) —
     one consistent header level across all four sections. */
  summary.editor-section {
    font-size: 1rem;
    font-weight: 600;
    color: var(--ink);
    margin: 4px 0;
    padding: 2px 0;
  }
  .empty {
    text-align: center;
    color: var(--muted);
    padding: 24px;
  }
  .footer-note {
    text-align: center;
    color: var(--muted);
    font-size: 0.8rem;
    margin-top: 24px;
  }
  .sync-badge {
    font-size: 0.75rem;
    padding: 2px 8px;
    border-radius: 10px;
    border: 1px solid currentColor;
    display: inline-block;
    margin-top: 4px;
    opacity: 0.85;
  }
  /* The sync progress line (renderSyncProgress): non-modal, never a hit target. z-index 950 — a
     rung above the live step view (900), below the whole modal ladder (1000+). */
  .sync-progress { position: fixed; top: 0; left: 0; right: 0; z-index: 950; pointer-events: none;
    display: flex; justify-content: center; padding-top: env(safe-area-inset-top); }
  .sync-progress .sp-line { position: absolute; top: 0; left: 0; right: 0; height: 3px; overflow: hidden;
    background: color-mix(in srgb, var(--accent) 22%, transparent); }
  .sync-progress .sp-line::after { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 35%;
    background: var(--accent); animation: sp-slide 1.2s ease-in-out infinite; }
  @keyframes sp-slide { from { transform: translateX(-100%); } to { transform: translateX(290%); } }
  @media (prefers-reduced-motion: reduce) { .sync-progress .sp-line::after { animation: none; width: 100%; opacity: 0.6; } }
  .sync-progress .sp-label { margin-top: 6px; font-size: 0.78rem; line-height: 1.2; padding: 3px 10px;
    border-radius: 999px; background: var(--card); color: var(--muted); border: 1px solid var(--border);
    box-shadow: 0 2px 8px rgba(0,0,0,0.08); max-width: calc(100vw - 32px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  @media print { .sync-progress { display: none !important; } }
  /* Header sign-in pill — the signed-out counterpart of the sync badge */
  .header-signin {
    font-size: 0.8rem;
    padding: 3px 14px;
    border-radius: 999px;
    background: transparent;
    color: var(--accent-dark);
    border: 1px solid var(--accent);
    margin-top: 4px;
  }
  .header-signin:hover { background: var(--accent); color: #fff; }
  /* New-share banner — surfaces "someone shared a recipe with you" app-wide */
  .share-alert {
    display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap;
    max-width: 760px; margin: 12px auto 0; padding: 8px 14px;
    background: var(--card); border: 1px solid var(--accent); border-radius: 10px;
    font-size: 0.9rem; color: var(--accent-dark);
  }
  .share-alert .share-alert-dismiss {
    background: none; border: none; color: var(--muted); cursor: pointer;
    font-size: 0.9rem; padding: 2px 6px; flex: none;
  }
  /* Steps-only mode: deadline-driven controls hide; the schedule reads as elapsed steps */
  body.steps-only #deadline-default-input,
  body.steps-only #deadline-override-note,
  body.steps-only #plan-deadline-override-note,
  body.steps-only #recipe-deadlines-details,
  body.steps-only .plan-recipe-deadlines,
  body.steps-only [id^="deadline-"][id$="-row"]:not(#deadline-default-row),
  body.steps-only [id^="bake-time-"][id$="-row"] { display: none !important; }
  body.steps-only #deadline-steps-note { display: inline !important; }
  .steps-only-banner {
    background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--accent);
    border-radius: 8px; padding: 8px 12px; margin: 0 0 10px; font-size: 0.88rem;
  }
  .schedule-time-plain { font-variant-numeric: tabular-nums; }
  /* An untimed card says WHEN in words, and the words are not a clock — smaller, muted, wrapping,
     so the time column does not read as a very long timestamp. Prints the same: paper has no
     clock for it either. */
  .schedule-time-untimed, .bs-tspan-untimed {
    font-variant-numeric: normal; font-size: 0.78rem; color: var(--muted);
    font-style: italic; white-space: normal;
  }
  .settings-section label { min-width: 120px; }
  .settings-section input[type="password"],
  .settings-section input[type="text"] { flex: 1; min-width: 180px; }
  .settings-hint { font-size: 0.8rem; color: var(--muted); margin: 3px 0 10px 130px; }
  /* ---- Inline invalid state on the equipment add forms (reportFieldErrors) ---------------
     🔴 A REST-STATE SIGNAL, not a hover or a focus one: the baker who needs this has just
     pressed Save and is looking at the form with nothing under the cursor. The field carries a
     red border AND a ring, and the sentence sits on its own full-width line inside the same
     .row, so the message and the control it is about are never on different screens.
     ⚠️ Colour is the SECOND signal. The note is words, and the field carries aria-invalid, so
     the state survives a reader who cannot separate the red from the beige. */
  .field-invalid,
  select.field-invalid,
  input.field-invalid {
    border-color: var(--danger) !important;
    box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.16);
    background-color: #fdf7f6;
  }
  .field-invalid:focus { border-color: var(--danger) !important; box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.24); }
  /* flex-basis 100% puts the note on its own line INSIDE the row it belongs to, under the field,
     rather than squeezing in beside the "min"/"g" suffix and breaking the reading order. */
  .field-error {
    flex: 1 1 100%;
    width: 100%;
    font-size: 0.82rem;
    line-height: 1.35;
    color: var(--danger);
    margin: 4px 0 0;
  }
  /* The forms' fixed-width labels indent the field; the note lines up under the field, not the
     label — except on phones, where the media query neutralizes those widths anyway. */
  .card .row > .field-error { margin-left: 0; }
  hr.sep { border: none; border-top: 1px solid var(--border); margin: 12px 0; }
  #deadline-loaf-input {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 8px;
    font-size: 0.95rem;
    background: white;
    color: var(--ink);
    font-family: inherit;
  }
  .schedule-inputs-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 18px;
    row-gap: 10px;
    margin-top: 12px;
  }
  /* Collapsible "Manual Schedule Controls" section on the Schedule tab. */
  details#schedule-controls-details { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 10px; }
  .schedule-section-summary {
    cursor: pointer; font-weight: 700; font-size: 0.95rem; color: var(--accent-dark);
    list-style: none; display: flex; align-items: center; gap: 7px; user-select: none;
  }
  .schedule-section-summary::-webkit-details-marker { display: none; }
  .schedule-section-summary::before { content: '▸'; font-size: 0.8em; transition: transform 0.12s ease; }
  details[open] > .schedule-section-summary::before { transform: rotate(90deg); }
  .schedule-inputs-grid.single-col,
  .schedule-inputs-grid.cols-1 { grid-template-columns: 1fr; }
  .schedule-inputs-grid.cols-2 { grid-template-columns: 1fr 1fr; }
  .schedule-inputs-grid.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
  .schedule-inputs-grid.cols-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
  .schedule-inputs-grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
  .schedule-inputs-grid.cols-6 { grid-template-columns: repeat(6, 1fr); }
  .schedule-input-col-header {
    font-weight: 600;
    font-size: 0.8rem;
    color: var(--accent-dark);
    letter-spacing: 0.3px;
    text-transform: uppercase;
    margin-bottom: 4px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--border);
  }
  .schedule-input-col .row { margin-top: 6px; }
  .schedule-input-col .row:first-of-type { margin-top: 0; }
  /* Per-process deadline & bake-time rows are superseded by the default deadline +
     per-recipe overrides; keep the inputs in the DOM (scheduler still reads them as
     fallbacks) but hide them. Cold-proof rows stay visible. */
  #schedule-inputs-grid [id^="deadline-"][id$="-row"],
  #schedule-inputs-grid [id^="bake-time-"][id$="-row"] { display: none !important; }
  .schedule-input-col label { min-width: 88px; font-size: 0.9rem; }
  .schedule-input-col .settings-select,
  .schedule-input-col input[type="datetime-local"] { flex: 1; min-width: 0; }
  @media (max-width: 600px) {
    .schedule-inputs-grid,
    .schedule-inputs-grid.cols-2,
    .schedule-inputs-grid.cols-3,
    .schedule-inputs-grid.cols-4,
    .schedule-inputs-grid.cols-5,
    .schedule-inputs-grid.cols-6 { grid-template-columns: 1fr; }
  }
  .schedule-day { margin-bottom: 16px; }
  .schedule-day:last-child { margin-bottom: 0; }
  .schedule-day-header {
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--accent-dark);
    border-bottom: 1px solid var(--border);
    padding-top: 4px;
    padding-bottom: 4px;
    margin-bottom: 6px;
    letter-spacing: 0.3px;
    position: sticky;
    top: 0;
    z-index: 4;
    background: var(--card);
  }
  .schedule-day-header-with-cols {
    top: 38px;
  }
  .schedule-row {
    display: grid;
    grid-template-columns: 78px 1fr;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px dashed var(--border);
  }
  .schedule-row:last-child { border-bottom: none; }
  .schedule-hour-row {
    display: grid;
    grid-template-columns: 56px 1fr;
    column-gap: 10px;
    padding: 8px 0;
    border-bottom: 1px solid var(--border);
    align-items: start;
  }
  .schedule-hour-row:last-child { border-bottom: none; }
  .schedule-hour-header-row {
    border-bottom: 1px solid var(--border);
    padding-bottom: 4px;
    margin-bottom: 2px;
  }
  .schedule-sticky-header {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--card);
    padding-top: 6px;
    padding-bottom: 6px;
    margin-bottom: 8px;
    box-shadow: 0 1px 0 var(--border);
  }
  .schedule-hour-label {
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--accent-dark);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.3px;
    padding-top: 2px;
  }
  .schedule-hour-content {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .schedule-col-header {
    font-weight: 600;
    font-size: 0.8rem;
    color: var(--accent-dark);
    letter-spacing: 0.3px;
    text-transform: uppercase;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 7px;
  }
  .schedule-col-names { min-width: 0; }
  /* Far-left icon split/merge controls, vertically centered in the column header. */
  .schedule-col-iconbtn {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; padding: 0; border-radius: 6px;
    border: 1px solid transparent; background: transparent; color: var(--accent-dark);
    cursor: pointer; opacity: 0.7; transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
  }
  .schedule-col-iconbtn:hover { opacity: 1; background: var(--accent-dark); color: #fff; }
  .schedule-col-header:hover .schedule-col-iconbtn { opacity: 1; }
  /* Instant styled hover tooltip for the split/merge controls. */
  .schedule-col-iconbtn { position: relative; }
  .schedule-col-iconbtn::after {
    content: attr(data-tip);
    position: absolute; top: calc(100% + 7px); left: 0;
    background: var(--accent-dark, #2b2b2b); color: #fff;
    padding: 6px 9px; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.25);
    font-size: 0.72rem; font-weight: 500; line-height: 1.25;
    text-transform: none; letter-spacing: normal; white-space: nowrap;
    opacity: 0; transform: translateY(-2px); pointer-events: none;
    transition: opacity 0.12s ease, transform 0.12s ease; z-index: 50;
  }
  .schedule-col-iconbtn:hover::after, .schedule-col-iconbtn:focus-visible::after {
    opacity: 1; transform: translateY(0);
  }
  .schedule-hour-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 12px;
    row-gap: 6px;
    align-items: start;
  }
  .schedule-bucket-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .schedule-event {
    padding: 4px 0;
    border-bottom: 1px dashed var(--border);
  }
  .schedule-bucket-cell .schedule-event:last-child { border-bottom: none; }
  .schedule-event-shared {
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 10px;
  }
  /* P5: another bake's step on the kitchen's schedule. It must READ as something you cannot edit
     here — a dashed edge and a muted ground — because a card that looks editable and does nothing
     is worse than one that is plainly somebody else's. */
  .schedule-event-other,
  .schedule-row-other {
    border-style: dashed;
    background: color-mix(in srgb, var(--border) 14%, transparent);
  }
  .schedule-row-other { border: 1px dashed var(--border); border-radius: 6px; }
  .schedule-event-other .schedule-title, .schedule-row-other .schedule-title { font-weight: 500; }
  /* ⚠️ NOT `nowrap`. A bake called "Wednesday market prep" in a narrow column overflowed its own
     pill and was clipped mid-word — the chip has to be able to wrap. */
  .schedule-other-chip {
    display: inline-block; font-size: 0.72rem; border: 1px solid var(--border); border-radius: 10px;
    padding: 0 6px; max-width: 100%; opacity: 0.85;
  }
  /* LIVE BATCH 5: …and the bake in the editor names itself on the same kitchen, because two bakes
     of one recipe have identical step titles and an unnamed row is indistinguishable from the
     other bake's. QUIETER than the chip above on purpose — no border, so the rows you can act on
     do not grow a second frame — and it renders only when another bake is on the board. */
  .schedule-own-chip {
    display: inline-block; font-size: 0.72rem; color: var(--muted);
    font-weight: 500; max-width: 100%;
  }
  /* Per-column breakdown shown inside a shared step (e.g. which loaves of each
     dough go on which deck for a shared bake). Aligns with the schedule columns. */
  .schedule-shared-cols {
    display: grid;
    column-gap: 12px;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px dashed var(--border);
  }
  .schedule-shared-col { font-size: 0.78rem; line-height: 1.3; white-space: pre-line; }
  .schedule-shared-col-empty { opacity: 0.3; }
  .schedule-controls { display: flex; justify-content: flex-end; margin-bottom: 10px; }
  .schedule-split-toggle {
    border: 1px solid var(--border); background: var(--surface, #fff); color: var(--accent-dark);
    border-radius: 6px; padding: 5px 11px; font-size: 0.82rem; font-weight: 600; cursor: pointer;
  }
  .schedule-split-toggle:hover { border-color: var(--accent-dark); }
  .schedule-split-toggle.is-on { background: var(--accent-dark); color: #fff; border-color: var(--accent-dark); }
  .schedule-day-grid {
    display: grid;
    grid-template-columns: 56px 1fr;
    column-gap: 10px;
    row-gap: 6px;
    align-items: start;
  }
  /* Wide split views (≥5 columns) scroll horizontally; keep the left time gutter pinned. */
  .schedule-hscroll { overflow-x: auto; overflow-y: visible; padding-bottom: 6px; }
  .schedule-hscroll .schedule-hour-label,
  .schedule-hscroll .schedule-hour-header-row > div:first-child {
    position: sticky; left: 0; z-index: 3; background: var(--card);
  }
  .schedule-day-grid .schedule-hour-label {
    padding-top: 4px;
  }
  @media (max-width: 640px) {
    .schedule-day-grid {
      grid-template-columns: 64px 1fr;
      column-gap: 6px;
    }
  }
  .schedule-time-clickable {
    cursor: pointer;
    text-decoration: underline dotted;
    text-decoration-color: var(--muted);
    text-underline-offset: 2px;
  }
  .schedule-time-clickable:hover {
    color: var(--accent);
  }
  /* Long-press a step to pick it up, then slide up/down to shift its time.
     On touch devices, suppress the OS text-selection callout so the long-press
     feels clean; desktop keeps normal text selection. */
  @media (hover: none) and (pointer: coarse) {
    #schedule-output.move-step-on .schedule-row, #schedule-output.move-step-on .schedule-event {
      -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
    }
  }
  .move-step-switch { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 8px; }
  .mill-view-switch { display: inline-flex; gap: 0; margin: 0 0 0.3rem; font-size: 0.72rem; }
  .mill-view-btn { padding: 0.1rem 0.5rem; border: 1px solid var(--border, #ccc); background: transparent; color: inherit; border-radius: 0; cursor: pointer; font-size: inherit; }
  .mill-view-btn:first-child { border-radius: 6px 0 0 6px; }
  .mill-view-btn:last-child { border-radius: 0 6px 6px 0; border-left: none; }
  .mill-view-btn.on { background: var(--accent-dark); color: #fff; border-color: var(--accent-dark); }
  .move-step-btn { min-height: 36px; padding: 6px 12px; border-radius: 18px; border: 1px solid var(--border);
    background: var(--card); color: var(--text); font-size: 0.85rem; font-weight: 600; cursor: pointer; }
  .move-step-btn[aria-pressed="true"] { background: var(--accent-dark); color: #fff; border-color: var(--accent-dark); }
  .schedule-step-dragging {
    position: relative; z-index: 20; background: var(--card);
    border-radius: 8px; box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28); opacity: 0.97;
    will-change: transform;
  }
  /* Steps that will move with the picked-up step (everything at/after its time). */
  .schedule-step-willshift { opacity: 0.4; transition: opacity 0.12s ease; }
  /* Floating preview while dragging a step's time. */
  .anchor-drag-bubble {
    position: fixed; z-index: 2000; pointer-events: none; white-space: nowrap;
    background: var(--accent-dark); color: #fff; font-size: 0.82rem; font-weight: 600;
    padding: 4px 9px; border-radius: 7px; box-shadow: 0 3px 12px rgba(0, 0, 0, 0.28);
  }
  .anchor-drag-bubble .adb-delta { font-weight: 400; opacity: 0.85; margin-left: 5px; }
  .anchor-drag-bubble .adb-day { font-weight: 700; margin-right: 6px; padding-right: 6px; border-right: 1px solid rgba(255, 255, 255, 0.35); }
  .schedule-time-anchored {
    color: var(--accent);
    font-weight: 700;
  }
  .schedule-time-anchored::after {
    content: ' ⚓';
    font-size: 0.75em;
  }
  .modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 16px;
  }
  /* A small inline spinner — first used by the transcript modal's reading state. */
  .bm-spin {
    width: 18px; height: 18px; flex: 0 0 auto;
    border: 3px solid var(--border); border-top-color: var(--ink);
    border-radius: 50%;
    animation: bmspin 0.9s linear infinite;
  }
  @keyframes bmspin { to { transform: rotate(360deg); } }
  /* Indeterminate progress bar — first used by the import panel's reading state. The edge
     function is one opaque call, so the bar shows MOTION, not completion; the narrated line
     under it (setImportBusy) is what changes over time. */
  .bm-progress {
    height: 6px; border-radius: 3px; background: var(--border);
    position: relative; overflow: hidden;
  }
  .bm-progress::after {
    content: ''; position: absolute; top: 0; bottom: 0; left: -40%; width: 40%;
    border-radius: 3px; background: var(--accent);
    animation: bmslide 1.3s ease-in-out infinite;
  }
  @keyframes bmslide { to { left: 100%; } }
  /* 🔴 DETERMINATE, because the count behind it is REAL (P0 guarantees the evaluated count
     and its denominator are monotone and never exceeded). The sliding bar above says "something
     is happening"; this one says how far, which is the whole point of a wait that must read as
     calculated rather than as lag. Same height, radius and accent as its indeterminate sibling —
     one visual voice — and the fill never animates backwards because the number never does. */
  .bm-progress-det { height: 6px; border-radius: 3px; background: var(--border);
    position: relative; overflow: hidden; }
  .bm-progress-det > i { display: block; height: 100%; width: 0; border-radius: 3px;
    background: var(--accent); transition: width .12s linear; }
  /* The selection cards (P3): one box per schedule on offer. The CURRENT one is what you have,
     the RECOMMENDED one is what the search found, the rest are trades — and every one of them
     carries its own apply, because nothing here commits until a baker picks it. */
  .sched-opt-card { margin: 8px 0 0; padding: 8px 10px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--card-bg, #fff); }
  .sched-opt-card.sched-opt-current { background: transparent; }
  .sched-opt-card.sched-opt-best { border-color: var(--accent); }
  .sched-opt-head { font-size: 0.92rem; margin: 0 0 2px; }
  .sched-opt-gain, .sched-opt-cost { font-size: 0.85rem; display: block; }
  .sched-opt-cost { color: var(--muted); }
  /* P3 of PLAN-start-time: when the schedules on offer BEGIN materially far apart, the card
     asks about it ONCE above the cards, and stamps each in-scope card with its own start so the
     comparison the header invites can be made by scanning rather than by reading five sentences.
     ⚠️ No clamp, no ellipsis and no overflow:hidden anywhere here — a clock is short, and a
     clipped one on the printed bake sheet has no hover and no tap to recover it (CLAUDE.md's
     line-clamp rule). The chip therefore wraps rather than truncating. */
  /* File drop zone — the styled face of a hidden <input type=file>. Click opens the picker,
     drag-and-drop hands the files to the same onchange handler. First used by the import
     panel's saved-page and photos inputs. */
  .bm-dropzone {
    border: 2px dashed var(--border);
    border-radius: 8px;
    padding: 16px 14px;
    text-align: center;
    cursor: pointer;
    color: var(--muted);
    font-size: 0.9rem;
    background: var(--card);
    transition: border-color 0.15s ease, background 0.15s ease;
  }
  .bm-dropzone:hover, .bm-dropzone:focus-visible { border-color: var(--accent); }
  .bm-dropzone.dragover { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-dark); }
  /* A zone holding a file says so — solid accent border, tinted, named. The invitation state
     is for empty zones only. */
  .bm-dropzone.hasfile { border-style: solid; border-color: var(--accent); background: var(--accent-tint); color: var(--accent-dark); }
  /* Long-press tooltip: title attrs don't hover on touch devices */
  .longpress-tip {
    position: fixed;
    z-index: 1200;
    background: var(--ink);
    color: var(--bg);
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 0.85rem;
    line-height: 1.3;
    max-width: 80vw;
    pointer-events: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  }
  /* ---- the CHOICE dialog (2026-10-10): named decisions in a column, an ✕ in the corner as the only "neither" ---- */
  .app-choice { position: relative; }
  .app-choice-x { position: absolute; top: 8px; right: 8px; width: 32px; height: 32px; padding: 0; border-radius: 50%;
    border: 1px solid var(--border); background: var(--card); color: var(--muted); font-size: 1.1rem; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
  .app-choice-x:hover { color: var(--ink); background: var(--bg); }
  .app-choice-list { display: flex; flex-direction: column; gap: 8px; }
  .app-choice-btn { display: flex; flex-direction: column; align-items: flex-start; text-align: left; gap: 2px;
    padding: 10px 12px; min-height: 44px; width: 100%; white-space: normal; }
  .app-choice-label { font-weight: 600; }
  .app-choice-hint { font-size: 0.8rem; font-weight: 400; opacity: 0.85; }
  .modal-content {
    background: var(--card);
    padding: 18px 20px 16px;
    border-radius: 10px;
    max-width: 420px;
    width: 100%;
    border: 1px solid var(--border);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .modal-content h3 { margin: 0 0 8px; font-size: 1.05rem; }
  /* ---- Crop-on-upload (shared by every photo upload surface) ----------------
     The stage clips the crop box's giant box-shadow (the outside-the-crop shade);
     the HANDLES live one level up, on .crop-outer, so they are not clipped when
     the box reaches the image's edge — which is exactly where the default box
     sits. Their positions are set by cropRender(), not by CSS. */
  .crop-outer { position: relative; display: inline-block; max-width: 100%;
    touch-action: none; user-select: none; -webkit-user-select: none; }
  .crop-stage { position: relative; overflow: hidden; line-height: 0; }
  .crop-stage img { max-width: 100%; max-height: 56vh; display: block; }
  .crop-box { position: absolute; box-sizing: border-box; cursor: move;
    border: 2px solid #fff; outline: 1px solid rgba(0, 0, 0, 0.55);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45); }
  /* 26px touch targets with a smaller drawn square inside — phones never hover. */
  .crop-handle { position: absolute; width: 26px; height: 26px; box-sizing: border-box; }
  .crop-handle::after { content: ''; position: absolute; inset: 6px; border: 2px solid #fff;
    background: rgba(17, 17, 17, 0.55); border-radius: 3px; }
  .crop-h-nw { cursor: nwse-resize; }
  .crop-h-ne { cursor: nesw-resize; }
  .crop-h-sw { cursor: nesw-resize; }
  .crop-h-se { cursor: nwse-resize; }
  /* Community notes on a recipe */
  .rn-list { display: flex; flex-direction: column; gap: 6px; }
  .rn-note { border-left: 2px solid var(--border); padding: 3px 0 3px 8px; }
  .rn-note-hidden { opacity: 0.55; }
  .rn-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 0.8rem; }
  .rn-head .muted { font-size: 0.75rem; }
  .rn-actions { margin-left: auto; display: flex; gap: 4px; }
  .rn-body { font-size: 0.86rem; line-height: 1.45; white-space: pre-wrap; margin-top: 1px; }
  .note-oncopy { font-size: 0.72rem; color: var(--muted); font-style: italic; }
  .rn-compose { margin-top: 8px; }
  .rn-compose textarea, .rn-replybox textarea { width: 100%; font: inherit; font-size: 0.86rem;
    padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; resize: vertical; }
  .rn-thread + .rn-thread { margin-top: 4px; }
  .rn-replies { margin-left: 14px; border-left: 2px solid var(--accent-light, #e8f0e8);
    padding-left: 8px; display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
  .rn-replyrow { margin: 3px 0 0 10px; }
  .rn-replybox { margin: 4px 0 0 10px; }
  /* Seeded summary of the SOURCE page's discussion. Styled deliberately UNLIKE .rn-note — a
     tinted card, not a border-left row — because the one thing it must never look like is a
     baker note. Same reason it carries an "AI summary" pill and its own heading. */
  .rn-summary { margin-top: 10px; padding: 8px 10px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--surface-alt, #f7f7f4); }
  .rn-summary-pending { border-style: dashed; opacity: 0.92; }
  .rn-sum-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    font-weight: 600; font-size: 0.85rem; margin-bottom: 4px; }
  .rn-sum-head .pill { font-weight: 400; }
  /* overflow-wrap: this text is model-written from arbitrary internet comments, so an unbroken
     token (a hash, a product code, a run-together word) is possible. Real URLs and long source
     keys already wrap; this stops the pathological case scrolling the panel sideways. */
  .rn-sum-body { font-size: 0.86rem; line-height: 1.45; overflow-wrap: anywhere; }
  .rn-sum-list { margin: 2px 0 0; padding-left: 18px; }
  .rn-sum-list li { margin: 2px 0; }
  /* Printable recipe card — the "what goes on it" picker */
  .rc-pick-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 14px; }
  .rc-pick {
    display: flex; align-items: flex-start; gap: 8px; padding: 6px 8px;
    border-radius: 6px; cursor: pointer; line-height: 1.3;
  }
  .rc-pick:hover { background: var(--accent-light, #eef2ee); }
  .rc-pick input { margin: 2px 0 0; flex: 0 0 auto; }
  .rc-pick-text { display: flex; flex-direction: column; min-width: 0; }
  .rc-pick-label { font-size: 0.88rem; font-weight: 600; }
  .rc-pick-hint { font-size: 0.75rem; color: var(--muted, #6b7280); }
  .rc-pick-off { opacity: 0.5; cursor: default; }
  .rc-pick-off:hover { background: none; }
  @media (max-width: 520px) { .rc-pick-grid { grid-template-columns: 1fr; } }
  /* Choosing a cover photo from the recipe's own source page (PLAN-recipe-cover-photos.md).
     ⚠️ A REST SURFACE, not a :hover one — phones never hover, and a tile that looks like nothing
     until you touch it is a control nobody finds. The border and the ground are there at rest. */
  .src-photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; }
  .src-photo-pick {
    display: block; padding: 0; margin: 0; width: 100%; cursor: pointer;
    background: var(--panel, #f6f7f6); border: 1px solid var(--border, #d5d9d5);
    border-radius: 8px; overflow: hidden; line-height: 0;
  }
  .src-photo-pick img { width: 100%; height: 110px; object-fit: cover; display: block; }
  .src-photo-pick:hover, .src-photo-pick:focus-visible { border-color: var(--accent, #3f6f4a); }
  .src-photo-pick[disabled] { opacity: 0.5; cursor: progress; }
  /* The same tiles at review size — the import preview is a card, not an overlay, so the strip is
     shorter and scrolls sideways rather than growing the card by three rows.
     ⚠️ A REST SURFACE here too: the tiles carry their border and ground with nothing hovered. */
  .imp-cover-grid { display: grid; grid-auto-flow: column; grid-auto-columns: 116px;
    gap: 8px; overflow-x: auto; padding-bottom: 4px; }
  .imp-cover-grid .src-photo-pick img { height: 84px; }
  /* The page's OWN claim, marked so the first tiles are not merely first. */
  .imp-cover-declared { border-color: var(--accent, #3f6f4a); box-shadow: 0 0 0 1px var(--accent, #3f6f4a); }
  .imp-cover-cap { line-height: 1.2; font-size: 0.7rem; padding: 3px 4px; text-align: center; }
  .imp-cover-chosen { display: flex; gap: 10px; align-items: flex-start; }
  .imp-cover-chosen img { width: 96px; height: 72px; object-fit: cover; border-radius: 6px;
    border: 1px solid var(--border, #d5d9d5); flex: none; }
  /* Bakers & hours paint grid (PLAN-two-bakers.md P2). When2meet-style: drag paints,
     the first cell decides paint-vs-erase, the other bakers' hours show as an underlay
     so overlap is visible while painting. touch-action none ONLY on the grid — the
     page keeps scrolling everywhere else. */
  /* The per-day range rows — the primary hours control; the grid below refines them.
     ⚠️ Every affordance here has a REST surface (`.secondary` borders): the remove and
     add controls are the whole interaction on a phone, and phones never hover. */
  .avail-rows { border-top: 1px solid var(--border); margin: 0 0 4px; }
  .avail-row { display: flex; align-items: flex-start; gap: 6px; padding: 5px 0; border-bottom: 1px solid var(--border); }
  .avail-row-day { width: 30px; flex: none; font-size: 0.85rem; font-weight: 600; padding-top: 6px; }
  .avail-row-times { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 6px; flex: 1; min-width: 0; }
  .avail-range { display: inline-flex; align-items: center; gap: 3px; }
  /* ⚠️ Tight on purpose: a one-range day has to fit ONE line on a phone (day + two pickers
     + remove + add ≈ 320px inside a card at 390 CSS px). Loosen these and every row wraps,
     which is how the block gets tall enough to hide the grid below it. */
  /* 🔴 SPECIFICITY: `select.settings-select` (padding-right 26px, font 0.95rem) is declared
     LATER in this stylesheet, so a bare `.avail-time` rule loses every property it shares —
     the pickers rendered 120px wide and every row wrapped. Match on both classes. */
  select.settings-select.avail-time { padding: 3px 16px 3px 5px; font-size: 0.82rem; background-position: right 4px center; }
  .avail-row-times .tiny { padding: 3px 7px; }
  /* The calendar's own control bar. It carries a REST SURFACE by construction (the buttons are
     .secondary, bordered) because this is now the only way to the grid and phones never hover. */
  .avail-cal-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0 6px; }
  /* 🔴 SAY IT, don't merely dim it: a reader who cannot see the colour difference (or is looking
     at one screenshot) still gets the sentence. The dim is the second signal, not the only one. */
  .avail-cal-stale { font-size: 0.8rem; font-weight: 600; color: var(--danger);
    background: #fdece9; border: 1px solid var(--danger); border-radius: 6px; padding: 2px 8px; }
  /* 🔴 THE SAME RULE, ONE SURFACE OVER: a search result that no longer describes the plan is
     not an answer. Dimmed AND said in words AND its buttons disabled — the dim is the second
     signal, never the only one (a reader who cannot see it still gets the sentence). */
  .sched-opt-stale { opacity: 0.45; }
  .sched-opt-stale button { cursor: not-allowed; }
  .sched-opt-staleline { display: block; font-size: 0.8rem; font-weight: 600; color: var(--danger);
    background: #fdece9; border: 1px solid var(--danger); border-radius: 6px; padding: 4px 8px; margin: 0 0 8px; opacity: 1; }
  .avail-grid { display: grid; grid-template-columns: 44px repeat(7, 1fr); gap: 1px; background: var(--border);
    border: 1px solid var(--border); border-radius: 8px; overflow: hidden; touch-action: none; user-select: none; max-width: 560px; }
  /* A grid that no longer describes the hours above it is not a control: dimmed, and its cells
     refuse the gesture (the JS guard is the real one — this is what makes the refusal legible). */
  .avail-grid.stale { opacity: 0.42; }
  .avail-grid.stale .avail-cell { cursor: not-allowed; }
  .avail-grid .ag-head { background: var(--card-bg, #fff); font-size: 0.72rem; font-weight: 600; text-align: center; padding: 4px 0; color: var(--muted); }
  .avail-grid .ag-hour { background: var(--card-bg, #fff); font-size: 0.68rem; color: var(--muted); text-align: right; padding-right: 4px; line-height: 1; display: flex; align-items: start; justify-content: flex-end; }
  .avail-cell { display: block; width: 100%; height: 9px; padding: 0; margin: 0; border: none; border-radius: 0; background: #f6f3ec; cursor: pointer; min-height: 0; }
  .avail-cell.hr0 { box-shadow: inset 0 1px 0 rgba(0,0,0,0.07); }
  .avail-cell.under { background: #e4dfc9; }
  .avail-cell.on { background: var(--accent); }
  .avail-cell.on.under { background: #2e6b45; }
  .avail-cell:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: -2px; }
  /* color: the base button rule paints white text, which on a transparent chip (the bakers not selected) and on the
     pale selected one was unreadable (2026-10-13, Jonathan's phone). */
  .baker-tab { border: 1px solid var(--border); background: transparent; color: var(--ink); border-radius: 16px; padding: 4px 12px; font-size: 0.85rem; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
  .baker-tab.on { border-color: var(--accent); background: #e3ebe3; font-weight: 600; }
  .baker-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; flex: none; }
  /* The per-step picker (P4c). A REST SURFACE, because this is the whole interaction on a phone
     and phones never hover: the select keeps .settings-select's border and white ground, and the
     dot beside it says who has the step at a glance. */
  .baker-pick { display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; vertical-align: middle; }
  .baker-pick .baker-dot { width: 7px; height: 7px; }
  /* 🔴 SPECIFICITY, the same trap the hours pickers hit: `select.settings-select` is declared later
     with padding-right 26px, and `.settings-select` with font-size 0.95rem — a bare class rule here
     would lose and the picker would render full-size on every schedule row. */
  /* 🔴 THE CHEVRON IS OURS, NOT THE BROWSER'S — so the right padding is not decoration, it is the
     gutter that glyph is painted into. The base `select` rule sets `appearance:none` and draws a
     10px chevron at `right 9px center`, which occupies the band 9–19px in from the right edge; a
     select therefore needs **19px** of padding-right before its text is clear of it, and this rule
     was giving it **6**. 📐 Reported live 2026-08-27 (*"for the baker selector on the schedule,
     the dropdown arrow obscures the names making it seem like there's a typo"*) and measured on a
     1280px desktop: the option read "auto · Jonathan" in a 99px box with 85px of content width for
     85px of text, and the chevron sat on the last **10.5px of the name** — the tail of a name
     under a ✓-shaped glyph is exactly what reads as a misspelling. `text-overflow: ellipsis` was
     hidden under it too, so a truncated name did not even show that it had been truncated.
     ⇒ Pull the chevron in (right 4px, as .avail-time already does) and reserve 17px, which clears
     its 14px band by 3. The 15px this costs is given back to the name via max-width, so the
     legible room goes UP: 150 − 6 − 6 − 2 = 136px of box holding 123px of visible name, against
     165 − 6 − 17 − 2 = 140px all of which is legible.
     ⚠️ The invariant is pinned generically in dom.test.js [select-chevron] — every laid-out select
     must reserve padding-right for its OWN chevron — because this is the third compact-select rule
     in this file and the next one will be written by someone who has not read this comment. */
  select.settings-select.baker-step-select { padding: 1px 17px 1px 6px; font-size: 0.72rem; max-width: 165px; background-position: right 4px center; }
  /* A PINNED step reads differently at rest — this is the one bit of state the row carries that
     nothing else says, and it must be legible without touching anything. */
  .baker-pick-pinned select.settings-select.baker-step-select {
    border-color: var(--accent); background-color: #e3ebe3; font-weight: 600; }
  /* Deep-link flash: a warning that points at one control inside a long form has to
     say WHICH control, not just which section — the section it lives in holds several. */
  @keyframes editorFocusFlash { 0%, 100% { background: transparent; } 25%, 60% { background: #FBEFC9; } }
  .editor-focus-flash { animation: editorFocusFlash 1.9s ease-in-out 1; border-radius: 6px; }
  /* The cost on a recipe card is a door to the Prices tab: dotted, in the summary's own colour, so it reads as a figure
     that can be asked about rather than as a navigation link shouting from a meta line. */
  .recipe-summary .rc-prices-door { color: inherit; text-decoration: underline dotted; text-underline-offset: 2px; }
  .recipe-summary .rc-prices-door:hover { color: var(--accent); }
  /* ---- The tour's own classes: WHAT SURVIVED THE MODAL WIZARD ----------------------------
     The tour is a cross-tab coach now (#setup-coach, styled near the top of this sheet), and the
     coach owns the shell, the kicker, the body and the nav row. So the modal era's .welcome-shell
     (+ its h3/p descendants), .welcome-kicker, .welcome-nav, the .welcome-dots pager, the
     .welcome-card-anim-* classes and their welcomeSlideNext/Back keyframes had nothing left
     writing them and are gone. ⚠️ .welcome-chips went with them, and it is named in the
     button.secondary.active comment above as one of the three places that rule was written out
     longhand — that comment is history, not a pointer, so it still reads true.
     Every name below was grepped across the whole file first: each one is still emitted by
     paintTourCard or renderTourSignInLead. */
  .welcome-glyph { font-size: 2.1rem; line-height: 1; margin: 0 0 10px; }
  .welcome-skip { margin-left: auto; background: none; border: none; color: var(--muted); font-size: 0.85rem; text-decoration: underline; padding: 6px 2px; cursor: pointer; }
  .welcome-skip:hover { background: none; color: var(--accent-dark); }
  .welcome-choice { display: block; width: 100%; text-align: left; background: transparent; color: var(--ink); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 0 0 8px; font-size: 0.92rem; cursor: pointer; }
  .welcome-choice:hover { background: #eef3ee; }
  .welcome-choice.on { border-color: var(--accent); background: #e3ebe3; }
  .welcome-choice .wc-t { font-weight: 600; color: var(--accent-dark); }
  .welcome-choice .wc-s { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 2px; }
  .welcome-next-btns { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 8px; }
  /* An edit-form card promoted into a centered overlay (openFormOverlay). The card keeps
     its own .card styling; this caps it to the viewport and scrolls long forms inside. */
  .overlay-form {
    margin: 0;
    width: 100%;
    max-width: 640px;
    max-height: 88vh;
    max-height: 88dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.22);
    position: relative;   /* anchors the injected ✕ */
  }
  /* The familiar top-right ✕. STICKY (not absolute) + floated as the first child of the
     scrolling card, so it follows the user down a long overlay (recipe library, editor)
     instead of scrolling out of reach. Opaque pill background so content scrolling under
     it stays readable and it never overwrites the text/buttons beneath. */
  .overlay-close-x {
    position: sticky;
    top: 0;
    float: right;
    z-index: 4;
    margin: -6px -8px 2px 10px;
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 50%;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--muted);
    opacity: 0.92;
    cursor: pointer;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15);
  }
  .overlay-close-x:hover { opacity: 1; color: var(--ink); background: var(--bg); }
  /* Unified search bar — ONE look everywhere a list is searched (your recipes, the
     community/library browse, …). A 🔍-led, accent-bordered input with an optional
     trailing ✕ clear. New search UIs should reuse .app-search rather than restyle. */
  .app-search { position: relative; flex: 1; min-width: 200px; }
  .app-search .app-search-icon {
    position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
    font-size: 1.05rem; pointer-events: none; opacity: 0.55;
  }
  .app-search input[type="text"], .app-search input[type="search"] {
    width: 100%; box-sizing: border-box;
    padding: 11px 38px 11px 40px;
    font-size: 1rem;
    border: 2px solid var(--accent); border-radius: 10px;
    background: var(--card);
  }
  .app-search-clear {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    border: none; background: none; cursor: pointer;
    font-size: 1.1rem; line-height: 1; color: var(--muted); padding: 4px 8px;
  }
  .app-search-clear:hover { color: var(--ink); }
  /* Bake Plan quick-add search results: clean, tappable rows with dividers + hover. */
  .plan-quickadd-item {
    display: block; width: 100%; box-sizing: border-box; text-align: left;
    border: 0; border-bottom: 1px solid var(--border); background: none;
    padding: 9px 12px; cursor: pointer; color: var(--ink); font: inherit; line-height: 1.25;
  }
  .plan-quickadd-item:last-child { border-bottom: 0; }
  .plan-quickadd-item:hover, .plan-quickadd-item:focus { background: var(--accent-tint); }
  .plan-quickadd-item .pqa-name { font-weight: 600; display: block; }
  .plan-quickadd-item .pqa-meta { font-size: 0.76rem; color: var(--muted); display: block; margin-top: 1px; }
  /* The Batching card moonlights as a warning-action overlay (openBatchingOverlay).
     As an .overview-item it has no card chrome of its own — over the dimmed
     backdrop that read as loose, unreadable text. Give it the full modal look. */
  .overlay-form.batching-overlay {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    margin-bottom: 0;
    border-top: 1px solid var(--border);
  }
  .overlay-form-wide {
    max-width: 940px;
    max-height: 92vh;
    max-height: 92dvh;
  }
  /* Full-focus mode: the editor (or any expandable overlay) fills the screen so a long
     recipe can be worked on without the cramped modal width. Toggled by the ⤢ button. */
  .overlay-form.overlay-form-expanded {
    max-width: 1400px;
    width: 96vw;
    max-height: 96vh;
    max-height: 96dvh;
  }
  /* The ⤢/⤡ expand toggle sits just left of the sticky ✕, sharing its pill look. */
  .overlay-expand-btn {
    position: sticky;
    top: 0;
    float: right;
    z-index: 4;
    margin: -6px 2px 2px 4px;
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 50%;
    font-size: 1.05rem;
    line-height: 1;
    color: var(--muted);
    opacity: 0.92;
    cursor: pointer;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15);
  }
  .overlay-expand-btn:hover { opacity: 1; color: var(--ink); background: var(--bg); }
  /* The manual/edit bake form: an inline section of the History card normally,
     a self-contained card look when promoted into an overlay. */
  #manual-bake-form {
    margin-top: 14px;
    border-top: 1px solid var(--border);
    padding-top: 14px;
  }
  #manual-bake-form.overlay-form {
    margin-top: 0;
    border-top: none;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
  }
  /* In-card autocomplete chips under the pantry Name field (aligned with the input;
     full-width on phones where the row's label stacks above the input). */
  #pantry-name-suggest {
    margin: 4px 0 8px 150px;   /* label 140px + row gap 10px = aligned with the input; bottom gap clears the helper text below */
    flex-wrap: wrap;
    gap: 6px;
  }
  @media (max-width: 640px) {
    #pantry-name-suggest { margin-left: 0; }
  }
  .modal-content input[type="time"] {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 8px;
    font-size: 1rem;
    font-family: inherit;
  }
  .anchor-clear-link {
    background: transparent;
    color: var(--danger);
    border: none;
    padding: 4px 0;
    font-size: 0.85rem;
    cursor: pointer;
    text-decoration: underline;
  }
  .anchor-clear-link:hover { color: #6e2222; }
  .schedule-event-time {
    font-weight: 600;
    font-size: 0.85rem;
    color: var(--accent-dark);
    font-variant-numeric: tabular-nums;
    margin-bottom: 2px;
  }
  @media (max-width: 640px) {
    .schedule-hour-row {
      grid-template-columns: 44px 1fr;
      column-gap: 6px;
    }
    .schedule-hour-cols { column-gap: 6px; }
  }
  .schedule-time {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--accent-dark);
    font-size: 0.95rem;
  }
  .schedule-time-end {
    display: block;
    font-size: 0.78rem;
    font-weight: 500;
    opacity: 0.85;
    letter-spacing: -0.2px;
  }
  .schedule-title { font-size: 0.95rem; }
  .schedule-detail { font-size: 0.82rem; color: var(--muted); margin-top: 2px; white-space: pre-line; }
  .schedule-meta {
    margin: 0 0 12px;
    font-size: 0.85rem;
    color: var(--muted);
  }
  /* The start-choice pop-up (2026-09-09): rows are buttons — a start is a press, not a link. */
  .start-choice-group { margin: 0 0 10px; }
  .start-choice-head { font-weight: 600; font-size: 0.85rem; margin: 0 0 4px; color: var(--accent-dark); }
  /* 🔴 The base `button` rule paints WHITE text; a white row swallowed the start time — the one
     thing the row exists to show (📐 the first CDP screenshot: every row read only its chill hour,
     the clock invisible). Colour and alignment are set here, not inherited. */
  button.start-choice-row { display: block; width: 100%; text-align: left; margin: 0 0 6px; padding: 8px 10px; background: var(--card); color: var(--ink); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; font-weight: 400; }
  button.start-choice-row:hover { background: #f4f8f4; }
  .start-choice-when { font-weight: 600; }
  .start-choice-bits { display: block; font-size: 0.8rem; margin-top: 2px; }
  /* The row's PREVIEW (Jonathan, 2026-09-27: "hover over those options and see when each step occurs …
     a compact overview of what the shape of the bake would look like"). One item = the row button, a
     small Preview toggle at its right edge (phones never hover — the toggle is the affordance a thumb
     finds; a mouse opens it on hover too), and the panel under it: a strip of the days with the closed
     hours shaded and every hands-on step marked, then the steps by day. One panel open at a time. */
  .start-choice-item { position: relative; margin: 0 0 6px; }
  .start-choice-item > button.start-choice-row { margin: 0; padding-right: 84px; }
  button.start-choice-peek { position: absolute; top: 7px; right: 7px; padding: 3px 9px; font-size: 0.75rem; line-height: 1.2; background: var(--card); color: var(--accent-dark); border: 1px solid var(--border); border-radius: 12px; cursor: pointer; font-weight: 500; }
  button.start-choice-peek[aria-expanded="true"] { background: var(--accent-dark); color: #fff; border-color: var(--accent-dark); }
  .start-choice-preview { border: 1px solid var(--border); border-top: 0; border-radius: 0 0 6px 6px; padding: 6px 10px 8px; margin: -1px 0 0; background: #fbfcfb; color: var(--ink); font-size: 0.8rem; }
  .start-choice-preview[hidden] { display: none; }
  .start-choice-item.scp-open > button.start-choice-row { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
  .scp-head { margin: 0 0 2px; }
  .scp-strip { position: relative; height: 14px; border-radius: 4px; background: #e6eee6; overflow: hidden; margin: 6px 0 1px; }
  .scp-closed { position: absolute; top: 0; bottom: 0; background: #c9d3c9; }
  .scp-hand { position: absolute; top: 2px; bottom: 2px; min-width: 3px; background: var(--accent-dark); border-radius: 1px; }
  .scp-daytick { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(0, 0, 0, 0.28); }
  .scp-days { position: relative; height: 13px; font-size: 0.7rem; color: var(--muted); margin: 0 0 4px; }
  .scp-days span { position: absolute; white-space: nowrap; }
  .scp-table { border-collapse: collapse; margin: 2px 0 0; font-size: 0.8rem; }
  .scp-table td { padding: 1px 6px 1px 0; vertical-align: top; line-height: 1.35; }
  .scp-table td.scp-day { font-weight: 600; color: var(--accent-dark); width: 2.6em; }
  .scp-table td.scp-time { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--muted); }
  .scp-table td.scp-what { padding-right: 0; }
  .scp-more { color: var(--muted); }
  .schedule-warn {
    background: #faf4e8;
    border: 1px solid #d6c79f;
    border-radius: 6px;
    padding: 8px 28px 8px 10px;
    font-size: 0.85rem;
    color: #6a4a10;
    margin: 0 0 12px;
    position: relative;
  }
  /* Hard failures (a bake past its deadline): red, bold, not dismissible. */
  .schedule-danger {
    background: #fdecec;
    border: 2px solid var(--danger);
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--danger);
    margin: 0 0 12px;
    position: relative;
  }
  /* A PROMPT (2026-10-01): a question the schedule asks, with its answers as buttons. Placed with
     the danger rows (top, never grouped), no × — "Keep …" is the way to say no. */
  .schedule-prompt {
    background: #f3f7ec;
    border: 1px solid var(--accent-dark);
    border-left-width: 4px;
    border-radius: 6px;
    padding: 9px 10px 9px 12px;
    font-size: 0.88rem;
    color: var(--ink);
    margin: 0 0 12px;
    position: relative;
  }
  .schedule-prompt .schedule-highlight-btn { margin-top: 6px; }
  .schedule-info {
    background: #e8f0e8;
    border: 1px solid #94b496;
    border-radius: 6px;
    padding: 8px 28px 8px 10px;
    font-size: 0.85rem;
    color: #1f4d2e;
    margin: 0 0 12px;
    position: relative;
  }
  .schedule-opt-card {
    background: #eef2f7;
    border: 1px solid #9db2cc;
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 0.85rem;
    color: #24405e;
    margin: 0 0 12px;
  }
  .schedule-opt-head {
    font-weight: 600;
    margin-bottom: 4px;
  }
  .schedule-opt-count {
    display: inline-block;
    min-width: 18px;
    text-align: center;
    background: #9db2cc;
    color: #fff;
    border-radius: 9px;
    font-size: 0.75rem;
    padding: 0 5px;
    margin-left: 4px;
  }
  .schedule-opt-item {
    position: relative;
    padding: 6px 24px 6px 0;
    border-top: 1px solid #ccd7e5;
  }
  .schedule-opt-item:first-of-type { border-top: none; }
  .schedule-opt-item .schedule-dismiss-btn { top: 4px; right: 0; }
  /* "Why this schedule" — the decision-trace disclosure (green, vs the optimizer's blue) */
  details.schedule-why {
    background: #f2f7f2;
    border: 1px solid #a9c4a9;
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 0.85rem;
    color: #2b4a2b;
    margin: 0 0 12px;
  }
  details.schedule-why > summary { cursor: pointer; font-weight: 600; }
  .schedule-why-sect { font-weight: 600; margin: 8px 0 2px; }
  .schedule-why-when { font-weight: 400; font-size: 0.86em; opacity: 0.78; }
  .schedule-why-item { padding: 4px 0 4px 10px; border-left: 2px solid #a9c4a9; margin: 4px 0; }
  details.schedule-why-fine { margin-top: 2px; }
  details.schedule-why-fine > summary { cursor: pointer; color: #567856; font-size: 0.8rem; }
  .schedule-why-foot { margin-top: 8px; font-size: 0.75rem; color: #607060; }
  /* The Bake Plan card's ⓘ — hover reveals (desktop), tap toggles (.open) everywhere */
  .why-info-wrap { position: relative; display: inline-block; margin-top: 4px; }
  button.why-info-btn {
    background: transparent;
    border: none;
    padding: 0 2px;
    font-size: 0.85rem;
    color: #24405e;
    cursor: pointer;
    text-decoration: underline dotted;
  }
  .why-info-pop {
    display: none;
    position: absolute;
    left: 0;
    top: calc(100% + 4px);
    z-index: 40;
    width: min(88vw, 380px);
    max-height: 60vh;
    overflow-y: auto;
    background: #f2f7f2;
    border: 1px solid #a9c4a9;
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 0.85rem;
    color: #2b4a2b;
    text-align: left;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  }
  /* Hover reveals only on hover-capable devices, and a ✕-dismissed wrap suppresses it
     until the pointer leaves (else :hover would instantly overrule the close — the ✕
     "didn't work" because the mouse was still over the wrap). No :focus-within: the ✕
     itself takes focus on click, which kept the popover open forever. Keyboard users
     toggle via the ⓘ button (Enter → .open). */
  @media (hover: hover) and (pointer: fine) {
    .why-info-wrap:not(.why-dismissed):hover .why-info-pop { display: block; }
  }
  .why-info-pop.open { display: block; }
  /* The field-level ⓘ: a bare glyph beside a label, so it needs no underline and must not
     shift the row's baseline. `.why-info-pop` is a <span> here (a <div> is illegal inside the
     <label>-adjacent inline flow), so it is told to lay out as a block when shown. */
  /* A little air after the colon — measured at 830px, the glyph was touching it. */
  .why-info-field { margin-top: 0; margin-left: 5px; vertical-align: middle; }
  button.why-info-btn.why-info-plain { text-decoration: none; font-size: 1rem; line-height: 1;
    color: var(--muted); padding: 0 1px; }
  button.why-info-btn.why-info-plain:hover, button.why-info-btn.why-info-plain:focus { color: var(--ink); }
  .why-info-field .why-info-pop { font-weight: 400; white-space: normal; }
  .why-info-field .why-info-pop.open { display: block; }
  button.why-info-close {
    position: absolute;
    top: 4px;
    right: 6px;
    width: 22px;
    height: 22px;
    line-height: 1;
    padding: 0;
    background: transparent;
    border: none;
    color: #567856;
    font-size: 1.1rem;
    cursor: pointer;
  }
  button.schedule-dismiss-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    line-height: 1;
    padding: 0;
    background: transparent;
    border: none;
    color: inherit;
    opacity: 0.5;
    cursor: pointer;
    font-size: 1.1rem;
    border-radius: 4px;
  }
  button.schedule-dismiss-btn:hover { opacity: 1; background: rgba(0,0,0,0.06); }
  .schedule-dismissed-note {
    font-size: 0.8rem;
    color: var(--muted);
    margin: 0 0 12px;
  }
  /* Warnings triage: chip summary strip + themed group cards. Grouped mode only
     kicks in when the list is long enough to overwhelm — small plans keep the
     flat cards above. Danger blocks never enter a group. */
  .schedule-warn-strip { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
  .schedule-warn-chip {
    border-radius: 14px; padding: 3px 10px; font-size: 0.78rem; cursor: pointer;
    border: 1px solid #d6c79f; background: #faf4e8; color: #6a4a10;
  }
  .schedule-warn-chip.chip-info { border-color: #94b496; background: #e8f0e8; color: #1f4d2e; }
  .schedule-warn-chip .warn-chip-count { font-weight: 700; margin-left: 2px; }
  .schedule-warn-chip.chip-cleared { opacity: 0.55; border-style: dashed; }
  /* ⚠️ NO overflow:hidden at rest — the per-note ⓘ Why? popover is position:absolute INSIDE a
     row of this card and opens past its bottom edge; a resting clip cuts it to a sliver
     (Jonathan, 2026-09-06, measured: 104 of 113px invisible). The only thing that ever pokes
     out is a swipe-to-dismiss row mid-drag, so the clip rides the gesture (.warn-swiping,
     added/removed by initWarnSwipe), never the card. */
  .schedule-warn-group { border-radius: 8px; margin: 0 0 12px; font-size: 0.85rem; padding: 0; }
  .schedule-warn-group.warn-swiping { overflow: hidden; }
  .schedule-warn-group.gwarn { background: #faf4e8; border: 1px solid #d6c79f; color: #6a4a10; }
  .schedule-warn-group.ginfo { background: #e8f0e8; border: 1px solid #94b496; color: #1f4d2e; }
  .schedule-warn-group-head {
    display: flex; align-items: center; gap: 6px; font-weight: 600;
    padding: 8px 10px; cursor: pointer; user-select: none; -webkit-user-select: none;
  }
  .warn-group-caret { margin-left: auto; opacity: 0.6; font-size: 0.8rem; }
  .schedule-warn-group.collapsed .schedule-warn-group-body { display: none; }
  .schedule-warn-group-body { padding: 0 10px 6px; }
  /* Rows inside a group flatten to list items (severity classes keep tinting the
     flat mode; inside a card the card carries the tint). */
  .schedule-warn-group .schedule-warn, .schedule-warn-group .schedule-info {
    background: transparent; border: none; border-top: 1px solid rgba(0,0,0,0.1);
    border-radius: 0; margin: 0; padding: 6px 24px 6px 0; color: inherit;
  }
  .schedule-warn-group .schedule-warn:first-child, .schedule-warn-group .schedule-info:first-child { border-top: none; }
  .schedule-warn-group .schedule-dismiss-btn { top: 4px; right: 0; }
  /* Swipe-to-dismiss: rows own horizontal gestures; vertical scroll stays native. */
  [data-warn-swipe] { touch-action: pan-y; }
  button.schedule-linklike {
    background: none;
    border: none;
    padding: 0;
    color: var(--accent-dark);
    cursor: pointer;
    font-size: inherit;
    text-decoration: underline;
  }
  /* Stage color coding — vertical label pill per bake phase */
  .schedule-row[data-stage], .schedule-event[data-stage], .stage-group[data-stage] {
    position: relative;
    padding-left: 22px;
  }
  .stage-group { border-bottom: 1px dashed var(--border); }
  .stage-group:last-child { border-bottom: none; }
  .stage-group .schedule-row { border-bottom: 1px dashed var(--border); }
  .stage-group .schedule-row:last-child { border-bottom: none; }
  .schedule-row[data-stage]::before, .schedule-event[data-stage]::before, .stage-group[data-stage]::before {
    content: attr(data-stage);
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    writing-mode: vertical-rl;
    font-size: 0.46rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: white;
    border-radius: 2px;
    overflow: hidden;
  }
  .schedule-row[data-stage="prep"]::before,   .schedule-event[data-stage="prep"]::before,   .stage-group[data-stage="prep"]::before   { background: #ECDCB6; color: #4a3618; }
  .schedule-row[data-stage="levain"]::before, .schedule-event[data-stage="levain"]::before, .stage-group[data-stage="levain"]::before { background: #E3C98C; color: #4a3618; }
  .schedule-row[data-stage="poolish"]::before,.schedule-event[data-stage="poolish"]::before,.stage-group[data-stage="poolish"]::before{ background: #EAD9A0; color: #4a3618; }
  .schedule-row[data-stage="biga"]::before,.schedule-event[data-stage="biga"]::before,.stage-group[data-stage="biga"]::before{ background: #EAD9A0; color: #4a3618; }
  .schedule-row[data-stage="sour"]::before,.schedule-event[data-stage="sour"]::before,.stage-group[data-stage="sour"]::before{ background: #D8C3C0; color: #4a2f2a; }
  .schedule-row[data-stage="tangzhong"]::before,.schedule-event[data-stage="tangzhong"]::before,.stage-group[data-stage="tangzhong"]::before{ background: #E7D3BF; color: #4a3618; }
  .schedule-row[data-stage="soaker"]::before,.schedule-event[data-stage="soaker"]::before,.stage-group[data-stage="soaker"]::before{ background: #D7DFC3; color: #3d4a26; }
  .schedule-row[data-stage="porridge"]::before,.schedule-event[data-stage="porridge"]::before,.stage-group[data-stage="porridge"]::before{ background: #E4DCC9; color: #4a4128; }
  .schedule-row[data-stage="laminate"]::before,.schedule-event[data-stage="laminate"]::before,.stage-group[data-stage="laminate"]::before{ background: #D9B8A0; color: #4a2c18; }
  .schedule-row[data-stage="mix"]::before,    .schedule-event[data-stage="mix"]::before,    .stage-group[data-stage="mix"]::before    { background: #D6AE63; color: #4a3618; }
  .schedule-row[data-stage="knead"]::before,  .schedule-event[data-stage="knead"]::before,  .stage-group[data-stage="knead"]::before  { background: #CFA24C; color: #3a2a12; }
  .schedule-row[data-stage="fold"]::before,   .schedule-event[data-stage="fold"]::before,   .stage-group[data-stage="fold"]::before   { background: #C28C40; color: #3a2a12; }
  .schedule-row[data-stage="shape"]::before,  .schedule-event[data-stage="shape"]::before,  .stage-group[data-stage="shape"]::before  { background: #A86E2B; color: #fbf6ec; }
  .schedule-row[data-stage="proof"]::before,  .schedule-event[data-stage="proof"]::before,  .stage-group[data-stage="proof"]::before  { background: #8F5A22; color: #fbf6ec; }
  .schedule-row[data-stage="chill"]::before,  .schedule-event[data-stage="chill"]::before,  .stage-group[data-stage="chill"]::before  { background: #4F6A73; color: #eef4f6; }
  .schedule-row[data-stage="freeze"]::before, .schedule-event[data-stage="freeze"]::before, .stage-group[data-stage="freeze"]::before { background: #8FD0E8; color: #0c3a4a; }
  .schedule-row[data-stage="boil"]::before,   .schedule-event[data-stage="boil"]::before,   .stage-group[data-stage="boil"]::before   { background: #C0492E; color: #fbf6ec; }
  .schedule-row[data-stage="topping"]::before,.schedule-event[data-stage="topping"]::before,.stage-group[data-stage="topping"]::before{ background: #6F7A2E; color: #fbf6ec; }
  .schedule-row[data-stage="bake"]::before,   .schedule-event[data-stage="bake"]::before,   .stage-group[data-stage="bake"]::before   { background: #5E3413; color: #fbf6ec; }
  .schedule-row[data-stage="cool"]::before,   .schedule-event[data-stage="cool"]::before,   .stage-group[data-stage="cool"]::before   { background: #A9C7D6; color: #22414f; }
  .schedule-row[data-stage="done"]::before,   .schedule-event[data-stage="done"]::before,   .stage-group[data-stage="done"]::before   { background: #1F4D2E; color: #fbf6ec; }
  /* Recipe stage editor */
  .stage-editor { display: flex; flex-direction: column; gap: 6px; }
  .stage-row { display: flex; flex-direction: column; gap: 6px; padding: 7px 8px; border: 1px solid var(--border); border-radius: 8px; }
  .stage-row.is-expanded { border-color: var(--accent); background: #fbfcfb; }
  .stage-summary { display: flex; align-items: center; gap: 8px; }
  .stage-sum-label { flex: 1; min-width: 80px; font-weight: 600; font-size: 0.9rem; }
  .stage-sum-meta { display: inline-flex; align-items: center; gap: 8px; font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
  .stage-sum-flag { cursor: default; }
  .stage-edit-btn { white-space: nowrap; }
  .stage-edit-body { display: flex; flex-direction: column; gap: 8px; padding-top: 7px; border-top: 1px dashed var(--border); }
  .stage-field-lbl { font-size: 0.78rem; color: var(--muted); }
  .stage-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .stage-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .stage-badge { font-size: 0.72rem; font-weight: 600; padding: 2px 9px; border-radius: 999px; white-space: nowrap; text-transform: capitalize; }
  .stage-custom-mark { font-size: 0.82rem; color: #9a6b12; cursor: help; margin: 0 1px; }
  .stage-row input.stage-label { flex: 1; min-width: 110px; }
  .stage-row .stage-dur { display: inline-flex; align-items: center; gap: 4px; font-size: 0.85rem; color: var(--muted); }
  .stage-row .stage-locked { font-size: 0.72rem; white-space: nowrap; cursor: help; }
  .stage-row .stage-dur input { width: 60px; }
  /* Uneven stretch-and-fold cadence: the gap boxes wrap onto their own line under the count. */
  .stage-row .stage-dur { flex-wrap: wrap; }
  .fold-uneven-toggle { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }
  .fold-uneven-toggle input { width: auto !important; flex: none; }
  .fold-gaps { display: flex; flex-wrap: wrap; gap: 6px; flex-basis: 100%; margin-top: 4px; }
  .fold-gap { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px; }
  .fold-gap label { font-size: 0.68rem; letter-spacing: 0.02em; opacity: 0.8; }
  .stage-row .stage-dur .fold-gap input { width: 56px; }
  .fold-cadence-hint { flex-basis: 100%; font-size: 0.75rem; line-height: 1.35; margin-top: 3px; }
  .stage-ctrl { display: inline-flex; align-items: center; gap: 4px; font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
  .stage-actmin input { width: 46px; }
  .stage-move { display: inline-flex; flex-direction: column; gap: 1px; }
  .stage-move button { padding: 0 7px; font-size: 0.7rem; line-height: 1.5; }
  .stage-del { padding: 2px 8px; font-size: 0.85rem; line-height: 1.2; }
  .schedule-action-note {
    display: inline-block;
    margin-left: 8px;
    font-size: 0.75rem;
    opacity: 0.65;
    font-style: italic;
  }
  button.schedule-highlight-btn {
    background: transparent;
    color: inherit;
    border: 1px solid currentColor;
    border-radius: 999px;
    padding: 3px 10px;
    font-size: 0.78rem;
    opacity: 0.75;
    cursor: pointer;
    margin-top: 6px;
    display: inline-block;
  }
  button.schedule-highlight-btn:hover { opacity: 1; }
  .schedule-row-highlighted, .schedule-event-highlighted {
    background: #fff3cd !important;
    box-shadow: inset 3px 0 0 #f59e0b;
    border-radius: 4px;
  }
  .settings-select {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 5px 8px;
    font-size: 0.95rem;
    background-color: white;   /* longhand — the shorthand would wipe the select chevron */
    color: var(--ink);
    font-family: inherit;
  }
  select.settings-select { padding-right: 26px; }
  /* Equipment dropdowns inside the Bake Plan cards: match the app's select aesthetic,
     compact, with a custom chevron and accent hover/focus so they sit in the cards. */
  .bp-equip-select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    font-size: 0.82rem;
    padding: 4px 26px 4px 9px;
    max-width: 100%;
    cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%231f4d2e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
  }
  .bp-equip-select:hover { border-color: var(--accent); }
  .bp-equip-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(31, 77, 46, 0.15); }
  #bake-chart-card { display:none }
  #bake-chart-card svg { display: block; max-width: 100% }
  .bhc-tip { position: absolute; pointer-events: none; display: none; white-space: nowrap; z-index: 5;
    background: rgba(253,250,242,0.96); border: 1px solid var(--border); border-radius: 6px;
    padding: 6px 9px; font-size: 0.78rem; color: #4a3618; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
  .bhc-tip .bhc-tip-date { font-weight: 600; margin-bottom: 3px; }
  /* The touch reading of the fermentation chart: a fixed line UNDER the plot, where no thumb sits
     (see boDrawTempChart). Wraps rather than truncates — a phone is narrow and every row matters. */
  .dtc-readout { position: relative; margin: 4px 0 0; padding: 6px 30px 6px 8px; font-size: .8rem; line-height: 1.45;
    color: #4a3618; background: rgba(253,250,242,0.96); border: 1px solid var(--border); border-radius: 6px; }
  .dtc-readout .dtc-readout-when { font-weight: 700; margin-right: 6px; }
  .dtc-readout .dtc-readout-sep { color: var(--muted); }
  .dtc-readout .dtc-readout-x { position: absolute; top: 3px; right: 4px; width: 24px; height: 24px; padding: 0; border: 1px solid var(--border);
    border-radius: 50%; background: var(--card); color: var(--muted); font-size: .9rem; line-height: 1; cursor: pointer; }
  .dtc-axis-title { font-size: 10.5px; fill: #6b5638; }
  #bake-chart-card button.tiny.active { background: var(--accent); color: #fff; border-color: var(--accent); }

  /* Bake sheet */
  .bakesheet-header { margin-bottom: 20px; }
  .bakesheet-title { font-size: 1.4rem; margin: 0 0 4px; }
  /* P5 — the bake selector on the sheet and the step view. ⚠️ THE CHEVRON: the base `select` rule
     paints a 10px glyph at `right 9px center`, so it occupies 9–19px from the right edge; 28px of
     padding clears it with room, and without that the last letters of a bake's name sit under it
     (CLAUDE.md, 2026-08-27). */
  .bakesheet-summary { color: var(--muted); margin: 0 0 2px; font-size: 0.9rem; }
  .bakesheet-volumes { margin: 6px 0 2px; }
  .bakesheet-volumes .bs-vol-name { font-size: 0.8rem; font-weight: 600; color: var(--accent-dark); }
  .bakesheet-day { margin-bottom: 28px; }
  .bakesheet-day-header { font-size: 0.95rem; font-weight: 600; margin: 0 0 6px;
    padding: 5px 0; border-bottom: 2px solid var(--border); color: var(--accent-dark); }
  .bakesheet-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; table-layout: fixed; }
  .bakesheet-table td { overflow-wrap: break-word; }
  .bakesheet-table th { text-align: left; padding: 5px 8px; background: var(--bg);
    border-bottom: 2px solid var(--border); font-size: 0.78rem; color: var(--muted); }
  .bakesheet-table td { padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
  .bs-time { color: var(--accent-dark); font-weight: 600; font-size: 0.82rem;
    width: 110px; vertical-align: top; height: 1px; }
  /* Each clock time stays intact; the range may wrap onto two lines between them
     so it never overflows the fixed-width Time column into the Step column. */
  .bs-tspan { white-space: nowrap; }
  /* Fill the (possibly tall, rowspanned) Time cell; the note button sits vertically centered
     in the whitespace left below the recipe pills, still left-justified. */
  .bs-time-inner { display: flex; flex-direction: column; height: 100%; }
  .bs-time-main { flex: 0 0 auto; }
  .bs-note-btn-wrap { flex: 1 1 auto; display: flex; align-items: center; justify-content: flex-start; padding: 8px 0; }
  .bs-step strong { font-size: 0.88rem; }
  .bs-detail { color: var(--muted); font-size: 0.78rem; margin-top: 2px; }
  .bs-recipes { margin: 5px 0 1px; display: flex; flex-wrap: wrap; gap: 3px; }
  .bs-recipe-tag {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1.3;
    padding: 1px 6px;
    border-radius: 999px;
    background: #eef2f7;
    color: #334155;
    border: 1px solid #d8e0ea;
  }
  /* Who is doing the step, on the sheet you carry to the bench. ⚠️ THE NAME IS THE READING and the
     dot is decoration — a printer that drops backgrounds still prints the name, and nothing here
     clamps or hides overflow, because paper has no click, no hover and no tooltip. */
  .bs-baker {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1.3;
    padding: 1px 7px;
    margin-left: 6px;
    border-radius: 999px;
    background: #f1efe6;
    color: #4a4335;
    border: 1px solid var(--border);
    vertical-align: middle;
    white-space: nowrap;
  }
  .bs-baker .baker-dot { width: 7px; height: 7px; }
  @media print {
    .bs-recipe-tag { background: #eef2f7 !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
    .bs-baker { background: #f1efe6 !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  }
  .bs-ings { width: 236px; }
  .bs-ings table { font-size: 0.78rem; margin: 0; width: 100%; }
  .bs-ings table td { padding: 1px 4px; border: none; overflow-wrap: break-word; }
  /* Keep a weight ("4452 g") on one line — never split the unit onto a new line — and let
     the ingredient name wrap instead when the column is tight (e.g. on the printed sheet). */
  .bs-ings table td.num { white-space: nowrap; text-align: right; width: 1%; }
  .bs-ing-temp { color: #2f6f86; font-weight: 600; white-space: nowrap; }
  .bs-ings .bs-recipe-label { font-size: 0.75rem; font-weight: 600;
    color: var(--accent-dark); margin-bottom: 2px; }
  .bs-ings .bs-batch-note { font-size: 0.72rem; font-weight: 600; font-style: italic;
    color: var(--muted); margin: 0 0 2px; }
  .bs-ings .bs-batch-gap { height: 6px; }
  /* The Ingredient check's levain rows sit under a rule, because they are what you FEED THE JAR
     — not more of the dough's own weigh-out. Same rule on paper: a printed check that runs the
     two lists together tells the baker to put the build flour in the mixing bowl. */
  .bs-check-levain { margin-top: 6px; padding-top: 5px; border-top: 1px dashed var(--border); }
  .bs-container { width: 116px; font-size: 0.78rem; color: var(--muted); white-space: pre-line; vertical-align: top; }
  .bs-equip-edit { margin-top: 5px; display: flex; flex-direction: column; gap: 2px; white-space: normal; }
  .bs-equip-row { display: flex; align-items: baseline; gap: 4px; font-size: 0.74rem; }
  .bs-equip-lbl { color: var(--muted); min-width: 58px; font-size: 0.7rem; text-transform: lowercase; }
  .bs-equip-note { font-size: 0.74rem; color: #3a4a55; }
  .bs-equip-note .bs-equip-link { color: var(--accent, #4F6A73); cursor: pointer; }
  /* Bake-sheet equipment pickers stay compact, but read as CONTROLS at rest — a faint
     always-on border + chevron so they're clearly a dropdown, not a repeat of the adjacent
     equipment text. Border sharpens to the accent on hover/focus. */
  .bs-equip-edit .bp-equip-select {
    border: 1px solid var(--border);
    background-color: transparent;
    color: var(--accent-dark);
    font-size: 0.76rem;
    max-width: 100%;
    padding: 1px 18px 1px 6px;
    border-radius: 5px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23648a72' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-position: right 5px center;
  }
  .bs-equip-edit .bp-equip-select:hover,
  .bs-equip-edit .bp-equip-select:focus {
    border-color: var(--accent);
    background-color: #fff;
    box-shadow: none;
  }
  .bs-stage { width: 24px; padding: 2px; display: flex; align-items: center; justify-content: center; border-left: 1px solid #ccc; }
  .bs-stage-label {
    display: block;
    writing-mode: vertical-rl;
    font-size: 0.46rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #666;
  }
  /* Bake sheet — mobile stacked cards (one per step). Only emitted on phones
     (renderDayCards), so these rules need no media gate. */
  /* On screen the bake sheet is a list of step cards (mobile + desktop); print
     uses the dense table instead. Cap card width so it reads well on wide screens. */
  .bs-cards { display: flex; flex-direction: column; gap: 10px; max-width: 760px; }
  .bs-card { position: relative; border: 1px solid var(--border); border-radius: 9px; padding: 11px 12px; background: var(--card);
    transition: opacity 0.15s ease, background 0.15s ease, transform 0.15s ease; }
  /* Swipe-right-to-complete (touch): card follows the finger, tints green once
     past the commit threshold, and a ✓ hint sits at the left edge it reveals. */
  .bs-card-head { display: flex; align-items: center; gap: 8px; }
  .bs-check { flex: 0 0 auto; display: inline-flex; align-items: center; cursor: pointer; }
  .bs-check input { width: 20px; height: 20px; cursor: pointer; accent-color: var(--accent); margin: 0; }
  /* A span step's box says what the tick means (2026-10-24): "start" beside it, "started" once ticked; a plain step's box says nothing, as before. */
  .bs-check-word { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-left: 4px; }
  .bs-chime { display: inline-flex; align-items: center; gap: 4px; font-size: .8rem; }
  .bs-chime input { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); }
  .bs-card-stage { margin-left: auto; }
  /* Completed steps grey out and strike through the title. */
  .bs-card-done { opacity: 0.5; background: var(--bg); }
  /* 2026-10-29: a WAIT's card — slim, unboxed, the state's own clock on it (Jonathan: "slim unboxed card on the bake tab") */
  .bs-card-wait { padding: 6px 10px; background: var(--bg); border-style: dashed; }
  .bs-card-wait .bs-card-title { font-weight: normal; }
  .bs-card-wait .bs-card-title strong { font-weight: 500; }
  .bs-wait-glyph { flex: 0 0 auto; display: inline-flex; align-items: center; width: 20px; justify-content: center; opacity: 0.75; }
  .bs-wait-actions { margin-top: 2px; }
  .lm-wait-banner { margin: 6px 0 8px; padding: 6px 10px; border: 1px dashed var(--border); border-radius: 8px; background: var(--bg); font-size: 0.92rem; }
  .bs-card-done .bs-card-title strong { text-decoration: line-through; }
  .bs-card-time { font-weight: 600; color: var(--accent-dark); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
  .bs-card-stage { flex: 0 0 auto; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: #5a6b5e; background: #eef2ee; border: 1px solid #d6e0d6;
    border-radius: 999px; padding: 1px 7px; }
  .bs-card-title { margin-top: 3px; }
  /* ---- the live bake (PLAN-live-bake-mode M1) ---- */
  .bs-now { margin: 0 0 12px; padding: 12px 14px; border-left: 4px solid var(--accent); }
  .bs-now-idle { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 0.9rem; }
  .bs-finished { font-weight: 700; color: var(--ok); font-size: 1.05rem; }
  .bs-now-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
  .bs-now-title { font-weight: 700; color: var(--accent-dark); }
  .bs-now-progress { color: var(--muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }
  /* the head's buttons (⛶ Step view · End bake) sit right, as one group */
  .bs-now .bo-nowread { font-size: 0.95rem; }
  /* the step the clock says is next: a rest surface, never a hover */
  .bs-card-next { border-color: var(--accent); box-shadow: inset 4px 0 0 var(--accent); }
  .bs-next-tag { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: #fff; background: var(--accent); border-radius: 999px; padding: 1px 7px; }
  /* The Schedule tab's own cue for a bake under way (Jonathan, 2026-09-10): a ticked step is
     dimmed and wears "✓ done" — it is held where it happened — and the next step coming wears
     "next", the same two states the Bake tab's cards show. Screen only: paper has no ticks. */
  .schedule-done { opacity: 0.55; }
  .schedule-done .schedule-title { text-decoration: line-through; text-decoration-color: var(--muted); }
  .schedule-done-tag { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--accent-dark); background: #e7f1e7; border: 1px solid var(--accent); border-radius: 999px; padding: 0 6px; margin-left: 6px; vertical-align: middle; }
  .schedule-next-tag { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: #fff; background: var(--accent); border-radius: 999px; padding: 1px 7px; margin-left: 6px; vertical-align: middle; }
  @media print { .schedule-done { opacity: 1; } .schedule-done .schedule-title { text-decoration: none; } .schedule-done-tag, .schedule-next-tag { display: none; } }
  .livebake-chip { display: flex; align-items: center; gap: 8px; width: 100%; max-width: 760px; margin: 0 auto 8px; padding: 4px 4px 4px 12px;
    background: #e7f1e7; color: var(--accent-dark); border: 1px solid var(--accent); border-radius: 8px;
    font-size: 0.85rem; text-align: left; cursor: pointer; font-variant-numeric: tabular-nums; box-sizing: border-box; }
  .livebake-chip:hover { background: #dcebdc; }
  .livebake-chip-text { flex: 1 1 auto; min-width: 0; padding: 2px 0; }
  /* the ✕: its own thumb-sized target at the bar's end, painted at rest (phones never hover) */
  .livebake-chip-x { flex: 0 0 auto; width: 32px; height: 32px; padding: 0; border: 1px solid var(--accent); border-radius: 50%;
    background: var(--card); color: var(--accent-dark); font-size: 0.95rem; line-height: 1; cursor: pointer; }
  .livebake-chip-x:hover { background: #fbeaea; border-color: var(--danger); color: var(--danger); }
  /* LIVE BATCH 1, item 4: 2+ under-way bakes turn the chip into a LIST, one line per bake, each
     its own press and its own ✕, inside the same outer box. */
  .livebake-chip-lines { display: flex; flex-direction: column; gap: 6px; width: 100%; }
  .livebake-chip-row { display: flex; align-items: center; gap: 8px; width: 100%; cursor: pointer; }
  .livebake-chip-row-text { flex: 1 1 auto; min-width: 0; padding: 2px 0; }
  /* THE CHIP'S PHOTO AND PROGRESS (Jonathan, 2026-10-20): a small round cover of the bake's first photographed recipe before the
     words, and a thin bar under them (done steps over the bake's timed steps). Both paint at rest — phones never hover. A chip
     with no photo has no thumb element at all. The bar takes the full width on its own line (flex-wrap), under text and ✕. */
  .livebake-chip, .livebake-chip-row { flex-wrap: wrap; }
  .livebake-chip-thumb { position: relative; flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; overflow: hidden;
    border: 1px solid var(--accent); background: var(--card); box-sizing: border-box; }
  .livebake-chip-thumb .recipe-photo-thumb { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; border: 0; border-radius: 0; object-fit: cover; }
  .livebake-chip-thumb .recipe-photo-credit { display: none; }
  .livebake-chip-thumb.livebake-chip-thumb-empty { border: 0; background: none; }
  .livebake-chip-bar { flex: 0 0 100%; order: 9; height: 4px; margin: 0 8px 4px 0; border-radius: 2px; background: rgba(0,0,0,0.12); overflow: hidden; }
  .livebake-chip-bar > i { display: block; height: 100%; background: var(--accent); border-radius: 2px; }
  .bs-card-title strong { font-size: 0.95rem; }
  .bs-card .bs-detail { margin-top: 3px; }
  .bs-card-section { margin-top: 9px; }
  /* Same as the print sheet: keep a weight ("4452 g") together; let the name wrap instead. */
  .bs-card-section table { width: 100%; }
  .bs-card-section table td.num { white-space: nowrap; text-align: right; width: 1%; }
  /* A volume with its convention ("1½ cups (~186 g · Spooned & leveled)") is a SENTENCE, not a number
     (2026-10-09, Jonathan's phone: the no-wrap cell pushed every amount off the card's right edge and
     the names wrapped one word per line). The bold volume stays on one line; the hint wraps under it. */
  .bs-card-section table td.num-vol, .bs-ings table td.num-vol { white-space: normal; width: auto; }
  td.num-vol strong { white-space: nowrap; }
  td.num-vol .bs-vol-hint { display: block; white-space: normal; font-size: 0.9em; }   /* only there — inline in the print card, the Plan table and the recipe summary (UKMTC 2026-10-10) */
  .bs-card-lbl { display: block; font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--muted); margin-bottom: 3px; }
  .bs-card-section .bs-equip-edit { margin-top: 4px; }
  .bs-card .bp-equip-select, .bs-card select { width: 100%; max-width: 100%; min-width: 0; font-size: 16px; padding-top: 7px; padding-bottom: 7px; }
  /* min-width:0 on the flex chain so a long-option select shrinks to the card. */
  .bs-card .bs-equip-edit, .bs-card .bs-equip-row { min-width: 0; max-width: 100%; }
  .bs-card .bs-equip-row { flex-wrap: wrap; }
  .bs-card-batch { margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border); }
  .bs-card-batch-equip { font-size: 0.8rem; color: var(--muted); white-space: pre-line; margin-bottom: 4px; }
  .bs-card-foot { margin-top: 9px; }
  /* ---- per-step actions in the live bake (PLAN-live-bake-mode M3) ---- */
  .bs-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.85rem; }
  .bs-actions .bs-act-lab { color: var(--muted); }
  .bs-actions button.tiny { padding: 3px 9px; }
  .bs-done-at { color: var(--accent-dark); font-variant-numeric: tabular-nums; }
  .bs-done-at-input { font-size: 0.85rem; padding: 3px 6px; }
  .bs-anchored { font-size: 0.75rem; color: var(--accent-dark); }
  /* ---- readiness prompts (PLAN-live-bake-mode M4): the app asks, the baker judges ---- */
  .bs-ask { margin-top: 10px; padding: 10px 12px; border: 1px solid var(--accent); border-radius: 8px; background: #f3f8f3; font-size: 0.9rem; }
  .bs-ask-cue { color: var(--muted); margin: 4px 0 2px; }
  .bs-ask-note { margin-top: 8px; padding: 8px 12px; border-left: 3px solid var(--accent); background: #f3f8f3; font-size: 0.88rem; }
  .bs-ask .bs-actions button:first-child { font-weight: 600; }
  .bs-live-read { margin-top: 8px; font-size: 0.88rem; color: var(--muted); line-height: 1.45; }
  .bs-live-read b { color: var(--accent-dark); }
  .bs-live-read button.tiny { margin-left: 4px; }
  .bs-alerts { font-size: 0.85rem; }
  .bs-push-status { color: var(--accent-dark); }
  .bs-push-devices { list-style: none; margin: 0.3rem 0 0; padding: 0; font-size: 0.82rem; width: 100%; }
  .bs-push-devices li { margin: 0.15rem 0; }
  .bs-push-devices .bs-push-dead { color: var(--danger); }
  .bs-timing-line button.tiny { margin-left: 6px; }
  .bs-measured { margin-top: 9px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.85rem; }
  .bs-measured label { display: flex; align-items: center; gap: 6px; width: auto; margin: 0; font-size: 0.85rem; }
  .bs-measured input { width: 70px; }
  .bs-measured-warn { color: var(--warn-text, #9a3412); font-size: 0.82rem; }
  .bs-card .bs-recipes { margin-top: 6px; }
  /* ---- added-it ingredient checks on the live bake's cards (2026-09-06) ---- */
  .bs-ing-check { width: 1%; padding-right: 7px; }
  .bs-ing-check input { width: 19px; height: 19px; accent-color: var(--accent); margin: 0; cursor: pointer; display: block; }
  .lm-card .bs-ing-check input { width: 22px; height: 22px; }
  tr.bs-ing-added { opacity: 0.5; }
  tr.bs-ing-added td:not(.bs-ing-check) { text-decoration: line-through; }
  .bs-ing-reset { margin-top: 6px; }
  .bs-ing-reset button:disabled { opacity: 0.45; cursor: default; }
  /* ---- the live STEP VIEW (2026-09-06): the whole screen is the step you are on ----
     z-index 900 — BELOW the form-overlay ladder (1000+20/level, see the transcript-modal
     comment): every modal a step can open (note editor, appConfirm, anchor modal) must
     paint over this surface, and Escape must close THEM first. */
  #livemode { position: fixed; inset: 0; z-index: 900; background: var(--bg); display: none;
    flex-direction: column; overscroll-behavior: contain; }
  .lm-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 14px;
    padding-top: max(10px, env(safe-area-inset-top)); border-bottom: 1px solid var(--border); background: var(--card); }
  .lm-progress { font-weight: 700; color: var(--accent-dark); font-variant-numeric: tabular-nums; }
  /* P5: which bake this worklist is. Truncates rather than pushing the counters off a phone. */
  .lm-bake { font-weight: 700; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* R2/R3 (2026-10-20): the one line saying WHICH bake and WHY when the app chose. Its own row in the view's wrapping head. */
  .sheet-which { flex-basis: 100%; width: 100%; font-size: 0.9rem; color: var(--muted); margin: 2px 0 4px; }
  .sheet-which b { color: var(--text); }
  .sheet-which-switch { margin-left: 4px; }
  .sheet-draft-discard { margin-left: 6px; }
  .lm-drift, .lm-pos { color: var(--muted); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
  /* The drift as a BUTTON: a rest surface (border, tint), never a hover-only affordance — phones
     never hover, and this is the way back to the re-plan ask for a baker who said "Not now". */
  .lm-drift-act { background: #f7efe7; border: 1px solid #d6ae63; color: #6b4f1d; border-radius: 12px;
    padding: 3px 10px; font-weight: 600; cursor: pointer; }
  .lm-drift-act:hover { background: #f2e4cf; }
  .lm-pos { margin-left: auto; }
  .lm-scroll { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 14px;
    touch-action: pan-y; }
  .lm-card { max-width: 560px; margin: 0 auto; transition: transform 0.15s ease, opacity 0.15s ease; }
  .lm-card .bs-card-title strong { font-size: 1.2rem; }
  .lm-time { font-size: 1rem; }
  .lm-rel { font-weight: 700; color: var(--accent-dark); white-space: nowrap; }
  .lm-in { animation: lm-in 0.16s ease; }
  @keyframes lm-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
  .lm-swiping { transition: opacity 0.15s ease; }
  .lm-hint { text-align: center; font-size: 0.72rem; color: var(--muted); padding: 4px 14px 0; }
  .lm-hint-exit { padding-top: 2px; }
  /* The escape reads as the escape: a real target, and not the same weight as ▤ Overview beside
     it. ⚠️ It keeps the LEFT slot the old "☰ All steps" held — changing the label and the
     position in one go would lose the bakers who had learned where the way out lives. */
  .lm-close { font-weight: 600; border-color: var(--accent); color: var(--accent-dark); }
  .lm-foot { display: flex; align-items: stretch; gap: 10px; padding: 8px 14px;
    padding-bottom: max(10px, env(safe-area-inset-bottom)); border-top: 1px solid var(--border); background: var(--card); margin-top: 6px; }
  .lm-nav { flex: 0 0 auto; width: 52px; font-size: 1.4rem; border-radius: 12px; padding: 8px 0; }
  .lm-done { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; gap: 10px;
    border: 2px solid var(--accent); border-radius: 12px; padding: 11px; font-weight: 700; font-size: 1.05rem;
    cursor: pointer; background: var(--card); margin: 0; width: auto; }
  .lm-done input { width: 26px; height: 26px; accent-color: var(--accent); margin: 0; cursor: pointer; }
  /* the overview page's big jump into the steps — primary (solid), the page's one main act */
  .lm-go { flex: 1 1 auto; border-radius: 12px; padding: 11px; font-weight: 700; font-size: 1.05rem; }
  .bs-kitchen-now { margin-top: 8px; font-size: 0.85rem; }
  .lm-done-on { background: #e7f1e7; }
  .bs-now-btns { margin-left: auto; display: flex; gap: 6px; }
  /* ---- the levain jar + ferment progress (2026-09-06) ---- */
  /* The step view's card never fades wholesale: a ripening build's card is DONE by definition
     (you tick it when you feed the jar), and the jar + temp riding it were unreadable at the
     sheet's 0.5 done-fade. Done-ness still reads: strikethrough, ✓ done, the checked box. */
  .lm-card.bs-card-done { opacity: 1; }
  .lm-cond { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
  .lm-jar-row { display: flex; align-items: center; gap: 10px; font-size: 0.88rem; }
  .lm-jar { flex: 0 0 auto; }
  .lm-jar-glass { fill: none; stroke: var(--accent-dark); stroke-width: 1.6; }
  .lm-jar-rim { fill: var(--accent-dark); }
  .lm-jar-fill { fill: var(--accent); opacity: 0.4; }
  .lm-jar-over .lm-jar-fill { fill: #b07a2a; opacity: 0.5; }
  .lm-jar-bub { fill: #fff; opacity: 0.9; }
  .lm-jar-mark { stroke: var(--muted); stroke-width: 1.2; }
  .lm-bar { position: relative; height: 12px; border: 1px solid var(--border); border-radius: 999px;
    background: var(--card); overflow: hidden; margin: 4px 0 2px; min-width: 120px; }
  .lm-bar-fill { height: 100%; background: var(--accent); opacity: 0.45; border-radius: 999px; }
  .lm-bar-over .lm-bar-fill { background: #b07a2a; opacity: 0.55; }
  .lm-bar-pct { position: absolute; top: -1px; right: 6px; font-size: 0.62rem; font-weight: 700;
    color: var(--accent-dark); line-height: 12px; font-variant-numeric: tabular-nums; }
  .lm-ferm-line { font-size: 0.88rem; }
  .lm-temp { font-size: 0.85rem; }

  .bakesheet-notes { margin-top: 24px; }
  .bakesheet-notes-header { font-size: 0.95rem; font-weight: 600; margin: 0 0 6px;
    padding: 5px 0; border-bottom: 2px solid var(--border); }
  .bakesheet-notes-lines { height: 120px;
    background: repeating-linear-gradient(to bottom, transparent, transparent 29px,
      var(--border) 29px, var(--border) 30px); }

  /* The printed sheet gives a step's RECIPE notes their own row, spanning Step → Equipment →
     Ingredients (renderDayTable). A light frame keeps it reading as a note rather than as a
     fourth column, and the frame is a BORDER, not a fill: printers drop backgrounds unless a
     rule asks for them, so a tinted box would be invisible on paper. */
  .bs-noterow-cell { padding: 0 8px 7px; }
  .bs-noterow-cell .recipe-note-list { margin: 0; border: 1px solid #b9cbd3; border-radius: 5px; padding: 5px 8px; }
  .bs-noterow-cell .recipe-note { margin-bottom: 0; }
  .bs-noterow-cell .recipe-note + .recipe-note { margin-top: 5px; }
  /* A step and its own note are one thing — drop the rule between them. The Time and stage cells
     rowspan PAST the note row, so their own bottom border still closes the whole block, which is
     why they are excluded here rather than swept up with the rest. */
  .bakesheet-table tr:has(+ tr.bs-noterow) > td:not(.bs-time):not(.bs-stage) { border-bottom: none; }

  /* Per-step notes on the bake sheet */
  .bs-note-wrap { margin-top: 5px; }
  .bs-note { font-size: 0.82rem; color: #6b4f1d; background: #fbf1d6; border-left: 3px solid #d6ae63;
    padding: 3px 7px; border-radius: 3px; margin-bottom: 4px; white-space: pre-wrap; }
  .bs-note-btn { font-size: 0.74rem; padding: 1px 7px; background: transparent; color: var(--muted);
    border: 1px dashed var(--border); border-radius: 10px; cursor: pointer; }
  .bs-note-btn:hover { color: var(--accent-dark); border-color: var(--accent); }

  /* 📓 Bake notes: THIS bake's journal entries on a step — a cooler tint than the amber
     standing note (📝) so the two kinds read apart at a glance. Printable on purpose. */
  .bs-bakenote-wrap { margin-top: 5px; }
  .bs-bakenote { font-size: 0.82rem; color: #2c4a7c; background: #e8eef9; border-left: 3px solid #7c9bd6;
    padding: 3px 7px; border-radius: 3px; margin-bottom: 4px; white-space: pre-wrap; }

  /* The History tab's rows (✅ Option C, 2026-09-26): one card per bake day — the bake half
     (.hist-bake) above the sale half (.hist-sale), either of which may be absent. */
  .hist-row { margin-top: 10px; }
  .hist-row-here { outline: 2px solid var(--accent); outline-offset: 2px; }
  .hist-bake { margin-top: 2px; }
  .hist-sale { border-top: 1px dashed var(--border); margin-top: 8px; padding-top: 8px; }
  .hist-sale:first-child { border-top: none; margin-top: 0; padding-top: 0; }
  .hist-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
  .bh-timings { font-size: 0.82rem; margin: 4px 0; }
  .bh-timings summary { cursor: pointer; }
  .bh-head { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 0.92rem; }
  .bh-badge { font-size: 0.72rem; border: 1px solid var(--border); border-radius: 10px; padding: 0 6px;
    color: var(--muted); white-space: nowrap; }
  .bh-badge-now { color: var(--accent-dark); border-color: var(--accent); font-weight: 600; }
  .bh-items { font-size: 0.82rem; margin: 2px 0 4px; }
  .bh-note { font-size: 0.82rem; color: #2c4a7c; background: #e8eef9; border-left: 3px solid #7c9bd6;
    padding: 3px 7px; border-radius: 3px; margin: 4px 0; white-space: pre-wrap; }
  .bh-note-edit { margin-left: 4px; padding: 0 6px; font-size: 0.72rem; }

  /* Inline note + add/edit control on schedule steps */
  .schedule-note-wrap { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
  .schedule-note { font-size: 0.78rem; color: #6b4f1d; background: #fbf1d6; border-left: 3px solid #d6ae63;
    padding: 2px 6px; border-radius: 3px; white-space: pre-wrap; }
  .schedule-note-btn { font-size: 0.72rem; padding: 0 7px; line-height: 1.5; background: transparent;
    color: var(--muted); border: 1px dashed var(--border); border-radius: 10px; cursor: pointer; }
  .schedule-note-btn:hover { color: var(--accent-dark); border-color: var(--accent); }

  /* Recipe-stage notes (authored in the recipe editor), shown read-only on schedule + bake sheet */
  .recipe-note-list { margin-top: 4px; }
  .recipe-note { font-size: 0.78rem; color: #3a4a55; background: #eaf1f4; border-left: 3px solid #4F6A73;
    padding: 2px 6px; border-radius: 3px; margin-bottom: 3px; white-space: pre-wrap; }
  /* Long method notes clamp to 2 lines; tap/click toggles the full text */
  /* 📈 kitchen temp-curve check (kt-curve-details): d3, three series, the app's hover chrome */
  .ktcv-wrap { position: relative; }
  .ktcv-svg { width: 100%; height: auto; display: block; }
  .ktcv-grid { stroke: var(--border); stroke-width: 0.5; }
  .ktcv-grid-day { stroke-width: 1; }
  .ktcv-range { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 0 0 6px; }
  .ktcv-range-custom { font-size: 0.8rem; color: var(--muted); }
  .ktcv-range-hint { font-size: 0.75rem; margin-left: auto; }
  .ktcv-brush .selection { fill: var(--accent); fill-opacity: 0.12; stroke: var(--accent); stroke-opacity: 0.5; }
  .ktcv-brush .overlay { cursor: crosshair; }
  .ktcv-lbl { font-size: 10px; fill: var(--muted); }
  .ktcv-out { fill: none; stroke: #7a9cc4; stroke-width: 1.5; }
  .ktcv-model { fill: none; stroke: #1f4d2e; stroke-width: 2; }
  .ktcv-dot { fill: #b3541e; }
  /* The sensor away from the room (kitchenExcursions): a pale band behind the span, and its dots
     hollow — still measured, no longer the room. */
  .ktcv-away { fill: #b3541e; fill-opacity: 0.09; pointer-events: none; }
  .ktcv-dot-away { fill: var(--card, #fff); stroke: #b3541e; stroke-width: 1.2; }
  .ktcv-key { display: flex; gap: 14px; flex-wrap: wrap; font-size: 0.8rem; color: var(--muted); margin: 0 0 4px; }
  .ktcv-key-item { display: inline-flex; align-items: center; gap: 5px; }
  .ktcv-swatch { width: 14px; height: 3px; display: inline-block; border-radius: 2px; }
  .ktcv-swatch-out { background: #7a9cc4; }
  .ktcv-swatch-model { background: #1f4d2e; height: 4px; }
  .ktcv-swatch-dot { width: 7px; height: 7px; border-radius: 50%; background: #b3541e; }
  /* 🧊 the fridge's curve (2026-09-26): the fermentation chart's fridge ink (BO_FRIDGE_INK), and the
     set temperature as a dashed muted reference it is read against */
  .ktfr-line { fill: none; stroke: #4FB3E8; stroke-width: 1.8; }
  .ktfr-dot { fill: #2b8cc4; }
  .ktfr-ref { stroke: var(--muted); stroke-width: 1.2; stroke-dasharray: 5 4; }
  .ktfr-swatch-line { background: #4FB3E8; }
  .ktfr-swatch-ref { background: repeating-linear-gradient(90deg, var(--muted) 0 5px, transparent 5px 9px); height: 2px; }
  /* after .ktcv-swatch, which it must override (📐 the first screenshot drew it as a 3 px hairline):
     the band's own tint with a hollow dot in it, the two marks the plot uses for a sensor away */
  .ktcv-swatch-away { width: 14px; height: 12px; border-radius: 2px; background: rgba(179, 84, 30, 0.14)
    radial-gradient(circle at 50% 50%, transparent 0 2px, #b3541e 2.2px 3.2px, transparent 3.4px); }
  .recipe-note-long { cursor: pointer; }
  .recipe-note-long.clamped { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .schedule-tooltip .tt-note + .tt-note { margin-top: 4px; }

  /* Per-stage note control in the recipe editor */
  .stage-note-btn { white-space: nowrap; }
  .stage-note-text { flex-basis: 100%; font-size: 0.78rem; color: #3a4a55; background: #eaf1f4;
    border-left: 3px solid #4F6A73; padding: 2px 7px; border-radius: 3px; white-space: pre-wrap; }
  /* Always-visible note line on a stage row (collapsed or expanded): click to edit.
     Clamped to 3 lines — the edit modal shows the full text. */
  .stage-note-click { cursor: pointer; margin: 2px 0 4px;
    display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
  .stage-note-click:hover { background: #ddeaf0; }

  /* Per-oven preference control on the bake step (preferred / required / avoid) */
  .stage-oven-prefs { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; font-size: 0.8rem; }
  .stage-oven-pref { display: inline-flex; align-items: center; gap: 4px; }
  .stage-oven-name { color: #3a4a55; }
  .stage-oven-sel { font-size: 0.78rem; padding: 1px 4px; width: auto; flex: none; }
  /* Convection pick on the bake step — sits alongside the oven matrix, same scale */
  .stage-convection { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 6px; font-size: 0.8rem; margin-left: 10px; }
  .stage-convection-sel { font-size: 0.78rem; padding: 1px 4px; width: auto; flex: none; }
  .stage-steam { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 6px; font-size: 0.8rem; margin-left: 10px; }
  .stage-steam-sel { font-size: 0.78rem; padding: 1px 4px; width: auto; flex: none; }

  /* Per-oven preference matrix in the Bake Plan "Assign ovens" section */
  .bp-oven-row { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; padding: 4px 0; }
  .bp-oven-grouplabel { flex: 1 0 110px; }
  .bp-oven-matrix { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }
  .bp-oven-pref { display: inline-flex; align-items: center; gap: 4px; font-size: 0.82rem; }
  .bp-oven-name { color: #3a4a55; }
  .bp-oven-sel { font-size: 0.78rem; padding: 1px 4px; width: auto; flex: none; }

  /* The dough-temperature chart (Bake Plan · Dough temperature card, d3-drawn) */
  .dtc-block { margin: 10px 0 4px; }
  .dtc-proc { font-weight: 600; font-size: 0.88rem; color: var(--accent-dark); margin: 2px 0 4px; }
  .dtc-wrap { position: relative; }
  .dtc-axis text { font-size: 10px; fill: #6b5638; }
  .dtc-axis path, .dtc-axis line { stroke: #c9bfa8; }
  .dtc-mark-g circle { fill: #8F5A22; stroke: #fdfaf2; stroke-width: 1.5; }
  .dtc-mark-g text { font-size: 10.5px; fill: #5d4a2e; paint-order: stroke; stroke: rgba(253,250,242,0.9); stroke-width: 3px; }
  .dtc-mark-g.dtc-mk-prog circle { fill: #4a7c59; }
  .dtc-mark-g.dtc-mk-prog text { fill: #3d6549; }
  .dtc-key { display: flex; gap: 14px; flex-wrap: wrap; font-size: 0.75rem; color: #6b5638; margin-top: 4px; }
  .dtc-key i { display: inline-block; width: 14px; height: 0; border-top: 2px solid; margin-right: 4px; vertical-align: middle; }
  .dtc-k-kitchen i { border-color: var(--accent); opacity: 0.5; }
  .dtc-k-dough i { border-color: #8F5A22; }
  .dtc-step { fill: #fdfaf2; stroke: #8F5A22; stroke-width: 2; }
  .dtc-k-step i { width: 8px; height: 8px; border: 2px solid #8F5A22; border-radius: 50%; background: #fdfaf2; }
  .dtc-k-prog i { border-color: #4a7c59; border-top-style: dashed; }
  .dtc-k-butter i { border-top: 6px solid rgba(232,196,107,0.45); }
  .dtc-note { font-size: 0.8rem; color: #6b5638; margin: 6px 0 0; }
  .dtc-budget { font-size: 0.8rem; margin: 6px 0 0; }
  .dtc-budget b { color: var(--accent-dark); }

  /* Per-stage ingredient tagging in the recipe editor */
  .stage-ing-btn { white-space: nowrap; }
  .stage-ing-text { flex-basis: 100%; font-size: 0.78rem; color: #4a3618; background: #f3ead4;
    border-left: 3px solid #D6AE63; padding: 2px 7px; border-radius: 3px; }
  .stage-ing-panel { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 4px 14px; padding: 6px 8px;
    margin-top: 2px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }
  .stage-ing-opt { display: inline-flex; align-items: center; gap: 5px; font-size: 0.82rem; cursor: pointer; }

  /* Open-question cards in the import preview (PLAN-open-questions.md). One at a time. */
  .oq-wrap { margin: 10px 0; padding: 10px 12px; border: 1px solid var(--accent); border-radius: 9px; background: var(--card); }
  .oq-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .oq-dots { display: inline-flex; gap: 4px; flex: none; }
  .oq-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(0,0,0,.18); }
  .oq-dot.past { background: var(--accent); opacity: .5; }
  .oq-dot.on { background: var(--accent); }
  .oq-sub { font-size: .82rem; opacity: .8; margin-top: 2px; }
  .oq-sub code { background: rgba(0,0,0,.06); padding: 1px 5px; border-radius: 4px; }
  .oq-why { margin: 7px 0 9px; font-size: .84rem; opacity: .88; line-height: 1.45; }
  /* The page's own words under a question (meta.openQuestions[].evidence). Wraps anywhere: a pasted
     URL or a long unbroken token must never push the card past a phone's edge. */
  .oq-ev { display: flex; flex-direction: column; gap: 6px; margin: 2px 0 9px; min-width: 0; }
  .oq-quote { margin: 0; padding: 6px 10px; border-left: 3px solid var(--accent); border-radius: 0 6px 6px 0;
              background: rgba(127,127,127,.12); font-size: .82rem; line-height: 1.4; min-width: 0;
              overflow-wrap: anywhere; word-break: break-word; }
  .oq-quote .oq-src { display: block; margin-top: 2px; font-size: .74rem; opacity: .7; }
  .oq-opts { display: flex; flex-direction: column; gap: 6px; }
  .oq-opt { display: grid; grid-template-columns: auto 1fr auto auto; gap: 8px; align-items: baseline;
    padding: 7px 9px; border: 1px solid rgba(0,0,0,.16); border-radius: 7px; cursor: pointer; font-size: .86rem; }
  .oq-opt.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
  .oq-opt input { width: auto; flex: none; margin: 0; }
  .oq-lab em { font-style: normal; opacity: .6; font-size: .85em; }
  .oq-g { font-weight: 700; font-variant-numeric: tabular-nums; }
  .oq-per { opacity: .6; font-size: .85em; font-variant-numeric: tabular-nums; }
  .oq-note { grid-column: 2 / -1; font-size: .78rem; opacity: .7; line-height: 1.35; }
  .oq-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
  /* The app's own recommendation among a question's answers. Deliberately NOT `.active`: that is
     the "you chose this" language, and a recommendation is a suggestion nobody has accepted yet. */
  .bw-rec { border-color: var(--accent) !important; box-shadow: inset 0 0 0 1px var(--accent); }
  .bw-rec-pill { display: inline-block; font-size: .68rem; font-weight: 700; letter-spacing: .02em;
    text-transform: uppercase; background: var(--accent); color: #fff; border-radius: 999px;
    padding: 1px 7px; margin-right: 6px; vertical-align: 1px; }
  /* An answer that WORKS but is not ideal — it splits the dough, or cannot take even one unit.
     ⚠️ Still pressable: a dough legitimately spreads across tubs, and a baker may know something
     the app does not. It reads as a stated drawback, never as a disabled control or an alarm. */
  .bw-warn { border-style: dashed !important; border-color: #c9a227 !important; opacity: .82; }
  /* A way to change the situation rather than answer the question — quieter than an answer. */
  .bw-extra { text-align: left; border-style: dotted !important; }
  .bw-warn-pill { display: inline-block; font-size: .68rem; font-weight: 700; letter-spacing: .02em;
    text-transform: uppercase; background: #c9a227; color: #3a2c05; border-radius: 999px;
    padding: 1px 7px; margin-right: 6px; vertical-align: 1px; }
  .oq-actions button { margin: 0; }
  /* ---- Bake overview panel (the walk's ending — PLAN-bake-overview.md P1). Every tile is a
     HANDLE: affordance BEFORE interaction (phones never hover), so tiles carry a border and a
     pointer at rest and the open one is outlined — the same one-word-for-selected rule the walk
     follows elsewhere. */
  .bo-panel { margin-top: 8px; }
  .bo-title { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  .bo-when { font-size: .8rem; color: var(--muted); }
  .bo-kitchen-line { margin: 6px 0 0; font-size: .8rem; color: var(--muted); }
  .bo-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin: 10px 0 2px; }
  .bo-kpi { background: var(--accent-tint); border: 1px solid transparent; border-radius: 9px; padding: 8px 10px;
    color: var(--ink); cursor: pointer; font: inherit; text-align: left; }
  .bo-kpi b { display: block; font-size: 1.3rem; font-weight: 700; color: var(--accent-dark);
    font-variant-numeric: tabular-nums; line-height: 1.15; }
  .bo-kpi span { font-size: .68rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
  .bo-kpi:hover, .bo-chip:hover { border-color: var(--accent); }
  /* A READING, not a handle: no pointer, no hover border, nothing that promises a press. */
  .bo-kpi.bo-stat { cursor: default; }
  .bo-kpi.bo-stat:hover { border-color: transparent; }
  .bo-sec { margin-top: 14px; }
  .bo-sec h4 { margin: 0 0 6px; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); font-weight: 700; }
  /* One track per lane, all against the PLAN's time axis — so segments are positioned, not packed.
     A lane that starts three hours in must LOOK three hours in. */
  /* The tracks and the clock line under them inset to the CHART's plot box when one is drawn
     (`--bo-plot-l/r`, published by boDrawTempChart) so a step sits exactly above its own curve.
     Unset — no chart, chart closed, or the no-d3 fallback, which spans edge to edge — they fall
     back to 0 and the ribbon uses the full width as before. */
  .bo-ribbon { position: relative; height: 28px; border-radius: 7px; overflow: hidden;
    border: 1px solid var(--border); background: var(--bg);
    margin-left: var(--bo-plot-l, 74px); margin-right: var(--bo-plot-r, 0px); }
  /* 🔴 A BAR NOBODY CAN NAME IS A PUZZLE (Jonathan, 2026-08-26: *"we need the stages and active
     steps bar to have labels! No one will know what these show right now"*). The name sits in the
     gutter the chart already reserves for its row names, so the two surfaces read as one column of
     labels beside one shared clock — and the gutter has a FLOOR wide enough to hold these words,
     which is why the chart's own left margin is floored to the same number. */
  /* ⚠️ THE LABEL CANNOT LIVE INSIDE THE TRACK: the band clips its own segments
     (`overflow:hidden`), so a name positioned in the gutter beside it was invisible — the rail,
     which clips nothing, showed its label and the band did not. Each row is a wrapper holding the
     name and the track side by side. */
  .bo-lane-row { position: relative; }
  /* 🔴 WHERE THE BAKER IS. The ribbon drew the whole plan with no indication of which part of
     it has happened, so a baker mid-bake had to find themselves on it by reading clock labels.
     One line, through every lane, at the same instant — drawn INSIDE each track because
     .bo-ribbon clips its own children, so a single overlay across the block would be cut by
     the first band it crossed.
     ⚠️ Ink, not a hit area: this is a 2px hairline and nothing else, because the last thing
     painted into this 28px lane that was sized for touch read as "a broken renderer". It is
     not interactive at all — the readout above the ribbon carries the words. */
  .bo-nowline { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
    background: var(--danger); opacity: .85; pointer-events: none; z-index: 6; }
  .bo-nowline::after { content: ''; position: absolute; top: 0; left: -2px; width: 6px; height: 3px;
    background: var(--danger); border-radius: 0 0 2px 2px; }
  /* The sentence a baker actually reads. Sits above the tracks, so it is legible with the
     chart collapsed — a readout inside a closed disclosure does not exist. */
  .bo-nowread { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    margin: 2px 0 8px; font-size: .78rem; }
  .bo-nowread b { font-weight: 700; }
  .bo-nowread .bo-drift-late { color: var(--danger); }
  .bo-nowread .bo-drift-early { color: var(--accent-dark); }
  .bo-nowread .bo-nownext { color: var(--muted); }
  /* What the dough is doing now — the sentence a baker in the kitchen came for, so it carries
     the panel's own emphasis rather than the muted tone of the supporting facts around it. */
  .bo-nowread .bo-nowstate { color: var(--accent-dark); }
  .bo-track-lab { position: absolute; left: 0; top: 0; bottom: 0; width: var(--bo-plot-l, 74px);
    display: flex; align-items: center; justify-content: flex-end; padding-right: 7px;
    font-size: .62rem; font-weight: 700; color: var(--muted); white-space: nowrap;
    overflow: hidden; pointer-events: none; }
  /* 🔴 THE GROUPING MUST READ THE RIGHT WAY ROUND (Jonathan, 2026-08-27: *"can we add some
     spacing between the different recipes? right now it's too cramped … specifically the active
     steps and the second recipe"*). A band and its rail are ONE recipe and belong tight together;
     the next recipe needs a visible break. 📐 Measured at 900px before the fix: 3px INSIDE a
     recipe and **0px between recipes** — the grouping exactly inverted, which is what "cramped"
     was describing.
     ⚠️ THE RULE FOR IT EXISTED AND HAD SILENTLY STOPPED MATCHING. It was written as
     `.bo-rail + .bo-lane-lab` when the rail was the label's own previous sibling; giving each
     track a name (2026-08-26) wrapped both in `.bo-lane-row`, so the adjacency broke and the
     9px it asked for became 0. Nothing failed — a sibling selector that stops matching is
     silent, which is why the gap is now PINNED as a relationship in dom.test.js rather than
     trusted to a stylesheet. */
  .bo-lane-row + .bo-lane-lab { margin-top: 16px; }
  .bo-lane-lab { font-size: .7rem; font-weight: 700; color: var(--muted); margin: 0 0 4px;
    text-transform: uppercase; letter-spacing: .03em; }
  .bo-lane-shared { font-weight: 500; text-transform: none; letter-spacing: 0; font-style: italic; }
  /* P5 — one lane GROUP per bake. The heading names the bake and its state; the lanes inside it
     are that bake's own doughs. Another bake's group is dimmed and its bands carry no press. */
  .bo-lane-group + .bo-lane-group { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--border); }
  .bo-bake-lab { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    font-size: .82rem; font-weight: 700; margin: 0 0 6px; }
  .bo-bake-state { font-size: .68rem; font-weight: 500; color: var(--muted);
    border: 1px solid var(--border); border-radius: 10px; padding: 0 6px; }
  .bo-lane-group-other .bo-lane-lab { opacity: .8; }
  .bo-seg-other { cursor: default; opacity: .62; }
  .bo-hand-other { cursor: default; opacity: .62; }
  /* 🔴 SQUARE ENDS, OR EVERY BOUNDARY LOOKS LIKE A GAP (Jonathan, 2026-08-26). A segment is a
     <button>, and the global button rule rounds every button by 6px — which the ribbon never
     reset. So each phase was a lozenge, and where two phases abut, the two curves carved a wedge
     of background out of the join: a notch at EVERY boundary, on a track whose whole subject is
     that there are no gaps. 📐 On a 10 px Folds bar the rounding was the entire bar. The track
     keeps its own radius and clips the outer ends, which is the only place a curve belongs. */
  .bo-seg { position: absolute; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center;
    min-width: 2px; border: 0; border-radius: 0; padding: 0; cursor: pointer; font: inherit; z-index: 2;
    /* a label never leaves its own bar: the width gate is %-of-plan, so on a phone a "Proof"
       that passes it can still be wider than its bar and collide with the next label */
    overflow: hidden; }
  /* 🔴 STATES UNDER, ACTIONS OVER, TICKS ON TOP. Phases can genuinely overlap (a levain ferments
     while you gather flour; the fridge holds the batard while the boule is shaped), and DOM order
     alone put whichever phase STARTED later on top — which buried a shaping bar under the fridge
     band it overlaps. A waiting-stage band is background by meaning, so it is background by
     z-order; the open/lit segment comes forward so a pressed band is never hidden by what covers
     it at rest. */
  .bo-seg-wait { z-index: 1; }
  .bo-seg-tick { z-index: 3; }
  .bo-seg.bo-open, .bo-seg.bo-lit, .bo-seg.bo-hot { z-index: 4; }
  .bo-seg:hover { filter: brightness(1.07); }
  /* The reverse highlight: point at a piece of gear and its steps stand up out of the ribbon.
     An inset ring rather than an outline, so a 2px segment still shows it and nothing overflows
     the track's rounded ends. */
  .bo-seg.bo-lit, .bo-seg.bo-hot { box-shadow: inset 0 0 0 2px var(--ink); filter: brightness(1.1); }
  .bo-seg-lab { font-size: .62rem; font-weight: 700; opacity: .9; white-space: nowrap;
    max-width: calc(100% - 4px); overflow: hidden; text-overflow: ellipsis; }
  /* 🔴 A MOMENT IS A TICK, NOT A BAR. A step with no duration ("Gather ingredients", "Turn off the
     oven") used to be dropped from the ribbon entirely, because a hairline nobody can hit is worse
     than nothing — but dropping it means the only route to that step is the Schedule tab. A tick
     is a fixed 14px mark centred on the moment, INSET from the track's full height so it cannot be
     read as a span: a full-height block is a duration, a short pill is a point in time. Half of it
     clips at either extreme, so the narrowest it ever renders is 7px across a 28px track. */
  /* 🔴 A TICK IS A MARK ON THE TRACK, NOT A LITTLE BLOCK ON IT. 📐 Reported live 2026-08-26
     ("the steps don't span the space correctly and are weirdly spread out"): the first draft
     painted the 14px HIT AREA, so every momentary step — prep, mix, shape, cool, oven on, oven
     off, ready — became a rounded 14px square sitting beside and on top of the bars. Five of them
     in a lane reads as a broken rendering, which is exactly what it was called.
     ⇒ The BUTTON stays 14px so it is tappable; the INK is a 3px hairline inside it. The reason
     the old code dropped these entirely was that a hairline cannot be hit — separating the two
     answers both halves at once. */
  /* 🔴 THE HANDS-ON RAIL (Jonathan, 2026-08-26: *"lets have a thin bar underneath depict the
     active steps (all one color) … this bar should read as a guide to the shape of the sequence
     of active steps over time"*). It replaces the ticks, which were the third attempt to fit
     moments INTO the band and the third to be reported as broken: a 3 px mark in its stage's
     colour read as a slot cut through the band, landed on top of the bar labels, and collided
     with its neighbours (two of them measured 2 px apart, each 14 px wide).
     📐 The rail carries MORE than the ticks did, which is the point: 8–15 marks a lane against
     4–7 ticks, because most hands-on work lives INSIDE bars — the six folds hidden in one "Folds"
     bar are six marks here, and the rhythm of the day is what the strip is for.
     One colour throughout: a mark means "this needs you", and the stage is the band above it. */
  .bo-rail { position: relative; height: 11px; margin: 3px 0 0;
    margin-left: var(--bo-plot-l, 74px); margin-right: var(--bo-plot-r, 0px);
    border-radius: 3px; background: var(--bg); border: 1px solid var(--border); }
  .bo-hand { position: absolute; top: 0; bottom: 0; min-width: 3px; border: 0; border-radius: 0;
    padding: 0; cursor: pointer; background: #6b5a3e; opacity: .82; }
  .bo-hand:hover, .bo-hand:focus { opacity: 1; background: var(--ink); }
  .bo-hand.bo-lit, .bo-hand.bo-open { background: var(--ink); opacity: 1; }
  .bo-ends { display: flex; justify-content: space-between; font-size: .72rem; color: var(--muted); margin-top: 3px;
    margin-left: var(--bo-plot-l, 74px); margin-right: var(--bo-plot-r, 0px); }
  /* 🌡 The precision layer. The chart sits directly under the ribbon and shares its x-axis, so the
     two must stay the same width and take no horizontal padding of their own — the night dip
     lining up with the phase it lengthens IS the feature. */
  .bo-temp { margin-top: 8px; }
  .bo-temp-wrap { position: relative; }
  .bo-temp-svg { width: 100%; height: 96px; display: block; border: 1px solid var(--border);
    border-radius: 7px; background: var(--card); }
  /* Markers are HTML overlays, not SVG text: the chart is drawn with preserveAspectRatio="none"
     so any text inside it would stretch with the viewBox. */
  .bo-mark { position: absolute; transform: translate(-3px, -50%); display: flex; align-items: center;
    gap: 4px; pointer-events: none; white-space: nowrap; }
  .bo-mark i { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
  .bo-mark em { font-style: normal; font-size: .66rem; color: var(--ink); background: var(--card);
    border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px; opacity: .95; }
  .bo-mk-stall i { background: #4a7fb5; }
  .bo-mk-done i { background: #8F5A22; }
  /* 🔴 A LABEL PAST THE MIDDLE READS LEFTWARDS, or it hangs off the panel — the bake terminus sits
     at ~98% of the axis by construction (it is the end of the bake), so this is the normal case,
     not an edge case. Found by screenshotting: "≈205°F internal — typical for this ki" was cut. */
  .bo-mark-l { flex-direction: row-reverse; transform: translate(calc(-100% + 3px), -50%); }
  /* Left-hand side: the markers cluster on the right (the fridge floor and the bake terminus). */
  /* ⚠️ The y-bounds live INSIDE the chart. As a row underneath they sat immediately above the
     ribbon's clock row, and two ends-rows stacked read as one axis labelled twice. */
  /* 🔴 The step you are reading, banded on the curve. Sits UNDER the lines (z-index) so it never
     obscures the thing it is pointing at, and is drawn in the same ink as the ribbon's own
     highlight so the two read as one gesture. */
  .bo-temp-band { position: absolute; top: 0; bottom: 0; pointer-events: none;
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    border-left: 1px solid var(--accent); border-right: 1px solid var(--accent); }
  .bo-temp-band-lit { background: color-mix(in srgb, var(--accent) 16%, transparent); }
  /* The d3 chart: real pixels, so it needs a positioned wrapper for the tooltip + hover band. */
  /* 🔴 THE INVITATION ONTO THE FOLDED CHART. A REST STATE is the point: a bordered, tinted row
     that is visible with nothing hovered and nothing pressed, because phones never hover and a
     control that cannot be found is worse than one that is not there. */
  .bo-chart-invite { display: flex; align-items: center; gap: 10px; width: 100%; margin: 10px 0 0;
    padding: 10px 12px; text-align: left; cursor: pointer;
    background: color-mix(in srgb, var(--accent) 7%, var(--card));
    border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
    border-radius: 10px; color: var(--ink); font: inherit; }
  .bo-chart-invite:hover { background: color-mix(in srgb, var(--accent) 13%, var(--card)); }
  .bo-chart-invite:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .bo-ci-ico { font-size: 1.05rem; line-height: 1; flex: none; }
  .bo-ci-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
  .bo-ci-lead { font-weight: 600; font-size: .92rem; }
  .bo-ci-sub { font-size: .78rem; color: var(--muted); }
  .bo-ci-chev { flex: none; color: var(--muted); font-size: .9rem; }
  .bo-chart-body[hidden] { display: none; }
  .bo-assign-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; flex-wrap: wrap; }
  .bo-assign-step { flex: 1 1 auto; min-width: 110px; font-size: .88rem; }
  .bo-fridge-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; flex-wrap: wrap; }
  .bo-fridge-name { flex: 1 1 auto; min-width: 90px; font-size: .88rem; }
  .bo-fridge-temp { width: 64px; flex: none; }
  .bo-fridge-unit { color: var(--muted); font-size: .85rem; }
  /* The prep invitation, standing where the panel will be. A REST STATE that reads as the next
     thing to do — it is the only control on the tab until it is pressed. */
  /* P0 of PLAN-multi-bake: which bake this editor is editing. A plain line, not a control — the
     Bakes card that lists and switches them is P1. */
  /* P1 of PLAN-multi-bake: the Bakes card. Reuses .card/.bh-badge/button.secondary rather than a
     second visual language — a state chip is just a .bh-badge with its own colour. */
  .bakes-card-row1 { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  /* THE BAKE CARDS (2026-10-13): one card per picked bake plus ＋ New bake, on the Plan tab; the same cards (no ＋) at the
     top of the bake sheet and the step view. A card is a BUTTON, so its text colour is set here — the base button rule
     paints white (the baker-chip lesson). ≥44px tall, wrapping, never scrolling sideways. */
  .bake-cards { display: flex; flex-wrap: wrap; gap: 8px; }
  .bake-card { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; flex: 1 1 140px; max-width: 260px;
    min-height: 44px; padding: 8px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--card, #fff);
    color: var(--ink); text-align: left; font: inherit; cursor: pointer; }
  .bake-card:hover { background: var(--card, #fff); }
  .bake-card-current { border-color: var(--accent); background: var(--accent-light, #e8f0e8); cursor: default; }
  .bake-card-current:hover { background: var(--accent-light, #e8f0e8); }
  .bake-card-new { border-style: dashed; justify-content: center; align-items: center; color: var(--muted); font-weight: 600; }
  .bake-card-name { font-weight: 600; font-size: .95rem; line-height: 1.25; }
  .bake-cards-sheet { margin: 6px 0 2px; }
  /* The tick-started countdown (2026-10-24): a cold proof or a bake the baker ticked IN, its planned length running from the
     tick. On the cards, the chip, the Now card and the step line; "time's up" turns it the danger colour. */
  .bake-card-span, .bs-span-left, .span-countdown { font-size: .8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
  .span-countdown-over { color: var(--danger); font-weight: 600; }
  .bs-span-hint { font-size: .8rem; }
  /* 🔒 The lock on a picked bake's plan (2026-10-13). The line wraps on a phone; the veil is a BUTTON over the whole
     editing region (so a tap anywhere on a locked control asks), painted nothing at rest and at hover — the base
     button rule would paint it solid accent. The body under it is dimmed and `inert`. */
  .plan-lock-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 10px; font-size: .88rem; color: var(--muted); }
  .plan-lock-line button { min-height: 32px; }
  .plan-edit-region { position: relative; }
  .plan-edit-locked { opacity: .55; }
  button.plan-lock-veil, button.plan-lock-veil:hover { position: absolute; inset: 0; z-index: 5; background: transparent; border: 0; border-radius: 8px; padding: 0; margin: 0; cursor: pointer; }
  button.plan-lock-veil:focus-visible { outline: 2px dashed var(--accent); }
  /* flex-basis 100% keeps the actions on their OWN line under every row, whether it offers one
     button or two — a row with only Archive sitting beside the name chips (room enough at some
     widths, not others) read as an inconsistent layout rather than a deliberate one. */
  /* The Schedule press. Same shape as .bo-prep beside it (text left, one press right, wrapping on
     a phone) so the two reads as one header rather than two competing cards. */
  .bake-sched-bar { display: flex; align-items: center; justify-content: space-between; gap: 14px;
    flex-wrap: wrap; }
  .bake-sched-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 240px; }
  .bake-sched-lead { font-weight: 600; font-size: 1rem; }
  .bake-sched-sub { font-size: .85rem; color: var(--muted); }
  .bake-sched-actions { display: flex; gap: 8px; flex-wrap: wrap; }
  /* ⚠️ 36px unconditional, like the Bakes card's own actions — a phone at 390px is the surface
     this press exists to be usable on, and a `white-space: nowrap` label must not shrink it. */
  .bake-sched-actions button { min-height: 36px; white-space: nowrap; }
  .bake-sched-msg { flex: 1 1 100%; font-size: .85rem; margin-top: 4px; }
  /* P3: always its own line (`flex: 1 1 100%`, the Bakes card's action-row lesson) — a name list
     wedged beside a press reads as a broken layout the moment there are two bakes. */
  .bake-fitted-line { flex: 1 1 100%; font-size: .85rem; margin-top: 6px; color: var(--muted);
    border-top: 1px solid var(--border, #ddd); padding-top: 6px; }
  /* P4: always its own line, like the Fitted-around line above it — a bake's joint history is a
     sentence, not a chip, and a name list wedged beside two presses reads as a broken layout. */
  .bake-joint-line { flex: 1 1 100%; font-size: .85rem; margin-top: 6px; color: var(--accent-dark); }
  /* P4: the Re-plan together press and the proposal it produces. Same shape as .bake-sched-bar
     beside it (text left, presses right, wrapping on a phone) so the header reads as one thing. */
  .replan-card { display: flex; align-items: center; justify-content: space-between; gap: 14px;
    flex-wrap: wrap; }
  .replan-card .bake-sched-actions button { min-height: 36px; }
  .replan-optins { flex: 1 1 100%; display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
  /* ⚠️ A tick box a thumb can hit, and its label on the same line as the box — phones never hover,
     so the whole label is the target. */
  .replan-optin { display: flex; align-items: flex-start; gap: 8px; font-size: .85rem;
    min-height: 36px; line-height: 1.35; }
  .replan-optin input { margin-top: 3px; flex: 0 0 auto; }
  .replan-optin-txt { min-width: 0; }
  .replan-msg { flex: 1 1 100%; font-size: .85rem; margin-top: 4px; }
  .replan-bakes { flex: 1 1 100%; display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
  .replan-bake { border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
    background: var(--accent-tint); }
  .replan-bake-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .replan-bake-name { font-weight: 600; }
  .replan-count { font-size: .8rem; }
  .replan-moves { list-style: none; margin: 6px 0 0; padding: 0; }
  /* ⚠️ The step name wraps, the times do not: a move row that breaks between "Sat 6:35 AM" and
     "→ 7:05 AM" stops reading as one fact. */
  .replan-move { display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap;
    font-size: .85rem; padding: 2px 0; }
  .replan-move-title { min-width: 0; }
  .replan-move-times { white-space: nowrap; color: var(--muted); }
  .replan-move-delta { font-variant-numeric: tabular-nums; }
  .replan-more, .replan-none { font-size: .82rem; margin-top: 4px; }
  .replan-summary { flex: 1 1 100%; font-size: .82rem; margin-top: 6px;
    border-top: 1px solid var(--border, #ddd); padding-top: 5px; }
  .replan-respected { flex: 1 1 100%; font-size: .82rem; margin-top: 8px; }
  .bo-prep { display: flex; align-items: center; justify-content: space-between; gap: 14px;
    flex-wrap: wrap; }
  .bo-prep-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 240px; }
  .bo-prep-lead { font-weight: 600; font-size: 1rem; }
  .bo-prep-sub { font-size: .85rem; color: var(--muted); }
  .bo-prep button { white-space: nowrap; flex-shrink: 0; }
  /* P3: what the schedule is taking on trust. Quiet by default — it is a disclosure, not a
     warning — with the one genuinely costly item (a single oven holding two temperatures)
     wearing the danger colour, because that one changes how long the day takes. */
  .bo-assume { margin-top: 10px; padding: 8px 10px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--accent-tint); }
  .bo-assume h5 { margin: 0 0 4px; font-size: .82rem; font-weight: 600; }
  .bo-assume ul { margin: 0; padding-left: 18px; }
  .bo-assume li { font-size: .82rem; color: var(--muted); margin: 3px 0; }
  .bo-assume li.bo-assume-warn { color: var(--danger); }
  /* A MEASURED source is the one line in this list that is not an assumption — it reads as good
     news rather than as a gap, so it takes the ok colour instead of the muted body colour. */
  .bo-assume li.bo-assume-measured { color: var(--ok); }
  .bo-assume button { margin-left: 4px; vertical-align: baseline; }
  /* The flow feet (2026-09-24): one full-width door at the foot of Plan, Schedule and Bake. */
  .flow-foot .flow-btn { width: 100%; padding: 12px 14px; font-size: 1rem; text-align: center; }
  .flow-foot .flow-note { margin: 0; padding: 10px 12px; font-size: .9rem; text-align: center; border: 1px dashed var(--border); border-radius: 10px; }
  .bs-now-armed .bs-armed-lead { font-weight: 600; display: block; margin-bottom: 4px; }
  .bo-cta { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    flex-wrap: wrap; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
  .bo-cta-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .bo-cta-lead { font-weight: 600; font-size: .92rem; }
  .bo-cta-sub { font-size: .8rem; color: var(--muted); }
  .bo-cta button { white-space: nowrap; flex-shrink: 0; }
  .bo-choice-sub { color: var(--muted); font-size: .78rem; margin: 6px 0 2px; }
  .bo-order-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
  .bo-order-n { color: var(--muted); font-size: .8rem; width: 1.4em; flex: none; }
  .bo-order-name { flex: 1 1 auto; min-width: 0; font-size: .88rem; }
  /* The count travels with the row: the Bake Order card said "4 loaves" beside each name, and a
     reorder is a decision about how much goes in first as much as about which. */
  .bo-order-count { color: var(--muted); font-size: .8rem; flex: none; }
  /* ⚠️ ON PAPER THERE IS NO PRESS. The folded chart genuinely should not print — it is a choice,
     not a clamp — but the INVITATION must not print either: a printed sheet cannot be tapped, so
     the row would be an instruction the reader can never follow. */
  @media print { .bo-chart-invite { display: none; } }
  .bo-dtc-wrap { position: relative; }
  .bo-dtc-svg { display: block; width: 100%; max-width: 100%; }
  .bo-dtc-band { fill: color-mix(in srgb, var(--accent) 16%, transparent); }
  .bo-dtc-svg .dtc-axis text { font-size: .68rem; fill: var(--muted); }
  .bo-dtc-svg .dtc-axis path, .bo-dtc-svg .dtc-axis line { stroke: var(--border); }
  .bo-dtc-svg .dtc-step { fill: #fff; stroke: #8F5A22; stroke-width: 1.5; }
  .bo-dtc-svg .dtc-mark-g circle { fill: #8F5A22; }
  /* The now line: a hairline the eye finds, the label haloed like the marks, the measured reading
     a filled point on the room's rail in the kitchen line's own colour. */
  .dtc-now { stroke: #b45309; stroke-width: 1.2; stroke-dasharray: 2 3; }
  .dtc-now-lab { font-size: 10.5px; fill: #92400e; font-weight: 600; paint-order: stroke; stroke: rgba(253,250,242,0.9); stroke-width: 3px; }
  .dtc-now-reading { fill: var(--accent); stroke: #fff; stroke-width: 1.5; }
  .bo-dtc-svg .dtc-mk-stall circle { fill: #4a7fb5; }
  .bo-dtc-svg .dtc-mk-prog circle { fill: #4a7c59; }
  /* ⚠️ A HALO, because the marks sit ON the lines they describe. 📐 Read off a live render: the
     dashed accumulation line crossing behind "— 8 h in" made it look like ".8 h in", and I briefly
     took that for a tenfold error in the cooling model before measuring the label text (it was
     right) and the curve (74.3°F → 42.1°F over 361 min at τ=165 — also right). Paint the stroke
     under the fill so a crossing line cannot eat a digit. */
  .bo-dtc-svg .dtc-mark-g text { font-size: .66rem; fill: var(--ink);
    paint-order: stroke; stroke: var(--card); stroke-width: 3px; stroke-linejoin: round; }
  .bo-dtc-svg .dtc-axis text { paint-order: stroke; stroke: var(--card); stroke-width: 3px; }
  /* The percentage strip: its own floor and ceiling, so it reads as a second chart rather than as
     more of the temperature panel. Faint — it is a container, not a series. */
  .bo-dtc-svg .dtc-pct-bg { fill: var(--ink); opacity: .035; }
  .bo-dtc-svg .dtc-pct-rule { stroke: var(--border); stroke-width: 1; }
  .bo-dtc-svg .dtc-panel-floor { stroke: var(--border); stroke-width: 1; opacity: .7; }
  .bo-dtc-svg .dtc-pct-title { font-size: .62rem; fill: var(--muted); }
  /* A lane is a track plus a fill. The track has to be visible on its own, or a bar that is barely
     started looks like nothing was drawn at all. */
  .bo-dtc-svg .dtc-lane-track { fill: var(--ink); opacity: .07; }
  .bo-dtc-svg .dtc-lane-notch { stroke: var(--card); stroke-width: 1; opacity: .8; }
  .bo-dtc-svg .dtc-lane-lab { font-size: .6rem; fill: var(--muted); }
  /* The stage name under its own bar — smaller than the row label, because the row is the subject
     and the stage is a part of it. */
  .bo-dtc-svg .dtc-bar-lab { font-size: .57rem; fill: var(--muted); }
  .bo-dtc-svg .dtc-lane-cursor { fill: var(--card); stroke: var(--ink); stroke-width: 1.2; }
  .bo-k-levain { background: repeating-linear-gradient(90deg, #B08A3E 0 3px, transparent 3px 6px) !important; }
  /* The ripeness line is the levain's colour on a LONGER dash — the same relationship the dough's
     temperature line has to its fermentation line, so the pair reads as one subject. */
  /* The chart's explanation, collapsed: the first sentence is the summary, the rest one tap away. */
  .bo-temp-note-d > summary { list-style: none; cursor: pointer; }
  .bo-temp-note-d > summary::-webkit-details-marker { display: none; }
  .bo-temp-more { font-size: .72rem; color: var(--accent-dark, #2f6f4f); margin-left: 6px;
    white-space: nowrap; text-decoration: underline; }
  .bo-temp-note-d[open] .bo-temp-more { display: none; }
  .bo-temp-note-d[open] > summary > .bo-temp-note { margin-bottom: 0; }
  /* 🔴 THE KEY'S ENTRIES ARE CHECKBOXES, AND THEY LOOK LIKE IT AT REST.
     ⚠️ The first cut styled them `background:none;border:none` with only a `:hover` surface, and
     wrote in its own comment that they "look like the labels they replaced until you touch them"
     — framing the defect as a feature. PHONES NEVER HOVER, so on the device this panel is designed
     for they were indistinguishable from plain text and Jonathan reported "I don't see the
     toggles?" while pressing them would have worked. This project already has that rule written
     down (ui-density-feedback: *affordance BEFORE interaction — secondary buttons get a REST
     surface, phones never hover*), and shipping past it cost a live round trip.
     ⇒ a real box with a real tick, visible without touching anything, in the shape Jonathan's own
     words asked for ("so they could UNCHECK kitchen temp"). */
  .bo-k-btn { background: none; border: 1px solid var(--border); padding: 2px 7px 2px 5px;
    margin: 0; font: inherit; color: inherit; cursor: pointer; display: inline-flex;
    align-items: center; gap: 5px; border-radius: 999px; line-height: 1.4; }
  .bo-k-btn:hover { background: rgba(0,0,0,0.05); }
  .bo-k-box { width: 12px; height: 12px; flex: none; border: 1.5px solid var(--muted);
    border-radius: 3px; display: inline-flex; align-items: center; justify-content: center;
    font-size: 9px; line-height: 1; color: #fff; background: var(--accent); border-color: var(--accent); }
  .bo-k-off .bo-k-box { background: transparent; border-color: var(--muted); color: transparent; }
  .bo-k-off { opacity: .55; }
  .bo-k-off .bo-k-lab { text-decoration: line-through; }
  .bo-k-sub { opacity: .8; font-size: .95em; }
  /* ⚠️ THE SWATCH IS A CLAIM ABOUT THE MARK. These two became BARS on 2026-08-21, so their key
     entries became bars too — a dashed-line swatch beside a filled bar is a small lie the reader
     has to correct for, and the key exists precisely so they do not have to. Each shows the same
     pale-to-full fill the bar uses. */
  .bo-k-levain-ripe { background: linear-gradient(90deg, rgba(176,138,62,.16), #B08A3E) !important; }
  .bo-k-prog { background: linear-gradient(90deg, rgba(74,124,89,.16), #4a7c59) !important; }
  .bo-hover-dough { margin-left: 8px; color: var(--muted); font-size: .74rem; }
  .bo-temp-ax { position: absolute; left: 5px; font-size: .6rem; color: var(--muted);
    background: var(--card); padding: 0 2px; }
  .bo-temp-ax-hi { top: 2px; }
  /* The no-d3 chart's now label — the twin of .dtc-now-lab, on the same rung as the axis labels. */
  .bo-temp-now-lab { top: 2px; color: #92400e; font-weight: 600; transform: translateX(3px); white-space: nowrap; }
  .bo-temp-now-lab-l { transform: translateX(-100%) translateX(-3px); }
  /* ⚠️ The temperature FLOOR is no longer the chart's floor — the percentage strip sits under it,
     so this label is positioned from the panel boundary and nudged up to sit on its own line. */
  .bo-temp-ax-lo { transform: translateY(-100%); margin-top: -2px; }
  .bo-pct-ax-hi { margin-top: 2px; }
  .bo-pct-ax-lo { bottom: 2px; }
  .bo-key { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 5px;
    font-size: .72rem; color: var(--muted); }
  .bo-key i { display: inline-block; width: 14px; height: 3px; border-radius: 2px; margin-right: 4px;
    vertical-align: middle; }
  /* ⚠️ THE SWATCH MUST LOOK LIKE THE LINE. The kitchen line was pushed into the background
     (dashed, thin, faint) on 2026-08-26, and a solid dark chip beside it would be the key
     disagreeing with the chart it explains — the same rule that keeps both renderings sharing
     one key. */
  .bo-k-kitchen { background: repeating-linear-gradient(to right,
    var(--accent) 0 4px, transparent 4px 7px); opacity: .5; }
  .bo-k-dough { background: #8F5A22; }
  /* The fridge line is faint and dotted across the plan and SOLID over each cold proof — so the
     swatch is both halves, the same rule the kitchen's dashed swatch keeps. */
  .bo-k-fridge { background: linear-gradient(90deg, rgba(79,179,232,.35) 0 45%, #4FB3E8 45%); }
  .bo-k-butter { background: #E8C46B; height: 8px !important; border-radius: 2px; }
  .bo-temp-note { font-size: .77rem; color: var(--muted); margin: 5px 0 0; line-height: 1.45; }
  .bo-temp-note b { color: var(--ink); }
  /* A quiet handle, not a tile: it discloses provenance rather than a part of the bake. */
  .bo-why { background: none; border: none; padding: 0; margin-left: auto; font-size: .72rem;
    color: var(--accent); text-decoration: underline; cursor: pointer; }
  .bo-why.bo-open { font-weight: 700; }
  .bo-tag { font-size: .66rem; font-weight: 600; color: var(--accent); border: 1px solid var(--accent);
    border-radius: 999px; padding: 0 6px; margin-left: 6px; vertical-align: middle; }
  /* The timeline's read-out line: the legend's information, on hover, one step at a time.
     Fixed height so a hover never nudges the panel. */
  .bo-hover { margin-top: 7px; min-height: 1.15rem; font-size: .78rem; color: var(--ink);
    display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
  /* 🔴 THE READ-OUT RESERVES ITS SECOND LINE WHERE IT CAN NEED ONE. It sits directly under the
     ribbon now, with the temperature chart beneath it, so a line that GROWS on hover pushes the
     chart down — 📐 measured at 390px: 18.4px of hint becomes 36.2px on "Mix · 1 h 30 m · 10 min
     hands-on · Wed 3:14 PM → Wed 4:44 PM · 🌡 dough 78°F", shoving the chart 17.8px on every
     hover, and on a phone the interaction is a TAP so the chart moves under the finger.
     ⚠️ Scoped to narrow viewports because the wrap is: at 1100px the worst segment still measures
     18.4px, and reserving a blank line there would be cost with no cause. Top-aligned so the
     first line stays put under the ribbon instead of drifting down when the second arrives. */
  @media (max-width: 640px) {
    .bo-hover { min-height: 2.35rem; align-items: flex-start; }
  }
  /* ⚠️ AND A THIRD LINE ON A REAL PHONE. 📐 The two-line reserve above was measured with
     --window-size=390 — but Chrome floors a headless window at 500px on macOS, so it was
     measured at 500 and the "390px" in its first commit message was wrong. Re-measured through
     CDP device emulation at a TRUE viewport (UKMTC, 2026-08-24): the read-out reaches 51.5px at
     390 and 54.3px at 320 — three lines — so the chart was still being shoved 13.9px and 16.7px.
     ⇒ Reserve what was measured, at the width where it was measured. */
  @media (max-width: 430px) {
    .bo-hover { min-height: 3.5rem; }
  }
  .bo-hover-hint { color: var(--muted); }
  .bo-hover-sw { width: 10px; height: 10px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }
  .bo-recs { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
  /* 🔴 `button { color: white }` IS THE GLOBAL DEFAULT — a tile that re-backgrounds itself and
     forgets `color` renders its name white on white. That is not hypothetical: the P1 equipment
     chip shipped exactly that way and every gear name was invisible in production for a day
     (found 2026-08-20 by SCREENSHOTTING the panel; no assertion could see it, so dom.test now
     compares computed colours). Any new .bo-* tile states its colour. */
  .bo-rec { border: 1px solid var(--border); border-radius: 9px; padding: 8px 11px; background: var(--bg);
    color: var(--ink); cursor: pointer; font: inherit; text-align: left; display: block; }
  .bo-rec:hover { border-color: var(--accent); }
  .bo-rec-name { display: block; font-weight: 600; font-size: .9rem; margin-bottom: 2px; }
  .bo-rec-line { display: block; font-size: .76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
  .bo-rec-line b { color: var(--ink); font-weight: 600; }
  .bo-rec-line em { font-style: normal; }
  .bo-sec-note { text-transform: none; letter-spacing: 0; font-weight: 500; }
  .bo-chips { display: flex; flex-wrap: wrap; gap: 7px; }
  .bo-chip { border: 1px solid var(--border); border-radius: 9px; padding: 6px 10px; background: var(--bg);
    color: var(--ink); cursor: pointer; font: inherit; text-align: left; }
  /* "This step uses this" — the click-lit and hover-lit states of a chip. Deliberately the same
     look from two sources: a solid accent edge and a tint, so the answer reads identically whether
     the baker is pointing at a step or has opened it. */
  .bo-chip.bo-lit, .bo-chip.bo-hot { border-color: var(--accent); background: var(--accent-tint); }
  .bo-chip.bo-lit .bo-chip-name, .bo-chip.bo-hot .bo-chip-name { color: var(--accent-dark); }
  .bo-chip-name { display: block; font-weight: 600; font-size: .82rem; }
  .bo-chip-role { display: block; font-size: .7rem; color: var(--muted); }
  table.bo-ing { width: 100%; border-collapse: collapse; font-size: .84rem; }
  table.bo-ing th { text-align: left; font-weight: 500; padding: 3px 0; }
  .bo-bar-cell { width: 50%; padding: 3px 10px; }
  .bo-bar { display: block; height: 8px; border-radius: 5px; background: var(--accent); opacity: .5; }
  .bo-num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
  tr.bo-row { cursor: pointer; }
  tr.bo-row:hover th { color: var(--accent); }
  tr.bo-row.bo-open th { color: var(--accent); }
  .bo-open { outline: 2px solid var(--accent); outline-offset: 1px; }
  .bo-detail { margin-top: 12px; border: 1px solid var(--accent); border-radius: 9px;
    background: var(--accent-tint); padding: 10px 13px; font-size: .85rem; }
  .bo-detail .bo-steps { margin: 7px 0 0; padding-left: 17px; }
  .bo-steps li { font-size: .8rem; margin: 2px 0; }
  .bo-steps em { font-style: normal; color: var(--muted); font-size: .92em; }
  .bo-sub { font-size: .77rem; color: var(--muted); margin-top: 4px; }
  /* "What you can change": the owning card's own controls, wearing this panel's clothes. The
     buttons are the app's ordinary secondary/active pair — a third dialect of "this is selected"
     is exactly what this panel spent the day removing. */
  .bo-acts { margin-top: 11px; padding-top: 9px; border-top: 1px solid var(--border); }
  .bo-acts-lab { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); font-weight: 700; margin-bottom: 6px; }
  /* The box now opens mid-panel, so it names itself and carries its own way out. */
  .bo-detail-head { display: flex; align-items: center; gap: 8px; margin: -1px 0 7px; }
  .bo-detail-lab { flex: 1; min-width: 0; font-size: .68rem; text-transform: uppercase;
    letter-spacing: .05em; color: var(--muted); font-weight: 700; }
  /* ⚠️ A REST SURFACE, not a hover one — phones never hover, and this is the only way out of the
     box that does not require remembering which handle opened it. Same rule the legend toggles
     were shipped in breach of on 2026-08-21. */
  .bo-detail-x { flex: none; width: 27px; height: 27px; padding: 0; line-height: 1;
    font-size: .8rem; cursor: pointer; border-radius: 7px; color: var(--muted);
    background: var(--card); border: 1px solid var(--border); }
  .bo-detail-x:hover { border-color: var(--accent); color: var(--accent-dark); }
  /* The controls arrive folded behind a nudge that looks pressable with nothing hovered. */
  .bo-acts-d { margin-top: 11px; padding-top: 9px; border-top: 1px solid var(--border); }
  .bo-acts-sum { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
    font-size: .74rem; font-weight: 600; color: var(--accent-dark); background: var(--card);
    border: 1px solid var(--border); border-radius: 7px; padding: 4px 10px; list-style: none; }
  .bo-acts-sum::-webkit-details-marker { display: none; }
  .bo-acts-sum::after { content: '▾'; font-size: .68rem; color: var(--muted); }
  .bo-acts-d[open] > .bo-acts-sum::after { content: '▴'; }
  .bo-acts-sum:hover { border-color: var(--accent); }
  .bo-acts-d > .bo-acts { margin-top: 9px; padding-top: 0; border-top: 0; }
  .bo-choice + .bo-choice { margin-top: 9px; }
  .bo-choice-lab { font-size: .78rem; font-weight: 600; margin-bottom: 3px; }
  .bo-choice-opts { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
  .bo-choice-opts .row { margin: 0 !important; }
  .bo-edit { margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--border); font-size: .8rem; }
  .bo-empty { color: var(--muted); font-size: .82rem; margin: 0; }
  .oq-foot { font-size: .76rem; opacity: .65; margin-top: 7px; }
  .oq-donehead { font-weight: 600; font-size: .86rem; margin-bottom: 4px; }
  .oq-done { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: .84rem; padding: 2px 0; }
  @media (max-width: 560px) {
    .oq-opt { grid-template-columns: auto 1fr auto; }
    .oq-per { grid-column: 2 / -1; }
    .oq-actions button { flex: 1 1 auto; }
  }

  /* Per-stage sub-step notes in the recipe editor */
  .stage-sub-btn { white-space: nowrap; }
  .stage-sub-panel { flex-basis: 100%; flex-direction: column; gap: 5px; padding: 6px 8px; margin-top: 2px;
    background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }
  .stage-sub-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .stage-sub-label { font-size: 0.82rem; min-width: 150px; }
  .stage-sub-note { font-size: 0.78rem; color: #4a3618; background: #f3ead4; border-left: 3px solid #D6AE63;
    padding: 2px 7px; border-radius: 3px; }

  /* Mixed-tagging warning in the bake-sheet / hover ingredient column */
  .bs-ing-warn { font-size: 0.76rem; color: #8a5a00; background: #fdf3da; border: 1px solid #e8c879;
    border-radius: 4px; padding: 3px 6px; margin-bottom: 5px; }
  .schedule-tooltip .tt-ings .bs-ing-warn { font-size: 0.74rem; }

  /* Schedule step hover tooltip */
  .schedule-row[data-hover], .schedule-event[data-hover] { cursor: help; }
  .schedule-tooltip {
    position: absolute; z-index: 1000; max-width: 320px; max-height: 60vh; overflow-y: auto;
    background: #fff; border: 1px solid var(--border); border-radius: 8px;
    box-shadow: 0 6px 24px rgba(0,0,0,0.18); padding: 10px 12px; font-size: 0.82rem; color: var(--text);
  }
  .schedule-tooltip .tt-title { font-weight: 600; margin-bottom: 6px; }
  .schedule-tooltip .tt-recipes { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
  .schedule-tooltip .tt-tag { font-size: 0.72rem; background: var(--bg); border: 1px solid var(--border);
    border-radius: 10px; padding: 1px 7px; }
  .schedule-tooltip .tt-sec { margin-top: 6px; }
  .schedule-tooltip .tt-h { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--muted); margin-bottom: 2px; }
  .schedule-tooltip .tt-weight div:last-child { font-size: 0.95rem; color: var(--accent-dark); }
  .schedule-tooltip .tt-ings table { width: 100%; border-collapse: collapse; }
  .schedule-tooltip .tt-ings td { padding: 1px 0; }
  .schedule-tooltip .tt-ings td.num { text-align: right; color: var(--muted); padding-left: 10px; white-space: nowrap; }
  .schedule-tooltip .tt-ings .bs-recipe-label { font-weight: 600; margin: 4px 0 1px; }
  .schedule-tooltip .tt-note { background: #fbf1d6; border-left: 3px solid #d6ae63; padding: 3px 7px;
    border-radius: 3px; white-space: pre-wrap; }
  .schedule-tooltip .tt-note .tt-h { color: #6b4f1d; }

  /* Cold-proof window buttons: an into-fridge step's window text opens a preset popover */
  .schedule-cp-clickable {
    cursor: pointer; color: var(--accent-dark);
    text-decoration: underline dotted; text-underline-offset: 2px;
  }
  .schedule-cp-clickable:hover { text-decoration-style: solid; }
  .cp-popover {
    position: absolute; z-index: 1001; max-width: 320px;
    background: #fff; border: 1px solid var(--border); border-radius: 8px;
    box-shadow: 0 6px 24px rgba(0,0,0,0.18); padding: 10px 12px; font-size: 0.82rem; color: var(--text);
  }
  .cp-pop-title { font-weight: 600; margin-bottom: 2px; }
  /* Guided-setup coach panel: a small card pinned beside the editor control it's
     walking you through (sits above the editor overlay at z 1000, below appConfirm 1300). */
  #setup-coach {
    position: fixed; z-index: 1200; width: 290px; max-width: calc(100vw - 20px);
    max-height: calc(100dvh - 24px); overflow-y: auto; overscroll-behavior: contain;
    background: var(--card); border: 1.5px solid var(--accent); border-radius: 11px;
    box-shadow: 0 10px 32px rgba(0,0,0,0.24); padding: 12px 14px; color: var(--ink);
  }
  /* Wider variant for the app tour's anchor-less cards (intro / usage / how-deep / finish),
     which carry full-width choice buttons and longer copy than a single-control coach step. */
  #setup-coach.coach-wide { width: 380px; }
  #setup-coach .coach-kicker { font-size: 0.68rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
  #setup-coach h4 { margin: 0 0 5px; font-size: 1rem; color: var(--accent-dark); }
  #setup-coach .coach-body { font-size: 0.86rem; line-height: 1.4; margin-bottom: 11px; }
  #setup-coach .coach-nav { display: flex; gap: 6px; align-items: center; }
  /* The control being walked through gets a soft ring so the eye connects panel → control. */
  .coach-highlight { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 7px; }
  /* Verified recipe opened for kitchen setup: the recipe CONTENT (identity, ingredients/%,
     steps) is locked ground truth; only the equipment/capacity/temp controls stay live. */
  .setup-lock-banner { background: #e8f0e8; border: 1px solid var(--accent); border-radius: 8px;
    padding: 8px 12px; margin: 0 0 12px; font-size: 0.86rem; color: var(--accent-dark); }
  #recipe-editor.setup-locked #r-identity-rows,
  #recipe-editor.setup-locked #r-stages-settings { pointer-events: none; }
  /* The PHOTO is not ground truth — sanitizeSharedRecipe strips photoPath from the frozen
     snapshot and recipeContentKey never reads it — so it stays live under the lock, exactly like
     the pantry link below (Jonathan, 2026-09-06: a verified recipe's cover photo could not be
     replaced without un-verifying, because the row sat inside #r-identity-rows). The dimming
     therefore goes on the SIBLING rows rather than the container: opacity composites, so a child
     cannot undo an ancestor's 0.5 with its own 1. */
  #recipe-editor.setup-locked #r-identity-rows > :not(#r-photo-row),
  #recipe-editor.setup-locked #r-stages-settings { opacity: 0.5; }
  #recipe-editor.setup-locked #r-photo-row { pointer-events: auto; opacity: 1; }
  /* Ingredients: the amounts/tags/components are ground-truth (locked), but the pantry LINK is a
     kitchen/costing concern (stripped when the recipe is shared) — keep it live so a verified
     recipe can still be linked to your pantry for costing without un-verifying it. */
  #recipe-editor.setup-locked #r-ingredients-settings { pointer-events: none; }
  #recipe-editor.setup-locked #r-ingredients-settings .row,
  #recipe-editor.setup-locked #r-ingredients-settings > p,
  #recipe-editor.setup-locked #r-ingredients-settings > button,
  #recipe-editor.setup-locked #r-ingredients-settings > details,
  #recipe-editor.setup-locked #r-ingredients-settings #r-flour-summary,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-name,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-pct,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-tag,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-component,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-main > .danger,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-water-temp,
  #recipe-editor.setup-locked #r-ingredients-settings .ing-heldback { opacity: 0.5; }
  #recipe-editor.setup-locked #r-ingredients-settings .ing-pantry { pointer-events: auto; opacity: 1; }
  .cp-pop-recipes { color: var(--muted); font-size: 0.76rem; margin-bottom: 7px; }
  .cp-chips { display: flex; flex-wrap: wrap; gap: 5px; max-height: 200px; overflow-y: auto; }
  .cp-chip {
    font-size: 0.78rem; background: var(--bg); border: 1px solid var(--border);
    border-radius: 12px; padding: 3px 10px; cursor: pointer; color: var(--text);
  }
  .cp-chip:hover { border-color: var(--accent-dark); }
  .cp-chip-selected { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; font-weight: 600; }
  .cp-pop-note { color: var(--muted); font-size: 0.74rem; margin-top: 8px; }

  @media print {
    header, .nav-wrapper, .no-print { display: none !important; }
    main > section { display: none !important; }
    #tab-bakesheet { display: block !important; }
    /* Steps-view print (transient body class from printStepsSchedule): print the
       schedule's step list instead of the Bake tab, minus its control cards. */
    body.print-steps #tab-bakesheet { display: none !important; }
    body.print-steps #tab-schedule { display: block !important; }
    body.print-steps #tab-schedule > .card { display: none !important; }
    body.print-steps #tab-schedule > #schedule-card { display: block !important; }
    body.print-steps .share-alert { display: none !important; }
    body { background: white; }
    .bakesheet-table th { background: #efefef !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
    .bakesheet-day-header { color: #333 !important; }
    .bs-time { color: #333 !important; }
    .schedule-tooltip { display: none !important; }
    /* 🔴 A LINE CLAMP IS A SCREEN AFFORDANCE AND A PRINT DEFECT. Recipe method notes over
       NOTE_CLAMP_CHARS render as `.recipe-note-long.clamped` — two lines, click to expand.
       That rule is NOT media-gated, so a printed sheet showed two lines of a paragraph-long
       note and silently dropped the rest, with no click available to recover it. Paper has no
       hover, no tap and no title tooltip: print the whole note. (Reported 2026-08-24.) */
    .recipe-note-long.clamped {
      display: block !important;
      -webkit-line-clamp: none !important;
      line-clamp: none !important;
      overflow: visible !important;
    }
    /* The recipe box clamps a long recipe name to two lines and hides the shut card's pills —
       both are TAP affordances, and paper has neither. `main > section` above means this tab
       cannot reach paper today, so these rules are insurance for the day one of the print
       routes below opens a second section: a clamp shipped without its print cancel is exactly
       how the bake sheet lost the bottom of every long method note (2026-08-24). */
    #recipes-list .rc-title-row strong {
      -webkit-line-clamp: none !important;
      line-clamp: none !important;
      overflow: visible !important;
      display: block !important;
    }
    #recipes-list .recipe-grid > .card { overflow: visible !important; }
    #recipes-list .recipe-grid > .card .rc-pills,
    #recipes-list .recipe-grid > .card .recipe-summary { display: block !important; }
    .cp-popover { display: none !important; }
    /* 📐 An open coach card lives on <body>, not inside a section, so it printed straight over the
       sheet — found 2026-08-20 by actually PRINTING the new cover to PDF and looking at page 1,
       where the first-run tour sat on top of the bake. Pre-existing on every print surface; a
       baker mid-tour who prints their sheet gets guidance where the schedule should be. */
    #setup-coach { display: none !important; }
  }

  /* ============================================================
     Mobile (phones). Foundations: full-width controls, stacked
     form rows, comfortable tap targets. Bake-sheet cards + table
     fallbacks live further down in their own blocks.
     ============================================================ */
  @media (max-width: 640px) {
    header { padding: 16px 14px 6px; }
    header h1 { font-size: 1.4rem; }
    main { padding: 10px 12px 56px; }
    .card { padding: 13px; border-radius: 9px; }
    nav.tabs button { padding: 10px 12px; }

    /* Recipes moves off the crowded bar into the hamburger on phones. */
    #tab-recipes-btn-wide { display: none; }
    #hamburger-menu #tab-recipes-btn { display: block; }

    /* No control may overflow its container. min-width:0 lets a <select> shrink
       below its longest option (otherwise it sizes to that and pushes its
       flex/table container past the card). 16px font avoids focus-zoom. */
    input, select, textarea, .settings-select, .bp-equip-select {
      max-width: 100%;
      min-width: 0;
      box-sizing: border-box;
    }
    input[type="text"], input[type="number"], input[type="date"],
    input[type="datetime-local"], input[type="time"], select, textarea, .settings-select {
      font-size: 16px;
    }

    /* Fields go full-width and wrap below their label, without flipping every
       row to a column (which would stack button groups awkwardly). Inline
       fixed-width labels (style="width:120px") are neutralized; inputs take a
       100% flex-basis so they drop onto their own full-width line. */
    .card .row > label { width: auto !important; min-width: 0 !important; }
    .card .row > input[type="text"], .card .row > input[type="number"],
    .card .row > input[type="date"], .card .row > input[type="datetime-local"],
    .card .row > input[type="time"], .card .row > select,
    .card .row > textarea, .card .row > .settings-select {
      flex: 1 1 100%; width: 100%;
    }
    .card .row > button.dlp-btn { flex: 1 1 100%; width: 100%; }   /* the deadline picker's button takes the native field's place */

    /* Larger tap targets for the small inline buttons + nav. */
    nav.tabs button, .hamburger-btn, #hamburger-menu button { min-height: 42px; }
    button.tiny, .tiny { min-height: 34px; }

    /* Recipe-card actions: a tidy equal-width 2-col grid on phones instead of a ragged
       wrapping row (the reorder / re-import / verify / share / delete buttons overflowed
       messily). Buttons fill their cell and stretch to a uniform height per row. */
    .recipe-card-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; align-items: stretch; }
    .recipe-card-actions > button,
    .recipe-card-actions > .plan-btn { width: 100%; margin: 0; white-space: normal; }
    .recipe-card-actions > .muted { grid-column: 1 / -1; }   /* the "N copies · ⚠ reports" note spans the row */
    .recipe-card-actions > .reorder-arrows { flex-direction: row; gap: 4px; }
    .recipe-card-actions > .reorder-arrows > button { flex: 1; }

    /* Bake-plan recipe rows: keep name + count on one line but let it wrap. */
    .bake-recipe-compact { gap: 8px; }
    /* Recipe-specific deadlines: stack the deadline beneath the name on phones. */
    .bake-recipe-firstline { flex-direction: column; align-items: stretch; gap: 4px; }
    /* The firstline stacks full-width on phones, so left-align the split rows to match. */
    .bake-batch-extra-row { justify-content: flex-start; }

    /* Recipe editor: the 5-column ingredient grid (~400px of fixed columns)
       overflows phones — restack it (name full-width, then %/tag/link/remove). */
    .ingredient-edit-row .ing-main { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
    .ingredient-edit-row .ing-main .ing-name-wrap { flex: 1 1 100%; }
    .ingredient-edit-row .ing-main .ing-vol-toggle { flex: 0 0 28px; }

    /* The 70px belongs to the AMOUNT WRAPPER now — the input fills it, and the unit suffix is
       painted inside, so the restacked phone row is exactly as wide as it was. */
    .ingredient-edit-row .ing-main .ing-amt { flex: 0 0 70px; width: 70px; }
    .ingredient-edit-row .ing-main .ing-tag,
    .ingredient-edit-row .ing-main .ing-component,
    .ingredient-edit-row .ing-main .ing-pantry { flex: 1 1 40%; min-width: 0; }
    .ingredient-edit-row .ing-main .danger.tiny { flex: 0 0 auto; margin-left: auto; }
    /* On a phone the row restacks, so there is no ✕ column to clear — but the sub-lines stay
       right-aligned, which is what ties them to the row above. They also wrap on a narrow
       screen, so let the label text sit right-aligned rather than ragged. */
    .ing-water-temp, .ing-heldback { padding-right: 2px; flex-wrap: wrap; }

    /* Stage editor rows wrap instead of overflowing. */
    .stage-row { flex-wrap: wrap; }
  }
/* THE LOADING STATE (2026-10-28): the shell is on screen before its 107 scripts have arrived, and its tab bar and menu carry
   inline handlers (showTab, toggleHamburger) that are not defined until their files load — a tap in that window threw a
   ReferenceError and did nothing (measured: scripts/cdp-early-listener.mjs). Under body.bm-loading, which the boot tail
   removes last, those controls take no pointer events and read as inert; everything else stays readable. */
body.bm-loading nav.tabs, body.bm-loading .hamburger-btn { pointer-events: none; opacity: 0.55; }
/* The accounts this device remembers (js/store/accounts.js, 2026-10-29): one row each — the address, the bakery's name,
   the press, and a quiet × that forgets it here. A button here sets its own colours (the base rule paints white). */
.acct-list { display: flex; flex-direction: column; gap: 6px; }
.acct-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--card); }
.acct-label { flex: 1 1 auto; min-width: 0; }
.acct-email { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acct-sub { font-size: 0.8rem; }
.acct-row .acct-go { flex: 0 0 auto; }
button.link-btn { background: none; border: none; padding: 0; margin: 0; color: var(--accent-dark); cursor: pointer; font-size: inherit; text-decoration: underline; }
.acct-row button.acct-forget { flex: 0 0 auto; text-decoration: none; color: var(--muted); font-size: 1.2rem; line-height: 1; padding: 4px 6px; }
