/* A Recomend — the three loyalty surfaces: the owner's panel (/loyalty), the
   till (/till) and the customer's cards (/cards).

   THE LOOK IS NOT DEFINED HERE. It comes from auth.css, the same sheet the
   owner and admin consoles are built on — the tokens, the .topbar/.wrap shell,
   .section, .field, .stats/.stat, table.data, .chip/.badge, .empty/.loading and
   the button classes. This file only adds shapes that sheet has no name for: a
   repeating editable row, a till-sized tap target, the stamp dots, the QR
   block, the balance headline.

   So there is no :root here, no colour literal, and no radius or spacing that
   is not a token — a change to auth.css has to move these pages too, which was
   the whole point of dropping the standalone stylesheet this replaces. Any rule
   below that can be answered by something auth.css already carries should be
   deleted rather than kept.

   owner.css carries near-identical row shapes (.menu-row, .mw-row) and is
   deliberately NOT loaded instead: it ends in a print block that hides every
   body child except #printRoot, so a page that never builds one prints blank —
   and a member list is a thing a shop prints. */

/* Cleared messages ---------------------------------------------------------- */
/* Every message on these three pages is written with textContent and cleared
   with "" — so an emptied box has to leave the flow, or a dismissed error
   leaves a coloured band behind and a saved form keeps a stale "تم الحفظ" gap. */
.error-box:empty, .ok-box:empty, .section-sub:empty { display: none; }
/* The success twin of auth.css's .error-box, which has no counterpart there. */
.ok-box {
  color: var(--accent-press); font-size: 13px; background: var(--accent-weak);
  border-radius: var(--r-btn); padding: var(--s3); margin: var(--s3) 0;
  word-break: break-word;
}

/* Repeating editable rows ---------------------------------------------------- */
/* auth.css has .field and .grid-2 for a form whose fields are known in advance.
   It has no shape for a list the owner ADDS rows to — the rewards, the tier
   ladder, the double-point days. Same construction the console uses for its own
   row lists: a hairline card, fields that wrap before they squeeze, the delete
   at the end of the line. .lp-row on its own is just the wrapping line, so the
   same class also lays out the fixed rows inside a section. */
.lp-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s2) var(--s3); }
.lp-rows { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s3); }
.lp-rows .lp-row { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3); }
.lp-row .field { flex: 1 1 150px; min-width: 0; margin-bottom: 0; }
/* Buttons and messages sitting on a field row align with the inputs, never
   stretch, and carry no margin of their own — the row owns the spacing. */
.lp-row > button, .lp-row > .ok-box { flex: 0 0 auto; margin: 0; }
/* The vertical rhythm inside a .section is carried by .field's bottom margin,
   which the row above deliberately strips — so the row itself has to replace
   it, or two stacked rows of fields touch. */
.section > .lp-row { margin-bottom: var(--s4); }
.section > .lp-row:last-child { margin-bottom: 0; }

/* In the console every <h2> is followed by a .section-sub, and that line is
   what holds the content off the heading — .section > h2 leaves only 4px. The
   sections here that have nothing to explain need the same air anyway. */
.section > h2 + *:not(.section-sub) { margin-top: var(--s3); }

/* A consent checkbox is one line WITH its sentence; .field would stack a label
   above a full-width control, which is wrong for a box you tick. */
.lp-check {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 14px; margin-bottom: var(--s3); cursor: pointer;
}
.lp-check input {
  width: 18px; height: 18px; flex: none; accent-color: var(--accent); margin: 0; padding: 0;
}

/* The one line of page copy on /cards, whose body is entirely JS-rendered
   sections — there is no .section for it to be a .section-sub of. */
.lp-lead { margin: 0 0 var(--s4); font-size: 13px; color: var(--muted); }

/* The row of actions under a customer's card: the QR, the menu link. */
.lp-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s3); }

/* The customer's card ---------------------------------------------------- */
/* EVERY CARD WEARS ITS OWN SHOP'S CLOTHES. cards.js writes that shop's menu
   theme onto this element's own style attribute — --surface, --text, --brand,
   --line, --font, --radius, plus the four auth.css tokens the theme has no
   name for — so the whole card and everything auth.css draws inside it (the
   .section shell, .chip, .badge, .ghost, .field) resolves in the shop's
   palette. Nothing here names a colour: this file only says WHICH properties
   follow the theme, never what they are.

   Scoped by construction, not by selector — a page shows cards from several
   businesses at once and one shop's palette must never reach the next. */
/* `color` is set here and not left to the --ink remap alone: <body> resolved
   var(--ink) once, and everything inside inherits that COMPUTED colour, not
   the variable — so a shop on a dark theme got its own near-black surface
   under the console's near-black ink and its name disappeared. Setting color
   on the card re-roots the inheritance at the shop's own text colour, and the
   remap then covers the few rules that name --ink explicitly. */
.lp-card { font-family: var(--font, inherit); color: var(--text, inherit); }
.lp-card > h2 { font-family: var(--heading, inherit); }
/* A shop's logo is any shape and any aspect; `contain` keeps it whole and the
   fixed box keeps twenty cards on one page in a straight line. */
.lp-logo {
  width: 34px; height: 34px; flex: 0 0 auto;
  border-radius: var(--r-btn); object-fit: contain;
}

/* THE CARD HEAD, DRESSED IN THE PROGRAMME'S OWN DESIGN ------------------------ */
/* The founder's «a banner image across the card head, custom logo, font, font
   colour». cards.js writes the shop-chosen banner/colours/font onto the <h2>'s
   OWN style attribute — the head is the h2, and the balance and the stamp row
   are its siblings below, so a banner is physically incapable of covering the
   numbers the customer opened the card to read.
   Applied to the h2 DIRECTLY, never a wrapper: an undesigned card keeps the exact
   .section > h2 it always had, and only a card with a design grows a box. */
.lp-logo.lp-logo-bg { padding: 3px; }
/* A colour or a banner needs a box to sit in and clip to. Two classes on the
   element clear .section > h2 without reaching for !important. */
.lp-card > h2.lp-boxed {
  padding: var(--s3);
  border-radius: var(--r-card);
  background-size: cover; background-position: center; background-repeat: no-repeat;
  overflow: hidden;
}
/* A photo head is taller and sets its text at the foot — where a banner's
   subject is least likely to be — with a whisper of shadow so light text lifts
   off a busy image, faint enough to leave dark text untouched. */
.lp-card > h2.lp-head-photo {
  min-height: 84px; align-items: flex-end;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .28);
}

/* THE CARD-DESIGN PANEL (the owner's console only) --------------------------- */
/* The live preview is the customer card in miniature, built from the SAME
   .lp-card / .lp-boxed / .lp-logo classes above — so the panel can never promise
   a look /cards will not deliver, the same contract the stamp preview keeps. */
.lp-prevwrap { margin-bottom: var(--s4); }
.lp-prevcard {
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s3); max-width: 360px;
}
/* The preview card sits outside any .section, so it inherits no .section > h2 —
   restate the head shape so the miniature matches the real card exactly. */
.lp-prevcard h2 {
  margin: 0; font-size: 20px; font-weight: 600;
  display: flex; align-items: center; gap: var(--s2);
}

/* A banner/logo upload: a thumbnail of what is set, the picker, the remove. */
.lp-cd-thumbs { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); }
.lp-cd-thumb {
  width: 56px; height: 40px; flex: 0 0 auto;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--bg) center / cover no-repeat;
}
/* A colour swatch, and the checkbox that decides whether it is sent at all — a
   native colour input can never be empty, and empty is how a field says
   "inherit the shop's theme". */
.lp-cd-color { max-width: 190px; }
.lp-cd-color .lp-check { margin-bottom: var(--s1); }
.lp-cd-color .cd-col {
  width: 100%; height: 38px; padding: 2px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-btn); background: var(--surface);
}
.lp-cd-color .cd-col:disabled { opacity: .45; cursor: not-allowed; }
/* Each tier's own card look, folded under its row on a hairline. */
.lp-cd-tier {
  flex: 1 1 100%; margin-top: var(--s2); padding-top: var(--s3);
  border-top: 1px dashed var(--line);
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s2) var(--s3);
}
.lp-cd-tier-lbl {
  flex: 1 1 100%; font-size: 12px; font-weight: 600; color: var(--muted);
}

/* THE WEEKLY MESSAGE (the owner's console only) ------------------------------ */
/* A counter that only matters when it is wrong: muted while there is room, the
   danger colour the moment the draft is longer than the server will accept —
   the same instant the send button goes flat. */
.lp-bc-count { margin: var(--s1) 0 0; font-size: 12px; color: var(--muted); }
.lp-bc-count.over { color: var(--danger); font-weight: 600; }
/* auth.css gives .chip a role tint and a muted one and stops there. A refusal
   needs the third: «ما تمت الموافقة» in the accent colour reads like approval. */
.chip.bad { background: var(--danger-weak); color: var(--danger); }
.lp-bc-list { display: flex; flex-direction: column; gap: var(--s2); }
.lp-bc-item {
  border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s3);
}
.lp-bc-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); }
/* The owner's text as they typed it, line breaks and all — a message shown back
   reflowed into one paragraph is not the message they approved. */
.lp-bc-body {
  margin: var(--s2) 0 0; font-size: 14px;
  white-space: pre-wrap; word-break: break-word;
}
.lp-bc-meta { margin: var(--s1) 0 0; font-size: 12px; color: var(--muted); }

/* A GIFT FROM THE MENU (the owner's console only) ---------------------------- */
/* The count and the select-all sit on ONE line directly above the list, because
   the list scrolls and the send button is below it — an owner must never have
   to scroll to find out how many people they are about to pay for. */
.lp-gf-bar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 var(--s2);
}
.lp-gf-all {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 13px; cursor: pointer;
}
.lp-gf-count { font-size: 12px; color: var(--muted); }
/* Over the server's cap — the same danger tint the message counter uses, so
   "you have gone too far" looks the same everywhere on this page. */
.lp-gf-count.over { color: var(--danger); font-weight: 600; }
/* A BOUNDED, SCROLLING LIST. A shop with hundreds of members would otherwise
   push the send button off the bottom of the page; capping the height keeps the
   picker and its action visible together. */
.lp-gf-list {
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--r-card);
}
.lp-gf-row {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.lp-gf-row:last-child { border-bottom: 0; }
.lp-gf-row:hover { background: var(--surface-2, rgba(0, 0, 0, 0.03)); }
/* The name takes the room; the mobile and the balance are the disambiguators
   and sit at the end, quiet. */
.lp-gf-who { flex: 1 1 auto; font-size: 14px; word-break: break-word; }
.lp-gf-meta { font-size: 12px; color: var(--muted); white-space: nowrap; }

/* The till --------------------------------------------------------------- */
/* USED ONE-HANDED, AT SPEED, ON A PHONE BEHIND A COUNTER. The console's 40px
   controls are a mis-tap here, so the till's own actions are full-width and
   56px tall: the affordance has to be unmissable rather than tasteful. */
.tap { width: 100%; min-height: 56px; font-size: 17px; }
/* 16px is the iOS threshold — below it Safari zooms the page on focus, and a
   till that jumps mid-sale costs more than a large font does. 48px is the
   smallest target a thumb hits reliably while holding the phone. */
#till input, #till select { min-height: 48px; font-size: 16px; }
/* till.js flips style.display on the video, so the closed state lives here
   rather than in an inline style it would have to fight. */
.till-cam { display: none; width: 100%; border-radius: var(--r-card); margin-top: var(--s3); }

/* The balance ------------------------------------------------------------ */
/* Read across a counter on the till, and the entire point of the page on
   /cards. .stat-num (28px) is a dashboard figure; this is a headline. Tabular
   numerals so a balance that ticks up does not shuffle its own digits. */
.balance { display: flex; align-items: baseline; gap: var(--s2); margin: var(--s3) 0; }
.balance-num {
  font-size: 44px; font-weight: 700; line-height: 1.1; margin: 0;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.balance-unit { font-size: 15px; color: var(--muted); }

/* Stamps ----------------------------------------------------------------- */
/* A stamp card is READ AT A GLANCE — that is its whole appeal — so the balance
   is drawn and not only counted. Nothing in the system draws a row of tokens.

   FILLED VERSUS FADED IS THE WHOLE MESSAGE, so it is carried by three things
   at once and not by colour alone: opacity, ring weight, and a tinted disc.
   A card read in daylight, or by somebody who cannot separate the two hues,
   still counts the same number of stamps. */
.stamps { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0; }
.stamp {
  width: 34px; height: 34px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); color: var(--muted); opacity: .4;
}
.stamp .ic { width: 20px; height: 20px; }
/* --brand is the SHOP's, set per card by cards.js; the console's accent is
   the fallback for the till and the owner's preview, which wear ours. */
.stamp.on {
  opacity: 1; color: var(--brand, var(--accent));
  border: 2px solid currentColor;
  /* Two declarations: a browser without color-mix() keeps the plain ring
     rather than losing the token's fill to an invalid value. */
  background: transparent;
  background: color-mix(in srgb, currentColor 14%, transparent);
}

/* The stamp picker ------------------------------------------------------- */
/* The owner is choosing a DRAWING, so the grid shows drawings — a <select> of
   twenty Arabic nouns would make them save and reload to see what they picked.
   auto-fill rather than a fixed count: this is opened on a phone behind a
   counter as often as on a laptop. */
.lp-stamp { margin-top: var(--s4); }
.icon-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: var(--s2); margin-bottom: var(--s4);
}
.icon-pick {
  display: flex; flex-direction: column; align-items: center; gap: var(--s1);
  min-height: 74px; padding: var(--s3) var(--s1);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); color: var(--muted);
  font-size: 11px; font-weight: 600; line-height: 1.3; text-align: center;
}
.icon-pick .ic { width: 26px; height: 26px; color: var(--ink); }
/* THE SHOP'S OWN LOGO AS A PICKER TILE. This img had NO rule: the tile only
   ever showed the camera placeholder until the stamp logo learned to fall
   back to the shop's menu logo — the first real image then rendered at its
   NATURAL size, a 989px wordmark sprawled across the whole grid. An image in
   a 26px tile is 26px, whatever file it came from. */
.icon-pick { overflow: hidden; }
.icon-pick-logo {
  width: 26px; height: 26px;
  object-fit: contain;
  display: block;
  flex: 0 0 auto;
}
/* The label is one line always: twenty tiles of different heights read as a
   broken grid rather than a set to choose from. */
.icon-pick span {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.icon-pick:hover:not(.on) { border-color: var(--accent); }
.icon-pick.on {
  border-color: var(--accent); background: var(--accent-weak); color: var(--accent-press);
}
.icon-pick.on .ic { color: var(--accent-press); }

/* The check-in QR -------------------------------------------------------- */
/* Minted on demand and only then, so the block is empty until it is asked for
   and must take up no room in the meantime. The SVG comes from our own server
   (segno) with no intrinsic size; it is held up to a scanner on whatever phone
   the customer happens to have, so it is capped in vw as well as px. */
.qr { display: none; margin-top: var(--s3); text-align: center; }
.qr:not(:empty) { display: block; }
.qr svg { width: min(240px, 60vw); height: auto; }

/* THE SHOP'S LOGO AS THE STAMP ------------------------------------------- */
/* An EMPTY slot in the logo's shape until it is earned, then the logo itself.
   The unearned state is the same drawing at a whisper — never a grey box and
   never a different shape — so the card reads as "three of these six" the way
   a paper punch card lets it. The plate keeps its ring at both states, which is
   what makes an unstamped slot look like somewhere a stamp GOES. */
.stamp-logo { overflow: hidden; padding: 3px; }
.stamp-logo img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  /* Unearned: the logo held back to a ghost of itself, desaturated so a
     colourful mark does not read as already collected. */
  opacity: .22; filter: grayscale(1);
  transition: opacity .18s ease, filter .18s ease;
}
.stamp-logo.on img { opacity: 1; filter: none; }

/* ============================== THE REAL CARD, LIVE ====================== */
/* The card-design preview — the apps' card anatomy, drawn at phone-card width
   so what the owner approves is what a phone will show. Colours arrive as
   custom properties set inline by previewCardHtml; everything here reads them
   so the CSS never guesses. */
.cdp {
  max-width: 380px;
  border-radius: 18px;
  overflow: hidden;
  background: var(--cdp-bg, #5B2C91);
  color: var(--cdp-ink, #fff);
  box-shadow: 0 10px 28px rgb(0 0 0 / 0.22);
  margin-top: var(--s3);
}
.cdp-banner {
  position: relative;
  /* The iOS card's band (132pt on a ~360pt card). The preview wore 2.1 and
     the founder saw two different cards for one design. */
  aspect-ratio: 2.6;
  background-size: cover;
  background-position: center;
}
/* The identity reads against a colour WE chose, not against whatever
   photograph the owner uploaded — nothing at the top, the card's own colour
   at the foot. Same scrim the apps draw. */
.cdp-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--cdp-bg) 72%, transparent) 55%,
    color-mix(in srgb, var(--cdp-bg) 94%, transparent) 100%);
}
.cdp-banner .cdp-id { position: absolute; inset: auto 0 0 0; }
.cdp-id {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
}
.cdp-logo {
  width: 44px; height: 44px; flex: 0 0 auto;
  border-radius: 12px; display: grid; place-items: center;
  overflow: hidden;
}
.cdp-logo img { max-width: 82%; max-height: 82%; object-fit: contain; }
.cdp-name { flex: 1 1 auto; font-weight: 700; font-size: 15px; min-width: 0; }
.cdp-count { text-align: center; flex: 0 0 auto; }
.cdp-count-l {
  display: block; font-size: 9px; font-weight: 700; letter-spacing: 1px;
  opacity: 0.72;
}
.cdp-count-n { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cdp-body { padding: 12px 14px 14px; }
.cdp-marks {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  margin-bottom: 10px;
}
.cdp-mark {
  width: 34px; height: 34px; display: grid; place-items: center;
  color: var(--cdp-ink);
  opacity: 0.26;
}
.cdp-mark .ic { width: 26px; height: 26px; }
.cdp-mark.on { opacity: 1; }
/* The logo slot: FULL-STRENGTH plate in both states (a dimmed plate on a dark
   card is the invisible-slot bug back again) — only the ART ghosts. */
.cdp-mark-logo { border-radius: 8px; overflow: hidden; opacity: 1; }
.cdp-mark-logo img {
  max-width: 76%; max-height: 76%; object-fit: contain;
  opacity: 0.32; filter: grayscale(1);
}
.cdp-mark-logo.on img { opacity: 1; filter: none; }
.cdp-stats {
  display: flex; justify-content: space-between; gap: var(--s3);
  font-size: 11px; opacity: 0.9;
}
.cdp-stats b { font-size: 18px; display: block; }


/* THE LOGO'S OWN SHAPE AS THE STAMP — no boxes. The mark is the logo's alpha
   painted in the card's ink: hollow-faint when empty, filled when earned.
   Only a ONE-COLOUR mark is stencilled; a colourful crest keeps its plate
   (flattening somebody's colours is brand damage, not a style). */
.cdp-silo {
  position: relative;
  opacity: 1;
}
.cdp-silo::after {
  content: ""; position: absolute; inset: 1px;
  background: var(--cdp-ink);
  -webkit-mask: var(--silo-url) center / contain no-repeat;
  mask: var(--silo-url) center / contain no-repeat;
  opacity: 0.3;
}
.cdp-silo.on::after { opacity: 1; }

/* The same rule on the customer web wallet (see silhouetteStamps in cards.js). */
.stamp { position: relative; }
.stamp-logo.silo { background: none; border-color: transparent; }
.stamp-logo.silo img { visibility: hidden; }
.stamp-logo.silo::after {
  content: ""; position: absolute; inset: 3px;
  background: currentColor;
  -webkit-mask: var(--silo-url) center / contain no-repeat;
  mask: var(--silo-url) center / contain no-repeat;
  opacity: 0.3;
}
.stamp-logo.silo.on::after { opacity: 1; }
