/* TOLYAN runtime patches —
 * keyboard / black-bar fix on iOS, friend-request toast.
 *
 * This file is loaded BEFORE the bundle's own CSS so we use
 * !important + html.kb-open to win against bundle styles.
 */

/* Belt-and-suspenders: ensure both the document and the app shell paint
   the dark theme so the browser never reveals a different colour while
   the layout viewport is shrinking around the on-screen keyboard. */
html,
body {
  background-color: #09090b;
}

/* When the on-screen keyboard is open on iOS Safari, the layout viewport
   stays at full height and only the *visual* viewport shrinks.  Without
   the rules below the bundled `body { min-height: var(--app-vh) }` and
   `#root { height: var(--app-vh) }` would still paint a chunk of empty
   black space below the chat input — because `min-height: var(--app-vh)`
   makes body grow back up to full layout height while #root stays at
   visual-viewport height, leaving body's background visible underneath.

   Cap everything to the visual viewport's height while the keyboard is
   open and disable the min-height fallback so the chat-input bar stays
   glued right above the keyboard with no black gap. */
html.kb-open {
  height: var(--vv-h, 100dvh) !important;
  max-height: var(--vv-h, 100dvh) !important;
  min-height: 0 !important;
  overflow: hidden;
}

html.kb-open body {
  height: var(--vv-h, 100dvh) !important;
  max-height: var(--vv-h, 100dvh) !important;
  min-height: 0 !important;
  overflow: hidden;
}

html.kb-open #root {
  height: var(--vv-h, 100dvh) !important;
  max-height: var(--vv-h, 100dvh) !important;
  min-height: 0 !important;
  overflow: hidden;
}

/* iOS Safari keeps `position: fixed; inset: 0` sized to the *layout*
   viewport, ignoring the on-screen keyboard.  That makes the
   registration form (and the OTP / SMS code modal) centre vertically
   in the full-screen layout viewport — half the form ends up below
   the keyboard, leaving an empty black band where the password input
   should be.

   Re-pin the bottom of every `fixed inset-0` container to the top of
   the keyboard while it's open, so flex centring lays the form out
   inside the visible area only.  We also flip flex centring to
   start-aligned and add a touch of top padding so the focused field
   never sits right under the status bar. */
html.kb-open .fixed.inset-0 {
  bottom: var(--kb-h, 0px) !important;
}

html.kb-open .fixed.inset-0.flex.items-center {
  align-items: flex-start !important;
  padding-top: max(env(safe-area-inset-top, 0px), 16px) !important;
  padding-bottom: 16px !important;
}

/* Drawers and side-panels that span the full viewport height on iPhone
   should also stop short of the keyboard so their internal scroll
   container can reach the focused field. */
html.kb-open .fixed.inset-y-0,
html.kb-open .fixed.bottom-0 {
  bottom: var(--kb-h, 0px) !important;
}

/* Tailwind's `min-h-screen` resolves to `100dvh` (or `100vh`) which
   on iOS Safari ignores the visual-viewport shrinkage.  When the
   keyboard is open, treat those containers as `min-height: 100%` so
   the scroll container actually matches the visible area. */
html.kb-open .min-h-screen,
html.kb-open .h-screen {
  min-height: 100% !important;
  height: var(--vv-h, 100%) !important;
  max-height: var(--vv-h, 100%) !important;
}

/* While the keyboard is open we don't want anything to add an extra
   safe-area-inset-bottom gap on top of --kb-h — that's where the
   famous «black bar above the keyboard» on iPhone comes from when
   the bundle's chat composer (or any other bottom-pinned bar) keeps
   reserving 34 px for the home indicator that iOS has already hidden
   in favour of the keyboard. */
html.kb-open .pb-safe,
html.kb-open .safe-area-bottom,
html.kb-open [class*="pb-[env"],
html.kb-open [style*="safe-area-inset-bottom"] {
  padding-bottom: 0 !important;
}

/* iOS-only — lock both html and body to the layout viewport's origin
   so Safari can't scroll the layout viewport up to reveal the focused
   input. iOS does this scroll BEFORE visualViewport.resize fires, so
   even with overflow: hidden the page ends up offset by ~hundreds of
   pixels, producing the visible black gap. position: fixed pins both
   elements and the gap goes away. */
@supports (-webkit-touch-callout: none) {
  html.kb-open,
  html.kb-open body {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    right: 0 !important;
    width: 100% !important;
  }
}

/* Scrollable reaction picker injected by patches.js («расширенная
   лента реакций»). The bundle renders a 6-emoji strip;
   we append more and ask the browser to scroll horizontally on touch.
   touch-action: pan-x keeps the long-press handler from hijacking the
   horizontal swipe inside this strip. */
.tolyan-reactions-strip {
  overflow-x: auto !important;
  overflow-y: hidden !important;
  flex-wrap: nowrap !important;
  -webkit-overflow-scrolling: touch !important;
  touch-action: pan-x !important;
  scrollbar-width: none !important;
  scroll-snap-type: x proximity;
}
.tolyan-reactions-strip::-webkit-scrollbar {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
}
.tolyan-reactions-strip > button {
  flex: 0 0 auto !important;
  scroll-snap-align: start;
}

/* One-shot «Включить уведомления» banner shown by patches.js when
   Notification.permission is still 'default'. Sits above the iOS
   safe area and the PWA install banner so it doesn't fight for
   z-index with anything in the bundle. */
#tolyan-push-banner {
  position: fixed;
  left: max(env(safe-area-inset-left, 0px), 8px);
  right: max(env(safe-area-inset-right, 0px), 8px);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
  z-index: 2147483646;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 14px;
  background: rgba(24, 24, 27, 0.96);
  color: #fafafa;
  border: 1px solid rgba(63, 63, 70, 0.7);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  font: 500 14px/1.2 Inter, system-ui, -apple-system, sans-serif;
}
#tolyan-push-banner .tolyan-push-banner-text {
  flex: 1;
  min-width: 0;
}
#tolyan-push-banner .tolyan-push-banner-title {
  font-weight: 600;
  font-size: 14px;
  margin-bottom: 2px;
}
#tolyan-push-banner .tolyan-push-banner-body {
  font-size: 12px;
  color: #d4d4d8;
  line-height: 1.3;
}
#tolyan-push-banner button {
  flex: 0 0 auto;
  height: 32px;
  padding: 0 12px;
  border-radius: 999px;
  border: 0;
  font: inherit;
  cursor: pointer;
}
#tolyan-push-banner .tolyan-push-banner-enable {
  background: #3b82f6;
  color: #fff;
  font-weight: 600;
}
#tolyan-push-banner .tolyan-push-banner-close {
  background: transparent;
  color: #a1a1aa;
  width: 32px;
  padding: 0;
}

/* Disable iOS callout / selection magnifier on long-press inside chat
   areas so our synthetic context-menu UX takes over cleanly.
   The bundle marks each message with `id="msg-..."`. */
@media (hover: none) and (pointer: coarse) {
  [id^="msg-"],
  .bubble-sent,
  .bubble-received,
  .message-bubble,
  .chat-message {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}

/* Message bubble hardening — normalise box-sizing, prevent transparent
   borders from disappearing on certain GPUs, and avoid sub-pixel
   rendering glitches that occasionally make the rounded corners on a
   bubble look clipped or jagged on retina displays. */
.bubble-sent,
.bubble-received {
  box-sizing: border-box;
  word-break: break-word;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  /* Promote to its own layer so border + background composite
     consistently across iOS Safari, Chrome on Android and desktop
     Firefox.  Without this, .bubble-received's 1px translucent border
     occasionally renders as a hairline gap on high-DPR screens. */
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

.bubble-received {
  /* The bundle declares this border as `1px solid #ffffff14` (~8% white).
     Bump alpha slightly on top of the existing rule so the bubble's
     edge is reliably visible against the dark chat background, while
     still looking subtle. */
  border-color: rgba(255, 255, 255, 0.12);
}

/* Friend rows in the settings → friends sub-page should look clickable
   now that we open the chat on click. */
.group\/friend {
  cursor: pointer;
}

/* ──────────────────────────────────────────────────────────────
 * Friend-request toast — slides down from the top of the screen
 * for ~5 seconds with Принять / Отменить buttons.
 * ────────────────────────────────────────────────────────────── */
#tolyan-toast-container {
  position: fixed;
  top: max(env(safe-area-inset-top, 0px), 8px);
  left: 0;
  right: 0;
  z-index: 2147483600; /* above almost everything */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  padding: 0 12px;
}

.tolyan-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  max-width: 420px;
  padding: 10px 12px;
  border-radius: 16px;
  background: rgba(24, 24, 27, 0.96);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.45),
    0 2px 6px rgba(0, 0, 0, 0.3);
  color: #fafafa;
  font-family: Inter, system-ui, -apple-system, sans-serif;
  transform: translateY(-120%);
  opacity: 0;
  transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1),
              opacity 0.32s ease;
}

.tolyan-toast.tolyan-toast-show {
  transform: translateY(0);
  opacity: 1;
}

.tolyan-toast.tolyan-toast-hide {
  transform: translateY(-120%);
  opacity: 0;
}

.tolyan-toast.tolyan-toast-busy {
  opacity: 0.85;
}

.tolyan-toast-avatar {
  width: 40px;
  height: 40px;
  border-radius: 9999px;
  object-fit: cover;
  flex-shrink: 0;
  background: linear-gradient(135deg, #6366f1, #a855f7);
}

.tolyan-toast-avatar-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 16px;
  color: #fff;
}

.tolyan-toast-info {
  flex: 1 1 auto;
  min-width: 0;
}

.tolyan-toast-title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tolyan-toast-sub {
  font-size: 12px;
  line-height: 1.2;
  color: rgba(250, 250, 250, 0.65);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tolyan-toast-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.tolyan-toast-btn {
  appearance: none;
  border: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 12px;
  border-radius: 10px;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.1s ease, opacity 0.15s ease;
  white-space: nowrap;
}

.tolyan-toast-btn:active {
  transform: scale(0.96);
}

.tolyan-toast-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.tolyan-toast-btn-accept {
  background: #3b82f6;
  color: #fff;
}

.tolyan-toast-btn-accept:hover:not(:disabled) {
  background: #2563eb;
}

.tolyan-toast-btn-decline {
  background: rgba(255, 255, 255, 0.08);
  color: #fafafa;
}

.tolyan-toast-btn-decline:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.14);
}

/* ════════════════════════════════════════════════════════════════
 * Responsive audit — universal hardening across all viewports.
 * Specific fixes per viewport size are at the bottom.
 * ════════════════════════════════════════════════════════════════ */

/* ─── A. Block accidental horizontal scrolling everywhere. Without
 * this any rogue `w-screen` / `100vw` element pushes the page side-
 * ways on iOS Safari and small Android browsers. */
html,
body,
#root {
  overflow-x: clip;
  max-width: 100vw;
}

/* The chat container and the sidebar can sometimes overflow due to
 * absolutely-positioned popovers; clip them as well so they never
 * cause a horizontal scrollbar. */
#root > * {
  max-width: 100vw;
}

/* ─── B. User-supplied strings (usernames, message text, group names,
 * URLs) should always wrap rather than blow out their container.  The
 * bundle declares `word-break: break-word` only on a couple of
 * elements; we extend it.
 *
 * Use `break-word` rather than `anywhere` on the BUBBLE itself:
 * `anywhere` shrinks the intrinsic min-content width of the bubble to
 * a single character, which collapses message bubbles laid out inside
 * flex/grid parents (the chat list does this on desktop) — every
 * character ends up on its own line.  `break-word` only kicks in on
 * actual overflow and does not touch min-content sizing. */
.bubble-sent,
.bubble-received,
.tolyan-toast-title,
.tolyan-toast-sub {
  overflow-wrap: break-word;
  word-break: normal;
}

/* The text <p> INSIDE the bubble, however, must use `anywhere` so a
 * pasted unbreakable string ("dddddd…dddd") wraps to the next line
 * instead of overflowing past the bubble edge and clipping the
 * trailing timestamp.  Combined with `min-width: 0` this lets the
 * flex algorithm shrink the paragraph below its natural min-content
 * width and break the long token at any character.  The bubble's own
 * width is already pinned by `width: fit-content; max-width: 100%`
 * (B-ter) plus the F-section cap, so allowing the inner <p> to break
 * doesn't trigger the per-character wrapping bug — the bubble width
 * is no longer driven by the paragraph's min-content.
 *
 * NB: the bundle marks each message wrapper with `id="msg-..."`, NOT
 * `data-message-id="..."` — match by `.bubble-sent`/`bubble-received`
 * descendants directly so the rules apply regardless of attribute
 * naming changes upstream. */
.bubble-sent p,
.bubble-received p,
.bubble-sent span,
.bubble-received span {
  overflow-wrap: anywhere;
  word-break: normal;
}
.bubble-sent p,
.bubble-received p {
  min-width: 0;
}

/* ─── B-bis. Tailwind v3 → v4 migration shim.
 *
 * The React bundle is compiled with Tailwind v4 but its source still
 * references the old Tailwind v3 utility class names `flex-shrink-0`,
 * `flex-grow` and `sr-only`.  In Tailwind v4 the first two were
 * renamed to `shrink-0` / `grow`.  Result: the JSX emits 60+
 * instances of `flex-shrink-0` but no `.flex-shrink-0{flex-shrink:0}`
 * rule exists in the compiled stylesheet, and any element that
 * relied on those classes silently inherits its parent's defaults.
 *
 * Polyfill the missing v3 class names so existing JSX keeps working
 * without touching the bundled JS. */
.flex-shrink-0 {
  flex-shrink: 0;
}
.flex-grow {
  flex-grow: 1;
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── B-ter. Bubble width must include trailing inline timestamp.
 *
 * The text bubble renders as
 *   <div .bubble-{sent,received} px-4 py-2.5 overflow-hidden>
 *     <div .flex .items-end .gap-2>
 *       <p   .flex-1 …>Message text</p>
 *       <span .flex-shrink-0 …>00:05 ✓</span>
 *     </div>
 *   </div>
 *
 * inside a flex-col wrapper whose only width constraint is
 * `max-w-[65%]`.  Without an explicit width the bubble's auto width
 * is resolved through the flex max-content algorithm.  Tailwind v4's
 * `.flex-1{flex:1}` expands to `flex:1 1 0%` and Chrome treats that
 * 0% basis as a definite zero when the *flex container's* main size
 * is itself being computed (the bubble has auto width).  The text
 * <p>'s max-content contribution becomes 0, so the bubble's auto
 * width collapses to roughly `padding + gap + timestamp`, which is
 * narrower than the rendered "text + gap + timestamp" content row.
 * The trailing characters of the timestamp / read receipt then end
 * up under the bubble's `overflow:hidden` clip and disappear.
 *
 * `width: fit-content` opts the bubble out of that flex-cross-size
 * algorithm — it computes its width via the regular fit-content
 * formula (clamp(min-content, max-content, available-size)) — and
 * `max-width: 100%` keeps it inside the wrapper's `max-w-[65%]`.
 * Long messages still wrap normally because max-content is capped
 * by the wrapper's width. */
.bubble-sent,
.bubble-received {
  width: fit-content;
  max-width: 100%;
}

/* ─── C. Tap targets — Apple / Material both recommend ≥40 px on
 * touch surfaces.  The bundle ships some 32 px icon buttons in chat
 * headers and dropdowns; bump only the icon-only ones to 40 px on
 * coarse pointers, leaving text buttons untouched. */
@media (hover: none) and (pointer: coarse) {
  button.h-8.w-8,
  button.h-9.w-9,
  a.h-8.w-8 {
    min-width: 40px;
    min-height: 40px;
  }
}

/* ─── D. Forms — keep input font-size ≥ 16 px on iOS so the browser
 * doesn't zoom into the field on focus.  This applies only to
 * touch / coarse-pointer devices to keep desktop inputs compact. */
@media (hover: none) and (pointer: coarse) {
  input[type='text'],
  input[type='email'],
  input[type='password'],
  input[type='search'],
  input[type='tel'],
  input[type='url'],
  input[type='number'],
  textarea,
  select {
    font-size: max(16px, 1em);
  }
}

/* ─── E. Safe-area insets for iPhone notch / home-indicator and
 * Android display cutouts.  The bundle's chat-input footer sometimes
 * sits flush with the bottom edge — add an inset-bottom padding so
 * the home-indicator doesn't overlap the mic / send buttons. */
@supports (padding: env(safe-area-inset-bottom)) {
  /* Sticky/fixed bottom toolbars (chat input, mobile tab bar). */
  .fixed.bottom-0,
  .sticky.bottom-0,
  [class*='bottom-0'][class*='fixed'],
  [class*='bottom-0'][class*='sticky'] {
    padding-bottom: max(
      env(safe-area-inset-bottom, 0px),
      var(--tolyan-safe-bottom, 0px)
    );
  }
}

/* ─── F. Message bubbles cap on huge screens so reading line-length
 * stays comfortable.  Tailwind's `max-w-[...]` on bubbles is usually
 * 70–75 % of container; on a 2560 px monitor that becomes 1900+ px
 * which is unreadable.
 *
 * NB: the bubble's containing block is the inner
 * `.max-w-[65%].flex.flex-col` wrapper, NOT the viewport, so a `70%`
 * value used here resolves against the wrapper width.  When the
 * wrapper has shrink-to-fit width (e.g. for a short message) that
 * 70% becomes smaller than the bubble's own intrinsic content width
 * and clips trailing characters of the timestamp / read-receipt.
 *
 * Use vw-based units instead so the cap is measured against the
 * actual viewport, which is what the original intent was.  For
 * normal message rows this is a non-binding cap; only on very wide
 * monitors does the 720px clamp kick in. */
@media (min-width: 1280px) {
  .bubble-sent,
  .bubble-received {
    max-width: min(720px, 70vw);
  }
}

/* ─── G. Cap absolute popovers and modals to viewport.  The bundle
 * sometimes places dropdown menus with intrinsic widths; on 320 px
 * iPhone SE that overflows.  Clamp universally. */
[role='dialog'],
[role='menu'],
[role='listbox'],
[role='alertdialog'] {
  max-width: calc(100vw - 16px);
  max-height: calc(100dvh - 16px);
}

/* ─── H. Avatar rings, initials and other circular elements should
 * never become ovals — preserve aspect ratio in flex layouts. */
img.rounded-full,
[class*='rounded-full'].h-10,
[class*='rounded-full'].h-12,
[class*='rounded-full'].h-14,
[class*='rounded-full'].h-16 {
  flex-shrink: 0;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* ─── I. Very narrow screens (≤360 px — iPhone SE, Galaxy S5) ─────
 * Compact chat header so all icons (search / call / video / menu)
 * fit alongside the avatar + name. */
@media (max-width: 360px) {
  /* Tighten generic horizontal padding inside chat header. */
  header.h-14,
  div.h-14[class*='border-b'] {
    padding-left: 8px !important;
    padding-right: 8px !important;
  }
  /* Compress gap between header buttons. */
  header.h-14 > div[class*='gap-'],
  div.h-14[class*='border-b'] > div[class*='gap-'] {
    gap: 4px !important;
  }
  /* Allow the chat title to truncate aggressively rather than push
   * the right-side controls off-screen. */
  header.h-14 .truncate,
  div.h-14[class*='border-b'] .truncate {
    max-width: 100%;
  }
  /* Toast at the very top reserves less side padding. */
  #tolyan-toast-container {
    padding: 0 6px;
  }
}

/* ─── J. Landscape mobile (height ≤ 480 px, width > height) ───────
 * Compact the chat header to leave more room for messages. */
@media (max-height: 480px) and (orientation: landscape) {
  header.h-14,
  div.h-14[class*='border-b'] {
    height: 44px !important;
  }
  /* Toast goes top-right corner instead of full-width strip. */
  #tolyan-toast-container {
    left: auto;
    right: 8px;
    width: min(360px, 100vw - 16px);
    align-items: flex-end;
  }
  .tolyan-toast {
    max-width: 360px;
  }
}

/* ─── K. Tablet portrait (≥ 768 px) — the bundle switches between
 * one-column and two-column layouts at the `md:` breakpoint
 * (≥768).  Make the chat-list sidebar a comfortable width rather
 * than expanding to fill 50 % of the viewport. */
@media (min-width: 768px) and (max-width: 1023px) {
  /* The bundle uses `md:w-1/3` and `md:w-2/3` patterns — keep them. */
}

/* ─── L. Wide desktop (≥ 1536 px) — the bundle ships a fixed 340 px
 * chat-list sidebar (md:w-[340px]) which is comfortable on every
 * size up to ultrawide.  We don't cap the conversation column —
 * message bubbles are already capped (rule F).  Nothing to do here.
 */

/* ─── M. Reduce-motion users — let CSS animations honour the OS
 * preference even where the bundle forgot. */
@media (prefers-reduced-motion: reduce) {
  .tolyan-toast {
    transition: opacity 0.2s ease;
    transform: none !important;
  }
  .tolyan-toast.tolyan-toast-show {
    transform: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────────
 * Mobile layout guard 0.4.5 — conservative patch.
 *
 * Keep the compiled app's original mobile visuals intact. Only add
 * guardrails that prevent iOS/Android viewport overflow, clipped
 * dialogs/popovers, undersized icon taps, and bottom UI collisions.
 */
@media (max-width: 767px) {
  html,
  body,
  #root {
    width: 100%;
    max-width: 100%;
    overflow-x: hidden;
    overscroll-behavior-x: none;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }

  #root,
  #root * {
    min-width: 0;
    box-sizing: border-box;
  }

  header.h-14,
  div.h-14[class*='border-b'] {
    padding-left: max(env(safe-area-inset-left, 0px), 8px) !important;
    padding-right: max(env(safe-area-inset-right, 0px), 8px) !important;
  }

  button[title='Меню'],
  button[title='Новый чат'],
  button[aria-label='Скрыть'],
  button[aria-label='Toggle password visibility'],
  .p-1\.5.rounded-lg,
  button[class*='p-1.5'][class*='rounded-lg'] {
    min-width: 40px;
    min-height: 40px;
  }

  #root .md\:w-\[340px\],
  #root .w-\[340px\],
  #root .w-\[360px\],
  #root .w-\[380px\],
  #root .w-\[420px\],
  #root .w-\[min\(80vw\,340px\)\] {
    max-width: 100vw !important;
  }

  [id^="msg-"] {
    max-width: 100%;
    padding-left: max(env(safe-area-inset-left, 0px), 8px);
    padding-right: max(env(safe-area-inset-right, 0px), 8px);
    scroll-margin-block: 72px 96px;
  }

  [id^="msg-"] > div,
  [id^="msg-"] .max-w-\[65\%\],
  [id^="msg-"] .max-w-\[80\%\],
  .bubble-sent,
  .bubble-received {
    max-width: min(82vw, 560px) !important;
  }

  textarea,
  input {
    scroll-margin-bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
  }

  textarea {
    max-height: min(34dvh, 160px);
  }

  [role='dialog'],
  [role='alertdialog'] {
    width: min(100vw - 18px, 440px) !important;
    max-width: calc(100vw - 18px) !important;
    max-height: calc(var(--vv-h, 100dvh) - 18px) !important;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
  }

  [role='menu'],
  [role='listbox'] {
    max-width: calc(100vw - 14px) !important;
    max-height: min(70dvh, calc(var(--vv-h, 100dvh) - 24px)) !important;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
  }

  #tolyan-toast-container {
    padding-left: max(env(safe-area-inset-left, 0px), 8px);
    padding-right: max(env(safe-area-inset-right, 0px), 8px);
  }

  .tolyan-toast {
    max-width: calc(100vw - 16px);
  }

  #tolyan-push-banner {
    left: max(env(safe-area-inset-left, 0px), 8px);
    right: max(env(safe-area-inset-right, 0px), 8px);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
    max-width: none;
  }

  html body:has(#root input[placeholder^="Найти пользователя"]) #tolyan-push-banner,
  html body:has(#root [role='dialog']) #tolyan-push-banner,
  html body:has(#root [role='alertdialog']) #tolyan-push-banner {
    display: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────────
 * N. Deleted-user avatar — when an account has been removed by the
 *    admin the backend rewrites its avatar to /deleted-avatar.svg
 *    and its display name to «Пользователь удалён». Render those
 *    avatars as a flat grey square so the user's previous photo
 *    is never even briefly visible (defence-in-depth in case a
 *    cached avatar URL is still referenced somewhere). */
img[src$="/deleted-avatar.svg"],
img[src*="/deleted-avatar.svg?"] {
  background: #3a3a3a !important;
  filter: grayscale(1) brightness(.85);
}
.tolyan-deleted-user-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: #3a3a3a;
  color: #c8c8c8;
  font-size: 22px;
  border-radius: inherit;
}
.tolyan-deleted-user-name {
  font-style: italic;
  color: #94a3b8 !important;
}

/* ──────────────────────────────────────────────────────────────────
 * O. Delete-chat confirmation — on desktop (≥ 768 px) the bundle
 *    sometimes renders the dialog backdrop full-bleed and the modal
 *    inflates past its declared `max-w-[360px]` because of viewport
 *    units in long Russian translation strings. Force the dialog
 *    box to stay compact and centred. The `.tolyan-confirm-modal`
 *    class is added by patches.js when it detects the bundle's
 *    confirm dialog. */
@media (min-width: 768px) {
  .tolyan-confirm-modal,
  div[role="dialog"][aria-modal="true"][class*="max-w-[360px]"] {
    width: auto !important;
    max-width: 360px !important;
    min-width: 280px !important;
    margin: auto !important;
    inset: auto !important;
    box-shadow: 0 25px 60px -15px rgba(0,0,0,.6) !important;
  }
  .tolyan-confirm-backdrop {
    align-items: center !important;
    justify-content: center !important;
  }
}

/* ──────────────────────────────────────────────────────────────────
 * P. Admin panel tabs — injected by patches.js. */
.tolyan-admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.tolyan-admin-tab {
  appearance: none;
  border: 1px solid transparent;
  background: rgba(255,255,255,0.03);
  color: #cbd5e1;
  padding: 8px 14px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.tolyan-admin-tab:hover {
  background: rgba(255,255,255,0.06);
  color: #fff;
}
.tolyan-admin-tab[data-active="true"] {
  background: rgba(139, 92, 246, 0.18);
  border-color: rgba(139, 92, 246, 0.5);
  color: #c4b5fd;
}
.tolyan-admin-section {
  border: 1px solid rgba(255,255,255,0.06);
  background: rgba(30, 41, 59, 0.4);
  border-radius: 16px;
  padding: 20px;
  margin-bottom: 16px;
}
.tolyan-admin-section h3 {
  font-size: 16px;
  font-weight: 700;
  color: #fff;
  margin: 0 0 4px;
}
.tolyan-admin-section p.tolyan-admin-section-desc {
  font-size: 13px;
  color: #94a3b8;
  margin: 0 0 14px;
}
.tolyan-admin-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.tolyan-admin-row > label {
  flex: 0 0 220px;
  color: #cbd5e1;
  font-size: 13px;
}
.tolyan-admin-row > input,
.tolyan-admin-row > select {
  flex: 1;
  min-width: 120px;
  background: rgba(15, 23, 42, 0.6);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  color: #fff;
  padding: 8px 12px;
  font-size: 13px;
  outline: none;
}
.tolyan-admin-row > input:focus,
.tolyan-admin-row > select:focus {
  border-color: rgba(139, 92, 246, 0.6);
}
.tolyan-admin-button {
  appearance: none;
  border: none;
  background: linear-gradient(90deg, #8b5cf6, #a855f7);
  color: white;
  padding: 8px 16px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.1s ease, opacity 0.15s ease;
}
.tolyan-admin-button:hover { transform: translateY(-1px); }
.tolyan-admin-button[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; }
.tolyan-admin-button.tolyan-admin-button-danger {
  background: rgba(239,68,68,0.15);
  color: #fca5a5;
  border: 1px solid rgba(239,68,68,0.35);
}
.tolyan-admin-button.tolyan-admin-button-secondary {
  background: rgba(255,255,255,0.06);
  color: #cbd5e1;
  border: 1px solid rgba(255,255,255,0.08);
}
.tolyan-pack-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.tolyan-pack-card {
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 12px;
  padding: 12px;
  background: rgba(15, 23, 42, 0.45);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tolyan-pack-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.tolyan-pack-card-name {
  font-weight: 700;
  color: #fff;
  font-size: 13px;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
.tolyan-pack-card-count {
  font-size: 11px;
  color: #94a3b8;
}
.tolyan-pack-card-preview {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}
.tolyan-pack-card-preview img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: 6px;
  background: rgba(255,255,255,0.03);
}
.tolyan-admin-message {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 12px;
}
.tolyan-admin-message.is-success {
  background: rgba(16,185,129,0.12);
  color: #6ee7b7;
  border: 1px solid rgba(16,185,129,0.3);
}
.tolyan-admin-message.is-error {
  background: rgba(239,68,68,0.12);
  color: #fca5a5;
  border: 1px solid rgba(239,68,68,0.3);
}
.tolyan-checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #cbd5e1;
  font-size: 13px;
  margin-bottom: 8px;
}
.tolyan-pack-tabs {
  scrollbar-width: thin;
}

/* ──────────────────────────────────────────────────────────────────
 * Q. Chat-theme «cursor spotlight» animations (Неон / Аврора / Кибер /
 *    Стекло / Полночь / Закат).
 *
 *    The bundle paints a radial-gradient on a full-viewport :before
 *    pseudo-element of the chat container and updates `--mouse-x` /
 *    `--mouse-y` on every `mousemove`.  It also sets
 *    `transition: background .1–.15s ease-out` on the same element.
 *
 *    On desktop the cursor fires `mousemove` at 60–120 Hz, so each
 *    frame schedules a fresh full-area `background` transition on top
 *    of the previous one.  Browsers either:
 *      – interpolate radial-gradient stops between the old and new
 *        position (Chrome / Edge), producing visible smearing /
 *        ghosting trails, or
 *      – queue overlapping transitions and drop frames, producing
 *        the «laggy / glitchy» feel the bundle calls «animation».
 *
 *    Touch devices barely fire `mousemove` so the bug is desktop-only.
 *
 *    Fix: drop the `transition` from these pseudo-elements so the
 *    gradient just follows the cursor 1:1, and promote them to their
 *    own compositor layer with `will-change: background-position` so
 *    paints stay cheap.  The `.chat-theme-glass:before` rule uses
 *    `left` / `top` (not `background`) so we keep its smooth motion
 *    transition but cap it at 0.25s so it doesn't lag visibly behind
 *    the cursor on fast sweeps. */
.chat-theme-neon::before,
.chat-theme-aurora::before,
.chat-theme-cyber::before,
.chat-theme-void::before,
.chat-theme-sunset::after {
  transition: none !important;
  will-change: background;
  /* `transform: translateZ(0)` forces a separate compositor layer so
     the gradient repaint never touches the chat list paint tree. */
  transform: translateZ(0);
}

.chat-theme-glass::before {
  /* Keep the glass theme's smooth easing but make sure it's snappy
     enough on desktop to not visibly lag behind the cursor. */
  transition: left 0.25s cubic-bezier(.2,.8,.2,1),
              top 0.25s cubic-bezier(.2,.8,.2,1) !important;
  will-change: left, top;
}

/* Honour `prefers-reduced-motion` for the cursor-spotlight effect — it
 * is a decorative animation and should not move at all when the user
 * has asked the OS to reduce motion. */
@media (prefers-reduced-motion: reduce) {
  .chat-theme-neon::before,
  .chat-theme-aurora::before,
  .chat-theme-cyber::before,
  .chat-theme-void::before,
  .chat-theme-glass::before,
  .chat-theme-sunset::after {
    background: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────
 * 21. Notification bell (desktop only).
 *
 * Bell sits in the top-right corner of the viewport.  Popover
 * drops down beneath it.  Both are hidden on mobile widths so
 * the mobile sidebar / chat header keep their native layout.
 * Colours match the bundle's dark theme (zinc-900 / zinc-700,
 * primary indigo) without needing the bundle's Tailwind classes.
 * ────────────────────────────────────────────────────────────── */
#tolyan-notif-bell {
  position: fixed;
  top: 14px;
  right: 18px;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(24, 24, 27, 0.85);
  color: #e4e4e7;
  border-radius: 999px;
  cursor: pointer;
  z-index: 2147483600;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
#tolyan-notif-bell:hover {
  background: rgba(39, 39, 42, 0.95);
}
#tolyan-notif-bell:active {
  transform: scale(0.96);
}
#tolyan-notif-bell:focus-visible {
  outline: 2px solid #818cf8;
  outline-offset: 2px;
}
#tolyan-notif-bell svg {
  width: 22px;
  height: 22px;
  display: block;
}
#tolyan-notif-bell.tolyan-notif-has-unread svg {
  color: #fbbf24;
}
#tolyan-notif-bell-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  font-family: 'Inter', system-ui, sans-serif;
  border: 2px solid rgba(24, 24, 27, 0.95);
  box-sizing: content-box;
  pointer-events: none;
}
#tolyan-notif-bell-badge[hidden] {
  display: none !important;
}

#tolyan-notif-popover {
  position: fixed;
  top: 62px;
  right: 18px;
  width: 320px;
  max-height: 70vh;
  background: rgba(24, 24, 27, 0.98);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  color: #e4e4e7;
  z-index: 2147483600;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: 'Inter', system-ui, sans-serif;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.12s ease, transform 0.12s ease;
}
#tolyan-notif-popover[hidden] {
  display: none !important;
}
#tolyan-notif-popover.tolyan-notif-popover-open {
  opacity: 1;
  transform: translateY(0);
}
.tolyan-notif-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.tolyan-notif-title {
  font-size: 14px;
  font-weight: 600;
  color: #fafafa;
}
.tolyan-notif-close {
  background: transparent;
  border: 0;
  color: #a1a1aa;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.tolyan-notif-close:hover {
  color: #fafafa;
}
.tolyan-notif-list {
  list-style: none;
  margin: 0;
  padding: 6px 0;
  overflow-y: auto;
  flex: 1 1 auto;
}
.tolyan-notif-item {
  margin: 0;
  padding: 0;
}
.tolyan-notif-item-btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: transparent;
  border: 0;
  color: inherit;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.tolyan-notif-item-btn:hover,
.tolyan-notif-item-btn:focus-visible {
  background: rgba(99, 102, 241, 0.12);
  outline: none;
}
.tolyan-notif-avatar {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  border-radius: 999px;
  object-fit: cover;
  background: #3f3f46;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fafafa;
  font-size: 14px;
  font-weight: 600;
}
.tolyan-notif-avatar-placeholder {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
.tolyan-notif-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.tolyan-notif-name {
  font-size: 13px;
  font-weight: 600;
  color: #fafafa;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tolyan-notif-preview {
  font-size: 12px;
  color: #a1a1aa;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tolyan-notif-count {
  flex: 0 0 auto;
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  border-radius: 999px;
  background: #6366f1;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 22px;
  text-align: center;
}
.tolyan-notif-empty {
  padding: 18px 14px;
  text-align: center;
  font-size: 13px;
  color: #a1a1aa;
}

/* Hide the bell + its popover on mobile widths — PWA / mobile users
 * see notifications via the bundled mobile sidebar instead. */
@media (max-width: 768px) {
  #tolyan-notif-bell,
  #tolyan-notif-popover {
    display: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────
 * 22. PWA — iOS install banner.
 *
 * iOS Safari has no programmatic install prompt; we show a small
 * sticky banner at the bottom of the viewport so first-time
 * visitors discover Share → На экран «Домой».  Hidden once the
 * user dismisses it (state persisted for 30 days in localStorage).
 * ────────────────────────────────────────────────────────────── */
#tolyan-pwa-ios-banner {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: max(12px, env(safe-area-inset-bottom));
  z-index: 2147483600;
  background: rgba(15, 18, 36, 0.95);
  border: 1px solid rgba(99, 102, 241, 0.35);
  border-radius: 14px;
  box-shadow: 0 14px 38px rgba(0, 0, 0, 0.55);
  color: #fafafa;
  font-family: 'Inter', system-ui, sans-serif;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: tolyanPwaBannerIn 0.35s cubic-bezier(.2, .8, .2, 1) both;
}
@keyframes tolyanPwaBannerIn {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}
.tolyan-pwa-banner-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
}
.tolyan-pwa-banner-icon {
  width: 42px;
  height: 42px;
  flex: 0 0 42px;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.45);
}
.tolyan-pwa-banner-text {
  flex: 1 1 auto;
  min-width: 0;
}
.tolyan-pwa-banner-title {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  background: linear-gradient(90deg, #67e8f9, #818cf8);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.tolyan-pwa-banner-body {
  margin-top: 4px;
  font-size: 12px;
  line-height: 1.4;
  color: #d4d4d8;
}
.tolyan-pwa-banner-body b {
  color: #fafafa;
  font-weight: 600;
}
.tolyan-pwa-banner-share {
  display: inline-block;
  vertical-align: -2px;
  margin: 0 1px;
  color: #67e8f9;
}
.tolyan-pwa-banner-close {
  flex: 0 0 32px;
  width: 32px;
  height: 32px;
  background: transparent;
  border: 0;
  color: #a1a1aa;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  border-radius: 999px;
}
.tolyan-pwa-banner-close:hover,
.tolyan-pwa-banner-close:focus-visible {
  background: rgba(255, 255, 255, 0.08);
  color: #fafafa;
  outline: none;
}

/* Desktop never sees the iOS banner. */
@media (min-width: 769px) {
  #tolyan-pwa-ios-banner {
    display: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────
 * 23. Firefox <img> remount flicker mitigation.
 *
 * Firefox briefly paints a blank rectangle when an <img> element
 * is unmounted and immediately re-mounted with the same cached
 * src (Chrome keeps the decoded bitmap in memory across remounts
 * so this never shows).  Promote every <img> to its own
 * compositor layer in Firefox only so the previously-painted
 * bitmap is preserved.
 * ────────────────────────────────────────────────────────────── */
@-moz-document url-prefix() {
  img {
    backface-visibility: hidden;
    transform: translateZ(0);
    image-rendering: auto;
  }
}
