/* site/app/screens/cafe.css — the cafe.
 * Depends on tokens.css + ui.css + shell.css. Everything is scoped under `.cafe` so class
 * names that read generically — .chip, .step, .row, .tile, .rail, .slide — cannot leak into
 * another screen. proshop.css and bookings.css both learned that the hard way.
 *
 * ⚠ NOT ONE COLOUR IN THIS FILE. Every value is a token or a mix of tokens. A hex written
 *   here does not follow the palette into dark mode, and this app is used courtside at
 *   night, which is when the cafe is busiest. Two shipped defects came from exactly that.
 *
 * ⚠ THIS IS THE PRO SHOP'S STYLESHEET WITH A CAFE IN IT, AND THAT IS THE POINT (Syd:
 *   "i want it to look more like the PRO shop tab"). The chip, the choice row, the totals
 *   block and the view push are deliberately the same controls, written the same way. Where
 *   the two files agree, they must keep agreeing.
 *
 * ⚠ THE MENU HALF WAS REBUILT AGAINST GRAB, 2026-09-03. Syd: *"for the cafe section the MENU
 *   itself should be like community.supply/menu, i want it to resemble a grab UI and
 *   ordering system. to be honest this doesnt look good. and is not good for customer
 *   conversion."* What it was: a text list — name, note, chips, price, +. What was missing
 *   was the thing both references lead with, a PICTURE PER ITEM at a size worth looking at.
 *   Four pieces came in with it, and each earns its place by making ordering faster or the
 *   food more appetising, not by being tidier:
 *     · an 88px artwork on every row, drawn inline (see Dish artwork below)
 *     · a sticky category rail, so Food is one tap and not two screens of scrolling
 *     · Signature surfaced FIRST as a card band, at four times the row artwork's size
 *     · the add control moved onto the artwork's corner, where the thumb already rests
 *   The cart bar was already right and was left alone.
 *
 * ONE ORANGE PER VIEW, AND IT MOVES:
 *   menu    the cart pill's Review chip
 *   cart    the slide-to-pay knob
 *   pager   nothing. There is nothing to press on a pager.
 */

.cafe { position: relative; }

/* ── View push ────────────────────────────────────────────────────────────── */
/* ⚠ GATED ON [data-ready]. boot.js stamps that after the first frame; without the gate the
   whole screen slides into place on load as its initial styles resolve, which reads as jank
   on the slow phones that are actually at the venue.
   ⚠ THE RESTING STATE IS HERE AND THE OFFSET IS THE STARTING STYLE, not the other way round.
   An offset set inline and cleared in requestAnimationFrame makes the correct layout depend
   on a callback of ours running, and rAF is suspended in a backgrounded tab. */
/* ⚠⚠ A PUSH HAS TWO HALVES AND THIS ONLY HAD ONE. The old rule faded a new view in from 12px
   over nothing at all, because paint() deleted the previous screen before building the next —
   which is a cross-fade wearing a slide. Syd, 2026-09-03: "add animations between all pages
   and the cart." A push is the animation that answers "where did that go and how do I get
   back": the outgoing screen leaves the way you would return to it, so the direction of travel
   is stated rather than implied.
   ⚠ THE LEAVING LAYER TRAVELS A THIRD AS FAR AS THE ARRIVING ONE (28px against 100%-ish
   feel at these distances). That is iOS's parallax and it is not decoration — matching speeds
   read as two unrelated screens crossing; a slower back layer reads as one screen sliding
   over another that is still there.
   ⚠ THE RESTING STATE IS HERE AND THE OFFSET IS THE STARTING STYLE. An offset set inline and
   cleared in requestAnimationFrame makes the correct layout depend on a callback of ours
   running, and rAF is suspended in a backgrounded tab. */
.cafe .cafe-view { opacity: 1; transform: translate3d(0, 0, 0); }
[data-ready] .cafe .cafe-view {
  transition: transform 320ms var(--ease-out), opacity 240ms var(--ease-out);
  will-change: transform, opacity;
}
@starting-style { .cafe .cafe-view.from-right { opacity: 0; transform: translate3d(34px, 0, 0); } }
@starting-style { .cafe .cafe-view.from-left  { opacity: 0; transform: translate3d(-34px, 0, 0); } }

/* ⚠ THE LEAVING VIEW IS PULLED OUT OF FLOW OR IT WOULD DOUBLE THE PAGE HEIGHT for the length
   of the animation — the scrollbar would jump and the incoming screen would start below the
   old one instead of over it. cafe.js sets its `top` to the outgoing scroll offset so it
   begins exactly where it was on screen. pointer-events off: it is a picture of a screen you
   have already left, and a tap landing on it would act on the wrong page. */
.cafe .pane-scroll { position: relative; }
.cafe .cafe-view.is-leaving {
  position: absolute; left: 0; right: 0; z-index: 1;
  pointer-events: none; opacity: 0;
}
.cafe .cafe-view.is-leaving.to-left  { transform: translate3d(-28px, 0, 0); }
.cafe .cafe-view.is-leaving.to-right { transform: translate3d(28px, 0, 0); }

/* ── Head strip: hours, and the signature legend ──────────────────────────── */
/* The two facts worth carrying at the top of a menu, both as labels. The subtitle that used
   to sit here ("Order, pay, then watch the number.") is deleted, not rewritten. */
/* ⚠ THE STATUS ROW IS GONE — Syd, 2026-09-03: "remove this entire row Open until 9:00 PM".
   It held the opening hours and the ✱ legend. The legend now rides the Signature heading
   instead, so the mark is still bound to the word exactly once, and it costs no row.
   ⚠ THE MARK IS EXPLAINED ONCE AND NEVER AGAIN ON A ROW. That is what the reference menu
   does, and it is why the mark can be this quiet: repeating "signature" on four rows would
   cost more space than the four rows are worth. */
.cafe .sec-head .star { font-size: 13px; vertical-align: 3px; margin-left: 2px; }
.cafe .star { color: var(--action); font-size: 12px; line-height: 1; }
/* ⚠ THE SELECTOR MOVED WITH THE MARKUP. The row stopped being a `.row .title` when it grew
   an artwork column, and `.cafe .row .title .star` silently stopped matching — the mark went
   back to inheriting the name's 16px and sat on the baseline like a typo. */
/* ⚠ THE MARK IS GLUED TO THE LAST WORD WITH A NBSP IN cafe.js, and this is the other half of
   that fix. "Lumpiang Shanghai ✱" is 19 characters: it fills the name column on a 320px
   phone and dropped the ✱ alone onto a second line, which cost the row 22px and made the
   mark look like a bullet starting a new sentence. The reference menu solves the same
   orphan with `text-wrap:balance`; here the orphan is always the mark, so gluing it is
   exact where balancing is a guess. */
.cafe .dish-name .star { font-size: 11px; vertical-align: 2px; }

/* ── The live order, back to the pager ───────────────────────────────────── */
/* The reference floats "Already ordered? ›" beside its cart. Here the answer is a real paid
   order, so it is a row that states it rather than a question that asks. */
.cafe .cafe-live {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  margin: 0 20px 20px; padding: 12px 16px; border: 0; cursor: pointer;
  /* ⚠ THE WIDTH SUBTRACTION IS TWICE THE MARGIN AND HAS TO MOVE WITH IT. A <button> does not
     take `width: auto` from its container the way a div does, hence the explicit calc — and
     it was 32 against a 16px margin. Change one without the other and the row hangs 8px off
     the right edge. */
  width: calc(100% - 40px); border-radius: var(--r-lg);
  background: var(--bg-raised); box-shadow: var(--shadow-1);
  font-family: var(--font-ui); color: var(--ink);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out);
}
.cafe .cafe-live .grow { flex: 1; min-width: 0; }
.cafe .cafe-live .title { display: block; font: 600 15px/1.3 var(--font-ui); letter-spacing: -.008em; }
.cafe .cafe-live .meta { display: block; margin-top: 2px; font: 400 13px/1.35 var(--font-ui); color: var(--ink-tertiary); }
.cafe .cafe-live-go { flex: none; width: 18px; height: 18px; color: var(--ink-tertiary); }
.cafe .cafe-live-go svg { width: 18px; height: 18px; }

/* ── Menu sections ───────────────────────────────────────────────────────── */
/* One short factual line per section, exactly as the reference has it. It says what the
   section holds; it does not address anyone and it does not explain the screen. */
.cafe .cafe-secnote {
  margin: -4px 20px 10px; padding: 0;
  font: 400 13px/1.45 var(--font-ui); color: var(--ink-tertiary);
}

/* ⚠⚠ 20px, AND IT WAS 16. Every page-level block on this screen sat four pixels inside the
   `.sec-head` label above it (shell.css: `padding: 0 20px 9px`) — two left edges, four pixels
   apart, all the way down the cart. This file already fixed exactly this on the pager and its
   own comment there names the failure: "Two different left edges, four pixels of ambiguity
   apart, is what reads as broken." The cart sections were left behind.
   ⚠ THE PADDING INSIDE A CARD IS UNCHANGED. This is about where a block's EDGE sits against
     the page, not about how much room its contents get. */
.cafe .cafe-list { margin: 0 20px; }

/* ── The category rail ────────────────────────────────────────────────────────
 * ⚠ THE CONTROL THIS SCREEN WAS MISSING. Fifteen items is two and a half phone screens; a
 *   player who came for lunch was scrolling past the entire coffee list to reach Food, every
 *   time. Grab pins the same rail under its header and it is the reason a long menu stays
 *   usable on a phone.
 * ⚠ STICKY TO THE SCROLLER, top:0. `.pane-scroll` is the scrolling box, so this pins under
 *   the app header rather than to the window. A rail that scrolls away is a table of
 *   contents, and a table of contents is not a control.
 * ⚠ OPAQUE, AND IT IS THE ONE PIECE OF CHROME IN THIS APP THAT IS NOT THE --chrome MATERIAL.
 *   Rows pass UNDER a sticky element, so it needs a ground either way; the question was
 *   whether to use the header's translucent one. It cannot. What scrolls under this rail
 *   first is the signature band — navy glassware on sunken tiles — and at --chrome's 0.72
 *   the blur pulled that up as a grey wash across the pills, so the current one read as
 *   pressed and the others read as smudged. Measured at 0.90 it still bled the price of the
 *   row behind it through the gaps. A solid ground and a hairline is also what the reference
 *   this screen is modelled on does, and legibility of the control beats the material. */
.cafe .cafe-rail {
  position: sticky; top: 0; z-index: 5;
  margin: 0 0 14px; padding: 8px 0;
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}
/* ⚠ scrollbar-width:none AND the WebKit pseudo-element. A visible scrollbar under a 40px
   pill rail on a trackpad Mac adds a grey slab to the chrome; on iOS it never appears. */
.cafe .cafe-railtrack {
  display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x proximity;
  padding: 0 20px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.cafe .cafe-railtrack::-webkit-scrollbar { display: none; }
.cafe .cafe-railbtn {
  position: relative; flex: none; scroll-snap-align: start;
  padding: 9px 15px; min-height: 38px; border: 1.5px solid var(--rule);
  border-radius: var(--r-full); background: var(--bg-raised); color: var(--ink-secondary);
  font: 600 13px/1 var(--font-ui); letter-spacing: -.004em; white-space: nowrap;
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}
/* 38px drawn, 44px tapped — the pill has to stay short enough that four of them fit a 320px
   phone without the rail becoming a second header. */
.cafe .cafe-railbtn::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 100%; height: var(--tap-min);
}
/* Ink-filled, matching the selected chip exactly — the rail and the variant chips are the
   same "this one is current" idea and must not be two different marks. */
.cafe .cafe-railbtn[aria-current="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised);
}

/* ── Dish artwork ─────────────────────────────────────────────────────────────
 * ⚠ NO PHOTOGRAPHS AND NO FILES. site/_headers has no img-src for anything off-origin, so a
 *   stock photo URL is a silently broken box, and this screen has no asset pipeline of its
 *   own yet. Flat brand shapes drawn inline follow the theme into dark mode instead of being
 *   a picture that is wrong at night, which is when the cafe is busiest.
 * ⚠ THE TONE LADDER IS proshop.css's, VALUE FOR VALUE. Two screens that sell things must not
 *   be two illustration styles. f-cut is painted in the tile's own ground so it reads as a
 *   hole cut out of the shape rather than as a second colour.
 * ⚠ NO CHARTREUSE, unlike the pro shop's ball. `.tag.live` on this screen already means "an
 *   order is being made right now"; a chartreuse calamansi one card away from it is the same
 *   colour doing two jobs. */
.cafe .dish-svg { width: 100%; height: 100%; display: block; }
.cafe .dish-svg .f-fig  { fill: var(--ink); }
.cafe .dish-svg .f-soft { fill: var(--ink); opacity: .28; stroke: var(--ink); }
.cafe .dish-svg .f-cut  { fill: var(--bg-sunken); stroke: var(--bg-sunken); }

/* ── Signature, first ─────────────────────────────────────────────────────────
 * ⚠ FIRST AND AS CARDS, NOT MARKED IN PLACE. The reference leaves its signatures in their
 *   sections with a ✱ beside the name, which is right for a printed menu read all at once.
 *   A phone menu is read top-down and abandoned, so the four things the kitchen wants
 *   ordered go where the thumb already is, drawn at a size that can actually be seen. */
.cafe .cafe-sigrail {
  display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
  padding: 2px 20px 6px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.cafe .cafe-sigrail::-webkit-scrollbar { display: none; }
.cafe .sigcard {
  position: relative; flex: none; width: 158px; scroll-snap-align: start;
  background: var(--bg-raised); border-radius: var(--r-lg); box-shadow: var(--shadow-1);
  overflow: visible;
}
.cafe .sigcard-art {
  position: relative;
  display: grid; place-items: center; aspect-ratio: 1 / 1; padding: 14px;
  background: var(--bg-sunken); border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.cafe .sigcard-body { padding: 10px 12px 12px; }
/* ⚠ TWO LINES CLAMPED, NOT ONE ELLIPSIS. "Lumpiang Shanghai" and "Calamansi Cooler" both
   run past 158px; truncated to one line they lose the word that identifies them. */
.cafe .sigcard-name {
  margin: 0; font: 600 14px/1.28 var(--font-ui); letter-spacing: -.008em; color: var(--ink);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  min-height: 2.56em;
}
.cafe .sigcard-price {
  margin: 5px 0 0; font: 600 14px/1.2 var(--font-ui); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* ⚠ INSIDE THE ARTWORK, NOT ON THE CARD. It was anchored to the card's bottom-right corner —
   correct at quantity zero, and wrong the moment it expanded: `− 1 +` is about 106px on a
   158px card, so it grew leftwards straight across the price. Adding a second Spanish Latte
   hid what a Spanish Latte costs. Inside the 158px art tile the expanded pill has room, and
   the only thing it covers is a picture the customer has already looked at. */
.cafe .sigcard-stepper { position: absolute; right: 8px; bottom: 8px; }

/* ── An item row ──────────────────────────────────────────────────────────────
 * ⚠ ARTWORK RIGHT, WORDS LEFT, ADD CONTROL ON THE ARTWORK'S CORNER. That is Grab's list row
 *   and it is also `community.supply/menu`'s art slot, which anchors its ＋ at
 *   `bottom:-3px; right:4px` for the same reason: that corner is where a thumb rests on a
 *   phone held one-handed, and the picture is what the eye lands on before the name.
 * ⚠ THE ROW IS NOT A .row ANY MORE. ui.css centres a .row, which put the price and the
 *   stepper in the middle of a 90px-tall cell while the name sat at the top — the column of
 *   prices stopped being a column. This block owns its own rhythm. */
/* ⚠⚠ THE ROW IS A <button> AND EVERY ROW IS THE SAME HEIGHT. Both are new, and they are the
   same change. Measured on the deployed build, rows carrying variant chips were 159px tall
   and rows without were 116px — a 43px step down a list that is meant to be scanned, so the
   list read as ragged rather than as a list, and the ragged ones were exactly the drinks.
   The chips moved to the item screen (see viewItem in cafe.js), which leaves the row with two
   columns and one job: name, note, price on the left; picture on the right; open on tap.
   ⚠ A ROW WITH ONE JOB CAN BE A REAL BUTTON. The old comment here explained that it could not
   — chips and a stepper lived inside it, and a button inside a button is flattened into one
   confused control by a screen reader. With the choosers gone the whole row is one tap target
   with one destination, which is both better semantics and a bigger target.
   ⚠ THE HEIGHT IS PINNED TO THE ARTWORK, not to the text. A two-line note would otherwise
   make one row taller than its neighbours all over again. */
.cafe .dish {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 12px 16px; border: 0; border-bottom: 1px solid var(--rule);
  background: transparent; text-align: left; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.cafe .dish:last-child { border-bottom: 0; }
.cafe .dish-text { flex: 1 1 auto; min-width: 0; display: grid; gap: 3px; }
.cafe .dish-name {
  margin: 0; font: 600 16px/1.3 var(--font-ui); letter-spacing: -.008em; color: var(--ink);
}
.cafe .dish-note {
  margin: 0; font: 400 13px/1.4 var(--font-ui); color: var(--ink-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cafe .dish-price {
  margin: 4px 0 0; font: 600 15px/1.2 var(--font-ui); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* A "+" after the price, not the word "from". The row shows the base price and the item can
   cost more once a milk is chosen; one glyph says so without spending a line on it. */
.cafe .dish-from {
  color: var(--ink-tertiary); font-weight: 500; font-size: 12px;
  margin-left: 3px; vertical-align: 1px;
}

/* ⚠ 92px SQUARE, flex:none, AND THE DRAWING FILLS MORE OF IT. The tile was 88px with 9px of
   padding, so a 70px drawing sat in a 88px plate and the row read as mostly empty tile. The
   padding is now 6px and the plate is 92, which is a 25% larger picture in 4px more row.
   ⚠ overflow is VISIBLE on the wrap, because the + hangs off the corner. */
.cafe .dish-artwrap { position: relative; flex: none; width: 92px; height: 92px; }
.cafe .dish-art {
  display: grid; place-items: center; width: 92px; height: 92px; padding: 6px;
  background: var(--bg-sunken); border-radius: var(--r-md);
}

/* ⚠⚠ THE + IS ANCHORED INSIDE THE CORNER NOW, NOT HANGING OFF IT. It used to sit at
   right:-6px, bottom:-8px — measured on the deployed build its right edge landed 6px PAST the
   artwork and 10px short of the row's own edge, so it floated in the gutter belonging to
   neither. A control that touches nothing reads as a stray dot.
   ⚠ AND IT IS NO LONGER A STEPPER. It opens the item screen; it does not add. So it never
   grows into three controls, which is what the old right-anchoring existed to absorb — the
   whole "it must grow leftwards or the tile jumps under the thumb" problem is gone with it.
   ⚠ THE RING IS THE TILE'S OWN BACKGROUND, so the circle reads as sitting ON the picture
   rather than punched through it. */
.cafe .dish-add {
  position: absolute; right: 6px; bottom: 6px;
  display: grid; place-items: center; width: 34px; height: 34px;
  border: 0; border-radius: 50%;
  background: var(--ink); color: var(--bg);
  font: 400 22px/1 var(--font-ui); padding-bottom: 2px;
  box-shadow: 0 0 0 3px var(--bg-sunken);
  cursor: pointer; touch-action: manipulation;
}
/* ⚠ THE 44px TARGET IS A PSEUDO-ELEMENT, NOT THE CIRCLE. A 34px circle is the right SIZE on a
   92px tile and the wrong TARGET on a phone; tokens.css calls 44px the floor. On the menu row
   the whole row opens the same screen so a miss is harmless, but the signature card's + is a
   real button beside a scrolling rail, and there a miss scrolls the rail instead. */
.cafe .dish-add::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; }

/* ⚠⚠ THE COUNT REPLACES THE +; IT IS NOT A SECOND BADGE BESIDE IT. The first draft put an
   orange count on the opposite corner, and it was wrong twice over. Orange is this app's
   "press this" and tokens.css is explicit that two orange things on a screen means one of
   them is wrong — fifteen orange counts down a menu would drown the cart bar, which is the
   one control that finishes an order. And a corner already carrying a control does not want a
   second mark on the other corner: that is a pile, not a hierarchy.
   One circle, two readings: "+" means none of these yet, a number means that many. Both open
   the same screen, so the control's meaning never changes — only its label. */
.cafe .dish-add.has-qty {
  font: 700 15px/1 var(--font-ui); padding-bottom: 0;
  font-variant-numeric: tabular-nums;
}

/* ── Variant chips ────────────────────────────────────────────────────────── */
/* ⚠ CHIPS, NEVER A NATIVE <select>. Syd's standing rule, and it is the pro shop's control,
   character for character. The hard reason as well as the stylistic one: an OS select wheel
   covers the price while it is open and cannot show a per-option price change against a
   single line. These can, and they are one tap instead of three. */
/* ⚠ FULL WIDTH, UNDER BOTH COLUMNS — NOT INSIDE THE TEXT COLUMN. Measured at 375px: the text
   column is 215px next to an 88px tile, and "Hot · Iced · Dairy · Oat +₱25" wraps to three
   lines in it, taking the row past 150px tall. Given the whole row they are one line, and
   the artwork stops being squeezed by how many ways a drink can be made. */
.cafe .dish-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
.cafe .chips { display: flex; flex-wrap: wrap; gap: 6px; }
.cafe .chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 12px; border: 1.5px solid var(--rule); border-radius: var(--r-full);
  background: var(--bg-raised); color: var(--ink-secondary); cursor: pointer;
  font: 500 13px/1 var(--font-ui); letter-spacing: -.004em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}
/* ⚠ 28px DRAWN, 44px TAPPED. A chip drawn at the 44px floor turns every menu row into a
   control panel and pushes the price off the line on a 320px phone; the hit area is grown
   past the painted pill instead, which is the pattern tokens.css publishes as .tap-min. */
.cafe .chip { position: relative; }
.cafe .chip::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 100%; height: 100%; min-width: var(--tap-min); min-height: var(--tap-min);
}
/* Selected is INK-filled, not orange. Orange is the one thing on the screen you are meant to
   press — on the cart view that is the slide; a selected chip would be competing with it. */
.cafe .chip[aria-checked="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised); font-weight: 600;
}
.cafe .chip-add {
  font: 600 11px/1 var(--font-ui); font-variant-numeric: tabular-nums; opacity: .72;
}

/* ── The stepper ─────────────────────────────────────────────────────────── */
/* A quantity lives on the menu row on purpose: a second coffee is the most common next
   action, and making somebody open the cart to add it is a trip for nothing. */
.cafe .stepper { display: flex; align-items: center; gap: 2px; flex: none; }
.cafe .step {
  width: 32px; height: 32px; flex: none; position: relative;
  display: grid; place-items: center;
  border: 1px solid var(--rule); border-radius: 50%;
  background: var(--bg-raised); color: var(--ink);
  font: 500 18px/1 var(--font-ui); cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out);
}
/* 32px drawn, 44px tappable — the ring stays small because a column of big circles pulls the
   eye off the names and the prices, which are what somebody is actually reading. */
.cafe .step::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min);
}
.cafe .step-add { background: var(--bg-sunken); border-color: transparent; }
.cafe .step-n {
  min-width: 22px; text-align: center;
  font: 600 15px/1 var(--font-ui); color: var(--ink);
}

/* ── The stepper where it sits on artwork ─────────────────────────────────────
 * ⚠ THE ADD CONTROL IS INK-FILLED, NOT ORANGE, AND THIS IS THE ONE PLACE THE SCREEN
 *   DEPARTS FROM GRAB. Grab paints every add button in its action colour because it has one
 *   action colour and one floating bar. Here `--action` is the app's whole "press this"
 *   signal, and tokens.css is explicit that two orange things on a screen means one of them
 *   is wrong. Fifteen orange circles would drown the cart bar — the one control that
 *   actually finishes an order — which is the opposite of what this rebuild is for. Ink at
 *   36px on a sunken tile is unmissable at row level and the orange stays with the bar.
 * ⚠ THE PILL GROUND ONLY APPEARS WHEN THE STEPPER EXPANDS. At qty 0 it is a single circle
 *   with its own shadow, floating on the picture; at qty 1 it is three controls, and a
 *   `−` drawn straight onto the artwork behind it is unreadable. `data-has` is written by
 *   the renderer at the same moment the `−` appears, so the two can never disagree. */
.cafe .dish-stepper, .cafe .sigcard-stepper { gap: 3px; }
.cafe .dish-stepper[data-has], .cafe .sigcard-stepper[data-has] {
  padding: 3px; border-radius: var(--r-full);
  background: var(--bg-raised); box-shadow: var(--shadow-2);
}
.cafe .dish-stepper .step-add, .cafe .sigcard-stepper .step-add {
  width: 36px; height: 36px;
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised);
  font: 500 21px/1 var(--font-ui); box-shadow: var(--shadow-2);
}
/* Inside the pill the shadow is the pill's job — two stacked shadows on a 36px circle read
   as a smudge at this size. */
.cafe .dish-stepper[data-has] .step-add,
.cafe .sigcard-stepper[data-has] .step-add { box-shadow: none; }
.cafe .dish-stepper .step:not(.step-add),
.cafe .sigcard-stepper .step:not(.step-add) {
  border-color: transparent; background: var(--bg-sunken);
}

/* ── The preview controls ────────────────────────────────────────────────────
 * ⚠ DELIBERATELY UGLIER THAN THE APP. Dashed, uppercase, quiet — it has to be obvious at a
 *   glance that this is scaffolding for looking at the thing, not part of the thing. A
 *   preview switch that looks like app UI gets reviewed as app UI.
 */
.cafe .pv {
  margin: 4px 20px 18px; padding: 11px 13px 12px;
  border: 1px dashed var(--steel-40); border-radius: var(--r-md);
  background: transparent;
}
.cafe .pv-lab {
  font: 600 10px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-tertiary); margin-bottom: 9px;
}
.cafe .pv-row { display: flex; gap: 7px; flex-wrap: wrap; }
/* 34px was under the 44px floor. Scaffolding or not, it is a control somebody taps on a
   phone — and the person tapping it is who this screen exists for right now. */
.cafe .pv-opt {
  flex: 1 1 auto; min-height: var(--tap-min); padding: 7px 12px;
  border: 1px solid var(--rule); border-radius: var(--r-full);
  background: var(--bg-raised); color: var(--ink-secondary);
  font: 500 13px/1 var(--font-ui); cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}
/* The chosen persona is marked by a filled ground AND the word it carries — never by colour
   alone, which is invisible in daylight on a court. */
.cafe .pv-opt[aria-pressed="true"] { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.cafe .pv-note { margin-top: 9px; font: 400 12px/1.45 var(--font-ui); color: var(--ink-tertiary); }

/* ── The floating cart pill ──────────────────────────────────────────────────
 * ⚠ IT SAYS "CART" AND "NOT PAID", NEVER "ORDER". Nothing has been ordered: the bar has not
 *   been told, no drink is being made, and the money has not moved.
 */
.cafe .cartbar {
  position: absolute; z-index: 55;
  /* Clears the floating nav via shell.css's --fnav-clear rather than a number of its own.
     The number here used to be 84px, which was right for a 66px nav and silently wrong the
     day the nav grew to fit its labels — the nav then sat on the bottom 12px of this bar,
     which is where the running total and the Review chip live. */
  left: 12px; right: 12px; bottom: calc(var(--fnav-clear) + 4px);
  /* Enters from below with the nav, exits the same way and faster. */
  transform: translateY(16px); opacity: 0;
  transition: transform var(--t-menu) var(--ease-out), opacity var(--t-exit) var(--ease-out);
}
.cafe .cartbar.is-up { transform: translateY(0); opacity: 1; }
.cafe .cartbar-btn {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 9px 10px 9px 9px; border: 0; border-radius: var(--r-full);
  background: var(--ink); color: var(--bg);
  box-shadow: var(--shadow-3); cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out);
}
.cafe .cartbar-n {
  width: 30px; height: 30px; flex: none; display: grid; place-items: center;
  border-radius: 50%; background: var(--bg); color: var(--ink);
  font: 600 14px/1 var(--font-ui);
}
.cafe .cartbar-txt { flex: 1; min-width: 0; text-align: left; }
.cafe .cartbar-lab { display: block; font: 600 14px/1.2 var(--font-ui); letter-spacing: -.006em; }
.cafe .cartbar-sum { display: block; font: 400 12px/1.3 var(--font-ui); opacity: .72; }
.cafe .cartbar-cta {
  flex: none; padding: 8px 14px; border-radius: var(--r-full);
  background: var(--action); color: var(--action-ink);
  font: 600 13px/1 var(--font-ui);
}
/* The pill floats over the end of the list, so the list has to end above it: the nav's own
   clearance, plus the pill's 4px gap, its ~50px height and a little air. */
.cafe .pane-scroll.has-cart { padding-bottom: calc(var(--fnav-clear) + 68px); }

/* ── Back bar ────────────────────────────────────────────────────────────── */
/* ⚠ NOT ORANGE, even though iOS would tint a back link — and proshop.css carries the same
   note for the same control. Orange is the action colour and the slide is already wearing it
   on this view. 15px orange on cream is also the lowest-contrast text in the app. */
.cafe .cafe-backbar { padding: 4px 20px 2px; }
.cafe .cafe-back {
  display: inline-flex; align-items: center; gap: 3px;
  border: 0; background: transparent; cursor: pointer;
  padding: 8px 10px 8px 4px; margin-left: -4px; border-radius: var(--r-full);
  color: var(--ink-secondary);
  font: 600 15px/1 var(--font-ui); letter-spacing: -.008em;
  min-height: var(--tap-min);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.cafe .cafe-back svg { width: 18px; height: 18px; }

/* ── A cart line ──────────────────────────────────────────────────────────────
 * ⚠ IT CARRIES THE MENU'S ARTWORK AT THUMBNAIL SIZE, and the pro shop's cart does the same
 *   with its product art. A cart is where somebody checks they ordered what they meant to,
 *   and matching four pictures is faster than re-reading four names.
 * ⚠ `.cline`, NOT `.dish`. The menu row grew an artwork column and a chip strip; a cart line
 *   that inherited both would be a menu row that cannot be configured — and the − / + here
 *   address a cart LINE key, not an item id, so the two must not share a selector either. */
.cafe .cline { align-items: center; gap: 12px; padding: 12px 14px; }
.cafe .cline-art {
  flex: none; display: grid; place-items: center; width: 48px; height: 48px; padding: 5px;
  background: var(--bg-sunken); border-radius: var(--r-sm);
}
.cafe .cline-side { flex: none; display: flex; align-items: center; gap: 10px; }
.cafe .cline .dish-price { margin: 0; font: 600 15px/1.2 var(--font-ui); color: var(--ink); }

/* ── Destination ─────────────────────────────────────────────────────────── */
/* The pro shop's fulfilment control, unchanged. Two rows at most, and the second only exists
   because a real booking supplied the first — there is no third row where a court number
   could be typed. */
.cafe .choices { margin: 0 20px; display: grid; gap: 10px; }
.cafe .choice {
  display: flex; align-items: flex-start; gap: 12px; text-align: left;
  width: 100%; padding: 14px 15px; cursor: pointer;
  border: 1.5px solid var(--rule); border-radius: var(--r-lg);
  background: var(--bg-raised); color: var(--ink);
  font-family: var(--font-ui);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out), border-color var(--t-press) var(--ease-out);
}
.cafe .choice[aria-checked="true"] { border-color: var(--ink); }
.cafe .choice .grow { flex: 1; min-width: 0; }
.cafe .choice-tick {
  flex: none; width: 22px; height: 22px; margin-top: 1px;
  display: grid; place-items: center; border-radius: 50%;
  border: 1.5px solid var(--rule); color: transparent;
}
.cafe .choice-tick svg { width: 13px; height: 13px; }
.cafe .choice[aria-checked="true"] .choice-tick {
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised);
}
.cafe .choice-title { display: block; font: 600 15px/1.3 var(--font-ui); letter-spacing: -.008em; }
.cafe .choice-meta {
  display: block; margin-top: 4px;
  font: 400 13px/1.45 var(--font-ui); color: var(--ink-tertiary);
}
/* The state word sits with the tick, not instead of it: DESIGN.md §2 — colour is never the
   only carrier of a state, and a bare tick is a colour with no word at all. */
.cafe .choice .tag { flex: none; margin-top: 2px; }

/* The cart and the destination, frozen while a payment is in flight. Everything stays
   readable — what cannot happen is changing a basket whose total has already been sent. */
.cafe .is-locked { pointer-events: none; opacity: .5; }

/* ── Totals ──────────────────────────────────────────────────────────────── */
.cafe .totals {
  margin: 0 20px; padding: 15px 16px;
  background: var(--bg-raised); border-radius: var(--r-lg); box-shadow: var(--shadow-1);
}
.cafe .totals-row {
  display: flex; justify-content: space-between; gap: 16px; padding: 4px 0;
  font: 400 15px/1.4 var(--font-ui); color: var(--ink-secondary);
}
.cafe .totals-row.is-total {
  font: 600 18px/1.3 var(--font-ui); letter-spacing: -.012em; color: var(--ink);
}

/* ── Slide to pay ───────────────────────────────────────────────────────────
 * The commit control from community.supply/queue.
 * ⚠ IT IS A SLIDE AND NOT A BUTTON BECAUSE OF WHAT IS ON THE OTHER SIDE OF IT. This is the
 *   instant a cart becomes a real, paid order with a number, on a phone held one-handed at a
 *   venue. A deliberate gesture cannot be made by accident; a 44px button can.
 */
.cafe .cafe-commit { margin: 18px 20px 0; }
.cafe .slide {
  position: relative; height: 60px; border-radius: var(--r-full);
  background: var(--bg-sunken); overflow: hidden;
  display: grid; place-items: center;
}
.cafe .slide-fill {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--action) 24%, transparent);
  transform: scaleX(0); transform-origin: left center;
}
.cafe .slide-fill.is-full { transform: scaleX(1); }
.cafe .slide-label {
  position: relative; padding-left: 46px; pointer-events: none;
  font: 600 15px/1 var(--font-ui); letter-spacing: -.006em; color: var(--ink-secondary);
}
.cafe .slide-knob {
  position: absolute; left: 4px; top: 4px;
  width: 52px; height: 52px; border: 0; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--action); color: var(--action-ink);
  cursor: grab; -webkit-tap-highlight-color: transparent;
  /* ⚠ touch-action: none, NOT manipulation. The gesture is horizontal inside a vertical
     scroller, and with the default the browser claims the drag as a scroll after a few
     pixels — the knob follows the finger for one frame and then sticks while the page moves
     underneath it. This is the one control in the app that must own its pointer stream. */
  touch-action: none;
}
.cafe .slide-knob:active { cursor: grabbing; }
/* ⚠ THE PRESS SCALE LIVES ON THE INNER GRIP, NOT ON THE KNOB, AND IT IS CSS RATHER THAN
   pressable(). Both pressable() and the drag write el.style.transform, so on the knob they
   overwrite each other and it jumps to the left on the first frame of every drag. A child
   element cannot collide, and :active is already instant. cafe.js carries the same note. */
.cafe .slide-grip {
  display: grid; place-items: center; width: 22px; height: 22px;
  transition: transform var(--t-press) var(--ease-out);
}
.cafe .slide-knob:active .slide-grip { transform: scale(0.9); }
.cafe .slide-grip svg { width: 22px; height: 22px; }
.cafe .slide.is-paying .slide-label { color: var(--ink); padding-left: 0; }

.cafe .cafe-note {
  margin: 11px 2px 0; text-align: center;
  font: 400 13px/1.5 var(--font-ui); color: var(--ink-tertiary); letter-spacing: .02em;
}

/* ── The pager ──────────────────────────────────────────────────────────────
 * ⚠ `.card.navy`, NOT A LOCAL NAVY. This file used to mint its own --pager-bg with its own
 *   dark-mode override, because in dark --bg IS navy and a navy card on a navy page is one
 *   flat rectangle. ui.css now solves that for every navy card in the app (it swaps to
 *   --bg-raised with a rule after dark), so the local copy is deleted — a second definition
 *   of the same idea is a second thing to forget to update.
 * ⚠ INK ON THIS CARD COMES FROM --on-navy / --on-navy-dim, NEVER FROM --ink. The card is
 *   navy in light mode by design, and --ink is navy there too: an --ink label on it is
 *   invisible.
 */
.cafe .pager {
  padding: 18px 20px 20px; cursor: pointer;
  --pager-rule: color-mix(in srgb, var(--cream) 16%, transparent);
  transition: transform var(--t-press) var(--ease-out);
}
.cafe .pager-top {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.cafe .pager-paid { font: 500 12px/1 var(--font-ui); color: var(--on-navy-dim); }

/* The two stat tiles — the queue page's furniture, reading NUMBER and DESTINATION instead of
   IN THE LINE and NOW SERVING, because this number is a pager for a drink already paid for
   and already moving. Nobody here is in a line. */
.cafe .tiles {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  margin: 18px 0 16px;
}
.cafe .tile { min-width: 0; }
/* The rule between them is a hairline, not a box: two boxed tiles inside a card is three
   nested rectangles, and the number stops being the loudest thing on the screen. */
.cafe .tile + .tile { padding-left: 14px; border-left: 1px solid var(--pager-rule); }
.cafe .tile-lab {
  display: block; font: 600 10px/1 var(--font-ui); letter-spacing: .1em;
  text-transform: uppercase; color: var(--on-navy-dim);
}
/* ⚠ 52px, NOT ui.css's 76px. The number shares its row with the destination now, and at 76px
   a three-digit number and "Counter" cannot both fit on a 320px phone. It is still by far
   the largest thing in the tab, which is the requirement — it is read across a table, at
   arm's length, by somebody not looking at their phone properly. */
.cafe .tile-num { display: block; margin-top: 8px; font-size: 52px; color: var(--on-navy); }
.cafe .tile-val {
  display: block; margin-top: 10px;
  font-family: var(--font-display); font-weight: 600; font-size: 26px; line-height: 1.1;
  letter-spacing: -.02em; color: var(--on-navy);
  overflow-wrap: break-word;
}
.cafe .pager-state {
  font: 600 18px/1.25 var(--font-ui); letter-spacing: -.012em; color: var(--on-navy);
  text-wrap: balance;
}

/* The rail. Four labelled stops — a colour with no word beside it says nothing to a
   colourblind player, or to anyone reading this in the sun. */
.cafe .rail { margin-top: 18px; }
.cafe .rail-track {
  height: 3px; border-radius: 2px; overflow: hidden;
  background: color-mix(in srgb, var(--cream) 18%, transparent);
}
.cafe .rail-fill {
  display: block; height: 100%; width: 100%; transform-origin: left center;
  background: var(--live);
  /* transform only — animating width would relayout the card on every state change */
  transition: transform var(--t-sheet) var(--ease-out);
}
.cafe .rail-labs {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin-top: 8px;
}
.cafe .rail-labs span {
  font: 500 10px/1.2 var(--font-ui); letter-spacing: .02em;
  color: var(--on-navy); opacity: .42; text-align: center;
  transition: opacity var(--t-menu) var(--ease-out);
}
.cafe .rail-labs span:first-child { text-align: left; }
.cafe .rail-labs span:last-child { text-align: right; }
.cafe .rail-labs span.on { opacity: 1; }

/* The Live tag has to hold its own on navy, and it already does: chartreuse on navy is the
   app's loudest legible pair. Nothing to override — this note exists so the next person does
   not go looking for the rule that must surely be here. */

/* ── Hover, gated ─────────────────────────────────────────────────────────── */
/* ⚠ A touch device fires hover on tap, so an ungated :hover leaves a control looking stuck
   in a hover state after the finger lifts. */
@media (hover: hover) and (pointer: fine) {
  .cafe .chip[aria-checked="false"]:hover { border-color: var(--ink-secondary); color: var(--ink); }
  .cafe .step:hover { background: var(--bg-sunken); }
  .cafe .choice:hover { border-color: var(--ink-secondary); }
  .cafe .cafe-live:hover { background: var(--bg-sunken); }
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
 * Naming this file's own animations, per DESIGN.md §4.8: the view push, the cart pill's
 * rise, the rail fill and its labels, the slide knob's press, and the press transitions on
 * chips, steppers, choices and the pager card. The colour and opacity changes stay — they
 * are what tells you the state changed; the movement goes.
 * ⚠ The slide-to-pay knob still tracks the finger, and that is correct: it is not an
 *   animation, it is the control. Its spring-back is handled in motion.js, which snaps
 *   instead of springing under this preference.
 */
@media (prefers-reduced-motion: reduce) {
  [data-ready] .cafe .cafe-view { transition-property: opacity; }
  @starting-style {
    .cafe .cafe-view.from-right, .cafe .cafe-view.from-left { opacity: 0; transform: none; }
  }
  .cafe .cartbar { transition-property: opacity; transform: none; }
  .cafe .cartbar.is-up { transform: none; }
  .cafe .rail-fill, .cafe .rail-labs span { transition-duration: 1ms; }
  .cafe .slide-grip { transition-property: opacity; }
  .cafe .chip, .cafe .step, .cafe .choice, .cafe .pv-opt, .cafe .cartbar-btn,
  .cafe .cafe-live, .cafe .pager { transition-property: background-color, border-color, color; }
}

@media (prefers-contrast: more) {
  .cafe .pv { border-style: solid; border-color: var(--ink); }
  .cafe .chip, .cafe .step, .cafe .choice, .cafe .totals, .cafe .slide { border: 1px solid var(--ink); }
  .cafe .rail-labs span { opacity: .8; }
}


/* ══ THE ITEM SCREEN ═══════════════════════════════════════════════════════════
   Syd, 2026-09-03: "when you click the + button or the food item it opens a second screen
   with all the customize optioons / milk options ect for those drinks."

   ⚠ THE CHOICES ARE FULL-WIDTH ROWS HERE, NOT CHIPS. Chips were right in a list row, where
     the whole point was to spend as little vertical space as possible. On a screen whose only
     job is choosing, a row is the better control: it has room for the option, its price and a
     selection mark; it is a 44px target instead of a 30px one; and a column of rows can be
     read down in one pass, which a wrapped bag of chips cannot.
   ⚠ STILL NOT A NATIVE <select> — Syd's standing rule, and the same reasons the chips gave:
     an OS wheel covers the price while it is open and cannot show a per-option price change. */
.cafe .itm { padding: 0 20px 8px; }

/* The picture gets a real size here — this is the one screen where the drawing is the subject
   rather than a thumbnail identifying a row. */
.cafe .itm-hero {
  display: grid; place-items: center;
  height: 190px; padding: 22px; margin-bottom: 18px;
  background: var(--bg-sunken); border-radius: var(--r-lg);
}
.cafe .itm-head { margin: 0 0 20px; }
.cafe .itm-name {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 27px;
  line-height: 1.12; letter-spacing: -.02em; color: var(--ink);
}
.cafe .itm-note { margin: 5px 0 0; font: 400 14px/1.45 var(--font-ui); color: var(--ink-secondary); }
.cafe .itm-price {
  margin: 10px 0 0; font: 600 18px/1 var(--font-ui); color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.cafe .itm-grp { margin: 0 0 18px; }
.cafe .itm-grp-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin: 0 0 8px;
}
.cafe .itm-grp-head h2 {
  margin: 0; font: 700 11px/1 var(--font-ui); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.cafe .itm-grp-n { font: 500 12px/1 var(--font-ui); color: var(--ink-tertiary); }

.cafe .itm-opts { display: grid; gap: 8px; }
.cafe .itm-opt {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: var(--tap-min); padding: 11px 14px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--bg); color: var(--ink); text-align: left; cursor: pointer;
  font: 500 15px/1.2 var(--font-ui);
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
/* ⚠ A MARK, NOT ONLY A FILL. DESIGN.md §2: every state has a colour, a word and — where it is
   an icon — a glyph. A selected row that differs from its neighbours by background alone
   disappears in greyscale and in sunlight, which is where this app is used. */
.cafe .itm-mark {
  flex: none; width: 21px; height: 21px; border-radius: 50%;
  border: 1.5px solid var(--rule); background: transparent;
  display: grid; place-items: center;
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.cafe .itm-opt[aria-checked="true"] {
  border-color: var(--ink); background: var(--selected-soft-bg);
}
.cafe .itm-opt[aria-checked="true"] .itm-mark { background: var(--ink); border-color: var(--ink); }
.cafe .itm-opt[aria-checked="true"] .itm-mark::after {
  content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--bg);
}
.cafe .itm-opt-lab { flex: 1 1 auto; min-width: 0; }
.cafe .itm-opt-add { flex: none; color: var(--ink-secondary); font-variant-numeric: tabular-nums; }

.cafe .itm-qty {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 22px 0 0; padding: 12px 14px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
}
.cafe .itm-qty-lab { font: 600 15px/1 var(--font-ui); color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .cafe .cafe-view.is-leaving.to-left,
  .cafe .cafe-view.is-leaving.to-right { transform: none; }
  .cafe .itm-opt, .cafe .itm-mark { transition-property: background-color, border-color; }
}
@media (prefers-contrast: more) {
  .cafe .itm-opt { border-color: var(--ink); }
}


/* ══ THE PAGER PAGE'S OWN LANE ═════════════════════════════════════════════════
   Syd, 2026-09-03: the queue page "is all messed up in trms of scalling and margins".

   ⚠ THE MEASUREMENT, BECAUSE IT NAMES THE BUG EXACTLY. On the deployed build the page's
     content lane ran 32 → 343: the pager card, the preview control and the New order button
     all sat in it. `.sec` and `.list` are ui.css's full-bleed list chrome and ran 16 → 359 —
     16px proud of the lane on BOTH sides. Worse, `.sec-head` pads its label INTO the lane, so
     the words "ITEMS" and "RECEIPT" lined up correctly above cards that did not. Two
     different left edges, four pixels of ambiguity apart, is what reads as broken.
   ⚠ THE TYPE COMES DOWN TOO. These rows were 16px titles in 66px-tall cells — the same weight
     as the item names on the menu, directly under a card whose whole job is to be the biggest
     thing on the screen. A receipt is supporting detail; it is read once, after the number it
     is confirming. 15px titles in a 56px row keep the card first. */
.cafe .pg-sec { padding-inline: 20px; }
.cafe .pg-sec .sec-head { padding-inline: 0; }
.cafe .pg-sec .list { border-radius: var(--r-md); overflow: hidden; }
.cafe .pg-sec .row { min-height: 0; padding: 13px 14px; }
.cafe .pg-sec .row .title { font: 600 15px/1.3 var(--font-ui); }
.cafe .pg-sec .row .meta  { font: 400 13px/1.35 var(--font-ui); margin-top: 2px; }
.cafe .pg-sec .row .value { font: 600 15px/1.2 var(--font-ui); font-variant-numeric: tabular-nums; }
/* The count beside a section label is a label too, not a second heading. It was set at body
   size and read as loud as the rows it was counting. */
.cafe .pg-sec .sec-head .muted { font: 500 13px/1 var(--font-ui); color: var(--ink-tertiary); }
