/* A Recomend — business-owner console specifics (shell in auth.css, cards in pro.css). */

/* Intro steps ------------------------------------------------------------- */
.steps { list-style: none; margin: var(--s4) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); counter-reset: step; }
.steps .step { display: flex; align-items: flex-start; gap: var(--s3); font-size: 14px; color: var(--ink); }
.steps .step .step-n {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--r-pill);
  background: var(--surface); color: var(--accent-press); border: 1px solid var(--accent);
  display: grid; place-items: center; font-size: 12px; font-weight: 700; margin-top: 2px;
}

/* Data-entry (staff) mode -------------------------------------------------- */
/* Deliberately quieter than the owner intro: staff work through many places a
   day and this is orientation, not a pitch. */
.staff-mode { border-inline-start: 3px solid var(--accent); }
.badge.staff {
  background: var(--surface); border: 1px solid var(--accent);
  color: var(--accent-press);
}
.biz-card.staff-card { border-style: dashed; }

/* The provenance banner at the top of the builder. Informational, never an
   error: nothing is wrong, the reader is just being told who they are. */
.pg-provenance {
  border: 1px solid var(--accent-weak); border-inline-start: 3px solid var(--accent);
  background: var(--accent-weak); border-radius: var(--r-card);
  padding: var(--s3) var(--s4); margin-bottom: var(--s4);
}
.pg-provenance .prov-h { margin: 0 0 var(--s1); font-size: 14px; font-weight: 600; color: var(--accent-press); }
.pg-provenance .prov-body { margin: 0; font-size: 13px; line-height: 1.7; color: var(--ink); }
.pg-provenance .prov-meta { margin: var(--s2) 0 0; font-size: 12px; color: var(--muted); }
/* A page an owner INHERITED is a milder case than staff editing live — same
   shape, less colour, so it reads as a footnote rather than a warning. */
.pg-provenance.seeded { background: var(--bg); border-color: var(--line); border-inline-start-color: var(--accent); }
.pg-provenance.seeded .prov-h { color: var(--ink); }

/* Search ------------------------------------------------------------------ */
.search-row { display: grid; grid-template-columns: 2fr 1fr auto; gap: var(--s3); align-items: end; }
.search-row .field { margin-bottom: 0; }
.search-row .primary { height: 42px; }
@media (max-width: 700px) { .search-row { grid-template-columns: 1fr; } }

.res-list { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s4); }
.res-card {
  border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3) var(--s4);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap;
}
.res-card.taken { background: var(--bg); }
.res-main { min-width: 0; }
.res-name { font-size: 15px; font-weight: 600; margin: 0; }
.res-sub { font-size: 12px; color: var(--muted); margin: 2px 0 0; }
.res-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* Claim ------------------------------------------------------------------- */
.claim-target {
  border: 1px solid var(--accent); background: var(--accent-weak); border-radius: var(--r-card);
  padding: var(--s3) var(--s4); margin-bottom: var(--s4); font-size: 14px;
}
.claim-target .ct-label {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent-press); margin-bottom: 2px;
}
.claim-target .ct-name { font-weight: 600; }
.claim-target .ct-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
.row-actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

/* Business cards ---------------------------------------------------------- */
.biz-card { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s4); margin-bottom: var(--s3); }
.biz-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--s3); flex-wrap: wrap; }
.biz-name { font-size: 16px; font-weight: 600; margin: 0; }
.biz-sub { font-size: 12px; color: var(--muted); margin: 2px 0 0; }
.biz-note {
  font-size: 13px; margin: var(--s3) 0 0; padding: var(--s3);
  border-radius: var(--r-btn); background: var(--bg); border: 1px solid var(--line); color: var(--ink);
}
.biz-note.warn { background: var(--danger-weak); border-color: var(--danger-weak); color: var(--danger); }
.biz-note.info { background: var(--accent-weak); border-color: var(--accent-weak); color: var(--accent-press); }
.biz-panel { margin-top: var(--s4); border-top: 1px solid var(--line); padding-top: var(--s4); }

/* Branches ----------------------------------------------------------------- */
/* A chain is ONE business with several shops. The group frames them as such,
   and the open branch is outlined so "which one am I editing" is answerable
   from the list as well as from inside the panel. */
.biz-group {
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s3); margin-bottom: var(--s3); background: var(--bg);
}
.biz-group-head {
  display: flex; align-items: baseline; gap: var(--s2);
  flex-wrap: wrap; margin-bottom: var(--s2);
}
.biz-group-name { margin: 0; font-size: 15px; font-weight: 700; }
.biz-group .biz-card { background: var(--surface); margin-bottom: var(--s2); }
.biz-group .biz-card:last-child { margin-bottom: 0; }
.biz-card.editing { border-color: var(--accent); box-shadow: var(--ring); }

/* The one line that answers "which branch am I editing" — first thing in the
   panel, above the tabs, so it is on screen whichever tab is open. */
.editing-now {
  border: 1px solid var(--accent); border-radius: var(--r-card);
  background: var(--accent-weak); padding: var(--s3); margin-bottom: var(--s4);
}
.editing-now .en-line { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.editing-now .en-label {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-press);
}
.editing-now .en-name { font-size: 16px; font-weight: 700; color: var(--ink); }
.editing-now .en-place { font-size: 12px; color: var(--muted); }
.editing-now .en-hint { margin: var(--s2) 0 0; font-size: 12px; line-height: 1.7; color: var(--ink); }

/* Copy from another branch -------------------------------------------------- */
.cp-block .cp-what { display: grid; gap: var(--s2); margin: var(--s3) 0; }
.cp-block select { width: 100%; }
.cp-out:empty { display: none; }
.cp-out { margin-top: var(--s3); }

.panel-block { margin-bottom: var(--s5); }
.panel-block:last-child { margin-bottom: 0; }
.panel-h { font-size: 15px; font-weight: 600; margin: 0 0 var(--s1); }
.panel-sub { font-size: 12px; color: var(--muted); margin: 0 0 var(--s3); line-height: 1.6; }

/* Offers ------------------------------------------------------------------ */
.offers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--s2); margin-bottom: var(--s3); }
.offer-chk {
  display: flex; align-items: center; gap: var(--s2); font-size: 14px; font-weight: 500;
  border: 1px solid var(--line); border-radius: var(--r-btn); padding: var(--s2) var(--s3); cursor: pointer;
}
.offer-chk:hover { background: var(--accent-weak); border-color: var(--accent-weak); }
.offer-chk input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; }

/* Menu editor ------------------------------------------------------------- */
.menu-legend { font-size: 12px; color: var(--muted); margin: 0 0 var(--s2); }
.menu-rows { display: flex; flex-direction: column; gap: var(--s2); }
.menu-row { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
.menu-row.is-open { border-color: var(--accent); background: var(--surface); }
/* The dish filter hides rows and touches nothing else — every row stays in the
   DOM, which is what lets a search run without ever losing a dish. */
.menu-row.is-filtered { display: none; }
.menu-row input[type="text"], .menu-row input[type="number"] { width: 100%; font-size: 13px; padding: 8px 10px; }
.menu-row .avail { display: inline-flex; align-items: center; gap: var(--s1); font-size: 12px; color: var(--muted); white-space: nowrap; }
.menu-row .avail input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; }

/* THE TOOLBAR over a long menu — find one dish, or see the shape of all of
   them. Sticks to nothing: it belongs to the list, not to the viewport. */
.menu-tools {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin: 0 0 var(--s2);
}
.menu-tools .row-filter {
  flex: 1 1 180px; min-width: 0; font-size: 13px; padding: 8px 10px;
}

.menu-tools .sec-filter {
  flex: 0 1 170px; min-width: 0; font-size: 13px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--ink);
}

/* THE DRAG HANDLE — the one place `touch-action: none` may appear ------------ */
/* Only here, and never on the row. `touch-action: none` tells the browser not
   to interpret a touch as a scroll, which is exactly what a drag needs and
   exactly what a 40-dish list must not have: put it on the row and the page
   can no longer be scrolled anywhere near a dish, which is most of the page.
   A 30px square is the smallest thing a thumb reliably finds; it is also a
   real <button>, so it is in the tab order and Up/Down move the row. */
.drag-h {
  flex: none; width: 30px; height: 30px; display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--muted); padding: 0;
  cursor: grab; touch-action: none;
}
.drag-h:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-press); }
.drag-h:active:not(:disabled) { cursor: grabbing; }
.drag-h:disabled { opacity: .4; cursor: not-allowed; }
/* The grip: the three dots of the "more" glyph, turned on their side. */
.drag-h .ic { width: 16px; height: 16px; transform: rotate(90deg); }
/* While a drag is running the moving row is lifted off the list and everything
   else stops reacting to the pointer, so a hover state cannot light up a row
   that is only being passed over. */
.menu-rows.is-sorting, .sec-list.is-sorting { cursor: grabbing; }
.menu-row.is-drag, .sec-row.is-drag {
  border-color: var(--accent); background: var(--accent-weak);
  box-shadow: var(--ring); opacity: .92;
}

/* THE FOLDED LINE. Name, price and a summary of what else is set — the two
   fields a shop keying in forty dishes actually types stay live inputs, so the
   fast path (name, price, tab, name, price) never goes behind a tap. */
.mr-head { display: grid; grid-template-columns: auto auto minmax(0, 1fr) 96px auto; gap: var(--s2); align-items: center; }
.mr-toggle {
  flex: none; width: 30px; height: 30px; display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--muted); cursor: pointer; padding: 0;
}
.mr-toggle:hover { border-color: var(--accent); color: var(--accent-press); }
.mr-toggle .ic { width: 16px; height: 16px; transition: transform .15s ease; }
.menu-row.is-open .mr-toggle { background: var(--accent-weak); border-color: var(--accent); color: var(--accent-press); }
.menu-row.is-open .mr-toggle .ic { transform: rotate(180deg); }
.mr-name { font-weight: 600; }

/* What is set on this dish, readable without opening it. Deliberately small
   and quiet EXCEPT where something is wrong — a broken discount has to be
   findable from a folded list of forty. */
.mr-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: var(--s2); }
.mr-flags:empty { display: none; }
.mr-flag {
  font-size: 11px; line-height: 1.6; padding: 1px 8px;
  border-radius: var(--r-pill); border: 1px solid var(--line);
  background: var(--bg); color: var(--muted);
  max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mr-flag.is-sec { font-weight: 600; color: var(--ink); }
.mr-flag.is-on { border-color: var(--accent); color: var(--accent-press); background: var(--accent-weak); }
.mr-flag.is-warn { border-color: var(--accent); color: var(--ink); }
.mr-flag.is-bad { border-color: var(--danger); color: var(--danger); background: var(--danger-weak); font-weight: 600; }
/* THE TWO CHIPS THAT GO SOMEWHERE. The discount and the combo are read-only
   here — they are edited on their own tabs — so the chip is a button that
   takes you there, on this dish. A chip that said «عرض» and did nothing would
   leave an owner hunting for where their discount went. */
.mr-jump {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  height: auto; font-family: inherit;
}
.mr-jump .ic { width: 11px; height: 11px; opacity: .8; }
.mr-jump:hover { border-color: var(--accent); color: var(--accent-press); }
.mr-jump.is-bad:hover { border-color: var(--danger); color: var(--danger); }

/* THE OPENED DISH, in four named groups. Fifteen fields in one column is the
   thing this replaces; the groups are what make it possible to look for one. */
.mr-body { margin-top: var(--s3); }
.mr-group {
  padding-top: var(--s3); margin-top: var(--s3);
  border-top: 1px dashed var(--line);
}
.mr-body > .mr-group:first-child { padding-top: 0; margin-top: 0; border-top: 0; }
.mr-g-h {
  margin: 0 0 var(--s2); font-size: 12px; font-weight: 700;
  color: var(--accent-press); letter-spacing: .02em;
}
.mr-g-sub { margin: var(--s3) 0 2px; font-size: 12px; font-weight: 600; color: var(--ink); }
.mr-two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
.mr-three { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--s2); }
.mr-tags { margin-top: var(--s2); }
.mr-group .avail { margin-top: var(--s2); }
@media (max-width: 760px) {
  .mr-head { grid-template-columns: auto auto minmax(0, 1fr) 84px; }
  /* A FOLDED row on a phone is four controls: drag it, open it, name it, price
     it. Delete is not one of them — it would take a whole line of a forty-row
     list and put a destructive button under every thumb. It appears when the
     dish is open, which is also when the owner is actually looking at it.
     The HANDLE stays, because reordering on a phone is the whole point of
     having built it with pointer events. */
  .menu-row:not(.is-open) .mr-head .del-row { display: none; }
  .menu-row.is-open .mr-head .del-row { grid-column: 3 / -1; justify-self: start; }
  .mr-two, .mr-three { grid-template-columns: 1fr; }
}

/* THE OFFERS TAB, and the combos tab under it ------------------------------ */
/* Both are LISTS of cards, because that is the thing an owner could not do
   when these lived inside a dish: see all of them at once. The card is the
   unit — one discount, or one combo — and it carries the same controls the
   dish editor used to, so nothing had to be relearned. */
.deal-cards, .combo-cards { display: flex; flex-direction: column; gap: var(--s3); }
.deal-card, .combo-card {
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s3) var(--s4); background: var(--surface);
}
.deal-card input[type="text"], .deal-card input[type="number"], .deal-card input[type="date"],
.combo-card input[type="text"], .combo-card input[type="number"],
.combo-card input[type="search"] { width: 100%; font-size: 13px; padding: 8px 10px; }

/* WHAT IS BEING DISCOUNTED, named at the top: an offer is applied TO a dish,
   so the dish is the card's subject and not one of its fields. */
.dc-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s2);
}
.dc-title { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dc-lbl {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.dc-name { font-size: 15px; font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.dc-en { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
/* WHAT THIS OFFER IS DOING TODAY. Four states, and only the broken one is red:
   an offer that starts next month is a fact about a date, not a mistake. */
.dc-status {
  flex: none; font-size: 11px; font-weight: 700; line-height: 1.7;
  border-radius: var(--r-pill); padding: 1px 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--muted);
}
.dc-status.is-live { border-color: var(--accent); background: var(--accent-weak); color: var(--accent-press); }
.dc-status.is-soon, .dc-status.is-over { border-color: var(--accent); color: var(--ink); }
.dc-status.is-bad { border-color: var(--danger); background: var(--danger-weak); color: var(--danger); }
/* A card opened on a dish and not filled in yet. Dashed and quiet: it is a
   blank waiting for one number, not a mistake to be warned about. */
.dc-status.is-draft { border-style: dashed; }
/* The computed saving, beside the two numbers it is computed FROM — read-only,
   because a typed percentage is a percentage that can disagree with them. */
.dc-pct {
  display: block; font-size: 13px; font-weight: 700; padding: 8px 10px;
  border: 1px dashed var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--accent-press);
  font-variant-numeric: tabular-nums;
}
.dc-pct.is-empty { color: var(--muted); font-weight: 400; }
.dc-foot {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin-top: var(--s3); padding-top: var(--s3); border-top: 1px dashed var(--line);
}
.dc-foot .menu-count { flex: 1 1 200px; line-height: 1.6; }

/* The empty state of a tab. It explains what the thing IS, because an owner
   who has never made a combo is exactly the person reading it. */
.tab-empty {
  border: 1px dashed var(--line); border-radius: var(--r-card);
  padding: var(--s4); background: var(--bg);
}
.tab-empty .te-h { margin: 0 0 var(--s2); font-size: 14px; font-weight: 700; color: var(--ink); }
.tab-empty .te-b { margin: 0; font-size: 13px; line-height: 1.8; color: var(--muted); }

/* THE DISH PICKER for a new offer. A forty-item menu is a search, not a
   dropdown — and the list only holds dishes that do not have one yet. */
.pick-box {
  border: 1px solid var(--accent); border-radius: var(--r-card);
  background: var(--accent-weak); padding: var(--s3) var(--s4); margin-top: var(--s3);
}
.pick-h { margin: 0 0 var(--s1); font-size: 14px; font-weight: 700; color: var(--accent-press); }
.pick-box .pick-q { width: 100%; font-size: 13px; padding: 8px 10px; margin-bottom: var(--s2); }
.pick-list {
  display: flex; flex-direction: column; gap: 4px;
  max-height: 260px; overflow-y: auto; margin-bottom: var(--s2);
}
.pick-item {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--ink); cursor: pointer;
  padding: var(--s2) var(--s3); font-size: 13px; text-align: start; height: auto;
}
.pick-item:hover { border-color: var(--accent); color: var(--accent-press); }
.pick-item.is-filtered { display: none; }
.pick-name { min-width: 0; overflow-wrap: anywhere; }
.pick-price { margin-inline-start: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* A COMBO IS A MENU ITEM: its own name and its own price sit at the top of the
   card, exactly where they sit on a dish row, because they are the same two
   fields on the same row of the same array. */
.cc-head { display: grid; grid-template-columns: minmax(0, 1fr) 120px; gap: var(--s3); align-items: end; }
.cc-head label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.cc-name-cell, .cc-price-cell { min-width: 0; }
.cc-total {
  display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
  margin: var(--s3) 0 0;
}
.cc-metric {
  font-size: 12px; line-height: 1.7; padding: 1px 10px;
  border-radius: var(--r-pill); border: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font-variant-numeric: tabular-nums;
}
.cc-metric.is-empty { color: var(--muted); }
.cc-metric.is-good { border-color: var(--accent); background: var(--accent-weak); color: var(--accent-press); font-weight: 600; }
/* The combo costing MORE than its parts is not refused — a shop may price a
   meal however it likes — but it is never left unsaid. */
.cc-metric.is-bad { border-color: var(--danger); background: var(--danger-weak); color: var(--danger); font-weight: 600; }
.combo-card .parts-q { margin-bottom: var(--s2); }

.inc-block {
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px dashed var(--line);
}
.inc-h { margin: 0 0 2px; font-size: 12px; font-weight: 600; color: var(--ink); }
.inc-block .menu-legend { margin-bottom: var(--s2); }

/* THE PAIR THE FOUNDER ASKED FOR, side by side and labelled exactly: what the
   customer pays, what it used to cost, and — read-only beside them — what we
   compute the saving to be. There is exactly one input per stored field. */
.deal-pair { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--s2); }
.deal-cell { min-width: 0; }
.deal-cell label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.deal-cell input { width: 100%; font-size: 13px; padding: 8px 10px; }
.deal-pair + .mr-g-sub { margin-top: var(--s3); }
.deal-dates { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); margin-bottom: var(--s2); }
.deal-date { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--muted); min-width: 0; }
.deal-date input { width: 100%; min-width: 0; font-size: 13px; padding: 7px 8px; font-variant-numeric: tabular-nums; }
.deal-out:empty { display: none; }
.deal-out { margin-top: var(--s2); display: flex; flex-direction: column; gap: var(--s2); align-items: flex-start; }
/* The verdict on the two prices. The `ok` case states the SAME saving the
   public page will compute and print, so the owner recognises it later. */
.deal-note {
  display: inline-block; font-size: 12px; font-weight: 600; line-height: 1.6;
  border-radius: var(--r-pill); padding: 2px 10px;
}
.deal-note.ok { background: var(--accent-weak); color: var(--accent-press); border: 1px solid var(--accent); }
.deal-note.bad { background: var(--danger-weak); color: var(--danger); border: 1px solid var(--danger-weak); font-weight: 500; border-radius: var(--r-btn); padding: var(--s2) var(--s3); }
/* An offer OUTSIDE its window is not an error — it is a fact about a date. It
   gets the plain framed treatment, never the red one, or an owner setting next
   month's offer would think they had broken something. */
.deal-note.warn {
  background: var(--bg); color: var(--ink); border: 1px solid var(--accent);
  font-weight: 500; border-radius: var(--r-btn); padding: var(--s2) var(--s3);
}

/* THE COMBO PICKER. A list of the OTHER dishes on this menu, ticked — never a
   free-text box, because a typed part is a part that stops existing the moment
   the dish it names is renamed. */
.combo-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s2); margin-bottom: var(--s2);
}
.combo-chk {
  display: flex; align-items: center; gap: var(--s2); min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s2) var(--s3); font-size: 13px; cursor: pointer;
}
.combo-chk:hover { background: var(--accent-weak); border-color: var(--accent-weak); }
.combo-chk input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; flex: none; }
.combo-name { min-width: 0; overflow-wrap: anywhere; }
.combo-price { margin-inline-start: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.combo-chk:has(input:checked) { border-color: var(--accent); background: var(--accent-weak); }
/* A part whose dish is GONE. Kept on screen and marked, because the server
   drops it silently and a box that just vanished teaches nobody anything. */
.combo-chk.is-gone { border-color: var(--danger); background: var(--danger-weak); }
.combo-chk.is-gone .combo-name { color: var(--danger); text-decoration: line-through; }
/* The part search hides with a class and touches nothing else — every box
   stays in the DOM, which is what lets a search run without ever dropping a
   ticked part. A ticked one is never hidden at all. */
.combo-chk.is-filtered { display: none; }

.inc-lines { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s2); }
.inc-line { display: grid; grid-template-columns: 1fr 1fr auto; gap: var(--s2); align-items: center; }
.inc-line input { width: 100%; font-size: 13px; padding: 8px 10px; }
.inc-lines:empty { display: none; }

/* THE VARIANTS OF A DISH — a collapsible group inside the opened dish. The
   summary is the group heading and the disclosure control at once; a dish with
   no variants stays folded so most cards keep their old height. Each row is a
   name pair and an optional own price, laid out like the combo lines above. */
.mr-var > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--s2);
  margin: 0 0 var(--s2); font-size: 12px; font-weight: 700;
  color: var(--accent-press); letter-spacing: .02em;
}
.mr-var > summary::-webkit-details-marker { display: none; }
.mr-var > summary::before {
  content: "+"; display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; font-weight: 700; line-height: 1;
  border: 1px solid var(--line); border-radius: var(--r-btn); color: var(--muted);
}
.mr-var[open] > summary::before { content: "\2212"; }
.mr-var-n {
  font-size: 11px; font-weight: 700; color: var(--accent-press);
  background: var(--accent-weak); border-radius: var(--r-pill); padding: 0 8px;
}
.var-lines { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s2) 0; }
/* One-box name, the price, the delete — then the TAG CHIPS wrapping on
   their own row underneath (the same picker the dish badges use, smaller). */
.var-line { display: grid; grid-template-columns: 1.4fr 110px auto; gap: var(--s2); align-items: center; }
.var-tags { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px; }
.var-tag-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px;
  font-size: 12px;
  border: 1px solid var(--line, #e8ddce);
  border-radius: 999px;
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.var-tag-chip.on {
  border-color: var(--accent, #c8873f);
  background: color-mix(in srgb, var(--accent, #c8873f) 14%, transparent);
  font-weight: 600;
}
.var-tag-chip .ic-sm { width: 13px; height: 13px; }
.var-line input { width: 100%; font-size: 13px; padding: 8px 10px; }
.var-lines:empty { display: none; }

/* THE OFFER PRICE ON A DISH. It reuses the discount pair's layout (menu price,
   offer box, computed saving), so the two offer surfaces look like one thing. */
.mr-offer .deal-pair { margin-top: 2px; }
.offer-out:empty { display: none; }

/* THE OFFER OVERVIEW on the offers tab — every named dish on one screen with an
   inline offer box beside its menu price. A row is a grid; on a phone it stacks
   its own way (below) so the box and the price stay readable. */
.offer-ov { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px dashed var(--line); }
.offer-list { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s2); }
.offer-row {
  display: grid; align-items: center; gap: var(--s2) var(--s3);
  grid-template-columns: minmax(0, 1fr) auto 140px 96px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s2) var(--s3);
}
.or-name { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-width: 0; }
.or-title { font-weight: 600; overflow-wrap: anywhere; }
.or-en { font-size: 12px; color: var(--muted); }
.or-tag {
  font-size: 11px; font-weight: 700; color: var(--accent-press);
  background: var(--accent-weak); border: 1px solid var(--accent);
  border-radius: var(--r-pill); padding: 1px 8px;
}
.or-cell { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--muted); min-width: 0; }
.or-cell > span:first-child { font-weight: 600; }
.or-menu { font-size: 13px; color: var(--ink); font-variant-numeric: tabular-nums; }
.or-cell input { width: 100%; min-width: 0; font-size: 13px; padding: 7px 8px; font-variant-numeric: tabular-nums; }
.or-pct { text-align: center; }
.or-out { grid-column: 1 / -1; }
.or-out:empty { display: none; }

@media (max-width: 760px) {
  .deal-pair, .deal-dates { grid-template-columns: 1fr; }
  .cc-head { grid-template-columns: 1fr; align-items: stretch; }
  .inc-line { grid-template-columns: 1fr 1fr; }
  .inc-line .inc-del { grid-column: 1 / -1; justify-self: start; }
  .var-line { grid-template-columns: 1fr 1fr; }
  .var-line input[data-var="name"] { grid-column: 1 / -1; }
  .var-line .var-del { grid-column: 1 / -1; justify-self: start; }
  .offer-row { grid-template-columns: 1fr 1fr; }
  .offer-row .or-name { grid-column: 1 / -1; }
  .offer-row .or-pct { grid-column: 1 / -1; justify-self: start; text-align: start; }
}
@media (max-width: 360px) {
  /* At the narrowest phone the card keeps its content and loses its inset —
     a 16px gutter on each side of a price box is a price box you cannot read. */
  .deal-card, .combo-card { padding: var(--s3); }
  .pick-box { padding: var(--s3); }
  /* The folded line now carries a fourth control, and the DISH NAME is the one
     thing on it that must stay readable. The two square buttons and the price
     give back what the handle took; the name keeps its minmax(0,1fr), so it
     absorbs the difference rather than the row overflowing. */
  .mr-head { grid-template-columns: auto auto minmax(0, 1fr) 74px; gap: var(--s1); }
  .drag-h, .mr-toggle { width: 26px; height: 26px; }
  .drag-h .ic, .mr-toggle .ic { width: 14px; height: 14px; }
}

/* Item badges -------------------------------------------------------------- */
/* The same control as the allergen picker below, deliberately: they are two
   fixed vocabularies an owner ticks on a phone, and one of them is a safety
   declaration — making them look and behave alike is what keeps the ticking
   habit the same. The LOUD four are marked, because "this one shouts on your
   page" is the only thing distinguishing them and an owner deciding blind
   would use all four on every dish. */
.badge-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: var(--s2); margin-bottom: var(--s2);
}
.badge-chk {
  display: flex; align-items: center; gap: var(--s2); min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s2) var(--s3); font-size: 13px; cursor: pointer;
}
.badge-chk:hover { background: var(--accent-weak); border-color: var(--accent-weak); }
.badge-chk input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; flex: none; }
.badge-chk .ic { color: var(--muted); }
.badge-chk span { min-width: 0; overflow-wrap: anywhere; }
.badge-chk:has(input:checked) { border-color: var(--accent); background: var(--accent-weak); }
.badge-chk:has(input:checked) .ic { color: var(--accent-press); }
/* A dashed edge for the four the page emphasises — visible before it is
   ticked, so the choice is informed rather than discovered on the page. */
.badge-chk.is-loud { border-style: dashed; }
.badge-chk.is-loud:has(input:checked) { border-style: solid; box-shadow: var(--ring); }

/* Mealtimes on a dish ------------------------------------------------------ */
/* The same control again — a third fixed vocabulary ticked on a phone, and it
   would be perverse to give it a different shape from the two beside it. It is
   narrower because there are only six short words in it and a 132px column
   would leave «حلى» sitting in a field of nothing. */
.meal-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: var(--s2); margin-bottom: var(--s2);
}
.meal-chk {
  display: flex; align-items: center; gap: var(--s2); min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s2) var(--s3); font-size: 13px; cursor: pointer;
}
.meal-chk:hover { background: var(--accent-weak); border-color: var(--accent-weak); }
.meal-chk input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; flex: none; }
.meal-chk span { min-width: 0; overflow-wrap: anywhere; }
.meal-chk:has(input:checked) { border-color: var(--accent); background: var(--accent-weak); }
/* "Empty means all day" is the SEMANTICS, not a footnote — an owner who reads
   it as "not served" will tag every dish and destroy the signal. So it gets a
   rule of its own and reads at the same weight as the labels above it. */
.meal-allday { color: var(--ink); }
.meal-allday strong { color: var(--accent-press); }

/* The dish's own photo, beside the dish ------------------------------------- */
.mr-photo { padding-top: var(--s3); margin-top: var(--s3); border-top: 1px dashed var(--line); }
.mr-photo-row { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s2); }

/* Calories + allergens, inside a dish row ---------------------------------- */
/* Inset like the discount and combo blocks above, for the same reason: most
   rows will never carry either, and an editor that opens with them shouting
   looks like unfinished work. */
.facts-block {
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px dashed var(--line);
}
.facts-block .menu-legend { margin-bottom: var(--s2); }
.menu-row-cal { display: grid; grid-template-columns: minmax(0, 220px); gap: var(--s2); }
.menu-row-cal input { width: 100%; font-size: 13px; padding: 8px 10px; }
.cal-out:empty { display: none; }
.cal-out { margin-top: var(--s2); }
/* THE WALK IS NOT A VERDICT. It is deliberately NOT the accent pill the
   discount note wears: it is a neutral fact about the dish, not a result the
   owner achieved, and it must never read as praise or as a warning. Plain
   muted text, the same weight as the hints around it. */
.cal-note {
  display: inline-block; font-size: 12px; font-weight: 500; line-height: 1.6;
  color: var(--muted);
}
.cal-basis { margin-top: var(--s2); }

.alg-h { margin-top: var(--s3); }
/* auto-fit down to 132px keeps three columns on a desktop and two at 360px,
   with the whole row a comfortable thumb target either way. */
.alg-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: var(--s2); margin-bottom: var(--s2);
}
.alg-chk {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 13px; font-weight: 500; min-height: 38px;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  padding: var(--s1) var(--s2); cursor: pointer;
}
.alg-chk:hover { background: var(--accent-weak); border-color: var(--accent-weak); }
.alg-chk input { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; padding: 0; flex: none; }
.alg-chk .ic { color: var(--muted); }
.alg-chk span { min-width: 0; overflow-wrap: anywhere; }
/* Ticked reads as an ASSERTION ("this contains X"), so it is marked — but with
   the neutral accent the rest of the console uses, never a hazard colour. An
   allergen is not a defect in the dish. */
.alg-chk:has(input:checked) { border-color: var(--accent); background: var(--accent-weak); }
.alg-chk:has(input:checked) .ic { color: var(--accent-press); }
/* The caveat the whole feature turns on: what is NOT ticked is not a claim.
   Given its own inset block so it cannot be skimmed past as another hint. */
.alg-note {
  margin: 0; padding: var(--s2) var(--s3);
  border-inline-start: 3px solid var(--line);
  background: var(--bg);
  line-height: 1.7;
}
.alg-off { margin: 0 0 var(--s2); }

/* Auto-translate ----------------------------------------------------------- */
/* Reuses the CSS assistant's card (.css-ai-card / .css-ai-working / actions):
   same contract — a proposal that saves nothing until it is accepted — so it
   should look like the same thing. Only the review list is new. */
.tr-box { margin: 0 0 var(--s3); }
.tr-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
.tr-copy { min-width: 0; flex: 1 1 240px; }
.tr-h { margin: 0 0 2px; font-size: 13px; font-weight: 600; }
.tr-head .menu-legend { margin: 0; }
.tr-go { flex: 0 0 auto; white-space: nowrap; }
.tr-out:empty { display: none; }
.tr-out { margin-top: var(--s3); }
/* The proposal covers two different things — the page header and the dishes —
   and they are saved through two different endpoints. Grouping them says so,
   and keeps a one-line header suggestion from reading as a 41st dish. Either
   group renders alone when the other has nothing to fill. */
.tr-group + .tr-group { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }
.tr-group-h {
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
  margin: 0; font-size: 12px; font-weight: 600; color: var(--ink);
}
.tr-group-n { font-weight: 400; color: var(--muted); }
.tr-list { margin: var(--s2) 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow-y: auto; }
.tr-group:last-child .tr-list { margin-bottom: 0; }
.tr-list li { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; font-size: 12px; line-height: 1.7; }
.tr-dish { font-weight: 600; color: var(--ink); }
.tr-field { color: var(--muted); }
.tr-val {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 1px 8px; color: var(--accent-press);
}
.menu-actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; margin-top: var(--s3); }
.menu-count { font-size: 12px; color: var(--muted); }

/* Panel tabs --------------------------------------------------------------- */
.panel-tabs {
  display: flex; gap: var(--s1); border-bottom: 1px solid var(--line);
  margin-bottom: var(--s4); overflow-x: auto; scrollbar-width: none;
}
.panel-tabs::-webkit-scrollbar { display: none; }
.ptab {
  background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--muted); font-size: 14px; font-weight: 600; white-space: nowrap;
  padding: var(--s2) var(--s3); border-radius: 0; height: auto;
}
.ptab:hover:not(:disabled) { color: var(--ink); }
.ptab.on { color: var(--accent-press); border-bottom-color: var(--accent); }
/* NINE tabs now, each saying what is under it. «الصفحة» on its own does not
   tell an owner where the opening hours went, and a console this size is only
   navigable if the top-level labels answer that without a click.
   Nine will not fit a 360px row, so the strip scrolls sideways and no tab is
   ever squeezed: `flex: none` keeps each one its own width, and the active one
   is nudged into view by keepTabVisible() after every render. */
.ptab { display: flex; flex-direction: column; gap: 1px; text-align: start; flex: 0 0 auto; }
.ptab-t { font-size: 14px; font-weight: 600; }
.ptab-s { font-size: 11px; font-weight: 400; color: var(--muted); }
.ptab.on .ptab-s { color: var(--accent-press); }
@media (max-width: 760px) {
  /* On a phone the strip scrolls, so the second line is dropped rather than
     dragging nine two-line tabs across four screen widths. The title attribute
     keeps it, and the tab's own subtitle is repeated at the top of its body. */
  .ptab-s { display: none; }
  .ptab { padding: var(--s2); }
}

/* Folding groups ----------------------------------------------------------- */
/* `<details>` rather than conditional markup, and that is a data decision as
   much as a layout one: a folded group's inputs stay in the DOM, so collectPage
   keeps reading them and a folded section can never be mistaken for a cleared
   one. The open state is mirrored in JS because the tab re-renders on save. */
.pg-sec {
  border: 1px solid var(--line); border-radius: var(--r-card);
  margin-bottom: var(--s3); background: var(--surface);
}
.pg-sec[open] { border-color: var(--accent-weak); }
.pg-sec-h {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s4); cursor: pointer; list-style: none;
  font-size: 15px; font-weight: 600; color: var(--ink);
}
.pg-sec-h::-webkit-details-marker { display: none; }
.pg-sec-h:hover { color: var(--accent-press); }
.pg-sec-t { min-width: 0; overflow-wrap: anywhere; }
/* A count on the summary, so a folded group still answers "is there anything
   in there?" — the same job the dish row's flags do. */
.pg-sec-n {
  font-size: 11px; font-weight: 700; color: var(--accent-press);
  background: var(--accent-weak); border-radius: var(--r-pill); padding: 1px 8px;
}
.pg-sec-mark { margin-inline-start: auto; display: inline-flex; color: var(--muted); }
.pg-sec-mark .ic { width: 18px; height: 18px; transition: transform .15s ease; }
.pg-sec[open] > .pg-sec-h .pg-sec-mark .ic { transform: rotate(180deg); }
.pg-sec-b { padding: 0 var(--s4) var(--s4); }
.pg-sec-b > .panel-sub { margin-top: 0; }
/* Advanced groups are quieter still: present, never inviting. */
.pg-sec.is-adv { background: var(--bg); border-style: dashed; }
.pg-sec.is-adv .pg-sec-h { font-size: 14px; color: var(--muted); }
.pg-sec.is-adv[open] > .pg-sec-h { color: var(--ink); }
/* A fold nested inside a fold (the eleven surface colours inside the look)
   steps back a level rather than repeating the frame. */
.pg-sec .pg-sec { margin-top: var(--s3); margin-bottom: 0; }
.pg-sub-block { margin-bottom: var(--s4); }
.pg-sub-block:last-child { margin-bottom: 0; }

/* The staff worklist ------------------------------------------------------- */
.wl-head { margin-bottom: var(--s3); }
.wl-h { margin: 0 0 var(--s1); font-size: 15px; font-weight: 600; }
/* A place an owner has since claimed. Still listed — staff should see what
   became of their work — but it has no editor, because the server refuses this
   account every read AND every write on it. */
.biz-card.staff-card.is-locked { border-style: solid; background: var(--bg); opacity: .92; }

/* Page builder layout ------------------------------------------------------ */
.pg-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--s5); align-items: start; }
.pg-preview-col { position: sticky; top: var(--s4); }
@media (max-width: 900px) {
  /* Phone: the preview leads, so a colour change is visible before scrolling. */
  .pg-grid { grid-template-columns: 1fr; gap: var(--s4); }
  .pg-preview-col { position: static; order: -1; }
}
.pg-status { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s3); }
.pg-status .pg-live { padding: 4px 10px; font-size: 12px; }
.pg-actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }
.pg-actions .btn-line { height: 42px; padding: 0 var(--s4); }

/* Theme picker ------------------------------------------------------------- */
.theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s2); }
.theme-card {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s1);
  text-align: start; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s2); height: auto; cursor: pointer;
}
.theme-card:hover:not(:disabled) { border-color: var(--accent); }
.theme-card.on { border-color: var(--accent); box-shadow: var(--ring); background: var(--accent-weak); }
.theme-swatch { display: flex; height: 26px; border-radius: 6px; overflow: hidden; border: 1px solid var(--line); }
.theme-swatch i { flex: 1 1 0; display: block; }
.theme-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); }
.theme-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.theme-on {
  display: none; font-size: 10px; font-weight: 700; color: var(--accent-press);
  background: var(--surface); border: 1px solid var(--accent); border-radius: var(--r-pill); padding: 1px 6px;
}
.theme-card.on .theme-on { display: inline-block; }
.theme-note { font-size: 11px; color: var(--muted); line-height: 1.5; }
.theme-layout { font-size: 10px; color: var(--muted); font-weight: 600; }

/* Menu structure picker ---------------------------------------------------- */
/* Four shapes and a default, and the words for them are useless on their own —
   «بطاقات» and «شبكة» mean nothing until the difference has been seen. So each
   option is a DRAWING of itself: schematic, monochrome, inheriting the card's
   own ink, and never a screenshot of somebody else's menu. */
.lay-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s2); }
.lay-card {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s1);
  text-align: start; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s2); height: auto; cursor: pointer;
  color: var(--ink);
}
.lay-card:hover:not(:disabled) { border-color: var(--accent); }
.lay-card.on { border-color: var(--accent); box-shadow: var(--ring); background: var(--accent-weak); }
.lay-sketch {
  display: grid; place-items: center; background: var(--bg);
  border: 1px solid var(--line); border-radius: 6px; padding: var(--s2);
  color: var(--accent-press);
}
.lay-card.on .lay-sketch { border-color: var(--accent); background: var(--surface); }
.lay-sketch svg { width: 100%; max-width: 74px; height: auto; display: block; }
.lay-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); }
.lay-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.lay-on {
  display: none; font-size: 10px; font-weight: 700; color: var(--accent-press);
  background: var(--surface); border: 1px solid var(--accent); border-radius: var(--r-pill); padding: 1px 6px;
}
.lay-card.on .lay-on { display: inline-block; }
.lay-note { font-size: 11px; color: var(--muted); line-height: 1.5; }

/* Font picker -------------------------------------------------------------- */
/* A list of font NAMES is not a picker — "Amiri" and "Reem Kufi" mean nothing
   until you see them set. Every option is therefore rendered IN ITS OWN FACE
   (owner.js loads the fourteen families from Google Fonts for exactly this),
   and the sample line carries both Arabic and Latin because a Saudi menu is
   always both. */
.font-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--s2); }
.font-card {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s1);
  text-align: start; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s3); height: auto; cursor: pointer;
}
.font-card:hover:not(:disabled) { border-color: var(--accent); }
.font-card.on { border-color: var(--accent); box-shadow: var(--ring); background: var(--accent-weak); }
.font-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); }
.font-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.font-on {
  display: none; font-size: 10px; font-weight: 700; color: var(--accent-press);
  background: var(--surface); border: 1px solid var(--accent); border-radius: var(--r-pill); padding: 1px 6px;
}
.font-card.on .font-on { display: inline-block; }
/* The whole point of the card. Big enough that the shapes of the letterforms
   are actually legible at a glance, on one line that never wraps mid-word. */
.font-sample {
  font-size: 19px; line-height: 1.8; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.font-note { font-size: 11px; color: var(--muted); line-height: 1.5; }

/* Sections tab ------------------------------------------------------------- */
/* The headings of the menu, in the order a diner reads them. Numbered like the
   branches and for the same reason: the ORDER is the content here, so it has
   to be readable without counting rows. */
.sec-list { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s3) 0; }
.sec-row { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
.sec-row.is-asking { border-color: var(--danger); }
.sec-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
.sec-n {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--r-pill);
  background: var(--surface); color: var(--accent-press); border: 1px solid var(--accent);
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
/* HOW MANY DISHES THIS HEADING HOLDS — the one number that decides whether it
   appears on the public page at all, so the empty state is marked rather than
   left as a quiet zero the owner has to interpret. */
.sec-count { font-size: 12px; color: var(--muted); }
.sec-count.is-empty { color: var(--danger); font-weight: 600; }
.sec-head .sec-del { margin-inline-start: auto; }
.sec-row .field { margin-bottom: var(--s2); }
.sec-row .field label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.sec-row input { width: 100%; font-size: 13px; padding: 8px 10px; }
.sec-note {
  display: block; margin-top: var(--s1);
  font-size: 12px; line-height: 1.6;
  border-radius: var(--r-btn); padding: var(--s2) var(--s3);
}
.sec-note.bad { background: var(--danger-weak); color: var(--danger); }
/* DELETING IS A QUESTION, not a confirmation — its dishes have to go somewhere
   and only the owner knows where. So the row opens into the answer rather than
   throwing a yes/no box that would have to choose one silently. */
.sec-ask {
  margin-top: var(--s2); padding: var(--s3);
  border: 1px solid var(--danger); border-radius: var(--r-card);
  background: var(--danger-weak);
}
.sec-ask-h { margin: 0 0 var(--s1); font-size: 14px; font-weight: 700; color: var(--danger); }
.sec-ask-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin: var(--s2) 0; }
.sec-ask-row label { font-size: 12px; font-weight: 600; }
.sec-ask select {
  flex: 1 1 160px; min-width: 0; font-size: 13px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--ink);
}
/* Sections the DISHES name that were never registered. They already work — the
   server prints them after the declared ones — so this is an offer of control,
   not an error, and it reads like one. */
.sec-extra { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s2) 0; }
.sec-extra-row {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  border: 1px dashed var(--line); border-radius: var(--r-card); padding: var(--s2) var(--s3);
}
.sec-extra-row .sec-take { margin-inline-start: auto; }

/* The section field on a dish: a picker, with free text under it ------------ */
.mr-sec { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.mr-sec-pick {
  width: 100%; font-size: 13px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--surface); color: var(--ink);
}

/* Announced, not just shown ------------------------------------------------ */
/* Every reorder is written into #dragLive. Off-screen rather than
   `display:none`, because a hidden element is not announced at all — and the
   keyboard reorder path exists for exactly the people who would hear this. */
.sr-live {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  border: 0;
}

/* Branches ----------------------------------------------------------------- */
/* One menu, several addresses. Numbered, because the FIRST one is ours — the
   server seeds it from the place we located — and the rest are theirs. */
.br-list { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s3) 0; }
.br-row { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
.br-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
.br-n {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--r-pill);
  background: var(--surface); color: var(--accent-press); border: 1px solid var(--accent);
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.br-tag { font-size: 11px; color: var(--muted); }
.br-head .br-del { margin-inline-start: auto; }
.br-row .field { margin-bottom: var(--s2); }
.br-row .field label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.br-row input { width: 100%; font-size: 13px; padding: 8px 10px; }
.br-out:empty { display: none; }
.br-note {
  display: block; margin-top: var(--s1);
  font-size: 12px; line-height: 1.6;
  border-radius: var(--r-btn); padding: var(--s2) var(--s3);
}
.br-note.bad { background: var(--danger-weak); color: var(--danger); }

/* Brand colours ------------------------------------------------------------ */
.pg-colors { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--s3); }
.pg-color label { display: block; font-size: 13px; font-weight: 600; margin-bottom: var(--s2); }
.pg-color-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.pg-color-row input[type="color"] {
  width: 46px; height: 34px; padding: 2px; border: 1px solid var(--line);
  border-radius: var(--r-btn); background: var(--surface); cursor: pointer; flex: 0 0 auto;
}
.pg-color-state { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 74px; }

/* THE SETUP CHECKLIST STRIP. Pills, numbered until done, green tick after —
   and the whole strip leaves the page when every step is done. */
.su-strip {
  border: 1px solid var(--accent-weak);
  border-inline-start: 3px solid var(--accent);
  background: var(--accent-weak);
  border-radius: var(--r-card);
  padding: var(--s3);
  margin-bottom: var(--s3);
}
.su-h { margin: 0 0 var(--s2); font-size: 13px; font-weight: 700; color: var(--accent-press); }
.su-count { font-weight: 400; color: var(--muted); margin-inline-start: var(--s2); }
.su-pills { display: flex; flex-wrap: wrap; gap: var(--s2); }
.su-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 999px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  cursor: pointer; text-decoration: none;
}
.su-pill:hover { border-color: var(--accent); }
.su-pill.done { border-color: var(--accent); color: var(--accent-press); }
.su-pill.done .su-n { background: var(--accent); color: #fff; }
.su-pill.neutral { opacity: 0.75; }
.su-n {
  width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center;
  background: var(--line); font-size: 10px; font-weight: 700;
}
.su-n .ic, .su-n svg { width: 11px; height: 11px; }

/* THE LIVE PREVIEW STAGE. The frame is laid out at DEVICE width (390 phone,
   768 tablet — set inline by sizeLivePreview) and scaled down to fit the fold,
   so everything inside wraps exactly as the published page will. The stage
   clips; the frame keeps a bezel so it reads as a device, not a broken div. */
.lp-bar { display: flex; gap: var(--s2); align-items: center; margin-bottom: var(--s2); }
.lp-stage { overflow: hidden; }
.lp-frame {
  transform-origin: top right;
  border: 8px solid var(--ink);
  border-radius: 22px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);
  display: inline-block;
}
[dir="ltr"] .lp-frame { transform-origin: top left; }
.lp-frame iframe { border: 0; width: 100%; height: 100%; display: block; }

/* THE GRADIENT STRIP — painted with the very linear-gradient the page will
   use, so the control shows the answer, not a description of it. */
.sf-grad-strip {
  height: 18px; border-radius: 9px; margin-bottom: var(--s2);
  border: 1px solid var(--line);
}

/* THE SHAPE PICKER — icon buttons, not a dropdown (founder, 2026-07-25: "have
   those shapes to be like icon buttons to choose from"). A shape names itself
   badly: "round" and "very rounded" are indistinguishable as words and obvious
   as pictures. Each swatch is drawn with the SAME radius the server will apply,
   so the button is a preview rather than a label, and the whole set is visible
   at once instead of hidden behind a click.

   The touch target stays 40px even though the swatch is 26px — a control an
   owner uses on a phone in their shop. */
.sf-shapes { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.sf-shape {
  width: 40px; height: 40px; padding: 0; flex: 0 0 auto;
  display: grid; place-items: center; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-btn);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.sf-shape:hover { border-color: var(--muted); }
.sf-shape.on {
  border-color: var(--accent);
  /* A ring rather than a thicker border: a border that grows on selection moves
     every other button by a pixel. */
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent);
}
.sf-shape-ico {
  width: 26px; height: 26px;
  background: var(--muted);
  /* Overridden inline with the option's own (scaled) radius — that IS the
     icon. Square is the base, so no inline radius means square. */
  border-radius: 0;
}
.sf-shape.on .sf-shape-ico { background: var(--accent); }
/* "Follow the theme" is the absence of a choice, so it is drawn as an absence:
   an outline, not a filled shape. */
.sf-shape-ico.is-theme {
  background: transparent;
  border: 2px dashed var(--muted);
  border-radius: 3px;
}
.sf-shape.on .sf-shape-ico.is-theme { border-color: var(--accent); }

/* Surfaces ----------------------------------------------------------------- */
/* Eleven controls is a wall unless it is grouped, so they are grouped the way
   an owner thinks about a page rather than the way the API lists them. */
.sf-group { margin-bottom: var(--s4); }
.sf-group:last-of-type { margin-bottom: var(--s3); }
.sf-h {
  margin: 0 0 var(--s2); font-size: 12px; font-weight: 700;
  letter-spacing: .02em; color: var(--muted);
}
.sf-contrast:empty { display: none; }
.sf-contrast { margin-top: var(--s3); }
.sf-pairs { margin: 0 0 var(--s2); padding-inline-start: 18px; }
.sf-pairs li { font-size: 13px; line-height: 1.8; color: var(--ink); font-variant-numeric: tabular-nums; }

/* Opening hours ------------------------------------------------------------ */
/* Seven rows, each with THREE states side by side. The states are buttons and
   not a checkbox because "not set" is a real third answer, and a two-position
   control is exactly how an owner ends up publishing "closed" for a day nobody
   ever asked them about. */
.hr-week { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s3) 0; }
.hr-day { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
/* The three states, told apart at a glance and not only by the pressed pill:
   an unset day is dashed and quiet, a closed day is flat, an open day is
   ordinary. */
.hr-day.is-unknown { border-style: dashed; background: var(--bg); }
.hr-day.is-closed { background: var(--bg); }
.hr-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.hr-name { font-size: 14px; font-weight: 600; min-width: 76px; }
.hr-states { display: inline-flex; gap: 2px; margin-inline-start: auto; flex-wrap: wrap; }
.hr-st {
  padding: 6px 10px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--line); background: var(--surface); color: var(--muted);
  border-radius: var(--r-btn); cursor: pointer;
}
.hr-st.on { background: var(--accent-weak); border-color: var(--accent); color: var(--accent-press); }
.hr-body { margin-top: var(--s2); }
.hr-body:empty { display: none; }
.hr-note { margin: 0; font-size: 12px; line-height: 1.7; color: var(--muted); }
.hr-range-row { display: flex; align-items: flex-end; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
/* min-width:0 on BOTH the label and the input, or a `type="time"` box keeps
   its intrinsic width (the clock affordance is wide) and the two fields never
   share a line on a phone — which is where an owner actually fills this in. */
.hr-t {
  display: flex; flex-direction: column; gap: 3px;
  flex: 1 1 96px; min-width: 0;
  font-size: 11px; color: var(--muted);
}
.hr-t input {
  width: 100%; min-width: 0;
  font-size: 13px; padding: 7px 8px; border: 1px solid var(--line);
  border-radius: var(--r-btn); background: var(--surface); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.hr-del { flex: 0 0 auto; }
/* An after-midnight close is CONFIRMED, not warned about: 16:00 → 02:00 is an
   ordinary cafe here, and a red box on it would teach owners to "fix" a
   correct entry. */
.hr-over { flex-basis: 100%; font-size: 11px; line-height: 1.6; color: var(--muted); }
.hr-acts { display: flex; gap: var(--s2); flex-wrap: wrap; }
.hr-rg { display: inline-flex; align-items: baseline; gap: 4px; font-variant-numeric: tabular-nums; }
.hr-next { font-size: 11px; color: var(--muted); }

/* What the page says TODAY, computed with the server's own rules. */
.hr-live {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin: 0; padding: var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); font-size: 13px;
}
.hr-live-lbl { font-size: 12px; color: var(--muted); }
.hr-live-st { font-weight: 700; }
/* Several ranges in one day is normal here, so they need a gap between them or
   "08:00 – 12:00" and "16:00 – 23:00" run into one unreadable number. */
.hr-live-body { display: inline-flex; flex-wrap: wrap; gap: var(--s2); }
.hr-live-sp { flex-basis: 100%; font-size: 12px; color: var(--muted); }
/* THREE states, three marks — never two. A hollow ring is "we do not know",
   which is not the same statement as "closed". */
.hr-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--muted); border: 1px solid var(--muted);
}
.is-open .hr-dot { background: var(--accent); border-color: var(--accent); }
.is-closed .hr-dot { background: transparent; border-color: var(--muted); }
.is-unknown .hr-dot { background: transparent; border-style: dashed; }

/* Special periods ---------------------------------------------------------- */
.sp-list { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s3) 0; }
.sp-row { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
/* Dates the SERVER would drop. The row stays on screen and says why. */
.sp-row.bad { border-color: var(--danger); }
.sp-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
.sp-n {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--r-pill);
  background: var(--surface); color: var(--accent-press); border: 1px solid var(--accent);
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.sp-tag {
  font-size: 11px; font-weight: 700; color: var(--accent-press);
  background: var(--accent-weak); border-radius: var(--r-pill); padding: 2px 8px;
}
.sp-head .sp-del { margin-inline-start: auto; }
.sp-row .field { margin-bottom: var(--s2); }
.sp-row .field label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.sp-row input, .sp-row select { width: 100%; font-size: 13px; padding: 8px 10px; }
.sp-closed { margin: var(--s2) 0; }
.sp-week { margin-top: var(--s2); }
/* A special's week is a nested editor, so it steps back a level visually. */
.sp-week .hr-week { gap: var(--s1); }
.sp-week .hr-day { padding: var(--s2); }

@media (max-width: 640px) {
  .hr-states { margin-inline-start: 0; width: 100%; }
  .hr-st { flex: 1 1 0; }
  /* The two clocks keep the row to themselves; the delete drops under them
     rather than stealing width from the fields being typed into. */
  .hr-range-row > .hr-del { order: 3; }
}

/* The private answer sheet (info tab) -------------------------------------- */
/* THE THIRD STATE HAS TO BE VISIBLE, not inferred. Everything below exists to
   make "we haven't said" look like a deliberate answer sitting beside the other
   two, rather than like an empty box waiting to be filled — an owner who reads
   blank as failure answers all 26 lines defensively and the sheet stops being
   worth reading. */
/* The form is also the PRESENCE SIGNAL collectFacts reads: no form on screen,
   no read — which is what stops a panel showing the Menu tab from being read as
   an owner having cleared all 26 answers. It is a wrapper, so it keeps the
   groups evenly spaced and nothing else. */
.of-form { margin-bottom: var(--s5); }
.of-form > .pg-sec:last-child { margin-bottom: 0; }
.of-tri-note {
  border: 1px solid var(--accent-weak); border-inline-start: 3px solid var(--accent);
  background: var(--accent-weak); border-radius: var(--r-card);
  padding: var(--s3) var(--s4);
}
.of-tri-h { margin: 0 0 var(--s1); font-size: 14px; font-weight: 700; color: var(--accent-press); }
.of-tri-b { margin: 0 0 var(--s2); font-size: 13px; line-height: 1.75; color: var(--ink); }
.of-tri-b:last-child { margin-bottom: 0; }

.of-flags { display: flex; flex-direction: column; gap: var(--s2); }
.of-flag {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2) var(--s3); flex-wrap: wrap; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s2) var(--s3);
}
.of-flag-l { font-size: 13px; font-weight: 500; min-width: 0; overflow-wrap: anywhere; }
/* A real radiogroup, drawn as one segmented control: three answers that are
   obviously alternatives to each other, which three separate tickboxes could
   never be. It never wraps internally — at 360px the whole control drops to its
   own line under the label instead of breaking into three stacked words. */
.of-tri {
  display: inline-flex; flex: none; margin-inline-start: auto;
  border: 1px solid var(--line); border-radius: var(--r-pill); overflow: hidden;
  background: var(--surface);
}
.of-opt {
  position: relative; display: inline-flex; align-items: center; gap: 4px;
  min-height: 36px; padding: 0 var(--s3); cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--muted);
  border-inline-start: 1px solid var(--line); white-space: nowrap;
}
.of-opt:first-child { border-inline-start: 0; }
/* Visually hidden, NEVER display:none — the control has to stay in the tab
   order and answerable from the keyboard, which is the main reason it is a
   radio group at all. */
.of-opt input {
  position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
  opacity: 0; pointer-events: none;
}
.of-opt:hover { background: var(--bg); color: var(--ink); }
.of-mark { display: inline-flex; align-items: center; flex: none; }
.of-opt-t { min-width: 0; }
.of-opt .ic { width: 14px; height: 14px; }
/* UNSAID gets a mark of its own. An empty slot beside a tick and a cross reads
   as "nothing here yet"; a dash reads as an answer, which is what it is. */
.of-dash { font-size: 13px; line-height: 1; }
/* Focus lands on the hidden radio; the ring is drawn on the segment around it,
   so keyboard use is as visible here as anywhere else in the console. */
.of-opt:focus-within { box-shadow: var(--ring); z-index: 1; }
/* YES is the accent the whole console uses for an assertion. NO is the danger
   colour — not because a "no" is bad, but because it is the ONE value that can
   rule the shop out of a search, and it must not be settable by accident.
   UNSAID is deliberately quiet and deliberately NOT empty-looking: it is a
   selected state like the other two. */
.of-opt.on.is-yes { background: var(--accent-weak); color: var(--accent-press); }
.of-opt.on.is-no { background: var(--danger-weak); color: var(--danger); }
.of-opt.on.is-un { background: var(--bg); color: var(--ink); }
.of-flag:has(.of-opt.on.is-yes) { border-color: var(--accent); }
.of-flag:has(.of-opt.on.is-no) { border-color: var(--danger); }

.of-fields { display: flex; flex-direction: column; gap: var(--s3); }
.of-fields .field { margin-bottom: 0; }
.of-fields label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.of-note textarea { width: 100%; min-height: 62px; font-size: 13px; line-height: 1.7; }
.of-num input { width: 100%; max-width: 220px; font-size: 13px; padding: 8px 10px; }
/* WHY WE ASK, for the two fields nobody guesses: the kitchen's own closing time
   and the honest "we are not right for this". Both read at full weight — a
   hint-grey footnote is exactly how they get skipped. */
/* Same inset block as the allergen caveat, on purpose: both are "read this
   before you fill the thing below in", and no physical border-radius, so the
   stripe sits on the reading edge in both directions. */
.of-why {
  margin: 0 0 var(--s2); padding: var(--s2) var(--s3);
  border-inline-start: 3px solid var(--accent);
  background: var(--bg);
  font-size: 12px; line-height: 1.75; color: var(--ink);
}
/* Out of the server's range — said before the save, never discovered after it,
   the same contract the calorie box on a dish already has. */
.of-num.is-bad input { border-color: var(--danger); }
.of-num.is-bad .hint { color: var(--danger); font-weight: 600; }

@media (max-width: 520px) {
  /* The label takes the row and the control takes the next one, at full width:
     a name like «يصلح للشغل والدراسة» beside three segments is unreadable at
     360px, and squeezing the segments makes them un-tappable. */
  .of-flag { flex-direction: column; align-items: stretch; }
  .of-tri { margin-inline-start: 0; width: 100%; }
  .of-opt { flex: 1 1 0; justify-content: center; padding: 0 var(--s2); }
}

/* Logo / cover / dish photos ----------------------------------------------- */
.pg-media { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--s3); }
.pg-slot { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
.pg-slot-label { display: block; font-size: 13px; font-weight: 600; margin-bottom: var(--s2); }
/* `is-logo`/`is-cover`, not `logo`/`cover`: auth.css already owns a global
   `.logo` (the 40x40 topbar mark) and it would clamp this preview box. */
.pg-slot-img {
  display: grid; place-items: center; width: 100%; height: 92px;
  background: var(--bg); border: 1px dashed var(--line);
  border-radius: var(--r-btn); margin-bottom: var(--s2); overflow: hidden;
}
.pg-slot-img img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.ph-empty { font-size: 12px; color: var(--muted); }
.file-btn { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.file-btn.busy { opacity: .5; pointer-events: none; }
/* Replace and Remove sit side by side, and Remove only exists while there IS
   an image — so the row is never a lone button with a gap where the other one
   would be. It wraps rather than shrinking: at 360px two 44px-tall buttons on
   one line leave neither of them readable. */
.pg-slot-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

.ph-list { display: flex; flex-direction: column; gap: var(--s2); }
.ph-row {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s2) var(--s3);
}
.ph-thumb {
  width: 54px; height: 54px; flex: 0 0 auto; border-radius: var(--r-btn); overflow: hidden;
  background: var(--bg); border: 1px solid var(--line); display: grid; place-items: center;
}
.ph-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph-thumb .ph-empty { font-size: 10px; text-align: center; line-height: 1.3; }
.ph-main { flex: 1 1 140px; min-width: 0; }
.ph-name { font-size: 14px; font-weight: 600; margin: 0; }
.ph-sub { font-size: 12px; color: var(--muted); margin: 2px 0 0; }

/* Original vs background-removed cut-out ------------------------------------ */
/* Shown ONLY when the algorithm produced a cut, so this block never renders
   empty. The two options sit side by side and wrap to one column on a narrow
   phone; the selected one wears the same accent ring as the layout picker so the
   two "pick one" controls read the same way. */
.cut-choose {
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px dashed var(--line);
}
.cut-h { margin: 0 0 2px; font-size: 13px; font-weight: 600; color: var(--ink); }
.cut-sub { margin: 0 0 var(--s2); font-size: 12px; color: var(--muted); line-height: 1.5; }
.cut-opts { display: flex; flex-wrap: wrap; gap: var(--s2); }
.cut-opt {
  flex: 1 1 140px; min-width: 130px; display: flex; flex-direction: column;
  gap: var(--s1); text-align: start; cursor: pointer; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s2);
}
.cut-opt:hover:not(:disabled) { border-color: var(--accent); }
.cut-opt.on { border-color: var(--accent); box-shadow: var(--ring); background: var(--accent-weak); }
.cut-thumb {
  display: grid; place-items: center; width: 100%; height: 92px;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-btn);
  overflow: hidden;
}
.cut-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* The cut-out is a TRANSPARENT png. A checker under it is what lets "no
   background" read as no background instead of a plain white tile. */
.cut-thumb.is-cut {
  background-color: var(--surface);
  background-image:
    linear-gradient(45deg, var(--line) 25%, transparent 25%),
    linear-gradient(-45deg, var(--line) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--line) 75%),
    linear-gradient(-45deg, transparent 75%, var(--line) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 0 7px, 7px -7px, -7px 0;
}
.cut-top { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); }
.cut-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.cut-on {
  display: none; font-size: 10px; font-weight: 700; color: var(--accent-press);
  background: var(--surface); border: 1px solid var(--accent);
  border-radius: var(--r-pill); padding: 1px 6px; white-space: nowrap;
}
.cut-opt.on .cut-on { display: inline-block; }
.cut-low {
  margin: var(--s2) 0 0; font-size: 11px; line-height: 1.5;
  color: var(--danger);
}

/* Social accounts ---------------------------------------------------------- */
/* Ten short fields. Auto-fit rather than a fixed count so it is three columns
   on a laptop and one clean column on a 360px phone, with no breakpoint list. */
.pg-socials { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--s2) var(--s3); }
.pg-socials .field { margin-bottom: 0; }
.pg-socials label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; }
.pg-socials input { width: 100%; font-size: 13px; padding: 8px 10px; }

/* Custom CSS --------------------------------------------------------------- */
.css-area { min-height: 150px; font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; font-size: 12px; line-height: 1.6; }
.css-count { font-variant-numeric: tabular-nums; }
.css-count.over { color: var(--danger); font-weight: 600; }

/* Describe-the-change box -------------------------------------------------- */
/* Sits ABOVE the textarea because it is the path most owners will take; the
   textarea below stays exactly as it was for the ones who write CSS by hand.
   Neither disables the other at any point. */
.css-ai { margin-bottom: var(--s4); }
.css-ai > label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.css-ai-row { display: flex; gap: var(--s2); align-items: stretch; }
.css-ai-prompt {
  flex: 1 1 auto; min-width: 0; min-height: 46px; resize: vertical;
  font-size: 13px; line-height: 1.6;
}
.css-ai-send { flex: 0 0 auto; align-self: stretch; padding: 0 var(--s4); white-space: nowrap; }
.css-ai-send[disabled] { opacity: .6; cursor: default; }

.css-ai-out:empty { display: none; }
.css-ai-out { margin-top: var(--s3); }

.css-ai-working { display: flex; align-items: center; gap: var(--s2); font-size: 13px; color: var(--muted); }

/* The proposal. Deliberately framed rather than dropped inline: nothing here
   is saved yet, and the owner has to be able to see that at a glance. */
.css-ai-card {
  border: 1px solid var(--accent); border-radius: var(--r-card);
  background: var(--accent-weak); padding: var(--s3);
}
.css-ai-card .ai-h { margin: 0 0 4px; font-size: 13px; font-weight: 600; color: var(--accent-press); }
.css-ai-card .ai-sub { margin: 0 0 var(--s3); font-size: 12px; line-height: 1.7; color: var(--ink); }
.css-ai-code {
  margin: 0 0 var(--s3); max-height: 220px; overflow: auto;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-btn);
  padding: var(--s2) var(--s3);
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 12px; line-height: 1.6; white-space: pre; direction: ltr; text-align: left;
}
.css-ai-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* On a phone the send button drops under the prompt rather than squeezing it
   to a couple of words per line — an owner writes a sentence in there. */
@media (max-width: 700px) {
  .css-ai-row { flex-direction: column; align-items: stretch; }
  .css-ai-send { align-self: flex-start; min-height: 42px; }
}

/* What the server stripped, and why. Shown whenever `removed` is non-empty —
   including on a successful proposal, because getting less than you asked for
   without being told is the thing this box exists to prevent. */
.css-ai-removed {
  margin: 0 0 var(--s3); padding: var(--s2) var(--s3);
  border: 1px solid var(--warn, var(--line)); border-radius: var(--r-btn);
  background: var(--surface);
}
.css-ai-removed p { margin: 0 0 4px; font-size: 12px; font-weight: 600; }
.css-ai-removed ul { margin: 0; padding-inline-start: 18px; }
.css-ai-removed li { font-size: 12px; line-height: 1.7; color: var(--ink); }

/* Slug --------------------------------------------------------------------- */
.slug-line { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s3); }
.slug-url {
  font-size: 12px; background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--r-btn); padding: 4px 8px; word-break: break-all;
}

/* Live preview ------------------------------------------------------------- */
/* The mock reads the SAME custom-property names the real menu page does
   (--bg, --brand, --size-item …), set as an inline style on .pv-phone. Two
   reasons, and the second is the important one:
     * it is one vocabulary instead of a parallel --pv-* set to keep in sync;
     * CSS the owner writes — or asks the assistant for — is injected scoped to
       .pv-page, INSIDE .pv-phone. A rule like `.item-name{font-size:2rem}` or
       `:root{--brand:#0a0}` therefore lands on a real token and the preview
       actually moves. Had the tokens stayed on .pv-page itself, an inline
       style would outrank every injected rule and nothing would ever change.
   The ramp values are MOCK-scaled, not the page's real 18-23px: this is a
   280px-wide phone drawing, so the tokens keep their relative proportions
   rather than their absolute sizes. */
.pv-phone {
  width: 100%; max-width: 280px; margin: 0 auto var(--s3); border: 8px solid #23201C;
  border-radius: 26px; overflow: hidden; background: #23201C; box-shadow: var(--overlay-shadow);
}
.pv-page {
  background: var(--bg); color: var(--text); height: 380px; overflow-y: auto;
  font-size: 12px; line-height: 1.6; padding-bottom: var(--s3);
}
.pv-cover { height: 78px; overflow: hidden; }
.pv-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* --header-bg is the surface control for the identity block. Its fallback is
   `transparent`, which is what the page does today: the body colour shows
   through and nothing moves until an owner picks something. */
.pv-head { padding: var(--s3); text-align: center; background: var(--header-bg, transparent); }
.pv-head.with-cover { padding-top: var(--s2); }
.pv-logo {
  width: 46px; height: 46px; margin: -30px auto var(--s2); border-radius: 50%; overflow: hidden;
  background: var(--surface); border: 2px solid var(--surface); display: grid; place-items: center;
}
.pv-head:not(.with-cover) .pv-logo { margin-top: 0; }
.pv-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pv-name { margin: 0; font-size: var(--size-section); font-weight: 700; color: var(--brand); letter-spacing: var(--tracking); }
.pv-tag { margin: 2px 0 0; font-size: var(--size-desc); line-height: var(--lh-desc); color: var(--muted); }
.pv-empty { padding: var(--s4); text-align: center; color: var(--muted); font-size: var(--size-desc); }
.pv-section { margin-top: var(--s3); }
.pv-section-name {
  margin: 0 0 4px; padding: 0 var(--s3);
  font-size: var(--size-section); font-weight: 700;
  color: var(--brand); letter-spacing: var(--tracking);
}
.pv-items { padding: 0 var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.pv-item { background: var(--surface); border-radius: 10px; padding: var(--s2); overflow: hidden; }
.pv-item-top { display: flex; justify-content: space-between; gap: var(--s2); align-items: baseline; }
.pv-item-name { font-weight: 600; font-size: var(--size-item); }
/* Same weight as the name, as on the real page. --price-color is the surface
   control; without one the mock keeps the accent it has always used. */
.pv-item-price { color: var(--price-color, var(--accent)); font-weight: 600; font-size: var(--size-price); white-space: nowrap; }

/* The section nav, mocked. Same token names the real .secchip reads, so a
   colour picked here is a colour a diner sees. */
.pv-chips { display: flex; gap: 4px; padding: 0 var(--s3) var(--s2); overflow: hidden; }
.pv-chip {
  flex: none; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--chip-bg, var(--surface));
  color: var(--chip-text, var(--muted));
  font-size: 10px; font-weight: 500; white-space: nowrap;
  max-width: 84px; overflow: hidden; text-overflow: ellipsis;
}
.pv-chip.on {
  background: var(--btn-bg, var(--brand));
  border-color: var(--btn-bg, var(--brand));
  color: var(--btn-text, var(--surface));
  font-weight: 600;
}

/* Opening hours in the mock. WHEN the door is open — it says nothing about
   breakfast, lunch or dinner, and neither does the real page. */
.pv-hours {
  display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
  margin: 0 var(--s3) var(--s2); padding: 6px 9px;
  border: 1px solid var(--line); border-radius: 8px;
  font-size: 10px; line-height: 1.5;
}
.pv-hours-st { font-weight: 700; }
.pv-hours-body { display: inline-flex; flex-wrap: wrap; gap: 5px; }
.pv-hours .hr-rg { font-variant-numeric: tabular-nums; }
.pv-hours-sp {
  flex-basis: 100%; color: var(--muted); font-size: 9px;
}
.pv-item-desc { margin: 2px 0 0; font-size: var(--size-desc); line-height: var(--lh-desc); color: var(--muted); }
/* A discount, a combo and a deal label, mocked with the SAME class names the
   real page uses (.item-price-old, .item-save, .includes, .tag.badge) so CSS an
   owner writes against the menu lands here too — the reason every other mock
   element carries its alias as well. */
.pv-price-wrap { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: none; }
.pv-item-old {
  font-size: var(--size-desc); font-weight: 500; color: var(--muted);
  white-space: nowrap;
}
.pv-item-save {
  font-size: 9px; font-weight: 700; line-height: 1.4;
  padding: 1px 6px; border-radius: var(--r-pill);
  border: 1px solid var(--accent); color: var(--accent); white-space: nowrap;
}
.pv-includes {
  list-style: none; margin: 3px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 1px;
}
.pv-includes li {
  position: relative; padding-inline-start: 9px;
  font-size: var(--size-desc); line-height: var(--lh-desc); color: var(--text);
}
.pv-includes li::before {
  content: ""; position: absolute; inset-inline-start: 1px; top: .62em;
  width: 3px; height: 3px; border-radius: 50%; background: var(--accent);
}
.pv-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.pv-tags .tag {
  font-size: 9px; font-weight: 700; line-height: 1.5;
  padding: 1px 7px; border-radius: var(--r-pill);
  border: 1px solid var(--accent); color: var(--accent);
}
/* An owner-DECLARED badge, mocked as the page draws it: the loud four wear the
   filled pill, the other ten read quietly. Same `.tag`/`.tag.badge` split the
   public page uses, so CSS written against the menu lands here too. */
.pv-tags .tag-key { display: inline-flex; align-items: center; gap: 3px; }
.pv-tags .tag-key .ic { width: 10px; height: 10px; stroke-width: 1.8; }
.pv-tags .tag:not(.badge) { border-color: var(--line); color: var(--muted); font-weight: 500; }
.pv-tags .tag.badge { background: var(--accent-weak); }
.pv-photo { margin: calc(var(--s2) * -1) calc(var(--s2) * -1) var(--s2); height: 74px; overflow: hidden; }
.pv-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The theme's own layout switch, mirrored so the picker means something. */
.pv-items.pv-list { gap: 0; }
.pv-items.pv-list .pv-item { background: none; border-bottom: 1px dashed var(--line); border-radius: 0; padding: var(--s2) 0; }
.pv-items.pv-list .pv-photo { display: none; }
.pv-items.pv-grid { display: grid; grid-template-columns: 1fr 1fr; }
.pv-items.pv-photo .pv-photo { height: 96px; }
.pv-more { margin: var(--s2) 0 0; padding: 0 var(--s3); font-size: var(--size-desc); color: var(--muted); text-align: center; }

/* Calories, the walk and the allergen marks in the mock, carrying the same
   class aliases as everything else here (.item-facts, .item-kcal, .item-walk,
   .allergens, .alg) so owner CSS aimed at the real page lands on the preview.
   Quiet by construction: this is information sitting under a dish, not a
   headline, and nothing in it is coloured as good or bad news. */
.pv-facts {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 3px var(--s2); margin-top: 5px;
}
.pv-kcal { font-size: var(--size-desc); font-weight: 600; color: var(--muted); }
/* The assumed body travels INSIDE this string (owner.js t("calWalk")), so the
   figure can never be shown without it. Never sorted on, never highlighted. */
.pv-walk { font-size: var(--size-desc); color: var(--muted); }
.pv-alg { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; flex-basis: 100%; }
.pv-alg-lbl { font-size: 9px; font-weight: 700; color: var(--muted); }
.pv-alg-chip {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 9px; font-weight: 600; line-height: 1.5;
  padding: 1px 6px; border-radius: var(--r-pill);
  border: 1px solid var(--line); color: var(--muted);
}
.pv-alg-chip .ic { width: 11px; height: 11px; stroke-width: 1.6; }
.pv-alg-note {
  margin: 0 var(--s3) var(--s2); padding: var(--s2);
  font-size: 9px; line-height: 1.6; color: var(--muted);
  border-inline-start: 2px solid var(--line);
}

/* QR card ------------------------------------------------------------------ */
.card-sizes { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s4); }
.size-btn.on { background: var(--accent-weak); border-color: var(--accent); color: var(--accent-press); }
.qr-card-wrap { overflow-x: auto; padding: var(--s2) 0 var(--s4); }
/* THE CARD LOOKS LIKE THE SHOP, NOT LIKE US.
   Every colour and the typeface come from --qr-* / --font, which owner.js
   injects into a <style> INSIDE the card (the theme's variables, then the
   owner's font, then their own CSS last). The literals here are fallbacks and
   nothing more: if that block never arrives — a themes outage, a card rendered
   before the payload lands — this is the plain white card it has always been,
   never an unstyled one. */
.qr-card {
  background: var(--qr-bg, #FFFFFF);
  color: var(--qr-text, #14110D);
  border: 1px solid var(--qr-line, #E3DCD0);
  border-radius: 12px;
  margin: 0 auto; padding: 10mm 8mm; text-align: center; box-sizing: border-box;
  font-family: var(--font, "Readex Pro", "Tajawal", system-ui, sans-serif);
  print-color-adjust: exact; -webkit-print-color-adjust: exact;
}
/* The style element that carries all of the above must never take up space. */
.qr-card > style { display: none; }
.qr-card.size-tent { width: 90mm; }
.qr-card.size-half { width: 140mm; }
/* Screen only — the print rules below keep the real millimetres. */
@media screen { .qr-card { max-width: 100%; } }
.qr-shop {
  margin: 0; font-size: 18px; font-weight: 700; line-height: 1.4;
  font-family: var(--heading, inherit);
}
.qr-card.size-half .qr-shop { font-size: 24px; }
.qr-shop-en { margin: 2px 0 0; font-size: 12px; font-weight: 600; color: var(--qr-muted, #6B6459); letter-spacing: .02em; }
/* Where the accent colour lands: a short brand rule under the name. Small on
   purpose — an accent that owns half the card fights the code for attention. */
.qr-rule {
  display: block; width: 34px; height: 2px; margin: 3mm auto 0;
  background: var(--qr-accent, #E3DCD0); border-radius: 2px;
}
.qr-box { position: relative; width: 62%; margin: 6mm auto; aspect-ratio: 1 / 1; }
.qr-svg { width: 100%; height: 100%; display: block; }
.qr-svg svg { width: 100%; height: 100%; display: block; }
.qr-svg.qr-fail { display: grid; place-items: center; font-size: 12px; color: var(--danger); aspect-ratio: 1 / 1; }
/* Error correction H tolerates ~30% loss; the patch stays well under that. The
   pad follows the CARD's background, so the logo reads as a hole punched in the
   code rather than a white sticker taped onto a dark card. */
.qr-logo {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 22%; height: 22%; background: var(--qr-bg, #FFFFFF); border-radius: 6px; padding: 3px;
  display: grid; place-items: center; box-sizing: border-box;
}
.qr-logo img { width: 100%; height: 100%; object-fit: contain; display: block; }
.qr-inst { margin: 0; font-size: 14px; font-weight: 600; font-family: var(--heading, inherit); }
.qr-card.size-half .qr-inst { font-size: 18px; }
.qr-inst-en { margin: 1px 0 0; font-size: 11px; font-weight: 500; color: var(--qr-muted, #6B6459); }
.qr-url { margin: 4mm 0 0; font-size: 11px; color: var(--qr-text, #14110D); word-break: break-all; letter-spacing: .01em; }
.qr-credit {
  margin: 3mm 0 0; font-size: 9px; color: var(--qr-muted, #9A9188); display: flex;
  justify-content: center; gap: 6px; letter-spacing: .02em; opacity: .8;
}

/* Card styling controls ---------------------------------------------------- */
.qr-theme-row { display: inline-flex; margin-bottom: var(--s2); }
/* While the theme is being followed the swatches still SHOW the colours in use
   — they are just not the owner's to set until the switch comes off. */
.pg-colors.is-theme .pg-color { opacity: .62; }
.pg-colors.is-theme input[type="color"] { cursor: default; }
.qr-contrast { margin-top: var(--s3); }
.qr-contrast .qr-ok { margin: 0; }
/* THE CONTRAST WARNING. A QR is read by contrast and nothing else, so this is
   louder than the rest of the console deliberately — and it never disables the
   print button. A beautiful unscannable code is worse than an ugly one, but the
   card is still theirs. */
.qr-warn {
  border: 1px solid var(--danger); border-inline-start: 3px solid var(--danger);
  background: var(--danger-weak); border-radius: var(--r-card);
  padding: var(--s3) var(--s4);
}
.qr-warn-h { margin: 0 0 var(--s1); font-size: 14px; font-weight: 700; color: var(--danger); }
.qr-warn-b { margin: 0; font-size: 13px; line-height: 1.7; color: var(--ink); }

/* Print -------------------------------------------------------------------- */
/* The card is CLONED into #printRoot (a body child) just before window.print(),
   so hiding every other body child leaves the sheet carrying the card alone. */
#printRoot { display: none; }
@media print {
  @page { margin: 12mm; }
  body { background: #FFFFFF; }
  body > * { display: none !important; }
  body > #printRoot { display: block !important; }
  #printRoot .qr-card {
    border: 1px dashed #C9C2B6;   /* a cut line, not decoration */
    box-shadow: none; margin: 0 auto; break-inside: avoid; page-break-inside: avoid;
  }
}

/* ------------------------------------------- meal serving windows (hours tab)
   Same field shape as the opening-hours ranges, so the two read as one form.
   The state word carries the meaning of an EMPTY pair — «طول اليوم» — because
   a blank box otherwise looks like a question the owner forgot to answer, when
   it is in fact the answer most shops want. */
.mw-list { display: flex; flex-direction: column; gap: var(--s2); }
.mw-row {
  display: flex; align-items: flex-end; gap: var(--s2); flex-wrap: wrap;
  padding-bottom: var(--s2); border-bottom: 1px solid var(--line);
}
.mw-row:last-child { border-bottom: 0; padding-bottom: 0; }
.mw-name { font-size: 13px; font-weight: 600; flex: 1 1 84px; min-width: 0; }
/* min-width:0 on the label AND the input: a type="time" box keeps a wide
   intrinsic size for its clock affordance, and without this the two fields
   never share a line on the phone where this is actually filled in. */
.mw-t {
  display: flex; flex-direction: column; gap: 3px;
  flex: 1 1 96px; min-width: 0;
  font-size: 11px; color: var(--muted);
}
.mw-t input {
  width: 100%; min-width: 0;
  font-size: 13px; padding: 7px 8px; border: 1px solid var(--line);
  border-radius: var(--r-btn); background: var(--surface); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.mw-state { font-size: 11px; color: var(--muted); align-self: center; }

/* SUPPORT MODE — a super-admin standing in for an owner who asked for help.
   Deliberately unmissable and deliberately not our brand purple: this is a
   STATE the person is in, not a feature of the product, and it must never feel
   like editing your own shop. */
.notice-bar {
  position: sticky; top: 0; z-index: 60;
  background: #8A4B00; color: #FFF;
  padding: 10px var(--s4, 16px);
  font-size: .9rem; font-weight: 600; text-align: center;
}


/* ------------------------------------------------- the translations tab */
/* One row per bilingual pair: what it is, the Arabic half, the English half.
   The label column keeps its width so forty rows read as a TABLE the eye can
   run down, not as forty separate little forms. */
.trw { display: flex; flex-direction: column; gap: var(--s2); }
.trw-grp-h {
  margin: var(--s3) 0 var(--s1);
  font-weight: 700;
  font-size: 14px;
}
.trw-grp { display: flex; flex-direction: column; gap: var(--s1); }
.trw-row {
  display: grid;
  grid-template-columns: minmax(96px, 150px) 1fr 1fr;
  gap: var(--s1);
  align-items: center;
}
.trw-lbl {
  font-size: 12.5px;
  color: var(--muted, #7a6a5c);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trw-row input { min-width: 0; }
/* A dish is one bordered block of its pairs — the name, the description, the
   deal line and the variants belong to ONE thing and read as one thing. */
.trw-dish {
  border: 1px solid var(--line, #e8ddce);
  border-radius: 10px;
  padding: var(--s2);
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}
@media (max-width: 640px) {
  /* On a phone the two halves stack under the label — side-by-side inputs
     at 160px each are two unusable slivers. */
  .trw-row { grid-template-columns: 1fr; }
  .trw-lbl { white-space: normal; }
}
