/* ════════════════════════════════════════════════════════════════════
   MaalCards storefront — Tajawal RTL design system
   Ported from the MaalCardsStoreTajawal design doc (direction A theme,
   light + dark via prefers-color-scheme).
   ════════════════════════════════════════════════════════════════════ */

/* ---- Tajawal (self-hosted, CSP: font-src 'self') ------------------ */
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 300; font-display: swap;
  src: url("../fonts/tajawal-300-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFC, U+200C-200E, U+2010-2011;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 300; font-display: swap;
  src: url("../fonts/tajawal-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/tajawal-400-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFC, U+200C-200E, U+2010-2011;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/tajawal-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../fonts/tajawal-500-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFC, U+200C-200E, U+2010-2011;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../fonts/tajawal-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 700; font-display: swap;
  src: url("../fonts/tajawal-700-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFC, U+200C-200E, U+2010-2011;
}
@font-face {
  font-family: 'Tajawal'; font-style: normal; font-weight: 700; font-display: swap;
  src: url("../fonts/tajawal-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}

/* ---- tokens ------------------------------------------------------- */
:root {
  --canvas: #E6E8E3; --bg: #F3F4F0; --surface: #FFFFFF; --surface2: #F7F8F5;
  --ink: #111A15; --muted: #6A776F; --line: #E0E4DE;
  --brand: #0E9F5B; --brand-soft: #E4F5EB; --on-brand: #FFFFFF;
  --hot: #E0552B; --hot-soft: #FBE9E2;
  /* Shapes are rounded rectangles throughout — no pills, no circles.
     The lone exception is .spinner, which has to be round to rotate. */
  --radius-sm: 8px; --radius: 14px; --radius-lg: 20px;
  --shadow: 0 1px 2px rgba(16,26,21,.06), 0 10px 28px -16px rgba(16,26,21,.35);
  --shadow-lift: 0 2px 4px rgba(16,26,21,.07), 0 18px 40px -20px rgba(16,26,21,.45);
  --font: 'Tajawal', system-ui, sans-serif;
  --mono: 'Tajawal', ui-monospace, monospace;
}
@media (prefers-color-scheme: dark) {
  :root {
    --canvas: #0B100D; --bg: #121814; --surface: #1A211C; --surface2: #212A24;
    --ink: #E8EFE9; --muted: #94A49A; --line: #2A342D;
    --brand: #2FBF77; --brand-soft: rgba(47,191,119,.16); --on-brand: #06180F;
    --hot: #F4753F; --hot-soft: rgba(244,117,63,.16);
    --shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 28px -16px rgba(0,0,0,.9);
    --shadow-lift: 0 2px 4px rgba(0,0,0,.5), 0 18px 40px -20px rgba(0,0,0,1);
  }
}
:root[data-theme="dark"] {
  --canvas: #0B100D; --bg: #121814; --surface: #1A211C; --surface2: #212A24;
  --ink: #E8EFE9; --muted: #94A49A; --line: #2A342D;
  --brand: #2FBF77; --brand-soft: rgba(47,191,119,.16); --on-brand: #06180F;
  --hot: #F4753F; --hot-soft: rgba(244,117,63,.16);
  --shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 28px -16px rgba(0,0,0,.9);
  --shadow-lift: 0 2px 4px rgba(0,0,0,.5), 0 18px 40px -20px rgba(0,0,0,1);
}
:root[data-theme="light"] {
  --canvas: #E6E8E3; --bg: #F3F4F0; --surface: #FFFFFF; --surface2: #F7F8F5;
  --ink: #111A15; --muted: #6A776F; --line: #E0E4DE;
  --brand: #0E9F5B; --brand-soft: #E4F5EB; --on-brand: #FFFFFF;
  --hot: #E0552B; --hot-soft: #FBE9E2;
  --shadow: 0 1px 2px rgba(16,26,21,.06), 0 10px 28px -16px rgba(16,26,21,.35);
  --shadow-lift: 0 2px 4px rgba(16,26,21,.07), 0 18px 40px -20px rgba(16,26,21,.45);
}

/* ---- base --------------------------------------------------------- */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* Nothing may stick out sideways.
   A captive-portal mini-browser is an old WebView, and there a document
   that scrolls horizontally takes `position: fixed` with it — measured
   against the document instead of the viewport. In RTL the document
   starts scrolled to its right edge, so a page a hundred pixels too wide
   drags the whole checkout sheet a hundred pixels off the screen, with
   its right side cut off and no way to scroll back to it.

   The overflow that caused it is fixed at its source (see
   .support-banner); this is the guard that stops the next one costing a
   sale. On `html` rather than `body` so the page still scrolls normally,
   and nothing here is `position: sticky`, which is what this would
   otherwise break. */
html { -webkit-text-size-adjust: 100%; overflow-x: hidden; }
body {
  background: var(--bg); color: var(--ink);
  font-family: var(--font); line-height: 1.6;
  min-height: 100vh; min-height: 100dvh;
}
a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--ink); }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, select { font: inherit; color: inherit; }
input:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px;
}
.num { font-family: var(--mono); direction: ltr; unicode-bidi: embed; letter-spacing: .04em; }

/* ---- scrollbars ---------------------------------------------------- */
/* Left alone, the page and every scrolling panel render the platform's
   default bar: a light-grey trough that stays light-grey on the dark
   theme and reads as a seam down the edge of a design that has no other
   hard grey. These map it onto the same tokens as the rest of the
   surface, so it follows light/dark with everything else.

   Both syntaxes are stated on purpose — `scrollbar-width` is ignored by
   WebKit/Blink and `::-webkit-scrollbar` by Firefox, so neither alone
   covers the browsers customers actually arrive on. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 999px;
  /* Transparent border clipped to the padding box: insets the thumb so
     it reads as a slim rail without shrinking the 10px grab target. */
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover  { background: var(--muted); background-clip: padding-box; }
::-webkit-scrollbar-thumb:active { background: var(--brand); background-clip: padding-box; }
/* Where a vertical and a horizontal bar meet; the default is a raised square. */
::-webkit-scrollbar-corner { background: transparent; }

/* Touch devices paint their own overlay bar that fades away on its own.
   A styled rail there is permanently visible furniture stealing width
   from the content, so hand it back to the platform. */
@media (hover: none) and (pointer: coarse) {
  * { scrollbar-width: auto; scrollbar-color: auto; }
  ::-webkit-scrollbar { width: 0; height: 0; }
}

@keyframes mc-rise { from { opacity:0; transform:translateY(10px);} to { opacity:1; transform:translateY(0);} }
@keyframes mc-bump { 0% { transform:scale(1);} 28% { transform:scale(1.055);} 60% { transform:scale(.985);} 100% { transform:scale(1);} }
@keyframes mc-spin { to { transform:rotate(360deg);} }
@keyframes mc-pop  { 0% { transform:scale(.4); opacity:0;} 60% { transform:scale(1.12); opacity:1;} 100% { transform:scale(1); opacity:1;} }

/* ---- tooltips ------------------------------------------------------
   An icon-only control has to say what it does somewhere, and `title`
   says it badly: the operating system draws the box, at the pointer,
   about a second late, in its own colours, ignoring the page entirely.
   This is the same label drawn by the page — on the control, at once,
   in the store's own palette, light and dark alike.

   Carried by data-tip. aria-label stays and is what a screen reader
   reads; the two say the same thing. An element with data-tip must not
   also carry title, or the browser paints its own box over this one.

   Hover and keyboard focus only. There is no hover on a phone, and a
   bubble that appears on tap would cover the thing being tapped — a
   control a touch customer cannot read has to be labelled instead, the
   way استخدم الإنترنت below is. */
[data-tip] { position: relative; --tip-from: 4px; }
[data-tip]::after,
[data-tip]::before {
  position: absolute; z-index: 40;
  opacity: 0; pointer-events: none;
  transition: opacity .14s ease, transform .14s ease;
}
[data-tip]::after {
  content: attr(data-tip);
  bottom: calc(100% + 9px); left: 50%;
  width: max-content;
  transform: translate(-50%, var(--tip-from));
  padding: 6px 9px; border-radius: var(--radius-sm);
  background: var(--ink); color: var(--surface);
  font-size: 11px; font-weight: 600; line-height: 1.3; white-space: nowrap;
  box-shadow: 0 8px 20px rgba(0, 0, 0, .18);
}
/* The tail. A rotated square rather than a border triangle, so it takes
   the bubble's own colour instead of needing a second one kept in step. */
[data-tip]::before {
  content: ''; width: 8px; height: 8px; border-radius: 1px;
  bottom: calc(100% + 5px); left: 50%;
  transform: translate(-50%, var(--tip-from)) rotate(45deg);
  background: var(--ink);
}
@media (hover: hover) {
  [data-tip]:hover::after, [data-tip]:hover::before { opacity: 1; }
  [data-tip]:hover::after  { transform: translate(-50%, 0); }
  [data-tip]:hover::before { transform: translate(-50%, 0) rotate(45deg); }
}
[data-tip]:focus-visible::after, [data-tip]:focus-visible::before { opacity: 1; }
[data-tip]:focus-visible::after  { transform: translate(-50%, 0); }
[data-tip]:focus-visible::before { transform: translate(-50%, 0) rotate(45deg); }

/* Below instead of above — for a control near the top of a card that
   clips its own corners, where the bubble would otherwise be cut in half
   by the card it sits in. */
[data-tip][data-tip-pos="bottom"] { --tip-from: -4px; }
[data-tip][data-tip-pos="bottom"]::after  { bottom: auto; top: calc(100% + 9px); }
[data-tip][data-tip-pos="bottom"]::before { bottom: auto; top: calc(100% + 5px); }

/* A control at the row's inline end has no room on that side, so the
   bubble grows inward from its edge rather than centring on it. The tail
   stays over the control. */
[data-tip][data-tip-align="end"]::after {
  left: auto; right: auto; inset-inline-end: 0;
  transform: translate(0, var(--tip-from));
}
@media (hover: hover) {
  [data-tip][data-tip-align="end"]:hover::after { transform: translate(0, 0); }
}
[data-tip][data-tip-align="end"]:focus-visible::after { transform: translate(0, 0); }

@media (prefers-reduced-motion: reduce) {
  [data-tip]::after, [data-tip]::before { transition: opacity .14s ease; }
}

/* ---- shell -------------------------------------------------------- */
.shell { max-width: 560px; margin: 0 auto; padding: 0 20px 120px; }
/* padding-bottom clears the fixed footer bar (see .mini-footer) so the
   last row of content is never hidden behind it. */
@media (min-width: 900px) { .shell { max-width: 1080px; padding-bottom: 72px; } }

/* top header */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 18px 0 14px;
}
.topbar-id { display: flex; align-items: center; gap: 11px; min-width: 0; }
.store-logo {
  width: 40px; height: 40px; border-radius: 12px; background: var(--brand);
  display: flex; align-items: center; justify-content: center; flex: none;
  color: var(--on-brand); font-weight: 700; font-size: 16px; overflow: hidden;
}
.store-logo img { width: 100%; height: 100%; object-fit: cover; }
.store-title { display: flex; flex-direction: column; min-width: 0; }
.store-title strong {
  font-size: 17px; font-weight: 700; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* City + manager, in place of a generic tagline. Wraps rather than
   truncating: on a narrow phone the second half drops to its own line
   instead of turning into an ellipsis. */
.store-title span { font-size: 11px; color: var(--muted); }
.store-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin-top: 2px; }
.store-meta .sm { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.store-meta .sm svg { width: 11px; height: 11px; flex: none; opacity: .8; color: var(--brand); }
/* The shop's number is a link, and links here are brand-coloured — which
   would pull it out of a line the other two items sit quietly in. It
   reads as part of the line until it is touched. */
.store-meta a.sm { color: inherit; }
.store-meta a.sm:hover { color: var(--brand); }
.topbar-actions { display: flex; gap: 8px; }
.icon-btn {
  width: 38px; height: 38px; border-radius: 12px; background: var(--surface);
  border: 1px solid var(--line); display: flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.icon-btn svg { width: 18px; height: 18px; }

/* ---- support banner ---------------------------------------------- */
/* The number and the way to reach a human, side by side — and onto two
   lines rather than off the screen when there is no room for both.
   تواصل معنا مباشرة does not shrink (white-space: nowrap, and shortening
   it to fit would be shortening the invitation), so on a narrow phone it
   took the page 30px wider than the viewport. In a captive-portal
   browser that is enough to drag the checkout sheet off-screen — see the
   note on `html` above. */
.support-banner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px 14px;
  padding: 16px 18px; background: var(--brand-soft);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.support-banner > div { flex: 1 1 auto; min-width: 0; }
/* Both ways out of the banner, on one line and wrapping together rather
   than one of them being stranded on a line of its own. */
.support-banner .sb-acts {
  display: flex; align-items: stretch; flex-wrap: wrap; gap: 10px;
  flex: 0 1 auto; min-width: 0;
}
/* Once the row can no longer hold the number and both buttons, the
   buttons take the width they have and share it equally. Two controls
   that stack at different widths look like one of them matters less;
   they don't, and the wrap is not the place to say so. */
@media (max-width: 560px) {
  .support-banner .sb-acts { flex: 1 1 100%; }
  .support-banner .sb-acts > * { flex: 1 1 200px; }
}
.support-banner .label { font-size: 12px; color: var(--muted); display: block; }
.support-banner .number { font-size: 19px; font-weight: 700; }
/* The shared primary link — also the empty-state button on كروتي and
   مشترياتي, and the call button on the maintenance page, so everything
   here has to read well at label size with no icon beside it. */
/* The fill is declared plainly first and only then as a gradient. A
   browser that cannot parse color-mix() drops that whole declaration,
   and this one is the button's background — without the flat line under
   it, a captive-portal WebView a few years old renders white text on
   nothing. Elsewhere in this file color-mix() paints tints and shadows,
   where losing it costs a nicety; here it would cost the control. */
.btn-call {
  padding: 10px 18px; color: var(--on-brand);
  background: var(--brand);
  background: linear-gradient(160deg, color-mix(in srgb, var(--brand) 86%, #fff) 0%, var(--brand) 62%);
  border-radius: var(--radius); font-size: 13px; font-weight: 600; white-space: nowrap;
  /* Wrapped onto its own line it is the whole line, not a tab stranded
     at one end of it. */
  flex: 0 1 auto; text-align: center;
  box-shadow: 0 1px 1px rgba(16,26,21,.06), 0 8px 18px -12px rgba(16,26,21,.4);
  transition: transform .16s ease, box-shadow .16s ease, filter .16s ease;
}
.btn-call:hover {
  color: var(--on-brand); filter: brightness(1.04);
  transform: translateY(-1px);
  box-shadow: 0 2px 3px rgba(16,26,21,.07), 0 14px 26px -14px rgba(16,26,21,.5);
}
/* Pressed is a real state on a phone, where there is no hover to tell
   you the tap landed. */
.btn-call:active { transform: translateY(0) scale(.985); filter: brightness(.97); }
.support-banner .number:hover { color: var(--brand); }

/* Reach-a-human, in either form the shop offers: a conversation when the
   chat widget is on, a call when it is not. Both are the same object —
   an icon that is doing something, the invitation in full, and the
   promise underneath it — because the choice between them is the
   manager's and the customer should not be able to tell which one they
   were given by which one looks like the afterthought.

   Both stay still the moment they are touched. An animation that keeps
   moving under the pointer is a distraction, not an invitation. */
.btn-contact {
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  position: relative; padding: 11px 18px; text-align: start;
  isolation: isolate; overflow: hidden;
}
/* One line, and the icon is the other half of it. The second line used
   to explain what pressing would do — "محادثة بدون مكالمة", "اشترِ
   وتابع طلباتك منه" — which the icon says faster and the label had
   already said. What is left is a name and a picture, both big enough
   to read at arm's length. */
.btn-label {
  font-size: 14.5px; font-weight: 700; line-height: 1.6;
  min-width: 0; white-space: nowrap;
}
/* A slow sheen across the face — the one moving thing that survives on a
   dark theme, where a glow around a green button is invisible against a
   near-black page. Behind the label, never over it. */
.btn-contact::after {
  content: ''; position: absolute; inset-block: 0; inline-size: 45%;
  inset-inline-start: -60%; z-index: -1;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.22), transparent);
  animation: contact-sheen 4.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes contact-sheen {
  0%, 62%   { transform: translateX(0); }
  92%, 100% { transform: translateX(360%); }
}
.btn-contact:hover::after, .btn-contact:focus-visible::after { animation: none; opacity: 0; }

/* Sized to be seen. At the button's own font size the icon read as a
   piece of punctuation beside the words rather than the thing they are
   about — this is the invitation, so it leads. */
.chat-ic, .call-ic, .app-ic {
  position: relative; display: grid; place-items: center;
  width: 34px; height: 34px; flex: none;
}
.chat-ic svg, .call-ic svg { width: 32px; height: 32px; stroke-width: 1.8; }
/* The dots sit inside the bubble, and have to look like they are in a
   room rather than wedged into one: at the bigger icon size they had
   grown until they touched the stroke on both sides. Small enough to
   have air around them, and placed as a share of the icon — the offsets
   centre the row on the bubble's body, so resizing the icon again keeps
   them there instead of sliding out of its mouth. */
.chat-dots {
  position: absolute; inset-block-start: 41%; inset-inline-start: 28%;
  display: flex; gap: 2.6px; align-items: center;
}
.chat-dots i {
  width: 3.2px; height: 3.2px; border-radius: 50%; background: currentColor;
  opacity: .35; animation: chat-type 1.25s ease-in-out infinite;
}
.chat-dots i:nth-child(2) { animation-delay: .16s; }
.chat-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes chat-type {
  0%, 65%, 100% { opacity: .3; transform: translateY(0); }
  32%           { opacity: 1;  transform: translateY(-2px); }
}
/* The old chat button scaled a 2px ring out past its own edge every 2.6
   seconds. It is gone: the button is clipped now (the sheen has to be),
   so the ring would have been cut off — and it was the wrong idea
   anyway. A control that grows and shrinks under a thumb aiming at it is
   a control that gets missed. What is alive now is inside the icon,
   where it says "someone is there" without moving the target. */

/* The handset rings: a short shake, then a long pause — the rhythm of a
   phone actually ringing, not a permanent jitter. */
.call-ic svg { animation: call-shake 3.4s ease-in-out infinite; transform-origin: 50% 60%; }
@keyframes call-shake {
  0%, 62%, 100%                 { transform: rotate(0); }
  66%, 74%, 82%                 { transform: rotate(-11deg); }
  70%, 78%, 86%                 { transform: rotate(11deg); }
}
/* Two arcs leaving the handset, in time with the shake. Squares would be
   wrong here and circles are the exception the tokens allow for a shape
   that has to read as a wave. */
.call-wave {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  width: 32px; height: 32px; margin: -16px 0 0 -16px;
  border: 1.5px solid currentColor; border-radius: 50%;
  opacity: 0; animation: call-wave 3.4s ease-out infinite; pointer-events: none;
}
.call-wave:nth-of-type(2) { animation-delay: .22s; }
@keyframes call-wave {
  0%, 60%   { opacity: 0;   transform: scale(.55); }
  70%       { opacity: .45; transform: scale(.9); }
  88%, 100% { opacity: 0;   transform: scale(1.32); }
}
.btn-ring:hover .call-ic svg, .btn-ring:hover .call-wave { animation: none; }
.btn-ring:hover .call-wave { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .chat-dots i, .btn-contact::after,
  .call-ic svg, .call-wave { animation: none; }
  .btn-contact::after, .call-wave { opacity: 0; }
}

/* ---- the app ------------------------------------------------------ */
/* The same button as the one beside it — same anatomy, same size, same
   row — one step quieter. Two identical brand slabs side by side make a
   banner with two shouts and no hierarchy; this is the shop's own
   surface with the brand at its edge, which reads as "and also" rather
   than as a second, competing invitation.

   It was a card of its own under the banner. That was wrong twice over:
   it pushed the products further down the page, and it made downloading
   the app look like a different kind of offer from reaching a human,
   when they are the same offer made in the same breath. */
.btn-app {
  background: var(--surface);
  color: var(--brand);
  border: 1px solid var(--line);
  border-color: color-mix(in srgb, var(--brand) 32%, var(--line));
  box-shadow: 0 1px 1px rgba(16,26,21,.05);
}
.btn-app:hover {
  color: var(--brand); filter: none;
  border-color: var(--brand);
  box-shadow: 0 2px 3px rgba(16,26,21,.07), 0 12px 22px -14px rgba(16,26,21,.45);
}
/* The sheen is a white wash: invisible on a white button, and wrong on
   one that is not asking for the attention anyway. */
.btn-app::after { content: none; }
/* The robot lands, waits, lands again — the only moving part it needs,
   and it holds still under the pointer like everything else here. */
.app-ic svg {
  width: 32px; height: 32px;
  animation: app-drop 3.1s ease-in-out infinite;
}
@keyframes app-drop {
  0%, 58%, 100% { transform: translateY(0); }
  72%           { transform: translateY(3px); }
  86%           { transform: translateY(0); }
}
.btn-app:hover .app-ic svg, .btn-app:focus-visible .app-ic svg { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .app-ic svg { animation: none; }
}

/* ---- support chat --------------------------------------------------
   Our own chat, drawn with the shop's tokens and driven by store.js
   against the support site's REST API — a bottom sheet on a phone, a
   floating panel on a desktop. Three rows: header, the scrolling log,
   and the composer pinned to the bottom. */
.supm { position: fixed; inset: 0; z-index: 70; display: flex; }
/* `display: flex` outranks the UA's [hidden], and a closed modal that
   still covered the page would silently eat every click on it. */
.supm[hidden] { display: none; }
.supm-backdrop {
  position: absolute; inset: 0; background: rgba(8, 16, 12, .5);
  opacity: 0; transition: opacity .24s ease;
  -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0);
}
.supm.on .supm-backdrop {
  opacity: 1;
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
}
.supm-panel {
  position: relative; margin-top: auto; width: 100%;
  height: 92dvh; max-height: 92dvh;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line); border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lift);
  transform: translateY(100%);
  transition: transform .26s cubic-bezier(.22, 1, .36, 1), opacity .2s ease;
}
.supm.on .supm-panel { transform: translateY(0); }
@media (min-width: 720px) {
  .supm { align-items: flex-end; justify-content: flex-end; padding: 24px; }
  .supm-panel {
    margin-top: 0; width: min(94vw, 400px);
    height: min(640px, 84dvh); max-height: 84dvh;
    border: 1px solid var(--line); border-radius: var(--radius-lg);
    transform: translateY(14px) scale(.97); opacity: 0;
  }
  .supm.on .supm-panel { transform: none; opacity: 1; }
}

/* Header */
.sup-head {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; background: var(--brand); color: var(--on-brand);
}
.sup-ava {
  flex: none; display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: 50%; background: rgba(255, 255, 255, .18);
}
.sup-ava svg { width: 20px; height: 20px; }
.sup-id { flex: 1 1 auto; min-width: 0; line-height: 1.35; }
.sup-id strong { display: block; font-size: 14.5px; font-weight: 700; }
.sup-status {
  display: flex; align-items: center; gap: 6px;
  font-size: 11.5px; opacity: .9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sup-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: #7CF0B0; box-shadow: 0 0 0 0 rgba(124, 240, 176, .7);
  animation: sup-pulse 2s infinite;
}
@keyframes sup-pulse {
  0% { box-shadow: 0 0 0 0 rgba(124, 240, 176, .6); }
  70% { box-shadow: 0 0 0 6px rgba(124, 240, 176, 0); }
  100% { box-shadow: 0 0 0 0 rgba(124, 240, 176, 0); }
}
.sup-chat .supm-x {
  position: static; flex: none; width: 32px; height: 32px;
  display: grid; place-items: center; border-radius: 9px;
  color: var(--on-brand); background: rgba(255, 255, 255, .16);
  border: 0; box-shadow: none;
  transition: background .18s ease;
}
.sup-chat .supm-x svg { width: 16px; height: 16px; }
.sup-chat .supm-x:hover { background: rgba(255, 255, 255, .3); }

/* Message log */
.sup-log {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px;
  padding: 16px 14px; background: var(--bg);
  overscroll-behavior: contain;
}
.sup-hint {
  margin: auto; text-align: center; font-size: 12.5px; color: var(--muted);
  max-width: 34ch; line-height: 1.6;
}
.sup-hint.error { color: var(--hot, #d64545); }
.sup-msg {
  max-width: 80%; padding: 8px 12px; font-size: 13.5px; line-height: 1.55;
  border-radius: 16px; word-wrap: break-word; overflow-wrap: anywhere; white-space: pre-wrap;
  animation: sup-in .18s ease;
}
@keyframes sup-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
/* The customer's own messages sit at the end of the row (the left, in
   RTL — the WhatsApp convention), tinted with the brand. */
.sup-msg.me {
  align-self: flex-end; background: var(--brand); color: var(--on-brand);
  border-end-end-radius: 5px;
}
.sup-msg.them {
  align-self: flex-start; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-end-start-radius: 5px;
}
.sup-msg.sys {
  align-self: center; max-width: 90%; text-align: center;
  background: transparent; color: var(--muted); font-size: 11.5px; padding: 2px 8px;
}
.sup-msg .sup-time {
  display: block; margin-top: 3px; font-size: 10px; opacity: .7; text-align: start;
}
.sup-msg .sup-from { display: block; font-size: 10.5px; font-weight: 700; opacity: .85; margin-bottom: 2px; }
.sup-msg a { color: inherit; text-decoration: underline; }
.sup-msg .sup-att {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 4px;
  font-size: 12px; font-weight: 600;
}
.sup-msg img.sup-img { display: block; max-width: 100%; border-radius: 10px; margin-top: 4px; }
/* Press and hold a bubble: copy, and on your own message edit or delete.
   Positioned against .supm-panel, which is the chat's own box, so the menu
   cannot end up outside the panel on a small screen. */
.sup-menu {
  position: absolute; z-index: 12; min-width: 150px; padding: 4px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 12px 30px rgba(16, 24, 40, .18);
}
.sup-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 11px; border: 0; border-radius: 8px; background: none;
  font: inherit; font-size: 13px; color: var(--ink); text-align: start; cursor: pointer;
}
.sup-menu button:hover { background: var(--bg); }
.sup-menu button.danger { color: var(--hot, #d64545); }
.sup-menu button svg { width: 16px; height: 16px; flex: none; }
.sup-menu-veil { position: absolute; inset: 0; z-index: 11; }
/* The composer while an already-sent message is being reworded. */
.sup-editing {
  display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  border-top: 1px solid var(--line); background: var(--bg);
  font-size: 12px; color: var(--muted);
}
.sup-editing[hidden] { display: none; }
.sup-editing .sup-editing-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sup-editing button { border: 0; background: none; color: inherit; cursor: pointer; padding: 2px; line-height: 0; }
.sup-editing button svg { width: 15px; height: 15px; }
/* Agent "typing" bubble. Shaped like one of their messages, because that
   is what it is promising — and it sits at the end of the log where the
   message will land, not in the header where it would be missed. */
.sup-typing {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 7px;
  max-width: 80%; padding: 9px 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 16px; border-end-start-radius: 5px;
  animation: sup-in .18s ease;
}
.sup-typing-who {
  font-size: 10.5px; font-weight: 700; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 16ch;
}
.sup-typing-dots { display: inline-flex; gap: 4px; }
.sup-typing-dots i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--muted);
  animation: sup-bounce 1.2s infinite;
}
.sup-typing-dots i:nth-child(2) { animation-delay: .16s; }
.sup-typing-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes sup-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-4px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  /* Still alive, just not hopping. */
  .sup-typing-dots i { animation: sup-fade 1.6s ease-in-out infinite; }
  @keyframes sup-fade { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
}

/* Pre-chat gate: name + mobile, before a conversation exists at all. It
   replaces the log and the composer rather than sitting above them —
   there is nothing to read and nothing to write yet. */
.sup-gate {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 20px 18px; background: var(--bg);
  display: flex; flex-direction: column;
}
.sup-gate[hidden] { display: none; }
.sup-chat.gated .sup-log,
.sup-chat.gated .sup-compose { display: none; }
.sup-gate-head { text-align: center; margin-bottom: 18px; }
.sup-gate-ic {
  display: grid; place-items: center; width: 52px; height: 52px; margin: 0 auto 10px;
  border-radius: 50%; background: var(--brand-soft); color: var(--brand);
}
.sup-gate-ic svg { width: 26px; height: 26px; }
.sup-gate-head h4 { margin: 0 0 6px; font-size: 15.5px; font-weight: 800; color: var(--ink); }
.sup-gate-head p { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--muted); }
.sup-gate label {
  display: block; margin-bottom: 6px;
  font-size: 12.5px; font-weight: 600; color: var(--ink);
}
.sup-field {
  width: 100%; height: 46px; margin-bottom: 14px; padding: 0 14px;
  font: inherit; font-size: 14px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.sup-field:focus {
  outline: 0; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.sup-field.bad { border-color: var(--hot, #d64545); }
.sup-field.ltr { direction: ltr; text-align: start; }
.sup-gate .alert { margin: 0 0 14px; }
.sup-gate .alert.hidden { display: none; }
/* Scoped past `.sup-btn { flex: 1 }`, which is meant for the two buttons
   sharing a row in the confirm dialog. In a column it means "take the
   whole panel", which is what it did here. */
.sup-gate .sup-btn.sup-gate-go {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  flex: none; height: 48px; margin-top: 2px;
}
.sup-gate .sup-gate-go svg { width: 18px; height: 18px; flex: none; }
.sup-gate .sup-gate-go:disabled { opacity: .6; cursor: wait; filter: none; }
.sup-gate-note {
  margin: 10px 0 0; text-align: center; font-size: 11.5px; line-height: 1.6;
  color: var(--muted);
}

/* Composer */
.sup-compose {
  position: relative;
  flex: none; display: flex; align-items: flex-end; gap: 6px;
  padding: 10px 12px; background: var(--surface); border-top: 1px solid var(--line);
}
/* attach / mic buttons */
/* The composer reads as one band: the message box sets the height and the
   round controls match it. They were 38 / 40 / 41 apart, which is close
   enough to look accidental and left the input looking squashed between
   its neighbours. */
.sup-tool {
  flex: none; display: grid; place-items: center; width: 44px; height: 44px;
  border: 0; border-radius: 50%; background: transparent; color: var(--muted);
  cursor: pointer; transition: background .16s ease, color .16s ease;
}
.sup-tool svg { width: 21px; height: 21px; }
.sup-tool:hover:not(:disabled) { background: var(--surface2); color: var(--brand); }
.sup-tool:disabled { opacity: .45; cursor: default; }
.sup-tool.danger:hover { color: var(--hot, #d64545); }
.sup-tool.recording { color: var(--hot, #d64545); }
/* recording bar overlays the composer while a voice note is captured */
.sup-rec {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; background: var(--surface);
}
.sup-rec[hidden] { display: none; }
.sup-rec-meter {
  flex: 1 1 auto; display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--muted);
}
.sup-rec-dot {
  flex: none; width: 9px; height: 9px; border-radius: 50%;
  background: var(--hot, #d64545); animation: sup-blink 1s steps(2, start) infinite;
}
@keyframes sup-blink { 50% { opacity: .25; } }
/* voice note — Facebook-style waveform player (play/pause + seek, no download) */
.voice { display: flex; align-items: center; gap: 10px; margin-top: 5px; width: 232px; max-width: 100%; }
.voice .v-audio { display: none; }
.voice .v-play {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--brand); color: var(--on-brand);
}
.voice .v-play:hover { filter: brightness(1.08); }
.voice .v-wave { position: relative; flex: 1; display: flex; align-items: center; gap: 2px; height: 26px; cursor: pointer; }
.voice .v-wave i { flex: 1; min-width: 2px; max-width: 3px; border-radius: 2px; background: var(--line); transition: background .1s; }
.voice .v-wave i.on { background: var(--brand); }
.voice .v-time { flex: none; font-size: 11px; color: var(--muted); min-width: 30px; text-align: end; unicode-bidi: plaintext; }
/* on the customer's own (brand-filled) bubble, invert for contrast */
.sup-msg.me .voice .v-play { background: rgba(255, 255, 255, .28); color: #fff; }
.sup-msg.me .voice .v-wave i { background: rgba(255, 255, 255, .4); }
.sup-msg.me .voice .v-wave i.on { background: #fff; }
.sup-msg.me .voice .v-time { color: rgba(255, 255, 255, .85); }

/* "start over" confirmation — themed dialog inside the panel, not a browser alert() */
.sup-confirm {
  position: absolute; inset: 0; z-index: 6; display: grid; place-items: center;
  padding: 20px; background: rgba(8, 16, 12, .45);
  opacity: 0; transition: opacity .18s ease;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.sup-confirm[hidden] { display: none; }
.sup-confirm.on { opacity: 1; }
.sup-confirm-card {
  width: 100%; max-width: 300px; text-align: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  padding: 20px 18px 16px;
  transform: translateY(8px) scale(.97); transition: transform .2s cubic-bezier(.22, 1, .36, 1);
}
.sup-confirm.on .sup-confirm-card { transform: none; }
.sup-confirm-ic {
  display: grid; place-items: center; width: 44px; height: 44px; margin: 0 auto 10px;
  border-radius: 50%; background: var(--brand-soft); color: var(--brand);
}
.sup-confirm-ic svg { width: 22px; height: 22px; }
.sup-confirm-text { margin: 0 0 16px; font-size: 13.5px; line-height: 1.6; color: var(--ink); }
.sup-confirm-actions { display: flex; gap: 8px; }
.sup-btn {
  flex: 1; padding: 10px 12px; font: inherit; font-size: 13px; font-weight: 600;
  border-radius: var(--radius); cursor: pointer; border: 1px solid transparent;
  transition: filter .15s ease, background .15s ease;
}
.sup-btn.brand { background: var(--brand); color: var(--on-brand); }
.sup-btn.brand:hover { filter: brightness(1.06); }
.sup-btn.ghost { background: var(--surface2); color: var(--ink); border-color: var(--line); }
.sup-btn.ghost:hover { background: var(--line); }
@media (prefers-reduced-motion: reduce) {
  .sup-confirm, .sup-confirm-card { transition: none; }
}

/* One line to start, growing with the message. `overflow-y: hidden` is
   the point: a textarea defaults to `auto`, and an empty one-line box
   whose content is a pixel taller than its height gets a full Windows
   scrollbar — arrow buttons and all — sitting inside the composer, which
   reads as the stepper on a number field. store.js turns scrolling back
   on only once the text passes max-height, and the bar below is drawn
   without those buttons. */
/* ---- Sadad: the bank's window -------------------------------------
   The step where the shop hands over. It states what the bank is about
   to be asked for — the shop, the invoice, the amount — because the
   customer is about to approve that number somewhere else entirely, and
   the only thing carrying them across is what they read here. */
.sadad-head { text-align: center; padding: 4px 4px 2px; }
.sadad-mark {
  display: grid; place-items: center; width: 56px; height: 56px; margin: 0 auto 10px;
  border-radius: 50%; background: var(--brand-soft); color: var(--brand);
}
.sadad-mark svg { width: 26px; height: 26px; }
.sadad-head h4 { margin: 0 0 6px; font-size: 16px; font-weight: 800; color: var(--ink); }
.sadad-head p { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--muted); }

.sadad-facts {
  margin: 16px 0 0; padding: 4px 12px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface2);
}
.sadad-facts > div {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 2px; border-bottom: 1px dashed var(--line);
}
.sadad-facts > div:last-child { border-bottom: 0; }
.sadad-facts dt { font-size: 12.5px; color: var(--muted); }
.sadad-facts dd { margin: 0; font-size: 13.5px; font-weight: 700; color: var(--ink); }
.sadad-facts .ltr { direction: ltr; }

/* Only after the window has been opened — before that there is nothing
   to wait for, and saying so would be a lie about a button not pressed. */
.sadad-wait {
  margin: 10px 0 0; text-align: center; font-size: 12.5px; color: var(--muted);
}
.sadad-wait::before {
  content: ''; display: inline-block; width: 8px; height: 8px; margin-inline-end: 7px;
  border-radius: 50%; background: var(--brand); animation: mc-pulse 1.4s ease-in-out infinite;
}
@keyframes mc-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .sadad-wait::before { animation: none; }
}

/* An inline SVG with no width of its own fills whatever box it is given,
   and the only rule that sized these lived under .btn-row — where the
   opening button does not sit. Sized here for both, so the icons stay
   icons and the label keeps its line. */
#stage-sadad .btn-primary { gap: 9px; }
#stage-sadad .btn-ghost { gap: 8px; flex: 1; min-width: 0; width: auto; }
#stage-sadad .btn-primary svg,
#stage-sadad .btn-ghost svg { width: 18px; height: 18px; flex: none; }
#stage-sadad .btn-row { margin-top: 12px; }
#stage-sadad #sadad-error { margin-top: 14px; }

#support-input {
  flex: 1 1 auto; min-width: 0; resize: none;
  min-height: 44px; max-height: 132px;
  overflow-y: hidden;
  padding: 11px 16px; font: inherit; font-size: 14px; line-height: 1.45;
  color: var(--ink); background: var(--surface2);
  border: 1px solid var(--line); border-radius: 22px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
#support-input::-webkit-scrollbar { width: 6px; }
#support-input::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
#support-input::-webkit-scrollbar-track { background: transparent; }
#support-input::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
#support-input:focus { outline: none; border-color: var(--brand); }
#support-input:disabled { opacity: .6; }
.sup-send {
  flex: none; display: grid; place-items: center; width: 44px; height: 44px;
  border: 0; border-radius: 50%; background: var(--brand); color: var(--on-brand);
  cursor: pointer; transition: opacity .18s ease, transform .12s ease;
}
.sup-send svg { width: 19px; height: 19px; }
/* Flip the send glyph so it points the way text flows (RTL). */
.sup-send svg { transform: scaleX(-1); }
.sup-send:hover:not(:disabled) { transform: scale(1.06); }
.sup-send:disabled { opacity: .45; cursor: default; }
@media (prefers-reduced-motion: reduce) {
  .sup-dot, .sup-typing i, .sup-msg, .sup-rec-dot { animation: none; }
}

/* ---- slides ------------------------------------------------------- */
/* ---- ad slides ---------------------------------------------------- */
/* Stacked by default so every slide is readable without JS. store.js adds
   .js-rotate, which collapses them into one frame and cross-fades — that
   way a script that never runs degrades to a list rather than one slide
   hiding the rest. */
.slides { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.slide {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 18px; border-radius: var(--radius-lg);
  border: 1px solid var(--line); background: var(--surface); box-shadow: var(--shadow);
  animation: mc-rise .4s ease both;
}
.slide.kind-alert { background: var(--hot-soft); border-color: var(--hot); }
.slide.kind-special_offer { background: var(--brand-soft); border-color: var(--brand); }
.slide-ic {
  flex: none; width: 34px; height: 34px; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-soft); color: var(--brand);
}
.slide.kind-alert .slide-ic { background: var(--hot-soft); color: var(--hot); }
.slide-ic svg { width: 17px; height: 17px; }
.slide-text { min-width: 0; }
.slide h3 { font-size: 14px; font-weight: 700; margin-bottom: 2px; }
.slide p { font-size: 13px; color: var(--muted); }

/* Rotating mode. The track keeps the tallest slide's height, so the page
   below it doesn't jump as slides change. */
.slides.js-rotate .slide-track { position: relative; display: grid; }
.slides.js-rotate .slide {
  grid-area: 1 / 1; animation: none;
  opacity: 0; transform: translateX(14px);
  transition: opacity .45s ease, transform .45s ease;
  pointer-events: none;
}
.slides.js-rotate .slide.on { opacity: 1; transform: none; pointer-events: auto; }
.slide-dots { display: flex; justify-content: center; gap: 6px; margin-top: 10px; }
.slide-dot {
  width: 18px; height: 4px; border-radius: 3px; background: var(--line);
  transition: background .25s ease, width .25s ease;
}
.slide-dot.on { width: 26px; background: var(--brand); }
.slides:not(.js-rotate) .slide-dots { display: none; }
@media (prefers-reduced-motion: reduce) {
  .slides.js-rotate .slide { transition: none; transform: none; }
  .slide { animation: none; }
}

/* ---- filters + catalog ------------------------------------------- */
.catalog-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 18px; flex-wrap: wrap;
}
/* Takes the row's spare width so its own chips wrap inside it — otherwise a
   store with several categories pushes the cart button onto a second line. */
.filters { flex: 1; min-width: 0; display: flex; gap: 8px; flex-wrap: wrap; }
/* Shared by the catalog's category filter and "كروتي"'s (which overrides
   the metrics). Icon and count are optional parts of the same control.

   The selected chip is brand green, like every other "this one is active"
   surface in the store — the nav underline, the cart bar, أضف للسلة. It
   used to be near-black, which read as a different design system sitting
   on top of a green page. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 10px 16px; border-radius: var(--radius); font-size: 13px; font-weight: 500;
  background: var(--surface); color: var(--muted); border: 1px solid var(--line);
  transition: background .18s ease, color .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.chip:hover { border-color: var(--brand); color: var(--brand); }
.chip.on {
  background: var(--brand); color: var(--on-brand); border-color: transparent; font-weight: 700;
  box-shadow: 0 3px 10px -3px color-mix(in srgb, var(--brand) 60%, transparent);
}
.chip.on:hover { color: var(--on-brand); }
.chip > svg { width: 14px; height: 14px; flex: none; opacity: .85; }
.chip .num {
  font-size: 10.5px; font-weight: 700; padding: 1px 5px; border-radius: 6px;
  background: var(--surface2); color: var(--muted);
}
/* Legible against the brand fill in both themes: on-brand is near-white on
   light and near-black on dark, so a fixed white wash would vanish. */
.chip.on .num { background: color-mix(in srgb, var(--on-brand) 22%, transparent); color: var(--on-brand); }
.card-filters .chip > svg { width: 13px; height: 13px; }
.card-filters .chip .num { background: var(--line); }

/* cart icon — lives where the old product-count pill was */
.cart-icon-btn {
  position: relative; flex: none; width: 40px; height: 40px;
  border-radius: var(--radius); background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); display: flex; align-items: center; justify-content: center;
}
.cart-icon-btn svg { width: 19px; height: 19px; }
.cart-icon-btn:hover { border-color: var(--brand); color: var(--brand); }
.cart-icon-badge {
  position: absolute; top: -6px; inset-inline-end: -6px;
  min-width: 18px; height: 18px; padding: 0 4px; border-radius: 6px;
  background: var(--hot); color: #fff; font-size: 10.5px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--bg);
}
.cart-icon-badge.bump { animation: mc-bump .45s cubic-bezier(.34,1.56,.64,1) both; }

.catalog { display: flex; flex-direction: column; gap: 14px; margin-top: 16px; }
@media (min-width: 900px) {
  .catalog { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
}

/* product card
   ------------------------------------------------------------------
   A column, so the rule and the button can be pinned to the bottom of it
   rather than floating wherever the text above happens to end. Two cards
   side by side are the same height (the grid stretches them), and with
   the slack collected above the rule instead of below it, their rules
   and their buttons line up — which is the whole reason the card reads
   as one of a set rather than one of a pile. */
.product {
  position: relative;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden;
  transition: box-shadow .2s ease, border-color .2s ease;
  animation: mc-rise .4s ease both;
}
.product.in-cart { border: 1.5px solid var(--brand); box-shadow: var(--shadow-lift); }
.product.out { opacity: .55; }
.product.addable { cursor: pointer; }
/* Promo badge: a pill floating clear of the card's corner rather than a
   ribbon welded to the edge. Inset on both axes so it can't collide with
   the speed row beneath it, and inline-start/end so RTL and LTR both put
   it on the outer corner. */
.product-badge {
  position: absolute; top: 10px; inset-inline-end: 10px; z-index: 3;
  display: inline-flex; align-items: center; gap: 5px;
  max-width: calc(100% - 20px);
  padding: 5px 10px; border-radius: 9px;
  font-size: 10.5px; font-weight: 800; line-height: 1.4; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  box-shadow: 0 3px 10px -2px rgba(15, 23, 42, .35);
}
.product-badge svg { width: 12px; height: 12px; flex: none; }
.product-badge.badge-bestseller { background: linear-gradient(135deg, #fb923c, #ea580c); }
.product-badge.badge-economic { background: linear-gradient(135deg, #38bdf8, #0369a1); }
/* Manager's own wording — deliberately a different family from the two
   presets, so a custom label never reads as one of them. */
.product-badge.badge-custom { background: linear-gradient(135deg, #a78bfa, #6d28d9); }
.add-check {
  position: absolute; inset: 0; z-index: 5; display: flex; align-items: center; justify-content: center;
  pointer-events: none; opacity: 0; background: color-mix(in srgb, var(--brand) 12%, transparent);
}
.add-check svg {
  width: 36px; height: 36px; padding: 14px; border-radius: 12px;
  background: var(--brand); color: var(--on-brand); box-shadow: var(--shadow-lift);
  opacity: 0; transform: scale(.4);
}
.add-check.show { animation: mc-check-bg .55s ease both; }
.add-check.show svg { animation: mc-check-pop .55s cubic-bezier(.34,1.56,.64,1) both; }
@keyframes mc-check-bg {
  0%, 70% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes mc-check-pop {
  0% { opacity: 0; transform: scale(.4); }
  35% { opacity: 1; transform: scale(1.08); }
  55% { transform: scale(.96); }
  70% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}
.product-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 16px 16px 12px; }
/* Clears the floating badge so the speed row never tucks under it. */
.product.has-badge .product-head { padding-top: 40px; }
.product-info { min-width: 0; }
/* Room for two lines whether or not the name needs them, so a one-line
   product and a two-line one still put their rule in the same place. Not
   clamped to two: a longer name grows the card rather than being cut off
   — the customer needs to read what they are buying, and the flex column
   above keeps the row aligned anyway. */
.product-head h3 {
  font-size: 17px; font-weight: 700; line-height: 1.35;
  min-height: calc(2 * 1.35em);
}
.price-tag { flex: none; display: flex; align-items: baseline; gap: 3px; }
.price-tag b { font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--brand); }
.price-tag span { font-size: 12px; color: var(--muted); }
.speed-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.speed-row .speed { font-size: 12px; font-weight: 700; color: var(--brand); }
.bars { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
.bars i { width: 3px; border-radius: 1px; background: var(--line); }
.bars i:nth-child(1) { height: 7px; }
.bars i:nth-child(2) { height: 10px; }
.bars i:nth-child(3) { height: 13px; }
.bars i:nth-child(4) { height: 16px; }
.bars[data-level="1"] i:nth-child(-n+1),
.bars[data-level="2"] i:nth-child(-n+2),
.bars[data-level="3"] i:nth-child(-n+3),
.bars[data-level="4"] i:nth-child(-n+4) { background: var(--brand); }
.badge-hot {
  flex: none; padding: 6px 11px; background: var(--hot-soft); color: var(--hot);
  border-radius: 8px; font-size: 11px; font-weight: 700; white-space: nowrap;
}
/* One row's worth of room, kept even when a brand carries no tags at
   all, so an untagged card is not shorter than its neighbours by exactly
   the height of a chip. */
.product-tags {
  display: flex; gap: 7px; flex-wrap: wrap; padding: 0 16px 14px;
  min-height: calc(29px + 14px);
}
.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 11px; background: var(--surface2); border: 1px solid var(--line);
  border-radius: 8px; font-size: 12px; color: var(--muted);
}
.tag-ic { width: 13px; height: 13px; flex: none; }
.tag.stock-ok { background: var(--brand-soft); color: var(--brand); border-color: transparent; font-weight: 500; }
.tag.stock-low { background: var(--hot-soft); color: var(--hot); border-color: transparent; font-weight: 500; }
.tag.stock-out { background: var(--hot-soft); color: var(--hot); border-color: transparent; font-weight: 500; }
/* The brand's minimum sale amount. Deliberately not an alarm colour — it
   is a condition of the sale, not a problem with it. */
.tag.tag-min { border-style: dashed; font-weight: 600; }
/* margin-top:auto is what does the work: the rule and everything under
   it are pushed to the foot of the card, so the slack of a stretched
   grid item lands above the rule rather than below it. */
.product-sep {
  height: 1px; margin: auto 16px 0;
  background: repeating-linear-gradient(to left, var(--line) 0 6px, transparent 6px 12px);
}
.product-foot { flex: none; display: flex; align-items: center; padding: 14px 16px 16px; }
.product-foot .btn-add { width: 100%; }
.product-foot .qty-ctl { width: 100%; justify-content: space-between; padding: 4px 6px; }
.btn-add {
  height: 48px; padding: 0 26px; background: var(--brand); color: var(--on-brand);
  border-radius: var(--radius); font-size: 15px; font-weight: 700;
}
.btn-add:hover { filter: brightness(1.06); }
.btn-add:disabled { opacity: .5; cursor: not-allowed; filter: none; }
.qty-ctl {
  display: flex; align-items: center; gap: 2px;
  background: var(--brand-soft); border-radius: var(--radius); padding: 3px;
}
.qty-ctl .dec, .qty-ctl .inc {
  width: 28px; height: 28px; border-radius: 8px; font-size: 16px; font-weight: 500;
  display: flex; align-items: center; justify-content: center;
}
.qty-ctl .dec { background: var(--surface); color: var(--ink); }
.qty-ctl .inc { background: var(--brand); color: var(--on-brand); }
.qty-ctl .qty { min-width: 34px; text-align: center; font-size: 16px; font-weight: 700; color: var(--brand); }

/* ---- cart bar ----------------------------------------------------- */
.cartbar {
  position: fixed; right: 16px; left: 16px; bottom: 92px; z-index: 30;
  display: none; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 10px 10px 14px;
  background: linear-gradient(135deg, var(--surface) 0%, var(--brand-soft) 160%);
  color: var(--ink); border: 1px solid var(--line);
  border-radius: 22px; box-shadow: var(--shadow-lift);
  max-width: 528px; margin: 0 auto;
}
.cartbar.show { display: flex; animation: mc-rise .3s ease both; }

/* Room for the bar, only while there is a bar.
   It floats at bottom:92px and stands about 64px tall, so it reaches
   ~156px up the screen — past the 120px .shell reserves for the bottom
   nav. The last product card was underneath it, its add button covered.
   Reserved on the body class rather than always, because an empty cart
   has no bar and a page that always ends in dead space is its own
   smaller bug. */
body.has-cartbar .shell { padding-bottom: 184px; }
body.has-cartbar.cart-tucked .shell { padding-bottom: 132px; }
@media (min-width: 900px) {
  body.has-cartbar .shell { padding-bottom: 132px; }
  body.has-cartbar.cart-tucked .shell { padding-bottom: 96px; }
}

/* Tucked: the bar shrinks to its own count and gets out of the way.
   It keeps its place at the end of the row rather than sliding off
   screen — the point is to uncover the catalogue, not to hide the cart
   from someone who is filling it. */
.cartbar .cart-peek { display: none; }
.cartbar.is-tucked {
  right: auto; left: 16px;
  padding: 8px; gap: 0;
  border-radius: var(--radius-lg);
}
.cartbar.is-tucked .cart-actions,
.cartbar.is-tucked .totals { display: none; }
.cartbar.is-tucked .cart-peek {
  display: flex; align-items: center; gap: 7px;
  background: none; border: 0; padding: 2px 4px;
  color: var(--brand); cursor: pointer;
}
.cartbar.is-tucked .cart-peek svg { width: 21px; height: 21px; }
.cartbar.is-tucked .cart-peek .badge {
  min-width: 22px; height: 22px; padding: 0 6px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; background: var(--brand); color: var(--on-brand);
  font-size: 12px; font-weight: 700;
}
.cartbar .btn-pay {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 8px;
  height: 48px; padding: 0 22px; background: var(--brand); color: var(--on-brand);
  border-radius: var(--radius); font-size: 15px; font-weight: 700;
  animation: mc-glow 2.4s ease-in-out infinite;
}
.cartbar .btn-pay svg { width: 19px; height: 19px; flex: none; }
.cartbar .btn-pay::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(120deg, transparent 35%, rgba(255,255,255,.4) 50%, transparent 65%);
  transform: translateX(-130%);
  animation: mc-shine 3s ease-in-out infinite;
}
.cartbar .btn-pay:hover { filter: brightness(1.06); }
@keyframes mc-glow {
  0%, 100% { box-shadow: 0 4px 14px -4px color-mix(in srgb, var(--brand) 55%, transparent); }
  50% { box-shadow: 0 6px 22px -2px color-mix(in srgb, var(--brand) 85%, transparent); }
}
@keyframes mc-shine {
  0%, 20% { transform: translateX(-130%); }
  55%, 100% { transform: translateX(130%); }
}
/* Pay and empty travel together at one end of the bar; the totals stay at
   the other. */
.cartbar .cart-actions { display: flex; align-items: center; gap: 8px; }

/* The bar's two secondary controls, as one pair.
   They were different shapes and different sizes — one a 48px pill that
   dropped its word on a narrow phone, the other a 34x40 box — sitting
   next to each other and reading as two unrelated things. They are the
   same square now, and quiet beside the paying button: a soft chip, not
   a second call to action. Each takes its own colour only under the hand
   about to press it, which is where the two do differ — one empties the
   cart, the other just gets out of the way. */
.cartbar .btn-clear,
.cart-hide {
  flex: none; width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; cursor: pointer;
  transition: color .16s ease, border-color .16s ease,
              background .16s ease, transform .16s ease;
}
.cartbar .btn-clear svg,
.cart-hide svg { width: 18px; height: 18px; flex: none; }
.cartbar .btn-clear:hover,
.cartbar .btn-clear:focus-visible {
  color: var(--hot, #d64545); border-color: currentColor;
  background: color-mix(in srgb, var(--hot, #d64545) 8%, transparent);
}
.cart-hide:hover, .cart-hide:focus-visible {
  color: var(--brand); border-color: var(--brand); background: var(--brand-soft);
}
.cartbar .btn-clear:active, .cart-hide:active { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) {
  .cartbar .btn-clear, .cart-hide { transition: color .16s ease, border-color .16s ease, background .16s ease; }
  .cartbar .btn-clear:active, .cart-hide:active { transform: none; }
}

.cartbar .totals { display: flex; align-items: center; gap: 12px; }
.cartbar .amount { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.cartbar .amount b { font-size: 19px; font-weight: 700; }
.cartbar .amount span { font-size: 11px; color: var(--muted); }
.cartbar .badge {
  min-width: 34px; height: 30px; padding: 0 8px; border-radius: 10px; background: var(--brand); color: var(--on-brand);
  display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 700;
}
.cartbar .badge.bump { animation: mc-bump .45s cubic-bezier(.34,1.56,.64,1) both; }

/* ---- bottom nav --------------------------------------------------- */
.bottomnav {
  position: fixed; bottom: 0; right: 0; left: 0; z-index: 20;
  height: 72px; border-top: 1px solid var(--line); background: var(--surface);
  display: grid; grid-template-columns: repeat(3, 1fr); align-items: center;
  padding-bottom: env(safe-area-inset-bottom);
}
.bottomnav a {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  color: var(--muted); font-size: 11px;
  transition: color .18s ease;
}
.bottomnav a svg { width: 22px; height: 22px; }
/* Colour and weight carry "you are here" on their own. There used to be
   a brand bar on the tab's top edge as well; it read as a stray rule
   across the nav rather than as a marker, the same reason the desktop
   nav's underline came out. */
.bottomnav a.on { color: var(--brand); font-weight: 700; }
.bottomnav a.on svg { animation: mc-tab-pop .3s cubic-bezier(.34,1.56,.64,1) both; }
@keyframes mc-tab-pop {
  0% { transform: translateY(1px) scale(.9); }
  100% { transform: translateY(0) scale(1); }
}
@media (min-width: 900px) { .bottomnav { display: none; } }

/* desktop nav */
.desknav { display: none; align-items: center; gap: 6px; }
@media (min-width: 900px) { .desknav { display: flex; } }
.desknav a {
  position: relative;
  padding: 10px 18px; border-radius: var(--radius); font-size: 14px; color: var(--muted);
  transition: color .18s ease, background .18s ease;
}
.desknav a:hover { color: var(--ink); background: var(--surface2); }
.desknav a.on {
  background: var(--brand-soft); color: var(--brand); font-weight: 700;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--brand) 35%, transparent);
}
/* No underline: the tinted fill plus the brand ring above already say
   "you are here", and a bar hanging below the button read as a second,
   competing marker. (The bottom tab bar keeps its own top-edge marker —
   there is no ring down there to carry the job.) */

/* ---- auth / forms ------------------------------------------------- */
.auth-card { max-width: 420px; margin: 6vh auto 0; text-align: center; }
/* Reuses the animated empty-state glyph (see .empty-ic) so the sign-in
   screen speaks the same language as كروتي. */
.auth-ic { margin-bottom: 18px; }
.auth-card h1 { font-size: 26px; font-weight: 700; }
.auth-card .sub { font-size: 14px; color: var(--muted); margin: 6px auto 0; max-width: 30ch; }
.form { display: flex; flex-direction: column; gap: 14px; margin-top: 30px; text-align: right; }
/* `display: flex` above outranks the UA rule for [hidden], so a form put
   away in script — the lookup, once it has an answer to show instead —
   would otherwise stay on screen under it. */
.form[hidden] { display: none; }
.form label { font-size: 13px; font-weight: 600; color: var(--muted); }
.field {
  width: 100%; padding: 15px 18px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); font-size: 16px; color: var(--ink);
}
.field:focus { border-color: var(--brand); outline: none; }
.field.ltr { direction: ltr; text-align: left; }
/* The number owns its own stage now, so give it room and centre it —
   a 10-digit MSISDN is easier to proof-read big and tracked out. The
   doubled class is deliberate: it has to outrank .field.ltr's
   text-align: left, which matches at the same specificity. */
.field.field-phone {
  height: 68px; padding: 0 16px;
  font-size: 25px; font-weight: 700; letter-spacing: .12em;
  text-align: center;
}
.field.field-phone::placeholder {
  font-size: 17px; font-weight: 500; letter-spacing: .06em; opacity: .5;
}
/* The registration code borrows the phone field's stage — same job (a
   short string of digits that has to be proof-read), same shape — but
   tracked wider still, because six characters have the room for it. */
.field.field-code {
  height: 68px; padding: 0 16px;
  font-size: 28px; font-weight: 700; letter-spacing: .38em;
  /* The tracking is applied to the right of each glyph, so the string
     sits visually off-centre unless the trailing space is paid back. */
  text-indent: .38em; text-align: center;
}
.field.field-code::placeholder { opacity: .28; letter-spacing: .3em; }
.btn-primary {
  height: 56px; background: var(--brand); color: var(--on-brand);
  border-radius: var(--radius); font-size: 16px; font-weight: 700; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: center; width: 100%;
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-primary:disabled { opacity: .6; cursor: wait; filter: none; }
.btn-ghost {
  height: 48px; border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--radius); font-size: 14px; font-weight: 600; color: var(--muted);
  display: flex; align-items: center; justify-content: center; width: 100%;
}
.alert {
  padding: 13px 16px; border-radius: var(--radius); font-size: 14px;
  margin-top: 18px; text-align: right;
}
.alert.err { background: var(--hot-soft); color: var(--hot); }
.alert.ok { background: var(--brand-soft); color: var(--brand); }
/* A heads-up, not a refusal: "one try left" has to read differently from
   "that failed", or the customer stops distinguishing the two. Quieter
   than .err on purpose — nothing has gone wrong yet. */
.alert.warn {
  background: var(--surface2); color: var(--muted);
  border: 1px solid var(--line); font-size: 13px;
}
.fineprint { font-size: 12px; color: var(--muted); margin-top: 12px; }

/* Secondary ways out of an auth screen — register, recover, resend.
   Deliberately quiet: they sit below the one button the screen is
   actually about and must not compete with it. */
.auth-alt {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  margin-top: 18px;
}
.link-btn {
  display: inline-block; padding: 8px 4px; background: none; border: 0;
  font: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--brand); cursor: pointer;
  text-decoration: underline; text-underline-offset: 4px;
  text-decoration-color: color-mix(in srgb, var(--brand) 35%, transparent);
  transition: text-decoration-color .18s ease;
}
.link-btn:hover { text-decoration-color: var(--brand); }
.auth-recover { width: 100%; }
.auth-recover summary {
  list-style: none; text-align: center;
}
.auth-recover summary::-webkit-details-marker { display: none; }
/* The form inside is already a .form (30px top margin); inside a folded
   panel that reads as a gap, so it is pulled back up. */
.auth-recover[open] .form { margin-top: 14px; }
.auth-recover .btn-ghost { margin-top: 4px; }

/* ---- checkout sheet ----------------------------------------------- */
/* ---- confirm dialog ----------------------------------------------- */
/* Replaces window.confirm, which renders as browser chrome bearing the
   domain name and can't be styled. Above the checkout sheet's z-index so
   it can also confirm something raised from inside it. */
.confirm-wrap {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.confirm-backdrop {
  position: absolute; inset: 0; background: rgba(8, 16, 12, .45);
  opacity: 0;
  -webkit-backdrop-filter: blur(0px); backdrop-filter: blur(0px);
  transition: opacity .24s ease, -webkit-backdrop-filter .3s ease, backdrop-filter .3s ease;
}
.confirm-wrap.in .confirm-backdrop {
  opacity: 1;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.confirm-box {
  position: relative; width: 100%; max-width: 380px;
  padding: 22px 20px 18px; text-align: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  opacity: 0; transform: translateY(14px) scale(.97);
  transition: opacity .24s ease, transform .28s cubic-bezier(.34, 1.4, .64, 1);
}
.confirm-wrap.in .confirm-box { opacity: 1; transform: none; }
.confirm-ic {
  width: 46px; height: 46px; margin: 0 auto 12px; border-radius: 13px;
  display: flex; align-items: center; justify-content: center;
  background: var(--hot-soft); color: var(--hot);
}
.confirm-ic svg { width: 22px; height: 22px; }
.confirm-box h3 { font-size: 16px; font-weight: 700; }
.confirm-msg { margin-top: 8px; font-size: 13.5px; line-height: 1.65; color: var(--ink); }
.confirm-detail { margin-top: 6px; font-size: 12px; line-height: 1.6; color: var(--muted); }
.confirm-actions { display: flex; gap: 8px; margin-top: 18px; }
.confirm-actions button {
  flex: 1; height: 44px; border-radius: var(--radius);
  font-size: 14px; font-weight: 700; transition: background .18s ease, border-color .18s ease;
}
.confirm-cancel { background: var(--surface2); border: 1px solid var(--line); color: var(--ink); }
.confirm-cancel:hover { border-color: var(--muted); }
.confirm-ok { background: var(--hot); border: 1px solid transparent; color: #fff; }
.confirm-ok:hover { filter: brightness(1.06); }
/* The dialog that has to ASK something, not only confirm it — recovering
   a password needs the mobile number, and a box for one field is cheaper
   than a page for it. Sized like the sign-in fields it stands in for, so
   the number looks the same here as it does behind the dialog. */
.confirm-field { margin-top: 14px; text-align: start; }
.confirm-field[hidden] { display: none; }
.confirm-field-label {
  display: block; margin-bottom: 6px; padding-inline-start: 2px;
  font-size: 12px; font-weight: 700; color: var(--muted);
}
.confirm-field .confirm-input { width: 100%; }
/* Shorter than the sign-in field's 64px: inside a dialog that already
   carries an icon, a heading and two lines of copy, the full height
   pushes the buttons off a small screen. */
.confirm-field .field.field-mid { height: 54px; font-size: 18px; }
/* The shared rule tucks the message up under a field that has margin
   below it; this one sits in its own box and needs the space back. */
.confirm-field .field-msg { margin-top: 7px; }

@media (prefers-reduced-motion: reduce) {
  .confirm-backdrop, .confirm-box { transition: none; }
}

.sheet-wrap { position: fixed; inset: 0; z-index: 60; display: flex; }
/* `display: flex` above outranks the UA rule for [hidden], so without this
   the closed sheet keeps a full-screen backdrop over the page and silently
   eats every click on the catalog. */
.sheet-wrap[hidden] { display: none; }
.sheet-backdrop {
  position: absolute; inset: 0; background: rgba(8, 16, 12, .45);
  opacity: 0;
  -webkit-backdrop-filter: blur(0px); backdrop-filter: blur(0px);
  transition: opacity .26s ease, -webkit-backdrop-filter .32s ease, backdrop-filter .32s ease;
}
.sheet-wrap.in .sheet-backdrop {
  opacity: 1;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
body.sheet-open { overflow: hidden; }

.sheet {
  position: relative; margin-top: auto; width: 100%; max-height: 92dvh;
  display: flex; flex-direction: column;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lift);
  transform: translateY(100%); transition: transform .24s cubic-bezier(.22,1,.36,1);
}
.sheet-wrap.in .sheet { transform: translateY(0); }
@media (min-width: 720px) {
  .sheet-wrap { align-items: center; justify-content: center; padding: 24px; }
  .sheet {
    margin: 0; max-width: 460px; max-height: 88dvh;
    border: 1px solid var(--line); border-radius: var(--radius-lg);
    transform: translateY(14px) scale(.98);
  }
  .sheet-wrap.in .sheet { transform: translateY(0) scale(1); }
}
.sheet-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 16px 18px 12px; border-bottom: 1px solid var(--line);
}
.sheet-head h2 { font-size: 17px; font-weight: 700; }
.sheet-x {
  flex: none; width: 34px; height: 34px; border-radius: 10px;
  background: var(--surface2); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
}
.sheet-x svg { width: 17px; height: 17px; }
.sheet-x:hover { color: var(--ink); border-color: var(--brand); }
/* While the payment is in flight there is no way out — hide the exits
   rather than let a tap strand a paid order. */
.sheet.locked .sheet-x { visibility: hidden; }
.sheet-body { overflow-y: auto; padding: 16px 18px 22px; -webkit-overflow-scrolling: touch; }

/* ---- stage indicator ---------------------------------------------- */
.steps { display: flex; align-items: center; gap: 6px; list-style: none; margin-bottom: 18px; }
.step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; position: relative; }
.step-no {
  width: 28px; height: 28px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 700;
  background: var(--surface2); color: var(--muted); border: 1px solid var(--line);
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.step-label { font-size: 10px; color: var(--muted); text-align: center; line-height: 1.25; }
.step.on .step-no { background: var(--brand); color: var(--on-brand); border-color: transparent; }
.step.on .step-label { color: var(--brand); font-weight: 700; }
.step.done .step-no { background: var(--brand-soft); color: var(--brand); border-color: transparent; }
/* Connector running back to the previous step. Anchored with logical
   properties so it reaches toward the step before it in RTL as well —
   the earlier right/left pair drew it on the wrong side. It spans centre
   to centre minus the badge radius and a little breathing room. */
.step + .step::before {
  content: ''; position: absolute; top: 13px; height: 2px; border-radius: 2px;
  inset-inline-end: calc(50% + 20px); width: calc(100% - 40px);
  background: var(--line);
  transition: background .25s ease;
}
.step.on::before, .step.done::before { background: var(--brand); }

/* One number, stated plainly. The line-by-line breakdown lives on the
   catalog; at the point of paying, the only figure that matters is what
   is about to leave the customer's wallet. */
.co-summary { padding-bottom: 4px; }
.co-total {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 16px; margin-bottom: 20px;
  background: var(--brand-soft); border-radius: var(--radius);
}
.co-total-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.co-total-value {
  display: flex; align-items: baseline; gap: 6px;
  color: var(--brand); direction: ltr;
}
/* Amount and ₪ are set identically so the pair reads as a single figure
   rather than a number with a superscripted unit. */
.co-total-value .num,
.co-cur { font-size: 40px; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; }
.co-stage { margin-top: 4px; }
.co-title { font-size: 17px; font-weight: 700; margin-bottom: 14px; }
.co-panel .steps { margin-top: 4px; }
.co-done-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.co-done-ic {
  flex: none; width: 34px; height: 34px; border-radius: 10px;
  background: var(--brand); color: var(--on-brand);
  display: flex; align-items: center; justify-content: center;
  animation: mc-pop .45s cubic-bezier(.34,1.56,.64,1) both;
}
.co-done-ic svg { width: 18px; height: 18px; }
.co-done-head h3 { font-size: 16px; font-weight: 700; }

/* ---- checkout ----------------------------------------------------- */
.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 18px;
  margin-top: 16px;
}
.panel h2 { font-size: 17px; font-weight: 700; margin-bottom: 12px; }
.summary-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 14px;
}
.summary-row:last-child { border-bottom: 0; }
.summary-row .meta { color: var(--muted); font-size: 12px; }
.summary-total {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 14px; margin-top: 6px; border-top: 1px dashed var(--line);
  font-weight: 700; font-size: 17px;
}
/* ---- OTP field -----------------------------------------------------
   One input, styled as one field. It held six squares drawn over it for
   a while; they looked deliberate but read as six separate boxes, and a
   code is one value. This is that value, shown as one wide field with
   the digits spaced out so they are still countable at a glance. */
.otp-row { direction: ltr; margin-top: 8px; }
.otp-input {
  width: 100%; height: 60px; text-align: center; padding: 0;
  font-size: 26px; font-weight: 800;
  /* The tracking is what makes six digits legible at a glance. It adds
     a trailing gap after the last one, so the same amount of indent
     puts the run back in the middle of the field. */
  letter-spacing: .42em; text-indent: .42em;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); caret-color: var(--brand);
  transition: border-color .16s ease, box-shadow .16s ease, background .16s ease;
}
.otp-input::placeholder {
  color: var(--muted); opacity: .38; font-weight: 600; font-size: 22px;
}
.otp-input:focus {
  border-color: var(--brand); outline: none;
  box-shadow: 0 0 0 4px var(--brand-soft);
}
/* A complete code says so before the button is pressed. */
.otp-input.full {
  border-color: var(--brand); background: var(--brand-soft);
}
/* A rejected code: the field carries the state its message describes,
   so the two are not read as unrelated. */
.otp-row.bad .otp-input {
  border-color: var(--hot); background: var(--hot-soft);
}
.otp-row.bad .otp-input:focus { box-shadow: 0 0 0 4px var(--hot-soft); }

/* ---- payment method picker ---------------------------------------- */
.pay-methods { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.pay-method {
  position: relative; display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 13px 14px; text-align: right;
  background: var(--surface); border: 1.5px solid var(--line);
  border-radius: var(--radius); color: var(--ink);
  transition: border-color .18s ease, background .18s ease;
}
.pay-method:not(:disabled):hover { border-color: var(--brand); }
.pay-method.on { border-color: var(--brand); background: var(--brand-soft); }
.pay-method:disabled { cursor: not-allowed; opacity: .62; }

.pm-logo {
  flex: none; width: 44px; height: 44px; border-radius: 10px;
  background: var(--surface2); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; color: var(--muted);
  overflow: hidden;
}
/* Same background-image treatment as the orbit mark: a missing file
   leaves a clean empty tile rather than a broken-image glyph. */
.pm-logo-jawwal { background: center / contain no-repeat url("../img/jawwal-pay.png"); }
.pm-logo-sadad svg { width: 21px; height: 21px; }

.pm-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.pm-name { font-size: 13.5px; font-weight: 700; line-height: 1.35; }
.pm-note { font-size: 11px; color: var(--muted); }

.pm-tick {
  flex: none; width: 22px; height: 22px; border-radius: 7px;
  background: var(--brand); color: var(--on-brand);
  display: none; align-items: center; justify-content: center;
}
.pm-tick svg { width: 13px; height: 13px; }
.pay-method.on .pm-tick { display: flex; animation: mc-pop .3s cubic-bezier(.34,1.56,.64,1) both; }

.pm-soon {
  flex: none; padding: 4px 9px; border-radius: 7px;
  background: var(--hot-soft); color: var(--hot);
  font-size: 10.5px; font-weight: 700; white-space: nowrap;
}

/* Jawwal Pay mark with a spinner orbiting it while the charge is in
   flight. The ring is a spinner, so it keeps its 50% — that is the one
   documented exception to the rounded-rectangle rule.
   The logo is a background-image on purpose: if the file is missing the
   slot renders empty and the ring still spins, instead of showing a
   broken-image glyph mid-payment. */
.pay-orbit {
  position: relative; width: 112px; height: 112px;
  margin: 6px auto 16px; display: grid; place-items: center;
}
.pay-orbit-ring {
  position: absolute; inset: 0; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--brand);
  animation: mc-spin 1s linear infinite;
}
.pay-orbit-logo {
  width: 74px; height: 74px; border-radius: var(--radius);
  background: center / contain no-repeat url("../img/jawwal-pay.png");
  animation: mc-orbit-pulse 1.8s ease-in-out infinite;
}
.pay-orbit-note { text-align: center; font-size: 13px; color: var(--muted); margin-bottom: 16px; }
@keyframes mc-orbit-pulse {
  0%, 100% { transform: scale(1); opacity: .92; }
  50% { transform: scale(1.05); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .pay-orbit-ring, .pay-orbit-logo { animation: none; }
}

/* Primary action paired with an icon-only secondary on the inline end. */
.btn-row { display: flex; align-items: stretch; gap: 10px; }
.btn-row .btn-primary { flex: 1; min-width: 0; gap: 9px; }
.btn-row .btn-primary svg { width: 18px; height: 18px; flex: none; }
.btn-icon {
  flex: none; width: 56px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: color .18s ease, border-color .18s ease;
}
.btn-icon svg { width: 19px; height: 19px; }
.btn-icon:hover { color: var(--ink); border-color: var(--brand); }

/* Glow + sweep for the button that commits the step, same treatment the
   pay bar uses so the primary action reads the same everywhere. */
.btn-shine { position: relative; overflow: hidden; animation: mc-glow 2.4s ease-in-out infinite; }
.btn-shine::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(120deg, transparent 35%, rgba(255,255,255,.38) 50%, transparent 65%);
  transform: translateX(-130%);
  animation: mc-shine 3s ease-in-out infinite;
  pointer-events: none;
}
.btn-shine:disabled { animation: none; }
.btn-shine:disabled::after { display: none; }

/* Resend control. The countdown lives inside the button rather than beside
   it, and the fill drains across it as the cooldown runs — the numeral and
   the bar are the same control. Cosmetic either way: the API owns the
   budget and re-checks it. */
.resend-btn {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 42px; margin-top: 14px;
  border-radius: var(--radius); background: var(--surface2);
  border: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--brand);
  transition: color .18s ease, border-color .18s ease;
}
.resend-fill {
  position: absolute; inset-inline-start: 0; top: 0; bottom: 0; width: 0;
  background: var(--brand-soft);
}
.resend-face {
  position: relative; display: flex; align-items: center; gap: 8px;
}
.resend-face svg { width: 15px; height: 15px; flex: none; }
.resend-btn:not(:disabled) .resend-face svg { animation: mc-spin 4s linear infinite; }
.rs-timer:not(:empty) { font-weight: 700; color: var(--muted); }
.rs-timer:not(:empty)::before { content: '·'; margin-inline-end: 8px; opacity: .6; }
.resend-btn:not(:disabled):hover { border-color: var(--brand); }
.resend-btn:disabled { color: var(--muted); cursor: not-allowed; }

.pay-steps { display: flex; flex-direction: column; gap: 14px; padding: 8px 0; }
.pay-step { display: flex; align-items: center; gap: 12px; font-size: 14px; color: var(--muted); }
.pay-step .dot {
  flex: none; width: 18px; height: 18px; border-radius: 5px;
  background: var(--surface2); border: 1px solid var(--line);
}
.pay-step.done { color: var(--ink); font-weight: 600; }
.pay-step.done .dot { background: var(--brand); border-color: var(--brand); animation: mc-pop .35s ease both; }
.paybar { height: 6px; border-radius: 3px; background: var(--surface2); overflow: hidden; margin-top: 10px; }
.paybar i { display: block; height: 100%; width: 0; background: var(--brand); border-radius: 3px; transition: width .6s cubic-bezier(.22,1,.36,1); }
.spinner {
  width: 22px; height: 22px; border-radius: 50%; margin: 0 auto;
  border: 3px solid var(--line); border-top-color: var(--brand);
  animation: mc-spin .8s linear infinite;
}

/* success card reveal */
.card-code {
  display: block; width: 100%; margin-top: 10px; padding: 16px;
  border-radius: var(--radius); text-align: center; font-size: 21px; font-weight: 700;
  letter-spacing: .14em; background: var(--brand-soft); color: var(--ink);
  border: 1px dashed var(--line); direction: ltr;
}

/* ---- the bought cards, on the done stage ----------------------------
   A voucher is a username, a password and a portal to use them on. The
   green box used to hold only the PIN, which left the serial in a grey
   line above it and neither of them copyable. */
.dcard {
  margin-top: 12px; padding: 12px; border-radius: var(--radius);
  background: var(--brand-soft); border: 1px dashed var(--line);
}
.dcard-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px; padding: 0 2px;
}
.dcard-name { font-size: 13px; font-weight: 700; color: var(--ink); }

/* Side by side where there is room for it, stacked where there is not —
   two credentials squeezed into half a phone's width would truncate the
   thing the customer came for. */
.dcard-creds { display: flex; flex-wrap: wrap; gap: 8px; }
.dcred {
  flex: 1 1 190px; min-width: 0;
  display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.dcred-ic { flex: none; color: var(--brand); display: flex; }
.dcred-ic svg { width: 17px; height: 17px; }
.dcred-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.dcred-label { font-size: 10.5px; color: var(--muted); line-height: 1.3; }
.dcred-value {
  font-size: 15.5px; font-weight: 700; color: var(--ink);
  letter-spacing: .06em; direction: ltr; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.dcard-login {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 8px; height: 42px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--brand);
  color: var(--brand); font-size: 13.5px; font-weight: 700;
  transition: background .18s ease, color .18s ease;
}
.dcard-login svg { width: 17px; height: 17px; flex: none; }
.dcard-login:hover { background: var(--brand); color: var(--on-brand); }

/* Two ways out, neither of them the afterthought. */
/* First-purchase password. Sits between the codes and the way out, so it
   reads as one more step of the purchase rather than an unrelated form. */
.done-pw {
  margin-top: 14px; padding: 14px;
  background: var(--surface2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.done-pw.hidden { display: none; }
.done-pw-title { font-size: 15px; margin-bottom: 4px; }
.done-pw-note { font-size: 13px; color: var(--muted); margin-bottom: 10px; }
.done-pw-note .num { color: var(--ink); font-weight: 700; }
/* The generate button sits on the field's line: taking the offered password
   and typing your own are the same decision, so they belong together rather
   than one under the other. */
.done-pw-field { display: flex; align-items: stretch; gap: 8px; }
/* width:auto beats the width:100% both of these inherit — .field for being a
   full-width form control, .btn-ghost for being a full-width button. Left
   alone in a flex row, each resolves its flex-basis from that 100% and the
   two fight over the whole line: the button won, the input collapsed to a
   sliver, and the pair overflowed the card. */
.done-pw-field .field {
  flex: 1 1 auto; width: auto; min-width: 0; margin: 0;
  /* The card already sits on --surface2; the field's lifted shadow reads as
     a second card floating inside the first. */
  box-shadow: none;
  padding: 13px 14px; font-size: 15px;
}
.done-pw-gen {
  flex: 0 0 auto; width: auto; height: auto;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 0 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); font-size: 14px; font-weight: 600;
}
.done-pw-gen svg { width: 16px; height: 16px; flex: none; }
.done-pw-gen:hover { border-color: var(--brand); color: var(--brand); }
.done-pw-hint { font-size: 12px; color: var(--muted); margin-top: 8px; }
.done-pw .alert { margin-top: 10px; }
.done-pw .btn-primary { margin-top: 12px; width: 100%; }

.done-actions { display: flex; align-items: stretch; gap: 10px; }
.done-actions .btn-primary,
.done-actions .btn-ghost { flex: 1 1 0; min-width: 0; gap: 8px; }
.done-actions .btn-ghost { display: flex; align-items: center; justify-content: center; }
.done-actions svg { width: 17px; height: 17px; flex: none; }
.done-actions span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- my cards ----------------------------------------------------- */
.cards-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 14px;
  margin-top: 18px;
}
.cards-head h1 { font-size: 21px; font-weight: 700; }
.cards-head p { font-size: 13px; color: var(--muted); margin-top: 3px; }
/* Names the browser the list belongs to, so "this device" is concrete. */
.device-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 5px 11px; border-radius: 9px;
  background: var(--surface2); border: 1px solid var(--line);
  font-size: 11.5px; font-weight: 600; color: var(--ink);
}
.device-chip svg { width: 12px; height: 12px; flex: none; color: var(--brand); }
.cards-tally {
  flex: none; display: flex; flex-direction: column; align-items: center;
  padding: 8px 14px; border-radius: var(--radius); background: var(--brand-soft);
}
.cards-tally b { font-size: 20px; font-weight: 800; color: var(--brand); line-height: 1.1; }
.cards-tally span { font-size: 10.5px; color: var(--brand); opacity: .8; }

/* ---- my cards: filter chips (store / category) --------------------- */
/* One row per facet: a label, then the values. Rows scroll sideways rather
   than wrap, so a store with a dozen categories doesn't push the vouchers
   off the first screen. */
.card-filters {
  display: flex; flex-direction: column; gap: 8px; margin-top: 16px;
  padding: 12px 14px; border-radius: var(--radius-lg);
  background: var(--surface); border: 1px solid var(--line);
}
.card-filters[hidden] { display: none; }
.filter-row { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.filter-label {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding-top: 6px;   /* sit on the first chip row's text, not above it */
  font-size: 11.5px; font-weight: 700; color: var(--muted);
}
.filter-label svg { width: 13px; height: 13px; flex: none; opacity: .8; }
/* Wraps rather than scrolls: an RTL scroll container's initial position is
   not agreed on between engines, and a chip row that opens half-scrolled
   hides the "الكل" reset the user needs most. */
.chips { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* Scoped: `.chip` is the catalog's control too, and these metrics are the
   compact variant this bar needs — a page-wide override would shrink it. */
.card-filters .chip {
  flex: none; gap: 5px; white-space: nowrap;
  padding: 6px 11px; border-radius: 9px;
  background: var(--surface2); color: var(--ink);
  font-size: 12px; font-weight: 600;
}
/* The resting background above ties with `.chip.on` on specificity and wins
   only by coming later in the file, which would leave the selected chip
   grey here and green on the catalog. Re-assert it rather than rely on
   source order staying put. */
.card-filters .chip.on { background: var(--brand); color: var(--on-brand); }
/* Shown by the filter code when a combination matches nothing. */
.filter-none {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 18px; padding: 22px; border-radius: var(--radius-lg);
  border: 1px dashed var(--line); color: var(--muted); font-size: 13px;
}
.filter-none[hidden] { display: none; }
.filter-none svg { width: 16px; height: 16px; flex: none; }

/* ---- my cards: invoices, each holding its vouchers ----------------- */
.order-list { display: flex; flex-direction: column; gap: 18px; margin-top: 16px; }
/* Filtered out. Stated explicitly so a future `display:` on either rule
   can't quietly outrank the UA's [hidden] and leave ghosts in the list. */
.order[hidden], .voucher[hidden] { display: none; }
/* Not clipped. The card used to hide its overflow so the header's tinted
   band would follow the rounded top corners — but that also cut the
   tooltip off the buttons in the header, which sit right at the card's
   edge. The band rounds its own corners now, and the card lets its
   overlays out. */
.order {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
  animation: mc-rise .4s ease both;
}
.order-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 10px 12px; flex-wrap: wrap;
  padding: 13px 16px; background: var(--surface2);
  border-bottom: 1px solid var(--line);
  /* Inside the card's 1px border, so the tint meets the corner instead
     of leaving a hairline of surface showing through it. */
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
}
/* Time and the Jawwal reference share the top line, small; the invoice
   number sits under them as the order's own identifier. */
/* One fact per line: time, then رقم الحركة, then the invoice number. */
.order-lines { display: flex; flex-direction: column; gap: 3px; }
.order-lines .om { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--muted); }
.order-lines .om svg { width: 12px; height: 12px; flex: none; opacity: .75; }
.order-lines .om .num { color: var(--ink); font-weight: 600; }
.order-inv b { font-size: 12px; font-weight: 700; color: var(--ink); }

.voucher-list { display: flex; flex-direction: column; }
.voucher { padding: 14px 16px; border-bottom: 1px dashed var(--line); }
.voucher:last-child { border-bottom: 0; }
.voucher.used { opacity: .62; }
.voucher-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.voucher-id { min-width: 0; }
.voucher-head h3 { font-size: 14.5px; font-weight: 700; line-height: 1.35; }
.voucher-store {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 3px;
  font-size: 11px; color: var(--muted);
}
.voucher-store svg { width: 11px; height: 11px; flex: none; opacity: .75; }
/* The voucher's shelf, so a filtered list still says what each card is. */
.voucher-cat {
  display: inline-flex; align-items: center; gap: 4px; margin: 3px 8px 0 0;
  padding: 2px 8px; border-radius: 7px;
  background: var(--brand-soft); color: var(--brand);
  font-size: 10.5px; font-weight: 700;
}
.voucher-cat svg { width: 11px; height: 11px; flex: none; }
.voucher-side { flex: none; display: flex; align-items: center; gap: 8px; }
/* Order-level controls, pinned to the head's inline end. Autologin
   carries a label now, and a long reference number beside it will not fit
   a phone — so the head wraps and the controls take their own line rather
   than being pushed off the card's edge. The auto margin keeps them at
   the inline end on either line. */
.order-actions {
  flex: none; display: flex; align-items: center; gap: 6px;
  margin-inline-start: auto;
}
.order-lines { min-width: 0; }
.order-act {
  flex: none; width: 32px; height: 32px; border-radius: 9px;
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.order-act svg { width: 15px; height: 15px; }
.order-act:disabled { opacity: .5; cursor: wait; }
.act-hide:hover { color: var(--hot); border-color: var(--hot); }
.owned-value { display: flex; align-items: baseline; gap: 3px; }
.owned-value b { font-size: 16px; font-weight: 800; color: var(--brand); }
.owned-value span { font-size: 11px; color: var(--muted); }
.status-pill {
  flex: none; padding: 4px 10px; border-radius: 8px;
  font-size: 10.5px; font-weight: 700; background: var(--brand-soft); color: var(--brand);
  white-space: nowrap;
}
.status-pill.off { background: var(--surface2); color: var(--muted); }
/* "مخفي من كروتي" — a card the customer took off their card list. It is
   still on its invoice, and this says which, quietly: it is a note about
   where the card is listed, not a verdict on the card. */
.status-pill.quiet {
  background: transparent; color: var(--muted);
  border: 1px dashed var(--line); font-weight: 600;
}

/* Credential row: label, the value, and a copy control that is icon-only
   and sits right at the number. */
.cred {
  display: flex; align-items: center; gap: 10px; margin-top: 10px;
  padding: 9px 10px 9px 12px;
  background: var(--surface2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.cred-label { flex: none; font-size: 11px; color: var(--muted); min-width: 66px; }
.cred-value {
  flex: 1; min-width: 0; font-size: 17px; font-weight: 700; color: var(--ink);
  letter-spacing: .08em; direction: ltr; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cred-copy {
  flex: none; width: 34px; height: 34px; border-radius: 9px;
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.cred-copy svg { width: 15px; height: 15px; }
.cred-copy:hover { color: var(--brand); border-color: var(--brand); }
.cred-copy.copied { background: var(--brand); border-color: transparent; color: var(--on-brand); }

/* ---- "كروت سابقة" — the lookup, as a dialog -------------------------
   The form used to stand above the card list for every guest, which made
   a wallet read as a search page. Looking a purchase up from another
   device is the exception, so it is behind a button now.

   Nothing here applies until store.js adds .rcv-live: without it the
   panel is an ordinary panel on the page, and its plain POST works the
   way it always did. */
/* Sized and shaped to the device chip it stands beside — same corner,
   same height, same weight. It arrived as a pill, which is the one shape
   this site does not use anywhere (see the radius tokens), and beside a
   rounded rectangle it read as a control borrowed from somewhere else. */
.rcv-open {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 5px 11px; border-radius: 9px;
  background: var(--surface2); border: 1px solid var(--line);
  color: var(--ink); font-size: 11.5px; font-weight: 600;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.rcv-open svg { width: 12px; height: 12px; flex: none; color: var(--brand); }
.rcv-open:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
.rcv-open:hover svg { color: inherit; }
.rcv-open:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.rcv-wrap.rcv-live { display: none; }
.rcv-wrap.rcv-live.is-open {
  display: flex; align-items: center; justify-content: center;
  position: fixed; inset: 0; z-index: 80; padding: 20px;
}
.rcv-wrap.rcv-live .rcv-backdrop {
  position: absolute; inset: 0; background: rgba(8, 16, 12, .45);
  opacity: 0;
  -webkit-backdrop-filter: blur(0px); backdrop-filter: blur(0px);
  transition: opacity .24s ease, -webkit-backdrop-filter .3s ease, backdrop-filter .3s ease;
}
.rcv-wrap.rcv-live.in .rcv-backdrop {
  opacity: 1;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
/* Same entrance as the confirm dialog: rise and settle, so the two
   popups on this site do not each have their own idea of how a dialog
   arrives. */
.rcv-wrap.rcv-live .recover {
  position: relative; width: 100%; max-width: 420px; margin: 0;
  max-height: 88dvh; overflow-y: auto;
  box-shadow: var(--shadow-lift);
  opacity: 0; transform: translateY(12px) scale(.97);
  transition: opacity .22s ease, transform .22s cubic-bezier(.22,1,.36,1);
}
.rcv-wrap.rcv-live.in .recover { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .rcv-wrap.rcv-live .recover { transition: opacity .01ms; transform: none; }
}
.rcv-x {
  position: absolute; inset-block-start: 12px; inset-inline-end: 12px;
  width: 32px; height: 32px; border-radius: 10px;
  background: var(--surface2); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: color .18s ease, border-color .18s ease;
}
.rcv-x svg { width: 16px; height: 16px; }
.rcv-x:hover { color: var(--hot); border-color: var(--hot); }

/* The answer, in the same box the question was asked in. */
.rcv-result {
  text-align: center; padding: 6px 2px 2px;
  opacity: 0; transform: translateY(8px);
  transition: opacity .24s ease, transform .24s cubic-bezier(.22,1,.36,1);
}
.rcv-result.in { opacity: 1; transform: none; }
.rcv-result-ic {
  width: 52px; height: 52px; margin: 4px auto 14px; border-radius: 16px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-soft); color: var(--brand);
  animation: rcv-pop .34s cubic-bezier(.22,1.4,.4,1) both;
}
.rcv-result-ic svg { width: 26px; height: 26px; }
@keyframes rcv-pop {
  from { transform: scale(.5); opacity: 0; }
  to   { transform: none;      opacity: 1; }
}
.rcv-result-title { font-size: 16px; font-weight: 700; }
.rcv-result-sub { margin-top: 6px; font-size: 13px; color: var(--muted); }
.rcv-result .btn-primary { margin-top: 16px; }
.rcv-result .btn-ghost { margin-top: 8px; }
/* The submit carries its icon inside .btn-label, so the spinner and the
   tick fade it out along with the words rather than landing on top. */
.recover .btn-label {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.recover .btn-label svg { width: 17px; height: 17px; flex: none; }
@media (prefers-reduced-motion: reduce) {
  .rcv-result, .rcv-result-ic { animation: none; transition: none; opacity: 1; transform: none; }
}

@media (min-width: 900px) {
  .order-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); align-items: start; }
}

/* ---- account ------------------------------------------------------ */
.profile-head { display: flex; align-items: center; gap: 14px; margin-top: 18px; }
.avatar {
  width: 56px; height: 56px; border-radius: var(--radius); background: var(--brand-soft);
  color: var(--brand); display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; overflow: hidden; flex: none;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-head h2 { font-size: 19px; font-weight: 700; }
.profile-head .sub { font-size: 13px; color: var(--muted); }
.kv { display: flex; align-items: center; justify-content: space-between; padding: 13px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.kv:last-child { border-bottom: 0; }
.kv .k { color: var(--muted); }

/* ---- states ------------------------------------------------------- */
.empty { text-align: center; padding: 60px 20px; color: var(--muted); }
.empty .big { font-size: 44px; margin-bottom: 10px; }
.empty-alt { margin-top: 22px; font-size: 12.5px; }
.empty-alt a { color: var(--brand); font-weight: 700; text-decoration: underline; }

/* Animated empty-state glyph (Lucide outlines, self-drawing).
   The icon traces itself, its detail strokes pop in behind it, and the
   disc keeps a slow breath so the state reads as waiting, not broken. */
.empty-ic {
  position: relative;
  width: 84px; height: 84px; margin: 0 auto 20px;
  display: grid; place-items: center;
  border-radius: var(--radius-lg); background: var(--brand-soft);
  animation: mc-ic-float 3.4s ease-in-out infinite;
}
.empty-ic::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  border: 1.5px solid var(--brand);
  animation: mc-ic-ring 3.4s ease-out infinite;
}
.empty-ic svg { width: 38px; height: 38px; color: var(--brand); overflow: visible; }
/* First path is the silhouette — trace it. 200 comfortably exceeds the
   real path length, so the dash sweep covers the whole outline. */
.empty-ic svg > :first-child {
  stroke-dasharray: 200; stroke-dashoffset: 200;
  animation: mc-ic-draw 1.1s cubic-bezier(.65,0,.35,1) .15s forwards;
}
/* Remaining paths are interior detail — fade them in after the trace. */
.empty-ic svg > :not(:first-child) { opacity: 0; animation: mc-ic-pop .45s ease forwards; }
.empty-ic svg > :nth-child(2) { animation-delay: .85s; }
.empty-ic svg > :nth-child(3) { animation-delay: .97s; }
.empty-ic svg > :nth-child(4) { animation-delay: 1.09s; }
@keyframes mc-ic-draw { to { stroke-dashoffset: 0; } }
@keyframes mc-ic-pop {
  from { opacity: 0; transform: translateY(2px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes mc-ic-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}
@keyframes mc-ic-ring {
  0% { opacity: .4; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(1.35); }
}
/* Motion is decoration here — hand back a static icon when asked.
   These selectors deliberately mirror the specificity of the animated
   rules above; a plain `svg > *` loses to `:first-child` and leaves the
   outline stuck mid-trace. */
@media (prefers-reduced-motion: reduce) {
  .empty-ic { animation: none; }
  .empty-ic::after { animation: none; opacity: 0; }
  .empty-ic svg > :first-child { animation: none; stroke-dashoffset: 0; }
  .empty-ic svg > :not(:first-child) { animation: none; opacity: 1; }
}
.error-page { text-align: center; padding: 16vh 20px 0; }
.error-page .code { font-size: 56px; font-weight: 700; color: var(--brand); }
.error-page p { color: var(--muted); margin-top: 8px; }
.browse-note {
  margin-top: 14px; padding: 13px 16px; border-radius: var(--radius);
  background: var(--surface2); border: 1px dashed var(--line);
  color: var(--muted); font-size: 13px; text-align: center;
}

/* ---- footer (tiny) -------------------------------------------------- */
/* Desktop only: a slim bar fixed to the very bottom of the viewport.
   On mobile the fixed bottom-nav already owns that space, so the
   footer is dropped entirely there rather than stacking a second
   fixed bar or competing for the same edge. */
.mini-footer {
  display: none;
}
@media (min-width: 900px) {
  .mini-footer {
    display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
    gap: 6px; font-size: 12px; color: var(--muted); text-align: center;
    position: fixed; right: 0; left: 0; bottom: 0; z-index: 15;
    padding: 13px 20px; background: var(--surface); border-top: 1px solid var(--line);
  }
}
.mini-footer strong { color: var(--ink); font-weight: 700; }
.mini-footer .num { direction: ltr; unicode-bidi: embed; }
.mini-footer-sep { opacity: .5; }

/* landing */
.landing { text-align: center; padding-top: 18vh; }
.landing .mark {
  width: 72px; height: 72px; border-radius: 22px; background: var(--brand);
  color: var(--on-brand); font-size: 28px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; margin: 0 auto 18px;
  box-shadow: var(--shadow-lift);
}
.landing h1 { font-size: 30px; font-weight: 700; }
.landing p { color: var(--muted); margin-top: 8px; }

/* util */
.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---- مشترياتي: the order ledger ------------------------------------
   كروتي answers "what codes do I have"; this answers "what did I buy".
   The difference drives the layout: a ledger is scanned down the time
   axis, so every row leads with when, and the money — the one number
   a receipt exists for — closes the row on the far side. */
.buy-head { margin: 4px 0 18px; }
.buy-head h1 { font-size: 22px; font-weight: 700; }
.buy-head p { margin-top: 4px; font-size: 13px; color: var(--muted); }

/* Headline totals. auto-fit rather than a fixed count: the stores tile
   only appears once a customer has bought from more than one, and the
   row must not leave a hole when it doesn't. */
/* Two per row on a phone, on purpose. auto-fit fitted three across at
   390px and left the fourth tile stranded alone on its own line — a
   2x2 block reads as a block, a 3+1 reads as a mistake. */
.buy-stats {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 10px; margin-bottom: 20px;
}
@media (min-width: 620px) {
  .buy-stats { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
}
.bstat {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 14px 10px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); text-align: center;
}
.bstat-ic {
  display: grid; place-items: center; width: 30px; height: 30px; margin-bottom: 3px;
  border-radius: 9px; background: var(--brand-soft); color: var(--brand);
}
.bstat-ic svg { width: 16px; height: 16px; }
.bstat b { font-size: 18px; font-weight: 700; line-height: 1.1; }
/* Same treatment as the rows' .buy-cur, so a price reads the same way
   wherever it appears on the page. */
.bstat-cur { font-size: 12px; font-weight: 600; margin-inline-start: 3px; color: var(--muted); }
.bstat-k { font-size: 11.5px; color: var(--muted); }

.buy-list { display: flex; flex-direction: column; gap: 12px; list-style: none; }
.buy { animation: mc-rise .4s ease both; }
/* Stagger, capped: past a handful the cascade stops reading as motion
   and starts reading as lag. */
.buy:nth-child(2) { animation-delay: .04s; }
.buy:nth-child(3) { animation-delay: .08s; }
.buy:nth-child(4) { animation-delay: .12s; }
.buy:nth-child(n+5) { animation-delay: .16s; }

.buy-link {
  display: flex; align-items: stretch; gap: 12px;
  padding: 13px 14px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); color: var(--ink);
  transition: border-color .18s ease, transform .18s ease;
}
.buy-link:hover { border-color: var(--brand); transform: translateY(-1px); }
.buy-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The time rail. Its own column so every row's clock aligns down the
   list — that vertical edge is what makes a ledger scannable. */
.buy-when {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; flex: none; width: 70px; padding-inline-end: 12px;
  border-inline-end: 1px solid var(--line);
}
/* Clock and meridiem are one reading, so they sit on one line — the
   ص/م set smaller so the digits still carry the row. */
.buy-clock { display: inline-flex; align-items: baseline; gap: 2px; white-space: nowrap; }
.buy-when b { font-size: 15px; font-weight: 700; line-height: 1; }
.buy-mer { font-size: 9.5px; font-weight: 600; color: var(--muted); }
/* nowrap is load-bearing: a wrapped date turns the rail into two ragged
   lines and the column stops aligning, which is the only thing it is for. */
.buy-when span { font-size: 10.5px; color: var(--muted); white-space: nowrap; }
.buy-when .buy-year { font-size: 9.5px; opacity: .75; }

/* The strip at the foot belongs to the row's controls. Reserved here
   rather than by keeping the chips clear of the buttons horizontally:
   the buttons are as wide as their labels, and إرجاع للمشتريات is not
   إخفاء, so any offset that clears one runs under the other. Nothing
   flows into the strip, whatever the label says. */
.buy-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px;
  padding-block-end: 34px; }
.buy-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.buy-store {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
  font-size: 13px; font-weight: 700;
}
.buy-store svg { width: 14px; height: 14px; flex: none; color: var(--brand); }
.buy-store img {
  width: 18px; height: 18px; flex: none;
  border-radius: 5px; object-fit: contain; background: var(--surface2);
}
.buy-ago { font-size: 11px; color: var(--muted); flex: none; }

.buy-inv { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--muted); }
.buy-inv svg { width: 12px; height: 12px; flex: none; opacity: .75; }
.buy-inv b { font-size: 12px; font-weight: 700; color: var(--ink); }

.buy-facts { display: flex; flex-wrap: wrap; gap: 5px 6px; }
.bfact {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 7px; border-radius: 999px;
  background: var(--surface2); border: 1px solid var(--line);
  font-size: 10.5px; color: var(--muted);
}
.bfact svg { width: 11px; height: 11px; flex: none; opacity: .8; }
.bfact.ok {
  color: var(--brand); border-color: color-mix(in srgb, var(--brand) 35%, transparent);
  background: var(--brand-soft);
}
.bfact.ok svg { opacity: 1; }

/* Aligned to the row's first line rather than centred down its height.
   The row's controls sit in a band across its foot, and a centred price
   stretches into that band — its box covering the buttons even where its
   ink does not, which is a hit target lost to something invisible. On
   the first line it sits level with the shop's name, where the eye is
   already reading. */
.buy-total {
  display: flex; flex-direction: column; align-items: flex-end; justify-content: flex-start;
  align-self: flex-start;
  gap: 2px; flex: none; padding-inline-start: 4px;
}
/* A price is one thing. The amount and its symbol were stacked, so 1.00
   sat above a ₪ on its own line and read as two facts — and with the
   amount right-aligned and the symbol under it, neither lined up with
   the other. They share a baseline row now, as they do everywhere else
   on the page. */
.buy-amount { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; }
.buy-total b { font-size: 17px; font-weight: 700; line-height: 1; }
.buy-cur { font-size: 11.5px; color: var(--muted); }
/* The chevron points the way the language runs, so it is mirrored with
   the document rather than pinned to one side. */

/* ---- one order ------------------------------------------------------ */
.back-link {
  display: inline-flex; align-items: center; gap: 5px; margin: 2px 0 14px;
  font-size: 13px; font-weight: 600; color: var(--muted);
}
.back-link svg { width: 15px; height: 15px; }
.back-link:hover { color: var(--brand); }

.inv-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
  overflow: hidden; animation: mc-rise .4s ease both;
}
.inv-top {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px; background: var(--surface2); border-bottom: 1px solid var(--line);
}
.inv-store { display: flex; align-items: center; gap: 10px; min-width: 0; }
.inv-store > svg { width: 22px; height: 22px; flex: none; color: var(--brand); }
.inv-store img {
  width: 38px; height: 38px; flex: none;
  border-radius: 10px; object-fit: contain; background: var(--surface);
  border: 1px solid var(--line);
}
.inv-store b { display: block; font-size: 15px; font-weight: 700; }
.inv-store small { font-size: 11.5px; color: var(--muted); }
.inv-total { display: flex; align-items: baseline; gap: 3px; flex: none; }
.inv-total b { font-size: 22px; font-weight: 700; color: var(--brand); }
.inv-total span { font-size: 13px; color: var(--muted); }

/* Flex, not grid, precisely because the number of facts varies: an
   invoice may carry three or five, and a grid cannot fill a partial last
   row — it leaves a hole that reads as an empty box. Flex items grow to
   take the leftover width instead, so the block is always solid however
   many facts an invoice happens to have. */
.inv-facts { display: flex; flex-wrap: wrap; background: var(--surface); }
.ifact {
  flex: 1 1 150px; min-width: 0; padding: 12px 16px;
  border-top: 1px solid var(--line); border-inline-start: 1px solid var(--line);
}
/* The first cell of each visual row would otherwise carry a stray edge
   against the card border; there is no selector for "first in row", so
   the card clips it instead. */
.inv-facts { margin-inline-start: -1px; }
.ifact dt {
  display: flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--muted); margin-bottom: 3px;
}
.ifact dt svg { width: 12px; height: 12px; flex: none; opacity: .75; }
.ifact dd { font-size: 13.5px; font-weight: 600; }

.inv-items { list-style: none; border-top: 1px solid var(--line); }
.inv-items li {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 16px; font-size: 13px;
}
.inv-items li + li { border-top: 1px dashed var(--line); }
.inv-items b { color: var(--muted); font-weight: 700; }

.inv-cards-title { margin: 22px 0 10px; font-size: 15px; font-weight: 700; }
/* The vouchers below reuse كروتي's .voucher card wholesale, so the two
   pages can never drift into showing a code two different ways. This
   only gives the group the frame .order used to. */
.inv-cards-title + .voucher-list,
.inv-cards-title + .filter-none + .voucher-list {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden;
}
/* Autologin, per voucher. Under the codes at the card's inline end —
   after the thing the customer came to read, in the order they act: read
   the code, then open the portal with it. Named rather than drawn: a wifi
   arc in the corner had to be guessed at, and there is room down here to
   simply say what it does. Which card it uses is in the aria-label, which
   one shared button at the foot of the page never could say.

   Shared with مشترياتي's card list, which carried the same control on the
   order until it turned out to sign the customer in with whichever card
   came first. */
.voucher-foot { display: flex; justify-content: flex-end; margin-top: 10px; }
.voucher-login {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 11px; border-radius: 9px;
  background: var(--brand-soft); color: var(--brand);
  font-size: 12px; font-weight: 700; white-space: nowrap;
  transition: background .18s ease, color .18s ease, transform .18s ease;
}
.voucher-login svg { width: 15px; height: 15px; flex: none; }
.voucher-login:hover { background: var(--brand); color: var(--on-brand); transform: translateY(-1px); }
.voucher-login:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* On a store whose cards are registered on an external system this is a
   button rather than a link — the code has to be resolved before there
   is anywhere to go — so it wears the same three states as every other
   action. That means its icon and its words live inside one .btn-label,
   which the spinner and the tick fade out together. */
.voucher-login .btn-label,
.dcard-login .btn-label {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.dcard-login .btn-label { gap: 8px; }


@media (max-width: 400px) {
  .buy-when { width: 62px; padding-inline-end: 9px; }
  .buy-link { gap: 9px; padding: 12px; }
  .buy-total b { font-size: 15.5px; }
}

/* ---- form validation ------------------------------------------------ */
/* Both login fields hold a short ASCII token — a msisdn, a 4-8 character
   password — so they are centred and tracked like the checkout's phone
   field rather than set as running text. A ten-digit number is easier to
   proof-read big and centred than flush to one edge. */
.field.field-mid {
  direction: ltr; text-align: center;
  font-size: 20px; font-weight: 600; letter-spacing: .1em;
  /* Fixed height, not padding. Left to padding, the box is sized by the
     line-box, so the password field's larger glyphs made it 3px taller
     than the one above it — enough to read as a misalignment when the
     two sit one under the other. An explicit height takes the font size
     out of the geometry; an input centres its own text vertically. */
  height: 64px; padding-top: 0; padding-bottom: 0;
}
/* Dots are already wide; the extra tracking on top pushes a long
   password out of the field. */
.field.field-mid[type="password"] { letter-spacing: .18em; font-size: 22px; }
.field.field-mid::placeholder { font-size: 15px; font-weight: 500; letter-spacing: .04em; opacity: .45; }

/* The durable half of the signal: it stays until the field is fixed. */
.field.invalid {
  border-color: var(--hot);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--hot) 16%, transparent);
}
.field.invalid:focus { border-color: var(--hot); outline: none; }

/* The explanatory half: one line under the field, and it auto-hides —
   the border carries the state, this only has to say why once. */
.field-msg {
  margin-top: -8px; padding-inline-start: 2px;
  font-size: 12px; font-weight: 600; color: var(--hot);
  animation: mc-msg-in .18s ease both;
}
.field-msg[hidden] { display: none; }
@keyframes mc-msg-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .field-msg { animation: none; }
}

/* Page-level alerts fade rather than vanish: an element that disappears
   between one frame and the next reads as a glitch, and the fade is also
   what tells someone glancing back that the message is on its way out
   rather than already gone. */
.alert[data-autohide] { transition: opacity .25s ease, transform .25s ease; }
.alert[data-autohide].fading { opacity: 0; transform: translateY(-4px); }
.alert[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  .alert[data-autohide] { transition: none; }
}

/* ---- a button that reports what happened ----------------------------
   Four states on one element: resting, working, succeeded, refused. The
   label and the three glyphs are all present in the markup and stacked
   on top of each other, so switching between them cross-fades instead of
   reflowing — the button never changes size, and nothing under it moves. */
.btn-state { position: relative; overflow: hidden; }
.btn-state,
.btn-state .btn-label,
.btn-state .btn-ic {
  transition: background-color .28s ease, color .28s ease,
              opacity .2s ease, transform .2s ease;
}
.btn-state .btn-ic {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  opacity: 0; transform: scale(.6);
  pointer-events: none;
}
.btn-state .btn-ic svg { width: 24px; height: 24px; }

/* working */
.btn-state.is-loading .btn-label { opacity: 0; transform: scale(.9); }
.btn-state.is-loading .ic-spin { opacity: 1; transform: scale(1); }
.btn-state.is-loading .ic-spin svg { animation: mc-btn-spin .8s linear infinite; }

/* succeeded — the tick draws itself rather than appearing whole */
.btn-state.is-ok .btn-label { opacity: 0; transform: scale(.9); }
.btn-state.is-ok .ic-ok { opacity: 1; transform: scale(1); }
.btn-state.is-ok .ic-ok path {
  stroke-dasharray: 26; stroke-dashoffset: 26;
  animation: mc-btn-draw .32s cubic-bezier(.65,0,.35,1) .04s forwards;
}

/* refused — red, and a short shake so a glance catches it */
.btn-state.is-err { background: var(--hot); }
.btn-state.is-err .btn-label { opacity: 0; transform: scale(.9); }
.btn-state.is-err .ic-err { opacity: 1; transform: scale(1); }
.btn-state.is-err { animation: mc-btn-shake .34s ease both; }
/* Disabled dims the button by design; while it is reporting an outcome
   that reads as "broken" rather than "busy", so the states override it. */
.btn-state.is-loading:disabled,
.btn-state.is-ok:disabled,
.btn-state.is-err:disabled { opacity: 1; }

@keyframes mc-btn-spin { to { transform: rotate(360deg); } }
@keyframes mc-btn-draw { to { stroke-dashoffset: 0; } }
@keyframes mc-btn-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-state, .btn-state .btn-label, .btn-state .btn-ic { transition: none; }
  .btn-state.is-err { animation: none; }
  .btn-state.is-loading .ic-spin svg { animation: mc-btn-spin 1.4s linear infinite; }
  .btn-state.is-ok .ic-ok path { animation: none; stroke-dashoffset: 0; }
}

/* ---- show/hide password --------------------------------------------- */
.field-wrap { position: relative; }
/* Equal padding both sides, not just the side the button is on: the text
   is centred, so padding only on one side would push it off-centre by
   exactly the button's width. */
.field.has-toggle { padding-inline: 52px; }

.pw-toggle {
  position: absolute; top: 50%; inset-inline-end: 10px; transform: translateY(-50%);
  display: grid; place-items: center; width: 38px; height: 38px;
  border: 0; background: none; border-radius: 10px;
  color: var(--muted); cursor: pointer;
  transition: color .18s ease, background .18s ease;
}
.pw-toggle:hover { color: var(--brand); background: var(--brand-soft); }
.pw-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.pw-toggle svg { width: 20px; height: 20px; }
/* Both glyphs ship; which one shows is the pressed state, so the swap
   costs no DOM work and the button never changes size. */
.pw-ic { display: none; }
.pw-toggle[aria-pressed="false"] .pw-show { display: grid; }
.pw-toggle[aria-pressed="true"]  .pw-hide { display: grid; }
/* Revealed, the value is real text rather than dots — dots need the wide
   tracking to be countable, characters do not. */
.field.field-mid[type="text"].has-toggle { letter-spacing: .1em; font-size: 20px; }

/* ---- topbar logout --------------------------------------------------- */
.topbar-actions { display: flex; align-items: center; gap: 8px; }
.logout-form { display: flex; }
/* Outlined danger rather than filled: it sits permanently in the chrome
   next to the theme toggle, and a solid red button parked beside every
   page would shout at someone who is not trying to leave. */
.icon-btn-danger {
  border: 1.5px solid color-mix(in srgb, var(--hot) 45%, transparent);
  color: var(--hot); background: transparent;
}
.icon-btn-danger:hover { background: var(--hot); border-color: var(--hot); color: #fff; }
.icon-btn-danger:focus-visible { outline: 2px solid var(--hot); outline-offset: 2px; }

/* ---- avatar upload --------------------------------------------------- */
.avatar-form { flex: none; }
/* The label is the target, not a button beside it — on a phone that is
   the difference between a comfortable tap and a 20px one. */
.avatar-edit { position: relative; cursor: pointer; display: block; }
.avatar-edit img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.avatar-default { width: 60%; height: 60%; color: var(--brand); opacity: .75; }
.avatar-cam {
  position: absolute; bottom: -2px; inset-inline-end: -2px;
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; background: var(--brand); color: var(--on-brand);
  border: 2px solid var(--bg);
  transition: transform .18s ease;
}
.avatar-cam svg { width: 13px; height: 13px; }
.avatar-edit:hover .avatar-cam { transform: scale(1.08); }

/* ---- saved payment wallets ------------------------------------------- */
.panel-sub { margin: -6px 0 14px; font-size: 12.5px; color: var(--muted); }
.wallet-list { list-style: none; display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.wallet {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 13px; background: var(--surface2);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.wallet-ic {
  display: grid; place-items: center; width: 30px; height: 30px; flex: none;
  border-radius: 9px; background: var(--brand-soft); color: var(--brand);
}
.wallet-ic svg { width: 15px; height: 15px; }
.wallet-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.wallet-id b { font-size: 14px; font-weight: 700; }
.wallet-label { font-size: 11px; color: var(--muted); }
/* Which one checkout will pre-select — the only thing that makes the
   list actionable rather than a record. */
.wallet-last {
  flex: none; padding: 2px 8px; border-radius: 999px;
  background: var(--brand-soft); color: var(--brand);
  font-size: 10.5px; font-weight: 700;
}
.wallet-del {
  flex: none; display: grid; place-items: center; width: 30px; height: 30px;
  border: 0; background: none; border-radius: 8px; color: var(--muted);
  cursor: pointer; transition: color .18s ease, background .18s ease;
}
.wallet-del:hover { color: var(--hot); background: var(--hot-soft); }
.wallet-del svg { width: 15px; height: 15px; }
.wallet-add { margin-top: 4px; }
.wallet-add .btn-ghost { margin-top: 2px; }

/* ---- saved numbers at checkout ------------------------------------
   The same wallets as حسابي, but here they are a choice rather than a
   list, so they read as chips in a radio group. */
.paywallets { margin-bottom: 16px; }
.paywallets-head {
  display: block; font-size: 12px; font-weight: 700;
  color: var(--muted); margin-bottom: 8px;
}
/* Rectangles, not pills. These are a choice between saved numbers, and
   a row of pills reads as tags describing something rather than options
   to pick from. Auto-fit keeps them a tidy grid: two across on a phone,
   more on a wide screen, without a breakpoint per size. */
.paywallet-row {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}
.paywallet {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 12px 13px; font: inherit; font-size: 13.5px; font-weight: 600;
  text-align: start;
  color: var(--ink); background: var(--surface2);
  border: 1.5px solid var(--line); border-radius: var(--radius);
  cursor: pointer;
  transition: background-color .16s ease, border-color .16s ease,
              color .16s ease, box-shadow .16s ease;
}
/* The icon gets its own rounded square so the tile has a left edge to
   read from, rather than text floating against a border. */
.paywallet > svg {
  width: 32px; height: 32px; flex: none; padding: 7px;
  border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--line);
  color: var(--muted);
  transition: background-color .16s ease, border-color .16s ease, color .16s ease;
}
.paywallet .num {
  direction: ltr; unicode-bidi: isolate;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.paywallet:hover { border-color: color-mix(in srgb, var(--brand) 55%, var(--line)); }
.paywallet:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 28%, transparent);
}
.paywallet.on {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, var(--surface));
}
.paywallet.on > svg {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 40%, var(--line));
  background: color-mix(in srgb, var(--brand) 12%, var(--surface));
}
/* A tick in the corner: colour alone is not a selection, and on a small
   screen two tinted tiles side by side are easy to misread. */
.paywallet-check {
  position: absolute; inset-block-start: 7px; inset-inline-end: 7px;
  width: 16px; height: 16px; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: var(--brand); color: #fff;
  opacity: 0; transform: scale(.5);
  transition: opacity .16s ease, transform .16s cubic-bezier(.2,.8,.2,1);
}
.paywallet-check svg { width: 10px; height: 10px; }
.paywallet.on .paywallet-check { opacity: 1; transform: none; }

/* "another number" is an action, not a saved number — dashed, so it
   reads as the empty slot at the end of the set. */
.paywallet-other { border-style: dashed; color: var(--muted); }
.paywallet-other > svg { background: transparent; }
.paywallet-other.on { border-style: solid; }

.paywallet-label {
  display: block; font-size: 11px; font-weight: 500; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.paywallet.on .paywallet-label { color: color-mix(in srgb, var(--brand) 70%, var(--muted)); }
@media (prefers-reduced-motion: reduce) {
  .paywallet, .paywallet > svg, .paywallet-check { transition: none; }
}

/* ---- the three states, everywhere they are worn --------------------
   .btn-state carries them on ordinary buttons (see above). These are
   the two places that are not a text button: the dialog's confirm, and
   the avatar. */

/* The confirm button is already red; the error state must not repaint
   it the same red it already is, or the only signal left is the shake. */
.confirm-ok.is-err { background: var(--hot); filter: saturate(1.3) brightness(.88); }
.confirm-ok .btn-ic svg { width: 20px; height: 20px; }
.confirm-cancel:disabled { opacity: .45; pointer-events: none; }

/* The avatar wears them over the picture: the image dims, the icon sits
   on top. No label to hide — the picture is the label. */
.avatar-edit.btn-state { overflow: visible; }
.avatar-edit.btn-state > img,
.avatar-edit.btn-state > .avatar-default { transition: opacity .2s ease, filter .2s ease; }
.avatar-edit.is-loading > img,
.avatar-edit.is-loading > .avatar-default,
.avatar-edit.is-ok > img,
.avatar-edit.is-ok > .avatar-default,
.avatar-edit.is-err > img,
.avatar-edit.is-err > .avatar-default { opacity: .25; filter: grayscale(1); }
.avatar-edit .btn-ic {
  border-radius: 50%;
  background: color-mix(in srgb, var(--surface) 70%, transparent);
}
.avatar-edit .btn-ic svg { width: 26px; height: 26px; }
.avatar-edit.is-ok .btn-ic svg { color: var(--brand); }
.avatar-edit.is-err .btn-ic svg { color: var(--hot); }
/* The camera badge is an invitation; while something is happening it is
   not one. */
.avatar-edit.is-loading .avatar-cam,
.avatar-edit.is-ok .avatar-cam,
.avatar-edit.is-err .avatar-cam { opacity: 0; }

/* ---- toast ---------------------------------------------------------
   Feedback that does not move the page. Bottom-centre on a phone, where
   the thumb already is and clear of the top bar. */
#mc-toasts {
  position: fixed; inset-block-end: 22px; inset-inline: 0;
  z-index: 90; display: flex; flex-direction: column;
  align-items: center; gap: 8px; pointer-events: none;
  padding-inline: 16px;
}
.mc-toast {
  max-width: min(520px, 100%);
  padding: 11px 16px; border-radius: var(--radius);
  font-size: 13.5px; font-weight: 600; line-height: 1.5;
  text-align: center;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 8px 26px rgb(0 0 0 / .16);
  opacity: 0; transform: translateY(10px) scale(.97);
  transition: opacity .24s ease, transform .24s cubic-bezier(.2,.8,.2,1);
}
.mc-toast.in { opacity: 1; transform: none; }
.mc-toast.ok {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 40%, var(--line));
  background: color-mix(in srgb, var(--brand) 9%, var(--surface));
}
.mc-toast.err {
  color: var(--hot);
  border-color: color-mix(in srgb, var(--hot) 40%, var(--line));
  background: color-mix(in srgb, var(--hot) 9%, var(--surface));
}
@media (prefers-reduced-motion: reduce) {
  .mc-toast { transition: opacity .2s ease; transform: none; }
  .btn-state.is-err { animation: none; }
}
.paywallet-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }

/* ---- مشترياتي: the two tabs and the archive control ----------------- */
/* Present but out of the way until the archive has something in it. */
.buy-tabs.is-quiet { display: none; }
.buy-tabs {
  display: flex; gap: 8px; margin-bottom: 14px;
  border-bottom: 1px solid var(--line); padding-bottom: 10px;
}
.buy-tab {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 14px; border-radius: var(--radius);
  font-size: 13.5px; font-weight: 700; text-decoration: none;
  color: var(--muted); background: var(--surface2);
  border: 1px solid var(--line);
  transition: color .16s ease, background-color .16s ease, border-color .16s ease;
}
.buy-tab svg { width: 15px; height: 15px; flex: none; }
.buy-tab:hover { color: var(--ink); }
.buy-tab.on {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 9%, var(--surface));
}
/* The count is the reason the tab is worth looking at, so it reads as a
   quantity rather than decoration. */
.buy-tab-n {
  min-width: 20px; padding: 1px 6px; border-radius: var(--radius-sm);
  font-size: 11.5px; line-height: 1.5; text-align: center;
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  color: var(--brand);
}
.buy-tab:not(.on) .buy-tab-n { background: var(--line); color: var(--muted); }

/* ---- archiving a purchase -------------------------------------------
   In the time rail, under the stamp: the one column of the row that is
   not the order itself, so the control sits beside the row rather than
   on top of anything. Danger-outlined because it removes something from
   view, icon-only because the rail is narrow and the tooltip says the
   rest. */
.buy { position: relative; }
/* The rail centres its stamp again. It used to reserve 30px underneath
   because the archive button sat in it; the controls have moved to the
   row's other end, and the reservation was pushing every clock in the
   list off the centre of its own row for nothing. */
/* The row's own controls, at its foot. One flex row rather than two
   absolutely positioned boxes at hand-picked offsets: the archive button
   grew a label and landed on top of the download, because an offset
   cannot know how wide its neighbour is. Anchored at the end edge — the
   left, in RTL — where the row's foot is empty. */
.buy-tools {
  position: absolute; inset-block-end: 8px; inset-inline-end: 12px;
  display: flex; align-items: center; gap: 8px;
}
/* The download sits outermost — the left, in RTL. Ordered here rather
   than by shuffling the markup, so the row reads archive-then-download
   to a screen reader in the order the labels describe. */
.buy-tools .buy-pdf { order: 2; }
.buy-tools .buy-arch { order: 1; }
.buy-arch { position: static; width: auto; display: flex; }
/* The icon carries its word. A rose-tinted glyph on its own reads as
   "delete" — which is the one thing this does not do — and there is no
   hover on a phone to find out that it only moves the row to the other
   tab. Sized to the words rather than to a square, and anchored at the
   rail's own edge so it grows inward across the empty half of the row's
   foot instead of pushing anything. */
.buy-arch-btn {
  width: auto; height: 30px; gap: 5px; padding: 0 9px;
  font-size: 11px; font-weight: 700; white-space: nowrap;
}
.buy-arch-btn svg { width: 14px; height: 14px; flex: none; }
.buy-arch-lbl { line-height: 1; }

/* ---- the order on paper ---------------------------------------------
   One sheet per invoice, printed by the browser — which is also where
   "Save as PDF" lives, and which shapes Arabic correctly without a font
   to ship. The control is an icon on the lists, where the row is already
   full, and a named button on the sheet after a purchase, where the
   customer has just been handed codes and has not seen the page before. */
.buy-pdf {
  flex: none; width: 30px; height: 30px; border-radius: 10px;
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.buy-pdf svg { width: 15px; height: 15px; }
.buy-pdf:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
/* Preparing the file takes a moment on a long order — the control says
   so, rather than looking like a tap that did nothing. */
.buy-pdf.is-busy, .act-pdf.is-busy { color: var(--brand); border-color: var(--brand); }
.buy-pdf.is-busy svg, .act-pdf.is-busy svg { animation: pdf-work .9s linear infinite; }
.buy-pdf.is-done, .act-pdf.is-done { background: var(--brand); border-color: transparent; color: var(--on-brand); }
@keyframes pdf-work {
  0%   { transform: translateY(-2px); opacity: .55; }
  50%  { transform: translateY(2px);  opacity: 1; }
  100% { transform: translateY(-2px); opacity: .55; }
}
@media (prefers-reduced-motion: reduce) {
  .buy-pdf.is-busy svg, .act-pdf.is-busy svg { animation: none; }
}

.act-pdf:hover { color: var(--brand); border-color: var(--brand); }

.done-pdf {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 12px; height: 44px; border-radius: var(--radius);
  background: var(--surface2); border: 1px dashed var(--line);
  color: var(--muted); font-size: 13.5px; font-weight: 700;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.done-pdf svg { width: 17px; height: 17px; flex: none; }
.done-pdf:hover {
  color: var(--brand); border-color: var(--brand);
  background: var(--brand-soft); border-style: solid;
}
.done-pdf[hidden] { display: none; }

/* ---- and the row leaving ---------------------------------------------
   It is not deleted, it moved to the other tab — so it leaves rather
   than blinking out, wiped away from the start edge in the direction the
   archive lives. The height collapses with it so the rows below close
   the gap in the same motion instead of jumping afterwards. */
.buy.wiping {
  animation: mc-wipe .42s cubic-bezier(.4, 0, .2, 1) forwards;
  pointer-events: none;
  overflow: hidden;
}
@keyframes mc-wipe {
  0%   { opacity: 1; transform: none; max-height: 240px; margin-block-end: 10px; }
  55%  { opacity: 0; transform: translateX(calc(-30% * var(--mc-dir, 1))); max-height: 240px; }
  100% { opacity: 0; transform: translateX(calc(-30% * var(--mc-dir, 1)));
         max-height: 0; margin-block-end: 0; padding-block: 0; border-width: 0; }
}
/* RTL runs the other way, so the wipe follows the writing direction
   rather than always sliding left. */
[dir="rtl"] .buy.wiping { --mc-dir: -1; }

@media (prefers-reduced-motion: reduce) {
  .buy-tab { transition: none; }
  /* Still leaves, just without the travel — the row vanishing with no
     transition at all reads as a glitch. */
  .buy.wiping { animation: mc-fade-out .2s linear forwards; }
  @keyframes mc-fade-out { to { opacity: 0; max-height: 0; margin-block-end: 0; } }
}

/* ---- كروتي: recovering cards bought without an account -------------- */
.recover-done-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.recover-done-head .btn-ghost { width: auto; padding-inline: 18px; margin: 0; }
/* An order number or a six-digit code — both read as a reference, so
   they get the same monospaced, left-to-right treatment. */
#rc-ref { letter-spacing: .06em; }

/* ---- المتجر تحت الصيانة ---------------------------------------------
   A dead end on purpose: nothing to click that would only fail, except
   the one thing that still works — the phone. */
.maint { text-align: center; padding: 48px 20px 40px; max-width: 460px; margin-inline: auto; }
.maint-ic {
  width: 76px; height: 76px; margin: 0 auto 22px;
  display: grid; place-items: center;
  border-radius: var(--radius-lg);
  background: var(--brand-soft); color: var(--brand);
}
.maint-ic svg { width: 36px; height: 36px; }
.maint h1 { font-size: 21px; font-weight: 800; margin-bottom: 10px; }
.maint-lead { font-size: 15px; line-height: 1.85; color: var(--ink); }
.maint-note {
  margin-top: 16px; padding: 12px 14px;
  font-size: 13px; line-height: 1.8; color: var(--muted);
  background: var(--surface2); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.maint-call {
  display: inline-flex; align-items: center; justify-content: center;
  margin-top: 22px; gap: 8px;
}
/* .btn-call carries no icon of its own, so an unsized <svg> here renders
   at its intrinsic size and takes the page's width with it. */
.maint-call svg { width: 17px; height: 17px; flex: none; }
.maint-call .num { direction: ltr; unicode-bidi: isolate; opacity: .85; }
.maint-retry { margin-top: 18px; font-size: 13px; }
.maint-retry a { color: var(--muted); text-decoration: underline; }
.maint-retry a:hover { color: var(--brand); }

/* ---- the app interstitial -------------------------------------------
   A picture over the shop front on arrival, for twenty seconds or until
   it is dismissed. Only the chrome is here: the artwork carries its own
   inline styles, because it is a composition that will be replaced
   whole rather than edited (see partials/app-ad.php).

   Above everything, including the support launcher at 90 — a thing that
   covers the page has to be the thing on top, or the corner of it that
   pokes through is the corner that gets tapped. */
.adv {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
}
.adv[hidden] { display: none; }
/* Same arrival as every other popup on this site: the backdrop settles
   in, the panel rises. Two dialogs with two ideas of how a dialog
   appears is how a site starts to feel assembled rather than made. */
.adv-backdrop {
  position: absolute; inset: 0; background: rgba(8, 16, 12, .55);
  opacity: 0;
  -webkit-backdrop-filter: blur(0px); backdrop-filter: blur(0px);
  transition: opacity .24s ease, -webkit-backdrop-filter .3s ease, backdrop-filter .3s ease;
}
.adv.in .adv-backdrop {
  opacity: 1;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.adv-frame {
  position: relative;
  /* Sized by store.js from the scale it worked out for the artwork —
     the frame has to be the picture's size, not the viewport's, or the
     controls float away from the corner they belong to. */
  width: var(--adv-w, 320px); height: var(--adv-w, 320px);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--shadow-lift);
  opacity: 0; transform: translateY(12px) scale(.97);
  transition: opacity .22s ease, transform .22s cubic-bezier(.22,1,.36,1);
}
.adv.in .adv-frame { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .adv-frame { transition: opacity .01ms; transform: none; }
  .adv-backdrop { transition: opacity .01ms; }
}

/* The artwork at its native size, shrunk to whatever fits.
   Positioned rather than laid out: the page is RTL, so a 1080px box in
   a 358px frame is placed from the right edge and hangs 700px off to
   the left — and scaling from the top-left corner then keeps it there,
   neatly shrinking it in place outside the frame. Pinned to the frame's
   own corner, the direction of the page stops mattering and the scale
   factor is the only thing deciding where it lands. */
.adv-stage {
  position: absolute; top: 0; left: 0;
  width: 1080px; height: 1080px;
  transform: scale(var(--adv-scale, .3));
  transform-origin: top left;
}

/* The countdown, over the picture rather than in it. Pinned to the
   physical top-right: the ad has its own logo mark in its top-left, and
   a number sitting on a logo reads as part of it. Physical rather than
   logical for that reason — the composition underneath does not flip
   with the language.

   No chip around it. It is the only thing here now that the close
   button has gone, so it is set large and plain and carries a shadow to
   hold it against whatever the artwork puts behind it. */
.adv-count {
  position: absolute; top: 12px; right: 16px; z-index: 2;
  color: #fff;
  font-size: 34px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 10px rgba(8, 16, 12, .55), 0 0 2px rgba(8, 16, 12, .4);
  pointer-events: none;
}
/* The mark in the ad's corner, which is the way out.
   The drawing is 72px in the artwork's own coordinates, and the artwork
   is shown at about a third of that on a phone — so the thing the
   customer has to hit is roughly 24px across. The picture is not
   redrawn to fix that; the button is given a hit area larger than its
   ink, in the artwork's units so it scales with everything else. At the
   size a phone renders this, the inset below lands the target near
   45px. */
.adv-mark { position: absolute; }
.adv-mark::after {
  content: ''; position: absolute; inset: -30px; border-radius: 50%;
}
.adv-mark:hover { box-shadow: 0 6px 18px rgba(43, 45, 61, .28); }
.adv-mark:focus-visible { outline: 6px solid #fff; outline-offset: 4px; }

/* A phone shows the picture small; the number keeps its proportion to
   it rather than staying the size it was on a desktop. */
@media (max-width: 480px) {
  .adv-count { font-size: 27px; top: 10px; right: 12px; }
}

.adv-frame:focus { outline: none; }
/* ---- صفحات الهوتسبوت: the kit page ----------------------------------
   The previews are the real router pages in iframes, shrunk with a
   transform rather than re-styled — what the manager sees here is what
   the customer sees on the network, at postcard size. */
.hk { max-width: 860px; margin: 0 auto; padding: 26px 0 40px; }
.hk-head { text-align: center; margin-bottom: 22px; }
.hk-ic {
  display: grid; place-items: center; width: 58px; height: 58px; margin: 0 auto 12px;
  border-radius: 18px; background: var(--brand-soft); color: var(--brand);
}
.hk-ic svg { width: 30px; height: 30px; }
.hk-head h1 { font-size: 20px; font-weight: 800; margin-bottom: 8px; }
.hk-head p { font-size: 13.5px; line-height: 1.8; color: var(--muted); max-width: 60ch; margin: 0 auto; }
.hk-bar {
  display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 16px; margin-bottom: 18px;
}
.hk-field { flex: 1 1 220px; }
.hk-field label { display: block; font-size: 12.5px; font-weight: 600; margin-bottom: 6px; }
.hk-bar .btn-primary { width: auto; padding: 0 22px; height: 48px; font-size: 14px; flex: none; }
.hk-previews {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 14px; margin-bottom: 20px;
}
.hk-shot {
  margin: 0; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
}
.hk-shot iframe {
  /* A phone-width page, scaled to the card. pointer-events off: these
     are pictures of pages, not pages to use. */
  width: 400px; height: 720px; border: 0;
  transform: scale(.575); transform-origin: top right;
  pointer-events: none; display: block; margin-bottom: calc(-720px * .425);
}
.hk-shot figcaption {
  padding: 9px 12px; font-size: 12.5px; font-weight: 700; text-align: center;
  border-top: 1px solid var(--line); background: var(--surface2);
}
.hk-download { text-align: center; }
.hk-download .btn-primary {
  display: inline-flex; align-items: center; gap: 9px; width: auto; padding: 0 26px;
}
.hk-download .btn-primary svg { width: 19px; height: 19px; }
.hk-download .fineprint { margin-top: 12px; max-width: 64ch; margin-inline: auto; line-height: 1.8; }
