/* Kilian's Pizza Dough Calculator — one palette, two themes.
 *
 * The layout is a single living document — the formula sheet — instead of a
 * control panel beside a results panel:
 *   - every number in the sheet is its own control (a scrub: drag it, tap it
 *     for a fine −/+, arrow keys to step);
 *   - the formula is a stack of typographic sliders, each row a range track
 *     under the line you read;
 *   - the process is a numbered timeline whose bake numbers are edited where
 *     they are read;
 *   - the shelf keeps the sheet-on-phone / column-on-desktop split, with a
 *     formula fingerprint on every card;
 *   - the pinned title keeps the name and total on screen once they scroll.
 */

:root {
  color-scheme: light;
  --bg: #FBFAF7; --ink: #141310; --text2: #54504A; --text3: #3A362F;
  /* muted2 carries real words (the yeast share, the footnote, the step count)
     and faint carries interactive glyphs (grip, ⋯, ×), so they clear WCAG's
     4.5:1 for text and 3:1 for controls on --bg. The old #A7A297/#B4AFA3 sat
     at 2.4:1 and 2:1 -- atmosphere at the price of legibility. */
  --muted: #6F6A61; --muted2: #757068; --faint: #8E897E;
  --accent: #C24A34; --accent2: #C7A69E; --accent3: #E0C4BD; --accent4: #E7CFC8;
  --line: #E7E3DA; --line2: #EFEBE2; --line3: #CFCABF; --track: #DEDAD0;
  --panel: #FFFFFF; --shadow: rgba(20, 19, 16, .16);
  --mono: 'IBM Plex Mono', monospace;
  --bar-h: 58px;
  /* The vertical seam scale. Every page-level gap comes from these four --
     never a raw number. Kilian watched the openers drift apart three times
     (Home's phantom flex gap, the wall's stacked 30, Settings' odd 28) and
     vetoed the whole category; make test now trips on any raw padding-top.
     --seam-page: the first content below the masthead, on every page, and
     the air under the headline. --seam-card: a section's own padding.
     --seam-sec: the air above a section heading. --seam-entry: between
     stacked entries of a list. */
  --seam-page: 26px; --seam-card: 30px; --seam-sec: 46px; --seam-entry: 26px;
  /* --seam-head: the air *under* a heading, before the thing it names. It was
     10px in three places, 16px in two more and 30px in the bake card -- the
     same relationship wearing four numbers, which is the drift Kilian kept
     seeing come back. One token, so a heading can only ever sit one distance
     above its content. */
  --seam-head: 10px;
  /* The air a formula row keeps under its track, so two stacked rows read as
     two rows. Named because it has a second reader: where a row is the last
     thing in a section, this air is the first 14px of the boundary below it,
     and the boundary subtracts it rather than stacking on it. Unnamed, it made
     the seam above Process measure 60px against the 48 above Formula and
     Method -- and 48 again the moment the method was switched to direct, the
     same boundary moving 12px because a lever moved. */
  --row-foot: 14px;
  /* The air a step's list keeps inside its own top and bottom edge -- an
     ingredient row's padding, and the leading-trimmed padding of a sentence,
     which are made the same number on purpose so the two lists inside a step
     sit on one beat.
     Its second reader is every seam that lands on a list: the eye measures
     from the last mark to the next one, so a seam above a list is the token
     less this air, never the token stacked on top of it (DESIGN 1.3). It is
     also what a lit row's band is drawn around -- the highlight is the row's
     own air taking colour, so nothing moves when it lights. */
  --row-air: 8px;
  /* The white a line of prose carries above its glyphs at --lh-prose: the
     line box is taller than the ink by about this much at each end. A
     sentence subtracts it from its own padding so its ink sits exactly where
     an ingredient row's does; unsubtracted, the instructions sat 5px further
     from their heading than the ingredients sat from theirs, one of the four
     mismatched seams that read as "no rhythm". */
  --prose-lead: 2px;
  /* The pinned title is exactly as tall as the brand row it stands in for.
     Any other height and its hairline lands somewhere the page's own hairline
     does not, so scrolling shows the two rules a few pixels apart -- the
     double line Kilian spotted on the desktop. One height, one line. */
  --sticky-h: var(--bar-h);
  /* The page's own geometry, named once. Anything fixed to the window that
     has to line up with the page -- the pinned title, so far -- works out its
     inset from these rather than guessing at the numbers again. Guessing is
     how the bar came to sit on a 24px inset while the page sat on 40. */
  --page-max: 1020px;
  /* The reading measure: what the document column caps at, and therefore what
     anything standing *in place of* it caps at too -- the shelf, where the
     shelf is the page's own body. Named once because two of them drifting is
     a tablet with the shelf wall-to-wall beside a 620px spread. */
  --doc-max: 620px;
  --page-pad: clamp(16px, 4vw, 40px);
  /* The phone's bottom chrome: the floating tab dock. --tab-h is the height
     of the capsule (and the diameter of the + button beside it); --tab-inset
     is the air between the dock and the screen's bottom edge -- above the
     home indicator on a notched phone, above the bezel elsewhere. Everything
     that must clear the dock (page bottom, toasts) sums the same two. */
  /* 56, not 54: the capsule spends 5px of padding top and bottom plus a 1px
     border on its glass, and what's left inside must still be a 44pt thumb
     target -- the layout guard measures the slots, not the capsule. */
  --tab-h: 56px;
  --tab-inset: 12px;
  /* The type ramp — eight sizes cover every recurring text role. What stays
     literal below is off it on purpose: glyphs and emoji, the brand lockup,
     the big mono numerals, and the 16px mobile inputs iOS must not zoom. */
  --fs-micro: 10.5px; --fs-small: 11px; --fs-body: 12.5px; --fs-prose: 13px;
  --fs-ui: 13.5px; --fs-emphasis: 15px; --fs-title: 17px;
  --fs-display: clamp(44px, 10vw, 68px);
  --ls-mono: .02em; --ls-wide: .04em; --ls-tight: -.02em;
  --lh-solid: 1; --lh-note: 1.55; --lh-prose: 1.6;
}
:root[data-theme=dark] {
  color-scheme: dark;
  --bg: #14120D; --ink: #ECE7DC; --text2: #C3BDB0; --text3: #D2CCC0;
  --muted: #A6A095; --muted2: #837D72; --faint: #6A645B;
  --accent: #E06A4F; --accent2: #B07C6E; --accent3: #5C3F37; --accent4: #3E2C26;
  --line: #302C24; --line2: #26221A; --line3: #3C372E; --track: #38332B;
  --panel: #1C1913; --shadow: rgba(0, 0, 0, .5);
}

* { box-sizing: border-box; }
/* Headings exist for the reader who can't see the layout -- a screen reader's
   outline -- so they take their look entirely from the classes they carry. */
h1, h2, h3 { margin: 0; font-size: inherit; font-weight: inherit; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: 'Instrument Sans', system-ui, sans-serif;
  -webkit-text-size-adjust: 100%;
}
body.sheet-open { overflow: hidden; }
body.scrubbing { user-select: none; -webkit-user-select: none; cursor: ew-resize; }
button { font-family: inherit; cursor: pointer; }
input, textarea { font-family: inherit; }
hr.rule { border: none; border-top: 1px solid var(--line); margin: 0; width: 100%; }
[hidden] { display: none !important; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.page {
  /* Capped where the two columns still feel like the moment before the
     mobile collapse: rail, a seam of air, and a sheet you can read without
     turning your head. A wall-to-wall document on a big display was the
     one screen where the sheet stopped feeling like a sheet. */
  min-height: 100vh; max-width: var(--page-max); margin: 0 auto;
  padding: 0 var(--page-pad);
  /* Room for the floating dock, plus the home indicator on a notched phone. */
  padding-bottom: calc(var(--tab-h) + var(--tab-inset) + 28px + env(safe-area-inset-bottom));
}

.stack-12 { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.kicker {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500;
  text-transform: uppercase; letter-spacing: .12em; color: var(--muted);
}
.hint { margin: 0; font-size: var(--fs-body); line-height: var(--lh-note); color: var(--muted); }

/* "This dough has changes you have not saved", said the same way everywhere it
   can be said: beside the headline, on the shelf card, and -- since the mark
   learned to point -- beside the one figure or the one step that changed. It
   lives here, not inside any section, because it belongs to none of them: when
   the bar's identity was deleted it took the shared rule with it and left the
   dots with no styling at all.

   One mark at two scales, and deliberately the same mark. The document's dot
   says there is something to save; a row's says which row it was. A second
   shape for the smaller answer would be a second thing to learn about a state
   the reader already understands. */
/* The title's mark holds its place whether it is showing or not: a dot that
   arrives when the dough goes dirty is a dot that can push the heading onto a
   second line and drop the page 48px (views/head.js says the rest). The
   shelf's row-dot is not in a heading and keeps `hidden`, and so do the
   located marks below: a row's name, a numeral and a step's title all truncate
   rather than wrap, so an arriving mark there rewraps nothing -- measured at
   375px in the shipping face with all 38 of them lit at once, and the page did
   not move by a pixel. */
.title-dot {
  display: inline-block; margin-left: .28em; vertical-align: middle;
  visibility: hidden;
}
.title-dot.is-on { visibility: visible; }
/* In the heading the mark is out of the flow, at the place in the flow it
   would have had: `position: absolute` with nothing to position it by leaves
   a box at its static position, which is the end of the last line, wherever
   that line ends up. So it is drawn after the last word and takes part in
   nothing -- the name wraps the same whether it is marked or not (a mark that
   arrived pushed the whole page down 48px), it can never take a line of its
   own (that put an empty line under the title and the credit 21px from the
   name), and it is not in the button's scrollable width, which is how the
   page asks whether the name has been clipped.

   The containing block is `.doc-title`, which is already positioned, and the
   margin is what `vertical-align: middle` used to do: out of the flow there
   is no baseline to align to, so the mark is dropped to where the inline one
   sat -- measured against it at both phone widths, in em so it holds at every
   step of the title's clamp. */
.doc-title .title-dot { position: absolute; margin-top: .51em; }
.hero-dot, .row-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  flex: none; align-self: center;
}
/* On a formula row it hugs the name, for the ⓘ's reason: the label's own gap is
   the distance between two *words*, and a mark on a word sits closer than that.
   Baseline-aligned rows put it on the middle of the type it marks. */
.f-label .f-dot { margin-left: -8px; align-self: center; }
/* On a quantity chip and on the chosen word of a picker the mark is inline,
   after the type it marks -- inside the scrub's own box on the first, inside
   the button on the second, so it hugs the glyphs rather than the box and adds
   no width to a row that has none to give. The scrub keeps the underline it
   already had: the dashed rule is as wide as the 44pt box either way. */
.stat-dot { display: inline-block; margin-left: .32em; vertical-align: middle; }
.word-dot { display: inline-block; margin-left: .4em; vertical-align: middle; }
/* A step's title line is baseline-aligned like a formula row's. */
.step-title-main .step-dot { align-self: center; }

/* ---- masthead --------------------------------------------------------- */
/* display:contents, so the sticky bar's parent is the page itself and it
   can stay stuck for the whole scroll, not just the masthead's own height. */
.masthead { display: contents; }
/* One composition at every width. Nothing here is allowed to wrap: the
   lockup owns the left, the chrome owns the right, and each piece is sized
   so the tightest phone still fits them on one line -- a designed bar, not
   a flex accident. */
/* One shared baseline: "Kilian's" and the chrome sit on the same line by
   flexbox's own baseline rule -- no margin arithmetic, no drift. */
/* Fixed geometry, one axis. The bar is 58px tall and every occupant -- the
   emoji, the two-line lockup, the language group, the moon -- is centered on
   its middle line. No baseline synthesis across nested flexboxes, no margin
   arithmetic: one rule, measurable to the pixel. */
.brand-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: nowrap; min-width: 0;
  height: 58px; padding: 0; border-bottom: 1px solid var(--line);
}
/* One bar, both jobs: sticky at rail widths, where there is no tab bar. */
@media (min-width: 900px) {
  .brand-row { position: sticky; top: 0; z-index: 25; background: var(--bg); }
}
.brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: inherit; min-width: 0; }
.brand-emoji {
  font-size: 24px; line-height: var(--lh-solid); flex: none;
  animation: floaty 4.5s ease-in-out infinite; transform-origin: 50% 60%;
}
@media (prefers-reduced-motion: reduce) { .brand-emoji { animation: none; } }
/* The decorative rule yields before the words do. */
@media (max-width: 479px) { .brand-rule { display: none; } }
@keyframes floaty {
  0%, 100% { transform: translateY(0) rotate(-5deg); }
  50% { transform: translateY(-6px) rotate(6deg); }
}
.brand-text { display: flex; flex-direction: column; gap: 3px; }
.brand-name { font-weight: 600; font-size: clamp(19px, 2.6vw, 23px); line-height: .9; letter-spacing: -.03em; }
.brand-sub {
  display: flex; align-items: center; gap: 7px; font-family: var(--mono);
  font-size: 9px; font-weight: 500; letter-spacing: .26em; text-transform: uppercase; color: var(--accent);
  white-space: nowrap;
}
.brand-rule { flex: 1; height: 1px; background: var(--accent3); min-width: 0; }
.header-actions { display: flex; align-items: center; gap: 4px; flex-wrap: nowrap; flex: none; height: 100%; }
/* The language row lives on You now; the class stays shared with it. */
.langs { display: flex; gap: 0; }
.lang-btn {
  background: none; border: none; padding: 4px 0; font-family: var(--mono);
  font-size: var(--fs-small); letter-spacing: var(--ls-wide); line-height: var(--lh-solid); color: var(--muted2);
  min-width: 28px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
.lang-btn.active { color: var(--ink); font-weight: 600; }
/* The bar's three destinations at rail widths; the tab bar has them under 900. */
.mast-nav { gap: 18px; }
.mast-link {
  font-family: var(--mono); font-size: var(--fs-small); letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); text-decoration: none;
  display: inline-flex; align-items: center; height: 44px;
}
.mast-link:hover { color: var(--accent); }
.mast-link.active { color: var(--ink); font-weight: 600; }
@media (max-width: 899px) { .mast-nav { display: none; } }

.origin-line {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 13px; margin-top: 14px; /* sanctioned-gap: a banner clipped to the masthead, not a page seam */
  border-left: 2px solid var(--accent); background: var(--line2);
  border-radius: 0 6px 6px 0;
}
.origin-text { font-size: var(--fs-body); color: var(--text2); }

/* ---- layout ----------------------------------------------------------- */
/* Phone: one column, the shelf is a sheet. >=900: shelf column + the sheet
   of paper, centered. There is no third column any more — the document is
   the whole surface. */
.layout { display: grid; gap: clamp(24px, 4vw, 44px); align-items: start; }
.doc { min-width: 0; width: 100%; max-width: var(--doc-max); margin: 0 auto; padding-top: var(--seam-page); }

@media (min-width: 900px) {
  .layout { grid-template-columns: 300px minmax(0, 1fr); }
  .panel {
    grid-column: 1;
    position: sticky; top: 52px;
    max-height: calc(100vh - 76px); overflow-y: auto;
    padding-top: var(--seam-page);
  }
  .doc { grid-column: 2; }
}

/* ---- scrubs ----------------------------------------------------------- */
/* The control IS the numeral. Dashed underline says "take me", ew cursor
   says how, and the popover is the fine adjustment for taps. */
.scrub {
  font-family: var(--mono); font-weight: 500; color: var(--accent);
  border-bottom: 1px dashed var(--accent2); padding: 1px 1px 0;
  cursor: ew-resize; touch-action: pan-y;
  -webkit-user-select: none; user-select: none; white-space: nowrap;
}
.scrub.is-editing { background: var(--line2); border-radius: 4px 4px 0 0; }
.scrubbable { touch-action: pan-y; }
.scrubbable.is-scrubbing { cursor: ew-resize; }

.scrub-pop {
  position: fixed; z-index: 70; display: flex; align-items: center; gap: 2px;
  padding: 4px; border-radius: 12px; background: var(--panel);
  border: 1px solid var(--line); box-shadow: 0 8px 30px var(--shadow);
}
.pop-btn {
  min-width: 44px; min-height: 44px; border: none; background: none;
  color: var(--muted); font-size: 20px; line-height: var(--lh-solid);
  border-radius: 9px; touch-action: manipulation;
  -webkit-user-select: none; user-select: none;
}
.pop-btn:hover { background: var(--line2); color: var(--accent); }
.pop-btn[disabled] { opacity: .3; cursor: default; }
.pop-val {
  /* The floor; ui/scrub.js raises it per control to the widest value it can
     land on, so stepping never resizes the popover under a tapping finger. */
  min-width: 76px; min-height: 44px; text-align: center; font-family: var(--mono);
  font-weight: 500; font-size: 14px; background: none; border: none;
  padding: 0 6px; color: inherit; cursor: text; border-radius: 9px;
}
.pop-val[disabled] { cursor: default; }
.pop-val:not([disabled]):hover { background: var(--line2); }
.pop-input {
  width: 84px; text-align: center; font-family: var(--mono); font-weight: 500;
  font-size: 16px; background: none; border: none;
  border-bottom: 1px solid var(--accent); color: var(--ink); padding: 2px 0;
}

/* ---- the headline ----------------------------------------------------- */
/* One seam under the headline, not two.
   The headline carried --seam-page (26px) on its underside and the next
   section head brought --seam-sec (46px) of its own margin, so the boundary
   measured 72px -- the "way too high" gap Kilian spotted, and exactly the
   stacking DESIGN.md warns about. The head's margin is the seam here; the
   headline stops adding a second one. Its own last child (the quantity note)
   keeps the block together. */
.headline { padding-bottom: 0; }
.headline > .sec-head:first-child { margin-top: 0; }
/* The quantity head opens a labeled group inside the headline; the stat row
   under it keeps its own 16px, margins collapsing to the larger. */
/* The @handle above a recipe is the way back to the wall. Inherits the
   kicker's ink until you touch it, so it reads as a fact that turns out to be
   a door. */
.kick-home { color: inherit; text-decoration: none; }
.kick-home[href]:hover { color: var(--accent); text-decoration: underline; }

.hero-kicker { display: flex; align-items: baseline; gap: 7px; }
/* The way down to the bakes, riding in the kicker beside whose sheet this is.
   It keeps the foot-link dress every other jump in the app wears -- mono, the
   accent, its own case -- so it reads as the one thing in the row you can
   press, against a label that is only telling you whose page this is. */
.kick-sep { color: var(--faint); }
.journal-jump { padding: 0; font-size: inherit; letter-spacing: inherit; }
/* In the headline it holds the filmstrip's slot: same seam as the strip,
/* The sheet is titled by the dough. The CTA variant wears the scrub's dashed
   underline — the same "take me" affordance, pointed at naming. */
/* The name owns its line, whatever its length -- the whole of it, now that
   the verbs are words on the credit's line below. It used to share the line
   with four 44px icon buttons, which on a phone meant "Focaccia Genovese alle
   Olive" rendered as "Focaccia..." beside a stretch of empty gutter, and then
   with a 44px corner reserved for them; both were the most important word on
   the page giving up room to controls. It wraps rather than ellipsing, because
   a recipe's name is worth two lines. */
.doc-title {
  position: relative; display: block; margin: 0;
  font-weight: 600; font-size: clamp(30px, 7.5vw, 46px);
  line-height: 1.04; letter-spacing: -.025em; min-width: 0;
}
/* The name is a button that looks like a title, because it is both: tap it
   and it becomes the input. Inherits every bit of the h2's type so nothing
   moves when it turns into a field. */
.doc-title-name {
  min-width: 0; overflow-wrap: anywhere; text-align: left;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; letter-spacing: inherit; color: inherit;
}
.doc-title-name[disabled] { cursor: default; }
/* A recipe with no name yet says so, in the ink of a thing not yet filled in
   -- and wears the app's own "take me" dash, the same one the scrubs wear,
   because an empty name is the one field the page is actually asking for. */
.doc-title-name.is-placeholder { color: var(--faint); }
.doc-title-name.is-nameable.is-placeholder {
  color: var(--accent); border-bottom: 2px dashed var(--accent2);
}
.doc-title-name.is-nameable:hover { color: var(--accent); }
/* What the name means, for a reader who does not read the author's language.
   Under the credit rather than above it (views/formula.js says why), tucked
   with the same 2px the byline row uses so the four lines stay one block --
   and in the muted ink of a thing that is context rather than content. It must
   never look like a second title: no mono, no caps, and a size the eye passes
   over on the way to the numbers. */
.doc-title-gloss {
  margin-top: 2px;
  font-size: var(--fs-small); font-weight: 400; line-height: var(--lh-note);
  color: var(--muted); overflow-wrap: anywhere;
}
/* "by @kilian", quietly under the name -- the byline of a recipe card. */
.byline {
  display: inline-block;
  font-family: var(--mono); font-size: var(--fs-small); font-weight: 500;
  letter-spacing: var(--ls-mono); color: var(--muted);
}
.title-act.copied { color: var(--accent); }

/* The pinned title: the document's name, standing in while the real title
   is off screen. Phone only -- the rail keeps its own context. */
.title-sticky {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  display: flex; align-items: center; gap: 8px;
  height: calc(var(--sticky-h) + env(safe-area-inset-top));
  /* Fixed to the window, aligned to the page: the inset is the page's own
     padding plus whatever the centred column leaves at the side, so the name
     starts exactly under the brand above it at every width. max() covers the
     narrow case, where the column is the window and the gutter is zero. */
  padding-top: env(safe-area-inset-top);
  padding-bottom: 0;
  padding-left: max(var(--page-pad), calc((100vw - var(--page-max)) / 2 + var(--page-pad)));
  padding-right: max(var(--page-pad), calc((100vw - var(--page-max)) / 2 + var(--page-pad)));
  /* No hairline under it. The bar is the recipe's own title standing in for
     itself, so it keeps the recipe's rule: nothing horizontal. It is opaque
     paper over paper, and the page simply passes underneath. */
  background: var(--bg);
}
/* The name is the button -- tap to go back up -- and the verbs beside it are
   their own controls, so the bar is a row, not one big button wrapping two
   smaller ones.

   The credit sits *under* the name here exactly as it does in the headline.
   "By kilian should always be on a new line, even on the sticky header": the
   name and who it is from are one stacked unit wherever the name goes, and a
   bar that set them side by side was the header speaking a second dialect of
   its own identity. */
.title-sticky-top {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: 0;
  background: none; border: none; padding: 0;
  font-weight: 600; font-size: var(--fs-title); letter-spacing: -.01em;
  color: var(--ink); text-align: left;
}
.title-sticky-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.title-sticky-by {
  flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: var(--ls-mono); color: var(--muted);
}
/* With the real title still on screen the bar carries the verb alone: no
   name, no credit, nothing the reader can already see an inch below. It
   shrinks to the button it is holding and pushes it to the right, where the
   bar's controls always sit. */
/* The drift's two verbs, in short form: the bar stands in for the headline,
   it does not repeat it. */
.title-sticky-verbs { display: flex; align-items: center; gap: 6px; flex: none; }
.title-sticky-verbs:empty { display: none; }
/* The bar is not a phone affordance any more: it is where Save lives, so it
   has to exist wherever you can edit. At rail widths it tucks under the
   sticky brand row rather than covering it, and stays inside the document
   column instead of spanning the window. */
/* One behaviour at every width, and one inset. Two placements were tried and
   both were wrong: sticky inside the column sits in the flow, so it shoved
   the sheet down the instant a lever moved -- the very reflow it exists to
   avoid -- and parking it under the brand row landed it on the recipe's own
   name. It stays what it is on a phone: fixed to the top of the window,
   clear of the document, over the navigation rather than the content. */

/* The byline locks to the name -- title and credit are one unit -- and the
   recipe's verbs ride the far end of the same line. One row, one place, the
   same order for every recipe: duplicate, share, rename, delete. Which of
   them are lit is the only thing that says whose recipe this is. */
/* The title is a block, not a stack of strays: the name, who it is from, and
   what it comes out like. Its parts sit tight against each other and the
   page's seam falls only underneath the whole thing. */
/* The identity is one unit and nothing gets between its parts: the name, then
   who it is from, locked together. The byline drifted once already when the
   controls joined its row -- a 44px button in a text-height line centres the
   text in 44px of box and pushes the credit half an inch off the name it
   belongs to. Kilian: "the by kilian move away from the title. this cannot
   happen." So the row aligns on the baseline, not the box: the credit sits on
   its own line right under the name, and whatever the controls need hangs
   below it instead of shoving it down. */
.title-block { display: flex; flex-direction: column; align-items: stretch; }
/* The credit and the verbs share one line and never wrap: the credit gives way
   first (it ellipses), because a verb on a line of its own would move the
   photographs under it every time a lever made the recipe dirty -- the exact
   reflow the pinned bar exists to avoid. min-height pins the line whether the
   verbs are lit or not, so lighting them costs width and never height. */
.byline-row {
  display: flex; align-items: baseline; gap: 14px; margin-top: 2px;
  min-height: 19px;
}
.byline-row .byline { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One primary button and one menu, following the handle rather than flung to
   the far margin: signed out there is no byline at all, and controls alone
   against the right edge read as debris. Left is where the line starts. */
/* Hard right of the credit, and out of the heading entirely.
   As a flex child of the h2 these *sized the heading*: the title's box became
   the button's 34px while its own ink was 37px, so the byline sat wherever the
   control decided -- the byline drifting off the name for the third time. They
   are words on the line below now, aligned on its baseline, so the h2 is
   measured by its text and nothing else and no control can move the credit
   again. */
.title-acts { margin-left: auto; }
/* The pair reads by contrast: Save takes the weight because it is the one that
   commits, Revert stays in the ink of a small word because discarding should
   never be the loudest thing in a header. (.strong and .quiet on .text-btn
   already say this; the pair is named here too so the reason travels with the
   row it is about.) */
.act-menu-wrap { position: relative; display: inline-flex; }
/* The overflow: every other verb, in words, in one place, for every reader.
   A menu rather than a row of glyphs because a glyph row was the second
   action language this header used to speak. */
/* Hangs from the right, because its button does. Anchored left it ran 118px
   past the edge of a 402pt phone -- a menu you cannot read is a menu that is
   not there. max-width keeps it inside even on the narrowest screen. */
.act-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  max-width: calc(100vw - 32px);
  display: flex; flex-direction: column; min-width: 168px;
  background: var(--panel); border: 1px solid var(--line3); border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow); overflow: hidden;
}
.act-menu-item {
  background: none; border: none; text-align: left;
  padding: 11px 14px; min-height: 44px;
  font-family: var(--mono); font-size: var(--fs-small); letter-spacing: var(--ls-mono);
  color: var(--ink);
}
/* No rules between the items. The panel is a shape of its own -- its outline
   and its corners say where it starts and stops -- and the recipe underneath it
   carries no horizontal lines, so neither does the menu it opens. */
.act-menu-item:hover { color: var(--accent); }
.act-menu-item.danger { color: var(--accent); }
.act-menu-item.armed { font-weight: 600; }

.doc-title-input {
  display: block; width: 100%; margin: 4px 0 0; padding: 0 0 2px;
  background: none; border: none; border-bottom: 1px dashed var(--accent);
  font-family: inherit; font-weight: 600; font-size: clamp(30px, 7.5vw, 46px);
  line-height: 1.04; letter-spacing: -.025em; color: var(--ink);
}
.doc-title-input:focus { outline: none; }

/* The math is one row, always: the numbers share a line, and each caption
   wraps beneath its own number instead of pushing a chip to a second row or
   truncating into ellipses. */
.stat-row { display: flex; align-items: flex-start; gap: clamp(14px, 4vw, 40px); flex-wrap: nowrap; margin-top: var(--seam-head); min-width: 0; }
.stat { min-width: 0; flex: 0 1 auto; }
.stat-cap { white-space: normal; line-height: 1.35; max-width: 9em; }
@media (max-width: 480px) {
  .stat-cap { font-size: 9px; letter-spacing: .07em; }
  .stat .scrub { font-size: 19px; }
  .total-val { font-size: 26px; }
}
.stat { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; cursor: ew-resize; }
.stat .scrub { font-size: 21px; display: inline-block; min-width: 44px; }
.total-stat { cursor: default; }
.total-val {
  font-family: var(--mono); font-weight: 500; font-size: 27px;
  line-height: 1; color: var(--accent); letter-spacing: var(--ls-tight);
  padding: 1px 1px 0; border-bottom: 1px dashed transparent;
}
.stat-cap {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500;
  text-transform: uppercase; letter-spacing: .1em; color: var(--muted2);
}
.qty-note { margin-top: var(--seam-head); }

/* ---- the formula ------------------------------------------------------ */
/* Sections breathe: the gap above a section head is the seam you can see. */
.sec-head { margin: var(--seam-sec) 0 var(--seam-head); }
/* No border under a formula row: the range track is the row's own rule, and
   two hairlines an em apart read as clutter. */
.f-row { padding: 11px 0 var(--row-foot); }
/* ...and the formula ends where its last row's ink ends, not where that row's
   box does. The rows keep their foot -- it is the air under the track, and the
   whole row is the drag surface, so the box may not shrink -- and the section
   pulls its own bottom back over it instead, leaving the seam above Process to
   measure the same 46px as the seam above Formula and Method.

   `ends-in-row` comes from views/formula.js, beside `by-weight`: what ends the
   section is the prefermented-flour row, or the method's own words when there is
   no preferment to divide. Only the first of those two carries a foot. */
.formula.ends-in-row { margin-bottom: calc(-1 * var(--row-foot)); }
.f-row:not(.f-anchor) { cursor: ew-resize; }
.f-row.is-scrubbing { background: linear-gradient(to bottom, transparent, var(--line2)); }
.f-line { display: flex; align-items: baseline; gap: 12px; }
.f-name { flex: 1 1 auto; min-width: 0; font-size: var(--fs-ui); color: var(--text3); }
.f-pct { font-size: var(--fs-ui); }
.f-pct-fixed {
  font-family: var(--mono); font-weight: 500; color: var(--muted);
  padding: 1px 1px 0; border-bottom: 1px dashed transparent;
}
.f-amt {
  min-width: 64px; text-align: right; font-family: var(--mono);
  font-weight: 500; font-size: var(--fs-ui); color: var(--ink);
}
/* The same cell, typed into. It wears the read-mode type exactly, plus the
   dashed underline the app uses everywhere for "this is yours to change", so
   switching units moves no number by a pixel. */
.f-amt-in {
  flex: none; width: 78px; background: none; border: none; padding: 1px 1px 0;
  border-bottom: 1px dashed var(--accent2); color: var(--accent);
}
.f-amt-in:focus { outline: none; border-bottom-style: solid; border-bottom-color: var(--accent); }
/* By weight the percentage still reads, but it is no longer the control. */
.formula.by-weight .f-row { cursor: default; }
.scrub.is-inert {
  color: var(--muted); border-bottom-color: transparent;
  cursor: default; -webkit-user-select: text; user-select: text;
}
.f-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.f-head .sec-head { margin-bottom: 0; }
.unit-words { gap: 14px; }
.unit-words .word { font-size: var(--fs-small); letter-spacing: var(--ls-wide); text-transform: uppercase; }
.f-units-note { margin: 8px 0 2px; }
.f-track {
  position: relative; height: 2px; margin-top: 9px; background: var(--track);
  border-radius: 999px; overflow: hidden;
}
/* The bars are proportions of the heaviest row on the sheet, and a bowl holds
   things at 1% of its flour: at that width a fill is three pixels on a phone
   and the row reads as holding nothing. So a row that holds anything draws at
   least a nib of it — wide enough to be a bar rather than a dot, narrow enough
   that it is still unmistakably the smallest quantity on the page, and the same
   width at every screen size because what it is fighting is eyesight, not
   layout. `is-none` is the one row that really is empty: an ingredient scrubbed
   to zero, which is a different statement from a pinch. */
.f-fill {
  position: absolute; inset: 0 auto 0 0; width: 0; min-width: 6px;
  background: var(--accent); border-radius: 999px;
  transition: width .12s ease;
}
.f-fill.is-none { min-width: 0; }
.f-anchor .f-fill { background: var(--line3); }
@media (prefers-reduced-motion: reduce) { .f-fill { transition: none; } }
.hydration-bounds {
  display: flex; justify-content: space-between; margin-top: 7px;
  font-family: var(--mono); font-size: 10px; letter-spacing: var(--ls-wide); color: var(--muted);
}
/* The low end when the ingredients set it: a floor the recipe chose, not the
   ordinary low the app names. Accent, because it is the one end of this track
   that is about *this* dough — and the only ink on the line that means "look".
   No box, no badge: it is still one of the two words under a track. */
.hydration-bounds .is-floor { color: var(--accent); }

/* Why a row reads what it reads, under the row's own track.
   The one row that needs it today is a liquid pouring 0g, where the figure is
   correct and says the opposite of what it looks like. Prose voice — it is a
   sentence, not a fact — at the hint's size and colour, so it reads as the
   sheet talking rather than as a row that has grown a second value.
   Its air is the track's own 9px, matched, so a row gaining a note grows by a
   line of type and nothing else moves. */
.f-note {
  margin: 9px 0 0; font-size: var(--fs-body); line-height: var(--lh-note);
  color: var(--muted2);
}

/* A count the solver moved: the row wears the wash while it is one of the
   unsaved things.

   The same accent ground the lit row takes, for a related reason — this is the
   row the page is pointing at — and drawn the same way, **inside the row's own
   padding**, so a count that re-rounds mid-drag costs the sheet no height. A
   sentence in the flow was the first cut and shoved everything below down by a
   line under the reader's finger; `layout.js` catches that and is right to.

   It is not `is-lit`. That one is a reading aid you are holding, one at a time,
   stored nowhere; this one is drift from the shelf's copy and stays until Save
   or Revert clears it with every other mark. Two meanings, one ink — which is
   fine, because the accent has meant "look here" on this page from the start.
   The entrance says a figure just moved; the ground says it is still moved. */
.f-row.is-recount { background: var(--accent4); border-radius: 6px; }
.f-row.is-recount { animation: recount .4s ease; }
@keyframes recount { from { background: var(--accent3); } }
@media (prefers-reduced-motion: reduce) { .f-row.is-recount { animation: none; } }
/* Paper has no shelf copy to differ from, and the count printed is the count
   you crack. The dots go for the same reason (see the kill-list below). */
@media print { .f-row.is-recount { background: none; animation: none; } }

/* Which liquid the hydration is. It rides the row's own line, right after the
   word it qualifies, so the numbers stay hard right and the row costs no extra
   height. Small uppercase, the same voice the unit words wear in the formula
   head: a choice about this row, not a section of its own. */
.f-label { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: 14px; }
.f-label .f-name { flex: 0 1 auto; }
.f-liquid-words { flex: 0 0 auto; gap: 12px; }
/* The same voice for every set of words that rides a formula row: the liquid on
   its label line, the preferment's flour on the line below it. One rule, so a
   second picker cannot arrive in a different size than the first. */
.f-liquid-words .word, .f-words .word {
  position: relative; min-height: 0; padding: 2px 0;
  font-size: var(--fs-small); letter-spacing: var(--ls-wide); text-transform: uppercase;
  cursor: pointer;
}

/* The footnote marker: where this number came from, asked beside the number.
   It is a mark on a word, not a verb in the row, so it hugs the name it
   qualifies -- the label's own 14px gap is the distance between two *words*,
   and at that distance the ⓘ read as something else on the line. The ink is
   the faint every dismiss and every aside here wears, going accent under the
   finger like all of them; the row's ew-resize stops at its edge, because a
   press on a button is never a scrub (ui/scrub.js). Baseline-nudged: an icon
   centred in its box sits high against a line of type. */
.f-info {
  position: relative; flex: none; margin-left: -8px;
  display: inline-flex; align-items: center; padding: 0;
  background: none; border: none; color: var(--faint);
  cursor: pointer; transform: translateY(2px);
}
.f-info:hover { color: var(--accent); }

/* The way in and the way out for an ingredient: a dashed pill, the same "add
   here" affordance the process wears for + Sauce, opening a sheet over the page.

   A sheet and not a disclosure, because a list that lives inside the document it
   edits is coupled to it: a row lands above the list, everything below slides, and
   the option leaves your thumb. Holding the list still meant scrolling the page to
   compensate, which is the same complaint wearing a different coat. Fixed to the
   viewport with the page locked behind it, tapping moves nothing because nothing
   can move. Same shape the journal's open photograph uses. */
.ing-overlay {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: flex-end; justify-content: center;
  background: rgba(23, 21, 18, .34);
}
.ing-panel {
  width: 100%; max-width: 520px; max-height: 78vh; overflow-y: auto;
  background: var(--bg); border-radius: 16px 16px 0 0;
  /* One rhythm on all four sides. It was 10px at the top against 18px at the
     bottom against the page's own side padding, which reads as a panel that has
     slipped -- most visible on the shortest sheet, where the three edges are all
     on screen at once. */
  padding: var(--page-pad) var(--page-pad) calc(var(--page-pad) + env(safe-area-inset-bottom));
  box-shadow: 0 -2px 30px var(--shadow);
}
/* No drag handle: the sheet does not drag (see ui/sheet.js), and the pill
   plus its clearance was 40px of dead air between the panel's edge and its
   title -- the "weird padding" at the head of every sheet. */
.ing-panel-head {
  /* Centred, not baseline: the close is a glyph in a 34px button and a baseline
     lines its box up with the heading's text rather than its middle. */
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  /* The head owns the air under it, same seam as every heading. */
  margin-bottom: var(--seam-head);
}
/* The name of the surface, in the type every title on this app wears. It was a
   kicker -- 10.5px mono caps -- against a 19px close glyph, which made the way
   out the loudest thing on the sheet. The title leads; the ✕ keeps the faint
   ink every dismiss here has. */
.ing-title {
  margin: 0; font-size: var(--fs-title); font-weight: 600;
  letter-spacing: -.01em; color: var(--ink);
}
/* Desktop: the same sheet, centred rather than docked, because a bottom sheet on
   a wide screen is a long way from where the pointer already is. */
@media (min-width: 900px) {
  .ing-overlay { align-items: center; padding: 24px; }
  .ing-panel { border-radius: 14px; box-shadow: 0 8px 40px var(--shadow); }
}
/* The list itself is a plain column, because ten things do not need a grid. */
/* --seam-head, because that is exactly what this gap is: the air under the
   table, before the control that adds to it. */
.f-adds { margin-top: var(--seam-head); display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ing-sheet { display: flex; flex-direction: column; width: 100%; }
/* What the sheet says instead of a list when it has nothing to offer: a step
   already drawing everything the formula holds, or one at the row cap. Prose,
   in the sheet's own column, where the first option would have been. */
.ing-empty { padding: 8px 0; }
/* A row of the sheet keeps the grammar every list on this page keeps: a name
   on the left, an amount on the right, and the words about what a thing is
   under the name. Only figures sit in the amount column -- the composition
   lived there for a while, prose dressed as a weight, and the owner asked
   three times for it to come down. The rules under the rows went with every
   other horizontal line on this sheet -- the list reads as a list because the
   names line up, not because something was drawn beneath each one. */
.ing-opt {
  display: flex; align-items: baseline; gap: 12px; width: 100%;
  padding: 8px 0; background: none; border: none;
  text-align: left; cursor: pointer; color: var(--text3); font-size: var(--fs-ui);
}

/* A step's verbs, at the end of its title's column and in the same place on
 * every step.
 *
 * Quiet: small words, because they are always on screen and the title is what
 * the eye is looking for. Right-aligned rather than tucked
 * after the title -- after the title they started at whatever x that step's
 * name ended at, which is three answers to "where do I press Edit" on a
 * three-step recipe. The weight keeps the far right and reserves its column
 * (`min-width`), so the slot lands in one place whether the step totals or
 * not; the arrows are gone from this line entirely, onto the rail with the
 * number they move. */
.step-title-line { display: flex; align-items: baseline; gap: 10px; }
.step-title-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: 8px; }
.step-title-line > .step-title-main > .step-acts { margin-left: auto; }
.step-title-line > .card-amt { flex: none; min-width: 5ch; }

/* Delete, under the editor. Keep and discard went up to the verb slot on the
   title line, so they are found where Edit was pressed; throwing the step away
   is a different size of act and stays down here, at the end of the thing it
   would remove and nowhere near the tick. */
.step-edit-foot {
  display: flex; align-items: center; gap: 8px;
  margin-top: var(--seam-head);
}
/* Delete starts where the editor does, and nothing pushes it.
 *
 * It has had both of the wrong answers. `margin-left: auto` put it hard against
 * the card's right side and opened a hole across the row at every width -- 449px
 * on a desktop, 203px on a phone -- and the 16px step that replaced it was a gap
 * *from keep and discard*, which now sit up in the verb slot beside the title.
 * A step away from nothing is an indent nobody can read a reason for, so there
 * is none: the only verb down here begins at the editor's own left edge.
 */
.step-edit-foot .card-remove { margin-left: 0; }
/* Small words, full-size targets. They stay in the small print because the
   title beside them is what the eye wants; the thumb's 44pt is drawn outward by
   .text-btn::after, which is what keeps a line of type a line of type. (They
   were 22pt glyph boxes with `min-width: 0` -- legible, and failing the 44pt
   rule on every phone at once, which `make qa` caught on eleven states.) */
.step-acts { display: flex; align-items: baseline; gap: 12px; flex: none; }
.step-acts .step-act:disabled { opacity: .4; }
/* One already in the recipe reads as chosen and is the way to take it out --
   accent, the only interactive colour, and no second control to discover. */
.ing-opt.active { color: var(--ink); font-weight: 600; }
/* The tick hangs in the panel's own padding, so an ingredient going in or out
   never moves its name: every name keeps one left edge, chosen or not, and the
   subtitle sits flush under the word it describes. Inline, the ✓ pushed the
   active name right and left its subtitle hanging under the tick. */
.ing-opt.active .ing-opt-name::before {
  content: '\2713'; color: var(--accent);
  position: absolute; right: 100%; padding-right: .3em;
}
.ing-opt-name { position: relative; }
/* Name over its words; the pair is one flex item so the amount takes the
   name's own baseline, exactly as the formula's rows align theirs. */
.ing-opt-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.ing-opt-made {
  font-size: 12px; color: var(--muted); font-weight: 400;
  line-height: var(--lh-note); margin-top: 2px;
}
/* The amount column, in the amount column's own clothes (.f-amt): what there
   is of this ingredient -- in the dough on the formula's sheet, still on the
   shelf on a step's. Empty when there is none, which is its own answer. */
.ing-opt-qty {
  flex: none; min-width: 64px; text-align: right;
  font-family: var(--mono); font-weight: 500; font-size: var(--fs-ui); color: var(--ink);
}
/* The same row with nothing to press: a line of the hydration's working, where
   the name is a source of water, the words under it are what that source is,
   and the figure is what it put in the bowl. It borrows the option row's
   grammar and its three children rather than a copy of them -- one list shape
   on this sheet, whether its lines are choices or facts -- and skips only what
   an option needs and a fact does not: the pointer, and the 44pt a thumb needs
   to hit a target that isn't there. */
.ing-fact {
  display: flex; align-items: baseline; gap: 12px; width: 100%;
  padding: 8px 0; color: var(--text3); font-size: var(--fs-ui);
}
/* What the lines above add up to. The weight is the whole separation -- no rule
   under the last source, because the recipe draws no horizontal lines -- and
   the air lands once, above the first of them. */
.ing-fact.is-sum { margin-top: var(--seam-head); }
.ing-fact.is-sum + .ing-fact.is-sum { margin-top: 0; }
/* Where the working turns from the water to the flour: the same air a total
   takes, because what starts there is the second half of a division and not
   another thing that brought water. */
.ing-fact.is-turn { margin-top: var(--seam-head); }
.ing-fact.is-sum .ing-opt-name, .ing-fact.is-sum .ing-opt-qty {
  color: var(--ink); font-weight: 600;
}
/* And the one sentence the working can need, under it, in the voice every
   aside on this app wears. Same seam as the sum it qualifies. */
.hyd-note { margin-top: var(--seam-head); }
/* ---- what the flour is ------------------------------------------------
   A flour's name is typed where it is read, exactly like the recipe's own
   title: the word carries the dashed "yours to change" underline, and the field
   takes the same slot on the same line, so opening it moves nothing. */
.f-flour-name {
  border: none; background: none; padding: 0; text-align: left;
  font: inherit; font-size: var(--fs-ui); color: var(--text3);
  border-bottom: 1px dashed transparent;
}
.f-flour-name.is-nameable { cursor: pointer; }
.f-flour-name.is-nameable:hover { color: var(--accent); }
.f-flour-name[disabled] { cursor: default; }
/* A named flour reads as what it is -- a row's name, in a row's ink. The dash
   and the accent are for the flour that has no name yet, which is the same rule
   the recipe's own title follows: a title you have given is plain, and it is the
   blank one that asks. Six red names down the flour column would make the table
   look like a form. */
.f-flour-name.is-placeholder.is-nameable {
  color: var(--accent); border-bottom-color: var(--accent3);
}
.f-flour-in {
  flex: 1 1 auto; min-width: 0; background: none; border: none; padding: 0 0 1px;
  border-bottom: 1px dashed var(--accent); font: inherit; font-size: var(--fs-ui);
  color: var(--ink);
}
.f-flour-in:focus { outline: none; border-bottom-style: solid; }
/* Taking a flour out, from inside the editor that has its name open. A foot
   link, the same quiet shape Delete wears in a card's editor -- and only there,
   so the table itself stays a column of names, percentages and grams. */
.f-flour-del { flex: 0 0 auto; }
/* The flours you can buy, under the field that is being typed into.

   It is the ⋯ menu's panel (.act-menu, and it carries that class) hung off the
   left of the row instead of the right of a button, because that is where the
   name it is completing starts. Two things it must not do: move the rows under
   it -- so it floats, and typing a name shifts the page 0px like every other
   edit on this sheet -- and cover the line you are typing, so it hangs below
   the row rather than over it. */
.f-flour-row, .f-part { position: relative; }
.f-sugg { left: 0; right: auto; top: 100%; max-width: min(100%, 320px); }
/* A flour's name is a name, not a verb: it reads in the row's own type rather
   than in the mono the menu's actions wear. Wrapping rather than ellipsis --
   "Molino Dallagiovanna La Napoletana" cut short is a suggestion you cannot
   identify, which is worse than a suggestion on two lines. */
.f-sugg-opt {
  font-family: inherit; letter-spacing: normal; font-size: var(--fs-ui);
  color: var(--text3); line-height: 1.3;
}
/* Where the arrow keys are. The same accent hover already means, because it is
   the same thing said by a different input. */
.f-sugg-opt.is-on { color: var(--accent); }
/* A flour of a blend is an ordinary row: on a blend there is no total above it
   to be subordinate to, so it lines up with the hydration and the salt like
   anything else you weigh. (It keeps the class -- scripts/layout.js asks which
   rows are flours, and one of its checks is about what may not sit on them.) */
/* The line under a row, for a choice with no room on the label line: which
   flour the starter is made of, where the names are as long as the baker typed
   them. Same geometry as the hydration bounds it sits opposite. */
.f-sub-line {
  display: flex; align-items: baseline; gap: 12px; margin-top: 7px;
  padding-left: 15px; flex-wrap: wrap;
}
.f-sub-cap {
  font-family: var(--mono); font-size: 10px; letter-spacing: var(--ls-wide);
  text-transform: uppercase; color: var(--muted);
}
.f-words { display: inline-flex; gap: 12px; flex-wrap: wrap; }
/* ---- the starter, after the table -------------------------------------
   Its numbers wear the quantity block's clothes: a figure over a caption. The
   share of the flour is the control, its weight is what that produces, and the
   split is one figure per flour captioned by the flour's own name -- so "which
   flours" is answered in the vocabulary the rest of the sheet already uses.

   Its own seam, because it is the second stat row of a section rather than the
   first thing under a heading. */
/* The word over the split takes a section heading's air above it and a heading's
   own gap below -- it *is* a heading, for the row under it. */
.pref-froms-kick { margin: 22px 0 0; /* sanctioned-gap: a group label inside a
  section, closer than --seam-sec would put a new section and farther than
  --seam-head, because what it labels is a second row of the same block. */ }
.pref-froms { margin-top: var(--seam-head); }
/* ---- a total, divided --------------------------------------------------
   The same 2px rule every row on this sheet already wears, cut where the parts
   meet. The first attempt was a 14px rounded capsule in two tones of rust, and
   the owner's verdict was that it was ugly -- it was: this page is built from
   hairlines, mono figures and white space, and a fat coloured pill is furniture
   from another application. So the bar *is* the proportion bar, with a seam you
   can push: accent for the first part, the same grey the flour total's own fill
   uses for what is left, and the boundary drawn as a tick crossing the rule
   rather than as a gap knocked out of it.

   Thin does not mean untouchable: the whole track takes the press (the nearest
   seam wins), and the seam's own reach is 44pt via ::after. */
/* Its own air, so it reads as one object under the rows rather than as another
   row's track. */
.sb { margin-top: 16px; /* sanctioned-gap: between a set of rows and the bar that
  divides them -- more than the 9px a row gives its own track, less than the
  --seam-entry that would make it a separate block. */ }
/* And the rows stop drawing the division themselves. A row's track says how far
   up its own dial the value sits; when a bar below is dividing those same rows,
   the two say one thing twice, and the bar says it better. */
.f-liquid-row.is-split .f-track { display: none; }
.sb-track {
  position: relative; height: 2px; background: var(--track);
  border-radius: 999px; display: flex; touch-action: pan-y; cursor: ew-resize;
}
.sb-seg { height: 100%; transition: width .12s ease; }
.sb-seg:first-child { border-radius: 999px 0 0 999px; }
.sb-seg:last-child { border-radius: 0 999px 999px 0; }
/* Accent is the part you are holding; the rest steps back into the ink the flour
   total's fill already uses, so the bar says "this much of it" in the vocabulary
   the tracks above it established. */
.sb-seg-0 { background: var(--accent); }
.sb-seg-1 { background: var(--line3); }
.sb-seg-2 { background: var(--accent3); }
.sb.is-splitting .sb-seg { transition: none; }
@media (prefers-reduced-motion: reduce) { .sb-seg { transition: none; } }
/* The seam: a tick crossing the rule, which is legible on a 2px line where a
   handle would not be, and reads as a mark on a scale rather than a widget. */
.sb-seam {
  position: absolute; top: -5px; bottom: -5px; width: 1px; margin-left: -0.5px;
  background: var(--accent); cursor: ew-resize;
}
/* The thumb's reach, kept inside the page. At -20px a seam sitting near the end
   of the track pushed its own hit area past --page-pad and the document could
   scroll sideways by 4px -- invisible to any check that measures elements, because
   a pseudo-element has no box to measure. 12px stays inside the smallest padding
   the page ever uses. */
.sb-seam::after { content: ''; position: absolute; inset: -16px -12px; }
.sb-seam:focus-visible { outline: none; }
.sb-seam:focus-visible::before {
  content: ''; position: absolute; inset: -3px -2px;
  border: 1px solid var(--accent); border-radius: 2px;
}
/* What the parts come to, in the row's own type: the weight, then whose it is.
   Same line the hydration bounds sit on, same size, same colour. */
.sb-legend {
  display: flex; justify-content: space-between; gap: 14px; margin-top: 9px;
}
.sb-item { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.sb-item:only-child { justify-content: flex-start; }
.sb-amount {
  font-family: var(--mono); font-weight: 500; font-size: var(--fs-small); color: var(--ink);
}
.sb-name {
  font-family: var(--mono); font-size: 10px; letter-spacing: var(--ls-wide);
  text-transform: uppercase; color: var(--muted);
}

.f-method { margin: 2px 0 20px; display: flex; flex-direction: column; gap: 8px; }
.words { display: inline-flex; gap: 18px; flex-wrap: wrap; }
.word {
  padding: 6px 0; border: none; background: none; font-size: var(--fs-ui);
  font-weight: 400; color: var(--muted2); border-bottom: 2px solid transparent;
  min-height: 34px;
}
.word.active { font-weight: 600; color: var(--ink); border-bottom-color: var(--accent); }

.footnote { margin: var(--seam-head) 0 0; font-size: var(--fs-small); color: var(--muted2); font-weight: 400; line-height: var(--lh-note); }

/* ---- the process ------------------------------------------------------ */
.process { margin-top: 8px; }
/* No override: the process heading takes the same seam every other section
   heading takes. Its old 54/6 was tuned to stack with the 30px a card used
   to carry on its top -- 84px of hole before the first step. The card kept
   its air on one side only, so the heading can stop compensating. */
/* A card carries its air on both sides and gives the top one up only when it
   follows another card -- there, the seam below the one above is already the
   whole boundary, and keeping both made the 60px hole this rule was written
   to close.

   It was the other way round for a day (`.card + .card { padding-top }`),
   which is only correct while cards are adjacent siblings. They are not: the
   sauce card follows the steps and the journal follows the whole process
   section -- so each of them lost its top air and butted straight against what
   came before it. Stating the exception rather than the rule keeps every case
   right, wherever a card turns up.

   No hairline under it either. Kilian: "I don't like any kind of horizontal
   lines in the recipe" -- so the whole sheet is ruled by air and by the mono
   kickers that name each part, and the only rules left on the page are the
   ones that carry a number (a lever's track, the split bar). White space was
   already doing this work; the lines were saying it a second time. */
/* The card's top padding is a variable because the rail's number reads it:
   the number sits on the title's line, and the title sits wherever this
   padding puts it. One value, two readers -- when they were two literals the
   padding changed (cards gave up their top air) and the number stayed at the
   old offset, 26px below every stacked step's title. */
.card { position: relative; --card-top: var(--seam-card); padding: var(--card-top) 0 var(--seam-card); }
.card + .card { --card-top: 0px; }
/* ...and a card that opens a section takes its air from the heading. */
.sec-head + .card { --card-top: 0px; }
/* Phases hang off a timeline rail; the number sits on the rail. */
.phase { padding-left: 32px; }
.phase::before {
  content: ''; position: absolute; left: 8px; top: 0; bottom: 0;
  width: 1px; background: var(--line);
}
.phase:last-of-type::before { bottom: auto; height: 100%; }
/* ...and it ends on a mark, never in mid-air.
 *
 * A card's foot is 30px of `--seam-card`, and the rail runs the whole card, so
 * the last step ends in 30px of hairline with nothing at its end. On your own
 * sheet that is right: the "Add a step" row sits flush under the card and
 * `.add-step::before` picks the line straight up and carries it into the `+`
 * -- "an open end, not a line stopping in mid-air", which is what that rule
 * says it is for. A visitor has no such row, so nothing caught the line and it
 * stopped in exactly the mid-air the rule was written against.
 *
 * The foot goes rather than the rail being shortened inside it: what that 30px
 * was for was carrying the timeline to the next step, and when there is no next
 * step it is not a foot, it is the first part of the boundary below (DESIGN 4).
 * Shortening the line and keeping the padding would have left the journal's
 * opener measuring 76px where every other opener on this sheet measures 46. */
.process.ends-in-step .phase:last-of-type { padding-bottom: 0; }
/* The number, and the two arrows that change it.
 *
 * The rail carries the position, the number sits in it exactly where it always
 * sat, and the arrows hang off the number -- above for up, below for down, each
 * one pointing at where the step would land. Hung rather than stacked in flow
 * so `01` never moves: the first step has no ↑ and the last has no working ↓,
 * and a number that shifted with them would be the timeline wobbling as you
 * sorted it. Both mask the rail line the way the number does. */
/* The whole column rides the hairline: 8.5px is the middle of `.phase::before`
   (left: 8px, width: 1px), and the rail is centred on it so the number and both
   arrows share one axis. Left at the card's edge, the number's own middle fell
   2px short of the line -- which nobody can name and everybody can see. */
.phase-rail {
  position: absolute; left: 8.5px; transform: translateX(-50%);
  /* On the title's line, whatever the card's padding: the number's box top
     sits exactly on the title's, and the digits' own 3px of padding plus the
     leading of the smaller type do the centring -- ink centre to ink centre
     within a pixel, which §14 asserts. Two literals have died here: a flat
     26px that survived the padding it was tuned to, and a -4px that carried
     an offset from a title whose metrics had since changed -- "the rail and
     the titles vertical position is not in sync". No third: the only term is
     the padding, by its name. */
  top: var(--card-top); z-index: 1;
}
.phase-num {
  display: block; text-align: center;
  font-family: var(--mono); font-weight: 500; font-size: var(--fs-small);
  color: var(--accent); background: var(--bg); padding: 3px 0;
}
/* Scoped to the rail so it outranks `.icon-btn`'s own 34pt minimum, which sits
   further down the file and would otherwise win on order alone -- and did,
   holding the arrows a third of the number's height away from it. Small glyph,
   full-size target: the ::after at the bottom of this file reaches to 44. */
.phase-rail .phase-move {
  position: absolute; left: 50%; transform: translateX(-50%);
  width: 22px; height: 34px; min-width: 0; min-height: 0; padding: 0;
  font-size: var(--fs-small); line-height: 1;
  color: var(--faint); background: var(--bg);
}
/* The air is the mask, not the spacing.
 *
 * "the top arrow has some padding top bottom the bottom doesn't I like the
 * padding" -- the top arrow of the first step floats in the seam above the
 * card, where the rail has not started yet, and that is what the whole cluster
 * should look like. So the boxes are 34px around a 22px glyph and they are
 * pulled back over the number's own 4px of clearance: the glyphs do not move,
 * the three masks meet, and the hairline breaks **once** for the marker rather
 * than three times for two 4px stubs of line nobody can read a meaning into.
 * 12px of white above the arrow and below it, on every step and both ends.
 *
 * Keep the arithmetic together if either number changes: a 34px box is 6px
 * taller each side than the glyph it holds, so the -2px is that 6 taken off
 * the 4px of clearance the glyph still keeps from the number. */
.phase-up { bottom: calc(100% - 2px); }
.phase-down { top: calc(100% - 2px); }
/* An arrow that cannot fire fades its *glyph*, never its mask.
 *
 * `opacity: .35` fades the element, and the element is also the piece of --bg
 * that cuts the hairline. So the first step's ↑ and the last step's ↓ had the
 * rail running straight through them while every other arrow sat in clean
 * white -- read as "the disabled one has no padding", and it was exactly that.
 * The colour is the same 35% of --faint over --bg that the opacity produced,
 * so the glyph is unchanged and the break is now the same on all four.
 * `--line3` is the fallback for an engine without color-mix. */
.phase-move:disabled {
  color: var(--line3);
  color: color-mix(in srgb, var(--faint) 35%, var(--bg));
}
.phase-move:hover:not(:disabled) { color: var(--accent); }
/* The head is a head and nothing else; the air under it belongs to whatever
   follows, because only that block knows how much of the seam it is already
   carrying inside its own top edge. (One --seam-head lived here, on top of an
   ingredient row's own 8px of padding: 18px of white where the rows sat 17px
   apart, so the head read as one more row of the list. See "a step's rhythm"
   below for the ramp all of it now comes from.) */
.card-head { display: flex; align-items: baseline; gap: 14px; }
.card-body { flex: 1 1 auto; min-width: 0; }
.card-title { font-weight: 600; font-size: var(--fs-title); letter-spacing: -.01em; }
.card-sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
.card-amt { text-align: right; font-family: var(--mono); font-weight: 500; font-size: 16px; }

/* ---- a step's rhythm ---------------------------------------------------
 *
 * "I want the vertical spacing perfect, with rythm, right now is not."
 *
 * It was not. Measured from ink, one step held 3px, 18.5, 17, 33.5, 26 and
 * 14.8px of white -- six distances with no relationship between them, so
 * nothing on the card said which parts belonged to each other. The list sat
 * as far from the title as its rows sat from one another, and the widest gap
 * inside a step was nearly the gap between two steps.
 *
 * One ramp, every rung measured the way the eye measures -- from the last
 * mark above to the first mark below, never from a box edge:
 *
 *     4px                  a title and its subtitle: a pair, not a seam
 *    10px  --seam-head     a heading and the list it names
 *    16px  2 × --row-air   one line of a list and the next
 *    26px  --seam-entry    one half of a step and the next
 *    38px  --seam-card     one step and the next, the rail running between
 *
 * Each rung is about 1.6x the one under it, which is what makes it read as a
 * ramp rather than as six numbers that happen to differ. A step is a title
 * and its content, and its content is two lists -- what it weighs and what
 * you do -- so those two are the halves the 26 separates, whichever of them a
 * given step happens to have.
 *
 * A block subtracts the air it already carries inside its own top edge, so
 * the seam on screen is the token and not the token plus a padding nobody can
 * see. That is the whole trick, and it is DESIGN 1.3's rule for the boundary
 * *below* a block turned upwards.
 */
.phase > .rows, .phase > .xrows, .phase > .bake-dials,
.phase > .step-note, .phase > .all-yeast-note, .phase > .add-ing,
.phase > .steps-input, .phase > .step-edit-foot { margin-top: var(--seam-entry); }
/* The instructions' heading carries its own seams as padding -- see there. */
.phase > .steps-bar { margin-top: 0; }
/* ...less the air the block carries itself: a list's rows are padded so a lit
   row is a band rather than a stripe, and that padding is the first --row-air
   of every seam that lands on the list. */
.phase > .rows, .phase > .xrows { margin-top: calc(var(--seam-entry) - var(--row-air)); }
/* The sentences are the one block *named* by what stands above it, and that
   heading is already carrying the seam in its own padding. */
.phase > .steps-bar + .steps { margin-top: 0; }
/* `+ Ingredient` is the foot of the list it adds to, not a half of its own:
   the list's seam, so it reads as belonging to the rows above it. */
.phase > .add-ing { margin-top: calc(var(--seam-head) - var(--row-air)); }

/* The add-on pills sit where the cards they summon will appear -- and now that
   the one left is a row on the rail, it keeps the list's own pitch: no margin
   of its own, so the gap above it is the last card's bottom padding and
   nothing else, exactly the seam any two stacked steps leave between them. A
   --seam-card here on top of that made the last stretch of rail twice as long
   as every other, which reads as the timeline having stopped and something
   else beginning.

   Nothing under it either, for the same reason on the other side: the 8px it
   carried there was not a seam anybody had chosen, and the journal's own air
   stacked on top of it. */
.add-extras { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }

/* Adding a step is a verb like every other verb here, so it is a word like
   every other word -- at the foot of the list, where the next step would go. It
   was a full-width dashed rectangle: the "add one more" affordance the bakes
   grid uses, which is right for a tile standing in for a photograph and wrong
   for a line standing in for a paragraph. It keeps the place and loses the
   box.

   And it stands where the step it makes will stand. As a plain word at the
   page's left margin it was the one line on this timeline at an x nothing else
   uses, carrying its own `+` while the rail beside it was already the place
   this sheet writes step numbers. So: the `+` on the hairline in the number's
   own type, the words on the title's column, and the rail carried down from
   the last step to end behind the mark.

   Scoped to the row it sits in, like the rail's arrows are scoped to the rail:
   `.text-btn` resets padding to 0 further down the file and would otherwise
   take the indent back on order alone -- which is exactly what happened to the
   6px this rule used to carry, a declaration that had never once applied. */
.add-extras .add-step { position: relative; display: block; width: 100%; padding: 0 0 0 32px; }
/* The rail itself: it starts where the last card's ended -- the row sits flush
   under it -- and runs to the middle of this row, where the `+` masks it the
   way every number masks it. An open end, not a line stopping in mid-air. */
.add-step::before {
  content: ''; position: absolute; left: 8px; width: 1px;
  top: 0; height: 50%; background: var(--line);
}
/* The mark itself wears `.phase-num` -- one presentation for every number on
   this rail, this one standing for the step that does not exist yet. Centred
   on the row rather than offset from it: the row is one line of type, so its
   middle *is* the word's line, and the number that replaces this `+` will be
   centred on its title the same way. No literal to go stale. */
.add-num { position: absolute; left: 8.5px; top: 50%; transform: translate(-50%, -50%); }
.card-remove { flex: none; align-self: baseline; }

/* A section's title and subtitle edit in place, wearing their read-mode type
   with the scrub family's dashed underline for "this is yours to change". */
.xcard-title {
  width: 100%; background: none; border: none; padding: 0 0 2px;
  border-bottom: 1px dashed var(--line3);
  font-family: inherit; font-weight: 600; font-size: var(--fs-title);
  letter-spacing: -.01em; color: var(--ink);
}
.xcard-title:focus { outline: none; border-bottom-color: var(--accent); }
.xcard-sub {
  width: 100%; background: none; border: none; padding: 3px 0 2px; margin-top: 3px;
  border-bottom: 1px dashed var(--line3);
  font-family: inherit; font-size: 12px; color: var(--muted);
}
.xcard-sub:focus { outline: none; border-bottom-color: var(--accent); }
.xcard .steps-input { margin-top: 4px; }

/* The ingredient editor: the read rows again, with the share on a lever and
   the weight typeable. Every row is a draw on the formula, so there is nothing
   here to name -- the two free-text cells retired with the typed row (v17). */
.xrows { display: flex; flex-direction: column; }
/* The editor's rows keep the read rows' rhythm -- same height, same right edge
   -- so pressing Edit changes what a row *offers* and not where it sits. A list
   that jumps when you start editing it loses your place. */
.xrow { display: flex; align-items: center; gap: 8px; padding: 8px 0; }
/* Taking a row out: the word for it, quiet, at the end of the row it acts on. */
.xrow-del { flex: none; }

/* A drawn row in the editor: the ingredient's name is the formula's, not
   something to retype, and the numeral beside it is the one control this sheet
   uses for every proportion. A remainder says so in words instead, because it
   reads and does not set -- the same arrangement the base flour's share has. */
.xrow-fixed { flex: 1 1 auto; min-width: 0; font-size: var(--fs-ui); }
/* The way out of a sheet: the ✕ every overlay here wears (ui/sheet.js holds
   the reasoning). Quiet on purpose -- the title is the head's loud element,
   and a close that outweighs the name of the place is hierarchy backwards. */
.sheet-x { flex: none; }

.xrow-share { flex: 0 0 56px; text-align: right; font-family: var(--mono); }
/* The same draw in grams, beside the share. Both are the row and both are
   yours: a share is what gets stored, a weight is what you have in your hand,
   and typing either moves the other. */
.xrow-grams {
  flex: 0 0 68px; text-align: right;
  background: none; border: none; border-bottom: 1px dashed var(--line3);
  padding: 0 0 2px; color: var(--ink);
  font-family: var(--mono); font-size: var(--fs-ui); font-weight: 500;
}
.xrow-grams:focus { outline: none; border-bottom-color: var(--accent); }
.xrow-gramsread { flex: 0 0 68px; text-align: right; font-family: var(--mono); color: var(--muted); }
/* "the rest" is two words and was folding onto two lines in a 64px column,
   which turned every remainder row into a double-height one. It is a label, so
   it does not wrap. */
.xrow-rest {
  flex: 0 0 56px; text-align: right; white-space: nowrap;
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted);
}
/* What a step makes, in the head with the title and the subtitle: three fields
   about what this step is. It wears the subtitle's type and the same dashed
   underline, and stops where they stop -- a field that ran the full width read
   as a rule across the card rather than as a line under a word. */
.xrow-yields { margin-top: 2px; }
.xcard .add-ing { margin-top: 8px; }

/* What does not add up: a count beside the heading, and the list behind a tap.
 *
 * Seven sentences above the steps was the sheet shouting -- these are things to
 * look at, not to re-read every time a recipe opens. The figure wears the
 * stamp's clothes, which is this page's existing way of saying "how many", and
 * accent is its one attention colour so no second red is invented. */
.sec-head-row {
  display: flex; align-items: center; gap: 10px;
  margin: var(--seam-sec) 0 var(--seam-head);
}
.sec-head-row .sec-head { margin: 0; }
/* The word and its figure, in the accent, beside the heading: press it and
   what does not add up opens. It was a bordered chip holding a bare number,
   which read as a badge stuck on the heading rather than as something to press,
   and needed a title attribute to say what it counted. Mono, because the figure
   in it is a figure. */
.warn-count { font-family: var(--mono); letter-spacing: var(--ls-mono); }
.warn-rows { display: grid; gap: 10px; }
.process-warning {
  margin: 0; font-size: var(--fs-body); line-height: var(--lh-note); color: var(--ink);
}

/* ↑ ↓ wear the foot-link dress; disabled means "already at that end". */
.foot-link:disabled { opacity: .35; text-decoration: none; }

/* The step's own numbers. No margin of its own in either direction: the ramp
   above sets the seam over it, and whatever follows sets its own.
   --dial-air is this block's version of --row-air: the white its own boxes
   carry around their ink, which the seam inside it subtracts. */
.bake-dials { display: flex; flex-direction: column; gap: 4px; margin: 0; --dial-air: 5px; }
.bake-dials .stat-row { margin-top: 0; }
/* The rate asking for a bake longer than this app can hold. Accent, because the
   card is stating a proportion it is not honouring and that is the one thing on
   it worth looking at; and only ever present when it is true, so an ordinary
   bake carries none of it. Its air is the box's own 4px gap. */
.bake-dials .bake-railed { color: var(--accent); }
/* The same voice the formula's pickers speak, for the same reason it gives:
   "one rule, so a second picker cannot arrive in a different size than the
   first." Its 44pt target is drawn rather than sized (the block at the foot of
   this file) -- a 44px box on a phone put 14px of white inside the step that no
   seam had asked for, which is what made the same two gaps measure 16 and 26 on
   a desktop and 21 and 33 on a phone. */
.bake-dials .word { position: relative; min-height: 0; padding: 2px 0; }
/* The word-picker is another line of the same block, so it keeps the block's
   own beat -- the one an ingredient row keeps, 2 x --row-air of ink -- less the
   white the cap above and the button below carry inside their boxes. At one
   --seam-head it measured 22px on screen, close enough to the 26 between two
   halves of a step to read as a new half starting.
   And it ends the block with exactly the --row-air a list ends with, so the
   seam under it is the same seam whether a step's content is rows or numbers:
   every half of a step carries the first 8px of the boundary below it. */
.bake-dials .words {
  margin-top: calc(2 * var(--row-air) - var(--dial-air));
  padding-bottom: calc(var(--row-air) - 2px);
}

.rows { display: flex; flex-direction: column; }
/* A card with no ingredient rows keeps one seam between its head and its
   disclosure, not the two an empty flex box would hold apart. */
.rows:empty { display: none; }
/* A name on the left, a weight on the right, and nothing drawn between the
   rows: the column of grams is its own alignment, and the hairlines under it
   were furniture. */
.row { display: flex; align-items: center; gap: 10px; padding: var(--row-air) 0; }
/* ...and under a finger or a pointer, the row takes colour so the name and
   its weight are one thing.
 *
 * "tapping / hovering a row should highlight it so that you can see the grams
 * with your eyes." Between a name on the left and a figure on the right there
 * is an inch of white to cross, and a hairline across it was already vetoed --
 * "I don't like any kind of horizontal lines in the recipe". A band is the
 * same guide without the line: it lights the whole row at once, so the eye
 * carries the name to the gram figure instead of tracking across paper.
 *
 * Drawn in the row's own air, and reaching a thumb's width past the column on
 * both sides, so lighting one moves nothing: the padding is already there
 * (--row-air) and the negative margin gives the band its bleed back.
 * `--line2` is this sheet's lightest existing wash -- no new gray (DESIGN 7).
 */
.rows .row { margin: 0 -10px; padding: var(--row-air) 10px; border-radius: 6px; cursor: pointer; }
/* Two strengths, for two different things being said. A tap means "this is the
   one I am on" and it holds while you pour, so it takes the accent wash -- the
   accent is the part you are holding, everywhere else on this sheet. Hover only
   means the pointer is passing over, so it is the quietest wash there is: a
   mouse crossing the list must not fire six accents on its way down. */
.rows .row.is-lit { background: var(--accent4); }
/* Behind a query so a touch browser's emulated hover cannot leave a row lit
   that nobody is touching -- there the tap is the whole gesture. */
@media (hover: hover) {
  .rows .row:hover { background: var(--line2); }
  .rows .row.is-lit:hover { background: var(--accent4); }
}
.rows .row-name { flex: 1 1 auto; font-size: var(--fs-ui); font-weight: 400; color: var(--text3); }
.rows .row-note { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: var(--ls-mono); color: var(--muted2); }
.rows .row-amt { font-family: var(--mono); font-weight: 500; font-size: var(--fs-ui); }
.row.accent .row-name, .row.accent .row-amt { color: var(--accent); font-weight: 500; }

/* When the preferment's own yeast meets or exceeds the recipe's total, the
   final mix gets none — say so rather than leaving a bare 0g. */
.all-yeast-note {
  font-size: 11.5px; font-style: italic; color: var(--muted); line-height: 1.5;
  padding-left: 13px; border-left: 2px solid var(--accent4);
}

/* The disclosure row: it sits exactly where the steps unfold, names itself,
   and counts what is behind it. */
/* No rule above the instructions.
 *
 * The ingredient rows are already a ruled list, and a second hairline a few
 * pixels under the last one read as a divider fighting the rows it sat on --
 * the same collision the `What this makes` field made before it moved into the
 * head. Air separates these two, the way it separates every other pair of
 * things on this page; the caret and the mono kicker are enough to say a new
 * thing has started. */
/* Its box *is* its seams: the air over the heading and the air under it are
   the control, so the thumb target and the rhythm are the same rectangle.
 *
 * It used to be a 44px min-height with 10px of padding, which is 30px of white
 * belonging to no seam -- and that is what held the two halves of a step apart
 * at 33px above the heading and 26px below it whatever the margins said. Now
 * the padding is the ramp's own arithmetic (--seam-entry above, --seam-head
 * below, each less the air the list on that side already carries), the ink
 * lands where the ramp says, and the target is the whole line, full width, 34pt
 * tall -- as tall as the rhythm has room for. It stops short of 44 on purpose:
 * the alternative is a hit area reaching over the last ingredient row and the
 * first sentence, and a control that steals its neighbours' taps is worse than
 * one you have to aim at. Both of those neighbours are a tap you can take back
 * anyway. */
.steps-bar {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: none;
  padding: calc(var(--seam-entry) - var(--row-air)) 0 calc(var(--seam-head) - var(--row-air));
  font-family: var(--mono); color: var(--muted); cursor: pointer;
  text-align: left;
}
.steps-bar:hover { color: var(--accent); }
.steps-caret {
  width: 14px; text-align: center; font-size: 12px; line-height: var(--lh-solid);
  transition: transform .18s ease;
}
.steps-bar.is-open .steps-caret { transform: rotate(90deg); }
.steps-bar.is-open { color: var(--text2); }
.steps-bar-label {
  font-size: var(--fs-micro); font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
}
.steps-bar-count { font-size: var(--fs-micro); color: var(--muted2); }
@media (prefers-reduced-motion: reduce) { .steps-caret { transition: none; } }

.steps { margin-top: var(--seam-head); }
.steps:empty { margin-top: 0; }
/* A step is a button you can tick off with a floury knuckle: tap to strike it
   through, tap to bring it back. Ephemeral by design -- see views/process.js.
 *
 * Its padding is an ingredient row's, less the leading a line of prose carries
 * over its own glyphs, so a sentence and a weighed row leave exactly the same
 * white between them: one beat for both of a step's lists. */
.step {
  display: flex; gap: 14px; align-items: flex-start;
  padding: calc(var(--row-air) - var(--prose-lead)) 0;
  width: 100%; background: none; border: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.step.done { opacity: .45; }
.step.done .step-text { text-decoration: line-through; }
.step-num { font-family: var(--mono); font-weight: 400; font-size: var(--fs-small); color: var(--accent2); min-width: 16px; margin-top: 3px; }
.step-text { font-size: var(--fs-prose); font-weight: 400; line-height: var(--lh-prose); color: var(--text2); }
.step-note {
  font-size: 12px; font-style: italic; color: var(--muted); line-height: var(--lh-note);
  margin: var(--seam-head) 0; padding-left: 15px; border-left: 2px solid var(--accent4);
}

.steps-input {
  width: 100%; background: none; border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; color: var(--text2); font-size: var(--fs-prose); line-height: 1.7;
  resize: vertical; min-height: 96px;
}
.steps-input:focus { border-color: var(--accent); outline: none; color: var(--ink); }
.steps-input.is-custom { border-left: 2px solid var(--accent); }
/* The line that will not survive being stored, named under the field it is
   being typed into. Accent, because it is the one thing on this card asking to
   be acted on before Save — and it only ever appears when there is such a line,
   which on a normal instruction is never. It rides the field's own 8px, so a
   step gaining it grows by a line of type and nothing above it moves. */
.steps-over { margin-top: 8px; color: var(--accent); }
/* Paper is not going to save anything. */
@media print { .steps-over { display: none !important; } }

/* ---- the shelf -------------------------------------------------------- */
/* One element, two placements, no sheet. At 900+ it is the rail column it
   always was. Under 900 it exists only on Home, where it renders as the
   page's own body -- the tab bar took over navigation, so nothing slides up
   from the bottom any more. */
.panel { min-width: 0; }
.panel:focus-visible { outline: none; }
.panel-inner { display: flex; flex-direction: column; gap: 18px; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.sheet-handle { display: none; }
.panel-close { display: none; }

/* The rail has no phone width, so the shelf shows only where it IS the page:
   your own home while you have no page of your own (the root, with the front
   page under it), and your wall in compact. */
@media (max-width: 899px) {
  .panel { display: none; }
  body.on-home .panel { display: block; padding-top: var(--seam-page); }
  /* On the reading measure, like the column it stands in for. A phone is
     narrower than the measure so the cap does nothing there; a tablet is not,
     and a wall-to-wall shelf over a centred spread read as two pages.

     `width: 100%` is not decoration, it is the whole rule working: auto
     margins on a grid item drop it out of `stretch` into shrink-to-fit, so
     without this the shelf sizes to its longest recipe name — 351px inside a
     343px column on a phone, which is a page that scrolls sideways by
     however much the name overhangs. Same three declarations as `.doc`,
     because it is the same job. */
  body.on-home .panel, body.wall-compact .panel {
    width: 100%; max-width: var(--doc-max); margin-left: auto; margin-right: auto;
  }
}
/* Compact: the owner's wall as rows. The panel -- the row list, drag grips
   and all -- docks under the wall head, and the poster grid stands down.
   Phone only; the rail column already is the rows. */
.wall-compact-toggle { align-self: flex-start; }
@media (min-width: 900px) { .wall-compact-toggle { display: none; } }
@media (max-width: 899px) {
  body.wall-compact .doc { order: 1; }
  body.wall-compact .panel { display: block; order: 2; padding-top: var(--seam-card); }
  body.wall-compact .panel-head { display: none; }
  body.wall-compact .wall-grid, body.wall-compact .wall > .hint,
  body.wall-compact .kitchen { display: none; }
  /* Three seams were stacking in the dark under the head -- the head's
     grid margin, the wall's own padding, the layout gap -- ~76px of air
     over a hidden grid. One seam, from the scale. */
  body.wall-compact .layout { gap: 0; }
  body.wall-compact .wall { padding-bottom: 0; }
  body.wall-compact .wall-head { margin-bottom: 0; }
}

.icon-btn {
  background: none; border: none; color: var(--faint);
  min-width: 34px; min-height: 34px; font-size: 19px; line-height: var(--lh-solid); padding: 0;
}
.icon-btn:hover { color: var(--accent); }

/* ---- recipe cards ----------------------------------------------------- */
.rows-list { display: flex; flex-direction: column; gap: 8px; }
.rows-list.is-reordering { user-select: none; }
.recipe-row {
  background: var(--panel); border: 1px solid var(--line2); border-radius: 12px;
  padding: 2px 8px 2px 2px;
}
@media (max-width: 899px) { .recipe-row { border-color: var(--line); } }
.recipe-row.active { border-color: var(--accent2); }
.recipe-row.dragging {
  position: relative; z-index: 2;
  box-shadow: 0 6px 20px var(--shadow);
}
.row-top { display: flex; align-items: center; gap: 2px; }
.row-grip {
  background: none; border: none; color: var(--faint); font-size: 13px;
  padding: 0 6px; min-width: 26px; min-height: 44px; cursor: grab;
  touch-action: none; line-height: var(--lh-solid);
}
.row-grip:active { cursor: grabbing; }
.row-main {
  flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px;
  align-items: flex-start; text-align: left;
  background: none; border: none; padding: 9px 4px; min-height: 44px;
}
.row-main[disabled] { opacity: .55; cursor: default; }
.recipe-row .row-name {
  font-size: 14px; font-weight: 500; color: var(--ink);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recipe-row.active .row-name { color: var(--accent); }
.recipe-row .row-sub {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .01em; color: var(--muted);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recipe-row.broken .row-sub { color: var(--accent); }
/* The formula fingerprint: hydration, salt, oil, yeast as four hairline bars.
   Two recipes with the same name stop looking like the same recipe. */
/* ------------------------------------------------------------------ wall
 * A page as a spread of bakes. Poster scale on purpose: the tally marks and
 * 34px thumbs were correct and invisible, and photographs are the point of
 * the page, so they get the page.
 */
/* No top padding of its own: the doc column already carries 26px, and the
   two stacked reads as a hole under the masthead -- Kilian spotted it on
   the phone the day the wall became a front page. */
.wall { padding: 0 0 var(--seam-card); }
.wall-head { display: flex; flex-direction: column; gap: 8px; margin-bottom: var(--seam-page); }
.wall-stats { margin: 0; font-family: var(--mono); font-size: var(--fs-small); color: var(--muted); }
/* The address is one control -- drawn icon, then the URL -- so the line sits
   on the head's own rhythm instead of inside a 44px box that held 12px of
   air over it and pushed it out of the column. The thumb target is drawn
   outward by ::after rather than sized on the box, which is the one case
   where a target may leave the flow: the ink here has to stay on the
   baseline the seams measure from. */
.wall-sharerow { display: flex; align-items: center; }
.wall-sharerow .share-url {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  margin-top: 0; align-self: flex-start;
}
.share-url-icon { display: inline-flex; align-items: center; color: var(--muted); }
.share-url-icon svg { display: block; width: 15px; height: 15px; }
.share-url:hover .share-url-icon, .share-url.accent .share-url-icon { color: var(--accent); }
.share-url-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Stats on the left, the compact toggle on the right of the same line. */
.wall-statsrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.wall-acts { margin-top: 6px; }

/* ---- the kitchen: Home's own column ----------------------------------- */
.kitchen { display: flex; flex-direction: column; gap: 8px; }
.friends { margin-top: var(--seam-head); }
.friends-strip {
  --strip-foot: 6px;
  display: flex; gap: 10px; overflow-x: auto; padding: 12px 0 var(--strip-foot);
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
/* On Discover the strip is the last thing in the block of people, so its own
   foot is the first 6px of the boundary below it rather than extra air on top of
   one: Recipes opened 52px under the handles where Who's baking and Bakes both
   opened at 46. Same arithmetic as `.formula.ends-in-row`, one component along. */
.discover .friends-strip { margin-bottom: calc(-1 * var(--strip-foot)); }
.friends-strip::-webkit-scrollbar { display: none; }
.friends-item { flex: none; width: 104px; text-decoration: none; color: inherit; }
.friends-photo {
  display: block; width: 104px; height: 104px; border-radius: 10px;
  overflow: hidden; background: var(--track);
}
.friends-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.friends-cap {
  display: block; margin-top: 6px; font-family: var(--mono);
  font-size: var(--fs-micro); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.guest-landing { display: flex; flex-direction: column; gap: 16px; align-items: flex-start; padding: 10px 0 20px; }
.guest-pitch { font-size: var(--fs-prose); max-width: 48ch; }
.guest-landing .pill { text-decoration: none; min-height: 44px; padding: 11px 20px; display: inline-flex; align-items: center; }

/* The reader's card after a borrowed recipe: the honest account pitch. */
.guest-card { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.guest-card .google-btn { text-decoration: none; }
.guest-kick { color: var(--accent); }
.wall-title {
  font-size: clamp(28px, 6vw, 44px); font-weight: 600;
  letter-spacing: var(--ls-tight); line-height: 1.05;
  /* Leading this tight on a display face does not contain the face: the
     descenders of "What people are baking" paint *below* the line box. Every
     seam under this title is then declared box-to-box and read ink-to-box, so
     it measures short by the overhang — and how far a descender overhangs is
     the renderer's opinion, which is how the same stylesheet read 44px here
     and 42px on CI against a 46px seam. Room for the ink, in em so it holds at
     every step of the clamp; the leading between wrapped lines is untouched. */
  padding-bottom: .07em;
}
/* align-items: start, or the grid quietly re-inflates the collapsed cards --
   a row's default is stretch, so a caption-only card sharing a row with a
   4:3 photograph was pulled back up to photograph height, empty again.
   Caught on the iPad pass, second round. */
.wall-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; align-items: start; }
@media (max-width: 559px) { .wall-grid { grid-template-columns: 1fr; } }
/* The card is a poster with, for its owner, a shelf row's verbs underneath.
   The root holds nothing visual; .wall-open is the poster face (it must be
   its own button -- a button cannot contain the ⋯ button). */
.wall-card { position: relative; width: 100%; }
.wall-open {
  position: relative; display: block; padding: 0; text-align: left;
  border: none; border-radius: 8px; overflow: hidden;
  background: var(--line2); cursor: pointer; color: inherit;
  aspect-ratio: 4 / 3; width: 100%;
}
.wall-card.broken .wall-open { cursor: default; opacity: .6; }
.wall-more {
  position: absolute; top: 6px; left: 6px; z-index: 1;
  min-width: 34px; min-height: 34px; padding: 0 8px;
  border: none; border-radius: 8px; background: none;
  color: var(--faint); font-size: 19px; line-height: 1;
}
.wall-card.has-photo .wall-more { background: rgba(0, 0, 0, .35); color: #fff; }
.wall-card-acts { margin-top: 8px; }
.wall-photo { position: absolute; inset: 0; }
.wall-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The caption sits on the photograph, so it brings its own contrast. On a
   photoless card it sits on the panel and reads as a typographic cover. */
.wall-caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; flex-direction: column; gap: 3px;
  padding: 26px 14px 12px;
}
.wall-card.has-photo .wall-caption {
  background: linear-gradient(transparent, rgba(0, 0, 0, .62));
  color: #fff;
}
.wall-name { font-weight: 600; font-size: var(--fs-title); letter-spacing: -.01em; }
.wall-sub { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: var(--ls-mono); opacity: .85; }
.wall-card:not(.has-photo) .wall-caption { position: static; padding: 14px; margin-top: auto; }
/* No photograph, no poster: a bare recipe collapses to its caption. Found on
   the iPad QA pass -- three photoless 4:3 boxes made the page read half
   empty, and the whole point of the wall is that the photos are the big
   thing. An unbaked recipe is a line, an invitation to be the next card. */
.wall-card:not(.has-photo) .wall-open { display: flex; flex-direction: column; aspect-ratio: auto; min-height: 84px; }
.wall-card:not(.has-photo) .wall-name { color: var(--ink); }
.wall-card:not(.has-photo) .wall-sub { color: var(--muted); }

/* Discover reuses the wall's poster cards; its face is a link (the ordinary
   arriving-by-link path), not a button, so the anchor dress comes off. */
.discover .wall-open { text-decoration: none; }
/* A baker card's second caption line: the address under the name. */
.discover .disc-handle { margin-top: 1px; color: var(--faint); }

/* --------------------------------------------------------------- admin
 * The back room (views/admin.js): the whole kitchen's numbers, in the app's
 * own dress. Figures are mono, columns grow from a hairline baseline with the
 * chart marks' rules -- thin, 4px-rounded data ends, a 2px gap of page between
 * neighbors -- and the ranked lists ride the formula row's own track idiom.
 */
.admin-controls { display: flex; align-items: center; gap: 16px; }
.admin-controls .foot-link:first-of-type { margin-left: auto; }
.admin-ranges .lang-btn { min-width: 44px; }
.admin-num {
  font-family: var(--mono); font-weight: 500; font-size: 27px;
  line-height: 1; color: var(--ink); letter-spacing: var(--ls-tight);
}
.admin-stats { margin-top: var(--seam-head); }
.admin .sec-head { margin-bottom: var(--seam-head); }
.admin-chart { display: flex; flex-direction: column; gap: 8px; }
.admin-readout { margin: 0; font-family: var(--mono); font-size: var(--fs-small); color: var(--muted); }
.admin-readout.accent { color: var(--accent); }
/* The plot is one keyboard stop: arrows walk the columns, the readout above
   speaks the one under the pointer or the caret. touch-action keeps a finger
   scrub horizontal while the page still scrolls. */
.admin-plot { touch-action: pan-y; }
.admin-cols {
  display: flex; align-items: flex-end; gap: 2px;
  height: 120px; border-bottom: 1px solid var(--line);
}
.admin-col {
  flex: 1 1 0; min-width: 0; height: 100%; position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  border-radius: 3px 3px 0 0;
}
.admin-col.hot { background: var(--line2); }
.admin-peak {
  font-family: var(--mono); font-size: var(--fs-micro); line-height: 1;
  color: var(--muted2); margin-bottom: 4px; white-space: nowrap;
}
.admin-fill {
  width: 100%; max-width: 22px; height: 0;
  background: var(--accent); border-radius: 4px 4px 0 0;
  transition: height .28s ease;
}
.admin-axis {
  display: flex; justify-content: space-between;
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted2);
}
.admin-list { display: flex; flex-direction: column; gap: var(--seam-entry); }
/* The views breakdown rides under its own chart; the axis needs air first. */
.admin-kinds { margin-top: var(--seam-head); }
.admin-row {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: 6px 12px; text-decoration: none; color: inherit;
}
.admin-row-name {
  min-width: 0; font-size: var(--fs-ui); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.admin-row:hover .admin-row-main { color: var(--accent); }
.admin-row-by {
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted);
  margin-left: 8px;
}
.admin-row-by.accent { color: var(--accent); }
.admin-lang-chip {
  font-family: var(--mono); font-size: var(--fs-small); color: var(--muted2);
  margin-right: 10px;
}
.admin-row-count { font-family: var(--mono); font-size: var(--fs-small); color: var(--muted); }
/* The formula row's track, on loan: the rule under a ranked row IS its bar. */
.admin-track {
  grid-column: 1 / -1; height: 2px; background: var(--track);
  border-radius: 999px; overflow: hidden;
}
.admin-track-fill {
  display: block; height: 100%; width: 0;
  background: var(--accent); border-radius: 999px;
  transition: width .28s ease;
}
.admin-facts { display: flex; flex-direction: column; gap: 12px; max-width: 300px; }
.admin-fact { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.admin-fact-val {
  font-family: var(--mono); font-size: var(--fs-small); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.admin-fact-val.bad { color: var(--accent); }
/* Waiting in its own shape: a stat line, then two chart-sized veils. */
.admin-veils { display: flex; flex-direction: column; gap: 14px; margin-top: var(--seam-card); }
.admin-veils .veil-kick { margin-top: var(--seam-sec); }
.veil-chart { height: 156px; border-radius: 8px; }
@media (prefers-reduced-motion: reduce) {
  .admin-fill, .admin-track-fill { transition: none; }
}
@media print {
  .admin-controls { display: none !important; }
}

/* ----------------------------------------------------------------- stamp
 * The bake count as a rubber stamp: inked, tilted, unmissable. This is the
 * "something fun" the number kept not being.
 */
.stamp {
  display: inline-flex; align-items: baseline; gap: 1px;
  padding: 3px 8px; border: 2px solid var(--accent); border-radius: 5px;
  color: var(--accent); background: var(--bg);
  font-family: var(--mono); font-weight: 500; letter-spacing: .04em;
  transform: rotate(-6deg); line-height: 1;
}
.stamp-x { font-size: 11px; }
.stamp-n { font-size: 15px; font-weight: 600; }
.wall-card .stamp { position: absolute; top: 10px; right: 10px; z-index: 1; }
.stamp-head { margin-left: 6px; transform: rotate(-6deg) translateY(-1px); }
@keyframes stampdown {
  0% { transform: rotate(-18deg) scale(2.4); opacity: 0; }
  60% { transform: rotate(-4deg) scale(.92); opacity: 1; }
  100% { transform: rotate(-6deg) scale(1); }
}
.stamp-in { animation: stampdown .45s cubic-bezier(.2, .9, .3, 1.4); }
@media (prefers-reduced-motion: reduce) { .stamp-in { animation: none; } }


/* The recipe's face on the shelf: its newest bake, 34px of proof. */
.row-thumb {
  flex: none; width: 34px; height: 34px; border-radius: 3px;
  overflow: hidden; background: var(--line2);
}
.row-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The version, measured against the present. Mono, because it is numbers
   talking about numbers -- and quiet, because the photo is the headline. */
.bake-diff {
  margin: 0; font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--muted);
}

/* The evidence, before the numbers.
   A single quiet row inside the title block: small enough not to compete with
   the dough total, big enough to answer "how does this come out?". Scrolls
   sideways rather than wrapping, because a second row would start to be a
   gallery and the gallery is the journal. No margin of its own -- the block's
   gap owns the space, so the header can never grow a seam between its parts. */
/* Its own opener, on the page's own rhythm: the seam above comes from the
   section head, the strip sits the same 10px under its heading as every
   other section's content does. */
.bakes-opener { display: flex; flex-direction: column; }
.bakes-head {
  background: none; border: none; padding: 0; text-align: left;
  color: var(--muted2); cursor: pointer;
}
.bakes-head:hover { color: var(--accent); }
.bake-strip {
  display: flex; gap: 8px; margin: 0;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.bake-strip::-webkit-scrollbar { display: none; }
/* A filmstrip, not a contact sheet: each frame at its photograph's own
   aspect (the JS sets the width from the stored w/h), rounded like the
   friends strip, tall enough to actually see the crumb. */
.bake-strip-thumb {
  flex: none; width: 128px; height: 128px; padding: 0;
  border: none; border-radius: 10px; overflow: hidden;
  background: var(--line2); cursor: zoom-in; scroll-snap-align: start;
}
.bake-strip-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The last tile is the next photo: a dashed invitation where it would land. */
.bake-strip-add {
  flex: none; height: 128px; padding: 0 18px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 1.5px dashed var(--accent); border-radius: 10px;
  background: none; color: var(--accent); cursor: pointer; scroll-snap-align: start;
}
.strip-add-plus { font-size: 17px; line-height: 1; }
.strip-add-label { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: var(--ls-mono); white-space: nowrap; }
@media print { .bake-strip, .act-menu, .title-acts { display: none; } }

.fprint {
  display: flex; align-items: flex-end; gap: 2px;
  width: 24px; height: 20px; flex: none; margin: 0 4px;
}
.fprint i { flex: 1 1 0; background: var(--accent3); border-radius: 1px; min-height: 2px; }
.recipe-row.active .fprint i { background: var(--accent2); }
.row-rename {
  flex: 1 1 auto; min-width: 0; background: none; border: none;
  border-bottom: 1px solid var(--accent); padding: 10px 0; color: var(--ink);
  font-size: 14px; font-weight: 500;
}
.row-more {
  background: none; border: none; color: var(--faint); font-size: 16px;
  min-width: 34px; min-height: 44px; padding: 0; line-height: var(--lh-solid);
}
.row-actions {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 4px 12px 26px;
}
.row-act {
  background: none; border: 1px solid var(--line3); border-radius: 999px;
  padding: 6px 11px; font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--muted); min-height: 32px;
}
.row-act.danger { color: var(--accent); border-color: var(--accent3); }
/* Read-only rows lose the grip and the dots, and those controls were the
   card's real horizontal padding -- without them the text sat 7px from a
   12px-radius border. The quiet card carries its own inset instead. */
.rows-list.readonly .recipe-row {
  background: none; border-color: var(--line2);
  padding: 2px 8px;
}

.local-banner {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px 13px; border-left: 2px solid var(--accent); background: var(--line2);
  border-radius: 0 6px 6px 0;
}

.offline-note {
  margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--muted);
  padding-left: 11px; border-left: 2px solid var(--accent3);
}
.filter-input {
  width: 100%; background: none; border: none; border-bottom: 1px solid var(--line);
  padding: 8px 0; color: var(--ink); font-family: var(--mono); font-size: 12px;
}
.group { display: flex; flex-direction: column; gap: 6px; margin-top: var(--seam-head); }
/* A person's line: their name, their handle, and the way to stop following
   them at the far end -- not wedged against the handle where it read as
   punctuation. */
.group-head { display: flex; align-items: center; gap: 10px; }
.group-person {
  display: flex; align-items: baseline; gap: 8px; min-width: 0;
  text-decoration: none; color: inherit;
}
.group-person:hover .profile-name { color: var(--accent); }
.group-person .profile-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-unfollow { margin-left: auto; flex: none; }
.group-handle {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: var(--ls-mono);
  color: var(--muted2); text-decoration: none;
}
.group-handle:hover { color: var(--accent); }

/* ---- panel foot ------------------------------------------------------- */
.panel-foot { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.chips { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.pill {
  background: none; border: 1px solid var(--line3); border-radius: 999px;
  padding: 8px 14px; font-family: var(--mono); font-size: var(--fs-small);
  letter-spacing: var(--ls-mono); line-height: 1.3; color: var(--muted); min-height: 36px;
}
.pill.dashed { border-style: dashed; }
.pill.accent { border-color: var(--accent); color: var(--accent); }
.pill.danger { border-color: var(--accent3); color: var(--accent); }
.pill.block {
  width: 100%; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- text buttons: the recipe's one verb voice ------------------------- */
/* Kilian: "I don't like the ill buttons remve them all use text buttons iOS
   style." So on the recipe -- the headline, the formula, the process, the
   journal and the sheets they open -- a verb is a word in the accent, and
   nothing else: no capsule, no border, no glyph standing in for a word.
   That is what a button is on a phone, and it is also the honest shape here,
   because this page is built out of type and white space and a bordered
   capsule is furniture from another application (the same verdict the split
   bar's first draft got).

   Two things it must not do. It must not decide the height of a line of type,
   so the thumb's 44pt is *drawn* outward with ::after rather than sized on the
   box -- the same trick the wall's address and the flour words already use.
   And it must not wrap: a verb broken across two lines reads as two verbs. */
.text-btn {
  position: relative; background: none; border: none; padding: 0;
  font-family: inherit; font-size: var(--fs-ui); font-weight: 400;
  line-height: 1.25; letter-spacing: 0; white-space: nowrap;
  color: var(--accent); cursor: pointer; text-align: left;
}
/* The one that commits carries the weight, the way the tick used to carry the
   accent while the arrow beside it stayed grey. */
.text-btn.strong { font-weight: 600; }
/* A verb that is not the point of the line: still a word, in the ink small
   words wear, going accent under the finger. */
.text-btn.quiet { color: var(--muted2); }
.text-btn.quiet:hover:not(:disabled) { color: var(--accent); }
.text-btn:hover:not(:disabled) { opacity: .72; }
.text-btn:disabled { color: var(--faint); cursor: default; opacity: 1; }
/* Small print for verbs that ride a step's title line, where the title is the
   thing the eye wants. */
.text-btn.small { font-size: var(--fs-small); }
.text-acts { display: inline-flex; align-items: baseline; gap: 14px; flex: none; }
.field-input {
  flex: 1 1 auto; min-width: 0; background: none; border: none;
  border-bottom: 1px solid var(--accent); padding: 9px 0; color: var(--ink);
  font-family: var(--mono); font-size: var(--fs-body); letter-spacing: var(--ls-mono);
}
.google-btn {
  display: inline-flex; align-items: center; gap: 8px; background: none;
  border: 1px solid var(--line3); border-radius: 999px; padding: 9px 15px;
  font-family: var(--mono); font-size: 12px; letter-spacing: var(--ls-mono);
  color: var(--ink); min-height: 40px;
}
.profile-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
/* The URL is the button: mono, accent, dashed underline -- the app's own
   "tap me" language -- and it flips to Copied! for a moment when tapped.
   The dash sits under the address, not under the icon riding with it. */
.share-url {
  align-self: flex-start; padding: 2px 0;
  background: none; border: none;
  font-family: var(--mono); font-size: var(--fs-small); letter-spacing: var(--ls-mono);
  color: var(--accent); max-width: 100%;
}
.share-url .share-url-text { border-bottom: 1px dashed var(--accent2); }
.profile-name { font-weight: 600; font-size: var(--fs-emphasis); letter-spacing: -.01em; }
.profile-handle { font-family: var(--mono); font-size: var(--fs-small); color: var(--accent); }
.at-sign { font-family: var(--mono); font-size: 12px; color: var(--muted2); }
.auth-foot {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted2);
}
.auth-foot .email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 170px; }
.foot-link {
  background: none; border: none; padding: 2px 0; font-family: var(--mono);
  font-size: var(--fs-micro); letter-spacing: var(--ls-mono); color: var(--muted2);
  text-decoration: underline; text-underline-offset: 2px;
}
.foot-link:hover { color: var(--accent); }
.foot-link.accent { color: var(--accent); }
.auth-error { margin: 0; font-family: var(--mono); font-size: var(--fs-micro); color: var(--accent); }
.auth-wait { font-family: var(--mono); font-size: var(--fs-small); color: var(--faint); }

/* ---- settings --------------------------------------------------------- */
.settings { max-width: 520px; padding-top: var(--seam-page); display: flex; flex-direction: column; gap: 22px; }
.settings-title { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: var(--ls-tight); }
.settings-body { display: flex; flex-direction: column; gap: 26px; }

/* ---- You: who you are, then your preferences -------------------------- */
.you-pitch { max-width: 46ch; }
.you-prefs { display: flex; flex-direction: column; gap: 14px; }
.you-pref-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.you-langs .lang-btn { min-width: 34px; }
.field { display: flex; flex-direction: column; gap: 8px; }
.field-label { font-size: var(--fs-prose); font-weight: 500; }
.field-row { display: flex; align-items: center; gap: 8px; }

/* ---- the tab bar: a floating glass dock -------------------------------- */
/* The phone's own current grammar, not the web's: a capsule inset from the
   edges, hovering above the home indicator, content scrolling underneath a
   frosted pane. The capsule holds the places -- auto columns rather than a
   count, because Discover retires when the cloud is off -- and the "+"
   stands outside it as a raised accent circle: navigation and the verb,
   separated by geometry the way iOS separates them.
   --dock-edge is the glass's top catchlight: strong where light hits white
   glass, almost gone on the dark theme's smoked pane. */
.tabbar {
  position: fixed; z-index: 45;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tab-inset) + env(safe-area-inset-bottom));
  width: min(100% - 28px, 392px);
  display: flex; align-items: center; gap: 10px;
  --dock-edge: rgba(255, 255, 255, .55);
}
:root[data-theme=dark] .tabbar { --dock-edge: rgba(255, 255, 255, .07); }
.tab-dock {
  flex: 1; min-width: 0;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  height: var(--tab-h); padding: 5px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--line3) 65%, transparent);
  box-shadow:
    0 12px 32px var(--shadow),
    0 2px 8px color-mix(in srgb, var(--shadow) 55%, transparent),
    inset 0 1px 0 var(--dock-edge);
}
/* Real glass where the browser can grind it; the solid panel above is the
   fallback pane. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tab-dock {
    background: color-mix(in srgb, var(--panel) 72%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
    backdrop-filter: blur(18px) saturate(1.6);
  }
}
.tab {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; background: none; border: none; padding: 0;
  border-radius: 999px;
  color: var(--muted); text-decoration: none;
  transition: background-color .22s ease, color .22s ease;
}
.tab-ic { display: inline-flex; align-items: center; justify-content: center; height: 22px; }
.tab-ic svg { display: block; transition: transform .18s cubic-bezier(.34, 1.56, .64, 1); }
.tab:active .tab-ic svg { transform: scale(.86); }
.tab-label {
  font-size: 10px; font-weight: 600; letter-spacing: .01em;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The lozenge: where you are, lit softly in the dough's own warmth.
   accent3, not accent4 -- on the dark theme's smoked glass the paler tint
   disappears entirely, and a you-are-here light that needs squinting is a
   light that isn't on. */
.tab.active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent3) 75%, transparent);
}
/* The verb, raised: an accent circle the height of the capsule, glazed by a
   top light and floating on its own color's shadow. */
.tab-new {
  flex: none; width: var(--tab-h); height: var(--tab-h);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: 50%;
  color: var(--bg);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 84%, #fff) 0%, var(--accent) 62%);
  box-shadow:
    0 10px 24px color-mix(in srgb, var(--accent) 38%, transparent),
    0 2px 6px color-mix(in srgb, var(--accent) 30%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, .38);
  transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), box-shadow .22s ease;
}
.tab-new-ic { display: inline-flex; }
.tab-new-ic svg { display: block; }
.tab-new:active { transform: scale(.92); }
/* On the sheet the verb made, it glows a halo instead of pretending to be
   a place -- the lozenge stays for destinations. */
.tab-new.active {
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent),
    0 10px 24px color-mix(in srgb, var(--accent) 38%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, .38);
}
/* Open, the glyph is the way back out: 45° turns the plus into a ×, which is
   the one rotation every phone has already taught everybody. */
.tab-new .tab-new-ic svg { transition: transform .26s cubic-bezier(.32, .72, 0, 1); }
.tab-new.picking .tab-new-ic svg { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .tab, .tab-ic svg, .tab-new, .tab-new .tab-new-ic svg { transition: none; }
  .tab:active .tab-ic svg, .tab-new:active { transform: none; }
}

/* ---- the "+" picker: which dough, before the composer has to ask ------- */
/* A sheet that hugs the bottom edge and is sized by its contents. The first
   shape was a fan of chips bursting from the button -- right up to five doughs
   and a lie after that, because it showed five of your twenty and hid the rest
   silently. Sized to its contents, a sheet lands in the same place the chips
   did when the shelf is short, and scrolls and filters when it is long: one
   shape at both ends rather than a control that changes behaviour as the shelf
   grows.

   Rows read like shelf rows, because they are the same fact -- a name and the
   formula in one line. "New dough" is the foot, behind a hairline, still doing
   what the "+" has always done.

   The wash is paper, not theatre: the page fades toward --bg and blurs, so the
   sheet keeps its own hairline and the kicker stays a readable word in both
   themes. A dark scrim would have made every color on the panel a special
   case. */
.picker-root { position: fixed; inset: 0; z-index: 44; }
.picker-root:not(.is-open) { pointer-events: none; }
/* The trigger never goes into the wash -- it is also the way back out. On a
   phone the dock already sits above the sheet; at rail widths the masthead,
   which is where the "+" is, has to be lifted for the run of the menu. */
body.picker-open .brand-row { z-index: 46; }
.picker-scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--bg) 74%, transparent);
  opacity: 0; transition: opacity .2s ease;
}
.picker-root.is-open .picker-scrim { opacity: 1; }
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .picker-root.is-open .picker-scrim {
    -webkit-backdrop-filter: blur(6px) saturate(1.1);
    backdrop-filter: blur(6px) saturate(1.1);
  }
}
/* Above the dock, inset from the same edges, as wide as the capsule beside it
   -- so the sheet reads as belonging to the bar it rose out of. */
.picker-sheet {
  position: fixed; z-index: 1;
  left: 14px; right: 14px; margin: 0 auto;
  bottom: calc(var(--tab-h) + var(--tab-inset) + 12px + env(safe-area-inset-bottom));
  width: auto; max-width: 392px;
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line3); border-radius: 18px;
  padding: 14px;
  box-shadow:
    0 16px 40px var(--shadow),
    0 2px 8px color-mix(in srgb, var(--shadow) 55%, transparent);
  opacity: 0; transform: translateY(12px);
  transition: opacity .18s ease, transform .26s cubic-bezier(.32, .72, 0, 1);
}
.picker-root.is-open .picker-sheet { opacity: 1; transform: none; }
.picker-kick { padding: 0 4px 4px; }
.picker-filter { margin-top: 4px; }
/* The list is what scrolls, not the sheet: the kicker, the filter and the foot
   stay put while the doughs move under them. overscroll-behavior so reaching
   the end does not start scrolling the page behind the wash. */
.picker-list {
  display: flex; flex-direction: column;
  max-height: min(46vh, 420px); overflow-y: auto; overscroll-behavior: contain;
}
.picker-row {
  display: flex; flex-direction: column; gap: 2px;
  align-items: flex-start; text-align: left;
  background: none; border: none; border-radius: 10px;
  padding: 9px 4px; min-height: 44px; width: 100%;
}
.picker-row:hover { background: color-mix(in srgb, var(--line2) 55%, transparent); }
.picker-name {
  font-size: 14px; font-weight: 500; color: var(--ink);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.picker-sub {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .01em;
  color: var(--muted);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.picker-row:hover .picker-name { color: var(--accent); }
.picker-empty { padding: 9px 4px; }
/* The foot: the one verb, behind the one hairline that separates it from the
   nouns above it. */
.picker-new {
  flex-direction: row; align-items: center; gap: 8px;
  border-top: 1px solid var(--line2); border-radius: 0;
  margin-top: 6px; /* sanctioned-gap: a hairline's breathing room, not a seam */
  font-family: var(--mono); font-size: var(--fs-small); letter-spacing: var(--ls-mono);
  color: var(--muted);
}
.picker-new:hover { color: var(--accent); background: none; }
.picker-new-ic { display: inline-flex; }
.picker-new-ic svg { display: block; width: 15px; height: 15px; }
/* At rail widths there is no bottom edge to hug: the same list hangs off the
   masthead's New, measured against it by views/picker.js. */
@media (min-width: 900px) {
  .picker-sheet.picker-hung {
    left: auto; bottom: auto; margin: 0; width: min(320px, calc(100vw - 24px));
    transform: translateY(-12px);
  }
  .picker-root.is-open .picker-sheet.picker-hung { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .picker-scrim, .picker-sheet { transition: none; }
}

/* On a desktop the shelf is a column, the total lives in the document, and
   the masthead carries the same three destinations as links. */
@media (min-width: 900px) {
  .tabbar { display: none; }
  .page { padding-bottom: clamp(16px, 4vw, 40px); }
}

/* ---- the kitchen pass: arm's length, one thumb, flour ------------------ */
@media (max-width: 899px) {
  /* 16px inputs or iOS zooms the whole page on focus. */
  .filter-input, .field-input, .row-rename, .steps-input,
  .stepper-input, .hydration-input,
  .xcard-sub, .f-amt-in { font-size: 16px; }

  /* The gram amounts are read twenty times per mix, at a distance. */
  .rows .row-name, .rows .row-amt { font-size: var(--fs-emphasis); }
  .step-text { font-size: 14.5px; line-height: 1.65; }
  .card-sub { font-size: var(--fs-prose); }
  .f-name, .f-pct, .f-amt { font-size: var(--fs-emphasis); }
  .f-row { padding: 13px 0 14px; }
  .stat .scrub { font-size: 24px; }
  .total-val { font-size: 30px; }

  /* Thumb targets where fingers land. */
  .word { min-height: 44px; padding: 6px 4px; }
  .row-act { min-height: 40px; }
  .toast-action { padding: 10px 8px; }
  .pop-btn { min-width: 48px; min-height: 48px; }
}

/* Touch targets follow the finger, not the viewport: any coarse pointer --
   iPad landscape, a touch laptop -- gets 44pt, and a mouse-driven desktop
   never sees the menu restyle at some arbitrary width. */
@media (max-width: 899px), (pointer: coarse) {
  .foot-link { min-height: 44px; display: inline-flex; align-items: center; }
  /* A text button's thumb target is drawn, never sized: these sit on lines of
     type -- a recipe's byline, a step's title -- and a 44px box there would
     push the words it belongs to around. The reach is generous sideways
     because the words are short and the gap between two of them is 14px, so
     neighbouring targets meet rather than overlap. */
  /* -15, not -14: the small ones are 11px of ink on a 1.25 line, so the box
     is 13.75px and the reach has to make up the other 30. */
  .text-btn::after { content: ''; position: absolute; inset: -15px -7px; }
  .icon-btn, .row-more { min-width: 44px; min-height: 44px; }
  /* Targets drawn rather than sized. The wall's address is a line of type in
     a head measured by the seam scale, and growing its box to 44px is what
     left it hanging in white space; the header's menu button sits beside a
     34px pill and must not out-grow it. The hit area reaches out past the ink
     instead -- same thumb, same rhythm. */
  .share-url::after { content: ''; position: absolute; inset: -13px -8px; }
  /* The rail's arrows, drawn rather than sized -- but their box is 34px of mask
     around a 22px glyph, so it only reaches 5px to make 44. Reaching 11 the way
     a 22px control does would be 56pt, and the two arrows' targets would swallow
     the number between them and each other. Sideways it stops at 10 rather than
     11: the gutter is all there is to the left of them, and a hit area wider
     than that is a page you can scroll right. */
  .phase-move::after { content: ''; position: absolute; inset: -5px -10px; }
  /* The dashed "add here" pills stand alone with room around them, so they can
     simply be 44 rather than reaching for it -- + Sauce and + Section were 36
     the whole time, which nothing was measuring until + Ingredient joined them
     in the guard. */
  .pill.dashed, .ing-opt { min-height: 44px; }
  /* Same trick for the liquid words: they sit on a line of type inside the
     hydration row, and a 44px box there would make the app's signature row
     half again as tall. The thumb reaches out past the ink instead. */
  .f-liquid-words .word::after,
  .f-words .word::after,
  /* And the step's own picker -- the surface a bake sits on, the place a rest
     happens in. Same words, same line of type, same reach. */
  .bake-dials .word::after { content: ''; position: absolute; inset: -12px -7px; }
  /* And the same for the mark beside the hydration: a 15px picture on the app's
     signature row, reaching out to 44 rather than growing the row to it. */
  .f-info::after { content: ''; position: absolute; inset: -15px -9px; }
}

/* ---- print: paper is the most flour-proof display there is ------------- */
@media print {
  :root, :root[data-theme=dark] {
    --bg: #fff; --ink: #111; --text2: #333; --text3: #222;
    --muted: #555; --muted2: #666; --faint: #888;
    --accent: #7c3526; --accent2: #8a6b62; --accent3: #c9aaa2; --accent4: #d8c2bb;
    --line: #ddd; --line2: #e8e8e8; --line3: #bbb; --track: #ccc;
    --panel: #fff; --shadow: transparent;
  }
  .header-actions, .panel, .tabbar, .toasts, .picker-root,
  .origin-line, .steps-bar, .steps-input, .filter-input,
  .byline-row, .title-verbs, .guest-card,
  .add-extra, .card-remove, .xrows, .scrub-pop,
  /* Paper answers no questions: the mark that opens the hydration's working is
     a control, and the working itself lives in a sheet that is not on the page. */
  .f-info,
  /* Nor does paper have a saved version to differ from. The headline's dot
     stays -- one line at the top saying this sheet is not what is on the shelf
     is worth printing -- but forty of them scattered down a recipe you are
     about to cook from are not. */
  .f-dot, .stat-dot, .word-dot, .step-dot { display: none !important; }
  /* A lit row is where your eye is standing right now; paper has no use for it
     and no printer should spend ink on a band around one weight. */
  .rows .row.is-lit { background: none; }
  .page { max-width: none; padding: 0; }
  .layout { display: block; }
  .doc { max-width: none; }
  .scrub { border-bottom-color: transparent; }
  /* On paper the grams are grams, not a field. */
  .f-amt-in { border-bottom-color: transparent; color: #111; }
  .unit-words, .f-units-note { display: none !important; }
  .card, .step, .f-row { break-inside: avoid; }
  .step.done { opacity: 1; }
  .step.done .step-text { text-decoration: none; }
}

/* ---- toasts ----------------------------------------------------------- */
.toasts {
  position: fixed; z-index: 60; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tab-h) + var(--tab-inset) + 14px + env(safe-area-inset-bottom));
  width: min(440px, calc(100vw - 24px));
}
@media (min-width: 900px) { .toasts { bottom: 22px; } }
.toast {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 16px; border-radius: 10px;
  background: var(--ink); color: var(--bg);
  box-shadow: 0 6px 26px var(--shadow);
  animation: toast-in .2s ease;
}
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
.toast-text { font-size: var(--fs-prose); line-height: 1.4; min-width: 0; }
.toast-action {
  background: none; border: none; padding: 4px 2px; flex: none;
  font-family: var(--mono); font-size: var(--fs-small); letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent2); font-weight: 500;
}
:root[data-theme=dark] .toast-action { color: var(--accent); }

/* ---- print: paper is the most flour-proof display ---------------------- */
/* The recipe and its steps, nothing that moves. app.js opens every
   instructions section on beforeprint, since a closed section has no step
   nodes to print, and restores the reader's sections after. */
@media print {
  :root, :root[data-theme=dark] {
    --bg: #fff; --ink: #111; --text2: #333; --text3: #222;
    --muted: #555; --muted2: #666; --faint: #888;
    --accent: #7c3526; --accent2: #8a6b62; --accent3: #c9aaa2; --accent4: #d8c2bb;
    --line: #ddd; --line2: #e8e8e8; --line3: #bbb; --track: #ccc;
    --panel: #fff; --shadow: transparent;
  }
  .header-actions, .panel, .sheet-scrim, .summary-bar, .toasts,
  .controls, .origin-banner, .steps-bar, .steps-input,
  .filter-input { display: none !important; }
  /* A lit row is where your eye is standing right now, which paper has no use
     for -- and no printer should spend ink on a band around one weight. */
  .rows .row.is-lit { background: none; }
  .page { max-width: none; padding: 0; }
  .layout { display: block; padding: 16px 0 0; }
  .hero { position: static; border-bottom-color: #111; }
  .card, .step { break-inside: avoid; }
  .step.done { opacity: 1; }
  .step.done .step-text { text-decoration: none; }
}

/* ---------------------------------------------------------------- journal
 *
 * The past tense, under the present one. It reads as a column of dated
 * entries rather than as another card of numbers, because the thing being
 * scanned here is photographs — the formula is the footnote.
 */
/* Its kicker is the last opener on the sheet, so the air above it is a section
   seam like the three above it -- not a card's own padding, which is what it was
   taking. --seam-card read 8px tighter than Formula, Method and Process, which
   is the whole of the complaint: one relationship, one number. It rides
   --card-top rather than the head's margin because the card's padding is the
   only air there is above the head, and --card-top is already the knob two other
   rules use to say what air a card opens with. */
.journal.card { --card-top: var(--seam-sec); }
.journal-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: var(--seam-head); }
/* The log button needs air *under* it, not just over it. With nothing below
   it the newest entry's date read as the button's caption rather than as its
   own line. The margin is on the button rather than on .bakes so the empty
   state -- where the hint follows it instead -- gets the same separation.
   (It was a full-width accent capsule; it is the journal's one strong word
   now, on its own line, where the capsule was.) */
.journal-log { display: block; margin-bottom: var(--seam-page); }
.bake-count { font-size: var(--fs-small); color: var(--faint); font-family: var(--mono); letter-spacing: var(--ls-mono); }

.bakes { display: flex; flex-direction: column; gap: 26px; }
.bake { display: flex; flex-direction: column; gap: 8px; }
/* Dated entries, separated by the seam and by their own dates -- no rule
   between them, the same as everywhere else on this page. */
.bake + .bake { padding-top: var(--seam-entry); }
.bake-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.bake-when {
  font-family: var(--mono); font-size: var(--fs-small);
  letter-spacing: var(--ls-mono); color: var(--muted);
}
.bake-from { font-size: var(--fs-small); color: var(--faint); }
/* What the frozen recipe's name means, for a reader who does not have the
   language it was named in. The same footnote the recipe page carries under
   its own title (.doc-title-gloss), tucked the same way: the -6px pulls it out
   of the entry's 8px flex gap into the 2px that makes the date, the name and
   this read as one block instead of three lines. A hairline nudge inside a
   component, not a seam. */
.bake-gloss {
  margin: -6px 0 0;
  font-size: var(--fs-small); font-weight: 400; line-height: var(--lh-note);
  color: var(--muted); overflow-wrap: anywhere;
}
/* "Translated from Italian · Show original", per bake.
   Quieter than the masthead's .origin-line — that one is a banner about the
   whole sheet, and a column of accented boxes down a journal would shout the
   same sentence five times. Same words, same verb, the weight of a caption. */
.bake-trans { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.bake-trans .origin-text { font-size: var(--fs-small); }
.bake-title { margin: 0; font-size: var(--fs-emphasis); font-weight: 600; letter-spacing: -.01em; }
/* The note is a <button> on a bake of mine so a keyboard can reach what a
   thumb can, and it must not look like one: this is prose that happens to open
   when tapped, the same as the recipe's own name. Everything a button brings
   with it comes off, and the text keeps the alignment prose has. */
.bake-note {
  margin: 0; font-size: var(--fs-prose); line-height: var(--lh-prose);
  color: var(--text2); white-space: pre-wrap;
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 0; font-family: inherit;
  cursor: text;
}
.bake-note:disabled { cursor: auto; color: var(--text2); }
/* No note yet. The invitation reads as the absence it is -- the same dashed,
   accented language every "add one" in this app speaks -- and never as a note
   somebody wrote. */
.bake-note-empty { color: var(--faint); font-style: italic; }
.bake-note-edit {
  width: 100%; box-sizing: border-box; padding: 9px 11px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line3); border-radius: 3px;
  font: inherit; font-size: var(--fs-prose); line-height: var(--lh-note);
  resize: vertical;
}
.bake-note-edit:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* Two up on a phone, three where there is room. The tiles carry their own
 * aspect-ratio from the stored dimensions, so nothing reflows as they decode.
 */
.bake-photos { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin: 2px 0; }
/* The first photo is the entry's hero: full width, feed-sized. The rest keep
   the contact-sheet rectangle below it. */
.bake-photos .bake-photo:first-child { grid-column: 1 / -1; aspect-ratio: 16 / 10; }
@media (min-width: 560px) { .bake-photos { grid-template-columns: repeat(3, 1fr); } }
/* One rectangle for every tile, and the photo cropped into it.
   Sizing each tile to its own aspect ratio put a portrait beside a landscape
   at the same width and wildly different heights, which left a ragged hole
   under the shorter one. A contact sheet wants a grid; the uncropped frame is
   one tap away in the overlay. */
.bake-photo {
  display: block; overflow: hidden; border-radius: 3px;
  background: var(--line2); aspect-ratio: 3 / 2; cursor: zoom-in;
}
.bake-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The tile that adds to this bake. Same rectangle as its neighbours, dashed
   and in the accent, the way every "add one more" in this app is drawn --
   `+ Flour`, `+ Ingredient`, the composer's own `+`.
   It never takes the hero slot: on a bake with no photographs yet it would be
   a full-width empty box promising nothing, which is the reading that got the
   headline strip's `+` removed from bare recipes. */
.bake-photo-add,
.bake-photos .bake-photo-add:first-child {
  grid-column: auto; aspect-ratio: 3 / 2;
  border: 1.5px dashed var(--accent); border-radius: 3px;
  background: none; color: var(--accent); cursor: pointer;
  font-size: 22px; line-height: var(--lh-solid);
}
.bake-photo-add:disabled { opacity: 0.4; cursor: default; }

/* ---------------------------------------------------------- photo loading
 * Every tile above is already its final size -- aspect-ratio or fixed pixels,
 * and the wall knows its faces before the journal lands -- so loading is
 * paint, not layout. While the bytes are on the wire (ui/photo.js puts
 * `photo-wait` on the tile) the placeholder breathes; the photograph fades in
 * when it decodes; a failed fetch settles back to the flat placeholder
 * instead of a broken-image glyph. The veil is the ink at 6%, so it darkens
 * the light theme and lightens the dark one without a color of its own.
 */
.bake-photo, .bake-strip-thumb, .row-thumb, .friends-photo { position: relative; }
.wall-photo img, .bake-photo img, .bake-strip-thumb img, .row-thumb img, .friends-photo img {
  transition: opacity .28s ease;
}
.photo-wait img { opacity: 0; }
.photo-broken img { visibility: hidden; }
.photo-wait::after {
  content: ''; position: absolute; inset: 0;
  background: var(--ink); opacity: 0;
  animation: photo-breathe 1.6s ease-in-out infinite;
}
@keyframes photo-breathe { 50% { opacity: .06; } }
@media (prefers-reduced-motion: reduce) {
  .photo-wait::after { animation: none; }
  .wall-photo img, .bake-photo img, .bake-strip-thumb img, .row-thumb img, .friends-photo img {
    transition: none;
  }
}

/* ------------------------------------------------------------- arriving
 * A page still on the wire waits in its own shape (views/arriving.js). The
 * veil is the placeholder block: the tile gray, breathing the photo tiles'
 * own 6% ink, sized in ems and chs so it stands exactly where the text will
 * -- when the fetch lands, the words replace the veils and nothing moves.
 * The boot skeleton in index.html speaks the same language, so a cold start
 * flows from bones to veils to page without a jump.
 */
.veil { position: relative; overflow: hidden; background: var(--line2); border-radius: 3px; }
.veil::after {
  content: ''; position: absolute; inset: 0;
  background: var(--ink); opacity: 0;
  animation: photo-breathe 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .veil::after { animation: none; } }
/* Each size stands in for one text role: the em and ch units ride the class
   beside them (.wall-title, .wall-stats, .brand-sub), so the veil is always
   exactly one line of whatever it is waiting to be. */
.veil-title { display: block; height: 1.04em; width: min(56%, 6.5em); border-radius: 6px; }
.veil-line { display: inline-block; height: 1em; width: 15ch; vertical-align: -.15em; }
.veil-kick { display: block; height: var(--fs-micro); width: 9ch; }
.veil-sub { display: block; height: 9px; width: 110px; }
.veil-pill { display: inline-block; height: 36px; width: 108px; border-radius: 999px; }
.veil-poster { aspect-ratio: 4 / 3; width: 100%; border-radius: 8px; }
.veil-row { height: 50px; border-radius: 12px; }
.arriving { padding: 0 0 var(--seam-card); }
/* The settled title row never renders shorter than its icons' tap targets,
   so the veil title keeps the same floor -- the byline and strip below it
   must not climb when the real name and its share icon land. */
.arriving .doc-title { min-height: 44px; }
/* The quantity opener's stand-in: its kicker, then its numbers. */
.arriving-next { margin-top: var(--seam-sec); display: flex; flex-direction: column; gap: 14px; }
.veil-stat { display: block; height: 30px; width: 70%; max-width: 300px; border-radius: 6px; }
/* The boot skeleton is page chrome, not content: display contents, so the
   brand row and the doc column land in the page's own layout. */
#boot { display: contents; }
/* The whole frame, over everything, on a tap. Fixed rather than absolute so it
   does not care what scrolled it into view, and the image is bounded by the
   viewport in both axes so a tall photo cannot overflow the way it would with
   width alone. */
.lightbox {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(12px, 4vw, 40px);
  background: rgba(0, 0, 0, .82); cursor: zoom-out;
  padding-bottom: calc(clamp(12px, 4vw, 40px) + env(safe-area-inset-bottom));
}
.lightbox-img {
  max-width: 100%; max-height: 100%;
  object-fit: contain; border-radius: 3px; display: block;
}
/* A frame opened by a link, waiting for the journal that says what it is of.
   The same veil the page waits under, in the shape of a photograph: the frame
   is up from the first paint, so a sent link opens *something* immediately
   rather than showing the page and dropping a picture over it a beat later. */
.lightbox-veil {
  width: min(72vw, 62vh * 4 / 3); aspect-ratio: 4 / 3;
  background: var(--line2); opacity: .5;
}
/* What you can do to the photograph you are looking at. Anchored to the
 * bottom, clear of the image, on its own cursor -- the overlay around them
 * closes, these don't. Two words in four languages do not fit one line on a
 * narrow phone, so the row wraps and stays centred rather than reaching past
 * the viewport. */
/* Above the dock, the same sum the toasts use -- and for the same reason they
   use it. One verb sat down here before and merely looked crowded; two, one of
   them the red one, landed square on the `+` and read as a pile. The overlay
   is above the dock in the stack, so nothing was ever mis-tappable; it was
   the picture that was wrong, and on a phone that is the whole of it. */
.lightbox-acts {
  position: absolute;
  bottom: calc(var(--tab-h) + var(--tab-inset) + 14px + env(safe-area-inset-bottom));
  left: 50%; transform: translateX(-50%);
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  max-width: calc(100% - 24px);
}
@media (min-width: 900px) {
  .lightbox-acts { bottom: calc(22px + env(safe-area-inset-bottom)); }
}
/* The frame makes room for its own verbs, and only when it has them: a
   stranger's photograph carries none and must not be shrunk to leave a gap
   for buttons that are not there. Two rows' worth, because four languages
   wrap differently and the tall one decides. */
.lightbox.has-acts {
  padding-bottom: calc(var(--tab-h) + var(--tab-inset) + 108px + env(safe-area-inset-bottom));
}
@media (min-width: 900px) {
  .lightbox.has-acts { padding-bottom: calc(80px + env(safe-area-inset-bottom)); }
}
/* Words on the dark, the way a phone labels a photograph it has opened. They
   sit on the scrim rather than on the page's paper, so they take their own ink:
   white for the neutral one, the accent for the one that removes -- the same
   red every other delete in the app wears, and legible on any photograph
   because the scrim under it is 82% black. */
.lightbox-acts .text-btn { color: #fff; padding: 6px 4px; }
.lightbox-acts .lightbox-remove { color: var(--accent); }
/* Armed, the same weight the menu's Delete takes -- one language for one act. */
.lightbox-remove.armed { font-weight: 600; }

.bake-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 2px; }
.bake-sub {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--faint);
}
.bake.broken .bake-sub { color: var(--accent); font-family: inherit; letter-spacing: 0; }

.bake-composer { display: flex; flex-direction: column; gap: 10px; margin: var(--seam-head) 0 var(--seam-page); }
.bake-title-input, .bake-note-input {
  width: 100%; box-sizing: border-box; padding: 9px 11px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line3); border-radius: 3px;
  font: inherit; font-size: var(--fs-ui);
}
.bake-note-input { line-height: var(--lh-note); resize: vertical; }
.bake-title-input:focus, .bake-note-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* Photos lead the composer. Empty, the picker is a tile the size of the grid
 * it will become -- the form's first and biggest control, because most entries
 * are their photographs. Staged, the pile shows as the bakes it will save to:
 * one dated section per day, and a "+" tile to keep adding.
 */
.bake-pick {
  width: 100%; min-height: 96px; padding: 14px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border: 1.5px dashed var(--accent); border-radius: 3px;
  background: none; color: var(--accent); cursor: pointer;
  font: inherit;
}
.bake-pick-main { font-size: var(--fs-ui); }
.bake-pick-sub { font-size: var(--fs-micro); color: var(--muted); text-align: center; }
.bake-groups { display: flex; flex-direction: column; gap: 12px; }
.bake-groups:empty { display: none; }
.bake-group-head {
  font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--faint);
  margin-bottom: 6px;
}
.bake-add {
  flex: none; width: 74px; height: 74px;
  border: 1.5px dashed var(--accent); border-radius: 3px;
  background: none; color: var(--accent); cursor: pointer;
  font-size: 22px; line-height: 1;
}
.bake-add:disabled { opacity: 0.4; cursor: default; }
.bake-add-row { margin-top: -4px; }

.bake-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
.bake-thumb { position: relative; width: 74px; height: 74px; border-radius: 3px; overflow: hidden; background: var(--line2); }
.bake-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Taking a staged photograph back off the pile: the word, on the shade that
   makes it readable over any picture. It was a "×" in a circle, which is a
   glyph in a page with no glyphs left. */
.thumb-remove {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 3px 5px;
  background: color-mix(in srgb, #000 55%, transparent); color: #fff;
  text-align: center; border-radius: 0 0 3px 3px; overflow: hidden;
}
.thumb-remove:hover:not(:disabled) { color: #fff; opacity: .8; }

/* Paper gets the photographs and the bakes' own notes, and none of the controls. */
@media print {
  .bake-composer, .bake-foot .text-btn, .journal-log { display: none !important; }
  .bake { break-inside: avoid; }
}

/* ---- what never goes in the dough (model/toppings.js)

   Its own heading, its own column word, and otherwise the formula's rows
   exactly: the whole point of the feature is that a topping is an ordinary row
   whose arithmetic happens somewhere the dough cannot see. Nothing here styles
   a second control shape, because there isn't one. */
/* No margin of its own: `.sec-head` already carries --seam-sec above every
   section heading on the page, and adding one here stacked two of them into a
   92px gap -- the exact double-count the seam scale exists to make visible. */
.f-top { position: relative; }
/* A topping's name is typed where it is read, the way a flour's is -- but it is
   its own control, not the flour's borrowed: a topping is always nameable, so
   it carries none of the nameable/placeholder states that make a flour's name a
   button only sometimes (layout.js). */
.f-top-name {
  border: none; background: none; padding: 0; text-align: left;
  font: inherit; font-size: var(--fs-ui); color: var(--text3);
  border-bottom: 1px dashed transparent; cursor: pointer;
}
.f-top-name:hover { color: var(--accent); }
.f-top-in {
  flex: 1 1 auto; min-width: 0; background: none; border: none; padding: 0 0 1px;
  border-bottom: 1px dashed var(--accent); font: inherit; font-size: var(--fs-ui);
  color: var(--ink);
}
.f-top-in:focus { outline: none; border-bottom-style: solid; }
.f-top-del { flex: 0 0 auto; }
/* The amount that was never a number. It sits in the numeral's own slot, right
   aligned with the column of grams above it, so the eye still reads one column
   down the page whether a row says "80g" or "to taste". */
.f-top-amt {
  flex: 0 0 auto; width: 6.5em; text-align: right;
  background: none; border: none; padding: 0 0 1px;
  border-bottom: 1px dashed var(--accent3);
  font: inherit; font-size: var(--fs-ui); color: var(--text3);
}
.f-top-amt:focus { outline: none; border-bottom-style: solid; border-bottom-color: var(--accent); }
.f-top-amt::placeholder { color: var(--muted); }
/* The note under the section, in the hint voice the rest of the sheet's
   explanations already use. */
.f-top-note { margin-top: var(--seam-head); }

/* ---- what this dough used to be (views/history.js, docs/VERSIONS.md) --------
   The sheet's own furniture. Everything here rides the sheet component above,
   so there is no panel, no backdrop and no close button in this block -- only
   what a list of past documents needs that a list of ingredients does not. */

/* The head: what the sheet is, and which of the two readings is on. */
.hist-hint { margin: 0 0 var(--seam-head); }
.hist-hint.failed { color: var(--accent); }
.hist-toggle { display: flex; gap: 6px; flex-wrap: wrap; }
/* A segment is a pill that reports a state rather than firing a verb, so it
   takes the pill's shape and the accent only when it is the one in force --
   the same way the method's words say which method is chosen. */
.seg {
  background: none; border: 1px solid var(--line3); border-radius: 999px;
  padding: 6px 11px; font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--muted); min-height: 32px;
}
.seg.on { border-color: var(--accent); color: var(--accent); }

/* The day a version belongs to. A kicker, because a date is a fact. */
.hist-day {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500;
  text-transform: uppercase; letter-spacing: var(--ls-wide); color: var(--muted);
  padding-top: var(--seam-entry); padding-bottom: var(--seam-head);
}
.hist-day:first-child { padding-top: var(--seam-head); }

/* An entry: the clock on the left, the sentences and the verb on the right.
   The column is fixed so every stamp lines up down the list -- a time that
   moves with the length of the line beside it is not a column. */
.hist-entry {
  display: grid; grid-template-columns: 58px minmax(0, 1fr); gap: 10px;
  padding: var(--row-air) 0; border-top: 1px solid var(--line2);
}
.hist-day + .hist-entry { border-top: none; }
.hist-entry.live { border-top: none; }
.hist-time {
  font-family: var(--mono); font-size: var(--fs-small); color: var(--muted2);
  font-variant-numeric: tabular-nums;
  /* 58px and no wrapping, because the clock is not the same width in every
     language: a 12-hour locale reads "08:08 PM" where a 24-hour one reads
     "20:08", and at 46px the first wrapped onto two lines. The column is fixed
     rather than sized to content so the stamps line up down the list -- each
     entry is its own grid, so `auto` would let every row pick its own width. */
  white-space: nowrap;
  /* The stamp is mono and the line beside it is prose, and prose carries this
     much white above its glyphs (--prose-lead). Without it the clock sits a
     couple of pixels proud of the sentence it belongs to -- the mismatch this
     token was named for, in its smallest form. */
  padding-top: var(--prose-lead);
}

/* One change. The label is a sentence and the two figures are facts, so they
   keep the two voices -- and they wrap as a group rather than breaking a value
   away from its arrow. */
.hist-lines { display: flex; flex-direction: column; gap: 5px; }
.hist-chg { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px; }
.hist-chg .what { font-size: var(--fs-prose); color: var(--text3); }
.hist-chg .fig {
  font-family: var(--mono); font-size: var(--fs-body); color: var(--muted2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.hist-chg .fig.to { color: var(--ink); font-weight: 500; }
.hist-chg .arrow { font-family: var(--mono); font-size: var(--fs-body); color: var(--faint); }

.hist-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: var(--seam-head);
}
/* Why the entry exists, in the quietest ink on the sheet: it is provenance,
   not content, and it must never outshout the change it labels. */
.hist-cause {
  font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: var(--ls-mono); color: var(--faint);
}
.hist-cause.now { color: var(--accent); }

.hist-empty { margin: 0; padding: var(--seam-card) 0; text-align: center;
  font-size: var(--fs-prose); color: var(--muted); line-height: var(--lh-note); }

/* The waiting shape: three entries' worth, so the sheet opens at the size it
   settles at and nothing jumps when the versions land. */
.hist-veil .hist-entry { border-top: none; }
.hist-veil .veil-line { display: block; height: 11px; margin-bottom: 6px; }
.hist-veil .veil-line:last-child { width: 62%; }
.hist-veil .veil-kick { display: block; height: 11px; width: 34px; }
