/* =============================================================================
   Components — buttons, forms, cards, data display, overlays, feedback.
   ========================================================================== */

/* --- Buttons -------------------------------------------------------------- */
.btn {
  --btn-bg: var(--surface);
  --btn-fg: var(--text);
  --btn-border: var(--border-strong);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.68rem 1.15rem;
  /* 44px is the smallest reliable touch target (WCAG 2.5.5 / iOS HIG). It is
     a floor, not a size — `flex: 0 0 auto` keeps a crowded row from squeezing
     a button below it instead of wrapping. */
  min-height: 44px;
  flex: 0 0 auto;
  border-radius: var(--r-md);
  border: 1px solid var(--btn-border);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              opacity var(--dur-1) var(--ease);
}
@media (hover: hover) {
  .btn:hover { box-shadow: var(--shadow-sm); }
}
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.55; pointer-events: none; }

.btn--primary {
  --btn-bg: var(--primary);
  --btn-fg: var(--text-on-brand);
  --btn-border: transparent;
  box-shadow: var(--shadow-brand);
}
.btn--soft { --btn-bg: var(--primary-soft); --btn-fg: var(--primary-soft-text); --btn-border: transparent; }
.btn--ghost { --btn-bg: transparent; --btn-border: transparent; --btn-fg: var(--text-muted); }
.btn--danger { --btn-bg: var(--danger); --btn-fg: var(--text-on-danger); --btn-border: transparent; }
.btn--danger-soft { --btn-bg: var(--danger-soft); --btn-fg: var(--danger-soft-text); --btn-border: transparent; }
.btn--success { --btn-bg: var(--success); --btn-fg: var(--text-on-success); --btn-border: transparent; }

/* Hover recolouring is pointer-only: on a touch screen the "hover" state
   sticks to the last element tapped, which reads as a stuck selection. */
@media (hover: hover) {
  .btn--primary:hover { --btn-bg: var(--primary-hover); }
  .btn--ghost:hover { --btn-bg: var(--surface-3); --btn-fg: var(--text); }
  .btn--danger:hover { --btn-bg: var(--danger-hover); }
}

/* --sm keeps the compact look but never drops below a 40px touch box; the
   extra height comes from a transparent outset rather than padding, so the
   visual size is unchanged. */
.btn--sm {
  min-height: 36px;
  padding: 0.4rem 0.8rem;
  font-size: var(--fs-xs);
  border-radius: var(--r-sm);
}
@media (pointer: coarse) {
  .btn--sm { min-height: 40px; padding-inline: 0.9rem; }
}
.btn--lg { min-height: 52px; padding: 0.9rem 1.6rem; font-size: 1rem; border-radius: var(--r-lg); }
.btn--block { width: 100%; }
.btn--icon { padding: 0; width: 44px; min-height: 44px; flex: 0 0 44px; border-radius: var(--r-md); }
.btn--icon.btn--sm { width: 36px; min-height: 36px; flex: 0 0 36px; }
@media (pointer: coarse) {
  .btn--icon.btn--sm { width: 40px; min-height: 40px; flex: 0 0 40px; }
}

.btn.is-loading { color: transparent !important; position: relative; pointer-events: none; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  width: 18px; height: 18px;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: 50%;
  color: var(--btn-fg);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- Segmented control ----------------------------------------------------- */
.segmented {
  display: inline-flex;
  max-width: 100%;
  padding: 4px;
  gap: 2px;
  background: var(--surface-3);
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
}
.segmented::-webkit-scrollbar { display: none; }
.segmented button {
  flex: 0 0 auto;
  padding: 0.4rem 0.9rem;
  min-height: 34px;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-muted);
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (pointer: coarse) {
  .segmented button { min-height: 40px; }
}
.segmented button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-xs);
}

/* --- Theme toggle ---------------------------------------------------------- */
.theme-toggle {
  display: inline-flex;
  flex: 0 0 auto;
  padding: 3px;
  gap: 2px;
  background: var(--surface-3);
  border-radius: var(--r-full);
  border: 1px solid var(--border);
}
.theme-toggle button {
  display: grid; place-items: center;
  flex: 0 0 auto;
  width: 34px; height: 34px;
  border-radius: 50%;
  color: var(--text-subtle);
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
/* Three 40px circles still fit the top bar of a 320px phone once the wordmark
   has dropped out, so the theme switch stays a comfortable touch target
   instead of shrinking with the screen. */
@media (pointer: coarse) {
  .theme-toggle button { width: 40px; height: 40px; }
}
@media (hover: hover) {
  .theme-toggle button:hover { color: var(--text); }
}
.theme-toggle button[aria-pressed="true"] { background: var(--surface); color: var(--primary); box-shadow: var(--shadow-xs); }

/* --- Cards ---------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-xs);
}
.card--flat { box-shadow: none; }
.card--pad-lg { padding: var(--space-6); }
.card--tight { padding: var(--space-4); }
.card--interactive { transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease); cursor: pointer; text-align: start; width: 100%; display: block; }
@media (hover: hover) {
  .card--interactive:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--border-strong); }
}
/* Touch gets a press state instead of a lift, so tapping a card gives the
   same "it responded" feedback without leaving it raised afterwards. */
.card--interactive:active { border-color: var(--primary); background: var(--surface-2); }

.card__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.card__title { font-size: var(--fs-h3); font-weight: 700; letter-spacing: -0.01em; }
.card__sub { font-size: var(--fs-sm); color: var(--text-muted); }

/* Accent strip used on hero-ish cards (wallet, confirmation). */
.card--accent {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(30rem 16rem at 100% 0%, var(--primary-soft), transparent 65%),
    var(--surface);
}

/* --- Stat tiles ------------------------------------------------------------ */
.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
.stat__label { font-size: var(--fs-xs); color: var(--text-muted); font-weight: 600; }
.stat__value { font-size: 1.55rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.25; }
.stat__hint { font-size: var(--fs-xs); color: var(--text-subtle); }
.stat--brand { background: linear-gradient(150deg, var(--primary-soft), var(--surface) 70%); }
.stat--success .stat__value { color: var(--success); }
.stat--danger .stat__value { color: var(--danger); }

/* --- Badges & chips -------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0.2rem 0.6rem;
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 700;
  background: var(--neutral-soft);
  color: var(--neutral-soft-text);
  white-space: nowrap;
}
.badge::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.8;
}
.badge--plain::before { display: none; }
.badge--success { background: var(--success-soft); color: var(--success-soft-text); }
.badge--warning { background: var(--warning-soft); color: var(--warning-soft-text); }
.badge--danger  { background: var(--danger-soft);  color: var(--danger-soft-text); }
.badge--info    { background: var(--info-soft);    color: var(--info-soft-text); }
.badge--brand   { background: var(--primary-soft); color: var(--primary-soft-text); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: 0.35rem 0.75rem;
  border-radius: var(--r-full);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
}
/* A chip that links somewhere (call this customer, open this profile) is a
   control, so it gets a control's hit area rather than a label's. */
@media (pointer: coarse) {
  a.chip { min-height: 44px; }
}

/* --- Avatar ---------------------------------------------------------------- */
.avatar {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 42px; height: 42px;
  border-radius: var(--r-md);
  font-weight: 800;
  font-size: var(--fs-sm);
  color: var(--primary-soft-text);
  background: var(--primary-soft);
  overflow: hidden;
}
.avatar--lg { width: 60px; height: 60px; font-size: 1.1rem; border-radius: var(--r-lg); }
.avatar--sm { width: 34px; height: 34px; font-size: var(--fs-xs); border-radius: var(--r-sm); }

/* --- Forms ----------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--space-2); }
.field__label { font-size: var(--fs-sm); font-weight: 600; }
.field__hint { font-size: var(--fs-xs); color: var(--text-subtle); }
.field__error { font-size: var(--fs-xs); color: var(--danger-soft-text); font-weight: 700; }
/* The required marker is text, so it needs text contrast: --danger is tuned
   as a *fill*, and at this size it lands under 4.5:1 on a white card. */
.req { color: var(--danger-soft-text); font-weight: 700; }

.input, .select, .textarea {
  width: 100%;
  min-width: 0;
  min-height: 46px;
  padding: 0.65rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--text);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--text-subtle); }
@media (hover: hover) {
  .input:hover, .select:hover, .textarea:hover { border-color: var(--text-subtle); }
}

/* Mobile Safari zooms the whole page in when a focused field's text is
   smaller than 16px, and never zooms back out. The body scale is 15.6px, so
   every control gets bumped to exactly 16px on touch devices. */
@media (pointer: coarse) {
  .input, .select, .textarea { font-size: 16px; min-height: 48px; }
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 4px var(--primary-ring);
}
.input[aria-invalid="true"] { border-color: var(--danger); }
.textarea { min-height: 96px; resize: vertical; line-height: 1.7; }

.select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: left 1rem top 55%, left 0.7rem top 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-inline-start: 2rem;
}

.input-group { display: flex; gap: var(--space-2); align-items: stretch; flex-wrap: wrap; }
.input-group .input { flex: 1 1 12rem; }

.switch { display: inline-flex; align-items: center; gap: var(--space-3); cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__track {
  position: relative;
  width: 46px; height: 27px;
  border-radius: var(--r-full);
  background: var(--border-strong);
  transition: background var(--dur-2) var(--ease);
  flex: 0 0 auto;
}
.switch__track::after {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  width: 21px; height: 21px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur-2) var(--ease-out);
}
.switch input:checked + .switch__track { background: var(--primary); }
.switch input:checked + .switch__track::after { transform: translateX(-19px); }
.switch input:focus-visible + .switch__track { box-shadow: 0 0 0 4px var(--primary-ring); }

/* OTP entry — 6 single-character boxes that behave like one field. Sized in
   fractions of the container so six boxes always fit, down to a 280px phone,
   without a horizontal scroll. */
.otp {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-2);
  direction: ltr;
  justify-content: center;
  max-width: 21rem;
  margin-inline: auto;
  width: 100%;
}
.otp input {
  width: 100%; height: 56px;
  min-width: 0;
  text-align: center;
  font-size: 1.3rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--text);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.otp input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 4px var(--primary-ring); }
.otp input.is-filled { border-color: var(--primary); }

/* --- Lists ---------------------------------------------------------------- */
.list { display: flex; flex-direction: column; }
.list > * + * { border-top: 1px solid var(--divider); }

.list-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-1);
  text-align: start;
}
.list-item__body { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.list-item__title { font-weight: 700; font-size: var(--fs-sm); }
.list-item__meta { font-size: var(--fs-xs); color: var(--text-muted); display: flex; gap: var(--space-3); flex-wrap: wrap; }
.list-item__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: flex-end; }

/* --- Tables ---------------------------------------------------------------- */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
}
table.table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); min-width: 34rem; }
.table th, .table td { padding: var(--space-3) var(--space-4); text-align: start; white-space: nowrap; }
.table thead th {
  position: sticky; top: 0;
  background: var(--surface-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}
.table tbody tr + tr td { border-top: 1px solid var(--divider); }
@media (hover: hover) {
  .table tbody tr:hover td { background: var(--surface-2); }
}

/* Stacked mode: below the break the same markup becomes one card per row,
   with each cell labelled by its column header (carried in `data-label`).
   Sideways-scrolling a table is the single worst thing to hand a phone user,
   and this keeps every value visible without one. */
@media (max-width: 720px) {
  .table-wrap--stack {
    border: none;
    border-radius: 0;
    background: none;
    overflow: visible;
  }
  table.table--stack,
  .table--stack tbody,
  .table--stack tr,
  .table--stack td { display: block; min-width: 0; width: 100%; }
  .table--stack thead { display: none; }
  .table--stack tr {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
    padding: var(--space-3) var(--space-4);
  }
  .table--stack tr + tr { margin-top: var(--space-3); }
  .table--stack tbody tr + tr td { border-top: none; }
  .table--stack td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-2) 0;
    white-space: normal;
    text-align: end;
  }
  .table--stack td + td { border-top: 1px solid var(--divider); }
  .table--stack td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: var(--fs-xs);
    font-weight: 600;
    text-align: start;
  }
  /* The first cell is the row's identity (avatar + name) — it reads as a
     heading, so it spans the card instead of being label/value. */
  .table--stack td:first-child { padding-top: 0; }
  .table--stack td:first-child::before,
  .table--stack td[data-label=""]::before { display: none; }
  .table--stack td:first-child,
  .table--stack td[data-label=""] { justify-content: flex-start; }
  .table--stack td[data-label=""] { justify-content: flex-end; padding-bottom: 0; }
}

/* --- Tabs ------------------------------------------------------------------ */
.tabs {
  display: flex;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  position: relative;
  flex: 0 0 auto;
  scroll-snap-align: start;
  min-height: 44px;
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease);
}
@media (hover: hover) {
  .tabs button:hover { color: var(--text); }
}
.tabs button[aria-selected="true"] { color: var(--primary); }
.tabs button[aria-selected="true"]::after {
  content: "";
  position: absolute;
  inset-inline: var(--space-3);
  bottom: -1px;
  height: 2px;
  border-radius: var(--r-full);
  background: var(--primary);
}

/* --- Slot picker ----------------------------------------------------------- */
.daystrip {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: var(--space-2);
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--space-2);
  scrollbar-width: none;
}
.daystrip::-webkit-scrollbar { height: 0; display: none; }
.day {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 2px;
  min-width: 64px;
  min-height: 72px;
  padding: var(--space-3) var(--space-2);
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  transition: all var(--dur-1) var(--ease);
}
@media (hover: hover) {
  .day:hover { border-color: var(--border-strong); }
}
.day strong { font-size: 1.05rem; color: var(--text); font-weight: 700; }
.day[aria-pressed="true"] {
  background: var(--primary);
  border-color: transparent;
  /* The weekday sits a step quieter than the date it labels. The plain
     declaration comes first so a browser without color-mix still gets a
     readable colour rather than inheriting the muted one. */
  color: var(--text-on-brand);
  color: color-mix(in srgb, var(--text-on-brand) 82%, transparent);
  box-shadow: var(--shadow-brand);
}
.day[aria-pressed="true"] strong { color: var(--text-on-brand); }
.day--closed { opacity: 0.45; }

.slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 5.6rem), 1fr)); gap: var(--space-2); }
.slot {
  min-height: 48px;
  padding: var(--space-3) var(--space-2);
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--surface);
  font-weight: 700;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  transition: all var(--dur-1) var(--ease);
}
@media (hover: hover) {
  .slot:hover:not([disabled]) { border-color: var(--primary); color: var(--primary); }
}
.slot[aria-pressed="true"] { background: var(--primary); border-color: transparent; color: var(--text-on-brand); box-shadow: var(--shadow-brand); }
.slot[disabled] { opacity: 0.4; text-decoration: line-through; cursor: not-allowed; }
.slot--booked { background: var(--danger-soft); color: var(--danger-soft-text); border-color: transparent; }
.slot--blocked { background: var(--surface-3); color: var(--text-subtle); border-color: transparent; }

/* --- Stepper --------------------------------------------------------------- */
.stepper { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-6); }
.stepper__step { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-xs); color: var(--text-subtle); font-weight: 600; }
.stepper__dot {
  display: grid; place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 700;
}
.stepper__step.is-active .stepper__dot { background: var(--primary); color: var(--text-on-brand); }
.stepper__step.is-done .stepper__dot { background: var(--success-soft); color: var(--success-soft-text); }
.stepper__step.is-active { color: var(--text); }
.stepper__bar { flex: 1 1 auto; height: 2px; background: var(--divider); border-radius: var(--r-full); }

/* --- QR panel -------------------------------------------------------------- */
.qr-frame {
  display: grid;
  place-items: center;
  padding: var(--space-5);
  background: #fff;
  border-radius: var(--r-xl);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  width: fit-content;
  margin-inline: auto;
}
.qr-frame img, .qr-frame canvas, .qr-frame svg { width: min(260px, 60vw); height: auto; image-rendering: pixelated; }

.scanner {
  position: relative;
  border-radius: var(--r-xl);
  overflow: hidden;
  background: #000;
  aspect-ratio: 3 / 4;
  max-height: 60dvh;
  margin-inline: auto;
  width: 100%;
}
.scanner video { width: 100%; height: 100%; object-fit: cover; }
.scanner__reticle {
  position: absolute;
  inset: 14%;
  border: 2px solid rgb(255 255 255 / 0.9);
  border-radius: var(--r-lg);
  box-shadow: 0 0 0 100vmax rgb(0 0 0 / 0.45);
}
.scanner__reticle::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: 2px;
  background: var(--teal-400);
  box-shadow: 0 0 12px var(--teal-400);
  animation: scanline 2.4s var(--ease) infinite;
}
@keyframes scanline { 0%, 100% { top: 4%; } 50% { top: 96%; } }

/* --- Feedback -------------------------------------------------------------- */
.empty {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-10) var(--space-5);
  text-align: center;
  color: var(--text-muted);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}
.empty__icon { color: var(--text-subtle); opacity: 0.8; }
.empty h3 { color: var(--text); font-size: var(--fs-h3); }
.empty p { font-size: var(--fs-sm); max-width: 26rem; }

.alert {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  background: var(--info-soft);
  color: var(--info-soft-text);
}
.alert--danger { background: var(--danger-soft); color: var(--danger-soft-text); }
.alert--warning { background: var(--warning-soft); color: var(--warning-soft-text); }
.alert--success { background: var(--success-soft); color: var(--success-soft-text); }
.alert__body { min-width: 0; }

.skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--surface-3);
  min-height: 1em;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.16), transparent);
  animation: shimmer 1.4s infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }
.skeleton--card { height: 96px; border-radius: var(--r-lg); }
.skeleton--line { height: 12px; }
.skeleton--title { height: 20px; width: 40%; }

.progress { height: 6px; border-radius: var(--r-full); background: var(--surface-3); overflow: hidden; }
.progress__bar { height: 100%; background: var(--primary); border-radius: var(--r-full); transition: width var(--dur-3) var(--ease); }

/* --- Toasts ---------------------------------------------------------------- */
.toast-stack {
  position: fixed;
  z-index: 120;
  inset-block-end: calc(var(--tabbar-h) + var(--space-4));
  inset-inline: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: center;
  pointer-events: none;
}
@media (min-width: 900px) {
  .toast-stack { inset-block-end: var(--space-6); inset-inline-start: var(--space-6); inset-inline-end: auto; align-items: flex-start; }
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: min(30rem, 92vw);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  font-weight: 600;
  animation: toast-in var(--dur-3) var(--ease-out);
}
.toast.is-leaving { animation: toast-out var(--dur-2) var(--ease) forwards; }
.toast__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex: 0 0 auto; }
.toast--success .toast__dot { background: var(--success); }
.toast--error .toast__dot { background: var(--danger); }
.toast--warning .toast__dot { background: var(--warning); }

@keyframes toast-in { from { opacity: 0; transform: translateY(12px) scale(0.98); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px) scale(0.98); } }

/* --- Connection bar --------------------------------------------------------- */
.conn-bar {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 130;
  padding: var(--space-2) var(--space-4);
  text-align: center;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-on-danger);
  background: var(--danger);
  animation: conn-in var(--dur-2) var(--ease);
}
.conn-bar--back { background: var(--success); color: var(--text-on-success); }
@keyframes conn-in { from { transform: translateY(-100%); } }

/* --- Modal / sheet --------------------------------------------------------- */
.modal-root:empty { display: none; }
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--overlay);
  backdrop-filter: blur(3px);
  animation: fade-in var(--dur-2) var(--ease);
}
.modal {
  width: min(100%, 30rem);
  max-height: min(86dvh, 44rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  animation: modal-in var(--dur-3) var(--ease-out);
}
.modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-4); }
.modal__title { font-size: var(--fs-h3); font-weight: 700; }
.modal__foot { display: flex; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-6); flex-wrap: wrap; }
.modal__foot .btn { flex: 1 1 auto; }
@media (min-width: 520px) { .modal__foot .btn { flex: 0 0 auto; } }

@keyframes fade-in { from { opacity: 0; } }
@keyframes modal-in { from { opacity: 0; transform: translateY(16px) scale(0.97); } }

@media (max-width: 519px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%;
    max-height: 90dvh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom));
    animation: sheet-in var(--dur-3) var(--ease-out);
  }
  @keyframes sheet-in { from { transform: translateY(100%); } }
}

/* --- Sticky action bar ------------------------------------------------------ */
/* For long editors (a seven-day hours grid is several screens on a phone) the
   primary action follows the user instead of waiting at the bottom. It sits
   above the tab bar so it never covers navigation. */
.sticky-actions {
  position: sticky;
  bottom: calc(var(--tabbar-h) + var(--space-2));
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: flex-end;
  align-items: center;
  padding: var(--space-3);
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  background: var(--surface-glass);
  backdrop-filter: saturate(1.6) blur(14px);
  -webkit-backdrop-filter: saturate(1.6) blur(14px);
  box-shadow: var(--shadow-md);
}
@media (min-width: 900px) {
  .sticky-actions { bottom: var(--space-5); }
}
@media (max-width: 560px) {
  .sticky-actions > .btn { flex: 1 1 auto; }
}

/* --- Misc ------------------------------------------------------------------ */
.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-2) var(--space-4); font-size: var(--fs-sm); align-items: center; }
.kv dt { color: var(--text-muted); }
.kv dd { text-align: end; font-weight: 600; min-width: 0; }

.copy-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-inset);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  direction: ltr;
}
/* `min-width: 0` is what actually lets the ellipsis happen: without it the
   flex item's automatic minimum is the full width of the unbreakable URL,
   which used to drag the whole page sideways on a phone. */
.copy-field span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }

/* Icons never shrink — a squeezed 20px icon in a tight flex row collapses to
   a sliver, which is how icon-only buttons lose their hit area. */
.icon { width: 20px; height: 20px; flex: 0 0 auto; stroke-width: 1.9; }
.icon--sm { width: 16px; height: 16px; }
.icon--lg { width: 28px; height: 28px; }
.icon--xl { width: 44px; height: 44px; }

.divider-text {
  display: flex; align-items: center; gap: var(--space-3);
  color: var(--text-subtle); font-size: var(--fs-xs);
}
.divider-text::before, .divider-text::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--divider);
}

.view-enter { animation: view-in var(--dur-3) var(--ease-out); }
@keyframes view-in { from { opacity: 0; transform: translateY(8px); } }

/* --- Print ------------------------------------------------------------------ */
/* Providers do print from this app — a booking list to work from at the desk,
   or the QR page. Strip the chrome and force a light, ink-cheap page. */
@media print {
  :root, :root[data-theme="dark"] {
    --bg: #fff;
    --surface: #fff;
    --surface-2: #fff;
    --surface-3: #f2f2f2;
    --text: #000;
    --text-muted: #333;
    --text-subtle: #555;
    --border: #bbb;
    --border-strong: #999;
    --divider: #ddd;
  }
  body { background: #fff !important; }
  .topbar, .rail, .tabbar, .toast-stack, .modal-root, .skip-link,
  .conn-bar, .page-head__actions, .btn, .theme-toggle { display: none !important; }
  .layout { display: block; max-width: none; padding: 0; }
  .main { padding: 0; }
  .card, .table-wrap { break-inside: avoid; box-shadow: none !important; border-color: #ccc; }
  a[href]::after { content: ""; }
  .qr-frame { box-shadow: none; border: none; }
}

/* --- Image picker ------------------------------------------------------------ */
/* Preview + controls for the three uploadable images (customer avatar, business
   logo, business banner). The preview keeps the aspect ratio each image is
   actually displayed at, so what someone sees while choosing is what the page
   will show — an avatar cropped square, a banner cropped wide. */
.imagepicker {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.imagepicker__preview {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  overflow: hidden;
  background: var(--surface-inset);
  border: 1px solid var(--border);
  color: var(--text-subtle);
}
.imagepicker__preview img {
  width: 100%;
  height: 100%;
  /* The server crops to these same proportions; `cover` keeps the preview
     honest instead of squashing the picture to fit. */
  object-fit: cover;
  display: block;
}
.imagepicker__preview--avatar {
  width: 84px; height: 84px;
  border-radius: var(--r-lg);
}
.imagepicker__preview--banner {
  /* Matches the 1600×500 the API stores. */
  width: 100%;
  max-width: 20rem;
  aspect-ratio: 16 / 5;
  border-radius: var(--r-md);
}
.imagepicker__empty { opacity: 0.5; }

/* --- Public page banner -------------------------------------------------------- */
.banner {
  width: 100%;
  aspect-ratio: 16 / 5;
  max-height: 260px;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--primary-soft);
}
.banner img { width: 100%; height: 100%; object-fit: cover; display: block; }
