/* ==========================================================================
   TurbonX BD — Components: buttons, fields, badges, price, cards, skeletons,
   pagination, swatches, toast, states
   Single-class selectors; state carried on .is-* classes.
   ========================================================================== */

/* --- Buttons ------------------------------------------------------------ */
.btn {
  --_bg: transparent;
  --_fg: var(--text-primary);
  --_bd: var(--border-strong);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 0.85em 1.5em;
  min-height: 46px;
  font-family: var(--font-text);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--_fg);
  background: var(--_bg);
  border: 1px solid var(--_bd);
  border-radius: var(--r-sm);
  cursor: pointer;
  position: relative;
  isolation: isolate;
  transition: transform var(--dur-1) var(--ease-out),
              background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
@media (hover: hover) { .btn:hover { transform: translateY(-1px); } }
.btn:active { transform: translateY(0) scale(0.985); }
.btn:disabled,
.btn[aria-disabled="true"] {
  --_bg: var(--surface-sunken);
  --_fg: var(--text-tertiary);
  --_bd: var(--border);
  pointer-events: none;
  box-shadow: none;
  transform: none;
}
/* Looks exactly like :disabled above but WITHOUT pointer-events:none — the
   product page's Add to cart/Buy now stay genuinely clickable while an
   option is still unpicked, on purpose, so the click can nudge the
   missing swatch row instead of silently doing nothing (see
   nudgeMissingVariants() in page.product.js). */
.btn.is-soft-disabled {
  --_bg: var(--surface-sunken);
  --_fg: var(--text-tertiary);
  --_bd: var(--border);
  box-shadow: none;
  transform: none;
}

.btn--primary {
  --_bg: var(--button-bg);
  --_fg: var(--button-text);
  --_bd: var(--button-bg);
}
.btn--ghost { --_bg: transparent; --_bd: var(--border-strong); }
.btn--quiet { --_bd: transparent; padding-inline: 0.75em; }

/* Hover-only visuals: on touch, :hover sticks after a tap until you tap
   elsewhere, leaving a stuck highlight / inverted button behind. */
@media (hover: hover) {
  .btn--primary:hover { box-shadow: var(--shadow-2); }
  .btn--ghost:hover { --_bg: var(--text-primary); --_fg: var(--bg-primary); --_bd: var(--text-primary); }
  .btn--quiet:hover { --_bd: var(--border); }
}

.btn--sm { min-height: 38px; padding: 0.6em 1em; font-size: var(--step--2); }
.btn--block { display: flex; width: 100%; }

.btn__label { transition: opacity var(--dur-1) linear; }
.btn.is-loading { pointer-events: none; }
.btn.is-loading .btn__label { opacity: 0; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  width: 16px; height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: tx-spin 0.7s linear infinite;
}
.btn.is-success { --_bg: var(--button-bg); --_fg: var(--button-text); --_bd: var(--button-bg); }

@keyframes tx-spin { to { transform: rotate(1turn); } }

/* --- Icon button (header) ------------------------------------------- */
.icon-btn {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 42px; height: 42px;
  border-radius: var(--r-sm);
  color: var(--text-primary);
  transition: background var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
@media (hover: hover) { .icon-btn:hover { background: var(--surface-sunken); } }
.icon-btn:active { transform: scale(0.92); }
.icon-btn__badge {
  position: absolute;
  top: 3px; right: 3px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--bg-primary);
  background: var(--text-primary);
  border: 2px solid var(--bg-primary);
  border-radius: var(--r-pill);
  transform: scale(0);
  transition: transform var(--dur-2) var(--ease-out);
}
.icon-btn__badge.is-shown { transform: scale(1); }
.icon-btn__badge.is-bump { animation: tx-bump var(--dur-3) var(--ease-out); }
@keyframes tx-bump { 40% { transform: scale(1.35); } }

/* While a count is showing, the badge gives a small periodic wiggle so a
   stocked cart / wishlist stays noticeable. The one-time bump on change
   (:not(.is-bump)) still wins the moment something is added or removed. */
@media (prefers-reduced-motion: no-preference) {
  .icon-btn__badge.is-shown:not(.is-bump) {
    animation: tx-badge-hint 5.5s var(--ease-out) 2.5s infinite;
  }
  #cart-badge.is-shown:not(.is-bump) { animation-delay: 4s; }
}
@keyframes tx-badge-hint {
  0%, 78%, 100% { transform: scale(1) translateX(0) rotate(0); }
  82% { transform: scale(1.16) translateX(-1.4px) rotate(-7deg); }
  87% { transform: scale(1.12) translateX(1.4px) rotate(6deg); }
  92% { transform: scale(1.06) translateX(-0.8px) rotate(-2deg); }
  96% { transform: scale(1) translateX(0) rotate(0); }
}

/* --- Fields ---------------------------------------------------------- */
.field { display: grid; gap: var(--sp-2); }
.field__label { font-size: var(--step--1); font-weight: 500; color: var(--text-secondary); }
.field__input,
.select__native {
  width: 100%;
  min-height: 48px;
  padding: 0.7em 0.9em;
  font-size: var(--step-0);
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
  appearance: none;
}
.field__input::placeholder { color: var(--text-tertiary); }
.field__input:focus,
.select__native:focus {
  outline: none;
  border-color: var(--text-primary);
  box-shadow: 0 0 0 3px var(--border-faint);
}
.field__input:user-invalid,
.field.is-invalid .field__input {
  border-color: var(--danger);
}
.field__error {
  font-size: var(--step--2);
  color: var(--danger);
  min-height: 1em;
}
.field__hint { font-size: var(--step--2); color: var(--text-tertiary); }

.select { position: relative; }
.select::after {
  content: "";
  position: absolute;
  right: 14px; top: 50%;
  width: 8px; height: 8px;
  border-right: 1.5px solid var(--text-secondary);
  border-bottom: 1.5px solid var(--text-secondary);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}
.select__native { padding-right: 2.5em; }

/* --- Badges -------------------------------------------------------- */
/* Fixed (theme-independent) ink, not --text-primary/--bg-primary: these sit
   on top of .product-card__media, whose --media-bg well is intentionally the
   same light grey in both themes. If badges tracked the page theme instead,
   dark mode would flip their text to near-white on that light-grey well and
   they'd all but disappear — so they anchor to the media well's own contrast.
   A frosted-glass treatment (blur + translucency, same language as the
   wishlist/quick-view pills on the media) keeps that fixed contrast while
   still letting the product photo show through instead of sitting as a flat,
   opaque sticker. */
.badge {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 8px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: var(--r-xs);
  background: rgba(5, 5, 5, 0.62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #ffffff;
  white-space: nowrap;
}
.badge--outline {
  background: rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #050505;
  border: 1px solid rgba(5, 5, 5, 0.16);
}
.badge--muted {
  background: rgba(241, 241, 241, 0.68);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #050505;
}

/* --- Price -------------------------------------------------------- */
.price { display: inline-flex; align-items: baseline; gap: 0.5em; flex-wrap: wrap; }
.price__now {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-1);
  font-stretch: 112%;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
}
.price__was {
  font-size: var(--step--1);
  color: var(--text-tertiary);
  text-decoration: line-through;
  font-variant-numeric: tabular-nums;
}
.price__off {
  font-size: var(--step--2);
  font-weight: 600;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  padding: 1px 6px;
  border-radius: var(--r-xs);
}

/* --- Rating ----------------------------------------------------- */
.rating { display: inline-flex; align-items: center; gap: 5px; color: var(--text-secondary); font-size: var(--step--1); }
.rating__stars { display: inline-flex; color: var(--text-primary); }
.rating__stars svg { width: 14px; height: 14px; }

/* --- Swatch / variant selector -------------------------------- */
.variant-group { display: grid; gap: var(--sp-2); }
/* Add to cart/Buy now clicked before this group has a value — a quick
   double-blink instead of the click just silently doing nothing, so it
   reads as "pick one of these" rather than "the button is broken". */
@keyframes variantNudge { 0%, 100% { opacity: 1; } 25%, 75% { opacity: 0.35; } 50% { opacity: 1; } }
.variant-group.is-nudging { animation: variantNudge 0.5s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) { .variant-group.is-nudging { animation: none; } }
.variant-group__label { font-size: var(--step--1); color: var(--text-secondary); }
.variant-group__label b { color: var(--text-primary); font-weight: 600; }
.swatches { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.swatch {
  --_bd: var(--border-strong);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 0.9em;
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--_bd);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
@media (hover: hover) { .swatch:hover { --_bd: var(--text-primary); } }
.swatch:active { transform: scale(0.97); }
.swatch.is-active {
  background: var(--text-primary);
  color: var(--bg-primary);
  border-color: var(--text-primary);
}
.swatch__check { display: none; }
.swatch.is-active .swatch__check { display: inline-flex; }
.swatch.is-active .swatch__check svg { width: 14px; height: 14px; }
/* Out of stock for this exact combination — still visible/selectable-looking
   at a glance (not just gone), but struck through and inert. */
.swatch.is-out { position: relative; color: var(--text-tertiary); cursor: not-allowed; opacity: 0.6; }
.swatch.is-out::after { content: ""; position: absolute; left: 10%; right: 10%; top: 50%; height: 1px; background: currentColor; }
@media (hover: hover) { .swatch.is-out:hover { --_bd: var(--border-strong); } }
/* A swatch with its own cheaper price (set in the admin's Variants step)
   says so right on the swatch — monochrome pill, no chromatic "sale"
   colour, same "inversion, not colour" language the rest of this design
   uses for state. */
.swatch__off { font-size: 0.68em; font-weight: 700; color: var(--text-tertiary); padding: 1px 5px; border-radius: var(--r-pill); border: 1px solid currentColor; line-height: 1.4; }
.swatch.is-active .swatch__off { color: var(--bg-primary); }
/* Compact variant, used for the per-colour size row in multi-select mode. */
.swatch--sm { min-height: 32px; padding: 0 0.7em; font-size: var(--step--2); }

/* --- Quantity stepper --------------------------------------- */
.qty {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.qty__btn {
  width: 38px; height: 38px;
  display: grid;
  place-items: center;
  color: var(--text-primary);
  transition: background var(--dur-1) var(--ease-out);
}
@media (hover: hover) { .qty__btn:hover { background: var(--surface-sunken); } }
.qty__btn:disabled { opacity: 0.3; pointer-events: none; }
.qty__btn svg { width: 15px; height: 15px; }
.qty__val {
  min-width: 34px;
  text-align: center;
  font-size: var(--step--1);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Compact variant — the order summary's per-line stepper, not full size. */
.qty--sm .qty__btn { width: 28px; height: 28px; }
.qty--sm .qty__btn svg { width: 11px; height: 11px; }
.qty--sm .qty__val { min-width: 22px; font-size: var(--step--2); }

/* --- Product card ----------------------------------------------- */
.product-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border-faint);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
@media (hover: hover) {
  .product-card:hover {
    transform: translateY(-4px);
    border-color: var(--border);
    box-shadow: var(--shadow-2);
  }
}
.product-card__media {
  position: relative;
  aspect-ratio: 1 / 1;
  /* --bg-primary, not --media-bg: a background-removed (transparent PNG)
     product photo should blend straight into the page — white in light
     mode, pure black in dark mode — not sit in a fixed light-grey well
     that stays grey even when the whole site has gone dark. Only visible
     where a photo actually has transparency; a normal opaque photo covers
     the well completely (object-fit: cover) either way. */
  background: var(--bg-primary);
  overflow: hidden;
}
.product-card__img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-4) var(--ease-out);
  /* Same reason as .pdp__slide img in pages.css: base.css's generic
     img{background:var(--media-bg)} would otherwise sit on top of
     .product-card__media's theme-matched background and be the thing a
     transparent PNG actually shows through. */
  background: transparent;
}
.product-card__img--alt { opacity: 0; }
@media (hover: hover) {
  .product-card:hover .product-card__img--main { opacity: 0; }
  .product-card:hover .product-card__img--alt { opacity: 1; transform: scale(1.04); }
}
.product-card__badges {
  position: absolute;
  top: 10px; left: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  z-index: 2;
}
.product-card__wish {
  position: absolute;
  top: 8px; right: 8px;
  width: 36px; height: 36px;
  display: grid;
  place-items: center;
  color: var(--text-primary);
  background: var(--scrim);
  backdrop-filter: blur(6px);
  border-radius: var(--r-pill);
  z-index: 2;
  transition: transform var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
@media (hover: hover) { .product-card__wish:hover { transform: scale(1.08); } }
.product-card__wish:active { transform: scale(0.9); }
.product-card__wish.is-active { color: var(--danger); }
.product-card__wish.is-active svg { animation: tx-pop var(--dur-3) var(--ease-out); }
@keyframes tx-pop { 30% { transform: scale(1.3); } 60% { transform: scale(0.92); } }

.product-card__quick {
  position: absolute;
  left: 10px; right: 10px; bottom: 10px;
  display: flex;
  justify-content: center;
  gap: 6px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  z-index: 2;
}
@media (hover: hover) {
  .product-card:hover .product-card__quick { opacity: 1; transform: none; }
}
.product-card__quick .btn {
  background: var(--surface);
  backdrop-filter: blur(6px);
  border-color: var(--border-strong);
}

.product-card__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--sp-3);
  flex: 1;
}
.product-card__name {
  font-size: var(--step--1);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--tracking-tight);
}
.product-card__name a {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Reserve the full 2-line box even for a short, one-line name. Grid row
     stretch only equalises cards that share a row; a one-line name a few
     rows down (or on a different page/grid — search, related products)
     would otherwise sit shorter than its neighbours. This keeps every card
     on the site the same height regardless of name length or where it renders. */
  min-height: 2.6em;
}
.product-card__price { margin-top: auto; }
.product-card__price .price__now { font-size: var(--step-0); }
.product-card__stock { font-size: var(--step--2); color: var(--text-secondary); }
.product-card__stock.is-low { color: var(--danger); }
.product-card__cta { position: relative; z-index: 3; }
.product-card__cta .btn { min-height: 34px; padding: 0.55em 0.9em; }

.product-card.is-out .product-card__media { opacity: 0.55; }
.product-card.is-out .product-card__img { filter: grayscale(1); }

/* Touch devices: no quick view. Tapping anywhere on the card opens the
   product page (the stretched .product-card__name a::after covers the media),
   which is what people expect on a phone. */
@media (hover: none) {
  .product-card__quick { display: none; }
}

/* --- Category card (shop-by-category grid) --------------------- */
.category-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 260px;
  padding: var(--sp-5);
  background: var(--surface-raised);
  border: 1px solid var(--border-faint);
  border-radius: var(--r-md);
  overflow: hidden;
  color: var(--text-primary);
  transition: border-color var(--dur-2) var(--ease-out);
}
@media (hover: hover) { .category-card:hover { border-color: var(--border); } }
/* the generated art is texture here, not the subject — keep it quiet */
.category-card__media {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.7;
  transition: transform var(--dur-4) var(--ease-out);
}
@media (hover: hover) {
  .category-card:hover .category-card__media { transform: scale(1.05); }
}
.category-card__body {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 4px;
}
.category-card__title { font-size: var(--step-1); font-weight: 700; letter-spacing: var(--tracking-tight); }
.category-card__blurb { font-size: var(--step--1); color: var(--text-secondary); }
.category-card__count { font-size: var(--step--2); color: var(--text-tertiary); }
.category-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface-raised) 35%, transparent) 25%,
    var(--surface-raised) 92%);
}

/* --- Sub-category card --------------------------------------- */
.subcat-card {
  position: relative;
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border-faint);
  border-radius: var(--r-md);
  text-align: left;
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out), box-shadow var(--dur-3) var(--ease-out);
}
@media (hover: hover) {
  .subcat-card:hover { transform: translateY(-3px); border-color: var(--border); box-shadow: var(--shadow-1); }
}
.subcat-card:active { transform: translateY(0) scale(0.99); }
.subcat-card.is-active { border-color: var(--text-primary); }
.subcat-card.is-active::before {
  content: "";
  position: absolute;
  top: 10px; right: 10px;
  width: 7px; height: 7px;
  background: var(--text-primary);
  border-radius: 50%;
}
.subcat-card__media {
  aspect-ratio: 1;
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  object-fit: cover;
  width: 100%;
}
.subcat-card__name { font-size: var(--step--1); font-weight: 600; }
.subcat-card__count { font-size: var(--step--2); color: var(--text-tertiary); }

/* --- Category chip rail ------------------------------------ */
.cat-chip {
  --_bd: var(--border-strong);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0.7em 1.15em;
  font-size: var(--step--1);
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--_bd);
  border-radius: var(--r-pill);
  scroll-snap-align: start;
  transition: background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}
@media (hover: hover) { .cat-chip:hover { --_bd: var(--text-primary); } }
.cat-chip.is-active {
  background: var(--text-primary);
  color: var(--bg-primary);
  border-color: var(--text-primary);
}
.cat-chip__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  border: 1px solid currentColor;
  opacity: 0.5;
}
.cat-chip.is-active .cat-chip__dot { background: currentColor; opacity: 1; }

/* --- Skeleton loaders ------------------------------------- */
.skeleton { position: relative; overflow: hidden; background: var(--surface-sunken); border-radius: var(--r-sm); }
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--border-faint), transparent);
  animation: tx-shimmer 1.4s ease-in-out infinite;
}
@keyframes tx-shimmer { 100% { transform: translateX(100%); } }
.skeleton-card { border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
.skeleton-card__media { aspect-ratio: 1 / 1; }
.skeleton-card__body { display: grid; gap: 10px; padding: var(--sp-4); }
.skeleton-line { height: 12px; border-radius: var(--r-xs); }
.skeleton-line--sm { width: 40%; height: 9px; }
.skeleton-line--lg { width: 75%; }
.skeleton-line--price { width: 30%; height: 16px; margin-top: 4px; }

/* --- Pagination ------------------------------------------ */
.pagination { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; margin-top: var(--sp-7); }
.pagination__btn {
  min-width: 42px; height: 42px;
  padding: 0 0.6em;
  display: inline-grid;
  place-items: center;
  font-size: var(--step--1);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  transition: background var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
@media (hover: hover) {
  .pagination__btn:hover:not(:disabled):not(.is-active) { border-color: var(--text-primary); }
}
.pagination__btn.is-active { background: var(--text-primary); color: var(--bg-primary); border-color: var(--text-primary); }
.pagination__btn:disabled { opacity: 0.35; pointer-events: none; }
.pagination__btn svg { width: 16px; height: 16px; }
.pagination__gap { padding: 0 4px; color: var(--text-tertiary); }

/* --- Fly-to-cart / fly-to-wishlist ghost (see TX.flyTo in ui.js) ----- */
.fly-ghost {
  position: fixed;
  z-index: var(--z-toast);
  border-radius: var(--r-md);
  overflow: hidden;
  pointer-events: none;
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}
.fly-ghost img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fly-ghost--round { border-radius: 50%; }
.fly-ghost--icon {
  display: grid;
  place-items: center;
  background: var(--text-primary);
  color: var(--bg-primary);
  border-radius: 50%;
}
.fly-ghost--icon svg { width: 55%; height: 55%; }

/* --- Toast --------------------------------------------- */
.toasts {
  position: fixed;
  left: 50%;
  bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column-reverse;
  gap: var(--sp-2);
  width: min(92vw, 380px);
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.9em 1.1em;
  background: var(--text-primary);
  color: var(--bg-primary);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-pop);
  font-size: var(--step--1);
  font-weight: 500;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  pointer-events: auto;
}
.toast.is-in { opacity: 1; transform: none; }
.toast__icon { display: inline-flex; flex-shrink: 0; }
.toast__icon svg { width: 18px; height: 18px; }
.toast--error { background: var(--danger); color: #fff; }

/* --- Empty / error / offline states ------------------- */
.state {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--sp-3);
  padding: clamp(2.5rem, 1.5rem + 5vw, 5rem) var(--sp-4);
  max-width: 42ch;
  margin-inline: auto;
}
.state__icon {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--text-secondary);
}
.state__icon svg { width: 24px; height: 24px; }
.state__title { font-size: var(--step-1); font-weight: 700; overflow-wrap: anywhere; max-width: 100%; }
.state__body { color: var(--text-secondary); font-size: var(--step-0); overflow-wrap: anywhere; max-width: 100%; }
.state__action { margin-top: var(--sp-2); }
.state__actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; justify-content: center; margin-top: var(--sp-2); }
.state__actions .state__action { margin-top: 0; }
