/*
 * EasyVisit — Mobile Responsive Styles
 * Targets: 320px – 768px
 * Rules: NO logic changes, CSS/layout only
 */

html,
body {
  overflow-x: hidden;
}

:root {
  --tg-theme-bg-color-fallback: var(--page-bg);
  --tg-theme-text-color-fallback: var(--text-primary);
}

/* ============================================================
   0. BASE — touch-targets & input safety
   ============================================================ */
@media (max-width: 768px) {
  /* Custom mobile-visibility helpers; not defined elsewhere. Source order
     resolves vs any inline Tailwind utility on the same element. */
  .desktop-only-ui,
  .header-desktop {
    display: none;
  }

  #tma-page-island-root[data-page="calendar"] {
    display: block;
    min-height: calc(100vh - 7rem);
  }

  #calendar-mobile-skeleton {
    display: block;
  }

  .tma-react-active.tma-page-calendar #calendar-mobile-skeleton {
    display: none;
  }

  /* Ensure tap targets are at least 44x44px */
  button,
  a,
  input,
  select,
  textarea,
  [role="button"],
  .btn,
  .btn-primary,
  .btn-cta,
  .btn-secondary,
  .btn-outline,
  .btn-danger,
  .btn-ghost,
  .sidebar-link {
    min-height: 44px;
  }

  /* Full-width CTAs on mobile. `.btn-cta` replaces `.btn-cta-mobile`, which was
     declared here but had zero call sites — so every apricot CTA silently sat
     outside the mobile button vocabulary (no full width, no tap-target floor).
     That only stayed invisible while `cta` was rare and lived on marketing
     pages inside flex-col rows, where align-items:stretch filled it in. */
  .btn-primary,
  .btn-cta {
    width: 100%;
    justify-content: center;
  }

  /* Inputs — prevent iOS focus zoom by forcing 16px floor.
     `:root` (pseudo-class, 0,1,0) + element (0,0,1) = 0,1,1, beats
     class selectors like `.input-warm` (0,1,0) without !important. */
  :root input,
  :root select,
  :root textarea {
    font-size: 16px;
  }

  /* Prevent keyboard from hiding active input */
  .form-group,
  .input-group {
    padding-bottom: env(keyboard-inset-height, 0px);
  }
}

/* ============================================================
   1. LAYOUT — consumer header & main
   ============================================================ */
@media (max-width: 768px) {
  /* Fixed so header never scrolls off-screen on mobile/TMA.
     Raise z-index above any page content stacking contexts.
     Single class (0,1,0). mobile.css loads after app.css, so at equal
     specificity mobile.css wins on source order — no !important needed.
     tailwind.css's .glass-header at @layer components only sets background
     and backdrop-filter; no conflict on position/inset/z-index. */
  .glass-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    z-index: 200;
    min-height: 56px;
    height: auto;
  }

  .glass-header .max-w-7xl {
    padding-left: 15px;
    padding-right: 15px;
  }

  .glass-header .max-w-7xl > .flex {
    display: flex;
    justify-content: space-between;
    align-items: center;
    min-height: 56px;
    height: auto;
  }

  /* Header notif icon: full 44×44 tap target */
  #consumer-notif-btn {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    position: relative;
    z-index: 10001;
    pointer-events: auto;
  }

  /* Safe-area bottom padding for footer */
  footer {
    padding-bottom: env(safe-area-inset-bottom);
  }

  /* Main content top padding is intentionally NOT zeroed: the layout spacer
     matches the fixed header exactly (no breathing room), so each layout's
     <main> provides its own top padding via Tailwind utilities (`p-4` on
     dashboard / staff-portal, `py-12` on auth). Consumer's <main> has no
     padding class, so consumer pages own their top padding via per-page
     wrappers (`pt-comfy` etc.). */

  /* Default mobile gutter for max-width containers. 0.75rem = the `cozy`
     spacing token (tailwind.config.js). Wrapped in :where() so specificity
     is 0 — any page-level Tailwind utility (e.g. px-2, px-[5vw], px-0)
     wins without needing !important. */
  :where(.max-w-7xl, .max-w-6xl, .max-w-5xl, .max-w-4xl) {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }
}

/* ============================================================
   2. DASHBOARD LAYOUT — sidebar & content
   ============================================================ */
@media (max-width: 1279px) {
  /* Dashboard wrapper becomes single column */
  .dashboard-layout,
  .flex.min-h-screen {
    flex-direction: column;
  }

  /* Dashboard/staff sidebars become full overlays — JS toggles `.open` /
     `.translate-x-0`. Scoped by ID to avoid matching unrelated `aside.lg\:w-64`
     uses (the search-page filter aside used to get caught here and parked off-
     screen — keep this scoped, not by class). */
  #dashboard-sidebar,
  #staff-sidebar,
  aside.sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    z-index: var(--z-sidebar, 40);
    transform: translateX(-100%);
    transition: transform 0.3s ease;
    width: 280px;
  }

  #dashboard-sidebar.open,
  #dashboard-sidebar.translate-x-0,
  #staff-sidebar.open,
  #staff-sidebar.translate-x-0,
  aside.sidebar.open,
  aside.sidebar.translate-x-0 {
    transform: translateX(0);
  }

  #dashboard-sidebar,
  #staff-sidebar {
    z-index: 9999;
  }

  #sidebar-backdrop {
    z-index: 9998;
  }

  #notification-btn,
  #dashboard-profile-trigger,
  #staff-profile-trigger {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    z-index: 1;
  }

  #notification-dropdown,
  #dashboard-profile-menu {
    right: 0;
    left: auto;
    max-width: calc(100vw - 1rem);
  }

  /* Main content occupies full width. `:root main` (0,1,1) beats utility
     classes; `.main-content` (0,1,0) ties and wins on source order
     (mobile.css is loaded last). */
  :root main,
  .main-content {
    margin-left: 0;
    width: 100%;
  }

  /* Dashboard page padding. `.p-6.lg\:p-8` is 0,2,0; `.px-8`/`.px-10` are
     0,1,0 but mobile.css loads after Tailwind so source-order wins. */
  .p-6.lg\:p-8,
  .px-8,
  .px-10 {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Stat cards — compact on mobile. .stat-card in tailwind.css @layer components
   only sets border-left; no padding conflict, source order resolves cleanly. */
@media (max-width: 768px) {
  .stat-card {
    padding: 0.875rem 1rem;
  }
}

/* ============================================================
   4. SEARCH PAGE — sticky toolbar + bottom-sheet filter/sort
   ============================================================
   Mobile-first redesign:
   - Sticky toolbar (search input + filter/sort/view pill row) replaces the
     dual stacked cards above the results.
   - Filter & sort each open a bottom sheet (.bottom-sheet) instead of a
     legacy drawer + inline custom-select.
   - Active filter chips render under the toolbar so the user always knows
     what's narrowing the result set without re-opening the sheet.
   Desktop (lg+) keeps the sidebar + inline sort layout — toolbar still
   sticks but stays just the search input + submit button.
*/

/* Toolbar has no background and no backdrop filter — the colorful body
   blobs read straight through unchanged, so at rest the toolbar is
   invisible and the input + pills feel like discrete controls floating on
   the page surface (not a contained card). When the user scrolls, the
   small gaps between controls show some sliding content — acceptable
   trade-off for a clean rest state, which is what the user sees first.
   The input + pills carry their own backdrops to remain legible. */
.search-toolbar {
  position: sticky;
  top: 64px;
  z-index: 30;
}

/* Ghost-style pills — transparent fill, subtle outline, muted text. Sits
   quietly under the search input so the input remains the primary control.
   Smaller text (13px) + secondary text color keeps them as affordances,
   not buttons that compete for attention. */
.search-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  padding: 0.5rem 0.75rem;
  border-radius: 999px;
  background: transparent;
  border: 1px solid rgba(15, 23, 42, 0.1);
  color: var(--text-secondary, #475569);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  min-height: 40px;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.05s;
}
.search-pill:hover {
  background: var(--surface-glass-faint);
  border-color: rgba(15, 23, 42, 0.2);
  color: var(--text-primary, #0f172a);
}
.search-pill:active { transform: scale(0.97); }

/* Active state — used when a filter sheet has selections or sort != default */
.search-pill.is-active {
  background: var(--surface-900, #0f172a);
  color: #ffffff;
  border-color: var(--surface-900, #0f172a);
}

.search-pill-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* `.hidden` wins by source order (mobile.css after Tailwind), so the badge's
   own `display: inline-flex` would override it — re-assert `display: none`
   when the badge is hidden via the utility. */
.search-pill-badge.hidden { display: none; }

.search-pill-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--primary-500, #0ea5e9);
  color: #ffffff;
  font-size: 11px;
  font-weight: 700;
  margin-left: 0.125rem;
}
.search-pill.is-active .search-pill-badge {
  background: var(--surface-solid);
  color: var(--surface-900, #0f172a);
}

/* iOS-style segmented control for view toggle */
.search-pill-segmented {
  display: inline-flex;
  padding: 3px;
  background: rgba(15, 23, 42, 0.06);
  border-radius: 999px;
  gap: 2px;
  flex-shrink: 0;
}
.search-pill-segmented button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  width: 36px;
  height: 36px;
  /* Section-0 base rule sets `button { min-height: 44px }` on mobile — that
     constraint always wins over `height` (used value is max(min-height, height)),
     so a 36×36 square would stretch to 36×44 ovals. Override here. */
  min-height: 36px;
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary, #475569);
  transition: background 0.15s, color 0.15s;
}
.search-pill-segmented button[aria-pressed="true"] {
  background: var(--surface-solid);
  color: var(--text-primary, #0f172a);
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.10);
}

/* Active filter chips (under the toolbar) — removable */
.chip-removable {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.3125rem 0.5rem 0.3125rem 0.625rem;
  background: var(--primary-50, #f0f9ff);
  color: var(--primary-700, #0369a1);
  border: 1px solid var(--primary-100, #e0f2fe);
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: 999px;
  line-height: 1;
  transition: background 0.15s;
}
.chip-removable svg { width: 12px; height: 12px; flex-shrink: 0; }
.chip-removable:hover { background: var(--primary-100, #e0f2fe); }

/* ============================================================
   4b. BOTTOM SHEET — generic filter/sort dialog primitive
   ============================================================ */
.bottom-sheet {
  position: fixed;
  inset: 0;
  /* Must beat the consumer tabbar (z-index 130). Otherwise the tabbar paints
     over the bottom of the sheet, hiding lower options — that's why
     "Рядом со мной" appeared missing from the sort sheet. */
  z-index: 140;
  pointer-events: none;
  visibility: hidden;
}
.bottom-sheet.open { pointer-events: auto; visibility: visible; }

.bottom-sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.bottom-sheet.open .bottom-sheet-backdrop { opacity: 1; }

.bottom-sheet-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--surface-glass-strong);
  backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
  -webkit-backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
  border-radius: 1.25rem 1.25rem 0 0;
  box-shadow: 0 -20px 60px -20px rgba(15, 23, 42, 0.35);
  display: flex;
  flex-direction: column;
  max-height: 85vh;
  transform: translateY(100%);
  transition: transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
  padding-bottom: env(safe-area-inset-bottom);
}
.bottom-sheet.open .bottom-sheet-panel { transform: translateY(0); }

.bottom-sheet-handle {
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.18);
  margin: 0.625rem auto 0;
  flex-shrink: 0;
}

.bottom-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.75rem 1.25rem 0.5rem;
  flex-shrink: 0;
}
.bottom-sheet-header h2 {
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--text-primary, #0f172a);
  letter-spacing: -0.01em;
}

.bottom-sheet-close {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  color: var(--text-muted, #94a3b8);
  background: rgba(15, 23, 42, 0.05);
  transition: background 0.15s, color 0.15s;
}
.bottom-sheet-close:hover {
  background: rgba(15, 23, 42, 0.1);
  color: var(--text-primary, #0f172a);
}

.bottom-sheet-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 0.25rem 1.25rem 1rem;
  -webkit-overflow-scrolling: touch;
}

.bottom-sheet-section-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted, #94a3b8);
  font-weight: 700;
  margin: 1.125rem 0 0.625rem;
}
.bottom-sheet-body section:first-child .bottom-sheet-section-label {
  margin-top: 0.25rem;
}

.bottom-sheet-footer {
  display: flex;
  gap: 0.625rem;
  padding: 0.75rem 1.25rem 1rem;
  border-top: 1px solid rgba(15, 23, 42, 0.06);
  background: var(--surface-glass-strong);
  flex-shrink: 0;
}
.bottom-sheet-footer button {
  flex: 1 1 0;
  min-height: 48px;
  border-radius: 0.875rem;
  font-weight: 600;
  font-size: 0.9375rem;
  transition: background 0.15s, color 0.15s;
}
.bottom-sheet-footer .sheet-secondary {
  background: rgba(15, 23, 42, 0.06);
  color: var(--text-primary, #0f172a);
  flex: 0 0 38%;
}
.bottom-sheet-footer .sheet-secondary:hover { background: rgba(15, 23, 42, 0.1); }
.bottom-sheet-footer .sheet-primary {
  background: var(--surface-900, #0f172a);
  color: #ffffff;
}
.bottom-sheet-footer .sheet-primary:hover { background: #1e293b; }

/* Chip-group inside a sheet — category multi-select as toggleable pills */
.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.chip-group label {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5625rem 0.875rem;
  border-radius: 999px;
  border: 1.5px solid rgba(15, 23, 42, 0.1);
  background: var(--surface-solid);
  color: var(--text-primary, #0f172a);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  min-height: 44px;
  user-select: none;
  position: relative;
}
.chip-group label:hover { border-color: rgba(15, 23, 42, 0.22); }
.chip-group input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}
.chip-group label:has(input:checked) {
  background: var(--surface-900, #0f172a);
  border-color: var(--surface-900, #0f172a);
  color: #ffffff;
}
.chip-group .chip-check {
  display: none;
  width: 14px;
  height: 14px;
}
.chip-group label:has(input:checked) .chip-check { display: inline-flex; }

/* Sort sheet — large radio rows with a check-mark on the right */
.sort-radio-list {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding-top: 0.25rem;
}
.sort-radio-list label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 0.875rem;
  border-radius: 0.875rem;
  cursor: pointer;
  background: transparent;
  transition: background 0.15s, color 0.15s;
  min-height: 56px;
  color: var(--text-primary, #0f172a);
  font-size: 0.9375rem;
  font-weight: 500;
  position: relative;
}
.sort-radio-list label:hover { background: rgba(15, 23, 42, 0.04); }
.sort-radio-list label:has(input:checked) {
  background: var(--primary-50, #f0f9ff);
  color: var(--primary-700, #0369a1);
  font-weight: 600;
}
.sort-radio-list input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}
.sort-radio-list .check-mark {
  display: none;
  width: 20px;
  height: 20px;
  color: var(--primary-500, #0ea5e9);
  flex-shrink: 0;
}
.sort-radio-list label:has(input:checked) .check-mark { display: inline-flex; }

/* Body scroll-lock when a sheet is open */
body.sheet-open { overflow: hidden; }

/* ── Mobile-only adjustments ────────────────────────────────── */
@media (max-width: 1023px) {
  .search-toolbar { top: 56px; }

  /* Make the search input the prominent primary control: opaque white,
     soft shadow for elevation off the page, slightly more vertical room
     so it feels like a proper mobile search bar instead of a translucent
     form field. The pills below intentionally remain ghost-styled so the
     input wins the hierarchy. */
  .search-toolbar .input-warm {
    background: var(--surface-solid);
    border-color: rgba(15, 23, 42, 0.08);
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
  }
}

/* ── Desktop: search-toolbar reduces to just the search bar ── */
@media (min-width: 1024px) {
  .search-toolbar { top: 64px; }
}

/* ============================================================
   5. NOTIFICATIONS / FILTER CHIPS
   ============================================================ */
@media (max-width: 768px) {
  .mobile-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
  }

  .mobile-page-header-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
  }

  .mobile-page-header-actions > * {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* Custom class — source order resolves. */
  .mobile-page-header-link {
    min-height: 40px;
    padding-left: 0.875rem;
    padding-right: 0.875rem;
    justify-content: center;
  }

  /* --- Consumer notifications: 2 chips → equal-width, no scroll ---
     Custom class — source order resolves. */
  .notif-filter-chips {
    display: flex;
    gap: 6px;
    overflow-x: visible;
    white-space: normal;
  }

  /* Class chain (0,2,0) — already beats Tailwind utilities without !important. */
  .notif-filter-chips .notif-filter-btn {
    flex: 1;
    min-height: 38px;
    padding-top: 0.375rem;
    padding-bottom: 0.375rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* --- Business notifications: 4 chips → horizontally scrollable ---
     Custom class — source order resolves. */
  .tab-filter-chips {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding-bottom: 4px; /* prevents clipping of box-shadow on active tab */
  }

  .tab-filter-chips::-webkit-scrollbar {
    display: none;
  }

  /* Class chain (0,2,0) — !important not needed. */
  .tab-filter-chips .tab-btn {
    flex: 0 0 auto;
    min-height: 38px;
    min-width: 72px;
    padding-top: 0.375rem;
    padding-bottom: 0.375rem;
    white-space: nowrap;
  }

  /* Exactly two tabs → a segmented control, not a scroll strip.
     `:first-child:nth-last-child(2)` matches a first child that is also the
     second-from-last, i.e. the track holds precisely two buttons; the sibling
     combinator then picks up the other one. Three or more chips keep the
     scrolling behaviour above, which is what that rule was written for.
     Without this, two tabs shrink to their text width and leave a dead gap —
     the halves should split the track like the Mini App's segmented control. */
  .tab-filter-chips .tab-btn:first-child:nth-last-child(2),
  .tab-filter-chips .tab-btn:first-child:nth-last-child(2) ~ .tab-btn {
    flex: 1 1 0;
    min-width: 0;
  }

  /* A two-tab track has nothing to scroll to, so don't make it a scroll
     container — that also kills the stray horizontal drag on the panel. */
  .tab-filter-chips:has(.tab-btn:first-child:nth-last-child(2)) {
    overflow-x: visible;
  }

  /* Exempt filter chips from the global 44px min-height rule.
     Class chain (0,2,0). */
  .notif-filter-chips button,
  .tab-filter-chips button {
    min-height: 38px;
  }

  /* Empty state cards. IDs (1,0,0) — !important not needed. */
  #consumer-notifications-empty,
  #emptyState {
    width: 92%;
    max-width: 92%;
    margin: 0 auto;
    padding: 20px;
    border-radius: 12px;
  }

  .notification-card,
  .warm-card,
  .warm-card-static {
    overflow-wrap: anywhere;
  }

  .notification-card .flex.items-center.justify-between,
  .notification-card .flex.justify-between,
  #notificationsList .flex.items-center.justify-between,
  #notificationsList .flex.justify-between {
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: flex-start;
  }

  #consumer-notifications-content > * + *,
  #notificationsList > * + * {
    margin-top: 0.75rem;
  }

  /* Booking cards rendered on client */
  #upcoming-list > div,
  #past-list > div {
    border-radius: 1rem;
  }

  #upcoming-list .flex.items-start.justify-between,
  #past-list .flex.items-start.justify-between {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  #upcoming-list .text-right,
  #past-list .text-right {
    text-align: left;
    width: 100%;
  }

  /* ID + class chain (1,4,0) — !important not needed. */
  #upcoming-list .flex.items-center.gap-4.mt-4,
  #past-list .flex.items-center.gap-4.mt-4,
  #upcoming-list .flex.items-center.gap-4.mt-3,
  #past-list .flex.items-center.gap-4.mt-3 {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  #upcoming-list .flex.gap-3.mt-4,
  #past-list .flex.gap-3.mt-4,
  #cancel-modal .flex.gap-3,
  #rate-review-modal .flex.gap-3,
  #reschedule-modal .flex.gap-3,
  #delete-account-modal .flex.justify-end.gap-3 {
    flex-direction: column;
  }

  #upcoming-list .flex.gap-3.mt-4 > *,
  #past-list .flex.gap-3.mt-4 > *,
  #cancel-modal .flex.gap-3 > *,
  #rate-review-modal .flex.gap-3 > *,
  #reschedule-modal .flex.gap-3 > *,
  #delete-account-modal .flex.justify-end.gap-3 > * {
    width: 100%;
  }

  /* Dashboard and detail page section headers */
  .dashboard-section-header,
  .business-section-header,
  .consumer-card-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
  }

  .dashboard-section-header > :last-child,
  .business-section-header > :last-child,
  .consumer-card-header > :last-child {
    flex-shrink: 0;
  }

  /* Custom class — source order resolves. */
  .business-service-row {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  .business-service-row > :first-child {
    width: 100%;
  }

  /* Class + element chain (0,1,1) — beats utilities, no !important needed. */
  .business-service-row > a {
    opacity: 1;
    margin-left: 0;
    width: 100%;
    text-align: center;
  }

  /* Settings and favorites top actions */
  #settings-page .flex.justify-end,
  .favorites-page-header {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  #settings-page .flex.justify-end > *,
  .favorites-page-header > * {
    width: 100%;
  }

  /* Appointment list items become cards. Note: `.service-row` removed from
     this selector — that class is now owned by views/partials/service-row.ejs
     which intentionally lays out as an inline row, not a stacked card. */
  .appointment-row,
  tr.appointment-row {
    display: flex;
    flex-direction: column;
    background: var(--surface-glass);
    border-radius: 0.75rem;
    padding: 1rem;
    margin-bottom: 0.75rem;
    gap: 0.5rem;
    border: 1px solid var(--surface-border);
  }

  /* Tables that should scroll horizontally. Custom class — source order resolves. */
  .table-responsive {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .table-responsive table {
    min-width: 600px;
  }
}

/* ============================================================
   6. CALENDAR — bottom sheet / modal on mobile

   Note: mobile.css is loaded LAST in dashboard/staff-portal layouts (after
   flatpickr.min.css and flatpickr-theme.css). At equal specificity, source
   order resolves in our favour. No !important needed here.
   ============================================================ */
@media (max-width: 768px) {
  /* Flatpickr inline calendar in modal — full width */
  .flatpickr-calendar {
    width: 100%;
    max-width: 100%;
    font-size: 1rem;
    border-radius: 1rem;
    box-shadow: none;
  }

  /* Calendar grid — 7 equal columns.

     flatpickr hard-codes `width/min-width/max-width: 307.875px` on both
     .flatpickr-days and .dayContainer, so the body grid stayed 307.875px wide
     while .flatpickr-weekdays (width: 100%) stretched to the full calendar —
     two different column pitches. Releasing the min-width floor is what lets
     the box actually shrink; overriding max-width alone does nothing.

     The explicit 7-column grid is load-bearing, not cosmetic: flatpickr emits
     42 sibling day cells with no row markup, so weeks exist only as flex-wrap
     line breaks. Any overflow — even a sub-pixel one — re-shards the month into
     6-day rows and slides every date under the wrong weekday, while the header
     (span.flatpickr-weekday is `flex: 1`, no min-width) cannot wrap to match.
     Grid tracks cannot wrap, so the columns stay aligned at any width.
     Guard: tests/unit/pages/flatpickr-week-grid.test.js */
  .flatpickr-days,
  .flatpickr-days .dayContainer {
    width: 100%;
    min-width: 0;
    max-width: 100%;
  }

  .flatpickr-days .dayContainer {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
  }

  /* .flatpickr-rContainer is an inline-block flex item that shrink-to-fits its
     children; without an explicit width its percentage-width children have
     nothing stable to resolve against. */
  .flatpickr-innerContainer,
  .flatpickr-rContainer {
    width: 100%;
  }

  /* Day cells — 44px min tap target.
     Height only: the cell's WIDTH must come from the grid track. A fixed px
     width here overrides flatpickr's `max-width: 39px` cap (min-width is
     clamped last, per CSS sizing) and is what broke the grid — 7 x 44px = 308px
     against a 307.875px container. */
  .flatpickr-day {
    width: auto;
    min-width: 0;
    max-width: none;
    height: 44px;
    min-height: 44px;
    line-height: 44px;
    font-size: 0.875rem;
  }

  /* Calendar container shown as bottom-sheet on mobile.
     Custom class — source order resolves. */
  .calendar-bottom-sheet {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: var(--z-modal, 60);
    background: var(--surface-glass-strong);
    backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    -webkit-backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    border-radius: 1.5rem 1.5rem 0 0;
    padding: 1.5rem 1rem 2rem;
    max-height: 90vh;
    overflow-y: auto;
    transform: translateY(100%);
    transition: transform 0.3s ease;
  }

  .calendar-bottom-sheet.open {
    transform: translateY(0);
  }

  .calendar-bottom-sheet-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(4px);
    z-index: calc(var(--z-modal, 60) - 1);
    display: none;
  }

  .calendar-bottom-sheet-backdrop.open {
    display: block;
  }

  /* The popup z-index for .flatpickr-calendar.open is owned by
     flatpickr-theme.css (z-index: 250) — that value is correct against
     the actual modal z-indices used in start.ejs. No override here. */

  /* Weekly calendar grid cells on mobile — scroll horizontally.
     Custom class — source order resolves. */
  .calendar-week-grid {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Business time-slot grid. Custom classes — source order resolves. */
  .time-slot-grid,
  .slots-container {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* ============================================================
   7. CHARTS — horizontal scroll wrapper
   ============================================================ */
@media (max-width: 768px) {
  /* Wrap chart canvases in scrollable container.
     Custom class — source order resolves. */
  .chart-scroll-wrapper {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Class + element (0,1,1) — !important not needed. */
  .chart-scroll-wrapper canvas {
    min-width: 480px;
  }

  /* Move chart legend below the chart. Custom class — source order resolves. */
  .chart-with-legend {
    flex-direction: column;
  }

  /* Class chain (0,2,0) — !important not needed. */
  .chart-with-legend .chart-legend {
    margin-top: 0.75rem;
    margin-left: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
  }
}

/* ============================================================
   8. MODALS — full screen on mobile
   ============================================================ */
@media (max-width: 640px) {
  /* .modal-content in tailwind.css @layer components sets background/backdrop-
     filter/border-radius/border/box-shadow/animation/padding. Conflicting props
     here (border-radius, animation) resolve to mobile.css via source order. */
  .modal-content {
    border-radius: 1.5rem 1.5rem 0 0;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    max-width: 100%;
    max-height: 92vh;
    overflow-y: auto;
    animation: slideUpMobile 0.3s ease-out;
    margin: 0;
    padding-bottom: env(safe-area-inset-bottom, 1rem);
  }

  /* Time-off modal: center positioning instead of bottom sheet.
     ID + class (1,1,0) — already beats utilities, no !important needed. */
  #time-off-modal .modal-content {
    position: fixed;
    bottom: auto;
    top: 50%;
    left: 50%;
    right: auto;
    transform: translate(-50%, -50%);
    width: 90%;
    max-width: 90%;
    max-height: 80vh;
    border-radius: 1rem;
    animation: modalCenterFade 0.3s ease-out;
    margin: 0;
  }

  /* ID + class (1,1,0) — !important not needed. */
  #time-off-modal .modal-overlay {
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  @keyframes modalCenterFade {
    from {
      opacity: 0;
      transform: translate(-50%, -45%);
    }
    to {
      opacity: 1;
      transform: translate(-50%, -50%);
    }
  }

  /* Custom class — source order resolves. */
  .modal-backdrop {
    align-items: flex-end;
  }

  /* ID (1,0,0) — !important not needed. */
  #time-off-modal {
    z-index: 1001;
  }

  @keyframes slideUpMobile {
    from {
      transform: translateY(100%);
      opacity: 0;
    }
    to {
      transform: translateY(0);
      opacity: 1;
    }
  }
}

/* ============================================================
   9. PROFILE DROPDOWN — full-width on small screens
   ============================================================ */
@media (max-width: 480px) {
  /* Profile / notification dropdowns. IDs (1,0,0) and one custom class —
     all win without !important. */
  #profile-dropdown,
  #notif-dropdown,
  .dropdown-panel {
    right: 0;
    left: 0;
    width: auto;
    margin: 0 0.5rem;
  }

  /* Consumer notification dropdown — fixed to viewport so it isn't
     clipped by its tiny bell-button containing block.
     ID (1,0,0) — !important not needed. */
  #consumer-notif-dropdown {
    position: fixed;
    top: 4rem;
    left: 1rem;
    right: 1rem;
    width: auto;
    margin: 0;
  }
}

/* ============================================================
   10. BOOKING PAGE — full width steps
   ============================================================ */
@media (max-width: 768px) {
  /* All custom classes / IDs below — source order resolves. */
  .booking-step-panel {
    padding: 1rem;
  }

  .services-grid {
    grid-template-columns: 1fr;
  }

  .staff-cards-row {
    flex-wrap: wrap;
  }

  .staff-card {
    width: 100%;
  }

  /* "Book" CTA always full width. ID + custom classes + attribute selector. */
  #book-btn,
  .book-action-btn,
  [data-action="book"] {
    width: 100%;
    min-height: 52px;
    font-size: 1rem;
  }

  .booking-summary-sticky {
    position: sticky;
    bottom: 0;
    background: var(--surface-glass-strong);
    backdrop-filter: blur(8px);
    border-top: 1px solid rgba(203, 213, 225, 0.5);
    padding: 0.75rem 1rem;
    z-index: 20;
  }
}

/* ============================================================
   11. SAFE AREA — iPhone notch/home indicator
   ============================================================ */
@supports (padding-bottom: env(safe-area-inset-bottom)) {
  .modal-content,
  .calendar-bottom-sheet,
  .filter-panel,
  .booking-summary-sticky {
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
  }
}

/* ============================================================
   12. BUSINESS PUBLIC PAGE — mobile hero & info  
   ============================================================ */
@media (max-width: 768px) {
  /* All custom classes — source order resolves. */
  .business-hero {
    height: 200px;
  }

  .tabs-row {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    flex-wrap: nowrap;
    scrollbar-width: none;
  }

  .tabs-row::-webkit-scrollbar {
    display: none;
  }

  .services-list-grid {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   13. MISC HELPERS
   ============================================================ */
@media (max-width: 768px) {
  .clients-page-filters .custom-select,
  .clients-page-filters .search-bar {
    width: 100%;
  }

  /* All chains here are 0,2,0 or 1,1,0 — !important not needed. */
  .calendar-toolbar-mobile .view-btn,
  .calendar-toolbar-mobile .cal-filter-btn,
  .calendar-toolbar-mobile .btn-create-promo,
  .calendar-toolbar-mobile #go-today-btn,
  .calendar-toolbar-mobile #reset-filters-btn {
    min-height: 44px;
  }

  .calendar-chip-ribbon {
    flex-wrap: wrap;
    overflow: visible;
  }

  .clients-list .client-card {
    border-radius: 14px;
  }

  /* ID (1,0,0) — !important not needed. */
  #pagination {
    padding-left: 16px;
    padding-right: 16px;
  }

  #pagination .flex.items-center.gap-2 {
    width: 100%;
  }

  #prev-btn,
  #next-btn {
    flex: 1;
    min-height: 44px;
  }

  /* Calendar is a viewport-fit, internal-scroll page — opt its <main>
     out of the 100px tabbar-reserve padding (doc pattern at line 1370).
     Scoped via `:has(> #calendar-page)` so no layout-file edit is needed
     and only the calendar route is affected. */
  main.app-mobile-main:has(> #calendar-page) {
    padding-bottom: 0;
  }

  /* All ID-rooted selectors below — !important not needed.
     Bound #calendar-page to the actual viewport so only #calendar-scroll
     scrolls. Subtractions:
       - 68px : real mobile glass-header height (matches layouts/dashboard.ejs
                spacer `h-[68px]` — 4px taller than the desktop xl:h-16).
                Was 4rem (64px), undercut by 4px → the calendar's bottom edge
                slipped under the tabbar.
       - var(--space-4) : <main> top padding (p-4 = 1rem = `comfy` token)
       - 70px : real mobile-tabbar height (56px min-height + 6+8px padding,
                see mobile-tabbar-dashboard.ejs)
       - var(--space-3) : `cozy` spacing token (0.75rem / 12px) — the
                breathing-room gap above the tabbar
       - env(safe-area-inset-bottom) : home-indicator clearance
     `dvh` so iOS Safari URL-bar collapse doesn't shrink/grow the
     calendar; `vh` fallback for older WebKit. */
  #calendar-page {
    height: calc(100vh - 68px - var(--space-4) - 70px - var(--space-3) - env(safe-area-inset-bottom, 0px));
    height: calc(100dvh - 68px - var(--space-4) - 70px - var(--space-3) - env(safe-area-inset-bottom, 0px));
    min-height: 0;
  }

  /* #day-headers is a sibling of #calendar-scroll inside the grid card —
     structurally sticky (lives outside the scroll axis, can't move).
     Tighten vertical padding to 4px on mobile — intentional sub-token
     exception. The `tight` spacing token (10px, the floor of the
     semantic scale) is correct for desktop header-bar density, but on
     mobile the row needs to be denser to maximize the calendar grid
     area. Same exception pattern as `py-2.5` on input internals
     (CLAUDE.md "Intentional exceptions"). */
  #day-headers > div {
    padding-top: 4px;
    padding-bottom: 4px;
  }

  #calendar-scroll {
    overflow-y: auto;
  }

  /* Week view → horizontal scroll on mobile.
     At flex:1 the 7 day-columns collapse to ~37px on a phone — too narrow
     to render an appointment chip. In week view we instead give each day a
     usable min-width (6rem) and let the grid scroll sideways, showing ~3
     days at a time with the time gutter pinned. Gated on
     [data-cal-view="week"] (set by renderCalendar) so day/month are untouched.
       - width:max-content on #calendar-grid is load-bearing: the sticky
         gutter's containing block must span the FULL scroll width, or it
         un-sticks once you scroll past ~1 viewport. (#day-headers is itself
         the scroll container, so its gutter sticks without this.)
       - scrollbar-width:none keeps #day-headers and #calendar-scroll the same
         client width — a desktop vertical scrollbar offsets only one of them
         and drifts the header/column alignment at the far edge. Mobile uses
         overlay scrollbars, so nothing visible is lost.
     The two scrollers are kept in horizontal lockstep by ensureWeekScrollSync()
     in business-calendar.js. */
  #calendar-page[data-cal-view="week"] #day-headers,
  #calendar-page[data-cal-view="week"] #calendar-scroll {
    overflow-x: auto;
    scrollbar-width: none;
  }
  #calendar-page[data-cal-view="week"] #day-headers::-webkit-scrollbar,
  #calendar-page[data-cal-view="week"] #calendar-scroll::-webkit-scrollbar {
    display: none;
  }
  #calendar-page[data-cal-view="week"] #calendar-grid {
    width: max-content;
  }
  #calendar-page[data-cal-view="week"] #day-headers .day-column,
  #calendar-page[data-cal-view="week"] #calendar-grid .day-column {
    flex: 0 0 6rem;
    min-width: 6rem;
  }
  #calendar-page[data-cal-view="week"] #day-headers .time-column,
  #calendar-page[data-cal-view="week"] #calendar-grid .time-column {
    position: sticky;
    left: 0;
    z-index: 8;
    background: #eef4f8;
  }

  .calendar-mobile-agenda .agenda-item {
    min-height: 64px;
  }

  /* Remove hover transforms on touch devices to prevent stutter.
     Class + pseudo (0,1,1) — source order resolves. */
  .warm-card:hover {
    transform: none;
  }

  /* All custom classes below — source order resolves. */
  .flex-nowrap-desktop {
    flex-wrap: wrap;
  }

  /* Element + class (0,1,1) — !important not needed. */
  select.w-auto {
    width: 100%;
  }

  .hero-section {
    padding-top: 3rem;
    padding-bottom: 3rem;
  }

  /* `.page-title` (0,1,0) resolves via source order; `h1.text-3xl`/
     `h1.text-4xl` (0,1,1) already beat utility text-* classes. */
  .page-title,
  h1.text-3xl,
  h1.text-4xl {
    font-size: 1.5rem;
    line-height: 1.3;
  }

  .section-gap {
    gap: 1rem;
  }
}

@media (min-width: 1280px) {
  /* ID (1,0,0) — !important not needed. */
  #mobile-new-appointment-fab {
    display: none;
  }
}

@media (max-width: 1279px) {
  body {
    background: var(--tg-theme-bg-color, var(--tg-theme-bg-color-fallback));
    color: var(--tg-theme-text-color, var(--tg-theme-text-color-fallback));
  }

  /* Mobile horizontal gutter is owned by page-level wrappers (px-cozy on
     consumer/auth pages, p-4 on dashboard/staff <main>) — not by .app-mobile-main
     itself. Stacking both caused 28px effective gutter on consumer layouts. */

  /* Bottom reserve for the fixed .mobile-tabbar. Load-bearing — must beat
     Tailwind padding utilities so pages aren't obscured. Class-selector
     specificity (0,1,0) ties with .p-4 / .pb-* and mobile.css loads AFTER
     app.css, so source order wins. To opt out (e.g. internal-scroll pages),
     drop the .app-mobile-main class from <main>. */
  .app-mobile-main {
    padding-bottom: calc(100px + env(safe-area-inset-bottom, 0px));
  }

  /* Stack-spacing between visible siblings inside <main>. Skip <style> and
     <script> on either side — they're non-rendered nodes (display:none) but
     under the previous unguarded `* + *` rule, a page that opened with an
     inline <style> would trip the cozy-token (12px) margin onto its first
     visible div. Result: calendar/staff-list/etc. sat 12px lower than the
     dashboard's "Обзор" benchmark on mobile. Filtering both sides aligns
     every page's first-content top to <main>'s p-4 alone. */
  .app-mobile-main > *:not(style):not(script) + *:not(style):not(script) {
    margin-top: 12px;
  }

  .mobile-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 120;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 4px;
    padding: 8px 10px calc(8px + env(safe-area-inset-bottom, 0px));
    background: var(--surface-glass-strong);
    border-top: 1px solid var(--surface-border);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }

  .mobile-more-modal {
    position: fixed;
    inset: 0;
    z-index: 130;
    background: rgba(15, 23, 42, 0.46);
    backdrop-filter: blur(4px);
  }

  .mobile-more-sheet {
    background: var(--surface-glass-strong);
    backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    -webkit-backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    padding: 18px 16px calc(20px + env(safe-area-inset-bottom, 0px));
  }

  .mobile-more-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 14px;
  }

  .mobile-more-head h3 {
    font-size: 20px;
    font-weight: 700;
  }

  .mobile-more-head button {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #f8fafc;
  }

  .mobile-more-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  .mobile-more-item {
    min-height: 52px;
    border-radius: 14px;
    border: 1px solid var(--surface-border);
    background: var(--surface-glass);
    padding: 0 14px;
    display: flex;
    align-items: center;
    font-weight: 600;
    color: var(--text-primary);
  }

  .calendar-toolbar-clean {
    background: var(--surface-glass-strong);
    backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    -webkit-backdrop-filter: blur(var(--glass-strong-blur)) saturate(var(--glass-strong-saturate));
    border: 1px solid var(--surface-border);
  }

  .calendar-toolbar-clean .calendar-chip-ribbon {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 10px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .calendar-toolbar-clean .calendar-chip-ribbon::-webkit-scrollbar {
    display: none;
  }

  /* All chains (0,2,0 / 1,1,0) — !important not needed. */
  .calendar-toolbar-clean .cal-filter-btn,
  .calendar-toolbar-clean #reset-filters-btn,
  .calendar-toolbar-clean #status-dropdown-btn,
  .calendar-toolbar-clean #staff-dropdown-btn {
    flex: 0 0 auto;
    min-height: 44px;
  }

  .calendar-toolbar-clean .cal-dropdown-menu {
    right: auto;
    left: 0;
  }

  /* Class + ID chains (1,1,0) — !important not needed. */
  .calendar-toolbar-clean #go-today-btn,
  .calendar-toolbar-clean #date-display,
  .calendar-toolbar-clean #nav-prev-btn,
  .calendar-toolbar-clean #nav-next-btn {
    display: none;
  }

  .mobile-tabbar-item {
    min-height: 56px;
    border-radius: 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-muted);
  }

  .mobile-tabbar-item.active {
    background: var(--primary-ring);
    color: var(--primary-dark);
  }

  .mobile-tabbar-item svg {
    width: 20px;
    height: 20px;
  }

  /* IDs (1,0,0) — !important not needed. */
  #dashboard-sidebar,
  #staff-sidebar,
  #sidebar-close {
    display: none;
  }

  /* `:root h1` (0,1,1) beats utility text-* (0,1,0); `.page-title` ties
     and wins on source order. */
  :root h1, .page-title {
    font-size: 20px;
    line-height: 1.3;
    font-weight: 700;
  }

  :root h2, .section-title {
    font-size: 18px;
    line-height: 1.35;
    font-weight: 700;
  }

  /* Calendar toolbar date-display is an h2 but shares the row with the "+"
     CTA and prev/next chevrons — at the global 18px floor it truncates
     "среда, 20 мая 2026 г." to "среда, 20 …". Drop to the toolbar chip
     family (14px = text-sm token) on mobile so the full date fits. The
     :root h2#date-display selector (0,2,1) beats :root h2 (0,1,1) without
     !important; the EJS template already declares `text-sm sm:text-lg` on
     this element, so desktop is handled at the Tailwind layer. */
  :root h2#date-display {
    font-size: 0.875rem;
    line-height: 1.4;
  }

  p, span, label, input, select, textarea, button {
    font-size: 16px;
  }

  /* All custom classes — source order resolves.
     `.service-card` was removed 2026-07-28: it is used only by the consumer
     booking page, where the `border` shorthand here reset `border-color` and
     the `box-shadow` overwrote Tailwind's `ring-*` (also box-shadow). Because
     mobile.css loads after app.css at equal (0,1,0) specificity, that silently
     erased the *selected service* state below 1280px — the chip kept its tint
     but lost its blue border and ring. book.ejs now owns that chip's surface. */
  .warm-card-static,
  .warm-card,
  .client-card,
  .appointment-item,
  .notification-card {
    border-radius: 16px;
    border: 1px solid var(--surface-border);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
  }

  /* Mix of custom classes and IDs — source order / ID specificity resolve. */
  .clients-list,
  #upcoming-list,
  #past-list,
  #notificationsList,
  #appointment-list,
  #services-list,
  .staff-list,
  .reviews-list {
    gap: 12px;
  }

  .clients-list > *,
  #upcoming-list > *,
  #past-list > *,
  #notificationsList > * {
    margin-bottom: 12px;
  }

  /* `:root button` (0,1,1) beats utility h-*/min-h-* (0,1,0);
     `.btn*` and `[type=…]` (0,1,0) tie and win on source order. */
  :root button,
  .btn,
  .btn-primary,
  .btn-cta,
  .btn-secondary,
  .btn-outline,
  .btn-danger,
  [type="button"],
  [type="submit"] {
    min-height: 48px;
  }

  .btn-primary,
  .btn-cta,
  .btn-secondary,
  .btn-outline,
  .btn-danger,
  .toolbar .custom-select,
  .toolbar .custom-select-trigger,
  .toolbar input,
  .toolbar select {
    width: 100%;
  }

  /* Buttons that opt into `shrink-0` (via wrapperClass on the button partial)
     explicitly signal "I belong in an inline row, don't grow to fill it".
     Specificity 0,2,0 beats the global `.btn-*` (0,1,0) rule above. This
     supersedes the prior calendar-specific override at .calendar-toolbar-wrap
     and also fixes the Сброс button on the clients page, which was being
     stretched to the full width and squeezing the filter dropdowns to 0. */
  .btn-primary.shrink-0,
  .btn-cta.shrink-0,
  .btn-secondary.shrink-0,
  .btn-outline.shrink-0,
  .btn-danger.shrink-0 { width: auto; }

  /* Chains (0,2,0) + custom single classes — source order resolves. */
  .clients-page-filters > .flex,
  .calendar-toolbar-mobile > .flex,
  .mobile-page-header,
  .mobile-page-header-actions {
    gap: 12px;
  }

  .calendar-mobile-day-ribbon {
    display: flex;
    overflow-x: auto;
    gap: 8px;
    padding: 10px 12px 6px;
    border-bottom: 1px solid var(--surface-border);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .calendar-mobile-day-ribbon::-webkit-scrollbar {
    display: none;
  }

  .calendar-day-chip {
    flex: 0 0 auto;
    min-width: 68px;
    min-height: 48px;
    border-radius: 14px;
    border: 1px solid var(--surface-border);
    background: var(--surface-glass-strong);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 6px 10px;
    font-size: 12px;
    color: var(--text-secondary);
  }

  .calendar-day-chip.active {
    background: var(--primary-ring);
    border-color: rgba(14, 165, 233, 0.35);
    color: var(--primary-dark);
  }

  .calendar-mobile-agenda-empty {
    text-align: center;
    padding: 32px 16px;
    color: var(--text-muted);
  }

  .calendar-mobile-agenda-empty .empty-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto 12px;
    border-radius: 50%;
    border: 1px solid var(--surface-border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-faint);
  }

  /* ID (1,0,0) — !important not needed. */
  #mobile-new-appointment-fab {
    right: 16px;
    bottom: calc(84px + env(safe-area-inset-bottom, 0px));
    width: 56px;
    height: 56px;
    padding: 0;
  }
}

/* ============================================================
   14. MOBILE COMPONENT TOOLKIT
   Generic iOS-list / settings-style components used across all
   mobile surfaces (business, consumer, staff). Single source of
   truth: edit here and every page that uses these classes updates.
   Historical note: these classes were formerly prefixed `tma-`
   (when they were thought to be Telegram-Mini-App-specific) before
   the 2026-05-16 cleanup. The TMA-only rules live in tma-premium.css.
   ============================================================ */
:root {
  --mobile-card: #ffffff;
  --mobile-card-bg: #f8fafc;
  --mobile-card-text: #111827;
  --mobile-card-hint: #6b7280;
  --mobile-sep: rgba(0, 0, 0, 0.08);
  --mobile-link: #2ea6ff;
  --mobile-link-ring: rgba(46, 166, 255, 0.5);
  --mobile-link-tint: rgba(46, 166, 255, 0.12);
}

.mobile-section {
  background: var(--mobile-card);
  border: 1px solid var(--mobile-sep);
  border-radius: 14px;
  overflow: hidden;
}

.mobile-section-title {
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 10px;
}

.mobile-cell {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 12px;
  border-bottom: 1px solid var(--mobile-sep);
  background: var(--mobile-card);
}

.mobile-cell:last-child {
  border-bottom: 0;
}

.mobile-cell-avatar {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: #e5e7eb;
  color: #374151;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  flex-shrink: 0;
}

.mobile-cell-content {
  min-width: 0;
  flex: 1;
}

.mobile-cell-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--mobile-card-text);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mobile-cell-subtitle {
  font-size: 13px;
  color: var(--mobile-card-hint);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mobile-cell-action {
  color: var(--mobile-card-hint);
  flex-shrink: 0;
}

.mobile-icon-btn {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  border: 1px solid var(--mobile-sep);
  background: var(--mobile-card);
  color: var(--mobile-card-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Header buttons get a guaranteed 44x44 tap area so finger taps land cleanly. */
.header-tap-btn {
  min-width: 44px;
  min-height: 44px;
}

.mobile-filter-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 8px 0 0;
}

.mobile-filter-strip::-webkit-scrollbar {
  display: none;
}

.mobile-chip {
  min-height: 40px;
  border: 1px solid var(--mobile-sep);
  border-radius: 999px;
  padding: 0 12px;
  background: var(--mobile-card);
  color: var(--mobile-card-text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.mobile-chip-active {
  color: var(--mobile-link);
  border-color: var(--mobile-link-ring);
  background: var(--mobile-link-tint);
}

.mobile-modal-page {
  position: fixed;
  inset: 0;
  z-index: 140;
  background: var(--mobile-card);
  padding: 14px 12px calc(16px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
}

.mobile-week-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 8px 0;
}

.mobile-week-strip::-webkit-scrollbar {
  display: none;
}

.mobile-day-pill {
  min-width: 64px;
  min-height: 48px;
  border-radius: 12px;
  border: 1px solid var(--mobile-sep);
  background: var(--mobile-card);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--mobile-card-hint);
}

.mobile-day-pill.active {
  color: var(--mobile-link);
  border-color: var(--mobile-link-ring);
  background: var(--mobile-link-tint);
}

.mobile-fab {
  position: fixed;
  right: 16px;
  bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  width: 56px;
  height: 56px;
  border-radius: 999px;
  border: 0;
  background: var(--mobile-link);
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 110;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
}

.mobile-placeholder {
  text-align: center;
  padding: 28px 14px;
  color: var(--mobile-card-hint);
}

.mobile-placeholder-icon {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  border: 1px solid var(--mobile-sep);
  margin: 0 auto 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (min-width: 1280px) {
  /* Custom classes, defined only above — source order resolves. */
  .mobile-fab,
  .mobile-week-strip,
  .mobile-modal-page {
    display: none;
  }
}
