/* ── Responsive tokens ── */
:root {
  --profile-balance-pt: 56px;
  --profile-balance-gap: 30px;
  --carousel-height: 141px;
  --sheet-radius: 60px;
  --sheet-header-height: 85px;
  --balance-font-size: 40px;
  --balance-amount-height: 48px;
  --action-btn-height: var(--btn-height-primary);
  --sheet-content-pb: 120px;

  --page-header-title-size: 34px;
  --page-header-pb: 10px;

  --story-ring-size: 64px;
  --story-img-size: 59px;
  --stories-row-pt: 12px;
  --stories-row-pb: 4px;

  --category-bar-height: 67px;
  --category-tab-height: 67px;
  --category-icon-size: 30px;

  --events-padding-v: 20px;

  /* Height of the area the user can actually see. Telegram keeps
     --tg-viewport-stable-height on the root element (visible height with the
     keyboard closed); it is unset outside Telegram, and min() also guards the
     one case where it exceeds the window — an open keyboard. */
  --app-viewport-height: min(100dvh, var(--tg-viewport-stable-height, 100dvh));
  /* The tallest a bottom sheet may be: the visible height less the strip under
     the status bar and Telegram's own buttons (non-zero in fullscreen) and a
     gap that leaves the scrim visible above the sheet. */
  --sheet-ceiling: calc(var(--app-viewport-height) - var(--safe-top) - var(--space-24));
  /* Room the scrolling shell keeps under every page for the floating nav. */
  --shell-content-pb: 160px;
}

@media (max-height: 700px) {
  :root {
    --profile-balance-pt: 24px;
    --profile-balance-gap: 14px;
    --carousel-height: 96px;
    --sheet-radius: 40px;
    --sheet-header-height: 65px;
    --balance-font-size: 32px;
    --balance-amount-height: 38px;
    --action-btn-height: var(--btn-height-regular);
    --sheet-content-pb: 100px;

    --page-header-title-size: 28px;
    --page-header-pb: 4px;

    --story-ring-size: 48px;
    --story-img-size: 44px;
    --stories-row-pt: 6px;
    --stories-row-pb: 2px;

    --category-bar-height: 52px;
    --category-tab-height: 52px;
    --category-icon-size: 22px;

    --events-padding-v: 8px;
  }
}

/* ── Reset & Base ── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Inter', system-ui, sans-serif;
  background: #030303;
  color: #F8F6F4;
  height: 100dvh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Form elements don't inherit font-family by default — force them to use the
   project font instead of the browser's UA default (often Arial/Helvetica). */
button, input, select, textarea, optgroup {
  font-family: inherit;
}

/* ── App Shell ── */
.app-shell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}

.app-shell__content {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  padding-bottom: var(--shell-content-pb);
}

/* A page that owns its scrolling (the profile). It has a scroller of its own
   inside, while the balance header sits OUTSIDE that scroller and must stay put.
   As long as the shell can scroll too there are two scrollers on screen, and the
   outer one carries the balance past the top edge along with everything else —
   which is the complaint this rule exists for.

   The bottom inset goes as well: the page's inner scroller already provides it,
   and a second one on the shell would add an empty band under the list.

   The rule is declarative on purpose. The profile used to set the shell's
   `overflow-y` from code and restore it on unmount; navigating away and back let
   the old visit's cleanup override the new visit's set-up, and the lock silently
   failed. A class lives exactly as long as the page is mounted and has no such
   seam. Engines without `:has()` simply keep the previous behaviour. */
.app-shell__content:has(.owns-scroll) {
  overflow-y: hidden;
  padding-bottom: 0;
}

.app-content {
  min-height: 100%;
}

/* Non-scrolling page mode (issue #320). A screen whose whole content is an
   empty state must fit the viewport exactly: the page box is the visible height
   minus the nav reserve, so `.app-shell__content` has nothing to scroll and the
   screen cannot rubber-band. `:has()` keeps the mode declarative — any route
   that renders <EmptyState/> opts in by rendering it, nothing reaches into the
   shell — and engines without `:has()` simply keep the scrolling layout. */
.app-content:has(.empty-state) {
  display: flex;
  flex-direction: column;
  height: calc(var(--app-viewport-height) - var(--shell-content-pb));
  overflow: hidden;
}

/* The header keeps its size in that mode — on a short screen the empty state
   below it gives up space (its mascot shrinks), never the page chrome. */
.app-content:has(.empty-state) > .app-content__sticky-header {
  flex-shrink: 0;
}

/* No `touch-action` here on purpose. This wrapper only pins its children; it
   pans nothing itself, and its contents differ per route — on `my_bets` it is
   the title row alone. Constraining touch at this level swallowed every
   vertical swipe that started anywhere in the header, so the feed refused to
   scroll under a thumb that landed on the title. The one child that really
   pans sideways — the category strip — declares `touch-action` itself, in
   `market_top.module.css`. */
.app-content__sticky-header {
  position: sticky;
  /* `top: 0`, and not `--safe-top`, for two reasons that compound.

     The top inset is already inside the header's own padding —
     `page_header.module.css` opens with `max(20px, calc(var(--safe-top) + 8px))`
     — so Telegram's controls are cleared there, once. A non-zero `top` here
     applies the same inset a second time and pushes the title that much lower.

     And this wrapper sits at the very start of `.app-shell__content`, the scroll
     port: `.app-shell` is a flex column with no top padding and nothing above
     the port, and the wrappers in between (`.page-transition`, `.app-content`)
     add none either, so the port's top edge is the top of the viewport.
     A sticky box comes to rest `top` below that edge, so at
     `--safe-top` it is already displaced at `scrollTop: 0`, opening a
     transparent band of that height above it — the background paints the box
     only, so the feed scrolls through that band directly under Telegram's
     controls. At zero the opaque header covers the zone and the pin is seamless.

     In a browser the token resolves to 0, so neither the local stand nor the
     visual suite can show the difference; it is a device-only effect. */
  top: 0;
  z-index: 5;
  background: #030303;
}

/* ── TON Connect widget ── */
/* The wallet-selection modal must sit above the deposit sheet (z-index 9001)
   and its own confirmation popup (9002). */
#tc-widget-root {
  position: relative;
  z-index: 10010;
}

/* ── Loading ── */
.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100dvh;
  color: #A8A8A8;
  font-size: 1rem;
}

/* ── Page Transition ── */
@keyframes slide-in-from-right {
  from { transform: translateX(30px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

@keyframes slide-in-from-left {
  from { transform: translateX(-30px); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}

.page-transition {
  min-height: 100%;
}

.page--enter-right > * {
  animation: slide-in-from-right 220ms ease-out;
}

.page--enter-left > * {
  animation: slide-in-from-left 220ms ease-out;
}

/* ── Skeleton shimmer ── */
@keyframes shimmer {
  0%   { background-position: -400% 0; }
  100% { background-position:  400% 0; }
}

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

/* ── Keyframes (shared by toggle tabs & bottom nav modules) ── */
@keyframes toggle-stretch-a {
  0%   { transform: scaleX(1);    }
  30%  { transform: scaleX(1.12); }
  100% { transform: scaleX(1);    }
}

@keyframes toggle-stretch-b {
  0%   { transform: scaleX(1);    }
  30%  { transform: scaleX(1.12); }
  100% { transform: scaleX(1);    }
}

@keyframes nav-stretch-a {
  0%   { transform: scaleX(1);    }
  30%  { transform: scaleX(1.08); }
  100% { transform: scaleX(1);    }
}

@keyframes nav-stretch-b {
  0%   { transform: scaleX(1);    }
  30%  { transform: scaleX(1.08); }
  100% { transform: scaleX(1);    }
}

/* Copy-icon animation (papers → check → papers), triggered by .is-active */
.save-ico { display: block; }
.save-ico__papers,
.save-ico__check {
  transform-box: view-box;
  transform-origin: center;
  will-change: transform, opacity;
}
.save-ico__check { opacity: 0; transform: scale(0); }
.is-active .save-ico__papers {
  animation: saveIcoPapers 1.2s linear both;
}
.is-active .save-ico__check {
  animation: saveIcoCheck 1.2s linear both;
}
@keyframes saveIcoPapers {
  0%   { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
  20%  { opacity: 0; transform: scale(0); animation-timing-function: linear; }
  86%  { opacity: 0; transform: scale(0); animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes saveIcoCheck {
  0%   { opacity: 0; transform: scale(0); animation-timing-function: linear; }
  22%  { opacity: 0; transform: scale(0); animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }
  42%  { opacity: 1; transform: scale(1); animation-timing-function: linear; }
  78%  { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); }
  86%  { opacity: 0; transform: scale(0); animation-timing-function: linear; }
  100% { opacity: 0; transform: scale(0); }
}

/* Chip bump animation */
@keyframes chipBump {
  0%   { transform: scale(0.94); }
  55%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}

.app-content__sticky-categories {
  position: sticky;
  top: var(--safe-top);
  z-index: 5;
  background: #030303;
}

/* The stories pass underneath the title; categories stop below it.
   Explicit shared geometry keeps both sticky offsets in sync. */
.events-feed {
  --events-header-height: calc(max(20px, calc(var(--safe-top) + 8px)) + 42px + var(--page-header-pb));
}

.events-feed__header {
  z-index: 6;
}

.events-feed__header > div {
  box-sizing: border-box;
  height: var(--events-header-height);
}

.events-feed__categories {
  top: var(--events-header-height);
}
