/* ============================================================
   TableSet Desk — a legacy word processor for wine lists.
   Four quiet regions: toolbar, desk, page, status bar.
   ============================================================ */

/* ============================================================
   TWO ZONES, AND THE LINE BETWEEN THEM

   Everything in this app is either CHROME or PAPER.

   Chrome is the application: toolbar, desk, drawer, menus, status
   bar. It is furniture, it responds to the theme, and in dark mode
   it goes to warm coffee-black.

   Paper is the sheet: the page, its masthead, its rows, its rules.
   It is a picture of something that will be printed on white stock,
   so it is white at midnight and white at noon. The theme does not
   reach it. The tokens below are split on exactly that line, and the
   paper set is declared ONCE with no dark counterpart — not as an
   oversight but as the guarantee.

   v7.2.99 learned this the hard way. Its dark theme painted the
   sheet cream, then spent three releases failing to take the cream
   back out, because the override had won on specificity and nobody
   could find it. The sheet also inherited its text colour from
   `body`, which meant a dark theme turned the wine names pale — on
   screen, and in anything that printed from the DOM. One token set,
   one owner, and `.page` states its own ink rather than inheriting
   somebody else's.
   ============================================================ */

:root {
  /* ---------------- PAPER — never themed ---------------- */
  --paper:       #fff;
  --paper-ink:   #2c2925;
  --paper-dim:   #6e665d;
  --paper-rule:  #ddd7cd;
  --margin-guide: #cfd7e0;
  /* The editor's own affordances — column labels, grips, row inserters —
     are drawn ON the sheet. Conceptually they are chrome, but their
     backdrop is white in both themes, so they take the light accent in
     both. Themed with the rest of the chrome, the dark palette's dark
     `--accent-soft` would put a dark-red label on a dark-red pill on
     white paper. */
  --paper-faint:       #9b9287;
  --accent-paper:      #7a1f2b;
  --accent-paper-soft: #f0e3e3;
  --accent-paper-line: #c9a2a6;

  /* ---------------- CHROME — themed ---------------- */
  /* Warm paper-adjacent neutrals */
  --chrome:        #efece6;
  --chrome-2:      #e4e0d8;
  /* The Cellar's own ground. It had been borrowing --chrome-2, which
     was right by accident — a cellar is a deeper room than a dining
     room — and wrong to leave to chance, because --chrome-2 also draws
     the toolbar gradient, the status bar and two menu hovers and so
     cannot be tuned for one screen. Warmer and a step deeper than the
     lists, which also lets the white establishment cards read as
     paper on a shelf rather than panels on a panel. */
  --cellar-ground: #e8e0d2;
  --chrome-line:   #c9c3b8;
  --chrome-line-2: #d9d4ca;
  /* A standalone divider, not a seam between surfaces. Needs more
     contrast than --chrome-line to be findable at 1px. */
  --chrome-rule:   #b3ab9c;
  --ink:           #2c2925;
  --ink-soft:      #6b655c;
  --ink-faint:     #938c81;

  /* Raised surfaces. `--lift` is what a control becomes under the
     cursor and what a popover is made of; `--lift-2` is a panel
     inset into chrome; `--field` is a well you type into. Three
     roles that were previously nine different warm whites. */
  --lift:          #fffdf9;
  --lift-2:        #fdfcfa;
  --field:         #fff;

  /* The bevel. A light theme fakes relief with a white top edge; a
     dark one has to fake it with a much fainter one, or every button
     grows a glowing rim. */
  --bevel:         rgba(255,255,255,.6);
  --bevel-2:       rgba(255,255,255,.7);
  --bevel-soft:    rgba(255,255,255,.28);
  --shade:         rgba(0,0,0,.14);

  /* Toolbar and raised-button gradients */
  --tb-top:        #f6f3ee;
  --btn-a:         #fffefb;
  --btn-b:         #f1ede6;
  --btn-a-hi:      #fff;
  --btn-b-hi:      #f6f2eb;
  --btn-line:      #b8b1a5;
  --seg-a:         #fdfcf9;
  --seg-b:         #eeeae2;

  /* Desk — the warm surround the sheet sits on.
     This was a mid grey-taupe (#8d8983 -> #827e78), chosen to make white
     paper pop. It did, but it made the app heavy: a bright rectangle on
     a steel table. v7.2.99 put the sheet on cream (#faf6f0) and the eye
     moves from surround to paper without a step. The page still reads as
     paper because the edge and the shadow do that work, not the
     contrast of the ground. */
  --desk:          #faf6f0;
  --desk-2:        #f2ebe1;
  --page-edge:     #cfc5b6;
  --desk-glow:     rgba(255,255,255,.55);
  /* A shadow on cream has to be warm and light or it reads as dirt.
     Tokenised because the dark desk needs the opposite. */
  --page-shadow:
    0 0 0 1px var(--page-edge),
    0 1px 3px rgba(74,58,42,.10),
    0 10px 26px rgba(74,58,42,.14);
  --scroll-thumb:  rgba(0,0,0,.28);
  --thumb-edge:    rgba(40,32,26,.14);
  --danger:        #9a2a22;
  --m-track:       rgba(255,255,255,.6);
  --toast-bg:      #302c28;
  --toast-ink:     #f6f2ec;
  --scrim:         rgba(38,36,33,.52);

  /* Accent — TableSet dark red.
     `--accent` is a fill, `--accent-ink` is the same idea used as
     text on chrome. They differ only in dark mode, where a fill can
     stay saturated but text at that saturation flares against black
     and fails contrast.

     There used to be a third, `--accent-mark`, on the argument that
     the chalice is a filled shape answering to the 3:1 non-text
     threshold and could therefore hold a deeper red than any label.
     The argument was sound and the result was wrong: the mark and the
     word TableSet sit five pixels apart in the same bar, and in dark
     mode they were two different reds. A logo beside its own wordmark
     is one thing, not two. The token is gone; the chalice takes
     `--accent-ink` and is the pinker for it. v0.39.1. */
  --accent:        #7a1f2b;
  --accent-ink:    #7a1f2b;
  --accent-soft:   #f0e3e3;
  --accent-line:   #c9a2a6;
  --on-accent:     #fff;

  --menu-shadow:   0 10px 28px rgba(0,0,0,.26);

  --font-ui:    'EB Garamond', Georgia, serif;
  --font-ui-2:  'Jost', system-ui, sans-serif;

  /* Two rows of 30px plus a little air. See DESIGN-two-rows.md. */
  --toolbar-h: 66px;
  --status-h:  28px;
  --drawer-w:  292px;

  --r: 3px;
  --r-m: 10px;      /* phone: cards, fields and their buttons */
  --r-sheet: 16px;  /* phone: the top corners of a bottom sheet */

  /* ---------------- MOTION ----------------
     One easing and three durations, so that every panel, menu and room
     in the program arrives the same way. The curve is the one the
     drawer already used; it is written down here rather than in four
     places. --t-fast is for something small appearing under the cursor
     (a menu), --t for something taking over the screen (a room), and
     --t-slow for a wide piece of chrome sliding (the drawer and the
     desk edge it moves). Nothing new is animated because of this
     block: it renames what was already there. */
  --ease:   cubic-bezier(.22,.68,.28,1);
  --t-fast: 120ms;
  --t:      180ms;
  --t-slow: 260ms;
}

/* ============================================================
   DARK CHROME
   Coffee-neutral, carried over from v7.2.99's palette: warm brown
   without the pink cast. Only chrome tokens appear here. If a paper
   token ever shows up in this block, the sheet has started lying
   about what it will print as.
   ============================================================ */
[data-theme="dark"] {
  --chrome:        #1b1918;
  --chrome-2:      #141312;
  /* Same correction the desk took in v0.32.0: the near-neutral dark was
     colder than anything else in the app. Below the chrome, so the bar
     still has an edge against it. */
  --cellar-ground: #17130f;
  --chrome-line:   #3a3634;
  --chrome-line-2: #2d2a27;
  --chrome-rule:   #544e4a;
  --ink:           #f7f5f0;
  --ink-soft:      #cfc8bb;
  /* Lifted off the obvious mid-grey so the faint tier still clears 4.5:1
     on both chrome and a raised control. The light theme's equivalent
     manages only 2.8:1, so this is the tier dark mode improves.
     v0.17.0 dropped every surface and raised every ink tier: body text
     went 13.6:1 -> 16.1:1 and this faint tier 5.2:1 -> 6.8:1 on chrome,
     while desk-to-chrome and lift-to-chrome separation was held, so the
     panel edges did not flatten as the greys went down. */
  --ink-faint:     #a8a095;

  --lift:          #2a2725;
  --lift-2:        #232120;
  --field:         #121111;

  --bevel:         rgba(255,255,255,.055);
  --bevel-2:       rgba(255,255,255,.05);
  --bevel-soft:    rgba(255,255,255,.04);
  --shade:         rgba(0,0,0,.5);

  --tb-top:        #211f1e;
  --btn-a:         #2b2826;
  --btn-b:         #201e1d;
  --btn-a-hi:      #34302d;
  --btn-b-hi:      #272422;
  --btn-line:      #474240;
  --seg-a:         #262321;
  --seg-b:         #1a1918;

  /* The desk goes darker than the chrome, not lighter. The page has
     to be the brightest thing on screen — that is the whole point of
     the surround — so in dark mode it is the only bright thing.
     It was #060605, which is so close to black that the sheet read as
     die-cut and the page shadow was invisible. v7.2.99 sat at #1c1a18
     and looked far better — but this app's chrome is #1b1918, so taking
     that value literally erased the boundary between the toolbar and
     the desk and the two became one dark field. So: the same warmth,
     one clear step below the chrome instead of level with it. Dark
     enough to keep the rule above true, light enough that the sheet's
     shadow has something to fall on. */
  --desk:          #141211;
  --desk-2:        #0d0c0b;
  --page-edge:     #000;
  --desk-glow:     rgba(255,255,255,.035);
  --page-shadow:
    0 0 0 1px var(--page-edge),
    0 2px 6px rgba(0,0,0,.46),
    0 16px 38px rgba(0,0,0,.52);
  --scroll-thumb:  rgba(255,255,255,.2);
  --thumb-edge:    rgba(0,0,0,.55);
  --danger:        #b5303f;
  --m-track:       rgba(255,255,255,.08);
  --toast-bg:      #45403b;
  --toast-ink:     #f6f2ec;
  --scrim:         rgba(0,0,0,.68);

  /* Red on near-black is contrast-bound: a true burgundy sits at 30%
     lightness and scores 1.7:1 on this chrome, which is unreadable.
     The old ink answered that by climbing to 76% lightness, and a red
     that light is pink. These are the measured compromise instead —
     each one as deep as its own contrast threshold allows.

     --accent-ink   hsl(354 69% 63%)  5.11:1 on chrome, 4.74:1 on soft
     --accent       hsl(353 58% 45%)  a fill, judged against --on-accent

     The chalice takes --accent-ink too, so the mark and the wordmark
     beside it are one colour. It clears its own 3:1 threshold with
     room to spare at 5.11:1; the cost is that the chalice is pinker
     in dark mode than it would be on contrast grounds alone, and that
     is the trade — matching the word it stands next to matters more
     than the extra depth.

     --accent-soft was darkened with the ink so the tinted pills that
     carry accent text still clear 4.5:1. */
  --accent:        #b5303f;
  --accent-ink:    #e2606c;
  /* v0.164: two reds in the dark, not four. One for fills (Count, Add,
     the chosen grouping), one for words, marks and the chalice. The
     tint and the hairline are that same word-red let into the chrome,
     so they read as the one red at a lower voice rather than as two
     more shades. 12% keeps red words on the tint above 4.5:1. */
  --accent-soft:   color-mix(in srgb, #e2606c 12%, #1b1918);
  --accent-line:   color-mix(in srgb, #e2606c 40%, #1b1918);
  --on-accent:     #fdf4f4;

  --menu-shadow:   0 10px 30px rgba(0,0,0,.62);

  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  background: var(--desk);
  color: var(--ink);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button { font: inherit; color: inherit; cursor: pointer; }

/* ════════════════ REGION A — TOOLBAR ════════════════ */
/* Two rows, always, at every desktop width.

   The row used to be one line of 1410px that neither wrapped nor
   shrank, so on a 13-inch laptop Export PDF sat past the right edge.
   `overflow-x: auto` meant it was still reachable — Tab scrolled it
   into view — but with the scrollbar hidden there was nothing to say
   the bar continued, so in practice the primary action of the
   application was invisible.

   Wrapping only below a breakpoint was rejected: the one-row and
   two-row arrangements put the output group in different places, so
   supporting both from one DOM order needs `display: contents` and a
   set of width-conditional `order` values — an invisible second layout
   to keep correct forever, bought for a wide-screen case this user
   never sees. Two rows always is simpler and is what a word processor
   does.

   The overflow is gone with it. It was the mechanism that let the bar
   hide things quietly, and now that the content fits it would only go
   on silently absorbing the next overflow instead of showing one. */
.toolbar {
  position: fixed; inset: 0 0 auto 0; z-index: 40;
  height: var(--toolbar-h);
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 8px;
  background: linear-gradient(180deg, var(--tb-top) 0%, var(--chrome) 62%, var(--chrome-2) 100%);
  border-bottom: 1px solid var(--chrome-line);
  /* The cast shadow is earned rather than worn. At rest the bar has its
     hairline and nothing else, because nothing is passing under it; once
     the desk has scrolled, the shadow deepens and the sheet reads as
     sliding beneath the chrome. Toggled by body.desk-scrolled from
     js/editor.js. The inset bevel stays in both states — it is the bar's
     own edge, not a relationship with the page. */
  box-shadow: 0 1px 0 var(--bevel) inset;
  transition: box-shadow var(--t) var(--ease);
  user-select: none;
}
body.desk-scrolled .toolbar {
  box-shadow: 0 1px 0 var(--bevel) inset, 0 3px 9px var(--shade);
}

/* No rule between the rows. The bar already has a gradient and a
   bottom border; a third horizontal line inside 66px of chrome is one
   too many, and the rows separate on their own because their contents
   are plainly different things. */
.tb-row {
  display: flex; align-items: center; gap: 0;
  height: 30px; flex: none;
}

.brand { display: flex; align-items: center; gap: 5px; color: var(--accent-ink); padding-right: 2px; flex: none; }
/* A touch larger than the ruled square it replaces: the chalice leaves
   air at the shoulders of its box, so matched pixel heights read smaller. */
.brand-mark { width: 21px; height: 21px; flex: none; }
.brand-name {
  font-family: var(--font-ui);
  font-size: 15px; font-weight: 600; letter-spacing: .045em;
  color: var(--accent-ink);
}

/* Twenty-seven controls in one row only read as groups if the boundary
   between groups beats the 1px gap inside one. The three tiers were
   1px inside a run, 3px between runs, 9px between groups — too close
   together to register, so the bar read as a single undifferentiated
   rank of icons.

   The obvious fix is more space, and it is the wrong one: the row runs
   to 1410px of content and already clips its right-hand end on a
   1366px laptop, so every pixel spent on air is a pixel taken off
   Export PDF. The boundary is made findable with contrast and height
   instead, which costs nothing horizontally.

   --chrome-line is the token for a seam between two surfaces. A
   divider standing alone on one surface is not a seam and needs more
   than a seam's worth of contrast to be seen at 1px. */
.tb-div { width: 1px; height: 24px; margin: 0 4px; background: var(--chrome-rule); box-shadow: 1px 0 0 var(--bevel-2); flex: none; }
.tb-gap { width: 3px; flex: none; }
.tb-spring { flex: 1 1 auto; min-width: 8px; }

/* ---------------- Breadcrumb ----------------
   Which establishment, which list. Borrowed from v7.2.99, where it sat
   top-left; here it is right-aligned on the editing row, because the
   first row is full at 1280 and this is a label rather than a control.
   The establishment takes the rosy pill the old build gave it; the list
   name is plain ink after a slash. Both truncate rather than push the
   editing controls, since a bar that reflows when you rename a list is
   worse than a name you have to hover to read in full. */
.tb-crumb {
  display: flex; align-items: baseline; gap: 6px;
  min-width: 0; padding-right: 2px;
  font-family: var(--font-ui); font-size: 13px;
  color: var(--ink-soft);
  user-select: none; cursor: default;
}
.crumb-house {
  max-width: 132px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 1px 9px 2px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-ink);
  border: 1px solid var(--accent-line);
  font-size: 12.5px;
}
.crumb-sep { color: var(--ink-faint); font-size: 12px; }
.crumb-house[hidden] + .crumb-sep { display: none; }
.crumb-name {
  max-width: 218px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink);
}
@media (max-width: 1040px) {
  .crumb-house { max-width: 92px; }
  .crumb-name { max-width: 128px; }
}
@media (max-width: 820px) { .tb-crumb { display: none; } }
.tb-group { display: flex; align-items: center; gap: 1px; flex: none; }

/* Square icon / toggle buttons */
.tb-btn {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--r);
  background: transparent; color: var(--ink);
  transition: background .12s, border-color .12s, color .12s;
}
.tb-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
/* The exception that used to live here — .tb-lib-master svg clearing
   fill and stroke — existed because the Master Library button borrowed
   the chalice, a filled glyph in a row of stroked ones, and the 1.5
   above fattened its bowl. It is a stroked book now and needs none. */
.tb-btn:hover { background: var(--lift); border-color: var(--chrome-line-2); }
.tb-btn:active { background: var(--chrome-2); }
.tb-btn.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.tb-serif-b b, .tb-serif-i i { font-family: var(--font-ui); font-size: 16px; line-height: 1; }
/* 86 reads as a strikethrough because that is what it does to the row.
   A struck letterform rather than an imported glyph, so it sits in the
   same vocabulary as the B and the I either side of it. `text-decoration`
   is used over a drawn rule so the line tracks the face and the size. */
.tb-serif-s s {
  font-family: var(--font-ui); font-size: 16px; line-height: 1;
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 0;
}

/* Named dropdowns */
.tb-menu {
  height: 26px; padding: 0 5px 0 6px;
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid transparent; border-radius: var(--r);
  background: transparent;
  font-size: 13.5px; line-height: 1; white-space: nowrap;
  transition: background .12s, border-color .12s;
}
.tb-menu:hover { background: var(--lift); border-color: var(--chrome-line-2); }
.tb-menu[aria-expanded="true"] { background: var(--lift); border-color: var(--chrome-line); box-shadow: 0 1px 0 rgba(0,0,0,.04) inset; }
.tb-menu-label { color: var(--ink); }
.tb-menu .tb-val { color: var(--accent-ink); font-style: italic; }
/* Six of these sit in the bar at once — Template, Font, Size, Line,
   Section, Border — and every one is a statement of the current state,
   not an action. In light mode burgundy italic reads as a quiet aside.
   In dark, --accent-ink is deliberately lifted to hsl(354 69% 63%) for
   contrast, and six labels at that lift stop being an aside: they
   compete with Export PDF, which is the one thing in the bar that has
   any business being red. So dark mode spends the accent where it
   means something and states the current values in ink instead. */
[data-theme="dark"] .tb-menu .tb-val { color: var(--ink-soft); }
.tb-caret { color: var(--ink-faint); font-size: 9.5px; margin-top: 1px; }

/* Output buttons */
.tb-out {
  height: 26px; padding: 0 9px;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--chrome-line); border-radius: var(--r);
  background: linear-gradient(180deg, var(--btn-a), var(--btn-b));
  font-size: 13.5px; white-space: nowrap;
  box-shadow: 0 1px 1px rgba(0,0,0,.05);
  transition: background .12s, border-color .12s, transform .08s;
}
.tb-out svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.tb-out:hover { background: linear-gradient(180deg, var(--btn-a-hi), var(--btn-b-hi)); border-color: var(--btn-line); }
.tb-out:active { transform: translateY(1px); }
/* Lit the same way a pressed .tb-btn is lit. Logo & credit is the one
   .tb-out that holds a state: it says which of the drawer's two panels
   is showing, not merely that the drawer is open. */
.tb-out.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.tb-out-primary {
  background: linear-gradient(180deg, #8d2733, var(--accent));
  border-color: var(--accent); color: var(--on-accent);
  text-shadow: 0 1px 0 rgba(0,0,0,.25);
}
.tb-out-primary:hover { background: linear-gradient(180deg, #9a2b38, #86222e); border-color: #4d1219; }
/* v0.165: in the dark the bevel's two reds read as a third and fourth
   shade; Export is the one fill red, flat, like Count and Add. */
[data-theme="dark"] .tb-out-primary { background: var(--accent); border-color: var(--accent); }
[data-theme="dark"] .tb-out-primary:hover { background: var(--accent); border-color: var(--accent); filter: brightness(1.1); }
.tb-group-out { gap: 4px; }

/* ——— the keyboard ———
   v0.26.0. Every other surface in the app had a designed focus ring —
   the drawer fields, the status bar, the shelf cards, the house rows —
   and the toolbar had none, so tabbing into it fell back to the
   browser's hard black box. That was the one un-drawn thing in the
   bar, and it became more visible in v0.25.0, which doubled the number
   of controls you reach by keyboard.

   This is not a new idea, it is the existing one applied where it was
   missing: 2px of --accent with a 1px offset, exactly as .sb-icon and
   .cell-lib already do. In dark the token lifts to --accent-ink for
   the same reason the six state labels did not — #b5303f on #1b1918 is
   legible but quiet, and a focus ring is the one thing that should not
   be quiet. */
.tb-btn:focus-visible,
.tb-menu:focus-visible,
.tb-out:focus-visible,
.brand-back:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  /* Above the neighbouring button's border, so the ring is a closed
     rectangle rather than three sides and a gap. */
  position: relative; z-index: 2;
}
[data-theme="dark"] .tb-btn:focus-visible,
[data-theme="dark"] .tb-menu:focus-visible,
[data-theme="dark"] .tb-out:focus-visible,
[data-theme="dark"] .brand-back:focus-visible { outline-color: var(--accent-ink); }

/* Export PDF and its caret are accent-filled, so an accent ring would
   be a red line on a red button. They take --on-accent, the same
   colour as the button's own label, which is the one value guaranteed
   to read against that fill in both themes.

   The offset goes negative, which is the part that actually matters.
   Two earlier attempts were wrong and both were caught by looking at
   the render rather than the computed value: --accent-paper is the
   paper family's dark red for use on white, not a pale tint; and
   --on-accent at +1px offset put a white line on the cream bar just
   outside the button, where it is invisible. Inset by 3px it lands on
   the red fill and is unmistakable. */
.tb-out-primary:focus-visible,
[data-theme="dark"] .tb-out-primary:focus-visible {
  outline-color: var(--on-accent);
  outline-offset: -3px;
}

/* ════════════════ REGION B — THE DESK ════════════════ */
.desk {
  position: fixed;
  top: var(--toolbar-h); bottom: var(--status-h); left: 0;
  right: 0;
  overflow: auto;
  background:
    radial-gradient(120% 80% at 50% 0%, var(--desk-glow), transparent 60%),
    linear-gradient(180deg, var(--desk) 0%, var(--desk-2) 100%);
  transition: right var(--t-slow) var(--ease);
  scrollbar-color: var(--scroll-thumb) transparent;
}
body.drawer-open .desk { right: var(--drawer-w); }
.desk::-webkit-scrollbar { width: 13px; height: 13px; }
.desk::-webkit-scrollbar-thumb { background: rgba(0,0,0,.26); border-radius: 7px; border: 3px solid transparent; background-clip: content-box; }
.desk::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.38); background-clip: content-box; }

.stack-wrap { display: block; padding: 26px 0 46px; }
.stack-outer { margin: 0 auto; position: relative; }
.stack { transform-origin: top left; display: flex; flex-direction: column; align-items: center; gap: 22px; }

/* Ruler */
.ruler-slot { display: block; padding-top: 16px; }
.ruler {
  margin: 0 auto;
  position: relative; height: 19px;
  background: linear-gradient(180deg, var(--seg-a), var(--seg-b));
  border: 1px solid var(--chrome-line);
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(0,0,0,.12);
  overflow: hidden;
}
.ruler .tick { position: absolute; top: 0; width: 1px; background: #a9a29a; }
.ruler .tick.maj { height: 8px; }
.ruler .tick.min { height: 5px; background: #c0b9b0; }
.ruler .num { position: absolute; top: 7px; font-size: 8.5px; font-family: var(--font-ui-2); color: var(--ink-faint); transform: translateX(-50%); letter-spacing: .02em; }
.ruler .marginband { position: absolute; top: 0; bottom: 0; background: rgba(0,0,0,.07); }

/* Offscreen measuring host — identical content width & type to a real page */
.measure-host {
  position: absolute; top: 0; left: -99999px;
  visibility: hidden; pointer-events: none;
}

/* ════════════════ REGION C — THE PAGE CANVAS ════════════════ */
.page {
  position: relative;
  /* Stated, not inherited. `body` carries the chrome's ink, which in
     dark mode is cream; a sheet that inherited it would show pale wine
     names on white stock, on screen and on anything printed from the
     DOM. */
  background: var(--paper);
  color: var(--paper-ink);
  box-shadow: var(--page-shadow);
}
.page::after {
  /* crisp inner edge line */
  content: ''; position: absolute; inset: 0;
  border: 1px solid rgba(0,0,0,.055); pointer-events: none;
}
.page-margin {
  position: absolute; pointer-events: none;
  border: 1px dashed var(--margin-guide);
}
.page-content { position: absolute; display: flex; }
.flow-col { position: relative; flex: 1 1 0; min-width: 0; }
.flow-col + .flow-col { margin-left: var(--gutter, 26px); }

/* The foot of the sheet.

   Set in the sheet's own soft ink rather than the near-white grey it
   used to be. A number that only exists on screen is not a page
   number: this text goes into the PDF, and at #bdb6ac it survived the
   export and then vanished on paper, especially off a laser printer
   with a tired drum. --paper-dim is the tone the detail column already
   prints in, which is the right rank for it — legible, subordinate to
   every wine on the page, and unaffected by dark mode, since the sheet
   never changes colour. */
.page-num {
  position: absolute; left: 0; right: 0; bottom: 14px;
  text-align: center; font-family: var(--font-ui-2);
  font-size: 8.5px; letter-spacing: .28em; text-transform: uppercase;
  color: var(--paper-dim); pointer-events: none;
}
/* "PAGE 2 OF 3" at .28em tracking sprawls most of the way across a
   letter sheet. The wordy settings get the tracking of running text. */
.page-num-words { letter-spacing: .1em; }

/* The "starts a new page" marker — screen only, and never measured.

   A section with a break is by definition the first thing on its sheet,
   so the marker is always drawn at the top of a page. That is exactly
   where the column labels and the margin guide already live, and a
   third dashed line at that height read as clutter. It is lifted clear
   into the sheet's top margin instead, which is blank on every template
   — above the margin guide, in the white, where nothing else competes.

   Absolutely positioned, so switching it on does not shift the section
   it describes by a pixel; [data-noprint] and the body.exporting rule
   keep it out of the PDF, the HTML export and the Library thumbnail. */
.sec-break {
  position: absolute; left: 0; right: 0; top: -34px;
  display: flex; align-items: center; gap: .55em;
  pointer-events: none; z-index: 5;
}
.sec-break::before,
.sec-break::after {
  content: ''; flex: 1 1 auto; height: 0;
  border-top: 1px dashed var(--accent-paper-line);
  opacity: .55;
}
.sec-break span {
  flex: none; font-family: var(--font-ui-2);
  font-size: 7.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent-paper); opacity: .68;
}

/* ——— Blocks on the page ——— */
.blk { position: relative; }
.blk-masthead { text-align: center; }
.mast-title {
  font-size: 1.9em; line-height: 1.15; letter-spacing: .02em;
  margin: 0 0 .28em; outline: none;
}
.mast-sub {
  font-size: .82em; letter-spacing: .2em; text-transform: uppercase;
  color: #7d756c; margin: 0 0 1.5em; outline: none;
}
.mast-rule { height: 1px; background: #d8d2c8; margin: 0 auto 1.6em; width: 46%; }

/* ——— The house: logo and credit ———

   A bitmap on a page that is otherwise entirely type. Three rules keep
   it from looking pasted on:

     • the box is sized in the flow and the image fills it, so the only
       dimension anyone sets is width and a mark can never be squashed
     • grey is a filter, not a second file — reversible, and it cannot
       fall out of step with the bitmap the user actually chose
     • slightly under full black, because a logo printed at 100% next to
       Garamond at #2c2925 reads as a sticker rather than as letterpress */
.house-img { position: relative; display: inline-block; vertical-align: bottom; max-width: 100%; }
.house-img > img { display: block; width: 100%; height: 100%; object-fit: contain; }
.house-img.no-ratio > img { height: auto; }
.house-img.is-gray > img { filter: grayscale(1) contrast(1.04); }
.house-logo > img { opacity: .94; }
.mast-logo { margin: 0 0 1.1em; line-height: 0; }
/* Air on both sides. With only a top margin the role line landed two
   pixels off the column-label strip — two rows of small tracked grey
   caps stacked together, so the credit read as a heading for the
   table rather than as part of the masthead.

   The clearance is padding rather than a bottom margin because the
   masthead has a bottom margin of its own and the two collapse: 2em
   here bought three pixels. Padding does not collapse. The masthead
   is measured before pagination, so the extra band is accounted for
   on paper as well as on the desk. */
.mast-credit { margin: 1.2em 0 0; padding-bottom: 6px; }

/* The corner. Direct manipulation instead of the old build's 40–160px
   slider: the sheet is already what-you-see-is-what-prints, so the
   size control belongs on the thing being sized. Invisible until the
   sheet is hovered, so a calm page stays calm. */
.house-grip {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px;
  border: 1px solid var(--accent-paper); background: #fff;
  border-radius: 2px; cursor: nwse-resize; opacity: 0;
  transition: opacity .12s ease; z-index: 6;
}
.house-grip::after {
  content: ''; position: absolute; inset: 3px;
  border-right: 1.5px solid var(--accent-paper);
  border-bottom: 1.5px solid var(--accent-paper);
  opacity: .7;
}
.page:hover .house-grip { opacity: .5; }
.house-grip:hover, .house-grip.is-dragging { opacity: 1 !important; }
.house-img.is-sizing { outline: 1px dashed var(--accent-paper-line); outline-offset: 3px; }

/* The masthead's hover pill is gone. It was the only screen-only
   control drawn on the paper rather than out in the page's margins,
   and it was there because no toolbar button opened the house panel.
   Row one carries Logo & credit now. See DESIGN-toolbar-options.md. */

/* The title, hushed rather than deleted. A wordmark logo already
   carries the restaurant's name; printing the text as well sets it
   twice, six lines apart. The node stays in the DOM and stays
   editable so turning the choice off brings the same words back. */
.mast-title.is-hushed { display: none; }
body.exporting .mast-title.is-hushed { display: none !important; }

/* ——— The foot band ———

   Anchored to the bottom margin rule and growing upward into height
   that layout() reserved before it paginated, so nothing it contains
   can ever land on a wine. */
.page-foot-wrap { position: absolute; }
.page-foot { display: flex; flex-direction: column; gap: .5em; }
.foot-logo { line-height: 0; }
.page-foot .foot-logo { align-self: center; }

/* ——— Credit styles ———

   Three, not the old build's four. Stamp — the name inside a ruled box
   — is gone: this build prints real page borders, and a bordered
   credit inside a bordered page reads as two frames arguing. */
.house-credit { line-height: 1.35; }
.house-credit[data-align="left"]   { text-align: left; }
.house-credit[data-align="center"] { text-align: center; }
.house-credit[data-align="right"]  { text-align: right; }
.house-credit[data-align="left"]   .house-sig { margin-right: auto; }
.house-credit[data-align="center"] .house-sig { margin: 0 auto; }
.house-credit[data-align="right"]  .house-sig { margin-left: auto; }
.house-sig { display: block; }
.house-sig > img { opacity: .88; filter: grayscale(1) contrast(1.06); }

.credit-label {
  font-size: .58em; letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent-paper); opacity: .85;
}
.credit-name { font-size: .95em; margin: .2em 0; font-family: var(--head-font, inherit); }
.credit-role {
  font-size: .58em; letter-spacing: .16em; text-transform: uppercase;
  color: var(--paper-dim);
}

/* Formal — a short rule, then label, name, role. The rule sits on the
   same side the credit is aligned to, or it reads as a stray dash.

   A real element rather than a ::before, because the PDF renderer
   walks the DOM: it can see a div with a background, and cannot see a
   pseudo-element at all. A rule that prints has to exist. */
.credit-formal { position: relative; padding-top: .5em; }
.credit-formal .credit-rule {
  width: 26px; height: 1px;
  background: var(--accent-paper); opacity: .45; margin: 0 0 .5em;
}
.house-credit[data-align="center"].credit-formal .credit-rule { margin: 0 auto .5em; }
.house-credit[data-align="right"].credit-formal .credit-rule  { margin: 0 0 .5em auto; }

/* Minimal — the name, in italic, and nothing else. */
.credit-minimal .credit-name { font-size: .78em; font-style: italic; color: var(--paper-dim); margin: 0; }

/* Compact — one row, the least room a credit can take. */
.credit-compact .credit-line {
  font-family: var(--font-ui-2); font-size: .56em; letter-spacing: .12em;
  text-transform: uppercase; color: var(--paper-dim);
  /* No nowrap. A long name and a long title on one tracked line will
     exceed a narrow column, and a credit that runs off the paper is
     worse than a credit on two lines. */
  line-height: 1.5;
}

.blk-section { margin: 0 0 .5em; }
.sec-title {
  font-size: 1.02em; letter-spacing: .18em; text-transform: uppercase;
  padding-bottom: .28em; margin: 0;
  border-bottom: 1px solid #ddd7cd;
  outline: none;
}
.blk-section.is-sel .sec-title { box-shadow: inset 0 0 0 100vmax rgba(122,31,43,.045); }

/* A continuation heading. The name sets exactly as every other section
   head — same caps, same tracking, same rule beneath — because it is
   the same heading, met again after a page turn. The mark is the only
   part that differs, and it differs three ways at once so that no one
   of them has to be loud: lowercase against the name's uppercase,
   italic, and in --paper-dim, the tone the detail column already prints
   in. Rank it any higher and the sheet has two headings arguing about
   which is the heading; any lower and it reads as an error.

   The tracking is wound back from .18em to almost nothing. Letterspaced
   caps are a heading's voice; a lowercase word held open to the same
   width just looks broken. */
.cont-mark {
  text-transform: none;
  letter-spacing: .01em;
  font-style: italic;
  color: var(--paper-dim);
}

/* Section reorder grip — six dots in the page's outside margin, the same
   language as the row grip: an affordance that lives at the edge of the
   sheet and never inside it. Faint at rest rather than invisible, because
   an affordance nobody can see is an affordance that does not exist. */
.sec-grip {
  position: absolute; left: -19px; top: .1em;
  width: 13px; height: 17px; cursor: grab;
  opacity: .3; transition: opacity .12s; z-index: 6;
  background-image: radial-gradient(currentColor 1px, transparent 1.2px);
  background-size: 5px 5px; background-position: 1px 2px;
  background-repeat: repeat; color: #a79f93;
}
.blk-section:hover .sec-grip { opacity: .85; }
/* The caret's section wears its grip. Short of the hover strength, so a
   grip under the pointer is still the brighter of the two. */
.blk-section.has-caret .sec-grip { opacity: .6; }
.sec-grip:hover, .sec-grip.dragging { color: var(--accent-paper); opacity: 1; }
.sec-grip.dragging { cursor: grabbing; }

/* ——— Flights ———
   A flight is a heading for the wines under it, so it has to read as
   subordinate to the section above and superior to the rows below. It
   gets there with one notch of scale and italic — not with colour. The
   burgundy in this app is chrome; nothing on the sheet prints in it,
   because the sheet has to survive a black-and-white laser printer at
   4pm on a Friday. */
.blk-flight { position: relative; padding: 0; }
.fl-line { display: flex; align-items: center; gap: .7em; }
/* rgba rather than `opacity`, deliberately: element opacity is not
   carried into the PDF, but a composited rgba fill is. */
.fl-rule {
  flex: 1; height: 1px; min-width: 10px;
  background: rgba(44, 41, 37, .3);
}
/* The title wraps rather than runs past the column edge. A long
   producer name in the two-column layout has nowhere to go, and a
   heading that hangs off the paper is worse than one on two lines. */
/* --paper-ink, not --ink: the sheet is white in both themes, so a
   heading painted in the chrome's ink turns near-white on white the
   moment dark mode is on. Found while checking the repeated flight
   line at a page turn; the live flight heading had it too. */
.fl-title {
  font-style: italic; font-size: .94em; color: var(--paper-ink);
  flex: 0 1 auto; min-width: 0; padding: 0;
  text-align: center; text-wrap: balance;
  word-break: break-word; hyphens: auto;
  outline: none; min-height: 1em;
}
.fl-price {
  flex: 0 0 auto; text-align: right; font-style: italic;
  font-variant-numeric: tabular-nums; padding: 0;
}
.blk-flight.is-sel { background: rgba(122,31,43,.035); box-shadow: -6px 0 0 rgba(122,31,43,.28); }

/* Pours — v0.62. After the name, same size, a shade lighter; on a
   narrow card, a line of its own under it. */
.fl-pours {
  font-style: italic; font-size: .94em; color: rgba(44, 41, 37, .62);
  flex: 0 1 auto; min-width: 0; padding: 0;
  outline: none; min-height: 1em;
}
.fl-dot { flex: none; color: rgba(44, 41, 37, .45); margin: 0 -.35em; }
.fl-sub { text-align: center; margin-top: .12em; }
.fl-sub .fl-pours { display: block; }
.blk-flight.fl-twocol .fl-sub, .blk-flight.fl-prose .fl-sub { text-align: left; }

/* Two-Column has no width to spare for a centred title, so the rule
   goes to one side and the name sits on the left margin like a
   sub-category. */
.blk-flight.fl-twocol .fl-line { gap: .55em; }
.blk-flight.fl-twocol .fl-title {
  font-style: normal; font-size: .82em;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--paper-ink); text-align: left;
}
.blk-flight.fl-twocol .fl-rule:first-child { display: none; }

/* Prose sets each wine as a paragraph; a ruled band would fight the
   text. Italic on the left, nothing else. */
.blk-flight.fl-prose .fl-rule { display: none; }
.blk-flight.fl-prose .fl-line { gap: .8em; }
.blk-flight.fl-prose .fl-title { font-size: 1em; text-align: left; }
.blk-flight.fl-prose .fl-price { margin-left: auto; }

.blk-row { padding: 0; }
.blk-row .cells { display: grid; align-items: baseline; }
.cell {
  outline: none; min-height: 1em;
  padding: 0 .5em 0 0; word-break: break-word; hyphens: auto;
}
.cell:last-child { padding-right: 0; }
.cell.is-empty::before {
  content: attr(data-ph); color: #c6bfb4; font-style: italic; pointer-events: none;
}
.blk-row.is-sel { background: rgba(122,31,43,.035); box-shadow: -6px 0 0 rgba(122,31,43,.28); }
.cell.num { font-variant-numeric: tabular-nums; }
/* v0.63: a price is one word. It never wraps; layout widens its column
   instead (fitColumns in editor.js). */
.cell.num { white-space: nowrap; word-break: normal; hyphens: manual; }
.fl-price { white-space: nowrap; }
.cell.dim { color: #6e665d; }

/* ——— 86'd ———
   A wine that has run out still prints: the guest reads the list, sees
   the strike, and understands without being told. Full ink is
   deliberate — dimming as well as striking is two signals where one
   will do, and the second one only makes the line hard to read. */
.blk-row.sold-out .cell,
.blk-row.sold-out .prose-detail {
  text-decoration: line-through;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 0;
  opacity: 1;
}
.blk-row.sold-out .leader { opacity: .45; }

/* Prose row layout */
.blk-row.prose .prose-head { display: flex; align-items: baseline; gap: .4em; }
.blk-row.prose .prose-head .cell.wine { flex: 0 1 auto; padding-right: 0; }
.blk-row.prose .leader { flex: 1 1 auto; border-bottom: 1px dotted #cfc8bd; transform: translateY(-.22em); min-width: 1.5em; }
.blk-row.prose .prose-detail { font-style: italic; color: #6e665d; font-size: .92em; }

/* Selection & hover affordances */
.blk-row:hover { background: rgba(0,0,0,.018); }

/* Row height grip — bottom edge of a row */
.row-grip {
  position: absolute; left: 0; right: 0; bottom: -3px; height: 7px;
  cursor: row-resize; opacity: 0; z-index: 6;
}
.blk-row:hover .row-grip { opacity: 1; }
.row-grip::after {
  content: ''; position: absolute; left: 0; right: 0; top: 3px; height: 1px;
  background: transparent; transition: background .1s;
}
.row-grip:hover::after, .row-grip.dragging::after { background: var(--accent-paper-line); }

/* Column grips — vertical strips at internal column boundaries */
.col-grip {
  position: absolute; top: 0; bottom: 0; width: 9px;
  margin-left: -4px; cursor: col-resize; z-index: 5;
}
.col-grip::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px;
  background: transparent; transition: background .1s;
}
.col-grip:hover::after, .col-grip.dragging::after { background: var(--accent-paper-line); }

/* ——— Column label strip ———
   Editor-only chrome in the page's top margin band. One chip per
   column, positioned at the same running-cumulative percent
   boundaries the resize grips use, so the labels track the columns
   pixel-for-pixel during a drag. Absolutely positioned, so it takes
   no space in the flow and cannot leave a ghost margin behind in the
   PDF. Quiet at rest, legible on hover, and a handle for reordering. */
.col-labels {
  position: absolute; left: 0; right: 0; top: 0; height: 15px;
  z-index: 4; pointer-events: none;
}
.col-label {
  position: absolute; top: 0; height: 15px;
  display: flex; align-items: center; gap: 3px;
  padding: 0 2px; box-sizing: border-box;
  font-family: var(--font-ui-2); font-size: 8.5px;
  letter-spacing: .1em; text-transform: uppercase;
  color: #b3aca1; white-space: nowrap; overflow: hidden;
  border-radius: 2px; cursor: grab; pointer-events: auto;
  transition: color .12s, background .12s;
}
.col-label .cl-name { overflow: hidden; text-overflow: ellipsis; }
.col-label .cl-grip {
  flex: none; width: 5px; height: 9px; opacity: 0; transition: opacity .12s;
  background-image: radial-gradient(currentColor .85px, transparent 1px);
  background-size: 3px 3px;
}
.flow-col:hover .col-label { color: var(--paper-faint); }
.col-label:hover { color: var(--accent-paper); background: var(--accent-paper-soft); }
.col-label:hover .cl-grip { opacity: .8; }
.col-label.dragging {
  color: var(--accent-paper); background: var(--accent-paper-soft);
  cursor: grabbing; outline: 1px solid var(--accent-paper-line);
}
.col-label.dragging .cl-grip { opacity: 1; }

/* Inline contextual inserters — appear only on hover, never persist */
/* These were `opacity: 0` and appeared only on `.flow-col:hover`, which
   made the one on-page way to add a wine invisible until you happened to
   sweep the column. The same argument is already written down two rules
   above for `.sec-grip`: an affordance nobody can see is an affordance
   that does not exist. v7.2.99 drew them at rest as dashed outline pills
   at the tail of the flow, so they now sit quietly visible and come up
   to full strength on hover. The hairline across the column stays
   hover-only — a rule drawn at rest would make the sheet look ruled. */
/* Left-aligned, not centred. Centred under the whole flow they read as
   a widget parked in the middle of the page; started at the wine
   column's own left edge, where every row of text starts, they read as
   the next line — which is what pressing one makes. */
.inserters {
  position: absolute; left: 0; right: 0; height: 16px;
  display: flex; align-items: center; justify-content: flex-start; gap: 6px;
  opacity: .62; transition: opacity .12s; z-index: 7;
}
.flow-col:hover .inserters { opacity: 1; }
.inserters .ins {
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-paper); background: var(--paper);
  border: 1px dashed var(--accent-paper-line); border-radius: 20px;
  padding: 1px 8px 2px; line-height: 1.4;
  opacity: .5; transition: opacity .12s, background .12s;
}
.inserters .ins:hover { opacity: 1; background: var(--accent-paper-soft); }
.inserters::before {
  content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: rgba(122,31,43,.14);
  opacity: 0; transition: opacity .12s;
}
.flow-col:hover .inserters::before { opacity: 1; }

/* ════════════════ MENUS ════════════════ */
.menu-layer { position: fixed; inset: 0; z-index: 60; }
.menu {
  /* 520 cut the Page menu 54px short and the Border menu 61px, and on
     Page the last whole row was the ORIENTATION label — a heading with
     nothing under it reads as an empty section, not as more to come.
     648 fits both whole at 800 and at 720, the 13-inch cases. Template
     is 696 and fits in neither, so it still scrolls, and .is-scroll
     below is the cue for that. Same fix .lookup already carries. */
  position: absolute; min-width: 176px; max-height: min(calc(100vh - 104px), 648px); overflow: auto;
  background: var(--lift);
  border: 1px solid #b8b1a5; border-radius: var(--r);
  box-shadow: var(--menu-shadow), 0 1px 0 var(--bevel-2) inset;
  padding: 4px;
  animation: menuIn var(--t-fast) var(--ease);
}
@keyframes menuIn { from { opacity: 0; transform: translateY(-3px); } }
/* Set by showMenu when the content really overflows, and dropped once
   the reader reaches the end. A fade rather than a drawn bar: it
   belongs to the strip instead of sitting on top of it, and the
   phone's chip strip already speaks this way. */
/* Not on the two menus with a pinned foot — Template's hint and
   Border's placement strip are both position:sticky, and the fade
   landed on them and dimmed a row that never scrolls. Those two show
   their overflow honestly already: the content runs under the foot. */
.menu.is-scroll:not(.menu-tpl):not(.menu-border) {
  mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 22px), transparent 100%);
}
.menu-head {
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint); padding: 7px 10px 6px;
}
.menu-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 7px 10px; border: 0; border-radius: var(--r); background: transparent;
  text-align: left; font-size: 14px; line-height: 1.35;
}
.menu-item:hover { background: var(--accent); color: var(--on-accent); }
.menu-item:hover .mi-sub, .menu-item:hover .mi-check { color: rgba(255,255,255,.8); }
.menu-item .mi-body { flex: 1 1 auto; min-width: 0; }
.menu-item .mi-sub { display: block; font-family: var(--font-ui-2); font-size: 10.5px; color: var(--ink-faint); margin-top: 2px; }
.menu-item .mi-check { color: var(--accent-ink); font-size: 12px; flex: none; }
.menu-item.sel { background: var(--accent-soft); }
.menu-item.sel:hover { background: var(--accent); }
.menu-sep { height: 1px; background: var(--chrome-line-2); margin: 4px 2px; }
.menu-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px; padding: 2px; }
.menu-grid .menu-item { justify-content: center; padding: 7px 4px; font-variant-numeric: tabular-nums; }

/* Template menu cards */
.tpl-card { display: flex; gap: 9px; align-items: flex-start; padding: 8px 9px; }
.tpl-thumb {
  flex: none; width: 34px; height: 44px; background: var(--paper);
  border: 1px solid #cfc8bd; border-radius: 1px; padding: 4px 3px;
  display: flex; flex-direction: column; gap: 2px;
}
.menu-item:hover .tpl-thumb { border-color: rgba(255,255,255,.5); }
.tpl-thumb i { display: block; flex: none; height: 1.5px; background: #bdb6ab; border-radius: 1px; }
.tpl-thumb i.h { background: #8d2733; width: 70%; }
.tpl-thumb .cols { display: flex; gap: 2px; align-items: flex-start; }
.tpl-thumb .cols > span { flex: 1; display: flex; flex-direction: column; gap: 2px; }
/* The thumb now draws as many lines as the template's line setting
   implies, so a dense one can ask for more than the 44px holds. Hiding
   the overflow is the honest answer — a sheet runs off the bottom of
   the page too. */
.tpl-thumb { overflow: hidden; }

/* ════════════════ WINE LOOKUP POPOVER ════════════════ */
/* 268px was set when the offer was shorter, and it cut the last row and
   the hint under it clean in half. Seven offers, a head and a foot come
   to about 364; the viewport clamp keeps it honest on a short window,
   where it scrolls as before. */
.lookup {
  position: fixed; z-index: 70; width: 342px;
  max-height: min(372px, calc(100vh - 150px)); overflow: auto;
  background: var(--lift); border: 1px solid var(--btn-line); border-radius: var(--r);
  box-shadow: 0 12px 30px rgba(0,0,0,.28);
  padding: 3px;
}
.lk-head {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint); padding: 6px 8px 5px;
}
.lk-head svg { width: 11px; height: 11px; fill: none; stroke: var(--accent); stroke-width: 1.6; }
.lk-item {
  display: block; width: 100%; text-align: left;
  padding: 6px 8px; border: 0; border-radius: var(--r); background: transparent;
}
.lk-item .lk-name { font-size: 14px; line-height: 1.25; }
.lk-item .lk-meta { font-family: var(--font-ui-2); font-size: 10.5px; color: var(--ink-soft); margin-top: 1px; line-height: 1.3; }
.lk-item.active, .lk-item:hover { background: var(--accent); }
.lk-item.active .lk-name, .lk-item:hover .lk-name { color: var(--on-accent); }
.lk-item.active .lk-meta, .lk-item:hover .lk-meta { color: rgba(255,255,255,.82); }
.lk-foot {
  font-family: var(--font-ui-2); font-size: 9.5px; color: var(--ink-faint);
  padding: 5px 8px 4px; border-top: 1px solid var(--chrome-line-2); margin-top: 3px;
}
.lk-empty { font-size: 13px; color: var(--ink-faint); font-style: italic; padding: 8px; }

/* Contextual library chip on the focused wine cell. Chrome-coloured, not
   paper-coloured: it is a tool sitting over the sheet, not something that
   prints. It sits out in the sheet's left margin, level with the line the
   caret is in, so it never covers the text being edited; see
   placeCellLib(). Body-level, so the PDF walker never sees it. */
.cell-lib {
  position: fixed; z-index: 45;
  width: 21px; height: 21px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid var(--chrome-line); border-radius: 50%;
  background: var(--chrome);
  color: var(--accent-ink);
  box-shadow: 0 1px 4px var(--shade);
  opacity: .62;
  transition: opacity .12s, transform .1s, background .12s;
}
.cell-lib svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.cell-lib:hover { opacity: 1; background: var(--lift); transform: scale(1.08); }
.cell-lib:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 1px; }
.cell-lib.on { opacity: 1; background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* The Library and the phone both hide the desk without moving the caret,
   so the chip is suppressed by state rather than relying on the overlays
   sitting above it. */
body.lib-open .cell-lib, body.mobile .cell-lib { display: none; }

/* Alignment guides — visible only mid-drag */
.guide-v { position: fixed; width: 1px; background: var(--accent); z-index: 55; pointer-events: none; opacity: .65; }
.guide-h { position: fixed; height: 1px; background: var(--accent); z-index: 55; pointer-events: none; opacity: .65; }
.guide-chip {
  position: fixed; z-index: 56; pointer-events: none;
  background: var(--accent); color: var(--on-accent);
  font-family: var(--font-ui-2); font-size: 10px; letter-spacing: .04em;
  padding: 2px 6px; border-radius: var(--r);
}

/* ════════════════ REGION D — DRAWER ════════════════ */
.drawer {
  position: fixed; top: var(--toolbar-h); bottom: var(--status-h); right: 0;
  width: var(--drawer-w); z-index: 30;
  background: linear-gradient(180deg, var(--tb-top), var(--chrome));
  border-left: 1px solid var(--chrome-line);
  box-shadow: -3px 0 12px rgba(0,0,0,.16);
  transform: translateX(100%);
  transition: transform var(--t-slow) var(--ease);
  display: flex; flex-direction: column;
}
body.drawer-open .drawer { transform: none; }
.drawer-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: 12px 12px 11px 16px; border-bottom: 1px solid var(--chrome-line-2);
}
/* v0.130: a panel's name is set as Settings and the phone's sheets set
   theirs; Garamond, as written, in the red. It was the one title in small
   capitals. */
.drawer-head h2 { margin: 0; font-family: var(--font-ui); font-size: 20px; font-weight: 400; line-height: 1.2; color: var(--accent-ink); }
.drawer-close { border: 0; background: transparent; color: var(--ink-faint); font-size: 13px; padding: 2px 4px; border-radius: 2px; }
.drawer-close:hover { color: var(--accent-ink); background: var(--lift); }
.drawer-body { flex: 1 1 auto; overflow: auto; padding: 16px 16px 24px; }

.dr-lookup { margin-bottom: 14px; }
.dr-field { margin-bottom: 9px; }
.dr-field label {
  display: block; font-family: var(--font-ui-2);
  font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 3px;
}
.dr-field input, .dr-field textarea {
  width: 100%; font-family: var(--font-ui); font-size: 14px; color: var(--ink);
  background: var(--field); border: 1px solid var(--chrome-line-2); border-radius: var(--r);
  padding: 5px 7px; outline: none;
}
.dr-field input:focus, .dr-field textarea:focus { border-color: var(--accent-line); box-shadow: 0 0 0 2px rgba(122,31,43,.08); }
.dr-field textarea { resize: vertical; min-height: 62px; line-height: 1.4; }
.dr-field input[type="search"] { font-style: italic; }
.dr-note {
  font-family: var(--font-ui-2); font-size: 10.5px; line-height: 1.5;
  color: var(--ink-faint); margin: 2px 0 14px;
}
.dr-empty { font-size: 14px; font-style: italic; color: var(--ink-faint); line-height: 1.5; padding: 6px 0; }
.dr-sel {
  font-size: 15px; line-height: 1.3; padding-bottom: 10px; margin-bottom: 12px;
  border-bottom: 1px solid var(--chrome-line-2);
}
.dr-sel small { display: block; font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 3px; }

/* ——— The house panel, in the drawer ———

   Reached by clicking the masthead. Everything here is set once when a
   restaurant first opens a list, so it is deliberately not in the
   toolbar — the spec's "if a control isn't used in the majority of
   editing sessions, it's tucked away." */
.dr-group { margin: 0 0 16px; }
.dr-group > h3 {
  font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-faint);
  margin: 0 0 7px; padding-bottom: 5px; border-bottom: 1px solid var(--chrome-line-2);
}
.dr-seg { display: flex; gap: 0; margin-bottom: 9px; }
.dr-seg button {
  flex: 1 1 0; min-width: 0; padding: 6px 7px;
  font-family: var(--font-ui-2); font-size: 10px; letter-spacing: .06em;
  background: var(--field); color: var(--ink-soft);
  border: 1px solid var(--chrome-line-2); border-right-width: 0;
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dr-seg button:first-child { border-radius: 3px 0 0 3px; }
.dr-seg button:last-child { border-right-width: 1px; border-radius: 0 3px 3px 0; }
.dr-seg button:hover { background: var(--lift); color: var(--ink); }
.dr-seg button[aria-pressed="true"] {
  background: var(--accent-soft); color: var(--accent-ink);
  border-color: var(--accent-line); position: relative; z-index: 1;
}
.dr-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; z-index: 2; }

/* The one file-picking control in the app. Reads as a slot waiting to
   be filled rather than as a button, because that is what it is. */
.dr-drop {
  width: 100%; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 16px 10px; margin-bottom: 9px;
  background: var(--field); border: 1px dashed var(--chrome-line);
  border-radius: 3px; color: var(--ink-soft); cursor: pointer;
  font-family: var(--font-ui-2); font-size: 10.5px; letter-spacing: .06em;
}
.dr-drop:hover { border-color: var(--accent-line); color: var(--accent-ink); background: var(--lift); }
.dr-drop small { font-size: 9px; color: var(--ink-faint); letter-spacing: .04em; }
.dr-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The chosen bitmap, on a checkerboard so a transparent PNG reads as
   transparent rather than as white-on-white. */
.dr-thumb { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.dr-thumb-box {
  flex: none; width: 74px; height: 50px; display: grid; place-items: center;
  border: 1px solid var(--chrome-line-2); border-radius: 3px; padding: 4px;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #eeeae2 25%, transparent 25%, transparent 75%, #eeeae2 75%),
    linear-gradient(45deg, #eeeae2 25%, transparent 25%, transparent 75%, #eeeae2 75%);
  background-size: 10px 10px; background-position: 0 0, 5px 5px;
}
.dr-thumb-box img { max-width: 100%; max-height: 100%; object-fit: contain; }
.dr-thumb-acts { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.dr-thumb-acts button {
  font-family: var(--font-ui-2); font-size: 10px; letter-spacing: .06em;
  padding: 5px 9px; border-radius: 3px; cursor: pointer;
  border: 1px solid var(--chrome-line-2); background: var(--field); color: var(--ink-soft);
}
.dr-thumb-acts button:hover { background: var(--lift); color: var(--ink); }
.dr-thumb-acts button.is-danger:hover { color: var(--accent-ink); border-color: var(--accent-line); }

/* A whole-row toggle with its own explanation. Same shape as the
   phone's toggles, so the two views teach the same thing. */
.dr-toggle {
  width: 100%; display: flex; align-items: flex-start; gap: 8px; text-align: left;
  padding: 8px 9px; margin-bottom: 9px; cursor: pointer;
  background: var(--field); border: 1px solid var(--chrome-line-2); border-radius: 3px;
}
.dr-toggle:hover { background: var(--lift); }
.dr-toggle[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); }
.dr-toggle:disabled { opacity: .45; cursor: default; }
.dr-toggle .dr-tick {
  flex: none; width: 13px; height: 13px; margin-top: 1px; border-radius: 2px;
  border: 1px solid var(--chrome-line); background: #fff;
}
.dr-toggle[aria-pressed="true"] .dr-tick { background: var(--accent); border-color: var(--accent); }
.dr-toggle .dr-tick::after {
  content: ''; display: block; width: 6px; height: 3px; margin: 3px auto 0;
  border-left: 1.5px solid #fff; border-bottom: 1.5px solid #fff;
  transform: rotate(-45deg); opacity: 0;
}
.dr-toggle[aria-pressed="true"] .dr-tick::after { opacity: 1; }
.dr-toggle .dr-tx { min-width: 0; }
.dr-toggle strong { display: block; font-size: 12px; font-weight: 600; color: var(--ink); }
.dr-toggle span { display: block; font-size: 10.5px; color: var(--ink-faint); line-height: 1.35; margin-top: 2px; }
.dr-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.dr-sub {
  margin: 11px 0 5px;
  font-family: var(--font-ui-2); font-size: 9.5px;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-faint);
}
.dr-hint { font-size: 10.5px; color: var(--ink-faint); line-height: 1.4; margin: -3px 0 9px; }

/* ════════════════ REGION E — STATUS BAR ════════════════ */
.statusbar {
  position: fixed; inset: auto 0 0 0; z-index: 40;
  height: var(--status-h);
  display: flex; align-items: center; gap: 0; padding: 0 8px;
  background: linear-gradient(180deg, var(--chrome), var(--chrome-2));
  border-top: 1px solid var(--chrome-line);
  font-size: 12px; color: var(--ink-soft);
  user-select: none;
}
.sb-item { white-space: nowrap; letter-spacing: .02em; }
.sb-muted { color: var(--ink-faint); }
/* The section the caret is in. Capped, because a section can be called
   anything and the bar has a page count and a wine count to keep. */
.sb-sec { max-width: 210px; overflow: hidden; text-overflow: ellipsis; }
.sb-div { width: 1px; height: 13px; margin: 0 6px; background: var(--chrome-line); flex: none; }

/* ——— The signature ———
   One line, four screens: the end of the desk's status bar and the
   bottom edge of the shelf, the Cellar and the Wine Library. Jost at
   the size the ruler numbers and column labels already use, because
   this is a label rather than a reading — Garamond at 10px in the
   faintest ink turns to lint, while Jost stays a word.

   Not selectable and not a control: it never takes the caret, so a
   ⌘A on the shelf does not sweep it up with the list names.

   It lives in chrome only. The credit that prints belongs to the Logo
   & credit feature and sits on the sheet; this one is the application
   signing itself, and the paginator never sees it. */
.sb-credit {
  flex: none; white-space: nowrap;
  font-family: var(--font-ui-2); font-size: 9.5px;
  letter-spacing: .04em; color: var(--ink-faint);
  user-select: none; padding-right: 2px;
}

/* The rooms are flex columns whose stack scrolls, so this sits below
   the scroll rather than at the end of the content: a fixed bottom
   edge, present on a short list and a long one alike, and never
   scrolled past. 22px of the screen, which is what the line is worth.

   Aligned to each room's own left edge rather than centred, so it
   lands under the first letter of everything above it. */
.app-credit {
  flex: none; margin: 0;
  /* v0.128: the same gutter as the bar above it, in every room. */
  padding: 5px clamp(18px, 5vw, 56px) 8px;
  font-family: var(--font-ui-2); font-size: 9.5px;
  letter-spacing: .04em; color: var(--ink-faint);
  user-select: none;
  border-top: 1px solid var(--chrome-line);
  /* Transparent, because these two sit below their room's scroll area
     rather than over it: nothing passes under them, and a band painted
     --chrome-2 under the Cellar's warmer ground showed as a tone shift
     along the bottom edge. Only the shelf's fixed band needs a fill. */
  background: transparent;
}
/* The shelf scrolls as one page rather than holding a scrolling stack,
   so in normal flow the line sits wherever the content happens to end —
   with one list, a hairline stranded mid-screen above 290px of empty
   ground, which reads as a rule someone forgot to delete. Sticky does
   not help: there is nothing to scroll, so it never leaves its flow
   position.

   Fixed to the bottom of the viewport instead, which is what the other
   two rooms get from their flex column for nothing. Not by making the
   shelf a flex column too: its bar, head and stack would become
   shrinkable items inside a fixed-height box, and a shelf that
   compresses its own cards to fit is a worse bug than the one being
   fixed. The stack already ends in 80px of padding, so the last card
   stays clear of it. Its own background, since content passes under.

   Its left edge is the shelf's clamped one, not the 28px of the other
   two rooms. */
#library .app-credit {
  position: fixed; left: 0; right: 0; bottom: 0;
  background: var(--chrome);
  padding-left: clamp(18px, 5vw, 56px);
  padding-right: clamp(18px, 5vw, 56px);
}
.sb-spring { flex: 1 1 auto; }
.sb-toggle {
  border: 1px solid transparent; background: transparent; border-radius: var(--r);
  padding: 1px 7px; font-size: 12px; color: var(--ink-soft);
}
.sb-toggle:hover { background: var(--lift); border-color: var(--chrome-line-2); }
.sb-toggle.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }

/* The page readout, which is also the numbering menu. Styled as a status
   item first and a control second: no border, no caret, nothing to draw
   the eye — it only declares itself on hover. The setting is chosen once
   a document, so it should not sit in the footer asking to be pressed. */
.sb-menu {
  border: 1px solid transparent; background: transparent; border-radius: var(--r);
  padding: 1px 7px; margin-left: -7px;
  font: inherit; font-size: 12px; color: var(--ink-soft); cursor: pointer;
  transition: color .14s, background .14s, border-color .14s;
}
.sb-menu:hover { background: var(--lift); border-color: var(--chrome-line-2); color: var(--accent-ink); }
.sb-menu[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.sb-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Theme switch. Same quiet footprint as the Ruler toggle beside it,
   sized to the icon. Only one of the two glyphs is ever present: the
   moon in light mode, the sun in dark, so the control always shows
   where pressing it goes rather than where it already is. */
.sb-icon {
  border: 1px solid transparent; background: transparent; border-radius: var(--r);
  padding: 2px 6px; color: var(--ink-soft);
  display: inline-flex; align-items: center; justify-content: center;
  transition: color .14s, background .14s, border-color .14s;
}
.sb-icon:hover { background: var(--lift); border-color: var(--chrome-line-2); color: var(--accent-ink); }
.sb-icon:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sb-icon svg { width: 14px; height: 14px; display: block; }
.sb-icon .th-moon { fill: currentColor; stroke: none; }
.sb-icon .th-sun  { fill: none; stroke: currentColor; stroke-width: 1.45; stroke-linecap: round; }
.sb-icon .th-sun  { display: none; }
[data-theme="dark"] .sb-icon .th-sun  { display: block; }
[data-theme="dark"] .sb-icon .th-moon { display: none; }
.zoom { display: flex; align-items: center; gap: 1px; }
.zoom-btn, .zoom-val {
  border: 1px solid transparent; background: transparent; border-radius: var(--r);
  font-size: 12px; color: var(--ink-soft); padding: 1px 5px; line-height: 1.5;
}
.zoom-val { min-width: 44px; text-align: center; font-variant-numeric: tabular-nums; }
.zoom-btn:hover, .zoom-val:hover { background: var(--lift); border-color: var(--chrome-line-2); }

/* ——— the press ———
   Hover said a thing was live; nothing said it had been struck. Every
   control in the chrome now takes the same one pixel down that .tb-out
   already had, in one place rather than eleven. One pixel, because two
   is a bounce and a bounce in a word processor is a toy.

   Deliberately not on: the sheet's own inserters and grips (they sit on
   paper, and paper does not depress), the cells (a caret is not a
   button), or anything with a transform of its own to lose. */
.tb-btn:active,
.tb-menu:active,
.sb-toggle:active,
.sb-menu:active,
.sb-icon:active,
.zoom-btn:active,
.zoom-val:active,
.drawer-close:active,
.dr-seg button:active,
.dr-toggle:active,
.dr-drop:active,
.dr-thumb-acts button:active,
.menu-item:active,
.lk-item:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .tb-btn:active, .tb-menu:active, .sb-toggle:active, .sb-menu:active,
  .sb-icon:active, .zoom-btn:active, .zoom-val:active, .drawer-close:active,
  .dr-seg button:active, .dr-toggle:active, .dr-drop:active,
  .dr-thumb-acts button:active, .menu-item:active, .lk-item:active,
  .tb-out:active { transform: none; }
}

/* ——— the saved whisper ———
   Nothing in the program ever acknowledged a write. Not a toast, which
   would put a floating card over the sheet nine hundred milliseconds
   after every sentence; one word in the status bar's own small type,
   where the page count and the wine count already live, for a second
   and a half. It says the last thing you typed is on the disk and then
   it stops saying it. */
.sb-saved {
  font-family: var(--font-ui-2); font-size: 9.5px;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-faint);
  opacity: 0; transition: opacity var(--t) var(--ease);
  margin-left: 8px;
}
.sb-saved.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sb-saved { transition: none; } }

/* ════════════════ OVERLAYS ════════════════ */
.overlay {
  position: fixed; inset: 0; z-index: 80;
  background: #6f6b66;
  display: flex; flex-direction: column;
  /* A room rises the same 3px a menu does, in the same curve, a little
     slower because it is the whole screen rather than a panel under the
     cursor. The old rule faded only, which made the shelf appear rather
     than arrive. */
  animation: roomIn var(--t) var(--ease);
}
@keyframes fadeIn { from { opacity: 0; } }
@keyframes roomIn { from { opacity: 0; transform: translateY(3px); } }
@media (prefers-reduced-motion: reduce) {
  .overlay { animation: fadeIn .01s linear; }
  .menu, .lookup { animation: none; }
}
.overlay-bar {
  /* `min-height`, not `height`. `.ov-setup` wraps, and with a fixed
     height the wrapped rows left the bar and drew on top of the paper
     — which is exactly what the phone did at 360px. */
  flex: none; min-height: 46px; display: flex; align-items: center; gap: 8px;
  padding: 0 12px;
  background: linear-gradient(180deg, var(--tb-top), var(--chrome-2));
  border-bottom: 1px solid var(--chrome-line);
}
.ov-title { font-size: 15px; letter-spacing: .05em; color: var(--accent-ink); font-weight: 600; }
.ov-setup { display: flex; align-items: center; gap: 6px; margin-left: 10px; flex-wrap: wrap; }
.ov-setup .seg { display: flex; border: 1px solid var(--chrome-line); border-radius: var(--r); overflow: hidden; background: var(--lift); }
.ov-setup .seg button { border: 0; background: transparent; padding: 3px 8px; font-size: 12px; color: var(--ink-soft); }
.ov-setup .seg button + button { border-left: 1px solid var(--chrome-line-2); }
.ov-setup .seg button.on { background: var(--accent-soft); color: var(--accent-ink); }
.ov-setup .seg-label { font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); margin-right: 1px; }
.overlay-scroll { flex: 1 1 auto; overflow: auto; padding: 28px 0 40px; display: flex; flex-direction: column; align-items: center; gap: 24px; }
.overlay-scroll .page { cursor: default; }
.overlay-scroll .page .cell, .overlay-scroll .page [contenteditable] { pointer-events: none; }
.overlay-scroll .col-labels, .overlay-scroll .sec-grip,
.overlay-scroll .stock-mark, .overlay-scroll .cellar-door, .overlay-scroll .ed-only,
.overlay-scroll .fl-pours.is-empty, .overlay-scroll .fl-dot.is-empty { display: none !important; }
.ov-pagelabel { font-family: var(--font-ui-2); font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.6); }

/* Screen-only editing affordances, dropped for any output route.
   `.cell.is-empty::before` is the greyed column-name placeholder — an
   editing hint, not content. It must not appear in Print Preview, in
   print, or in the PDF, or "what you see is what prints" is a lie. */
body.exporting .page-margin,
body.exporting .inserters,
body.exporting .row-grip,
body.exporting .col-grip,
body.exporting .stock-mark, body.exporting .cellar-door, body.exporting .ed-only,
body.exporting .fl-pours.is-empty, body.exporting .fl-dot.is-empty,
body.exporting .col-labels,
body.exporting .sec-grip,
body.exporting .sec-break,
body.exporting .house-grip,
body.exporting .cell.is-empty::before,
.overlay-scroll .cell.is-empty::before { content: none !important; }
/* An open pass is chrome, and chrome does not print. Belt and
   braces: the pass cannot be open during an export, because the
   export lives on the other side of the room. */
body.exporting .pass { display: none !important; }
body.exporting .blk-row.is-sel { background: transparent; box-shadow: none; }
body.exporting .blk-section.is-sel .sec-title { box-shadow: none; }
/* Flights are content, not chrome: they belong in every export and
   every print. Only the selection tint comes off. */
body.exporting .blk-flight.is-sel { background: transparent; box-shadow: none; }

.busy { position: fixed; inset: 0; z-index: 95; background: var(--scrim); display: grid; place-items: center; }
.busy-card {
  background: var(--lift); border: 1px solid var(--btn-line); border-radius: var(--r);
  padding: 16px 22px; display: flex; align-items: center; gap: 12px;
  font-size: 14px; box-shadow: 0 16px 40px rgba(0,0,0,.3);
}
.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--chrome-line); border-top-color: var(--accent);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.toast {
  position: fixed; left: 50%; bottom: calc(var(--status-h) + 18px); transform: translateX(-50%);
  z-index: 90; background: var(--toast-bg); color: var(--toast-ink);
  font-size: 13.5px; padding: 7px 14px; border-radius: 3px;
  box-shadow: 0 8px 22px rgba(0,0,0,.3);
  animation: toastIn .16s ease-out;
}
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 6px); } }

/* ════════════════ NARROW SCREENS ════════════════ */

/* The full bar is 1473px wide. Above that the wordmark sits beside the
   mark; below it the word steps aside and the chalice carries the
   brand on its own, which is what it was drawn to do. The alternative
   was a toolbar that scrolls sideways on a 1440 laptop, and a control
   you have to go looking for is worse than a logo you have to infer. */
@media (max-width: 1530px) {
  .brand-name { display: none; }
}

@media (max-width: 820px) {
  :root { --drawer-w: 100vw; }
  .brand-name { display: none; }
  .tb-menu-label { max-width: 74px; overflow: hidden; text-overflow: ellipsis; }
  body.drawer-open .desk { right: 0; }
  .sb-item:nth-of-type(3), .sb-div:nth-of-type(2) { display: none; }
}

/* ---------------- Print Preview on a narrow window ----------------
   The bar used to be `height: 46px` with a wrapping `.ov-setup`, so at
   780–900px the wrapped row left the bar and drew on top of the paper.
   `min-height` above is the whole fix; the bar now grows to 55px.

   The three segmented groups deliberately stay, at every width. This
   overlay is only ever reached from the desk shell — the phone shell
   hides the toolbar entirely and never opens it — and on the desk this
   bar is the only place Paper, Margins and Orientation exist. Hiding
   them on a narrow window would stand between a user who chose Desk
   view and the only controls they have. */
@media (max-width: 820px) {
  .overlay-bar { padding: 0 10px; gap: 6px; }
  .overlay-scroll { padding: 16px 0 30px; }
}

@media print {
  /* Paper is paper. The chrome is all hidden below, so in principle no
     themed token reaches the printer — but `pdf-vector.js` reads
     `getComputedStyle().color` off the live sheet to set its text
     colour, and a single sheet rule that ever inherits `--ink` would
     put cream text on white stock in a real PDF. This block makes the
     invariant enforced rather than merely true. */
  [data-theme="dark"] {
    --ink:           #2c2925;
    --ink-soft:      #6b655c;
    --ink-faint:     #938c81;
    --chrome:        #efece6;
    --chrome-2:      #e4e0d8;
    --cellar-ground: #e8e0d2;
    --chrome-line:   #c9c3b8;
    --chrome-line-2: #d9d4ca;
    --lift:          #fff;
    --lift-2:        #fff;
    --field:         #fff;
    --accent:        #7a1f2b;
    --accent-ink:    #7a1f2b;
    --on-accent:     #fff;
    --desk:          #fff;
    --desk-2:        #fff;
    --desk-glow:     transparent;
    --page-shadow:   none;
  }
  .toolbar, .statusbar, .drawer, .ruler-slot, .overlay-bar, .inserters,
  .row-grip, .col-grip, .col-labels, .sec-grip, .stock-mark, .cellar-door, .ed-only, .page-margin, .toast, .busy,
  .fl-pours.is-empty, .fl-dot.is-empty,
  .sec-break, .house-grip, .pass,
  #pdfReady, #htmlReady { display: none !important; }
  .cell.is-empty::before { content: none !important; }
  html, body { overflow: visible; background: #fff; height: auto; }
  .desk, .overlay { position: static; inset: auto; overflow: visible; background: #fff; }
  .overlay-scroll { padding: 0; display: block; }
  .stack-wrap { padding: 0; }
  .stack { transform: none !important; gap: 0; }
  .ov-pagelabel { display: none; }
  .page {
    box-shadow: none !important; break-after: page; page-break-after: always;
    margin: 0 auto;
  }
  .page::after { display: none; }
  .page:last-child { break-after: auto; page-break-after: auto; }
}

.page-masthead { position: absolute; }

/* ═══════════════ PDF-ready panel ═══════════════
   Delivery surface for environments that refuse a programmatic download
   (the sandboxed app-preview frame). A single quiet card on the desk:
   what it is, why you are seeing it, and the two ways to take it. */
.pdf-overlay {
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--scrim);
  backdrop-filter: blur(2px);
}
.pdf-card {
  width: min(420px, 100%); box-sizing: border-box;
  padding: 30px 32px 22px; text-align: center;
  background: var(--lift-2); border: 1px solid var(--chrome-line-2); border-radius: var(--r);
  box-shadow: 0 18px 48px rgba(0,0,0,.30), 0 2px 6px rgba(0,0,0,.14);
  animation: pdfCardIn .22s cubic-bezier(.2,.8,.3,1) both;
}
@keyframes pdfCardIn { from { opacity: 0; transform: translateY(6px) scale(.985); } }
@media (prefers-reduced-motion: reduce) { .pdf-card { animation: none; } }

.pdf-mark { width: 34px; height: 34px; color: var(--accent-ink); opacity: .85; }
.pdf-head {
  margin: 10px 0 0; font-family: var(--font-display, var(--font-ui));
  font-size: 21px; font-weight: 600; letter-spacing: .1px; color: var(--ink);
}
.pdf-file {
  margin: 6px 0 0; font-family: var(--font-ui); font-size: 13px;
  color: var(--ink); opacity: .9; word-break: break-word;
}
.pdf-size { opacity: .6; }
.pdf-size:not(:empty)::before { content: ' · '; }
.pdf-note {
  margin: 14px 0 0; font-family: var(--font-ui); font-size: 13px;
  line-height: 1.55; color: #5c554b; text-align: left;
}
.pdf-actions {
  display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;
  margin-top: 20px;
}
.pdf-actions .tb-out { padding: 7px 15px; }
.pdf-dismiss {
  margin: 14px 0 0; padding: 4px 8px; border: 0; background: none;
  font-family: var(--font-ui); font-size: 12.5px; color: #8a8177;
  cursor: pointer; border-radius: 3px;
}
.pdf-dismiss:hover { color: var(--ink); background: rgba(0,0,0,.05); }
.pdf-dismiss:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ——— Split export control ———
   The two halves have to read as one control, so the seam is a single
   inside hairline and the outer radii survive. */
.tb-split { display: inline-flex; }
.tb-split > .tb-out:first-child {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
}
/* v0.26.0 — 7px of padding either side of a 10px caret made a 21px
   target sitting flush against a 120px one. A near-miss did not do
   nothing, it exported a PDF, which is the worse of the two failures.
   10px brings it to 27px. Row one has several hundred pixels of slack
   at 1280 so the six pixels cost nothing. */
.tb-split-more {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  padding-left: 10px; padding-right: 10px;
  box-shadow: inset 1px 0 0 var(--bevel-soft);
}
.tb-split-more .tb-caret { font-size: 10px; line-height: 1; }

/* ——— HTML snippet panel ——— */
.html-card { width: min(620px, 100%); }
.html-code {
  display: block; width: 100%; height: 148px; resize: vertical;
  margin: 14px 0 0; padding: 10px 12px;
  font-family: var(--font-mono, ui-monospace, Menlo, monospace);
  font-size: 11.5px; line-height: 1.5;
  color: var(--ink-soft); background: var(--lift-2);
  border: 1px solid var(--line, #ddd7cd); border-radius: var(--r);
  white-space: pre; overflow: auto; tab-size: 2;
}
.html-code:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

a.tb-out { text-decoration: none; display: inline-flex; align-items: center; }
/* Anchors in the card are buttons, not links — the browser's blue would
   be the only saturated colour on the sheet. */
.pdf-actions a.tb-out { color: var(--ink); }
.pdf-actions a.tb-out:visited { color: var(--ink); }
.pdf-actions .tb-out-primary,
.pdf-actions a.tb-out-primary { color: var(--on-accent); }

@media (max-width: 820px) {
  .pdf-card { padding: 24px 20px 18px; }
  .pdf-head { font-size: 19px; }
  .pdf-actions { flex-direction: column; }
  .pdf-actions .tb-out { justify-content: center; }
}

/* ---------------- Page borders ----------------
   The border box is page furniture: it sits above the paper and below nothing,
   takes no pointer events, and is never a drop target. Its children are
   positioned in px from the same numbers TSBorders hands the PDF, so there is
   no CSS here that describes a measurement. */
.page-border {
  position: absolute;
  pointer-events: none;
  z-index: 1;
}
.page-border img {
  display: block;
  /* Nearest-neighbour would alias the engraving's hatching at 1/4 scale;
     the tiles are 8-grey so smooth downscaling is the right call. */
  image-rendering: auto;
  user-select: none;
}
/* Print: the frame is content, not chrome. Nothing here may be display:none —
   TSPdf measures .page geometry and the print stylesheet must agree with it. */
@media print {
  .page-border { z-index: 1; }
}

/* Border picker */
.menu-border .bgrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  padding: 4px 8px 8px;
}
.menu-border .bcard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 7px 3px 6px;
  border: 1px solid transparent;
  border-radius: var(--r);
  background: none;
  cursor: pointer;
  font: inherit;
  color: var(--ink-soft);
}
.menu-border .bcard:hover { background: var(--chrome-2); }
.menu-border .bcard.sel {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--ink);
}
.menu-border .bsw {
  display: block;
  width: 44px;
  height: 56px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .16);
}
.menu-border .bsw svg { display: block; width: 100%; height: 100%; }
.menu-border .bcl {
  font-family: var(--font-ui-2);
  font-size: 10px;
  line-height: 1.15;
  text-align: center;
  letter-spacing: .01em;
}
/* Sticky, because the placement choice must be reachable without scrolling
   past fourteen swatches to find it. */
.menu-border { display: flex; flex-direction: column; }
.menu-border .menu-foot {
  position: sticky;
  bottom: -4px;
  margin: 4px -4px -4px;
}
.menu-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-top: 1px solid var(--chrome-line-2);
  background: var(--chrome-2);
  border-radius: 0 0 var(--r) var(--r);
}
.menu-foot .mf-lab {
  font-family: var(--font-ui-2);
  font-size: 11px;
  color: var(--ink-soft);
}
.menu-foot .mf-seg { display: flex; gap: 0; }
.menu-foot .mf-seg button {
  font-family: var(--font-ui-2);
  font-size: 11px;
  padding: 4px 9px;
  border: 1px solid var(--chrome-line);
  background: var(--chrome);
  color: var(--ink-soft);
  cursor: pointer;
}
.menu-foot .mf-seg button:first-child { border-radius: var(--r) 0 0 var(--r); }
.menu-foot .mf-seg button:last-child { border-radius: 0 var(--r) var(--r) 0; border-left: 0; }
.menu-foot .mf-seg button.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* ════════════════ TEMPLATE PICKER ════════════════
   Eighteen templates is too many for a plain list, and a list would
   also be the wrong shape: what distinguishes these is how they LOOK,
   so each card is set in its own face and carries a thumbnail of its
   grid. Two columns per group, grouped by what the choice is about —
   the service format (what the sheet sells) above, the house voice
   (how it reads) below. */
/* Taller than the other menus on purpose: eighteen cards in two groups
   fit a laptop screen in one view, and a picker you have to scroll to
   discover half of is a picker that hides half its choices. */
.menu-tpl {
  display: flex; flex-direction: column; width: 468px;
  max-height: min(calc(100vh - 76px), 760px);
}
.menu-tpl .tgrid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 3px; padding: 2px 6px 7px;
}
.menu-tpl .tcard {
  display: flex; gap: 9px; align-items: center; text-align: left;
  padding: 7px 7px; border: 1px solid transparent; border-radius: var(--r);
  background: transparent; color: var(--ink); cursor: pointer; width: 100%;
}
.menu-tpl .tcard:hover { background: var(--chrome-2); }
.menu-tpl .tcard.sel { border-color: var(--accent-line); background: var(--accent-soft); }
.menu-tpl .tcard .tpl-thumb { flex: none; }
.menu-tpl .tc-body { display: block; min-width: 0; }
.menu-tpl .tc-name {
  display: block; font-size: 15px; line-height: 1.2;
  margin-bottom: 1px; letter-spacing: .01em;
}
.menu-tpl .tc-sub {
  display: block; font-family: var(--font-ui-2); font-size: 10px;
  line-height: 1.3; color: var(--ink-soft);
}
.menu-tpl .tcard.sel .tc-sub { color: var(--ink); }
.menu-tpl .menu-foot { position: sticky; bottom: -4px; margin: 0 -4px -4px; }
.menu-tpl .menu-foot .mf-lab { line-height: 1.35; }

/* ════════════════ HOUSE VOICES ════════════════
   The fourteen typographic voices ported from v7.2.99, each keyed by
   `data-voice` on #stack (and on #measureHost, so pagination measures
   what the sheet actually paints).

   THREE RULES GOVERN EVERY BLOCK BELOW.

   1. Only real elements, never ::before or ::after. The PDF writer
      walks the live DOM and reads computed styles off elements; a
      pseudo-element is invisible to it, so a rule drawn that way would
      show on screen and vanish in the export. That is the one bug
      class this app cannot ship.
   2. Only weights 400 and 700. Those are the two faces shipped for
      each family; asking for 500 or 600 gets a synthesised weight on
      screen that the embedded TTF cannot reproduce in print.
   3. No colour but ink. The sheet has to survive a black-and-white
      laser at 4pm on a Friday, so hierarchy comes from size, case,
      tracking and rules — never from hue.

   `--name-scale` reproduces v7.2.99's separate name-size multiplier:
   there, a preset stored a base size AND a factor for the wine name.
   Here the toolbar owns the base and the voice owns the factor, which
   keeps the old proportions while leaving the user one number to turn
   and no numeric field to turn it in. */
.cell.col-wine,
.blk-row.prose .prose-head .cell.wine {
  font-size: calc(1em * var(--name-scale, 1));
}

/* ---- Bistro — warm serif, hairline, no caps ---- */
[data-voice="bistro"] { --name-scale: 1.20; }
[data-voice="bistro"] .sec-title {
  text-transform: none; letter-spacing: .01em; font-size: 1.2em;
  font-style: italic; padding-bottom: .2em;
}

/* ---- Editorial — small tracked caps; price folded onto the name line.
   v7.2.99 ran the price into the detail line after a slash. Here the
   stacked prose row already puts the detail underneath, so the price
   sits at the end of the name line and the dot leader is suppressed —
   a leader across three ems of gap reads as a mistake. ---- */
[data-voice="editorial"] { --name-scale: .95; }
[data-voice="editorial"] .sec-title { font-size: .88em; letter-spacing: .26em; }
/* v0.104: Editorial's leaders are off by default through the template
   (leader: 'none'), not hidden here, so the Leaders menu can turn them on. */
[data-voice="editorial"] .blk-row.prose .prose-head { gap: 1.1em; }
[data-voice="editorial"] .blk-row.prose .prose-head .cell.wine {
  text-transform: uppercase; letter-spacing: .07em;
}
[data-voice="editorial"] .blk-row.prose .prose-head .cell.num { margin-left: auto; }
[data-voice="editorial"] .blk-row.prose .prose-detail { font-size: .9em; }

/* ---- Prose — the tasting-note voice; detail set as running text ---- */
[data-voice="prose"] .sec-title { font-size: .96em; letter-spacing: .2em; }
[data-voice="prose"] .blk-row.prose .prose-detail {
  font-style: normal; font-size: .95em; color: #5c554d;
}

/* ---- Book — dense hardcover; italic heads, no rule ---- */
[data-voice="book"] { --name-scale: 1.10; }
[data-voice="book"] .sec-title {
  border-bottom: 0; text-transform: none; font-style: italic;
  letter-spacing: .01em; font-size: 1.22em; padding-bottom: .06em;
}
/* v7.2.99 set these heads in the accent burgundy. Nothing on this
   sheet prints in colour, so the emphasis is carried by the italic and
   the extra notch of scale instead. */

/* ---- Typewriter — inked ribbon, heavy break ---- */
[data-voice="typewriter"] { --name-scale: 1.15; }
[data-voice="typewriter"] .sec-title {
  border-bottom: 2px solid #4a4640; letter-spacing: .2em;
  font-size: .94em; font-weight: 700; padding-bottom: .3em;
}
[data-voice="typewriter"] .mast-title {
  text-transform: uppercase; letter-spacing: .1em; font-size: 1.5em;
}
[data-voice="typewriter"] .mast-rule { display: none; }

/* ---- Próximo — wide caps, dot leaders to the price ---- */
[data-voice="proximo"] { --name-scale: 1.10; }
[data-voice="proximo"] .sec-title {
  border-bottom: 0; letter-spacing: .32em; font-size: .9em; padding-bottom: 0;
}
[data-voice="proximo"] .blk-row.prose .prose-head .cell.wine {
  text-transform: uppercase; letter-spacing: .05em;
}

/* ---- Contemporary — geometric sans, open, unruled ---- */
[data-voice="contemporary"] { --name-scale: 1.05; }
[data-voice="contemporary"] .sec-title {
  border-bottom: 0; text-transform: none; letter-spacing: .03em;
  font-size: 1.12em; padding-bottom: 0;
}

/* ---- Étoile — ledger doubles.
   TWO real 1px borders, one on the block and one on the title, never
   `border-style: double`: the PDF writer reads a border width and a
   colour and draws one line, so a doubled style would print single. ---- */
[data-voice="etoile"] { --name-scale: 1.10; }
[data-voice="etoile"] .blk-section { border-bottom: 1px solid #cfc8bd; padding-bottom: 3px; }
[data-voice="etoile"] .sec-title { letter-spacing: .24em; font-size: .92em; }

/* ---- Modern — dense sans; whitespace is the divider ---- */
[data-voice="modern"] { --name-scale: 1.05; }
[data-voice="modern"] .sec-title {
  border-bottom: 0; letter-spacing: .22em; font-size: .8em; padding-bottom: 0;
}

/* ---- Grand Cru — ceremonial tome, doubles, large names ---- */
[data-voice="grandcru"] { --name-scale: 1.22; }
[data-voice="grandcru"] .blk-section { border-bottom: 1px solid #c9c2b6; padding-bottom: 3px; }
[data-voice="grandcru"] .sec-title {
  border-bottom: 1px solid #c9c2b6; text-transform: none;
  letter-spacing: 0; font-size: 1.26em; font-weight: 700;
}

/* ---- Trattoria — the tightest list in the app ---- */
[data-voice="trattoria"] { --name-scale: 1.25; }
[data-voice="trattoria"] .sec-title { letter-spacing: .16em; font-size: .94em; padding-bottom: .2em; }

/* ---- Cellar Book — monospaced staff reference ---- */
[data-voice="cellar"] .sec-title {
  letter-spacing: .14em; font-size: .92em; font-weight: 700;
  border-bottom: 1px solid #cfc8bd;
}
[data-voice="cellar"] .mast-title { font-size: 1.4em; letter-spacing: .04em; }

/* ---- Steakhouse — formal chophouse, heavy break ---- */
[data-voice="steakhouse"] { --name-scale: 1.22; }
[data-voice="steakhouse"] .sec-title {
  border-bottom: 2px solid #4a4640; letter-spacing: .22em;
  font-size: .94em; padding-bottom: .32em;
}

/* ---- Aperitivo — script section heads.
   The one voice that changes face rather than treatment. `--head-font`
   is set on the stack by applyTypeVars(), so the script never enters
   the Font menu and can never become a body face. Tangerine ships
   normal-only, hence weight 400 and no italic anywhere near it. ---- */
[data-voice="aperitivo"] { --name-scale: 1.15; }
[data-voice="aperitivo"] .sec-title {
  font-family: var(--head-font);
  text-transform: none; letter-spacing: 0; font-weight: 400;
  font-size: 2.3em; line-height: 1.02;
  border-bottom: 0; padding-bottom: 0;
}
[data-voice="aperitivo"] .mast-title {
  font-family: var(--head-font); font-size: 3em; line-height: 1;
  letter-spacing: 0;
}

/* ---- Bar à Vin (v0.103) — the natural-wine bar card.
   Everything lowercase, the heads set big and bold in the sans, and no
   rules at all; space does the dividing, as it does on a chalkboard. ---- */
[data-voice="barvin"] { --name-scale: 1.04; }
[data-voice="barvin"] .blk-masthead,
[data-voice="barvin"] .blk-section { text-transform: lowercase; }
[data-voice="barvin"] .mast-title {
  font-weight: 700; font-size: 2.4em; letter-spacing: -.02em; line-height: 1;
  text-transform: lowercase;
}
[data-voice="barvin"] .mast-sub { text-transform: lowercase; letter-spacing: .02em; font-size: .95em; }
[data-voice="barvin"] .mast-rule { display: none; }
[data-voice="barvin"] .sec-title {
  text-transform: lowercase; letter-spacing: -.01em; font-weight: 700;
  font-size: 1.5em; line-height: 1.1; border-bottom: 0; padding: .5em 0 .12em;
}
[data-voice="barvin"] .blk-row .cell { text-transform: lowercase; }

/* ---- Gazette (v0.103) — a broadsheet.
   Heavy-over-thin rule under a bold capital masthead, made of two real
   borders on one element so the PDF draws both; section heads between
   a heavy rule above and a hairline below; two columns of entries. ---- */
[data-voice="gazette"] { --name-scale: 1.08; }
[data-voice="gazette"] .mast-title {
  font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  font-size: 2.3em; line-height: 1.05;
}
[data-voice="gazette"] .mast-sub { font-style: italic; text-transform: none; letter-spacing: .02em; font-size: .95em; }
[data-voice="gazette"] .mast-rule {
  width: 100%; height: 2px; background: none;
  border-top: 3px solid #26221e; border-bottom: 1px solid #26221e;
  margin: 0 0 1.3em;
}
[data-voice="gazette"] .sec-title {
  font-weight: 700; letter-spacing: .16em; font-size: .86em;
  border-top: 2px solid #26221e; border-bottom: 1px solid #8f877c;
  padding: .32em 0 .26em;
}
[data-voice="gazette"] .blk-row.prose .prose-head .cell.wine { font-weight: 700; }
[data-voice="gazette"] .blk-row.prose .prose-detail { font-style: italic; }
/* A bold name runs wide; the leader starts a clear space after it. */
[data-voice="gazette"] .blk-row.prose .leader { margin-left: .45em; margin-right: .3em; }

/* The language switch. Two letters rather than a flag or a globe: a
   flag names a country and Spanish is not one country, and a globe at
   14px is a grey circle. The letters are the same tracked caps the
   status bar already uses for its other labels, so it reads as part of
   the bar and not as a badge stuck onto it.

   It shows the language it switches *to*, matching the theme moon. */
.sb-lang { padding: 2px 5px; }
.sb-lang > span {
  font: 600 9px/1 var(--font-ui-2);
  letter-spacing: .09em;
  font-variant-numeric: tabular-nums;
  display: block;
}

/* ——— The cellar's mark ———
   Five pixels in the page's outside margin, on the same vertical lane
   as the section grip, saying one of two things: this wine is counted
   to zero (filled), or there is one bottle left (hollow). It is drawn
   in --accent-paper-line, the rose the sheet already uses for a dashed
   rule, because it belongs to the paper's own palette and not to the
   chrome's.

   It is NOT the sold-out flag. That one strikes the row through and
   prints, because somebody decided it; this one only observes, and
   never prints. The two look nothing alike on purpose — an
   observation that dresses as a decision will sooner or later be
   read as one.

   No number and no text. The count is a fact about the room, not about
   the document, and the one thing this sheet must never learn is how
   many bottles are behind it. */
.stock-mark {
  /* The RIGHT outside margin since v0.59: the left one is the list's
     (its grips), the right one the Cellar's (this mark and the door
     beside the selected wine). Everything on the page that comes from
     the Cellar is on one side. */
  position: absolute; right: -15px; top: .42em;
  width: 5px; height: 5px; border-radius: 50%;
  box-sizing: border-box; background: transparent;
  border: 1px solid var(--accent-paper-line);
  z-index: 6;
}
.blk-row[data-stock="out"] > .stock-mark { background: var(--accent-paper-line); }

/* The status bar's own half of it. One item, and only when there is
   something to say: an uncounted house prints nothing here at all.
   The hairline is drawn on the item rather than by a sibling .sb-div,
   so there is no separator left hanging when the item hides. */
#sbStock {
  position: relative; margin-left: 13px; padding-left: 0;
  color: var(--accent-ink); letter-spacing: .02em;
}
#sbStock::before {
  content: ''; position: absolute; left: -7px; top: 50%;
  width: 1px; height: 13px; margin-top: -6.5px;
  background: var(--chrome-line);
}

/* ============================================================
   THE PASS  —  one wine, filling the screen (js/pass.js)

   A full-field card rather than a panel over the rack: the whole
   claim of a pass is that the wine in front of you is the only wine
   on screen. Built for the hand and centred on the laptop, where the
   same card is simply smaller than its field.
   ============================================================ */

.pass {
  position: fixed; inset: 0; z-index: 900;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--desk) 82%, #000 18%);
  padding: 22px;
}
body.pass-open { overflow: hidden; }

.pass-sheet {
  width: min(420px, 100%);
  max-height: 100%;
  display: flex; flex-direction: column;
  background: var(--paper);
  border-radius: var(--r);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .34);
  overflow: hidden;
}

.pass-head {
  display: flex; align-items: baseline; gap: 12px;
  padding: 13px 16px 11px;
  border-bottom: 1px solid var(--chrome-line);
  background: var(--chrome);
}
.pass-shelf {
  font-family: var(--font-ui-2); font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-ink);
}
.pass-count {
  font-family: var(--font-ui-2); font-size: 8.5px;
  letter-spacing: .14em; color: var(--ink-faint);
  margin-left: auto;
}
.pass-x {
  font-family: var(--font-ui-2); font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint);
  background: none; border: 0; padding: 2px 0; cursor: pointer;
}
.pass-x:hover { color: var(--accent); }
/* With no progress figure to push it, the way out still belongs at
   the right edge. */
.pass-sheet.is-end .pass-x { margin-left: auto; }

.pass-body {
  padding: 26px 22px 22px;
  overflow: auto;
  text-align: center;
}
.pass-wine {
  font-family: var(--font-ui); font-weight: 400;
  font-size: 22px; line-height: 1.25;
  color: var(--paper-ink); margin: 0;
}
.pass-meta {
  font-family: var(--font-ui); font-size: 13px;
  color: var(--ink-faint); margin: 7px 0 0;
}
.pass-last {
  font-family: var(--font-ui-2); font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint); margin: 14px 0 0;
}

/* The two controls and the number, all one row, all thumb-sized. The
   number is the largest type in the program on purpose: it is read at
   arm's length in a cold room. */
.pass-dial {
  display: flex; align-items: center; justify-content: center;
  gap: 18px; margin: 20px 0 4px;
}
.pass-step {
  width: 64px; height: 64px;
  flex: 0 0 auto;
  display: grid; place-items: center;
  font-family: var(--font-ui-2); font-size: 26px; line-height: 1;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pass-step:hover { background: color-mix(in srgb, var(--accent-soft) 74%, var(--accent) 26%); }
.pass-step:active { transform: translateY(1px); }
.pass-step[disabled] { opacity: .34; cursor: default; transform: none; }
.pass-n {
  font-family: var(--font-ui); font-size: 54px; line-height: 1;
  color: var(--paper-ink);
  min-width: 2.2ch;
  font-variant-numeric: tabular-nums;
}

.pass-foot {
  display: flex; gap: 0;
  border-top: 1px solid var(--chrome-line);
}
.pass-skip, .pass-go {
  flex: 1 1 0; padding: 16px 10px;
  font-family: var(--font-ui-2); font-size: 10.5px;
  letter-spacing: .12em; text-transform: uppercase;
  background: none; border: 0; cursor: pointer;
}
.pass-skip { color: var(--ink-faint); border-right: 1px solid var(--chrome-line); }
.pass-skip:hover { color: var(--paper-ink); }
.pass-go { color: var(--accent); flex: 1.6 1 0; }
.pass-go:hover { background: var(--accent-soft); }

/* The end card: what moved, and nothing else. */
.pass-sum { list-style: none; margin: 12px 0 0; padding: 0; text-align: left; }
.pass-sum li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--chrome-line);
}
.pass-sum li:last-child { border-bottom: 0; }
.pass-sum-w { font-family: var(--font-ui); font-size: 14px; color: var(--paper-ink); }
.pass-sum-n {
  margin-left: auto; white-space: nowrap;
  font-family: var(--font-ui-2); font-size: 10px;
  letter-spacing: .06em; color: var(--accent-ink);
}

/* The way in: under the masthead, beside the line that says when the
   room was last counted. */
.hs-mast-count {
  display: inline-block; margin-top: 9px;
  font-family: var(--font-ui-2); font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.hs-mast-count:hover { color: var(--paper-ink); }

/* Count the cellar, on the day nothing has been counted — v0.56.0.
   The Add button's weight, for as long as counting is the obvious next
   thing; the first count returns it to the quiet line above. */
.hs-mast-count.is-first {
  display: inline-block; margin-top: 12px;
  width: auto; align-self: flex-start; justify-self: start;
  height: 30px; padding: 0 15px; border-radius: 3px;
  border: 1px solid var(--accent);
  background: var(--accent); color: #fff;
  font-family: var(--font-ui); font-size: 13.5px;
  letter-spacing: 0; text-transform: none;
}
.hs-mast-count.is-first:hover { color: #fff; filter: brightness(1.08); }
.hs-mast-count.is-first:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }



/* The empty dial — v0.58.0. A dash in the faint ink until the first
   step; Next waits, visibly, until there is a number. A held step
   must not become a long-press menu or a text selection. */
.pass-n.is-empty { color: var(--ink-faint); }
.pass-go[disabled] { color: var(--ink-faint); opacity: .55; cursor: default; background: none; }
.pass-step { touch-action: manipulation; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }

/* "On the sheet" on a reference-shelf row — v0.58.0. A remark in the
   meta line, in the accent, not a badge. */
.ml-there { color: var(--accent); }
/* The keyboard, said once, on the laptop only. */
.pass-keys { margin: 0 0 14px; text-align: center; font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: var(--ink-faint); }
body.mobile .pass-keys { display: none; }

/* ---------------- the Cellar's door on the page — v0.59 ----------------
   A 22px square in the right-hand lane, level with the selected wine,
   on paper white with a hairline ring. The stock dot stays nearer the
   text; the door sits outside it. */
.cellar-door {
  position: absolute; right: -44px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 3px; cursor: pointer;
  color: var(--accent-paper); background: #fff;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-paper) 20%, transparent), 0 2px 6px -2px rgba(60, 40, 20, .3);
  z-index: 7;
  transition: box-shadow .18s ease, transform .18s ease;
}
.cellar-door svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.4; }
.cellar-door:hover { transform: translateY(calc(-50% - 1px)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-paper) 34%, transparent), 0 6px 12px -4px rgba(60, 40, 20, .35); }
.cellar-door.is-fresh { animation: door-in .5s ease-out; }
@keyframes door-in { from { opacity: 0; transform: translateY(-50%) scale(.7); } to { opacity: 1; transform: translateY(-50%) scale(1); } }
@media (prefers-reduced-motion: reduce) { .cellar-door.is-fresh { animation: none; } }

/* ---------------- the toolbar's dot — v0.59 ---------------- */
#btnHouseLib { position: relative; }
#btnHouseLib[data-stock]::after {
  content: ''; position: absolute; right: 2px; top: 2px;
  width: 7px; height: 7px; border-radius: 50%; box-sizing: border-box;
  box-shadow: 0 0 0 2px var(--chrome);
}
#btnHouseLib[data-stock="out"]::after { background: var(--accent-ink); }
#btnHouseLib[data-stock="low"]::after { background: var(--chrome); border: 1.5px solid var(--accent-ink); }

/* ---------------- the Cellar's band in Wine details — v0.59 ---------------- */
.cellar-band {
  display: flex; align-items: center; gap: 12px;
  margin: -2px 0 16px; padding: 12px;
  border-radius: 12px;
  background: var(--bridge-ground);
  border: 1px solid var(--bridge-edge);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .55) inset, 0 4px 14px -10px rgba(60, 40, 20, .35);
}
[data-theme="dark"] .cellar-band { box-shadow: 0 4px 14px -10px rgba(0, 0, 0, .7); }
.cellar-band .cb-ic {
  flex: none; width: 32px; height: 32px; border-radius: 9px;
  display: grid; place-items: center;
  color: var(--accent-ink); background: var(--bridge-tile);
}
.cellar-band .cb-ic svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.4; }
.cellar-band .cb-tx { display: flex; flex-direction: column; min-width: 0; font-family: var(--font-ui); font-size: 14px; line-height: 1.3; color: var(--ink); }
.cellar-band .cb-tx small {
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-ink); margin-bottom: 2px;
}
.cellar-band.is-absent .cb-tx > span { color: var(--ink-soft); }
.cellar-band .cb-go {
  margin-left: auto; flex: none; white-space: nowrap;
  padding: 7px 11px; border: 0; border-radius: 3px; cursor: pointer;
  background: var(--lift); color: var(--accent-ink);
  font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase;
  box-shadow: 0 0 0 1px var(--bridge-ring);
  transition: transform .18s ease, box-shadow .18s ease;
}
.cellar-band .cb-go:hover { transform: translateY(-1px); box-shadow: 0 0 0 1px var(--bridge-ring), 0 6px 12px -6px rgba(60, 40, 20, .35); }

/* ---------------- Find a wine — v0.61 ----------------
   Drawn as a search box: paper-white, a hairline, the magnifier, the
   words, and the shortcut at the right end. Square 3px like every
   other control on the bar. It is a button that looks like a field,
   because the field it stands for is in the Wine Library, where the
   results have room. */
.tb-find {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  height: 26px; width: 168px; box-sizing: border-box;
  padding: 0 5px 0 8px; margin-left: 4px;
  border: 1px solid var(--chrome-line-2); border-radius: var(--r);
  background: var(--lift); color: var(--ink-soft);
  font-family: var(--font-ui); font-size: 13px; text-align: left;
  cursor: text;
  box-shadow: 0 1px 2px rgba(60, 40, 20, .06) inset;
  transition: border-color .12s, box-shadow .12s, color .12s;
}
.tb-find svg { width: 14px; height: 14px; flex: none; fill: none; stroke: var(--accent-ink); stroke-width: 1.6; stroke-linecap: round; }
.tb-find .tbf-label { flex: 1 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tb-find .tbf-key {
  flex: none; font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .04em;
  padding: 2px 4px; border-radius: 3px;
  color: var(--ink-faint); background: var(--chrome); border: 1px solid var(--chrome-line);
}
.tb-find:hover { border-color: var(--accent-line); color: var(--ink); }
.tb-find:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
body.master-open .tb-find { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-ink); }


/* ============================================================
   FLIGHTS IN THE EDITOR — v0.62
   Everything below is editing furniture (`ed-only`): it is stripped
   from Preview, print, the PDF, the HTML export and the phone.
   ============================================================ */
/* An empty Pours line is shown only on the selected flight. */
.fl-pours.is-empty, .fl-dot.is-empty { display: none; }
.blk-flight.is-sel .fl-pours.is-empty, .blk-flight.is-sel .fl-dot.is-empty { display: block; }
.blk-flight.is-sel .fl-dot.is-empty { opacity: .5; }

/* The bracket: a cap on the heading, one segment per member, a foot on
   the last. 2px in the paper accent at low strength, 14px out into the
   left margin, clear of the selection bar at 6px. */
.fl-cap {
  position: absolute; left: -14px; top: 50%; bottom: -4px; width: 6px;
  border-left: 2px solid rgba(122, 31, 43, .36);
  border-top: 2px solid rgba(122, 31, 43, .36);
  border-top-left-radius: 3px;
  pointer-events: none;
}
.fl-cap.is-none { border-left-color: transparent; }
.fl-count {
  position: absolute; right: calc(100% + 7px); top: -6px;
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; white-space: nowrap;
  color: rgba(122, 31, 43, .72);
}
.fl-cap.is-none .fl-count { color: rgba(44, 41, 37, .45); }
.fl-brk {
  position: absolute; left: -14px; top: 0; bottom: 0; width: 2px;
  background: rgba(122, 31, 43, .36);
}
.fl-brk.is-foot { bottom: 45%; }
.fl-brk.is-foot::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 6px; height: 2px;
  background: inherit; border-bottom-left-radius: 2px;
}
.fl-foot {
  position: absolute; left: -5px; bottom: -6px; width: 12px; height: 12px;
  box-sizing: border-box; border-radius: 3px; cursor: ns-resize;
  background: #fff; box-shadow: 0 0 0 1px rgba(122, 31, 43, .34), 0 1px 3px rgba(60, 40, 20, .2);
  opacity: .0; transition: opacity .15s ease, box-shadow .15s ease;
}
.fl-foot::before {
  content: ''; position: absolute; left: 3px; right: 3px; top: 4px; height: 3px;
  border-top: 1px solid rgba(122, 31, 43, .6); border-bottom: 1px solid rgba(122, 31, 43, .6);
}
.blk-row:hover .fl-foot, .blk-row.is-sel .fl-foot, body.set-dragging .fl-foot { opacity: 1; }
.fl-foot:hover { box-shadow: 0 0 0 1px rgba(122, 31, 43, .6), 0 3px 8px -2px rgba(60, 40, 20, .35); }
.fl-brk.is-ghost { display: none; }
body.set-dragging .fl-brk.is-ghost { display: block; }
body.set-dragging .fl-brk.is-out { opacity: 0; }
body.set-dragging .fl-brk.is-foot { bottom: 0; }
body.set-dragging .fl-brk { bottom: 0; }
body.set-dragging .fl-brk.is-foot:not(.is-out) { bottom: 45%; }
body.set-dragging, body.set-dragging * { cursor: ns-resize !important; }

/* The glass sum a set is priced against, in the right margin. */
.fl-guide {
  position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
  font-family: var(--font-ui-2); font-size: 11px; line-height: 1.15;
  color: rgba(122, 31, 43, .78); white-space: nowrap; pointer-events: none;
}
.fl-guide small { display: block; font-size: 7.5px; letter-spacing: .12em; text-transform: uppercase; color: rgba(44, 41, 37, .5); }

/* ---------------- the flight's panel ---------------- */
.fp-price { display: flex; align-items: center; gap: 10px; }
.fp-price-v { font-family: var(--font-ui); font-size: 16px; color: var(--ink); min-width: 2.5em; }
.fp-price-v.is-empty { color: var(--ink-faint); font-size: 14px; }
.fp-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 6px 10px; border: 0; border-radius: 3px; cursor: pointer;
  background: var(--lift); color: var(--accent-ink);
  font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase;
  box-shadow: 0 0 0 1px var(--chrome-line-2);
  transition: box-shadow .15s ease, transform .15s ease;
}
.fp-btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 0 0 1px var(--accent-line), 0 4px 10px -6px rgba(60, 40, 20, .35); }
.fp-btn:disabled { opacity: .4; cursor: default; }
.fp-sq { width: 30px; height: 30px; padding: 0; font-size: 16px; letter-spacing: 0; }
.fp-wide { width: 100%; margin: 6px 0 8px; padding: 9px 10px; }
.fp-step { display: flex; align-items: center; gap: 10px; }
.fp-n { font-family: var(--font-ui); font-size: 20px; min-width: 1.4em; text-align: center; color: var(--ink); }
.fp-guide { margin: 6px 0 0; font-family: var(--font-ui-2); font-size: 10px; letter-spacing: .06em; color: var(--ink-soft); }
.fp-wines { margin: 10px 0 0; padding: 0 0 0 1.4em; font-family: var(--font-ui); font-size: 13.5px; color: var(--ink-soft); }
.fp-wines li { margin: 2px 0; }
.fp-wines button { border: 0; background: none; padding: 1px 0; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.fp-wines button:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }

/* ═══════════ GLYPH AND WORD, ON THE LAPTOP (v0.70) ═══════════ */
/* The inserters: the phone's Add wine and Flight, drawn small on the
   page. Solid edge, 3px, glyph first. Editor chrome only. */
.inserters { height: 24px; gap: 6px; opacity: .78; }
.inserters .ins {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-ui); font-size: 12.5px; letter-spacing: 0; text-transform: none;
  color: var(--accent-paper); background: var(--paper);
  border: 1px solid var(--accent-paper-line); border-radius: 3px;
  padding: 2px 9px 2px 6px; line-height: 1.3; opacity: .72;
}
.inserters .ins:hover { opacity: 1; }
.ins-ic { display: inline-grid; place-items: center; width: 13px; height: 13px; }
.ins-ic svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.3; }

/* The way the page is viewed: the ruler and the zoom in one group. */
.sb-view {
  display: inline-flex; align-items: center;
  border: 1px solid var(--chrome-line-2); border-radius: 3px;
  background: var(--lift);
}
[data-theme="dark"] .sb-view { background: transparent; }
.sb-view .sb-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  border: 0; border-right: 1px solid var(--chrome-line-2); border-radius: 0;
  padding: 2px 9px 2px 7px;
}
.sb-view .sb-toggle svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.2; }
.sb-view .sb-toggle.on { background: var(--accent-soft); color: var(--accent-ink); }
.sb-view .zoom-btn, .sb-view .zoom-val { border: 0; border-radius: 0; }
.sb-view .zoom-btn { padding: 1px 8px; }
.sb-view .zoom-btn:hover, .sb-view .zoom-val:hover { background: var(--accent-soft); color: var(--accent-ink); }

/* The Cellar band in Wine details: a glyph on the way there, and the
   shared Put in the Cellar button when the wine is not in it yet. */
.cellar-band .cb-file { margin-left: auto; flex: none; }
.cellar-band.is-absent { flex-wrap: wrap; }
.cellar-band.is-absent .cb-tx { flex: 1 1 0; }
.cellar-band.is-absent .cb-file { flex: 1 0 100%; margin-left: 0; justify-content: center; min-height: 34px; }

/* ═══════════ SHOWN, NOT SAID (v0.72) ═══════════ */
/* A toolbar button that needs a selection looks unavailable without
   one; its tooltip says what to click. */
.tb-btn:disabled { opacity: .32; cursor: default; }
.tb-btn:disabled:hover { background: transparent; border-color: transparent; }
.tb-out:disabled { opacity: .45; cursor: default; }

/* A row or section just made or changed glows once. Editor chrome. */
#stack .is-justset { animation: ts-justset 1.7s ease-out; }
@keyframes ts-justset {
  0%   { background-color: color-mix(in srgb, var(--accent-paper, #7a1f2b) 16%, transparent); }
  100% { background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) { #stack .is-justset { animation: none; outline: 1px solid var(--accent-paper-line); } }

/* Undo, as a button, after a change that removed or moved something. */
.sb-undo {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 8px; padding: 1px 9px 1px 7px;
  border: 1px solid var(--accent-line); border-radius: 3px;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 12px; line-height: 1.5; cursor: pointer;
  animation: sbu-in .18s ease-out;
}
.sb-undo[hidden] { display: none; }
.sb-undo svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.4; }
.sb-undo .sbu-w { color: var(--ink-soft); }
.sb-undo .sbu-w::before { content: '\00b7'; margin: 0 5px 0 1px; color: var(--ink-faint); }
.sb-undo:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
.sb-undo:hover .sbu-w, .sb-undo:hover .sbu-w::before { color: rgba(255,255,255,.85); }
@keyframes sbu-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

/* A button that has just done its job says so for a moment. */
.tb-out.is-done:not(.tb-out-primary) { border-color: var(--accent-line); color: var(--accent-ink); background: var(--accent-soft); }

/* The logo and signature drop zones lead with their drawing. */
.dr-drop-ic { display: grid; place-items: center; width: 30px; height: 30px; margin-bottom: 2px; border-radius: 3px; background: var(--lift); border: 1px solid var(--chrome-line); color: var(--accent-ink); font-style: normal; }
.dr-drop-ic svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.2; }
#drPlaceNote .act-glyph { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.3; }

/* ═══════════ v0.73 ═══════════ */
/* "Not that one": a short sideways shake instead of a sentence. */
#stack .is-nudge { animation: ts-nudge .36s ease-in-out; }
@keyframes ts-nudge { 0%,100% { transform: none; } 20% { transform: translateX(-4px); } 45% { transform: translateX(4px); } 70% { transform: translateX(-2px); } }
@media (prefers-reduced-motion: reduce) { #stack .is-nudge { animation: none; } }

/* A new column: its label glows and its grip pulses where to drag. */
.col-label.is-justset { color: var(--accent-ink); background: color-mix(in srgb, var(--accent-paper, #7a1f2b) 14%, transparent); border-radius: 2px; animation: col-new 2.1s ease-out; }
@keyframes col-new { 0%,30% { box-shadow: inset -2px 0 0 var(--accent); } 100% { box-shadow: inset 0 0 0 transparent; } }

/* The Wine details panel with nothing selected. */
.dr-blank { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 28px 18px 18px; gap: 6px; }
.dr-blank-ic { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 3px; border: 1px solid var(--chrome-line); background: var(--lift); color: var(--accent-ink); margin-bottom: 6px; }
.dr-blank-ic svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.1; }
.dr-blank-t { margin: 0; font-size: 16px; color: var(--ink); }
.dr-blank-s { margin: 0 0 10px; font-size: 13px; line-height: 1.45; color: var(--ink-faint); max-width: 230px; }
.dr-blank-go { display: inline-flex; align-items: center; gap: 6px; }
.dr-blank-go svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; }

/* ═══════════ v0.74 ═══════════ */
/* The Cellar's toolbar door says what it is. */
#btnHouseLib.tb-inv { width: auto; padding: 0 8px 0 6px; gap: 5px; display: inline-flex; align-items: center; }
#btnHouseLib .tbh-label { font-family: var(--font-ui-2); font-weight: 600; font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
#btnInfo svg { width: 15px; height: 15px; }
#btnInfo.is-on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
#libNewBtn.has-ic { display: inline-flex; align-items: center; gap: 6px; }
#libNewBtn.has-ic svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8; }
/* Clean-up: the template's name already labels the Template menu. */
#sbTemplate, #sbPages + .sb-div { display: none; }

/* ═══════════ v0.75 — NOT GETTING LOST ═══════════ */
/* Every way out shows the key that also takes it. Chrome only, and
   only where there is a keyboard. */
.kbd-esc {
  display: inline-block; margin-left: 6px;
  font-family: var(--font-ui-2); font-size: 9.5px; font-weight: 400; letter-spacing: .04em; line-height: 1;
  padding: 2px 4px; border-radius: 3px;
  color: var(--ink-faint); background: var(--chrome); border: 1px solid var(--chrome-line);
}
@media (max-width: 760px), (hover: none) { .kbd-esc { display: none !important; } }
body.mobile .kbd-esc { display: none !important; }
.tb-out .kbd-esc { margin-left: 4px; }

.drawer-close { display: inline-flex; align-items: center; gap: 2px; padding: 3px 5px; border: 1px solid transparent; border-radius: 3px; }
.drawer-close svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.drawer-close:hover { border-color: var(--chrome-line); }
.drawer-close .kbd-esc { margin-left: 4px; }

/* Print Preview's paper strip draws its choices, as the Page menu does. */
.ov-setup .seg button.has-draw { display: inline-flex; align-items: center; gap: 5px; }
.ov-setup .seg-draw { display: inline-grid; place-items: center; }
.ov-setup .seg-draw svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.1; }
.ov-setup .seg-draw .mi-fill { fill: var(--accent-soft); stroke: var(--accent-line); stroke-width: .8; stroke-dasharray: 1.2 1; }
.ov-setup .seg button.on .seg-draw { color: var(--accent-ink); }
/* v0.76 — a refused save stays visible until a save succeeds. */
.sb-saved.is-err { color: var(--accent-ink); font-weight: 600; opacity: 1 !important; }
/* v0.78 — the policy links in the credit line. */
.app-credit a, .m-credit a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--chrome-line-2); user-select: auto; }
.app-credit a:hover, .m-credit a:hover { color: var(--accent-ink); border-bottom-color: var(--accent-line); }

/* v0.98: the Inventory door in the editor is set like every other word
   on the toolbar (Preview, Info, Logo & credit), not in small capitals
   that made it the one label shouting. */
#btnHouseLib .tbh-label {
  font-family: var(--font-ui); font-weight: 400; font-size: 14px;
  letter-spacing: 0; text-transform: none; color: var(--ink);
}
#btnHouseLib:hover .tbh-label { color: var(--accent-ink); }

/* v0.99: the Logo & credit panel speaks in the same face as Wine
   details; its drop zones and closing note were the only body text in
   the drawers set in the label sans. */
.dr-drop { font-family: var(--font-ui); font-size: 14px; letter-spacing: 0; }
.dr-drop small { font-family: var(--font-ui); font-size: 12px; letter-spacing: 0; }
.dr-note { font-family: var(--font-ui); font-size: 12.5px; }
/* The Wine Library search in Wine details is set upright like the fields under it. */
.dr-field input[type="search"] { font-style: normal; }

/* ════════════════ TEMPLATE REFINEMENTS (v0.102) ════════════════
   A typography and spacing pass across the house voices. Small moves
   only; every one is a real box or a real property the PDF writer
   reads, so the sheet and the export stay the same picture. */

/* Stacked entries (Editorial, Prose, Próximo). A wine and its detail
   are one entry, so the gap between two entries is now wider than the
   gap between a name and the line under it. */
.blk-row.prose { padding-top: .36em; }
.blk-row.prose .prose-detail { line-height: 1.3; margin-top: .04em; }
[data-voice="editorial"] .blk-row.prose .prose-detail { font-size: .92em; color: #5f584f; }
[data-voice="editorial"] .sec-title { padding-bottom: .4em; }

/* Bistro. The italic head in bold read as shouting; the roman weight a
   size up is the warmer bistro card voice. */
[data-voice="bistro"] .sec-title { font-weight: 400; font-size: 1.34em; letter-spacing: 0; padding-bottom: .14em; }

/* Typewriter. With no rule under the masthead, the title needs its own
   room before the first heading; the detail a notch smaller wraps less
   in a monospaced column. */
[data-voice="typewriter"] .blk-masthead { padding-bottom: 1.2em; }
[data-voice="typewriter"] .cell.dim { font-size: .9em; }

/* Modern. Heads were the smallest type on the sheet. */
[data-voice="modern"] .sec-title { font-size: .84em; padding-bottom: .25em; }

/* Contemporary. An unruled head sits a little closer to its wines. */
[data-voice="contemporary"] .blk-section { margin-bottom: .35em; }

/* Étoile and Steakhouse. Bodoni's hairlines thin out in a small tracked
   head; one notch of size keeps the head readable at nine points. */
[data-voice="etoile"] .sec-title { font-size: .98em; }
[data-voice="steakhouse"] .sec-title { font-size: .98em; }

/* Grand Cru. The double rule sat far below a large head; pull it up. */
[data-voice="grandcru"] .sec-title { padding-bottom: .12em; }

/* Trattoria and Cellar Book. The tight lists keep their density, but a
   head now has a breath above it, so it reads as a head and not as the
   last wine of the section before. */
[data-voice="trattoria"] .blk-section,
[data-voice="cellar"] .blk-section { padding-top: .3em; }

/* ════════════════ LEADERS (v0.104) ════════════════
   What runs from a wine's name to its price: nothing, dots or a fine
   rule. `data-leader` is set beside `data-voice` on the stack, the
   measuring host, every preview page and the HTML export root. The
   leader is a real element with a real bottom border, so the PDF
   writer draws it; dotted borders print as its fine dash. */
.blk-row .cells .cell-lead {
  display: flex; align-items: baseline; min-width: 0;
}
.blk-row .cells .cell-lead > .cell { flex: 0 1 auto; min-width: 0; }
.blk-row .cells .cell-lead > .leader {
  flex: 1 1 auto; min-width: 0; margin: 0 0 0 .55em;
  transform: translateY(-.22em);
}
/* The price half: the figure keeps its width, the run fills the rest of
   the column and stops a clear space short of the number. */
.blk-row .cells .cell-lead.to-price > .cell { flex: 0 0 auto; }
.blk-row .cells .cell-lead.to-price > .leader { margin: 0 .45em 0 0; min-width: .6em; }
[data-leader="none"] .blk-row .leader { border-bottom: 0; }
[data-leader="line"] .blk-row .leader { border-bottom: 1px solid #d6d0c6; }
[data-leader="dots"] .blk-row .leader { border-bottom: 1px dotted #bfb8ad; }

/* ════════════════ SECTION HEADS NEVER UNDER THE WINES (v0.104) ════════════════
   A section head is the larger voice on the sheet, so it is never set
   smaller than the wine names beneath it. Each voice's head is raised
   to at least its own name size (`--name-scale`); the tracking comes
   in a little where a wide-tracked head grew, so it keeps its width. */
[data-voice="typewriter"] .sec-title   { font-size: calc(1em * var(--name-scale)); letter-spacing: .16em; }
[data-voice="editorial"] .sec-title    { font-size: calc(1em * var(--name-scale)); letter-spacing: .22em; }
[data-voice="prose"] .sec-title        { font-size: 1.02em; letter-spacing: .18em; }
[data-voice="proximo"] .sec-title      { font-size: calc(1em * var(--name-scale)); letter-spacing: .26em; }
[data-voice="etoile"] .sec-title       { font-size: calc(1em * var(--name-scale)); letter-spacing: .2em; }
[data-voice="modern"] .sec-title       { font-size: calc(1em * var(--name-scale)); letter-spacing: .16em; }
[data-voice="trattoria"] .sec-title    { font-size: calc(1em * var(--name-scale)); letter-spacing: .1em; }
[data-voice="cellar"] .sec-title       { font-size: 1em; letter-spacing: .1em; }
[data-voice="steakhouse"] .sec-title   { font-size: calc(1em * var(--name-scale)); letter-spacing: .16em; }
[data-voice="gazette"] .sec-title      { font-size: calc(1em * var(--name-scale)); letter-spacing: .12em; }
/* The plain service formats: the head was a hair over the names; it now
   reads as a head. */
[data-voice="plain"] .sec-title        { font-size: 1.06em; }

/* v0.137: Inventory's count for the wine the caret is in, as a quiet
   way to it. Text until pointed at; desktop only. */
.sb-cellar {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; gap: 5px;
  height: 22px; padding: 0 7px; margin: 0 -7px;
  border: 1px solid transparent; border-radius: 3px;
  background: none; color: var(--ink-soft); cursor: pointer;
  font: inherit; letter-spacing: .02em;
}
.sb-cellar[hidden] { display: none; }
.sb-cellar-ic { display: inline-grid; place-items: center; width: 13px; height: 13px; color: var(--accent-ink); }
.sb-cellar-ic svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.3; }
.sb-cellar-go { color: var(--ink-faint); font-size: 14px; line-height: 1; }
.sb-cellar:hover { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-ink); }
.sb-cellar:hover .sb-cellar-go { color: var(--accent-ink); }
.sb-cellar:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 860px) { .sb-cellar, #sbCellarDiv { display: none !important; } }
/* v0.140: the list prints a different year from Inventory's. */
.sb-cellar.is-stale { color: var(--accent-ink); }

/* ═══════════ THE LIBRARY PANEL (v0.153) ═══════════
   Head and foot hold still; only the offers scroll, and the panel's
   height is set by where it is placed, never by sliding over the cell. */
.lookup { display: flex; flex-direction: column; overflow: hidden; max-height: none; padding: 3px; }
.lookup .lk-list { overflow-y: auto; overscroll-behavior: contain; flex: 1 1 auto; min-height: 0; }
.lookup.is-above { box-shadow: 0 -10px 28px rgba(0,0,0,.24); }
.lk-item .lk-name .lk-hit { font-weight: 700; color: inherit; }
.lk-item:not(.active) .lk-name .lk-hit { color: var(--accent-ink); }
.lk-empty { font-style: normal; }
.lk-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex: none;
  padding: 4px 4px 2px 8px;
}
.lk-keep {
  appearance: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
  min-height: 24px; padding: 2px 6px 2px 8px; border-radius: var(--r);
  border: 1px solid var(--chrome-line-2); background: transparent;
  font-family: var(--font-ui); font-size: 12.5px; color: var(--ink-soft);
  max-width: 62%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lk-keep:hover { color: var(--accent-ink); border-color: var(--accent-line); }
.lk-keep kbd {
  font-family: var(--font-ui-2); font-size: 9px; letter-spacing: .06em; color: var(--ink-faint);
  border: 1px solid var(--chrome-line-2); border-radius: 2px; padding: 0 3px; line-height: 14px;
}

/* ═══════════ THE FIRST ROW, FITTED (v0.154) ═══════════
   fitToolbar() adds these one at a time until the row fits. */
.tb-short { display: none; }
.toolbar.tb-fit-1 .tbf-key { display: none; }
.toolbar.tb-fit-1 .tb-find { width: 124px; }
.toolbar.tb-fit-2 .tb-long { display: none; }
.toolbar.tb-fit-2 .tb-short { display: inline; }
.toolbar.tb-fit-3 #btnPreview > span, .toolbar.tb-fit-3 #btnInfo > span:not(.tb-caret) { display: none; }
.toolbar.tb-fit-3 #btnPreview, .toolbar.tb-fit-3 #btnInfo { padding-left: 7px; padding-right: 7px; }
.toolbar.tb-fit-4 .tb-row:first-child .tb-menu .tb-name { display: none; }
.toolbar.tb-fit-4 .tb-row:first-child .tb-menu .tb-val { margin-left: -3px; }
.toolbar.tb-fit-5 #btnHouseLib .tbh-label, .toolbar.tb-fit-5 #btnFields > span { display: none; }
.toolbar.tb-fit-5 .tb-find { width: 104px; }
.toolbar.tb-fit2-1 .tb-row + .tb-row .tb-crumb { display: none; }
.toolbar.tb-fit2-2 .tb-row + .tb-row .tb-btn { width: 23px; }
.toolbar.tb-fit2-2 .tb-row + .tb-row .tb-menu { padding-left: 4px; padding-right: 4px; }
.toolbar.tb-fit2-3 .tb-row + .tb-row .tb-menu .tb-name { display: none; }

/* ═══════════ THE DRAWER BY WIDTH (v0.155) ═══════════
   A narrow window gives the sheet the 20px the drawer can spare; a wide
   one gives the drawer room for the Fields rows and long names. */
@media (min-width: 861px) and (max-width: 1000px) { :root { --drawer-w: 272px; } }
@media (min-width: 1440px) { :root { --drawer-w: 320px; } }

/* ═══════════ THE WAYS IN, ON THE PAGE (v0.166) ═══════════ */
/* One button family for both places: the tail of the flow and each
   section's heading. Paper colours (they sit on the sheet), glyph and
   word, 3px. A little firmer at rest than before; at .56 of the paper
   red the words read as disabled. */
.inserters { opacity: 1; }
.inserters .ins, .sec-tools .ins {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 400;
  letter-spacing: 0; text-transform: none; font-style: normal;
  color: var(--accent-paper); background: var(--paper);
  border: 1px solid var(--accent-paper-line); border-radius: 3px;
  padding: 2px 9px 2px 6px; line-height: 1.3; opacity: .82;
  cursor: pointer; white-space: nowrap;
  transition: opacity .12s, background .12s, border-color .12s;
}
.inserters .ins:hover, .sec-tools .ins:hover,
.inserters .ins:focus-visible, .sec-tools .ins:focus-visible {
  opacity: 1; background: var(--accent-paper-soft); border-color: var(--accent-paper);
}
.inserters .ins:focus-visible, .sec-tools .ins:focus-visible { outline: 2px solid var(--accent-paper); outline-offset: 1px; }
/* Section starts something new rather than adding to what is here, so
   it stands apart from the three that add. */
.inserters .ins.is-section { margin-left: 10px; }
/* Each section's own: at the right end of the heading's line, where
   every list leaves the line empty. Hidden at rest; shown for the
   section under the pointer or with the caret in it. Not focusable while
   hidden, so Tab does not wander into buttons nobody can see. */
.sec-tools {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  display: flex; gap: 5px; align-items: center; z-index: 6;
  visibility: hidden; opacity: 0; transition: opacity .12s, visibility 0s linear .12s;
  background: var(--paper); padding-left: 8px;
}
.sec-tools.is-on { visibility: visible; opacity: 1; transition: opacity .12s, visibility 0s; }
.sec-tools .ins { font-size: 11.5px; padding: 1px 8px 1px 5px; }
.sec-tools .ins-ic, .sec-tools .ins-ic svg { width: 12px; height: 12px; }
/* A heading set to the right or centre keeps its words clear: the
   tools then sit under the heading's line instead of on it. */
.blk-section:has(.sec-title[style*="right"]) .sec-tools,
.blk-section:has(.sec-title[style*="center"]) .sec-tools { top: auto; bottom: -26px; transform: none; }

/* The Library chip on a selected wine: it only ever shows beside the
   line being edited, so it no longer waits at 62%. A little larger and
   ringed in the word red, the colour of the Library everywhere else. */
.cell-lib { width: 24px; height: 24px; opacity: 1; border-color: var(--accent-line); }
.cell-lib svg { width: 13px; height: 13px; }
.cell-lib:hover { border-color: var(--accent-ink); }

/* v0.171: the open list changed on another device. Stays until answered. */
.sb-cloud { display: inline-flex; align-items: center; margin-left: 10px; }
.sb-cloud[hidden] { display: none; }
.sb-cloud .cl-strip { display: inline-flex; align-items: center; gap: 6px; }
.sb-cloud .cl-said { font-family: var(--font-ui-2); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
.sb-cloud .cl-act {
  padding: 1px 9px; border: 1px solid var(--accent-line); border-radius: 3px;
  background: var(--accent-soft); color: var(--accent-ink);
  font: inherit; font-size: 12px; line-height: 1.5; cursor: pointer;
}
.sb-cloud .cl-act.cl-mine { background: transparent; }
.sb-cloud .cl-act:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
