/* ============================================================
   CHAAVLE — Design Enhancement Layer v2
   Loaded after Tailwind. Overrides and refines base styles.
   ============================================================ */

/* ============================================================
   [hidden] — ერთხელ და ყველასთვის
   ------------------------------------------------------------
   ბრაუზერის `[hidden] { display: none }` მისი *საკუთარი* სტილია,
   ანუ ავტორის ნებისმიერი წესი მას აჯობებს თანაბარი
   სპეციფიკურობითაც კი. შედეგად ყოველი კლასი, რომელიც `display`-ს
   აყენებს (`display: flex`, `inline-flex`, `grid`), ჩუმად აუქმებს
   `hidden` ატრიბუტს: JS წერს `el.hidden = true`, ელემენტი კი
   ეკრანზე რჩება.

   ეს ხაფანგი ამ ფაილში უკვე სამჯერ იყო ცალკე გასწორებული
   (`#chaavle-dialog[hidden]`, `.chaavle-dialog-list[hidden]`,
   `.chaavle-emoji-panel[hidden]`, `.chaavle-dialog-btn[hidden]`) და
   მეოთხედ `.results-meta`-ზე გამეორდა: მრიცხველის რიგს `hidden`
   ეწერა, `display: flex` კი მას აჩვენებდა — სუფთა კატალოგზე
   „გასუფთავების" ღილაკი ისედაც ჩანდა.

   ერთი წესი მთელ კლასს ხურავს. `!important` აქ საჭიროა სწორედ
   იმიტომ, რომ ის ავტორის სხვა წესებს უნდა აჯობოს — `hidden`
   ატრიბუტს სხვა განზრახვა არ აქვს, ის ყოველთვის „არ აჩვენო"-ს
   ნიშნავს.

   ⚠ ეს არ ეხება Tailwind-ის `.hidden` *კლასს* — ის ცალკე რამაა.

   ქვემოთ დარჩენილი ცალკეული `[hidden]` წესები ახლა ზედმეტია,
   მაგრამ განზრახ რჩება: ისინი ზიანს არ აყენებენ და მათი წაშლა
   იმ კომპონენტების ხელახლა შემოწმებას მოითხოვდა.
   ============================================================ */
[hidden] {
  display: none !important;
}

/* ============================================================
   VIEWPORT LOCK — HARD STOP ON HORIZONTAL OVERFLOW
   ------------------------------------------------------------
   ეს ბლოკი ფაილში პირველია განზრახ: `!important` მას ისედაც
   ანიჭებს უმაღლეს პრიორიტეტს, მაგრამ ადრეული პოზიცია აქ იმასაც
   ნიშნავს, რომ ეს არის პირველი წესი, რასაც ბრაუზერი კითხულობს —
   არცერთ შემდეგ განზრახვას (ან შემთხვევით მოხვედრილ ფართო
   ელემენტს) არ შეუძლია მთელი გვერდის ჰორიზონტალურად სქროლვადობა.

   რატომ იყო საჭირო: `overflow-x: hidden` აქამდე მხოლოდ ცალკეულ
   გვერდებზე (index.html-ის `body`-ზე) ან მხოლოდ `html.is-native-app`
   სკოუფში იდგა — ანუ არასდროს იყო გარანტირებული ბაზისური დაცვა.
   თუ ჰედერში, bottom sheet-ში თუ სხვაგან ერთი ელემენტიც კი
   ვიზუალურად გასცდება viewport-ს, ბრაუზერი მთელ დოკუმენტს
   ჰორიზონტალურად სქროლვადს გახდის — ეს არის ის ეფექტი, როცა
   თითის შეხებაზე მთელი გვერდი "იძვრება" და header-ი კიდეზე იჭრება.

   `overflow-x: hidden` ამ შესაძლებლობას საძირკვლიდან კვეთს: შიდა
   ელემენტმა შეიძლება კვლავ სცადოს გაფართოება, მაგრამ ეს ვეღარასდროს
   გახდის დოკუმენტს სქროლვადს — უბრალოდ ვიზუალურად შეიჭრება (რაც
   თავისთავად კვლავ ხარვეზია), მაგრამ არ გამოიწვევს გვერდის მთლიან
   "გადაცურებას".

   ⚠ REGRESSION FIX (მომდევნო ვერსია): პირველ ვარიანტში ეს წესი
   `html, body { overflow-x: hidden }` იყო — ანუ `overflow-x`
   პირდაპირ `<html>`-საც ედებოდა. ეს კლავდა ვერტიკალურ სქროლსაც,
   არა მხოლოდ ჰორიზონტალურს, და ეს არ იყო შემთხვევითობა, არამედ
   დოკუმენტირებული Chromium/WebView ქცევა:

   ბრაუზერი (და მასზე დაფუძნებული Android System WebView, რომელშიც
   Capacitor-ის აპი ეშვება) ირჩევს ერთ ელემენტს, რომელიც რეალურად
   პასუხობს გვერდის სქროლს ("root scroller"). სტანდარტული, ათწლეულების
   განმავლობაში მოქმედი წესია: `<html>` თავად რჩება `overflow: visible`
   (ანუ ხელუხლებლად), ხოლო `<body>`-ზე დასმული overflow მნიშვნელობა
   ავტომატურად "ინაცვლებს" ზემოთ, viewport-ზე (CSS Overflow Module-ის
   "overflow propagation" მექანიზმი). როცა `overflow-x` საკუთარ თავზე,
   პირდაპირ `<html>`-ს მივეცი, ეს პროპაგაცია გავტეხე — root-ი
   აღარასოდეს ჩანდა "სუფთა/ნაგულისხმევ" მდგომარეობაში, WebView-მ
   ვეღარ დაადგინა ცალსახად, ვინ არის რეალური scroll container, და
   შედეგად საერთოდ არცერთმა ღერძმა ვეღარ იმუშავა.

   ფიქსი: `overflow-x` მხოლოდ `body`-ზე რჩება (სადაც სპეციფიკაციითვე
   მოსალოდნელია და გამართულად მუშაობს), `<html>`-ს overflow საერთოდ
   აღარ ეხება — მისი სიგანის/viewport-ის კონტროლი მთლიანად `body`-ს
   overflow-ის პროპაგაციაზეა დამოკიდებული, როგორც ბრაუზერისთვის
   ბუნებრივია. `overflow-y: auto` აქ განზრახ ცხადადაა ჩაწერილი და არ
   არის დატოვებული ბრაუზერის ნაგულისხმევ გამოთვლაზე (`visible` →
   `auto` ავტომატური კონვერტაცია overflow-x-თან წყვილში) — ორაზროვნება
   საერთოდ არ რჩება. */
   ⚠ `100vw` განზრახ აღარ გამოიყენება (იყო წინა ვერსიაში):
   `100vw` არის viewport-ის სრული სიგანე ვერტიკალური სქროლბარის
   ჩათვლით, ხოლო `html`-ის რეალური სიგანე სქროლბარის გარეშეა.
   ანუ `max-width: 100vw` სქროლბარიან ეკრანზე ელემენტს viewport-ზე
   რამდენიმე პიქსელით უფრო განიერს ხდის — თავად ხდება ჰორიზონტალური
   overflow-ის წყარო. cutout-იან (notch) ეკრანებზე `viewport-fit=cover`-
   თან ერთად იგივე პრობლემაა. `100%` მშობლის რეალურ სიგანეს ეყრდნობა
   და ამ ხაფანგს საერთოდ არ ქმნის. */
html {
  width: 100%;
  max-width: 100%;
}

body {
  overflow-x: hidden !important;
  overflow-y: auto !important;
  width: 100% !important;
  max-width: 100% !important;
  position: relative;
}

/* Preflight (Tailwind-ის ბაზისური რესეტი) ამასაც აკეთებს, მაგრამ ეს
   ვერსია პირველი იტვირთება და არცერთ პირობაზე არ არის დამოკიდებული —
   მუშაობს მაშინაც კი, თუ `dist/tailwind.css` რაიმე მიზეზით ვერ
   ჩაიტვირთა (ქსელის შეცდომა, ძველი ქეში build-ის წინ). box-sizing-ის
   გარეშე padding/border ემატება დეკლარირებულ სიგანეს ზემოთ — ეს
   ზუსტად ის მექანიზმია, რომლითაც ერთი პიქსელით ზედმეტი padding
   მთელ სვეტს viewport-ს გარეთ გამოსწევს. */
*, *::before, *::after {
  box-sizing: border-box;
}

/* --- Main wrapper containment ------------------------------
   `overflow-x: hidden` on html/body stops the DOCUMENT from becoming
   scrollable, but a fixed header row (flex, `justify-between`, no
   wrap) can still refuse to shrink below its content's width — by
   default a flex item's minimum width is `auto` (its own content
   size), not 0. That silently defeats `flex-shrink` on every sibling
   and is the single most common cause of "header icons pushed off
   the left edge" on a narrow phone. `min-width: 0` overrides that
   default so these rows can actually compress instead of forcing
   width. `max-width: 100%` is the same clamp as the html/body rule,
   scoped down to the wrappers that actually define page width. */
header, nav, main, .nav-glass, #main-header {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

/* NOT included above: `#account-cluster` is a content-sized pill (it
   shares the header row with the vendor CTA), not a full-width
   wrapper — forcing `width: 100%` on it would stretch the pill across
   the whole row and break its layout. It only needs the shrink-fix,
   not the width lock. */
header .flex, nav .flex, #main-header .flex, #account-cluster {
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

/* --- დეკორატიული glow ლაქები --------------------------------
   `<div class="glow glow-blue w-[500px] h-[500px] fixed top-0 left-0">`
   500px სიგანისაა — 360px-იან ტელეფონზე ეკრანს 140px-ით სცდება.
   `position: fixed` ჩვეულებრივ დოკუმენტის სქროლის არეს არ ზრდის,
   ამიტომ ვებზე ეს უვნებელი იყო; მაგრამ როგორც კი რომელიმე წინაპარს
   transform გაუჩნდება, fixed ელემენტი ჩვეულებრივ absolute-ად
   იქცევა და overflow-ში სრულად ითვლება. ეს ზუსტად ის სცენარი იყო,
   რომელიც page-transition-ის transform-მა შექმნა.
   ახლა ის ბაგი აღმოფხვრილია, მაგრამ ლაქებს მაინც ვზღუდავთ: ისინი
   წმინდა დეკორაციაა (`pointer-events: none`, `filter: blur(110px)`),
   ანუ ეკრანზე მოჭრა მათ ვიზუალურად არაფერს აკლებს, სამაგიეროდ
   ერთი პოტენციური overflow-წყარო სამუდამოდ იხურება. */
.glow {
  max-width: 100%;
  max-height: 100%;
}

/* --- Skeleton loaders (universal — web AND app) ------------- */
/* Shared by any page that wants a shimmering placeholder instead of a
   blank/text loading state — currently index.html's product grid. Not
   gated behind html.is-native-app: a loading skeleton is strictly
   better than plain text everywhere, not an app-only nicety. */
.skeleton-block {
  position: relative;
  overflow: hidden;
  background: rgba(51, 65, 85, 0.4);
}

.skeleton-block::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent);
  animation: skeletonShimmer 1.6s ease-in-out infinite;
}

@keyframes skeletonShimmer {
  100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-block::after { animation: none; }
}

/* --- Root canvas ------------------------------------------- */
/* WHITE-FLASH FIX: ფონი ყველა გვერდზე მხოლოდ `body`-ზე იყო აყენებული,
   `html`-ზე — არსად. მაგრამ დოკუმენტის ჩანაცვლებისას (და სქროლის
   საზღვრებს გარეთ) ბრაუზერი სწორედ `html`-ის ფონს ხატავს, რაც
   ნაგულისხმევად თეთრია. სწორედ ეს იყო გვერდებს შორის გადასვლის
   თეთრი აფეთქება. `background-color` მემკვიდრეობით არ გადადის, ამიტომ
   ორივეზე ცალკე უნდა იდგეს. */
html {
  background-color: #0B1121;
}

/* --- Typography ------------------------------------------- */
body {
  line-height: 1.65;
  letter-spacing: -0.01em;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5 {
  letter-spacing: -0.028em;
  line-height: 1.15;
}

p { text-wrap: pretty; }

/* --- Glass Effects — Premium Frosted ---------------------- */
.glass-card,
.glass-form,
.glass-panel,
.prod-card,
.val-card,
.ticket-card,
.res-card,
.section-card {
  backdrop-filter: blur(28px) saturate(165%) !important;
  -webkit-backdrop-filter: blur(28px) saturate(165%) !important;
}

.glass-card,
.glass-form,
.prod-card,
.val-card,
.res-card {
  background: rgba(10, 16, 34, 0.58) !important;
  border: 1px solid rgba(255, 255, 255, 0.055) !important;
  border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
}

.glass-panel {
  background: rgba(10, 16, 34, 0.72) !important;
  border: 1px solid rgba(255, 255, 255, 0.055) !important;
}

.ticket-card {
  background: rgba(10, 16, 34, 0.62) !important;
  border: 1px solid rgba(255, 255, 255, 0.07) !important;
  border-top: 1px solid rgba(255, 255, 255, 0.12) !important;
}

.nav-glass {
  background: rgba(7, 11, 24, 0.92) !important;
  backdrop-filter: blur(32px) saturate(180%) !important;
  -webkit-backdrop-filter: blur(32px) saturate(180%) !important;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04) !important;
}

/* --- Button CTA — Tactile, color-locked ------------------- */
.btn-cta {
  background: linear-gradient(148deg, #FCD34D 0%, #FBBF24 42%, #D97706 100%) !important;
  color: #0a0a0a !important;
  font-weight: 900 !important;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition:
    transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow 0.22s ease !important;
  text-shadow: none !important;
}

.btn-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(255,255,255,0.18) 0%, transparent 55%);
  pointer-events: none;
  z-index: 1;
  border-radius: inherit;
}

.btn-cta:hover {
  transform: translateY(-3px) !important;
  box-shadow:
    0 20px 40px -8px rgba(251, 191, 36, 0.45),
    0 6px 16px -4px rgba(251, 191, 36, 0.22) !important;
  color: #0a0a0a !important;
}

.btn-cta:active {
  transform: translateY(0px) scale(0.98) !important;
  box-shadow: 0 4px 12px rgba(251, 191, 36, 0.2) !important;
}

.btn-cta:disabled,
.btn-sold {
  background: #1E293B !important;
  color: #475569 !important;
  box-shadow: none !important;
  transform: none !important;
  cursor: not-allowed !important;
  pointer-events: none !important;
}

.btn-cta:disabled::before { display: none; }

/* --- Button Ghost ----------------------------------------- */
.btn-ghost {
  color: #FBBF24 !important;
  transition:
    transform 0.2s ease,
    background 0.2s ease,
    border-color 0.2s ease,
    box-shadow 0.2s ease !important;
}

.btn-ghost:hover {
  background: rgba(251, 191, 36, 0.14) !important;
  border-color: rgba(251, 191, 36, 0.5) !important;
  color: #FBBF24 !important;
  transform: translateY(-1px) !important;
  box-shadow: 0 0 24px rgba(251, 191, 36, 0.1) !important;
}

.btn-ghost:active {
  transform: scale(0.98) !important;
}

/* --- Product / Value Cards — Lift + Glow ------------------ */
/* HOVER SMOOTHING v4: matches index.html's inline .prod-card/.val-card
   rule exactly (same 1s cubic-bezier) — this file loads after that
   inline <style> block, so on any cascade tie this is the one that wins;
   keeping both at the identical duration/easing means it doesn't matter
   which one actually applies. */
.prod-card {
  transition:
    transform 1s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 1s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 1s cubic-bezier(0.16, 1, 0.3, 1) !important;
}

.prod-card:hover {
  transform: translateY(-8px) !important;
  box-shadow:
    0 32px 60px -12px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(251, 191, 36, 0.1),
    0 0 50px -10px rgba(251, 191, 36, 0.14) !important;
  border-top-color: rgba(251, 191, 36, 0.22) !important;
}

.val-card {
  transition:
    transform 1s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 1s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 1s cubic-bezier(0.16, 1, 0.3, 1) !important;
}

.val-card:hover {
  transform: translateY(-4px) !important;
  box-shadow:
    0 20px 44px -8px rgba(0, 0, 0, 0.44),
    0 0 32px -10px rgba(251, 191, 36, 0.09) !important;
}

.res-card {
  transition: transform 0.28s ease, box-shadow 0.28s ease !important;
}

.res-card:hover {
  transform: translateY(-4px) !important;
  box-shadow: 0 16px 36px -8px rgba(0, 0, 0, 0.42) !important;
  border-top-color: rgba(56, 189, 248, 0.22) !important;
}

/* --- Input Fields ----------------------------------------- */
.input-field {
  background: rgba(5, 9, 20, 0.72) !important;
  border: 1px solid rgba(255, 255, 255, 0.07) !important;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease !important;
  font-size: 15px;
}

.input-field:focus {
  background: rgba(5, 9, 20, 0.9) !important;
  border-color: rgba(251, 191, 36, 0.42) !important;
  box-shadow:
    0 0 0 3px rgba(251, 191, 36, 0.08),
    inset 0 1px 3px rgba(0, 0, 0, 0.3) !important;
}

.input-field::placeholder {
  color: rgba(148, 163, 184, 0.5);
}

/* --- Glow refinements ------------------------------------- */
.glow-blue  { background: radial-gradient(circle at center, rgba(56,189,248,0.14) 0%, transparent 72%); }
.glow-amber { background: radial-gradient(circle at center, rgba(251,191,36,0.09) 0%, transparent 72%); }

/* --- Lucide icons baseline -------------------------------- */
[data-lucide] {
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

svg.lucide {
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

/* --- Custom scrollbar ------------------------------------- */
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: rgba(255,255,255,0.02); }
::-webkit-scrollbar-thumb { background: rgba(251,191,36,0.22); border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: rgba(251,191,36,0.42); }

/* --- Touch targets ---------------------------------------- */
.btn-cta  { min-height: 44px; }
.btn-ghost { min-height: 40px; }

/* --- Ticker / announce bar -------------------------------- */
.ticker-track {
  will-change: transform;
}

/* --- Category filter buttons ------------------------------ */
/* PERF: `all` აქ განსაკუთრებით ძვირი იყო — ჩიპები ჰორიზონტალურ სქროლში
   სხედან, ანუ სწორედ იმ მომენტში ხელახლა იხატებოდნენ, როცა თითი
   მოძრაობს. ცხადი სია + ერთიანი მრუდი. */
.cat-btn {
  transition:
    transform 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native),
    border-color 0.2s var(--ease-native),
    color 0.2s var(--ease-native) !important;
}
.cat-btn:hover {
  transform: translateY(-1px) !important;
}

/* --- FAQ accordion ---------------------------------------- */
.faq-item {
  transition: border-color 0.3s ease, background 0.3s ease !important;
}

/* --- Ticket gold bar -------------------------------------- */
.ticket-card::before {
  background: linear-gradient(90deg, #FCD34D, #FBBF24 50%, #D97706) !important;
}

/* --- Standards cards top accent --------------------------- */
.bg-slate-800\/40.group::before {
  transition: opacity 0.3s ease;
}

/* --- Product card reveal animation ------------------------ */
@keyframes cardSlideIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

.prod-card.active {
  animation: cardSlideIn 0.38s cubic-bezier(0.5, 0, 0, 1) both;
}

/* --- Modals ----------------------------------------------- */
#productModal  .bg-slate-900,
#checkoutModal .bg-slate-900 {
  background: rgba(8, 14, 30, 0.96) !important;
}

/* --- Approve / Reject buttons (admin) --------------------- */
.btn-approve {
  transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease !important;
}
.btn-approve:hover {
  transform: translateY(-1px) !important;
  box-shadow: 0 6px 16px rgba(16, 185, 129, 0.28) !important;
}

.btn-reject {
  transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease !important;
}
.btn-reject:hover {
  transform: translateY(-1px) !important;
  box-shadow: 0 6px 16px rgba(244, 63, 94, 0.28) !important;
}

/* --- Notification badge pulse ----------------------------- */
#notif-badge {
  animation: notifPulse 2.2s ease-in-out infinite;
}

@keyframes notifPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(244, 63, 94, 0.5); }
  50%       { box-shadow: 0 0 0 5px rgba(244, 63, 94, 0); }
}

/* --- Section action cards (dashboard) --------------------- */
.hover\:border-emerald-500\/50:hover { transition: border-color 0.2s ease; }
.hover\:border-sky-500\/50:hover     { transition: border-color 0.2s ease; }
.hover\:border-purple-500\/50:hover  { transition: border-color 0.2s ease; }

/* --- Price typography ------------------------------------- */
.price-big {
  font-feature-settings: "tnum" 1;
  letter-spacing: -0.03em;
}

/* --- Icon containers: consistent touch area --------------- */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: 12px;
  transition: background 0.2s ease, transform 0.2s ease;
}

/* ============================================================
   DESIGN AUDIT — UNIFIED LIVE-PULSE MOTIF
   Every "this is live" indicator on the site (hero viewer count,
   product-card social proof, etc) used to be its own hand-rolled
   two-span ping+dot pair, each with whatever timing happened to get
   copy-pasted in. That's now one shared class: a single element with
   `color: <accent>` set via a text-color utility drives both the
   solid dot (::after) and its expanding ring (::before) off
   `currentColor`, and every instance shares the exact same duration/
   easing — one calm rhythm site-wide instead of several
   independently-paced pulses competing for attention.
   Usage: <span class="live-pulse-dot w-2 h-2 text-amber-400"></span>
   ============================================================ */
@keyframes livePulseRing {
  0%   { transform: scale(1);   opacity: 0.65; }
  100% { transform: scale(2.2); opacity: 0; }
}
.live-pulse-dot {
  position: relative;
  display: inline-flex;
  border-radius: 9999px;
}
.live-pulse-dot::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  background: currentColor;
  animation: livePulseRing 1.8s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.live-pulse-dot::after {
  content: '';
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 9999px;
  background: currentColor;
}

/* ============================================================
   DESIGN AUDIT — QUIET STATUS CHIP SYSTEM
   Replaces the previous per-badge one-off styling (solid rose fill,
   solid amber fill, etc) with ONE neutral dark chip shell — only the
   text/icon color carries meaning. This is what keeps sold-out/
   locked/low-stock/discount badges feeling like one coherent system
   instead of four competing colored blocks, and keeps the site's one
   saturated amber gradient reserved for the actual reserve/checkout
   button rather than being spent on status chrome.
   ============================================================ */
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.4em 0.9em;
  border-radius: 9999px;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: rgba(8, 14, 30, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(6px);
  white-space: nowrap;
}
.status-chip.tone-rose    { color: #FB7185; }
.status-chip.tone-amber   { color: #FBBF24; }
.status-chip.tone-orange  { color: #FB923C; }
.status-chip.tone-emerald { color: #34D399; }

/* ============================================================
   MODAL CHIP — პროდუქტის მოდალის აბზინდები ერთ ზომაზე
   ------------------------------------------------------------
   მდგომარეობის აბზინდა („ვიტრინის ნიმუში") და მიტანის აბზინდა
   („მხოლოდ ადგილზე გატანით") ერთ მწკრივში დგანან, მაგრამ ვიზუალურად
   არ ემთხვეოდნენ. სამი მიზეზი იყო:

     1. მდგომარეობის აბზინდა ჩვეულებრივი `<span>`-ია, მიტანისა კი
        ხატულას შეიცავს და `inline-flex`-ია — ხატულის სიმაღლე
        ყუთს წელავდა;
     2. `padding`-ზე დაშენებული სიმაღლე ტექსტის line-height-ზეა
        დამოკიდებული, ანუ ქართული და ლათინური შიგთავსი სხვადასხვა
        სიმაღლეს იძლეოდა;
     3. მდგომარეობის კონსტანტას `mb-4` ჰქონდა შემორჩენილი იმ
        დროიდან, როცა ის ცალკე ბლოკი იყო — flex-მწკრივში ეს
        ქვედა კიდეს წევდა და ორ აბზინდას ვერტიკალურად ჰყოფდა.

   ფიქსი: ორივე ერთსა და იმავე კლასს იყენებს, ფიქსირებული
   `height`-ით. ფიქსირებული სიმაღლე აქ არსებითია — სწორედ ის
   უზრუნველყოფს, რომ ხატულიანი და ხატულის გარეშე აბზინდა ერთნაირი
   იყოს. ფერი ცალკე კლასებით ემატება.
   ============================================================ */
.modal-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border-radius: 7px;
  border: 1px solid transparent;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  /* ჩამონათვალის ვერტიკალურ ცენტრში, ტექსტის baseline-ის მიუხედავად. */
  vertical-align: middle;
}
.modal-chip svg,
.modal-chip i {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* ============================================================
   DISCOUNT BADGE — ფასდაკლების ნიშანი პროდუქტის ბარათზე
   ------------------------------------------------------------
   რატომ არ არის ეს `.status-chip`-ის კიდევ ერთი „ტონი":
   status-chip-ები ერთმანეთს გამორიცხავენ (იხ. getCardStatusChip) —
   ერთ ბარათზე ერთადერთი ჩნდება. ფასდაკლება კი ორთოგონალური
   ინფორმაციაა: ფასდაკლებული ნივთი შეიძლება ერთდროულად ბოლო
   ეგზემპლარიც იყოს და გაყიდულიც. ამიტომ ცალკე კომპონენტია და
   სურათის სხვა კუთხეში დგას — ისინი არასდროს ერთმანეთს არ ეფარება.

   ვიზუალურად განზრახ „ხმამაღალია": მაძღარი გრადიენტი და ნათება
   ბარათის მშვიდ, გამჭვირვალე chip-ებს შორის თვალს იპყრობს.
   ============================================================ */
.discount-badge {
  /* ⚠ ერთხაზიანი. ადრე `flex-direction: column` + `gap` ორ სართულს
     აწყობდა (პროცენტი და დაზოგილი თანხა). თანხა ამოღებულია, ანუ
     სვეტიც და დაშორებაც ზედმეტია — ერთადერთი შვილი ორივე ღერძზე
     ცენტრში დგება.

     `padding` ხელახლა შერჩეულია: 7px ორი ხაზისთვის იყო გათვლილი და
     ერთხაზიან ყუთს გაწელილს ხდიდა. 6/13 ოპტიკურად კვადრატულ
     შვებას იძლევა 16px-იან ციფრებთან. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 13px;
  border-radius: 10px;
  line-height: 1;
  /* ⚠ მკვრივი ფერი და არა გრადიენტი.
     გრადიენტის ღია ბოლო (#F43F5E) თეთრ ტექსტთან მხოლოდ 3.67:1
     კონტრასტს იძლეოდა — სწორედ ის კითხულობდა „გადავლებულად".
     მკვრივი #E11D48 იმავე თეთრთან 4.70:1-ია, ანუ სრული WCAG AA.
     ანუ უფრო მკვეთრიც გახდა და უფრო წაკითხვადიც — ერთდროულად. */
  background: #E11D48;
  border: 1px solid #F43F5E;
  box-shadow: 0 8px 20px -4px rgba(190, 18, 60, 0.55);
  color: #fff;
  white-space: nowrap;
  /* ბარათის სურათი hover-ზე იზრდება (group-hover:scale-105) —
     ნიშანი მასზე მაღლა უნდა იდგეს, თორემ ზუმი მას გადაფარავს. */
  z-index: 10;
  /* მუქ ფონზე თეთრი მსხვილი ციფრები subpixel-რენდერით სქელდება. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.discount-badge .discount-pct {
  font-size: 16px;
  font-weight: 900;
  letter-spacing: -0.01em;
  /* tabular-nums: „−5%" და „−50%" ერთნაირად ცენტრირებული რჩება. */
  font-variant-numeric: tabular-nums;
}
/* მინუსი ციფრს ეკვროდა („−40%"). სუფთა სივრცის სიმბოლოს ნაცვლად
   margin-ია: მისი სიგანე შრიფტზე არაა დამოკიდებული და ხაზის
   გადატანასაც ვერ გამოიწვევს. */
.discount-badge .discount-minus {
  margin-right: 0.14em;
  font-weight: 800;
}

/* გაყიდულ/ჩაკეტილ ბარათს `opacity-75` აქვს — ნიშანი იქ განზრახ
   ჩუმდება, რომ მიუწვდომელ ნივთს ყურადღება არ წაართვას. */
.prod-card.opacity-75 .discount-badge {
  background: rgba(8, 14, 30, 0.88);
  border-color: rgba(255, 255, 255, 0.1);
  box-shadow: none;
  color: #FB7185;
}

/* მოდალში ნიშანი ფასის გვერდით დგას, ანუ ოდნავ უფრო მჭიდრო უნდა
   იყოს. `flex-direction`/`gap` აქ აღარ იწერება — ბაზისი უკვე
   ერთხაზიანია და გადაწერას აღარ საჭიროებს. */
.discount-badge.badge-inline {
  padding: 5px 11px;
}

/* --- ფასის ზოლი ბარათზე ------------------------------------- */
.price-old {
  color: #64748B;
  text-decoration: line-through;
  font-size: 13px;
  font-weight: 700;
}
.price-save {
  color: #34D399;
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

/* --- Verified vendor badge --------------------------------- */
/* Sky is otherwise unused as an "action" color on this site (amber is
   reserved for that), so it reads distinctly as "informational trust
   signal" rather than competing with the CTA or the danger/urgency
   palette (rose/amber/orange above). */
.verified-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.01em;
  color: #38BDF8;
  white-space: nowrap;
}

/* --- Card title: fixed-height zone -------------------------- */
/* Caps product names at 2 lines so a long name can't push the price/
   button zone to a different height than its row-mates — the actual
   fix for grid-row misalignment, rather than relying only on flex
   mt-auto to compensate after the fact. */
.card-title-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.6em;
}

.icon-btn:hover {
  transform: scale(1.08);
}

/* --- Inline icon SVGs (Lucide-style replacements) --------- */
.ui-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 0;
}

/* --- Star rating ------------------------------------------ */
.star-btn {
  transition: transform 0.15s ease, color 0.15s ease !important;
}
.star-btn:hover {
  transform: scale(1.25) rotate(-4deg) !important;
}

/* ============================================================
   NATIVE APP GATING (Phase 0)
   Everything under an `html.is-native-app` selector only ever applies
   when native-shell.js has detected the Capacitor Android app — a
   regular web visitor's cascade never matches these rules, so the
   website itself is completely unaffected by anything added here.

   Add new native-only overrides in this section as later phases land
   (bottom nav, FAB, bottom-sheet filters, etc.) rather than scattering
   `.is-native-app` rules throughout the file, so every native-vs-web
   divergence stays easy to find and audit in one place.
   ============================================================ */

/* Example (Phase 0 proof): the marketing footer exists to build trust
   with cold web/SEO traffic — dead weight for someone who already
   installed the app. Hidden only inside the wrapped app. */
html.is-native-app #site-footer {
  display: none;
}

/* --- Bottom navigation + simplified header (native only) ---
   Only index.html and user-dashboard.html carry a `.bottom-nav` element —
   these are the customer app's two "home base" screens (catalog and the
   customer's own reservations/favorites). Vendor/admin tools
   (dashboard.html, partner.html, admin.html) already have their own
   internal tab systems and keep their existing desktop-oriented UI
   untouched; ticket.html stays nav-free on purpose, since it's a brief,
   single-purpose confirmation screen rather than a tab destination.

   The bare `.bottom-nav { display: none }` below is the one rule in this
   section NOT scoped to `html.is-native-app` — it has to apply
   unconditionally so the element can never flash visible/unstyled on a
   web pageview before (or if) the native gate takes effect. Everything
   else about the component still only ever renders under the native
   gate. */
.bottom-nav {
  display: none;
}

html.is-native-app body.has-bottom-nav {
  /* Keeps page content from being covered by the fixed bar below —
     scoped to `.has-bottom-nav` (not a blanket `body` rule) so the six
     pages that don't carry a bottom nav never get this padding. */
  padding-bottom: calc(64px + env(safe-area-inset-bottom));
}

/* GRID, არა FLEX — და ეს არსებითი განსხვავებაა.
   ადრე იყო `display: flex; justify-content: space-around` +
   `.bottom-nav-item { flex: 1 }`. `flex: 1` ნიშნავს
   `flex: 1 1 0%`, მაგრამ flex-item-ის ნაგულისხმევი `min-width`
   არის `auto` — ანუ "ჩემს კონტენტზე ვიწრო ვერასდროს გავხდები".
   ქართული წარწერები ("რჩეულები", "ჯავშნები", "პროფილი") 10px-ზეც
   კი საკმარისად გრძელია, რომ ოთხმა ელემენტმა ერთად 360px-იან
   ეკრანს გადააჭარბოს — და `flex: 1`-ის მიუხედავად ისინი არ
   იკუმშებოდნენ, არამედ ზოლს განიერს ხდიდნენ.

   `grid-template-columns: repeat(4, minmax(0, 1fr))` ამას ჭრის:
   `minmax(0, ...)`-ის `0` ცხადად აუქმებს "კონტენტზე ვიწრო ვერ
   ვიქნები" ნაგულისხმევს, ანუ ოთხივე სვეტი ყოველთვის ზუსტად
   ეკრანის მეოთხედია — მიუხედავად წარწერის სიგრძისა. `1fr`
   მარტო (`minmax(auto, 1fr)`-ის ტოლი) ამას ვერ გააკეთებდა. */
html.is-native-app .bottom-nav {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  align-items: stretch;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  overflow-x: hidden;
  background: rgba(11, 17, 33, 0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  padding-bottom: env(safe-area-inset-bottom);
}

html.is-native-app .bottom-nav-item {
  /* `flex: 1` აღარ საჭიროა — სვეტის სიგანეს ახლა grid განსაზღვრავს.
     `min-width: 0` კვლავ აუცილებელია: grid-item-საც აქვს იგივე
     ნაგულისხმევი `min-width: auto`, რომელიც კონტენტზე ვიწროდ
     შეკუმშვას კრძალავს. `minmax(0, 1fr)`-თან ერთად ეს ორმაგი
     გარანტიაა, რომ გრძელი წარწერა სვეტს ვერ გააგანიერებს. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 10px 2px 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #64748B;
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.15s ease, transform 0.1s ease;
  -webkit-tap-highlight-color: transparent;
  box-sizing: border-box;
}

/* წარწერა ერთ ხაზზე რჩება; თუ რომელიმე ენაზე/შრიფტზე მაინც არ
   ჩაეტევა, სამი წერტილით იჭრება — და არა ზოლის გაგანიერებით. */
html.is-native-app .bottom-nav-item > span {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ხატულა ვერასდროს შეიკუმშება ტექსტის სასარგებლოდ. */
html.is-native-app .bottom-nav-item [data-lucide],
html.is-native-app .bottom-nav-item svg {
  flex-shrink: 0;
}

/* სვეტების რაოდენობის ვარიანტები. ნაგულისხმევი ოთხია (მომხმარებლის
   აპი); ადმინს სამი ტაბი აქვს. `data-items`-ით მართვა ნიშნავს, რომ
   ახალი ეკრანისთვის მხოლოდ ატრიბუტი იცვლება და არა ცალკე კლასების
   ნაკრები. `minmax(0, 1fr)` — იგივე მიზეზით, რაც ოთხსვეტიანში:
   `1fr` მარტო კონტენტზე ვიწრო ვერ ხდება და ზოლს გაატანდა. */
html.is-native-app .bottom-nav[data-items="3"] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

html.is-native-app .bottom-nav[data-items="5"] {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* ADMIN: ზედა ჰორიზონტალური ტაბ-ზოლი აპში იმალება — მისი ფუნქცია
   ქვედა ბადემ ჩაიბარა. ვებზე `#admin-tabs` ხელუხლებელი რჩება. */
html.is-native-app #admin-tabs {
  display: none !important;
}

/* admin.html-ს lucide არ ჩაუტვირთავს, ამიტომ მისი ბოტომ-ნავის
   ხატულები pure inline SVG-ია და ზომა ცხადად უნდა მიეცეს (lucide-ის
   მიერ გენერირებულ svg-ებს ეს ატრიბუტებიდან მოსდით). */
html.is-native-app #admin-bottom-nav .bottom-nav-item svg {
  width: 22px;
  height: 22px;
}

html.is-native-app .bottom-nav-item [data-lucide],
html.is-native-app .bottom-nav-item svg {
  width: 22px;
  height: 22px;
}

html.is-native-app .bottom-nav-item.is-active {
  color: #FBBF24;
}

html.is-native-app .bottom-nav-item:active {
  transform: scale(0.92);
}

/* Simplified header: the vendor-signup CTA is a cold-web-traffic
   acquisition funnel — meaningless chrome for someone who already
   installed the app as a customer. */
html.is-native-app #nav-vendor-btn {
  display: none;
}

/* --- Bottom sheet filters + FAB (native only) ---------------
   On web, #category-filter-row/#filter-controls-row stay exactly where
   they are, inline in #offers — this whole block never applies there.
   In the app, index.html's initNativeFilterSheet() (see its own
   <script>) moves those SAME two elements into #filter-sheet-body once
   at load, so there's one copy of the filter DOM/logic either way.

   As with .bottom-nav, .filter-fab/.filter-sheet-backdrop/.filter-sheet
   get one unconditional `display: none` each — a safety default so
   they can never flash visible/unstyled on a web pageview — with the
   actual styled, positioned versions living under html.is-native-app. */
.filter-fab,
.filter-sheet-backdrop,
.filter-sheet {
  display: none;
}

/* Z-INDEX SCALE (documented so future additions don't guess):
     content 0–10 · header 50 · bottom-nav 60 · FAB 65 · PTR 70
     · modals 100–300 · filter sheet 9998/9999

   DELIBERATE DEVIATION: the FAB is NOT pushed to 9999+. It's always
   on screen, so a 9999 FAB would float ON TOP of the product modal
   (z-100), checkout modal (z-110), alerts (z-200) and toasts (z-300) —
   trading one bug for a worse one. At 65 it sits above all page content
   and the bottom nav (the actual requirement) while correctly falling
   behind any open modal, with no JS coordination needed. The SHEET does
   get 9998/9999, since it's mutually exclusive with modals (its backdrop
   blocks the taps that would open one). */
/* REGRESSION GUARD: this selector intentionally includes the `button`
   element (specificity 0,2,2) rather than the bare `.filter-fab`
   (0,2,1) it used to use, and pins `position` with !important. The tap
   ripple previously shipped `html.is-native-app .ripple-host {
   position: relative }` at the SAME 0,2,1 specificity but later in the
   file — so it silently won, knocked the FAB out of fixed positioning
   into normal flow, and made it move mid-gesture (which also ate the
   first tap). Raising specificity here means an equal-weight rule can
   no longer take this element's positioning away by accident. */
html.is-native-app button.filter-fab {
  display: flex;
  align-items: center;
  gap: 8px;
  position: fixed !important;
  right: 20px;
  bottom: calc(84px + env(safe-area-inset-bottom));
  z-index: 65;
  padding: 14px 20px;
  border-radius: 9999px;
  background: linear-gradient(135deg, #FBBF24, #D97706);
  color: #000;
  font-weight: 800;
  font-size: 13px;
  border: none;
  cursor: pointer;
  box-shadow: 0 10px 24px rgba(251, 191, 36, 0.35), 0 2px 8px rgba(0, 0, 0, 0.3);
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.15s ease;
}

html.is-native-app button.filter-fab [data-lucide],
html.is-native-app button.filter-fab svg {
  width: 18px;
  height: 18px;
}

html.is-native-app button.filter-fab:active {
  transform: scale(0.94);
}

html.is-native-app .filter-sheet-backdrop {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

html.is-native-app .filter-sheet-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

html.is-native-app .filter-sheet {
  display: flex;
  flex-direction: column;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  /* HARD WIDTH CLAMP: `left:0; right:0` already pins this box to the
     viewport width, but that only constrains the BOX — a child that
     refuses to shrink (a flex row with no wrap, or one that fails to
     wrap for any reason) can still paint outside it. `overflow-x`
     turns that from "bleeds off the fixed sheet, and can even widen
     the document's own scrollable area" into "gets silently clipped
     at the sheet's own edge" — visually contained no matter what a
     descendant does. */
  overflow-x: hidden;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  max-height: 80vh;
  background: #0B1121;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 24px 24px 0 0;
  box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
  transform: translateY(100%);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  padding-bottom: env(safe-area-inset-bottom);
}

html.is-native-app .filter-sheet.is-open {
  transform: translateY(0);
}

html.is-native-app .filter-sheet-handle {
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.2);
  margin: 12px auto 4px;
  flex-shrink: 0;
}

html.is-native-app .filter-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  flex-shrink: 0;
}

html.is-native-app .filter-sheet-header h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  color: #F8FAFC;
}

html.is-native-app .filter-sheet-header button {
  background: rgba(255, 255, 255, 0.06);
  border: none;
  color: #94A3B8;
  width: 32px;
  height: 32px;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}

html.is-native-app .filter-sheet-body {
  overflow-y: auto;
  overflow-x: hidden;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  padding: 16px 20px;
  flex: 1;
  /* min-height:0 — a flex item's implicit min-height is `auto`
     (= its content's height), which can defeat `overflow-y: auto`
     inside a flex column and let content push the sheet taller than
     its `max-height: 80vh` instead of scrolling internally. */
  min-height: 0;
}

/* CATEGORY / FILTER ROWS — hard-wrap guarantee.
   `#category-filter-row` already carries Tailwind's `flex flex-wrap`
   in the markup, and `#filter-controls-row`'s search/select bar is
   equally width-bound. This rule doesn't depend on those classes
   compiling or surviving future markup edits: once EITHER row lands
   inside the sheet body (moved there at runtime by
   initNativeFilterSheet()), it is force-wrapped and clamped to the
   sheet's own content width — `.cat-btn` pills MUST drop to the next
   line instead of pushing the row (and, through it, the whole fixed
   sheet) wider than the screen. */
html.is-native-app .filter-sheet-body > #category-filter-row,
html.is-native-app .filter-sheet-body > #filter-controls-row {
  display: flex !important;
  flex-wrap: wrap !important;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}

html.is-native-app .filter-sheet-body .cat-btn {
  flex: 0 1 auto;
  max-width: 100%;
  box-sizing: border-box;
}

html.is-native-app .filter-sheet-footer {
  padding: 12px 20px 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  flex-shrink: 0;
}

/* Once initNativeFilterSheet() moves its two children into the sheet,
   this wrapper is empty — hidden so it doesn't leave a stray gap
   (its mb-10 margin) where the filter row used to be. */
html.is-native-app #filters-section-wrapper {
  display: none;
}

/* --- Pull-to-refresh (native only) --------------------------- */
/* #ptr-indicator is opt-in per page (initPullToRefresh() in
   native-shell.js no-ops if the page doesn't have one) — a regular
   mobile browser already has its own OS-level pull-to-refresh, so this
   never renders or attaches its touch listeners on web. */
#ptr-indicator {
  display: none;
}

html.is-native-app #ptr-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  top: calc(8px + env(safe-area-inset-top));
  left: 50%;
  z-index: 70;
  width: 36px;
  height: 36px;
  margin-left: -18px;
  border-radius: 9999px;
  background: #0B1121;
  border: 1px solid rgba(251, 191, 36, 0.3);
  color: #FBBF24;
  opacity: 0;
  transform: translateY(-40px);
  transition: opacity 0.15s ease;
}

html.is-native-app #ptr-indicator.is-visible {
  opacity: 1;
}

html.is-native-app #ptr-indicator.is-armed {
  border-color: #FBBF24;
  box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.15);
}

html.is-native-app #ptr-indicator [data-lucide],
html.is-native-app #ptr-indicator svg {
  width: 18px;
  height: 18px;
}

html.is-native-app #ptr-indicator.is-spinning [data-lucide],
html.is-native-app #ptr-indicator.is-spinning svg {
  animation: ptrSpin 0.7s linear infinite;
}

@keyframes ptrSpin {
  to { transform: rotate(360deg); }
}

/* ============================================================
   NATIVE HOME-SCREEN LAYOUT (app only)
   Sheds index.html's website proportions — full-viewport hero, 96px
   section padding, 32px grid gaps, marketing/SEO sections — for phone
   proportions. All of it scoped under html.is-native-app, so the web
   layout is byte-for-byte unchanged.
   ============================================================ */

/* --- Remove web-only marketing/SEO blocks ------------------- */
/* #how, #business, the standards section and #faq exist to convert
   cold web/SEO traffic and build first-time trust. Someone who already
   installed the app doesn't need re-pitching on every catalog visit. */
html.is-native-app .web-only-section {
  display: none;
}

/* Inverse of the above: markup that ONLY exists for the native app.
   Unconditional `display: none` first (so it can never flash on a web
   pageview before the native gate is evaluated), then revealed. */
.native-only-section {
  display: none;
}

html.is-native-app .native-only-section {
  display: block;
}

/* --- Compact hero ------------------------------------------- */
/* The web hero is a full-screen statement (min-h-screen + pt-48). On a
   phone that's an entire screen of scrolling before any product shows —
   the single most "website, not app" thing on the page. */
/* padding-top ჰედერის რეალურ სიმაღლეს მიჰყვება და ხელით არ ირჩევა:
   ტიკერი (37px) + ჰედერი (20px padding + 44px სამიზნე = 64px) = 101px,
   ანუ 84px ჰეროს შიგთავსს ჰედერის ქვეშ ~20px ჰაერს უტოვებს. ადრე
   96px იყო და მაინც არ ჰყოფნიდა, რადგან ჰედერი ორმაგ safe-area
   ჩაჭრას ითვლიდა — იხ. `html.is-native-app #main-header` ქვემოთ. */
html.is-native-app #hero-section {
  min-height: 0 !important;
  padding-top: 84px !important;
  padding-bottom: 20px !important;
}

html.is-native-app #hero-section h1 {
  font-size: 2rem !important;
  margin-bottom: 12px !important;
}

html.is-native-app #hero-section p {
  font-size: 0.9375rem !important;
  margin-bottom: 18px !important;
}

/* --- Tighten section rhythm --------------------------------- */
html.is-native-app #offers {
  padding-top: 18px !important;
  padding-bottom: 24px !important;
}

html.is-native-app #offers > div {
  padding-left: 14px !important;
  padding-right: 14px !important;
}

/* The catalog's web heading is a 4xl italic display line; native gets a
   normal app section title. */
html.is-native-app #offers h2 {
  font-size: 1.25rem !important;
  font-style: normal !important;
}

html.is-native-app #offers .mb-14 {
  margin-bottom: 14px !important;
}

/* --- Horizontal shelf (native carousel) --------------------- */
html.is-native-app #native-shelf-section {
  padding-top: 4px;
  padding-bottom: 8px;
}

html.is-native-app .native-shelf-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 14px 10px;
}

html.is-native-app .native-shelf-head h2 {
  margin: 0;
  font-size: 1.125rem;
  font-weight: 900;
  color: #F8FAFC;
}

html.is-native-app .native-shelf-head span {
  font-size: 11px;
  font-weight: 700;
  color: #64748B;
}

html.is-native-app .native-shelf {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 14px 6px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  /* Hidden scrollbar — Firefox/IE + WebKit. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* SCROLL-TRAP FIX: this was `touch-action: pan-x`, which tells the
     browser "ONLY horizontal panning is allowed on this element" — so a
     vertical swipe starting on the rail was swallowed instead of
     scrolling the page. `pan-x pan-y` permits BOTH axes: the rail still
     scrolls horizontally, and a vertical drag bubbles up to the page as
     normal. */
  touch-action: pan-x pan-y;
}

html.is-native-app .native-shelf::-webkit-scrollbar {
  display: none;
}

html.is-native-app .native-shelf > * {
  flex: 0 0 66%;
  max-width: 250px;
  scroll-snap-align: start;
}

/* --- Compact product cards ---------------------------------- */
/* Two-up grid: at phone widths Tailwind's `sm:grid-cols-2` hasn't
   kicked in yet (640px), so the web layout gives phones ONE giant card
   per row. Two columns is the native shopping-app norm. */
html.is-native-app #dynamic-products-container {
  grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  gap: 12px !important;
}

/* PRODUCT GRID — უსაფრთხო ბადე, ნეიტივ კლასზე დამოკიდებულების გარეშე.
   ზემოთა წესი მხოლოდ `html.is-native-app`-ზე მუშაობს, ანუ თუ
   Capacitor-ის ხიდი დაგვიანდა ან საერთოდ არ ჩაირთო (ჩვეულებრივი
   მობილური ბრაუზერი), ბადე უკონტროლო რჩებოდა. ეს media query
   იმავეს აკეთებს წმინდა ეკრანის სიგანით.

   არსებითი დეტალი `minmax(0, 1fr)`-ზე: `1fr` სინამდვილეში
   `minmax(auto, 1fr)`-ის ტოლია, ხოლო `auto`-მინიმუმი ნიშნავს
   "კონტენტზე ვიწრო ვერ გავხდები". ანუ ერთი გრძელი სახელი, დიდი
   სურათი ან `white-space: nowrap`-იანი ფასი სვეტს გააგანიერებს და
   ბადეს ეკრანს გაატანს. `minmax(0, ...)` სწორედ ამ ნაგულისხმევს
   აუქმებს — სვეტი ყოველთვის ზუსტად ხელმისაწვდომი სიგანის ნახევარია.
   ეს არის #1 მიზეზი, რატომ "სკდება" Tailwind-ის `grid-cols-2`
   ვიწრო ეკრანზე. */
@media (max-width: 640px) {
  #dynamic-products-container {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
  }
}

/* ბადის ელემენტებს ასევე სჭირდებათ ცხადი min-width: 0 — იგივე
   ნაგულისხმევი `auto` მინიმუმი grid-item-ზეც მოქმედებს. */
#dynamic-products-container > * {
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

/* ბარათის შიგნით ყველაზე ხშირი გამტანი — სურათი და გრძელი
   უწყვეტი ტექსტი (სახელი, ვენდორი).

   ⚠⚠ `height: auto` აქედან განზრახ ამოღებულია — სწორედ ის ჭიმავდა
   ბარათებს.

   ეს ფაილი `dist/tailwind.css`-ის *შემდეგ* იტვირთება, ხოლო
   `.prod-card img`-ის სპეციფიკურობა (0,1,1) Tailwind-ის `.h-52`-ს
   (0,1,0) აღემატება. ანუ `height: auto` ბარათის სურათს ფიქსირებულ
   სიმაღლეს ართმევდა და ის ბუნებრივი პროპორციით იზრდებოდა:
   300×600 ფოტო 208px-ის ნაცვლად 698px-იან ყუთს ქმნიდა და მთელ
   ბარათს 1046px-მდე წელავდა. `object-fit: cover` ამ დროს უძლური
   იყო — ყუთი თავად სურათის ფორმისა ხდებოდა, ანუ დასაფარი აღარაფერი
   რჩებოდა.

   `max-width: 100%` რჩება: ჰორიზონტალური გადაცდენისგან დაცვა
   სწორედ ის იყო და ის Tailwind-ის სიმაღლეს არ ეხება.

   იგივე გადაფარვა dashboard.html-ის „ჩემი განცხადებების" 64px-იან
   მინიატიურებსაც (`w-16 h-16`) ეხებოდა — ისინიც ბუნებრივი
   სიმაღლით იხატებოდა. ერთი ხაზის მოხსნა ორივეს ასწორებს. */
.prod-card img {
  max-width: 100%;
}

.prod-card {
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

/* buildProductCard()'s inner wrapper is `p-6` (24px) — far too airy for
   a 2-up phone grid. */
html.is-native-app .prod-card > div {
  padding: 12px !important;
}

/* Uniformly compress the card's vertical rhythm. Targeting the direct
   children (rather than each individual Tailwind mb-* class) keeps this
   robust if buildProductCard's markup shifts. */
html.is-native-app .prod-card > div > * {
  margin-bottom: 8px !important;
}

html.is-native-app .prod-card > div > *:last-child {
  margin-bottom: 0 !important;
}

/* ⚠ აქ ადრე სურათს ფიქსირებული სიმაღლე ედებოდა
   (`.prod-card img { height: 132px !important }`). ორი პრობლემა
   ჰქონდა და ორივე რეალურად ჩნდებოდა:

     1. კატალოგის ბარათს ახლა `aspect-[4/3]` ყუთი აქვს. ფიქსირებული
        132px იმ ყუთს არ ემთხვეოდა — სურათი 19px-ით გადმოდიოდა და
        `overflow-hidden` მას ჭრიდა, ანუ კადრი არასწორად იჭრებოდა.

     2. `.prod-card` კლასი dashboard.html-ის „ჩემი განცხადებების"
        ბარათსაც აქვს, სადაც მინიატიურა 64px-იანია (`w-16 h-16`).
        `!important` სიმაღლე მასაც ეხებოდა და ორმაგზე მეტად
        აბერებდა.

   ორივეს ხსნის ის, რომ სიმაღლეს აღარ ვაწესებთ: ფორმას ყუთი
   (`.card-media`, aspect-ratio) განსაზღვრავს, სურათი კი უბრალოდ
   ავსებს მას. ვიწრო ეკრანზე სვეტიც ვიწროა, ანუ ყუთი თავისით
   დაბალია — ცალკე ნეიტივ-სიმაღლე აღარ სჭირდება. */
html.is-native-app .prod-card .card-media img {
  height: 100% !important;
}

html.is-native-app .prod-card .card-title-clamp {
  font-size: 0.875rem !important;
  line-height: 1.3 !important;
  min-height: 2.3em;
}

/* priceSpan is set via an inline style (font-size: 2.2rem) in
   buildProductCard, so this needs !important to win. */
html.is-native-app .prod-card .price-big {
  font-size: 1.375rem !important;
}

html.is-native-app .prod-card .status-chip {
  font-size: 9px;
  padding: 0.3em 0.6em;
}

html.is-native-app .prod-card .discount-badge {
  padding: 5px 9px;
  border-radius: 10px;
}
html.is-native-app .prod-card .discount-pct { font-size: 13px; }

html.is-native-app .prod-card .verified-badge {
  font-size: 10px;
}

/* Thumb-friendly tap targets: Material/HIG both call for ~44-48px. */
html.is-native-app .prod-card button {
  min-height: 44px !important;
  font-size: 11px !important;
}

/* The favourite heart is an absolutely-positioned 40px circle — nudge
   it to 44px and tighter to the corner on the smaller native card. */
html.is-native-app .prod-card > button {
  min-width: 44px;
  min-height: 44px;
  top: 8px !important;
  right: 8px !important;
}

/* ============================================================
   PERFORMANCE (app only)
   Everything animated in the native UI already runs on compositor-only
   properties: .filter-sheet slides with `transform`, its backdrop and
   #ptr-indicator fade with `opacity`, .bottom-nav-item/.filter-fab
   press with transform, and .skeleton-block sweeps with transform.
   (The tap ripple was removed — it set `position: relative` on
   whatever was tapped, which silently overrode .filter-fab's
   `position: fixed` at equal specificity and broke the FAB.)
   The two fixes below are the
   remaining non-accelerated costs, both hover-related — and hover
   doesn't exist on a touchscreen, so removing them in the app costs
   nothing visually.
   ============================================================ */

/* .prod-card's web transition is `transform, box-shadow, border-color`
   over a full 1s. box-shadow and border-color are painted, not
   composited, so animating them across a whole grid of cards is a real
   source of jank — and it's a mouse-hover effect the app can never
   trigger anyway. Native keeps only the compositor-friendly transform
   for the :active press. */
html.is-native-app .prod-card {
  transition: transform 0.18s ease !important;
  will-change: auto !important;
}

html.is-native-app .prod-card:hover {
  transform: none !important;
  box-shadow: none !important;
}

html.is-native-app .prod-card:active {
  transform: scale(0.98) !important;
}

/* Same reasoning for the CTA: drop the painted box-shadow hover, keep
   the transform press. */
html.is-native-app .btn-cta {
  transition: transform 0.18s ease !important;
}

html.is-native-app .btn-cta:hover {
  transform: none !important;
  box-shadow: none !important;
}

/* ============================================================
   NATIVE ADMIN / VENDOR PANELS (app only)
   admin.html and dashboard.html were built desktop-first: wide data
   tables, a 96px-wide tab strip, and web-sized tap targets. Everything
   below is scoped to html.is-native-app, so those pages stay exactly as
   they are in a desktop browser — where a real table genuinely IS the
   right tool for scanning 100 reservations.
   ============================================================ */

/* --- Data tables -> real native cards ------------------------ */
/* REPLACED APPROACH: this previously tried to re-purpose the <table>'s
   own display roles (display:block on table/tr/td + ::before labels
   from data-label). That is fragile — the Tailwind Play CDN injects its
   utilities at RUNTIME, landing in the cascade AFTER this stylesheet,
   and table display roles interact badly with border-collapse. It did
   not hold up on device.

   admin.html now renders GENUINE <div> cards for native
   (buildReservationCard / buildVendorCard) and keeps the real <table>
   for web, choosing between them at render time. So all this CSS has to
   do is show the right one and style plain divs — no display-role
   overrides, nothing for the cascade to undo. */

/* Web: the card list never exists visually. Native: the table doesn't.
   !important on all three so no utility class, inline style, or future
   rule can flip either surface on at the wrong time — this switch is
   the single guarantee that exactly one representation is ever
   visible, so it is deliberately unarguable. */
.native-cards-list {
  display: none !important;
}

html.is-native-app .native-cards-list {
  display: block !important;
}

html.is-native-app .web-only-table {
  display: none !important;
}

html.is-native-app .admin-card {
  background: rgba(15, 23, 42, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  margin-bottom: 10px;
  overflow: hidden;
}

html.is-native-app .admin-card-head {
  padding: 12px 14px 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

html.is-native-app .admin-kv {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px;
}

html.is-native-app .admin-kv-label {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #64748B;
}

html.is-native-app .admin-kv-value {
  text-align: right;
  font-size: 13px;
  color: #E2E8F0;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Actions sit at the card bottom as a full-width, thumb-sized row. */
html.is-native-app .admin-card-actions {
  display: flex;
  gap: 8px;
  padding: 10px 14px 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

html.is-native-app .admin-card-actions button {
  flex: 1;
  min-height: 44px;
}

/* --- Native top tab bar ------------------------------------- */
/* Horizontally scrollable, hidden scrollbar, thumb-sized targets —
   admin.html's 3 tabs at text-lg otherwise overflow a phone with no
   affordance that anything is off-screen. */
html.is-native-app .native-tabbar {
  gap: 20px !important;
  margin-bottom: 14px !important;
  font-size: 13px !important;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Same fix as .native-shelf — pan-x alone would trap vertical scroll. */
  touch-action: pan-x pan-y;
}

html.is-native-app .native-tabbar::-webkit-scrollbar {
  display: none;
}

html.is-native-app .native-tabbar button {
  flex-shrink: 0;
  min-height: 44px;
  font-size: 13px !important;
}

/* --- Panel spacing ------------------------------------------ */
html.is-native-app #admin-main,
html.is-native-app #vendor-main {
  padding-left: 14px !important;
  padding-right: 14px !important;
  padding-top: 14px !important;
}

/* The glass panel's rounded frame is pure desktop chrome once the table
   inside it is a stack of individual cards. */
html.is-native-app #reservations-section .glass-panel,
html.is-native-app #vendors-section .glass-panel {
  background: none !important;
  border: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border-radius: 0 !important;
  overflow: visible !important;
}

/* Admin's pending-product grid is a MODERATION QUEUE, not a shopping
   grid — the admin has to actually inspect each photo to approve or
   reject it, and each card carries two destructive/irreversible
   actions. Two-up would give ~165px cards on a 360px phone, squeezing
   the image and forcing the two action buttons into a cramped row.
   Single column is the correct call here (deliberately different from
   the customer catalog's 2-up, which is for scanning, not judging). */
html.is-native-app #pending-container {
  grid-template-columns: 1fr !important;
  gap: 12px !important;
}

/* Stack the approve/reject buttons full-width instead of w-1/2 side by
   side, so neither can end up below the 44px target. */
html.is-native-app #pending-container .flex.gap-2 {
  flex-direction: column;
  gap: 8px !important;
}

html.is-native-app #pending-container .flex.gap-2 > button {
  width: 100% !important;
  min-height: 44px;
}

/* --- Touch-friendly forms & actions ------------------------- */
/* Both panels' inputs/selects/buttons were sized for a mouse. 16px font
   on inputs also stops Android/iOS from auto-zooming the viewport on
   focus, which is its own layout-jank bug.

   ⚠⚠ `:not([type="checkbox"]):not([type="radio"])` — ეს გამონაკლისი
   აუცილებელია და მისი არარსებობა ხილული ხარვეზი იყო.

   წესი ტექსტურ ველებზეა გათვლილი: სრული სიგანე, 44px სიმაღლე, 16px
   შრიფტი. სელექტორი კი *ყველა* `input`-ს იჭერდა — მათ შორის
   მიტანის ჩექბოქსს. აპში ის `width: 100%` + `min-height: 44px`
   ხდებოდა, ანუ 16×16-ის ნაცვლად 236×44 ყუთად იშლებოდა, გვერდით
   მდგარ ტექსტს კი სიგანე 0-მდე უვიწროვდებოდა და მთელი ბლოკი
   283px-იდან 697px-მდე იზრდებოდა.

   ჩექბოქსსა და რადიოს არც სრული სიგანე სჭირდებათ და არც 16px
   შრიფტი (ისინი ტექსტს არ შეიცავენ, ანუ auto-zoom-საც არ იწვევენ).
   შეხების სამიზნეს მათთვის შემომსაზღვრელი `<label>` იძლევა. */
html.is-native-app #admin-main input:not([type="checkbox"]):not([type="radio"]),
html.is-native-app #admin-main select,
html.is-native-app #admin-main textarea,
html.is-native-app #vendor-main input:not([type="checkbox"]):not([type="radio"]),
html.is-native-app #vendor-main select,
html.is-native-app #vendor-main textarea {
  min-height: 44px;
  font-size: 16px !important;
  width: 100%;
}

html.is-native-app #admin-main button,
html.is-native-app #vendor-main button {
  min-height: 44px;
}

/* Admin header's action links: shrink to fit a phone without wrapping
   onto a second row. */
html.is-native-app header.app-header {
  padding-left: 14px !important;
  padding-right: 14px !important;
  margin-bottom: 14px !important;
}

html.is-native-app header.app-header h1 {
  font-size: 1.125rem !important;
}

/* ⚠ REGRESSION FIX — ეს იყო ადმინის ჰედერის ჩამოშლის ნამდვილი მიზეზი.
   ეს წესი ადრე `display: inline-flex`-ს ყველა <a>-ს და <button>-ს
   უპირობოდ აძლევდა. მაგრამ ჰედერში ერთი ბმული განზრახ იმალება
   მობილურზე — `<a class="hidden sm:flex">სტატისტიკა & ნოტიფიკაციები</a>`.
   Tailwind-ის `.hidden` არის (0,1,0) სპეციფიკურობის, ხოლო
   `html.is-native-app header.app-header a` — (0,2,3). ანუ ჩემი წესი
   იმარჯვებდა და `display: none`-ს აუქმებდა: ყველაზე გრძელი წარწერა
   ძალით ჩნდებოდა ვიწრო ეკრანზე, სამივე ელემენტს ერთმანეთში ატენიდა
   და ჰედერს არღვევდა.

   ახლა `display` მხოლოდ იმ ელემენტებს ედება, რომლებსაც `.hidden` არ
   აქვთ, და ცალკე წესი `.hidden`-ს ცხადად უბრუნებს `display: none`-ს. */
html.is-native-app header.app-header a,
html.is-native-app header.app-header button {
  min-height: 44px;
  align-items: center;
  font-size: 12px !important;
  padding-left: 12px !important;
  padding-right: 12px !important;
}

html.is-native-app header.app-header a:not(.hidden),
html.is-native-app header.app-header button:not(.hidden) {
  display: inline-flex;
}

/* Tailwind-ის `.hidden` ყოველთვის უნდა იმარჯვებდეს. */
html.is-native-app header.app-header .hidden {
  display: none !important;
}

/* --- ADMIN HEADER — ვერტიკალური დაწყობა + სქროლვადი ზოლი --------
   ჰედერის შიდა კონტეინერია `flex items-center justify-between`:
   მარცხნივ სათაური, მარჯვნივ მოქმედებების ჯგუფი. ტელეფონზე ორივე
   flex-item-ია ნაგულისხმევი `min-width: auto`-თი — ანუ არცერთი არ
   თანხმდება კონტენტზე ვიწრო გახდეს, და ისინი ერთმანეთს აწვებიან.

   ორ რიგად დაშლა ამას ბუნებრივად ხსნის: სათაური თავის ხაზზე, ქვემოთ
   კი მოქმედებები ერთ ჰორიზონტალურად სქროლვად ზოლად — ზუსტად ისე,
   როგორც ნეიტივ აპებში ხდება, როცა მოქმედება ეკრანზე არ ეტევა. */
html.is-native-app header.app-header > div {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

html.is-native-app header.app-header > div > h1 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* მოქმედებების ჯგუფი — სქროლვადი ზოლი. */
html.is-native-app header.app-header > div > div {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 12px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  /* pan-x pan-y (და არა მარტო pan-x) — თორემ ამ ზოლზე დაწყებული
     ვერტიკალური სვაიპი ჩაიკარგება და გვერდი არ დასქროლდება. */
  touch-action: pan-x pan-y;
  scrollbar-width: none;
  -ms-overflow-style: none;
  min-width: 0;
  max-width: 100%;
  /* padding-ი, რომ ბოლო ელემენტი კიდეს არ ეკვროდეს სქროლისას. */
  padding-bottom: 2px;
}

html.is-native-app header.app-header > div > div::-webkit-scrollbar {
  display: none;
}

/* ზოლის შიგნით ელემენტები არ იკუმშებიან — ისინი სქროლდებიან. */
html.is-native-app header.app-header > div > div > a,
html.is-native-app header.app-header > div > div > button {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* ============================================================
   PREMIUM NATIVE POLISH — applies app-wide (all 8 pages)
   Everything here is scoped to html.is-native-app, so the desktop
   website is untouched.
   ============================================================ */

/* --- Tactile press feedback (replaces the removed ripple) ---- */
/* One universal, compositor-only press state. transform+opacity are the
   only properties that never trigger layout or paint, so this stays at
   60fps on low-end Androids. Deliberately NOT applied to .filter-fab —
   that element's positioning is now guarded, and a transform on a
   position:fixed element creates a containing block for its
   descendants, which is exactly the class of bug we just fixed. */
html.is-native-app button:active,
html.is-native-app .btn-cta:active,
html.is-native-app .btn-ghost:active,
html.is-native-app .btn-approve:active,
html.is-native-app .btn-reject:active,
html.is-native-app a.btn-cta:active,
html.is-native-app a.btn-ghost:active,
html.is-native-app .prod-card:active,
html.is-native-app .res-card:active,
html.is-native-app .admin-card:active {
  transform: scale(0.97);
  transition: transform 0.1s ease;
}

/* Tapping a card shouldn't feel like tapping its button — keep the
   button's own press dominant by not double-scaling. */
html.is-native-app .admin-card:active .admin-card-actions button:active,
html.is-native-app .prod-card:active button:active {
  transform: scale(0.94);
}

/* Kill the grey tap-flash Android draws over every interactive element;
   the scale press above replaces it. */
html.is-native-app button,
html.is-native-app a,
html.is-native-app [role="button"],
html.is-native-app .prod-card,
html.is-native-app .res-card,
html.is-native-app .admin-card {
  -webkit-tap-highlight-color: transparent;
}

/* --- Glassmorphism consistency ------------------------------ */
/* The app's three fixed chrome surfaces (header, bottom nav, filter
   sheet) all sit on the same deep obsidian with the same blur, so they
   read as one material rather than three near-misses. */
html.is-native-app .nav-glass,
html.is-native-app header.app-header,
html.is-native-app .bottom-nav,
html.is-native-app .filter-sheet {
  background: rgba(7, 11, 24, 0.92) !important;
  backdrop-filter: blur(28px) saturate(180%) !important;
  -webkit-backdrop-filter: blur(28px) saturate(180%) !important;
}

html.is-native-app .admin-card,
html.is-native-app .res-card {
  background: rgba(15, 23, 42, 0.6) !important;
  border: 1px solid rgba(255, 255, 255, 0.08) !important;
}

/* --- Touch targets: 44x44 minimum, app-wide ------------------ */
/* WCAG 2.5.5 / Material both land on ~44-48px. Scoped to interactive
   elements only — a bare inline <a> inside a paragraph shouldn't get a
   44px box and wreck the text flow, so link targets are limited to ones
   that actually present as controls. */
html.is-native-app button,
html.is-native-app [role="button"],
html.is-native-app input[type="submit"],
html.is-native-app input[type="button"],
html.is-native-app .btn-cta,
html.is-native-app .btn-ghost,
html.is-native-app .btn-approve,
html.is-native-app .btn-reject,
html.is-native-app .cat-btn {
  min-height: 44px;
}

/* Icon-only round buttons need width too, not just height. */
html.is-native-app button.rounded-full,
html.is-native-app a.rounded-full {
  min-width: 44px;
}

/* --- Inputs: 16px minimum (prevents mobile auto-zoom) -------- */
/* Any input smaller than 16px makes Android/iOS zoom the viewport on
   focus — which then leaves the page zoomed and misaligned. This is the
   single most common "feels like a website" bug on mobile forms. */
html.is-native-app input,
html.is-native-app select,
html.is-native-app textarea,
html.is-native-app .input-field {
  font-size: 16px !important;
  min-height: 44px;
}

html.is-native-app textarea {
  min-height: 88px;
}

/* Checkboxes/radios are the exception — they have no text to zoom and
   a 44px box would break their inline label alignment. */
html.is-native-app input[type="checkbox"],
html.is-native-app input[type="radio"] {
  min-height: 0;
  font-size: inherit !important;
  width: 20px;
  height: 20px;
}

/* --- Vendor panel native compaction ------------------------- */
/* dashboard.html has no tables (its lists are already cards), so it
   needs proportion work rather than structural conversion. */
html.is-native-app #vendor-main {
  padding-top: 12px !important;
}

html.is-native-app #vendor-main h1,
html.is-native-app #admin-main h1 {
  font-size: 1.25rem !important;
}

html.is-native-app #vendor-main h2 {
  font-size: 1.0625rem !important;
}

/* Vendor stat tiles: 4-across desktop grid -> readable 2-up on phone. */
html.is-native-app #ceo-stats-container {
  grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  gap: 10px !important;
}

html.is-native-app .res-card {
  padding: 14px !important;
  margin-bottom: 10px;
}


/* --- Zero horizontal overflow guarantee (native) ------------- */
/* A single wide child anywhere on an admin screen would let the whole
   page pan sideways, which instantly reads as "desktop site in a
   WebView". These are belt-and-braces: the hidden desktop tables can't
   contribute (display:none), but long unbroken strings — emails, URLs,
   magic codes, Georgian addresses — still can. */
html.is-native-app body {
  overflow-x: hidden;
}

/* ⚠ `clip`, და არა `hidden` — იგივე ხაფანგი, რაც ამ ფაილის თავში
   `html, body`-სთვისაა აღწერილი, ოღონდ ერთი დონით ქვემოთ.

   `overflow-x: hidden` + `overflow-y: visible` სპეციფიკაციით
   შეუთავსებელი წყვილია: ბრაუზერი `overflow-y`-ს ჩუმად `auto`-დ
   გადააქცევს და ელემენტი სქროლის კონტეინერი ხდება. `#vendor-main`
   თავად არასდროს სქროლავს (სიმაღლე კონტენტის ტოლია), მაგრამ მისი
   შვილებისთვის სწორედ ის ხდება უახლოესი სქროლის წინაპარი — და
   `position: sticky` მასში მკვდარია. ზუსტად ამის გამო ჯავშნების
   ხელსაწყოთა ზოლი ვებზე ეკრანის ძირზე ეკვროდა, აპში კი — არა.

   `overflow-x: clip` ჰორიზონტალურ გადაცდენას ისევე კვეთს, მაგრამ
   სქროლის კონტეინერს არ ქმნის. `hidden` წინ განზრახ რჩება fallback-ად
   იმ ბრაუზერებისთვის, რომლებმაც `clip` არ იციან — ისინი მეორე
   დეკლარაციას უბრალოდ გამოტოვებენ. */
html.is-native-app #admin-main,
html.is-native-app #vendor-main {
  max-width: 100% !important;
  overflow-x: hidden;
  overflow-x: clip;
}

/* Long unbroken values wrap instead of pushing the card wider. */
html.is-native-app .admin-kv-value,
html.is-native-app .admin-card-head {
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Anything that legitimately must stay wide scrolls inside its own box
   rather than widening the page. */
html.is-native-app .native-scroll-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* ============================================================
   MOTION SYSTEM — ერთი საერთო რითმი მთელ აპში
   ------------------------------------------------------------
   აქამდე თითოეული ანიმაცია თავის easing-ს ირჩევდა: სადმე `ease`,
   სადმე `linear`, სადმე `all .25s cubic-bezier(.4,0,.2,1)`. შედეგი
   ის იყო, რომ ორი მეზობელი ელემენტი ერთსა და იმავე ჟესტზე ოდნავ
   სხვადასხვა ხასიათით პასუხობდა — სწორედ ეს კითხულობს ტვინი
   "იაფად". აქ ერთხელ განისაზღვრება ოთხი მრუდი და მთელი აპი
   მხოლოდ მათ იყენებს.
   ============================================================ */
:root {
  /* ძირითადი ნეიტივ მრუდი — სწრაფი დაწყება, რბილი დაშვება (iOS/Material) */
  --ease-native: cubic-bezier(0.2, 0.8, 0.2, 1);
  /* შემოსვლა/გაფართოება — ეკრანზე გამოჩენა */
  --ease-decel:  cubic-bezier(0.05, 0.7, 0.1, 1);
  /* გასვლა/დახურვა — მკვეთრი გაქრობა, ლოდინის გარეშე */
  --ease-accel:  cubic-bezier(0.3, 0, 0.8, 0.15);
  /* ზამბარა — მხოლოდ დადასტურებით/წარმატებით ჟესტებზე */
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  --dur-press: 100ms;   /* დაჭერის უკუკავშირი — მყისიერად უნდა იგრძნოს */
  --dur-fast:  180ms;   /* ჩიპები, ტაბები, პატარა ცვლილებები */
  --dur-base:  260ms;   /* მოდალი, sheet, გვერდის შემოსვლა */
  --dur-slow:  380ms;   /* დიდი ზედაპირები */
}

/* ============================================================
   PAGE TRANSITIONS (native only — იხ. native-transitions.js)
   ------------------------------------------------------------
   `is-page-entering` კლასს მხოლოდ JS წერს და მხოლოდ JS ხსნის,
   თანაც failsafe ტაიმერით. ანუ თუ სკრიპტი არ ჩაიტვირთა, ეს
   სელექტორი არასდროს ჩაირთვება და გვერდი ისე ჩანს, როგორც აქამდე.

   ⚠⚠ КРИТИКული REGRESSION FIX — რატომ არის აქ მხოლოდ `opacity`
   და აღარსად `transform`:

   თავდაპირველად ეს ანიმაციები `translate3d(0, 8px, 0)`-ს იყენებდნენ
   `body`-ზე, რომ გვერდი ოდნავ "ამოცურებულიყო". ეს იყო სერიოზული
   შეცდომა და სწორედ ის ტეხდა მთელ ლეიაუთს.

   CSS-ის წესი: ელემენტი, რომელსაც აქვს `transform` (ან `filter`,
   `perspective`, `contain`, `will-change`) — `none`-ისგან
   განსხვავებული მნიშვნელობით — ხდება **containing block ყველა
   `position: fixed` შთამომავლისთვის**. ანუ `body`-ზე დადებული
   transform-ით `position: fixed` წყვეტს viewport-ზე მიბმას და
   `body`-ს ყუთზე ებმება.

   ამ გვერდებზე `position: fixed`-ია პრაქტიკულად მთელი ჩარჩო:
   `#main-header`, `.bottom-nav`, `.filter-fab`, `#ptr-indicator`,
   `.filter-sheet` და მისი backdrop. ყველა მათგანი `body`-ს ყუთიდან
   იწყებდა ათვლას — `right: 20px` აღარ ნიშნავდა "ეკრანის მარჯვენა
   კიდიდან 20px", არამედ "body-ს მარჯვენა კიდიდან". სწორედ ამიტომ
   იყო FAB ეკრანიდან ნახევრად გასული და მარჯვენა მხარე მოჭრილი.

   `animation-fill-mode: both` ამას კიდევ უფრო ამძიმებდა: ანიმაციის
   დასრულების შემდეგაც კი `to`-მდგომარეობა (`translate3d(0,0,0)` —
   რომელიც მაინც transform-ია, და არა `none`) ძალაში რჩებოდა.

   `opacity` ასეთ ეფექტს **არ** იძლევა — ის ქმნის stacking context-ს,
   მაგრამ არა containing block-ს fixed ელემენტებისთვის. ამიტომ
   გადასვლა ახლა წმინდა fade-ია. ვიზუალურად თითქმის იგივეა,
   ლეიაუთისთვის კი უსაფრთხო.

   ⚠ არასდროს დაამატო `transform` `body`-ზე ან `html`-ზე ამ ფაილში. */
html.is-native-app.is-page-entering body {
  opacity: 0;
}

html.is-native-app.is-page-entered body {
  animation: chaavlePageIn var(--dur-base) var(--ease-decel) both;
}

html.is-native-app.is-page-leaving body {
  animation: chaavlePageOut 150ms var(--ease-accel) both;
  /* გასვლისას შეხება უკვე აღარაფერს ცვლის — ორმაგი ნავიგაციის თავიდან
     აცილება ვიზუალურ დონეზეც. */
  pointer-events: none;
}

@keyframes chaavlePageIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes chaavlePageOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ============================================================
   NATIVE PERFORMANCE HARDENING
   ------------------------------------------------------------
   მობილურ WebView-ში მხოლოდ `transform` და `opacity` მუშაობს
   კომპოზიტორზე. ყველა დანარჩენი — box-shadow, background,
   border-color, width, height, margin, filter — ყოველ კადრზე
   ხელახალ დახატვას (ან თუნდაც layout-ს) იწვევს. ქვემოთ ყველა
   ასეთი ტრანზიცია ნეიტივზე ითიშება. ვებზე უცვლელი რჩება, სადაც
   მაუსის hover რეალურად არსებობს და ბიუჯეტიც სხვაა.
   ============================================================ */
html.is-native-app .btn-ghost,
html.is-native-app .btn-approve,
html.is-native-app .btn-reject,
html.is-native-app .cat-btn,
html.is-native-app .val-card,
html.is-native-app .res-card,
html.is-native-app .icon-btn,
html.is-native-app .faq-item {
  transition: transform var(--dur-press) var(--ease-native) !important;
  will-change: auto !important;
}

/* hover სენსორულ ეკრანზე არ არსებობს — Android მას დაჭერის შემდეგ
   "ჩარჩენილ" მდგომარეობად ტოვებს, რაც ბარათს გაყინულ ჩრდილში
   აჩერებს მანამ, სანამ სხვაგან არ შეეხები. */
html.is-native-app .val-card:hover,
html.is-native-app .res-card:hover,
html.is-native-app .btn-ghost:hover,
html.is-native-app .btn-approve:hover,
html.is-native-app .btn-reject:hover,
html.is-native-app .cat-btn:hover {
  transform: none !important;
  box-shadow: none !important;
}

/* backdrop-filter ერთ-ერთი ყველაზე ძვირი ოპერაციაა Android-ზე და
   მისი ღირებულება ელემენტების რაოდენობაზე მრავლდება. ფიქსირებული
   ზედაპირები (header, bottom nav, sheet, მოდალი) blur-ს ინარჩუნებენ
   — ისინი ცოტაა და სწორედ იქ იკითხება მასალა. სიაში მოძრავი
   ბარათები კი გაუმჭვირვალე ფონზე გადადიან: ვიზუალურად თითქმის
   იდენტურია, სქროლი კი აღარ ბჟირავს. */
html.is-native-app .prod-card,
html.is-native-app .val-card,
html.is-native-app .res-card,
html.is-native-app .admin-card,
html.is-native-app .glass-card {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background: rgba(15, 23, 42, 0.82) !important;
}

/* სქროლის ინერცია და overscroll — ვერტიკალური სქროლი გვერდის
   საზღვარზე აღარ "გადაეცემა" მშობელს (rubber-band ეფექტი, რომელიც
   WebView-ში მთელი დოკუმენტის გადაწევად ჩანს). */
/* ⚠ `contain` → `none`. `contain` მხოლოდ ჯაჭვს წყვეტს (სქროლი
   მშობელს არ გადაეცემა), მაგრამ თავად ზღვარზე მიღწევის ეფექტს —
   Android-ის ლურჯ ნათებას — ტოვებს. აპში ეს ნათება ბრაუზერის
   ნიშანია. `none` ორივეს ხურავს. native-shell.js-ის pull-to-refresh
   touch-მოვლენებზეა აწყობილი და ამ ცვლილებას არ ეხება.
   `html`-იც დაემატა: მხოლოდ `body` ზოგიერთ WebView-ში საკმარისი
   არაა, როცა სქროლის კონტეინერი დოკუმენტის ელემენტია. */
html.is-native-app,
html.is-native-app body {
  overscroll-behavior-y: none;
  -webkit-overflow-scrolling: touch;
}

/* ტექსტის შემთხვევითი მონიშვნა ხანგრძლივ დაჭერაზე — ვებზე ნორმაა,
   აპში კი მყისვე ამხელს WebView-ს. ველები და ბარათის აღწერა
   გამონაკლისია, იქ მონიშვნა რეალურად საჭიროა. */
html.is-native-app {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* გამონაკლისები. `.magic-code`/`#t-code` აქ სავალდებულოა: ჯავშნის კოდი
   ზუსტად ის ერთი რამაა, რისი კოპირებაც მომხმარებელს რეალურად სჭირდება —
   მისი მონიშვნის აკრძალვა პოლიშის ნაცვლად ხარვეზი იქნებოდა. */
html.is-native-app input,
html.is-native-app textarea,
html.is-native-app [contenteditable],
html.is-native-app .magic-code,
html.is-native-app #t-code,
html.is-native-app .admin-kv-value,
html.is-native-app .selectable {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* ============================================================
   REDUCED MOTION — მთელი აპისთვის, ერთ ადგილას
   ------------------------------------------------------------
   არცერთ HTML გვერდს არ ჰქონდა ეს ბლოკი. ვისაც სისტემაში
   ანიმაციები გამორთული აქვს (ვესტიბულური მოწყვლადობა), მისთვის
   უწყვეტი shimmer, pulse და ticker რეალური დისკომფორტია.
   ============================================================ */
@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;
  }

  /* გვერდის გადასვლა სრულად უნდა გაითიშოს, არა დაჩქარდეს —
     დაჩქარებული გაქრობა/გამოჩენა კიდევ უფრო მკვეთრად ციმციმებს. */
  html.is-native-app.is-page-entering body,
  html.is-native-app.is-page-leaving body {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }

  .ticker-track,
  .live-pulse-dot::before,
  .ticker-icon-glow,
  #notif-badge {
    animation: none !important;
  }
}

/* ============================================================
   `transition-all` ნეიტრალიზაცია (native only)
   ------------------------------------------------------------
   რვა HTML ფაილში Tailwind-ის `transition-all` კლასი 50 ადგილას ზის.
   ის transition-property-ს `all`-ზე აყენებს, ანუ ბრაუზერი ყოველ
   ცვლილებაზე ყველა ანიმირებად თვისებას ადევნებს თვალს — width,
   height, margin, padding, box-shadow, filter, backdrop-filter — და
   თითოეული მათგანი layout-ს ან paint-ს იწვევს ყოველ კადრზე.

   ორი გზა იყო: 50 ადგილას ხელით ჩასწორება, ან ერთი წესი. ვირჩევთ
   წესს — ის ვებს ხელს არ ახლებს (სადაც hover რეალურია და ბიუჯეტიც
   სხვა), აპში კი სიას მხოლოდ იმ თვისებებამდე ავიწროებს, რომლებსაც
   სენსორულ ეკრანზე რეალური ვიზუალური აზრი აქვთ.
   ============================================================ */
html.is-native-app .transition-all {
  transition-property: transform, opacity, color, background-color, border-color !important;
  transition-timing-function: var(--ease-native) !important;
}

/* Tailwind-ის ნაგულისხმევი `ease-in-out`/`ease-out` კლასები აპში
   ერთიან ნეიტივ მრუდზე გადადიან — ეს არის ის ერთი დეტალი, რაც ორ
   მეზობელ ანიმაციას ერთი ხელით დაწერილის შეგრძნებას აძლევს. */
html.is-native-app .transition,
html.is-native-app .transition-colors,
html.is-native-app .transition-transform,
html.is-native-app .transition-opacity {
  transition-timing-function: var(--ease-native) !important;
}

/* ============================================================
   MODALS — ერთიანი გახსნის/დახურვის ხასიათი
   ------------------------------------------------------------
   მოდალები `scale-95 -> scale-100` + `opacity-0 -> opacity-100`
   კლასების გადართვით იხსნება. ორივე კომპოზიტორზეა, ანუ თავად
   მიდგომა სწორია — აკლდა მხოლოდ ერთიანი მრუდი და სწორი მიმართულება:
   გახსნა რბილად ჩერდება (decel), დახურვა კი სწრაფად ქრება (accel),
   რომ დახურვაზე ლოდინის შეგრძნება არ დარჩეს.
   ============================================================ */
html.is-native-app #productModal > div,
html.is-native-app #checkoutModal > div,
html.is-native-app #ratingModal > div,
html.is-native-app #courierModal > div {
  transition:
    transform var(--dur-base) var(--ease-decel),
    opacity var(--dur-base) var(--ease-decel) !important;
}

/* მოდალის ბნელი ფონი — მხოლოდ opacity, blur-ის გარეშე. ფონის
   backdrop-filter-ის ანიმაცია სწორედ იმ მომენტში ტვირთავს GPU-ს,
   როცა ზემოდან უკვე მეორე ანიმაცია დადის. */
html.is-native-app .modal-backdrop {
  transition: opacity var(--dur-base) var(--ease-native) !important;
}

/* ============================================================
   EMPTY / LOADING STATES — თანმიმდევრული შემოსვლა
   ------------------------------------------------------------
   სია რომ ჩაიტვირთება, კარტები ერთბაშად "გამოხტომის" ნაცვლად ერთი
   რიგით შემოდიან. delay მხოლოდ პირველ რვა ელემენტზეა — მეტი უკვე
   ლოდინად აღიქმება და არა პოლიშად.
   ============================================================ */
html.is-native-app .prod-card.active,
html.is-native-app .res-card.is-in,
html.is-native-app .admin-card.is-in {
  animation: chaavleListIn var(--dur-base) var(--ease-decel) both;
}

html.is-native-app .prod-card.active:nth-child(2) { animation-delay: 30ms; }
html.is-native-app .prod-card.active:nth-child(3) { animation-delay: 60ms; }
html.is-native-app .prod-card.active:nth-child(4) { animation-delay: 90ms; }
html.is-native-app .prod-card.active:nth-child(5) { animation-delay: 120ms; }
html.is-native-app .prod-card.active:nth-child(6) { animation-delay: 150ms; }
html.is-native-app .prod-card.active:nth-child(7) { animation-delay: 180ms; }
html.is-native-app .prod-card.active:nth-child(n+8) { animation-delay: 200ms; }

@keyframes chaavleListIn {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* ============================================================
   SYSTEM OVERLAYS — exit toast + offline bar
   ------------------------------------------------------------
   ორივე ელემენტს JS ქმნის დინამიურად (native-back.js და
   chaavle-cache.js), ამიტომ markup-ში არსად წერია. ორივე
   `position: fixed`-ია და safe-area-ს ითვალისწინებს.
   ============================================================ */
#chaavle-exit-toast {
  position: fixed;
  left: 50%;
  bottom: calc(92px + env(safe-area-inset-bottom));
  z-index: 400;
  padding: 11px 20px;
  border-radius: 9999px;
  background: rgba(7, 11, 24, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: #E2E8F0;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  /* translate3d-ს X ღერძზე ცენტრირებაც აქვს — `left: 50%` + -50%.
     opacity/transform მხოლოდ, ანუ კომპოზიტორზე. */
  transform: translate3d(-50%, 10px, 0);
  opacity: 0;
  transition:
    opacity var(--dur-fast) var(--ease-native),
    transform var(--dur-fast) var(--ease-native);
}

#chaavle-exit-toast.is-visible {
  opacity: 1;
  transform: translate3d(-50%, 0, 0);
}

#chaavle-offline-bar {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 400;
  padding: calc(max(24px, env(safe-area-inset-top, 24px)) + 6px) 16px 8px;
  background: rgba(120, 53, 15, 0.96);
  color: #FDE68A;
  font-size: 12px;
  font-weight: 700;
  text-align: center;
  pointer-events: none;
  transform: translate3d(0, -100%, 0);
  transition: transform var(--dur-base) var(--ease-native);
}

#chaavle-offline-bar.is-visible {
  transform: translate3d(0, 0, 0);
}

@media (prefers-reduced-motion: reduce) {
  #chaavle-exit-toast,
  #chaavle-offline-bar {
    transition: none !important;
  }
}

/* ============================================================
   INFO SHEET — ზოგადი ქვედა ფურცელი (native only)
   ------------------------------------------------------------
   `.filter-sheet`-ის ტყუპი ძმა, მაგრამ განზრახ ცალკე კლასებით:
   ორივე ერთდროულად ვერ იხსნება, თუმცა საერთო კლასი ნიშნავდა, რომ
   ერთის სტილის შეცვლა მეორეს უჩუმრად შეეხებოდა. ამჟამად ამას
   „როგორ მუშაობს?" იყენებს; ნებისმიერი მომავალი საინფორმაციო
   ფურცელი იმავე მარკაპს გაიმეორებს.
   ============================================================ */
.info-sheet,
.info-sheet-backdrop {
  display: none;
}

html.is-native-app .info-sheet-backdrop {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-native);
}

html.is-native-app .info-sheet-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

html.is-native-app .info-sheet {
  display: flex;
  flex-direction: column;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  width: 100%;
  max-width: 100%;
  max-height: 82vh;
  box-sizing: border-box;
  overflow-x: hidden;
  background: #0B1121;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 24px 24px 0 0;
  box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
  transform: translateY(100%);
  /* decel — ფურცელი სწრაფად ამოდის და რბილად ჩერდება. */
  transition: transform var(--dur-slow) var(--ease-decel);
  padding-bottom: env(safe-area-inset-bottom);
}

html.is-native-app .info-sheet.is-open {
  transform: translateY(0);
}

html.is-native-app .info-sheet-handle {
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.2);
  margin: 12px auto 4px;
  flex-shrink: 0;
}

html.is-native-app .info-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 20px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  flex-shrink: 0;
}

html.is-native-app .info-sheet-header h3 {
  margin: 0;
  min-width: 0;
  font-size: 17px;
  font-weight: 900;
  letter-spacing: -0.02em;
  color: #F8FAFC;
}

html.is-native-app .info-sheet-header button {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  min-height: 32px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.06);
  border: none;
  color: #94A3B8;
  display: flex;
  align-items: center;
  justify-content: center;
}

html.is-native-app .info-sheet-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 18px 20px 4px;
}

html.is-native-app .info-sheet-footer {
  flex-shrink: 0;
  padding: 12px 20px 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* --- „როგორ მუშაობს?" საფეხურები ----------------------------- */
html.is-native-app .how-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

html.is-native-app .how-step {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  min-width: 0;
}

/* ნომერი — ბრენდის ამბერი, იგივე რაც ვების `#how` სექციაში. */
html.is-native-app .how-step-num {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  background: linear-gradient(148deg, #FCD34D, #D97706);
  color: #0a0a0a;
  font-weight: 900;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
}

html.is-native-app .how-step-text {
  min-width: 0;
}

html.is-native-app .how-step-text h4 {
  margin: 0 0 3px;
  font-size: 14.5px;
  font-weight: 800;
  color: #F8FAFC;
}

html.is-native-app .how-step-text p {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: #94A3B8;
}

html.is-native-app .how-note {
  margin: 20px 0 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(251, 191, 36, 0.06);
  border: 1px solid rgba(251, 191, 36, 0.16);
  color: #FCD34D;
  font-size: 12px;
  line-height: 1.55;
}

/* ============================================================
   COUNTDOWN URGENCY — ერთი ვიზუალური ენა ორივე გვერდზე
   ------------------------------------------------------------
   მდგომარეობას chaavle-countdown.js წერს `data-urgency`
   ატრიბუტად, ანუ კლასების ხელით მართვა არსად საჭირო არაა.
   ფერი მხოლოდ სამ საფეხურს აღნიშნავს — ხმაური რომ არ შეიქმნას:
     normal   — მშვიდი, ყურადღებას არ ითხოვს
     warning  — 3 საათზე ნაკლები, ამბერი
     critical — 1 საათზე ნაკლები, წითელი + ნაზი პულსი
     expired  — მკრთალი, მოქმედება აღარაა შესაძლებელი
   ============================================================ */
[data-urgency] {
  transition: color var(--dur-base) var(--ease-native);
}

[data-urgency="normal"]   { color: #34D399; }
[data-urgency="warning"]  { color: #FBBF24; }
[data-urgency="critical"] { color: #FB7185; }
[data-urgency="expired"]  { color: #64748B; }

/* პულსი მხოლოდ კრიტიკულზე და მხოლოდ opacity-ით (კომპოზიტორი).
   `!important` არსად — ეს დეკორაციაა და გვერდის სტილს არ ეკამათება. */
@keyframes countdownPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

[data-urgency="critical"] {
  animation: countdownPulse 1.6s var(--ease-native) infinite;
}

@media (prefers-reduced-motion: reduce) {
  [data-urgency="critical"] { animation: none; }
}

/* ჯავშნის ბარათის ტაიმერის სტრიქონი (user-dashboard). */
.res-timer {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* ============================================================
   SEARCH FIELD — ხატულა და ველი ნამდვილი მეზობლებია
   ------------------------------------------------------------
   ძველი კონსტრუქცია იყო `position: absolute` ხატულა + `pl-10`
   ველზე. ის მთლიანად ეყრდნობოდა იმას, რომ Tailwind-ის `pl-10`
   გადაფარავდა გვერდის `.input-field { padding: … }` shorthand-ს.
   ორივე წესი ერთი და იმავე სპეციფიკურობისაა (0,1,0), ანუ დავას
   მხოლოდ ფაილების თანმიმდევრობა წყვეტდა — და როგორც კი ის შეიცვალა,
   მარცხენა padding გაქრა და ხატულა ტექსტს დაედო.

   flex-ს ასეთი დამოკიდებულება არ აქვს: ხატულა ფიზიკურად იკავებს
   ადგილს, ტექსტი მის შემდეგ იწყება. გადაფარვა პრინციპულად
   შეუძლებელია, კასკადის რიგის მიუხედავად.
   ============================================================ */
.search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  /* სიმაღლე ცვლადიდან — ერთი რიცხვი მართავს ბარის ყველა კონტროლს
     (იხ. .filter-bar). ცვლადის გარეშე ველი ისეთივე რჩება, როგორიც
     იყო, ანუ ამ კლასის სხვაგან გამოყენება არ იშლება. */
  height: var(--filter-h, 42px);
  padding: 0 14px;
  border-radius: 12px;
  background: rgba(5, 9, 20, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  transition:
    border-color 0.2s var(--ease-native),
    box-shadow 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native);
}

/* ფოკუსი მთელ კონტეინერზე გადადის და არა მხოლოდ <input>-ზე —
   `:focus-within` სწორედ ამისთვისაა. ვიზუალურად ველი ერთ მთლიან
   ობიექტად რჩება. */
.search-field:focus-within {
  background: rgba(5, 9, 20, 0.9);
  border-color: rgba(251, 191, 36, 0.42);
  box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.08);
}

.search-field-icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: #64748B;
  pointer-events: none;
}

.search-field:focus-within .search-field-icon {
  color: #FBBF24;
}

/* ველი გამჭვირვალეა — ჩარჩოსა და ფონს კონტეინერი იძლევა.
   `min-width: 0` აუცილებელია, თორემ flex-item-ის ნაგულისხმევი
   `auto` მინიმუმი ველს კონტეინერზე განიერს გახდიდა. */
.search-field-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  background: none;
  border: none;
  outline: none;
  /* ვერტიკალურ padding-ს ადგილი აღარ აქვს: სიმაღლეს კონტეინერი
     იძლევა და ტექსტს `align-items: center` აცენტრებს. padding-ით
     ორივეს მართვა ნიშნავდა, რომ შრიფტის ყოველი ცვლილება
     სიმაღლესაც ცვლიდა და კონტროლები ერთმანეთს ასცდებოდა. */
  padding: 0;
  color: #F8FAFC;
  font-size: 14px;
  font-family: inherit;
}

.search-field-input::placeholder {
  color: rgba(148, 163, 184, 0.55);
}

/* აპში 16px-ზე ნაკლები შრიფტი ველზე ფოკუსისას viewport-ს აზუმებს. */
html.is-native-app .search-field-input {
  font-size: 16px;
}

/* ============================================================
   FILTER BAR — ძებნა და ფილტრები ერთ პანელად
   ------------------------------------------------------------
   რა იყო არასწორად:

   1. ორი განსხვავებული დიზაინის ენა ერთ ზოლში. ძებნას ჰქონდა
      `.search-field` (12px რადიუსი, მუქი ფონი, ამბერის focus-ring),
      ფასისა და მდგომარეობის ველებს კი Tailwind-ის სხვა ნაკრები
      (8px რადიუსი, სხვა ფონი, მხოლოდ ჩარჩოს ფერის ცვლილება
      ფოკუსზე). გვერდიგვერდ ისინი სხვადასხვა საიტიდან გამოიყურებოდა.

   2. ცარიელი ადგილი. კონტეინერს `p-4 sm:p-5` ჰქონდა, შიგნით კი
      მობილურზე `flex-col` — ანუ სამი-ოთხი დაბალი კონტროლი მაღალ,
      მუქ ყუთში იდგა და ჰაერი კონტენტზე მეტი იყო.

   3. `<select>`-ს `appearance: none` ჰქონდა, ისარი კი არავის
      დაუხატავს — ჩამოსაშლელი სიის ერთადერთი ვიზუალური ნიშანი
      საერთოდ არ ჩანდა.

   ახლა ყველა კონტროლს ერთი ტანსაცმელი აქვს: ერთი სიმაღლე
   (`--filter-h`), ერთი რადიუსი, ერთი ფონი, ერთი ჩარჩო და ერთი
   ფოკუსის ქცევა.

   ⚠ breakpoint-ები განზრახ არ გამოიყენება. ეს ზოლი აპში
   `#filter-sheet-body`-ში გადადის — ვიწრო ფურცელში, რომელიც
   განიერ ეკრანზეც ვიწროა. `lg:`-ტიპის წესი იქ სამაგიდო განლაგებას
   დახატავდა ვიწრო ადგილას. `flex-wrap` კონტეინერის რეალურ სიგანეს
   ეყრდნობა და არა ეკრანისას, ანუ ორივეგან სწორად იქცევა.
   ============================================================ */
.filter-bar {
  --filter-h: 40px;

  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  /* padding კონტროლის სიმაღლის ნახევარზე ნაკლებია — ზოლი ჩარჩოა
     და არა ცალკე ბლოკი. */
  padding: 8px;
  border-radius: 16px;
  background: rgba(15, 23, 42, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.06);
}

html.is-native-app .filter-bar {
  /* თითისთვის — და 16px შრიფტი ისედაც მეტ ადგილს ითხოვს. */
  --filter-h: 46px;
}

/* ძებნა ბარის მთავარი ველია და ზრდის თავისუფალ ადგილს სამჯერ
   უფრო სწრაფად იბრუნებს, ვიდრე ფილტრები.

   ⚠ თანაბარი `flex-grow`-ით (1 : 1 : 1) განიერ ეკრანზე ძებნა
   მდგომარეობის სიაზე *ვიწრო* გამოდიოდა — მეორეხარისხოვანი
   კონტროლი მთავარზე დიდი ჩანდა. */
.filter-search {
  flex: 3 1 240px;
}

/* --- საერთო ტანსაცმელი: number და select ---------------------- */
.filter-control {
  box-sizing: border-box;
  height: var(--filter-h);
  min-width: 0;
  padding: 0 12px;
  border-radius: 12px;
  background: rgba(5, 9, 20, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: #F8FAFC;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  outline: none;
  transition:
    border-color 0.2s var(--ease-native),
    box-shadow 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native);
}

html.is-native-app .filter-control {
  font-size: 16px;
}

.filter-control::placeholder {
  color: rgba(148, 163, 184, 0.55);
  font-weight: 500;
}

/* ზუსტად იგივე ფოკუსი, რაც `.search-field:focus-within`-ს — ეს
   ერთი წესია, უბრალოდ ორ სელექტორზე გაწერილი. */
.filter-control:focus {
  background: rgba(5, 9, 20, 0.9);
  border-color: rgba(251, 191, 36, 0.42);
  box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.08);
}

/* --- ფასის დიაპაზონი ------------------------------------------
   ორი ველი და ტირე ერთ ჯგუფად: ვიწრო ადგილას მთელი ჯგუფი გადადის
   ახალ ხაზზე და არა მხოლოდ „მაქს." — თორემ დიაპაზონი ორ ხაზზე
   იშლებოდა და კავშირი იკარგებოდა. */
.filter-price {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 180px;
  /* ჭერი: ორი მოკლე რიცხვის ველს განიერ ეკრანზე გაწელვა არ სჭირდება. */
  max-width: 215px;
  min-width: 0;
}

.filter-price .filter-control {
  flex: 1 1 0;
  width: 100%;
  text-align: center;
  padding: 0 6px;
}

.filter-price-sep {
  flex: 0 0 auto;
  color: #475569;
  font-size: 12px;
  user-select: none;
}

/* რიცხვის ისრები ვიწრო ველში მხოლოდ ხელს უშლის. */
.filter-control[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.filter-control[type="number"]::-webkit-outer-spin-button,
.filter-control[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* --- მდგომარეობის სია ----------------------------------------- */
.filter-select {
  flex: 1 1 180px;
  /* ყველაზე გრძელი ვარიანტი („ქარხნული წუნი / აღდგენილი") ამ
     სიგანეში ეტევა; მეტი მას მხოლოდ ცარიელ ადგილს ჰმატებდა. */
  max-width: 240px;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  /* ისარი აქ ჩნდება. `appearance: none` ნატიურს შლის და აქამდე
     მის ნაცვლად არაფერი იდგა. */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394A3B8' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 15px 15px;
  padding-right: 32px;
}

/* ჩამონათვალს ოპერაციული სისტემა ხატავს — ფონის გარეშე ის ზოგ
   პლატფორმაზე თეთრი რჩება და მუქ თემას ეხეთქება. */
.filter-select option {
  background: #0B1121;
  color: #E2E8F0;
}

/* --- „მხოლოდ მარაგშია" ---------------------------------------
   ნატიური checkbox-ი მუქ თემას არ ემორჩილება (`accent-color`
   მხოლოდ შევსების ფერს ცვლის, ჩარჩოსა და ფონს — არა), ამიტომ
   ველი აქ თავიდანაა აწყობილი. მთელი ჭდე დასაჭერია და არა
   16px-იანი კვადრატი. */
.filter-check {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: 0 0 auto;
  box-sizing: border-box;
  height: var(--filter-h);
  padding: 0 13px;
  border-radius: 12px;
  background: rgba(5, 9, 20, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: #CBD5E1;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  user-select: none;
  transition:
    border-color 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native),
    color 0.2s var(--ease-native);
}

/* ჩართული ფილტრი ცხადად უნდა ჩანდეს — თორემ მომხმარებელი ვერ
   მიხვდება, რატომ აკლია სია ნივთებს. */
.filter-check:has(input:checked) {
  background: rgba(251, 191, 36, 0.1);
  border-color: rgba(251, 191, 36, 0.35);
  color: #FCD34D;
}

.filter-check input {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  border-radius: 5px;
  border: 1.5px solid rgba(148, 163, 184, 0.45);
  background: transparent;
  cursor: pointer;
  transition: background-color 0.15s var(--ease-native),
              border-color 0.15s var(--ease-native);
}

.filter-check input:checked {
  background-color: #FBBF24;
  border-color: #FBBF24;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230B1121' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px 11px;
}

/* კლავიატურით ნავიგაცია: `appearance: none` ნატიურ ფოკუსის
   რგოლსაც შლის, ანუ მისი ჩანაცვლება სავალდებულოა. */
.filter-check input:focus-visible {
  box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.25);
}

/* ⚠ `:has()`-ის გარეშე (ძველი WebView) ჭდე ფერს არ იცვლის, მაგრამ
   თავად checkbox სწორად ინიშნება — ფილტრი მუშაობს, უბრალოდ
   ნაკლებად თვალშისაცემია. */

/* ============================================================
   TICKET SAVE BOX — სუფთა ვერტიკალური ბარათი
   ------------------------------------------------------------
   პაროლის ველი და „შენახვა" ერთ flex რიგში იდგნენ; ველს
   `width: 100%` ჰქონდა, ღილაკს `w-auto`, და flex-item-ის
   ნაგულისხმევი `min-width: auto` ორივეს შეკუმშვაზე უარს ათქმევინებდა
   — ღილაკის წარწერა იჭრებოდა, ველი კი იბჯინებოდა.
   ============================================================ */
.save-box-head {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  margin-bottom: 18px;
}

.save-box-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(251, 191, 36, 0.1);
  border: 1px solid rgba(251, 191, 36, 0.22);
  color: #FBBF24;
}

.save-box-icon [data-lucide],
.save-box-icon svg {
  width: 19px;
  height: 19px;
}

.save-box-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* ღილაკი სრულ სიგანეზე, ველების ქვემოთ. `!important` აქ იმიტომ,
   რომ გვერდის საკუთარი `.btn-cta` `display: flex`-ს და `padding`-ს
   თავად აყენებს — ეს წესი მას განზრახ ცვლის მხოლოდ ამ ერთ
   კონტექსტში. */
.save-box-submit {
  width: 100% !important;
  min-height: 50px;
  padding: 14px 20px !important;
  font-size: 14px !important;
  letter-spacing: 0.04em;
  margin-top: 2px;
}

/* ============================================================
   SWIPE-TO-ACTION (native only — იხ. chaavle-swipe.js)
   ------------------------------------------------------------
   სტრუქტურას JS აწყობს: .swipe-host ახვევს ბარათს, .swipe-actions
   უკან დგას, .swipe-content კი ზემოდან მოძრაობს. მოქმედების
   პანელები მუდმივად „იქ არიან" — უბრალოდ ბარათის ქვეშ. ეს არის
   მიზეზი, რატომაც ჟესტი მყისიერია: არაფერი იქმნება შეხებისას.
   ============================================================ */
.swipe-host {
  position: relative;
  overflow: hidden;
  border-radius: 18px;
  /* ვერტიკალური სქროლი ბრაუზერისაა, ჰორიზონტალური — ჩვენი.
     ამის გარეშე ბრაუზერი ელოდება preventDefault-ს და პირველი
     კადრები იკარგება. */
  touch-action: pan-y;
  /* ბარათებს შორის იგივე რიტმი, რაც .res-card-ს ჰქონდა. */
  margin-bottom: 10px;
  transition: height var(--dur-base) var(--ease-native),
              opacity var(--dur-base) var(--ease-native),
              margin-bottom var(--dur-base) var(--ease-native);
}

.swipe-actions {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  border-radius: inherit;
  overflow: hidden;
}

.swipe-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 108px;
  padding: 0 18px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #fff;
  opacity: 0.55;
  transition: opacity var(--dur-fast) var(--ease-native);
}

.swipe-action--left  { margin-right: auto; }
.swipe-action--right { margin-left: auto; }

/* მხოლოდ ის პანელი ნათდება, რომლისკენაც ჟესტი მიდის. */
.swipe-host.reveal-left  .swipe-action--left,
.swipe-host.reveal-right .swipe-action--right { opacity: 0.9; }

/* ზღვარის გადალახვა — პანელი სრულად ინთება. ჰაპტიკასთან ერთად
   ეს არის ორმაგი დასტური: თითიც გრძნობს და თვალიც ხედავს. */
.swipe-host.is-armed.reveal-left  .swipe-action--left,
.swipe-host.is-armed.reveal-right .swipe-action--right {
  opacity: 1;
}

.swipe-host.is-armed .swipe-action-icon {
  transform: scale(1.18);
}

.swipe-action-icon {
  width: 24px;
  height: 24px;
  display: block;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.swipe-action-icon svg { width: 100%; height: 100%; display: block; }

.swipe-action--approve { background: linear-gradient(90deg, #059669, #10B981); }
.swipe-action--reject  { background: linear-gradient(270deg, #E11D48, #F43F5E); }
.swipe-action--neutral { background: #334155; }

.swipe-content {
  position: relative;
  z-index: 1;
  /* გაუმჭვირვალე ფონი სავალდებულოა — ბარათი მოქმედების პანელს
     ფარავს და ნახევრად გამჭვირვალე ფონში ის გამოსჭვივის. */
  background: #0F172A;
  border-radius: inherit;
  will-change: transform;
}

/* ბარათს, რომელიც შიგნით ზის, საკუთარი გარე მარჯინი აღარ სჭირდება —
   რიტმს ახლა .swipe-host იჭერს. */
.swipe-content > .res-card,
.swipe-content > .admin-card {
  margin-bottom: 0 !important;
}

/* ჩადენის შემდეგ ბარათი იკეცება, თუ გამომძახებელმა თავად არ წაშალა. */
.swipe-host.is-collapsed {
  height: 0 !important;
  opacity: 0;
  margin-bottom: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .swipe-host,
  .swipe-content,
  .swipe-action,
  .swipe-action-icon {
    transition: none !important;
  }
}

/* --- „როგორ მუშაობს?" — ჯარიმის გაფრთხილება ------------------
   ერთადერთი ადგილი მთელ ნაკადში, სადაც მომხმარებელი ფულს კარგავს.
   განზრახ წითელია და არა ამბერი (ამბერი ამ აპში საინფორმაციო
   ტონია), და ცალკე დგას ზოგადი შენიშვნისგან — ფარული პირობა,
   რომელიც მხოლოდ გადახდის შემდეგ ვლინდება, ნდობას ბევრად უფრო
   აზიანებს, ვიდრე თავიდანვე ნათქვამი წესი. */
html.is-native-app .how-warning,
.how-warning {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 12px 0 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(244, 63, 94, 0.08);
  border: 1px solid rgba(244, 63, 94, 0.22);
  color: #FDA4AF;
  font-size: 12px;
  line-height: 1.55;
}

.how-warning strong { color: #FB7185; }

.how-warning-icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-top: 1px;
  color: #FB7185;
}

.how-warning-icon svg { width: 100%; height: 100%; display: block; }

/* ============================================================
   APP SPLASH — პრემიუმ პირველი შთაბეჭდილება (native only)
   ------------------------------------------------------------
   ტაიმინგი (ათვლა `.is-playing` კლასის დადებიდან — იხ.
   initSplashMotion index.html-ში). სულ 3.5 წამი:

     0.00–0.80s  ლოგო რბილად ამოდის სიბნელიდან (opacity 0→1)
     0.00–3.50s  „სუნთქვა" — უწყვეტი scale 0.92 → 1.03, პაუზის გარეშე
     0.30–2.10s  ნიღბის სვლა (1.8s) — ერთადერთი, რაც სიტყვას ავლენს
     1.00–3.00s  სპეკულარული ზოლი (2.0s) — ნიღბის კუდს ედება
     0.00–3.00s  ამბერის შუქი იფურჩქნება და ჯდება
     3.00s       იწყება გაქრობა
     3.50s       სრულად უხილავია და აღარ იჭერს შეხებას

   ⚠ მოხაზულობის ფენა (`.splash-stroke-layer`) მოხსნილია.
   ------------------------------------------------------------
   ორი მიზეზი. პირველი — ვიზუალური: ამბერის „ჩონჩხი" სიტყვის
   გამოჩენამდე იაფად იკითხებოდა. მეორე — წარმადობა: შემოხაზული
   ტექსტი ყველაზე ძვირი დასახატი ელემენტი იყო მთელ კომპოზიციაში
   (მოხაზულობის გეომეტრია + მრგვალი შეერთებები, ყოველ კადრზე).
   ახლა სიტყვა ერთი, მკვრივი ტიპოგრაფიული ელემენტია.

   ⚠ სუნთქვა და შესვლა ერთ keyframe ნაკრებშია.
   ------------------------------------------------------------
   ორივე `transform`-ს ცვლის, CSS-ში კი ერთსა და იმავე თვისებაზე ორი
   ანიმაცია არ ჯამდება — ბოლო უბრალოდ აუქმებს პირველს. ხასიათს
   თითოეულ მონაკვეთს `animation-timing-function` აძლევს keyframe-ის
   შიგნიდან.

   ⚠⚠ scale აუცილებლად უნდა იყოს მითითებული შუალედურ keyframe-ზეც
   (23%). თუ ის მხოლოდ 0%-სა და 100%-ზე წერია, ბრაუზერი მთელ
   მანძილს ერთ მონაკვეთად თვლის და მას 0%-ის მრუდს — ანუ
   დეცელერაციას — ადებს. შედეგად სუნთქვა ბოლოსკენ პრაქტიკულად
   ჩერდება, ანუ ზუსტად ის „გაყინვა" ბრუნდება, რისთვისაც ის დაემატა.

   ⚠⚠ REGRESSION FIX — ტექსტი ასოებად არ იშლება.
   ------------------------------------------------------------
   `getSubStringLength()`-ით გაზომილი ცალკეული `<text>` კვანძები
   მოწყობილობის WebView-ში Noto Sans Georgian-ის კერნინგს ანგრევდა
   („ლ" და „ე" ერთმანეთს ადგებოდა). ტექსტი ერთი, დაუშლელი კვანძია და
   განლაგებას მთლიანად ბრაუზერი ითვლის.

   გაქრობას მართავს CSS და არა JS (`forwards`), რომ სკრიპტის
   ჩავარდნამ მომხმარებელი ეკრანზე ვერ დაბლოკოს.

   Z-INDEX: 10000, და არა 9999. ფილტრის sheet უკვე იყენებს
   9998/9999-ს — ტოლობისას გამარჯვებას DOM-ის რიგი წყვეტდა, რაც
   მყიფე დამოკიდებულებაა.
   ============================================================ */
.app-splash {
  display: none;
}

/* ⚠ ორი სახელი ერთი და იმავე გაქრობისთვის.
   მხოლოდ `animation-delay`-ის შეცვლა მიმდინარე ანიმაციას თავიდან არ
   იწყებს — ვადა ძველ საწყის წერტილს მიემართება. სახელის შეცვლა კი
   ნამდვილად ტოვებს ანიმაციას თავიდან, ანუ ლოდინი ზუსტად იმ
   მომენტიდან ითვლება, როცა splash-ის პირველი კადრი დაიხატა. */
html.is-native-app .app-splash {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: #0B1121;
  /* სარეზერვო ვადა — მხოლოდ JS-ის სრული ჩავარდნისას ამოქმედდება. */
  animation: splashOutFailsafe 0.5s var(--ease-native) 5s forwards;
}

html.is-native-app .app-splash.is-playing {
  animation: splashOut 0.5s var(--ease-native) 3s forwards;
}

@keyframes splashOut {
  from {
    opacity: 1;
    visibility: visible;
  }
  to {
    opacity: 0;
    visibility: hidden;
    /* pointer-events ანიმაციაში ინტერპოლირებადი არ არის — ის
       ბოლო კადრზე მკვეთრად გადაირთვება, რაც ზუსტად გვინდა:
       სანამ splash ჩანს, შეხება არ უნდა გადიოდეს ქვემოთ. */
    pointer-events: none;
  }
}

@keyframes splashOutFailsafe {
  from { opacity: 1; visibility: visible; }
  to   { opacity: 0; visibility: hidden; pointer-events: none; }
}

/* ------------------------------------------------------------
   ლოგოს SVG — შესვლა და უწყვეტი სუნთქვა
   `overflow: visible` განზრახაა: თუ ქართული fallback ფონტით დაიხატა,
   გლიფები viewBox-ს ოდნავ სცდება — მოჭრილი ლოგო ბევრად უარესია,
   ვიდრე ოდნავ განიერი.
   ------------------------------------------------------------ */
html.is-native-app .app-splash-mark {
  position: relative;
  z-index: 1;
  width: min(78vw, 344px);
  height: auto;
  overflow: visible;
  transform-origin: 50% 50%;
  /* სარეზერვო გზა: თუ სკრიპტმა კლასი ვერ დადო, ლოგო თავისით გამოჩნდება.
     ანუ JS-ის სრული ჩავარდნაც კი ცარიელ ეკრანს არ ტოვებს.

     ⚠⚠ ვადა 0.4s-იდან 1.2s-ზე გაიზარდა. index.html-ის `BRIDGE_WAIT`
     600ms-ია: remote server.url-ზე Capacitor-ის ხიდი შეიძლება ამდენს
     დააგვიანოს, სანამ `.is-playing` დაედება. ძველი 0.4s ამ ფანჯარაში
     ეტეოდა, ანუ სარეზერვო წესი ასწრებდა და მთელ ტექსტს სტატიკურად
     აჩენდა — მერე კი `.is-playing` ყველაფერს თავიდან იწყებდა.
     სწორედ ეს იყო ჩატვირთვისას დანახული „ციმციმი".
     ⚠ თუ `BRIDGE_WAIT` შეიცვალა, ეს ვადაც უნდა გაიზარდოს. */
  opacity: 0;
  animation: splashMarkFailsafe 0.35s linear 1.2s forwards;
}

/* WebView-ის ჩატვირთვა 1–2 წამს გრძელდება და ამ დროს ეკრანი მუქია.
   სწორედ ამიტომ იწყება ლოგო სრული გამჭვირვალობით და 0.8 წამში
   ამოდის — მკვეთრი გამოჩენა ამ პაუზის შემდეგ „ხტომად" იკითხება. */
html.is-native-app .is-playing .app-splash-mark {
  opacity: 0;
  /* ცალკე შრე კომპოზიტორზე — ანიმაციის ხანგრძლივობით. */
  will-change: transform, opacity;
  animation: splashMarkIn 3.5s linear forwards;
}

@keyframes splashMarkFailsafe { to { opacity: 1; } }

@keyframes splashMarkIn {
  0% {
    opacity: 0;
    transform: scale(0.92);
    /* რბილი ამოსვლა — ლოგო ჩნდება, არ ხტება. */
    animation-timing-function: var(--ease-decel);
  }
  23% {                        /* 0.80s — გამჭვირვალობა სრულდება */
    opacity: 1;
    transform: scale(0.945);
    /* აქედან ბოლომდე თანაბრად: ნებისმიერი ease სუნთქვას შეანელებდა. */
    animation-timing-function: linear;
  }
  100% {
    opacity: 1;
    transform: scale(1.03);
  }
}

/* ტიპოგრაფიის ერთადერთი წყარო. clipPath-ის შიგნით მდგომ ტექსტსაც
   იგივე წესი ეხება, ამიტომ ბზინვის ნიღაბი სიტყვას ზუსტად ემთხვევა. */
.splash-type {
  font-family: 'Noto Sans Georgian', 'Inter', sans-serif;
  font-size: 104px;
  font-weight: 900;
  text-anchor: middle;
  dominant-baseline: central;
}

/* ერთი მკვრივი სიტყვა — არანაირი მოხაზულობა, არანაირი ჩონჩხი. */
.splash-word { fill: #F8FAFC; }
/* წერტილი ბრენდის აქცენტია. მხოლოდ `fill` — tspan-ზე `transform`
   საიმედო არ არის და ისედაც არაფერს გვჭირდება მისი გადაადგილება. */
.splash-dot { fill: #FBBF24; }

/* ბზინვა ანიმაციამდე უხილავია. */
.splash-sheen { opacity: 0; }

/* ------------------------------------------------------------
   1. ნიღბის სვლა — 1.8 წამი. ერთადერთი მექანიზმი, რომელიც სიტყვას
   ავლენს. ტექსტს არაფერი ეხება — მოძრაობს მხოლოდ ნიღბის მართკუთხედი,
   თანაც მხოლოდ `transform`-ით.

   ⚠⚠ ბაზისური მდგომარეობა დახურულია (translateX(-230px)), და არა ღია.
   ------------------------------------------------------------
   ადრე ის ღია იყო, ანუ პირველივე დახატულ კადრზე ნედლი ტექსტი უკვე
   ჩანდა და მხოლოდ ამის შემდეგ იწყებოდა სვლა — სწორედ ეს იყო
   „ციმციმი" ჩატვირთვისას.

   მაგრამ დახურული ბაზისი მარტო საშიშია: თუ JS საერთოდ არ შესრულდა,
   `.is-playing` არასდროს დაედება და ლოგო სამუდამოდ დაფარული დარჩება.
   ამიტომ ბაზისს თავისი სარეზერვო ანიმაცია აქვს, რომელიც ნიღაბს
   1.2 წამზე თავად ხსნის.

   სახელი განზრახ სხვაა (`splashWipeFailsafe` vs `splashWipe`): მხოლოდ
   ვადის შეცვლა მიმდინარე ანიმაციას თავიდან არ იწყებს, სახელის
   შეცვლა კი ტოვებს. ანუ `.is-playing`-ის დადებისას სარეზერვო სვლა
   სუფთად უქმდება და ნამდვილი სვლა ნულიდან იწყება.
   ------------------------------------------------------------ */
.splash-wipe-rect {
  transform: translateX(-230px);
  will-change: transform;
  animation: splashWipeFailsafe 0.6s var(--ease-decel) 1.2s forwards;
}

@keyframes splashWipeFailsafe {
  to { transform: translateX(660px); }
}

/* ⚠ `linear`, და არა `--ease-decel` — ეს განზრახ გადახვევაა.
   დეცელერაცია მოძრაობის უმეტეს ნაწილს პირველ მესამედში ატარებს.
   გაზომილი შედეგი 1.8 წამზე: ნიღბის კიდე სიტყვას მხოლოდ 0.65 წამში
   გადაუვლიდა, დანარჩენი 1.14 წამი კი ის ეკრანის გარეთ, უხილავად
   მოძრაობდა — ანუ „გაიელვა და გაიყინა". თანაბარი ტემპით სვლა
   1.58 წამს ხილულია 1.8-დან. რბილ შეგრძნებას გრადიენტის ფართო კიდე
   იძლევა და არა მრუდი. */
html.is-native-app .is-playing .splash-wipe-rect {
  animation: splashWipe 1.8s linear 0.3s forwards;
}

/* საზღვრები მჭიდროდაა მორგებული სიტყვის საზღვრებზე: სვლის ის ნაწილი,
   რომელიც viewBox-ის გარეთ ხდება, დაკარგული დროა. */
@keyframes splashWipe {
  from { transform: translateX(-230px); }
  to   { transform: translateX(660px); }
}

/* ------------------------------------------------------------
   2. სპეკულარული ზოლი — 2 წამი. ნიღბის კუდს ედება, ანუ სიტყვა ჯერ
   ბოლომდე არ გამოჩენილა, როცა შუქი უკვე მიდის. სწორედ ეს გადაფარვა
   კლავს პაუზას ორ ბიტს შორის.

   ზოლიც იმავე ნიღბის შიგნითაა (იხ. index.html): თორემ 1.0–2.1 წამში
   შუქი ჯერ კიდევ დაფარულ ასოებზეც გაივლიდა.
   ------------------------------------------------------------ */
html.is-native-app .is-playing .splash-sheen { opacity: 1; }

html.is-native-app .splash-sheen rect {
  opacity: 0;
  will-change: transform;
}

/* იგივე მიზეზი, რაც ნიღბის სვლაზე: შუქის ზოლი თანაბრად უნდა გავიდეს,
   თორემ 2 წამიდან ნახევარზე მეტს ის კადრის გარეთ გაატარებდა. */
html.is-native-app .is-playing .splash-sheen rect {
  animation: splashSheen 2s linear 1s forwards;
}

@keyframes splashSheen {
  0%   { transform: translateX(0);   opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateX(900px); opacity: 0; }
}

/* ------------------------------------------------------------
   3. ატმოსფერო — ვინიეტი + ძალიან მინავლებული თბილი შუქი.

   ⚠ ძველი „მზე" მოხსნილია. სამი რამ აქცევდა მას მზედ და სამივე
   შეცვლილია:
     • პიკური გამჭვირვალობა 0.20 იყო — ახლა 0.075, ანუ სამჯერ სუსტი
     • წრე იყო — ახლა განიერი ელიფსი, რომელსაც კიდე არ ეკითხება
       (სწორედ დანახული საზღვარი აქცევს შუქს დისკად)
     • სუფთა ყვითელი #FBBF24 იყო ბოლომდე — ახლა გარე საფეხურები
       ღრმა ამბერისკენ (#E8962D) ინაცვლებს, ანუ ფერი სიცხედ
       იკითხება და არა ნათურად

   ⚠ `filter: blur(28px)` მოხსნილია. ის ზედმეტი იყო — ფონი ისედაც
   გრადიენტია — და ანიმირებულ ელემენტზე ყოველ კადრზე ხელახლა
   გამოითვლებოდა, რაც მობილურ WebView-ზე ერთ-ერთი ყველაზე ძვირი
   ოპერაციაა.
   ------------------------------------------------------------ */
html.is-native-app .app-splash-glow {
  position: absolute;
  width: min(148vw, 620px);
  height: 300px;
  border-radius: 50%;
  background: radial-gradient(ellipse at center,
    rgba(251, 191, 36, 0.075) 0%,
    rgba(245, 175, 40, 0.042) 26%,
    rgba(232, 150, 45, 0.016) 50%,
    rgba(232, 150, 45, 0.004) 72%,
    rgba(232, 150, 45, 0) 88%);
  pointer-events: none;
  opacity: 0.9;
  transform: scale(1);
}

html.is-native-app .is-playing .app-splash-glow {
  opacity: 0;
  transform: scale(0.88);
  will-change: transform, opacity;
  animation: splashGlowIn 3.5s linear forwards;
}

/* აღარ არის „აფეთქება და დაწევა". შუქი ერთხელ ამოდის და მერე ისევე
   ნელა იზრდება, როგორც სიტყვა — ერთი და იგივე მოძრაობის ენა. */
@keyframes splashGlowIn {
  0% {
    opacity: 0;
    transform: scale(0.88);
    animation-timing-function: var(--ease-decel);
  }
  26% {
    opacity: 0.9;
    transform: scale(0.95);
    animation-timing-function: linear;
  }
  100% {
    opacity: 0.9;
    transform: scale(1.05);
  }
}

/* ------------------------------------------------------------
   4. ვინიეტი — სიღრმე კიდეების ჩამუქებით.

   ⚠ რატომ ცალკე ელემენტია და არა `.app-splash`-ის ფონი: ნეიტივი
   splash ბრტყელი #0B1121-ია. თუ ვებ-splash-ს კიდეები თავიდანვე
   ჩამუქებული ექნებოდა, ჩაბარების მომენტში ეს სხვაობა დაინახებოდა.
   ცალკე ფენა 0-დან შემოდის, ანუ ჩაბარებისას ის ჯერ არ არსებობს და
   ნაკერი კვლავ უხილავია.

   ანიმირდება მხოლოდ `opacity` — კომპოზიტორზე.
   ------------------------------------------------------------ */
html.is-native-app .app-splash-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 78% 58% at 50% 44%,
    rgba(4, 7, 16, 0) 0%,
    rgba(4, 7, 16, 0.16) 42%,
    rgba(4, 7, 16, 0.34) 68%,
    rgba(3, 5, 12, 0.55) 100%);
  opacity: 0;
}

html.is-native-app .is-playing .app-splash-vignette {
  will-change: opacity;
  animation: splashVignetteIn 1.4s var(--ease-decel) forwards;
}

@keyframes splashVignetteIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ანიმაციების გამორთვისას splash თავის ფუნქციას ინარჩუნებს —
   უბრალოდ სტატიკურია და უფრო სწრაფად ქრება. ვესტიბულური
   მოწყვლადობის მქონე მომხმარებლისთვის უწყვეტი მასშტაბირება და
   შუქის პულსაცია სწორედ ის ტიპის მოძრაობაა, რაც დისკომფორტს იწვევს —
   ანუ სუნთქვაც აქ სრულად ითიშება. */
@media (prefers-reduced-motion: reduce) {
  html.is-native-app .app-splash {
    animation: splashOutFailsafe 0.01s linear 2.5s forwards;
  }
  html.is-native-app .app-splash.is-playing {
    animation: splashOut 0.01s linear 1.2s forwards;
  }

  /* ბზინვა წმინდა დეკორაციაა — აქ ის საერთოდ არ უნდა დაიხატოს. */
  html.is-native-app .app-splash-mark .splash-sheen {
    display: none;
  }

  /* ⚠⚠ ნიღაბი ორივე გზაზე სრულად ღია უნდა იყოს — `.is-playing`-ის
     გარეშეც. ბაზისი ახლა დახურულია, ანუ მხოლოდ `.is-playing`-ის
     გადაფარვა აქ საკმარისი აღარ არის: სარეზერვო სვლაც ითიშება და
     უნიღბოდ დარჩენილი სიტყვა სამუდამოდ დაფარული დარჩებოდა.
     აქ „ციმციმის" საკითხი აღარ დგას — მოძრაობის გარეშე სტატიკური
     ლოგოს მაშინვე ჩვენება სწორედ სასურველი ქცევაა. */
  html.is-native-app .splash-wipe-rect,
  html.is-native-app .is-playing .splash-wipe-rect {
    animation: none;
    transform: translateX(660px);
    will-change: auto;
  }

  html.is-native-app .app-splash-mark,
  html.is-native-app .is-playing .app-splash-mark,
  html.is-native-app .is-playing .app-splash-glow,
  html.is-native-app .is-playing .app-splash-vignette {
    animation: none;
    opacity: 1;
    transform: none;
    will-change: auto;
  }

  html.is-native-app .is-playing .app-splash-glow {
    opacity: 0.9;
  }
}

/* ============================================================
   CHAAVLE DIALOG (chaavle-dialog.js)
   ------------------------------------------------------------
   ბრაუზერის `alert()`/`confirm()`-ის ჩამნაცვლებელი. მარკაპს თავად
   მოდული ქმნის, ანუ გვერდს მხოლოდ <script>-ის ჩართვა სჭირდება.

   Z-INDEX: 10050 — splash-ზე (10000) მაღლა. თუ splash-ის ჩვენებისას
   რაიმე ჩავარდა, შეტყობინება მაინც უნდა დაინახოს მომხმარებელმა.
   ============================================================ */
#chaavle-dialog {
  position: fixed;
  inset: 0;
  z-index: 10050;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  opacity: 0;
  transition: opacity 200ms var(--ease-native);
}

#chaavle-dialog[hidden] { display: none; }
#chaavle-dialog.is-open { opacity: 1; }

.chaavle-dialog-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 16, 0.82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.chaavle-dialog-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  padding: 30px 28px 24px;
  border-radius: 24px;
  background: #0B1121;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.05);
  text-align: center;
  transform: scale(0.94);
  transition: transform 200ms var(--ease-decel);
}

#chaavle-dialog.is-open .chaavle-dialog-card { transform: scale(1); }

.chaavle-dialog-icon {
  width: 60px;
  height: 60px;
  margin: 0 auto 18px;
  border-radius: 18px;
  border: 1px solid transparent;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chaavle-dialog-icon svg { width: 26px; height: 26px; }

.chaavle-dialog-title {
  margin: 0 0 10px;
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.02em;
  color: #F8FAFC;
}

.chaavle-dialog-msg {
  margin: 0 0 24px;
  font-size: 14px;
  line-height: 1.6;
  color: #94A3B8;
  /* გრძელი შეცდომის ტექსტი ბარათს არ უნდა გაარღვიოს. */
  overflow-wrap: anywhere;
}

/* ჩამონათვალი დიალოგში — `opts.details` (იხ. chaavle-dialog.js).
   ორსვეტიანი ბადე და არა ერთი სტრიქონი: სახელი და მიზეზი ვიზუალურად
   უნდა გაიმიჯნოს, თორემ თვალი მათ ერთ გრძელ ტექსტად კითხულობს. */
.chaavle-dialog-list {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
}

/* იგივე მიზეზი, რაც `.chaavle-emoji-panel[hidden]`-ს აქვს: ზემოთა
   `display: flex` ბრაუზერის `[hidden]`-ს აუქმებს. აქ სიმპტომი არ
   ჩანდა მხოლოდ იმიტომ, რომ დამალული სია ცარიელია და სიმაღლეს არ
   იკავებს — ანუ ბაგი ისედაც იყო, უბრალოდ უხილავი. */
.chaavle-dialog-list[hidden] { display: none; }

.chaavle-dialog-list-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 12px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(148, 163, 184, 0.07);
  border: 1px solid rgba(148, 163, 184, 0.12);
}

/* `min-width: 0` + `overflow: hidden` — ამის გარეშე გრძელი სახელი
   ბადის სვეტს აფართოებს და ბარათს გარეთ ამოაგდებს. JS-ის მხრიდან
   სახელი უკვე შუაშია მოკვეცილი (ChaavleUpload.shortenName); ეს
   ვიწრო ეკრანის დამცავია. */
.chaavle-dialog-list-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 700;
  color: #E2E8F0;
}

.chaavle-dialog-list-note {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  color: #FB7185;
  white-space: normal;
}

/* ვიწრო ეკრანზე ორი სვეტი ერთმანეთს ჭყლეტს — მიზეზი ქვემოთ ჩამოდის. */
@media (max-width: 420px) {
  .chaavle-dialog-list-item { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   EMOJI PICKER — იხ. chaavle-emoji.js
   ============================================================ */
.chaavle-emoji { position: relative; display: inline-block; margin-top: 8px; }

.chaavle-emoji-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 10px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  background: rgba(148, 163, 184, 0.08);
  color: #94A3B8;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}

.chaavle-emoji-btn:hover {
  color: #E2E8F0;
  border-color: rgba(56, 189, 248, 0.5);
  background: rgba(56, 189, 248, 0.10);
}

.chaavle-emoji-btn-face { font-size: 14px; line-height: 1; }

/* პანელი ზემოთ იხსნება: ველი ხშირად ფორმის ბოლოსკენაა და ქვემოთ
   გახსნა მობილურზე ეკრანს გარეთ გადიოდა. */
.chaavle-emoji-panel {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 200;
  width: max-content;
  max-width: min(280px, calc(100vw - 48px));
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 2px;
  padding: 8px;
  border-radius: 14px;
  border: 1px solid rgba(148, 163, 184, 0.20);
  background: #0B1121;
  box-shadow: 0 18px 40px rgba(2, 6, 23, 0.65);
}

.chaavle-emoji-cell {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: background .12s, transform .12s;
}

/* ⚠⚠ ეს წესი აუცილებელია და არა ზედმეტი.
   ------------------------------------------------------------
   ბრაუზერის `[hidden] { display: none }` ავტორის სტილს ვერ ჯობნის:
   ზემოთა `display: grid` მას პირდაპირ აუქმებს. ანუ `panel.hidden = true`
   არაფერს აკეთებდა და პანელი გვერდის ჩატვირთვისთანავე ღია იყო.

   იგივე შაბლონი ამ ფაილში უკვე ორჯერაა (`#chaavle-dialog[hidden]`,
   `.chaavle-dialog-btn[hidden]`) — ყოველ ელემენტს, რომელსაც `display`
   ვაძლევთ და `.hidden`-ით ვმალავთ, ეს წყვილი სჭირდება. */
.chaavle-emoji-panel[hidden] { display: none; }

.chaavle-emoji-cell:hover { background: rgba(56, 189, 248, 0.16); transform: scale(1.15); }
.chaavle-emoji-cell:focus-visible { outline: 2px solid #38BDF8; outline-offset: 1px; }

.chaavle-dialog-actions { display: flex; flex-direction: column; gap: 10px; }

.chaavle-dialog-btn {
  width: 100%;
  padding: 13px 18px;
  border-radius: 14px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color 180ms var(--ease-native),
              border-color 180ms var(--ease-native),
              transform var(--dur-press) var(--ease-native);
}

.chaavle-dialog-btn--primary {
  background: #FBBF24;
  color: #1A1206;
}

.chaavle-dialog-btn--primary:hover { background: #FCCB4B; }

.chaavle-dialog-btn--primary.is-danger {
  background: #E11D48;
  color: #FFF1F2;
}

.chaavle-dialog-btn--primary.is-danger:hover { background: #F43F5E; }

.chaavle-dialog-btn--ghost {
  background: rgba(148, 163, 184, 0.08);
  border-color: rgba(148, 163, 184, 0.2);
  color: #CBD5E1;
}

.chaavle-dialog-btn--ghost:hover { background: rgba(148, 163, 184, 0.14); }
.chaavle-dialog-btn[hidden] { display: none; }

/* ტაქტილური უკუკავშირი — იგივე, რაც აპის დანარჩენ ღილაკებს აქვს. */
html.is-native-app .chaavle-dialog-btn:active { transform: scale(0.97); }

.chaavle-dialog-btn:focus-visible {
  outline: 2px solid #FBBF24;
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  #chaavle-dialog,
  .chaavle-dialog-card { transition: none; }
  .chaavle-dialog-card { transform: none; }
}

/* ============================================================
   MAGIC CODE — ჯავშნის კოდის ჩვენება
   ------------------------------------------------------------
   კოდი 6-ციფრიდან 8-ციფრზე გაიზარდა (იხ. phase13_code_integrity.sql)
   და ბილეთის ეკრანზე ბოლო ციფრები იჭრებოდა.

   ⚠ ეს არ იყო „ერთი ციფრით მეტი" — გაზომვით 320px-იან ეკრანზე ტექსტი
   კონტეინერს 139 პიქსელით სცდებოდა, 412px-იანზეც კი 47-ით. მიზეზი
   სამი ფაქტორის ნამრავლი იყო:

     1. `text-5xl` (48px) ფიქსირებული ზომა — ეკრანზე არ იყო მიბმული
     2. `letter-spacing: 0.2em` — 8 სიმბოლოზე ეს დამატებით 77 პიქსელია
     3. სამი ჩალაგებული padding: გვერდი 20 + ბარათი 32 + ყუთი 24,
        ანუ 320px-იან ეკრანზე ტექსტს მხოლოდ 168px რჩებოდა

   ხოლო ბარათს `overflow: hidden` ჰქონდა (მომრგვალებული კუთხეებისა და
   გრადიენტის ფენისთვის), ამიტომ გადავსება ჩუმად იჭრებოდა და არა
   გადმოდიოდა — ანუ ვიზუალურად „ბოლო ციფრი აკლია"-დ ჩანდა.

   ეს განსაზღვრება ticket.html-ის ინლაინ `<style>`-იდან გადმოვიდა, რომ
   კოდი ყველა ეკრანზე ერთნაირად გამოიყურებოდეს (ბილეთი, მყიდველის
   დაფა, გამყიდველის დაფა).
   ============================================================ */
.magic-code {
  /* ⚠ ცხადი მონოსფენიანი სტეკი და არა უბრალოდ `monospace`: ზომების
     გათვლა სიმბოლოს სიგანეზეა დამოკიდებული, სისტემური `monospace` კი
     მოწყობილობის მიხედვით 0.60em-იდან 0.65em-მდე მერყეობს. */
  font-family: ui-monospace, 'SF Mono', 'Roboto Mono', 'DejaVu Sans Mono', 'Courier New', monospace;
  /* ციფრები ერთი სიგანისა — კოდი არ „ცეკვავს" ჩანაცვლებისას. */
  font-variant-numeric: tabular-nums;
  font-weight: 900;
  line-height: 1.1;

  /* არასდროს გადავიდეს ახალ ხაზზე. */
  white-space: nowrap;

  /* ⚠ letter-spacing ბოლო სიმბოლოს შემდეგაც ამატებს ადგილს — ანუ
     ტექსტს ზედმეტ სიგანესაც აძლევს და ოპტიკურად მარცხნივ წევს.
     უარყოფითი მარჟა ზუსტად ამ ბოლო შუალედს აბრუნებს. */
  letter-spacing: 0.06em;
  margin-inline-end: -0.06em;

  /* უკანასკნელი დამცავი: თუ რომელიმე მოწყობილობაზე შრიფტი მაინც
     უფრო განიერი აღმოჩნდა, ტექსტი ჩუმად კი არ მოიჭრება, არამედ
     ჰორიზონტალურად გადაიფურცლება. */
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.magic-code::-webkit-scrollbar { display: none; }

/* ბილეთის მთავარი კოდი — ეკრანის მიხედვით იზრდება.
   9.5vw შერჩეულია გაზომვით: ყველაზე ვიწრო (320px) ეკრანზეც კი
   8 სიმბოლო ყველაზე განიერი მონოსფენიანი შრიფტითაც ეტევა. */
.magic-code--hero {
  font-size: clamp(1.75rem, 9.5vw, 2.75rem);
  background: linear-gradient(135deg, #FBBF24, #D97706);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  /* text-shadow გამჭვირვალე ტექსტზე არ ჩანს — ბზინვა ცალკე ფენაა. */
  filter: drop-shadow(0 0 30px rgba(251, 191, 36, 0.25));
}

/* დაფების ბარათებზე კოდი უფრო მოკრძალებულია. */
.magic-code--card {
  font-size: clamp(1.35rem, 6.5vw, 1.875rem);
}

/* ვიწრო ეკრანებზე ტექსტის შემცირების ნაცვლად ჯერ padding-ს ვჭრით —
   კოდს დახლთან ხმამაღლა კითხულობენ, ანუ წაკითხვადობა უფრო ძვირია,
   ვიდრე ჰაერი. 320px-ზე ეს 40 პიქსელს ათავისუფლებს. */
@media (max-width: 380px) {
  .ticket-card { padding: 1.25rem !important; }
  .ticket-code-box { padding: 1rem !important; }
}

/* ============================================================
   TOAST — ქსელის/შეცდომის ერთიანი შეტყობინება (chaavle-net.js)
   ------------------------------------------------------------
   მარკაპს მოდული თავად ქმნის, ამიტომ HTML-ში არსად წერია.
   პოზიცია განზრახ #chaavle-exit-toast-ზე ოდნავ მაღლაა, რომ ორივე
   ერთდროულად გამოჩენისას (გასვლის დადასტურება + ქსელის ჩავარდნა)
   ერთმანეთს არ გადაფარონ.
   ============================================================ */
#chaavle-toast-host {
  position: fixed;
  left: 50%;
  bottom: calc(150px + env(safe-area-inset-bottom));
  z-index: 420;
  display: flex;
  flex-direction: column-reverse;   /* ახალი ყოველთვის ქვემოთ */
  gap: 8px;
  width: min(92vw, 420px);
  transform: translateX(-50%);
  pointer-events: none;             /* ფონი ღილაკებს არ უნდა ჭამდეს */
}

.chaavle-toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-radius: 14px;
  background: rgba(7, 11, 24, 0.96);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-left: 3px solid #FB7185;
  color: #E2E8F0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.45;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
  pointer-events: auto;             /* თავად toast დაწკაპუნებადია */
  cursor: pointer;
  opacity: 0;
  transform: translate3d(0, 12px, 0);
  transition:
    opacity var(--dur-fast) var(--ease-native),
    transform var(--dur-fast) var(--ease-native);
}

.chaavle-toast.is-visible {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.chaavle-toast--success { border-left-color: #34D399; }
.chaavle-toast--info    { border-left-color: #FBBF24; }

.chaavle-toast-text { flex: 1 1 auto; }

.chaavle-toast-action {
  flex: 0 0 auto;
  padding: 6px 12px;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  color: #FBBF24;
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

/* გატეხილი სურათის ჩამნაცვლებელი: მასშტაბით არ იჭიმება, რომ
   კონტურული ხატულა დამახინჯებული არ გამოჩნდეს. */
img.is-img-fallback {
  object-fit: contain;
  background: #111A2E;
  opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
  .chaavle-toast { transition: none !important; }
}

/* ============================================================
   SAFE AREA — ეკრანის ზედა ზოლი (notch / სტატუს-ბარი)
   ------------------------------------------------------------
   capacitor.config.json-ში `StatusBar.overlaysWebView: true` წერია,
   ანუ WebView სტატუს-ბარის *ქვეშ* იხატება და ეკრანის პირველი
   ~24-48 პიქსელი სისტემას ეკუთვნის. `viewport-fit=cover` რვავე
   გვერდზეა, ანუ `env(safe-area-inset-top)` მუშაობს — უბრალოდ
   ქვედა ზოლისთვის ექვსჯერ იყო გამოყენებული, ზედასთვის კი მხოლოდ
   ორჯერ (#ptr-indicator და #chaavle-offline-bar).

   ორი დეფექტი, რომელსაც ეს ბლოკი ხურავს:

   1. ჩაჭრა. მოდალის დამხურავი „X" ბარათის `top-4`-ზეა, ბარათი კი
      ეკრანის კიდემდე ადიოდა — ღილაკი საათისა და ბატარეის ქვეშ
      ხვდებოდა და მისი დაჭერა ხშირად სისტემურ ზოლს ხვდებოდა.

   2. ზედა გადაფარვა (ეს უფრო მძიმეა). კონტეინერი
      `items-center`-ით ცენტრავს ბარათს, მაგრამ `overflow`-ის
      გარეშე: როცა ბარათი ეკრანზე მაღალია — პროდუქტის მოდალი
      ტელეფონზე ყოველთვის ასეთია (320px სურათი + აღწერა + ღილაკები)
      — ჭარბი ნაწილი *ორივე* მხარეს გადის და ზედა ნაწილი მიუწვდომელი
      ხდება. ანუ „X" არა მხოლოდ სტატუს-ბარის ქვეშაა, არამედ ხშირად
      ეკრანის გარეთაც. ამის გამო მარტო ღილაკზე `env()`-ის დამატება
      საკმარისი არ იქნებოდა.

   ⚠ რატომ არაა `env()` თავად ღილაკზე. ორივე ადგილას რომ იყოს
   (კონტეინერზეც და ღილაკზეც), ჩაჭრა ორჯერ დაითვლებოდა და „X"
   ბარათის შიგნით ჩამოიწევდა. ერთი წყარო ჯობია, და ის კონტეინერია —
   იქ ის ერთდროულად წყვეტს ორივე დეფექტს, ყველა 18 მოდალისთვის
   რვავე გვერდზე, თითოეულის markup-ის შეხების გარეშე.
   ============================================================ */

/* --- ა. გადაფარვა — ყველა ზომაზე ------------------------------
   `align-items: flex-start` + ბავშვზე `margin-block: auto` არის
   ცენტრირების ის ვარიანტი, რომელიც გადავსებისას არ ჭრის: სივრცის
   არსებობისას auto-მინდვრები ბარათს შუაში აყენებენ, ხოლო როცა
   ბარათი მაღალია — ნულდებიან და ბარათი ზემოდან იწყება. */
#chaavle-dialog,
.fixed.inset-0.items-center.justify-center {
  align-items: flex-start;
  overflow-y: auto;
  /* სქროლი მოდალში არ უნდა გადავიდეს ქვედა გვერდზე. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

#chaavle-dialog > .chaavle-dialog-card,
.fixed.inset-0.items-center.justify-center > *:not(.absolute) {
  margin-top: auto;
  margin-bottom: auto;
}

/* ბნელი ფონი `absolute`-ია, ანუ კონტეინერის სქროლვისას მასთან
   ერთად მოძრაობდა და ქვემოთ გაუფარავ ზოლს ტოვებდა. `fixed` მას
   ეკრანზე აჩერებს — `inset-0` ისედაც აქვს. */
.fixed.inset-0.items-center.justify-center > .absolute.inset-0 {
  position: fixed;
}

/* --- ბ. ჩაჭრა — მხოლოდ ტელეფონის სიგანეზე ---------------------
   639.98px იმიტომ, რომ `courierModal`-ს `sm:p-0` აქვს (≥640px-ზე
   განზრახ სრულეკრანიანი ხდება) — იმ განზრახვას არ ვეხებით.
   ვებზე `env()` ნულია, ანუ max() 1rem-ს ირჩევს და ქცევა
   ზუსტად ისეთივე რჩება, როგორიც `p-4`-ით იყო. */
@media (max-width: 639.98px) {
  #chaavle-dialog,
  .fixed.inset-0.items-center.justify-center {
    padding-top: max(1rem, calc(env(safe-area-inset-top) + 10px));
    padding-bottom: max(1rem, calc(env(safe-area-inset-bottom) + 10px));
  }
}

/* --- გ. სტიკი ჰედერები ----------------------------------------
   იგივე დეფექტი, ოღონდ ყოველ გვერდზე და მუდმივად: `sticky top-0`
   ჰედერს ზედა ჩაჭრა საერთოდ არ ჰქონდა, ანუ ბრენდი და ღილაკები
   სტატუს-ბარის ქვეშ იდგა.

   ⚠ ბაზისური მნიშვნელობები განზრახ განსხვავდება, რადგან markup
   განსხვავებულია: `header.app-header` (admin.html) `py-4`-ს ატარებს
   თავად, `.nav-glass` კი უპადინგოა — მისი შიდა div-ია `py-5`. ერთი
   საერთო `calc(1rem + …)` ერთს გაუფუჭებდა სიმაღლეს. */
html.is-native-app header.app-header {
  padding-top: calc(1rem + env(safe-area-inset-top)) !important;
}

html.is-native-app .nav-glass {
  padding-top: env(safe-area-inset-top);
}

/* ⚠ გამონაკლისი — index.html: `#main-header` გვერდის ზედა კიდეზე არ
   დგას. მის თავზე `#top-ticker`-ია და სტატუს-ბარის ჩაჭრას სწორედ ის
   ატარებს (`#top-ticker { padding-top: env(safe-area-inset-top) }`),
   ჰედერი კი იმავე ინსეტით ჩამოწეულია (`top: calc(37px + inset)`).
   ზემოთა საერთო წესი აქ იმავე 24-48px-ს მეორედ ითვლიდა: ჰედერი
   მთელი ერთი ინსეტით უფრო მაღალი გამოდიოდა, ვიდრე ჰეროს დატოვებული
   ადგილი, ბლოკი მობილურზე უსაფუძვლოდ სქელი ხდებოდა და „Live
   შეთავაზებები" ბეიჯი მის ქვეშ ეკარგებოდა. ეს იგივე ორმაგი ჩაჭრაა,
   რაც ბანერისთვის უკვე გასწორდა (იხ. „ბანერი vs ჰედერი" ქვემოთ). */
html.is-native-app #main-header {
  padding-top: 0;
}

/* ============================================================
   TOUCH AUDIT — ხუთი ფარული დეფექტი სენსორულ ეკრანზე
   ------------------------------------------------------------
   ქვემოთ მხოლოდ ის დარჩა, რაც ფაილში ჯერ არ იყო. უკვე გაწერილია
   და აქ არ მეორდება: .prod-card/.btn-cta/.val-card/.res-card/
   .btn-ghost/.btn-approve/.btn-reject/.cat-btn hover-ის გამორთვა
   აპში, 44px სამიზნეები, 16px ველები, .card-title-clamp.
   ============================================================ */

/* --- 1. ჩარჩენილი :hover — ყველა სენსორულ ეკრანზე --------------
   არსებული გამორთვა `html.is-native-app`-ზეა მიბმული, ანუ მხოლოდ
   APK-ს ეხება. საიტზე ტელეფონის ბრაუზერიდან შემოსული მომხმარებელი
   იმავე ჩარჩენილ მდგომარეობას იღებდა — და ესენი უფრო მეტნი არიან,
   ვიდრე აპის მომხმარებლები.

   `hover: none` სწორი ღერძია და არა სიგანე: ის კითხულობს, შეუძლია
   თუ არა მოწყობილობას საერთოდ hover — ანუ ერთი წესი ფარავს აპსაც,
   მობილურ ვებსაც და ტაბლეტსაც, ხოლო დესკტოპს არ ეხება.

   ⚠ ორი წესი აქამდე არსად იყო გამორთული და ორივე ხილვადია:
   `.star-btn` (შეფასების ვარსკვლავი დაჭერის შემდეგ გადიდებული და
   შებრუნებული რჩებოდა, სანამ სხვაგან არ შეეხებოდი) და `.icon-btn`. */
@media (hover: none) {
  .prod-card:hover,
  .val-card:hover,
  .res-card:hover,
  .btn-cta:hover,
  .btn-ghost:hover,
  .btn-approve:hover,
  .btn-reject:hover,
  .cat-btn:hover,
  .icon-btn:hover,
  .star-btn:hover,
  .chaavle-dialog-btn--primary:hover,
  .chaavle-dialog-btn--ghost:hover {
    /* `!important` აუცილებელია: .star-btn-ის საკუთარ hover-საც აქვს. */
    transform: none !important;
    box-shadow: none !important;
  }

  /* ფერის ცვლილება ჩარჩენისას ნაკლებად ეხამუშება, ვიდრე გადიდება,
     მაგრამ ღილაკი მაინც „დაჭერილი" გამოიყურება. ფონი საწყისს
     უბრუნდება, ტექსტის ფერს არ ვეხებით — ის კონტრასტს განსაზღვრავს. */
  .chaavle-dialog-btn--primary:hover { background: #FBBF24; }
  .chaavle-dialog-btn--ghost:hover   { background: rgba(148, 163, 184, 0.08); }
}

/* --- 2. გრძელი უწყვეტი ტექსტი ---------------------------------
   `overflow-wrap` მემკვიდრეობითი თვისებაა, ამიტომ ის კონტეინერზე
   ისმება და ყველა შვილს გადაეცემა — თითოეული სათაურის ცალკე
   მონიშვნა არ სჭირდება.

   რას წყვეტს: 50-სიმბოლოიანი უწყვეტი სტრიქონი (გამყიდველის მიერ
   შეყვანილი სახელი ინტერვალების გარეშე, ან URL) ვერ ეწეოდა და
   ბარათს გვერდულად არღვევდა. ბადეში ეს ერთ სვეტს აფართოებდა და
   მთელ რიგს წევდა.

   ⚠ `min-width: 0` აქ არანაკლებ მნიშვნელოვანია: flex/grid ელემენტს
   ნაგულისხმევად `min-width: auto` აქვს, ანუ ის შიგთავსზე ვიწრო
   ვერ ხდება — `overflow-wrap` მარტო, ამ გარეშე, ხშირად არაფერს
   ცვლის. `admin-card`-ისთვის ეს ფაილში უკვე იყო; დანარჩენისთვის — არა. */
.prod-card,
.res-card,
.val-card,
.glass-card,
.ticket-card,
#m-title,
#m-vendor,
.card-title-clamp {
  overflow-wrap: anywhere;
  word-break: break-word;
  min-width: 0;
}

/* მოდალის სათაური ორ ხაზზე იჭრება — ბარათის სიმაღლე გრძელი
   სახელისგან ეკრანს არ უნდა გასცდეს. */
#m-title {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- 3. Android-ის კლავიატურა ---------------------------------
   `.bottom-nav` და `.filter-fab` `position: fixed`-ია. კლავიატურის
   გაშლისას ისინი ზუსტად მის ზემოთ ჩერდებიან და იმ ველს ფარავენ,
   რომელსაც მომხმარებელი სწორედ ავსებს. კლასს native-shell.js
   ამატებს visualViewport-ის მიხედვით. */
html.is-keyboard-open .bottom-nav,
html.is-keyboard-open .filter-fab,
html.is-keyboard-open #ptr-indicator {
  display: none !important;
}

/* კლავიატურის დროს ქვედა padding-იც ზედმეტია — ის სწორედ
   `.bottom-nav`-ის ადგილს ინახავდა. */
html.is-keyboard-open body {
  padding-bottom: env(safe-area-inset-bottom) !important;
}

/* --- 5. სამიზნის ზომა სენსორულ ვებზე --------------------------
   44px-იანი წესები `html.is-native-app`-ზეა და მართებულადაა
   შემოსაზღვრული — ტექსტიან ღილაკებს ვებზე გაზრდა განლაგებას
   შეუცვლიდა. მაგრამ ხატულიანი მრგვალი ღილაკები (მოდალის „X",
   გალერეის ისრები, რჩეულის გული) 36px-ია და ისინი კვადრატულია,
   ანუ 44px-მდე გაზრდა ვიზუალურად თითქმის არ ჩანს. მხოლოდ ესენი. */
@media (pointer: coarse) {
  button.rounded-full:not(.w-6):not(.w-5):not(.w-4),
  a.rounded-full:not(.w-6):not(.w-5):not(.w-4),
  [role="button"].rounded-full:not(.w-6):not(.w-5):not(.w-4) {
    min-width: 44px;
    min-height: 44px;
  }
}

/* ⚠ `w-4`/`w-5`/`w-6` განზრახ გამონაკლისია. ერთადერთი ასეთი
   ღილაკი — dashboard.html-ის სურათის წაშლის წითელი ბეჯი (24px,
   `absolute top-1 right-1` მინიატურაზე) — 44px-მდე გაზრდისას
   თავად მინიატურას გადაფარავდა. მისი სწორი გამოსწორება ზომის
   გაზრდა არაა, არამედ გამჭვირვალე ::after-ით დაჭერის არის
   გაფართოება ვიზუალური ზომის შენარჩუნებით — ცალკე ცვლილებაა და
   markup-ს ეხება, ამიტომ აქ არ კეთდება. */

/* ============================================================
   IMAGE REMOVE BADGE — დაჭერის არე ვიზუალური ზომის შეცვლის გარეშე
   ------------------------------------------------------------
   dashboard.html-ის ატვირთული სურათის წაშლის წითელი ბეჯი 24px-ია
   (`w-6 h-6`). ეს ვიზუალურად სწორი ზომაა — 96px-იან მინიატურაზე
   44px-იანი წრე თავად სურათს გადაფარავდა. მაგრამ 24px თითისთვის
   მცირეა და აცდენა ნიშნავს, რომ მომხმარებელი ან სხვა სურათს
   შეეხება, ან საერთოდ ვერაფერს.

   ამიტომ იზრდება მხოლოდ დაჭერის არე: გამჭვირვალე `::after`
   ღილაკის შვილია, ანუ მასზე დაჭერა ღილაკზე დაჭერაა და
   `removeImage()` ჩვეულებრივ ეშვება.

   ⚠ გეომეტრია მინიატურის `overflow: hidden`-მა განსაზღვრა.
   კონტეინერი 96px-ია (`w-24`), ბეჯი — 24px, კიდიდან 4px-ით
   (`top-1 right-1`). ცენტრირებული 44px-იანი კვადრატი კონტეინერს
   ზემოთ და მარჯვნივ 6-6 პიქსელით გასცდებოდა და იქვე გაიჭრებოდა —
   რეალურად ~38px დარჩებოდა. ამიტომ არე ცენტრირებული არაა:
   კიდეზე ზუსტად 4px-ით ჯდება (`top: -4px; right: -4px`), დანარჩენს
   კი შიგნით, სურათზე იღებს. ასე სრული 44×44 იჭრება კონტეინერში
   ისე, რომ არაფერი გაიჭრება.

   ⚠ მხოლოდ `pointer: coarse` — თაგვს ეს არე არ სჭირდება და
   დესკტოპზე კურსორის 20 პიქსელით მოშორებით ღილაკის აციმციმება
   (`hover:scale-110`) უცნაურად გამოიყურებოდა.
   ============================================================ */
@media (pointer: coarse) {
  .img-remove-btn::after {
    content: '';
    position: absolute;
    top: -4px;
    right: -4px;
    width: 44px;
    height: 44px;
    /* ვიზუალი უცვლელია — ეს არე მხოლოდ დაჭერას იჭერს. */
    background: transparent;
    border-radius: 9999px;
  }
}

/* ============================================================
   CONDITION PILL — მდგომარეობის აბზინდა პროდუქტის ბარათზე
   ------------------------------------------------------------
   `<span>` ნაგულისხმევად `display: inline`-ია. inline ელემენტზე
   ფონი და ჩარჩო ხაზების მიხედვით იჭრება, ვერტიკალური padding კი
   ხაზის სიმაღლეს არ ზრდის — ის მეზობელ ტექსტს უბრალოდ ადევს.
   სანამ წარწერა ერთ ხაზზე ეტეოდა, ეს არ ჩანდა. ვიწრო ბარათზე
   („ქარხნული წუნი / აღდგენილი", „მცირე ვიზუალური დეფექტი" —
   `uppercase` + `tracking-widest`-ით კიდევ უფრო გრძელი) წარწერა
   ორ ხაზზე გადადის და აბზინდა ორ ცალკე ნატეხად იშლება.

   `inline-block` სამივეს წყვეტს: ფონი ერთიანია, padding ხაზის
   სიმაღლეშია ჩათვლილი, ჩარჩო კი მთელ ბლოკს ერტყმის.

   ⚠ `nowrap` + ellipsis განზრახ არ ავირჩიე. მდგომარეობა ბარათზე
   ყველაზე მნიშვნელოვანი ნდობის სიგნალია და „ქარხნული წუნი / აღდ…"
   სწორედ იმ ნაწილს ჭრის, რომელიც მყიდველს გადაწყვეტილებას
   აცვლევინებს. ორ ხაზზე გადასვლა ბადეს არ არღვევს — ფასის ზოლს
   `mt-auto` აქვს, ანუ ის ბარათის ბოლოში რჩება სიმაღლის
   მიუხედავად.

   ფერს, ზომას, padding-სა და მომრგვალებას კვლავ Tailwind-ის
   კლასები ატარებენ (იხ. CONDITION_PILL_CARD index.html-ში) — აქ
   მხოლოდ ის დევს, რაც მათ ვერ ასწორებს.
   ============================================================ */
.condition-pill {
  display: inline-block;
  /* ბარათზე განიერი ვერ იქნება, თუნდაც სახელი უწყვეტი იყოს. */
  max-width: 100%;
  /* 1 ზედმეტად მჭიდროა ორხაზიანისთვის: ასოები ერთმანეთს ეხება. */
  line-height: 1.35;
  /* გრძელი უწყვეტი მდგომარეობა (თუ ოდესმე დაემატება) ჩარჩოს
     არ უნდა გაარღვიოს. */
  overflow-wrap: anywhere;
}

/* ============================================================
   TOAST / BANNER — ორი შესწორება მობილურ ტესტირებაზე
   ============================================================ */

/* --- 1. toast აღარ ბლოკავს ქვედა ღილაკებს --------------------
   ჰოსტს `pointer-events: none` თავიდანვე ჰქონდა, თავად შეტყობინებას
   კი — `auto` (დაჭერით დახურვისთვის). შედეგად ეკრანის ქვედა
   მესამედში ~50 პიქსელის სიმაღლის ზოლი დაჭერებს იჭერდა და
   პროდუქტის ბარათის „დაჯავშნა" მის ქვეშ ხვდებოდა.

   შეტყობინება ისედაც თავისით ქრება — დაჭერით დახურვა იმად არ ღირს,
   რომ მთავარ CTA-ს ეცილებოდე. დაჭერადი მხოლოდ მოქმედების ღილაკი
   რჩება (მაგ. „ხელახლა"), რომელიც განზრახ მოკლეა და იშვიათი. */
.chaavle-toast {
  pointer-events: none;
  cursor: default;
}

.chaavle-toast-action {
  pointer-events: auto;
  cursor: pointer;
}

/* --- 2. ბანერი vs ჰედერი — ორმაგი safe-area ------------------
   ⚠ ეს რეგრესია ჩემი წინა ცვლილებით შემოვიდა.

   ბანერი `position: fixed; top: 0`-ია და თავად ატარებს
   `padding-top: env(safe-area-inset-top)` — ანუ სტატუს-ბარის ზოლს
   ის ფარავს. ჰედერი კი მის ქვემოთ ჩამოდის (`top: var(--chaavle-
   banner-h)`) და, ჩემი safe-area ბლოკის მიხედვით, *თავისთვისაც*
   ამატებდა იმავე ჩაჭრას. ერთი და იგივე 24-48px ორჯერ ითვლებოდა:
   ჰედერის შიგთავსი ბანერიდან უცნაურად შორს ეცემოდა და ვიზუალურად
   ორი ზოლი ერთმანეთს ეჯახებოდა.

   როცა ბანერი დგას, ჩაჭრა უკვე მისია — ჰედერს ის აღარ სჭირდება.
   კლასს `chaavle-banner-host.js` რთავს და ხსნის. */
html.is-native-app.chaavle-has-banner header.app-header {
  padding-top: 1rem !important;
}

html.is-native-app.chaavle-has-banner .nav-glass {
  padding-top: 0;
}

/* ============================================================
   SITE FOOTER — ბრენდი, კონტაქტი, საავტორო უფლებები
   ------------------------------------------------------------
   ⚠ საიტს კონტაქტის არცერთი მისამართი არ ჰქონდა. ფუტერი
   არსებობდა, მაგრამ მხოლოდ ერთი ხაზით („© ჩაავლე"), ანუ
   მყიდველს, რომელსაც ჯავშანთან პრობლემა შეექმნებოდა, მიწერის
   გზა არსად ეძებნებოდა.

   განლაგება `flex-wrap`-ზეა და არა breakpoint-ებზე: ორი ელემენტია
   და ვიწრო ეკრანზე ისინი თავისით ეწყობა ერთმანეთის ქვეშ. ერთადერთი
   media query ცენტრირებას აკეთებს — ეს `flex-wrap`-ს არ შეუძლია.
   ============================================================ */
.site-footer {
  position: relative;
  z-index: 10;
  border-top: 1px solid rgba(148, 163, 184, 0.12);
  background: rgba(5, 9, 20, 0.35);
  padding: 34px 20px 30px;
}

.site-footer-inner {
  /* კატალოგის ბადის იგივე სიგანე (max-w-6xl) — ფუტერი მასთან
     ერთ ვერტიკალურ ხაზზე უნდა დადგეს. */
  max-width: 1152px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.site-footer-brand {
  font-size: 18px;
  font-weight: 900;
  letter-spacing: -0.03em;
  color: #F8FAFC;
}

.site-footer-brand i {
  font-style: normal;
  color: #FBBF24;
}

/* კონტაქტი ღილაკივით გამოიყურება, რადგან ის მოქმედებაა და არა
   ტექსტი — ფილტრის კონტროლების იგივე ტანსაცმელი. */
.site-footer-contact {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(5, 9, 20, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: #CBD5E1;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition:
    border-color 0.2s var(--ease-native),
    color 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native);
}

.site-footer-contact:hover,
.site-footer-contact:focus-visible {
  background: rgba(5, 9, 20, 0.9);
  border-color: rgba(251, 191, 36, 0.35);
  color: #FCD34D;
  outline: none;
}

.site-footer-contact svg {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  color: #FBBF24;
}

.site-footer-legal {
  max-width: 1152px;
  margin: 22px auto 0;
  padding-top: 18px;
  border-top: 1px solid rgba(148, 163, 184, 0.08);
  color: #64748B;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  text-align: center;
}

@media (max-width: 560px) {
  .site-footer-inner {
    flex-direction: column;
    justify-content: center;
  }
}

/* ============================================================
   RESULTS META — შედეგების რაოდენობა და გასუფთავება
   ------------------------------------------------------------
   ⚠ ცარიელი შედეგი ჩიხი იყო: „ვერ მოიძებნა" ეუბნებოდა რა მოხდა,
   მაგრამ უკან დასაბრუნებელი გზა არსად ეწერა — ხუთივე კონტროლი
   ხელით უნდა დაებრუნებინა საწყის მდგომარეობაში.

   რიგი მხოლოდ მაშინ ჩანს, როცა ფილტრი აქტიურია (`hidden`-ს JS
   მართავს). სუფთა კატალოგზე მრიცხველი ინფორმაციას არ ამატებს.
   ============================================================ */
.results-meta {
  /* ფილტრის ზოლზე (40px) შესამჩნევად დაბალი: ეს მეორეხარისხოვანი
     რიგია და მთავარ კონტროლებს ვიზუალურად არ უნდა შეედაროს. */
  --filter-h: 28px;

  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  /* უარყოფითი margin განზრახ არ არის: აპში #filters-section-wrapper
     display:none-ია, ანუ ზემოთ მისი mb-10 აღარ დგას და გაწევა
     სათაურში ჩაიჭრებოდა. */
  margin: 0 0 18px;

  /* ⚠ 8px — ზუსტად იმდენი, რამდენიც `.filter-bar`-ის padding-ია.
     ამის გარეშე რიგი ზოლის *გარე* კიდეზე სწორდებოდა და მისი
     შიგთავსი ზემოთა კონტროლებს 8px-ით ასცდებოდა: მრიცხველი
     ძებნის ველს, დალაგება კი „მარაგშია" ჭდეს. ახლა ორივე სვეტი
     ერთ ვერტიკალურ ხაზზეა. */
  padding: 0 8px;
}

.results-meta-count {
  color: #94A3B8;
  font-size: 13px;
  font-weight: 700;
}

/* იგივე ტანსაცმელი, რაც ფილტრის კონტროლებს — ღილაკი იმავე
   ოჯახის წევრად უნდა იკითხებოდეს. */
.results-meta-clear {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  border-radius: 12px;
  background: rgba(5, 9, 20, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: #CBD5E1;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition:
    border-color 0.2s var(--ease-native),
    color 0.2s var(--ease-native),
    background-color 0.2s var(--ease-native);
}

.results-meta-clear:hover,
.results-meta-clear:focus-visible {
  background: rgba(251, 191, 36, 0.1);
  border-color: rgba(251, 191, 36, 0.35);
  color: #FCD34D;
  outline: none;
}

.results-meta-clear svg {
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
}

/* ცარიელი შედეგის შიგნით ღილაკი ცალკე დგას და არა რიგში.
   მოდიფიკატორი და არა Tailwind-ის `mt-1`: ერთსა და იმავე
   ელემენტზე ორი სისტემის შერევა სწორედ ის იყო, რაც ფილტრის
   ზოლში გავასწორეთ. */
.results-meta-clear--block {
  margin-top: 4px;
}

/* ბილეთის დახმარების ბლოკი — იგივე კონტაქტის ელემენტი, ცენტრირებულ
   სვეტში. იხ. ticket.html-ის კომენტარი იმის შესახებ, რატომ არაა აქ
   სრული `<footer>`. */
.ticket-support {
  margin-top: 26px;
  padding-top: 22px;
  border-top: 1px solid rgba(148, 163, 184, 0.1);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.ticket-support-label {
  margin: 0;
  color: #64748B;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* დალაგება + გასუფთავება ერთ ჯგუფად, მარჯვნივ.
   `margin-left: auto` და არა მშობლის `space-between`-ზე დაყრდნობა:
   მრიცხველი ფილტრის გარეშე იმალება, ხოლო ერთი ბავშვის დარჩენისას
   `space-between`-ს გასაშლელი აღარაფერი აქვს — ჯგუფი მარცხნივ
   გადმოხტებოდა. */
.results-meta-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  /* ვიწრო ეკრანზე ორივე ერთ ხაზზე არ ეტევა. `wrap` მათ ცალ-ცალკე
     ხაზზე უშვებს (მარჯვნივ სწორებულს) და არა კონტროლების შეკუმშვას
     — შეკუმშვისას „ფილტრის გასუფთავება" ორ ხაზად იტეხებოდა და
     ღილაკი დალაგებაზე მაღალი ხდებოდა. */
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* ორივე კონტროლის ერთი ყუთი — ერთ წესში.
   ------------------------------------------------------------
   ⚠ სიმაღლე მათ ადრეც ერთი ჰქონდათ (ორივე ზუსტად 34px), მაგრამ
   თვალი მაინც შეუსაბამობას ხედავდა: `<select>`-ს მარჯვნივ 32px
   padding ჰქონდა ისრისთვის, ღილაკს კი 13px — ანუ შიდა რიტმი
   სხვადასხვა იყო და სია უფრო „ჰაეროვნად" და მასიურად იკითხებოდა.
   ამიტომ ორივეს ერთი და იგივე წესი ადგენს; განსხვავებული მხოლოდ
   ის რჩება, რაც ისრისთვისაა საჭირო.

   რიგი მეორეხარისხოვანია და ფილტრის ზოლს არ უნდა შეედრებოდეს —
   ამიტომ 28px და არა 34px. */
.results-meta-sort,
.results-meta-clear {
  height: var(--filter-h, 28px);
  padding: 0 10px;
  border-radius: 9px;
  font-size: 12px;
  font-weight: 700;
  /* ⚠ ფიქსირებულ სიმაღლეს აზრი აქვს მხოლოდ მაშინ, თუ ტექსტი ერთ
     ხაზზე რჩება. ამის გარეშე ვიწრო ეკრანზე წარწერა ორ ხაზად
     იტეხებოდა და ღილაკი ყუთს გადმოსცდებოდა. */
  white-space: nowrap;
}

.results-meta-sort {
  /* არ იზრდება: ტექსტს მორგებული სიგანე ჰყოფნის. */
  flex: 0 0 auto;
  width: auto;
  min-width: 126px;
  color: #CBD5E1;
  /* ისარი და მისი ადგილი დაბალ ყუთს მოერგო. */
  padding-right: 25px;
  background-position: right 8px center;
  background-size: 13px 13px;
}

.results-meta-clear {
  gap: 6px;
}

.results-meta-clear svg {
  width: 12px;
  height: 12px;
}
