:root{
  /* Dark theme on BeSmart navy (owner, 2026-09-06): the surfaces carry the brand navy instead of graphite. */
  --shell:#0E1526; --panel:#162340; --panel-2:#1E2E52; --line:#2B3E66;
  --tape:#FFFFFF; --tape-line:#DADDE1; /* the cheque paper is plain white (owner, 2026-09-05) */
  /* BeSmart navy (owner, 2026-09-05): the old brass accent is retired — --brass now carries a
     light navy so every existing var(--brass) frame/hover reads in the BeSmart theme, and the
     two navy tokens paint the cheque's buttons. */
  --navy:#203864; --navy-2:#2d4a80;
  --brass:#7C9BE0; --go:#2F7F5B; --stop:#B4402F; --alert:#D4622A;
  /* the PIN pad's own blue: never overridden by an outlet skin (owner, 2026-09-06) */
  --pin:#7C9BE0;
  --text:#E7ECEF; --text-dim:#8A9AA5; --ink:#191512;
  /* scroll bar thumb/track (owner, 14 Sep 2026): the bars follow the theme, see the *{scrollbar-color} rule below */
  --sb-thumb:#3E5588; --sb-track:transparent;
  --r:10px;
  --ui:'Space Grotesk',system-ui,sans-serif;
  --ar:'Noto Kufi Arabic','Space Grotesk',sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,monospace;
  /* The login screen's own tokens — see the LOGIN / REVENUE CENTRE PICKER section below for why
     these exist separately from --shell/--panel rather than reusing them directly. */
  --login-bg:radial-gradient(ellipse farthest-corner at 50% 0%,#24345c 0%,#1a2846 28%,#131d36 55%,#0c1325 80%,#080d1a 100%);
  --login-card-bg:rgba(22,35,64,.9);
  --login-menu-bg:rgba(22,35,64,.97);
  --login-opt-bg:rgba(255,255,255,.03);
  --login-opt-bg-hover:rgba(255,255,255,.06);
}
/* "Clean slate" light theme — same variable names as the dark palette above, so every rule in
   this file that already reads var(--shell)/var(--text)/etc. picks it up with no other changes.
   --tape/--tape-line/--ink/--go/--stop/--alert are already light-surface-safe (they're either
   already light, or used as solid button/badge backgrounds with fixed light text on top) and
   stay the same in both themes on purpose. --brass is darkened here specifically — the dark
   theme's gold reads fine on a near-black panel but loses too much contrast used as small text
   on a near-white one, so light mode gets a deeper gold instead of reusing the same hex. Applied
   by toggling data-theme on <html> — see /shared/theme.js — never edit this selector by hand
   without checking style.css's other var(--brass)/var(--text-dim) usages still read cleanly. */
:root[data-theme="light"]{
  --shell:#FFFFFF; --panel:#F5F6F8; --panel-2:#EAECEF; --line:#DADDE1;
  /* --brass and --text-dim: darkened again from the first light-mode pass — both still landed
     under the 4.5:1 WCAG AA floor against --panel (#A67C1A measured ~3.5:1, #6B7280 ~4.47:1),
     caught live on the guest order page's price text and secondary labels. These clear ~5:1/~7:1. */
  --brass:#2d4a80; --pin:#2d4a80;
  --text:#20242A; --text-dim:#4B5563;
  --sb-thumb:#C3C8D0;
  --login-bg:radial-gradient(ellipse farthest-corner at 50% 0%,#FFFFFF 0%,#F6F7F9 28%,#EEF0F3 55%,#E4E7EB 80%,#DDE1E6 100%);
  --login-card-bg:rgba(255,255,255,.9);
  --login-menu-bg:rgba(255,255,255,.97);
  --login-opt-bg:rgba(0,0,0,.03);
  --login-opt-bg-hover:rgba(0,0,0,.06);
}
/* "Terracotta" (owner, 7 Sep 2026, from his Willow House picture): cream ground, sand cards,
   terracotta buttons, near-black brown text, green checks, big rounded corners, Fraunces headings
   over Manrope text. Same token names, so every rule follows; --navy carries the terracotta so
   every primary button and the tab highlight take it. State colours stay their own. */
:root[data-theme="terracotta"]{
  --shell:#F3E6D3; --panel:#E8D5B6; --panel-2:#F1E3CB; --line:rgba(78,50,22,.18);
  --tape:#FFFDF8; --tape-line:#E3D3B8;
  --navy:#C8703A; --navy-2:#B25F2E;
  --brass:#A65A2A; --pin:#C8703A; --go:#4F7A45;
  --text:#2A2118; --text-dim:#6E5B48; --ink:#2A2118;
  --sb-thumb:rgba(78,50,22,.32);
  --r:16px;
  --ui:'Manrope','Space Grotesk',system-ui,sans-serif;
  --login-bg:radial-gradient(ellipse farthest-corner at 50% 0%,#F8EEDF 0%,#F3E6D3 40%,#EBD9BF 80%,#E4CFB1 100%);
  --login-card-bg:rgba(232,213,182,.94);
  --login-menu-bg:rgba(240,227,203,.98);
  --login-opt-bg:rgba(78,50,22,.05);
  --login-opt-bg-hover:rgba(78,50,22,.10);
}
/* Light Terracotta (owner, 7 Sep 2026): colour only. White grounds with the terracotta buttons; the shape,
   radius and type stay exactly the Light theme's. */
:root[data-theme="terracotta-light"]{
  --shell:#FFFFFF; --panel:#F7F2EA; --panel-2:#FBF8F3; --line:rgba(78,50,22,.14);
  --tape:#FFFFFF; --tape-line:#E9DFD0;
  --navy:#C8703A; --navy-2:#B25F2E;
  --brass:#A65A2A; --pin:#C8703A; --go:#4F7A45;
  --text:#2A2118; --text-dim:#6E5B48; --ink:#2A2118;
  --sb-thumb:rgba(78,50,22,.26);
  --login-bg:radial-gradient(ellipse farthest-corner at 50% 0%,#FFFFFF 0%,#FCF8F2 45%,#F6EEE3 100%);
  --login-card-bg:rgba(255,255,255,.96);
  --login-menu-bg:rgba(255,255,255,.98);
  --login-opt-bg:rgba(78,50,22,.05);
  --login-opt-bg-hover:rgba(78,50,22,.10);
}
/* "Dark Solid" / "Light Solid" (owner, 12 Sep 2026: originally one "Solid" that kept whatever
   theme was active, split same-day into its own dark and light pair — same relationship as
   Terracotta/Light Terracotta above). Dark Solid redefines no color at all (base :root above is
   already the dark palette); Light Solid copies the Light theme's colors exactly. Both only add
   --r:0 (the handful of rules already written against the token) plus the broad rule below, which
   covers the other ~160 corners across this file and resmaro.css that predate --r and are
   hardcoded to a pixel/percent value directly. Genuinely round/pill controls (badges, avatars,
   toggle switches, floating action buttons) are excluded on purpose — they're functional shapes,
   not box corners, and forcing them square would make them unreadable as what they are. #rmFloor
   (ResM's whole procedural room drawing — RM_FLOOR_CSS in resmaro-host.js: tables, chairs, sofas,
   zones) is excluded wholesale rather than shape-by-shape: that file's own comment already says
   "the room commits to its own fixed palette like a real drawing — it doesn't follow the app
   theme," so Solid shouldn't touch it at all (adversarial review, 12 Sep 2026: without this, Solid
   was silently squaring every round/oval table and sofa in the floor-plan builder). Add any NEW
   pill/circle element's class to the exclusion list too, or Solid will silently square it. */
:root[data-theme="dark-solid"]{ --r:0; }
:root[data-theme="light-solid"]{
  --shell:#FFFFFF; --panel:#F5F6F8; --panel-2:#EAECEF; --line:#DADDE1;
  --brass:#2d4a80; --pin:#2d4a80;
  --text:#20242A; --text-dim:#4B5563;
  --sb-thumb:#C3C8D0;
  --r:0;
  --login-bg:radial-gradient(ellipse farthest-corner at 50% 0%,#FFFFFF 0%,#F6F7F9 28%,#EEF0F3 55%,#E4E7EB 80%,#DDE1E6 100%);
  --login-card-bg:rgba(255,255,255,.9);
  --login-menu-bg:rgba(255,255,255,.97);
  --login-opt-bg:rgba(0,0,0,.03);
  --login-opt-bg-hover:rgba(0,0,0,.06);
}
[data-theme="dark-solid"] *:not(.check-pax, .check-overdue-btn, .fp-marker, .chip, .toast, .a-tag, .dl-btn, .pin-dots span, .order-ready-banner button, .order-theme-toggle, .order-bell-toggle, .order-cartbar, .order-paybar, .order-avatar-fab, .order-waiter-fab, .ov-countdown, .ov-close, .ov-lang, .mic-hint, .screen-dirty, .outlet-chip, #rmFloor, #rmFloor *),
[data-theme="light-solid"] *:not(.check-pax, .check-overdue-btn, .fp-marker, .chip, .toast, .a-tag, .dl-btn, .pin-dots span, .order-ready-banner button, .order-theme-toggle, .order-bell-toggle, .order-cartbar, .order-paybar, .order-avatar-fab, .order-waiter-fab, .ov-countdown, .ov-close, .ov-lang, .mic-hint, .screen-dirty, .outlet-chip, #rmFloor, #rmFloor *){
  border-radius:0!important;
}
:root[data-theme="terracotta"] h1,:root[data-theme="terracotta"] h2,:root[data-theme="terracotta"] .a-head h2,:root[data-theme="terracotta"] .login-title{font-family:'Fraunces',Georgia,serif;font-weight:900;letter-spacing:-.01em}
:root[data-theme="terracotta"] .btn,:root[data-theme="terracotta"] .opt,:root[data-theme="terracotta"] .a-field input,:root[data-theme="terracotta"] .a-field select,:root[data-theme="terracotta"] .a-field textarea{border-radius:12px}
:root[data-theme="terracotta"] .btn.primary{background:var(--navy);color:#fff}
:root[data-theme="terracotta"] .btn.primary:hover{background:var(--navy-2)}
:root[data-theme="terracotta"] .opt.on{border-color:var(--navy);color:var(--navy);background:rgba(200,112,58,.10)}
/* ---- The Terracotta till (owner's mock, 7 Sep 2026): round logo, pill chips, a count badge on
   every category, a serif category heading with a search box, big rounded sand tiles with a photo
   well, and the cheque as a rounded card. The cheque still flies in from the right and closed
   cheques are still picked up from the tab strip: same setup, new shape. Scoped to the till's own
   ids so BOH, the QR menu and the other two themes keep their look. ---- */
:root[data-theme="terracotta"] #topbar{padding:12px 22px;gap:12px;border-bottom:0;background:var(--shell)}
:root[data-theme="terracotta"] #topbar .brand{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;column-gap:12px;align-items:center;line-height:1.1}
:root[data-theme="terracotta"] #topbar .brand::before{content:"B";grid-row:1/3;width:44px;height:44px;border-radius:50%;background:var(--navy);color:#fff;font-family:'Fraunces',Georgia,serif;font-weight:900;font-size:23px;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 16px -6px rgba(200,112,58,.55)}
:root[data-theme="terracotta"] #topbar .brand b{font-family:'Fraunces',Georgia,serif;font-weight:900;font-size:20px;letter-spacing:0;align-self:end}
:root[data-theme="terracotta"] #topbar .brand span{font-size:10.5px;letter-spacing:.18em;font-weight:700;align-self:start;margin-top:2px}
:root[data-theme="terracotta"] #topbar .session-bar{gap:8px;margin-left:4px}
:root[data-theme="terracotta"] #topbar .session-user{border-radius:999px;padding:9px 15px;background:var(--panel-2);border:1px solid var(--line);font-weight:600;font-size:12.5px;color:var(--text)}
:root[data-theme="terracotta"] #terraActions{margin-left:auto;display:flex;align-items:center;gap:8px}
:root[data-theme="terracotta"] #terraActions .a-tab{margin:0!important;border-radius:999px;padding:9px 16px;background:var(--panel-2);border:1px solid var(--line);color:var(--text)!important;font-weight:700;font-size:12.5px;gap:7px}
:root[data-theme="terracotta"] #terraActions .a-tab:hover{background:var(--navy);border-color:var(--navy);color:#fff!important}
:root[data-theme="terracotta"] #terraActions #btnSettingsAction,:root[data-theme="terracotta"] #terraActions #btnLogoutAction{width:42px;height:42px;padding:0;justify-content:center;font-size:0;gap:0}
:root[data-theme="terracotta"] #terraActions #btnSettingsAction svg,:root[data-theme="terracotta"] #terraActions #btnLogoutAction svg{width:18px;height:18px}
:root[data-theme="terracotta"] #terraActions .a-tab[hidden]{display:none}

/* Category rail: round count badges, English over Arabic, the open one in terracotta. */
:root[data-theme="terracotta"] #mainView .cats-col{flex-basis:178px;background:var(--shell);border-right:0;padding-left:12px}
:root[data-theme="terracotta"] #cats{background:transparent;padding:6px 10px 12px 0;gap:8px}
:root[data-theme="terracotta"] #cats .cat{display:grid;grid-template-columns:32px 1fr;grid-template-rows:auto auto;column-gap:10px;align-items:center;padding:9px 12px 9px 9px;border-radius:999px;color:var(--text);font-weight:700;font-size:13.5px;border:1px solid transparent;line-height:1.15}
:root[data-theme="terracotta"] #cats .cat:hover{background:var(--panel-2);border-color:var(--line)}
:root[data-theme="terracotta"] #cats .cat .cat-n{grid-row:1/3;width:32px;height:32px;border-radius:50%;background:var(--panel-2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-style:normal;font-size:12px;font-weight:800;color:var(--text-dim)}
:root[data-theme="terracotta"] #cats .cat .cat-nm{grid-column:2;align-self:end}
:root[data-theme="terracotta"] #cats .cat small{grid-column:2;margin-top:1px;font-size:11px;opacity:.7;text-align:left}
:root[data-theme="terracotta"] #cats .cat.on{background:var(--navy);border-color:var(--navy);color:#fff}
:root[data-theme="terracotta"] #cats .cat.on .cat-n{background:rgba(255,255,255,.22);border-color:transparent;color:#fff}
:root[data-theme="terracotta"] #cats .cat.on small{opacity:.88}

/* Menu: serif heading with the Arabic beside it, a search pill, then the tiles. */
:root[data-theme="terracotta"] #grid{padding:6px 24px 24px 14px;gap:14px;grid-template-columns:repeat(auto-fill,minmax(176px,1fr))}
:root[data-theme="terracotta"] #grid .grid-head{display:flex;grid-column:1/-1;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:12px 20px;padding:10px 4px 6px}
:root[data-theme="terracotta"] #grid .grid-head h2{margin:0;font-family:'Fraunces',Georgia,serif;font-weight:900;font-size:32px;line-height:1;letter-spacing:-.01em;color:var(--text)}
:root[data-theme="terracotta"] #grid .grid-head h2 small{font-family:var(--ar);font-size:19px;font-weight:400;color:var(--text-dim);margin-left:12px;direction:rtl;display:inline-block;vertical-align:baseline}
:root[data-theme="terracotta"] #grid .grid-search{display:flex;align-items:center;gap:8px;background:var(--panel-2);border:1px solid var(--line);border-radius:999px;padding:9px 16px;min-width:230px;color:var(--text-dim)}
:root[data-theme="terracotta"] #grid .grid-search:focus-within{border-color:var(--navy);box-shadow:0 0 0 3px rgba(200,112,58,.18)}
:root[data-theme="terracotta"] #grid .grid-search input{flex:1;border:0;outline:0;background:transparent;font:inherit;font-size:13px;color:var(--text);min-width:0}
:root[data-theme="terracotta"] #grid .grid-search input::placeholder{color:var(--text-dim)}
:root[data-theme="terracotta"] #grid .tile{background:var(--panel-2);border:0;border-radius:26px;padding:14px;min-height:150px;box-shadow:0 1px 0 rgba(78,50,22,.05),0 14px 26px -20px rgba(78,50,22,.45);transition:transform .08s,box-shadow .12s}
:root[data-theme="terracotta"] #grid .tile:active{transform:scale(.97);box-shadow:0 0 0 2px var(--navy)}
:root[data-theme="terracotta"] #grid .tile.tile-blank{background:transparent;box-shadow:none}
:root[data-theme="terracotta"] #grid .tile.tile-addon{border:1.5px dashed var(--navy);box-shadow:none}
:root[data-theme="terracotta"] #grid .tile .nm{font-size:15px;font-weight:700;color:var(--text)}
:root[data-theme="terracotta"] #grid .tile .nar{font-size:12.5px;color:var(--text-dim)}
:root[data-theme="terracotta"] #grid .tile .pr{font-family:var(--ui);font-weight:800;font-size:14px;color:var(--navy);margin-top:8px}
:root[data-theme="terracotta"] #grid .tile-img{height:118px;border-radius:18px;margin-bottom:10px}
/* No photo on file yet: a dashed well where the photo will go, as on the owner's mock. */
:root[data-theme="terracotta"] #grid .tile[data-photo]::before{content:attr(data-photo);display:flex;align-items:center;justify-content:center;text-align:center;height:88px;border-radius:18px;border:1.5px dashed rgba(78,50,22,.28);background:rgba(255,253,248,.4);color:var(--text-dim);font-size:11.5px;font-weight:600;line-height:1.3;padding:0 10px;margin-bottom:10px}
:root[data-theme="terracotta"] #grid .tile .flag{top:12px;right:12px;border-radius:999px;background:var(--text);border-color:transparent}
:root[data-theme="terracotta"] #grid .tile .allergy-flag{top:12px;left:12px;border-radius:999px}

/* Cheque tab strip: same tabs, softer shapes; New table and Closed cheques as pills. */
:root[data-theme="terracotta"] #chequeTabsCol{flex-basis:120px;background:var(--shell);border-left:0}
:root[data-theme="terracotta"] #chequeTabs{background:transparent;padding:12px 8px 12px 0}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab{border-radius:0 14px 14px 0;font-family:var(--ui);font-weight:800;font-size:14px;background:var(--panel-2);border-color:var(--line)}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab:hover{color:var(--navy);border-color:var(--navy)}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.on{background:var(--tape);border-color:rgba(78,50,22,.14);color:var(--text);box-shadow:0 8px 18px -10px rgba(78,50,22,.5)}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.new{border-radius:999px;margin-left:8px;width:100px;height:38px;border:1px solid var(--navy);background:var(--navy);color:#fff;font-size:11.5px;font-weight:700}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.new::before{content:"+\00a0"}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.new:hover{background:var(--navy-2);border-color:var(--navy-2);color:#fff}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.closed{border-radius:999px;background:transparent;border:1.5px solid var(--navy);color:var(--navy);font-size:11px;width:100px;margin-left:8px}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.closed:hover{background:var(--navy);color:#fff}
:root[data-theme="terracotta"] #chequeTabs .cheque-tab.overdue,:root[data-theme="terracotta"] #chequeTabs .cheque-tab.overdue.on{background:var(--stop);border-color:var(--stop);color:#fff}

/* The cheque: a rounded sand card that still flies in from the right (same setup, new shape). */
:root[data-theme="terracotta"] #check{top:12px;bottom:12px;right:126px;width:352px;max-width:calc(100% - 132px);border-radius:24px;background:var(--panel-2);color:var(--text);box-shadow:0 24px 48px -24px rgba(78,50,22,.55),0 0 0 1px rgba(78,50,22,.08);transform:translateX(calc(100% + 132px));overflow:hidden}
:root[data-theme="terracotta"] #check.open{transform:none}
:root[data-theme="terracotta"] #check .check-hd{padding:18px 20px 10px;border-bottom:0;align-items:flex-start;flex-wrap:wrap;gap:6px 10px}
:root[data-theme="terracotta"] #check .check-hd b{font-family:'Fraunces',Georgia,serif;font-weight:900;font-size:24px;line-height:1.05;letter-spacing:-.01em}
:root[data-theme="terracotta"] #check .check-hd b::before{content:"Current cheque";display:block;font-family:var(--ui);font-size:10px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;color:var(--text-dim);margin-bottom:5px}
:root[data-theme="terracotta"] #check .check-hd span{font-family:var(--ui);font-weight:600;opacity:.7}
:root[data-theme="terracotta"] #check .check-pax{border-radius:999px;border:1px solid var(--line);background:rgba(255,253,248,.6);opacity:1}
:root[data-theme="terracotta"] #check .lines{padding:6px 20px}
:root[data-theme="terracotta"] #check .ln{border-bottom:1px solid rgba(78,50,22,.10);border-radius:10px}
:root[data-theme="terracotta"] #check .ln:hover{background:rgba(255,253,248,.55)}
:root[data-theme="terracotta"] #check .ln-top{font-family:var(--ui);font-weight:600}
:root[data-theme="terracotta"] #check .ln-n{font-weight:600}
:root[data-theme="terracotta"] #check .ln-sub{color:var(--text-dim)}
:root[data-theme="terracotta"] #check .ln-ctl button{border-radius:50%;background:rgba(200,112,58,.16);color:var(--navy)}
:root[data-theme="terracotta"] #check .empty{padding:34px 12px 30px;color:var(--text-dim);font-weight:600}
:root[data-theme="terracotta"] #check .empty::before{content:"";display:block;width:58px;height:58px;margin:0 auto 12px;border-radius:50%;border:1px solid var(--line);background:var(--tape) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23A65A2A' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 6.5v11M14.8 9.2a2.6 2.6 0 0 0-2.8-1.4c-1.6 0-2.6.9-2.6 2 0 2.6 5.4 1.4 5.4 4.1 0 1.2-1.1 2.1-2.8 2.1a2.9 2.9 0 0 1-3-1.6'/%3E%3C/svg%3E") center/28px no-repeat}
:root[data-theme="terracotta"] #totals{font-family:var(--ui);font-size:13px;padding:12px 20px;border-top:1px dashed rgba(78,50,22,.28)}
:root[data-theme="terracotta"] #totals .tr{padding:3px 0}
:root[data-theme="terracotta"] #totals .tr.grand{font-family:'Fraunces',Georgia,serif;font-weight:900;font-size:21px;border-top:1px solid var(--text)}
:root[data-theme="terracotta"] #check .pay{padding:10px 16px 16px;gap:8px}
:root[data-theme="terracotta"] #check .btn{border-radius:999px;padding:10px 12px;background:transparent;border:1.5px solid var(--navy);color:var(--navy);font-weight:700;font-size:12.5px}
:root[data-theme="terracotta"] #check .btn:hover{background:rgba(200,112,58,.12)}
:root[data-theme="terracotta"] #check .btn.white,:root[data-theme="terracotta"] #check .btn.fire{background:var(--navy);border-color:var(--navy);color:#fff}
:root[data-theme="terracotta"] #check .btn.white:hover,:root[data-theme="terracotta"] #check .btn.fire:hover{background:var(--navy-2);border-color:var(--navy-2)}
:root[data-theme="terracotta"] #check .btn.navy.primary{background:var(--text);border-color:var(--text);color:var(--shell)}
:root[data-theme="terracotta"] #check .btn.navy.primary:hover{background:#3d3122;border-color:#3d3122}
/* Phones: same bottom sheet and bottom tab strip as the other themes (these ids out-rank the
   880px rules further down, so the phone shape is restated here). */
@media (max-width:880px){
  :root[data-theme="terracotta"] #check{position:fixed;top:auto;inset:auto 0 56px 0;right:0;width:auto;max-width:none;height:72dvh;border-radius:20px 20px 0 0;transform:translateY(calc(100% + 60px));box-shadow:0 -12px 30px rgba(78,50,22,.35)}
  :root[data-theme="terracotta"] #check.open{transform:translateY(0)}
  :root[data-theme="terracotta"] #chequeTabsCol{flex-basis:0}
  :root[data-theme="terracotta"] #chequeTabs{padding:5px 8px;background:var(--panel)}
  :root[data-theme="terracotta"] #chequeTabs .cheque-tab{border-radius:12px 12px 0 0;width:68px;height:40px}
  :root[data-theme="terracotta"] #chequeTabs .cheque-tab.new{border-radius:999px;margin-left:0;width:auto;padding:0 12px;height:40px}
  :root[data-theme="terracotta"] #chequeTabs .cheque-tab.closed{margin-left:auto;width:auto;padding:0 12px;height:40px}
  :root[data-theme="terracotta"] #mainView .cats-col{flex-basis:118px;padding-left:6px}
  :root[data-theme="terracotta"] #grid{padding:6px 12px 80px 8px;gap:10px;grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}
  :root[data-theme="terracotta"] #grid .grid-head h2{font-size:24px}
  :root[data-theme="terracotta"] #grid .grid-search{min-width:0;flex:1 1 160px}
  :root[data-theme="terracotta"] #topbar{padding:8px 12px}
  :root[data-theme="terracotta"] #topbar .brand{flex:0 1 auto}
  :root[data-theme="terracotta"] #terraActions .a-tab{padding:7px 12px;font-size:11.5px}
  :root[data-theme="terracotta"] #terraActions #btnSettingsAction,:root[data-theme="terracotta"] #terraActions #btnLogoutAction{width:36px;height:36px}
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
/* Scroll bars follow the theme (owner, 14 Sep 2026). Standard property only: on Chromium 121+
   (Electron 32 = Chromium 128) any non-auto scrollbar-color makes ::-webkit-scrollbar rules on
   that element inert, so the two systems are never mixed. Universal selector on purpose: the
   value is inherited as a RESOLVED colour, so declaring it on :root alone would freeze the :root
   palette and the body[data-world] outlet skins (pos-themes.css, order-themes.css) would never
   reach the scrollers; re-read per element, their --sb-thumb wins. */
*{scrollbar-color:var(--sb-thumb) var(--sb-track)}
html,body{height:100%;margin:0}
body{background:var(--shell);color:var(--text);font-family:var(--ui);overflow:hidden}
button{font-family:inherit;color:inherit;border:0;background:none;cursor:pointer}
.ar{font-family:var(--ar);direction:rtl}

.conn-banner{position:fixed;top:0;left:0;right:0;z-index:80;background:var(--alert);color:#fff;text-align:center;font-size:12px;font-weight:600;letter-spacing:.03em;padding:6px}
.conn-banner[hidden]{display:none}
/* Admin's Stop POS services switch: a strip above the New table tabs while counting down and while
   down; the wall only for the last five seconds of the countdown */
.pos-strip{flex:0 0 auto;padding:8px 8px;font-size:12px;font-weight:700;line-height:1.25;text-align:center;color:#fff;background:#b45309;border-bottom:1px solid var(--line)}
.pos-strip.down{background:#991b1b}
.pos-strip.counting{background:#b45309}
/* Admin's Stop POS services switch: the countdown sits over the till, the wall replaces it */
.posdown-wall{position:fixed;inset:0;z-index:95;display:flex;align-items:center;justify-content:center;background:rgba(20,24,32,.92);color:#fff}
.posdown-wall.counting{background:rgba(20,24,32,.55);align-items:flex-start;padding-top:12vh}
.posdown-box{max-width:460px;text-align:center;padding:28px 32px;border-radius:16px;background:#1d2430;box-shadow:0 20px 60px rgba(0,0,0,.5)}
.posdown-box h2{margin:0 0 10px;font-size:22px}
.posdown-box p{margin:0;font-size:15px;line-height:1.5;opacity:.9}
.posdown-box b{font-size:20px}
@media print{.posdown-wall{display:none!important}}

.app{display:flex;flex-direction:column;height:100dvh}
.topbar{display:flex;align-items:center;gap:14px;padding:10px 16px;background:var(--panel);border-bottom:1px solid var(--line);flex:0 0 auto}
.topbar[hidden]{display:none}
/* Left-aligned stack: the outlet name starts under the first letter of the wordmark. */
.brand{display:flex;flex-direction:column;align-items:flex-start;text-align:left;line-height:1.15}
.brand b{font-size:15px;letter-spacing:.02em}
.brand span{font-size:10px;color:var(--text-dim);letter-spacing:.14em;text-transform:uppercase}
.open-tables{padding:7px 10px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);color:var(--text);font-family:inherit;font-size:12px;max-width:170px;margin-left:6px}
.open-tables:hover{border-color:var(--brass)}
.btn-newtable{padding:7px 12px;border-radius:8px;background:var(--panel-2);border:1px dashed var(--line);font-size:12px;white-space:nowrap;color:var(--text-dim)}
.btn-newtable:hover{border-color:var(--brass);color:var(--brass)}
.btn-cheques{margin-left:auto;padding:7px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);font-size:12px;white-space:nowrap;color:var(--text-dim)}
.btn-cheques:hover{border-color:var(--brass);color:var(--brass)}
/* At least one open cheque, anywhere, has an item sitting unsent for over 7 minutes — see
   anyTableHasOverdue. A glance-able signal for something you're not currently looking at,
   same red/pulse language as checkOverdueBtn (the per-table version of this same alert). */
.btn-cheques.overdue{background:var(--stop);border-color:var(--stop);color:#fff;animation:overdue-pulse 1.6s ease-in-out infinite}
/* BeSmart Admin's topbar has no Cheques button to carry the auto-margin that
   pushes the session-bar right, so give it one directly, scoped to this page only. */
.admin-topbar .session-bar{margin-left:auto}
/* Same reasoning for the KDS board's topbar — no Cheques button there either, so the
   workstation-name tag and Reload button (both now in .session-bar) need their own push right. */
.kds-topbar .session-bar{margin-left:auto}

.session-bar{display:flex;align-items:center;gap:6px}
.session-bar .a-addbtn{margin-top:0;white-space:nowrap}
.session-revenue-centre,.session-user{padding:7px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);font-size:12px;white-space:nowrap}
.session-revenue-centre[hidden]{display:none}
.session-revenue-centre:hover{border-color:var(--brass);color:var(--brass)}
/* .session-revenue-centre is a <button> in the till (opens the RVC picker) but a <select>
   in BeSmart Admin (the RVC switcher) — selects don't inherit font/color/cursor the way
   buttons do, so set them explicitly here for the select case. */
select.session-revenue-centre{color:var(--text);font-family:inherit;cursor:pointer}
.session-user{color:var(--text-dim)}
.session-user:hover{border-color:var(--stop);color:var(--stop)}
/* A plain name display, not a control — both apps now have a dedicated Logout button instead,
   so the red "this logs you out" hover cue would be actively misleading here. */
.session-user.static{cursor:default}
.session-user.static:hover{border-color:var(--line);color:var(--text-dim)}

.main{flex:1;display:flex;min-height:0;position:relative;overflow:hidden}
/* Open cheques as a tab strip on the right edge (owner, 2026-09-05): New table first, then one
   small square per open table. Tapping a tab slides that cheque in over the grid. */
.cheque-tabs-col{flex:0 0 112px;display:flex;flex-direction:column;min-height:0;z-index:31;background:var(--panel);border-left:1px solid var(--line)}
.cheque-tabs{flex:1 1 auto;background:var(--panel);display:flex;flex-direction:column;gap:0;padding:10px 6px 10px 0;overflow-y:auto;min-height:0}
/* Index tabs, like the tabs on a paper file (owner, 2026-09-05): square on the side that touches
   the cheque, rounded on the outer edge; the open one wears the cheque's own paper colour so the
   tab and the sheet read as one piece. */
.cheque-tab{width:100px;height:36px;border-radius:0 12px 12px 0;background:var(--panel-2);border:1px solid var(--line);border-left:0;font-family:var(--mono);font-weight:700;font-size:14px;color:var(--text);display:flex;align-items:center;justify-content:center;text-align:center;line-height:1.1;flex:0 0 auto;position:relative;box-shadow:2px 2px 6px rgba(0,0,0,.12);margin-bottom:-9px}
/* Stacked like a paper file: each tab sits a little over the one below it (z-index set per tab in
   pos.js so the upper one is on top); the open tab always comes to the front. */
.cheque-tab:last-child{margin-bottom:0}
.cheque-tab.on,.cheque-tab:hover{z-index:60!important}
.cheque-tab:hover{border-color:var(--brass);color:var(--brass)}
.cheque-tab.on{background:var(--tape);border-color:var(--tape-line);color:var(--ink);width:106px;box-shadow:3px 3px 8px rgba(0,0,0,.18)}
.cheque-tab.new{background:var(--navy);border-color:var(--navy);color:#fff;font-family:var(--ui);font-size:11px;white-space:nowrap;font-weight:600;letter-spacing:.02em}
/* Items sitting unsent too long: the table's own tab flashes red (owner, 2026-09-05). */
.cheque-tab.overdue{background:var(--stop);border-color:var(--stop);color:#fff;animation:overdue-pulse 1.6s ease-in-out infinite}
.cheque-tab.overdue.on{background:var(--stop);color:#fff}
/* Closed cheques: pinned at the bottom of the strip, one line, never part of the stack. */
.cheque-tab.closed{margin-top:auto;margin-bottom:0;align-self:center;margin-left:6px;height:36px;width:96px;border-radius:8px;border-left:1px solid var(--line);font-family:var(--ui);font-size:11.5px;font-weight:600;white-space:nowrap;box-shadow:none;z-index:1}
.cheque-tab.locked{opacity:.5}
.cheque-tab .cq-lock{position:absolute;right:-2px;bottom:-2px;font-size:9px}
.main[hidden]{display:none}
/* News-style crawl for "order ready" alerts — sits between the topbar and the grid, only takes
   up space while a message is scrolling (see showReadyTicker in pos.js), so it never steals
   room from the grid/check panel the rest of the time. */
.ticker{position:relative;overflow:hidden;height:30px;flex:0 0 auto;background:var(--go);border-bottom:1px solid var(--line)}
.ticker.cash{background:#e07f0e} /* the cash-assist strip — orange so a waiter tells it from the green ready bar at a glance */
.ticker.waiter{background:#e6b800} /* the "call a waiter" strip — yellow, the third colour in the same at-a-glance system */
.ticker.waiter .ticker-track{color:#1a1a1a} /* white text (the other two tickers' default) reads poorly on yellow */
.ticker-track{position:absolute;top:0;left:100%;z-index:1;white-space:nowrap;line-height:30px;padding:0 16px;font-size:13px;font-weight:700;letter-spacing:.02em;color:#fff}
.ticker-dismiss{position:absolute;top:0;right:0;z-index:2;height:30px;width:34px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.18);border:0;border-left:1px solid rgba(255,255,255,.25);color:#fff;font-size:14px;line-height:1;cursor:pointer}
.ticker-dismiss:hover{background:rgba(0,0,0,.32)}
@keyframes ticker-scroll{from{left:100%}to{left:-100%}}
/* .cats-col wraps the category nav plus the Logout button pinned under it — same left-sidebar,
   bottom, white-text placement as BeSmart Admin's own Logout (.a-tab, reused here as-is rather
   than a lookalike copy, so both apps' logout controls are pixel-identical, not just similar). */
.cats-col{flex:0 0 122px;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0}
.cats-col .a-tab{margin:0 8px 10px}
.cats-col .ws-name-tag{margin:0 8px 16px;text-align:center;white-space:normal;line-height:1.3}
/* order.html's QR page uses .cats directly, unwrapped (see .order-app .cats override below) —
   background/border-right stay on the base rule too so that page keeps its look unchanged. */
.cats{flex:1;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;padding:10px 8px;gap:6px;overflow-y:auto;min-height:0}
.cats-col .cats{border-right:0}
.cat{padding:13px 10px;border-radius:var(--r);text-align:left;font-size:13px;color:var(--text-dim);border:1px solid transparent}
.cat.on{background:var(--panel-2);color:var(--text);border-color:var(--line)}
.cat small{display:block;font-size:11px;opacity:.6;margin-top:3px;font-family:var(--ar);direction:rtl}
.cat .cat-n{display:none}
.grid-head{display:none}
.tile[hidden]{display:none}

/* grid-auto-rows:min-content is required, not cosmetic. With the default `auto` rows this grid
   resolved every row to the tile's 96px minimum instead of to its contents, so a tile carrying a
   photo (~199px) was laid out inside a 96px row and its picture spilled over the tiles below —
   the reported "pics are over each other", worst on a screen with gaps, where short blank tiles
   share the row. min-content sizes each row to what it actually holds: ~199px where there are
   photos, 96px for a row of text-only tiles or gaps. */
.grid{flex:1;padding:12px;display:grid;gap:10px;overflow-y:auto;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));grid-auto-rows:min-content;align-content:start}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:12px;min-height:96px;text-align:left;display:flex;flex-direction:column;justify-content:space-between;transition:transform .08s,border-color .12s;position:relative}
.tile:active{transform:scale(.97);border-color:var(--brass)}
.tile .nm{font-size:13.5px;font-weight:500;line-height:1.25}
.tile .nar{font-size:12px;color:var(--text-dim);margin-top:3px}
.tile .pr{font-family:var(--mono);font-size:13px;color:var(--brass);margin-top:10px}
.tile .flag{position:absolute;top:9px;right:9px;z-index:1;font-size:9px;letter-spacing:.1em;color:#fff;background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.2);border-radius:4px;padding:2px 5px}
/* Allergy warning badge on guest QR tiles — top-LEFT (the sizes .flag owns top-right), solid
   ground + z-index so it stays readable over a photo tile. Shown only when the item's allergy
   box is hand-ticked in Admin (never inferred). */
.tile .allergy-flag{position:absolute;top:9px;left:9px;z-index:1;font-size:11px;line-height:1;background:#7a5c1e;color:#ffd76a;border:1px solid #a8842f;border-radius:4px;padding:3px 5px}
.tile-addon{border-style:dashed;border-color:var(--brass)}
.tile-addon .nm::before{content:'+ ';color:var(--brass)}

/* The cheque is a slide-over now (owner, 2026-09-05: "the same as the ResM flyer"): it lives
   over the right side of the grid, slides in when a table is open and hides on Hide / after a
   send, leaving the tab strip to pick the next one. */
.check{position:absolute;top:0;right:112px;bottom:0;width:340px;max-width:calc(100% - 112px);background:var(--tape);color:var(--ink);display:flex;flex-direction:column;z-index:30;transform:translateX(calc(100% + 112px));transition:transform .22s;box-shadow:-8px 0 30px rgba(0,0,0,.28)}
.check.open{transform:none}
.check-hd{padding:12px 16px 8px;border-bottom:1px dashed var(--tape-line);display:flex;align-items:baseline;justify-content:space-between}
.check-hd b{font-size:14px}
.check-hd span{font-family:var(--mono);font-size:11px;opacity:.55}
.check-hd-right{display:flex;align-items:baseline;gap:8px}
.check-pax{font-family:var(--ui);font-size:11px;opacity:.75;padding:2px 8px;border-radius:999px;background:rgba(0,0,0,.06);border:1px dashed var(--tape-line)}
.check-pax:hover{border-color:var(--go);color:var(--go)}
.check-pax[hidden]{display:none}
/* The loyalty guest pill (#checkGuest, same shape as pax): solid green once the reward is ready. */
.check-pax.reward{border-style:solid;border-color:var(--go);color:var(--go);opacity:1;font-weight:600}
.ln-comp{color:var(--go);font-weight:600}
.check-overdue-btn{font-family:var(--ui);font-size:11px;font-weight:600;padding:3px 10px;border-radius:999px;background:var(--stop);color:#fff;border:none;animation:overdue-pulse 1.6s ease-in-out infinite}
.check-overdue-btn[hidden]{display:none}
@keyframes overdue-pulse{0%,100%{opacity:1}50%{opacity:.6}}
.overdue-line{color:var(--stop)}
.overdue-line .ln-n::after{content:" · not sent yet";font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em}
.lines{flex:1;overflow-y:auto;padding:8px 16px}
/* The cheque paper is white on every theme (--tape), so its bar takes the ink, not the panel thumb. */
.check .lines{--sb-thumb:color-mix(in srgb,var(--ink) 30%,transparent)}
.empty{padding:40px 8px;text-align:center;font-size:13px;color:#8a8272;line-height:1.6}
.floor-map{position:relative;width:100%;line-height:0}
.floor-map img{width:100%;height:auto;border-radius:var(--r);display:block}
.fp-marker{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:80px;height:80px;border-radius:50%;border:3px solid var(--line);background:var(--panel-2);color:var(--text);
  cursor:pointer;line-height:1.2;padding:0;gap:1px}
.fp-marker.occupied{border-color:var(--go);background:color-mix(in srgb, var(--go) 18%, var(--panel-2))}
.fp-marker.empty{border-style:dashed}
.fp-marker.overdue{border-color:var(--stop);background:color-mix(in srgb, var(--stop) 22%, var(--panel-2))}
.fp-marker.locked{opacity:.6}
.fp-marker .fp-num{font-family:var(--mono);font-size:19px;font-weight:700}
/* Seats/PAX has to be readable at a glance, at arm's length, without tapping in — same reason
   the number itself is large. A small pill (not just dim text) keeps it legible over any
   background photo, busy or plain. */
.fp-marker .fp-seats{font-size:12px;font-weight:600;color:var(--text);background:color-mix(in srgb, var(--panel) 70%, transparent);
  border-radius:8px;padding:0 5px;white-space:nowrap}
.fp-marker .fp-lock{position:absolute;bottom:-18px;font-size:9px;white-space:nowrap;color:var(--text-dim)}
.ln{padding:7px 0;border-bottom:1px dotted var(--tape-line);cursor:pointer;border-radius:6px}
.ln:hover{background:rgba(0,0,0,.04)}
.ln:active{background:rgba(0,0,0,.08)}
.ln-extracharge{cursor:default;color:#2e7d32}
.ln-extracharge:hover,.ln-extracharge:active{background:none}
.ln-extracharge .ln-n,.ln-extracharge .ln-p{font-style:italic}
.ln-top{display:flex;gap:8px;align-items:baseline;font-family:var(--mono);font-size:12.5px}
.ln-q{width:26px;flex:0 0 auto}
.ln-n{flex:1;font-family:var(--ui);font-size:13px}
.ln-p{font-variant-numeric:tabular-nums}
.ln-sub{margin:3px 0 0 26px;font-size:11px;color:#6f6857}
.ln-rem{margin:3px 0 0 26px;font-size:11px;color:var(--stop);font-weight:600}
.ln-alg{margin:3px 0 0 26px;font-size:10px;color:#fff;background:var(--stop);display:inline-block;padding:2px 6px;border-radius:4px;letter-spacing:.08em}
.ln-ctl{display:flex;gap:6px;margin:6px 0 0 26px}
.ln-ctl button{width:26px;height:26px;border-radius:6px;background:rgba(32,56,100,.12);color:var(--navy);font-size:14px;font-weight:600;line-height:1}
.ln-ctl button.rm{margin-left:auto;background:transparent;color:var(--stop);width:auto;padding:0 6px;font-size:11px}
.ln.sent{opacity:.72}
.ln.sent .ln-n::after{content:'SENT';font-family:var(--mono);font-size:9px;letter-spacing:.1em;background:var(--go);color:#fff;padding:2px 5px;border-radius:4px;margin-left:7px;vertical-align:1px}
.ln.sent .ln-ctl button:not(.rm){display:none}
.btn.fire{background:var(--navy);color:#fff;font-weight:600}

.kq{display:flex;flex-direction:column;gap:10px}
.kq-empty{padding:26px 8px;text-align:center;color:var(--text-dim);font-size:13px}
.tkt{background:#fff;color:#000;border-radius:8px;padding:12px;font-family:var(--mono);font-size:12.5px;line-height:1.5;border-left:5px solid var(--go)}
.tkt.warm{border-left-color:var(--brass)}
.tkt.late{border-left-color:var(--stop)}
.tkt-hd{display:flex;justify-content:space-between;font-weight:600;border-bottom:1px solid #ddd;padding-bottom:5px;margin-bottom:6px}
.tkt-fired{font-size:10.5px;color:#777;margin:-2px 0 6px}
.tkt-el{font-variant-numeric:tabular-nums}
.tkt.late .tkt-el{color:var(--stop)}
.tkt .no{font-weight:700;text-transform:uppercase}
.tkt .plus{padding-left:12px;color:#555}
.tkt .alg{background:#000;color:#fff;text-align:center;padding:4px;font-weight:700;letter-spacing:.08em;margin:-12px -12px 8px;border-radius:8px 8px 0 0}
.tkt .paid-tag{background:var(--go);color:#fff;border-radius:4px;padding:1px 6px;font-size:10px;letter-spacing:.05em;margin-left:6px;vertical-align:middle}
.tkt-act{margin-top:8px;width:100%;padding:9px;border-radius:6px;background:#111;color:#fff;font-family:var(--ui);font-size:12px}
.tkt.done{opacity:.45}
.tkt.done .tkt-act{background:#888}

/* ================= KDS BOARD (kds.html) =================
   Same .tkt ticket-card CSS as the till's Kitchen Queue modal above — this is only the full-page
   wrapper: a wide, wall-mounted-tablet-friendly grid instead of the modal's single-column list,
   plus the station filter control in the topbar. */
/* Multi-outlet KDS board — one column per assigned Revenue Centre, each tinted from a small
   fixed palette (data-color cycles c0..c5) so 2-3 outlets sharing one physical screen stay
   instantly tellable apart from across a kitchen. Ticket cards inside reuse .tkt as-is. */
.kds-outlets{flex:1;min-height:0;display:flex;overflow-x:auto}
.kds-outlets[hidden]{display:none}
.kds-outlet{flex:1 0 320px;display:flex;flex-direction:column;min-height:0;border-right:1px solid var(--line)}
.kds-outlet:last-child{border-right:0}
.kds-outlet-hd{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px 10px 16px;font-weight:700;font-size:15px;letter-spacing:.01em;border-bottom:1px solid rgba(0,0,0,.08)}
.kds-clear-btn{flex:0 0 auto;padding:6px 12px;border-radius:6px;border:1px solid rgba(0,0,0,.15);background:rgba(255,255,255,.65);color:inherit;font-family:var(--ui);font-size:11.5px;font-weight:600;letter-spacing:0;cursor:pointer}
.kds-clear-btn:hover{background:rgba(255,255,255,.95)}
.kds-outlet-board{flex:1;min-height:0;overflow-y:auto;padding:14px;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;align-content:start}
/* KDS boards are fixed pastel tints on every theme (below), so a navy dark-theme thumb would look foreign. */
.kds-outlets,.kds-outlet-board{--sb-thumb:rgba(0,0,0,.28)}
.kds-outlet-board .tkt{font-size:14px}
.kds-outlet-board .kq-empty{grid-column:1/-1;padding:60px 8px;font-size:15px}
.kds-outlet[data-color="c0"]{background:#e3f6e8}
.kds-outlet[data-color="c1"]{background:#fbe4e2}
.kds-outlet[data-color="c2"]{background:#e1eefb}
.kds-outlet[data-color="c3"]{background:#fbf3d9}
.kds-outlet[data-color="c4"]{background:#eee3fa}
.kds-outlet[data-color="c5"]{background:#fbe9d9}
/* Reload/Logout — plain buttons, distinct from the .session-user text-link style, since these
   are meant to read as clearly-tappable controls on a wall-mounted touch screen. */
.topbar .a-addbtn{margin-top:0;padding:7px 12px;font-size:12px}
/* Which physical device this is — see kds-boot.js's showWorkstationName. Same tag look on the
   login screen (centered, above the PIN card's own heading) and in the topbar once logged in. A
   plain rounded-square box, matching every other button/tag's corner radius in this app — not a
   pill, which read as an odd shape mismatched against the square Reload button next to it. */
.ws-name-tag{font-size:11px;letter-spacing:.04em;color:var(--text-dim);background:var(--panel-2);border:1px solid var(--line);border-radius:6px;padding:5px 12px;margin-bottom:14px;display:inline-block}
/* In the topbar (next to Reload), not the login card — drop the margin meant to space it from
   the PIN pad below it, and match Reload's own padding/font-size exactly so both sit on the same
   baseline instead of the tag looking slightly low/mismatched next to the button. */
.session-bar .ws-name-tag{margin-bottom:0;padding:7px 12px;font-size:12px}

.totals{padding:10px 16px;border-top:1px dashed var(--tape-line);font-family:var(--mono);font-size:12px}
.tr{display:flex;justify-content:space-between;padding:2px 0}
.tr.grand{font-size:17px;font-weight:600;padding-top:8px;margin-top:6px;border-top:1px solid var(--ink)}
.tr.alt{font-size:12px;opacity:.6}
.tr span:last-child{font-variant-numeric:tabular-nums}

.pay{padding:8px 12px 10px;display:flex;flex-direction:column;gap:6px}
.pay-row{display:flex;gap:6px}
/* Cheque buttons in the BeSmart theme (owner, 2026-09-05): light navy by default, full navy for
   the two row-wide actions (Send, Payment). The old beige/brass buttons are gone. */
.btn{flex:1;padding:8px 10px;border-radius:8px;font-size:12.5px;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:var(--navy-2);color:#fff;font-weight:500}
.btn:active{transform:scale(.98)}
.btn:disabled{opacity:.4;pointer-events:none}
/* Dimmed but still tappable (unlike :disabled) so the tap can toast WHY: used by the till's Print
   while a cheque still has unsent items (see pos.js unsentBlockingPrint). */
.btn.needs-send{opacity:.6}
.btn.primary{background:var(--navy);color:#fff;font-weight:600}
.btn.navy{background:var(--navy);color:#fff;font-weight:600}
.btn.white{background:#fff;color:var(--navy);border:1px solid var(--navy-2);font-weight:600}
.btn.ghost{background:var(--navy-2);color:#fff;border:0;font-size:12px;padding:9px}
.btn.small{flex:0 0 auto;padding:5px 10px;font-size:12px;min-height:30px}
.btn.voice{background:var(--ink);color:var(--brass);font-size:12px}
/* Small and square — it sits beside a full-width button, so flex:1 (the .btn default) would
   have it eat half the row for what is a single glyph. */
.btn.mic{flex:0 0 46px;padding:0;height:38px;font-size:17px;line-height:1;background:var(--ink);color:#fff;display:flex;align-items:center;justify-content:center}
.btn.mic.busy{opacity:.6}
/* Listening has to be obvious from across a room: a live mic nobody realises is live is the one
   real privacy problem this feature has. */
.btn.mic.on{background:var(--alert);color:#fff;animation:micPulse 1.4s ease-in-out infinite}
@keyframes micPulse{0%,100%{box-shadow:0 0 0 0 rgba(200,60,50,.55)}50%{box-shadow:0 0 0 7px rgba(200,60,50,0)}}
@media (prefers-reduced-motion:reduce){.btn.mic.on{animation:none;outline:2px solid var(--alert);outline-offset:2px}}
.voice-strip{padding:7px 10px;border-radius:var(--r);background:var(--panel-2);color:var(--text-dim);font-size:11.5px;line-height:1.35;text-align:center}
.voice-strip[hidden]{display:none}
.voice-strip.heard{color:var(--ink);font-weight:600}
/* A pending settle is the one state where the strip is telling the waiter that money is about to
   move, so it stops looking like a hint and starts looking like a prompt. */
.voice-strip.failed{background:var(--alert);color:#fff}
.voice-strip.confirming{background:var(--alert);color:#fff;font-weight:600}
.btn[disabled]{opacity:.35;pointer-events:none}

/* No dark backdrop behind pop-ups (owner, 2026-09-05): the floating card carries its own shadow. The full-screen layer stays so a tap outside still closes it. */
.scrim{position:fixed;inset:0;background:transparent;display:flex;align-items:center;justify-content:center;padding:18px;z-index:40}
.scrim[hidden]{display:none}
/* Modals must win over her (MAK, 14 Sep 2026): the guest-side language picker renders through
   this same .scrim, and at the default z-index:40 it was rendering BEHIND the digital waiter's
   panel (z-index:60) whenever she was up — invisible and untappable, which stalled her greeting
   entirely since it awaits a choice the guest could never reach. Scoped to while she's on screen
   rather than raised globally, so .scrim's stacking everywhere else in the app is untouched. */
body:has(.ov-panel:not([hidden])) .scrim { z-index: 65; }
/* border-radius:12px unifies this with the rest of the floating-overlay family (login card,
   shutdown box, context menus) — they used to carry five different one-off radii (8/10/12/14/16px)
   with no relation to each other or to the --r:10px token everything else in the app uses. */
/* Every pop-up wears the login card look (owner, 2026-09-05): a translucent floating card with a
   deep shadow, and a sane default width so a two-column list no longer spans half the screen. */
.modal{background:var(--login-card-bg);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:14px;width:100%;max-width:560px;max-height:88dvh;overflow-y:auto;padding:20px;box-shadow:0 20px 50px rgba(0,0,0,.5)}
.modal.wide{max-width:820px}
/* Every modal used to render at the same fixed 800px regardless of content — a two-line "Log out?"
   confirm stretched into the same box as a 20-field editor form, with huge dead side margins. This
   shrinks it automatically for genuine confirm/alert dialogs (no input/select/textarea and none of
   the richer content wrappers below) while leaving every real form/list/receipt/queue modal at its
   existing width — no JS changes, so it can't drift out of sync the way a hand-toggled class on 70+
   call sites would if a future one forgot to set it. */
.modal:not(:has(input, select, textarea, .receipt, .line-menu, .opts, .mgroup, .lines, .kq, .split-lines, .split-legs, .cheque-list, .layout-grid, .totals, .order-cartrow)){max-width:420px}
.modal h3{margin:0 0 2px;font-size:17px}
.modal .sub{margin:0 0 14px;font-size:12px;color:var(--text-dim)}
/* Darker/bolder than .sub on purpose — this is the one line in the order-placed confirmation a
   guest actually needs to act on (keep the tab, or save it), not background detail like the
   ticket number above it. */
.order-stay-note{margin:0 0 14px;font-size:13px;font-weight:600;color:var(--text)}
.mgroup{margin-bottom:14px}
.mgroup>p{margin:0 0 7px;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);display:flex;justify-content:space-between}
.opts{display:flex;flex-wrap:wrap;gap:7px}
.opt{padding:9px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);font-size:13px;display:flex;align-items:center;gap:6px}
.opt.on{border-color:var(--brass);color:var(--brass)}
.opt em{font-style:normal;font-family:var(--mono);font-size:11px;opacity:.7}
.opt .a{font-family:var(--ar);font-size:12px;opacity:.75}
.opt.rem.on{border-color:var(--stop);color:var(--stop);background:rgba(180,64,47,.12);text-decoration:line-through}
.opt.locked{opacity:.4;pointer-events:none}
.line-menu{display:flex;flex-direction:column;gap:8px;margin-bottom:14px}
.line-menu .opt{width:100%;justify-content:flex-start;padding:12px 14px;font-size:14px}
.opt.danger{color:var(--stop);border-color:rgba(180,64,47,.4)}
/* Armed tap-again-to-confirm state (guest order page's Cash button) — --alert, not --stop/--danger,
   since this isn't an error state, just "one more tap needed." */
.opt.confirming{color:var(--alert);border-color:var(--alert);background:rgba(212,98,42,.12);font-weight:600}
.cheque-list{display:flex;flex-direction:column;gap:8px;margin-bottom:4px}
.cheque-row{width:100%;padding:12px 14px;border-radius:10px;background:var(--panel-2);border:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:10px;text-align:left}
.cheque-row:hover{border-color:var(--brass)}
.cheque-row.overdue-cheque{border-color:var(--stop);background:rgba(180,64,47,.12)}
.cheque-row.overdue-cheque .cq-table{color:var(--stop)}
.cheque-row .cq-main{display:flex;flex-direction:column;gap:2px}
.cheque-row .cq-table{font-size:14px;font-weight:600}
.cheque-row .cq-ref{font-family:var(--mono);font-size:11px;color:var(--text-dim)}
.cheque-row .cq-amt{font-family:var(--mono);font-size:14px;font-variant-numeric:tabular-nums;flex:0 0 auto}
.cheque-empty{padding:14px;text-align:center;color:var(--text-dim);font-size:12px}
.opt.danger:hover{background:rgba(180,64,47,.12)}
.allergy{display:flex;align-items:center;gap:9px;padding:11px;border-radius:8px;background:rgba(212,98,42,.12);border:1px solid var(--alert);font-size:12.5px;margin-top:4px}
.allergy input{width:18px;height:18px;accent-color:var(--alert)}
.noteBox{width:100%;min-height:62px;padding:10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px;resize:vertical;line-height:1.45}
.noteBox:focus{outline:2px solid var(--brass);outline-offset:-1px}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.chip{padding:7px 10px;border-radius:999px;background:var(--panel-2);border:1px solid var(--line);font-size:12px;color:var(--text-dim)}
.chip:active{border-color:var(--brass)}
.ln-note{margin:3px 0 0 26px;font-size:11px;color:#5c5647;font-style:italic}
.tkt .note{background:#f2f2f2;border-left:3px solid #000;padding:3px 7px;margin:3px 0;font-style:italic}
.modal-foot{display:flex;gap:8px;margin-top:6px}
.modal-foot .btn{background:var(--panel-2);color:var(--text)}
.modal-foot .btn.primary{background:var(--navy);color:#fff;font-weight:600}
.modal-foot .btn.danger{background:var(--stop);color:#fff}

.receipt{background:var(--tape);color:var(--ink);font-family:var(--mono);font-size:12px;padding:18px;border-radius:10px;line-height:1.55}
.receipt h4{font-family:var(--ui);font-size:15px;margin:0 0 2px;text-align:center}
.receipt .ctr{text-align:center;font-size:11px;opacity:.6;margin-bottom:10px}
.receipt hr{border:0;border-top:1px dashed var(--tape-line);margin:8px 0}
.rl{display:flex;justify-content:space-between;gap:10px}
.rl span:last-child{font-variant-numeric:tabular-nums}

/* Printing a cheque: window.print() today, a real receipt printer later —
   either way only the .receipt itself should hit paper, not the app chrome
   around it. */
@media print{
  #connBanner,.app,.toast,.login-view,.revenue-centre-picker{display:none!important}
  .scrim{position:static!important;inset:auto!important;background:none!important;padding:0!important;display:block!important}
  .modal{box-shadow:none!important;border:none!important;max-width:100%!important;max-height:none!important;padding:0!important;border-radius:0!important;background:#fff!important;overflow:visible!important}
  .modal>*:not(.receipt){display:none!important}
  .receipt{border-radius:0!important;font-size:13px}
}



.tender{display:flex;flex-direction:column;gap:10px}
.tender .quick{display:flex;gap:7px;flex-wrap:wrap}
.tender .quick button{padding:10px 13px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);font-family:var(--mono);font-size:13px}
.tender input{width:100%;padding:12px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:18px}
/* The $+LBP tender's amount + method pairs — input stretches, dropdown keeps its width.
   Deliberately slimmer than the single-tender input (owner: the full-size rows ate the box). */
.tender .mix-row{display:flex;gap:6px;margin-bottom:6px}
.tender .mix-row input{flex:1;min-width:0;padding:7px 10px;font-size:14px}
.tender .mix-ccy{width:84px;padding:7px 6px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:13px}
.tip-custom-in{width:100%;padding:10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:14px;margin-top:8px}
.tip-note{color:var(--text-dim);font-size:11px;font-weight:400}
.change{font-family:var(--mono);font-size:14px;padding:10px;border-radius:8px;background:var(--panel-2)}
/* Payment window (owner, 2026-09-05): every control the same height as the Send button, the
   custom-tip box gets air on both sides, and the cash change can be split across both currencies. */
.modal .opt,.modal .quick button,.modal .mix-ccy{padding:4px 10px;min-height:30px;font-size:12.5px;line-height:1.2;box-sizing:border-box}
.modal .tender input,.modal .mix-row input,.modal .change,.modal .tip-custom-in,.modal .ret-row input{padding:5px 10px;min-height:32px;font-size:12.5px;line-height:1.2;box-sizing:border-box}
.modal .tip-custom-in{margin:8px 0}
/* Cancel and the other plain modal buttons in the light navy (owner, 2026-09-05). */
.modal-foot .btn{background:var(--navy-2);color:#fff}
.modal .tender input{margin:0}
.whish-pay{display:flex;gap:12px;align-items:center;margin-top:8px}
.whish-qr{width:200px;height:200px;object-fit:contain;background:#fff;border:1px solid var(--line);border-radius:8px;flex:0 0 auto;cursor:zoom-in}
.whish-pay .change{flex:1}
/* The QR filling the screen for the guest to scan from the tablet (pos.js data-whish-zoom). */
.whish-zoom{position:fixed;inset:0;z-index:200;background:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;cursor:pointer}
.whish-zoom img{width:min(78vmin,560px);height:min(78vmin,560px);object-fit:contain}
.whish-zoom-amt{font-family:var(--mono);font-size:34px;font-weight:700;color:#111}
.whish-zoom-hint{font-family:var(--ui);font-size:14px;color:#555}
/* Guest bill: the link button and the folded QR */
.whish-pay-guest{flex-direction:column;align-items:stretch;gap:8px}
.whish-open{display:block;text-align:center;text-decoration:none;padding:14px 16px;font-size:15px}
.whish-qr-details summary{cursor:pointer}
.whish-qr-details .whish-qr{display:block;margin:10px auto 0;width:220px;height:220px}
.ret-row{display:flex;gap:6px;margin-top:6px;align-items:center}
.ret-row label{flex:1;display:flex;flex-direction:column;gap:3px;font-size:11px;color:var(--text-dim);letter-spacing:.04em;text-transform:uppercase}
.ret-row input{width:100%;padding:7px 10px;min-height:36px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:13px;box-sizing:border-box}
.change b{color:var(--brass)}

/* ---------- split payment (pos.js) ---------- */
.split-lines,.split-legs{display:flex;flex-direction:column;gap:8px;margin-bottom:14px;max-height:44vh;overflow-y:auto}
.split-line-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);cursor:pointer;font-size:13px}
.split-line-row .pr{font-family:var(--mono);color:var(--brass);margin-left:auto}
.split-assignee{font-size:11px;color:var(--text-dim);min-width:76px;text-align:right}
.split-leg-row{padding:11px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line)}
.split-leg-row .split-leg-hd{display:flex;align-items:center;gap:8px;margin-bottom:9px;font-size:13.5px;font-weight:500}
.split-leg-row .split-leg-hd .pr{font-family:var(--mono);color:var(--brass);margin-left:auto}

/* pointer-events:none because a toast is purely informational — it has no click handler
   anywhere, and while it sat over a button (the guest page's "View order" bar) it silently
   swallowed taps meant for what was underneath. */
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:var(--panel-2);border:1px solid var(--brass);padding:11px 18px;border-radius:999px;font-size:13px;z-index:60;max-width:88vw;text-align:center;pointer-events:none}
.toast[hidden]{display:none}
/* On the guest page the toast must clear the fixed bottom bar (View order / Make Payment),
   which sits at bottom:14px and stands ~46px tall — "Added X" was landing right on top of it. */
.order-body .toast{bottom:calc(76px + env(safe-area-inset-bottom, 0px))}

.check-toggle{display:none}
@media (max-width:880px){
  /* Phones: the cheque is a bottom sheet, fully hidden until a tab is tapped; the tab strip
     runs along the bottom edge instead of the right. */
  .check{position:fixed;inset:auto 0 56px 0;right:0;width:auto;max-width:none;height:72dvh;z-index:30;border-radius:16px 16px 0 0;transform:translateY(calc(100% + 60px));transition:transform .22s;box-shadow:0 -8px 30px rgba(0,0,0,.5)}
  .check.open{transform:translateY(0)}
  .cheque-tabs{position:fixed;left:0;right:0;bottom:0;height:56px;flex:none;flex-direction:row;border-left:0;border-top:1px solid var(--line);padding:5px 8px;overflow-x:auto;overflow-y:hidden}
  .cheque-tab{width:68px;height:40px;border-radius:12px 12px 0 0;border-left:1px solid var(--line);border-bottom:0;margin-bottom:0;margin-right:-10px}
  .cheque-tab:last-child{margin-right:0}
  .cheque-tab.on{width:72px}
  .cheque-tab.closed{margin-top:0;margin-left:auto;margin-right:0;height:40px;width:78px;border-radius:8px}
  /* padding-bottom reserves room for the collapsed check-toggle bar (position:fixed, full
     width, pinned to the bottom) so it doesn't sit on top of whatever's at the bottom of the
     sidebar — Logout, right below Settings — the same overlap .grid's own padding-bottom below
     already exists to avoid for the menu tiles. */
  .cats-col{flex:0 0 96px;padding-bottom:70px}
  .grid{padding-bottom:80px;grid-template-columns:repeat(auto-fill,minmax(128px,1fr))}

  .topbar{flex-wrap:wrap;row-gap:8px;gap:8px;padding:8px 10px}
  /* Scoped to .topbar specifically — order.html's .order-hd reuses the same .brand class for its
     own, differently-structured header (a column flex layout, not a wrapping row), and this same
     rule unscoped used to also apply there: flex-basis:100% on a column child of an auto-height
     container inflated it to ~2.5x its real content height, pushing everything after it (the
     "Just here to pay?" link) down past the header's own box and into the category row below. */
  .topbar .brand{flex:1 0 100%;flex-direction:row;align-items:baseline;gap:6px}
  .open-tables{margin-left:0;max-width:none;flex:1 1 auto;min-width:0}
  .btn-newtable,.btn-cheques{flex:0 0 auto;margin-left:0}
  .session-bar{margin-left:auto}
  .open-tables,.btn-newtable,.btn-cheques,.session-revenue-centre,.session-user,.btn-admin{padding:6px 9px;font-size:11px}
}
@media (max-width:420px){
  .session-revenue-centre{display:none!important}
  /* Scoped to .topbar for the same reason as above — order.html's guests need to keep seeing
     their table number here, it isn't just decorative the way it is on the POS topbar. */
  .topbar .brand span{display:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* ================= ADMIN ================= */
.admin{flex:1;display:flex;min-height:0}
.admin[hidden]{display:none}
.a-tabs{flex:0 0 168px;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;padding:10px 8px;gap:4px;overflow-y:auto}
.a-tab{padding:11px 12px;border-radius:var(--r);text-align:left;font-size:13px;color:var(--text-dim);border:1px solid transparent;transition:background .12s,color .12s,border-color .12s}
/* SVG-icon tabs (Clock In / Settings / Logout) — inline SVG instead of Unicode symbol glyphs,
   which can render as tofu boxes on a machine whose fonts don't cover that codepoint. */
.icon-tab{display:flex;align-items:center;gap:8px}
.icon-tab svg{flex:none}
/* The cursor is already a hand (buttons get it globally) — what was missing is any sign the
   thing under the pointer reacts, which is what makes a sidebar feel clickable. */
.a-tab:hover{background:var(--panel-2);color:var(--text);border-color:var(--line)}
.a-tab.on{background:var(--panel-2);color:var(--text);border-color:var(--line)}
.a-tab.on:hover{border-color:var(--brass)}
.a-subnav{display:flex;gap:8px;margin-bottom:16px}
.a-subtab{padding:8px 14px;border-radius:8px;font-size:12.5px;color:var(--text-dim);background:var(--panel);border:1px solid var(--line);transition:color .12s,border-color .12s}
.a-subtab:hover{color:var(--text);border-color:var(--brass)}
.a-subtab.on{background:var(--panel-2);color:var(--brass);border-color:var(--brass)}
/* Reports tab — a second-level nav (by report group) alongside the main tab strip, since ~15
   report types in one flat sub-nav (the pattern every other tab's sub-nav uses) would be
   unusable. Each group gets its own small subnav row rather than one giant flat list. */
.a-report-layout{display:flex;gap:22px;align-items:flex-start}
.a-report-nav{flex:0 0 190px;display:flex;flex-direction:column;gap:14px}
.a-report-group-label{margin:0 0 6px;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim)}
.a-report-nav .a-subnav{flex-direction:column;gap:4px;margin-bottom:0}
.a-report-nav .a-subtab{text-align:left}
.a-report-main{flex:1;min-width:0}
.a-report-filters{display:flex;flex-wrap:wrap;align-items:end;gap:14px;margin-bottom:8px}
/* .btn's own base rule is flex:1 (fine for a full-width row of two or three EQUAL actions), which
   in a mixed row like this one — Apply plus the export/print group, squeezed further whenever a
   report adds its own Cheque ref/Cashier filters — divides the leftover space evenly regardless of
   each button's own text length and ellipsis-truncates whichever one needed more of it than its
   equal share ("Export PDF"/"Export Excel" reading as "Export P…"/"Export E…", owner's report, 13
   Sep 2026). flex:0 0 auto here (same idea as the existing .btn.small) keeps every button at its
   own natural width instead; .a-report-filters already wraps, so a genuinely narrow window drops
   the group to its own line rather than clipping the words on it. */
.a-report-filters .btn{flex:0 0 auto}
.a-report-filters label{display:flex;flex-direction:column;gap:4px;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim)}
.a-nightaudit-panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:16px;margin-bottom:18px}
.a-nightaudit-status{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.a-nightaudit-panel .a-hint{margin:8px 0 0}
.a-nightaudit-panel .mgroup{margin-top:14px;margin-bottom:0}
.a-report-filters input{padding:8px 10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px;color-scheme:dark}
.a-report-export{display:flex;gap:8px;margin-left:auto}
/* Stale as of 13 Sep 2026 — these two used to compensate for .btn.ghost inheriting .btn's dark
   ink color (meant for tan fills) and vanishing on the dark theme, back when .btn.ghost had no
   color of its own. .btn.ghost now sets color:#fff directly against its own --navy-2 fill (a
   dark/saturated accent in every theme, this one included), which reads fine everywhere on its
   own — but color:var(--text) here has higher specificity and overrides that white with the
   THEME's own text color, which is dark on every light-ground theme (Light, Terracotta, Light
   Terracotta, Light Solid): near-black text on a still-dark navy fill, unreadable ("why export
   in black", owner's report). Removed rather than fixed forward, since the base rule already
   does the right thing without a per-context override. */
/* Numeric report columns read right-aligned with lined-up digits, the way ledgers do. */
.a-report-table th.num,.a-report-table td.num{text-align:right;font-variant-numeric:tabular-nums}
.a-report-title{margin:4px 0 14px;font-size:15px;color:var(--text)}
.a-report-table-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r)}
.a-report-table{width:100%;border-collapse:collapse;font-size:12.5px;white-space:nowrap}
.a-report-table th,.a-report-table td{padding:9px 12px;text-align:left;border-bottom:1px solid var(--line)}
.a-report-table th{color:var(--text-dim);font-weight:600;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;background:var(--panel)}
.a-report-table tbody tr:last-child td{border-bottom:0}
.a-report-table tbody tr:hover td{background:var(--panel-2)}
.a-report-reprint{display:flex;gap:10px;margin-top:16px}
@media (max-width:880px){
  .a-report-layout{flex-direction:column}
  .a-report-nav{flex-direction:row;flex-wrap:wrap;width:100%}
}
/* AI Assistant tab — a chat, not a report table, so it gets its own small set of rules rather
   than stretching .a-report-* to cover a bubble layout they were never meant for. */
.a-ai-layout{max-width:820px}
.a-ai-transcript{display:flex;flex-direction:column;gap:12px;min-height:220px;max-height:56vh;overflow-y:auto;border:1px solid var(--line);border-radius:var(--r);background:var(--panel);padding:16px;margin-bottom:12px}
.a-ai-msg{display:flex}
.a-ai-msg-user{justify-content:flex-end}
.a-ai-bubble{max-width:78%;padding:10px 14px;border-radius:12px;font-size:13.5px;line-height:1.5;background:var(--panel-2);border:1px solid var(--line);color:var(--text)}
.a-ai-msg-user .a-ai-bubble{background:var(--brass);border-color:var(--brass);color:#fff}
.a-ai-msg-error .a-ai-bubble{border-color:#b0453a;color:#e8897c}
.a-ai-bubble p{margin:0 0 8px}
.a-ai-bubble p:last-child{margin-bottom:0}
.a-ai-bubble ul{margin:0 0 8px;padding-left:18px}
.a-ai-bubble table{width:100%;border-collapse:collapse;font-size:12.5px;margin:4px 0 8px}
.a-ai-bubble th,.a-ai-bubble td{padding:6px 9px;text-align:left;border-bottom:1px solid var(--line)}
.a-ai-bubble th{color:var(--text-dim);font-weight:600;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase}
.a-ai-typing{font-size:12px;color:var(--text-dim);margin:-4px 0 12px;font-style:italic}
.a-ai-chart{margin-top:6px;max-width:520px}
.a-ai-chart-title{margin:0 0 4px;font-size:11px;color:var(--text-dim)}
.a-ai-chart-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:4px}
.a-ai-chart-legend span{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text-dim)}
.a-ai-chart-legend i{display:inline-block;width:9px;height:9px;border-radius:2px}
.a-ai-actions{display:flex;gap:8px;margin-top:10px}
.a-ai-actions .btn.ghost{font-size:11.5px;padding:6px 10px;color:#fff}
.ai-chart-type-picker{display:flex;gap:10px;justify-content:center}
.a-ai-inputrow{display:flex;gap:10px;align-items:flex-end}
.a-ai-inputrow textarea{flex:1;resize:vertical;min-height:44px;padding:10px 12px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px}
.screen-editor{display:grid;grid-template-columns:1fr 1fr;gap:20px;align-items:start}
.screen-picker{display:flex;flex-direction:column;gap:10px}
.screen-picker input[type=text]{padding:9px 10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px}
.screen-picker-list{max-height:52vh;overflow-y:auto}
.screen-pick-row{cursor:grab}
.screen-pick-row.a-row-disabled{opacity:.4;pointer-events:none}
/* The item number, shown in the picker and in the placed-entries list — monospace and fixed
   width so the numbers line up into a readable column instead of drifting with name length. */
.screen-pick-num{font-family:var(--mono);font-size:11px;color:var(--text-dim);min-width:34px;display:inline-block;text-align:right;margin-right:8px}
/* From #/To # bulk add — mirrors the item grids' own "+ Add range" row. */
.screen-pick-range{display:flex;align-items:center;gap:6px;margin:8px 0;flex-wrap:wrap}
.screen-pick-range label{font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em}
.screen-pick-range input{width:72px;padding:6px 8px;border-radius:6px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:12px}
.screen-pick-range .btn{width:auto;padding:6px 12px;font-size:12px}
/* Both lists here are long — a whole outlet's menu on the left, a whole screen's contents on
   the right — so the rows are deliberately tighter than the standard .a-row/.a-checkrow used
   elsewhere in Admin: more of the menu fits on screen without scrolling, which is the whole
   job of this editor. Scoped to the screen editor so other tabs keep their roomier rows. */
.screen-contents{display:flex;flex-direction:column;gap:4px;min-height:120px;padding:6px;border-radius:var(--r);border:1px dashed var(--line);max-height:52vh;overflow-y:auto}
.screen-entry-row{cursor:grab;padding:5px 10px}
.screen-entry-row .a-nm{font-size:12.5px}
.screen-entry-row .a-meta{font-size:10.5px}
.screen-entry-row .a-actions button{width:26px;padding:3px 0;font-size:11px}
.screen-picker-list .screen-pick-row{padding:4px 8px;margin-bottom:3px}
.screen-picker-list .a-cn{font-size:12.5px}
.screen-picker-list .a-meta{font-size:10.5px}
/* Sits where the old read-only preview used to, at the bottom of the editor — the way into the
   Layout editor, which is now the single place the till grid is both seen and arranged. */
.screen-layout-cta{grid-column:1;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:14px 16px;border-radius:var(--r);border:1px dashed var(--line);background:var(--shell)}
.screen-layout-cta b{display:block;font-size:14px;margin-bottom:3px}
.screen-layout-cta p{margin:0 0 4px;font-size:12px;color:var(--text-dim);max-width:60ch}
.a-content{flex:1;overflow-y:auto;padding:18px 22px}
.a-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.a-head h2{margin:0;font-size:18px}
.a-head p{margin:2px 0 0;font-size:12px;color:var(--text-dim)}
.a-list{display:flex;flex-direction:column;gap:8px}
.a-row{display:flex;align-items:center;gap:10px;padding:11px 14px;border-radius:var(--r);background:var(--panel);border:1px solid var(--line)}
/* Clicking a row opens its editor (see admin.js's row->edit rule) — so say so on hover. Keyed
   off the row actually CONTAINING an Edit button, which is exactly the same condition the JS
   uses, so a row with no editor (Workstations) keeps the normal arrow and stays inert. */
.a-row:has(button[data-edit-serving-period]),
.a-row:has(button[data-edit-family-group]),
.a-row:has(button[data-edit-major-group]),
.a-row:has(button[data-edit-option-group]),
.a-row:has(button[data-edit-item-class]),
.a-row:has(button[data-edit-kds-class]),
.a-row:has(button[data-edit-printer]),
.a-row:has(button[data-edit-kds]),
.a-row:has(button[data-edit-user]),
.a-row:has(button[data-edit-role]),
.a-row:has(button[data-edit-revenue-centre]),
.a-row:has(button[data-open-screen]){cursor:pointer}
.a-row:has(button[data-edit-serving-period]):hover,
.a-row:has(button[data-edit-family-group]):hover,
.a-row:has(button[data-edit-major-group]):hover,
.a-row:has(button[data-edit-option-group]):hover,
.a-row:has(button[data-edit-item-class]):hover,
.a-row:has(button[data-edit-kds-class]):hover,
.a-row:has(button[data-edit-printer]):hover,
.a-row:has(button[data-edit-kds]):hover,
.a-row:has(button[data-edit-user]):hover,
.a-row:has(button[data-edit-role]):hover,
.a-row:has(button[data-edit-revenue-centre]):hover,
.a-row:has(button[data-open-screen]):hover{border-color:var(--brass)}
/* Inside such a row the controls keep their own meaning — a text field must still look typeable
   and a disabled button must not look clickable. */
.a-row input[type=text],.a-row input[type=number]{cursor:text}
.a-row button:disabled{cursor:default}
.a-row.inactive{opacity:.5}
.a-row .a-nm{font-size:13.5px;font-weight:500}
.a-row .a-nar{font-size:12px;color:var(--text-dim);margin-left:6px}
.a-tag{display:inline-block;margin-left:8px;padding:2px 8px;border-radius:999px;font-size:10.5px;letter-spacing:.03em}
.a-tag.warn{background:rgba(212,98,42,.15);border:1px solid var(--alert);color:var(--alert)}
.a-tag:not(.warn){background:rgba(184,151,90,.15);border:1px solid var(--brass);color:var(--brass)}
.mgroup-disabled>p{opacity:.55}
.pin-reveal{margin:14px 0;padding:18px;border-radius:var(--r);background:var(--shell);border:1px solid var(--line);text-align:center;font-family:var(--mono);font-size:32px;letter-spacing:.25em;color:var(--pin)}
/* The name column takes the slack, so status, dropdown, IP box and buttons sit at the same x on every
   row regardless of how long the name is (owner, 2026-09-05: Workstations looked staggered). */
.a-row > div:first-child{flex:1 1 auto;min-width:0}
.a-row .a-meta{font-family:var(--mono);font-size:11px;color:var(--text-dim)}
.a-row select{padding:5px 8px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);color:var(--text);font-family:inherit;font-size:11.5px}
.ws-ip-input{width:132px;padding:5px 8px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:11.5px}
.ws-ip-input::placeholder{color:var(--text-dim);opacity:.8}
.ws-ip-input:focus{border-color:var(--brass);outline:none}
/* Always at the right edge, whether or not a row has meta text in the middle (owner, 2026-09-05). */
.a-row .a-actions{display:flex;gap:6px;margin-left:auto}
.a-row .a-actions button{padding:6px 10px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);font-size:11px}
.a-row .a-actions button:hover{border-color:var(--brass);color:var(--brass)}
.a-row .a-actions button.danger:hover{border-color:var(--stop);color:var(--stop)}
.a-row .a-actions button:disabled{opacity:.35;pointer-events:none}
.a-cathd{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);margin:16px 0 6px}
.a-cathd:first-child{margin-top:0}
.a-empty{padding:24px;text-align:center;color:var(--text-dim);font-size:13px}
/* Settings tab section rail (owner, 12 Sep 2026: redesign it completely) — same mechanics as
   ResM's own Setup rail (resmaro.css .rm-setup2/.rm-setuprail/.rm-sec), reimplemented under
   admin's own a- prefix and tokens rather than sharing resmaro.css across apps: click a section,
   only it shows, so Settings stops being one long scroll through unrelated fields. */
.a-setup2{display:grid;grid-template-columns:220px minmax(0,760px);gap:24px;align-items:start}
.a-setuprail{position:sticky;top:12px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:8px;display:flex;flex-direction:column;gap:2px}
.a-setuprail button{display:flex;align-items:center;gap:10px;width:100%;text-align:left;background:none;border:0;border-radius:calc(var(--r) - 4px);padding:10px 14px;font:inherit;font-weight:600;font-size:13.5px;color:var(--text-dim);cursor:pointer}
.a-setuprail button i{width:8px;height:8px;border-radius:50%;background:var(--line);flex:none}
.a-setuprail button:hover{background:var(--panel-2);color:var(--text)}
.a-setuprail button.on{background:color-mix(in srgb, var(--navy) 14%, transparent);color:var(--text)}
.a-setuprail button.on i{background:var(--navy)}
.a-secpos{color:var(--text-dim);font-size:13px;margin:6px 0 10px}
.a-sec{display:none;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:6px 0 4px}
.a-sec.on{display:block}
.a-sec h2{margin:0;padding:16px 22px 8px;font-size:18px}
.a-lead{margin:0;padding:0 22px 10px;color:var(--text-dim);font-size:13px}
.a-secrow{display:grid;grid-template-columns:220px 1fr;gap:14px;align-items:start;padding:14px 22px;border-top:1px solid var(--line)}
.a-secrow:first-of-type{border-top:0}
.a-secrow>label{font-weight:700;font-size:13.5px;padding-top:8px;color:var(--text)}
.a-secrow>label small{display:block;font-weight:500;color:var(--text-dim);font-size:12px;margin-top:2px}
.a-secctl{display:flex;flex-wrap:wrap;gap:10px;align-items:center;min-width:0}
/* Same visual treatment as .a-field's inputs (adversarial review, 12 Sep 2026: the Appearance
   select rendered as a bare unstyled native dropdown here, since .a-field's own input/select rule
   never reaches a control sitting inside .a-secctl instead). */
.a-secctl input:not([type=checkbox]),.a-secctl select{width:auto;min-width:140px;margin:0;padding:7px 10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px}
.a-sechint{flex-basis:100%;color:var(--text-dim);font-size:12.5px}
@media (max-width:900px){.a-setup2{grid-template-columns:1fr}.a-setuprail{position:static;flex-direction:row;flex-wrap:wrap}.a-secrow{grid-template-columns:1fr}}
.a-field{margin-bottom:12px}
.a-field label{display:block;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin-bottom:5px}
.a-field input:not([type=checkbox]):not([type=radio]):not([type=hidden]),.a-field select,.a-field textarea{width:100%;padding:7px 10px;border-radius:8px;background:var(--shell);border:1px solid var(--line);color:var(--text);font-family:var(--ui);font-size:13px}
.a-field input[type=time]{color-scheme:dark}
.a-row2{display:flex;gap:10px}
.a-row2>.a-field{flex:1}

/* ---------- Menu Item spreadsheet grid ---------- */
.item-grid-config{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;padding:12px 14px;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);margin-bottom:14px}
.item-grid-config .a-field{margin-bottom:0;min-width:180px;flex:1}
/* The search box no longer eats the whole toolbar — it's a fixed, modest width so the add
   buttons and the Family Group picker sit right beside it instead of being pushed to the edge. */
.item-grid-config .a-field-search{flex:0 0 240px;min-width:160px}
.item-grid-range{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;padding:12px 14px;background:var(--panel-2);border:1px dashed var(--line);border-radius:var(--r);margin-bottom:14px}
.item-grid-range .a-field{margin-bottom:0}
.item-grid-range .a-field input{width:110px}
.item-grid-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r)}
/* table-layout:fixed — required for the <colgroup> widths set by manual column resize (see
   itemGridColgroupHtml/the mousedown handler in admin.js) to actually govern column sizing
   instead of just hinting at it. width:100% + a 9th unsized filler <col> (see
   itemGridColgroupHtml) is what lets the table always fill its container: the 8 real columns
   keep the exact literal pixel width they were dragged to, and the filler silently absorbs
   whatever's left — never the browser proportionally re-stretching every column to fill the
   gap, which would undo a manual resize. A total wider than the container still scrolls inside
   .item-grid-wrap's own overflow-x, same as before. */
.item-grid{width:100%;table-layout:fixed;border-collapse:collapse;font-size:12.5px}
.item-grid th{position:relative;text-align:left;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);padding:9px 8px;background:var(--panel-2);border-bottom:1px solid var(--line);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.item-grid td{padding:2px 5px;border-bottom:1px solid var(--line);vertical-align:middle;overflow:hidden;text-overflow:ellipsis}
.item-grid tr:last-child td{border-bottom:0}
.item-grid tr.inactive{opacity:.5}
/* Dragged from a header's right edge to resize that column — see the mousedown listener in
   admin.js. The hit area is a wider 8px strip (easy to grab without much precision), but only
   the thin ::after line down its middle is ever drawn, so the boundary itself is always visible
   as a fine separator rather than only appearing once you happen to hover over it. */
.item-grid th .col-resize-handle{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:1}
.item-grid th .col-resize-handle::after{content:"";position:absolute;top:0;left:50%;width:1px;height:100%;background:var(--line)}
.item-grid th .col-resize-handle:hover::after,.item-grid th .col-resize-handle.active::after{background:var(--brass);width:2px}
.item-grid input[type=text],.item-grid input[type=number]{width:100%;padding:4px 8px;border-radius:6px;background:var(--shell);border:1px solid transparent;color:var(--text);font-family:var(--ui);font-size:12.5px}
.item-grid input[type=text]:focus,.item-grid input[type=number]:focus{border-color:var(--brass);outline:none}
.item-grid input.code-cell,.item-grid input.price-cell{font-family:var(--mono)}
/* Photo column — every menu picture visible straight from the grid, and each thumbnail is the
   button that opens that item's photo editor. Items with no photo of their own show a "+"; an
   outlet item falling back to the catalogue photo is faded so inherited never looks like set. */
.item-grid td.photo-cell{padding:3px 5px}
.item-grid /* Reads as the cell's text, behaves as a control: an option-group set doesn't fit a <select>,
   so the cell itself opens the picker. Underlined on hover so it's discoverably clickable. */
/* Licensing tab: install on the left, what-you-own on the right. Single column on narrow. */
.shutdown-wall{flex:1;display:flex;align-items:center;justify-content:center;padding:40px}
.shutdown-box{max-width:520px;text-align:center;background:var(--panel);border:1px solid var(--stop);border-radius:12px;padding:34px 38px}
.shutdown-box h2{margin:0 0 12px;color:var(--stop)}
.shutdown-box p{font-size:13.5px;line-height:1.6;color:var(--text-dim)}
.unlicensed-badge{background:var(--stop);color:#fff;font-size:10px;font-weight:700;letter-spacing:.12em;padding:4px 9px;border-radius:5px;margin-left:10px;flex:0 0 auto}
.lic-cols{display:grid;grid-template-columns:1fr 1fr;gap:28px;max-width:960px;margin-top:14px}
@media (max-width:900px){.lic-cols{grid-template-columns:1fr}}
.lic-col{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:16px}
.lic-h{margin:0 0 12px;font-size:13px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim)}
.lic-ok{padding:9px 12px;border-radius:var(--r);background:rgba(60,140,80,.12);border:1px solid rgba(60,140,80,.4);font-size:12.5px;max-width:960px}
.lic-fact{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px dotted var(--line);font-size:12.5px}
.lic-fact b{text-align:right}
.lic-modules{margin-top:14px;display:flex;flex-direction:column;gap:6px}
.lic-module{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);font-size:13px;opacity:.55}
.lic-module.on{opacity:1;border-color:rgba(60,140,80,.5)}
.lic-module-mark{font-weight:700;width:16px;text-align:center}
.lic-module-exp{margin-left:auto;font-size:11.5px;color:var(--text-dim);font-variant-numeric:tabular-nums}
.lic-module.on .lic-module-mark{color:#3c8c50}
.lic-module:not(.on) .lic-module-mark{color:var(--stop)}
.screen-order-row{cursor:grab}
.screen-order-row:active{cursor:grabbing}
.drag-grip{color:var(--text-dim);font-size:13px;cursor:grab;user-select:none}
.og-cell{background:none;border:0;padding:0;font:inherit;color:inherit;text-align:left;cursor:pointer;width:100%}
.og-cell:hover{color:var(--brass);text-decoration:underline}
.photo-thumb{width:44px;height:44px;padding:0;border-radius:6px;overflow:hidden;background:var(--panel-2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;cursor:pointer}
.item-grid .photo-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.item-grid .photo-thumb span{color:var(--text-dim);font-size:16px;line-height:1}
.item-grid .photo-thumb:hover{border-color:var(--brass)}
.item-grid .photo-thumb.inherited img{opacity:.55}
/* A locked name (sold before, or disabled — see nameCellHtml/assertRenamable): reads as plain
   text rather than an editable field, so it's obvious before you click into it. Hovering shows
   the reason. */
.item-grid input[type=text][readonly]{background:transparent;color:var(--text-dim);cursor:default}
.item-grid input[type=text][readonly]:focus{border-color:transparent}
.item-grid select{width:100%;min-width:0;padding:4px 8px;border-radius:6px;background:var(--shell);border:1px solid transparent;color:var(--text);font-family:var(--ui);font-size:12px}
.item-grid select:focus{border-color:var(--brass);outline:none}
/* A <td> must stay a table cell: display:flex here pulled the actions column out of the row
   structure and drew it as its own stack beside the grid (owner, 2026-09-05: "not aligned"). */
.item-grid .item-grid-actions{white-space:nowrap}
.item-grid .item-grid-actions button{margin-right:4px}
.item-grid .item-grid-actions button{padding:5px 8px;font-size:11px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line)}
.item-grid .item-grid-actions button:hover{border-color:var(--brass);color:var(--brass)}
.item-grid .item-grid-actions button.danger:hover{border-color:var(--stop);color:var(--stop)}
.item-grid .item-grid-actions button:disabled{opacity:.35;pointer-events:none}
/* This outlet's own Menu Items grid only (data-context-grid="menuItem") — its More…/Disable
   buttons are tapped constantly on the floor, so they get a larger touch target than Full
   Menu's own single "More…" button, which shares the same base .item-grid-actions styling. */
table[data-context-grid="menuItem"] .item-grid-actions button{padding:6px 12px;font-size:12.5px}
.item-grid .tiers-note{font-family:var(--mono);font-size:11px;color:var(--text-dim);white-space:nowrap}
.item-grid td.major-group-cell{color:var(--text-dim);font-size:12px;white-space:nowrap}
.item-grid td.code-cell{font-family:var(--mono);font-size:12.5px;padding-left:8px;white-space:nowrap;cursor:default}
.item-grid .outlet-chips{display:flex;flex-wrap:wrap;gap:3px;max-width:160px}
.item-grid .outlet-chip{padding:2px 6px;border-radius:20px;background:var(--panel-2);border:1px solid var(--line);font-size:10px;color:var(--text-dim);white-space:nowrap}
.item-grid .outlet-chip.here{border-color:var(--brass);color:var(--brass)}
.item-grid .import-btn{padding:5px 8px;font-size:11px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);white-space:nowrap}
.item-grid .import-btn.on{border-color:var(--brass);color:var(--brass)}
.item-grid .import-btn:hover{border-color:var(--brass);color:var(--brass)}

.ctx-menu{position:fixed;z-index:500;min-width:150px;background:var(--panel-2);border:1px solid var(--line);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.35);padding:5px;font-size:12.5px}
.ctx-menu button{display:block;width:100%;text-align:left;padding:8px 10px;border-radius:6px;background:transparent;color:var(--text)}
.ctx-menu button:hover{background:var(--panel)}
.ctx-menu button.danger:hover{color:var(--stop)}
.ctx-menu-insert{position:fixed;z-index:500;background:var(--panel-2);border:1px solid var(--line);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.35);padding:14px;display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;max-width:360px}
.ctx-menu-insert .a-field{margin-bottom:0}
.ctx-menu-insert .a-field input{width:90px}
.ctx-menu-insert p.a-hint{width:100%;margin:0 0 2px}
.a-variant-row,.a-comp-row{display:flex;gap:8px;align-items:center;margin-bottom:6px}
.a-variant-row input{flex:1}
.a-variant-row button,.a-comp-row button{flex:0 0 auto;padding:8px 10px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);font-size:12px}
.a-addbtn{margin-top:4px;padding:6px 12px;border-radius:6px;min-height:30px;background:var(--panel-2);border:1px dashed var(--line);font-size:12px;color:var(--text-dim)}
.a-addbtn:hover{border-color:var(--brass);color:var(--brass)}
.a-addbtn:disabled{opacity:.4;cursor:not-allowed}
.a-addbtn:disabled:hover{border-color:var(--line);color:var(--text-dim)}
.a-checkrow{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--panel-2);border:1px solid var(--line);margin-bottom:6px}
.a-checkrow input[type=checkbox]{width:16px;height:16px}
.a-checkrow .a-cn{flex:1;font-size:13px}
.a-checkrow .a-removable{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-dim)}
.a-checklist{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:0 12px}
.a-warn{padding:10px 12px;border-radius:8px;background:rgba(212,98,42,.12);border:1px solid var(--alert);font-size:12.5px;margin-bottom:12px}
.a-formerr{padding:10px 12px;border-radius:8px;background:rgba(212,98,42,.12);border:1px solid var(--alert);font-size:12.5px;margin-bottom:12px}
/* Every other .a-formerr is only ever added to the DOM once there's a real message to show — the
   one exception is the Licensing tab's #stLicenseErr, left in the markup empty and filled in later
   on an install failure, which meant an empty box (padding+border+background, no text) sat there
   permanently. :empty catches that case generically, so the same mistake can't quietly reappear
   somewhere else the same way. */
.a-formerr:empty{display:none}
.a-hint{font-size:12px;color:var(--text-dim);margin:0 0 12px}
.a-headbtns{display:flex;gap:8px}
.a-headbtns .a-addbtn{margin-top:0}
.a-code{font-family:var(--mono);font-size:11.5px;color:var(--text-dim);margin-right:8px}

/* ---------- Admin on a phone/small tablet: the 168px text-label sidebar has no room to live
   next to content, so it becomes a horizontal scrollable strip along the top instead (same
   breakpoint the till uses for its own mobile layout, above). Everything else here just
   un-crowds rows/forms/grids that were laid out assuming desktop width. ---------- */
@media (max-width:880px){
  .admin{flex-direction:column}
  .a-tabs{flex:0 0 auto;flex-direction:row;overflow-x:auto;overflow-y:hidden;border-right:0;border-bottom:1px solid var(--line);-webkit-overflow-scrolling:touch}
  .a-tabs .a-tab{flex:0 0 auto;white-space:nowrap}
  .a-tabs .a-tab[data-tab="__exit"]{margin-top:0!important;margin-left:auto!important}
  .a-content{padding:14px 16px 28px}
  .a-head{flex-wrap:wrap;row-gap:10px}
  .a-row{flex-wrap:wrap;row-gap:8px}
  .a-row .a-meta{flex:1 1 100%;margin-left:0;order:2}
  .a-row .a-actions{order:3;flex:1 1 100%;flex-wrap:wrap;justify-content:flex-end}
  .a-row2{flex-direction:column;gap:0}
  .a-subnav{flex-wrap:wrap;row-gap:8px}
  .screen-editor{grid-template-columns:1fr}
}
@media (max-width:480px){
  .a-content{padding:12px 12px 24px}
  .item-grid-config .a-field,.item-grid-config{min-width:0}
  .item-grid-config .a-field{flex:1 1 128px}
}
/* Below 420px the till's own topbar rule (above) hides .session-revenue-centre outright to save
   space — fine there, since the till mostly stays on the RC it was logged into. In Admin it's a
   <select> and the only way a multi-RC admin switches location, so it needs to stay reachable
   even on a small phone; this wins on specificity over that rule despite both using !important. */
@media (max-width:420px){
  .admin-topbar .session-revenue-centre{display:block!important;flex:1 1 auto;min-width:0}
}

/* ================= LOGIN / REVENUE CENTRE PICKER =================
   KDS and ResM still use the old logo-v3.png, transparent and neutral enough to sit on either
   theme's background unchanged. The till/BOH/order screens wear the R&B mark instead (12 Sep
   2026), which needs separate light/dark cuts — see the :root[data-theme="dark"] rule below. This
   gradient just needs to track --shell/--panel the same way the rest of the app does, via the
   --login-* tokens defined in :root above. */
.login-view,.revenue-centre-picker,.workstation-setup{position:fixed;inset:0;z-index:70;background:var(--login-bg);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:32px;padding:24px}
.login-view[hidden],.revenue-centre-picker[hidden],.workstation-setup[hidden]{display:none}

.login-logo{width:min(80vw,440px);height:auto;filter:drop-shadow(0 16px 34px rgba(0,0,0,.55))}
/* R&B logo handover (12 Sep 2026): navy mark for every theme except the two dark ones, which sit
   on the shared --shell dark ground and need the white cut instead. dark-solid has no color rules
   of its own (see :root[data-theme="dark-solid"] above) — it inherits base :root's dark colors and
   only flattens corners — so it needs the dark logo the same as plain dark. */
:root[data-theme="dark"] .login-logo,
:root[data-theme="dark"] .order-logo,
:root[data-theme="dark"] .order-logo-lg,
:root[data-theme="dark-solid"] .login-logo,
:root[data-theme="dark-solid"] .order-logo,
:root[data-theme="dark-solid"] .order-logo-lg{content:url("assets/logo-rnb-dark.png")}
/* BOH handover (12 Sep 2026): same equal-specificity rule as above, so it has to come AFTER it to
   win for BOH's login screens — .login-logo-boh sits alongside .login-logo on the same <img>, not
   instead of it, so the till's rule immediately above would otherwise apply there too. */
:root[data-theme="dark"] .login-logo-boh,
:root[data-theme="dark-solid"] .login-logo-boh{content:url("assets/logo-boh-dark.png")}
/* KDS (13 Sep 2026): same rule shape, but KDS itself only ever stamps "dark" or "light" (no
   picker of its own — see kds.html's inline snippet), so no dark-solid entry is reachable here. */
:root[data-theme="dark"] .login-logo-kds{content:url("assets/logo-kds-dark.png")}
/* ResM (14 Sep 2026): its own mark (the table M with the candle dot, Logo/2026/ResM), same equal-
   specificity rule shape so it has to sit after the till's rule to win on ResM's login. */
:root[data-theme="dark"] .login-logo-resm,
:root[data-theme="dark-solid"] .login-logo-resm{content:url("assets/logo-resm-dark.png")}

/* Small corner control, page-level (not nested in any one screen) — lets anyone reaching this
   server's public URL (e.g. an ngrok tunnel) grab the desktop installers without needing to log
   in first, same reasoning as /downloads itself being unauthenticated. z-index above the
   login-view/revenue-centre-picker/workstation-setup overlays' own 70 so it stays reachable no
   matter which of those is currently covering the screen — including the very first thing a
   fresh install shows, the workstation-setup PIN screen, which sits on top of everything else. */
.dl-wrap{position:fixed;top:10px;right:10px;z-index:80}
.dl-btn{padding:6px 11px;border-radius:999px;background:var(--panel-2);border:1px solid var(--line);color:var(--text-dim);font-family:inherit;font-size:11px;cursor:pointer;white-space:nowrap}
.dl-btn:hover{color:var(--text);border-color:var(--text-dim)}
.dl-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:170px;background:var(--login-menu-bg);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:12px;padding:6px;box-shadow:0 20px 50px rgba(0,0,0,.5);display:flex;flex-direction:column;gap:2px}
.dl-menu[hidden]{display:none}
.dl-menu a{padding:9px 12px;border-radius:8px;color:var(--text);text-decoration:none;font-size:13px;display:block}
.dl-menu a:hover{background:var(--panel-2)}

.login-card{width:100%;max-width:320px;padding:28px;background:var(--login-card-bg);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:12px;text-align:center;box-shadow:0 20px 50px rgba(0,0,0,.5)}
/* .btn.ghost has no color of its own (see its own rule) since it sits on backgrounds the rest of
   the app doesn't share a class with (the login card here, the till's own check panel elsewhere
   for .check/.pay) — each context sets it explicitly rather than one value trying to fit both.
   var(--text) is correct in both themes here since the login card now tracks theme too. */
.login-card .btn.ghost{color:var(--text)}
.login-brand{margin-bottom:20px}
.login-brand b{display:block;font-size:19px;letter-spacing:.02em}
/* App name above the PIN prompt (owner, 2026-09-05): the name on its own line, the prompt under it. */
.login-brand .login-app{display:block;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-dim);margin-bottom:6px}
.login-brand span{display:block;font-size:11px;color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-top:4px}
.country-card{max-width:380px}
.country-card .login-brand span{text-transform:none;letter-spacing:normal;font-size:12.5px;margin-top:6px;line-height:1.4}
.country-options{display:flex;flex-direction:column;gap:10px}
.country-opt{display:flex;align-items:center;gap:14px;width:100%;padding:16px 18px;border-radius:12px;background:var(--login-opt-bg);border:1px solid var(--line);color:var(--text);font-size:15px;font-weight:600;text-align:left;cursor:pointer}
.country-opt:hover{border-color:var(--brass);background:var(--login-opt-bg-hover)}
.country-flag{font-size:28px;line-height:1}
.pin-dots{display:flex;justify-content:center;gap:14px;margin-bottom:10px}
/* The login screen's outlet dropdown — same quiet glassy language as the login option rows. */
.login-rc-select{width:100%;margin-bottom:14px;padding:12px 14px;border-radius:10px;background:var(--login-opt-bg);border:1px solid var(--line);color:var(--text);font-family:inherit;font-size:14px;font-weight:600;text-align:center}
.login-rc-select:focus{border-color:var(--brass);outline:none}
.login-rc-select[hidden]{display:none}
.pin-dots span{width:14px;height:14px;border-radius:50%;border:2px solid var(--line);background:transparent}
.pin-dots span.filled{background:var(--pin);border-color:var(--pin)}
.pin-error{color:var(--stop);font-size:12px;font-weight:600;min-height:16px;margin-bottom:10px}
.pin-error[hidden]{visibility:hidden}
.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.pin-pad button{padding:16px 0;border-radius:10px;background:var(--panel-2);border:1px solid var(--line);font-size:18px;font-family:var(--mono)}
.pin-pad button:active{transform:scale(.96);border-color:var(--pin)}
.pin-pad button.ghost{font-size:12px;font-family:var(--ui);color:var(--text-dim)}
.revenue-centre-list{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.revenue-centre-list button{padding:16px;border-radius:10px;background:var(--panel-2);border:1px solid var(--line);font-size:15px;text-align:left}

/* ---------- workstation setup (src/shared/workstationSetup.js) — reuses .pin-dots/.pin-pad as-is
   (6 dots instead of 4, same visual language as login) for the PIN entry; this is just the
   confirmation screen's centered name display. ---------- */
.ws-setup-name{text-align:center;font-size:28px;font-weight:700;color:var(--brass);letter-spacing:.01em;padding:18px 10px;word-break:break-word}

/* ================= CUSTOMER ORDER (order.html) =================
   Phone-first by default (customers are almost always on a phone), reusing
   .tile/.cats/.opt/.modal/.ln-* as-is for anything that isn't page layout.
   A desktop breakpoint below frames this same column as a floating card on
   a branded backdrop instead of stranding a bare phone-width layout in a
   sea of flat background — a guest who scans the QR code from a tablet or
   a laptop at the table still gets an intentional-looking page. */
.order-body{overflow-y:auto;display:flex;justify-content:center;background:var(--shell)}
.order-state{width:100%;max-width:520px;min-height:100dvh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px;text-align:center}
.order-state[hidden]{display:none}
.order-spinner{display:flex;flex-direction:column;align-items:center}
.order-spinner h2{margin:0 0 8px;font-size:18px}
.order-spinner p{margin:0;color:var(--text-dim);font-size:13.5px;max-width:38ch}
.order-logo-lg{width:min(46vw,140px);height:auto;margin-bottom:18px;filter:drop-shadow(0 10px 22px rgba(0,0,0,.45))}
.order-app{width:100%;max-width:520px;margin:0 auto;display:flex;flex-direction:column;min-height:100dvh}
.order-app[hidden]{display:none}
@media (min-width:701px){
  .order-body{background:var(--login-bg)}
  .order-app,.order-state{max-width:560px;min-height:calc(100dvh - 48px);margin:24px auto;background:var(--shell);border:1px solid var(--line);border-radius:20px;box-shadow:0 30px 80px rgba(0,0,0,.35);overflow:hidden}
}
/* "Your order's ready" — fixed above everything (even the header) so it's seen no matter where
   on the page the guest is when it lands; stays until they dismiss it, not timed like a toast,
   since this is worth more than a few seconds' notice. See order.js's showReadyBanner. */
.order-ready-banner{position:fixed;top:0;left:0;right:0;z-index:90;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px;padding-top:calc(12px + env(safe-area-inset-top, 0px));background:var(--brass);color: #fff;font-weight:600;font-size:13.5px;box-shadow:0 6px 20px rgba(0,0,0,.35)}
.order-ready-banner[hidden]{display:none}
.order-ready-banner button{flex:0 0 auto;width:26px;height:26px;border-radius:50%;background:rgba(0,0,0,.12);border:0;color:inherit;font-size:13px;line-height:1;display:flex;align-items:center;justify-content:center;padding:0}
/* env(safe-area-inset-top) on the header (and the corner buttons/ready banner below): when this
   page runs as an installed Home Screen app, apple-mobile-web-app-status-bar-style
   black-translucent (see order.html) draws the page BEHIND the iPhone status bar/notch — without
   this padding the top strip of the header (the 🔔 bell, the theme toggle) sat invisible under
   the clock, and the whole page read as "bigger than the screen." Resolves to 0 in a normal
   browser tab, so nothing changes there. */
.order-hd{position:relative;padding:16px;padding-top:calc(16px + env(safe-area-inset-top, 0px));background:var(--panel);border-bottom:1px solid var(--line);flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center}
/* .brand span is shared with the till's topbar (10px there is fine at arm's length on a fixed
   till screen) — bumped past the 11px legibility floor here only, where a guest reads it one-handed
   on a phone, without touching the shared rule the till still uses. */
.order-hd .brand span{font-size:11.5px}
/* Sun/moon theme toggle, same convention as the desktop app: the icon shows what you GET by
   tapping — a sun while the page is dark, a moon while it's light. Flips /shared/theme.js's
   data-theme attribute; the guest's choice persists per phone via localStorage. */
.order-theme-toggle{position:absolute;top:calc(12px + env(safe-area-inset-top, 0px));right:12px;width:34px;height:34px;border-radius:50%;background:var(--panel-2);border:1px solid var(--line);font-size:15px;line-height:1;display:flex;align-items:center;justify-content:center;padding:0}
.order-theme-toggle:active{transform:scale(.9)}
/* Mirrors .order-theme-toggle on the opposite corner — see order.js's subscribeToPush. */
.order-bell-toggle{position:absolute;top:calc(12px + env(safe-area-inset-top, 0px));left:12px;width:34px;height:34px;border-radius:50%;background:var(--panel-2);border:1px solid var(--line);font-size:15px;line-height:1;display:flex;align-items:center;justify-content:center;padding:0}
.order-bell-toggle[hidden]{display:none}
.order-bell-toggle:active{transform:scale(.9)}
.order-logo{height:32px;width:auto}
.order-settle-link{font-size:11px;color:var(--text-dim);text-decoration:underline;background:none;border:none}
.order-settle-link[hidden]{display:none}
/* mask fade on the trailing edge hints there's more to scroll when a category list runs off-screen
   (e.g. an outlet with more than ~5 categories) — a guest who never tries a sideways swipe on a
   nav row would otherwise never discover the later categories exist. */
.order-app .cats{flex:0 0 auto;flex-direction:row;overflow-x:auto;padding:10px 12px;border-right:0;border-bottom:1px solid var(--line);-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}
.order-app .cat{flex:0 0 auto;white-space:nowrap}
/* touch-action pan-y on the menu: vertical scroll stays native, but double-tap zoom and other
   ambiguous gestures are off — so a SECOND finger tapping/scrolling here registers immediately
   while the first finger holds the mic (multi-touch ordering: browse with one hand, talk with
   the other). Pairs with order.js's pointercancel handling on the mic FAB. */
.order-app .grid{flex:1;padding-bottom:88px;touch-action:pan-y}
.order-app .tile,.order-app .cat,.dish-row{touch-action:manipulation}
.order-empty{padding:40px 16px;text-align:center;color:var(--text-dim);font-size:13px}
.order-bottombar{position:fixed;left:12px;right:12px;bottom:calc(14px + env(safe-area-inset-bottom, 0px));display:flex;gap:10px;z-index:30}
/* Deliberately NOT brass, unlike .order-paybar right next to it — "View order" is informational
   (nothing fires), "Send to Kitchen"/"Make Payment" actually commits something. Sharing the same
   fill made the two read as equally weighted, easy to mis-tap one for the other one-handed. */
.order-cartbar{flex:1;display:flex;align-items:center;gap:10px;padding:14px 16px;border-radius:999px;background:var(--panel);color:var(--text);border:1px solid var(--line);font-weight:600;font-size:13.5px;box-shadow:0 10px 30px rgba(0,0,0,.2)}
.order-cartbar[hidden]{display:none}
.order-cartbar-cta{margin-left:auto;font-size:12px;text-decoration:underline;color:var(--brass)}
.order-paybar{flex:0 0 auto;padding:14px 18px;border-radius:999px;background:var(--brass);color: #fff;font-weight:700;font-size:13.5px;box-shadow:0 10px 30px rgba(0,0,0,.35)}
.order-paybar[hidden]{display:none}
/* Floating shortcut back to the voice-ordering avatar, for a guest who declined it at the QR
   gate (or just wants it now) — parked above the cart/pay bar so the two never collide. */
.order-avatar-fab{
  /* Mic LEFT, avatar RIGHT — owner's layout call 2026-08-30 (was mic-right/avatar-left). */
  position:fixed; left:14px; bottom:92px; z-index:29;
  width:50px; height:50px; border-radius:50%;
  background:var(--brass); color: #fff;
  display:flex; align-items:center; justify-content:center;
  font-size:21px; box-shadow:0 10px 24px rgba(0,0,0,.35);
  /* Press-and-hold to talk: without these, a real long-press on a phone triggers the browser's
     own text-selection/callout menu or a page scroll instead of reliably firing pointerdown/up
     on this button — confirmed needed the first time push-to-talk was tried on a real phone. */
  touch-action:none; user-select:none; -webkit-user-select:none; -webkit-touch-callout:none;
}
.order-avatar-fab:active{transform:scale(.93)}
.order-avatar-fab[hidden]{display:none}
/* Moved off the bottom-right corner (owner, 10 Sep 2026: "it is covering part of the menu") to
   sit with the header's own corner icons instead — same size and top offset as
   .order-theme-toggle, stacked directly under it on the same top-right corner so the two never
   collide. z-index above .ov-panel's 60 on purpose: that panel takes over the ENTIRE top strip
   the moment Maro comes up (full width, top:0), which is exactly the region this button now
   lives in — without outranking it here, "call a waiter" would vanish the instant a guest starts
   talking to her, the one moment a guest asking for a human is most likely to reach for it
   instead. */
.order-waiter-fab{
  position:fixed; top:calc(12px + env(safe-area-inset-top, 0px) + 34px + 8px); right:12px; z-index:65;
  width:34px; height:34px; border-radius:50%;
  background:var(--brass); color:#fff;
  display:flex; align-items:center; justify-content:center;
  font-size:15px; box-shadow:0 4px 14px rgba(0,0,0,.3);
  border:0; padding:0;
}
.order-waiter-fab:active{transform:scale(.9)}
.order-waiter-fab[hidden]{display:none}
/* Tap-again-to-confirm state (see order.js) — same idea as data-settle-cash's "confirming" class,
   a visual nudge rather than new markup: the emoji dims and a ring pulses while armed. */
.order-waiter-fab.confirming{animation:waiterFabPulse 1s ease-in-out infinite}
@keyframes waiterFabPulse{0%,100%{box-shadow:0 10px 24px rgba(0,0,0,.35),0 0 0 0 rgba(230,184,0,.6)}50%{box-shadow:0 10px 24px rgba(0,0,0,.35),0 0 0 8px rgba(230,184,0,0)}}
.order-cartrow{padding:9px 0;border-bottom:1px solid var(--line)}
.order-cartrow:last-child{border-bottom:0}
.order-cartrow-top{display:flex;gap:8px;justify-content:space-between;font-size:13.5px}
.order-cartrow-sub{margin-top:3px;font-size:11.5px;color:var(--text-dim)}
.order-cartrow-ctl{display:flex;align-items:center;gap:8px;margin-top:6px}
.order-cartrow-ctl button{padding:5px 10px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);font-size:13px}
.order-cartrow-ctl span{font-family:var(--mono);min-width:16px;text-align:center}
.order-cartrow-ctl button.danger{margin-left:auto;color:var(--stop);border-color:rgba(180,64,47,.4);font-size:11px}
.order-cartrow.sent{opacity:.68}
.order-sent-tag{font-style:italic}
.order-cartrow .ln-n em{font-style:normal;color:var(--text-dim);margin-left:4px;font-size:12px}
/* width:100% is load-bearing: .order-totals sits inside flex containers with align-items:center
   (e.g. .order-state), which shrinks block children to content width instead of stretching them —
   without this, .tr's justify-content:space-between has no extra width to distribute and the
   label/amount render jammed together ("Total due$18.87"). */
.order-totals{width:100%;margin-top:10px;padding-top:10px;border-top:1px dashed var(--line);font-family:var(--mono);font-size:12.5px}
.order-choice-actions{display:flex;flex-direction:column;gap:10px;margin:18px auto 0;width:100%;max-width:280px}
/* Stacked, full labels (MAK, 14 Sep 2026: "Pay for my …" was cut short with three in a row) */
.order-pay-choices{display:flex;flex-direction:column;gap:10px;margin:18px auto 0;width:100%;max-width:320px}
.order-pay-choices .btn{min-width:0;white-space:normal;padding:13px 16px;font-size:14px}
.order-settle-lines{text-align:left;width:100%;max-width:360px;margin-top:6px}
.mono{font-family:var(--mono)}
.order-qr-preview{display:flex;justify-content:center;padding:14px;background:#fff;border-radius:10px;margin:12px 0 4px}
.order-qr-preview img{display:block}
/* .tr.grand's default border-top is var(--ink), meant for the till's light cheque tape —
   invisible on this page's dark modal, so give it the dark-theme line color instead. */
.order-totals .tr.grand{border-top-color:var(--line)}
.revenue-centre-list button:hover{border-color:var(--brass);color:var(--brass)}

/* ---------- menu item photo (Full Menu -> More… editor; see admin.js's drawMasterPriceForm) ---------- */
.mp-photo-row { display: flex; gap: 14px; align-items: flex-start; }
.mp-photo-preview {
  flex: none; width: 96px; height: 96px; border-radius: 10px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim); font-size: 11px; text-align: center;
}
.mp-photo-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mp-photo-actions { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.mp-photo-actions .a-hint { flex: 0 0 100%; }

/* Guest QR menu item photo — see order.js's renderGrid. Fixed square so a row of tiles keeps a
   straight baseline whatever each photo's own aspect ratio is, and so the grid's height doesn't
   jump as lazily-loaded photos arrive. */
/* A DEFINITE height, deliberately not aspect-ratio. The tile is a grid item AND a flex column,
   and with aspect-ratio the photo's height depends on the column width the grid is still busy
   resolving — a circular dependency the browser breaks by sizing the row as if the photo were
   zero-tall. The row came out at the tile's 96px minimum, the photo then laid out at its real
   height, and it spilled over the tiles below (worst on screens with many gaps, where short
   blank tiles set the row height). A fixed height removes the circularity outright: the row
   always accounts for the photo. flex:0 0 auto additionally stops the flex column squeezing it
   into a squashed strip. object-fit:cover keeps every photo filled and undistorted. */
.tile-img {
  width: 100%; height: 104px; border-radius: 8px; object-fit: cover;
  display: block; margin-bottom: 6px; background: var(--panel-2);
  flex: 0 0 auto;
}

/* ---------- the digital waiter, in-page on the QR menu (see order.html's #ovPanel) ----------
   A FULL-WIDTH TOP BANNER (owner's call 2026-08-31: "the upper banner that has the outlet name
   gets covered and the avatar shows in that area like a big screen — better than a small icon").
   While a voice session is open, the banner takes over the header zone: big face on the left,
   her words filling the middle, controls on the right — reads like a video call with the
   waiter. Opaque and pointer-events:auto (it genuinely covers the header, so taps must not
   leak through to the invisible theme/bell buttons beneath); the menu below stays fully
   tappable, and body:has() pushes the content down so nothing important hides under her. */
.ov-panel {
  position: fixed; left: 0; right: 0; top: 0; z-index: 60;
  /* Grid, not a single flex row: on a phone the 🌐 pill and ✕ were eating the bubble's width.
     Face spans both rows on the left; controls sit top-right; her words get the whole width
     beside the face. */
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px 12px;
  grid-template-areas: "face lang close" "face bubble bubble";
  align-items: start;
  padding: calc(env(safe-area-inset-top, 0px) + 10px) 12px 12px;
  background: var(--panel); border-bottom: 1px solid var(--line);
  box-shadow: 0 12px 30px rgba(0,0,0,.25);
  pointer-events: auto;
}
.ov-panel[hidden] { display: none; }
/* Push the page down while the banner is up, so the category nav and first menu rows aren't
   buried under her. Sized to the face clamp + padding; browsers without :has() just overlap. */
body:has(.ov-panel:not([hidden])) .order-app { padding-top: clamp(215px, calc(55vw + 45px), 325px); }
/* While she's up, the page's own intro collapses so the FIRST thing under the banner is the
   MENU (owner 2026-09-02: on Farrouj the hero photo + oversized brand block pushed Starters/
   Main Course a full screen down — "cannot browse the menu, it doesn't show fully"). The hero
   and welcome line go, the outlet name shrinks to one modest line, and the category nav lands
   right under her. Everything returns the moment the session closes. */
body:has(.ov-panel:not([hidden])) .world-hero { display: none; }
body:has(.ov-panel:not([hidden])) .world-welcome { display: none; }
body:has(.ov-panel:not([hidden])) .order-logo { display: none; }
body:has(.ov-panel:not([hidden])) .order-settle-link { display: none; }
body:has(.ov-panel:not([hidden])) .order-hd { padding-top: 8px; padding-bottom: 4px; }
body:has(.ov-panel:not([hidden])) .brand b { font-size: 18px !important; letter-spacing: .08em !important; text-indent: 0 !important; }
body:has(.ov-panel:not([hidden])) .brand span { display: none; }
body:has(.ov-panel:not([hidden])) .brand::after { display: none !important; }
/* Deliberately large: she is the feature, not a notification badge, and at 78px the face was
   too small to read as a person at arm's length — and far too small to show off lip-sync.
   Clamped so she still leaves room for the menu on a narrow phone. */
/* The face is a big RECTANGULAR video tile filling the banner's left side (owner drew the box:
   "I want the avatar to fill that box") — a video-call window, not a floating circle. */
.ov-face { grid-area: face; position: relative; width: clamp(180px, 55vw, 270px); aspect-ratio: 1; pointer-events: auto; }
.ov-photo {
  position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px);
  border-radius: 16px; object-fit: cover; background: var(--panel-2);
}
/* The "Calling your waiter…" opening state — fills the face circle with one calm message while
   the live avatar session connects, replacing the photo→flicker→blank→video load-in. The soft
   pulse says "working on it" without a spinner. */
.ov-calling {
  position: absolute; inset: 3px; z-index: 2;
  border-radius: 16px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 14px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); font-family: var(--ui); font-size: 13px; line-height: 1.35;
  animation: ovCallingPulse 1.6s ease-in-out infinite;
}
.ov-calling[hidden] { display: none; }
@keyframes ovCallingPulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .ov-calling { animation: none; } }
/* The state ring — the only thing telling a guest whether they're being heard right now, so it
   carries listening/thinking/speaking the same way the standalone avatar page's ring does. */
.ov-ring { position: absolute; inset: 0; border-radius: 18px; border: 2px solid transparent; transition: border-color .2s, box-shadow .2s; }
.ov-panel[data-mode="listening"] .ov-ring { border-color: var(--go); animation: ov-pulse 1.5s ease-out infinite; }
.ov-panel[data-mode="thinking"] .ov-ring { border-color: var(--line); border-top-color: var(--brass); animation: ov-spin 1.1s linear infinite; }
.ov-panel[data-mode="speaking"] .ov-ring { border-color: var(--brass); box-shadow: 0 0 22px rgba(201,162,39,.35); }
@keyframes ov-pulse { 0% { box-shadow: 0 0 0 0 rgba(47,127,91,.4); } 100% { box-shadow: 0 0 0 16px rgba(47,127,91,0); } }
@keyframes ov-spin { to { transform: rotate(360deg); } }
/* Tiny silence-countdown badge on the avatar face: seconds remaining before her reply, shown
   only once the guest pauses mid-listen (order.js hides/resets it whenever they speak again).
   Mono + tabular so the ticking digits don't wobble the badge width. */
.ov-countdown {
  position: absolute; bottom: 2px; right: 2px; z-index: 2;
  min-width: 30px; height: 30px; padding: 0 5px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: var(--go); color: #fff;
  font-family: var(--mono); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
  box-shadow: 0 4px 10px rgba(0,0,0,.3);
}
.ov-countdown[hidden] { display: none; }
/* Fixed white-on-black-text on purpose (MAK, 14 Sep 2026), not themed: this used to take its
   colors from var(--panel-2)/var(--text), which on the full-screen dark backdrop below rendered
   as dark-grey-on-dark-grey — "can't see anything" on a live test. A caption has to stay legible
   over WHATEVER is behind it (a light guest theme, a dark full-screen photo, anything), so it
   ignores the theme entirely instead of chasing every background it could land on. */
.ov-bubble {
  pointer-events: auto;
  grid-area: bubble; min-width: 0;
  max-height: clamp(140px, 45vw, 230px); overflow-y: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  padding: 9px 12px; font-size: 13.5px; line-height: 1.45; color: #111;
}
.ov-bubble[hidden] { display: none; }
.ov-bubble .ov-heard { display: block; margin-top: 5px; font-size: 11.5px; color: #555; font-style: italic; }
.ov-close {
  pointer-events: auto; grid-area: close;
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text-dim); font-size: 11px;
}
/* Mid-session language switch — shows the language you GET by tapping (same convention as the
   theme toggle). Sits next to the ✕; flips guestLang for the next listen turn (see order.js). */
.ov-lang {
  pointer-events: auto; grid-area: lang; justify-self: end;
  padding: 4px 10px; border-radius: 999px; flex: none;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text); font-size: 11.5px;
  font-family: var(--ui);
}
.ov-lang[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .ov-ring { animation: none !important; } }

/* Mic button state (see order.js's ovMode) — the button the guest's thumb is actually on has to
   show what's happening, not just the panel beside it. Green while listening (and the glyph
   becomes a stop square, since a second tap ends the turn early), muted while thinking so a
   double-tap plainly reads as "not now", accent while she's speaking. */
.order-avatar-fab[data-mode="listening"] { background: var(--go); animation: ov-pulse 1.5s ease-out infinite; }
.order-avatar-fab[data-mode="thinking"] { opacity: .55; }
.order-avatar-fab[data-mode="speaking"] { background: var(--brass); }
/* "Your turn" — the turn-taking cue (owner wanted guests to know WHEN to tap; a soft green
   breathing glow on the button itself + the pill below beat the red-arrow idea: signal in the
   "go" color, exactly where the action is, zero tutorial clutter). Shown only while a session
   is open and she's waiting for the guest (see ovMode). */
/* !important so the per-outlet theme skins (which restyle the FAB with higher specificity)
   can't swallow the turn signal — "your turn" must read green in every world. */
.order-avatar-fab[data-hint="turn"] { background: var(--go) !important; animation: micTurnBreathe 1.8s ease-in-out infinite; }
@keyframes micTurnBreathe {
  0%, 100% { box-shadow: 0 10px 24px rgba(0,0,0,.35), 0 0 0 0 rgba(47,127,91,.55); }
  50% { box-shadow: 0 10px 24px rgba(0,0,0,.35), 0 0 0 12px rgba(47,127,91,0); }
}
.mic-hint {
  position: fixed; left: 14px; bottom: 148px; z-index: 29;
  padding: 6px 12px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  font-family: var(--ui); font-size: 12.5px; white-space: nowrap;
  box-shadow: 0 6px 18px rgba(0,0,0,.25);
  pointer-events: none;
}
.mic-hint[data-kind="turn"] { border-color: var(--go); color: var(--go); font-weight: 600; }
.mic-hint[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .order-avatar-fab[data-hint="turn"] { animation: none; } }

/* The live Simli stream, sitting exactly where the still photo does so the swap between them is
   invisible — same circle, same size, no reflow when she starts or stops speaking. */
.ov-video {
  position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px);
  border-radius: 16px; object-fit: cover; background: var(--panel-2);
}
.ov-video[hidden] { display: none; }
/* (banner layout: the bubble fills the middle of the top bar — no max-width cap needed) */

/* ================= Full-screen avatar (MAK, 13-14 Sep 2026) =================
   Two states, switched by .ov-panel's own [data-panel] attribute (order.js):
     data-panel="full"  set the instant a guest taps the mic: her face fills the whole screen,
                         mic/waiter/menu sit in a big row at the bottom.
     data-panel="menu"  set by tapping the Menu button: back to the ORIGINAL top-banner layout
                         above (owner's proven design, untouched) so the guest can browse/order
                         while she's still live in the strip. Tapping her face there flips back
                         to "full" (see order.js's click handler on #ovPanel) — a plain toggle,
                         nothing ends the session either way.

   Every rule below is scoped to [data-panel="full"] on purpose — "menu" needs ZERO new CSS, it
   just falls through to the banner rules above. An earlier version instead shrank the panel to a
   right-edge rail for "menu" and pushed .order-app over with padding, which needed every other
   full-screen sibling to separately know to make room for it — the bill/payment screen never got
   that treatment, so live testing (MAK, 14 Sep) showed the rail sitting on top of "Your bill" and
   clipping Total due / Full Pay / Back to menu. The banner already has that plumbing everywhere
   it's needed, so reusing it for "menu" removes the whole class of bug instead of chasing it
   sibling by sibling. */
/* ---- the video-call look (MAK, 14 Sep 2026: "I want her to look like you are on a video call") ----
   Her face full-bleed, a soft dark gradient top and bottom like a call app, her name and a live call
   timer at the top, live captions above a proper call bar (Waiter / Mic / Menu / red End, each
   with a label), and the opening "Calling…" state as a blurred ringing screen. The state ring is
   off here (a border pulsing at the screen edge reads as a glitch); the header status line and the
   mic button carry listening / thinking / speaking instead. */
.ov-panel[data-panel="full"] {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  padding: 0; background: #0E1526; border-bottom: 0; box-shadow: none;
  grid-template-areas: none;
}
.ov-panel[data-panel="full"] .ov-face {
  grid-area: unset; flex: 1 1 auto; width: 100%; aspect-ratio: unset; min-height: 0; overflow: hidden;
}
.ov-panel[data-panel="full"] .ov-photo, .ov-panel[data-panel="full"] .ov-video { inset: 0; width: 100%; height: 100%; border-radius: 0; }
.ov-panel[data-panel="full"] .ov-ring { display: none; }
/* a slow drift so the idle face reads as live video, not a poster. On the WRAPPER (.ov-media holds
   the photo, the live video and the portrait canvases), so the photo/video swap at the start of
   every reply stays at the same scale instead of snapping (captureIdleStill exists to make that
   swap pixel-identical). */
.ov-media { position: absolute; inset: 0; }
.ov-panel[data-panel="full"] .ov-media { animation: ov-kenburns 26s ease-in-out infinite alternate; transform-origin: 50% 38%; }
@keyframes ov-kenburns { from { transform: scale(1); } to { transform: scale(1.07); } }
@media (prefers-reduced-motion: reduce) { .ov-panel[data-panel="full"] .ov-media { animation: none; } }
/* the call app's gradients: readable name up top, readable captions and buttons down below */
.ov-panel[data-panel="full"] .ov-face::before, .ov-panel[data-panel="full"] .ov-face::after {
  content: ""; position: absolute; left: 0; right: 0; z-index: 1; pointer-events: none;
}
.ov-panel[data-panel="full"] .ov-face::before {
  top: 0; height: calc(env(safe-area-inset-top, 0px) + 160px);
  background: linear-gradient(180deg, rgba(6,10,22,.85) 0%, rgba(6,10,22,.7) 35%, rgba(6,10,22,.3) 70%, rgba(6,10,22,0) 100%);
}
.ov-panel[data-panel="full"] .ov-face::after {
  bottom: 0; height: calc(env(safe-area-inset-bottom, 0px) + 270px);
  background: linear-gradient(0deg, rgba(6,10,22,.9) 0%, rgba(6,10,22,.6) 42%, rgba(6,10,22,0) 100%);
}
/* her name + status line + timer, like the caller banner on a call */
.ov-callhd { display: none; }
.ov-panel[data-panel="full"] .ov-callhd {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  position: absolute; z-index: 3; left: 70px; right: 70px; top: calc(env(safe-area-inset-top, 0px) + 18px);
  text-align: center; color: #fff; text-shadow: 0 1px 10px rgba(0,0,0,.6); pointer-events: none;
}
.ov-callhd b { font-family: var(--ui); font-size: 23px; font-weight: 600; letter-spacing: .01em; }
.ov-callhd span { font-family: var(--ui); font-size: 13.5px; opacity: .92; font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 7px; }
.ov-callhd span::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--go); animation: ov-pulse 1.5s ease-out infinite; }
.ov-panel[data-mode="listening"] .ov-callhd span::before { background: #E53935; animation: ov-pulse-red .9s ease-out infinite; }
@keyframes ov-pulse-red { 0% { box-shadow: 0 0 0 0 rgba(229,57,53,.45); } 100% { box-shadow: 0 0 0 12px rgba(229,57,53,0); } }
.ov-panel[data-call="connecting"] .ov-callhd span::before { display: none; }
.ov-panel[data-call="ended"] .ov-callhd span::before { background: #9AA3AF; animation: none; }
/* language chip: a small translucent pill top-right, out of the way */
.ov-panel[data-panel="full"] .ov-lang {
  grid-area: unset; position: absolute; z-index: 3;
  top: calc(env(safe-area-inset-top, 0px) + 18px); right: 14px;
  background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.28); color: #fff; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
/* live captions: white card, black text (MAK), sitting above the call bar */
.ov-panel[data-panel="full"] .ov-bubble {
  grid-area: unset; position: absolute; z-index: 3; left: 16px; right: 16px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 176px);
  max-height: 26vh; background: #fff; color: #111; border: 0; border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.35); font-size: 15px;
}
.ov-panel[data-panel="full"] .ov-countdown { bottom: auto; top: calc(env(safe-area-inset-top, 0px) + 22px); left: 14px; right: auto; }
/* "Calling your waiter…": the ringing screen, her face soft behind a blur */
.ov-panel[data-panel="full"] .ov-calling {
  inset: 0; border-radius: 0; border: 0; z-index: 2;
  background: rgba(6,10,22,.5); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: #fff; font-size: 17px; letter-spacing: .01em; align-items: flex-end; padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 200px);
}
/* ---- the call bar: four round buttons with labels, fixed to the bottom ---- */
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-waiter-fab,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .ov-menu-btn,
.ov-panel[data-panel="full"] .ov-close {
  grid-area: unset; position: fixed; top: auto; right: auto; z-index: 62;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 44px);
  width: 62px; height: 62px; border-radius: 50%; flex: none; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center; font-size: 25px; line-height: 1;
  background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.22); color: #fff;
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); box-shadow: 0 10px 24px rgba(0,0,0,.35);
}
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab:active,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-waiter-fab:active,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .ov-menu-btn:active,
.ov-panel[data-panel="full"] .ov-close:active { transform: scale(.93); }
/* labels under each button, from data-label so order.js can switch them to Arabic */
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab::after,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-waiter-fab::after,
body:has(.ov-panel[data-panel="full"]:not([hidden])) .ov-menu-btn::after,
.ov-panel[data-panel="full"] .ov-close::after {
  content: attr(data-label); position: absolute; top: calc(100% + 7px); left: 50%; transform: translateX(-50%);
  width: 96px; text-align: center; font-family: var(--ui); font-size: 11.5px; font-weight: 500; letter-spacing: .01em;
  color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.6); pointer-events: none; white-space: nowrap;
}
/* four slots on centres -150 / -50 / +50 / +150 px (left = centre minus half the width) */
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-waiter-fab { left: calc(50% - 181px); }
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab {
  left: calc(50% - 86px); bottom: calc(env(safe-area-inset-bottom, 0px) + 40px);
  width: 72px; height: 72px; font-size: 30px; background: var(--go); border-color: rgba(255,255,255,.35);
  box-shadow: 0 0 0 6px rgba(47,127,91,.22), 0 14px 28px -6px rgba(0,0,0,.6);
}
/* while recording the base green pulse shows through (the 🔴 glyph, the red header dot and the
   "Tap when done" pill already say recording; a red circle next to the red End would say hang up);
   dimmed whenever a tap does nothing, so green means "a tap does something" */
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab[data-mode="thinking"],
body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab[data-mode="speaking"] { opacity: .7; }
body:has(.ov-panel[data-panel="full"]:not([hidden])) .ov-menu-btn { left: calc(50% + 19px); }
.ov-panel[data-panel="full"] .ov-close { left: calc(50% + 123px); width: 54px; height: 54px; font-size: 19px; background: var(--stop); border-color: rgba(255,255,255,.3); }
/* narrow phones: pull the outer two in a little */
@media (max-width: 380px) {
  body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-waiter-fab { left: calc(50% - 171px); }
  body:has(.ov-panel[data-panel="full"]:not([hidden])) .order-avatar-fab { left: calc(50% - 83px); }
  body:has(.ov-panel[data-panel="full"]:not([hidden])) .ov-menu-btn { left: calc(50% + 16px); }
  .ov-panel[data-panel="full"] .ov-close { left: calc(50% + 113px); }
}
/* the turn-taking pill sits centred above the bar instead of by the old corner mic */
body:has(.ov-panel[data-panel="full"]:not([hidden])) .mic-hint {
  left: calc(50% - 50px); transform: translateX(-50%); bottom: calc(env(safe-area-inset-bottom, 0px) + 128px); z-index: 63; /* centred over the mic slot */
  background: rgba(255,255,255,.94); color: #111; border: 0; box-shadow: 0 6px 18px rgba(0,0,0,.35);
}
body:has(.ov-panel[data-panel="full"]:not([hidden])) .mic-hint[data-kind="turn"] { color: var(--go); }
.ov-menu-btn span { display: none; } /* the label comes from data-label now, like the other three */
/* Hidden outside data-panel="full" — the banner grid above has no area for it, so this element
   only ever renders while the rules above turn it back on. */
.ov-menu-btn { display: none; }
.ov-menu-btn[hidden] { display: none; }
/* Her face is the door back to full screen while the banner's up — see order.js's click handler
   on #ovPanel, which checks for a tap on .ov-face specifically while data-panel="menu". */
.ov-panel[data-panel="menu"] .ov-face { cursor: pointer; }

/* Unsaved-changes marker in the screen editor header — screen edits are a draft until Save. */
.screen-dirty{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--stop);border:1px solid var(--stop);border-radius:999px;padding:2px 8px;margin-left:8px;vertical-align:middle}

/* ---------- Screen Layout editor (Admin) ----------
   A working copy of the till grid: same auto-fill columns and tile proportions as .grid/.tile in
   the POS, so what's arranged here is what the kitchen-facing screen actually shows. Every cell
   is draggable; a "gap" is a real entry that holds its place and renders as nothing on the till. */
.layout-grid{display:grid;gap:10px;grid-template-columns:repeat(5,1fr);padding:12px;background:var(--shell);border:1px solid var(--line);border-radius:var(--r);max-height:62vh;overflow-y:auto}
.layout-cell{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:12px;min-height:96px;display:flex;flex-direction:column;justify-content:space-between;cursor:grab}
.layout-cell:active{cursor:grabbing}
.layout-cell:hover{border-color:var(--brass)}
.layout-cell .nm{font-size:13px;line-height:1.25}
.layout-cell .pr{font-family:var(--mono);font-size:12px;color:var(--brass)}
.layout-num{position:absolute;top:6px;left:8px;font-family:var(--mono);font-size:10px;color:var(--text-dim)}
.layout-cell.tile-addon{border-style:dashed}
/* An empty cell — a drop target, nothing more. No content, no controls, nothing to drag out. */
.layout-empty{background:transparent;border-style:dashed;border-color:var(--line);cursor:default}
.layout-empty:hover{border-color:var(--brass);background:rgba(201,162,39,.06)}
.layout-x{position:absolute;top:4px;right:4px;width:22px;height:22px;border-radius:6px;background:var(--panel-2);border:1px solid var(--line);color:var(--text-dim);font-size:11px;line-height:1;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .12s}
.layout-cell:hover .layout-x{opacity:1}
.layout-x:hover{border-color:var(--stop);color:var(--stop)}
/* The read-only preview shows gaps too, so it matches what Layout arranged. */
.snap-blank{background:repeating-linear-gradient(45deg,transparent,transparent 5px,rgba(255,255,255,.05) 5px,rgba(255,255,255,.05) 10px);border-style:dashed}
/* The till's own empty cell — holds its place, shows nothing, ignores taps. */
.tile-blank{background:transparent;border:1px dashed transparent;pointer-events:none}

/* The Layout editor runs at till proportions so what's arranged is what staff see. */
.modal-wide{max-width:990px}

/* Photos in the layout editor, so arranging a screen looks like the screen being arranged. */
.layout-cell.has-photo{padding-top:8px}
.layout-img{width:100%;height:56px;object-fit:cover;border-radius:6px;display:block;margin-bottom:6px;background:var(--panel-2);flex:0 0 auto}
