:root {
  /* The space at the edges that is not ours to draw in.
   *
   * env() alone covers the device — a notch, a home indicator. It does NOT
   * cover Telegram own chrome, which in fullscreen floats its close button
   * over the top of the page. So the app drew underneath it, and anything
   * positioned from the top edge sat behind buttons the fan cannot move.
   *
   * app.js widens these from Telegram contentSafeAreaInset while fullscreen
   * is on, and clears them when it is off. Everything below measures from
   * here rather than from env() directly, so there is one place to fix. */
  --safe-top: env(safe-area-inset-top);
  --safe-bottom: env(safe-area-inset-bottom);
  color-scheme: dark;
  --bg: #000000;
  --panel: #0b0b0b;
  --panel-solid: #0b0b0b;
  --panel-raised: #151515;
  --text: #ffffff;
  --muted: #8e8e93;
  --line: rgba(255, 255, 255, 0.1);
  --hot: #ff1b8d;
  --hot-deep: #c10068;
  --hot-soft: rgba(255, 27, 141, 0.16);
  --shadow: 0 28px 80px rgba(0, 0, 0, 0.72);
  --r: 10px;
  --r-lg: 16px;
  /* One vocabulary for movement. A duration typed straight into a rule is a
     decision nobody can find again. See DESIGN.md §1. */
  --t-fast: 140ms;
  --t: 220ms;
  --t-slow: 340ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --display: Rubik, Heebo, ui-sans-serif, system-ui, sans-serif;
  font-family: Heebo, Rubik, ui-sans-serif, system-ui, -apple-system, sans-serif;
}

h1,
h2,
.brand,
.creator-name,
.creator-tile-name,
.creators-header h1,
.signal-card strong,
.profile-stats strong,
.search-result strong,
.primary-cta,
.primary-action,
.feed-tab,
.creator-filter,
.bottom-nav-button,
.open-profile,
.secondary-cta {
  font-family: var(--display);
}

.caption,
.profile-bio,
.creator-name,
.creator-handle,
.creator-tile-name,
.toast,
.profile-head h2,
.profile-head p {
  unicode-bidi: plaintext;
  text-align: start;
}

* {
  box-sizing: border-box;
}

/* A phone reads two quick taps as "zoom in", and closing a profile then
   tapping again fell inside that window — the whole app zoomed, with no
   obvious way back. Declaring that these elements only ever pan or tap tells
   the browser not to wait for a second tap, which removes the zoom and the
   300ms delay before a tap registers along with it. */
button,
a,
input,
select,
label,
.creator-tile,
.profile-tile,
.sheet-backdrop {
  touch-action: manipulation;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  /* The document itself must never scroll. Without this the webview satisfies
     "scroll the focused input into view" by scrolling the page behind the app,
     and since nothing here shows a scrollbar there is no way for the user to
     scroll it back. */
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  /* Not 100svh: the body must never be taller than what is on screen, or the
     webview has somewhere to scroll it to. */
  min-height: 100%;
  background: #000;
  color: var(--text);
  overflow: hidden;
}

button,
input {
  font: inherit;
}

button {
  color: inherit;
}

.shell {
  position: relative;
  width: min(100vw, 480px);
  /* --app-height is set from Telegram's reported viewport, which shrinks when
     the keyboard opens. 100svh does not: it is the height without browser
     chrome, and a keyboard is not chrome. Relying on it left the shell taller
     than the visible area, so focusing the search field made the webview scroll
     the whole app up with no way to scroll it back. */
  height: var(--app-height, 100svh);
  margin: 0 auto;
  overflow: hidden;
  background: #000;
  border-inline: 1px solid rgba(255, 255, 255, 0.08);
  isolation: isolate;
}

.topbar {
  position: absolute;
  inset: var(--safe-top) 0 auto;
  z-index: 30;
  display: grid;
  /* One column per control, and each control placed in its own.
   *
   * It was three columns for four children, which was invisible only because
   * the fullscreen button was hidden on every surface anybody had looked at.
   * The moment it appeared it took the third column, and search — the fourth
   * child, with nowhere to go — wrapped onto an implicit second row and landed
   * on top of the menu button.
   *
   * Placed explicitly rather than by source order, so adding or hiding a
   * control cannot shuffle the others along. The third track is auto: with
   * the fullscreen button display:none it collapses to zero and the bar is
   * exactly what it was before. */
  grid-template-columns: 48px 1fr auto 48px;
}

.topbar > #menuButton {
  grid-column: 1;
}

.topbar > .brand {
  grid-column: 2;
}

.topbar > .fullscreen-toggle {
  grid-column: 3;
}

.topbar > #searchButton {
  grid-column: 4;
  align-items: center;
  height: 62px;
  padding: 10px 14px 8px;
  background: linear-gradient(180deg, #000 40%, rgba(0, 0, 0, 0.7) 74%, transparent);
}

.brand {
  display: inline-flex;
  justify-content: center;
  align-items: baseline;
  gap: 0.07em;
  color: #fff;
  text-decoration: none;
  font-size: 23px;
  font-weight: 900;
  letter-spacing: -0.025em;
  line-height: 1;
}

.brand-vod {
  color: #fff;
}

.brand-heb {
  color: var(--hot);
}

.icon-button,
.action-button,
.primary-cta,
.secondary-cta,
.feed-tab,
.bottom-nav-button,
.creator-filter,
.creator-tile,
.profile-actions button {
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.icon-button {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.07);
}

.icon-button.ghost {
  background: transparent;
}

.icon-button span {
  display: block;
  width: 19px;
  height: 2px;
  border-radius: 2px;
  background: #fff;
}

.icon-button span + span {
  margin-top: 5px;
  width: 12px;
}

svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tabbar {
  position: absolute;
  top: calc(var(--safe-top) + 60px);
  left: 0;
  right: 0;
  z-index: 25;
  display: flex;
  gap: 8px;
  padding: 0 14px;
  overflow-x: auto;
  scrollbar-width: none;
  direction: rtl;
}

.tabbar::-webkit-scrollbar,
.reel-feed::-webkit-scrollbar {
  display: none;
}

/* The tabs keep their own row inside the rail so the toggle can be pushed past
   them without becoming one of them. */
.tabbar-tabs {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
}

.feed-tab {
  flex: 0 0 auto;
  min-width: 74px;
  height: 34px;
  padding: 0 14px;
  border-radius: var(--r);
  background: transparent;
  color: rgba(255, 255, 255, 0.62);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}

/* Pushed to the far end of the rail, away from the sorts — it belongs to the
   same row but is not one of them.

   Sticky, not merely placed there: the rail scrolls sideways once the sorts
   stop fitting, which on a 360px phone left this button parked off the edge of
   the screen with nothing to suggest it existed. Pinning it to the inline end
   keeps it reachable at any width. The auto margin still does the work
   whenever there is room to spare. */
.layout-toggle {
  position: sticky;
  inset-inline-end: 0;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  margin-inline-start: auto;
  border-radius: var(--r);
  /* Opaque, because a scrolled tab passes underneath it. Matches what
     rgba(255,255,255,0.1) resolves to over the black behind the rail. */
  background: #1a1a1a;
  color: rgba(255, 255, 255, 0.86);
}

.layout-toggle svg {
  width: 18px;
  height: 18px;
}

/* Both icons are in the markup; only the one for the layout a press would
   produce is shown. Driven off the view so there is a single source of truth
   for which layout is current. */
.shell[data-layout="grid"] .layout-icon-grid,
.shell[data-layout="wide"] .layout-icon-wide {
  display: none;
}

.feed-tab.is-active {
  background: var(--hot);
  color: #fff;
  font-weight: 900;
}

.reel-feed {
  height: 100%;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}

/* Which of the two tabs is showing. The viewer's strip is also a .reel-feed —
   it scrolls identically and reuses those rules — but it is an overlay that
   opens on top of either tab, so it is exempt. Without the exemption it opened
   with no box at all: present in the DOM, 0x0, and invisible. */
.shell[data-shell-view="creators"] .reel-feed:not(.viewer-strip) {
  display: none;
}

/* The sort rail sorts the grid, so it lives with the grid and hides with it. */
.shell[data-shell-view="reels"] .tabbar,
.shell[data-shell-view="reels"] .creators-view {
  display: none;
}

.reel {
  position: relative;
  /* The height of the feed it sits in, not of the window.
     On a phone those are the same number, which is why measuring against the
     window looked correct for months. On a desktop the shell is a box inside a
     larger page: the window was 800 and the shell 756, so every reel hung 44px
     past the bottom of its own feed — and the caption, which is positioned up
     from a reel's bottom edge, was carried down underneath the navigation bar.
     A percentage cannot drift from the box it is measured against. */
  height: 100%;
  scroll-snap-align: start;
  overflow: hidden;
  background:
    linear-gradient(160deg, rgba(255, 27, 141, 0.32), rgba(0, 0, 0, 0.9)),
    #0a0a0a;
}

.reel-media {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  transform: scale(1.01);
}

/* Behind the clip, never in front of it. Blurred and dimmed so it reads as a
   backdrop for something still loading rather than as the wrong still frame. */
.reel-backdrop {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  filter: blur(18px) brightness(0.55);
  /* The blur samples past the edges and would show the page through them. */
  transform: scale(1.15);
}

/* Fades in rather than snapping, so a clip that takes a moment does not arrive
   as a jump cut from the backdrop. */
.reel-video {
  opacity: 0;
  transition: opacity 0.22s ease;
}

.reel-video[src] {
  opacity: 1;
}

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

.reel-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.reel-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.01);
}

.reel::before,
.reel::after {
  position: absolute;
  inset: 0;
  content: "";
  pointer-events: none;
}

.reel::before {
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.55), transparent 28%),
    linear-gradient(90deg, rgba(0, 0, 0, 0.72), transparent 58%),
    linear-gradient(0deg, rgba(0, 0, 0, 0.88), transparent 44%);
}

.reel::after {
  background: linear-gradient(180deg, transparent 55%, rgba(0, 0, 0, 0.34));
}

.live-badge {
  position: absolute;
  top: calc(var(--safe-top) + 108px);
  left: 16px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 30px;
  padding: 0 11px;
  border-radius: var(--r);
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
}

.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--hot);
  box-shadow: 0 0 0 5px rgba(255, 27, 141, 0.22);
}

.signal-card {
  position: absolute;
  top: calc(var(--safe-top) + 108px);
  right: 14px;
  z-index: 4;
  display: grid;
  min-width: 82px;
  padding: 10px 11px;
  border-radius: var(--r);
  background: rgba(0, 0, 0, 0.62);
}

.signal-card span,
.signal-card em {
  color: rgba(255, 255, 255, 0.66);
  font-size: 11px;
  font-style: normal;
  font-weight: 800;
}

.signal-card strong {
  margin-top: 3px;
  color: #fff;
  font-size: 24px;
  line-height: 1;
}

/* Sat high enough to read as part of the picture rather than a caption under
   it. Dropped to just clear the bottom nav — 6px above it — so the clip keeps
   the middle of the screen and the text stays out of it. */
.reel-copy {
  position: absolute;
  direction: rtl;
  left: 17px;
  right: 86px;
  bottom: calc(var(--safe-bottom) + 76px);
  z-index: 5;
}

.creator-line {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.creator-line img {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.82);
}

.creator-meta {
  min-width: 0;
}

.creator-name {
  display: block;
  overflow: hidden;
  font-size: 18px;
  font-weight: 850;
  line-height: 1.1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.verified {
  display: inline-block;
  width: 15px;
  height: 15px;
  margin-inline-start: 5px;
  border-radius: 50%;
  background: var(--hot);
  vertical-align: -2px;
}

.verified::after {
  display: block;
  width: 8px;
  height: 5px;
  margin: 4px auto 0;
  border-bottom: 2px solid #fff;
  border-left: 2px solid #fff;
  content: "";
  transform: rotate(-45deg);
}

.creator-handle {
  display: block;
  margin-top: 2px;
  color: rgba(255, 255, 255, 0.75);
  font-size: 13px;
  font-weight: 650;
}

.open-profile {
  flex: 0 0 auto;
  height: 32px;
  padding: 0 12px;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.caption {
  margin: 14px 0 10px;
  color: rgba(255, 255, 255, 0.92);
  font-size: 15px;
  font-weight: 560;
  line-height: 1.42;
  text-wrap: pretty;
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  direction: rtl;
  justify-content: flex-end;
}

.tag-row span {
  max-width: 142px;
  overflow: hidden;
  padding: 6px 9px;
  border-radius: 8px;
  unicode-bidi: plaintext;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.82);
  font-size: 12px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cta-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  margin-top: 12px;
}

.primary-cta,
.secondary-cta {
  min-width: 0;
  height: 46px;
  border-radius: var(--r);
  font-weight: 900;
  font-size: 16px;
  line-height: 1;
}

.primary-cta {
  background: var(--hot);
  color: #fff;
  letter-spacing: -0.01em;
  transition: background 140ms ease;
}

.primary-cta:active {
  background: var(--hot-deep);
}

.secondary-cta {
  padding: 0 16px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-weight: 700;
  font-size: 15px;
}

/* The whole clip is the play/pause target, sitting under the copy and the rail
   so their taps still reach them. Without a real element here the tap landed on
   the <video>, which has its own default behaviour and no bubbling we control. */
.reel-tap {
  position: absolute;
  inset: 0;
  z-index: 3;
  border: 0;
  background: transparent;
  cursor: pointer;
}

/* Top corner, away from the action rail and the copy, and above the tap layer
   so pressing it does not also pause the clip. */
.reel-sound {
  position: absolute;
  top: calc(var(--safe-top) + 112px);
  inset-inline-end: 14px;
  z-index: 6;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.42);
  color: #fff;
  cursor: pointer;
}

.reel-sound svg {
  width: 19px;
  height: 19px;
}

/* Shows the state it is in, not the state a press would produce: with sound,
   what a fan needs at a glance is whether this clip is about to make noise. */
.reel-sound[aria-pressed="true"] .sound-icon-off,
.reel-sound[aria-pressed="false"] .sound-icon-on {
  display: none;
}

/* The viewer has no sort rail above it, so the button sits where the close
   button is not. */
.viewer-strip .reel-sound {
  top: calc(var(--safe-top) + 64px);
}

/* Shown only while paused. A stopped clip and a clip that failed to autoplay
   look identical otherwise — a still frame the fan assumes is broken. */
.reel-paused {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  place-items: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.16s ease;
}

.reel.is-paused .reel-paused {
  opacity: 1;
}

.reel-paused svg {
  width: 68px;
  height: 68px;
  fill: rgba(255, 255, 255, 0.92);
  stroke: none;
  filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.7));
}

/* It opens a profile, so it is a button — but it must not look like one. */
.creator-line {
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  text-align: start;
  cursor: pointer;
}

/* Her own words from the channel post, written with line breaks that read wrong
   collapsed into a paragraph. Clamped so a long caption cannot cover the clip;
   tapping it opens the rest. */
.reel .caption {
  white-space: pre-line;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  cursor: pointer;
}

.reel .caption.is-open {
  -webkit-line-clamp: unset;
  max-height: 42svh;
  overflow-y: auto;
}

.action-button.is-like[aria-pressed="true"] {
  background: var(--hot);
}

/* The viewer's copy of the rail. Same markup, so it inherits every rule below;
   only the placement differs, because there is no reel copy to sit beside. */
.action-rail {
  position: absolute;
  right: 12px;
  bottom: calc(var(--safe-bottom) + 106px);
  z-index: 5;
  display: grid;
  gap: 13px;
  justify-items: center;
}

.action-button {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}

.action-button.is-hot {
  background: var(--hot);
}

.action-count {
  margin-top: -8px;
  color: rgba(255, 255, 255, 0.88);
  font-size: 11px;
  font-weight: 790;
}

.creators-view {
  display: none;
  height: 100%;
  overflow-y: auto;
  padding: calc(var(--safe-top) + 84px) 12px calc(var(--safe-bottom) + 96px);
  scrollbar-width: none;
  direction: rtl;
}

.creators-view::-webkit-scrollbar {
  display: none;
}

.shell[data-shell-view="creators"] .creators-view {
  display: block;
}

.creators-header {
  margin-bottom: 12px;
}

.creators-header span {
  color: var(--hot);
  font-size: 12px;
  font-weight: 800;
}

.creators-header h1 {
  margin: 4px 0 0;
  font-size: 34px;
  line-height: 1;
}

.creator-filter-row {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  overflow-x: auto;
  scrollbar-width: none;
}

.creator-filter-row::-webkit-scrollbar {
  display: none;
}

.creator-filter {
  flex: 0 0 auto;
  height: 40px;
  padding: 0 16px;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.09);
  color: rgba(255, 255, 255, 0.72);
  font-weight: 700;
  white-space: nowrap;
}

.creator-filter.is-active {
  background: var(--hot);
  color: #fff;
  font-weight: 900;
}

/* Two across is the dense default on a phone. The wide layout drops to one, so
   a tile fills the screen — same tiles, fewer of them, bigger. The mode lives
   on the view rather than on this element, because the grid is rebuilt from
   scratch on every sort and would lose a class set on itself. */
.creator-tile-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.shell[data-layout="wide"] .creator-tile-grid {
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
}

/* A tile at full phone width is roughly twice the size, so the caption that
   suited a thumbnail now looks lost on it. */
.shell[data-layout="wide"] .creator-tile-name {
  font-size: 19px;
}

.shell[data-layout="wide"] .creator-tile-caption {
  padding: 16px 14px 14px;
}

.creator-tile {
  position: relative;
  min-width: 0;
  aspect-ratio: 0.72;
  overflow: hidden;
  border-radius: var(--r);
  background: #101010;
  color: #fff;
  text-align: start;
}

.creator-tile-bg,
.creator-tile-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.creator-tile-bg {
  background-position: center;
  background-size: cover;
}

.creator-tile-video {
  object-fit: cover;
}

.creator-tile::after {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.02) 42%, rgba(0, 0, 0, 0.74)),
    linear-gradient(90deg, rgba(0, 0, 0, 0.34), transparent 58%);
  content: "";
}

.creator-tile-rank,
.creator-tile-name {
  position: absolute;
  z-index: 1;
  max-width: calc(100% - 20px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.creator-tile-rank {
  top: 9px;
  inset-inline-start: 9px;
  padding: 5px 8px;
  border-radius: 7px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

.creator-tile-name {
  left: 10px;
  right: 10px;
  bottom: 10px;
  font-size: 17px;
  font-weight: 900;
  line-height: 1.1;
}

.bottom-nav {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: calc(var(--safe-bottom) + 10px);
  z-index: 35;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 6px;
  direction: rtl;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 14px;
  background: #0b0b0b;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.6);
}

.bottom-nav-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 0;
  height: 46px;
  border-radius: var(--r);
  background: transparent;
  color: rgba(255, 255, 255, 0.62);
  font-size: 14px;
  font-weight: 700;
}

.bottom-nav-button svg {
  width: 20px;
  height: 20px;
}

.bottom-nav-button.is-active {
  background: var(--hot);
  color: #fff;
  font-weight: 900;
}

.profile-sheet,
.search-sheet {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: grid;
  align-items: end;
  pointer-events: none;
  visibility: hidden;
}

.profile-sheet.is-open,
.search-sheet.is-open {
  pointer-events: auto;
  visibility: visible;
}

.sheet-backdrop {
  position: absolute;
  inset: 0;
  border: 0;
  background: rgba(0, 0, 0, 0);
  transition: background 180ms ease;
}

.is-open .sheet-backdrop {
  background: rgba(0, 0, 0, 0.58);
}

.sheet-card,
.search-panel {
  position: relative;
  /* Measured against the shell, which now tracks the real visible height, so
     the panel stays inside whatever the keyboard leaves behind. */
  max-height: min(78%, 720px);
  overflow: auto;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: #0b0b0b;
  box-shadow: var(--shadow);
  transform: translateY(102%);
  transition: transform 220ms ease;
}

.is-open .sheet-card,
.is-open .search-panel {
  transform: translateY(0);
}

.sheet-card {
  padding: 12px 16px calc(22px + var(--safe-bottom));
  direction: rtl;
}

.search-panel {
  padding: 12px 16px calc(24px + var(--safe-bottom));
  direction: rtl;
}

.sheet-handle {
  /* The bar stays 4px; the grabbable area is the padding around it. A 4px
     target is impossible to hit, which is part of why the drag gesture only
     ever reached Telegram. */
  box-sizing: content-box;
  width: 44px;
  height: 4px;
  padding: 12px 28px;
  margin: -12px auto 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.26);
  background-clip: content-box;
  touch-action: none;
  cursor: grab;
}

.sheet-handle:active {
  cursor: grabbing;
}

/* The panels are dragged by script, so they must not fight it with their own
   scroll-chaining or text selection. */
.search-panel,
.sheet-card {
  overscroll-behavior: contain;
  user-select: none;
}

.profile-cover {
  /* A button now, so the button defaults have to go: it must still fill the
     width and carry the background image the way the div did. */
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  min-height: 138px;
  margin-bottom: -36px;
  border-radius: 18px;
  background-color: transparent;
  background-position: center;
  background-size: cover;
  overflow: hidden;
  cursor: pointer;
}

.profile-cover::after {
  display: block;
  width: 100%;
  height: 138px;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.03), rgba(11, 11, 11, 0.9)),
    linear-gradient(90deg, rgba(0, 0, 0, 0.6), transparent);
  content: "";
}

.profile-head {
  position: relative;
  display: grid;
  grid-template-columns: 74px 1fr auto;
  gap: 13px;
  align-items: center;
}

/* The button is only there to take the tap — it adds no box of its own, so the
   header lays out exactly as it did when the avatar was a bare image. */
.profile-avatar-button {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  cursor: pointer;
}

.profile-avatar {
  display: block;
  width: 74px;
  height: 74px;
  border-radius: 18px;
  object-fit: cover;
  border: 3px solid #0b0b0b;
  pointer-events: none;
}

.profile-head h2,
.search-panel h2 {
  margin: 0;
  font-size: 22px;
  line-height: 1.1;
}

.profile-head p,
.profile-bio {
  color: var(--muted);
}

.profile-head p {
  margin: 5px 0 0;
  font-size: 13px;
  font-weight: 650;
}

.profile-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  padding: 0 12px;
  border-radius: 8px;
  background: var(--hot-soft);
  color: var(--hot);
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

.profile-signals {
  display: flex;
  gap: 7px;
  margin-top: 16px;
  overflow-x: auto;
  scrollbar-width: none;
}

.profile-signals::-webkit-scrollbar {
  display: none;
}

.profile-signals span {
  flex: 0 0 auto;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.07);
  color: rgba(255, 255, 255, 0.82);
  font-size: 12px;
  font-weight: 600;
}

.profile-signals strong {
  color: var(--hot);
  font-weight: 800;
}

.profile-bio {
  margin: 16px 0 0;
  font-size: 14px;
  line-height: 1.48;
}

.profile-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 18px 0 14px;
}

.profile-stats div {
  display: grid;
  gap: 3px;
  min-height: 66px;
  align-content: center;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.06);
  text-align: center;
}

.profile-stats strong {
  font-size: 19px;
}

.profile-stats span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
}

/* Equal columns rather than 1fr + auto: the two actions are peers, and an
   auto-sized one would change width with its label. `auto-fit` lets the row
   close up cleanly when "לפרטי" is hidden. */
.profile-actions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}

/* Any rule that sets display beats the hidden attribute, whose only power is
   the user agent's own display:none. The rule below sets it, so a creator with
   no username to message still showed a "לפרטי" button — and pressing it did
   nothing at all, because the code behind it returns immediately without one. */
.profile-actions button[hidden] {
  display: none;
}

.profile-actions button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-width: 88px;
  height: 48px;
  border: 0;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-weight: 700;
  transition: transform var(--t-fast) var(--ease-spring), background var(--t-fast) var(--ease);
}

/* Every control answers the finger. Nothing else in this rule set moves. */
.profile-actions button:active {
  transform: scale(0.97);
}

/* Pink means press this, and this is the press the whole product is for. */
#profileDmButton {
  position: relative;
  overflow: hidden;
  background: var(--hot);
  font-weight: 900;
  box-shadow: 0 8px 22px rgba(255, 27, 141, 0.32);
}

#profileDmButton:active {
  background: var(--hot-deep);
}

/* A slow sheen crossing the button. It is the one piece of motion here that is
   not answering an action — it is there because this button has to be noticed
   before it is understood. Slow and low-contrast enough to read as sheen
   rather than as a loading bar. */
#profileDmButton::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  animation: dm-sheen 3.4s ease-in-out infinite;
}

@keyframes dm-sheen {
  /* Most of the cycle is spent off screen: it should catch the eye every few
     seconds, not shimmer continuously. */
  0%, 62% { transform: translateX(-160%); }
  100% { transform: translateX(360%); }
}

.profile-actions button svg {
  width: 18px;
  height: 18px;
  flex: none;
}

/* The paper-plane points along its own axis, so in an RTL row it reads as
   flying backwards. Mirrored to follow the direction the label is read. */
.profile-actions #profileShareButton svg {
  transform: scaleX(-1);
}

.profile-actions .primary-action {
  background: var(--hot);
  color: #fff;
  font-size: 16px;
  font-weight: 900;
  transition: background 140ms ease;
}

.profile-actions .primary-action:active {
  background: var(--hot-deep);
}

.profile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
}

.profile-tile {
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: 8px;
  background: #141414;
}

.profile-tile img,
.profile-tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.search-panel h2 {
  margin-bottom: 14px;
}

.search-field {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 48px;
  padding: 0 13px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.06);
}

.search-field svg {
  width: 20px;
  height: 20px;
  color: rgba(255, 255, 255, 0.56);
}

.search-field input {
  width: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: #fff;
}

.search-results {
  display: grid;
  gap: 9px;
  margin-top: 14px;
}

.search-result {
  display: grid;
  grid-template-columns: 48px 1fr auto;
  gap: 11px;
  align-items: center;
  width: 100%;
  min-height: 64px;
  padding: 8px;
  border: 0;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.05);
  color: #fff;
  text-align: start;
}

.search-result img {
  width: 48px;
  height: 48px;
  border-radius: 10px;
  object-fit: cover;
}

.search-result strong,
.search-result span {
  display: block;
}

.search-result strong {
  font-size: 15px;
}

.search-result span {
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
}

.search-result em {
  color: var(--hot);
  font-size: 12px;
  font-style: normal;
  font-weight: 850;
}

.toast {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: calc(92px + var(--safe-bottom));
  z-index: 80;
  display: grid;
  min-height: 46px;
  place-items: center;
  padding: 10px 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--r);
  background: #101010;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 180ms ease,
    transform 180ms ease;
  backdrop-filter: blur(18px);
}

.toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (min-width: 720px) {
  body {
    display: grid;
    place-items: center;
    padding: 22px;
  }

  .shell {
    height: min(900px, calc(100svh - 44px));
    /* The floor has to bend. A flat 720px on a window shorter than that pushed
       the shell past the bottom of the screen, taking the navigation bar with
       it — at a 620px window the whole bar sat 112px below the fold, so there
       was no way to leave the reels at all. It is still a floor; it just
       cannot be taller than the room it has. */
    min-height: min(720px, calc(100svh - 44px));
    border-radius: 26px;
    box-shadow: var(--shadow);
  }

  .sheet-card,
  .search-panel {
    border-radius: var(--r-lg);
    margin: 0 10px 10px;
  }
}

@media (max-width: 360px) {
  .reel-copy {
    right: 74px;
  }

  .action-button {
    width: 48px;
    height: 48px;
  }

  .tag-row span {
    max-width: 120px;
  }

  .profile-head {
    grid-template-columns: 64px 1fr;
  }

  .profile-avatar {
    width: 64px;
    height: 64px;
  }

  .profile-badge {
    grid-column: 1 / -1;
  }
}


/* ------------------------------------------------- explore grid additions */

/* The sort rail sits above the grid, so the grid starts below it. */
.shell[data-shell-view="creators"] .creators-view {
  padding-top: calc(var(--safe-top) + 106px);
  padding-bottom: calc(var(--safe-bottom) + 120px);
}

.creator-tile-caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  gap: 1px;
  padding: 10px;
  text-align: start;
}
/* Deliberately not pink. The handoff's rule is that pink means "press this",
   and a pink line on every tile — none of them pressable on their own — spends
   the signal the logo, the active tab and the CTA depend on. */
.creator-tile-metric {
  color: var(--muted);
  font-family: var(--display);
  font-weight: 700;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.creator-tile-live {
  position: absolute;
  z-index: 2;
  top: 8px;
  inset-inline-start: 8px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.6);
  font-family: var(--display);
  font-weight: 700;
  font-size: 11px;
}

/* ------------------------------------------------------------ coming soon */

.coming-soon {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 10px;
  height: 100%;
  padding: 24px;
  text-align: center;
}
.coming-soon-mark {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-bottom: 6px;
  border-radius: var(--r-lg);
  background: var(--hot-soft);
  color: var(--hot);
}
.coming-soon-mark svg {
  width: 26px;
  height: 26px;
  fill: currentColor;
}
.coming-soon-kicker {
  color: var(--hot);
  font-family: var(--display);
  font-weight: 700;
  font-size: 12px;
}
.coming-soon h2 {
  font-family: var(--display);
  font-size: 24px;
  font-weight: 900;
}
.coming-soon p:not(.coming-soon-kicker) {
  max-width: 30ch;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
}

.empty-state {
  padding: 28px 8px;
  color: var(--muted);
  font-size: 14px;
  text-align: center;
}

/* ---------------------------------------------------------------- desktop */

/* The shell is a 480px phone column by default. On a real desktop viewport it
   widens into a proper catalogue instead of leaving two thirds of the screen
   black — the grid is the whole product, so it gets the room. */
/* Layout responds to WIDTH ONLY.
   The previous gate also required (pointer: fine), which meant a touchscreen
   laptop — ordinary on Windows — got the 480px phone column on a full-size
   screen. Pointer type says how you click, not how much room you have.

   The step starts at 48rem rather than 64rem because a 1440px Windows laptop at
   the default 150% display scaling reports a 960px CSS viewport, and 1024px
   left exactly that machine on the phone layout. */
@media (min-width: 48rem) {
  .shell {
    width: min(100vw, 860px);
    border-inline: 0;
  }

  /* On a wide screen "wide" cannot mean one per row — that would be a column of
     billboards. It means the size the desktop already used; the dense mode is
     what fits more of them on screen. Both written with the attribute so they
     carry equal specificity, since a media query adds none of its own. */
  .shell[data-layout="grid"] .creator-tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(116px, 100%), 1fr));
    gap: 8px;
  }

  /* A 17px name on a tile this narrow is three letters and an ellipsis. */
  .shell[data-layout="grid"] .creator-tile-name {
    left: 8px;
    right: 8px;
    bottom: 8px;
    font-size: 13px;
  }

  .shell[data-layout="grid"] .creator-tile-caption {
    padding: 8px;
  }

  .shell[data-layout="wide"] .creator-tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
    gap: 12px;
  }

  .shell[data-layout="wide"] .creator-tile-name {
    font-size: 15px;
  }

  .shell[data-layout="wide"] .creator-tile-caption {
    padding: 12px 10px 10px;
  }
}

@media (min-width: 64rem) {
  .shell {
    width: min(100vw, 1180px);
    border-inline: 0;
  }

  .tabbar,
  .creators-view {
    padding-inline: 32px;
  }

  /* The nav is a compact control, not a page-wide banner: it keeps its phone
     proportions and centres instead of stretching to the full column. */
  .bottom-nav {
    width: min(100%, 420px);
    margin-inline: auto;
    inset-inline: 0;
  }

  .shell[data-layout="grid"] .creator-tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(128px, 100%), 1fr));
    gap: 8px;
  }

  .shell[data-layout="wide"] .creator-tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(210px, 100%), 1fr));
    gap: 14px;
  }

  .creators-header h1 {
    font-size: 40px;
  }

  /* Only the hover lift depends on having a hover-capable pointer. */
  @media (hover: hover) {
    .creator-tile {
      transition:
        transform 220ms cubic-bezier(0.32, 0.72, 0, 1),
        box-shadow 220ms ease-out;
    }
    .creator-tile:hover {
      transform: translateY(-4px);
      box-shadow: 0 22px 44px rgba(0, 0, 0, 0.7);
    }
  }

  /* The reel feed is a one-per-screen surface; on a wide viewport it stays a
     column rather than stretching a portrait video across 1180px. */
  .reel-feed {
    width: min(100%, 480px);
    margin-inline: auto;
  }
}

/* --------------------------------------------------- stepping on a desktop */

/* Only where there is a real pointer. A touch device scrolls the feed by
   flicking it and needs no buttons; a mouse has no such gesture, and with the
   feed narrowed to a column the wheel did nothing unless the cursor happened to
   be inside it. */
.reel-steps {
  display: none;
}

@media (pointer: fine) and (min-width: 64rem) {
  .shell[data-shell-view="reels"] .reel-steps {
    position: absolute;
    /* Beside the column, not over it — nothing should cover the clip. */
    inset-inline-start: calc(50% + 260px);
    top: 50%;
    transform: translateY(-50%);
    z-index: 30;
    display: grid;
    gap: 12px;
  }

  .reel-step {
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    color: #fff;
    cursor: pointer;
    transition: background 140ms ease;
  }

  .reel-step:hover {
    background: rgba(255, 255, 255, 0.16);
  }
}

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


/* ------------------------------------------------------------------ drawer */

/* Anchored to the physical left because that is where the hamburger sits, in
   both directions. A panel that opens away from its own trigger reads as a
   different control. */
.drawer {
  position: absolute;
  inset: 0;
  z-index: 60;
  visibility: hidden;
  pointer-events: none;
}
.drawer.is-open {
  visibility: visible;
  pointer-events: auto;
}

.drawer .sheet-backdrop {
  position: absolute;
  inset: 0;
  border: 0;
  background: rgba(0, 0, 0, 0.66);
  opacity: 0;
  transition: opacity 260ms cubic-bezier(0.32, 0.72, 0, 1);
}
.drawer.is-open .sheet-backdrop {
  opacity: 1;
}

.drawer-panel {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: min(86%, 340px);
  display: flex;
  flex-direction: column;
  background: var(--panel-solid);
  border-inline-end: 1px solid var(--line);
  box-shadow: var(--shadow);
  transform: translateX(-100%);
  transition: transform 320ms cubic-bezier(0.32, 0.72, 0, 1);
}
.drawer.is-open .drawer-panel {
  transform: translateX(0);
}
.drawer-panel:focus {
  outline: none;
}

.drawer-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: calc(var(--safe-top) + 18px) 16px 12px;
  border-bottom: 1px solid var(--line);
}
.drawer-head h2 {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 900;
}
.drawer-close {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: var(--panel-raised);
  color: var(--muted);
  cursor: pointer;
}
.drawer-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.drawer-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 16px calc(var(--safe-bottom) + 24px);
  scrollbar-width: none;
}
.drawer-scroll::-webkit-scrollbar {
  display: none;
}

.me-card {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.me-photo {
  position: relative;
  flex: none;
  width: 72px;
  height: 72px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  cursor: pointer;
}
.me-photo img {
  width: 100%;
  height: 100%;
  border-radius: 999px;
  object-fit: cover;
  background: var(--panel-raised);
}
.me-photo-edit {
  position: absolute;
  right: -2px;
  bottom: -2px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  border: 2px solid var(--panel-solid);
  background: var(--hot);
  color: #fff;
}
.me-photo-edit svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linejoin: round;
}

.me-identity {
  min-width: 0;
}
.me-name {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 800;
  unicode-bidi: plaintext;
  text-align: start;
  overflow-wrap: anywhere;
}
.me-sub {
  margin-top: 2px;
  color: var(--muted);
  font-size: 12.5px;
  unicode-bidi: plaintext;
  text-align: start;
}

.me-form {
  display: grid;
  gap: 8px;
  margin-bottom: 4px;
}

.field {
  display: grid;
  gap: 6px;
}
.field > span {
  color: var(--muted);
  font-size: 12.5px;
}
.field input,
.field select {
  width: 100%;
  height: 42px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel-raised);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--hot);
  outline-offset: 1px;
}

.field-save {
  justify-self: start;
  height: 36px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-raised);
  color: var(--text);
  font-family: var(--display);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.field-save:hover {
  border-color: rgba(255, 255, 255, 0.24);
}

.field-hint {
  margin: 6px 0 18px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
}

.drawer-note {
  min-height: 18px;
  margin: 8px 0 4px;
  color: var(--hot);
  font-size: 12.5px;
}

.drawer-section {
  margin: 12px 0 12px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-family: var(--display);
  font-size: 15px;
  font-weight: 800;
}

.drawer-danger {
  width: 100%;
  height: 42px;
  border: 1px solid rgba(255, 27, 141, 0.4);
  border-radius: var(--r);
  background: var(--hot-soft);
  color: var(--hot);
  font-family: var(--display);
  font-weight: 800;
  font-size: 14px;
  cursor: pointer;
}
.drawer-danger:hover {
  background: rgba(255, 27, 141, 0.24);
}

@media (prefers-reduced-motion: reduce) {
  .drawer-panel,
  .drawer .sheet-backdrop {
    transition-duration: 1ms;
  }
}

/* Kept in the accessibility tree and focusable, just not painted — the visible
   control is the avatar button that forwards the click to it. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Every sheet needs a visible exit. The backdrop alone leaves a keyboard user
   and anyone on a wide screen with no obvious way back. */
.sheet-close {
  position: absolute;
  z-index: 3;
  top: 12px;
  inset-inline-end: 12px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  cursor: pointer;
}
.sheet-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}
.sheet-close:hover {
  border-color: rgba(255, 255, 255, 0.28);
}
.sheet-close:active {
  transform: scale(0.92);
}

/* ------------------------------------------------------------- media viewer */

.viewer {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
  background: #000;
}

.viewer.is-open {
  display: block;
}

/* Her media, scrolling exactly like the feed. It carries .reel-feed too, so the
   snap, the hidden scrollbar and the card sizing are the feed's own rules
   rather than a second set that has to be kept in step. */
.viewer-strip {
  height: 100%;
}

.viewer-close {
  position: absolute;
  /* Over the card's own controls. */
  z-index: 10;
  top: calc(var(--safe-top) + 10px);
  inset-inline-end: 12px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
}

.viewer-close svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A profile tile is a button now, so it needs the button defaults stripped. */
.profile-tile {
  position: relative;
  display: block;
  padding: 0;
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* A tap on the video element itself never reached the button wrapping it, so
   a video tile looked dead while photo tiles opened fine. The media is
   decoration here; the button underneath takes the tap. */
.profile-tile video,
.profile-tile img {
  pointer-events: none;
}

.profile-tile-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.profile-tile-play svg {
  width: 26px;
  height: 26px;
  fill: rgba(255, 255, 255, 0.92);
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.6));
}

/* The sort rail scrolls out of the way instead of riding down the page. It is
   positioned over the grid rather than inside it, so it does not leave on its
   own — this moves it once the grid has actually been scrolled. */
.tabbar {
  transition: transform 0.24s ease, opacity 0.24s ease;
}

.tabbar.is-tucked {
  transform: translateY(-160%);
  opacity: 0;
  pointer-events: none;
}

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

/* The preview sits over the tile while a finger is held down. */
.creator-tile {
  position: relative;
}

.tile-preview {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  /* The tile underneath owns the gesture. */
  pointer-events: none;
  /* Deliberately a transition on a class, not an entry animation. The clip is
     appended before it can paint anything, so what has to be timed is the
     arrival of its first frame — something CSS cannot see but the script can.
     A filling animation would also pin opacity above any inline style, which
     is how the profile sheet ended up undraggable. */
  opacity: 0;
  /* Settles in from very slightly larger, so it reads as the picture coming
     forward rather than a second image being switched on. */
  transform: scale(1.035);
  transition:
    opacity 240ms var(--ease-out),
    transform 420ms var(--ease-out);
}

.tile-preview.is-ready {
  opacity: 1;
  transform: scale(1);
}

/* On the way out it only fades. The scale has to be held at 1 explicitly:
   is-ready is dropped at the same moment, so without this the base rule's
   scale(1.035) returns — and since only opacity is transitioned here, it
   returns as a jump, a pop in the middle of the fade. */
.tile-preview.is-leaving {
  opacity: 0;
  transform: scale(1);
  transition: opacity 200ms var(--ease);
}

/* Reports a press before there is anything to show for it. The fill runs the
   length of the hold; when the clip starts downloading the same bar turns into
   a sliver that slides, because how long a fetch takes is not knowable in
   advance. Both phases fill from the right, the direction the app reads. */
.tile-hold-bar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 4;
  height: 4px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.16);
  /* The tile underneath owns the gesture. */
  pointer-events: none;
  animation: tile-hold-bar-in 0.12s ease both;
}

.tile-hold-bar i {
  display: block;
  height: 100%;
  background: var(--hot);
  box-shadow: 0 0 10px rgba(255, 27, 141, 0.75);
  transform-origin: right center;
  animation: hold-fill var(--hold-ms, 240ms) linear both;
}

.tile-hold-bar.is-loading i {
  width: 35%;
  /* Parked at the edge the app reads from. */
  margin-inline-end: auto;
  transform-origin: center;
  animation: hold-slide 0.9s ease-in-out infinite alternate;
}

@keyframes tile-hold-bar-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes hold-fill {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

@keyframes hold-slide {
  from { transform: translateX(0); }
  /* 65% of the track, expressed against the sliver's own 35% width. */
  to { transform: translateX(-186%); }
}


/* A held tile lifts slightly, so it is obvious which one is playing and that
   the press registered at all. */
.creator-tile.is-previewing {
  transform: scale(1.02);
  transition: transform 0.18s ease;
}

/* The caption stays legible over moving video. */
.creator-tile.is-previewing .creator-tile-caption {
  z-index: 3;
}

@media (prefers-reduced-motion: reduce) {
  .tile-preview { transition: none; }
  .tile-hold-bar { animation: none; }
  .tile-hold-bar i,
  .tile-hold-bar.is-loading i { animation: none; transform: none; }
  .creator-tile.is-previewing { transform: none; transition: none; }
}

/* ---------------------------------------------------------------- the tour */

/* Shown once, on a fan's first visit. The dark layer is the spotlight's own
   box-shadow rather than a separate element, which is what lets the hole track
   a real control instead of a drawing of one. */
.tour {
  position: absolute;
  inset: 0;
  z-index: 90;
  display: none;
}

.tour.is-open {
  display: block;
}

.tour-spot {
  position: absolute;
  border-radius: 14px;
  /* The dim is cast outwards from the hole, so the hole is genuinely clear. */
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.82);
  outline: 2px solid var(--hot);
  outline-offset: 2px;
  transition: top 0.34s cubic-bezier(0.4, 0, 0.2, 1), left 0.34s cubic-bezier(0.4, 0, 0.2, 1),
    width 0.34s cubic-bezier(0.4, 0, 0.2, 1), height 0.34s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

/* A step about the whole screen has nothing to point at: the hole collapses and
   only the dim remains. */
.tour.is-centred .tour-spot {
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
}

/* A finger on the control being described, so it reads as "press this". */
.tour-hand {
  position: absolute;
  width: 30px;
  height: 30px;
  margin: -15px 0 0 -15px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.28);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.72);
  opacity: 0;
  pointer-events: none;
  transition: top 0.34s cubic-bezier(0.4, 0, 0.2, 1), left 0.34s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
  animation: tour-tap 1.5s ease-in-out infinite;
}

@keyframes tour-tap {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(0.7); }
}

.tour-card {
  position: absolute;
  inset-inline: 16px;
  bottom: calc(var(--safe-bottom) + 22px);
  direction: rtl;
  display: grid;
  gap: 10px;
  padding: 18px 18px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel-solid);
  box-shadow: var(--shadow);
  transition: opacity 0.16s ease, transform 0.16s ease;
}

/* Moves out of the way when the thing being described is near the bottom. */
.tour-card.is-above {
  bottom: auto;
  top: calc(var(--safe-top) + 84px);
}

.tour-card.is-changing {
  opacity: 0;
  transform: translateY(6px);
}

.tour-card h2 {
  margin: 0;
  font-size: 20px;
}

.tour-card p {
  margin: 0;
  color: rgba(255, 255, 255, 0.78);
  font-size: 14.5px;
  line-height: 1.5;
}

.tour-dots {
  display: flex;
  gap: 6px;
  margin-top: 2px;
}

.tour-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
  transition: background 0.2s ease, width 0.2s ease;
}

.tour-dots span.is-now {
  width: 18px;
  border-radius: 3px;
  background: var(--hot);
}

.tour-actions {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 10px;
  align-items: center;
  margin-top: 4px;
}

.tour-skip {
  border: 0;
  padding: 10px 4px;
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}

.tour-actions .primary-cta {
  height: 46px;
  border-radius: var(--r);
}

/* Nothing underneath reacts while the tour is driving the app. */
.shell.is-touring .creators-view,
.shell.is-touring .reel-feed,
.shell.is-touring .bottom-nav,
.shell.is-touring .topbar,
.shell.is-touring .sheet-card {
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .tour-spot,
  .tour-hand,
  .tour-card {
    transition: none;
  }

  .tour-hand {
    animation: none;
  }
}

/* The one step that points at something outside the page: Telegram draws its
   own button below the webview, so there is nothing here to ring. An arrow
   against the bottom edge says "below this", which is true. */
.tour-card.has-arrow::after {
  content: "";
  display: block;
  justify-self: center;
  width: 14px;
  height: 14px;
  margin-top: 2px;
  border-right: 2px solid var(--hot);
  border-bottom: 2px solid var(--hot);
  transform: rotate(45deg);
  animation: tour-point 1.4s ease-in-out infinite;
}

@keyframes tour-point {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: 0.65; }
  50% { transform: rotate(45deg) translate(3px, 3px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tour-card.has-arrow::after {
    animation: none;
  }
}

/* ------------------------------------------------- life, per DESIGN.md §3 */

/* Every control answers the finger. One rule, so nothing is left out by being
   forgotten rather than by being decided against. */
.action-button,
.icon-button,
.bottom-nav-button,
.creator-tile,
.primary-cta,
.secondary-cta,
.reel-step,
.layout-toggle,
.tour-card .primary-cta {
  transition: transform var(--t-fast) var(--ease-spring), background var(--t-fast) var(--ease);
}

.action-button:active,
.icon-button:active,
.primary-cta:active,
.secondary-cta:active,
.reel-step:active,
.layout-toggle:active {
  transform: scale(0.94);
}

.creator-tile:active {
  /* Gentler: a tile is large, and .94 on something this size reads as a lurch. */
  transform: scale(0.985);
}

.bottom-nav-button:active {
  transform: scale(0.97);
}

/* On a reel, the message action is the only filled circle in the rail. The
   others are translucent until they have something to say. */
.action-button[data-dm] {
  background: var(--hot);
  box-shadow: 0 6px 18px rgba(255, 27, 141, 0.34);
}

.action-button[data-dm]:active {
  background: var(--hot-deep);
}

/* Its label earns the same weight, or the circle looks like an error. */
.action-button[data-dm] + .action-count {
  color: #fff;
  font-weight: 900;
}

/* The like fills and kicks once. "What just changed" — the third question a
   piece of motion is allowed to answer. */
.action-button.is-like[aria-pressed="true"] svg {
  fill: currentColor;
  animation: like-pop var(--t-slow) var(--ease-spring);
}

@keyframes like-pop {
  0% { transform: scale(1); }
  40% { transform: scale(1.32); }
  100% { transform: scale(1); }
}

/* The grid arrives rather than appearing. Staggered by position, capped so a
   long roster does not have its tail waiting a second and a half. */
.creator-tile {
  animation: tile-in var(--t) var(--ease-out) both;
  animation-delay: calc(var(--tile-index, 0) * 26ms);
}

@keyframes tile-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* A sheet rises from where it was tapped rather than fading in place.

   Filled backwards, never both. A finished animation keeps its last keyframe
   applied at a higher priority than an inline style, so "both" left
   transform:none pinned on the sheet — and the drag, which works by setting an
   inline transform, could not move it at all. It could only snap. */
.sheet-card,
.search-panel {
  animation: sheet-rise var(--t-slow) var(--ease-out) backwards;
}

@keyframes sheet-rise {
  from { transform: translateY(7%); opacity: 0.6; }
  to { transform: none; opacity: 1; }
}

/* The tab you are on is stated by more than colour. */
.bottom-nav-button.is-active svg {
  animation: nav-lift var(--t) var(--ease-spring);
}

@keyframes nav-lift {
  0% { transform: translateY(3px) scale(0.9); }
  100% { transform: none; }
}

/* Reduced motion removes the movement, never the state: a pressed button still
   looks pressed and a liked heart is still filled. */
@media (prefers-reduced-motion: reduce) {
  .action-button,
  .icon-button,
  .bottom-nav-button,
  .creator-tile,
  .primary-cta,
  .secondary-cta,
  .reel-step,
  .layout-toggle,
  .profile-actions button {
    transition: none;
    animation: none;
  }

  #profileDmButton::after,
  .action-button.is-like[aria-pressed="true"] svg,
  .sheet-card,
  .search-panel,
  .bottom-nav-button.is-active svg {
    animation: none;
  }
}

/* The dim lifts as a sheet is pulled down. --sheet-progress sits on the sheet
   rather than the panel because the backdrop is the panel's sibling, and a
   custom property inherits down, never sideways. */
.profile-sheet,
.search-sheet {
  --sheet-progress: 0;
}

.profile-sheet.is-open .sheet-backdrop,
.search-sheet.is-open .sheet-backdrop {
  background: rgba(0, 0, 0, calc(0.58 * (1 - var(--sheet-progress, 0))));
}

/* While a finger is on the sheet the dim must track it frame for frame. The
   180ms ease that makes opening pleasant makes dragging feel like the backdrop
   is being pulled along on a rubber band. */
.profile-sheet.is-dragging .sheet-backdrop,
.search-sheet.is-dragging .sheet-backdrop {
  transition: none;
}

/* --------------------------------------------------------------- heartbeat */

/* The message buttons beat, twice, then rest.

   These two are the only presses that produce anything for the creator, and
   they sat as still as the decoration around them. A double thump with a long
   rest reads as something alive asking to be pressed; a steady throb reads as
   a broken loading state, and it is the rest between beats that makes the
   difference — most of the cycle is deliberately nothing happening.

   Scale and box-shadow only, so nothing around them reflows. */
@keyframes dm-heartbeat {
  /* The ring is the second shadow, not a pseudo-element: the button clips its
     children so the sheen stays inside it, and a ring drawn as a child would
     be clipped away at exactly the moment it is supposed to leave the edge.
     A box-shadow is painted outside the box and ignores overflow. */
  0%, 68%, 100% {
    transform: scale(1);
    box-shadow: 0 8px 22px rgba(255, 27, 141, 0.32), 0 0 0 0 rgba(255, 255, 255, 0.5);
  }
  72% {
    transform: scale(1.055);
    box-shadow: 0 10px 30px rgba(255, 27, 141, 0.55), 0 0 0 5px rgba(255, 255, 255, 0.28);
  }
  76% {
    transform: scale(1);
    box-shadow: 0 8px 22px rgba(255, 27, 141, 0.34), 0 0 0 10px rgba(255, 255, 255, 0.1);
  }
  80% {
    transform: scale(1.035);
    box-shadow: 0 9px 26px rgba(255, 27, 141, 0.45), 0 0 0 14px rgba(255, 255, 255, 0);
  }
  86% {
    transform: scale(1);
    box-shadow: 0 8px 22px rgba(255, 27, 141, 0.32), 0 0 0 0 rgba(255, 255, 255, 0);
  }
}

#profileDmButton {
  animation: dm-heartbeat 4.4s var(--ease) infinite;
}

/* The round one in the reels rail. Same rhythm, its own resting shadow —
   it sits on video rather than on the sheet's background. */
@keyframes dm-heartbeat-round {
  0%, 68%, 100% {
    transform: scale(1);
  }
  72% {
    transform: scale(1.12);
  }
  76% {
    transform: scale(1);
  }
  80% {
    transform: scale(1.07);
  }
  86% {
    transform: scale(1);
  }
}

.action-button[data-dm] {
  animation: dm-heartbeat-round 4.4s var(--ease) infinite;
}

/* A press must always win over the idle beat: the animation would otherwise
   keep overwriting the transform that gives the button its give. */
#profileDmButton:active,
.action-button[data-dm]:active {
  animation: none;
}

/* Motion that nobody asked for is exactly the motion to drop first. The
   buttons keep their colour and their shadow — only the beat stops. */
@media (prefers-reduced-motion: reduce) {
  #profileDmButton,
  #profileDmButton::after,
  .action-button[data-dm] {
    animation: none;
  }
}

/* -------------------------------------------------------------- fullscreen */

/* The control only makes sense where the app is a box inside something larger.
   On a phone it already owns the screen, so there is nothing to fill and the
   button would be a lie. Hidden by default; the width query below is the only
   thing that reveals it, and the script removes it entirely when neither
   fullscreen route exists. */
.fullscreen-toggle {
  display: none;
}

.fullscreen-toggle .fullscreen-icon-exit,
body.is-fullscreen .fullscreen-toggle .fullscreen-icon-enter {
  display: none;
}

body.is-fullscreen .fullscreen-toggle .fullscreen-icon-exit {
  display: block;
}

/* Shown wherever filling the screen is a thing a person can do — which is a
   question about the POINTER, not the width.

   It used to be min-width: 720px alone. Telegram Desktop opens a Mini App in a
   window far narrower than that, so on the one surface where the button is
   most useful it only appeared after the window was dragged wider. A control
   that has to be uncovered is a control nobody finds.

   A fine pointer means a mouse, which means a resizable window. Phones report
   a coarse pointer and keep the old behaviour: no button, because the app
   already fills what there is. */
@media (pointer: fine) {
  .fullscreen-toggle:not([hidden]) {
    display: grid;
  }
}

@media (min-width: 720px) {
  .fullscreen-toggle:not([hidden]) {
    display: grid;
  }

  /* Filling the screen has to actually undo the box, or the app just sits in
     the middle of a larger black rectangle — which is what it was already
     doing, only bigger. */
  body.is-fullscreen {
    padding: 0;
  }

  body.is-fullscreen .shell {
    width: 100vw;
    height: 100svh;
    max-height: none;
    min-height: 0;
    border-radius: 0;
    border-inline: 0;
    box-shadow: none;
  }
}

/* The topbar holds three controls now rather than two on the trailing side, so
   the row is given the extra column instead of letting the brand be squeezed. */
@media (min-width: 720px) {
  .topbar {
    grid-template-columns: auto 1fr auto auto;
  }
}


/* ------------------------------------------------------------- write gate */

/* A sheet, not a wall.
   Refusing costs the action, not the visit: the feed keeps scrolling behind
   it, and the fan can dismiss and carry on. The first version covered the
   whole screen on arrival, which is a price charged before anything has been
   shown. */
.ask-write {
  position: absolute;
  inset: 0;
  z-index: 90;
  display: grid;
  align-items: end;
  visibility: hidden;
  pointer-events: none;
}

.ask-write[hidden] {
  display: none;
}

.ask-write.is-open {
  visibility: visible;
  pointer-events: auto;
}

.ask-write-card {
  position: relative;
  z-index: 1;
  padding: 12px 18px 26px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--panel);
  text-align: center;
  animation: sheet-rise var(--t-slow) var(--ease-out) backwards;
}

.ask-write-card h2 {
  margin-top: 6px;
  font-size: 19px;
  font-weight: 900;
}

.ask-write-lead {
  margin-top: 8px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

.ask-write-primary {
  width: 100%;
  margin-top: 18px;
  padding: 14px;
  border-radius: var(--r-md);
  background: var(--hot);
  color: #fff;
  font-size: 16px;
  font-weight: 900;
  box-shadow: 0 10px 26px rgba(255, 27, 141, 0.32);
}

.ask-write-primary:active {
  background: var(--hot-deep);
}

/* Revealed only after a second refusal — two ways out offered before the first
   answer makes the ask look negotiable, and most fans simply say yes. */
.ask-write-secondary {
  width: 100%;
  margin-top: 9px;
  padding: 12px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.09);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
}

.ask-write-secondary[hidden] {
  display: none;
}

/* Quiet on purpose. It has to exist — a sheet with no way out is a wall with
   extra steps — without competing with the answer we are hoping for. */
.ask-write-dismiss {
  width: 100%;
  margin-top: 4px;
  padding: 11px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}
