/* The menu page.
   Extracted from templates/restaurant/menu.blade.php on 2026-09-22; it was inlined into every
   ordering page, and those pages are never cached. Linked with a content
   hash (App\Support\StaticAsset), so the browser keeps it for a week and
   fetches it again only when a deploy changes it. Nothing here may depend on
   the page: a value that differs per request stays inline in the template. */

/* Spacing on a 4px scale (8/12/16/24/40/64). The old scale ran
   .75/.9/2/2.5rem with nothing between, which made a category break
   55.4px against a row break's 29.8px - 1.86x, not enough for the eye to
   read as a break, which is why the accordion was doing work that
   spacing does for free. */
.ord-menu{display:flex;flex-direction:column;gap:1.5rem}

/* --- category rail --- */
/* position:relative so the rail is the offsetParent of its own pills: the
   observer below centres the active pill by setting rail.scrollLeft from
   pill.offsetLeft, and offsetLeft is measured from the offsetParent's
   padding box - without one it resolves against <body> and is off by the
   shell's and the card's padding. In the BASE rule rather than the phone
   block on purpose: the observer's own guard is
   matchMedia('(min-width:900px)'), so a rule gated at max-width:899.98px
   would leave the band between them running the JS with no positioned
   ancestor. The desktop block below overrides this with position:sticky. */
.ord-rail{display:flex;gap:.4rem;overflow-x:auto;list-style:none;margin:0;padding:0 0 .4rem;scrollbar-width:thin;position:relative}
.ord-rail a{flex:0 0 auto;padding:.55rem .9rem;border:0;border-radius:var(--ord-btn-radius);text-decoration:none;font-size:.875rem;font-weight:600;color:var(--ord-ink-muted);white-space:nowrap;background:transparent;transition:background .15s,color .15s,box-shadow .15s}
.ord-rail a:hover{background:#f8fafc;color:var(--ord-ink)}
/* The tint stays exactly as it was, and deliberately so: this rule is the
   ONLY consumer of the merchant's secondary colour anywhere in the
   product - ThemeCss emits --c-secondary and, before this rule existed,
   nothing referenced it, so the theme editor's secondary picker changed
   nothing on a live site. Replacing it with a literal would re-create
   that.
   What IS wrong is that the tint measures ~1.26:1 on the warm and minimal
   presets and ~1.14:1 on dark, so on its own the marker was carried by a
   colour shift nobody can see. The inset rule below is preset-independent
   and does the actual work; the tint is the flourish on top. */
.ord-rail a{position:relative}
.ord-rail a.is-active{background:color-mix(in srgb,var(--secondary-ink) 12%,transparent);color:var(--ord-ink);font-weight:700}
/* A pseudo-element, not an inset box-shadow: a shadow follows the pill's
   border-radius, so the marker curved at both ends and read as a stray
   outline rather than a bar. */
.ord-rail a.is-active::before{content:"";position:absolute;left:.55rem;right:.55rem;bottom:.15rem;height:2px;border-radius:2px;background:var(--ord-ink)}

/* --- category --- */
.ord-cat{scroll-margin-top:5.5rem}
.ord-cat + .ord-cat{margin-top:4rem}
.ord-cat-desc{margin:-.5rem 0 1rem;color:var(--ord-ink-muted);font-size:.9375rem}
.ord-cat-title{font-size:1.5rem;font-weight:800;color:var(--ord-ink);margin:0 0 1rem;letter-spacing:-.02em;text-transform:none;line-height:1.15}
/* The category's own picture, beside its heading, the way the reference
   menu shows one. The photo and the name are wrapped together rather than
   dropped straight into the <h2>, because below 900px that heading is a
   flex row with justify-content:space-between over exactly TWO children -
   the name and the accordion toggle. A third child there spreads all three
   across the row and strands the picture at the far left, away from the
   name it belongs to. Wrapped, the heading keeps its two children at every
   width, and the wrapper is rendered whether or not there is a photo so
   that one layout is reasoned about rather than two.
   min-width:0 so a long name may still shrink and wrap inside the row;
   flex:none on the tile so the row takes that shrink out of the text
   instead of squashing the photograph. */
.ord-cat-heading{display:flex;align-items:center;gap:.625rem;min-width:0}
.ord-cat-photo{width:2.5rem;height:2.5rem;object-fit:cover;border-radius:10px;flex:none}
/* Base button reset so the heading toggle never picks up native button
   chrome (box, border, own padding). */
.ord-cat-toggle{width:100%;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-align:left;cursor:pointer}
/* The accordion, at every width. It used to be phone-only - desktop expanded
   every section and hid this control - so a customer on a laptop scrolling
   past seventy dishes had no way to fold away the courses they were done
   with. Desktop still ARRIVES expanded (the cats store opens every section
   there); it can close them now. */
.ord-cat-title{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:0 0 .75rem;border-bottom:1px solid var(--ord-rule)}
/* 2.75rem square, because this is the only control for collapsing a
   section and it wrapped a bare 20px chevron: measured at 390 AND 360 it
   was a 20x20 target, against the 2.75rem the item sheet's own controls
   already keep (ItemPanelTest pins those). The negative block margin
   cancels the 24px the taller box would otherwise add to .ord-cat-title,
   so the heading row keeps its height and only the tappable area grows. */
.ord-cat-toggle{display:inline-flex;align-items:center;justify-content:flex-end;flex:none;width:auto;min-width:2.75rem;min-height:2.75rem;margin-block:-.75rem}
.ord-cat-chevron{flex-shrink:0}
@media (prefers-reduced-motion:no-preference){
  .ord-cat-chevron{transition:transform .15s ease}
}
.ord-cat-chevron.is-open{transform:rotate(180deg)}

/* --- item rows ---
   A phone gets a compact row: thumbnail left, name and description
   beside it, price and the add control on a baseline of their own. The
   old row floated a 48px photo and put the price on the same line as the
   name, which left about 83px for a Cyrillic dish name at 320px - five
   lines for a real menu item. Giving the price its own line returns that
   width to the thing being sold.
   The row rule that used to separate items is gone: .ord-items wraps, and
   rows deliberately differ in height, so the left and right columns'
   hairlines landed at different heights all the way down and read as a
   table that had failed to render. Space separates them now. */
.ord-items{display:flex;flex-direction:column;gap:.5rem;list-style:none;margin:0;padding:0}
.ord-item{position:relative;display:flex;gap:.875rem;padding:.75rem;border-radius:14px;scroll-margin-top:5.5rem;transition:background-color .15s}
.ord-item:has(.ord-item-trigger:hover){background:#f8fafc}
/* Deliberately NOT opacity. A blanket .55 dropped the name, description,
   price and the chip itself to 2.1-2.5:1 - and this branch is the reason
   these rows are visible at all, so it would have been new exposure. The
   photo is dimmed instead, which is what carries "not tonight" visually,
   and the text keeps ratios that clear the floor. */
.ord-item.is-unavailable .ord-item-photo,.ord-item.is-unavailable .ord-item-photo-empty{opacity:.5;filter:grayscale(1)}
.ord-item.is-unavailable .ord-item-name{color:var(--ord-ink-muted)}
/* display:block, not display:contents: an <a> with display:contents is
   invisible to Chrome's focus handling (tabIndex reads 0 but .focus()
   silently no-ops), which breaks returning keyboard focus to the row
   when the item panel closes. */
.ord-item-trigger{display:flex;gap:.875rem;flex:1;min-width:0;width:100%;text-align:left;text-decoration:none;color:inherit;cursor:pointer;background:none;border:0;padding:0;font:inherit;border-radius:12px}
.ord-item-trigger:focus-visible{outline:2px solid var(--ord-ink);outline-offset:2px}
/* 4.5rem, not the old 3rem: a plated dish in a 48px square crop resolves
   to an ambiguous brown rectangle, which is a directory-listing pattern
   rather than a menu. */
.ord-item-photo,.ord-item-photo-empty{width:4.5rem;height:4.5rem;object-fit:cover;border-radius:12px;flex:none;display:block}
/* A faint plate-and-cutlery mark rather than a blank slab: at card size on
   desktop an empty tile is a large grey rectangle that reads as an image
   that failed to load. The stroke is a fixed mid-grey because a data URI
   cannot read var(); at this opacity it sits correctly on the tile. */
.ord-item-photo-empty{background:#f1f5f9 center/2rem no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 2v7a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2V2'/%3E%3Cpath d='M6 11v11'/%3E%3Cpath d='M20 22V2a4 4 0 0 0-4 4v6a2 2 0 0 0 2 2h2'/%3E%3C/svg%3E");border:1px solid var(--ord-rule)}
.ord-item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:.25rem}
.ord-item-foot{display:flex;align-items:center;gap:.5rem}
/* The name is the thing being sold and used to be the FOURTH largest
   size on the page, identical to its own price and separated from it by
   one weight step. */
.ord-item-name{margin:0;font-size:1.0625rem;font-weight:600;color:var(--ord-ink);line-height:1.3;letter-spacing:-.01em}
/* Clamped so rows stay even down a column; the full text is in the panel. */
.ord-item-desc{margin:0;font-size:.875rem;color:var(--ord-ink-muted);line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* Muted, not ink: the price informs, it does not compete with the name. */
.ord-item-price{font-size:.9375rem;font-weight:700;color:var(--ord-ink-muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.ord-item-options{list-style:none;margin:.25rem 0 0;padding:0;font-size:.8125rem;color:var(--ord-ink-muted)}
.ord-item-options li{padding:.1rem 0}
.ord-sold-out{display:inline-flex;align-items:center;padding:.2rem .5rem;border-radius:999px;background:#f1f5f9;color:#475569;font-size:.75rem;font-weight:700;letter-spacing:.02em}

/* --- the one-tap add --- */
.ord-item-cta{flex:none;display:flex;align-items:center}
.ord-item-cta form{margin:0}
/* Two hooks a skin may reveal. Drawn by nothing today except
   templates/poco/menu.blade.php: the rail here is a text index, and it is
   staying that way. */
.ord-rail-photo{display:none}
/* The empty basket's ghost lines (_cart_regions) are the third such hook. */
.ord-cart-ghosts{display:none}
/* Read by a screen reader, never drawn: the pane title's "Total" label. */
.ord-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.ord-quick-add,.ord-item-choose{display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:999px;border:0;cursor:pointer;padding:0;transition:background-color .15s,transform .08s}
.ord-quick-add{background:var(--c-primary);color:var(--c-primary-ink)}
.ord-quick-add:hover{background:var(--c-primary-hover)}
/* Outlined, not filled, and that is the whole point: this row has a
   required option group, so a filled '+' identical to the one beside it
   would promise a one-tap add the server is going to refuse. The
   fill-versus-outline vocabulary already exists here (.ord-panel-cta vs
   .ord-btn-quiet). */
.ord-item-choose{background:var(--ord-card);color:var(--ord-ink);border:1.5px solid var(--ord-rule)}
.ord-item-choose:hover{background:#f8fafc}
.ord-quick-add:focus-visible,.ord-item-choose:focus-visible{outline:2px solid var(--ord-ink);outline-offset:2px}
.ord-quick-add[disabled]{opacity:.45;cursor:not-allowed}

/* --- empty states ---
   One shape, used everywhere the surface has nothing to show: a line-art
   mark, a headline, one sentence, exactly one action. */
.ord-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.75rem;padding:3rem 1rem}
.ord-empty svg{color:var(--ord-rule)}
.ord-empty h2{margin:0;font-size:1.125rem;font-weight:700;color:var(--ord-ink)}
.ord-empty p{margin:0;max-width:26rem;font-size:.9375rem;color:var(--ord-ink-muted)}

/* --- desktop cart pane ---
   Hidden by a plain default rule and revealed only inside the >=1400 query
   below. The element is in the DOM at every width because there is no build
   step and the page has to work with JavaScript off, so nothing can insert
   it at the breakpoint. Left visible it would auto-place into the rail's
   track between 900 and 1399 and stack full-width under the menu on a phone,
   where the checkout bar already carries the cart. */
.ord-cart-pane{display:none}
/* NOT .skip-link. That one is positioned against the document and is only
   ever reached as the very first tab stop, where document and viewport
   coordinates agree; this one is reached after the header at any scroll
   position, so it has to be fixed to the viewport. And it is a real
   destination only where the pane is on screen, so below 1400 it is
   display:none rather than a tab stop pointing at something hidden. */
.ord-skip-cart{display:none}
/* The pane's parts, at every width. The pane itself stays display:none
   until the >=1400 column shows it (a template may show it earlier) or the
   basket store opens it as the sheet below the bar - which needs them too,
   so they no longer live inside the >=1400 block. */
.ord-cart-pane:focus{outline:none}
/* The title bar carries the count and the total, the action sits under it
   and the lines scroll below both - so however long the order, the total
   and the button are the first things in the pane. They used to sit in a
   footer under the lines, where a long order pushed them to the bottom
   edge of the screen. */
.ord-cart-pane-title{display:flex;flex-wrap:wrap;align-items:baseline;gap:.25rem .5rem;margin:0;font-size:1rem;font-weight:700;color:var(--ord-ink);letter-spacing:-.01em;text-transform:none}
.ord-cart-pane-count{display:inline-grid;place-items:center;align-self:center;min-width:1.35rem;height:1.35rem;padding-inline:.3rem;border-radius:999px;background:var(--c-primary);color:var(--c-primary-ink);font-size:.72rem;font-weight:700}
.ord-cart-pane-total{margin-left:auto;font-weight:700;font-size:1.05rem;white-space:nowrap;font-variant-numeric:tabular-nums}
.ord-cart-pane-action{flex:none;display:flex;flex-direction:column;gap:.75rem;padding-bottom:1rem;border-bottom:1px solid var(--ord-rule)}
.ord-cart-pane-action .ord-cta{width:100%}
/* The lines scroll; the title and the action never do. min-height:0
   because a flex item's default min-height:auto refuses to shrink below
   its content and the overflow would simply never engage. */
.ord-cart-pane-lines{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}
/* --ord-ink, not --ord-alert and not --ord-ink-muted: the banner at the
   top of the page already carries the red for this state, and a second red
   block beside a greyed-out button reads as an error the customer caused.
   Muted would be 4.30:1 if this pane ever moved onto --ord-canvas; ink is
   safe on either ground. */
.ord-cart-pane-note{margin:0;font-size:.85rem;font-weight:600;color:var(--ord-ink)}
/* Muted is safe here and only here: the pane paints --ord-card above. */
.ord-cart-pane-empty{margin:0;font-size:.9rem;color:var(--ord-ink-muted)}

/* --- checkout bar ---
   Once the cart has something in it, the way to checkout follows the
   customer down the menu rather than making them hunt for the header
   icon, which is a small target on a phone. */
/* Two elements, because the bar has two jobs. The BAR is the ground: it
   spans the glass, carries the rule, the shadow and the phone's
   safe-area inset, and is the element a cart sheet can later expand
   inside. The ROW is the part that has to line up with the menu above
   it, so it takes the shell's cap.

   Capped as one element the bar rendered as a 1120px plank floating in
   the middle of a 1920px viewport, with the menu card's edges 400px
   outside it - and once .ord-shell widens, worse.

   THE CAP LIVES ON .ord-checkout-inner AND NOWHERE ELSE. Anything that
   widens .ord-shell must widen this in the same media block (or in one
   shared rule); OrderingAppBarTest::test_the_checkout_bar_cap_tracks_the
   _shell_cap fails, by name, if the two ever disagree.

   Height is unchanged - .85rem + a 2.75rem control + .85rem + the inset,
   just split across the two elements - which is what keeps .ord-toast's
   bottom:calc(6.5rem + …) clearing the bar. The row keeps the bar's own
   1.25rem gutter rather than adopting the shell's 1.5rem, so every
   viewport below 1160 is pixel-identical to what shipped; above the cap
   the row's content sits 4px inside the shell's, the same 4px the
   header's brand ink already sits at. */
.ord-checkout-bar{position:fixed;left:0;right:0;bottom:0;z-index:43;padding-bottom:env(safe-area-inset-bottom);background:var(--ord-card);border-top:1px solid var(--ord-rule);box-shadow:0 -2px 12px rgba(15,23,42,.08)}
.ord-checkout-inner{display:flex;align-items:center;justify-content:space-between;gap:1rem;max-width:70rem;margin-inline:auto;padding:.85rem 1.25rem}
.ord-checkout-total{font-weight:700;font-size:1rem;white-space:nowrap;color:var(--ord-ink)}
.ord-checkout-go{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:2.75rem;padding:10px 20px;border-radius:var(--ord-btn-radius);background:var(--c-primary);color:var(--c-primary-ink);font-size:.9375rem;font-weight:600;text-decoration:none;border:0;transition:background-color .15s,transform .08s}
/* Not filter:brightness(.94): see .btn-primary:hover in _head.blade.php.
   --c-primary-hover already moves the right direction for whichever ink
   is on this control. */
.ord-checkout-go:hover{background:var(--c-primary-hover)}
.ord-checkout-go:focus-visible{outline:2px solid var(--ord-ink);outline-offset:2px}
/* The bar's total is also the way to see the order: the count, the total
   and a chevron, as one button that opens the basket sheet. Disabled in the
   markup until Alpine takes over, so without JavaScript it is the plain
   total it always was; its colours are stated here because a disabled
   button otherwise greys its text in the UA sheet. */
.ord-checkout-peek{display:inline-flex;align-items:center;gap:.55rem;min-width:0;min-height:2.75rem;margin-left:-.5rem;padding:.25rem .5rem;border:0;border-radius:var(--ord-btn-radius);background:none;color:var(--ord-ink);font:inherit;cursor:pointer}
.ord-checkout-peek:disabled{cursor:default}
.ord-checkout-peek:focus-visible{outline:2px solid currentColor;outline-offset:2px}
.ord-checkout-count{display:inline-grid;place-items:center;min-width:1.5rem;height:1.5rem;padding-inline:.35rem;border-radius:999px;background:var(--c-primary);color:var(--c-primary-ink);font-size:.78rem;font-weight:700;font-variant-numeric:tabular-nums}
.ord-checkout-chevron{flex:none}
.ord-checkout-peek.is-open .ord-checkout-chevron{transform:rotate(180deg)}

/* --- the basket as a sheet ---
   Below the width where the pane has a column, the bar's total opens this
   same pane over the menu: one list, one set of steppers, one region the add
   script swaps, so the sheet and the desktop column can never disagree. It
   rises from behind the bar - a z-index one under the bar's, so the join
   never shows - and stops at the bar's top edge (the bar's .85rem + 2.75rem
   + .85rem, its rule and the safe-area inset), so the bar that closes it and
   "to checkout" stay in reach. The pane's own "to checkout" strip would be
   the bar's button a second time, so the sheet leaves it out. Only the
   basket store sets .is-sheet, only while the bar is on screen, and it
   clears it on a resize that takes the bar away. */
.ord-cart-pane.is-sheet{display:flex;flex-direction:column;position:fixed;left:0;right:0;z-index:42;bottom:calc(4.45rem + 1px + env(safe-area-inset-bottom));max-height:calc(100vh - 11rem - var(--ord-top-extra, 0px));max-height:calc(100dvh - 11rem - var(--ord-top-extra, 0px));margin:0;padding:1rem 1.25rem 0;gap:.75rem;overflow:hidden;overscroll-behavior:contain;background:var(--ord-card);border-top:1px solid var(--ord-rule);border-radius:16px 16px 0 0;box-shadow:0 -12px 32px rgba(15,23,42,.18)}
.ord-cart-pane.is-sheet .ord-cart-pane-action{display:none}
.ord-cart-pane.is-sheet .ord-cart-pane-lines{padding-bottom:1rem}
/* Under the sheet and the bar, over everything else - the header and a
   pinned closed-for-now strip included (their block sits at 40), so the
   page dims as one. The bar sits at 43 for that: still under the toast (45)
   and the item sheet (60). A tap on the scrim closes the sheet; the page
   behind does not scroll under a finger or a wheel on it. */
.ord-basket-scrim{position:fixed;inset:0;z-index:41;background:rgba(15,23,42,.45)}
@media (prefers-reduced-motion:no-preference){
  .ord-checkout-chevron{transition:transform .2s ease}
  .ord-cart-pane.is-sheet{animation:ord-sheet-up .26s cubic-bezier(.2,.8,.2,1)}
  .ord-basket-scrim:not([hidden]){animation:ord-fade-in .2s ease-out}
  /* Set by the add script on the swap that creates the bar, never by the
     server, so a page that loads with a cart does not replay it. */
  .ord-checkout-bar.is-arriving{animation:ord-bar-up .32s cubic-bezier(.2,.8,.2,1)}
}
@keyframes ord-sheet-up{from{transform:translateY(100%)}}
@keyframes ord-bar-up{from{transform:translateY(100%)}}
@keyframes ord-fade-in{from{opacity:0}}
/* No padding reserve under the menu grid for the checkout bar. The rule
   that used to sit here claimed it stopped the bar covering the last row
   of the menu. It never could: the bar is position:fixed, so at maximum
   scroll it covers the last ~72px of the DOCUMENT, and the menu grid is
   never within 72px of that - the card's own bottom padding, the shell's
   footer and the shell's own padding-bottom always follow it, ~224px even
   for a merchant with no address, phone or hours. Height added above the
   footer cannot move what sits at the document's bottom edge, so the 96px
   this used to reserve was whitespace between the last dish and the
   footer and nothing else. (The bar DOES cover the tail of the footer at
   maximum scroll. That is real, and pre-existing, and not what this rule
   was doing.) */

/* Press feedback. On a phone this does more for perceived responsiveness
   than anything else on the page - the surface previously had no transform
   and no press state anywhere. */
@media (prefers-reduced-motion:no-preference){
  .ord-quick-add:active,.ord-item-choose:active,.ord-checkout-go:active{transform:scale(.94)}
  .ord-item:active{transform:scale(.995)}
}

@media (min-width:900px){
  /* The template is driven by an explicit class, never by how many children
     happen to be present. With the rail gated on a category count and the
     grid declared unconditionally, a one-category merchant's single child
     auto-placed into the 13rem rail track and their whole menu rendered
     208px wide. */
  .ord-menu{display:grid;grid-template-columns:1fr;align-items:start;gap:2.5rem}
  .ord-menu.has-rail{grid-template-columns:13rem 1fr}
  /* Bounded, because sticky alone is not enough: a twelve-category merchant's
     rail is taller than the viewport, so its tail sat below the fold with no
     way to reach it - the pills scroll now instead. 3rem is the shell's
     bottom breathing room; --ord-header-h keeps this tied to the one height
     the three coupled scroll offsets are also derived from.
     overflow-x stays declared but a non-visible overflow-y computes it to
     auto regardless; nothing overflows horizontally here because
     white-space:normal on the next line lets the pills wrap. */
  .ord-rail{flex-direction:column;position:sticky;top:calc(5.5rem + var(--ord-top-extra, 0px));max-height:calc(100vh - var(--ord-header-h) - 3rem - var(--ord-top-extra, 0px));overflow-x:visible;overflow-y:auto;padding:0}
  .ord-rail a{white-space:normal}
  /* The underline becomes a spine once the rail is a column. */
  .ord-rail a.is-active::before{left:0;right:auto;top:.4rem;bottom:.4rem;width:3px;height:auto}
  /* Cards, not rows, once there is room: a 4/3 photo is the difference
     between recognising a dish and guessing at a brown rectangle. A grid
     does stretch every card in a row to the tallest, which is exactly why
     the description is clamped to two lines above - the previous wrapping
     flex list existed to avoid that stretch, and clamping removes the
     reason for it. */
  /* 13rem, not 15: at the shell's 70rem cap the items column is about
     768px, so a 15rem floor gave two 350px cards - a gallery rather than a
     menu - where 13rem gives three at about 245px. */
  /* No !important on display: x-show collapses a section by writing an
     inline display:none, and a section can be collapsed at this width
     now. An !important here would pin every one of them open. */
  .ord-cat-items{display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));gap:1.5rem 1.25rem}
  .ord-item{position:relative;flex-direction:column;gap:.75rem;padding:0;background:none}
  .ord-item:has(.ord-item-trigger:hover){background:none}
  .ord-item-trigger{flex-direction:column;gap:.625rem;height:100%}
  /* Grid stretches every card in a row to the tallest, so the price - and
     with it the add control anchored to the same line - is pushed to the
     bottom rather than floating under a short description. */
  .ord-item-foot{margin-top:auto;min-height:2.75rem;padding-right:3.25rem}
  .ord-item-cta{position:absolute;right:0;bottom:0}
  .ord-item-photo,.ord-item-photo-empty{width:100%;height:auto;aspect-ratio:4/3;border-radius:12px}
  @media (prefers-reduced-motion:no-preference){
    .ord-item-photo{transition:transform .25s ease}
    .ord-item:has(.ord-item-trigger:hover) .ord-item-photo{transform:scale(1.03)}
  }
  .ord-item-photo,.ord-item-photo-empty{overflow:hidden}
}

/* --- the console, >=1400 ---
   ONE breakpoint for the shell and the pane together. Ungated at 1280 the
   shell would widen while the pane still gated at 1400, so dragging a window
   from 1399 to 1400 would drop the grid from five columns to three - and a
   classic 15px scrollbar would move that cliff to a different window width
   on every platform. Gated together the count goes 3 -> 3 across the
   boundary and nothing jumps.

   The middle track, re-derived in a headless browser and validated against
   the 766px/242px this page actually renders at 1280 today:

     T = min(V,1920)
         - 48   .ord-shell padding (2 x 1.5rem)
         - 58   .ord-card box: 2 x 1.75rem padding + 2 x 1px border, and
                *{box-sizing:border-box} in _head.blade.php:70 puts the
                border inside the width
         - 240  rail (15rem)
         - 336  pane (21rem)
         - 80   the two 2.5rem .ord-menu gaps
       = min(V,1920) - 762

   and a card is (T - 20(n-1))/n, the 20 being .ord-cat-items' column gap.
   1400 -> 638 -> 3 x 199px. 1680 -> 918 -> 4 x 215px. 1920 -> 1158 -> 5 x
   216px, and it stops there. A model that does not reproduce 766/242 at
   1280 is wrong; this one does. */
@media (min-width:1400px){
  /* All four combinations, explicitly. Never child-count placement: the
     single-category merchant whose entire menu rendered 208px wide got there
     exactly that way. The two rail-only forms restate the >=900 values
     rather than inheriting them, so this block reads as the whole grid
     vocabulary instead of half of it - and 13rem stays 13rem there, because
     a 15rem rail is only affordable once the shell is 120rem, which it is
     not on a site that sells no food. */
  .ord-menu{grid-template-columns:minmax(0,1fr)}
  .ord-menu.has-rail{grid-template-columns:13rem minmax(0,1fr)}
  .ord-menu.has-cart{grid-template-columns:minmax(0,1fr) 21rem}
  .ord-menu.has-rail.has-cart{grid-template-columns:15rem minmax(0,1fr) 21rem}
  /* Explicit counts, never auto-fill. auto-fill snaps back to its bare floor
     the instant one more column fits, so the card width sawtooths and lands
     on exactly 184px at V=1558 and V=1762 - 24% narrower than today, at two
     widths nobody would think to sample. Scoped to .has-cart so an
     ordering-off menu keeps the >=900 auto-fill rule completely untouched.
     The floor is minmax(0,1fr), not a rem figure: with the column count
     already explicit a floor buys nothing at a 16px root (the tightest
     case, 1400, fits 3 x 199px into 638 with room to spare), and a rem
     floor actively hurts at a bumped one, because the rail (15rem) and
     pane (21rem) shrink this track in the same breath a rem floor raises
     what it needs. Measured with an 11.5rem floor in a real browser: at a
     20px root (a browser's "Large" text setting) the track is already 448px
     against a 740px floor, so the three cards spill out from under the
     grid and overlap the cart pane by up to 292px - and past roughly a
     24px root that shortfall outgrows the pane's own space too and becomes
     real horizontal scroll on the document itself, hundreds of px wide by
     a 32px root. 0 never binds at any root, so THIS track just narrows
     instead of blowing out - though the rail's and pane's own 15rem/21rem
     widths are a separate story and can still overflow the shell at an
     extreme enough root; that is unrelated to the floor this rule sets and
     is not what this token change addresses. */
  .ord-menu.has-cart .ord-cat-items{grid-template-columns:repeat(3,minmax(0,1fr))}
  .ord-skip-cart{display:block;position:fixed;z-index:100;right:1rem;top:-3.5rem;padding:.65rem 1.1rem;border-radius:var(--ord-btn-radius);background:var(--ord-ink);color:var(--ord-canvas);text-decoration:none;font-weight:600;font-size:.875rem;transition:top .15s ease}
  .ord-skip-cart:focus{top:1rem}
  /* Painted on --ord-card, and that is load-bearing rather than cosmetic:
     .cart-line-options and .cart-line-price are --ord-ink-muted, which
     measures 4.76:1 on #ffffff and 4.30:1 on --ord-canvas #eef4fb. A pane
     left on the page ground would fail the 4.5:1 floor on every option line
     in the cart (ordering.blade.php:243-246 records the same number for the
     footer).
     position:sticky works only because .ord-menu sets align-items:start at
     >=900 - without it the grid stretches this column to the row's height
     and there is nothing left to stick within. Do not "tidy" that away. */
  /* --ord-pane-top is where the pane's top edge actually is, measured by the
     script after the pane: at the top of the page it rests below the header,
     any closed-for-now banner and the card's padding, and only once scrolled
     does it hold at 5.5rem. A cap written against 5.5rem alone ran a full
     pane past the bottom of the screen until the page scrolled. Without the
     script the fallback is exactly that old cap. */
  .ord-cart-pane{display:flex;flex-direction:column;gap:1rem;position:sticky;top:calc(5.5rem + var(--ord-top-extra, 0px));max-height:calc(100vh - var(--ord-pane-top, 5.5rem) - 1.5rem);background:var(--ord-card);border-left:1px solid var(--ord-rule);padding-left:1.5rem}
  /* A destination for the skip link, never a control. */
  .ord-cart-pane:focus{outline:none}
  /* The fixed bar is how the cart is reached BELOW 1400. Above it the pane is
     permanently on screen, so the bar would be a second, narrower copy of the
     same total and the same link floating over the middle of an 1856px page.
     No padding-bottom to undo here any more - the menu grid never reserved
     space for the bar in the first place (see the comment above
     .ord-checkout-bar's own rule). */
  .ord-checkout-bar{display:none}
}
@media (min-width:1680px){
  .ord-menu.has-cart .ord-cat-items{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (min-width:1920px){
  /* 1920 is the ceiling: past it the shell's 120rem cap holds the track at
     1158 and a sixth column would only shrink the cards again. */
  .ord-menu.has-cart .ord-cat-items{grid-template-columns:repeat(5,minmax(0,1fr))}
}
@media (max-width:899.98px){
  /* The rail bleeds to the card's edges. Inset by the card's padding it
     stopped 28px short of them, and a strip that stops short reads as
     "the list ends here" rather than "there is more to the right" - on a
     nine-category menu that is 2,088-2,622px of pills inside a ~332px
     strip. The bleed is the card's own horizontal padding, restated here
     rather than reached for through the shared shell (ordering.blade.php's
     .ord-card sets 1.75rem, and 1rem at <=640px), and given straight back
     as padding so the first pill still lines up with the dish rows above.
     The fade is exactly ONE BLEED long, which is what keeps it honest: at
     maximum scroll it covers the gutter and nothing else, so the last
     pill is never dimmed. scroll-padding on BOTH sides, not just the
     start: tabbing to the last pill scrolls it flush to the scrollport's
     right edge, i.e. straight under the fade. */
  .ord-rail{--ord-rail-bleed:1.75rem;margin-inline:calc(-1 * var(--ord-rail-bleed));padding:0 var(--ord-rail-bleed) .4rem;gap:.5rem;scroll-snap-type:x proximity;scroll-padding-inline:var(--ord-rail-bleed);-webkit-mask-image:linear-gradient(to right,#000 calc(100% - var(--ord-rail-bleed)),transparent);mask-image:linear-gradient(to right,#000 calc(100% - var(--ord-rail-bleed)),transparent)}
  /* 44px targets 8px apart, from 38.6px 6.4px apart (.875rem at the
     shell's line-height 1.5 is 21px, plus .55rem top and bottom). The
     padding is left alone and min-height does the growing, so the
     desktop rail - a column of pills with white-space:normal - keeps
     wrapping to whatever height its text needs. */
  .ord-rail a{display:flex;align-items:center;min-height:2.75rem;scroll-snap-align:start}
  @media (max-width:640px){
    .ord-rail{--ord-rail-bleed:1rem}
  }
  /* Two lines, like the description it sits above. A real menu name runs
     one to four lines at 360-390px, which swung the row pitch between
     about 117px and 183px down a scrolling list and made rows read as
     items of different importance. Phone only: above 900px the card grid
     already stretches every card in a row to the tallest, so a clamp
     there would truncate a name that has the room for itself. The
     truncation is visual - the full name stays in the DOM for a screen
     reader, and the panel behind the row prints it in full.
     overflow-wrap:anywhere for the same reason .ord-brand-name carries
     it: one very long unbroken word otherwise runs out of the row. */
  .ord-item-name{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
}
/* Hidden by a plain default rule, not x-cloak: x-cloak (or any
   pre-existing inline display:none) on the same element as x-show stops
   Alpine's reactivity from ever re-running on this bundled Alpine build,
   so the overlay never reopens after the first click. A default-hidden
   class toggled with :class has none of that baggage, and it is also the
   correct no-JS resting state (a no-JS visitor reaches the full item
   page through its real link instead). */
.item-panel-overlay{display:none;position:fixed;inset:0;z-index:60;align-items:flex-end;justify-content:center;background:rgba(15,23,42,.55);padding:0}
.item-panel-overlay.is-open{display:flex}
/* dvh, with the vh declaration kept immediately before it as the
   fallback for a browser that does not know the unit and drops the whole
   declaration (Safari before 15.4). vh measures against the LARGEST
   viewport, so with the iOS toolbars showing, the bottom of this sheet -
   the sticky footer, which is the add button - started below the fold. */
.item-panel-dialog{background:var(--ord-card);color:var(--ord-ink);width:100%;max-width:34rem;max-height:92vh;max-height:92dvh;overflow-y:auto;box-shadow:0 -12px 40px rgba(0,0,0,.25)}
/* The dialog is only a focus target, never a control, so the browser's
   default ring around the whole panel is noise. Its controls keep theirs. */
.item-panel-dialog:focus{outline:none}
@media (min-width:640px){
  .item-panel-overlay{align-items:center;padding:1.5rem}
  .item-panel-dialog{max-height:88vh;max-height:88dvh}
}

/* Allergen pictograms on a menu row: small, muted, never a control (a row is
   already the button that opens the dish panel). The panel's sentence, the
   badges beside a choice and the icon itself live in ordering.css, which the
   standalone item page loads too. */
.ord-allergens{display:inline-flex;flex-wrap:wrap;gap:.2rem;align-items:center;color:var(--ord-ink-muted,#64748b);vertical-align:middle;margin-top:.2rem}
.ord-allergens .ord-al-text{flex-basis:100%;font-size:.8rem}
.ord-item-options .ord-opt-allergens{margin:0 0 0 .35rem;vertical-align:middle}

/* Motion for menu-motion.js. Everything below is behind the motion guard;
   under reduced motion the script never runs, so .ord-motion, .is-popped and
   .is-bumped are never set and nothing here applies. */
@media screen and (prefers-reduced-motion:no-preference){
  .ord-motion .ord-item:not(.is-in){opacity:0;transform:translateY(14px)}
  .ord-motion .ord-item{transition:opacity .45s ease,transform .45s cubic-bezier(.2,.8,.2,1),background-color .15s,box-shadow .2s}
  .ord-motion .ord-item.is-in{transition:opacity .45s ease,transform .2s,background-color .15s,box-shadow .2s}
}
@media (prefers-reduced-motion:no-preference){
  .ord-quick-add.is-popped{animation:ord-pop .42s cubic-bezier(.3,1.6,.5,1)}
  .is-bumped{animation:ord-bump .38s cubic-bezier(.3,1.6,.5,1)}
}
/* A dish card lifts a little under a mouse; a finger gets the press state
   above instead. */
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .ord-item{transition:background-color .15s,transform .2s,box-shadow .2s}
  .ord-item:has(.ord-item-trigger:hover){transform:translateY(-2px);box-shadow:0 8px 22px rgba(15,23,42,.08)}
  .ord-item:has(.ord-item-trigger:hover):active{transform:translateY(-1px) scale(.995)}
}
@keyframes ord-pop{0%{transform:scale(1)}40%{transform:scale(1.22)}100%{transform:scale(1)}}
@keyframes ord-bump{0%{transform:scale(1)}45%{transform:scale(1.35)}100%{transform:scale(1)}}
