/* Mini App Shop — theme vars default to Telegram's own light palette;
   app.js overwrites these from Telegram.WebApp.themeParams on load and on
   every themeChanged event, so this file never needs a dark-mode media
   query of its own. */
:root {
  --tg-bg: #ffffff;
  --tg-text: #000000;
  --tg-hint: #999999;
  --tg-link: #2481cc;
  --tg-button: #2481cc;
  --tg-button-text: #ffffff;
  --tg-secondary-bg: #f0f0f0;
  --tg-section-bg: #ffffff;
  --tg-section-header-text: #6d6d71;
  --tg-subtitle-text: #999999;
  --tg-destructive-text: #cc2929;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;  /* the page itself never scrolls — see .app-shell/.main below */
  /* No background-color here — #store-background is the single source of
     truth for the page backdrop (plain theme color OR a custom photo),
     see its own rule below for why. */
  color: var(--tg-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
}

/* Full-viewport flex column; #main is the ONLY scrolling region inside it.
   Exists because position:fixed is unreliable inside Telegram's in-app
   WebView (worse on Android): a fixed element gets anchored relative to a
   stale viewport computed before content finishes rendering and the page
   grows taller, so it drifts out of sync with real scroll position instead
   of tracking it (this is what made #store-background only show a sliver
   near the bottom on scroll — its "fixed" position never actually tracked
   the viewport). With the page unable to scroll at all, there's no scroll
   position for anything to drift out of sync with — sidesteps the bug
   class instead of patching around one instance of it. See index.html's
   own comment on the #app-shell element for the full picture. */
.app-shell {
  height: 100%;
  display: flex;
  flex-direction: column;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
  color: inherit;
}

a { color: var(--tg-link); }

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

/* ── Page backdrop + Mini App Settings Screen store background ────────── */
/* This element is the SINGLE source of truth for the page's backdrop —
   both the plain default theme color (background-color, always present)
   and, when the owner has set one (Elite/Business), a custom photo layered
   on top via background-image. Deliberately NOT split across this element
   plus a separate html/body background-color: a real bug during
   development had html/body's own opaque background painting ABOVE this
   element despite its negative z-index (confirmed empirically — html/body
   having their own explicit background color is what caused it, contrary
   to the naive stacking-order assumption that a negative z-index child
   always paints above its ancestor's background). Consolidating to one
   element removes that ambiguity entirely rather than fighting it.

   position: absolute, not fixed — html/body never scroll (see above), so
   this resolves to the exact same box either way; absolute sidesteps any
   residual doubt about position:fixed support in Telegram's in-app
   WebView, a separate, real issue this same investigation found (see
   .app-shell above) — this element used to be fixed and only showed a
   sliver of the photo near the bottom edge once scrolled, before either
   fix landed.

   mode is always "cover" today (services/miniapp for the future contain/
   blur/repeat modes this is scaffolded for, not yet implemented
   client-side). The scrim (::after) is scoped to .has-photo — it must NOT
   darken the plain default theme-color case, which is what most stores
   actually show (no custom background set). */
#store-background {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--tg-bg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
#store-background.has-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.15);
}

/* ── Header ─────────────────────────────────────────────────────────── */

.header {
  /* No position:sticky/fixed needed — it's a normal flex child of
     .app-shell, sitting outside #main (the only scrolling region), so it's
     already always visible without being layered over anything. */
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(10px + var(--safe-top)) 12px 10px;
  background: var(--tg-secondary-bg);
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.icon-btn {
  font-size: 22px;
  line-height: 1;
  padding: 4px 8px;
  color: var(--tg-link);
}

[dir="rtl"] .icon-btn { transform: scaleX(-1); }

.header-title {
  flex: 1;
  font-size: 17px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wallet-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px;
  border-radius: 14px;
  background: var(--tg-section-bg);
  font-size: 14px;
  font-weight: 600;
}

/* ── Banners ────────────────────────────────────────────────────────── */

.banner {
  padding: 8px 14px;
  font-size: 13px;
  text-align: center;
}

.banner-owner {
  background: #fff3cd;
  color: #7a5b00;
}

.banner-offline {
  background: #ffe1e1;
  color: var(--tg-destructive-text);
}

/* ── Main / views ───────────────────────────────────────────────────── */

.main {
  /* The scrolling region — flex:1 fills whatever space header/tabbar
     don't take, min-height:0 overrides flexbox's default min-height:auto
     (which would otherwise let #main's content push .app-shell taller
     than the viewport instead of scrolling internally, a common flexbox
     gotcha). No padding-bottom for the tab bar anymore — it's a normal
     flex sibling below #main now, not an overlay sitting on top of it. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.view { padding: 14px; }
.view[hidden] { display: none; }

.view-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid var(--tg-secondary-bg);
  border-top-color: var(--tg-button);
  animation: spin 0.8s linear infinite;
}

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

.view-closed {
  text-align: center;
  padding-top: 18vh;
}

.closed-icon { font-size: 40px; margin-bottom: 10px; }
.closed-message { font-size: 15px; color: var(--tg-hint); white-space: pre-line; }
.closed-channels { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; }
.channel-join-btn {
  display: block;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--tg-button);
  color: var(--tg-button-text);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
}

/* ── Category grid ──────────────────────────────────────────────────── */

/* Horizontal scroll strip, not a wrapping grid — with many categories a
   wrapping grid pushes the product grid further down the page for every
   extra row, which gets worse the more categories a store has. Scrolling
   sideways keeps the category row's height constant (one row, always)
   regardless of count, same pattern most shopping apps use for this.
   Scrollbar hidden (still fully scrollable via touch/trackpad/drag) since
   a visible horizontal scrollbar under a one-row chip strip reads as
   visual clutter on mobile, where the "there's more, swipe" affordance is
   already implied by the last card being partially cut off at the edge. */
.category-grid {
  display: flex;
  gap: 10px;
  /* The padding here is glow room, not decoration. .category-card.active
     casts a soft halo and lifts 2px, and this strip is a scroll container
     (overflow-x:auto makes the other axis compute to auto too), so without
     space inside the padding box that halo gets sliced off flat at the
     chip's edges. The horizontal half does the same for the first and last
     chip and is pulled straight back out with a matching negative margin,
     so the chips stay optically flush with the product grid below. */
  padding: 10px 6px 12px;
  margin: 0 -6px 6px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.category-grid::-webkit-scrollbar { display: none; }

.category-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 84px;
  gap: 6px;
  padding: 12px 6px;
  border-radius: 14px;
  background: var(--tg-section-bg);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  text-align: center;
  transition: background-color .18s ease, color .18s ease,
              box-shadow .18s ease, transform .18s ease;
}

.category-emoji { font-size: 26px; }
.category-name { font-size: 12px; font-weight: 600; }
.category-count { font-size: 11px; color: var(--tg-hint); }

/* The open category's chip is the ONLY thing on screen telling the customer
   which category they are in (the product grid below shows no heading), so
   it has to read at a glance over a busy store background photo. A 2px
   outline -- what this was -- does not: user report 2026-08-21, "when click
   on any category, not understanding which category is opened".

   Filled accent + a soft accent halo + a 2px lift. The halo is derived from
   --tg-button with color-mix rather than hardcoded, so it tracks whatever
   accent Telegram hands us per theme, light or dark. The ring is left on
   `outline` instead of folding it into the box-shadow list deliberately: an
   invalid value anywhere in a box-shadow list drops the WHOLE declaration,
   so on any engine without color-mix this degrades to filled accent + ring
   (i.e. better than the old state) rather than to no indicator at all. */
.category-card.active {
  background: var(--tg-button);
  outline: 2px solid var(--tg-button);
  box-shadow:
    0 0 16px 3px color-mix(in srgb, var(--tg-button) 55%, transparent),
    0 3px 10px rgba(0,0,0,0.20);
  transform: translateY(-2px);
}

.category-card.active .category-name,
.category-card.active .category-emoji {
  color: var(--tg-button-text);
}

.category-card.active .category-count {
  /* --tg-hint (the resting colour) is a mid grey chosen for the panel
     background, and disappears against the filled accent. */
  color: var(--tg-button-text);
  opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
  .category-card { transition: none; }
}

/* ── Product grid ───────────────────────────────────────────────────── */

/* Customer-chosen column count (2/3/4, see .grid-density below) — defaults
   to 3 (JS's PRODUCT_COLS_DEFAULT); this fallback only applies before JS
   sets the custom property on first paint. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(var(--product-cols, 3), 1fr);
  gap: 12px;
}

.grid-density {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  margin-bottom: 10px;
}

.grid-density-icon {
  fill: var(--tg-hint);
  flex: 0 0 auto;
}

.grid-density-select {
  height: 28px;
  border-radius: 8px;
  background: var(--tg-section-bg);
  color: var(--tg-hint);
  font-size: 12px;
  font-weight: 700;
  padding: 0 6px;
}

.product-card {
  border-radius: 14px;
  overflow: hidden;
  /* 70% transparent (30% of the theme's section-bg), same treatment and
     same reasoning as .product-price-block above — a store background
     photo shows through the title/price area instead of this panel
     blocking it. .product-thumb/.product-thumb-placeholder below keep
     their own fully opaque background regardless (they show the actual
     product image or its placeholder icon — real product photography
     shouldn't blend with the store backdrop), so only the card's
     title+price section (.product-card-body, which has no background of
     its own and shows whatever's behind it) actually goes translucent.
     backdrop-filter blurs whatever's behind the panel (store background
     photo) rather than the panel's own tint, so title/price text stays
     legible against busy photos without giving up the see-through look. */
  background: color-mix(in srgb, var(--tg-section-bg) 30%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  text-align: start;
}

.product-thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--tg-secondary-bg);
}

.product-thumb-placeholder {
  width: 100%;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 30px;
  background: var(--tg-secondary-bg);
  color: var(--tg-hint);
}

.product-card-body { padding: 8px 10px 10px; }
.product-card-title {
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  min-height: 2.6em;
}
.product-card-price { margin-top: 4px; font-size: 13px; font-weight: 700; color: var(--tg-button); }

.empty-state {
  text-align: center;
  color: var(--tg-hint);
  padding: 40px 10px;
  font-size: 14px;
}

/* ── Pagination ─────────────────────────────────────────────────────── */

/* The pager sits directly on the store background photo with no panel of its
   own, which made all three controls hard to read (user report 2026-08-21):
   the counter was 12px hint-grey straight over the image, and both arrows
   drop to 40% opacity the moment they are disabled -- which is most of the
   time, since "Prev" is disabled on page 1 and "Next" on the last page.
   Wrapped in the same translucent blurred pill the product cards use, so the
   photo still shows through but the text on top has a surface to sit on. */
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: fit-content;
  margin: 16px auto 0;
  padding: 6px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--tg-section-bg) 30%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.page-btn {
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--tg-section-bg);
  color: var(--tg-text);
  font-size: 13px;
  font-weight: 600;
}

/* Disabled must read as "not available now", not as "invisible". Dimming the
   whole button to 40% over a photo did the latter; hint-grey text on a
   half-strength panel keeps the label readable while clearly inactive. */
.page-btn:disabled {
  opacity: 1;
  color: var(--tg-hint);
  background: color-mix(in srgb, var(--tg-section-bg) 55%, transparent);
  cursor: default;
}

.page-info {
  font-size: 13px;
  font-weight: 600;
  color: var(--tg-text);
  white-space: nowrap;
}

/* renderHome() sets pager.hidden = true on a single-page store, but the UA's
   [hidden]{display:none} is a USER-AGENT rule and .pagination's display:flex
   is an author rule -- author always wins, regardless of specificity -- so the
   pager stayed on screen showing a pointless "Page 1/1" with both arrows
   disabled. That is what the user was actually looking at when they reported
   the pager as unreadable. Restore the intent explicitly. */
.pagination[hidden] { display: none; }

/* ── Product detail ─────────────────────────────────────────────────── */

.view-product { padding: 0; }

.product-preview {
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--tg-secondary-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 46px;
  color: var(--tg-hint);
  overflow: hidden;
}

.product-preview img { width: 100%; height: 100%; object-fit: cover; }

.product-body { padding: 16px; }

.product-title { font-size: 19px; font-weight: 700; margin: 0 0 4px; }
.product-type-line { font-size: 13px; color: var(--tg-hint); margin-bottom: 10px; }

.product-description {
  font-size: 14px;
  line-height: 1.5;
  white-space: pre-wrap;
  margin-bottom: 14px;
}

.product-description blockquote {
  margin: 8px 0;
  padding: 6px 10px;
  border-inline-start: 3px solid var(--tg-button);
  background: var(--tg-secondary-bg);
  border-radius: 4px;
}

.product-description code {
  background: var(--tg-secondary-bg);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.92em;
}

.product-description pre {
  background: var(--tg-secondary-bg);
  padding: 10px;
  border-radius: 8px;
  overflow-x: auto;
}
.product-description pre code { background: none; padding: 0; }

.tg-spoiler {
  background: var(--tg-hint);
  color: transparent;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.tg-spoiler.revealed {
  background: transparent;
  color: inherit;
}

.custom-emoji {
  width: 1.2em;
  height: 1.2em;
  vertical-align: -0.2em;
  object-fit: contain;
}

.custom-emoji-lottie {
  display: inline-block;
  overflow: hidden;
}

.product-price-block {
  border-radius: 12px;
  /* 70% transparent (30% of the theme's own secondary-bg color) so a
     store background photo shows through here too, instead of this panel
     fully blocking it — color-mix keeps the tint theme/light-dark aware
     (matches whatever --tg-secondary-bg currently is) rather than a fixed
     rgba() that would look wrong against Telegram's dark theme. Text
     inside stays fully opaque — this dims the backing fill only, not
     content, unlike an `opacity` on the whole block. backdrop-filter blurs
     the store background photo behind this block (not the block's own
     text), keeping the stars/price value legible without a solid fill. */
  background: color-mix(in srgb, var(--tg-secondary-bg) 30%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  padding: 12px 14px;
  font-size: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.price-row { display: flex; justify-content: space-between; }
.price-row.total { font-weight: 700; font-size: 16px; }
.price-row .muted { color: var(--tg-hint); }

.product-footer {
  position: sticky;
  bottom: 0;
  padding: 12px 16px calc(12px + var(--safe-bottom));
  background: var(--tg-bg);
  border-top: 1px solid rgba(0,0,0,0.06);
}

.buy-btn {
  width: 100%;
  padding: 13px;
  border-radius: 12px;
  background: var(--tg-button);
  color: var(--tg-button-text);
  font-size: 15px;
  font-weight: 700;
}

.buy-btn:disabled { opacity: 0.5; }

/* ── Purchases ──────────────────────────────────────────────────────── */

.orders-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.order-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border-radius: 12px;
  background: var(--tg-section-bg);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}

.order-icon { font-size: 22px; }
.order-info { flex: 1; min-width: 0; }
.order-title {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.order-date { font-size: 11px; color: var(--tg-hint); }

.order-redownload-btn {
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--tg-button);
  color: var(--tg-button-text);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.order-redownload-btn:disabled { opacity: 0.5; }

.empty-state.purchases-empty { padding-top: 18vh; }

/* ── Tab bar ────────────────────────────────────────────────────────── */

.tabbar {
  /* No position:fixed needed — normal last flex child of .app-shell, so
     it's naturally pinned to the bottom without overlaying #main. */
  flex: 0 0 auto;
  display: flex;
  background: var(--tg-secondary-bg);
  border-top: 1px solid rgba(0,0,0,0.06);
  padding-bottom: var(--safe-bottom);
}

.tab-btn {
  flex: 1;
  padding: 10px 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--tg-hint);
}

.tab-btn.tab-active { color: var(--tg-button); }

/* ── Toast ──────────────────────────────────────────────────────────── */

.toast {
  /* absolute, not fixed, for the same reason as #store-background above —
     html/body never scroll, so both resolve to the same box; absolute
     removes any residual doubt about fixed's support in this WebView. */
  position: absolute;
  left: 50%;
  bottom: calc(84px + var(--safe-bottom));
  transform: translateX(-50%);
  background: rgba(0,0,0,0.82);
  color: #fff;
  padding: 10px 16px;
  border-radius: 10px;
  font-size: 13px;
  max-width: 88vw;
  text-align: center;
  z-index: 40;
}

[dir="rtl"] .toast { transform: translateX(50%); }

/* ── Settings (owner only) ─────────────────────────────────────────── */

.settings-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.settings-section {
  border-radius: 14px;
  background: var(--tg-section-bg);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  padding: 14px;
}

.settings-section-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--tg-section-header-text);
  margin-bottom: 10px;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.settings-row-hint {
  font-size: 13px;
  color: var(--tg-hint);
}

.settings-lock-note {
  margin-top: 10px;
  font-size: 12px;
  color: var(--tg-hint);
}

.settings-btn {
  padding: 9px 16px;
  border-radius: 10px;
  background: var(--tg-button);
  color: var(--tg-button-text);
  font-size: 13px;
  font-weight: 600;
}

.settings-btn:disabled { opacity: 0.5; }

.settings-btn-destructive {
  background: var(--tg-secondary-bg);
  color: var(--tg-destructive-text);
}

.settings-bg-actions {
  margin-top: 10px;
  display: flex;
  gap: 10px;
}

.settings-reset-btn { width: 100%; text-align: center; }

/* Toggle switch — plain checkbox visually restyled, no extra markup
   beyond the <span> slider (same "minimal DOM, CSS does the work"
   approach as .category-card/.product-card elsewhere in this file). */
.switch {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 26px;
  flex: none;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.switch-slider {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--tg-secondary-bg);
  transition: background 0.15s;
}

.switch-slider::before {
  content: "";
  position: absolute;
  top: 3px;
  inset-inline-start: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0,0,0,0.25);
  transition: transform 0.15s;
}

.switch input:checked + .switch-slider { background: var(--tg-button); }
.switch input:checked + .switch-slider::before { transform: translateX(20px); }
[dir="rtl"] .switch input:checked + .switch-slider::before { transform: translateX(-20px); }
.switch input:disabled + .switch-slider { opacity: 0.5; cursor: default; }
.switch input:disabled { cursor: default; }

.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: 10px;
}

.theme-swatch-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 4px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  color: var(--tg-text);
}

.theme-swatch-btn:disabled { opacity: 0.5; }

.theme-swatch-btn.selected {
  outline: 2px solid var(--tg-button);
  background: var(--tg-secondary-bg);
}

.theme-swatch {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
}

.theme-swatch-default {
  background: linear-gradient(135deg, var(--tg-button) 50%, var(--tg-secondary-bg) 50%);
}

.settings-bg-preview {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 10px;
  background: var(--tg-secondary-bg);
}
