/* =============================================================
   BEYLI BASE STYLES
   Global reset + body defaults + small layout utilities shared by
   every screen. Load after tokens.css.
   ============================================================= */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* The reset above also hits <dialog>, whose UA stylesheet centres a modal
   with `margin: auto`. Zeroing it strands every showModal() dialog in the
   top-left corner, on top of the sidebar, instead of centred. The shared
   .modal component sets `margin: auto` back by hand, which hid the problem
   for as long as every dialog used .modal — a page-specific dialog that
   styled itself from scratch got the broken position and nothing flagged
   it. Restoring it here fixes the cause rather than each symptom. */
dialog {
  margin: auto;
}

html, body {
  height: 100%;
}

/* Reserve the scrollbar gutter before a page becomes long. Without this,
   switching from a short admin view to a data-heavy view (such as Users &
   Sellers) can narrow the viewport by one scrollbar and make the whole
   interface appear to jump sideways. */
html {
  /* Reserve the scrollbar gutter always. Without it a page that grows
     past the viewport gains a scrollbar and every centred element
     shifts sideways by its width — visible as a jump when an admin
     table finishes rendering. */
  scrollbar-gutter: stable;
  overflow-y: scroll;
}
@supports not (scrollbar-gutter: stable) {
  html { overflow-y: scroll; }
}

body {
  background: var(--surface-page);
  color: var(--ink-1);
  font: var(--text-body-md);
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--link);
  text-decoration: none;
}
a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

/* One clear keyboard-focus treatment across the app and web surfaces.
   Individual components can refine it, but no actionable control is allowed
   to become visually silent when operated with a keyboard or switch device. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--beyli-violet);
  outline-offset: 3px;
  box-shadow: var(--focus-ring);
}

/* Respect users who request less movement. This keeps dialogs, live surfaces
   and page transitions usable without disabling the information itself. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

img {
  display: block;
  max-width: 100%;
}

/* ---------- Mobile screen frame ----------
   Every screen in mobile/ is designed at a 390x844 phone viewport. */
.screen-frame {
  width: 390px;
  height: 844px;
  margin: 0 auto;
  /* A query container: this frame is a fixed 390px phone mockup that can sit
     inside a much wider desktop browser window during preview/dev, so a
     shared component's OWN `min-width` media queries (correctly written for
     a full browser viewport on the web surface) end up reading the WINDOW's
     width instead of the frame's — see shared/feed-card.css's matching
     `@container` override for what that broke. On a real phone this is a
     no-op: frame width and viewport width are already the same. */
  container-type: inline-size;
  container-name: screen-frame;
  /* `hidden` still lets an anchor jump (e.g. the bottom nav's "current page"
     link, which targets its own <main id="x-top">) scroll THIS frame via
     scrollIntoView, shoving the header/tabs/banner that sit before <main>
     out of view — only <main> itself is meant to scroll. `clip` blocks
     that scroll path outright while clipping identically otherwise. */
  overflow: clip;
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-page);
}
/* Clipping .screen-frame itself only closed half the path: once it can't
   scroll, the SAME anchor jump just escalates to the next scrollable
   ancestor it finds — which was <body>, still scrollable here because
   `html { overflow-y: scroll }` above is deliberately on for the web/admin
   surfaces (scrollbar-gutter reservation). Scoped to :has(.screen-frame) so
   this only touches mobile pages; web/admin keep their real document
   scroll. */
body:has(.screen-frame) {
  overflow: hidden;
}

/* The desktop gallery deliberately presents a 390×844 phone canvas. On an
   actual iPhone or Android browser, however, that fixed canvas used to be
   wider than devices such as 360px Android phones and taller than short
   landscape viewports. Let the real device own the frame dimensions while
   preserving the fixed canvas for desktop review. `dvh` follows browser
   chrome as it collapses; `svh` keeps a stable minimum on Safari. */
@media (max-width: 420px) {
  body { min-height: 100dvh; }
  .screen-frame {
    width: 100vw;
    height: 100dvh;
    min-height: 100svh;
    margin: 0;
  }
  /* `100dvh` under-measures the visual viewport in an installed iOS
     standalone web app (display: standalone) — it leaves a gap at the
     bottom roughly the height of the home-indicator area, as if the
     frame were shorter than the real screen. `-webkit-fill-available`
     is WebKit's own answer to that same measurement and reports the
     true standalone viewport, so it overrides dvh only where the bug
     lives instead of changing behavior for every other browser. */
  @supports (-webkit-touch-callout: none) {
    body { min-height: -webkit-fill-available; }
    .screen-frame { height: -webkit-fill-available; }
  }
}

/* ---------- Web page shell ----------
   Screens in web/ are full-width responsive layouts, capped at --page-max. */
.page-shell {
  min-height: 100vh;
  /* On iOS Safari and Android Chrome, `vh` may include browser chrome and
     leave a visual gap or clipped footer while it animates. Keep `vh` as a
     fallback for older browsers and prefer the live viewport where present. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface-page);
}
.page-container {
  width: 100%;
  /* Grid and flex children can otherwise preserve their intrinsic width and
     create a page-level horizontal scroll on narrow web views. */
  min-width: 0;
  max-width: var(--page-max);
  margin: 0 auto;
  padding-inline: var(--gutter-desktop);
}

/* ---------- Small layout utilities ---------- */
.row {
  display: flex;
  align-items: center;
}
.col {
  display: flex;
  flex-direction: column;
}
.grow {
  flex: 1;
  min-width: 0;
}
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.scroll-x {
  /* Containing block, not decoration. A .sr-only label inside one of these
     tables is position:absolute with no positioned ancestor, so it resolved
     against the viewport instead of the scroller: it escaped the clip, landed
     at its static x — past the right edge on a narrow window — and dragged the
     whole document 30px wider. Every admin table with a visually hidden header
     cell did it. Relative here keeps absolute descendants inside the scroller. */
  position: relative;
  overflow-x: auto;
  scrollbar-width: none;
}
.scroll-x::-webkit-scrollbar {
  display: none;
}

/* =============================================================
   RTL — Arabic
   =============================================================
   Layout mirroring is handled by logical properties throughout
   (`margin-inline-start`, `inset-inline-end`, `text-align: start`),
   so setting `dir="rtl"` on <html> mirrors the whole design set with
   no per-screen rules. What follows covers only what logical
   properties cannot reach.

   To preview any screen in Arabic:  <html lang="ar" dir="rtl">          */

/* [hidden] must beat any class that sets `display`.

   Author-origin class rules always outrank the UA's own `[hidden]` rule, so
   `.btn { display: inline-flex }` silently un-hides every button the JS hides.
   This bit three times across two PRs and was each time patched per-element —
   `.live-face[hidden]`, `.seller-note[hidden]`, `.live-overlay[hidden]` — which
   fixes the instance and leaves the class of bug in place. One global rule
   retires all of them and stops the next one.

   !important is load-bearing here, not lazy: it is what outranks the author
   class rules this exists to defeat. */
[hidden] { display: none !important; }

/* Horizontal arrows and chevrons point at a direction, so they flip.
   Vertical ones (up/down) and non-directional glyphs must NOT — a
   flipped clock or search icon just looks broken. */
[dir="rtl"] .icon[data-lucide="chevron-left"],
[dir="rtl"] .icon[data-lucide="chevron-right"],
[dir="rtl"] .icon[data-lucide="arrow-left"],
[dir="rtl"] .icon[data-lucide="arrow-right"],
[dir="rtl"] .icon[data-lucide="corner-up-left"],
[dir="rtl"] .icon[data-lucide="log-out"] {
  transform: scaleX(-1);
}

/* ...except inside the bid slider. That chevron is not pointing at a
   direction in the text, it is labelling a gesture, and the gesture is
   pinned left-to-right in both languages (see .bid-slide__thumb in
   mobile/live.html). Flipping it left leaves an arrow that contradicts the
   only irreversible control on the screen. */
[dir="rtl"] .bid-slide__thumb .icon[data-lucide="chevron-right"] {
  transform: none;
}

/* Numerals, prices, timers and phone numbers stay left-to-right even
   inside Arabic text. `AED 145` must not render as `145 AED`, and a
   countdown must not reverse mid-tick.

   `.timer` is the real class — an earlier version of this rule listed
   `.auction-timer` and `.price`, neither of which exists on any element
   in react_conversion/. Both were dead selectors documenting protection
   that was never applied. */
[dir="rtl"] .timer,
[dir="rtl"] .live-badge__viewers,
[dir="rtl"] [class*="__value"],
[dir="rtl"] .admin-td-num {
  direction: ltr;
  unicode-bidi: isolate;
}

/* A price written with the dirham SYMBOL must not be isolated. The rule
   above exists so the letters `AED 145` cannot reorder to `145 AED`, which
   is a real hazard when the currency is a word. A symbol is not a word:
   `.currency` is an inline-block, bidi-neutral, and positions purely by DOM
   order and direction. Isolating it forces Latin order and produces
   `؋ 142` in an Arabic page where the correct reading is `142 ؋`.

   So prices carrying the symbol follow the page, and only the remaining
   text-currency cases stay pinned. Verified in a browser both ways. */
[dir="rtl"] .mono-price:not(:has(.currency)),
[dir="rtl"] .queue-price:not(:has(.currency)),
[dir="rtl"] [class*="__price"]:not(:has(.currency)),
[dir="rtl"] [class*="__amount"]:not(:has(.currency)) {
  direction: ltr;
  unicode-bidi: isolate;
}

/* Handles, and any label mixing a neutral character with digits, need the
   same isolation. Found by rendering marketplace.html with dir="rtl":
   `@kenzo.cards` came out as `kenzo.cards@`, and the filter `AED 150+`
   as `+AED 150`. Both are the bidi algorithm reordering a neutral
   character (`@`, `+`) that sits at the edge of an LTR run — correct per
   spec, wrong for a username or a price band. Isolating the run pins the
   character to the side the reader expects.

   The admin surface names its cells without the `__` convention, so the
   substring selectors below miss it entirely. `.admin-cell-sub` holds
   `@rania.kicks` and `.admin-cell-main` holds `#D-4811` — the exact bug
   this rule exists to fix, on 90 elements. Listed explicitly.

   `text-align: end` matters only for the block-level matches; on an
   inline <span> it does nothing. With `direction: ltr` on the same
   element it resolves to the right, which is what an RTL page wants —
   do not "correct" it to `start`. */
[dir="rtl"] [class*="__seller"],
[dir="rtl"] [class*="__handle"],
[dir="rtl"] .checkbox__label,
[dir="rtl"] [class*="__user"],
[dir="rtl"] [class*="__id"],
[dir="rtl"] [class*="__rank"],
[dir="rtl"] .cell-mono,
[dir="rtl"] .admin-cell-sub,
[dir="rtl"] .admin-cell-main {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: end;
}

/* The gallery's phone frame and any explicitly LTR island keep their
   own direction regardless of the page. */
[dir="rtl"] [dir="ltr"] { direction: ltr; }

/* ============================================================
   Keyboard focus
   ============================================================
   Measured before this existed: 24 of 24 app screens showed no focus change
   at all — focusing a button altered neither outline nor box-shadow. Anyone
   navigating by keyboard or switch had no idea where they were.

   :focus-visible rather than :focus, so the ring appears for keyboard and
   assistive input but never on a mouse click, which is what makes designers
   remove focus rings in the first place.

   Two tones, because Beyli runs light surfaces and near-black live rooms in
   the same app: the yellow ring carries on dark, the indigo halo carries on
   light, and one of the two always has contrast. currentColor would not — it
   disappears against whichever surface matches it. */
:focus-visible {
  outline: 2px solid var(--beyli-yellow);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(38, 14, 80, 0.55);
  border-radius: var(--radius-sm);
}
/* Inputs already carry a border; the halo alone reads better than a ring
   drawn just outside one. */
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline-offset: 0;
}

/* ============================================================
   Reduced motion
   ============================================================
   Three keyframes ship in the shared sheets and more inside screens — the
   pulse on every LIVE dot among them. On a phone set to reduce motion that is
   a constant throb the user explicitly asked not to see.

   Durations go to near-zero rather than zero: a 0s transition can skip
   transitionend, and anything waiting on that event would hang. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
