/* ==========================================================================
   OrderCaptain design system  —  oc.css
   Standalone. Does NOT depend on Bootstrap or the legacy Resi template.
   Load this INSTEAD of assets/css/style.css on revamped pages.

   Direction: "the invoice" — a mostly-light site with brand navy used sparingly
   (hero, closing band, footer only) and a white receipt as the proof device.
   Gold is a fill (never text on light) and carries the CTA on dark surfaces —
   brand blue on brand navy is only 1.17:1. Depth comes from hairline rules;
   buttons carry no shadow at all. Square edges instead of pills.
   Display: Alexandria (Latin + Arabic). Body: Manrope / IBM Plex Sans Arabic.
   Arabic: IBM Plex Sans Arabic (see [dir=rtl] block at the end).
   ========================================================================== */

/* ==========================================================================
   1. BRAND  —  the only block you normally need to edit.

   Colours are declared as "R,G,B" channel triplets so that every solid colour
   AND every translucent tint in this file derives from them. Change --c-brass
   here and the buttons, eyebrows, rules, menu highlights and focus rings all
   follow. (Hardcoding a literal colour anywhere is exactly what stops a
   palette swap from working, so nothing below hardcodes a brand colour.)
   ========================================================================== */
:root {
  /* --- brand colour channels (R,G,B) ---
     THE BRAND PALETTE. Two approved pairings:
       #264890 navy on #ebb44f gold   (4.63:1 — AA)
       #2a3edd blue on #ffffff white  (7.42:1 — AA)

     Gold on white is only 1.88:1, so gold NEVER carries text on a light
     surface. Use it as a fill behind navy, or as text on the navy ink. */
  --c-ink:       38, 72, 144;   /* #264890 brand navy — the ink */
  --c-ink-2:     30, 58, 118;   /* deeper navy, raised surface on ink */
  --c-ink-3:     62, 100, 176;  /* hairlines on ink */
  --c-brass:     235, 180, 79;  /* #ebb44f brand gold — the accent */
  --c-brass-lit: 245, 198, 110; /* gold, hover */
  --c-brass-deep: 145, 96, 8;   /* gold darkened for text on light (AA) */
  --c-action:    42, 62, 221;   /* #2a3edd brand blue — buttons and links */
  --c-action-lit: 62, 84, 240;  /* blue, hover */
  --c-live:      21, 128, 61;   /* live / positive state only */
  --c-white:     255, 255, 255;

  /* --- neutrals: a cool-neutral ramp keyed to the navy, so greys sit under
         the brand rather than fighting it. --- */
  --c-surface:   255, 255, 255; /* white — the invoice stock */
  --c-surface-2: 243, 245, 250; /* second surface, faint navy tint */
  --c-line:      219, 224, 235; /* hairline rules do the work shadows used to */
  --c-line-2:    180, 190, 212; /* hover borders */
  --c-text-2:    71, 85, 112;
  --c-text-mute: 88, 100, 124;   /* 5.95:1 on white — AA */
  --c-on-ink:    233, 238, 249;
  --c-on-ink-2:  190, 205, 232;  /* 5.43:1 on navy — AA */

  /* --- solid colours (built from the channels above) --- */
  --ink:        rgb(var(--c-ink));
  --ink-2:      rgb(var(--c-ink-2));
  --ink-3:      rgb(var(--c-ink-3));
  --brass:      rgb(var(--c-brass));
  --brass-lit:  rgb(var(--c-brass-lit));
  --brass-deep: rgb(var(--c-brass-deep));
  --action:     rgb(var(--c-action));
  --action-lit: rgb(var(--c-action-lit));
  --live:       rgb(var(--c-live));
  --white:      rgb(var(--c-white));
  --surface:    rgb(var(--c-surface));
  --surface-2:  rgb(var(--c-surface-2));
  --line:       rgb(var(--c-line));
  --line-2:     rgb(var(--c-line-2));
  --text:       rgb(var(--c-ink));
  --text-2:     rgb(var(--c-text-2));
  --text-mute:  rgb(var(--c-text-mute));
  --on-ink:     rgb(var(--c-on-ink));
  --on-ink-2:   rgb(var(--c-on-ink-2));

  /* --- derived tints: all follow the brand automatically --- */
  --brass-glow:   rgba(var(--c-brass), .65);  /* primary button shadow */
  --brass-wash:   rgba(var(--c-brass), .16);  /* gold wash behind navy */
  --brass-chip:   rgba(var(--c-brass), .22);  /* gold chip on ink */
  --brass-ring:   rgba(var(--c-action), .22); /* input focus ring — blue */
  --brass-pick:   rgba(var(--c-action), .06); /* selected choice background */
  --action-ring:  rgba(var(--c-action), .22);
  --live-chip:    rgba(var(--c-live), .16);
  --live-bg:      rgba(var(--c-live), .09);
  --live-line:    rgba(var(--c-live), .45);
  --on-dark-line: rgba(var(--c-on-ink), .28); /* ghost button border on dark */
  --raise:        rgba(var(--c-white), .035); /* raised panel on ink */
  --header-bg:    rgba(var(--c-surface), .88);

  /* --- elevation ---
     Print doesn't glow. Depth comes from hairline rules and a hard offset
     border; these stay only for the two places a lift genuinely helps. */
  --shadow-sm:  none;
  --shadow:     none;
  --shadow-lg:  4px 4px 0 rgba(var(--c-ink), 1);   /* hard offset, no blur */

  /* NOTE: buttons carry NO offset shadow. A hard offset needs two very
     different tones to read as an edge; on buttons it rendered three different
     ways across three surfaces (invisible navy-on-navy, a muddy band on gold,
     a white block on the hero) and never matched the flat ghost button beside
     it. Buttons are defined by fill + 1px border; the press is a translate. */

  /* --- typography ---
     Display: Alexandria, in BOTH languages (client choice, Sept 2026). It replaced
     Fraunces, which had no Arabic counterpart, so English headlines spoke in an
     editorial serif and Arabic ones in a plain sans: two brands on one site.
     Alexandria draws Latin and Arabic as one family, and its heavy geometric forms
     sit with the ORDER CAPTAIN wordmark.
     It is headlines, figures and invoice totals ONLY. It runs ~11% wider than
     Manrope and sets dark at text sizes, so body stays Manrope / Plex Arabic.
     Hierarchy comes from weight and width, not from a serif/sans contrast, so do
     not bring body copy up to 600 or headlines down to 400.
     Change these plus FONTS_LATIN/FONTS_ARABIC in build_pages.py. */
  --font-display: 'Alexandria', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-body:    'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-arabic:  'IBM Plex Sans Arabic', system-ui, sans-serif;

  --tracking-display: -.015em;   /* Latin display tracking; zeroed in RTL */
  --leading-body: 1.6;
  --leading-body-ar: 1.85;

  /* fluid type scale — top end pushed harder so the headline is the visual */
  --step--1: clamp(.82rem, .80rem + .10vw, .88rem);
  --step-0:  clamp(1rem, .97rem + .15vw, 1.075rem);
  --step-1:  clamp(1.2rem, 1.12rem + .38vw, 1.42rem);
  --step-2:  clamp(1.55rem, 1.34rem + .95vw, 2.25rem);
  --step-3:  clamp(2rem, 1.55rem + 2.05vw, 3.5rem);
  --step-4:  clamp(2.8rem, 1.9rem + 4.3vw, 6rem);

  /* --- shape & rhythm ---
     One small radius, and square where it counts. Four radii plus pill
     buttons is what makes everything read as the same rounded template. */
  --radius:     3px;
  --radius-sm:  2px;
  --radius-lg:  4px;
  --radius-pill: 2px;   /* kept for compatibility; buttons are not pills now */

  --wrap: 1220px;
  --gut: clamp(1.15rem, 4vw, 2.5rem);
  --section-y: clamp(4rem, 9vw, 7.5rem);
  --rule: 1px solid var(--line);   /* the shared hairline */

  --ease: cubic-bezier(.22, 1, .36, 1);
  --speed: .18s;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  color: var(--text);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: var(--tracking-display);
  margin: 0 0 .5em;
  text-wrap: balance;
}
h1 { font-size: var(--step-4); letter-spacing: -.022em; }
h2 { font-size: var(--step-3); letter-spacing: -.02em; }
h3 { font-size: var(--step-1); }
p  { margin: 0 0 1rem; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

:focus-visible {
  outline: 3px solid var(--action);
  outline-offset: 3px;
  border-radius: 4px;
}

/* skip link — keyboard users land here first.
   Hidden by translate, not by a negative offset: in RTL a left:-9999px
   element extends the document and creates a 10,000px horizontal scroll. */
.oc-skip {
  position: absolute; inset-inline-start: 0; top: 0; z-index: 200;
  background: var(--ink); color: var(--white); padding: .8rem 1.2rem;
  border-radius: 0 0 var(--radius-sm) 0;
  transform: translateY(-120%);
}
.oc-skip:focus { transform: none; }

/* ---------- layout ---------- */
.oc-wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gut); }
.oc-section { padding-block: var(--section-y); }
.oc-section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }
.oc-ink { background: var(--ink); color: var(--on-ink); }
.oc-ink h1, .oc-ink h2, .oc-ink h3 { color: var(--white); }
.oc-tint { background: var(--surface-2); }

/* mission statement (homepage "Who's behind it"): quoted verbatim from the company deck */
.oc-mission {
  margin: 1.5rem 0 0; padding-inline-start: 1rem;
  border-inline-start: 3px solid var(--brass);
  color: var(--text);
}
.oc-mission p { margin: 0; }
.oc-mission__label {
  font-size: .78rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-2); margin-bottom: .35rem !important;
}
[lang="ar"] .oc-mission__label { letter-spacing: 0; text-transform: none; }

.oc-grid { display: grid; gap: clamp(1.25rem, 2.5vw, 2rem); }
.oc-grid--2 { grid-template-columns: repeat(2, 1fr); }
.oc-grid--3 { grid-template-columns: repeat(3, 1fr); }
.oc-grid--4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) {
  .oc-grid--3, .oc-grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .oc-grid--2, .oc-grid--3, .oc-grid--4 { grid-template-columns: 1fr; }
}

/* ---------- shared bits ---------- */
/* Eyebrow is set in the BODY face, small caps-ish. A menu-section marker, not
   a pill badge.

   It used to draw a 34px rule before the label (`::before` with a 1px
   background). That rule appeared 7-8 times per page — 53 across the site —
   and at small sizes it reads as a dash before every heading, which is the
   same ornament-on-every-instance tell as the `01 / 02 / 03` counters and the
   per-item hairlines.

   Removing it exposed a second problem: the label was 18px above its headline
   but 115px below the section top, so it belonged to nothing and floated. The
   rule had been doing that anchoring. The fix is proximity rather than
   decoration — pull it tight to the headline (8px) so the two read as one
   unit, and nudge the size up so it is a label rather than a whisper next to a
   51px heading. */
.oc-eyebrow {
  font-family: var(--font-body);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--brass-deep);
  margin: 0 0 .5rem;
}
.oc-ink .oc-eyebrow,
.oc-hero .oc-eyebrow { color: var(--brass-lit); }

.oc-lede { font-size: var(--step-1); line-height: 1.5; color: var(--text-2); max-width: 58ch; }
.oc-ink .oc-lede,
.oc-hero .oc-lede { color: var(--on-ink-2); }

/* A lede used mid-section rather than in .oc-head — it needs its own top gap,
   since .oc-head's margin-bottom is not there to provide one. */
.oc-lede--mid { margin-top: clamp(2rem, 4vw, 3rem); }

/* Headings are left-aligned, always. Centred section heads repeated down a
   page are one of the clearest template tells, so there is no centred variant. */
.oc-head { max-width: 62ch; margin-bottom: clamp(2rem, 4vw, 3.25rem); }

/* ---------- buttons ----------
   Square, hard-edged, no glow. The primary carries a hard offset block
   instead of a blur — it reads as printed, and it is unmistakably not the
   default rounded-with-soft-shadow button. */
.oc-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  font-family: var(--font-body); font-weight: 600; font-size: var(--step--1);
  letter-spacing: .06em; text-transform: uppercase;
  padding: 1rem 1.6rem; border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer; text-align: center;
  transition: transform var(--speed) ease, box-shadow var(--speed) ease,
              background-color var(--speed) ease, color var(--speed) ease;
}
.oc-btn--primary {
  background: var(--action); color: var(--white);
  border-color: var(--action);
}
.oc-btn--primary:hover {
  background: var(--action-lit); border-color: var(--action-lit);
  transform: translateY(1px);
}
.oc-btn--ghost { border-color: var(--ink); color: var(--text); background: transparent; }
.oc-btn--ghost:hover { background: var(--ink); color: var(--surface); }
/* Ghost button on a dark surface. Opt in by adding .oc-on-dark to the
   container (the dark section classes below do it for you) — enumerating
   every dark wrapper is what let an invisible button ship once already. */
/* The hero is a NAVY surface (the white invoice needs a dark ground), so it
   belongs on this list. Check this whenever the hero surface changes — a
   mismatch here has shipped an invisible button twice. */
.oc-on-dark .oc-btn--ghost,
.oc-ink .oc-btn--ghost,
.oc-hero .oc-btn--ghost,
.oc-band .oc-btn--ghost,
.oc-footer .oc-btn--ghost { border-color: var(--on-ink); color: var(--white); }
.oc-on-dark .oc-btn--ghost:hover,
.oc-ink .oc-btn--ghost:hover,
.oc-hero .oc-btn--ghost:hover,
.oc-band .oc-btn--ghost:hover,
.oc-footer .oc-btn--ghost:hover { background: var(--on-ink); color: var(--ink); border-color: var(--on-ink); }

/* PRIMARY button on a dark surface — gold, not blue.
   Brand blue on brand navy is 1.17:1; WCAG 1.4.11 wants 3.0:1 for a UI
   component, so the blue button literally dissolved into the navy hero.
   Gold on navy is 4.63:1, and navy-on-gold is the approved brand pairing.
   Same selector list as the ghost rule above — one list to keep right. */
.oc-on-dark .oc-btn--primary,
.oc-ink .oc-btn--primary,
.oc-hero .oc-btn--primary,
.oc-band .oc-btn--primary,
.oc-footer .oc-btn--primary {
  background: var(--brass); color: rgb(var(--c-ink));
  border-color: var(--brass);
}
.oc-on-dark .oc-btn--primary:hover,
.oc-ink .oc-btn--primary:hover,
.oc-hero .oc-btn--primary:hover,
.oc-band .oc-btn--primary:hover,
.oc-footer .oc-btn--primary:hover {
  background: var(--brass-lit); border-color: var(--brass-lit);
  /* the translate must be restated — overriding only box-shadow here made the
     button appear to shrink instead of press down */
  transform: translateY(1px);
}
.oc-btn--block { display: flex; width: 100%; }
@media (prefers-reduced-motion: reduce) { .oc-btn:hover { transform: none; } }

.oc-cta-row { display: flex; flex-wrap: wrap; gap: .85rem; align-items: center; }

/* ---------- header ---------- */
.oc-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--header-bg);
  backdrop-filter: saturate(150%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.oc-header__inner { display: flex; align-items: center; gap: 1.5rem; min-height: 72px; }
.oc-logo { display: flex; align-items: center; flex: none; }
.oc-logo img { height: 38px; width: auto; }

.oc-nav { margin-inline-start: auto; display: flex; align-items: center; gap: .35rem; }
/* :not(.oc-btn) — otherwise this sets the CTA's text to --text-2 on top of
   the red fill and drops it to 1.5:1 contrast. */
.oc-nav a:not(.oc-btn) {
  padding: .55rem .85rem; border-radius: var(--radius-sm);
  font-size: var(--step--1); font-weight: 500; color: var(--text-2);
  transition: color .15s ease, background-color .15s ease;
}
.oc-nav a:hover, .oc-nav a[aria-current="page"] { color: var(--ink); background: var(--surface-2); }
/* Nav CTA. Two deliberate differences from a page button:
   1. No hard offset — a 3px drop shadow inside a 44px sticky header reads as
      the button peeling off the bar, and it fights the header's hairline.
   2. It stays BLUE even though the hero below it is gold. The nav sits on the
      white header, where gold is 1.88:1 and unusable; blue is 7.42:1 there.
      The nav CTA is the persistent, quiet one; the hero CTA is the loud one. */
.oc-nav .oc-btn {
  padding: .58rem 1.1rem; font-size: .78rem;
  box-shadow: none;
}
.oc-nav .oc-btn--primary:hover {
  transform: none;
  box-shadow: none;
  background: var(--action-lit); border-color: var(--action-lit);
}

.oc-menu-btn {
  display: none; margin-inline-start: auto;
  background: none; border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: .5rem .7rem; cursor: pointer; line-height: 1;
}
/* 1100, not 980: with Home + Packages the EN nav needs ~1085px for one row */
@media (max-width: 1100px) {
  .oc-menu-btn { display: block; }
  .oc-nav {
    display: none; position: absolute; inset-inline: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: .2rem;
    background: var(--surface); border-bottom: 1px solid var(--line);
    padding: .75rem var(--gut) 1.25rem;
    box-shadow: var(--shadow);
  }
  .oc-nav[data-open="true"] { display: flex; }
  .oc-nav a { padding: .75rem .5rem; font-size: var(--step-0); }
  .oc-nav .oc-btn { margin-top: .5rem; justify-content: center; }
}

/* ---------- hero ----------
   The headline IS the visual. Paper surface, oversized serif set tight, and
   a rule under the whole thing — nothing floats, nothing glows. */
/* Navy hero. The white invoice needs a dark ground to sit on — that contrast
   is the whole point of the device. */
.oc-hero {
  background: var(--ink); color: var(--on-ink);
  position: relative; overflow: hidden;
}
.oc-hero__inner {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: clamp(2rem, 6vw, 4.5rem); align-items: center;
  padding-block: clamp(3.5rem, 9vw, 7rem);
}
@media (max-width: 940px) { .oc-hero__inner { grid-template-columns: 1fr; align-items: start; } }
.oc-hero h1 {
  color: var(--white);
  /* Capped at 68px, not --step-4's 96px. Alexandria at 600 is a wide, heavy face: at the
     old size the ~50-character product headlines ran to 5 lines in the 614px hero column
     (cloud-POS, QR menu) and the homepage broke as "Turn one / order into". At 68px every
     hero sets in 2-4 lines, EN and AR, measured at 1280 and 1440. */
  font-size: clamp(2.4rem, 1.6rem + 3.2vw, 4.25rem);
  line-height: .98;
  letter-spacing: -.022em;
  /* 600: the weight the client approved in the Alexandria comparison. (The 400 here
     was tuned for Fraunces, whose serif contrast carried the weight on its own.) */
  font-weight: 600;
  /* No ch cap: `ch` is the width of this font's "0", which in Alexandria is wide enough
     that a 14ch/18ch measure squeezed the line below the column. The column is the measure. */
  max-width: none;
}
/* the emphasis word carries the gold — gold on navy is 9.5:1 */
em { font-style: normal; font-weight: 600; }
.oc-hero h1 em { font-style: normal; color: var(--brass); font-weight: 600; }   /* Alexandria has no italic */
[dir="rtl"] .oc-hero h1 em { font-style: normal; }   /* Arabic has no italic */
.oc-hero__sub {
  font-size: var(--step-1); color: var(--on-ink-2);
  max-width: 44ch; margin-bottom: 2rem;
}
.oc-hero__note {
  font-size: var(--step--1); color: var(--on-ink-2); margin-top: 1.4rem;
  padding-top: 1.1rem; border-top: 1px solid var(--ink-3);
  max-width: 44ch;
}

/* ---------- hero capture ----------
   One field + one button, in the first screen. The full demo form sits 5-7
   screens down (62-73% of page depth), and on a phone the hero invoice is
   already below the fold — so without this there is nothing convertible in the
   first screen at all. Toast runs the same pattern (email + Get Started).

   One input only: every extra field here is friction paid before any trust is
   earned. Qualification happens on the call, not in the hero. */
.oc-hero__capture { margin-top: 1.6rem; max-width: 30rem; }
.oc-hero__capture-row { display: flex; gap: .6rem; flex-wrap: wrap; }
.oc-hero__capture .oc-input {
  flex: 1 1 13rem; min-width: 0;
  /* The hero is navy; the default input is styled for light surfaces. */
  background: var(--white); color: rgb(var(--c-ink));
  border-color: var(--white);
}
.oc-hero__capture .oc-input::placeholder { color: rgb(var(--c-text-mute)); }
/* The phone input is dir="ltr" so a typed "+20 …" is not bidi-reordered, but
   that would set the Arabic placeholder left-aligned. RTL only while empty. */
[dir="rtl"] .oc-hero__capture .oc-input:placeholder-shown { direction: rtl; text-align: right; }
.oc-hero__capture .oc-btn { flex: 0 0 auto; }
.oc-hero__capture-note {
  font-size: var(--step--2); color: var(--on-ink-2);
  margin-top: .6rem;
}
/* The global `a` rule strips underlines, which left "fill in the full form"
   looking like plain text: same colour as the sentence around it. */
.oc-hero__capture-note a {
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
/* Status line for the inline form — errors must not be colour-only. */
.oc-hero__capture [data-form-status] {
  display: block; font-size: var(--step--2);
  color: var(--white); margin-top: .6rem;
}
/* Phone numbers stay LTR even in RTL. */
.oc-hero__capture input[type="tel"] { direction: ltr; text-align: start; }
[dir="rtl"] .oc-hero__capture input[type="tel"] { text-align: right; }

@media (max-width: 560px) {
  .oc-hero__capture .oc-input,
  .oc-hero__capture .oc-btn { flex: 1 1 100%; }

  /* ---- the mobile first screen ----
     A phone has ~650px of usable viewport after browser chrome, NOT the 812px
     an emulator reports. Measured on /loyalty.html before this block: the text
     stack ate 584px (header 73 + eyebrow 36 + h1 103 + sub 123 + capture 197 +
     note 52), leaving 66px — so ZERO pixels of the invoice were visible on
     load, on all ten pages. The hero ran 1106-1389px tall.

     Every rule below buys vertical space back, and the budget is tight enough
     that removing any one of them pushes the invoice's total row off-screen
     again. Measured after: invoice top 737 -> 467, ~92% of it visible, total
     row at ~640px, and the capture button still above the fold at 410px.

     Desktop is deliberately untouched — none of this applies above 560px. */

  /* Clamped, not rewritten: the full sentence stays in the DOM for crawlers and
     for desktop, and it cannot drift between the EN and AR builders. */
  .oc-hero__sub {
    font-size: var(--step-0);
    margin-bottom: .9rem;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Arabic runs a taller line-height (its own --leading), so two lines cost
     ~30px more than in English and push the total row to 676px. One line. */
  [dir="rtl"] .oc-hero__sub { -webkit-line-clamp: 1; }
  .oc-hero h1 {
    font-size: clamp(1.75rem, 7.8vw, 2.2rem);
    margin-bottom: .45rem;
  }
  /* Alexandria's Arabic runs wider than Plex's: at 7.8vw "حوّل أول طلب لعميل دائم." measured
     365px in a 338px column (108%) and wrapped, pushing ar/index and ar/cloud-pos past the
     fold (670 / 688px). 7vw brings the widest line to ~327px, 10px inside the column. */
  [dir="rtl"] .oc-hero h1 { font-size: clamp(1.6rem, 7vw, 2rem); }
  .oc-hero .oc-eyebrow { margin-bottom: .35rem; }
  .oc-hero__capture { margin-top: .65rem; }
  .oc-hero__capture-row { gap: .45rem; }
  /* 56px of top padding is generous on a 650px screen; the sticky header
     already separates the hero from the top of the window. */
  .oc-hero__inner { padding-block: 1.75rem 2.25rem; gap: 1.25rem; }

  /* The note and the capture's "or fill in the full form" link are both
     redundant here: the form is directly above, and #demo is reachable by
     scrolling. 92px of the first screen is worth more than either. */
  .oc-hero__note,
  .oc-hero__capture-note { display: none; }

  /* The homepage keeps a secondary anchor button above the capture; on a phone
     it pushes the capture past the fold to buy a jump-link the user can reach
     by scrolling anyway. The capture is the action here. */
  .oc-hero .oc-cta-row { display: none; }

  /* ---- compact invoice ----
     The dish rows are texture; the argument is the head (who, which visit), the
     subtotal, the line that makes the point, and the total. Hiding the dishes
     takes the card from 369px to ~225px, which is what gets the total row above
     the fold.

     The SUBTOTAL must survive: the argument line is a subtraction from it, so
     without it "Loyalty reward −165.00 → Total 0.00" has no referent and the
     arithmetic reads as arbitrary. Hence :nth-last-child rather than a blanket
     :not(--was):not(--live) — the subtotal is the last plain row, immediately
     before the argument rows.

     Hidden rows stay in the DOM, and the figure's aria-label already narrates
     the full receipt, so nothing is lost for assistive tech. */
  .oc-hero .oc-menu { padding: 14px 16px; }
  .oc-hero .oc-menu__head { padding: .2rem 0 .5rem; margin-bottom: .5rem; }
  .oc-hero .oc-menu__foot { margin-top: .6rem; padding-top: .6rem; }
  .oc-hero .oc-menu__row:not(.oc-menu__row--was):not(.oc-menu__row--live):not(:has(+ .oc-menu__row--was)):not(:has(+ .oc-menu__row--live)) {
    display: none;
  }
  /* The homepage receipt carries three argument rows: a redeemed loyalty item
     (Ice Cream, 300 points, 0.00) above the subtotal, then the struck-through
     delivery commission and "Your commission 0.00" below it. On a 650px screen
     that is two rows too many — all three pushed the total to 673/666px.

     Keep the REDEEMED ITEM and drop the commission pair. The commission
     comparison needs both of its rows to make sense (the struck-through figure
     is meaningless without the 0.00 beside it), it costs two rows to say one
     thing, and it is repeated in full one screen down in the figures section.
     The redemption is the loyalty payoff and the only retention beat on the
     first screen, which is the site's north star.

     Selector notes: the commission pair is matched as "a --was that has a
     --live after it" plus "a --live that follows a --was", which leaves a lone
     --live (the redeemed item) untouched. A blanket
     `--live:has(~ --live)` would hide the redeemed row instead, which is
     exactly the bug this replaced. Product pages have a single argument row and
     no --was, so this is a no-op for them. */
  .oc-hero .oc-menu__row--was:has(~ .oc-menu__row--live),
  .oc-hero .oc-menu__row--was ~ .oc-menu__row--live {
    display: none;
  }

  /* ---- compact menu card (QR menu page) ----
     The rules above are .oc-menu-specific and do not reach this component, so
     it needs its own budget. The capture button ends at ~400px, leaving ~250px.
     At full size the card ran to 837px — the sold-out row and the LIVE strip,
     which ARE the argument, both fell below the fold. Two dishes is enough to
     read as a menu; the sold-out row and the strip are non-negotiable. */
  .oc-hero .oc-menucard { gap: .5rem; padding: .7rem .85rem; }
  .oc-hero .oc-menucard__head { padding-bottom: .45rem; }
  .oc-hero .oc-menucard__name { font-size: var(--step-0); }
  .oc-hero .oc-menucard__list { gap: .45rem; }
  .oc-hero .oc-menucard__foot { padding-top: .45rem; }
  /* the frame's printed label costs ~34px here and the card says "Maison
     Thomas" immediately below it — redundant on a phone */
  .oc-hero .oc-shot__bar { display: none; }
  /* this page's sub is the longest of the four; one line keeps the card up */
  .oc-hero:has(.oc-menucard) .oc-hero__sub { -webkit-line-clamp: 1; }
  /* Which rows earn their place once the featured item is present: the feature
     (the hero dish), the POPULAR row, and the sold-out row. Those three are the
     capability — promote, flag, mark unavailable. A plain row adds nothing here
     and costs ~28px, and the card ran to 759px with it, dropping the LIVE strip
     below the fold. So plain rows go; tagged rows stay. */
  .oc-hero .oc-menucard__row:not(.oc-menucard__row--out):not(:has(.oc-menucard__tag)) {
    display: none;
  }
  /* The feature block is the point of the card on this page, so it is never
     hidden — but it is tightened. */
  .oc-hero .oc-menucard__feature { padding: .45rem .55rem; gap: .1rem; }
  .oc-hero .oc-menucard__feature-note { display: none; }
  /* Measured budget: the card is only 312px but starts at 422px, so the squeeze
     has to come from inside it. The section label ("Breakfast") costs 32px and
     is redundant once the feature block names the promoted dish; the head's
     second line (table / scanned) costs ~24px and the frame already says whose
     phone this is. Both go, which is the 86px needed to bring the LIVE strip
     back above the fold. */
  .oc-hero .oc-menucard__section { display: none; }
  .oc-hero .oc-menucard__meta { display: none; }
  .oc-hero .oc-menucard__head { padding-bottom: .35rem; }
  /* last 10px: the grid gap between the capture and the card */
  .oc-hero:has(.oc-menucard) .oc-hero__inner { gap: .75rem; }
}

/* ---------- hero stage: photograph behind, invoice in front ----------
   The invoice alone is vector artwork, so against a competitor's hero photo of
   a real kitchen it reads as a diagram rather than a product. Toast runs a
   full-bleed kitchen photo here; Odoo runs an autoplaying film.

   Tried and rejected: photo sized inside the second column with the invoice
   overlapping its corner. The column is ~450px and the invoice is ~560px tall,
   so the photo was reduced to a 118px sliver — a texture strip, not a scene.
   The photo therefore goes full-bleed BEHIND the hero, where it gets the whole
   width, and the invoice stays full-size in its column in front of it.

   The scrim is not decoration: it guarantees the headline keeps its contrast
   whatever the photograph underneath is doing. */
.oc-hero__photo {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  background: var(--ink);
}
/* object-position sits at 70% because the composition only leaves the right
   third of the frame visible: at 1440px the text column covers 0-53% and the
   invoice 59-89%, so the subject must land in the right third or it is hidden.
   Override per-image with a style attribute if a particular photo needs it. */
.oc-hero__photo img,
.oc-hero__photo video {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: 70% 50%;
}
[dir="rtl"] .oc-hero__photo img,
[dir="rtl"] .oc-hero__photo video { object-position: 30% 50%; }

/* Two-stop scrim, same construction as .oc-hero--media: heaviest at the text
   edge, lighter over the subject. RTL flips the direction below.

   Stops are .80 / .76 / .60, down from .95 / .90 / .60. The old pair was
   over-insuring: it held the headline at 7.01:1 against a 4.5 requirement and
   paid for that margin by erasing the photograph. Measured by quarter at
   1440px, luminance spread was 0.016 / 0.024 / 0.057 / 0.118 — so three
   quarters of the frame rendered as flat navy and only the right edge carried
   any image at all. We were shipping 76 KB and an LCP hit for one visible
   quarter, most of which the invoice then covers.

   The counter-intuitive part, and the reason the first attempt at this was too
   timid: the left of this photo is not dark. Raw, it is the BRIGHTEST and most
   detailed quarter (spread 0.89, mean 0.569). A scrim at .96 alpha compresses
   that 0.89 range into 0.025 — it does not scale detail, it crushes it. So
   small reductions buy almost nothing: .95 -> .84 moved left-half spread only
   0.025 -> 0.048, still nowhere near visible, while costing a full point of
   contrast.

   .80 is the defensible point on that curve. Measured against the real image:
   eyebrow 5.81, h1 5.73, sub 6.50, note 6.96, with left-half spread 0.058
   (2.4x the old 0.023). Against a pure-white photo, the luminance worst case,
   it still holds 5.53-6.62 — so it survives a future image swap, which is what
   the .95 was really guarding.

   Do not chase the full 0.118: that needs ~.65/.61, where the minimum lands at
   4.53:1. A 0.03 margin over AA is not a margin, and this file has already
   shipped one 4.37 failure found only by compositing real pixels. Re-measure
   with the canvas method whenever the photograph changes. */
.oc-hero__photo::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to right,
      rgba(var(--c-ink), .80) 0%,
      rgba(var(--c-ink), .76) 46%,
      rgba(var(--c-ink), .60) 100%),
    linear-gradient(to bottom, rgba(var(--c-ink), .30), rgba(var(--c-ink), .60));
}
[dir="rtl"] .oc-hero__photo::after {
  background:
    linear-gradient(to left,
      rgba(var(--c-ink), .80) 0%,
      rgba(var(--c-ink), .76) 46%,
      rgba(var(--c-ink), .60) 100%),
    linear-gradient(to bottom, rgba(var(--c-ink), .30), rgba(var(--c-ink), .60));
}

/* On a phone the scrim must run on the OTHER AXIS, and this is the whole
   reason the portrait asset needed one.

   The horizontal scrim above is built for the desktop layout: text in the left
   column, photo breathing in the right third, so it is heaviest at 0% and
   clears to .60 at 100%. A phone has no right-hand gap — the text column is
   full-bleed — so every pixel of the frame sits under the .90-.95 end. Shipping
   mobilebg.jpg under it rendered the photo as a flat navy field with a faint
   blur: AA passed at 6.7:1, and the photograph was invisible, which is the
   whole point of having one.

   So mobile runs top-to-bottom instead: heavy where the eyebrow/h1/sub stack
   sits (0-42%), clearing through the capture button, lightest over the counter
   at the bottom where the invoice overlaps and no body text runs. Direction-
   agnostic, so RTL needs no separate rule. */
@media (max-width: 700px) {
  .oc-hero__photo::after,
  [dir="rtl"] .oc-hero__photo::after {
    background:
      linear-gradient(to bottom,
        rgba(var(--c-ink), .92) 0%,
        rgba(var(--c-ink), .88) 30%,
        rgba(var(--c-ink), .72) 58%,
        rgba(var(--c-ink), .46) 100%);
  }
}

/* Stand-in until a real photograph exists.
   NOT a "missing asset" box — a hatched placeholder in the hero makes the whole
   page read as unfinished, which is worse than having no photo at all. This is
   an out-of-focus warm light field: the tonal impression of a lit cafe interior
   (bokeh highlights, a warm source at the subject end, falling to dark). It is
   built from brand tokens only, so it follows a palette swap, and it gives the
   white invoice something to sit against.

   When the real photo arrives this whole element is deleted, not restyled. */
.oc-hero__photo-ph {
  /* Above the scrim, not under it. The scrim is tuned to tame a real photograph
     (.95 navy at the text edge) and would crush this to a flat navy panel. The
     stand-in provides its own falloff, so it does not need taming. */
  position: absolute; inset: 0; z-index: 1;
  background-color: rgb(var(--c-ink-2));
  background-image:
    /* bokeh highlights, largest nearest the warm source */
    radial-gradient(circle at 78% 24%, rgba(var(--c-brass), .30) 0 6%, transparent 42%),
    radial-gradient(circle at 88% 52%, rgba(var(--c-brass), .20) 0 4%, transparent 34%),
    radial-gradient(circle at 66% 70%, rgba(var(--c-brass), .14) 0 3%, transparent 30%),
    radial-gradient(circle at 92% 12%, rgba(255, 255, 255, .10) 0 2.5%, transparent 26%),
    radial-gradient(circle at 58% 34%, rgba(255, 255, 255, .07) 0 2%, transparent 22%),
    /* warm wash at the subject end, falling away to the text end */
    linear-gradient(105deg,
      rgba(var(--c-ink), .96) 0%,
      rgba(var(--c-ink-2), .90) 38%,
      rgba(var(--c-brass-deep), .42) 78%,
      rgba(var(--c-brass), .28) 100%);
}
/* A photograph has depth of field; a flat gradient does not. A soft vignette is
   what keeps this reading as a defocused room rather than a CSS background. */
.oc-hero__photo-ph::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(ellipse 120% 100% at 72% 42%,
    transparent 0%, rgba(var(--c-ink), .55) 100%);
}
/* RTL: the headline moves to the right, so the warm end and the highlights have
   to move to the left with it — otherwise the light falls behind the text.
   linear-gradient has no logical-direction keyword, so this is mirrored by hand. */
[dir="rtl"] .oc-hero__photo-ph {
  background-image:
    radial-gradient(circle at 22% 24%, rgba(var(--c-brass), .30) 0 6%, transparent 42%),
    radial-gradient(circle at 12% 52%, rgba(var(--c-brass), .20) 0 4%, transparent 34%),
    radial-gradient(circle at 34% 70%, rgba(var(--c-brass), .14) 0 3%, transparent 30%),
    radial-gradient(circle at 8% 12%, rgba(255, 255, 255, .10) 0 2.5%, transparent 26%),
    radial-gradient(circle at 42% 34%, rgba(255, 255, 255, .07) 0 2%, transparent 22%),
    linear-gradient(255deg,
      rgba(var(--c-ink), .96) 0%,
      rgba(var(--c-ink-2), .90) 38%,
      rgba(var(--c-brass-deep), .42) 78%,
      rgba(var(--c-brass), .28) 100%);
}
[dir="rtl"] .oc-hero__photo-ph::after {
  background: radial-gradient(ellipse 120% 100% at 28% 42%,
    transparent 0%, rgba(var(--c-ink), .55) 100%);
}

/* The stage is the second grid column; the invoice sits in it at full size,
   above the photo's z-layer. */
.oc-hero__stage { position: relative; z-index: 1; }

/* ---------- media hero ----------
   Full-bleed photograph or video with the headline over it — the format the
   category actually uses (Toast runs a 1280x720 kitchen film here). Falls
   back to the plain navy hero + invoice when no asset exists, so the page is
   never waiting on material.

   The scrim is not decoration: it guarantees the overlaid text keeps its
   contrast whatever the photo underneath is doing. */
/* plain hero: no second column, so the headline gets the full measure */
.oc-hero--plain .oc-hero__inner { grid-template-columns: minmax(0, 1fr); }
.oc-hero--plain .oc-hero__inner > div { max-width: 60ch; }
.oc-hero--plain h1 { max-width: 18ch; }

.oc-hero--media { padding: 0; }
.oc-hero--media .oc-hero__media {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden;
}
.oc-hero--media .oc-hero__media img,
.oc-hero--media .oc-hero__media video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* two-stop scrim: solid enough at the text edge, clear over the subject.
   linear-gradient has no logical direction keyword, so RTL flips it below. */
.oc-hero--media .oc-hero__media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to right,
      rgba(var(--c-ink), .93) 0%,
      rgba(var(--c-ink), .82) 42%,
      rgba(var(--c-ink), .45) 100%),
    linear-gradient(to bottom, rgba(var(--c-ink), .25), rgba(var(--c-ink), .55));
}
[dir="rtl"] .oc-hero--media .oc-hero__media::after {
  background:
    linear-gradient(to left,
      rgba(var(--c-ink), .93) 0%,
      rgba(var(--c-ink), .82) 42%,
      rgba(var(--c-ink), .45) 100%),
    linear-gradient(to bottom, rgba(var(--c-ink), .25), rgba(var(--c-ink), .55));
}
/* single column when there is media — the photo is the second column */
.oc-hero--media .oc-hero__inner {
  grid-template-columns: minmax(0, 1fr);
  min-height: clamp(420px, 62vh, 640px);
  align-items: center;
}
.oc-hero--media .oc-hero__inner > div { max-width: 62ch; }

/* ---------- customer quote ----------
   Portrait + quote + attribution. Odoo puts one of these in the second
   viewport; a named face is proof in a way a logo row is not. */
.oc-quote { display: grid; grid-template-columns: auto 1fr; gap: clamp(1.25rem, 3vw, 2.25rem); align-items: start; }
@media (max-width: 620px) { .oc-quote { grid-template-columns: 1fr; } }
.oc-quote__portrait {
  width: 96px; height: 96px; border-radius: 50%;
  object-fit: cover; flex: none;
  border: 2px solid var(--brass);
}
.oc-quote__ph {
  width: 96px; height: 96px; border-radius: 50%;
  background: var(--surface-2); border: 2px solid var(--line);
  display: grid; place-items: center; color: var(--text-mute);
}
.oc-ink .oc-quote__ph { background: var(--ink-2); border-color: var(--ink-3); color: var(--on-ink-2); }
.oc-quote blockquote {
  margin: 0; font-family: var(--font-display);
  font-size: var(--step-2); line-height: 1.25; letter-spacing: -.02em;
  color: var(--text);
}
.oc-ink .oc-quote blockquote { color: var(--white); }
.oc-quote__who {
  margin-top: 1.1rem; font-size: var(--step--1); color: var(--text-2);
}
.oc-ink .oc-quote__who { color: var(--on-ink-2); }
.oc-quote__who b { color: var(--text); font-weight: 600; }
.oc-ink .oc-quote__who b { color: var(--white); }

/* ==========================================================================
   SIGNATURE: the invoice
   A white receipt on the navy ground — the artefact the restaurant and the
   guest both recognise. The last line is the argument: a loyalty reward that
   zeroes the bill, a commission line that reads 0.00, a sold-out mark.
   Static. No animation, no fake dashboard chrome.
   ========================================================================== */
.oc-menu {
  background: var(--white);
  border: 0;
  border-radius: var(--radius-sm);
  box-shadow: 0 24px 48px -20px rgba(0, 0, 0, .55);
  padding: clamp(1.5rem, 3vw, 2.1rem) clamp(1.4rem, 2.8vw, 2rem);
  color: rgb(var(--c-ink));
  max-width: 420px; margin-inline: auto;
  position: relative;
}
/* torn/perforated top edge, the way a printed receipt reads */
.oc-menu::before {
  content: ""; position: absolute; inset-inline: 0; top: 0; height: 4px;
  background: repeating-linear-gradient(90deg,
    var(--brass) 0 14px, transparent 14px 24px);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.oc-menu__head {
  display: flex; align-items: baseline; gap: .75rem;
  padding: .5rem 0 1rem; margin-bottom: 1.1rem;
  border-bottom: 1px solid rgb(var(--c-line));
}
.oc-menu__name {
  font-family: var(--font-display); font-size: var(--step-1);
  font-weight: 600; letter-spacing: -.01em;
  color: rgb(var(--c-ink));
}
.oc-menu__meta {
  margin-inline-start: auto; text-align: end;
  font-size: .64rem; letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--c-text-mute));
}
.oc-menu__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.oc-menu__row { display: flex; align-items: baseline; gap: .5rem; }
.oc-menu__dish { font-size: var(--step--1); color: rgb(var(--c-text-2)); }
/* dotted leader between item and price. ::after is the LAST flex item in DOM
   order, so without `order` on the price the leader drew after it and every
   price sat ragged against its label instead of in a right-hand column. */
.oc-menu__row::after {
  content: ""; flex: 1 1 auto; align-self: end;
  height: 1px; margin-bottom: .32em;
  background-image: radial-gradient(circle, rgb(var(--c-line-2)) 1px, transparent 1px);
  background-size: 5px 1px; background-repeat: repeat-x;
}
.oc-menu__price {
  order: 1;
  font-size: var(--step--1); font-weight: 600;
  font-variant-numeric: tabular-nums; color: rgb(var(--c-ink));
  direction: ltr; unicode-bidi: isolate;
}
/* "what it would have cost" — the cost being avoided, struck and muted so the
   eye moves past it to the live line below. Only the price is struck; striking
   the label too would read as a cancelled item rather than a saved cost. */
.oc-menu__row--was {
  margin-top: .35rem; padding-top: .7rem;
  border-top: 1px solid rgb(var(--c-line));
}
.oc-menu__row--was .oc-menu__dish { color: rgb(var(--c-text-mute)); }
.oc-menu__row--was .oc-menu__price {
  color: rgb(var(--c-text-mute));
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.oc-menu__row--was::after { border-bottom-color: transparent; }

/* the line that makes the argument. It carries the separator rule only when it
   is NOT preceded by a --was line, which already drew one. */
.oc-menu__row--live {
  margin-top: .35rem; padding-top: .7rem;
  border-top: 1px solid rgb(var(--c-line));
}
.oc-menu__row--was + .oc-menu__row--live {
  margin-top: 0; padding-top: 0; border-top: 0;
}
.oc-menu__row--live .oc-menu__dish,
.oc-menu__row--live .oc-menu__price {
  color: rgb(var(--c-action)); font-weight: 600;
}
.oc-menu__row--live::after { border-bottom-color: transparent; }

.oc-menu__foot {
  position: relative;             /* the stamp anchors to this row, see below */
  margin-top: 1.2rem; padding-top: 1rem;
  /* Reserve for the absolutely-positioned stamp. It cannot push this row, so
     the gap has to fit the LONGEST stamp label, not a typical one: "Paid" is
     4 characters but "Offline" is 7 and overlapped the total at 4.4rem (and
     still at 8ch — `ch` here is measured in this row's own 0.64rem font, so it
     badly under-reserves). Measured worst case is "Offline" at ~85px. */
  padding-inline-start: 6.2rem;
  border-top: 2px solid rgb(var(--c-ink));
  display: flex; align-items: center; gap: .6rem;
  font-size: .64rem; letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--c-text-mute));
}
.oc-menu__foot b {
  margin-inline-start: auto;
  font-family: var(--font-display); font-weight: 600; letter-spacing: -.01em;
  font-size: var(--step-1); text-transform: none;
  color: rgb(var(--c-ink));
  direction: ltr; unicode-bidi: isolate;
}
/* Origin strip — names which product produced which line. Without it the
   four-product attribution is invisible and the receipt reads as one product. */
.oc-menu__origin {
  margin-top: 1rem; padding-top: .9rem;
  border-top: 1px dotted rgb(var(--c-line-2));
  display: flex; flex-wrap: wrap; gap: .35rem .9rem;
  font-size: .64rem; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(var(--c-text-mute));
}
.oc-menu__origin span { display: inline-flex; align-items: center; gap: .35rem; }
.oc-menu__origin span::before {
  content: ""; width: 5px; height: 5px; flex: none;
  background: var(--brass); border-radius: 50%;
}

/* PAID stamp — gold block with navy text, the approved brand pairing.
   Sits at the start edge of the total row; .oc-menu__foot reserves space for
   it with padding-inline-start. */
.oc-menu__stamp {
  /* Anchored to the total row (.oc-menu__foot is the containing block), NOT to
     the card bottom. The origin strip below it wraps to one or two lines
     depending on language and width, so any fixed offset from the bottom either
     overlaps the strip or floats away from the total. */
  position: absolute; inset-inline-start: 0;
  top: 50%; transform: translateY(-50%) rotate(-9deg);
  background: var(--brass); color: rgb(var(--c-ink));
  font-size: .62rem; font-weight: 700; letter-spacing: .2em;
  text-transform: uppercase;
  padding: .3rem .7rem; border: 2px solid rgb(var(--c-ink));
  border-radius: var(--radius-sm);
}

/* ---------- menu card ----------
   The QR menu's artefact, and deliberately NOT the invoice.

   The invoice works on the other three product pages because each has a
   transaction whose bottom line the product changes: commission 0.00, a bill
   that goes to zero on the 4th visit, an order that totals and prints while
   offline. The QR menu has no transaction — it sits before money exists, and a
   guest may never order. Its value is that the menu is *current*, which is not
   a figure. The old hero put a timestamp in the total slot ("Last edit 2s
   ago"), i.e. a receipt that never gets paid.

   So this shows what the guest actually looks at: the menu on a phone, with one
   dish struck through as sold out and a LIVE strip. Chrome comes from
   .oc-shot__frame (via _frame() in build_pages.py) so it stays in the same
   visual family as the invoice — white card, hard offset, printed label. */
.oc-menucard { display: grid; gap: .85rem; padding: 1.25rem 1.35rem; }
.oc-menucard__head {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  padding-bottom: .8rem; border-bottom: 1px solid rgb(var(--c-line));
}
.oc-menucard__name {
  font-family: var(--font-display); font-size: var(--step-1);
  color: rgb(var(--c-ink)); letter-spacing: -.01em;
}
.oc-menucard__meta {
  margin-inline-start: auto; text-align: end;
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: rgb(var(--c-text-mute)); line-height: 1.5;
}
.oc-menucard__section {
  font-size: .62rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--brass-deep); font-weight: 600;
}

/* ---- featured item ----
   The menu does not just inform, it merchandises: the restaurant promotes a
   hero dish and flags the ones guests order most. That is the capability that
   raises average order value, and the page said nothing about it — all six
   feature cards were about convenience and findability.

   Gold TINT, not solid gold: on a tint navy body text stays safe, whereas a
   solid gold fill admits navy only (white is 1.88:1, grey 3.99:1, blue 3.95:1).
   The left rule carries the accent at full strength instead. */
.oc-menucard__feature {
  display: grid; gap: .15rem;
  padding: .6rem .7rem;
  background: var(--brass-chip);
  border-inline-start: 3px solid var(--brass);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
[dir="rtl"] .oc-menucard__feature { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
/* Navy, not brass-deep. brass-deep is AA on white (5.41:1) but only 2.88:1 on
   this gold tint — the tint darkens the background and eats the margin. On any
   gold surface, tint or solid, navy is the colour that holds. */
.oc-menucard__feature-tag {
  font-size: .56rem; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: rgb(var(--c-ink));
}
.oc-menucard__feature-row { display: flex; align-items: baseline; gap: .6rem; }
.oc-menucard__feature-dish {
  font-size: var(--step--1); font-weight: 600; color: rgb(var(--c-ink));
}
.oc-menucard__feature-price {
  margin-inline-start: auto;
  font-size: var(--step--1); font-weight: 700; color: rgb(var(--c-ink));
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}
/* Neither grey survives this tint: --text-mute is 3.17:1 on it and --text-2 is
   3.99:1, though both are AA on white. The gold tint has a much higher
   luminance than paper, so every muted tone loses its margin. Navy at .82 alpha
   keeps the note visibly secondary to the dish name while clearing AA. */
.oc-menucard__feature-note {
  font-size: .64rem; color: rgba(var(--c-ink), .82); line-height: 1.4;
}
.oc-menucard__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.oc-menucard__row { display: flex; align-items: baseline; gap: .6rem; }
.oc-menucard__dish { font-size: var(--step--1); color: rgb(var(--c-text-2)); }
.oc-menucard__price {
  margin-inline-start: auto;
  font-size: var(--step--1); font-weight: 600; color: rgb(var(--c-ink));
  font-variant-numeric: tabular-nums;
  /* prices read left-to-right even on the RTL pages */
  direction: ltr; unicode-bidi: isolate;
}
/* The sold-out row is the proof that the menu is live, so it must stay legible:
   --text-mute is 5.95:1 on white, and the strike is a 1px rule rather than a
   colour change, so the contrast requirement is still met. */
.oc-menucard__row--out .oc-menucard__dish,
.oc-menucard__row--out .oc-menucard__price {
  color: rgb(var(--c-text-mute));
  text-decoration: line-through; text-decoration-thickness: 1px;
}
.oc-menucard__row--out .oc-menucard__price { text-decoration: none; }
.oc-menucard__tag {
  font-size: .58rem; letter-spacing: .14em; text-transform: uppercase;
  font-weight: 600; color: rgb(var(--c-ink));
  background: var(--brass); border-radius: var(--radius-sm);
  padding: .12rem .4rem; white-space: nowrap;
}
/* POPULAR is an outline, SOLD OUT is a solid chip. Both were solid gold at
   first, which made an "order this" flag and an "unavailable" flag read as the
   same kind of thing when they are opposites. Must come AFTER the base rule —
   defined before it, the base won on source order and the modifier did nothing.
   Outline keeps brass-deep on white (5.41:1) rather than navy on gold. */
.oc-menucard__tag--pop {
  background: transparent; color: var(--brass-deep);
  border: 1px solid var(--brass); padding: .1rem .35rem;
}
.oc-menucard__foot {
  display: flex; align-items: center; gap: .5rem;
  padding-top: .8rem; border-top: 2px solid rgb(var(--c-ink));
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: rgb(var(--c-text-mute));
}
/* Green is reserved for positive states — a live menu is exactly that. */
.oc-menucard__live {
  display: inline-flex; align-items: center; gap: .35rem;
  color: var(--live); font-weight: 600;
}
.oc-menucard__live::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.oc-menucard__stamp { margin-inline-start: auto; }

/* ---------- trust strip ---------- */
.oc-trust { border-block: 1px solid var(--line); background: var(--surface-2); }
.oc-trust__inner { display: flex; align-items: center; gap: clamp(1.5rem,4vw,3rem); flex-wrap: wrap; padding-block: 1.6rem; }
.oc-trust__label {
  font-size: var(--step--1); color: var(--text-mute); font-weight: 500;
  flex: none; max-width: 26ch;
}
/* The count is the proof; the rest of the line is context. Display face and
   ink colour lift it out of the muted label without adding a second surface. */
.oc-trust__label b {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--step-1); color: var(--ink);
  letter-spacing: -.01em; margin-inline-end: .15rem;
  font-variant-numeric: tabular-nums;
}
.oc-trust__logos { display: flex; align-items: center; gap: clamp(1rem,3vw,2.25rem); flex-wrap: wrap; }
/* Client logos sit in COLOUR, slightly held back at rest and full strength on
   hover or focus.

   This replaced grayscale(1) + opacity .55 + mix-blend-mode: multiply, which
   only worked for marks that were dark artwork on real transparency. Five of
   the eleven supplied logos are artwork baked onto a coloured tile (AGMD red,
   Beitward magenta, TheMood black, Sushi9 near-black, SakuraSushi sage), and
   greyscale turned those tiles into grey rectangles that read as broken images.

   Worth noting: removing `multiply` alone does NOT fix them — the tile is in
   the pixels, not produced by the blend mode. Only colour makes a tiled logo
   read as a brand mark rather than a defect. In colour all eleven look
   deliberate, so every client can be shown instead of six.

   No grayscale means no contrast requirement either: these are decorative
   brand marks with the client name in `alt`, not text or UI controls. */
.oc-trust__logo { position: relative; display: inline-flex; align-items: center; }
.oc-trust__logos img {
  height: 42px; width: auto;
  opacity: .75;
  transition: opacity .25s ease, transform .25s ease;
  display: block;
}
.oc-trust__logos a:hover img,
.oc-trust__logos a:focus-visible img,
.oc-trust__logo:hover img,
.oc-trust__logo:focus-visible img { opacity: 1; }

/* The client name on hover. Several marks are wordless or set the brand in a
   script that does not resolve at 42px, so colour alone still leaves the
   reader guessing who these restaurants are. The name is what turns the strip
   from decoration into eleven checkable references.

   Absolutely positioned above the mark, so revealing it never reflows a row
   that is already wrapping to three lines. Navy on --surface-2 is 8.9:1, and
   it is aria-hidden because the same string is already the img alt — a screen
   reader should hear the client once, not twice. tabindex on the wrapper
   gives keyboard users the same reveal. */
.oc-trust__name {
  position: absolute; bottom: calc(100% + .45rem); left: 50%;
  transform: translateX(-50%) translateY(3px);
  padding: .18rem .45rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: .72rem; line-height: 1.2; font-weight: 600;
  color: var(--ink); white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.oc-trust__logo:hover .oc-trust__name,
.oc-trust__logo:focus-visible .oc-trust__name {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
/* No focus-ring rule here on purpose: the global :focus-visible (outline 3px
   var(--action)) already covers the wrapper. */
@media (prefers-reduced-motion: reduce) {
  .oc-trust__name { transition: opacity .01ms; transform: translateX(-50%); }
  .oc-trust__logo:hover .oc-trust__name,
  .oc-trust__logo:focus-visible .oc-trust__name { transform: translateX(-50%); }
}
/* Eleven logos wrap to three rows on a phone (324px of strip). Below the fold,
   so it costs nothing above it, but a smaller mark keeps the section from
   dominating the scroll. Touch devices have no hover, so they get the stronger
   .85 resting state.

   The name does NOT disappear here: it stops being a tooltip and becomes a
   static caption under each mark. Hiding it was the first attempt and it was
   wrong — a phone reader got eleven unlabelled marks with no way to resolve
   them, which is exactly the problem the name was added to solve, and touch is
   where the marks are smallest (32px) and hardest to read. So the wrapper
   stacks, the chip styling drops (no card, no border, no absolute position),
   and the name simply sits under its logo as a label. */
@media (max-width: 560px) {
  .oc-trust__logos { gap: .9rem 1.1rem; align-items: flex-start; }
  .oc-trust__logos img { height: 32px; opacity: .85; }
  .oc-trust__logo {
    flex-direction: column; align-items: center; gap: .3rem;
    max-width: 5.5rem;
  }
  .oc-trust__name {
    position: static; transform: none;
    opacity: 1; pointer-events: auto;
    padding: 0; background: none; border: 0;
    /* .72rem, not smaller: .62rem computed to 9.92px, which is below the ~11px
       floor where a caption stops being comfortably readable — and this is the
       one context where the mark itself is smallest (32px). */
    font-size: .72rem; font-weight: 500;
    color: var(--text-2); white-space: normal;
    text-align: center; line-height: 1.25;
  }
}

/* ---------- cards ----------
   No card. Ruled columns on the paper: a hairline above, content below.
   Boxes-with-shadows repeated in a grid is the template look. */
.oc-card {
  background: transparent; border: 0;
  border-top: 1px solid var(--ink);
  border-radius: 0; padding: 1.3rem 0 0;
  transition: border-color .2s ease;
}
.oc-card h3 { margin-bottom: .45rem; font-size: var(--step-1); }
.oc-card p { color: var(--text-2); font-size: var(--step--1); line-height: 1.6; }
a.oc-card:hover { border-top-color: var(--action); }

/* the icon is a rule-mark, not a rounded chip */
.oc-card__icon {
  width: auto; height: auto; border-radius: 0; margin-bottom: .9rem;
  display: block; background: none; color: var(--action);
}
.oc-card__more {
  display: inline-flex; align-items: center; gap: .4rem; margin-top: 1rem;
  font-family: var(--font-body); font-weight: 600; font-size: .74rem;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--brass-deep);
  border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.oc-card--ink { background: transparent; border-top-color: var(--on-ink-2); }
.oc-card--ink p { color: var(--on-ink-2); }
.oc-ink .oc-card { border-top-color: var(--on-ink-2); }
.oc-ink .oc-card p { color: var(--on-ink-2); }
.oc-ink .oc-card__icon { color: var(--brass-lit); }
.oc-ink .oc-card__more { color: var(--brass-lit); }

/* product entry gets a heavier rule — it is the thing being sold */
.oc-card--product { border-top-width: 3px; border-top-color: var(--ink); }
.oc-card--product:hover { border-top-color: var(--action); }

/* ---------- outcome figures ----------
   Set as a priced list, not three centred stat blobs. */
/* Figures default to a LIGHT surface. Navy is reserved for the hero and the
   closing band — three or four dark bands per page made the whole site read
   as blue. */
.oc-figs { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1.5rem,3vw,2.5rem); }
@media (max-width: 720px) { .oc-figs { grid-template-columns: 1fr; } }
.oc-figs > div { border-top: 1px solid var(--ink); padding-top: 1.1rem; }
.oc-fig__n {
  font-family: var(--font-display); font-size: var(--step-3); font-weight: 600;
  color: var(--action); letter-spacing: -.02em; line-height: 1;
}
.oc-fig__l { font-size: var(--step--1); color: var(--text-2); margin-top: .6rem; max-width: 26ch; }
/* on-dark variants, kept for any section that is still ink */
.oc-ink .oc-figs > div { border-top-color: var(--on-ink-2); }
.oc-ink .oc-fig__n { color: var(--brass-lit); }
.oc-ink .oc-fig__l { color: var(--on-ink-2); }

/* The invoice, placed as evidence under the figures it supports. On a light
   ground the white receipt needs its own panel to still read as an object. */
.oc-figs__aside {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  background: var(--ink);
  padding: clamp(1.75rem, 4vw, 3rem);
  border-radius: var(--radius-sm);
  display: grid; justify-items: center;
}
.oc-ink .oc-figs__aside { background: transparent; padding: 0; }

/* ---------- steps / how it works (a real sequence: numbering earns its place) ---------- */
/* Column count follows the number of steps, so a sequence never orphans its
   last step on a row of its own — which breaks the reading of it as a sequence.
   3 is the default; :has() gives 4 a row of its own width. */
.oc-flow {
  counter-reset: ocflow; display: grid; gap: 1.6rem 1.25rem;
  grid-template-columns: repeat(3,1fr);
  padding-top: 1.6rem; border-top: 1px solid var(--line);
}
.oc-flow:has(> :nth-child(4)) { grid-template-columns: repeat(4,1fr); }
@media (max-width: 1080px) { .oc-flow:has(> :nth-child(4)) { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 860px) { .oc-flow, .oc-flow:has(> :nth-child(4)) { grid-template-columns: 1fr; } }
/* No leading-zero counters and no rule above every item.
   "01 / 02 / 03" set in a display face above a hairline, repeated down a page,
   is one of the clearest template tells — it decorates a sequence instead of
   showing one. The steps still need order, so the number stays, but plain and
   small: 1. 2. 3. reading as part of the heading rather than as ornament.
   The rule moves to the flow container, so the group is separated once instead
   of each item being boxed. */
.oc-flow__item { counter-increment: ocflow; }
.oc-flow__item h3::before {
  content: counter(ocflow) ". ";
  color: var(--brass-deep); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.oc-flow__item h3 { font-size: var(--step-0); margin-bottom: .35rem; }
.oc-flow__item p { color: var(--text-2); font-size: var(--step--1); }

/* ==========================================================================
   VISUAL LAYER — screenshots, video, alternating rows.

   Each component has a placeholder state that is meant to look deliberate,
   so a page reads as finished before the real asset exists. Swapping in a
   real image is a one-line change in the builder; nothing here depends on a
   specific file. All colours come from the brand tokens, so a palette swap
   carries through.
   ========================================================================== */

/* ---------- product screenshot ----------
   A plate, not a browser window: hairline border, hard offset, no fake
   traffic-light chrome (that chrome is itself a generated-site cliché). */
.oc-shot__frame {
  border-radius: var(--radius-sm);
  border: 1px solid var(--ink);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.oc-ink .oc-shot__frame { border-color: var(--on-ink-2); background: var(--ink-2); }

/* caption strip: a printed label, not a window bar */
.oc-shot__bar {
  display: flex; align-items: center; gap: .5rem;
  padding: .6rem .9rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.oc-ink .oc-shot__bar { background: var(--ink-2); border-bottom-color: var(--ink-3); }
.oc-shot__dot { display: none; }   /* retired: the traffic-light dots */
.oc-shot__name {
  font-family: var(--font-body); font-size: .68rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-mute);
}
.oc-ink .oc-shot__name { color: var(--on-ink); }
.oc-shot__frame img { width: 100%; height: auto; display: block; }

/* two language renders of one screen, side by side (homepage localisation block) */
.oc-shot__pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--line);
}
.oc-shot__pair figure { margin: 0; background: var(--surface); }
.oc-shot__pair figcaption {
  padding: .45rem .6rem; font-size: .68rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-mute);
  border-top: 1px solid var(--line);
}

/* three screens of one journey (homepage product strip: menu -> checkout ->
   rewards). Sits right under the trust strip, so the first real screenshot
   lands ~1 screen down instead of ~4.7. */
.oc-shot--trio { max-width: 900px; margin-inline: auto; }
.oc-shot__pair--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 560px) {
  /* three ~105px phones are unreadable: scroll-snap instead, one screen
     readable and the next peeking in. Overflow stays inside the frame. */
  .oc-shot__pair--3 {
    grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 62%;
    overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
  }
  .oc-shot__pair--3 figure { scroll-snap-align: start; }
}

/* placeholder — a solid tinted panel with a small label. Dashed boxes read
   as unfinished; this reads as art direction until real material lands. */
.oc-shot__ph {
  aspect-ratio: 16 / 10;
  display: grid; place-content: center; justify-items: center; gap: .6rem;
  padding: 2rem;
  background: var(--surface-2);
  border: 0;
  border-radius: 0;
  text-align: center;
}
.oc-ink .oc-shot__ph { background: var(--ink-3); }
.oc-shot__ph--tall { aspect-ratio: 9 / 16; max-height: 620px; }
.oc-shot__ph svg { color: var(--line-2); }
.oc-ink .oc-shot__ph svg { color: var(--on-ink-2); opacity: .5; }
.oc-shot__ph b {
  font-family: var(--font-body); font-size: .68rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-mute);
}
.oc-ink .oc-shot__ph b { color: var(--on-ink); }
.oc-shot__ph span { display: none; }   /* no "drop the screenshot here" note */
.oc-shot__caption {
  margin-top: .9rem; font-size: var(--step--1);
  color: var(--text-mute); text-align: start;
}

/* ---------- live demo ----------
   A link to the real sample restaurant, used where a demo film would have gone.
   It reuses the .oc-shot frame so the page keeps one visual family, but the
   frame itself becomes the link, with a real button under it: a framed image
   that is clickable but looks inert gets missed, and the button is what the
   scanning reader actually sees.

   The lift on hover is the only place a shot frame moves. It is deliberate —
   it is the affordance that says "this one is a door, the others are pictures."
   Kept to 2px and paired with a border-colour change so it reads on a trackpad
   and in reduced-motion (where the transform is dropped but the colour stays). */
.oc-shot__frame--link {
  display: block; text-decoration: none; color: inherit;
  transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.oc-shot__frame--link:hover,
.oc-shot__frame--link:focus-visible { transform: translateY(-2px); border-color: var(--action); }
.oc-shot__cta {
  margin-top: 1.1rem; display: flex; flex-wrap: wrap; gap: .85rem;
  justify-content: center;
}
@media (prefers-reduced-motion: reduce) {
  .oc-shot__frame--link { transition: border-color var(--speed) var(--ease); }
  .oc-shot__frame--link:hover,
  .oc-shot__frame--link:focus-visible { transform: none; }
}

/* ---------- demo video ---------- */
.oc-video { margin-inline: auto; }
.oc-video--wide { max-width: 900px; }
.oc-video--tall { max-width: 360px; }
.oc-video__box {
  position: relative;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--ink);
  background: var(--surface-2);
  box-shadow: var(--shadow-lg);
}
.oc-video--wide .oc-video__box { aspect-ratio: 16 / 9; }
.oc-video--tall .oc-video__box { aspect-ratio: 9 / 16; }
.oc-video__box iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
/* placeholder — flat tinted panel, one square play mark. No gradient wash. */
.oc-video__ph {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center; gap: 1rem;
  background: var(--surface-2);
  text-align: center; padding: 1.5rem;
}
.oc-video__play {
  width: 58px; height: 58px; border-radius: 0;
  display: grid; place-items: center;
  background: transparent; border: 1px solid var(--action);
  color: var(--action);
}
.oc-video__ph b {
  font-family: var(--font-body); font-size: .68rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-mute);
}
/* :not() keeps this off .oc-video__play, which is also a <span> — without it
   the caption rule wins on source order and restyles the play mark. */
.oc-video__ph span:not(.oc-video__play) { display: none; }

/* ---------- alternating split row ----------
   Logical properties only, so RTL mirrors without extra rules. */
.oc-split {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(1.75rem, 5vw, 4rem); align-items: center;
}
.oc-split + .oc-split { margin-top: clamp(3rem, 7vw, 5.5rem); }
/* reverse every other row: media moves to the other side */
.oc-split--flip .oc-split__media { order: -1; }
@media (max-width: 860px) {
  .oc-split { grid-template-columns: 1fr; }
  .oc-split--flip .oc-split__media { order: 0; }
}
.oc-split__body h3 { font-size: var(--step-2); margin-bottom: .6rem; }
.oc-split__body p { color: var(--text-2); }
.oc-ink .oc-split__body p { color: var(--on-ink-2); }
.oc-split__list {
  list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: .6rem;
}
.oc-split__list li {
  display: flex; gap: .6rem; align-items: flex-start;
  font-size: var(--step--1); color: var(--text-2);
}
.oc-split__list svg { color: var(--action); flex: none; margin-top: .25rem; }
.oc-ink .oc-split__list li { color: var(--on-ink-2); }
.oc-ink .oc-split__list svg { color: var(--brass-lit); }

/* ---------- form ---------- */
.oc-form-card {
  background: var(--surface); border: 1px solid var(--ink);
  border-radius: var(--radius-sm); padding: clamp(1.5rem, 3vw, 2.25rem);
  box-shadow: var(--shadow-lg);
}
.oc-field { margin-bottom: 1rem; }
.oc-label {
  display: block; font-family: var(--font-display); font-weight: 600;
  font-size: var(--step--1); margin-bottom: .4rem; color: var(--text);
}
.oc-input {
  width: 100%; padding: .85rem 1rem; font: inherit; font-size: var(--step-0);
  color: var(--text); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.oc-input::placeholder { color: var(--text-mute); }
.oc-input:focus {
  outline: none; border-color: var(--action);
  box-shadow: 0 0 0 4px var(--brass-ring);
}
.oc-choices { display: grid; gap: .5rem; }
.oc-choice {
  display: flex; align-items: center; gap: .7rem;
  padding: .75rem .9rem; border: 1px solid var(--line);
  border-radius: var(--radius-sm); cursor: pointer;
  font-size: var(--step--1);
  transition: border-color .15s ease, background-color .15s ease;
}
.oc-choice:hover { border-color: var(--line-2); background: var(--surface-2); }
.oc-choice input { accent-color: var(--action); width: 18px; height: 18px; flex: none; margin: 0; }
.oc-choice:has(input:checked) { border-color: var(--action); background: var(--brass-pick); }
.oc-choice:has(input:focus-visible) { outline: 3px solid var(--action); outline-offset: 2px; }
.oc-form-note { font-size: .8rem; color: var(--text-mute); margin-top: .9rem; text-align: center; }
.oc-req { color: var(--brass-deep); }

/* ---------- FAQ (also the FAQPage schema surface) ---------- */
.oc-faq { max-width: 820px; margin-inline: auto; }
.oc-faq details {
  border-bottom: 1px solid var(--line); padding: 1.15rem 0;
}
.oc-faq summary {
  cursor: pointer; list-style: none; display: flex; gap: 1rem; align-items: flex-start;
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-0);
}
.oc-faq summary::-webkit-details-marker { display: none; }
.oc-faq summary::after {
  content: "+"; margin-inline-start: auto; color: var(--brass-deep);
  font-size: 1.4rem; line-height: 1; flex: none; transition: transform .2s ease;
}
.oc-faq details[open] summary::after { transform: rotate(45deg); }
.oc-faq details p { margin-top: .75rem; color: var(--text-2); font-size: var(--step--1); }

/* ---------- final CTA band ---------- */
.oc-band { background: var(--ink); color: var(--white); text-align: center; }
.oc-band .oc-lede { margin-inline: auto; color: var(--on-ink-2); }
.oc-band .oc-cta-row { justify-content: center; margin-top: 1.8rem; }

/* ---------- gold band ----------
   The one place gold carries a whole surface. Everything on it must be NAVY:
   white is 1.88:1 on gold, brand blue 3.95:1, the grey ramp 3.99:1 — only
   navy clears AA at 4.63:1. Nothing here may inherit the on-dark colours,
   which are light blue-greys and would vanish. */
.oc-band--gold { background: var(--brass); color: rgb(var(--c-ink)); }
.oc-band--gold h1,
.oc-band--gold h2,
.oc-band--gold h3 { color: rgb(var(--c-ink)); }
.oc-band--gold .oc-lede { color: rgb(var(--c-ink)); opacity: .82; }
.oc-band--gold .oc-eyebrow { color: rgb(var(--c-ink)); }

/* navy solid button on gold — 4.63:1 fill vs surface, white label on navy */
.oc-band--gold .oc-btn--primary {
  background: var(--ink); color: var(--white); border-color: var(--ink);
}
.oc-band--gold .oc-btn--primary:hover {
  background: var(--ink-2); border-color: var(--ink-2);
  transform: translateY(1px);
}
/* navy-outline ghost on gold */
.oc-band--gold .oc-btn--ghost {
  border-color: rgb(var(--c-ink)); color: rgb(var(--c-ink)); background: transparent;
}
.oc-band--gold .oc-btn--ghost:hover {
  background: rgb(var(--c-ink)); color: var(--brass); border-color: rgb(var(--c-ink));
}

/* ---------- footer ---------- */
.oc-footer { background: var(--ink); color: var(--on-ink-2); padding-block: clamp(3rem,6vw,4.5rem) 2rem; border-top: 1px solid var(--ink-3); }
.oc-footer__grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: 2rem; }
@media (max-width: 860px) { .oc-footer__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .oc-footer__grid { grid-template-columns: 1fr; } }
.oc-footer h4 {
  color: var(--white); font-size: var(--step--1); letter-spacing: .1em;
  text-transform: uppercase; margin-bottom: 1rem;
}
.oc-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.oc-footer a { font-size: var(--step--1); color: var(--on-ink-2); }
.oc-footer a:hover { color: var(--brass-lit); }
/* The logo artwork is single-colour brand blue (#2a3edd) on transparent, which
   is 1.17:1 against the navy footer — the same failure as the blue button on
   the navy hero. brightness(0) flattens the mark to black and invert(1) takes
   it to white, with the alpha channel untouched. Verified: 100% of opaque
   pixels land on white, so nothing in the mark is lost.
   If a multi-colour logo ever replaces this file, this filter must go and a
   dedicated white asset should be used instead. */
.oc-footer img {
  height: 34px; width: auto; margin-bottom: 1rem;
  filter: brightness(0) invert(1);
}
.oc-footer__tag { font-size: var(--step--1); max-width: 34ch; }
.oc-footer__base {
  margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ink-3);
  display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between;
  font-size: .82rem;
}

/* ---------- utilities ---------- */
.oc-center { text-align: center; }
.oc-mt-2 { margin-top: 2rem; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- page header (non-hero pages: blog, tools, legal) ---------- */
.oc-pagehead { background: var(--ink); color: var(--on-ink); }
.oc-pagehead__inner { padding-block: clamp(2.75rem, 6vw, 4.5rem); max-width: 74ch; }
.oc-pagehead h1 { color: var(--white); font-size: var(--step-3); }
.oc-pagehead .oc-lede { color: var(--on-ink-2); }

/* breadcrumb */
.oc-crumb { display: flex; flex-wrap: wrap; gap: .45rem; font-size: var(--step--1); margin-bottom: 1.1rem; }
.oc-crumb a { color: var(--on-ink-2); }
.oc-crumb a:hover { color: var(--brass-lit); }
.oc-crumb span { color: var(--on-ink-2); opacity: .55; }

/* ---------- prose / article ----------
   Used by blog posts and legal pages. Constrained measure, generous rhythm. */
.oc-prose { max-width: 72ch; margin-inline: auto; }
.oc-prose > * + * { margin-top: 1.15rem; }
.oc-prose h2 {
  font-size: var(--step-2); margin-top: 2.5rem; margin-bottom: .2rem;
  padding-top: 1.6rem; border-top: 1px solid var(--line);
}
.oc-prose h3 { font-size: var(--step-1); margin-top: 1.9rem; margin-bottom: .1rem; }
.oc-prose p, .oc-prose li { color: var(--text-2); }
.oc-prose strong { color: var(--text); font-weight: 600; }
.oc-prose a { color: var(--action); text-decoration: underline; text-underline-offset: 3px; }
.oc-prose a:hover { color: var(--ink); }
.oc-prose ul, .oc-prose ol { padding-inline-start: 1.35rem; display: grid; gap: .55rem; }
.oc-prose li::marker { color: var(--brass-deep); }
.oc-prose img { border-radius: var(--radius); margin-block: 1.75rem; }
.oc-prose blockquote {
  margin: 1.75rem 0; padding: .3rem 0 .3rem 1.25rem;
  border-inline-start: 3px solid var(--action);
  font-size: var(--step-1); color: var(--text); font-family: var(--font-display);
}
[dir="rtl"] .oc-prose blockquote { padding: .3rem 1.25rem .3rem 0; }
.oc-prose table { width: 100%; border-collapse: collapse; margin-block: 1.5rem; font-size: var(--step--1); }
.oc-prose th, .oc-prose td { padding: .7rem .8rem; border-bottom: 1px solid var(--line); text-align: start; }
.oc-prose th { font-family: var(--font-display); font-weight: 600; color: var(--text); }

/* article meta */
.oc-meta { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; font-size: var(--step--1); color: var(--on-ink-2); }
.oc-meta__tag {
  background: var(--brass-chip); color: var(--brass);
  padding: .2rem .7rem; border-radius: var(--radius-pill); font-weight: 600;
}

/* ---------- post list (blog index) ---------- */
.oc-post { display: block; height: 100%; }
.oc-post__date { font-size: var(--step--1); color: var(--text-mute); margin-bottom: .5rem; display: block; }
.oc-post h3 { font-size: var(--step-1); margin-bottom: .5rem; }
.oc-post p { color: var(--text-2); font-size: var(--step--1); }

/* ---------- inline CTA inside articles ---------- */
.oc-inline-cta {
  background: var(--ink); color: var(--on-ink);
  border-radius: var(--radius-lg); padding: clamp(1.5rem, 3vw, 2.25rem);
  margin-block: 2.5rem; text-align: center;
}
.oc-inline-cta h3 { color: var(--white); font-size: var(--step-2); margin-bottom: .5rem; }
.oc-inline-cta p { color: var(--on-ink-2); margin-bottom: 1.35rem; }

/* ---------- tool panel (QR generators) ---------- */
.oc-tool {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 4vw, 3rem); align-items: start;
}
@media (max-width: 860px) { .oc-tool { grid-template-columns: 1fr; } }
.oc-tool__out {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: clamp(1.5rem, 3vw, 2rem);
  box-shadow: var(--shadow); text-align: center;
  position: sticky; top: 96px;
}
@media (max-width: 860px) { .oc-tool__out { position: static; } }
.oc-tool__canvas {
  min-height: 240px; display: grid; place-items: center;
  background: var(--surface-2); border-radius: var(--radius);
  margin-bottom: 1.25rem; padding: 1rem;
}
.oc-tool__canvas img, .oc-tool__canvas canvas { margin-inline: auto; }
.oc-tool__hint { font-size: var(--step--1); color: var(--text-mute); }

/* ---------- where each one starts (comparison page) ----------
   Two stacks, layer by layer, each layer tagged GUEST or STAFF. The argument is
   the order, and the tag is what makes the order visible: one stack opens on a
   staff surface, the other on two guest surfaces. Our column carries the heavy
   rule, the same treatment as .oc-card--product. */
.oc-start { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.75rem, 4vw, 3.5rem); }
@media (max-width: 720px) { .oc-start { grid-template-columns: 1fr; } }
.oc-start__col { border-top: 1px solid var(--line-2); padding-top: 1.1rem; }
.oc-start__col--us { border-top: 3px solid var(--ink); }
.oc-start__name {
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-2); line-height: 1.1;
}
.oc-start__kind { color: var(--text-2); font-size: var(--step--1); margin-top: .35rem; }
.oc-start__list { list-style: none; margin: 1.1rem 0 0; padding: 0; counter-reset: ocstart; }
.oc-start__layer {
  counter-increment: ocstart;
  display: grid; grid-template-columns: 1fr auto; gap: .2rem 1rem; align-items: center;
  padding: .95rem 0; border-top: var(--rule);
}
.oc-start__h {
  grid-column: 1; grid-row: 1;
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-0);
}
.oc-start__h::before { content: counter(ocstart) ". "; color: var(--text-mute); }
.oc-start__note { grid-column: 1; grid-row: 2; font-size: var(--step--1); color: var(--text-2); }
.oc-start__who {
  grid-column: 2; grid-row: 1 / span 2;
  font-size: .72rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  padding: .25rem .55rem; border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  color: var(--text-2);
}
.oc-start__layer--guest .oc-start__who {
  color: var(--action); border-color: var(--action); background: var(--brass-pick);
}

/* ---------- packages ----------
   Four packages, cheapest first. Each card draws the three layers of the pyramid
   (ordering + loyalty / menu / POS), filled where the package includes it, so "the
   full stack, start where you need" reads without reading. The full-stack card
   carries the heavy rule (same as .oc-card--product) and a gold tab sitting ON that
   rule: absolute, so it does not push its name out of line with the other three. */
.oc-pkgs {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1.25rem, 2.5vw, 2rem);
}
@media (max-width: 900px) { .oc-pkgs { grid-template-columns: repeat(2, 1fr); } }
/* Phones: one column, compact. A swipe strip was tried first and rejected: on a
   choice between plans most readers never swipe past the first card, so three
   of four packages (including the full stack) went unseen and could not be
   compared. Stacked full-size cards ran to ~2,000px, so the layers become one
   row of chips and the spacing tightens; every package stays in reading order. */
@media (max-width: 620px) {
  .oc-pkgs { grid-template-columns: 1fr; gap: 1.6rem; }
}
.oc-pkg {
  position: relative; display: flex; flex-direction: column;
  border-top: 1px solid var(--line-2); padding-top: 1.25rem;
}
.oc-pkg--full { border-top: 3px solid var(--ink); padding-top: calc(1.25rem - 2px); } /* stacks stay level */
.oc-pkg__tag {
  position: absolute; top: -1.5px; inset-inline-start: 0; transform: translateY(-50%); /* centred on the 3px rule */
  margin: 0; padding: .2rem .55rem; border-radius: var(--radius-sm);
  background: var(--brass); color: var(--ink);
  font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
}
.oc-pkg__name {
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-1); line-height: 1.2;
  margin: 0 0 .9rem;
  /* names wrap to 1 or 2 lines; reserve 2 so the stacks stay level. In lines (lh),
     not em: Arabic headings run a taller line-height, and 2.4em fell 6px short. */
  min-height: 2.4em; min-height: 2lh;
}
.oc-pkg__stack { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 4px; }
.oc-pkg__layer {
  padding: .42rem .7rem; border: 1px dashed var(--line-2); border-radius: var(--radius-sm);
  font-size: .78rem; font-weight: 600; color: var(--text-mute);
}
.oc-pkg__layer.is-on { background: var(--ink); border: 1px solid var(--ink); color: var(--on-ink); }
.oc-pkg__for { flex: 1; margin: 0 0 1.1rem; color: var(--text-2); font-size: var(--step--1); line-height: 1.6; }
.oc-pkg__price { margin: 0; color: var(--text-2); font-size: var(--step--1); }
.oc-pkg__amt { font-family: var(--font-display); font-weight: 600; font-size: var(--step-2); color: var(--ink); line-height: 1; }
.oc-pkg__alt { margin: .35rem 0 0; color: var(--text-mute); font-size: .8rem; line-height: 1.45; }
.oc-pkgs__note { margin: clamp(1.75rem, 3vw, 2.25rem) 0 1.25rem; max-width: 80ch; color: var(--text-2); font-size: .85rem; }
@media (max-width: 620px) {
  .oc-pkg { padding-top: 1rem; }
  .oc-pkg--full { padding-top: calc(1rem - 2px); margin-top: .5rem; } /* room for the tab above it */
  .oc-pkg__name { min-height: 0; margin-bottom: .6rem; }
  .oc-pkg__stack { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: .7rem; }
  .oc-pkg__layer { padding: .3rem .5rem; font-size: .72rem; }
  .oc-pkg__for { margin-bottom: .7rem; }
}
[dir="rtl"] .oc-pkg__tag { letter-spacing: 0; text-transform: none; }

/* ---------- comparison table ----------
   A real <table>: it is tabular data, and a screen reader announces the column
   header with each cell. Our column takes the OTHER light surface from the
   section it sits in (tint on white, white on tint), so it reads as a column
   without a colour of its own and survives the section changing surface. */
.oc-compare { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.oc-compare th,
.oc-compare td { text-align: start; vertical-align: top; padding: .95rem 1rem; border-bottom: var(--rule); }
.oc-compare thead th {
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-1);
  border-bottom: 1px solid var(--ink);
}
.oc-compare tbody th { width: 22%; font-weight: 600; color: var(--text); }
.oc-compare td { width: 39%; color: var(--text-2); }
.oc-compare .oc-compare__us { background: var(--surface-2); color: var(--text); }
.oc-tint .oc-compare .oc-compare__us { background: var(--surface); }
.oc-compare thead .oc-compare__us { border-top: 3px solid var(--ink); }
.oc-compare__src { margin-top: 1.1rem; max-width: 80ch; font-size: .8rem; color: var(--text-mute); }

/* On a phone three columns are ~110px each and unreadable, so every row stacks:
   the row label, then each cell under its brand name (from data-label). The
   head is hidden visually only, the same way .visually-hidden does it. */
@media (max-width: 620px) {
  .oc-compare thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
  }
  .oc-compare, .oc-compare tbody, .oc-compare tr,
  .oc-compare th, .oc-compare td { display: block; width: auto; }
  .oc-compare tr { border-top: 1px solid var(--ink); padding: .9rem 0 .6rem; }
  .oc-compare tbody th {
    width: auto;   /* the desktop 22% outranks the reset above on specificity */
    padding: 0 0 .5rem; border: 0;
    font-family: var(--font-display); font-size: var(--step-0);
  }
  .oc-compare td { padding: .6rem .8rem; border: 0; }
  .oc-compare td::before {
    content: attr(data-label); display: block; margin-bottom: .15rem;
    font-size: .72rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-mute);
  }
  .oc-compare td.oc-compare__us::before { color: var(--action); }
}

/* ==========================================================================
   RTL / Arabic
   Arabic is a first-class version, not a mirrored afterthought: it gets its
   own typeface, its own line-height, and no letter-spacing (which damages
   Arabic letterforms).
   ========================================================================== */
[dir="rtl"] {
  --font-display: 'Alexandria', var(--font-arabic);   /* same headline face as English */
  --font-body: var(--font-arabic);
}
[dir="rtl"] body { line-height: var(--leading-body-ar); }
[dir="rtl"] h1, [dir="rtl"] h2, [dir="rtl"] h3, [dir="rtl"] h4 {
  letter-spacing: 0;      /* tracking is harmful to connected Arabic script */
  line-height: 1.35;
}
[dir="rtl"] .oc-eyebrow { letter-spacing: 0; text-transform: none; }
[dir="rtl"] .oc-footer h4 { letter-spacing: 0; text-transform: none; }
/* the shot labels are tracked caps in English; tracking breaks Arabic joins */
[dir="rtl"] .oc-shot__name,
[dir="rtl"] .oc-shot__pair figcaption,
.oc-shot__pair figcaption[dir="rtl"] { letter-spacing: 0; text-transform: none; }
/* numerals and latin brand names stay LTR inside Arabic text */
[dir="rtl"] .oc-fig__n,
[dir="rtl"] .oc-menu__price { direction: ltr; unicode-bidi: isolate; }
/* direction:ltr on a BLOCK also flips text-align:start to the left, which threw every
   figure to the far edge of its column, away from its right-aligned Arabic caption. */
[dir="rtl"] .oc-fig__n { text-align: right; }
/* comparison page labels are tracked caps in English; tracking breaks Arabic joins */
[dir="rtl"] .oc-start__who,
[dir="rtl"] .oc-compare td::before { letter-spacing: 0; text-transform: none; }
