/* The recipe studio — hand-written, like brew_timeline.css, and scoped under
   .st-page so nothing here leaks onto the rest of the app (or gets pruned by
   Tailwind's content scan, which never looks at this file). One breakpoint:
   lg (1024px). Below it every control is a touch target (>=44px); above it
   the list and the preview sit side by side and the preview never hides. */
.st-page{
  --bg:       var(--color-bg, #f5f1e8);
  --surface:  var(--color-surface, #ffffff);
  --surface2: var(--color-surface-2, #efe9d7);
  --border:   var(--color-border, #e6dfcc);
  --ink:      var(--color-ink, #2f1f19);
  --ink-soft: var(--color-ink-soft, #6e5d54);
  --accent:   var(--color-accent, #d97757);
  --accent-2: var(--color-accent-2, #c25a38);
  --bad:      var(--color-bad, #8c3b32);
  /* Accent-tint steps this file actually uses (see application.css for the
     full step list + dark-mode rgba values) — precomputed, no color-mix. */
  --accent-tint-06: var(--color-accent-tint-06, rgba(217,119,87,.06));
  --accent-tint-20: var(--color-accent-tint-20, rgba(217,119,87,.20));
  color:var(--ink);background:var(--bg);
  max-width:1100px;margin:0 auto;padding:16px;
}

/* ── Top bar: back link, title, Save ── */
/* Task 3b: 4px, not the original 12px — the below-lg bar now carries one
   more icon control (.st-preview-btn) than the sweep's own 390px width budget
   (scripts/verify/studio_sweep.mjs's own "no horizontal page scroll" check)
   had room for at 12px; tightening every gap by 8px buys back exactly the
   room one more 44px control costs. Cosmetic-only at lg+, where the bar
   spans (at minimum) a 1024px shell and was never anywhere near this edge. */
.st-page .st-topbar{display:flex;align-items:center;gap:4px;margin-bottom:16px}
.st-page .st-back{display:inline-flex;align-items:center;gap:4px;flex:none;min-height:44px;padding:0 4px;
  color:var(--ink-soft);text-decoration:none;font:500 14px "Inter"}
.st-page .st-back:hover{color:var(--accent)}
.st-page .st-back svg{width:18px;height:18px}
.st-page .st-title{flex:1;min-width:0;margin:0;font-family:"Source Serif 4",serif;font-weight:700;
  font-size:19px;line-height:1.25;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-page .st-undo,.st-page .st-redo{flex:none;display:grid;place-items:center;width:44px;height:44px;
  border:0;border-radius:8px;background:none;color:var(--ink-soft);cursor:pointer}
.st-page .st-undo svg,.st-page .st-redo svg{width:17px;height:17px}
.st-page .st-undo:hover:not([disabled]),.st-page .st-redo:hover:not([disabled]){color:var(--accent);background:var(--surface2)}
.st-page .st-undo[disabled],.st-page .st-redo[disabled]{opacity:.3;cursor:not-allowed}
.st-page .st-save{flex:none;min-height:44px;padding:0 18px;border:0;border-radius:8px;
  background:var(--ink);color:var(--bg);font:600 14px "Inter";cursor:pointer}
.st-page .st-save[disabled]{opacity:.4;cursor:not-allowed}
/* Task 3b: "Preview" — icon-only, the same 44x44 square treatment as .st-undo/
   .st-redo just above (a labelled button here was the one sweep-caught
   regression this change introduced — the mobile top bar's own width is
   already spoken for by back/title/units/undo/redo/save). Never disabled:
   unlike Save, previewing needs no dirty draft to be worth doing. */
.st-page .st-preview-btn{flex:none;display:grid;place-items:center;width:44px;height:44px;
  border:0;border-radius:8px;background:none;color:var(--ink-soft);cursor:pointer}
.st-page .st-preview-btn svg{width:18px;height:18px}
.st-page .st-preview-btn:hover{color:var(--accent);background:var(--surface2)}
/* Task 3c (v2.1): the in-studio units toggle — same small pill idiom as the
   header's own switcher (shared/_header.html.erb), sized to THIS page's own
   44px touch targets (the header's h-9/36px pill is fine on a mouse-first
   desktop chrome bar, but this page holds itself to assertTouchTargets at
   every width). min-width, not just min-height: "g/°C"/"oz/°F" is short
   enough that padding alone wouldn't clear 44px on its own. */
.st-page .st-units{flex:none;display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0 12px;border:1px solid var(--border);border-radius:8px;
  background:none;color:var(--ink-soft);font:600 13px "Inter";cursor:pointer;white-space:nowrap}
.st-page .st-units:hover{color:var(--accent);border-color:var(--accent)}

/* ── The figures line (shared brew_methods/_facts_line partial, mounted here
   with controller: "recipe-studio") — every rule this partial itself relies
   on for a cell's own look (border, padding, the two nudge buttons' size and
   position) is written scoped to .brew-timeline in brew_timeline.css, and
   the studio deliberately never wraps this instance in that class (no
   grinder picker/scale strip sits beside it here — see studio.html.erb's own
   comment). Left alone, a cell here renders with NONE of that: an
   unconstrained input inside an unbordered grid track blows the "1fr 1fr"
   two-up layout brew_timeline.css's own @media(max-width:640px) still
   applies (unscoped) past the viewport's edge, and the nudge buttons — an
   absolutely-positioned wrapper around two SVGs with no explicit size —
   collapse to 0x0 (a replaced element sized only by viewBox, inside a
   shrink-to-fit box, resolves to nothing). So: the studio gets its own
   from-scratch copy of a cell's look below, sized as this page's own touch
   targets (44px) rather than the player's 24x24 — the player's own
   .brew-timeline-scoped rules are untouched by any of this. */
.st-page .bt-facts{display:flex;flex-wrap:wrap} /* undoes the unscoped 2-col grid at <=640px */
.st-page .bt-cell{position:relative;display:flex;flex-direction:column;justify-content:center;gap:3px;
  min-width:188px;min-height:44px;padding:8px 92px 8px 10px;border:1px dashed var(--border);
  border-radius:7px;background:var(--surface);transition:border-color .12s ease,background-color .12s ease}
.st-page .bt-cell-field{display:flex;align-items:center;gap:3px;min-width:0}
.st-page .bt-cell-pre{font:600 13px/1 "JetBrains Mono",monospace;color:var(--ink-soft)}
.st-page .bt-cell-input{width:4.5ch;min-width:2.5ch;min-height:44px;padding:0;border:0;background:none;
  color:var(--ink);font:600 20px/1.15 "JetBrains Mono",monospace;font-variant-numeric:tabular-nums;
  -moz-appearance:textfield}
.st-page .bt-cell-input::-webkit-outer-spin-button,
.st-page .bt-cell-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.st-page .bt-cell-input:focus{outline:none}
.st-page .bt-cell:focus-within{border-style:solid;border-color:var(--accent)}
.st-page .bt-fact-unit{font:600 11px/1 "JetBrains Mono",monospace;color:var(--ink-soft)}
.st-page .bt-cell.is-edited{border-style:solid;border-color:var(--accent);background:var(--accent-tint-06)}
.st-page .bt-cell.is-edited .bt-fact-label{color:var(--accent)}
.st-page .bt-cell.is-edited .bt-cell-input,
.st-page .bt-cell.is-edited .bt-cell-pre,
.st-page .bt-cell.is-edited .bt-fact-unit{color:var(--accent-2)}

/* Task 2c (v2.2): the readout half of the line ("TOTAL TIME" — the one
   fact key with no `input:`, see brew_methods_helper.rb's own fact_list —
   rendered by _fact_read.html.erb, not _facts_line.html.erb's own .bt-cell
   branch). brew_timeline.css styles .bt-read/.bt-read-val too, but only
   scoped under .brew-timeline — a class this instance deliberately never
   wears (this section's own top comment) — so without a studio-scoped
   equivalent .bt-read fell back to being a plain block box: its label and
   value spans laid out INLINE at the box's own top edge rather than
   stacked and centred, and .bt-read-val carried no font-weight at all
   (browser default 400 — "thin" beside every .bt-cell-input's own 600).
   .bt-facts' own (unscoped) align-items:stretch already matches this box's
   OUTER height to the tallest .bt-cell sharing its flex row — centring
   only ever needed a flex column + justify-content:center on the inside. */
.st-page .bt-read{display:flex;flex-direction:column;justify-content:center;gap:3px;
  padding:8px 10px;min-width:0}
.st-page .bt-read-val{font:700 13px/1.25 "JetBrains Mono",monospace;
  font-variant-numeric:tabular-nums;color:var(--ink)}
.st-page .bt-read-sub{font:500 11px/1.2 "JetBrains Mono",monospace;color:var(--ink-soft)}
.st-page .bt-read-sub:empty{display:none}

/* The two nudge buttons, side by side (not stacked — each has to clear 44px
   in BOTH dimensions on its own, and stacking two 44px targets would double
   the cell's own height for no reason). */
.st-page .bt-spin{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;gap:2px}
.st-page .bt-spin button{flex:none;width:44px;height:44px;display:grid;place-items:center;
  border:1px solid var(--border);border-radius:8px;background:var(--surface2);
  color:var(--ink-soft);cursor:pointer;padding:0;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  transition:border-color .12s ease,color .12s ease,background-color .12s ease}
.st-page .bt-spin button:hover{color:var(--accent);border-color:var(--accent);background:var(--surface)}
.st-page .bt-spin button:active{background:var(--accent-tint-20)}
.st-page .bt-spin svg{width:13px;height:13px;stroke-width:3}

/* Task 3 (v2.1): the figures line's own "edit notes" affordance — one small
   icon button, same 44px touch target and hover treatment as a step's own
   .st-row-btn, sitting after every .bt-cell in source order (so it never
   competes with the "1fr 1fr" wrap the cells themselves rely on below
   640px — flex-wrap just carries it onto its own line there). */
.st-page .bt-figures-edit{flex:none;display:grid;place-items:center;width:44px;height:44px;
  border:1px dashed var(--border);border-radius:7px;background:var(--surface);
  color:var(--ink-soft);cursor:pointer;transition:border-color .12s ease,color .12s ease}
.st-page .bt-figures-edit:hover{color:var(--accent);border-color:var(--accent)}
.st-page .bt-figures-edit svg{width:18px;height:18px}
.st-page .bt-figures-edit[aria-expanded="true"]{color:var(--accent);border-style:solid;border-color:var(--accent)}

/* ── Task 2 (v2.1): the centre column's own copy fields — intro (above the
   figures line) and notes (below the step list), the same textareas THE
   WORDS rail group used to hold (now deleted), moved here to read like the
   method page's own reading order (page_header hook -> Brew with us ->
   Notes — see studio.html.erb's own comment) at EVERY width, not just lg+.
   A small mono section label, the studio's own rail-label idiom reused
   rather than duplicated. */
.st-page .st-copy{display:flex;flex-direction:column;gap:6px;margin:20px 0}
.st-page .st-copy-label{display:flex;align-items:center;gap:6px;font:600 10px "JetBrains Mono",monospace;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft)}
.st-page .st-copy textarea{width:100%;border:1px solid var(--border);border-radius:8px;background:var(--surface);
  color:var(--ink);font:500 14.5px "Inter";line-height:1.55;padding:10px 12px;resize:vertical;min-height:44px}
.st-page .st-copy-intro textarea{min-height:70px}
.st-page .st-copy-notes textarea{min-height:96px}

/* ── The centre column's flow: single column at every width — intro, the
   figures line, the preview, the step list + add-a-step, notes. Below lg
   this used to be v1's own byte-identical "list then preview" pair (a
   176-check sweep guarded it); Task 2 (v2.1) deliberately supersedes that
   constraint — the preview now sits ABOVE the list at every width (source
   order inside .st-grid: .st-preview-toggle then .st-editor — no `order`
   override needed, so below lg and lg+ can never disagree about which
   comes first). The lg+ shape is Task 5's own three-pane shell further down
   this file (see "the lg+ three-pane shell" below): .st-grid becomes a
   single flex COLUMN there instead of the old two-up split. */
.st-page .st-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:24px;margin-top:20px}

/* ── Step list ── */
.st-page .st-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.st-page .st-row{border:1px solid var(--border);border-radius:10px;background:var(--surface)}
.st-page .st-row[data-selected]{border-color:var(--accent)}
.st-page .st-row-pinned{border-style:dashed}
.st-page .st-row-head{display:flex;align-items:center;gap:10px;padding:8px 10px}
.st-page .st-row-handle,.st-page .st-row-pin{flex:none;display:grid;place-items:center;
  width:44px;height:44px;border:0;background:none;color:var(--ink-soft);cursor:grab}
.st-page .st-row-handle svg,.st-page .st-row-pin svg{width:18px;height:18px}
.st-page .st-row-n{flex:none;display:grid;place-items:center;width:24px;height:24px;border-radius:999px;
  background:var(--accent);color:#fff;font:700 11px "JetBrains Mono",monospace}
/* Task 6: the block's own scene art, one per row — see _step_row.html.erb's
   own comment for the shared URL a server-rendered row and a freshly
   client-added one both resolve it from. */
.st-page .st-row-thumb{flex:none;width:32px;height:32px;border-radius:6px;object-fit:contain;
  background:var(--surface2)}
.st-page .st-row-main{flex:1;min-width:0;display:flex;align-items:center;gap:8px;
  min-height:44px;padding:2px 0;cursor:pointer}
.st-page .st-row-name{font:600 14.5px "Inter";color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-page .st-row-secs{flex:none;margin-left:auto;font:600 12px "JetBrains Mono",monospace;color:var(--accent-2)}
/* Task 2 (v2.2): below lg, up/down/remove show only on the SELECTED row —
   reusing applySelection()'s own data-selected attr on the <li>
   (.st-row[data-selected], the same state the border-color rule just above
   already keys off — no parallel selection state added here), giving the
   name back its width on every OTHER row. The lg+ override further down
   this file (inside @media(min-width:1024px)) restores "always visible"
   there, same as before this task — the enjoy row is unaffected either way
   (it never renders .st-row-controls at all, template: pinned rows skip the
   block entirely — see _step_row.html.erb). */
.st-page .st-row-controls{flex:none;display:none;gap:2px}
.st-page .st-row[data-selected] .st-row-controls{display:flex;gap:2px}
.st-page .st-row-btn{display:grid;place-items:center;width:44px;height:44px;border:0;border-radius:8px;
  background:none;color:var(--ink-soft);cursor:pointer}
.st-page .st-row-btn:hover{color:var(--accent);background:var(--surface2)}
.st-page .st-row-btn svg{width:16px;height:16px}
.st-page .st-row-remove:hover{color:#b6412a}

/* ── Per-step fields (name / seconds / why) — hidden until Task 7 opens a
   row; rendered here so the composition is inspectable without JS. ── */
.st-page .st-fields{display:grid;grid-template-columns:1fr 90px;gap:8px 10px;
  padding:0 12px 12px;border-top:1px dashed var(--border);margin-top:2px;padding-top:10px}
.st-page .st-field{display:flex;flex-direction:column;gap:4px;min-width:0}
.st-page .st-field-wide{grid-column:1/-1}
.st-page .st-field-label{font:600 10px "JetBrains Mono",monospace;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}
.st-page .st-field input,.st-page .st-field textarea{border:1px solid var(--border);border-radius:6px;
  background:var(--surface);color:var(--ink);font:500 13.5px "Inter";padding:8px 10px;min-height:44px}
.st-page .st-field textarea{min-height:56px;resize:vertical}

/* ── Task 6: the fields panel's own header (block scene art + the
   "editing: …" language marker) and the language marker's own look,
   reused verbatim wherever else it appears (the left rail's Name/Intro/
   Notes labels — see studio.html.erb). ── */
.st-page .st-fields-head{grid-column:1/-1;display:flex;align-items:center;gap:10px;margin-bottom:2px}
.st-page .st-field-thumb{flex:none;width:64px;height:44px;object-fit:contain;border-radius:8px;
  background:var(--surface2)}
.st-page .st-lang-marker{font:700 10px "JetBrains Mono",monospace;letter-spacing:.06em;
  text-transform:uppercase;color:var(--accent-2)}
.st-page .st-rail-label .st-lang-marker{margin-left:6px}

/* A param's own note — a single-line aside, not a second full-width field;
   reuses .st-field's own input styling above. */
.st-page .st-field-note{flex:1 1 160px;min-width:140px}
.st-page .st-field-note input{font-size:12.5px}

/* ── Per-block EDITABLE params (Task 8) — from_frac/to_frac/grams/temp/…,
   built either by _step_fields.html.erb (first paint) or
   recipe_studio_controller.js#renderParamFields (every rebuild after). A
   flex-wrapping row of .st-field's, reusing that class's own input/textarea
   styling above rather than a second copy of it. ── */
.st-page .st-param-fields{grid-column:1/-1;display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px 10px;
  margin-top:2px}
.st-page .st-param-fields .st-field{flex:1 1 110px;min-width:110px}
.st-page .st-field input:disabled{opacity:.5}
.st-page .st-field-resolved{flex:none;align-self:center;min-height:44px;display:flex;align-items:center;
  font:600 11.5px "JetBrains Mono",monospace;color:var(--ink-soft)}
/* A checkbox field ("Pulse pours", "follow the recipe") reads as one row,
   not a column — the checkbox itself never needs 44px of its own height,
   the LABEL it sits inside already carries that touch target. */
.st-page .st-field-check,.st-page .st-field-follow{flex:1 1 100%;flex-direction:row;align-items:center;
  gap:8px;min-height:44px}
.st-page .st-field-check input,.st-page .st-field-follow input{width:20px;height:20px;min-height:0;padding:0}

/* The placeholder Sortable leaves at the drop target while a row is
   mid-drag (forceFallback: true, so this is a real class toggle applied to
   the dragged row's own clone, not a native-drag pseudo-element). */
.st-page .st-row-ghost{opacity:.4;background:var(--surface2)}

/* ── Add a step + total ── */
.st-page .st-add{margin-top:16px;display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.st-page .st-add-box{max-width:22rem;flex:1;min-width:220px}
.st-page .st-add-box input{min-height:44px}
.st-page .st-total{margin:0;font:600 12.5px "JetBrains Mono",monospace;color:var(--ink-soft)}

/* ── Preview pane: collapsed behind a native <details> below lg, always
   open (and its summary hidden) at lg+. ── */
.st-page .st-preview-toggle{border:1px solid var(--border);border-radius:10px;background:var(--surface);overflow:hidden}
.st-page .st-preview-summary{list-style:none;cursor:pointer;display:flex;align-items:center;
  min-height:44px;padding:0 14px;font:600 13.5px "Inter";color:var(--ink)}
.st-page .st-preview-summary::-webkit-details-marker{display:none}
.st-page .st-preview-summary::before{content:"▸";margin-right:8px;color:var(--ink-soft)}
.st-page .st-preview-toggle[open] .st-preview-summary::before{content:"▾"}
.st-page .st-preview-body{padding:12px}
@media (min-width:1024px){
  .st-page .st-preview-toggle{border:0;background:none;border-radius:0}
  .st-page .st-preview-summary{display:none}
  .st-page .st-preview-body{display:block!important;padding:0}
  /* display:block above is not enough on its own: a <details> with no
     `open` attribute hides its non-summary content through its own
     ::details-content box (Chrome 131+), which content-visibility:hidden's
     — and skips rendering/painting/hit-testing/the accessibility tree for
     — regardless of what `display` the child itself computes to. There is
     no `open` attribute to flip here (the server renders this <details>
     closed on every paint, on purpose — see the comment at the top of this
     rule block), so the ONE thing that actually has to be overridden at lg+
     is that box itself. block-size:auto undoes the 0-height collapse
     content-visibility:hidden leaves behind once painting is restored. */
  .st-page .st-preview-toggle::details-content{content-visibility:visible!important;block-size:auto!important}
}

/* ── Task 1 (v2.2): below lg, THE RECIPE / THE BEAN / THE PHOTOS reach a
   phone as collapsible sections in the flow — the SAME <details> the lg+
   rail already renders (studio.html.erb), never a second copy of any
   field. .st-rail-left switches from display:none (further down this
   file) to a normal block right here, at the top of the single column;
   .st-rail-right (the lg+ inspector) stays hidden below lg exactly as
   before. Every group starts CLOSED down here regardless of which ones the
   server rendered `open` for lg+'s own default (collapsed-by-default is
   the spec's own load-bearing constraint — recipe_studio_controller.js#connect
   closes them, once, on connect; see its own comment for why a tiny JS
   pass is the ONE mechanism this rewrite leans on rather than a second,
   CSS-only one). Sized for touch (44px) throughout, reusing the exact
   class vocabulary the lg+ block further down this file already defined
   for this SAME markup — this is deliberately not a copy of those
   declarations: the lg+ overrides (280px track, 40px fields, column
   layout, non-touch media controls) still live in their own @media block
   below and win there purely on source order, so none of this ever
   touches desktop's own byte-identical rendering. */
.st-page .st-rail-left{display:block;margin-bottom:20px}
.st-page .st-rail-group{border-bottom:1px solid var(--border)}
.st-page .st-rail-group:last-child{border-bottom:0}
.st-page .st-rail-group>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
  min-height:44px;padding:12px 2px;font:700 11px "JetBrains Mono",monospace;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}
.st-page .st-rail-group>summary::-webkit-details-marker{display:none}
.st-page .st-rail-group>summary::before{content:"▸";color:var(--ink-soft)}
.st-page .st-rail-group[open]>summary::before{content:"▾"}
.st-page .st-rail-body{display:flex;flex-direction:column;gap:12px;padding:0 2px 18px}
.st-page .st-rail-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.st-page .st-rail-field{display:flex;flex-direction:column;gap:4px;min-width:0}
.st-page .st-rail-label{font:600 10px "JetBrains Mono",monospace;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}
.st-page .st-rail-field input,.st-page .st-rail-field select,.st-page .st-rail-field textarea{
  width:100%;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--ink);
  font:500 14.5px "Inter";padding:10px;min-height:44px}
.st-page .st-rail-field textarea{resize:vertical;min-height:64px}
.st-page .st-rail-hint{margin:0;font:500 12.5px "Inter";color:var(--ink-soft);line-height:1.5}
.st-page .st-method-box{max-width:none;width:100%}
/* padding-left:38px is the below-lg twin of the lg+ override further down
   this file (@media(min-width:1024px) .st-page .st-method-box input) — same
   fix, same reason: the .st-rail-field input rule just above sets
   padding:10px on every rail input, which (0,2,1) beats the combobox's own
   Tailwind pl-10 and shoves the value text under the search glass icon
   (left-3.5 w-4, ~14px+16px). Without this twin the lg+ rule only ever
   covered >=1024px; below lg the icon-overlap bug was still live (F2,
   v2.2 fix wave). */
.st-page .st-method-box input{min-height:44px;padding-left:38px}
/* THE RECIPE's own Name field (railTitleSlot in studio.html.erb) — its ONE
   home at every width now, no more topbar/rail reparenting in JS. Below lg
   it reads exactly like every other rail field above, not the old topbar
   heading look (that base rule is gone — the input never renders outside
   a .st-rail-field any more). The lg+ override further down this file
   (`.st-rail-field .st-title-input`, inside @media(min-width:1024px)) is
   untouched and wins there purely on source order, so desktop's own look
   stays byte-identical. */
.st-page .st-rail-field .st-title-input{flex:none;min-height:44px;padding:10px;
  border:1px solid var(--border);border-radius:6px;background:var(--surface);
  font:500 14.5px "Inter";font-weight:500;color:var(--ink)}
.st-page .st-rail-field .st-title-input:hover,
.st-page .st-rail-field .st-title-input:focus{background:var(--surface)}
.st-page .st-rail-field .st-title-input:focus{outline:2px solid var(--accent);outline-offset:1px}

/* THE PHOTOS group's own upload form + gallery grid, reachable below lg
   for the first time too — verified to show the exact same owner-gated
   form / sign-in hint the lg+ rail already shows (studio.html.erb renders
   no new logic for this, just unhides). Buttons here are sized for touch
   (44px) rather than the lg+ block's own smaller, mouse-first sizing
   further down this file — same reasoning and same non-conflict as the
   rail-field input split just above. */
.st-page .st-media-errors{margin:0;padding-left:18px;font:500 12.5px "Inter";color:var(--bad)}
.st-page .st-media-form{display:flex;flex-direction:column;gap:12px}
.st-page .st-media-banner-preview{width:100%;aspect-ratio:2/1;object-fit:cover;border-radius:8px;
  border:1px solid var(--border)}
.st-page .st-media-upload{min-height:44px;padding:0 14px;border-radius:8px;border:1px solid var(--border);
  background:var(--surface);color:var(--ink);font:600 13px "Inter";align-self:flex-start}
.st-page .st-media-upload:hover{border-color:var(--accent);color:var(--accent)}
.st-page .st-media-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.st-page .st-media-thumb{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;
  border:1px solid var(--border)}
.st-page .st-media-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.st-page .st-media-thumb form{position:absolute;top:4px;right:4px;margin:0}
.st-page .st-media-remove{display:grid;place-items:center;width:44px;height:44px;border-radius:999px;
  border:0;background:rgba(0,0,0,.55);color:#fff;cursor:pointer}
.st-page .st-media-remove:hover{background:rgba(0,0,0,.75)}
.st-page .st-media-remove svg{width:18px;height:18px}

/* ── /brew-studio: the method picker (recipes#studio_home) ── */
.st-page.st-home{max-width:900px}
.st-page .st-home-intro{margin:28px 0 22px;text-align:center}
.st-page .st-home-title{margin:0;font-family:"Source Serif 4",serif;font-weight:700;font-size:28px;
  line-height:1.2;color:var(--ink)}
.st-page .st-home-sub{margin:8px 0 0;color:var(--ink-soft);font:400 14.5px "Inter"}
.st-page .st-home-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px;
  list-style:none;margin:0;padding:0}
.st-page .st-home-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:16px 12px;
  border:1px solid var(--border);border-radius:12px;background:var(--surface);text-align:center;
  text-decoration:none;color:var(--ink);transition:border-color .12s ease,box-shadow .12s ease}
.st-page .st-home-card:hover{border-color:var(--accent);box-shadow:0 1px 3px rgba(0,0,0,.06)}
.st-page .st-home-card-art{width:100%;max-width:120px;height:auto;aspect-ratio:120/82;object-fit:contain}
.st-page .st-home-card-name{font:600 14px "Inter";color:var(--ink)}
.st-page .st-home-card-sub{font:500 11.5px "JetBrains Mono",monospace;color:var(--ink-soft)}

/* ── Task 5: the lg+ three-pane shell (recipes/studio.html.erb, not the
   /brew-studio picker above) ── Everything ABOVE this point in the file is
   the below-lg stack — a sweep guards it (scripts/verify/studio_sweep.mjs).
   Task 2 (v2.1) deliberately edited PART of that stack (the centre column's
   own reading order — see .st-copy/.st-grid's own comments above); Task 1
   (v2.2) edited another part (the left rail — see its own comment further
   up this file). Neither is byte-identical to v1 any more, on purpose, per
   each task's own brief. Below lg every selector in THIS block, though,
   either targets an element that's hidden by the mobile-first defaults just
   below (so CSS alone keeps the very first paint identical, with no
   dependency on JS having run yet), or lives inside @media
   (min-width:1024px) and simply never matches. */

.st-page .st-brand,
.st-page .st-topbar-divider,
.st-page .st-topbar-tool,
.st-page .st-topbar-spacer,
.st-page .st-close,
/* Task 1 (v2.2): only the RIGHT rail (the lg+ inspector) hides below lg
   now — the left rail unhides further up this file (its own comment), the
   in-flow section stack a phone reaches THE RECIPE/THE BEAN/THE PHOTOS
   through. */
.st-page .st-rail-right{display:none}

@media (min-width:1024px){
  /* Full-bleed: drop the 1100px max-width/centering from the bare .st-page
     rule at the very top of this file (which below-lg still reads
     untouched) and turn the page itself into the shell — a fixed-height
     grid, three columns, FIVE rows: the top bar (auto, spans all three
     columns), the intro copy field (auto, centre column only — Task 2,
     v2.1), the figures line (auto, centre column only), the figures line's
     OWN notes fields (auto, centre column only, hidden/0-height whenever
     "figures" isn't selected — Task 3, v2.1), and everything else (1fr,
     centre column only — its own overflow-y is what actually scrolls; see
     .st-grid below, which now also holds the notes copy field as its own
     third flex child). The two rails span rows 2 THROUGH 5 (they run the
     full height beside the intro field, the figures line (+ its own notes
     row) and the list, matching the design doc's own sketch), each
     `overflow-hidden` with its own `overflow-y-auto` inner scroller
     (.st-rail-scroll) — the same shell/rail split ~/zz/dev/micepad's
     badge_designer_v3/show.html.erb uses. height:100dvh means the PAGE
     itself never scrolls — only these inner regions do. */
  /* :not(.st-home): the method PICKER page shares the .st-page class but is
     an ordinary scrolling page at every width — this fixed-height grid shell
     applied to it once hid every card below the fold with no scrollbar
     (bughunt finding 1). The descendant rules below are harmless there (the
     picker has none of the editor's elements); only this container rule has
     to exclude it. */
  .st-page:not(.st-home){
    max-width:none;margin:0;padding:0;
    display:grid;grid-template-columns:280px minmax(0,1fr) 360px;
    grid-template-rows:auto auto auto auto 1fr;
    height:100dvh;overflow:hidden;
  }
  .st-page .st-topbar{grid-column:1/-1;grid-row:1;margin-bottom:0;padding:10px 16px;
    border-bottom:1px solid var(--border)}

  /* Below-lg's own back-arrow-with-text steps aside for the brand | divider
     | "Brew Studio" | spacer | undo/redo | Save | ✕ idiom — undo/redo/save
     are the SAME three <button> elements either way (only their SURROUNDING
     chrome swaps), so recipe_studio_controller's own targets/actions never
     have to know which shape is currently showing. Task 1 (v2.2): the title
     input carries no top-bar presence to vacate any more — it renders only
     in THE RECIPE's own railTitleSlot (studio.html.erb), at every width, so
     there's nothing here to hide/reparent and no empty flex space to worry
     about either. */
  .st-page .st-back{display:none}
  .st-page .st-brand,.st-page .st-topbar-tool{display:inline-flex;align-items:center}
  .st-page .st-topbar-divider{display:block;width:1px;height:22px;background:var(--border);flex:none}
  .st-page .st-topbar-tool{color:var(--ink-soft);text-decoration:none;font:600 14px "Inter"}
  .st-page .st-topbar-tool:hover{color:var(--accent)}
  .st-page .st-topbar-spacer{display:block;flex:1}
  .st-page .st-close{display:grid;place-items:center;flex:none;width:44px;height:44px;border-radius:8px;
    color:var(--ink-soft);text-decoration:none}
  .st-page .st-close:hover{color:var(--accent);background:var(--surface2)}
  .st-page .st-close svg{width:18px;height:18px}

  /* The two rails — `overflow-hidden` shells with their own
     `overflow-y-auto` inner scroller (.st-rail-scroll), so a long bean card
     or a long step list only ever grows its OWN column, never the page.
     grid-row 2/6 spans every row below the top bar (intro/figures/figures'
     own notes/grid — Task 2, v2.1 grew this from 2/4 to 2/5; Task 3, v2.1's
     own figures-notes row grew it again, to 2/6). */
  .st-page .st-rail{display:flex;flex-direction:column;grid-row:2/6;min-height:0;overflow:hidden}
  /* margin-bottom:0 undoes the below-lg .st-rail-left rule's own 20px gap
     (top of this file — spacing between the in-flow section stack and
     whatever follows it in the single-column layout). Left unreset here,
     that 20px shortened the left rail's own border-right by exactly that
     much inside this grid cell, the one visible desktop regression a
     below-lg-only rule change should never cause (F1, v2.2 fix wave) — the
     right rail carries no such margin and always reached the bottom. */
  .st-page .st-rail-left{grid-column:1;border-right:1px solid var(--border);margin-bottom:0}
  .st-page .st-rail-right{grid-column:3;border-left:1px solid var(--border)}
  .st-page .st-rail-scroll{flex:1;min-height:0;overflow-y:auto;padding:8px 16px 20px}

  /* Collapsible group headers — one <details> per group (studio.html.erb),
     all but THE BEAN open by default. Native disclosure triangles, same
     ▸/▾ idiom the preview toggle above already uses. */
  .st-page .st-rail-group{border-bottom:1px solid var(--border)}
  .st-page .st-rail-group:last-child{border-bottom:0}
  .st-page .st-rail-group>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
    min-height:44px;padding:12px 2px;font:700 11px "JetBrains Mono",monospace;letter-spacing:.08em;
    text-transform:uppercase;color:var(--ink-soft)}
  .st-page .st-rail-group>summary::-webkit-details-marker{display:none}
  .st-page .st-rail-group>summary::before{content:"▸";color:var(--ink-soft)}
  .st-page .st-rail-group[open]>summary::before{content:"▾"}
  .st-page .st-rail-body{display:flex;flex-direction:column;gap:12px;padding:0 2px 18px}
  .st-page .st-rail-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .st-page .st-rail-field{display:flex;flex-direction:column;gap:4px;min-width:0}
  .st-page .st-rail-label{font:600 10px "JetBrains Mono",monospace;letter-spacing:.08em;
    text-transform:uppercase;color:var(--ink-soft)}
  .st-page .st-rail-field input,.st-page .st-rail-field select,.st-page .st-rail-field textarea{
    width:100%;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--ink);
    font:500 13.5px "Inter";padding:8px 10px;min-height:40px}
  .st-page .st-rail-field textarea{resize:vertical;min-height:64px}
  .st-page .st-rail-hint{margin:0;font:500 12.5px "Inter";color:var(--ink-soft);line-height:1.5}

  /* Task 7: THE PHOTOS group's own upload form + gallery grid — same
     .st-rail-field/.st-rail-label vocabulary as every other rail control,
     never a competing style. */
  .st-page .st-media-errors{margin:0;padding-left:18px;font:500 12.5px "Inter";color:var(--bad)}
  .st-page .st-media-form{display:flex;flex-direction:column;gap:12px}
  .st-page .st-media-banner-preview{width:100%;aspect-ratio:2/1;object-fit:cover;border-radius:8px;
    border:1px solid var(--border)}
  .st-page .st-media-upload{min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--border);
    background:var(--surface);color:var(--ink);font:600 13px "Inter";align-self:flex-start}
  .st-page .st-media-upload:hover{border-color:var(--accent);color:var(--accent)}
  .st-page .st-media-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
  .st-page .st-media-thumb{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;
    border:1px solid var(--border)}
  .st-page .st-media-thumb img{width:100%;height:100%;object-fit:cover;display:block}
  /* button_to's own wrapping <form> (action_view/helpers/url_helper.rb) —
     absolutely positioned over the thumb rather than sized itself, so it
     never disturbs the grid cell's own aspect-ratio box. */
  .st-page .st-media-thumb form{position:absolute;top:4px;right:4px;margin:0}
  .st-page .st-media-remove{display:grid;place-items:center;width:22px;height:22px;border-radius:999px;
    border:0;background:rgba(0,0,0,.55);color:#fff;cursor:pointer}
  .st-page .st-media-remove:hover{background:rgba(0,0,0,.75)}
  .st-page .st-media-remove svg{width:12px;height:12px}

  /* THE RECIPE's own Name field, restyled here to read as a rail control at
     lg+ (40px fields, tighter padding, no bold serif) — the mobile-first
     twin of this same selector further up this file (Task 1, v2.2) styles
     it for touch (44px) below lg instead; both target the SAME rendered
     node (studio.html.erb renders it once, in this one spot, at every
     width now — no reparenting left to document here). This rule wins at
     lg+ purely on source order (equal 3-class specificity, later in the
     file), leaving the mobile-first one to apply everywhere else. */
  .st-page .st-rail-field .st-title-input{flex:none;min-height:40px;padding:8px 10px;
    border:1px solid var(--border);border-radius:6px;background:var(--surface);
    font:500 13.5px "Inter";font-weight:500}
  .st-page .st-rail-field .st-title-input:hover,
  .st-page .st-rail-field .st-title-input:focus{background:var(--surface)}
  .st-page .st-rail-field .st-title-input:focus{outline:2px solid var(--accent);outline-offset:1px}

  .st-page .st-method-box{max-width:none;width:100%}
  /* The rail-field input rule above resets padding to 8px 10px, which shoves
     the combobox's absolutely-positioned search glass (left-3.5, w-4) under
     the value text — this wins by source order and clears it. */
  .st-page .st-method-box input{min-height:40px;padding-left:38px}

  /* The right rail — the inspector. Either the empty-state hint
     (server-rendered, restored by recipe_studio_controller.js's own
     updateInspectorEmptyState) or, once a step is selected, that row's OWN
     .st-fields node — reusing .st-fields' existing rules unscoped from any
     particular row width, the same markup rendering identically whether
     it's inline (below lg) or parked here. */
  .st-page .st-inspector-empty{margin:0;font:500 13.5px "Inter";color:var(--ink-soft)}

  /* The centre column, now FOUR rows (Task 2, v2.1 added the intro row;
     Task 3, v2.1 added the figures-notes row just below — see the shell's
     own grid-template-rows comment above): the intro copy field sits fixed
     (row 2, auto height, mirroring .bt-facts' own treatment — it's a short
     single field, no scroll needed), the figures line sits fixed (row 3,
     same reasoning), the figures line's OWN notes fields sit fixed right
     after it (row 4 — hidden/0-height whenever "figures" isn't selected, so
     this row simply collapses rather than reserving empty space), and
     everything else — preview, step list, add-a-step, and now the notes
     copy field too — is row 5, `1fr` with its own overflow-y so a long list
     only ever grows ITS row, never the page (mirrors the two rails' own
     .st-rail-scroll). All FOUR stay DIRECT children of .st-page (no
     wrapper) — see studio.html.erb's own comment on why: `.st-page >
     .bt-facts` is how this file's own preview-vs-studio figures-line checks
     (and scripts/verify/studio.mjs's own) tell the two .bt-facts instances
     on this page apart; the figures-notes row sits AFTER it in source order
     for the same reason (never wrapped around it, which would change that
     selector's own match). */
  .st-page>.st-copy-intro{grid-column:2;grid-row:2;padding:16px 20px 0;min-width:0;margin-top:0}
  .st-page>.bt-facts{grid-column:2;grid-row:3;padding:8px 20px 0;min-width:0}
  /* Reuses .st-fields' own display:grid/columns/gap (base rule, above) —
     only the OUTER box (margin/padding/border, all sized for sitting
     INSIDE a row's own padded head) needs overriding here, to match its
     siblings' 20px side gutter instead. Tied specificity with that base
     rule (two classes each) — this wins on source order alone, since it's
     later in the file; see that rule's own comment for why. */
  .st-page>.st-figures-fields{grid-column:2;grid-row:4;margin:0;padding:8px 20px 12px}
  /* Supersedes the old two-up split this same selector's base rule (top of
     this file) carried below lg — .st-grid becomes a single flex COLUMN at
     lg+ instead, preview/editor/notes in that exact source order (Task 2,
     v2.1 dropped the `order` overrides this rule used to carry — DOM order
     alone now matches the visual order at every width, so below lg and lg+
     can never disagree about which comes first — see the base .st-grid
     rule's own comment near the top of this file). */
  .st-page .st-grid{grid-column:2;grid-row:5;display:flex;flex-direction:column;gap:20px;
    margin-top:16px;padding:0 20px 24px;overflow-y:auto;min-width:0;min-height:0}
  /* .st-grid's own `gap:20px` (flex column, above) already spaces every
     child from the next — the base .st-copy rule's own margin-top:20px
     would double that here, so it's zeroed for this one lg+ instance. */
  .st-page .st-copy-notes{margin-top:0}

  /* Task 2 (v2.2): undo the below-lg "selected row only" gating on
     .st-row-controls (base rule, above) — at lg+ every row's up/down/remove
     stay always visible, same as before this task (desktop byte-identical).
     Equal specificity (two classes) to the base rule, later in the file, so
     this wins purely on source order; the higher-specificity
     `.st-row[data-selected] .st-row-controls` rule (base, above) still also
     matches a selected row here, in full agreement. */
  .st-page .st-row-controls{display:flex}
}
