/*
 * Owner-style ordering page, built entirely from the theme layer.
 *
 * Orderable ships no CSS custom properties, so every colour below is a rule
 * override rather than a token swap. Rules marked NEEDS-IMPORTANT are the ones
 * where the plugin's own stylesheet wins on specificity and there is no other
 * way to take the declaration; they are counted in the customization verdict.
 * There are THREE, all because Orderable used !important first: the add
 * button's padding, the product title's margin, and the card photo's height.
 */

/* ------------------------------------------------------------------------
   A standing warning about tidying this file.

   Twice now a change that removed something apparently redundant introduced a
   new fault, because the removed value was load-bearing elsewhere:

   - Dropping the card's own border-bottom to fix doubled separators was
     correct, but the `:last-child { margin-bottom: 0 }` added alongside it
     made that item's grid cell taller than its neighbour's. Since the actions
     are bottom-aligned, the last Select button dropped 18px below the one
     beside it. Narrowed to `border-bottom: 0`.
   - Two rules written against the theme's page spacing parsed fine and did
     nothing at all, because the values they targeted are inline style
     attributes. They sat here looking alive until the computed values were
     measured.

   Before deleting a declaration, check what else depends on it. A margin on a
   stretched grid item is part of that item's height; a value that reads as
   decorative may be doing layout. Measure after the change, not before it.
   ------------------------------------------------------------------------ */

/* ---------- icon tokens ----------
   ONE plus glyph, used by the Add button on every card and by the increase
   button in the quantity roller (card, drawer mini-cart), so the three cannot
   drift apart. It is declared on :root rather than on `.orderable-main`
   because the drawer and the mini-cart are rendered outside that wrapper and
   would otherwise fall back to the plugin's own square-ended bars.

   WHY A MASK AND NOT AN <svg> ELEMENT IN THE MARKUP. The only PHP seam for
   this button's contents is `orderable_add_to_cart_button_args`, and its
   `text` is run through `wp_kses_post()` twice -- once inside
   `Orderable_Products::get_add_to_cart_button()` and again in the plugin's
   actions.php. WordPress allows no `svg` element in the post context (checked
   on this install: `wp_kses_allowed_html('post')` has no `svg` key and the
   markup comes back empty), so shipping a real inline SVG through that filter
   means widening `wp_kses_allowed_html` for every post-context render on the
   site. That is a security-surface change to draw an icon. The quantity
   roller has no filter at all -- its "+" is echoed straight out of
   `get_quantity_roller()` -- so PHP could not reach that one anyway.

   A mask keeps the plugin's markup, data attributes and accessible name
   exactly as shipped, survives every AJAX re-render because there is no
   markup to re-apply, and reaches all three places at once. `currentColor`
   still decides the colour: the mask supplies the shape and
   `background-color` supplies the paint.

   The glyph itself is two stroked lines, `stroke-linecap: round`, 2 units on a
   24 unit box. The stroke colour inside the data URI is irrelevant to the
   result; a mask reads the alpha channel, and the visible colour comes from
   `background-color` below.

   THE MASK BOX IS 24px, SO ONE UNIT IS ONE PIXEL (changed 10 Sep, card polish
   3). Ryan asked for a plus "16px wide and tall and 2px thick", and the way to
   deliver an exact number rather than an approximate one is to stop scaling:
   at a 24px mask the viewBox maps 1:1 to pixels, so the drawing IS the
   measurement.

   NOW 14px, ON THE SAME 2px STROKE (changed 10 Sep, card polish 5). Ryan:
   "Make the plus icon slightly smaller, but keep 2px width." The arms move in
   one unit at each end -- they run 6 to 18, so 12 units -- and the round caps
   still add half a stroke at each end, so the glyph measures exactly
   12 + 2 = 14px across and 14px down on a 2.00px stroke, centred on the 38px
   button with 12px of air each side. Only the two path `d` values changed;
   the mask box, the stroke width and the round caps are untouched, which is
   why the 2px can be promised rather than measured after the fact.

   IT IS NOW THE CLOSE X'S TWIN EXACTLY. The X has always run 6 to 18 (see
   `--oo-icon-close` below), because a diagonal glyph reads larger than an
   orthogonal one at the same bounding box. At 16 that made them a deliberate
   pair of unequal drawings; at 14 they are the same 14px on the same 2px
   stroke, and the diagonal still reads a shade the larger, which is the
   effect the old asymmetry was there to produce.

   Every other copy of the plus is this same drawing at a smaller mask, so the
   ratio 14:2 is preserved wherever it appears: the roller's is at a 15.6px
   mask (65%), which is 9.1px of glyph on a 1.3px stroke, and the minus beside
   it is drawn to the same 65% so the two move together. Read the mask size to
   know the glyph size -- glyph = mask * 7/12, stroke = mask / 12. */
:root {
	/* Hover and focus, shared by every control the customer meets on the menu
	   page. On :root rather than .orderable-main because the sticky order bar
	   is rendered outside .orderable-main (wp_footer) and has to read them too.

	   --oo-hover-tint is the OPAQUE result of about 5% ink over white, not an
	   rgba veil. A veil would have to be painted as a background-image, and
	   background-image does not interpolate from `none`, so the hover would
	   snap instead of easing. An opaque colour transitions. */
	--oo-hover-tint: #f3f3f4;
	--oo-focus-ring: #16181c;
	--oo-icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 6v12'/%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
	/* The drawer's close X, drawn on the same 24 unit grid, the same 2 unit
	   stroke and the same round caps as the plus, so the two glyphs are one
	   family. Its arms run 6 to 18, which used to be one unit tighter than the
	   plus's 5 to 19 because a diagonal glyph reads larger than an orthogonal
	   one at the same bounding box. Card polish 5 brought the plus in to the
	   same 6 to 18, so at a 24px mask both are now a 14px glyph on a 2px
	   stroke and the X still reads a shade the larger of the two. */
	--oo-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12'/%3E%3Cpath d='M18 6L6 18'/%3E%3C/svg%3E");
}

/* ---------- palette ---------- */
.orderable-main {
	--oo-ink: #16181c;
	/* Darkened from #6b7280 on 10 Sep 2026, card polish 5. Ryan: "In general
	   --oo-muted is too light." It was 4.83:1 on white, 4.55:1 on --oo-sunk and
	   4.36:1 on the hover tint -- passing AA for body text by five hundredths in
	   the worst case, which is a pass on paper and a squint on a phone in
	   daylight. #565c66 measures 6.73:1 on white, 6.33:1 on --oo-sunk and
	   6.07:1 on the tint, so every place it lands clears AA with room and the
	   two smallest users (the 12px label line and the 13px drawer label line)
	   are no longer riding the floor. It stays a grey rather than becoming a
	   second ink: at 6.7:1 against the ink's 16.9:1 the hierarchy the token
	   exists for is intact. One token, so descriptions, the category subtitle,
	   the label line, the inactive service pill, the sheet hints and the
	   drawer's secondary text all moved together; the 20 literal fallbacks
	   beside `var(--oo-muted, ...)` in this file were changed with it, because
	   outside `.orderable-main` -- the drawer, the sheet, the order bar -- the
	   fallback IS the value. */
	--oo-muted: #565c66;
	--oo-ground: #ffffff;
	--oo-sunk: #f7f8f9;
	--oo-line: #e6e8eb;
	/* The hairline one step down, for the edge of a control rather than the
	   edge of a section. Added 10 Sep 2026, card polish 5, for the carousel's
	   arrow buttons; it is a token and not a literal because "one step darker
	   than the line" is a decision that should be made once if any other
	   control needs it. It is NOT a second line colour for separators: every
	   rule between things stays on --oo-line. */
	--oo-line-strong: #d3d8de;
	--oo-accent: #d92d20;
	/* Photo edge length, shared by the carousel card and the menu item card so
	   the two stay the same size. They had drifted to 212 and 172. */
	--oo-photo: 150px;
	/* The one colour a client site is expected to change. It defaults to the
	   ink so an unbranded build is coherent rather than accidentally red: the
	   page was carrying a red order bar next to a black primary button, which
	   reads as two different primary actions. Photography supplies the warmth;
	   set --oo-brand per client when there is a real brand colour to use. */
	--oo-brand: var(--oo-ink);
	--oo-radius: 14px;
	/* The Add button and the allergen (i) are rounded squares, not circles:
	   a corner radius of about a quarter of the 38px side. One token so the
	   two controls cannot drift apart. */
	--oo-btn-radius: 9px;
	/* How far the Add button is inset from the photo's corner. Named because
	   the price's variant `b` reads it: the two are meant to look like a pair
	   sitting the same distance inside opposite corners of the same card. */
	--oo-btn-inset: 10px;
	color: var(--oo-ink);
}

/* Declared after the palette block, not before it: a media query that precedes
   the rule it is overriding loses to it at equal specificity, and every photo
   stayed 150px at every width. */
@media (min-width: 700px) {
	.orderable-main { --oo-photo: 200px; }
}

/* ---------- category headings (test 14) ---------- */
.oo-cat { margin: 30px 0 12px; }
.oo-cat__title { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 2px; }
.oo-cat__sub { color: var(--oo-muted); margin: 0; font-size: 0.95rem; }

/* ---------- "most ordered" carousel (test 13) ----------
   The row is the plugin's own grid; only the flow changes. Horizontal scroll
   with snap, so it behaves like the reference on a phone. */
.oo-carousel .orderable-products-list--grid {
	/* The plugin's grid list carries margin-left:-12px as a gutter offset for
	   its own column layout. Switching to flex replaced the layout but left the
	   negative margin, so the first card sat 12px left of the section heading
	   and every other element on the page. Measured: everything at 398, cards
	   at 386. */
	margin-left: 0;
	margin-right: 0;
	display: flex;
	flex-wrap: nowrap;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	gap: 12px;
	padding-bottom: 6px;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
}
.oo-carousel .orderable-products-list--grid::-webkit-scrollbar { display: none; }
.oo-carousel .orderable-products-list__item {
	flex: 0 0 auto;
	width: var(--oo-photo, 150px);
	scroll-snap-align: start;
}
@media (min-width: 782px) {
	/* Was 200px, which put a 200px square directly above 112px list thumbnails
	   and made the shortcut row louder than the menu it shortcuts to. At 156px
	   four cards fit the 645px column instead of three-and-a-sliver, so the row
	   also reads as scrollable rather than clipped. */
	.oo-carousel .orderable-products-list__item { width: var(--oo-photo, 200px); }
}

/* ---------- photo-first card with an overlaid round add button (test 11) ----
   No template override needed: the plugin already renders hero-then-content,
   so the button is lifted onto the photo with position alone. */
.oo-carousel .orderable-product {
	position: relative;
	background: transparent;
	border: 0;
	/* The plugin lays the card out as a row with a fixed 60px thumbnail at
	   mobile widths. The Owner carousel is photo-above, so the direction and
	   the hero width are both taken back here. */
	flex-direction: column;
}
/* The plugin caps every list item's hero at `max-width: 95px`; the carousel
   wants a full-bleed square, so the cap is lifted rather than out-specified.
   Its grid rule also sets `object-fit: fill`, which stretches a non-square
   photo -- `cover` is restored so uploads are not distorted. */
.oo-carousel .orderable-product__hero { width: 100%; max-width: none; }
.oo-carousel .orderable-product__hero img { object-fit: cover; height: 100%; }
.oo-carousel .orderable-product__hero {
	border-radius: var(--oo-radius);
	overflow: hidden;
	background: var(--oo-sunk);
	aspect-ratio: 1 / 1;
}
/* Same !important as the list card's, for the same plugin rule. The carousel
   hero carries `aspect-ratio: 1 / 1`, so the square uploads on this bench fill
   it either way and nothing here looks different today; a client uploading a
   4:3 photograph is the case this stops, and it is the same one line. */
.oo-carousel .orderable-product__image { width: 100%; height: 100% !important; object-fit: cover; object-position: 50% 50%; }
.oo-carousel .orderable-product__content-wrap { padding: 8px 0 0; }
.oo-carousel .orderable-product__title { font-size: 0.98rem; font-weight: 600; margin: 0 0 2px; line-height: 1.3; }
.oo-carousel .orderable-product__description { display: none; }
.oo-carousel .orderable-product__actions { display: block; padding: 0; }
.oo-carousel .orderable-product__actions-price { font-size: 0.95rem; font-weight: 600; }

/* The add button is a sibling of the hero, not a child, so it is anchored to
   the card and pushed down by the hero's height. A percentage margin resolves
   against the containing block's WIDTH, and the hero is square, so
   `calc(100% - 46px)` lands it neatly inside the photo's bottom-right corner
   without hard-coding a pixel height. */
.oo-carousel .orderable-product__actions-button {
	position: absolute;
	right: 8px;
	top: 0;
	margin-top: calc(100% - 46px);
}
.oo-carousel .orderable-button,
.oo-carousel .orderable-product__add-to-order {
	width: 38px; height: 38px; padding: 0;
	border-radius: var(--oo-btn-radius, 9px);
	background: var(--oo-ground);
	border: 1px solid var(--oo-line);
	box-shadow: 0 2px 8px rgba(0,0,0,.14);
	display: grid; place-items: center;
	font-size: 0; line-height: 0;
}
.oo-carousel .orderable-product__add-to-order::after {
	content: "";
	/* Same 24px mask box and the same 16px glyph as the list card. */
	width: 24px; height: 24px;
	color: var(--oo-ink);
	background-color: currentColor;
	-webkit-mask: var(--oo-icon-plus) center / 24px 24px no-repeat;
	        mask: var(--oo-icon-plus) center / 24px 24px no-repeat;
}

/* ---------- list rows: text left, thumbnail right (test 12) ---------- */
.orderable-products-list--list .orderable-product {
	display: grid;
	grid-template-columns: 1fr var(--oo-thumb, 92px);
	gap: 14px;
	align-items: start;
	padding: 14px 0 0;
	background: transparent;
}
.orderable-products-list--list .orderable-product__hero {
	order: 2;
	width: var(--oo-thumb, 92px); height: var(--oo-thumb, 92px);
	border-radius: 10px; overflow: hidden;
	background: var(--oo-sunk);
	position: relative;
}
.orderable-products-list--list .orderable-product__image { width: 100%; height: 100%; object-fit: cover; }
.orderable-products-list--list .orderable-product__content-wrap { order: 1; display: flex; flex-direction: column; gap: 4px; }
.orderable-products-list--list .orderable-product__title { font-size: 1rem; font-weight: 600; margin: 0; }
.orderable-products-list--list .orderable-product__description {
	color: var(--oo-muted); font-size: 0.9rem; margin: 0;
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.orderable-products-list--list .orderable-product__actions {
	display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 4px 0 0;
}
.orderable-products-list--list .orderable-product__actions-price { font-weight: 600; }

/* ---------- out-of-stock button clipping (test 16) ----------
   The plugin gives the button a fixed width that a longer label overruns; the
   card also clipped it. Both are taken back here. */
.orderable-product__actions-button { min-width: 0; flex: 0 0 auto; }
.orderable-product__actions-button .orderable-button,
.orderable-product__add-to-order,
.orderable-product__out-of-stock {
	white-space: nowrap;
	width: auto;
	max-width: 100%;
	overflow: visible;
}
.orderable-product { overflow: visible; }

/* ---------- desktop (test 18) ---------- */
@media (min-width: 900px) {
	.orderable-products-list--list {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 32px;
	}
	.oo-cat { grid-column: 1 / -1; }
	.orderable-main--sections-side .orderable-main__sections { padding-left: 8px; }
}

/* ---------- sticky order bar (mobile) ----------
   Reuses the plugin's floating cart element, reshaped by the template override
   in orderable/drawer/floating-cart.php. Nothing new is injected into the DOM,
   so the plugin's own drawer JavaScript still drives it. */
.oo-bar {
	position: fixed;
	left: 0; right: 0; bottom: 0;
	top: auto;
	width: auto;                 /* left+right already span it; width:100% ADDED to that */
	box-sizing: border-box;      /* keep the padding inside the 390px, not outside it */
	padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
	background: var(--oo-ground, #fff);
	border-top: 1px solid var(--oo-line, #e6e8eb);
	box-shadow: 0 -8px 20px rgba(0, 0, 0, .06);
	z-index: 60;
}
.oo-bar__button {
	width: 100%;
	min-height: 52px;
	border: 0;
	border-radius: 10px;
	background: var(--oo-brand, #16181c);
	color: #fff;
	font-size: 1.02rem;
	font-weight: 600;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	cursor: pointer;
}
/* Label left, count and subtotal right. When the cart is empty there is only
   the label, so the button centres it instead. */
.oo-bar--filled .oo-bar__button { justify-content: space-between; padding: 0 18px; }
.oo-bar__meta {
	display: inline-flex;
	align-items: baseline;
	gap: 6px;
	font-weight: 500;
	font-variant-numeric: tabular-nums;
}
.oo-bar__sep { opacity: .55; }
.oo-bar__total .woocommerce-Price-amount { font-weight: 600; }
/* WooCommerce wraps the price in its own markup; keep it inheriting the bar. */
.oo-bar__total, .oo-bar__total * { color: inherit; text-decoration: none; }
/* Clearance for the sticky bar belongs to the LAST block on the page only.
   This page renders two [orderable] shortcodes -- the carousel and the menu --
   and each is its own .orderable-main, so a blanket rule put 84px of dead space
   between the carousel and the first category heading. */
.orderable-main:last-of-type { padding-bottom: 84px; }
.oo-carousel { padding-bottom: 0; }

@media (min-width: 900px) {
	/* On desktop the bar returns to a compact control rather than spanning the
	   full width, since the drawer opens as a side panel there. */
	.oo-bar { left: auto; right: 24px; bottom: 24px; width: auto; padding: 0;
	         background: transparent; border-top: 0; box-shadow: none; }
	.oo-bar__button { min-width: 210px; padding: 0 22px; border-radius: 999px; }
	.orderable-main:last-of-type { padding-bottom: 0; }
}

/* ---------- carousel: pin the button's containing block ----------
   The overlaid add button is positioned with a percentage margin, which
   resolves against its containing block's WIDTH. The plugin makes an
   intermediate wrapper positioned at desktop widths, so the same rule anchored
   to a 128px box there and a 152px box on mobile -- the button landed in the
   right place only by accident. Forcing the intermediate wrappers back to
   `static` makes the card itself the containing block at every width, so one
   rule holds everywhere instead of needing a breakpoint per card size. */
.oo-carousel .orderable-product__content-wrap,
.oo-carousel .orderable-product__actions { position: static; }

/* The plugin styles the list ITEM as the card at desktop widths -- background,
   border and shadow all sit there, not on .orderable-product -- so the
   carousel's flat treatment has to be taken on the item too. */
.oo-carousel .orderable-products-list__item {
	background: transparent;
	border: 0;
	box-shadow: none;
	padding: 0;
}

/* ---------- flatten the plugin's card chrome at tablet and up ----------
   Below ~768px the plugin renders bare cards, but at wider widths it adds its
   own padding, white background, radius and shadow to .orderable-product. That
   left the carousel cards 88px taller than their content and put a raised card
   behind every list row, which is not the Owner treatment at any width.
   Both layouts want the card itself flat; the row keeps its own padding and
   rule, the carousel keeps none. */
.oo-carousel .orderable-product {
	padding: 0;
	background: transparent;
	box-shadow: none;
	border-radius: 0;
}
.orderable-products-list--list .orderable-product {
	background: transparent;
	box-shadow: none;
	border-radius: 0;
}

/* ---------- the round add button must actually be round ----------
   The plugin sets `.orderable-button { padding: 0 24px !important; }`. With
   border-radius:50% that made a 63x38 ellipse rather than a 38px circle, at
   every viewport. This is the ONE declaration in this theme that needs
   !important to win, and only because the plugin used it first. min-width is
   set too: `width` alone loses to the button's intrinsic content width. */
.oo-carousel .orderable-product__add-to-order,
.oo-carousel .orderable-button,
.orderable-products-list--list .orderable-product__add-to-order,
.orderable-products-list--list .orderable-button {
	padding: 0 !important;
	width: 38px;
	min-width: 38px;
	max-width: 38px;
	height: 38px;
	/* place-items alone lost the inline axis to a later plugin rule, which left
	   the "+" hugging the left edge of the circle. Both axes are set here. */
	display: grid;
	justify-content: center;
	align-content: center;
	line-height: 1;
	/* The circle is white on a photo; without a firmer edge it disappears
	   against a pale image and the glyph reads as floating loose. */
	border: 1px solid rgba(0, 0, 0, .12);
	box-shadow: 0 1px 6px rgba(0, 0, 0, .22);
}

/* ---------- close the gap between the carousel and the first section ----------
   The page renders two [orderable] shortcodes, so the theme inserts its own
   block spacing between them, and the first category heading adds its 30px
   margin on top of that. Together that left ~92px of white on a phone. The
   heading's margin is enough on its own once the block gap is removed. */
.oo-carousel { margin-bottom: 0; }
.orderable-main + .orderable-main { margin-top: 0; }
.orderable-main:last-of-type > .orderable-main__sections > .orderable-main__group:first-child .oo-cat {
	margin-top: 8px;
}

/* ---------- list rows: align the price with the title ----------
   The plugin pads .orderable-product__content by 24px on the left but pads
   .orderable-product__actions by nothing, so the item name sat 24px to the
   right of its own price. Measured gap was exactly 24px. Zeroing the content
   padding aligns the whole row to the card edge, which is what the reference
   design does; padding the actions instead would indent the row for no reason. */
.orderable-products-list--list .orderable-product__content,
.oo-carousel .orderable-product__content {
	padding-left: 0;
}

/* ---------- postcode locator: stop the input overflowing its row ----------
   The row is a flex container, but the input carries an intrinsic width (386px
   measured inside a 330px wrapper) and flex items default to min-width:auto, so
   it refused to shrink and the button ended up painted on top of it. Giving the
   wrapper permission to shrink and the button permission not to fixes both the
   overflow and the overlap. */
.opml-postcode-locator__row {
	flex-wrap: wrap;
}
.opml-store-locator-input {
	flex: 1 1 180px;
	min-width: 0;          /* the part that actually lets it shrink */
}
.opml-store-locator-input__input {
	width: 100%;
	max-width: 100%;
	box-sizing: border-box;
}
.opml-postcode-locator__button {
	flex: 0 0 auto;
	white-space: nowrap;   /* it was allowed to wrap mid-label */
	padding-left: 22px;
	padding-right: 22px;
}

/* The "+" suffix should read as part of the price, not a separate token.
   Two adjacent inline spans still rendered a 7px gap (inline box spacing, not
   whitespace -- there is none in the source). inline-flex removes the inline
   layout entirely and makes the spacing an explicit 1px. */
.oo-price-from {
	display: inline-flex;
	align-items: baseline;
	gap: 1px;
	white-space: nowrap;
}
/* WooCommerce puts margin-right:6px on its price amount, which pushed the "+"
   away from the figure it belongs to. Only zeroed inside this wrapper, so
   prices everywhere else keep their normal spacing. */
.oo-price-from .woocommerce-Price-amount { margin-right: 0; }

/* Action buttons sat at 89px for "Select" and 137px for "Sold out today",
   which read as ragged down the column. A floor narrows the disparity without
   stretching the longer label. Their right edges already align. */
.orderable-products-list--list .orderable-product__actions-button > * {
	min-width: 118px;
}

/* REMOVED: a rule that zeroed `border-bottom` on the last item's
   `.orderable-product`. It was correct when a row was closed by a divider line
   and the card had no border of its own -- the divider under the final row drew
   a line with nothing beneath it. The card redesign further down this file gave
   `.orderable-product` a real 1px border on all four sides, at which point this
   rule stopped removing a divider and started cutting the bottom off the last
   card in every category. Measured before removal: last card 0px, every other
   card 1px, in all eight categories. */
/* ---------- page layout: sidebar + menu ----------
   The category rail used to live inside the menu shortcode, which meant it
   could only sit beside the product list and started level with the first
   section. It is now [oo_menu_sidebar] at page level, so it runs beside
   everything and carries search, the way Owner does it.

   WIDTH, UNDER GENERATEPRESS (changed 11 September 2026). Under Twenty
   Twenty-Five `.oo-layout` was a child of the theme's constrained content, so it
   inherited `max-width: 645px` and `margin-inline: auto !important`, and this
   rule existed to widen it back out to 1180px. The auto margins kept it centred,
   and the max-width was not !important, so widening worked.

   GeneratePress has no constrained-content wrapper. `.entry-content` is the full
   width of GP's container -- 1200px by default, less 2 x 40px of padding, so
   1120px of content -- and that container width is a customizer setting the
   client owns. A hardcoded 1180px here would fight it in both directions: it
   would overflow a client who narrows the container, and stop short of one who
   widens it.

   So the cap becomes a token that DEFAULTS TO NOT CAPPING. `--oo-layout-max` is
   `none` unless a site sets it, which means the ordering layout is exactly as
   wide as whatever container GP gives it, which is what the client configured.
   A site that wants the old behaviour writes one line:
   `.oo-layout { --oo-layout-max: 1180px }`. */
.oo-layout {
	max-width: min(var(--oo-layout-max, 100%), 100%);
}

@media (min-width: 900px) {
	.oo-layout {
		display: grid;
		/* Tokens rather than two literals, because the page title's indent under
		   the minimal chrome has to land on this column's left edge exactly and a
		   second copy of "232px" and "44px" is how those two drift apart. See the
		   RAIL, AGAINST THE TITLE block below. */
		grid-template-columns: var(--oo-rail-w, 232px) minmax(0, 1fr);
		column-gap: var(--oo-rail-gap, 44px);
		align-items: start;
	}
	.oo-sidebar {
		position: sticky;
		top: 24px;
		align-self: start;
		max-height: calc(100vh - 48px);
		overflow-y: auto;
		overscroll-behavior: contain;
	}
}

/* ---------- THE RAIL, AGAINST THE TITLE ----------
   (rewritten for GeneratePress, 11 September 2026)

   Ryan, 10 Sep 2026: "I want the top of the search bar to align with something."
   And on 11 Sep, on which part of the title to align it to: try the INK, not the
   line box -- the top edge of the field level with the top of the "M" in "Menu".

   WHY THIS CAME BACK. The move to GeneratePress removed the old rule, correctly:
   under Twenty Twenty-Five the title sat in its own 645px container that began to
   the RIGHT of the rail, so the rail could be pulled up beside it, and under
   GeneratePress `content-page.php` prints `.entry-header` and `.entry-content`
   inside one `.inside-article` at the same width and the same LEFT EDGE. Pulling
   the rail up there would have laid the search field straight across the word
   "Menu" at every width. That was measured, and it was right.

   WHAT CHANGED IS THAT THE TITLE CAN MOVE. Under the minimal chrome this plugin
   owns the whole of that page's furniture, so the title is put over the content
   column -- the same column the menu is in, the same relationship Twenty
   Twenty-Five had by accident -- and the rail then has a clear 232px of its own
   to the left of it. The indent is the grid's own two values, so the title's left
   edge and `.oo-main`'s left edge are the same number by construction rather than
   by coincidence. Measured after: both 486px at 1440, both 406px at 1280.

   ONLY UNDER `.oo-chrome-minimal`, and that is deliberate. On a client site
   running the theme chrome the page title is the CLIENT'S -- their font, their
   size, their place in their own header design -- and moving it would be this
   plugin redecorating a page it does not own. There the rail starts under the
   title, which is where GeneratePress puts it with no help.

   THE PULL IS COMPUTED, NOT PASTED. Every term below is a measured property of
   the title that sits above the rail, named separately so a site whose heading
   differs redeclares one token instead of guessing at a pixel count:

     --oo-title-size     GeneratePress 3.6.1's own h1 on the ordering pages.
                         Measured 40px at every width from 768px up (30px below
                         it, where the rail is not a rail and none of this
                         applies). GP exposes no custom property for it and this
                         site has no typography rows configured, so it is read
                         off the rendered page rather than out of an option that
                         does not exist.
     --oo-title-leading  48px / 40px. Same source.
     --oo-title-gap      the h1's own margin-bottom, measured at 34px: the
                         distance from the title's line box to the top of
                         `.entry-content`, which is where the rail starts.
     --oo-title-cap      Roboto's ascent above its cap height, as a fraction of
                         the em: hhea ascent 0.925em less cap height 0.700em.
                         Verified in the browser against the font itself --
                         `measureText('M')` reports fontBoundingBoxAscent 37 and
                         actualBoundingBoxAscent 28 at 40px, so 9/40 = 0.225.
                         This is the term that makes it an INK alignment; drop it
                         and the field lands on the line box, 13px high.

   So: line box, plus the gap under it, less the half-leading above the ascent,
   less the ascent above the cap. Measured before: the field's top edge sat 69px
   below the cap of the "M" at every width from 900px up. Measured after: 0px.
   Below 900px the rail is a stacked block above the menu rather than a rail, and
   nothing here applies -- there is no second column to align anything to. */
@media (min-width: 900px) {
	.oo-chrome-minimal .entry-header {
		/* The title moves over the content column. Same two tokens the grid
		   above uses, so the two left edges cannot drift apart. */
		padding-left: calc(var(--oo-rail-w, 232px) + var(--oo-rail-gap, 44px));
	}

	.oo-chrome-minimal .oo-sidebar {
		margin-top: calc(-1 * (
			  var(--oo-title-size, 40px) * var(--oo-title-leading, 1.2)
			+ var(--oo-title-gap, 34px)
			- var(--oo-title-size, 40px) * (var(--oo-title-leading, 1.2) - 1) / 2
			- var(--oo-title-size, 40px) * var(--oo-title-cap, 0.225)
		));
	}
}

.oo-main { min-width: 0; }

/* ---------- nothing in the ordering UI names a font ----------
   Ryan, 10 Sep 2026: "Tbh I want the online ordering to easily inherit the
   fonts of the broader site it's being integrated into."

   There is no `font-family` in this file, and that is not the same as
   inheriting. FORM CONTROLS DO NOT INHERIT: an `<input>`, `<button>`, `<select>`
   or `<textarea>` takes the browser's own font unless it is told otherwise.
   Measured on the menu before this rule, with the page in Manrope: the search
   field, both carousel arrows, every "Select" button, the drawer close and the
   hours-sheet close all rendered in ARIAL. On a client site running its own
   typeface those are the controls a customer touches, in a font from nowhere.

   `:where()` so this carries zero specificity. It is a base layer: any rule in
   this file, in the plugin, or in a client's own stylesheet still wins, and a
   client who really does want a different face on their buttons is not fighting
   it. Only `font-family` is inherited here -- size and weight are set
   deliberately all through this file and are not the browser's to hand back. */
:where(.oo-layout, .oo-bar, .oo-sheet, .oo-servicebar, .oo-controls, .oo-locator, .orderable-main, .orderable-drawer)
	:where(input, button, select, textarea, optgroup) {
	font-family: inherit;
}

/* ---------- sidebar search ---------- */
.oo-search { position: relative; margin: 0 0 18px; }
.oo-search__icon {
	position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
	width: 17px; height: 17px; color: var(--oo-muted, #565c66); pointer-events: none;
}
.oo-search__icon svg { width: 100%; height: 100%; display: block; }
/* ---------- form controls, and why these selectors carry their parent ----------
   (added 11 September 2026, with the move to GeneratePress)

   GeneratePress styles bare form controls with an ATTRIBUTE selector:

       input[type="email"], input[type="number"], ... input[type="search"],
       textarea, select { padding: 10px 15px; border-radius: 0; ... }

   That is specificity 0,1,1. Every rule below used to be a single class, 0,1,0,
   which loses to it -- and loses silently, since the declaration is right there
   in the file and DevTools shows it struck through only if you go looking.
   Measured on the menu page before this change: the sidebar search field
   computed `padding: 10px 15px` (ours says 10px 12px 10px 37px, which is what
   makes room for the magnifier icon), `border-radius: 0px`, and GP's grey
   #fafafa ground. The icon sat on top of the placeholder text.

   Twenty Twenty-Five never did this, so nothing here was written to expect it.

   The fix is one step of specificity from the element's own parent -- 0,2,0,
   which beats 0,1,1 -- rather than `!important`, which would take the
   declaration away from the client's child theme as well as from GeneratePress.
   ------------------------------------------------------------------------ */

.oo-search .oo-search__input {
	width: 100%;
	padding: 10px 12px 10px 37px;
	border: 1px solid var(--oo-line, #e6e8eb);
	border-radius: 10px;
	background: var(--oo-ground, #fff);
	color: var(--oo-ink, #16181c);
	font-size: 0.95rem;
	line-height: 1.3;
}
.oo-search .oo-search__input::placeholder { color: var(--oo-muted, #565c66); }
.oo-search .oo-search__input:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	/* INSIDE the field, not outside it. Ryan, 10 Sep 2026: "when I click in the
	   search bar the ring is clipped and you can't see it."

	   The rail is `overflow-y: auto` so it can scroll independently of the page,
	   and `overflow-y: auto` computes `overflow-x: auto` as well, so that box
	   clips on all four sides. The field is the full width of the rail and its
	   top is the rail's top, so a ring at `outline-offset: 1px` sat 3px outside
	   the scroller on the top, the left AND the right: measured, ring box
	   47..285 against a rail of 50..282.

	   Padding the scroller instead would move the rail's contents in from the
	   page's own left edge and then need a negative margin to put them back,
	   which is two changes to fix one, and it grows the sticky rail's height.
	   A negative offset draws the ring inside the field's own border box, where
	   nothing can clip it, and costs no layout at all. The border is already
	   transparent while focused, so what is seen is one 2px ring rather than a
	   ring and a line beside it. */
	outline-offset: -2px;
	border-color: transparent;
}
.oo-search__input::-webkit-search-cancel-button { cursor: pointer; }

/* ---------- sidebar navigation ---------- */
.oo-catnav__list { list-style: none; margin: 0; padding: 0; }
.oo-catnav__item { margin: 0; }
.oo-catnav__link {
	display: block;
	padding: 8px 12px;
	border-radius: 8px;
	color: var(--oo-muted, #565c66);
	text-decoration: none;
	font-size: 0.95rem;
	line-height: 1.35;
	transition: color .15s ease, background-color .15s ease;
}
/* Only where a real pointer can hover. On a touch screen the tapped link keeps
   its hover background after the finger lifts, so it stayed highlighted while
   the scrollspy correctly moved `is-current` to the section actually on screen
   -- two lit items, reading as a selection that never clears. */
@media (hover: hover) and (pointer: fine) {
	.oo-catnav__link:hover { color: var(--oo-ink, #16181c); background: var(--oo-sunk, #f7f8f9); }
}
.oo-catnav__link.is-current {
	color: var(--oo-ink, #16181c);
	background: var(--oo-sunk, #f7f8f9);
	font-weight: 600;
}
.oo-catnav__link:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 2px;
}
.oo-cat { scroll-margin-top: 24px; }
.oo-sectionhead { scroll-margin-top: 24px; }

/* Below the two-column breakpoint the headings do the navigating, as they do on
   a phone, but search still earns its place. */
@media (max-width: 899px) {
	.oo-catnav { display: none; }
	.oo-search { margin-bottom: 22px; }
}

/* An item hidden by search, and a section with nothing left to show. */
.oo-hidden { display: none !important; }

.screen-reader-text {
	position: absolute !important;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
	white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: no-preference) {
	html { scroll-behavior: smooth; }
}

/* ---------- finished-product pass ----------
   Two-line descriptions clipped most of these dishes mid-list ("cheese,
   guacamole, sour cream,…"). At tablet width and up there is room for the
   whole sentence, and a photo big enough to read. Both stay modest on a
   phone, where the column is 330px. */
@media (min-width: 700px) {
	.orderable-products-list--list { --oo-thumb: 112px; }
	.orderable-products-list--list .orderable-product__description {
		-webkit-line-clamp: 3;
	}
}

/* The carousel is a scroller, but cut flush at the container edge it read as
   a broken card rather than "there is more". A fade makes the overflow
   deliberate; the JS drops it at the end of the scroll so it never veils the
   last card once there is genuinely nothing more to see. */
.oo-carousel .orderable-products-list--grid {
	scroll-padding-left: 0;
	transition: mask-image .2s ease;
}
.oo-carousel .orderable-products-list--grid.oo-has-more {
	mask-image: linear-gradient(to right, #000 78%, rgba(0,0,0,.15) 97%, transparent 100%);
}

/* Studio-white plates sit next to lifestyle shots in the same row. Without a
   ground behind them the white ones float; the sunk panel gives every card the
   same frame regardless of how its photo was lit. */
.oo-carousel .orderable-product__hero { background: var(--oo-sunk); }

/* ---------- page framing ----------
   `:has()` scopes these to pages that actually carry an ordering menu, so the
   plugin does not reach across the whole site to restyle a page it does not
   own.

   TWO SELECTORS, because the title's markup depends on the theme's era.
   `.wp-block-post-title` is the block themes' Post Title block, which is what
   Twenty Twenty-Five rendered. GeneratePress is a classic theme and prints
   `h1.entry-title` from `content-page.php`. Both are listed rather than one
   being swapped for the other so this plugin keeps working on either, which is
   the whole reason it stopped being a child theme. */
body:has(.orderable-main) .wp-block-post-title,
body:has(.orderable-main) .entry-header .entry-title { margin-bottom: 6px; }

.oo-lede {
	color: var(--oo-muted, #565c66);
	font-size: 1.02rem;
	line-height: 1.5;
	margin: 0 0 20px;
	/* No max-width. WordPress's constrained layout centres any child narrower
	   than the content width with `margin-inline: auto !important`, so capping
	   the measure here indented the sentence away from the heading above it.
	   `text-wrap: pretty` shortens the last line without changing the box. */
	text-wrap: pretty;
}

/* Section headings for content the plugin does not label. The carousel had no
   heading at all, so it read as loose cards under the postcode field rather
   than a curated row. */
.oo-sectionhead {
	font-size: 1.35rem;
	font-weight: 700;
	letter-spacing: -0.01em;
	margin: 34px 0 12px;
}

/* The locator is a bare field with no context. Give it breathing room and stop
   it stretching the full column on a wide screen. */
body:has(.orderable-main) .orderable-store-locator,
body:has(.orderable-main) [class*="store-locator"] { margin-bottom: 4px; }

/* RESOLVED BY THE MOVE TO GENERATEPRESS (11 September 2026), and the history is
   worth keeping because the shape of the bug recurs.

   Under Twenty Twenty-Five there was a 140px band between the site header and
   the page title that no rule here could touch: the block page template stacked
   two spacing|60 values (70px each) as INLINE style attributes on <main> and on
   the group wrapping the title, and an inline style beats any stylesheet rule
   short of !important. Two CSS rules attempting it sat in this file doing
   nothing at all until the computed values were measured. The fix was a
   `templates/page.html` override in the child theme, restating the same markup
   with spacing|30.

   GeneratePress is a classic theme. It has no block page template, writes no
   inline spacing, and exposes the same distance as an ordinary customizer
   setting (Layout > Container > top/bottom padding) that a client can change
   without touching CSS. So the override is DELETED rather than ported, and
   nothing replaces it here.

   The lesson that outlives it: when a rule appears to do nothing, check whether
   the value is inline before rewriting the selector. */

/* Price and button bottom-aligned across a row.
   The two-column list puts a 2-line description beside a 3-line one, so each
   card's action row landed at a different height and the Select buttons
   stepped down the page. Stretching the card and pushing the actions to the
   bottom lines them up without imposing a fixed description height, which
   would clip the longer ones. */
.orderable-products-list--list .orderable-products-list__item { display: flex; }
.orderable-products-list--list .orderable-product { width: 100%; }
.orderable-products-list--list .orderable-product__content-wrap { height: 100%; }
.orderable-products-list--list .orderable-product__actions { margin-top: auto; }

/* REMOVED: the plugin's own per-item separator, plus its spacing and its
   last-child exception. All three were dead -- the card block further down sets
   padding-bottom, margin-bottom and border-bottom to 0 on the same selector at
   the same specificity, and wins on order. Leaving contradictory rules in place
   is what let the last-card border bug above hide for as long as it did. */

/* ---------- demonstration disclaimer ----------
   Printed by a wp_footer hook, not a template part: Orderable's custom checkout
   renders no theme footer, and checkout is the page where this matters most.
   Quiet, but it must survive inspection, so it is neither hidden nor tiny. */
.oo-disclaimer {
	border-top: 1px solid var(--oo-line, #e6e8eb);
	background: var(--oo-sunk, #f7f8f9);
	padding: 22px 24px calc(22px + env(safe-area-inset-bottom));
	/* The sticky order bar is fixed at the bottom; without this the disclaimer
	   sits underneath it and can never be read on a page with items in cart. */
	margin-bottom: 84px;
}
.oo-disclaimer p {
	color: var(--oo-muted, #565c66);
	font-size: 0.875rem;
	line-height: 1.5;
	margin: 0;
	text-align: center;
}

/* ---------- carousel controls ----------
   Buttons are built by script and placed in the section heading, so the heading
   becomes a row. Owner puts them here too, and it is the only place with
   horizontal room that does not steal from the cards. */
.oo-sectionhead.oo-sectionhead--hasnav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
}
.oo-carnav { display: flex; gap: 8px; flex: 0 0 auto; }
/* THE LINE IS ONE STEP DARKER THAN --oo-line (10 Sep 2026, card polish 5).
   Ryan, same sentence as the disabled state: "increase the strength of the
   border so that the buttons stand out more." `--oo-line` is the hairline that
   separates one row of the menu from the next, and at #e6e8eb it is right for
   a separator and too quiet for the edge of a control that has to be found on
   a busy page. `--oo-line-strong` is that hairline one step down, and it is a
   token rather than a literal because the (i) and the Add button may want it
   next; see the note where it is declared. The two arrows are the only users
   today -- Add and the (i) sit on photographs and already carry a shadow that
   separates them, and darkening their line as well would make the pair read
   heavier than the card they belong to. Left alone deliberately. */
.oo-carnav__btn {
	width: 34px; height: 34px;
	display: grid; place-items: center;
	border-radius: 50%;
	border: 1px solid var(--oo-line-strong, #d3d8de);
	background: var(--oo-ground, #fff);
	color: var(--oo-ink, #16181c);
	cursor: pointer;
	padding: 0;
	transition: opacity .15s ease, border-color .15s ease;
}
.oo-carnav__btn:hover:not(:disabled) { border-color: var(--oo-ink, #16181c); }
.oo-carnav__btn:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 2px;
}
/* DISABLED IS GREY, NOT FADED (changed 10 Sep 2026, card polish 5). Ryan:
   "Make the disabled arrow button greyed out instead of white, and increase
   the strength of the border so that the buttons stand out more."

   `opacity: .32` faded the whole control -- line, glyph and white fill
   together -- so at the start of the rail the left arrow read as a white
   circle that had almost vanished, which is not the same message as "this one
   is unavailable". Each part is now stated instead: the sunk grey as the fill,
   the muted grey as the glyph, and the line kept at full strength so the
   button is still a button. `opacity: 1` is written out because it is taking
   back the fade rather than merely not setting it.

   The glyph clears AA on the fill it now sits on: --oo-muted on --oo-sunk is
   6.33:1 after card polish 5 darkened the token. A disabled control is exempt
   from the contrast minimum, and meeting it anyway costs nothing here.

   These buttons carry the real `disabled` property, set by the carousel script
   in functions.php, so `:disabled` is the state and the button is also removed
   from the tab order and announced as unavailable. `aria-disabled` is not used
   and is not needed: it is the weaker of the two and would have to be paired
   with a click guard. */
.oo-carnav__btn:disabled {
	opacity: 1;
	background: var(--oo-sunk, #f7f8f9);
	border-color: var(--oo-line-strong, #d3d8de);
	color: var(--oo-muted, #565c66);
	cursor: default;
}
.oo-carnav__btn svg { width: 16px; height: 16px; display: block; }

/* On a phone the row is swiped, not clicked, and the buttons would only take
   room from the heading. */
@media (max-width: 781px) { .oo-carnav { display: none; } }

/* ---------- Owner-style item cards ----------
   Was: a plain row, text left, 112px thumbnail right, price and a "Select"
   button on a line below. Owner uses a bordered card with the photo filling the
   full card height flush to its right edge, the price directly under the name,
   and a round + on the photo instead of a separate button.

   Reordering needs `display: contents` on two wrappers. The plugin nests
   title/description inside .orderable-product__content and price/button inside
   .orderable-product__actions, so name, price and description are not siblings
   and CSS `order` alone cannot interleave them. `display: contents` promotes
   the children to the card's own flex context, after which order works. Both
   wrappers are generic divs carrying no semantics, so nothing is lost from the
   accessibility tree. */
.orderable-products-list--list { row-gap: 14px; column-gap: 14px; }

.orderable-products-list--list .orderable-products-list__item {
	padding-bottom: 0;
	margin-bottom: 0;
	border-bottom: 0;
}

.orderable-products-list--list .orderable-product {
	position: relative;                 /* the + button anchors to the card */
	/* The list item is a flex container, so the card is a flex item and sizes
	   to its content unless told otherwise. Without this it collapsed to 200px
	   inside a 445px cell and `1fr` resolved to 66px, which squeezed the text
	   to two characters per line while the photo kept its fixed 132px. */
	width: 100%;
	flex: 1 1 auto;
	grid-template-columns: minmax(0, 1fr) var(--oo-photo, 200px);
	gap: 0;
	padding: 0;
	/* +2px for the borders. The card is border-box, so a bare min-height of
	   --oo-photo leaves a content box 2px shorter than the photo wants, and the
	   photo then competes with the bottom border for the same pixel row. On a
	   fractional device pixel ratio the border loses and appears cut off. */
	min-height: calc(var(--oo-photo, 200px) + 2px);
	border: 1px solid var(--oo-line, #e6e8eb);
	border-radius: var(--oo-radius, 14px);
	background: var(--oo-ground, #fff);
	overflow: hidden;                   /* photo corners follow the card */
	align-items: stretch;
	transition: border-color .15s ease, box-shadow .15s ease;
}
.orderable-products-list--list .orderable-product:hover {
	border-color: #d7dbe0;
	box-shadow: 0 2px 10px rgba(0, 0, 0, .05);
}

.orderable-products-list--list .orderable-product__hero {
	order: 2;
	width: var(--oo-photo, 200px);
	/* The plugin caps list heroes at max-width:95px. That cap was lifted for the
	   carousel long ago but never here, so the photo silently stayed 95px wide
	   inside a 172px grid track and left a gap beside it. The grid column was
	   correct; the element inside it was not. */
	max-width: none;
	height: auto;
	/* No min-height of its own. `align-self: stretch` already sizes it to the
	   card's content box; a second constraint only lets it exceed that box. */
	min-height: 0;
	border-radius: 0;
	align-self: stretch;
	position: relative;
	overflow: hidden;
}
/* Fill the hero absolutely rather than with height:100%. A percentage height
   resolves against a parent whose own height is auto, so on a card made taller
   by three lines of description the photo kept its square intrinsic size and
   left a white band under it with the + button straddling the edge. Absolute
   inset does not care how the parent got its height.

   NEEDS-IMPORTANT #3, and the white band came back without it (card polish 6,
   11 Sep). Orderable's main.css ships

       .orderable-product__image { height: auto !important; max-width: none !important }

   so the `height: 100%` above was never the used value. An absolutely
   positioned REPLACED element with `top: 0; bottom: 0; height: auto` does not
   stretch between the two insets the way a div does -- the spec resolves its
   height from the intrinsic one (here a 300x300 photo at `aspect-ratio: auto
   300 / 300`, so 150px at a 150px column) and drops `bottom` as
   over-constrained. On a card exactly as tall as the photo is wide that is the
   right answer by coincidence, which is why this only showed on the cards the
   label line and the pinned price made taller: at 375, hero 166.3px against an
   image 150px, a 16px white band along the bottom edge. !important is reachable
   only by !important, and matching it is the whole fix.

   `object-position` is the initial value, written out because the brief asks
   for a centred focal point and because a client site cropping a dish off the
   bottom of its own photograph should find the knob here rather than guess. */
.orderable-products-list--list .orderable-product__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100% !important;
	object-fit: cover;
	object-position: 50% 50%;
}

.orderable-products-list--list .orderable-product__content-wrap {
	order: 1;
	display: flex;
	flex-direction: column;
	gap: 0;
	height: auto;
	padding: 14px 16px;
	justify-content: flex-start;
}
/* Measured before this: 15px between name and price, 6px between price and
   description -- backwards. The 15px was the theme's own h2 margin-bottom,
   which has nothing to do with this card. */
/* NEEDS-IMPORTANT #2. The plugin ships
   `.orderable-product__title { margin: 0 0 12px !important }`, which is why the
   name sat 12px off the price while the description sat 6px under it -- exactly
   backwards. Specificity cannot fix this: an INLINE margin-bottom was measured
   and still computed 12px, which is the signature of !important and the thing
   that finally identified it. The plugin also sets 6px !important below 480px,
   which this outranks on specificity. */
.orderable-products-list--list .orderable-product .orderable-product__title {
	margin: 0 0 4px !important;
}
.orderable-products-list--list .orderable-product__content,
.orderable-products-list--list .orderable-product__actions {
	display: contents;
}
.orderable-products-list--list .orderable-product__title       { order: 1; }
.orderable-products-list--list .orderable-product__actions-price { order: 2; margin: 0; }
.orderable-products-list--list .orderable-product__description {
	order: 3;
	margin-top: 11px;
	-webkit-line-clamp: 2;
}

/* The + sits on the photo, as it does on the carousel cards. */
.orderable-products-list--list .orderable-product__actions-button {
	order: 4;
	position: absolute;
	right: var(--oo-btn-inset, 10px);
	bottom: var(--oo-btn-inset, 10px);
	margin: 0;
	z-index: 2;
}
/* Same control as the carousel's, not a second smaller one. The padding
   override above now covers both, so this only needs the shared appearance.
   min-width matters as much as width: without it the button falls back to its
   intrinsic content width and reappears as an ellipse. */
.orderable-products-list--list .orderable-button,
.orderable-products-list--list .orderable-product__add-to-order {
	width: 38px; height: 38px;
	min-width: 38px;
	border-radius: var(--oo-btn-radius, 9px);
	background: var(--oo-ground, #fff);
	border: 1px solid var(--oo-line, #e6e8eb);
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
	display: grid; place-items: center;
	font-size: 0; line-height: 0;
	color: transparent;
}
.orderable-products-list--list .orderable-product__add-to-order::after {
	content: "";
	/* The mask box is 24px, which maps the data URI's 24 unit viewBox 1:1 onto
	   pixels: the plus measures exactly 16px across and 16px down, cap to cap,
	   on a 2px stroke with round caps, centred on the 38px button with 11px of
	   air each side. */
	width: 24px; height: 24px;
	/* The button sets `color: transparent` to hide the plugin's own text; the
	   pseudo-element takes the ink back so `currentColor` below is the ink. */
	color: var(--oo-ink, #16181c);
	background-color: currentColor;
	-webkit-mask: var(--oo-icon-plus) center / 24px 24px no-repeat;
	        mask: var(--oo-icon-plus) center / 24px 24px no-repeat;
}

/* ---------- carousel: tighter metadata, larger controls ---------- */
.oo-carousel .orderable-product__content-wrap { padding: 10px 0 0; }
.oo-carousel .orderable-product__title { margin: 0 0 1px; }
.oo-carousel .orderable-product__actions { display: block; padding: 0; margin: 0; }
.oo-carousel .orderable-product__actions-price { margin: 0; }

.oo-carnav__btn {
	width: 40px; height: 40px;
	border-radius: 10px;              /* Owner uses a rounded square, not a pill */
}
.oo-carnav__btn svg { width: 18px; height: 18px; }

/* ---------- mobile: the list is not a grid ----------
   Below the plugin's own breakpoint .orderable-products-list--list becomes
   `display: block`, so the row-gap that spaces the cards apart on desktop does
   nothing and every card touched the next. The plugin spaced them with a
   margin on the item, which this theme had zeroed when the cards gained their
   own borders. One column of grid restores a single spacing mechanism at every
   width rather than maintaining two. */
@media (max-width: 699px) {
	.orderable-main--sections-cat_titles .orderable-products-list--list {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------- the add button's loading spinner ----------
   The plugin draws its spinner as `.orderable-button--loading::after`, and this
   theme draws the "+" as `::after` on the same element. Two rules, one
   pseudo-element: the theme's is more specific so it kept `content: "+"` while
   the plugin's border-radius and borders still painted, and the plugin's
   `top:10px; right:22px` -- coordinates for its full-width default button --
   put the result low and left inside a 38px circle.

   So: while loading, hand the pseudo-element back to the plugin (content: "")
   and centre it for a round button.

   Centred with calc() rather than a translate, deliberately. The plugin's
   `spin` keyframes animate `transform`, which would overwrite a centring
   translate the moment the animation started and throw the spinner back into
   the corner mid-spin. */
.oo-carousel .orderable-product__add-to-order.orderable-button--loading::after,
.orderable-products-list--list .orderable-product__add-to-order.orderable-button--loading::after {
	content: "";
	width: 16px;
	height: 16px;
	top: calc(50% - 8px);
	left: calc(50% - 8px);
	right: auto;
	font-size: 0;
	/* The plus is painted as a masked background on this same pseudo-element,
	   and both declarations outrank the plugin's spinner rule on specificity.
	   Without these two the spinner span would spin with a plus inside it. */
	-webkit-mask: none;
	        mask: none;
	background-color: transparent;
}

/* ---------- the control row ----------
   Owner puts service, address and time in one row of pills above the menu.
   Time cannot go here (its blocks are checkout-only), so this row carries the
   two that can. */
/* ---------- order details sheet ----------
   Ours rather than Orderable's location popup, because it carries the apartment
   number and the delivery instructions as well as the address. z-index clears
   Orderable's popup (10000) and the Places dropdown sits above it again at
   10001 -- see the .pac-container note further down, which is why that value is
   what it is. */
.oo-sheet { position: fixed; inset: 0; z-index: 10000; }
.oo-sheet[hidden] { display: none; }
.oo-sheet__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .45); }
.oo-sheet__panel {
	position: relative;
	margin: 6vh auto 0;
	width: min(520px, calc(100% - 32px));
	max-height: 88vh;
	display: flex;
	flex-direction: column;
	background: var(--oo-ground, #fff);
	border-radius: var(--oo-radius, 14px);
	box-shadow: 0 18px 50px rgba(0, 0, 0, .22);
	overflow: hidden;
}
.oo-sheet__head {
	display: flex; align-items: center; justify-content: space-between;
	gap: 12px; padding: 18px 20px 6px;
}
.oo-sheet__title { margin: 0; font-size: 1.35rem; font-weight: 700; }
.oo-sheet__close {
	appearance: none; border: 0; background: var(--oo-sunk, #f7f8f9);
	color: var(--oo-ink, #16181c); width: 32px; height: 32px; border-radius: 999px;
	display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.oo-sheet__body { padding: 12px 20px 20px; overflow-y: auto; }
.oo-servicebar--sheet { display: flex; width: 100%; margin-bottom: 16px; }
.oo-servicebar--sheet .oo-servicebar__btn { flex: 1 1 0; }

.oo-field { margin: 0 0 14px; display: flex; flex-direction: column; gap: 5px; }
.oo-field__label { font-size: 0.95rem; font-weight: 600; color: var(--oo-ink, #16181c); }
.oo-field .oo-field__input {
	/* This theme sets no universal border-box, so a bare `width: 100%` here means
	   100% of the CONTENT box and the 28px of padding plus 2px of border land on
	   top of it. The field then renders 30px wider than the column it sits in,
	   the sheet body gets a horizontal scrollbar, and the right-hand edge of every
	   input is clipped. It was wrong at every width, not just on phones. */
	box-sizing: border-box;
	width: 100%; font: inherit; font-size: 1rem;
	padding: 12px 14px; border-radius: 10px;
	border: 1px solid var(--oo-line, #e6e8eb);
	background: var(--oo-ground, #fff); color: var(--oo-ink, #16181c);
}

/* Belt and braces for anything added to the sheet later. Scoped rather than
   global so it cannot disturb Orderable's own layout elsewhere on the page. */
.oo-sheet__panel, .oo-sheet__panel *, .oo-sheet__panel *::before, .oo-sheet__panel *::after {
	box-sizing: border-box;
}
.oo-field .oo-field__input:focus-visible { outline: 2px solid var(--oo-ink, #16181c); outline-offset: 1px; }
.oo-field textarea.oo-field__input { resize: vertical; min-height: 72px; }
.oo-field__hint { font-size: 0.8rem; color: var(--oo-muted, #565c66); }

/* Our own suggestion list, rendered INSIDE the sheet.
   Google's .pac-container is appended to <body>, which put it behind the modal
   (z-index) and made it unclickable (the focus trap's `inert`). A list that is a
   child of the dialog has neither problem, and can be styled to match the form
   instead of looking like a Google widget dropped into it. */
.oo-field { position: relative; }
.oo-ac {
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	z-index: 5;
	margin: 4px 0 0;
	padding: 4px;
	list-style: none;
	max-height: 260px;
	overflow-y: auto;
	background: var(--oo-ground, #fff);
	border: 1px solid var(--oo-line, #e6e8eb);
	border-radius: 10px;
	box-shadow: 0 10px 28px rgba(0, 0, 0, .12);
}
.oo-ac[hidden] { display: none; }
.oo-ac__item {
	display: flex;
	flex-direction: column;
	gap: 1px;
	padding: 9px 11px;
	border-radius: 7px;
	cursor: pointer;
	line-height: 1.3;
}
.oo-ac__item.is-active,
.oo-ac__item:hover { background: var(--oo-sunk, #f7f8f9); }
.oo-ac__main { font-size: 0.95rem; color: var(--oo-ink, #16181c); }
.oo-ac__sub { font-size: 0.82rem; color: var(--oo-muted, #565c66); }

.oo-sheet__from { border-top: 1px solid var(--oo-line, #e6e8eb); padding-top: 14px; margin-top: 4px; }
.oo-sheet__fromtitle { margin: 0 0 6px; font-size: 0.95rem; font-weight: 600; }
.oo-sheet__store { margin: 0; display: flex; align-items: center; gap: 8px; }
.oo-sheet__storename { font-weight: 600; }
.oo-sheet__distance {
	font-size: 0.85rem; color: var(--oo-muted, #565c66);
	background: var(--oo-sunk, #f7f8f9); padding: 3px 9px; border-radius: 999px;
}
.oo-sheet__storename { font-size: 1.1rem; }
/* "Closed" answers the wrong question; "Opens 11:00 am" answers the one the
   reader actually has, which is whether it is worth waiting. */
.oo-sheet__open { margin: 4px 0 0; font-size: 0.95rem; color: var(--oo-muted, #565c66); }
.oo-sheet__open.is-open { color: #1a7f4b; font-weight: 500; }
.oo-sheet__storeaddress { margin: 2px 0 4px; color: var(--oo-muted, #565c66); font-size: 0.9rem; }
.oo-sheet__note { margin: 12px 0 0; color: #b42318; font-size: 0.9rem; }
.oo-sheet__foot { padding: 14px 20px 18px; border-top: 1px solid var(--oo-line, #e6e8eb); }
.oo-sheet__done {
	appearance: none; border: 0; width: 100%; cursor: pointer;
	background: var(--oo-ink, #16181c); color: #fff;
	font: inherit; font-size: 1rem; font-weight: 600;
	padding: 13px 20px; border-radius: 999px;
}
.oo-sheet__done:focus-visible { outline: 2px solid var(--oo-ink, #16181c); outline-offset: 2px; }
body.oo-sheet-open { overflow: hidden; }

@media (max-width: 600px) {
	/* Bottom sheet on phones, which is where a dialog with three fields is
	   easiest to reach one-handed. */
	.oo-sheet__panel {
		margin: auto 0 0; width: 100%; max-height: 92vh;
		border-radius: var(--oo-radius, 14px) var(--oo-radius, 14px) 0 0;
	}
	.oo-sheet { display: flex; flex-direction: column; justify-content: flex-end; }
}

/* Restaurant address and open/closed, directly under the menu title. Owner puts
   the same line in the same place and it reads well: it answers "can I order from
   here right now" before the customer scrolls a single dish. */
.oo-storemeta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	margin: -6px 0 18px;
	color: var(--oo-muted, #565c66);
	font-size: 0.9rem;
	line-height: 1.4;
}
.oo-storemeta__item { display: inline-flex; align-items: center; gap: 5px; }
.oo-storemeta__sep { color: var(--oo-line, #e6e8eb); }
.oo-storemeta__status.is-open { color: #1a7f4b; font-weight: 500; }
/* The status line opens the hours sheet, so it has to be a real button. Styled
   back to looking like the text it sits in, with the underline as the only cue
   that it does something -- the same affordance Owner uses. */
.oo-storemeta__trigger {
	appearance: none;
	border: 0;
	background: none;
	padding: 0;
	font: inherit;
	cursor: pointer;
	text-decoration: underline;
	text-underline-offset: 2px;
}
.oo-storemeta__trigger:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

/* Weekly hours. `tabular-nums` so the times form a column instead of drifting. */
.oo-hours { width: 100%; border-collapse: collapse; margin-top: 10px; }
.oo-hours__row > * {
	padding: 9px 0;
	border-bottom: 1px solid var(--oo-line, #e6e8eb);
	font-weight: 400;
	text-align: left;
}
.oo-hours__row:last-child > * { border-bottom: 0; }
.oo-hours__row.is-today > * { font-weight: 700; }
.oo-hours__time {
	text-align: right;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	color: var(--oo-muted, #565c66);
}
.oo-hours__row.is-today .oo-hours__time { color: var(--oo-ink, #16181c); }


.oo-controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
	margin: 0 0 26px;
}

/* WordPress gives every block after the first `margin-block-start: 1.2em`. In a
   row with `align-items: center` that margin is asymmetric -- 19.2px on top, 0
   below -- so the centring is computed on a margin box that is 19.2px taller at
   the top, and the control lands ~10px lower than the pill beside it. The two
   bars measured 45px and 44px tall and still looked uneven, because the height
   was never the problem. `gap` already spaces this row. */
.oo-controls > * { margin-block: 0; }


.oo-servicebar {
	display: inline-flex;
	padding: 3px;
	border-radius: 999px;
	background: var(--oo-sunk, #f7f8f9);
	border: 1px solid var(--oo-line, #e6e8eb);
}
.oo-servicebar.is-busy { opacity: .6; pointer-events: none; }
/* ONE FONT WEIGHT IN BOTH STATES (changed 10 Sep 2026, card polish 5). Ryan:
   "The pickup and delivery buttons change size slightly when they're
   selected." Measured before the change, at 375 and 1280: the selected segment
   was 600 and the other 500, and "Delivery" is 3px wider bold than it is
   medium, so selecting it took the segment from 92px to 95px and the whole bar
   from 183px to 186px. Nothing else differed -- both segments carry `border:
   0`, the active one's shadow is outside the layout, and the padding is the
   same -- so the weight WAS the movement.

   The fix is that both states are 600 and the selection is carried entirely by
   what does not affect layout: the white chip, the shadow and the ink against
   the muted grey. 600 rather than 500 for both, so the selected segment keeps
   exactly the weight it has today rather than the unselected one setting the
   level. Measured after: 186px in both states at 375, 202px at 1280, and the
   text box does not move.

   The alternatives were considered and are worse. Reserving the bold width
   with an invisible copy needs the label in a data attribute, which means
   touching the markup to solve a paint problem. `flex: 1 1 0` on the segments
   makes them equal to each other but not stable across the toggle, because the
   widest max-content still grows when it goes bold. */
.oo-servicebar__btn {
	appearance: none;
	border: 0;
	background: transparent;
	color: var(--oo-muted, #565c66);
	font: inherit;
	font-size: 1rem;
	font-weight: 600;
	padding: 8px 20px;
	border-radius: 999px;
	cursor: pointer;
	transition: background-color .15s ease, color .15s ease;
}
.oo-servicebar__btn.is-active {
	background: var(--oo-ground, #fff);
	color: var(--oo-ink, #16181c);
	box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
}
.oo-servicebar__btn:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 2px;
}

/* The single address control. A button, not an input: the typing happens in
   Orderable's popup, which this opens. Sized to sit in the row beside the
   service pills. */
.oo-locator {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	height: 42px;
	padding: 0 6px 0 14px;
	border-radius: 999px;
	border: 1px solid var(--oo-line, #e6e8eb);
	background: var(--oo-ground, #fff);
	max-width: 100%;
	min-width: 0;
}
.oo-locator__icon {
	display: inline-flex;
	color: var(--oo-muted, #565c66);
	flex: 0 0 auto;
}
.oo-locator__text {
	font-size: 0.95rem;
	color: var(--oo-ink, #16181c);
	/* An address is arbitrarily long and this sits in a flex row. Without a
	   min-width of 0 the flex item refuses to shrink below its content and
	   pushes the row into a wrap at desktop width. */
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.oo-locator--empty .oo-locator__text { color: var(--oo-muted, #565c66); }
.oo-locator__edit {
	appearance: none;
	border: 0;
	background: var(--oo-sunk, #f7f8f9);
	color: var(--oo-ink, #16181c);
	font: inherit;
	font-size: 0.85rem;
	font-weight: 600;
	padding: 7px 14px;
	border-radius: 999px;
	cursor: pointer;
	flex: 0 0 auto;
	transition: background-color .15s ease;
}
.oo-locator__edit:hover { background: var(--oo-line, #e6e8eb); }
/* Pickup needs no address, so the control is not shown at all rather than shown
   saying so. `[hidden]` alone loses to `display: inline-flex` above. */
.oo-locator[hidden] { display: none; }

.oo-locator__edit:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 2px;
}

/* The popup is now the only place an address is typed, so its field carries the
   autocomplete and needs room for a full street address rather than a postcode. */
.opml-popup .opml-store-locator-input__input { width: 100%; }

/* The floating label is drawn INSIDE the input's box and takes pointer events,
   while having no `for` and not wrapping the input. `elementFromPoint` at the
   centre of the field returns the label, so a click on the middle of the address
   box focuses nothing at all -- the customer has to find the bare strip to the
   right of the label text. Harmless while this field was a secondary way in;
   blocking now that the popup is the only place an address is typed.
   The label is decorative text, so it does not need to take clicks. */
.opml-store-locator-input__label { pointer-events: none; }

/* Orderable's locator, made to sit in the row as another pill rather than a
   full-width field with its own call to action. */
.oo-controls .opml-postcode-locator { margin: 0; flex: 0 1 auto; }
.oo-controls .opml-postcode-locator__row {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}
/* Size the WRAPPER, not the input. The wrapper draws the rounded pill and the
   plugin gives it a fixed 250px; shrinking only the input left a 127px field
   inside a 250px pill, so the placeholder overflowed its own box and painted
   across the gap toward the button. */
.oo-controls .opml-store-locator-input { width: 200px; max-width: 100%; }
.oo-controls .opml-store-locator-input__input,
.oo-controls .opml-postcode-locator input[type="text"] {
	height: 42px;
	padding: 0 16px;
	border-radius: 999px;
	border: 1px solid var(--oo-line, #e6e8eb);
	font-size: 0.95rem;
	min-width: 0;
	width: 100%;
}
.oo-controls .opml-postcode-locator__button-wrap .orderable-button,
.oo-controls .opml-postcode-locator button {
	height: 42px;
	border-radius: 999px;
	font-size: 0.95rem;
}

/* The note sits BESIDE the control, never inside it. It used to be an ::after
   on .oo-servicebar, which is a flex container -- so the message became a flex
   item and stretched the pill from 194px to 385px, wrapping an error message in
   a rounded segmented control. `flex-basis: 100%` did not drop it to its own
   line because the bar has no flex-wrap. */
.oo-servicebar { flex: 0 0 auto; }
.oo-servicebar__note {
	flex: 1 1 100%;
	margin: 0;
	color: var(--oo-accent, #d92d20);
	font-size: 0.875rem;
	line-height: 1.4;
	order: 99;              /* below the row, whatever else is in it */
}

@media (max-width: 560px) {
	.oo-controls { gap: 8px; }
	.oo-servicebar__btn { padding: 8px 16px; }
	.oo-controls .opml-store-locator-input { width: 150px; }
}

/* ---------- checkout tidy ----------
   The "Ship to a different address?" toggle is hidden, not removed: unchecked
   and present is what makes WooCommerce copy billing into shipping, and that
   copy is what delivery tax is calculated from. */
.orderable-checkout-section #ship-to-different-address,
.orderable-checkout-section .woocommerce-shipping-fields__field-wrapper:empty {
	display: none;
}

/* ---------- Pickup before Delivery at checkout ----------
   Presentation only, and deliberately so. Reordering the rates themselves in
   `woocommerce_package_rates` also worked, but WooCommerce takes the FIRST
   rate as the default when none is chosen, so pickup became the default -- and
   because pickup hides the address fields, there was then no way to enter an
   address to make delivery available at all. Visual order, not rate order. */
ul.woocommerce-shipping-methods {
	display: flex;
	flex-direction: column;
	gap: 4px;
}
ul.woocommerce-shipping-methods li:has(input[value^="orderable_pickup"]) {
	order: -1;
}

/* Google's autocomplete dropdown, kept from covering the field when it has
   nothing to show.

   The z-index must clear Orderable's popup, which is 10000. Google appends this
   list to <body>, so at z-index 100 the predictions rendered BEHIND the modal --
   in the DOM, correct, and painted underneath. The field looked like it had no
   autocomplete at all. Counting `.pac-item` elements does not catch that; the
   check is `document.elementFromPoint` over the dropdown, which has to come back
   inside `.pac-container`. Google's own injected stylesheet says 1000, and it
   loads after ours, so this has to beat 1000 as well as 10000. */
.pac-container:empty { display: none !important; }
.pac-container { z-index: 10001; }


/* ---------- where the labels sit in the list card ----------
   This card is not laid out in DOM order. `.orderable-product__content` and
   `.orderable-product__actions` are both `display: contents` a few hundred
   lines up, which promotes title, price and description into one flex column so
   `order` can interleave them: title 1, price 2, description 3, the round + 4.

   The labels wrapper is promoted with them and carries no `order`, so it
   defaulted to 0 and rendered ABOVE the dish name -- the pills read as a
   heading for the card rather than as a note about the dish. Nothing in the
   plugin is wrong here; `before-description` is exactly where it put them in
   the source, and this theme moved the source order out from under it.

   They belong with the details, not with the name, so they take 3 and the two
   below them move down one. Those two declarations are restatements of rules
   further up and win on document order at equal specificity; they are here
   rather than edited in place so that deleting this whole block restores the
   original layout in one cut. */
.orderable-products-list--list .orderable-pro-product-labels__wrapper { order: 3; }
.orderable-products-list--list .orderable-product__description { order: 4; }
.orderable-products-list--list .orderable-product__actions-button { order: 5; }

/* ---------- dietary labels: quiet text, three treatments ----------
   REWORKED 10 September 2026. The first version restyled product-labels-pro's
   own pills. Ryan's verdict was that they take up too much space, are too
   colourful, and "consume too much attention for their priority level": a
   person with a gluten allergy will check carefully and everyone else should
   barely notice a label is there.

   Pro prints each label's colours as an INLINE style attribute read from term
   meta, and an inline attribute beats any stylesheet, so quietening the pills
   from here would have meant `!important` on every colour. The markup is
   replaced instead, through Pro's own `orderable_product_labels_product_label_markup`
   filter -- see inc/menu-labels.php -- so there is no inline style left to beat
   and every rule below is plain cascade.

   ONE MARKUP, THREE TREATMENTS. Each label carries its full word and its short
   word; the modifier class on `.orderable-main` decides which is painted. The
   other is left in the accessibility tree rather than removed, so what a screen
   reader announces is the same in all three.

     .oo-labels-a   a muted line under the price         (the default)
     .oo-labels-b   inline after the price, no extra card height
     .oo-labels-c   short words in small caps
     .oo-labels-a2  A's line, positives THEN allergens, clamped to one line
     .oo-labels-a3  A2 plus an "Allergens" link that opens the drawer

   A2 and A3 do NOT share the one markup: they cannot, because the line has to
   be ordered and the several "Contains x" terms joined into one phrase, and
   neither is possible one term at a time. Under those two the per-label filter
   prints nothing on the card and inc/menu-labels.php writes the line whole.

   Every card rule is scoped to `.oo-labels-on`, which inc/menu-labels.php adds
   only when the toggle is on. With the toggle off there is no markup AND not one
   of these rules can match.

   COLOUR. `--oo-muted` rather than the ink at an opacity. Ink at the 55% the
   brief suggested measures 3.95:1 on white, which fails AA for text this size;
   `--oo-muted` reads as the same quiet grey and passes. It measured 4.83:1 on
   the card when it was #6b7280 and measures 6.73:1 now that card polish 5 has
   darkened it to #565c66. Measured with probe.js, not eyeballed. */

/* The wrapper is Pro's; these three declarations are all wrong inside a card.
   100% height in a stretched grid item takes the whole cell, 100% width forces
   a line break after the labels whether or not they fill the row, and `flex`
   turns a sentence into a row of boxes. Text flows instead. */
.oo-labels-on .orderable-pro-product-labels__wrapper {
	display: block;
	height: auto;
	width: auto;
	margin: 0;
	font-size: 0.75rem;
	line-height: 1.4;
	color: var(--oo-muted, #565c66);
}

.oo-labels-on .orderable-pro-product-labels__wrapper--position-before-description {
	margin-top: 2px;
	margin-bottom: 0;
}

/* A dish whose only labels are allergen statements gets an EMPTY wrapper on the
   card, because those are dropped there and the wrapper is printed around the
   loop rather than by it. Six of the nineteen labelled Sonora dishes are in
   that state, and each was taking 2px of nothing. `:empty` cannot see it -- the
   plugin's template leaves whitespace inside the div -- so the test is for the
   labels themselves. */
.oo-labels-on .orderable-pro-product-labels__wrapper:not(:has(.oo-label)):not(:has(.oo-labels__link)) {
	display: none;
}

/* Shared by the card and the drawer, so they are not scoped to the treatment
   class -- the drawer is rendered outside `.orderable-main` and has none. They
   can only ever match markup this theme writes, which exists only while the
   toggle is on. */
.oo-label {
	/* Inherit the page's stack. Pro hardcodes a ui-sans-serif list, so a client
	   site with a brand typeface got everything in its own font except these
	   seven words. */
	font-family: inherit;
	white-space: nowrap;
}

/* The separator is a middle dot between items, never before the first. */
.oo-label + .oo-label::before {
	content: "\00b7";
	margin: 0 0.45em;
	/* The dot is punctuation between two words, not part of either, so it does
	   not inherit the small-caps treatment C puts on the wrapper. */
	font-variant-caps: normal;
}

/* The short word is treatment C's alone. */
.oo-label__short {
	display: none;
}

/* ---------- A: a muted line under the price ----------
   The base above IS treatment A, so this modifier adds nothing. It is written
   out rather than left implicit so that all three treatments are findable by
   the same search, and so choosing one really is deleting the other two. */

/* ---------- A2 and A3: positives, then allergens, on one line ----------
   Same size and same colour as A -- everything above already sets those, and
   these rules only decide where the line breaks and where the link sits.

   A2 and A3 print ONE element per phrase: the positives in their fixed order,
   then a single joined "Contains dairy, nuts". See inc/menu-labels.php for why
   the line is written whole rather than one term at a time.

   THE LINE IS CLAMPED AND THE LINK IS NOT. `text-overflow: ellipsis` truncates
   the END of a box, so a link sitting at the end of the same box is the first
   thing lost on a narrow card -- exactly backwards. The wrapper is a flex row
   instead: the words are a shrinkable box that ellipses, the link is `flex: 0 0
   auto` and always readable. On a 375px card with four labels the words are cut
   and "Allergens" survives, which is the behaviour worth having.

   `min-width: 0` on the words is what makes the ellipsis appear at all. A flex
   item's automatic minimum size is its content, so without it the box refuses
   to shrink, pushes the link off the card, and nothing is ever truncated. */
.oo-labels-a2 .orderable-pro-product-labels__wrapper,
.oo-labels-a3 .orderable-pro-product-labels__wrapper {
	display: flex;
	align-items: baseline;
	/* Both are Pro's, and both have to be said again the moment `display: flex`
	   puts them back in play. Pro's wrapper is a wrapping flex row with a gap,
	   which is right for pills and wrong for a sentence: `wrap` sent A3's link
	   to a second line the moment the words filled the first (measured: card
	   line 39px instead of 17px, +21px on every labelled card at 375), and the
	   gap opened a hole beside a separator that already has margins. Clamping to
	   one line means the row must not wrap; that is what makes the words shrink
	   and the ellipsis appear. */
	flex-wrap: nowrap;
	gap: 0;
}

/* HOW MUCH ROOM THERE ACTUALLY IS, measured rather than assumed, because the
   answer decides whether A3 is usable. The card's text column is 131px wide at
   375 (a 163px content-wrap less its 16px side padding) and 211px at 1280,
   where the list is a three-column grid and a card is 445px -- NOT the 600-odd
   px a full-width card would suggest. "Allergens" plus its separator is a fixed
   65px of that, so at 375 the words get 66px and are cut hard. That is A3's
   real cost at phone width and it is what the screenshots are for. */

.oo-labels__line {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}

/* The middle dot between the words and the link. It is its own element rather
   than a `::before` on the button so that the underline on hover belongs to the
   word "Allergens" and not to the punctuation in front of it, and so that the
   dot is not inside the button's hit area. */
.oo-labels__sep {
	flex: 0 0 auto;
}

.oo-labels__sep::before {
	content: "\00b7";
	margin: 0 0.45em;
}

/* A BUTTON, reset to look like text. It opens the drawer; it does not navigate,
   so an anchor would be a lie to a screen reader and, separately, would not
   work: Orderable's trigger handler resolves `$element.is('button')`.

   `font: inherit` and `color: inherit` are what make it the same 0.75rem muted
   grey as the rest of the line -- measured 6.73:1 on the card, the same as the
   labels, because it inherits the identical colour rather than being set to a
   link blue. */
.oo-labels__link {
	flex: 0 0 auto;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	color: inherit;
	line-height: inherit;
	text-decoration: none;
	white-space: nowrap;
	cursor: pointer;
}

.oo-labels__link:hover,
.oo-labels__link:focus-visible {
	text-decoration: underline;
}

.oo-labels__link:focus-visible {
	outline: 2px solid var(--oo-focus-ring, #16181c);
	outline-offset: 2px;
	border-radius: 2px;
}

/* Orderable adds its loading class to whatever was clicked while the drawer's
   markup is fetched. The class is styled for a 38px round button, and on a word
   of text it collapsed the line for as long as the request took. */
.oo-labels__link.orderable-button--loading {
	opacity: 0.6;
	background: none;
}

/* ---------- B: inline after the price ----------
   The card is not laid out in DOM order: `.orderable-product__content` and
   `.orderable-product__actions` are both `display: contents`, which promotes
   title, price, labels, description and the round + into one flex column on
   `.orderable-product__content-wrap` where `order` interleaves them.

   A column flex container gives every item its own line, so B turns the column
   into a wrapping ROW and puts the things that want a line to themselves back
   on 100% of it. Price and labels are left at `auto`, so they share a line and
   the labels wrap underneath when the dish name is long. The card gains no
   height. Anything not named here keeps a full-width line, which is what an
   unrecognised child should do. */
.oo-labels-b .orderable-product__content-wrap:has(.orderable-pro-product-labels__wrapper) {
	flex-flow: row wrap;
	align-items: baseline;
	/* Not the default `stretch`. A multi-line flex container distributes its
	   spare vertical space BETWEEN the lines, and this one is `height: 100%` in
	   a stretched grid cell, so every gap in the card grew. In the column layout
	   this replaces, `justify-content: flex-start` was doing the same job. */
	align-content: flex-start;
	column-gap: 10px;
}

/* `:has()` so an UNLABELLED card is not touched at all. Scoping by the treatment
   class alone re-laid-out every card in the menu, and although the height came
   out the same the unlabelled ones moved 2px -- measured, not visible, and still
   a change to a card this treatment has no business touching. On a browser
   without `:has()` the whole B block is dropped and the labels stack under the
   price, which is treatment A: a fallback, not a break. */
.oo-labels-b .orderable-product__content-wrap:has(.orderable-pro-product-labels__wrapper) .orderable-product__title,
.oo-labels-b .orderable-product__content-wrap:has(.orderable-pro-product-labels__wrapper) .orderable-product__description {
	flex: 0 0 100%;
}

.oo-labels-b .orderable-pro-product-labels__wrapper--position-before-description {
	margin-top: 0;
}


/* ---------- C: short words ----------
   Short WORDS, not letters, and no emoji: single letters need a key that is not
   on the card, and a chilli glyph renders differently on every platform. The
   full word stays in the DOM, moved out of sight the way a screen-reader-only
   caption is, so the accessible name does not change with the treatment. */
.oo-labels-c .oo-label__full {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.oo-labels-c .oo-label__short {
	display: inline;
}

.oo-labels-c .orderable-pro-product-labels__wrapper {
	font-variant-caps: all-small-caps;
	letter-spacing: 0.04em;
}

/* The carousel card hides its description, so the labels would otherwise sit
   against the price with no gap at all. */
.oo-labels-on .oo-carousel .orderable-pro-product-labels__wrapper--position-before-description {
	margin-bottom: 2px;
}

/* ---------- the same words in the drawer ----------
   The drawer is rendered outside `.orderable-main`, so it carries no treatment
   class and always shows the full words: it is the detail layer, and the
   allergen accordion below it is a sentence, not a label. The palette custom
   properties are out of scope there too, which is why the colour carries its
   literal fallback. */
.orderable-drawer .orderable-pro-product-labels__wrapper {
	display: block;
	height: auto;
	width: auto;
	margin-bottom: 14px;
	/* 14px, not the 13px it was: card polish 5 put a 14px floor under every
	   font in the drawer at phone width, and this line was the only one under
	   it. */
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--oo-muted, #565c66);
}

/* ---------- allergen detail in the drawer ----------
   allergen-info-pro renders into Orderable's accordion, which is only ever
   drawn inside the product drawer and on the single-product tab. It ships three
   declarations and no type scale, so the three lines came out at the drawer's
   default body size with a bold run-in and no rhythm.

   The drawer is not inside `.orderable-main`, so the palette custom properties
   are out of scope here. Every colour carries its literal fallback, which is
   the same thing the sheet and the category nav do a few hundred lines up. */
.orderable-pro-allergen-info {
	margin-bottom: 12px;
	font-size: 0.9rem;
	line-height: 1.5;
}

.orderable-pro-allergen-info .orderable-pro-allergen-info__field {
	margin-bottom: 6px;
}

.orderable-pro-allergen-info .orderable-pro-allergen-info__label {
	display: block;
	font-weight: 600;
	font-size: 0.72rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--oo-muted, #565c66);
}

.orderable-pro-allergen-info .orderable-pro-allergen-info__value {
	color: var(--oo-ink, #16181c);
}

/* ---------- the allergen info button, moved off the add button ----------
   allergen-info-pro and nutritional-info-pro both put an (i) on the card, and
   the customer opens the allergen detail with it. nutritional-info.scss pins it
   with

       .orderable-button--nutritional-info { position:absolute; bottom:10px; right:10px }

   which is the same corner of the same photo this theme puts its round + in
   (`.orderable-products-list--list .orderable-product__actions-button`, a few
   hundred lines up: absolute, right 10, bottom 10, z-index 2). The two sat on
   exactly the same 38px square, the + on top, so the (i) was invisible AND
   unclickable: a click at its centre landed on Add and put the dish in the
   basket instead of opening its allergens.

   Nothing about that looks wrong on screen, which is why it is worth a comment.
   The button is not missing, it is underneath, and the only symptom is that
   pressing "see allergens" adds a taco to your order.

   Moved to the top of the photo instead of hidden. The labels are the glance
   and the drawer accordion is the detail, but the card is the only place a
   customer can ask for the detail without adding anything, and an allergy is
   exactly the question you want answered before you commit.

   Found by measuring, not by looking: `document.elementFromPoint()` over the
   button's own centre came back as the add-to-order button. */
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	top: 10px;
	bottom: auto;
	right: 10px;
	z-index: 2;
}

/* The shared 38px circle rule above sets `color: transparent` so the plugin's
   own "+" glyph cannot show through the pseudo-element that replaces it. The
   (i) is an inline SVG whose path has no fill attribute, so it paints black
   rather than currentColor and survives that -- but only by accident. Setting
   the fill explicitly means a later change to the circle's colour cannot make
   the icon disappear. */
.orderable-product__hero .orderable-button--nutritional-info svg {
	width: 17px;
	height: 17px;
	fill: var(--oo-ink, #16181c);
}

/* ---------- while online ordering is paused ----------
   SIGNPOSTING, NOT ENFORCEMENT. The refusal lives in
   `inc/ordering-controls.php` on `woocommerce_add_to_cart_validation` and
   `woocommerce_checkout_process`, and it does not care what this stylesheet
   says. These rules exist because Orderable's AJAX add buffers and discards
   output before returning cart fragments, so the notice attached to the
   refusal never reaches the customer: without them, pressing Add spins the
   button briefly and then nothing happens, with no explanation on screen.

   Deliberately NOT `pointer-events: none` and deliberately not `disabled`.
   Both would move the state into the DOM, where a stale tab, the back button
   or a bookmarked `?add-to-cart=` walks straight past it -- which is the one
   thing the brief for this feature ruled out. The button still works, and the
   server still says no. */
body.oo-ordering-paused .orderable-product__add-to-order,
body.oo-ordering-paused .orderable-product__actions-button .orderable-button {
	opacity: 0.4;
	cursor: not-allowed;
}

/* The banner itself. Amber rather than red: the restaurant is not broken and
   nothing has gone wrong, it is closed for orders for a while. Red is what the
   out-of-zone refusal uses and the two should not look like the same event.
   Contrast of the text on its ground is 8.0:1. */
.oo-pausebanner {
	margin: 0 0 12px;
	padding: 12px 16px;
	border: 1px solid #e7d7a8;
	border-left: 4px solid #b7860b;
	border-radius: 10px;
	background: #fdf8ec;
	color: #6b4a12;
	font-size: 0.95rem;
	font-weight: 500;
	line-height: 1.45;
}

/* The minimum-delivery note. It sits under the service pills and deliberately
   does NOT use `.oo-servicebar__note`: the service-switch script rewrites the
   text of every element carrying that class on each switch, so a note
   borrowing it would be blanked the first time anyone touched the pills. */
.oo-minnote {
	/* `.oo-controls` is a wrapping flex row and this note is dropped into it as
	   a sibling of the pills, so without a full-width basis it sat BETWEEN the
	   service bar and the address locator and squeezed both. Same two
	   declarations `.oo-servicebar__note` already uses, for the same reason.
	   Order 98 puts it above that note rather than below it: "add $10.75 more"
	   is about the order and "add your address" is about the next step, and the
	   two read in that sequence. */
	flex: 1 1 100%;
	order: 98;
	margin: 0;
	font-size: 0.875rem;
	line-height: 1.4;
	color: #8a5a12;
}

/* The banner is dropped in ahead of the service bar, inside the same flex row,
   so it needs the full-width basis too -- and order -1, because a message that
   says ordering is closed has to come before the controls it applies to. */
.oo-pausebanner {
	flex: 1 1 100%;
	order: -1;
}

@media (max-width: 560px) {
	.oo-pausebanner { font-size: 0.9rem; padding: 10px 13px; }
}

/* ==========================================================================
   CARD POLISH, 10 September 2026 -- the plus glyph in the quantity roller,
   and the price moved to the bottom-left of the card.

   Kept as one block at the end of the file, after every rule it restates, so
   the whole change reverts in one cut and so the `order` values below are
   guaranteed to win on document order rather than on a specificity race.
   ========================================================================== */

/* ---------- the plus in the quantity roller ----------
   Once an item is in the cart the card's Add button is replaced by Orderable's
   quantity roller, and the same roller is the quantity control in the drawer's
   mini-cart. Its "+" is not text you can see: the plugin sets `font-size: 0;
   text-indent: 250%; overflow: hidden` on the button and draws the glyph as
   two square-ended 2px bars, `::before` horizontal and `::after` vertical.

   So this is a two-part swap: mask the SAME plus used on the Add button onto
   `::before`, and take `::after` out of the drawing (the plugin sets
   `display: block !important` on both, hence the !important here -- it is
   taking back a declaration the plugin made important first, which is the
   file's existing convention).

   `background` is deliberately NOT set. The plugin paints these bars, and
   changes their colour on hover and focus; leaving that alone means the mask
   inherits the plugin's hover behaviour for free.

   Scoped `:not(.orderable-button--loading)` so the loading state is handed
   back to the plugin untouched, exactly as the Add button's spinner is. */
.orderable-quantity-roller__button--increase:not(.orderable-button--loading)::before {
	/* 15.6px mask box -- 65% of the card's 24px -- which draws a 9.1px plus on
	   a 1.3px stroke now that the drawing runs 6 to 18 (card polish 5; it was
	   10.4px while it ran 5 to 19). The mask box did NOT change: the glyph got
	   smaller inside it because the artwork did, so the roller followed the
	   card without a second number to keep in step. The roller's button is
	   24px of content where the Add button is 38px, so the glyph is scaled to
	   its own control rather than set to the same absolute size: the three
	   pluses are one drawing at three sizes, which is what matching means
	   here. The plugin centres this pseudo-element with top/left 50% and a
	   negative margin of half its size, so the margin has to move with it. */
	width: 15.6px;
	height: 15.6px;
	margin: -7.8px 0 0 -7.8px;
	-webkit-mask: var(--oo-icon-plus) center / 15.6px 15.6px no-repeat;
	        mask: var(--oo-icon-plus) center / 15.6px 15.6px no-repeat;
}
.orderable-quantity-roller__button--increase:not(.orderable-button--loading)::after {
	display: none !important;
}

/* THE MINUS MOVES WITH IT. The plugin draws the roller's minus as a 12px by
   2px square-ended bar; the plus beside it used to be 12.1px across for
   exactly that reason. Leaving the minus at the plugin's 12 while the plus
   shrinks would put two glyph sizes and two stroke weights inside one control,
   which reads as a bug rather than as a choice. The bar is restated at the
   plus's own numbers and its centring margin moves with it.

   9.1px long and 1.3px thick as of card polish 5 (it was 10.4 by 1.3), which
   is the same 15.6px mask read through the new artwork: length = mask * 7/12,
   thickness = mask / 12. Rounded ends so the caps match the plus's. It is one
   declaration block and it reverts with the plus. */
.orderable-quantity-roller__button--decrease:not(.orderable-button--loading)::before {
	width: 9.1px;
	height: 1.3px;
	margin: -0.65px 0 0 -4.55px;
	border-radius: 0.65px;
}

/* ---------- the price at the bottom-left of the card ----------
   Was: title, price, labels, description, reading top to bottom, with the
   price directly under the dish name. Now the price is pinned to the bottom of
   the text column, on the same band as the Add button in the opposite corner,
   and the reading order is title, labels, description.

   The text column is already a flex column (`.orderable-product__content` and
   `.orderable-product__actions` are both `display: contents` further up, which
   promotes title, price, labels and description into one flex context), so
   pinning is `margin-top: auto` on the price -- no extra wrapper, no absolute
   positioning, and the card keeps growing with its content.

   `order: 6` rather than 5: the round + is 5, and although it is absolutely
   positioned and its order cannot matter, two items sharing an order value is
   the kind of thing that silently starts mattering after somebody removes a
   `position: absolute`.

   `padding-top` rather than a margin on the description: `margin-top: auto`
   eats all the free space, so a margin between them would be the FIRST thing
   to vanish on a card with no slack, which is exactly the card that needs it.
   Padding is inside the price's own box and cannot be absorbed, so there is
   always a gap, and the box's bottom edge still sits on the padding edge --
   which is what variant `a` below means by flush. */
.orderable-products-list--list .orderable-product__content-wrap {
	/* Named so the price variants can be written as an offset from the card's
	   own padding rather than as two more magic numbers. Same values as the
	   `padding: 14px 16px` further up, restated here as the shorthand reads
	   them so the two cannot drift. */
	--oo-cardpad-x: 16px;
	--oo-cardpad-y: 14px;
	padding: var(--oo-cardpad-y) var(--oo-cardpad-x);
}
.orderable-products-list--list .orderable-product__actions-price {
	order: 6;
	margin: auto 0 0;
	padding-top: 10px;
}

/* Variant A (the default, and the one left active): the price sits flush at
   the text column's padding edge -- 16px in from the card's left border, 14px
   up from its bottom -- so it lines up with the dish name and the description
   above it.

   Variant B: the price is pulled out to the same inset the Add button keeps
   from the photo's corner, so the two sit the same distance inside opposite
   corners of the same card and read as a pair. Written as the difference
   between the two insets rather than as a number, so changing either one keeps
   the pairing true.

   Both classes come from `inc/card-price-position.php`, on Orderable's own
   `orderable_main_class` filter -- the same seam the label treatments use. */
.oo-price-b .orderable-products-list--list .orderable-product__actions-price {
	margin-left: calc(var(--oo-btn-inset, 10px) - var(--oo-cardpad-x));
	margin-bottom: calc(var(--oo-btn-inset, 10px) - var(--oo-cardpad-y));
}

/* The carousel card, same idea, different card.

   Its text column has no padding at all -- the price already sits on the
   card's left edge, directly under the photo -- so there is nothing for the
   two variants to differ by and both render it identically. What the carousel
   DID need is the pinning: the content wrapper was a block, so a card whose
   neighbour carries a dietary label had its price 23px higher than the one
   beside it at 375px. Measured, not guessed.

   `height: 100%` is what gives the column something to distribute: the card is
   a stretched flex item, but a block child of it is only as tall as its own
   content and `margin-top: auto` has no free space to eat. */
.oo-carousel .orderable-product__content-wrap {
	display: flex;
	flex-direction: column;
	height: 100%;
}
/* The pin goes on `.orderable-product__actions`, not on the price inside it.
   In the carousel that wrapper is still a block (only the list layout promotes
   it with `display: contents`), so it -- not the price -- is the flex child
   with free space above it to claim. */
.oo-carousel .orderable-product__actions {
	margin-top: auto;
	padding-top: 6px;
}

/* ==========================================================================
   CARD POLISH 2, 10 September 2026 -- the allergen (i) given the Add button's
   treatment, and one hover/focus system for the four controls a customer meets
   on the menu page.

   Kept as one block at the end of the file, after every rule it restates, for
   the same reason the block above it is: the whole change reverts in one cut,
   and document order rather than a specificity race decides the winner.
   ========================================================================== */

/* ---------- the allergen (i) gets the Add button's treatment ----------
   It was a transparent 44px square holding the plugin's own black info glyph,
   so over a photograph it read as a ring around a dark blob rather than as a
   control. Now it is the same white 38px rounded square as the Add button in
   the opposite corner, with the same border, the same radius token and the
   same shadow -- two controls of the same kind, drawn the same way.

   WHY THE FILL IS THE BUTTON'S OWN background-color, AND WHY IT CARRIES
   !important (10 Sep, card polish 4). Two plugin rules claim this button's
   background with !important: nutritional-info.css ships
   `.orderable-button--icon { background: none !important }` and repeats it for
   hover, focus and active under a `body ` prefix, and Orderable's style
   settings print `.orderable-button:hover, :active, :focus { background: #000
   !important }` inline into every page head. An !important declaration is
   reachable only by another !important at higher specificity. The theme's
   standing rule is "one !important, and only because the plugin used it
   first"; this is that case, the same justification the `padding: 0
   !important` further up this file carries, and it is written on the two
   states the plugin made important rather than on all of them.

   TWO EARLIER PASSES AVOIDED THE !important AND EACH LEFT AN ARTEFACT ON THE
   CORNER ARCS. Card polish 2 painted the fill on a `::before` at `inset: 0`.
   That pseudo-element sits on the PADDING box -- one pixel smaller on every
   side than the border box -- while inheriting the button's 9px radius, which
   is 1px too round for it, so along each corner arc it pulled away from the
   border's inner edge and left the button's own background showing. On hover
   that background is the plugin's `#000`: measured at a 6x zoom, up to 3
   device pixels of pure black on all four corners, which read as a second
   outline.

   Card polish 3 replaced that with `box-shadow: inset 0 0 0 40px <fill>`,
   which IS clipped to the rounded padding box, so the geometry was right and
   the black arcs went. What an inset shadow cannot do is replace the
   background: it is painted ABOVE it. The button's background was still #000
   on hover, and every antialiased pixel along the padding box's corner arcs
   is a blend of the fill with whatever is underneath. Measured at a device
   pixel ratio of 2, hovering the list Add: a one-pixel band running 173,174,
   177 to 200,200,202 immediately inside a 230,232,235 border on a 243,243,244
   fill -- darker than the line and darker than the fill, and read as a gap
   between the two. That is the gap Ryan was still seeing.

   Setting the background REMOVES the layer instead of covering it. One paint,
   one box, one radius, in every state. `background-clip: border-box` is the
   initial value and is stated anyway, because it is the declaration that runs
   the fill out UNDER the 1px border: line and fill share an edge, so there is
   no seam left between them to antialias. The drop shadow keeps `box-shadow`
   to itself.

   The button's markup, its `title` ("See allergens information.") and its
   `data-orderable-trigger` are not touched, so the accessible name and the
   plugin's click handler are exactly as shipped. */
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	width: 38px;
	height: 38px;
	min-width: 38px;
	border-radius: var(--oo-btn-radius, 9px);
	border: 1px solid var(--oo-line, #e6e8eb);
	display: grid;
	place-items: center;
	background-color: var(--oo-ground, #fff) !important;
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
	/* `.orderable-button--icon` fades the whole button to 0.6 on hover, focus
	   and active. That is the plugin's own hover effect and it is not the one
	   this page uses; the tint below replaces it. Only `background` is
	   important in that rule, so this wins on specificity. */
	opacity: 1;
}
/* The carousel's Add button carries a slightly lighter shadow than the list
   card's; its (i) matches its own neighbour, not the other card's. */
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	box-shadow: 0 2px 8px rgba(0, 0, 0, .14);
}

/* ---------- one hover and focus system ----------
   Four controls, one behaviour: the fill shifts by a few percent and nothing
   else moves. The plugin's defaults were three different behaviours -- the Add
   button and the (i) inverted to solid black (`.orderable-button:hover`), the
   order bar flashed to white and wobbled (`.orderable-floating-cart__button
   :hover` plus a `wobble-hor-bottom` keyframe), and the service pills had no
   hover at all.

   `:focus` is overridden alongside `:hover` on the plugin's buttons and that is
   not belt-and-braces: `.orderable-button:focus` sets the same black
   background, so a keyboard user landing on Add would otherwise get the focus
   ring drawn around a button that had just inverted. */

/* Add button, list card and carousel: the same one-box treatment.

   The rest state is set further up the file, where the button already carries
   `background`, `border` and `box-shadow`. What it needs here is the drop
   shadow restated without an inset fill beside it, and `background-clip`
   stated on the same box, so the fill the customer sees at rest is the
   button's own background rather than a layer sitting on top of one.

   At rest nothing has to fight for it: no plugin rule sets a background on
   `.orderable-product__add-to-order`, so the white further up the file simply
   applies. Hover is different. Orderable's style settings print an inline
   stylesheet into the head:

       .orderable-button:hover, :active, :focus { background: #000000 !important }

   It is generated from the plugin's own button-colour setting, it is
   !important, and !important is reachable only by !important. The three ways
   past it were: change the plugin setting, which repaints every Orderable
   button on the site including the checkout and is not this task's to own;
   cover it, which is what the `::before` and then the inset shadow did and
   which left an artefact on the corner arcs both times (measured, see the note
   above the (i)); or match it. This matches it, at higher specificity, and it
   is the only !important this block adds.

   The button itself still has to be told to keep its border and its hidden
   label: `.orderable-button:hover` in the plugin's main stylesheet sets
   `border-color: #000`, `box-shadow: none` and `color: #fff`, and the last of
   those would take back the `color: transparent` that hides the plugin's own
   "Add" text behind the glyph. */
.orderable-products-list--list .orderable-product__add-to-order {
	border-color: var(--oo-line, #e6e8eb);
	background-color: var(--oo-ground, #fff);
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
}
.oo-carousel .orderable-product__add-to-order {
	border-color: var(--oo-line, #e6e8eb);
	background-color: var(--oo-ground, #fff);
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .14);
}

/* The tint. One declaration changes one colour on one box, because the fill is
   now the background the line is drawn around rather than a second thing laid
   over it. Add and (i) are listed side by side: they are the same control in
   two corners of the same card and there is no longer a reason for them to
   have separate rules. */
.orderable-products-list--list .orderable-product__add-to-order:hover,
.orderable-products-list--list .orderable-product__add-to-order:focus,
.orderable-products-list--list .orderable-product__add-to-order:active,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:hover,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:active {
	border-color: var(--oo-line, #e6e8eb);
	color: transparent;
	background-color: var(--oo-hover-tint, #f3f3f4) !important;
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
}
.oo-carousel .orderable-product__add-to-order:hover,
.oo-carousel .orderable-product__add-to-order:focus,
.oo-carousel .orderable-product__add-to-order:active,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:hover,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:active {
	border-color: var(--oo-line, #e6e8eb);
	color: transparent;
	background-color: var(--oo-hover-tint, #f3f3f4) !important;
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .14);
}

/* Sticky order bar.

   DELIBERATE DEPARTURE FROM THE BRIEF, which asked for the fill to darken by
   about 8%. It cannot. `--oo-brand` defaults to the ink (#16181c) so an
   unbranded clone is coherent, and 8% off a colour that is already 9% light is
   a change of two values per channel -- measurably a darken, visually nothing.
   Shipping it would have answered "view order turns white" with "view order
   has no hover", which is the complaint made about the service pills in the
   same sentence.

   So the fill moves 10% toward white instead. On the ink that is a visible
   lift to a charcoal and reads as the button responding; on a client site that
   sets `--oo-brand` to a real colour it is the same small, safe step, because
   `color-mix` works from whatever the token holds. A flat fallback precedes it
   for anything without `color-mix`, where the alternative is no hover at all.

   Nothing is said about the text, the shadow or the position, so the label
   stays white throughout and `animation: none` takes back the
   `wobble-hor-bottom` keyframe that `.orderable-floating-cart__button:hover`
   starts.

   It does NOT take back the same keyframe when the plugin's JavaScript writes
   it as an INLINE style, which it does once per add-to-cart as the "item
   added" bounce. An inline declaration outranks every stylesheet, so the only
   ways to stop that one are !important or touching the plugin's script, and
   neither is worth it: a one-shot bounce when something lands in the basket is
   feedback, not a hover state, and it is not what was complained about. Reload
   the page and the inline style is gone and this rule is what remains. */
.oo-bar .oo-bar__button:hover,
.oo-bar .oo-bar__button:focus {
	background-color: #2c2f35;
	background-color: color-mix(in srgb, var(--oo-brand, #16181c) 90%, #fff);
	animation: none;
}

/* Pickup / Delivery pills. The inactive segment tints; the active one is
   already the selected state and must not look like it is offering a change.

   THE LABEL DARKENS TOO, and that is an accessibility fix rather than a
   flourish. The inactive label is `--oo-muted` on the `--oo-sunk` track, which
   measures 4.55:1 -- five hundredths above the AA floor. Putting ANY tint
   under it fails: the 5% tint takes it to 4.36:1, and even a 2% one lands at
   4.44:1. There is no shade of grey that both reads as a hover and keeps the
   text legal, so the text moves instead, to the ink at 16.03:1. It also says
   the right thing: the segment you are pointing at is the one you are about to
   switch to. `color` joins the transition here for that reason, and only
   here. */
.oo-servicebar__btn:hover:not(.is-active) {
	background-color: var(--oo-hover-tint, #f3f3f4);
	color: var(--oo-ink, #16181c);
}

/* ---------- focus rings ----------
   2px ink at a 2px offset on all four. `outline: none` appears nowhere in this
   theme; where the plugin sets it, this takes it back.

   The order bar is the exception, and it is an exception about the mechanism,
   not the look: `.orderable-floating-cart__button` carries
   `outline: none !important`, which no amount of specificity reaches. Its ring
   is drawn as two stacked box-shadows instead -- 2px of the bar's own ground,
   then 2px of ink -- which is the same 2px ring at the same 2px offset, and
   the only declaration on that button the plugin has not already claimed. */
.orderable-products-list--list .orderable-product__add-to-order:focus-visible,
.oo-carousel .orderable-product__add-to-order:focus-visible,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus-visible,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus-visible,
.oo-servicebar__btn:focus-visible {
	outline: 2px solid var(--oo-focus-ring, #16181c);
	outline-offset: 2px;
}
.oo-bar .oo-bar__button:focus-visible {
	box-shadow: 0 0 0 2px var(--oo-ground, #fff), 0 0 0 4px var(--oo-focus-ring, #16181c);
}

/* ---------- transitions ----------
   Background only, and only where motion is welcome. The plugin puts
   `transition: all 150ms ease-out` on every one of these buttons, which is why
   the reduce branch exists: without it a reduced-motion visitor still gets the
   plugin's transition on everything it touches, including transform. */
@media (prefers-reduced-motion: no-preference) {
	/* background-color again, for every one of them. Card polish 2 and 3 had to
	   transition `box-shadow` here because the fill was a colour inside the
	   shadow list; card polish 4 put the fill back on the element's own
	   background, so the property that changes is the property that eases. */
	.orderable-products-list--list .orderable-product__add-to-order,
	.oo-carousel .orderable-product__add-to-order,
	.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
	.oo-carousel .orderable-product__hero .orderable-button--nutritional-info,
	.orderable-drawer .orderable-drawer__close {
		transition: background-color 130ms ease;
	}
	.oo-bar .oo-bar__button {
		transition: background-color 130ms ease;
	}
	.oo-servicebar__btn {
		transition: background-color 130ms ease, color 130ms ease;
	}
}
@media (prefers-reduced-motion: reduce) {
	.orderable-products-list--list .orderable-product__add-to-order,
	.oo-carousel .orderable-product__add-to-order,
	.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
	.oo-carousel .orderable-product__hero .orderable-button--nutritional-info,
	.orderable-drawer .orderable-drawer__close,
	.oo-bar .oo-bar__button,
	.oo-servicebar__btn {
		transition: none;
		animation: none;
	}
}

/* ==========================================================================
   CARD POLISH 3 -- the product drawer's header (10 September 2026)

   Ryan: "The slide out menu for the items has a strip at the spot that looks
   kind of bad because it's an overly zoomed in image of the food icon, and
   also the close button frequently has very bad contrast against it."

   WHAT THE PLUGIN DRAWS. `orderable/templates/product/options.php` is the
   drawer's body, and its first child is the shared hero template. Orderable
   styles that hero as `.orderable-product--image-cropped .orderable-product
   __hero { height: 72px }` with the image absolutely positioned across it at
   `object-fit: cover`. A square 300px photo forced into a 600 by 72 band is a
   12% horizontal slice through the middle of the dish, which is the strip: not
   a header, a texture. The close button is `position: absolute; top: 16px;
   left: 24px`, a 40px circle with a light grey border and two light grey bars,
   sitting on top of whatever that slice happens to be -- pale rice, dark
   sauce, a bright serape stripe -- so its contrast is luck.

   TWO TREATMENTS, SWITCHED BY A CLASS ON THE DRAWER ROOT. `oo-drawer-a` is
   active. See `inc/drawer-header.php` for the option and the filter, and
   `orderable/drawer/drawer.php` for where the class is printed.

   WHY A CLASS AND NOT TWO STYLESHEETS: the drawer is one element, reused for
   the product view and the cart view, and the treatments differ only in what
   is painted at the top of it. A class is the whole difference.

   NO OVERRIDE OF `product/options.php` OR `product/hero.php` WAS NEEDED, and
   that is deliberate. `hero.php` is shared with the menu card -- the same file
   draws the 92px thumbnail on every row -- so an override to change the drawer
   would have to branch on context inside a template, which is how a small
   change becomes a maintenance item on every plugin bump. Everything below is
   CSS on markup the plugin already ships. The only PHP is the root class and
   one image-size filter, both of which are the plugin's own extension points.

   The drawer is rendered outside `.orderable-main`, so the palette custom
   properties are out of scope in here and every colour carries its literal
   fallback -- the same thing the allergen block and the order sheet do further
   up. `--oo-hover-tint` and `--oo-focus-ring` are on `:root` and do reach.
   ========================================================================== */

/* ---------- shared: the close button becomes the Add button ----------
   Same 38px rounded square, same 1px line, same shadow, same one-box
   construction as the card's two controls, so there is one button shape on
   this site rather than three. It sits at the top LEFT at a 12px inset.

   WHY LEFT. Ryan, 10 September 2026: "Put the drawer close button on the left
   side." Card polish 3 had put it top right, reasoning that the panel slides
   in from the right so the close belongs on the edge it came from. He looked
   at it and wanted the left, which is also the side the plugin itself uses
   (`top: 16px; left: 24px`); only the inset differs, and it is the card's
   12px, so this button is placed the way the card's two controls are. One
   corner, one inset, every variant from a to d.

   WHAT HAS TO MAKE ROOM FOR IT. In `a` and `c` it lies over the photo band and
   nothing moves. In `b` and `d` the header row is white and shares that space,
   so the clearance that used to be a padding on the RIGHT of the title is now
   a padding on its LEFT, and in `d` the 72px thumbnail starts after the button
   rather than at the edge: close, then picture, then name. Both read
   `--oo-drawer-close-clear` below, so there is one number and it cannot drift.

   Contrast stops depending on the photo. The glyph is the ink on white at
   17.77:1 wherever the panel opens and whatever is behind it.

   The plugin's markup is untouched: `data-orderable-trigger="drawer.close"`
   still drives it and the visually hidden word "Close" is still its accessible
   name, so the button keeps working from the keyboard and from a screen
   reader exactly as shipped. */
/* The room the close button needs inside the drawer's own 24px padding:
   12px of inset, plus 38px of button, less the 24px `.orderable-drawer`
   already pads, plus 8px of air = 34px. It is the same 34px that used to sit
   on the right of b's title row; it has changed sides, not size. Declared on
   the drawer so b's padding and d's thumbnail read one value. */
.orderable-drawer {
	--oo-drawer-close-clear: 34px;
}
.orderable-drawer .orderable-drawer__close {
	top: 12px;
	left: 12px;
	right: auto;
	width: 38px;
	height: 38px;
	border: 1px solid var(--oo-line, #e6e8eb);
	border-radius: var(--oo-btn-radius, 9px);
	background-color: var(--oo-ground, #fff);
	background-clip: border-box;
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
}
/* No !important here, and that is not an oversight: the plugin's own rules for
   this button (`.orderable-button, .orderable-drawer__close { background: none
   }` and `.orderable-drawer__close:hover { background-color: #c7d4db }`) are
   ordinary declarations, so specificity is enough. The inline `#000
   !important` block is written against `.orderable-button`, a class this
   element does not carry. !important goes only where the plugin used it
   first. */
.orderable-drawer .orderable-drawer__close:hover,
.orderable-drawer .orderable-drawer__close:focus,
.orderable-drawer .orderable-drawer__close:active {
	border-color: var(--oo-line, #e6e8eb);
	background-color: var(--oo-hover-tint, #f3f3f4);
	box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
}
/* The X is the plus's twin: the same 24px mask box, the same 2px stroke, the
   same round caps, drawn from `--oo-icon-close`. The plugin builds its X from
   two 18px by 2px square-ended bars rotated 45 degrees on ::before and
   ::after; this takes ::before as the mask box, unrotates it, and drops
   ::after out of the drawing -- the same two-part swap the quantity roller's
   plus uses further up this file. */
.orderable-drawer .orderable-drawer__close::before {
	left: 50%;
	top: 50%;
	width: 24px;
	height: 24px;
	margin: -12px 0 0 -12px;
	transform: none;
	border-radius: 0;
	background-color: var(--oo-ink, #16181c);
	-webkit-mask: var(--oo-icon-close) center / 24px 24px no-repeat;
	        mask: var(--oo-icon-close) center / 24px 24px no-repeat;
}
.orderable-drawer .orderable-drawer__close::after {
	display: none;
}
/* The plugin repaints both bars on hover and focus. Only ::before survives,
   and it stays ink: the fill moves, the glyph does not, which is the same
   hover behaviour the card's controls have. */
.orderable-drawer .orderable-drawer__close:hover::before,
.orderable-drawer .orderable-drawer__close:focus::before,
.orderable-drawer .orderable-drawer__close:active::before {
	background-color: var(--oo-ink, #16181c);
}
.orderable-drawer .orderable-drawer__close:focus-visible {
	outline: 2px solid var(--oo-focus-ring, #16181c);
	outline-offset: 2px;
}

/* ---------- a: a photo header ----------
   The band gets a real aspect ratio instead of 72 fixed pixels, so the crop is
   a wide view of the dish rather than a slice through it. 16:9 rather than 3:2
   because the header does not scroll -- it is a flex item outside
   `.orderable-sb-container` -- so every pixel it takes is a pixel the options
   list does not get: at the 600px drawer 16:9 is 338px against 3:2's 400px.
   Change `--oo-drawer-hero` to `3 / 2` to see the other one; nothing else
   moves.

   `max-height: 40vh` is not a design choice, it is a floor under the worst
   case: a phone in landscape is 375 by 390, where an unbounded 16:9 header
   would take 211px of it and the options would open below the fold. It does
   nothing at any normal size.

   NO UPSCALING. The drawer is at most 600px wide (the plugin's own max-width)
   and `inc/drawer-header.php` serves the hero the `large` size, 1024px, in the
   drawer only. The photo is therefore always being scaled DOWN, at 1x and at
   2x, which is what stops a header at this size looking soft. The card's
   thumbnail is untouched and still 300px. */
.oo-drawer-a .orderable-product--options .orderable-product__hero {
	height: auto;
	aspect-ratio: var(--oo-drawer-hero, 16 / 9);
	max-height: 40vh;
}

/* ---------- b: no photo ----------
   The dish is named, described and priced in the drawer already; b is the
   treatment for a client whose photography is not good enough to open with, or
   a menu where the same photo has just been tapped on the card. The header
   becomes the title on white, sticky at the top of the scroller so it stays
   with the close button as the options scroll under it.

   `padding-left` clears the close button, which is top left; the number is
   `--oo-drawer-close-clear` above. Nothing sits at the right of the row any
   more, so the title reads at exactly the width it had when the clearance was
   on that side. Checked with a title long enough to wrap to three lines. */
.oo-drawer-b .orderable-product--options .orderable-product__hero {
	display: none;
}
.oo-drawer-b .orderable-product--options .orderable-product__title {
	position: sticky;
	top: 0;
	z-index: 2;
	margin: 0 0 16px !important;
	padding: 13px 0 13px var(--oo-drawer-close-clear, 34px);
	background: var(--oo-ground, #fff);
	border-bottom: 1px solid var(--oo-line, #e6e8eb);
}
/* On white the square reads as a floating chip, so b drops the fill, the line
   and the shadow and keeps the glyph. The hit area, the trigger and the
   accessible name are unchanged -- only the paint is.

   `d` is listed here too. It is b's white header row with a thumbnail added to
   the left of the title, so its close button sits on the same white and wants
   the same answer. */
.oo-drawer-b .orderable-drawer__close,
.oo-drawer-d .orderable-drawer__close {
	border-color: transparent;
	background-color: transparent;
	box-shadow: none;
}
.oo-drawer-b .orderable-drawer__close:hover,
.oo-drawer-b .orderable-drawer__close:focus,
.oo-drawer-b .orderable-drawer__close:active,
.oo-drawer-d .orderable-drawer__close:hover,
.oo-drawer-d .orderable-drawer__close:focus,
.oo-drawer-d .orderable-drawer__close:active {
	border-color: transparent;
	background-color: var(--oo-hover-tint, #f3f3f4);
	box-shadow: none;
}

/* ==========================================================================
   CARD POLISH 4 -- two shorter drawer headers (10 September 2026)

   Ryan, looking at a and b: "The B drawer looks better than A, but I do want
   some personality from an image. What about a middle ground image height that
   doesn't take up as much space."

   a and b stay. Two more treatments are added on the same root class, and the
   whole set is switched with `wp option update oo_drawer_variant <a|b|c|d>` or
   the `oo_drawer_variant` filter. See `inc/drawer-header.php`.

     a   16:9 photo band, 338px at the 600px drawer, 211px at 375.
     b   no photo at all; a sticky white header row carrying the dish name.
     c   a 3:1 photo band -- 125px at 375, 140px on desktop.  ACTIVE.
     d   no band; a 72px square thumbnail of the dish beside the title in b's
         header row. 92px of header, and still a picture.

   The drawer is rendered outside `.orderable-main`, so the palette custom
   properties are out of scope in here and every colour and radius carries its
   literal fallback -- the same thing the block above does.
   ========================================================================== */

/* ---------- c: a short photo band ----------
   The middle ground. a's band is the dish; c's band is a glimpse of it. The
   ratio does the work: at the phone's 375px drawer 3:1 is 125px, which is
   roughly a third of what a took (211px) and leaves the first option group
   above the fold on a 667px phone. `max-height` then stops the band growing
   with the drawer: at the 600px desktop panel 3:1 would be 200px, and 140px is
   the cap, so the crop widens to about 4.3:1 there rather than the header
   eating the options list. Both numbers are in the ratio and the cap; there is
   no third value anywhere.

   THIS IS NOT THE PLUGIN'S 72px STRIP COME BACK. The strip was 72px of a 300px
   image upscaled into a 600px box before anything cropped it -- a 12% slice
   through the middle of the dish at twice its natural size. c is 140px of an
   801px image scaling DOWN (`large`, from the filter in
   `inc/drawer-header.php`; the serape set's originals are 801px square, so
   `large` resolves to the full file), which is a 23% band, sharp at 1x and at
   2x, and it reads as food rather than as texture.

   `object-position` is stated rather than left to the initial value: the crop
   being centred on the middle of the photograph is the whole reason a 3:1 band
   of a square dish photo shows the dish, so it is a decision, not a default. */
.oo-drawer-c .orderable-product--options .orderable-product__hero {
	height: auto;
	aspect-ratio: var(--oo-drawer-band, 3 / 1);
	max-height: 140px;
}
.oo-drawer-c .orderable-product--options .orderable-product__image {
	object-position: center center;
}

/* ---------- d: a thumbnail in the header row ----------
   The least space that still carries a picture: b's white header row, with a
   72px square of the dish at the left of it and the title beside it. 92px of
   header against c's 140 and a's 338, and no band across the panel at all.

   HOW IT IS BUILT WITHOUT TOUCHING THE MARKUP. The plugin renders the hero as
   a SIBLING of the scroll container that holds the title
   (`.orderable-product__hero` then `.orderable-sb-container`), so there is no
   element that contains both and no way to put them in one row with flex or
   grid. `product/options.php` could be overridden to wrap them, and was not:
   it is a hundred lines of variation, addon and quantity logic that would then
   need re-checking on every plugin bump, to save one `position: absolute`.

   So the hero is lifted out of flow and pinned to the top of
   `.orderable-product--options` -- which is `position: relative` already, from
   the plugin's own `.orderable-product` rule -- one close-button clearance in
   from its left edge, and the title's header row is given a left padding that
   makes room for the button and the square together. Both read the same
   `--oo-drawer-thumb`, so the square and the gap beside it cannot drift apart,
   and the row's height is the square plus its padding top and bottom rather
   than a number of its own.

   Pinned rather than scrolled with the content on purpose: the header row is
   `position: sticky` at the top of the scroller in b, and a thumbnail that
   scrolled away while its title stayed would look broken. Absolute against the
   drawer and sticky against the scroller both mean "stays at the top", and the
   two edges start at the same place, so they stay together.

   `z-index: 3` puts the square above the header row's own white (which is 2),
   and above the options scrolling underneath it. The close button is the
   plugin's own `z-index: 100` and is unaffected. */
.oo-drawer-d {
	/* One square, named once: the hero reads it for its size, the title reads
	   it for the padding that clears it, and the row height is derived from
	   it. Change this and the row follows. */
	--oo-drawer-thumb: 72px;
	/* Air above and below the square, and between it and the title. */
	--oo-drawer-thumb-pad: 10px;
	--oo-drawer-thumb-gap: 14px;
}
.oo-drawer-d .orderable-product--options .orderable-product__hero {
	position: absolute;
	top: var(--oo-drawer-thumb-pad, 10px);
	/* Clear of the close button, which is top left from card polish 5. */
	left: var(--oo-drawer-close-clear, 34px);
	z-index: 3;
	margin: 0;
	width: var(--oo-drawer-thumb, 72px);
	height: var(--oo-drawer-thumb, 72px);
	/* The card's radius, not the button's: this is a picture of a dish at the
	   size the menu card draws one, so it is drawn the way the card draws it.
	   The drawer is outside `.orderable-main`, hence the literal fallback. */
	border-radius: var(--oo-radius, 14px);
	overflow: hidden;
}
/* b's header row, with room made at the left for the square. `min-height` is
   the square plus its padding, so the row is exactly as tall as its contents
   and the rule under it clears the bottom of the picture. `box-sizing` is
   stated because that arithmetic is only true on the border box. */
.oo-drawer-d .orderable-product--options .orderable-product__title {
	position: sticky;
	top: 0;
	z-index: 2;
	box-sizing: border-box;
	display: flex;
	align-items: center;
	min-height: calc(var(--oo-drawer-thumb, 72px) + 2 * var(--oo-drawer-thumb-pad, 10px));
	margin: 0 0 16px !important;
	/* Left: the close button's clearance, then the square, then the gap beside
	   it -- close, picture, name, each measured from the one before, so moving
	   any of the three moves the two after it. Right: nothing is over there any
	   more, and dropping the old 34px there gives the title back exactly the
	   width the clearance has taken from the left. */
	padding: var(--oo-drawer-thumb-pad, 10px) 0 var(--oo-drawer-thumb-pad, 10px)
	         calc(var(--oo-drawer-close-clear, 34px) + var(--oo-drawer-thumb, 72px) + var(--oo-drawer-thumb-gap, 14px));
	background: var(--oo-ground, #fff);
	border-bottom: 1px solid var(--oo-line, #e6e8eb);
}

/* ==========================================================================
   CARD POLISH 5 -- the drawer's Add button when a required group is
   unanswered (10 September 2026)

   Ryan: "Also hitting add here doesn't work, it got broken."

   IT IS NOT BROKEN, AND IT IS NOT THIS THEME'S. Measured on Regular Burrito
   at 375 and 1280, three states. With no meat chosen the Add button carries
   `disabled`, no `orderable_add_to_cart` request leaves the page, the cart
   does not move and the console is clean. With a meat chosen it adds at
   $12.50; with the cheese extra as well it adds at $12.99. Both send a 200
   and the cart total is right. `document.elementFromPoint()` at the button's
   centre returns the button itself in all three states, so nothing -- not the
   pinned header, not the sticky title row -- is over it. The same silent
   refusal reproduces on `/srv/sites/ordclone`, whose copy of this theme
   predates card polish 3 and 4 and carries none of these rules, so it is not
   a regression from the drawer work.

   WHAT ACTUALLY HAPPENS. "Choice of meat" is a REQUIRED addon group. Orderable
   Pro's `field_validator.validate()` runs on `orderable-drawer.opened`, finds
   it unanswered, adds `orderable-field--invalid` to the group and sets
   `disabled` on the drawer's Add button; the core script's
   `update_button_state()` then keeps it disabled for as long as an invalid
   field is in the drawer. That is the plugin refusing to add an incomplete
   item, and refusing is correct.

   THE FAULT IS THAT THE REFUSAL IS INVISIBLE. Neither plugin ships a rule for
   `:disabled` or for `.orderable-field--invalid`, and nothing is written into
   `.orderable-product__messages` -- the "please make a selection" string
   exists in `orderable_addons_pro_params.i18n` but only the single-product
   page's submit handler uses it. So the customer is looking at a button that
   is painted exactly like a working one, and a tap on it does nothing and says
   nothing. Verified as the plugin's and not ours by re-measuring with this
   theme's stylesheet disabled in the browser: identical computed values,
   `disabled` either way.

   THE FIX IS TO MAKE THE BUTTON TELL THE TRUTH. It reads the plugin's own
   state with the plugin's own selector, adds no markup and no script, and goes
   inert by itself the day Orderable styles `:disabled`.

   WHAT THIS DELIBERATELY DOES NOT DO IS PUT A MESSAGE IN THE DRAWER. A
   disabled control dispatches no pointer events at all, so a "choose an
   option" prompt on tap would mean the theme running its own copy of the
   plugin's validation against some other element -- a second validator to keep
   in step on every plugin bump, to say what the required group's own red
   asterisk already says. That is the fragile shape the standing instruction
   warns about. The prompt belongs in the plugin and is worth a vendor case;
   the honest button belongs here.
   ========================================================================== */

.orderable-drawer .orderable-product__add-to-order:disabled,
.orderable-drawer .orderable-product__update-cart-item:disabled {
	color: var(--oo-muted, #565c66);
	border-color: var(--oo-line, #e6e8eb);
	background-color: var(--oo-sunk, #f7f8f9);
	/* The same one-box construction as every other button on this page: the
	   fill IS the background and runs out under the 1px line, so there is no
	   second layer to leave a seam on the corner arcs. See the card polish 4
	   note above for the three passes that established this. */
	background-clip: border-box;
	box-shadow: none;
	cursor: not-allowed;
}
/* `:hover` still matches a disabled button -- the pointer is over it, and only
   activation is blocked -- so Orderable's inline
   `.orderable-button:hover, :active, :focus { background: #000 !important }`
   would invert it under the cursor and make it look live again at exactly the
   moment the customer is asking it to work. This is one more instance of the
   theme's standing rule: !important only on the property the plugin made
   important first, and only on the states it claimed. */
.orderable-drawer .orderable-product__add-to-order:disabled:hover,
.orderable-drawer .orderable-product__add-to-order:disabled:focus,
.orderable-drawer .orderable-product__add-to-order:disabled:active,
.orderable-drawer .orderable-product__update-cart-item:disabled:hover,
.orderable-drawer .orderable-product__update-cart-item:disabled:focus,
.orderable-drawer .orderable-product__update-cart-item:disabled:active {
	color: var(--oo-muted, #565c66);
	border-color: var(--oo-line, #e6e8eb);
	background-color: var(--oo-sunk, #f7f8f9) !important;
	box-shadow: none;
}

/* ==========================================================================
   CARD POLISH 5 -- the focus ring that appears on a mouse click
   (10 September 2026)

   Ryan's screenshot of the service pills showed a thick dark ring around the
   segment he had just tapped. It is not this theme's `:focus-visible` ring:
   that one is 2px of ink at a 2px offset, and the ring in the screenshot has
   no offset. Traced with `CSS.getMatchedStylesForNode` over CDP, because it
   appears in no stylesheet the page can read from script -- WordPress core
   ships

       :where(.wp-site-blocks :focus) { outline-width: 2px; outline-style: solid }

   with no colour, so it paints in `currentColor` on every focusable element
   inside the block wrapper the moment it takes focus, pointer or keyboard.
   Confirmed by measuring `document.activeElement` after a real mouse click:
   `:focus-visible` false, `:focus` true, outline solid 2px at offset 0.

   It reaches every control on the menu page, not only the pills, and it has
   been doing it since the theme was written -- the theme's own ring rules are
   all `:focus-visible` and none of them ever claimed the plain `:focus` state.
   The drawer and the sticky order bar are outside `.wp-site-blocks` and are
   not affected.

   THE RING IS SCOPED, NOT REMOVED. `outline: none` appears nowhere in this
   theme and does not appear here either. Instead the ring is declared on
   `:focus` in TRANSPARENT and given its colour back on `:focus-visible`, so
   the outline box exists in both states at the same width and the same offset
   -- nothing moves when it becomes visible -- and the only difference between
   a pointer focus and a keyboard focus is whether the ring is painted. A
   forced-colors user keeps a ring in both states, because forced-colors mode
   overrides a transparent outline with a system colour; that is a better
   outcome than `outline: none`, which it cannot bring back.

   The colour comes back from `--oo-focus-ring`, which is what the rules
   further up already use, so there is still one ring colour in this file. */
.orderable-products-list--list .orderable-product__add-to-order:focus,
.oo-carousel .orderable-product__add-to-order:focus,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus,
.oo-servicebar__btn:focus,
.oo-carnav__btn:focus {
	outline: 2px solid transparent;
	outline-offset: 2px;
}
.orderable-products-list--list .orderable-product__add-to-order:focus-visible,
.oo-carousel .orderable-product__add-to-order:focus-visible,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus-visible,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus-visible,
.oo-servicebar__btn:focus-visible,
.oo-carnav__btn:focus-visible {
	outline-color: var(--oo-focus-ring, #16181c);
}

/* ==========================================================================
   CARD POLISH 5 -- the drawer is drawn at the page's scale
   (10 September 2026)

   Ryan, on the Regular Burrito drawer at phone width: "The addon text is too
   big. In general everything looks zoomed in and scaled up, even the border
   width."

   ONE CAUSE FOR ALL OF IT, AND IT IS NOT A FONT-SIZE ANYWHERE IN THE DRAWER.
   Measured at 375: the document root is 16px, and `.orderable-drawer` computes
   to 18.2156px, which it inherits from `body` -- the theme's own body size.
   The menu page never notices, because every size on a card is written in REM
   and therefore reads the 16px root: the dish title is 1rem = 16px and the
   description 0.9rem = 14.4px. Nothing inside the drawer sets a size at all --
   Orderable's addons stylesheet has no `font-size` in it -- so every word in
   there inherits 18.2156px instead. That is the "zoomed in": the option rows
   and the helper text were rendering 26% larger than the description on the
   card behind them, from one inherited value.

   Measured, before and after, at 375:

     | element               | card         | drawer before | drawer after |
     |-----------------------|--------------|---------------|--------------|
     | dish / drawer title   | 16px  600    | 28.2px  400   | 20px  600    |
     | group heading         | --           | 18.2px  300   | 16px  600    |
     | helper text           | 14.4px (desc)| 18.2px  300   | 14.4px       |
     | option row label      | 14.4px (desc)| 18.2px        | 14.4px       |
     | fee pill              | --           | 18.2px        | 14px         |
     | price beside Add      | 18.2px  600  | 18.2px  300   | 18px  600    |
     | option group border   | 1px          | 2px           | 1px          |
     | row separator         | 1px          | 2px           | 1px          |
     | option row height     | --           | 53.5px        | ~46px        |

   THE FIX IS THE INHERITED VALUE, THEN THE SIZES THAT SHOULD NOT BE INHERITED.
   `font-size: 1rem` on the drawer root puts it on the page's scale in one
   declaration -- which is why the mini-cart in the same panel came down with
   it, correctly, since it was reading the same 18.2px. The rules after it size
   the things the plugin left to inherit, in the card's own steps rather than
   in new numbers: option rows and helper text at the card's description size,
   group headings one step above at the dish-title size, the drawer title below
   the category heading (1.25rem against the card's 1.4rem, because a drawer
   title names one dish where a category heading opens a section), and the
   price beside Add at the card's price size.

   NO FONT BELOW 14px AT PHONE WIDTH. That floor is why the fee pill is
   0.875rem rather than the 0.75rem the card's label line uses, and why the
   drawer's own label line moved from 0.8125rem to 0.875rem further up this
   file.

   THE BORDERS WERE REALLY 2px. Not a 1px line plus a 1px shadow: measured
   `border-width: 2px` on `.orderable-product-fields__field` and on every
   `.orderable-product-option`'s bottom rule, with `box-shadow: none` on both,
   which is Orderable's addons stylesheet shipping a 2px chrome around a
   control the rest of this page draws with a 1px hairline. The checkmark is
   the same story at 4px. All of them come to the page's weights here.

   WHY NOT A TEMPLATE OVERRIDE. Nothing below needs markup that does not exist
   -- every selector is the plugin's own -- so `product/options.php` stays
   unoverridden, for the reasons the card polish 3 block gives at length.
   ========================================================================== */

/* The one inherited value the whole drawer was scaling from. */
.orderable-drawer {
	font-size: 1rem;
}

/* The dish name. Below the card's category heading (1.4rem) on purpose: this
   names one dish, and at 375 a 1.4rem title on a wrapping name pushes the
   first option group off the screen. */
.orderable-drawer .orderable-product--options .orderable-product__title {
	font-size: 1.25rem;
	font-weight: 600;
	line-height: 1.3;
	color: var(--oo-ink, #16181c);
}

/* "Extras", "Choice of meat" -- one step above the rows they head, which puts
   them at the card's dish-title size. The plugin's own `line-height: 22px` is
   left alone; it is 1.375 at 16px, which is right for a heading. */
.orderable-drawer .orderable-product-fields__title {
	font-size: 1rem;
	font-weight: 600;
	color: var(--oo-ink, #16181c);
}

/* The sentence under a group heading is the same kind of text as the
   description on the card, so it is the same size and the same grey. */
.orderable-drawer .orderable-product-fields__description {
	font-size: 0.9rem;
	line-height: 1.4;
	color: var(--oo-muted, #565c66);
	margin: 0 0 10px;
}

/* The group's box and the rule between its rows: the page's hairline, at the
   page's weight and the page's colour. `--oo-line` does not reach the drawer,
   hence the literal fallback, the same as everywhere else in here. */
.orderable-drawer .orderable-product-fields__field {
	border: 1px solid var(--oo-line, #e6e8eb);
}
.orderable-drawer .orderable-product-option {
	font-size: 0.9rem;
	line-height: 1.4;
	color: var(--oo-ink, #16181c);
	padding: 10px 12px;
	border-bottom: 1px solid var(--oo-line, #e6e8eb);
}
.orderable-drawer .orderable-product-option:last-child {
	border-bottom: 0;
}

/* The "+ $1.00" pill. 14px is the floor rather than a chosen step: the card's
   label line is 0.75rem and that is 12px, which is too small to put on a
   phone. */
.orderable-drawer .orderable-product-option__label-fee {
	font-size: 0.875rem;
	padding: 3px 9px;
	border-radius: 7px;
	margin-left: 8px;
}

/* The tick is drawn out of two borders on a rotated box, so shrinking it means
   moving three numbers together: 16 by 10 on 4px borders becomes 12 by 7 on
   3px, which is the same drawing at three quarters and keeps the stroke in
   proportion to the arm. Its colours are the plugin's and stay. */
.orderable-drawer .orderable-product-option__label-state {
	height: 12px;
	width: 7px;
	border-width: 0 3px 3px 0;
	margin: 0 4px 0 10px;
}

/* The total beside the Add button, at the card's price size and weight. It is
   the number the customer checks before pressing, and at 300 weight in the
   inherited size it was both bigger and lighter than the price on the card it
   came from. */
.orderable-drawer .orderable-product__actions-price {
	font-size: 1.125rem;
	font-weight: 600;
	color: var(--oo-ink, #16181c);
}

/* The Add button's own label was 13.33px -- the browser's default button size,
   because neither plugin sets one -- under the 14px floor, and its border was
   the plugin's 2px against every other 1px line on the page. */
.orderable-drawer .orderable-product__actions-button .orderable-button {
	font-size: 0.9375rem;
	font-weight: 600;
	border-width: 1px;
}

/* ==========================================================================
   CARD POLISH 6 -- the allergen (i), four ways (11 September 2026)

   Ryan: "Let's do info button but experiment with different styles. I want it
   to seem less obtrusive and lower priority than the add button while still
   never getting blended into the picture."

   Card polish 4 made the (i) the Add button's twin: the same white 38px
   rounded square, the same border, the same shadow, in the opposite corner of
   the same photo. That fixed the contrast complaint and introduced this one --
   two identical controls on one card read as two equal offers, when one adds a
   dish to an order and the other answers a question about it.

   FOUR TREATMENTS, ONE CLASS. `inc/info-button-style.php` prints `oo-info` and
   `oo-info-a` .. `-d` on `.orderable-main` through Orderable's own
   `orderable_main_class` filter, the same seam the label treatment and the
   price variant already use. Switch with `wp option update oo_info_style b`.
   The Add button is not mentioned in any of them and does not move.

     a  28px rounded square, white, ink glyph, 1px line, NO shadow, top right
        of the photo at the inset it already had. Lower by being smaller and
        flatter: the shadow is what lifted it off the photo to Add's height.
        DEFAULT.
     b  30px, translucent white with a blur behind it, hairline. The frost is
        the point: it takes its colour from the photo underneath instead of
        punching a white hole in it, and still carries the glyph.
     c  30px, white, a smaller version of Add's shadow, moved to the BOTTOM
        LEFT so the two controls mirror each other across the photo and the top
        of the frame stays clean.
     d  glyph only. A 22px ink disc on a 26px white circle with no line and no
        shadow, 8px in from the top right corner. The white is a 2px halo
        rather than a button, which is the smallest thing that still cannot
        disappear into a photograph.

   WHAT NONE OF THEM TOUCH. The markup, the `title` ("See nutritional
   information."), `data-orderable-trigger` and `data-orderable-focus` are the
   plugin's and are not filtered, so the accessible name and the click handler
   are exactly as shipped in all four. Each is a size, a corner and a fill.

   THE GLYPH IS NOT A COLOUR WE SET, and that is worth saying because the
   contrast numbers look too good. `circle-information.svg` is a SOLID disc
   with the "i" knocked out of it, filled with `--oo-ink` a few hundred lines
   up, so "glyph on fill" is the ink disc on the button's own fill: 15.9:1 on
   white. Shrinking the button shrinks the disc, never the contrast.

   WHY THE CAROUSEL SELECTORS ARE COMPOUND, `.oo-info-a.oo-carousel`, WITH NO
   SPACE. `oo-carousel` is put on the wrapper by a second `orderable_main_class`
   filter in functions.php, so on the "Most ordered" row both classes are on the
   SAME element and a descendant combinator matches nothing. Written with a
   space it linted, deployed and styled the list card correctly while the
   carousel's (i) silently stayed at card polish 4's 38px square -- caught by
   measuring the carousel button, not by reading this file.

   THE HOVER TINT AND THE FOCUS RING ARE THE SAME ONES. Both systems live
   further up this file and neither is scoped to a treatment. The tint is
   restated at the end of this block only because the rest states here carry
   `!important` (the plugin claims this button's background with it) and a
   hover has to outrank a rest state of the same weight.
   ========================================================================== */

/* ---------- a: smaller and flat ----------
   7px rather than the 9px token: 9px on a 28px box is noticeably rounder than
   9px on a 38px one, and what is being kept is the shape, not the number. */
.oo-info-a .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-info-a.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	width: 28px;
	height: 28px;
	min-width: 28px;
	border-radius: 7px;
	box-shadow: none;
	top: 10px;
	right: 10px;
}
.oo-info-a .orderable-product__hero .orderable-button--nutritional-info svg {
	width: 15px;
	height: 15px;
}

/* ---------- b: frosted ----------
   The opaque white is declared FIRST and is what a browser without
   `backdrop-filter` renders -- a solid 30px chip, which is treatment a one size
   up rather than a broken b. The frost is added in an `@supports` block on top
   of it, so the fallback is the default rather than the patch.

   The line goes from `--oo-line` to a 12% ink hairline for the same reason the
   fill does: a solid grey border around a translucent fill is the one edge that
   would still read as a white block. */
.oo-info-b .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-info-b.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	width: 30px;
	height: 30px;
	min-width: 30px;
	border-radius: 8px;
	border: 1px solid rgba(22, 24, 28, .12);
	box-shadow: none;
	top: 10px;
	right: 10px;
}
@supports ((-webkit-backdrop-filter: blur(6px)) or (backdrop-filter: blur(6px))) {
	.oo-info-b .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
	.oo-info-b.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
		background-color: rgba(255, 255, 255, .72) !important;
		-webkit-backdrop-filter: blur(6px);
		        backdrop-filter: blur(6px);
	}
}
.oo-info-b .orderable-product__hero .orderable-button--nutritional-info svg {
	width: 16px;
	height: 16px;
}

/* ---------- c: bottom left, mirroring Add ----------
   `right: auto` is not tidiness. The plugin pins this button with
   `bottom: 10px; right: 10px` and the rule a few hundred lines up answers with
   `top: 10px; right: 10px`; setting `left` without unsetting `right` leaves an
   absolutely positioned element with both insets and a width, which is
   over-constrained -- the browser keeps `left` in a left-to-right document and
   the button lands correctly by luck, and lands on the right in an RTL one.
   `top: auto` unsets the same way for the same reason.

   The shadow is Add's, one step down: 1px of offset and 5px of blur against
   2px and 8px, at a lighter alpha. It has to lift off a photograph without
   claiming to be the same control. */
.oo-info-c .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-info-c.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	width: 30px;
	height: 30px;
	min-width: 30px;
	border-radius: 8px;
	box-shadow: 0 1px 5px rgba(0, 0, 0, .14);
	top: auto;
	right: auto;
	bottom: 10px;
	left: 10px;
}
.oo-info-c .orderable-product__hero .orderable-button--nutritional-info svg {
	width: 16px;
	height: 16px;
}

/* ---------- d: the glyph, and 2px of white ----------
   `border: 0` rather than a transparent border: the base rule's 1px line is
   part of the box, and keeping it transparent would shrink the white ring to
   1px on a border-box element. The circle is 2px wider than the disc all round
   and nothing else.

   The honest cost of d is the hover: the tint changes a 2px ring, which is
   nearly nothing to look at. It is still there, it is still the same colour as
   every other control's, and the focus ring is unaffected -- an outline is
   drawn outside the box at a 2px offset and does not care how small the box
   is. */
.oo-info-d .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info,
.oo-info-d.oo-carousel .orderable-product__hero .orderable-button--nutritional-info {
	width: 26px;
	height: 26px;
	min-width: 26px;
	border: 0;
	border-radius: 50%;
	box-shadow: none;
	top: 8px;
	right: 8px;
}
.oo-info-d .orderable-product__hero .orderable-button--nutritional-info svg {
	width: 22px;
	height: 22px;
}

/* ---------- the hover tint, restated at this block's weight ----------
   The rest states above carry `background-color: ... !important`, because
   `.orderable-button--icon { background: none !important }` is the plugin's and
   an !important is reachable only by another one. That puts every treatment's
   rest state at the same weight as the tint rule further up the file and one
   class ahead of it on specificity, so without this the fill would stop
   responding to hover the moment a treatment class was on the wrapper. The
   `.oo-info` marker exists for exactly this: one selector covers all four. */
.oo-info .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:hover,
.oo-info .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus,
.oo-info .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:active,
.oo-info.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:hover,
.oo-info.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus,
.oo-info.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:active {
	background-color: var(--oo-hover-tint, #f3f3f4) !important;
	background-clip: border-box;
}
/* b keeps its frost on hover. An opaque tint would be the only state in which
   the frosted button is a white chip, which is the one thing b exists not to
   be, so the tint is mixed into the translucent fill instead: the same colour,
   the same small step, a little more opaque so the press registers.

   Inside the SAME `@supports` as b's rest state, so the two never disagree: a
   browser with no `backdrop-filter` renders an opaque chip at rest and takes
   the opaque tint above on hover, and a browser with it is translucent in both
   states. Splitting them would have given the fallback a fill that turned
   see-through when you pointed at it. */
@supports ((-webkit-backdrop-filter: blur(6px)) or (backdrop-filter: blur(6px))) {
	.oo-info-b .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:hover,
	.oo-info-b .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:focus,
	.oo-info-b .orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info:active,
	.oo-info-b.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:hover,
	.oo-info-b.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:focus,
	.oo-info-b.oo-carousel .orderable-product__hero .orderable-button--nutritional-info:active {
		background-color: rgba(243, 243, 244, .82) !important;
	}
}

/* ==========================================================================
   CARD INTERACTION -- the card opens the drawer, the clamp says so, and the
   spinner stops disappearing (11 September 2026)

   Ryan: "Hovering on an item gives a drop shadow which kind of implies you can
   click it but you can't. Need to resolve this. Also on mobile I notice the
   description gets truncated, and I feel like I should be able to click
   something to see the full description. Also the add button icon turns white
   when you press it, blending into background."

   The first two are answered together, because they are the same answer: the
   card opens the product drawer, so the hover shadow is now telling the truth
   and the drawer is where the full description lives. The mechanism is
   Orderable's own Clickable Card setting, filled in from
   `inc/card-trigger.php`; the plugin's stylesheet already sets `cursor:
   pointer` on `.orderable-product--view-product`, so nothing here has to.

   The third is a different bug from the one it looked like, and the note over
   the spinner rule says what it actually was.
   ========================================================================== */

/* ---------- the dish name as the keyboard route ----------
   A real <button> inside the <h3>, printed by `oo_card_title_trigger()`. It
   has to look like the heading it is inside, so every box the user agent puts
   on a button is unset and the type comes from the parent. `display: block`
   and `width: 100%` rather than `inline`: the h3 is a flex item in the list
   card's text column and an inline button would leave the rest of the row
   inert for no visible reason -- this way the whole line of the title is the
   control, which is also a larger touch target.

   `text-align: left` is stated because a button's default is `center` and it
   would silently centre every dish name the moment the wrapper appeared.

   The focus ring is the same 2px ink at a 2px offset that every other control
   on this page has, from `--oo-focus-ring`. `outline: none` appears nowhere. */
.orderable-product__title .oo-card-open {
	display: block;
	width: 100%;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 2px;
	background: none;
	box-shadow: none;
	font: inherit;
	color: inherit;
	letter-spacing: inherit;
	text-align: left;
	cursor: pointer;
}
.orderable-product__title .oo-card-open:hover,
.orderable-product__title .oo-card-open:focus,
.orderable-product__title .oo-card-open:active {
	background: none;
	color: inherit;
	box-shadow: none;
}
.orderable-product__title .oo-card-open:focus-visible {
	outline: 2px solid var(--oo-focus-ring, #16181c);
	outline-offset: 2px;
}

/* ---------- "more" on a description that is actually clamped ----------
   The clamp itself is UNCHANGED: still `-webkit-line-clamp`, still 2 lines on
   a phone and 3 from 700px up, so the rows stay even and nothing about the
   card's height moves. All that is added is a word at the end of the last
   line, and only on the descriptions the browser really did cut -- 38 of the
   48 dishes at 375px, measured, and none of the other ten.

   NO ELLIPSIS-PLUS-MORE DOUBLING. `-webkit-line-clamp` puts its own "..." at
   the end of the last line, and the last line of clamped text is a full line,
   so that ellipsis is always at the right-hand edge. The word sits in that
   corner on a short left-to-right fade of the card's own ground, which covers
   the ellipsis rather than following it. The reader sees "...text more" once,
   not twice.

   ABSOLUTELY POSITIONED, WHICH IS ALSO WHY THE MEASUREMENT IS SAFE. Out of
   flow means it is not part of any line box, so a `-webkit-box` does not clamp
   it and it adds nothing to the `scrollHeight` the script compares against
   `clientHeight`. The thing being revealed cannot change the test that reveals
   it.

   IT IS NOT A SEPARATE TRIGGER. The span is inside the card, the card carries
   `data-orderable-trigger="view-product"`, and Orderable's delegated dispatcher
   matches the nearest ancestor with a trigger -- so a tap on the word opens the
   same drawer the card opens, with no handler and no second control. It is
   `aria-hidden` for the reason set out over `oo_card_more_affordance()`.

   The fade is 26px of the card's ground, which is `--oo-ground` -- measured on
   the live card, the description is painted by `.orderable-product` at #fff.
   The literal fallback is there because this file's rules run outside
   `.orderable-main` in some contexts and a missing custom property would make
   the gradient transparent, showing the ellipsis through it. */
.orderable-products-list--list .orderable-product__description {
	position: relative;
}
.orderable-product__description .oo-more {
	display: none;
}
.orderable-products-list--list .orderable-product__description.oo-desc--clamped .oo-more {
	display: block;
	position: absolute;
	right: 0;
	bottom: 0;
	padding-left: 26px;
	color: var(--oo-muted, #565c66);
	/* Underlined, because "more" in body copy at body colour reads as the last
	   word of the sentence. This is the same weight of hint the allergen link
	   uses, and it is the only thing that says the word is a control rather
	   than the text running out. */
	text-decoration: underline;
	text-underline-offset: 2px;
	background: linear-gradient(to right, rgba(255, 255, 255, 0), var(--oo-ground, #fff) 26px);
	cursor: pointer;
	pointer-events: none;
}
/* The card is the control; the word is paint on it. `pointer-events: none`
   above lets the tap land on the card itself, so there is exactly one click
   target and no chance of the word swallowing a press that should have opened
   the drawer. The cursor still comes from the card, which the plugin already
   sets to `pointer`. */

/* ---------- the Add button's press state ----------
   WHAT RYAN SAW IS REAL AND IT IS NOT THE RULE IT LOOKED LIKE. The diagnosis
   this started from was that the plugin's `:active` sets `color` while our
   hover rule only sets the background. Measured across rest, hover,
   mouse-down and focus, at 375 and 1280, on the list Add, the carousel Add,
   the (i) and the mini-cart roller: the glyph is rgb(22,24,28) in every one of
   those states, because the plus is painted by a `::after` that sets its own
   `color` and takes `currentColor` from that. The button's `color` never
   reaches it.

   WHAT ACTUALLY TURNS WHITE IS THE SPINNER, in the instant after the press.
   Orderable adds `orderable-button--loading` on click and draws a spinner as
   the same `::after`, two 2px borders on a rotating box. Then:

       .orderable-button--loading:active::after,
       .orderable-button--loading:focus::after,
       .orderable-button--loading.orderable-button--hover::after,
       .orderable-button--loading:hover::after {
           border-top-color: #fff; border-left-color: #fff;
       }

   That rule is correct for the plugin's own button, whose fill goes to the
   brand colour on those states. Ours does not: card polish 4 put the press
   fill at `--oo-hover-tint` #f3f3f4, so the spinner is white on near-white.
   Measured during the add: border-top-color rgb(255,255,255) on a background
   of rgb(243,243,244), which is 1.05:1 -- invisible. You press Add, the plus
   vanishes, nothing appears in its place, and about a second later the plus is
   back. "Turns white when you press it, blending into background" is an exact
   description of it.

   So the fix is to set the spinner's colour on the same four states the plugin
   sets it on, at higher specificity. No `!important`: the plugin's rule does
   not use one. `--oo-ink` on #f3f3f4 is 15.1:1.

   The drawer's Add is deliberately NOT in this list. It is a filled button
   whose fill really does go to black on press, so the plugin's white spinner
   is right there and taking it to ink would hide it. */
.orderable-products-list--list .orderable-product__add-to-order.orderable-button--loading:hover::after,
.orderable-products-list--list .orderable-product__add-to-order.orderable-button--loading:focus::after,
.orderable-products-list--list .orderable-product__add-to-order.orderable-button--loading:active::after,
.orderable-products-list--list .orderable-product__add-to-order.orderable-button--loading.orderable-button--hover::after,
.oo-carousel .orderable-product__add-to-order.orderable-button--loading:hover::after,
.oo-carousel .orderable-product__add-to-order.orderable-button--loading:focus::after,
.oo-carousel .orderable-product__add-to-order.orderable-button--loading:active::after,
.oo-carousel .orderable-product__add-to-order.orderable-button--loading.orderable-button--hover::after,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:hover::after,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:focus::after,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:active::after,
.orderable-products-list--list .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading.orderable-button--hover::after,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:hover::after,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:focus::after,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading:active::after,
.oo-carousel .orderable-product__hero .orderable-button--nutritional-info.orderable-button--loading.orderable-button--hover::after {
	border-top-color: var(--oo-ink, #16181c);
	border-left-color: var(--oo-ink, #16181c);
}
/* The glyph's own colour, stated on the press and focus states rather than
   left to the `::after` rule further up the file. It is already ink there and
   measured ink here, so this changes nothing today -- it exists so that the
   next person who reads the plugin's `color: #fff` and wonders whether it
   reaches the plus can see the answer written down beside the fill it belongs
   with, instead of deducing it from two rules 1200 lines apart. */
.orderable-products-list--list .orderable-product__add-to-order:hover::after,
.orderable-products-list--list .orderable-product__add-to-order:focus::after,
.orderable-products-list--list .orderable-product__add-to-order:active::after,
.oo-carousel .orderable-product__add-to-order:hover::after,
.oo-carousel .orderable-product__add-to-order:focus::after,
.oo-carousel .orderable-product__add-to-order:active::after {
	color: var(--oo-ink, #16181c);
}

/* ---------- the mini-cart roller inside the drawer ----------
   Not the card's roller: the card's quantity roller is off on this menu
   (`quantity_roller` is false and nothing turns it on), so the only roller a
   customer meets is the one on each line of the drawer's mini-cart. Measured
   there at rest, hover and press: bars rgb(64,87,99) on rgb(199,212,219),
   3.35:1 -- the plugin's own pair, readable, and it does not go white. Nothing
   is changed for it, and this note exists so the next person does not go
   looking for a bug that is not there. */

/* ---------- the drawer's description ----------
   Printed by `oo_drawer_description()` on the plugin's own
   `orderable_side_menu_before_product_options_wrapper`, at priority 5 so it
   sits above the label line Pro registers at 10.

   No clamp, deliberately: the card clamps and the drawer is where the clamp is
   answered. It takes the same 0.9rem the card's description takes and the same
   `--oo-muted`, so the sentence the customer was reading is the same sentence
   at the same weight, just complete. The drawer is outside `.orderable-main`,
   hence the literal fallback. */
.orderable-drawer .oo-drawer-description {
	margin: 0 0 12px;
	font-size: 0.9rem;
	line-height: 1.5;
	color: var(--oo-muted, #565c66);
}

/* ==========================================================================
   MINIMAL CHROME -- the one row that stands in for a theme header
   (11 September 2026)

   Ryan: "I want a minimal header for demonstration purposes now, and I want to
   use the header that the actual client site uses when integrating our order
   system in production."

   NOTHING HERE HIDES ANYTHING. The theme's header, navigation and footer are
   already gone by the time this file is read, taken off in PHP through
   GeneratePress's own `remove_action()` calls and its `generate_navigation_
   location` filter -- see `inc/chrome.php`. These rules only paint the row that
   `oo_chrome_bar()` prints in their place. If a rule below looks like it is
   compensating for something still in the page, it is not: check the PHP.

   EVERY SELECTOR IS UNDER `.oo-chrome-minimal`, the body class the same file
   adds. Under the `theme` setting no bar is printed and no rule here matches,
   so a client site carries these bytes and none of their effect.

   THE ROW IS THREE THINGS WITH ONE RULE BETWEEN THEM: the restaurant's name,
   the address-and-open-state line, and the way back into the order. On a phone
   the meta line wraps to its own row rather than being truncated, because the
   opening state is the one thing on it worth reading and an ellipsis in the
   middle of "Open now until 9:00 pm" says nothing. */

.oo-chrome-minimal .oo-chrome {
	background: var(--oo-ground, #fff);
	border-bottom: 1px solid var(--oo-line, #e6e8eb);
}

.oo-chrome__inner {
	/* THE BAR'S CONTENT SITS ON THE PAGE'S OWN LEFT EDGE, not on a margin of our
	   invention. Two numbers do that, and both are GeneratePress's rather than
	   ours:

	     --oo-chrome-max     the container width from GP's own settings. This one
	                         really is a GP variable -- `generate_get_option(
	                         'container_width' )`, 1100 on this build -- so
	                         `inc/chrome.php` reads it and prints it on the
	                         element. A client who widens their container widens
	                         this with it and nothing here needs editing.
	     --oo-chrome-gutter  `.inside-article`'s horizontal padding, which GP has
	                         no option for on this build. Measured instead: 30px
	                         below GP's 768px breakpoint and 40px from it up.

	   Checked rather than assumed: the bar's first character and the search
	   field's left edge both land on 210px at 1440, 130px at 1280, 40px at 1024
	   and 30px at 375. */
	max-width: var(--oo-chrome-max, 1100px);
	/* Explicit, because the width above is GeneratePress's CONTAINER width and
	   GP's own containers count their padding inside it. Left to inherit, the
	   bar came out 1180px against the page's 1100px and every element in it sat
	   40px to the left of the page below -- measured before this line. */
	box-sizing: border-box;
	margin: 0 auto;
	padding: 12px var(--oo-chrome-gutter, 30px);
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 6px 18px;
}

.oo-chrome__name {
	font-size: 1.15rem;
	font-weight: 600;
	line-height: 1.2;
	color: var(--oo-ink, #16181c);
	text-decoration: none;
	margin-right: auto;
}

.oo-chrome__name:hover,
.oo-chrome__name:focus {
	color: var(--oo-brand, var(--oo-ink, #16181c));
}

.oo-chrome__name:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 3px;
	border-radius: 2px;
}

/* The meta line arrives as the same `.oo-storemeta` markup the menu page used
   to carry in its content, so it is already styled. Only its margins change:
   inside a flex row a paragraph's default block margin would push the row
   taller than the text in it. */
.oo-chrome .oo-storemeta {
	margin: 0;
	order: 3;
	flex-basis: 100%;
}

/* On a phone the address and the open state each take a line of their own, and
   the middle dot between them then lands on the end of the address with nothing
   after it -- a separator separating one thing. It is removed at the widths
   where the line wraps rather than hidden at every width, because from 700px up
   the two really are side by side and the dot is doing its job. */
@media (max-width: 699px) {
	.oo-chrome .oo-storemeta { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
	.oo-chrome .oo-storemeta__sep { display: none; }
}

.oo-chrome__cart {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	min-height: 38px;
	padding: 0 16px;
	border: 1px solid var(--oo-line, #e6e8eb);
	border-radius: 999px;
	background: var(--oo-ground, #fff);
	color: var(--oo-ink, #16181c);
	font-size: 0.92rem;
	font-weight: 500;
	line-height: 1;
	cursor: pointer;
}

.oo-chrome__cart:hover,
.oo-chrome__cart:focus {
	background: var(--oo-hover-tint, #f3f3f4);
}

.oo-chrome__cart:focus-visible {
	outline: 2px solid var(--oo-ink, #16181c);
	outline-offset: 2px;
}

.oo-chrome__cart svg { display: block; flex: none; }

@media (min-width: 768px) {
	/* GeneratePress's own mobile breakpoint, where `.inside-article` goes from
	   30px of side padding to 40px. */
	.oo-chrome__inner { --oo-chrome-gutter: 40px; }
}

@media (min-width: 700px) {
	/* Room for all three on one line. The meta line goes back into the middle
	   and stops claiming a row of its own. */
	.oo-chrome .oo-storemeta {
		order: 0;
		flex-basis: auto;
	}

	.oo-chrome__name { margin-right: 0; }
	.oo-chrome__cart { margin-left: auto; }
}
