/* store.css — the customer screens (1..6), inside the WhatsApp WebView.
 *
 * Rules this file is held to (DESIGN.md §1, SPEC §7):
 *   * zero external requests — no CDN, no Google Fonts, no icon font. Cairo is
 *     self-hosted and declared in fonts.css; the only glyphs that are not text
 *     are inline <svg> in the views.
 *   * html{font-size:16px} is the density knob and does not move.
 *   * no dark mode: no prefers-color-scheme, no [data-theme], anywhere.
 *   * PHYSICAL RTL — right/left, margin-right, text-align:right. Not logical
 *     properties: the product is RTL and only RTL, and logical properties would
 *     make a stray dir="ltr" silently mirror a screen.
 *   * grid columns are minmax(0,1fr) so a long Arabic word cannot widen a track
 *     and push the page into a horizontal scroll.
 *   * every tappable thing is at least 44px tall.
 *   * NO inline style attributes and NO inline <script> anywhere in the store —
 *     the CSP has neither unsafe-inline nor unsafe-eval.
 *
 * Colour contrast, measured in the browser, not assumed:
 *   #fff on #0369a1 = 5.93:1   THE SOLID BUTTON (.s-btn).
 *   #fff on #0284c7 = 4.10:1   FAILS at this size. RENDER-2: this file carried
 *                               #0284c7 as .s-btn's background and justified it
 *                               in this very comment as "AA for large text".
 *                               WCAG large scale is >=24px, or >=18.66px when
 *                               bold; .s-btn is 1rem/700 = 16px bold, which is
 *                               NORMAL text, so the floor is 4.5:1 and 4.10
 *                               missed it — on the confirm the order, send the
 *                               rating and send the complaint buttons, the three
 *                               highest-stakes controls the customer ever taps.
 *   #fff on #0EA5E9 = 2.77:1   FAILS — which is why #0EA5E9 is never a button
 *                               background here, only a border and a tint.
 *   #0c4a6e on #e0f2fe = 8.25:1  (selected card)
 *   #64748b on #fff = 4.76:1     (muted text; 4.55:1 on the #f8fafc ground,
 *                                 which is the ground screens 7 and 9 sit on)
 *
 * RENDER-13 added these, every one read off the live page with
 * getComputedStyle, not computed from the hex by hand:
 *   #fff on #0369a1 = 5.93:1     .s-add, the add-to-cart button — 351 of them on
 *                                the Tagamoa menu. It was #0284c7 = 4.10:1.
 *   #475569 on #f8fafc = 7.24:1  the name of a dish this branch does not sell.
 *                                It was #94a3b8 = 2.45:1.
 *   #475569 on #f1f5f9 = 6.92:1  its «معاه» chip. It was #94a3b8 = 2.34:1.
 *   #475569 on #e2e8f0 = 6.15:1  .s-btn[disabled]. A second declaration of that
 *                                selector 370 lines below the first was painting
 *                                #f8fafc on #cbd5e1 = 1.42:1.
 *   #0369a1 on #f8fafc = 5.67:1  .s-help-link.
 */

/* overscroll-behavior: none (r3 D, 28/9 — «لما بسحب الصفحه كلها لتحت او لفوق بتطلع معايا وبيظهر فراغ دي نعىف نلغيها زي
   ما قفلت الزووم»): pulling the page past its top or bottom does not drag it and show empty space; the customer's
   store only (this file) — /admin keeps its own. */
html { font-size: 16px; -webkit-text-size-adjust: 100%; touch-action: manipulation; overscroll-behavior: none; }

body {
  overscroll-behavior: none;
  margin: 0;
  background: #f8fafc;
  color: #111827;
  font-family: 'Cairo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  text-align: right;
  overflow-x: hidden;
}

/* 1rem = 16px, and never less on a field: iOS zooms the page into a field under 16px when it takes focus
   (no zoom, 28/9 — layouts/store.html.erb names the four pieces). */
button, input, select, textarea { font-family: inherit; font-size: 1rem; color: inherit; }
* { box-sizing: border-box; }

.s-shell { max-width: 30rem; margin: 0 auto; min-height: 100vh; }

/* SHARP CORNERS — the store's one radius rule (Mohamed 30/9: «أيوه لأنه أشيك بكتير»; like the ads and the
   staff screens). Every corner in the shell is 0 whatever a rule below says, in this file, store_home.css,
   store_options.css and store_ltr.css alike, so a new component is born sharp and the radii written below
   are inert. Round stays only where round is the meaning, and those two keep their own rules:
     .s-track-dot  the tracking timeline's status dot;
     .s-mark       the radio circle of a one-of-many choice (a square one reads as a checkbox);
     .sc-round     «شات المتجر»'s launcher, its pulse ring and its «1» (point 40, Mohamed 2/10 21:54: a floating
                   round icon) — it rides on the cart bar, which is inside the shell (store_chat.css).
   Chained :not()s, not :not(a, b): an older Android WebView has only the one-selector form.
   admin.css carries the same rule for its copy of the customer's card (.ws-ip-store). */
.s-shell *:not(.s-track-dot):not(.s-mark):not(.sc-round),
.s-shell *:not(.sc-round)::before,
.s-shell *::after { border-radius: 0 !important; }

/* ── header ───────────────────────────────────────────────────────── */
.s-head {
  position: sticky;
  top: 0;
  z-index: 5;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
  padding: 0.625rem 1rem calc(0.5rem + env(safe-area-inset-top, 0px));
}
.s-head-row { display: flex; align-items: center; gap: 0.75rem; }
.s-head-id { flex: 1 1 auto; min-width: 0; }
.s-head-name { font-size: 1rem; font-weight: 700; color: #111827; }
.s-head-sub { font-size: 0.75rem; font-weight: 600; color: #64748b; }
/* THE BRANCH LINE IS ONE LINE (Mohamed 30/9 09:16). A long branch name — AlBaik's «دمياط الجديدة — شارع الصعيدي
   القديم», 35 characters — wrapped this line at 375px, the header grew 77.78 → 96.97px and the menu's sticky
   tabs (4.875rem below) slid under it. Now the BRANCH NAME is cut with «…» and the fulfilment word after it stays
   whole: a row, the name's <bdi> the one item that may shrink (min-width 0), and the gap standing for the spaces
   between the three (a flex row does not draw whitespace-only text). Both lines that come here are this shape —
   the funnel's (store/shared/_header) and the rating screen's :head_sub.
   The <bdi>'s clip box is widened 2px above and below and the height given back (padding + negative margin): an
   overflow that is hidden clips Cairo's ink vertically, 21px of glyph in a 19.2px line (memory
   reference_ellipsis_clips_glyphs_vertically). The margins cannot collapse here — a flex item's never do — so the
   header keeps its 77.78px to the hundredth. */
.s-head-sub { display: flex; align-items: baseline; gap: 0.25rem; white-space: nowrap; }
.s-head-sub > bdi {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0.125rem 0;
  margin: -0.125rem 0;
}
/* THE RESTAURANT'S LOGO before its name (Mohamed 30/9 08:23 and 09:16; store/shared/_header), on the name's own
   line: 24px inside the name's 25.6px line on every screen, 36px on the home, whose row is the exit box's 44px
   anyway — so no header grows, and the branch line under it keeps its whole width. The gap is direction-free, so an
   English page needs no mirror in store_ltr.css. Shown whole (contain), never cropped; square like every corner. */
.s-head-name-logo { display: flex; align-items: center; gap: 0.375rem; }
.s-head-logo { flex: none; display: block; width: 1.5rem; height: 1.5rem; object-fit: contain; }
.s-head-home .s-head-logo { width: 2.25rem; height: 2.25rem; }

/* ── «متجر X-Chef من [vROOT]» — the home's last line (Mohamed 30/9 08:23, 09:20) ─────────────────────────────────
   The LAST thing on the home's screen: the home's shell (layouts/store: .s-shell-home, the home only) is a column at
   least as tall as the screen, its main takes what is left, and the line's `margin-top: auto` puts it at the
   screen's bottom when the page is short (and the page does not scroll), 2rem after the rows when it is long. The
   home's own content stays in block flow inside .s-home-body, so its margins collapse as before. dvh after vh: a
   phone's toolbar is not counted. The main's bottom padding adds the home indicator's inset.
   The words are the lede's muted #64748b (4.55:1 on the #f8fafc page); the vROOT wordmark
   (store/shared/icons/_vroot_wordmark) keeps its own navy. It is as tall as the words' capitals (Mohamed 10:03 «نفس
   مقاس متجر X-Chef، وفي نفس السطر»): its box ends at T's flat bottom and is sized in em, so T is Cairo's cap height
   (0.7031em — 9.14px at 13px, measured in Chrome) and ROOT stands on the words' baseline (an inline SVG's bottom is
   its baseline). Its overflow is visible so the O's overshoot below the baseline is drawn, as a font draws it. */
.s-shell-home { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
.s-shell-home > .s-main {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
}
.s-made-by {
  margin: auto 0 0;
  padding-top: 2rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: #64748b;
  text-align: center;
  white-space: nowrap;
}
.s-made-by-logo { display: inline-block; width: 3.2843em; height: 0.8052em; overflow: visible; vertical-align: baseline; }
/* RENDER-11: #cbd5e1 on #fff is 1.48:1 at 12px/600 and this glyph is the only
   thing between the branch name and the fulfilment word, so when it vanishes
   the two read as one phrase. #64748b is 4.76:1 and still visually quiet. */
.s-head-sub .s-sep { color: #64748b; }

.s-back { flex: 0 0 auto; }
.s-back button {
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.625rem;
  background: #fff;
  color: #475569;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}
.s-back button:active { background: #f1f5f9; }

/* progress dots — five stages, SPEC §7 */
.s-dots { display: flex; gap: 0.375rem; margin-top: 0.625rem; }
.s-dot {
  height: 0.25rem;
  flex: 1 1 0;
  min-width: 0;
  border-radius: 999px;
  background: #e5e7eb;
}
.s-dot-done { background: #7dd3fc; }
.s-dot-now { background: #0284c7; }
.s-dot-skipped { background: repeating-linear-gradient(90deg, #e5e7eb 0 3px, transparent 3px 6px); }

/* ── page body ────────────────────────────────────────────────────── */
.s-main { padding: 1rem 1rem 1.25rem; }
.s-main-bar { padding-bottom: calc(6.5rem + env(safe-area-inset-bottom, 0px)); }

.s-h1 { margin: 0 0 0.25rem; font-size: 1.25rem; font-weight: 700; color: #111827; }
.s-lede { margin: 0 0 1rem; font-size: 0.875rem; color: #64748b; }
.s-h2 { margin: 1.25rem 0 0.5rem; font-size: 0.8125rem; font-weight: 700; color: #64748b; }

.s-note {
  margin: 0 0 1rem;
  padding: 0.75rem 0.875rem;
  border-radius: 0.75rem;
  background: #fffbeb;
  border: 1px solid #fde68a;
  color: #92400e;
  font-size: 0.875rem;
  font-weight: 600;
}
.s-empty {
  padding: 1.5rem 1rem;
  border-radius: 0.875rem;
  background: #fff;
  border: 1px dashed #e5e7eb;
  color: #64748b;
  font-size: 0.875rem;
  text-align: center;
}

/* ── choice cards (branch, address, fulfilment, variant) ──────────── */
.s-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.625rem; }

.s-choice {
  display: block;
  position: relative;
  min-height: 2.75rem;
  padding: 0.875rem 2.75rem 0.875rem 0.875rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.875rem;
  background: #fff;
  cursor: pointer;
}
.s-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.s-choice .s-mark {
  position: absolute;
  top: 50%;
  right: 0.875rem;
  margin-top: -0.625rem;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid #cbd5e1;
  border-radius: 999px;
  background: #fff;
}
/* block, not inline: a title and its meta must never share a line, and an
   inline title in RTL wraps into the middle of the meta text. */
.s-choice-title { display: block; font-size: 0.9375rem; font-weight: 700; color: #111827; }
.s-choice-meta { display: block; margin-top: 0.125rem; font-size: 0.8125rem; line-height: 1.5; color: #64748b; }
.s-choice-meta + .s-choice-meta { margin-top: 0.0625rem; }
.s-choice:has(input:checked) { border-color: #0EA5E9; background: #e0f2fe; }
.s-choice:has(input:checked) .s-choice-title,
.s-choice:has(input:checked) .s-choice-meta { color: #0c4a6e; }
.s-choice:has(input:checked) .s-mark { border-color: #0284c7; border-width: 0.4375rem; }
.s-choice:has(input:focus-visible) { outline: 2px solid #0284c7; outline-offset: 2px; }
.s-choice-off { background: #f8fafc; border-style: dashed; cursor: default; }
.s-choice-off .s-choice-title { color: #64748b; }

.s-pill {
  display: inline-block;
  padding: 0.0625rem 0.5rem;
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 700;
}
.s-pill-open { background: rgba(34, 197, 94, .12); color: #15803d; }
.s-pill-shut { background: rgba(100, 116, 139, .12); color: #334155; }

/* two-up (توصيل / استلام) */
.s-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.625rem; }
.s-pair .s-choice { padding: 1rem 0.75rem; text-align: center; }
.s-pair .s-choice-meta { margin-top: 0.25rem; }
.s-pair .s-choice .s-mark { display: none; }
.s-pair .s-ic { display: block; margin: 0 auto 0.375rem; }

/* ── forms ────────────────────────────────────────────────────────── */
.s-field { margin-bottom: 0.875rem; }
.s-label { display: block; margin-bottom: 0.375rem; font-size: 0.75rem; font-weight: 700; color: #64748b; }
.s-input, .s-select, .s-textarea {
  display: block;
  width: 100%;
  min-height: 2.875rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.75rem;
  background: #fff;
  text-align: right;
}
.s-textarea { min-height: 5.5rem; line-height: 1.5; resize: vertical; }
.s-input:focus, .s-select:focus, .s-textarea:focus {
  outline: none;
  border-color: #0EA5E9;
  box-shadow: 0 0 0 3px rgba(14, 165, 233, .12);
}
.s-help { margin: 0.25rem 0 0; font-size: 0.75rem; color: #64748b; }
/* r3 A: the area search's no-match line (store/addresses/show; the picker itself is in store_options.css). It is
   display:flex, so its [hidden] is restated: a class rule beats the UA's [hidden] otherwise. */
.s-area-none { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.5rem; margin-top: 0.25rem; font-size: 0.875rem; color: #475569; }
.s-area-none[hidden] { display: none; }

/* RENDER-13: AN ACTION HIDDEN INSIDE A SENTENCE IS STILL A CONTROL.
   Measured on /store/me: `a[href="/store/me/phone"]` — «عدّل رقم التواصل
   والعناوين», the ONLY route to the screen that fixes a wrong phone number and
   adds an address — was 23 x 132px, the single element under 44px on any
   customer screen (every control on branch, type, address, menu and review
   measured 44-56). 12px text in a `.s-help` paragraph gives a 23px line box and
   the anchor is exactly its text.
   inline-flex keeps it inside the running sentence while giving the box a real
   height, the way `.s-tab` (which carries a note about the same 36-vs-44 trap)
   and `.s-tag` already do. The horizontal padding is deliberately 0: the hit
   area must grow, but a 132px-wide link in the middle of a line must not gain
   side gaps that read as a button.
   #0369a1 is 5.67:1 on the #f8fafc ground and underlined, so it is not colour
   alone that says «this is a link».
   home-d4 render gate: the WIDTH has the same floor. v23's «شوفه» (store/shared/
   _open_orders, now on the home under V4's card) measured 33.3 x 44 at 360-390
   («View it» 35.4). min-width only reaches a link shorter than 44px, so the
   132px link above keeps no side gap; the short one is centred in its 44. */
.s-help-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  min-width: 2.75rem;
  color: #0369a1;
  font-weight: 700;
}

/* payment-card (2026-09-28): «نقدي» / «فيزا» on the review are the two-up above
   (.s-pair); these rules are what the approved mockup added to it. The chosen
   one's icon takes the accent too; one line under the pair follows the choice
   through :has(), no script — so it is right with JavaScript off. After .s-help
   on purpose: .s-pay-line's margin has to win over it. The receipt's payment
   row (.s-pay-row-v) is the word, then the same line, quieter. */
.s-pay .s-choice:has(input:checked) .s-ic { color: #0284c7; }
.s-pay-line { margin: 0.5rem 0 0.75rem; }
.s-pay-line [data-pay="card"] { display: none; }
.s-pay:has(input[value="card"]:checked) + .s-pay-line [data-pay="cash"] { display: none; }
.s-pay:has(input[value="card"]:checked) + .s-pay-line [data-pay="card"] { display: inline; }
.s-pay-row-v { text-align: end; }

/* ── buttons ──────────────────────────────────────────────────────── */
.s-btn {
  display: block;
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem 1rem;
  border: 1px solid transparent;
  border-radius: 0.875rem;
  /* RENDER-2: #0369a1, not #0284c7. See the contrast table at the head of
     this file - 16px bold is normal text, so 4.5:1 applies. */
  background: #0369a1;
  color: #fff;
  font-size: 1rem;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.s-btn:active { background: #075985; }   /* #fff on it = 7.56:1 */
/* RENDER-13: THIS RULE WAS DECLARED TWICE AND THE SECOND COPY UNDID IT.
   A `.s-btn[disabled] { background:#cbd5e1; color:#f8fafc }` lived 370 lines
   further down, next to the rating screen's chips, and being later it won
   everywhere — measured on the empty-cart menu at 1.42:1, the worst ratio on
   that screen, on the label of the primary call to action. WCAG 1.4.3 exempts
   disabled controls, so this was not a conformance failure; it was the customer
   unable to read the words «أكّد الطلب» on the button they are waiting to be
   allowed to press. One declaration now, and it is this one, and its colour is
   #475569 rather than the #64748b that used to be here: measured in the browser
   on the live page, #64748b on #e2e8f0 is 3.86:1 and #475569 is 6.15:1. The
   exemption is not a reason to stop at «legible enough» when the readable slate
   costs nothing and the grey fill is what says «off» anyway.
   `border-color` comes from the deleted copy so the ghost/solid pair keep the
   same silhouette when disabled. */
.s-btn[disabled] { background: #e2e8f0; border-color: #e2e8f0; color: #475569; cursor: not-allowed; }
.s-btn-ghost {
  background: #fff;
  border-color: #e5e7eb;
  color: #0c4a6e;
  font-weight: 600;
}
.s-btn-ghost:active { background: #f0f9ff; }
.s-btn-quiet {
  background: none;
  border-color: transparent;
  color: #64748b;
  font-size: 0.875rem;
  font-weight: 600;
  min-height: 2.75rem;
}
.s-stack > * + * { margin-top: 0.625rem; }
.s-stack { margin-top: 1rem; }

/* ── menu ─────────────────────────────────────────────────────────── */
/* RENDER-12: THE SECTION STRIP HAS TO STAY ON SCREEN.
   Measured on the live Tagamoa menu at 375px: 25 sections, document height
   61,311px — about 75 screenfuls — and `nav.s-tabs` computed position was
   `static`, so the only control that can move the customer between sections
   scrolled away after the first card and never came back. The strip itself is
   3,185px wide against a 343px viewport, so even while it IS on screen the last
   tab is ~2,842px of horizontal swiping away; that half is a layout problem for
   another day, but a strip that is gone entirely has no fix at all.
   `.s-head` and `.s-bar` are already sticky, so this is the pattern the file
   established, not a new one.

   The offset is MEASURED, not guessed: `.s-head` is 77.78px tall on the live
   menu (getBoundingClientRect().height at 375px with a branch line and the five
   dots). 4.875rem = 78px, so the strip tucks 0.22px under the header instead of
   leaving a hairline of scrolling cards between them. `env(safe-area-inset-top)`
   is added for the same reason `.s-head`'s own bottom padding adds it — on a
   notched phone with viewport-fit=cover the header grows by exactly that inset
   and the strip has to grow with it.
   z-index 4 is BELOW the header's 5: if the two ever disagree about the offset
   the header wins and the strip hides under it, which is survivable; the
   reverse would draw tabs over the restaurant's name.
   The background is the body ground, because without one the cards scroll
   through the gaps between the pills. */
.s-tabs {
  position: sticky;
  top: calc(4.875rem + env(safe-area-inset-top, 0px));
  z-index: 4;
  background: #f8fafc;
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding: 0.125rem 0 0.75rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.s-tabs::-webkit-scrollbar { display: none; }
.s-tab {
  /* inline-flex + min-height so the tab is a 44px touch target and not the
     36px an inline-block with padding gives you — measured in the browser, not
     assumed. */
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  min-height: 2.75rem;
  padding: 0.375rem 0.875rem;
  border: 1px solid #e5e7eb;
  border-radius: 999px;
  background: #fff;
  color: #0c4a6e;
  font-size: 0.8125rem;
  font-weight: 700;
  text-decoration: none;
  line-height: 1.5;
}

/* RENDER-12: 6.5rem was the header alone (104px against a measured 77.78px, so
   it already carried slack). Now that `.s-tabs` is sticky the anchor has to
   clear the header AND the strip or every tab lands its section title
   underneath the strip that was just tapped: 78 + 58 = 136px, the strip's own
   measured height included, + 4px of air so the section title is not flush
   against the strip's edge = 8.75rem. Measured after the change: tapping tab 13
   lands the section top at 140px against a strip bottom of 136px. Same inset
   term as `.s-tabs`. */
.s-cat { scroll-margin-top: calc(8.75rem + env(safe-area-inset-top, 0px)); margin-bottom: 1.25rem; }
/* w-3: a DISH is an anchor target too — an accepted add 302s to
   /store/menu#i-<id> — so it clears the same header + stuck strip with the same
   8.75rem. Without it the card lands at 0 and hides under both layers. */
.s-item { scroll-margin-top: calc(8.75rem + env(safe-area-inset-top, 0px)); }
.s-cat-name { margin: 0 0 0.5rem; font-size: 1rem; font-weight: 700; color: #111827; }

.s-item {
  padding: 0.875rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.875rem;
  background: #fff;
  margin-bottom: 0.625rem;
}
.s-item-off { background: #f8fafc; }
.s-item-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; align-items: start; }

/* THE DISH'S PHOTOGRAPH. Only the cards that HAVE one get the third column —
   restaurant #1 has a photo for 144 of its 352 items, and an empty first column
   on the other 208 would indent their names against nothing. The page is
   physically RTL (DESIGN.md), so column 1 is the right-hand edge: picture,
   then the name and the two descriptions, then the price. */
.s-item-has-photo .s-item-top { grid-template-columns: auto minmax(0, 1fr) auto; }
.s-item-photo { display: block; flex: 0 0 auto; }
.s-item-photo img {
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  object-fit: cover;
  border-radius: 0.625rem;
  border: 1px solid #e5e7eb;
  /* The ground the card shows while the photo is still on the wire, so a slow
     connection sees a placeholder of the right size instead of a collapsing
     layout. */
  background: #f1f5f9;
}
/* An item this branch does not sell is greyed everywhere else on the card; the
   photograph has to be greyed too or it is the one bright thing on a dead row. */
.s-item-off .s-item-photo img { opacity: 0.45; filter: grayscale(1); }
/* overflow-wrap (item-photo build 2, review renders finding 7): الوصف and
   معاه are TYPED by a manager now, and one 299-character word with no space
   pushed the card — and the customer's menu — 2,159px sideways. `anywhere`
   also lowers the min-content width, so the flex column can shrink. */
.s-item-name { font-size: 0.9375rem; font-weight: 700; color: #111827; overflow-wrap: anywhere; }
.s-item-desc { font-size: 0.8125rem; color: #64748b; overflow-wrap: anywhere; }
.s-item-price { font-size: 0.9375rem; font-weight: 700; color: #0c4a6e; white-space: nowrap; }
/* RENDER-13: #94a3b8 on the `.s-item-off` ground (#f8fafc) measures 2.45:1 at
   15px/700 — the name of the dish the branch cannot sell was the least readable
   text on the menu, and it is exactly the word the customer needs in order to
   understand why the card is dead. #475569 on #f8fafc is 7.24:1 and still reads
   as muted against the #111827 of a live row. The greyed photograph above
   (opacity .45 + grayscale) is what carries "unavailable"; it does not need the
   words to be illegible as well.
   This stops being one row the moment per-price-list sizes ship — a branch that
   does not carry a size greys the whole card. */
.s-item-off .s-item-name, .s-item-off .s-item-price { color: #475569; }

/* THE SECOND DESCRIPTIVE FIELD. `.s-item-desc` is what is IN the dish; this is
   what COMES WITH it. Same size, but indented behind a label and on a tinted
   ground, so the customer can tell "شرائح شاورما + ثومية" from
   "أرز 250جم + طحينة + سلطة + 2 خبز" without reading both. */
.s-item-sides { display: flex; gap: 0.375rem; align-items: baseline; margin-top: 0.25rem;
                font-size: 0.8125rem; color: #475569; overflow-wrap: anywhere; }
.s-item-sides-label { flex: 0 0 auto; font-weight: 700; color: #0369a1; font-size: 0.75rem;
                      background: #e0f2fe; border-radius: 0.375rem; padding: 0.0625rem 0.375rem; }
/* RENDER-13, the same defect one line down: «معاه» measured 2.34:1 (#94a3b8 on
   the #f1f5f9 chip). #475569 on #f1f5f9 is 6.92:1. */
.s-item-off .s-item-sides, .s-item-off .s-item-sides-label { color: #475569; background: #f1f5f9; }
.s-row .s-item-sides { margin-top: 0.1875rem; }

.s-variants { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.375rem; margin-top: 0.625rem; }
.s-variants .s-choice { padding: 0.625rem 2.25rem 0.625rem 0.625rem; }
.s-variants .s-mark { right: 0.625rem; }

.s-add-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.625rem; align-items: center; margin-top: 0.625rem; }
/* 2026-09-14 — THE ADD BUTTON WITH NO ROW AROUND IT.
   `.s-add-row`'s first track held the hint «ضيفه للسلة», which said nothing the
   button beside it did not already say — 352 times on restaurant #1's menu. The
   hint is kept where it carries information («اختار المقاس وضيفه للسلة», on a
   dish with sizes) and dropped where it does not, and with one child left the
   wrapper was 44 bytes per dish (15 KB) of nothing.
   `width: fit-content` keeps the button its own width instead of the block's,
   and `margin-right: auto` on a block box pushes it AWAY from the right — the
   left edge of the card, exactly where the two-track row put it. Physical
   properties only, like the rest of this file. If a WebView too old for
   fit-content ever renders this, the button goes full width: still tappable,
   still 44px, just wider. */
.s-add-solo { display: block; width: fit-content; margin-top: 0.625rem; margin-right: auto; }
.s-add {
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 1rem;
  border: 1px solid transparent;
  border-radius: 0.75rem;
  /* RENDER-13: #0284c7, the colour RENDER-2 already took off `.s-btn` for this
     exact reason, was still here — and this button is on screen 351 times on
     the Tagamoa menu against `.s-btn`'s one. #fff on #0284c7 is 4.10:1 at
     14px/700; WCAG large scale starts at 18.66px bold, so 14px bold is normal
     text and the floor is 4.5:1. #0369a1 is 5.93:1 and is the colour the
     contrast table at the head of this file already names as the answer, so no
     new token and now one blue for every primary action on the screen. */
  background: #0369a1;
  color: #fff;
  font-size: 0.875rem;
  font-weight: 700;
  cursor: pointer;
}
.s-add:active { background: #075985; }   /* #fff on it = 7.56:1, same as .s-btn:active */

.s-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.625rem;
  align-items: center;
  margin-top: 0.625rem;
  padding-top: 0.625rem;
  border-top: 1px solid #f3f4f6;
}
.s-line-name { font-size: 0.8125rem; font-weight: 600; color: #0c4a6e; }
.s-line-sub { font-size: 0.75rem; color: #64748b; }

.s-step { display: flex; align-items: center; gap: 0.25rem; }
.s-step button {
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.75rem;
  background: #fff;
  color: #0c4a6e;
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.s-step button:active { background: #e0f2fe; }
.s-step-qty {
  min-width: 2rem;
  text-align: center;
  font-size: 0.9375rem;
  font-weight: 700;
  color: #111827;
}
.s-inline { display: inline; margin: 0; }

/* ── totals (review) ──────────────────────────────────────────────── */
.s-card {
  padding: 0.875rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.875rem;
  background: #fff;
}
.s-card + .s-card { margin-top: 0.75rem; }
/* home-d4 render gate: the label track's floor is its own longest word, not 0.
   With `minmax(0, 1fr) auto` a value longer than the row (the lane's worst
   name, 60 letters, on «بياناتي») took the whole row as its auto track and
   squeezed «الاسم» to a 0px box: the name was painted over the label at
   360-390, and «Name» spilled 5px at 360 in English. min-content changes
   nothing while the row fits; when it does not, the value wraps instead. */
.s-row {
  display: grid;
  grid-template-columns: minmax(min-content, 1fr) auto;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.375rem 0;
  font-size: 0.875rem;
}
.s-row-total {
  margin-top: 0.375rem;
  padding-top: 0.75rem;
  border-top: 1px solid #f3f4f6;
  font-size: 1.0625rem;
  font-weight: 700;
  color: #111827;
}
.s-row-muted { color: #64748b; }
.s-amount { font-weight: 700; color: #0c4a6e; white-space: nowrap; }

/* ── sticky cart bar ──────────────────────────────────────────────── */
.s-bar {
  position: fixed;
  right: 0;
  left: 0;
  bottom: 0;
  z-index: 10;
  background: #fff;
  border-top: 1px solid #e5e7eb;
  padding: 0.625rem 1rem calc(0.625rem + env(safe-area-inset-bottom, 0px));
}
.s-bar-inner { max-width: 30rem; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
.s-bar-sum {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: baseline;
  font-size: 0.8125rem;
  color: #64748b;
}

/* ── 2026-09-14 · «بيحمّل» ────────────────────────────────
 * Mohamed opened the store on his phone inside WhatsApp: «اتأخر جدا ومفيش اي
 * حاجه بتقول انه كان بيحمل». Every screen here navigates by a full-page form
 * POST (SPEC §5 — it all works with JavaScript off), so there is nothing between
 * the tap and the next paint. Turbo's bar cannot fill that gap: all 21 store
 * forms carry data-turbo="false", so Turbo never handles a store submission.
 *
 * These rules are INERT until app/javascript/controllers/busy_controller.js
 * puts `.s-busy` on the shell. JavaScript off = display:none and nothing else.
 *
 * z-index 20 is above .s-head (5) and .s-bar (10): the one thing that must be
 * visible while the page is working is the thing that says it is working.
 */
.s-busy-bar {
  display: none;
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 20;
  height: 3px;
  background: #e0f2fe;
  overflow: hidden;
}
.s-busy .s-busy-bar { display: block; }
.s-busy .s-busy-bar::after {
  content: "";
  display: block;
  width: 40%;
  height: 100%;
  background: #0369a1;
  animation: s-busy-slide 1.1s ease-in-out infinite;
}
/* Physical RTL like the rest of this file: the bar travels from the right edge
   to the left, the direction Arabic reads. */
@keyframes s-busy-slide {
  from { margin-right: -40%; }
  to   { margin-right: 100%; }
}

/* The button the customer actually tapped. `pointer-events: none` is what stops
   the second tap of a double tap — NOT the `disabled` property, which would
   remove the button from the submitted entry list and, since the menu's add
   buttons now carry the dish id as their own name/value, would post a cart
   change with no item in it. busy_controller.js says the same thing at the line
   that sets aria-disabled. */
.s-busy-btn {
  pointer-events: none;
  opacity: 0.65;
  cursor: progress;
}

/* A customer who has asked for less motion still gets the bar — it is the
   information, not the decoration. It just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .s-busy .s-busy-bar::after { animation: none; width: 100%; }
}

/* M8 ADDS THE SECOND SELECTOR, AND IT IS A CORRECTION — see the M8 block at
   the end of this file. The closing strip's ABSOLUTE sentence («الفرع بيقفل
   الساعة 02:30 — كمّل طلبك قبلها.») is what JavaScript replaces on screen with
   the ticking one. It used to be `display: none` there, which takes it out of
   the ACCESSIBILITY TREE as well as off the screen — and since the digits
   themselves are aria-hidden, the whole accessible content of that
   role="status" region became «الفرع بيقفل خلال»: a deadline announced without
   its own number, on the one alarm screen the store has. MEASURED in a browser
   on the dev box. Off screen, still announced, is what this rule is for.
   It cannot use the CLASS: the class would have to be written by the server,
   and the server does not know whether JavaScript ran. One definition, two
   selectors, so the two can never drift apart. */
.s-visually-hidden,
.s-closing-js .s-closing-plain {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── confirmation, screen 7 (week 2) ──────────────────────────────
 * mockups/order-flow.html screen ٧: the order number and total across the top,
 * a promised-time panel, then a four-row timeline. Physical RTL like the rest
 * of this file — the dot rail is on the RIGHT, and the connecting line is drawn
 * with a ::before on the row rather than an absolutely positioned element, so a
 * row that wraps to two lines does not break the rail.
 */
.s-ordhead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: baseline;
  margin: 0 0 0.75rem;
}
.s-ordnum { font-size: 1.25rem; font-weight: 800; color: #111827; }

.s-panel {
  padding: 0.875rem;
  border: 1px solid #bae6fd;
  border-radius: 0.875rem;
  background: #f0f9ff;
}
.s-panel + .s-card, .s-card + .s-panel { margin-top: 0.75rem; }
.s-panel-lbl { font-size: 0.75rem; font-weight: 700; color: #0369a1; }
.s-panel-val { font-size: 1.0625rem; font-weight: 700; color: #0c4a6e; }

.s-track { margin: 0; padding: 0; list-style: none; }
.s-track-row {
  position: relative;
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  gap: 0.75rem;
  padding: 0.5rem 0;
  font-size: 0.875rem;
}
.s-track-rail { position: relative; display: flex; justify-content: center; padding-top: 0.35rem; }
.s-track-dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: #e2e8f0;
  border: 2px solid #e2e8f0;
}
.s-track-on .s-track-dot { background: #0284c7; border-color: #0284c7; }
.s-track-stop .s-track-dot { background: #b91c1c; border-color: #b91c1c; }
/* the line to the NEXT row, drawn from this row's rail */
.s-track-row:not(:last-child) .s-track-rail::after {
  content: "";
  position: absolute;
  top: 1.1rem;
  bottom: -0.6rem;
  width: 2px;
  background: #e2e8f0;
}
.s-track-on:not(:last-child) .s-track-rail::after { background: #7dd3fc; }
/* RENDER-3: #94a3b8 on the #f8fafc ground is 2.56:1. These are the steps the
   order has not reached yet - they are quiet, not absent. #64748b is 4.55:1. */
.s-track-tx { font-weight: 600; color: #64748b; }
.s-track-on .s-track-tx { color: #111827; }
.s-track-stop .s-track-tx { color: #b91c1c; }
.s-track-time { display: block; font-size: 0.75rem; font-weight: 600; color: #64748b; }

/* POINT 25 — the branch hides the steps: «طلبك اتأكد» and one line, in place of the four rows. #15803d on #fff is
   5.0:1, the home's «s-pill-done» green. */
.s-confirmed { display: grid; grid-template-columns: 2.25rem minmax(0, 1fr); gap: 0.75rem; align-items: center; }
.s-confirmed-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  background: rgba(34, 197, 94, .12);
  color: #15803d;
}
.s-confirmed-tx { min-width: 0; }
.s-confirmed-t { display: block; font-size: 1rem; font-weight: 700; color: #15803d; }
.s-confirmed-s { display: block; font-size: 0.875rem; font-weight: 600; color: #475569; }

/* the saved-address list on the profile screen */
.s-addr { padding: 0.625rem 0; border-top: 1px solid #f3f4f6; font-size: 0.875rem; }
.s-addr:first-child { border-top: 0; }
.s-addr-lbl { font-weight: 700; color: #111827; }
/* overflow-wrap (home-d4 render gate): this class also prints the customer's
   rating comment on the answered rate screen. A long unbroken run in it (a
   link) pushed the page 13-44px sideways at 390-360; the home card's .s-rc
   already wraps it. Changes nothing for text that fits. */
.s-addr-txt { color: #64748b; white-space: pre-line; overflow-wrap: anywhere; }

/* ══════════════════════════════════════════════════════════════════════
 * WEEK 3, AREA E — the rating screen (9) and the complaint screens.
 *
 * Everything below is APPENDED, not woven in: nothing above this line
 * changed. The complaint TYPE list and the order-link list deliberately add
 * no classes at all — they are `.s-list` + `.s-choice` + `.s-choice-title` +
 * `.s-choice-meta`, the same radio card screens 1, 3 and 4 use. Five screens
 * that each ship their own selectable row are five screens that disagree.
 *
 * What genuinely had no component yet: the star row, the tag chips, the
 * disabled submit, the file input and the complaint-code block.
 * ══════════════════════════════════════════════════════════════════════ */

/* ── the stars (screen 9) ─────────────────────────────────────────────
 * Each star is an <a>, not a radio, and choosing one is a full page load.
 * That is the point: SPEC §5 requires the screens to work with JavaScript
 * off, and "the send button stays disabled until a star is chosen" cannot be
 * done in the browser without a script the CSP would drop silently. So the
 * server knows the score, renders the chips that go with it, and enables the
 * button — one round trip, and it behaves identically with JS on or off. */
/* NO flex-direction here on purpose. The page is dir="rtl", so a plain `row`
   already lays 1..5 out right-to-left and the FIRST star lands on the right,
   which is where an Arabic reader starts. `row-reverse` would put star 5 on
   the right and silently invert the whole control. */
.s-stars {
  display: flex;
  justify-content: center;
  gap: 0.25rem;
  margin: 0.25rem 0 0.5rem;
}
.s-star {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.75rem;
  font-size: 1.75rem;
  line-height: 1;
  text-decoration: none;
  /* RENDER-5: #cbd5e1 on the #f8fafc ground is 1.42:1 - roughly invisible on a
     phone in daylight, which is the only context this screen ships into. The
     star is an enabled <a> with its own label, so the disabled-control
     exemption does not apply and it is held to TEXT contrast (the note under
     .s-star-on says exactly that, and this colour was breaking it by a factor
     of three). #64748b on #f8fafc is 4.55:1. */
  color: #64748b;
}
/* #b45309 on #fff = 5.32:1 — the star is a link and carries its own label,
   so it is held to text contrast, not to the 3:1 graphic floor. */
.s-star-on { color: #b45309; }
.s-star:focus-visible { outline: 2px solid #0284c7; outline-offset: 2px; }
.s-rword {
  margin: 0 0 0.75rem;
  text-align: center;
  font-size: 0.9375rem;
  font-weight: 700;
  color: #0c4a6e;
}

/* ── the tag chips ────────────────────────────────────────────────────
 * A checkbox, not a link: the chips are a multi-select and they travel with
 * the same POST as the comment, so nothing is lost by a customer who taps
 * three of them and then closes the page — nothing was written yet either. */
.s-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.875rem; }
.s-tag {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* RENDER-7: 2.25rem = 36px, and the header of this file asserts 44px. The
     label WRAPS the checkbox (which is 0x0 and transparent), so the label box
     IS the whole hit area and 36px was all the customer got. Every other
     control on the customer screens already measured >= 44px. At 360px four
     chips already wrap, so the extra height costs one row at most. */
  min-height: 2.75rem;
  padding: 0.375rem 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 999px;
  background: #fff;
  font-size: 0.8125rem;
  font-weight: 600;
  color: #334155;
  cursor: pointer;
}
.s-tag input { position: absolute; opacity: 0; width: 0; height: 0; }
.s-tag:has(input:checked) { border-color: #0EA5E9; background: #e0f2fe; color: #0c4a6e; }
.s-tag:has(input:focus-visible) { outline: 2px solid #0284c7; outline-offset: 2px; }

/* ── a submit that is not available yet ───────────────────────────────
 * `disabled` on the real <button>, so it is announced as disabled and cannot
 * be submitted by keyboard either. The server refuses a starless POST
 * regardless — this is the affordance, not the guard. */
/* RENDER-13: the `.s-btn[disabled]` that used to sit here was the SECOND
   declaration of a rule already set under «buttons» 370 lines up, and it
   overrode it — see the note there. Deleted; the single declaration now serves
   this screen too, with the border-color this copy contributed folded into it.
   `.s-btn-ghost[disabled]` stays: it is a different selector and the only
   declaration of it. #94a3b8 on #fff is 2.84:1, which is the same disabled-text
   exemption — but this control is the one screen 9 renders while the customer
   has not yet picked a star, so it is given the readable slate as well.
   #64748b on #fff is 4.76:1. */
.s-btn-ghost[disabled] { background: #fff; border-color: #e5e7eb; color: #64748b; }

/* ── the optional photo (complaint screen 1, step 3) ──────────────────
 * A bare file input is 2 lines tall on Android and unreadable in RTL, so it
 * gets the same field chrome as every other control. */
/* RENDER-8: the native control's own words live in the UA shadow DOM, so they
   never appear in document.body.innerText and only a SCREENSHOT found them:
   "Choose File" / "No file chosen", in English, left-aligned, between an Arabic
   label and an Arabic hint. There is no way to translate them - so the input is
   taken out of the visual layer and a real <label> is what the customer taps.
   The input is NOT display:none and NOT hidden: it stays focusable and keeps
   its accessible name, and the focus ring is drawn on the label instead.
   The chosen filename is not echoed back: doing that needs a change listener
   and the CSP has neither unsafe-inline nor unsafe-eval, so a script would be
   dropped by the browser while passing every server-side check. The label text
   is therefore static, and the complaint's confirmation screen is where the
   customer learns the photo arrived. */
.s-file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.s-file-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 2.875rem;
  padding: 0.625rem 0.75rem;
  border: 1px dashed #94a3b8;
  border-radius: 0.75rem;
  background: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  color: #0369a1;   /* 5.93:1 on #fff */
  cursor: pointer;
}
.s-file:focus + .s-file-btn { outline: 2px solid #0369a1; outline-offset: 2px; }

/* ── the complaint number (complaint screen 2) ────────────────────────
 * The one thing on that screen the customer may read out on the phone, so it
 * is the biggest thing on it. Latin digits, LTR-isolated at the call site. */
.s-tick {
  padding: 1.25rem 1rem;
  border-radius: 0.875rem;
  background: #ecfdf5;
  border: 1px solid #a7f3d0;
  text-align: center;
}
.s-tick-lb { font-size: 0.75rem; font-weight: 700; color: #047857; }
.s-tick-no {
  margin-top: 0.25rem;
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: #065f46;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ── the step counter on the complaint screens ────────────────────────
 * Three steps and the customer is told which one they are on. Screens 1-5
 * have .s-dots for the same job, but those are wired to the seven-step
 * funnel; a complaint is outside it (see Store::ComplaintsController). */
.s-steps { margin: 0 0 0.75rem; font-size: 0.75rem; font-weight: 700; color: #64748b; }

/* ── M8 · «الفرع هيقفل» ───────────────────────────────────────────────
 * The closing strip is the LAST CHILD OF .s-head, under the progress dots, so
 * it inherits the header's own `position: sticky; top: 0; z-index: 5` and is
 * «قدامه» — always on screen — without a new stacking rule and without
 * covering the restaurant's name. Inside <main> it would have scrolled away
 * after the first card on a 61,311px menu; a fixed overlay at top:0 would have
 * sat on top of the name; a third sticky layer would have needed its own
 * z-index argument with .s-tabs. This one needs none of that.
 *
 * Colours are .s-note's own palette (see :81 above), so nothing new enters the
 * colour system. Contrast MEASURED in the browser, not assumed:
 *   #92400e on #fffbeb = 6.84:1   the warning
 *   #991b1b on #fef2f2 = 7.60:1   the last two minutes
 * The strip is 33.5px tall and adds 41.5px to .s-head (77.78 -> 119.28px at
 * 375px with Cairo loaded, a branch line and the five dots) — MEASURED, which
 * is why the two offsets at the bottom of this block are what they are.
 *
 * ONE LINE IS TRUE FOR THREE OF THE FOUR SENTENCES, NOT FOR ALL OF THEM, AND
 * THE ONE THAT WRAPS IS THE DELIVERY ZERO. Measured in a real browser against
 * this file, 2026-09-18, on the dev box: the warning (53 chars), the countdown
 * (30) and the PICKUP zero take ONE line box at 320px and at 375px. The
 * DELIVERY zero — «التوصيل وقف — الفرع لسه شغّال للاستلام لحد HH:MM، وطلبك
 * زي ما هو.», 65 chars — takes TWO: 2 line boxes at 320px (strip 288px wide)
 * and at 375px (343px), 1 at 1280px (448px). The strip is then 53px instead of
 * 33.5 and .s-head is 138.78px instead of 119.28, so the two offsets below —
 * which were derived for a 33.5px strip — leave the sticky .s-tabs 19.78px
 * BEHIND the header for as long as that sentence is showing (elementFromPoint
 * at the tabs’ top edge returns this <p>). Cosmetic, phone-only, and it starts
 * at the delivery cut-off, not before.
 *
 * The claim that used to stand here — «ONE line … for all three sentences» —
 * was measured in M8-DESIGN §2 BEFORE §9.2 added this fourth sentence, and
 * nobody re-measured it afterwards. Shortening the copy or teaching the two
 * offsets to tolerate two lines is a COPY/DESIGN decision, not a CSS one. */
.s-closing {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  margin: 0.5rem 0 0;
  padding: 0.375rem 0.625rem;
  border-radius: 0.5rem;
  background: #fffbeb;
  border: 1px solid #fde68a;
  color: #92400e;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5;
}
.s-closing-last { background: #fef2f2; border-color: #fecaca; color: #991b1b; }
.s-closing-clock { font-weight: 700; font-variant-numeric: tabular-nums; }

/* The three sentences, all server-rendered, one visible at a time. NO
   JAVASCRIPT = the first one, the absolute wall-clock sentence, because no
   class is ever added. JS = the ticking one. Zero = the third. The controller
   adds .s-closing-js and .s-closing-over and does nothing else to this
   element.

   THE CHILD COMBINATOR IN THE FIRST RULE IS LOAD-BEARING AND IT IS THE TRAP.
   The state class the controller adds at zero, .s-closing-over, is the SAME
   NAME as the span it is meant to reveal — and it is added to the <p>. Written
   as a bare `.s-closing-live, .s-closing-over { display: none }` the rule
   therefore matches the PARAGRAPH as well as the spans, and the whole strip
   goes display:none at the one instant it exists for. MEASURED at 375px
   against this file: the <p> computed `flex` before the class and `none`
   after it, height 33.5px -> 0. `>` scopes the rule to the three spans, which
   are children of .s-closing; the <p> is not a child of itself, so it keeps
   its own display and every class name the controller writes stays as it is. */
.s-closing > .s-closing-live, .s-closing > .s-closing-over { display: none; }
.s-closing-js .s-closing-live { display: inline; }
.s-closing-js.s-closing-over .s-closing-live { display: none; }
.s-closing-js.s-closing-over .s-closing-over { display: inline; }

/* THE PLAIN SENTENCE IS NOT `display: none` UNDER .s-closing-js. It is taken
   OFF SCREEN and LEFT IN THE ACCESSIBILITY TREE, by the .s-visually-hidden
   rule at :608 — the ticking span is aria-hidden as a whole, so this is the
   only sentence a screen reader has, and it is the one that carries the
   deadline. Being out of flow (position: absolute) it costs no layout: the
   strip measures 33.5px and .s-head 119.28px with it, exactly as they did when
   it was display:none, which is why the two offsets below did not move.

   AT ZERO IT GOES, and that is not tidiness. «الفرع بيقفل الساعة 02:30 — كمّل
   طلبك قبلها.» is false the moment 02:30 arrives, and without this rule a
   screen reader would read it and the zero sentence one after the other — the
   store telling a customer to hurry and that it is closed, in one breath. The
   three classes outrank the two, so this wins wherever it is written. */
.s-closing-js.s-closing-over .s-closing-plain { display: none; }

/* RENDER-12's two constants, RE-DERIVED with the strip in place: 78 -> 119px
   and 140 -> 181px, measured the same way the originals at :316 and :354 were,
   in the browser at 375px against the real Cairo faces. With the two new
   offsets applied: header bottom 119.28 | .s-tabs top 119.00 -> the strip
   tucks 0.28px under the header (the original tucked 0.22px); .s-tabs bottom
   177.00 | .s-cat top 181.27 -> 4.27px of air under the strip (the original
   left 4.00px).
   :has() rather than a class the server would have to keep in sync: the rules
   apply only while the strip is actually in the DOM, so the moment
   closing_warning answers nil the selectors stop matching and the geometry
   returns to the 4.875rem / 8.75rem it has today, on its own, with nothing to
   remember. That is why app/views/layouts/store.html.erb is not edited at all.
   :has() is already load-bearing in this file — .s-choice:has(input:checked)
   at :170 is the selected-branch card. Same env(safe-area-inset-top) term as
   the originals, for the same reason. */
.s-head:has(.s-closing) + .s-main .s-tabs { top: calc(7.4375rem + env(safe-area-inset-top, 0px)); }
.s-head:has(.s-closing) + .s-main .s-cat  { scroll-margin-top: calc(11.3125rem + env(safe-area-inset-top, 0px)); }
.s-head:has(.s-closing) + .s-main .s-item { scroll-margin-top: calc(11.3125rem + env(safe-area-inset-top, 0px)); }

/* ── review-edit (2026-09-26) · «عدّل» على «مراجعة الطلب» ───────────
 * The approved mockup, round 2 (Claude-code-UI/mockups/2026-09-26-review-qty,
 * rq.css + rq2.css), renamed into the store's s- namespace. Direction-neutral
 * on purpose — flex/grid, flex-end, symmetric margins — so store_ltr.css needs
 * no twin: flex-end is the card's LEFT edge in Arabic and its RIGHT in English,
 * exactly as drawn. Every control is >= 44px (2.75rem).
 *
 * The control, in the order card's head: a blue word with a pencil, no box —
 * the screen is still v24's, and «أكّد الطلب» stays the only solid button. The
 * head is pulled into the card's padding (-10px top, -4px bottom; the first
 * row has 6px of its own), so the card grows by ~30px, not 44. */
.s-rv-head { display: flex; justify-content: flex-end; margin: -0.625rem -0.5rem -0.25rem; }
.s-rv-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.5rem;
  border: 1px solid transparent;
  border-radius: 0.75rem;
  background: none;
  color: #0369a1;          /* 5.67:1 on #fff (the store's link blue) */
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.s-rv-edit svg { flex: none; display: block; }
/* «خلصت»: the same control, tinted, so the mode shows without competing with
   «أكّد الطلب». #0c4a6e on #e0f2fe = 8.25:1 (the selected card's pair). */
.s-rv-edit[aria-pressed="true"] { background: #e0f2fe; border-color: #7dd3fc; color: #0c4a6e; padding: 0 0.875rem; }

/* the card being edited: one block per line */
.s-rv-editing { padding-top: 0.25rem; padding-bottom: 0.25rem; border-color: #7dd3fc; }
.s-rv-editing .s-rv-head { margin-top: 0; }
.s-rv-line { padding: 0.625rem 0; }
.s-rv-line + .s-rv-line { border-top: 1px solid #f3f4f6; }
/* name (+ size, + «معاه») | line total — the read row, with its ×N moved
   into the stepper under it */
.s-rv-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; align-items: baseline; }
.s-rv-name { font-size: 0.875rem; color: #111827; }
.s-rv-top .s-amount { font-size: 0.875rem; }
.s-rv-line .s-item-sides { margin-top: 0.1875rem; }
/* the controls row: the menu's own stepper at the start, «شيل» at the end */
.s-rv-ctl { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.5rem; }
.s-rv-line .s-step button { flex: none; }
.s-rv-line .s-step button[disabled] { background: #f1f5f9; color: #64748b; cursor: not-allowed; }
/* B9: a second press while one is in flight is QUEUED, not dropped
   (cart_inplace_controller.js), so the pressed control must still take the
   tap. busy's greying stays; only its pointer block is lifted, here only. */
.s-rv-line .s-busy-btn { pointer-events: auto; }
.s-rv-remove {
  flex: none;
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.875rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.75rem;
  background: #fff;
  color: #b91c1c;          /* 6.47:1 on #fff */
  font-size: 0.875rem;
  font-weight: 700;
  cursor: pointer;
}
/* «اتشال: … · رجّعه» in the removed line's place */
.s-rv-undo { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.25rem 0; }
/* flex 1 1 0: the words wrap inside their own box and «رجّعه» keeps the end
   side, as drawn — with auto the box asked for its whole max-content and the
   button fell to a second line under a long name (review pass, RC-6). */
.s-rv-undo-txt { flex: 1 1 0; min-width: 0; font-size: 0.875rem; color: #475569; }
.s-rv-undo form { flex: none; }
.s-rv-undo-btn {
  flex: none;
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.75rem;
  background: #fff;
  color: #0369a1;
  font-size: 0.875rem;
  font-weight: 700;
  cursor: pointer;
}
/* a press that FAILED offline: the old number stays, and this line says so */
.s-rv-msg {
  margin: 0.5rem 0 0;
  padding: 0.5rem 0.625rem;
  border-radius: 0.5rem;
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #991b1b;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.6;
}
/* under «رجّعه» the offline line takes the row's whole width */
.s-rv-undo .s-rv-msg { flex-basis: 100%; margin-top: 0; }
/* the last line removed: «السلة فاضية» under «رجّعه» */
.s-rv-empty { padding: 0.5rem 0 0.25rem; text-align: center; }
.s-rv-empty-t { margin: 0; font-size: 0.9375rem; font-weight: 700; color: #111827; }
.s-rv-undo + .s-rv-empty { margin-top: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #f3f4f6; }
/* INDEX P1, drawn: 7rem of room under the review (store.css's .s-main-bar
   leaves 6.5rem; v24's bar is 105.8px at 390) — on this screen only. */
.s-rv-end { margin-bottom: 0.5rem; }
