/* 03-chat.css — part 03/10 of the ordered bp bundle (the former single bp.css,
   split by surface; the content is UNREORDERED). ORDER IS LOAD-BEARING: cascade ties
   resolve by document order, so hosts must link 01→10 in exactly this sequence —
   CssHygieneTests pins both the list and the order in both hosts. */

/* ── Чат (/chat) inbox pane — Task 3, wave 2, Чат.dc.html + Чат тьютора.dc.html ──────────────
   Restyles the inbox sidebar (ChatInbox.razor + MessageSearchResults.razor) and the desktop
   no-room-selected placeholder (ChatPage.razor). The conversation pane (rp-convo-, rp-msg- and
   rp-composer- prefixed rules, below) was restyled by Task 4, which also retired chat.css outright.
   Avatars use .rp-chat-avatar* — Task 4 repointed ChatMessage/ChatConversation onto the SAME
   classes (via a size modifier, .rp-msg-avatar, for the smaller 34px in-bubble avatar), so a
   room's avatar looks identical in the inbox row, the conversation header, and message bubbles.
   .rp-chat-inbox is ChatInbox's root; chat.css's mobile show/hide rule now lives on
   .rp-chat-shell/.rp-chat-shell--show-convo (below, Task 4). */
.rp-chat-inbox {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--card);
  border-right: 1px solid var(--border);
}

.rp-chat-head {
  padding: 14px 14px 10px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.rp-chat-head-row {
  display: flex;
  /* Was baseline (text-only row); the tutor's «Объявление» trigger button (Task 5) needs its own
     box vertically centered against the title text, so switch to center — a plain text sibling
     (rp-chat-unread) looks the same under either alignment. */
  align-items: center;
  /* wrap: at 320px the tutor's row (title + help icon + unread badge + the flex-shrink:0
     «Объявление» button) doesn't fit on one line. Without wrap, the shrink pressure has nowhere
     to go but into rp-chat-unread's own text (no other item can shrink below its content), so the
     two-word "N непрочитано" badge split itself mid-phrase across two lines. Letting the row wrap
     drops the whole button to its own line instead — the badge fix below stops it splitting even
     while still squeezed on line 1 pre-wrap. */
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

.rp-chat-title { font-size: 15px; font-weight: 700; }
/* nowrap: see the flex-wrap comment above — this is what actually stops "N непрочитано" from
   breaking between the two words once the row can't fit everything on one line. */
.rp-chat-unread { font-size: 12px; color: var(--muted-foreground); white-space: nowrap; }

/* «Написать ученику» — the tutor-only header action that opens NewDirectChatDialog. Same box as
   the sibling .rp-anncmp-trigger (32px, flex-shrink:0 so the row wraps it whole rather than
   squeezing it) but OUTLINE instead of filled: two solid primary buttons in one header read as
   competing, and neither is the page's main job. */
.rp-chat-newdm-trigger {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--foreground);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
.rp-chat-newdm-trigger:hover { background: var(--secondary); }

.rp-chat-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 11px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--background);
  color: var(--muted-foreground);
  margin-bottom: 10px;
}

.rp-chat-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 13px;
  color: var(--foreground);
  outline: none;
}

.rp-chat-search-clear {
  display: inline-flex;
  flex-shrink: 0;
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--muted-foreground);
  cursor: pointer;
}
.rp-chat-search-clear:hover { color: var(--foreground); }

/* The segmented control sits in a fixed 320px sidebar regardless of viewport width — unlike
   RpSegmentedTabs' other (full page-width) consumers, stretch every tab to fill it. The base
   .rp-seg-tab's 7px 18px padding (pattern 13, above) left no room for 3 columns at this width —
   the longest label («Непрочитанные») pushed the pill to 65/143/70px and overflowed at ≥768px too.
   Mirror Чат.dc.html's own tab style exactly (flex:1; padding:6px 0; font-size:12.5px) instead —
   same vertical-only-padding + smaller-font recipe already used by the 640px breakpoint below.
   Measured against the real self-hosted Inter font at the sidebar's actual 320px-14px×2=292px
   width: «Все»/«Непрочитанные»/«Объявления» render at ~91/99/91px — short of a literal equal
   third each (flex items won't shrink below their text's min-content width), but every label
   fits on one line with room to spare, matching the mockup's own (non-pixel-equal) result. */
.rp-chat-head .rp-seg { width: 100%; }
.rp-chat-head .rp-seg-tab {
  flex: 1;
  padding: 6px 0;
  font-size: 12.5px;
}

.rp-chat-list { flex: 1; overflow-y: auto; padding: 8px; }

.rp-chat-empty {
  padding: 24px 12px;
  text-align: center;
  font-size: 13px;
  color: var(--muted-foreground);
}

.rp-chat-group-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 8px 4px;
  color: var(--muted-foreground);
  cursor: pointer;
}
.rp-chat-group-head > svg { flex-shrink: 0; }
.rp-chat-group-name {
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-chat-group-count { font-size: 11.5px; font-weight: 700; color: var(--primary); }

.rp-chat-row {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  text-align: left;
  padding: 10px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  font: inherit;
  color: var(--foreground);
  cursor: pointer;
}
.rp-chat-row:hover { background: var(--secondary); }
.rp-chat-row--active,
.rp-chat-row--active:hover { background: var(--accent); }

.rp-chat-avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: 15px;
  font-weight: 700;
}
.rp-chat-avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; }
/* Kind → shape/colour, per Чат.dc.html: bot = squared primary "Р"; human (Direct) = round accent
   initial; group (GroupLesson) = squared secondary initial (Чат тьютора.dc.html's "Г" card);
   announcement = squared warning-muted megaphone. */
.rp-chat-avatar--bot { border-radius: var(--radius-md); background: var(--primary); color: var(--primary-foreground); }
.rp-chat-avatar--human { border-radius: 50%; background: var(--accent); color: var(--accent-foreground); }
.rp-chat-avatar--group { border-radius: var(--radius-md); background: var(--secondary); color: var(--secondary-foreground); }
.rp-chat-avatar--announce { border-radius: var(--radius-md); background: var(--warning-muted); color: var(--warning-muted-foreground); }

.rp-chat-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rp-chat-row-top { display: flex; align-items: baseline; gap: 6px; }
.rp-chat-row-title {
  font-size: 13.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-chat-row-title--unread { font-weight: 700; }
.rp-chat-row-muted { display: inline-flex; flex-shrink: 0; color: var(--muted-foreground); }
.rp-chat-row-time { font-size: 11.5px; color: var(--muted-foreground); flex-shrink: 0; }
.rp-chat-row-bottom { display: flex; align-items: center; gap: 6px; }
.rp-chat-row-preview {
  flex: 1;
  font-size: 12.5px;
  color: var(--muted-foreground);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-chat-row-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--primary);
  color: var(--primary-foreground);
  font-size: 11px;
  font-weight: 700;
}

.rp-chat-foot { padding: 10px 14px; border-top: 1px solid var(--border); flex-shrink: 0; }
.rp-chat-archive-btn {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted-foreground);
  cursor: pointer;
}
.rp-chat-archive-btn:hover { color: var(--foreground); }

/* Debounced server-side message search hits (MessageSearchResults) — reuses .rp-chat-row/-avatar*
   so a hit looks like an ordinary inbox row, plus its own head/empty labels. */
.rp-chat-search-results { padding: 4px 8px 8px; }
.rp-chat-search-results-head { padding: 8px 8px 4px; font-size: 12px; font-weight: 600; color: var(--muted-foreground); }
.rp-chat-search-results-empty { padding: 12px; text-align: center; font-size: 13px; color: var(--muted-foreground); }

/* Desktop empty state: no room selected. .rp-convo-pane (below, Task 4) stays the outer wrapper
   purely so the mobile show/hide mechanic keeps hiding it until a room is picked — only this
   inner placeholder is Task 3's. */
.rp-chat-noroom { flex: 1; display: flex; align-items: center; justify-content: center; }
.rp-chat-noroom-inner { display: flex; flex-direction: column; align-items: center; gap: 12px; color: var(--muted-foreground); }
.rp-chat-noroom-inner svg { opacity: 0.7; }
.rp-chat-noroom-text { font-size: 13.5px; }

@media (max-width: 768px) {
  .rp-chat-avatar { width: 44px; height: 44px; font-size: 16px; }
  .rp-chat-row { padding: 11px 10px; }
}

/* ── Чат (/chat) page shell + conversation pane — Task 4, wave 2, Чат.dc.html ────────────────
   Retires chat.css outright: the two-pane/two-screen page shell (was .chat-shell, .show-convo)
   and the whole conversation pane (was .chat-pane.convo and chat.css's .convo-, .msg-, .composer-
   prefixed rules) move here, onto tokens.css vars directly — no bridge needed, unlike the old
   chat.css block, because the design-system v2 token NAMES (--card, --primary, --secondary,
   --border, --muted-foreground, …)
   already match the ones this file's other rp-* blocks use. Mobile collapse unifies on 768px
   (chat.css used 860px for the shell but bp.css uses 768px everywhere else — T3 review flagged
   the mismatch; this task fixes it). */
.rp-chat-shell {
  display: grid;
  grid-template-columns: var(--chat-inbox-w, 320px) minmax(0, 1fr);
  height: calc(100vh - 64px - 56px); /* topbar 64px + .rp-shell__content padding 28px top + 28px bottom */
  min-height: 480px;
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
}

/* Root of the conversation pane (ChatConversation.razor) AND the desktop no-room-selected
   placeholder's outer wrapper (ChatPage.razor) — kept as one class so the mobile show/hide rule
   below only has to target a single selector. */
.rp-convo-pane {
  background: var(--background);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ---------- Conversation header ---------- */
.rp-convo-head {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 64px;
  padding: 0 20px;
  border-bottom: 1px solid var(--border);
  background: var(--card);
  flex-shrink: 0;
}

/* The back arrow exists only for the collapsed single-pane (mobile) layout — on desktop both
   panes are visible side by side, so it's hidden until the media query below. */
.rp-convo-back {
  display: none;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--foreground);
  cursor: pointer;
  flex-shrink: 0;
}
.rp-convo-back:hover { background: var(--secondary); }

.rp-convo-titleblock { flex: 1; min-width: 0; }
.rp-convo-name {
  font-size: 14.5px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
/* Task: wave-2 smoke fix #5 — only the title text truncates; the chip(s) after it (Бот/audience/
   readonly) keep their own flex-shrink:0 below so they never get squeezed out. Without min-width:0
   here a flex item's default min-width:auto blocks the child from shrinking past its content size,
   so the ellipsis never kicks in and the row wraps instead — the bug this fixes. */
.rp-convo-name-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-convo-chip {
  font-size: 10.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--info-muted);
  color: var(--info-muted-foreground);
  flex-shrink: 0;
}
.rp-convo-chip--readonly { background: var(--secondary); color: var(--secondary-foreground); }
.rp-convo-sub { font-size: 12px; color: var(--muted-foreground); margin-top: 1px; }

/* BbDropdownMenuTrigger AsChild="false" renders this as the trigger element itself (mirrors
   .rp-tbw-chat) — no separate wrapping <button>. */
.rp-convo-kebab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  flex-shrink: 0;
}
.rp-convo-kebab:hover { background: var(--secondary); }

/* In-conversation search navigator — shown when the room was opened from a search hit. Holds the
   query, a "k из N" counter, ↑/↓ to step matches, and ✕. No mockup equivalent (a repo-only
   mechanic layered onto the design); styled on the same tokens as the rest of the pane. */
.rp-convo-searchnav {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 16px;
  background: var(--accent);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  color: var(--accent-foreground);
}
.rp-convo-searchnav-q { font-weight: 600; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-convo-searchnav-count { color: var(--muted-foreground); }
.rp-convo-searchnav-btns { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.rp-convo-navbtn {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--foreground);
  cursor: pointer;
}
.rp-convo-navbtn:hover { background: var(--secondary); }
.rp-convo-navbtn:disabled { opacity: 0.35; cursor: default; }
.rp-convo-navbtn--close { margin-left: 4px; }

/* ---------- Message list ---------- */
/* flex:1 + min-height:0 makes this the single scroll region: when the shell is short (mobile
   keyboard open, tiny viewport) the body shrinks and scrolls rather than pushing the fixed
   header/composer past the shell's overflow:hidden edge — which would clip the send button. */
.rp-convo-body { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 0 12px; }
/* min-height: 100% makes .rp-convo-inner fill .rp-convo-body so .rp-convo-empty (place-items:
   center) can vertically centre the spinner/empty text. */
.rp-convo-inner { max-width: 820px; margin: 0 auto; padding: 0 20px; display: flex; flex-direction: column; min-height: 100%; }

.rp-convo-loadmore { display: block; margin: 4px auto 10px; padding: 6px 14px; font-size: 13px; color: var(--primary); background: transparent; border: 1px solid var(--border); border-radius: 999px; cursor: pointer; }
.rp-convo-loadmore:hover { background: var(--accent); }

.rp-convo-empty { flex: 1; display: flex; align-items: center; justify-content: center; padding: 40px; }
.rp-convo-empty-inner { display: flex; flex-direction: column; align-items: center; gap: 12px; color: var(--muted-foreground); }
.rp-convo-empty-inner svg { opacity: 0.7; }
.rp-convo-empty-text { font-size: 14px; }

.rp-convo-daysep {
  align-self: center;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted-foreground);
  background: var(--secondary);
  padding: 3px 12px;
  border-radius: 999px;
  margin: 6px 0 14px;
}

/* ---------- Message row & bubble ---------- */
.rp-msg-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 14px; }
.rp-msg-mine { flex-direction: row-reverse; }
.rp-msg-mine .rp-msg-stack { align-items: flex-end; }
.rp-msg-gap { width: 34px; flex-shrink: 0; } /* aligns continuation messages with the 34px avatar grid */
/* Stacks onto the base .rp-chat-avatar (display/centering/flex-shrink + colour/shape modifiers) to
   shrink the in-bubble avatar from the 40px inbox/header size to the 34px grid above; must appear
   after .rp-chat-avatar's own 768px media-query bump (bp.css, pattern above) so equal-specificity
   cascade order keeps message avatars at 34px on mobile too. */
.rp-msg-avatar { width: 34px; height: 34px; font-size: 13px; }

.rp-msg-stack { display: flex; flex-direction: column; gap: 4px; min-width: 0; max-width: min(560px, 80%); }

.rp-msg-author { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted-foreground); margin: 0 0 4px; }
.rp-msg-who { font-weight: 600; color: var(--foreground); }
.rp-msg-role { color: var(--muted-foreground); }
.rp-msg-time { color: var(--muted-foreground); font-variant-numeric: tabular-nums; font-size: 11.5px; }

/* Sharp top-LEFT corner points toward the avatar on the left (other-party bubble); the --mine
   modifier flips the sharp corner to top-RIGHT, pointing away from the left-side avatar column. */
.rp-msg-bubble {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 4px var(--radius-lg) var(--radius-lg) var(--radius-lg);
  padding: 11px 14px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--foreground);
  position: relative;
  /* anywhere (not break-word): break-word leaves the min-content width at the longest word, so a
     long unbreakable token (a pasted URL, a 40-char word) keeps the bubble that wide and overflows
     the column; anywhere folds break opportunities into min-content so the bubble shrinks to the
     column and the token wraps. */
  overflow-wrap: anywhere;
  word-break: break-word;
}
.rp-msg-bubble--mine {
  background: var(--primary);
  color: var(--primary-foreground);
  border: 0;
  border-radius: var(--radius-lg) 4px var(--radius-lg) var(--radius-lg);
}
.rp-msg-bubble--removed { color: var(--muted-foreground); font-style: italic; background: var(--secondary); border-style: dashed; }
.rp-msg-bubble--typing { color: var(--muted-foreground); font-size: 12.5px; padding: 9px 14px; font-style: italic; }

.rp-msg-bubble p { margin: 0; }
.rp-msg-bubble p + p { margin-top: 6px; }
.rp-msg-bubble strong { font-weight: 700; }
.rp-msg-bubble em { font-style: italic; }
.rp-msg-bubble a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
.rp-msg-bubble--mine a { color: inherit; }
.rp-msg-bubble blockquote {
  margin: 8px 0 4px;
  padding: 6px 12px;
  background: var(--secondary);
  border-left: 3px solid var(--border);
  color: var(--secondary-foreground);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 13px;
}
/* White alpha (not a token) is intentional: the own-message bubble is always primary-filled in
   BOTH themes, so a translucent-white blockquote reads correctly on it in light and dark alike —
   a theme-flipping token here would fight the fixed primary background. */
.rp-msg-bubble--mine blockquote { background: rgba(255, 255, 255, 0.15); border-left-color: rgba(255, 255, 255, 0.4); color: inherit; }
.rp-msg-bubble ul, .rp-msg-bubble ol { margin: 6px 0 2px; padding-left: 22px; }
.rp-msg-bubble li { margin: 2px 0; }

/* Search-jump highlight — a brief flash on the bubble of the message scrolled to from a search
   hit (or a ↑/↓ step). chat-scroll.js toggles .rp-msg-highlight on the row; the animation runs
   once and decays to transparent so the row settles back into the normal list. */
@keyframes rp-msg-flash {
  0% { background: var(--accent); }
  100% { background: transparent; }
}
.rp-msg-row.rp-msg-highlight .rp-msg-bubble { animation: rp-msg-flash 2s ease-out; }

/* Optimistic-send stub. Translucent bubble + an "Отправляется…" footer; the dimming makes the
   in-flight state visually distinct from a committed message without looking broken. */
.rp-msg-bubble--pending { opacity: 0.65; }
.rp-msg-receipt--pending .rp-spin { width: 14px; height: 14px; }

/* Failed-send stub. Tints the bubble + footer so the user can tell at a glance the send didn't
   go through, while keeping the text legible (it's still the user's real draft). */
.rp-msg-bubble--failed { border: 1px solid var(--destructive); background: var(--destructive-muted); color: var(--destructive-muted-foreground); }
.rp-msg-receipt--failed { color: var(--destructive); }
.rp-msg-dismiss {
  background: transparent;
  border: 0;
  padding: 2px;
  margin-left: 2px;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-sm);
}
.rp-msg-dismiss:hover { background: var(--secondary); }

/* Receipt under own messages. check-check is tinted --primary only once every recipient has
   read the message (Чат.dc.html); the single-check "delivered" state stays muted. */
.rp-msg-receipt {
  font-size: 11.5px;
  color: var(--muted-foreground);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.rp-msg-receipt svg { color: var(--primary); }
.rp-msg-receipt--unread svg { color: var(--muted-foreground); }

/* Action buttons inside a bot/system message bubble. Rendered below attachments when
   ActionButtons is non-empty; each button is in state active (clickable) or a terminal status
   pill (done / declined / expired / error). Non-target viewers see active buttons disabled via
   CanClick=false (ChatService.ToView). The FIRST active button in a message is styled primary,
   any further one outline — a positional convention (Чат.dc.html's «Записаться» + «Другое
   время»), not a server-driven field. */
.rp-msg-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.rp-msg-action-btn {
  border: 0;
  border-radius: var(--radius-md);
  padding: 7px 16px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.rp-msg-action-btn--primary { background: var(--primary); color: var(--primary-foreground); }
.rp-msg-action-btn--primary:hover:not(:disabled) { opacity: 0.92; }
.rp-msg-action-btn--outline { background: var(--card); color: var(--foreground); border: 1px solid var(--border); }
.rp-msg-action-btn--outline:hover:not(:disabled) { background: var(--secondary); }
.rp-msg-action-btn:disabled { opacity: 0.5; cursor: default; }

.rp-msg-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-md);
  font-size: 12.5px;
  font-weight: 600;
}
.rp-msg-status--done { background: var(--success-muted); color: var(--success-muted-foreground); }
.rp-msg-status--muted { background: var(--secondary); color: var(--muted-foreground); }
.rp-msg-status--error { background: var(--destructive-muted); color: var(--destructive-muted-foreground); }

/* Delete affordance on own messages — hover/focus-revealed, in the left gutter beside the bubble.
   Top-anchored (NOT vertical-centered) so the icon lands at the same relative spot regardless of
   bubble height. */
.rp-msg-del {
  position: absolute;
  top: 6px;
  left: -34px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms, background 120ms, color 120ms;
}
.rp-msg-row:hover .rp-msg-del,
.rp-msg-row:focus-within .rp-msg-del { opacity: 1; }
.rp-msg-del:hover { background: var(--destructive-muted); color: var(--destructive); }

/* Attachment rendering in message bubbles */
.rp-msg-attachment { margin-top: 8px; }
/* Cap on wide screens but never exceed the bubble width — a fixed 320px overflowed the bubble
   (and the shell, which clips) on narrow phones. */
.rp-msg-att-image { max-width: min(320px, 100%); max-height: 320px; border-radius: var(--radius-md); display: block; }
.rp-msg-attachment audio { max-width: 100%; }
.rp-msg-att-file { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-md); text-decoration: none; color: inherit; max-width: 100%; }
.rp-msg-att-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-msg-att-file--pending { color: var(--muted-foreground); }

/* ---------- Composer ---------- */
.rp-composer-wrap { background: var(--card); border-top: 1px solid var(--border); padding: 14px 20px 16px; flex-shrink: 0; }
.rp-composer-inner { max-width: 820px; margin: 0 auto; }

.rp-composer-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 8px;
  padding: 4px 8px;
  border-radius: var(--radius-md);
  background: var(--secondary);
  color: var(--secondary-foreground);
  font-size: 12.5px;
}
.rp-composer-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.rp-composer-chip-x { border: 0; background: transparent; cursor: pointer; display: inline-flex; color: inherit; }

/* Live Markdown preview panel above the input, rendered through the SAME .rp-msg-bubble tokens
   as a real message — what the user sees here is exactly what gets posted. Height is capped +
   scrolls: without the cap a long draft pushes the input off-screen. */
.rp-composer-preview {
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  background: var(--secondary);
  max-height: 180px;
  overflow-y: auto;
  overflow-wrap: anywhere;
}
.rp-composer-preview-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-foreground); margin-bottom: 6px; }
.rp-composer-preview-empty { color: var(--muted-foreground); font-size: 13px; }
.rp-composer-preview-bubble { display: inline-block; max-width: 100%; }

.rp-composer-row { display: flex; align-items: flex-end; gap: 10px; }

.rp-composer-attach {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--muted-foreground);
  cursor: pointer;
  flex-shrink: 0;
}
.rp-composer-attach:hover { background: var(--secondary); color: var(--primary); }

.rp-composer-field { flex: 1; min-width: 0; }
/* Plain <textarea>, not RpTextArea/MudTextField — see the component-file comment. rows is set
   from C# (Rows getter) to reproduce auto-grow without JS. */
.rp-composer-textarea {
  /* display:block kills the inline-baseline gap under the textarea (~6px) that pushed the
     field wrapper taller than the 40px attach/send buttons; line-height is a fixed 18px so a
     single line sums to exactly the button height (18 + 2×10 padding + 2×1 border = 40px
     desktop; 18 + 2×12 + 2 = 44px on the ≤768px override). */
  display: block;
  width: 100%;
  min-height: 40px;
  max-height: 160px;
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--background);
  color: var(--foreground);
  font: inherit;
  font-size: 13.5px;
  line-height: 18px;
  resize: none;
  overflow-y: auto;
}
.rp-composer-textarea:focus { outline: none; border-color: var(--primary); box-shadow: var(--shadow-focus); }
.rp-composer-textarea::placeholder { color: var(--muted-foreground); }
.rp-composer-textarea:disabled { opacity: 0.6; }

.rp-composer-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  cursor: pointer;
  flex-shrink: 0;
}
.rp-composer-send:hover:not(:disabled) { opacity: 0.92; }
.rp-composer-send:disabled { opacity: 0.4; cursor: not-allowed; }

/* Editor tools (preview toggle + formatting help) live in the foot row below the field — never
   floating over the placeholder. Hidden on mobile with the whole foot (see media query below). */
.rp-composer-foot { margin-top: 8px; display: flex; justify-content: space-between; gap: 12px; font-size: 11.5px; color: var(--muted-foreground); }
.rp-composer-tools { display: flex; align-items: center; gap: 4px; }
.rp-composer-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 0 8px;
  gap: 6px;
  border: 0;
  background: transparent;
  color: var(--muted-foreground);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.rp-composer-tool:hover { background: var(--secondary); color: var(--primary); }
.rp-composer-tool--on { color: var(--primary); background: var(--accent); }
.rp-composer-tool-label { font-size: 11.5px; }

/* Formatting-help popover (MudMenu portal, body-level — outside .rp-chat-shell). Uses the SAME
   global tokens.css vars as everything above, so no bridge is needed even though the portal
   renders outside the shell's DOM subtree. */
.rp-composer-help { padding: 10px 12px; min-width: 230px; }
.rp-composer-help-title { font-weight: 600; font-size: 13px; margin-bottom: 8px; }
.rp-composer-help-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 3px 0; font-size: 12.5px; }
.rp-composer-help-row code { background: var(--secondary); color: var(--secondary-foreground); padding: 1px 6px; border-radius: var(--radius-sm); font-size: 12px; }
.rp-composer-help-row span { color: var(--muted-foreground); }
.rp-composer-help-foot { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11.5px; color: var(--muted-foreground); }

/* Read-only stand-in for the composer in announcement rooms (recipients cannot reply). */
.rp-composer-disabled {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 20px;
  border-top: 1px solid var(--border);
  background: var(--card);
  color: var(--muted-foreground);
  font-size: 13px;
  flex-shrink: 0;
}

/* ---------- Responsive: collapse to a single pane on narrow viewports ----------
   Below 768px the inbox and the conversation stack into one column; ChatPage adds
   .rp-chat-shell--show-convo once a room is selected so only the active pane is visible. The
   open pane fills the screen, so the conversation grows a back arrow to return to the list. */
@media (max-width: 768px) {
  /* ── Full-bleed mobile chat (owner feedback: two nested scrollbars + dead margin around the
     chat card) ───────────────────────────────────────────────────────────────────────────────
     Root cause of the double scroll: the shell had a FIXED viewport-calc height, and it stacked
     BELOW the sibling banners (PlatformBanner/PushEnrollPrompt/TimeZoneConfirmPrompt) + the content
     padding inside .rp-shell (min-height:100vh) — so topbar + padding + banners + fixed-height-shell
     summed to > one viewport and the DOCUMENT scrolled, while .rp-convo-body scrolled inside it.
     Fix, scoped to the chat page via :has(): the shell fills whatever height is left with flexbox
     (no fixed calc to overshoot), the surrounding chrome becomes one non-scrolling viewport box, and
     the card frame + gutters are dropped so the chat runs edge-to-edge. Only .rp-convo-body /
     .rp-chat-list scroll — a single scroll region. :has() is safe here (evergreen WebViews). */
  .rp-shell:has(.rp-chat-shell) { min-height: 0; height: 100dvh; overflow: hidden; }
  .rp-shell__content:has(.rp-chat-shell) {
    flex: none;                             /* stop growing to fill .rp-shell__main */
    height: calc(100dvh - 56px - 64px);     /* viewport − sticky topbar (56) − fixed bottom nav (~64) */
    min-height: 0;
    padding: 0;                             /* edge-to-edge; kills the 18/16/90 gutters */
    overflow: hidden;
    display: flex;
  }
  .rp-shell__content:has(.rp-chat-shell) .rp-shell__content-inner {
    max-width: none; margin: 0;
    flex: 1; min-width: 0; min-height: 0;
    display: flex; flex-direction: column;
  }
  .rp-chat-shell {
    /* minmax(0, 1fr) — NOT a bare 1fr (== minmax(auto, 1fr)): the auto minimum is the column's
       min-content, so a long unbreakable word or a fixed-width attachment expands the single
       column past the container, and .rp-chat-shell's overflow:hidden then CLIPS the right edge —
       the send button and message content get cut off. The 0 minimum lets the column shrink to
       the viewport so content wraps instead. */
    grid-template-columns: minmax(0, 1fr);
    /* Flex-fill the content box (banners above take their share first) instead of a fixed height
       that overshoots — the whole point of the :has() rules above. */
    flex: 1;
    height: auto;
    min-height: 0;
    border: 0;
    border-radius: 0;
  }
  .rp-chat-shell .rp-convo-pane { display: none; }
  .rp-chat-shell.rp-chat-shell--show-convo .rp-chat-inbox { display: none; }
  .rp-chat-shell.rp-chat-shell--show-convo .rp-convo-pane { display: flex; }

  /* Show the back arrow and give header controls finger-sized hit targets; shrink the header to
     the mockup's 60px mobile height. */
  .rp-convo-back { display: inline-flex; }
  .rp-convo-head { height: 60px; padding: 0 12px; gap: 10px; }
  .rp-convo-kebab { width: 36px; height: 36px; }

  /* Task: wave-2 smoke fix #5 — the tutor channel's «Новое объявление» button doesn't fit next to
     a title+chip at 390px; swap to the mockup's compact «Объявление» label instead of hiding the
     text entirely (an icon-only button would need a new icon this component doesn't have yet —
     the least invasive fix reuses the existing text, just shorter). */
  .rp-convo-newann { padding: 0 11px; }
  .rp-convo-newann-full { display: none; }
  .rp-convo-newann-short { display: inline; }

  /* Reclaim horizontal space eaten by the desktop gutters. */
  .rp-convo-inner { padding: 0 14px; }
  .rp-msg-stack { max-width: 86%; }

  /* Action buttons stack full-width per the mockup's phone-2 screen. */
  .rp-msg-actions { flex-direction: column; }
  .rp-msg-action-btn { width: 100%; height: 44px; }
  .rp-msg-status { width: 100%; height: 42px; justify-content: center; }

  .rp-composer-wrap { padding: 10px 12px 14px; }
  .rp-composer-attach, .rp-composer-send { width: 44px; height: 44px; }
  .rp-composer-textarea { min-height: 44px; padding: 12px 13px; }
  .rp-composer-disabled { padding: 14px 12px; }

  /* Drop the markdown/Enter hints — they wrap to two lines on a phone and steal the vertical
     room the composer needs when the on-screen keyboard is open. */
  .rp-composer-foot { display: none; }
}

/* ── Task 5, wave 2 — announcement compose (ОВ-2а, «Чат тьютора.dc.html») ──────────────────
   The inbox-header trigger button lives in ChatInbox's .rp-chat-head-row (rp-chat*, Task 3);
   everything else here belongs to AnnouncementComposeDialog's BbDialogContent (Class="rp-anncmp"). */

/* Portal z-index rules live in ONE consolidated block — «Portal stacking ladder» at the end of
   this file. (A wave-2 `.blazorblueprint-portal { z-index: 250 }` rule used to sit here; the
   portal wrapper is display:contents — it generates no box, so that rule was a silent no-op.) */

.rp-anncmp-trigger {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
/* Icon-collapse подписей обеих кнопок шапки инбокса на узком экране (availability-паттерн:
   caption-span + aria-label + title на кнопке). */
@media (max-width: 640px) {
  .rp-chat-head-row .rp-chat-head-btnlbl { display: none; }
  .rp-chat-head-row .rp-chat-newdm-trigger,
  .rp-chat-head-row .rp-anncmp-trigger { padding: 0 9px; }
}

.rp-anncmp { max-width: 520px; }

/* Hidden by default — shown only in the ≤600px bottom-sheet variant below. */
.rp-anncmp-grabber {
  display: none;
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  margin: 0 auto 14px;
}

.rp-anncmp-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

.rp-anncmp-icon {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  background: var(--warning-muted);
  color: var(--warning-muted-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.rp-anncmp-title { font-size: 16px; font-weight: 700; flex: 1; }

.rp-anncmp-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  flex-shrink: 0;
}
.rp-anncmp-close:hover { background: var(--secondary); }

.rp-anncmp-label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  margin-bottom: 6px;
}
.rp-anncmp-label-hint { font-weight: 500; color: var(--muted-foreground); }

.rp-anncmp-input {
  display: block;
  width: 100%;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--background);
  font: inherit;
  font-size: 13.5px;
  color: var(--foreground);
  outline: none;
  margin-bottom: 4px;
}

.rp-anncmp-textarea {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--background);
  font: inherit;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--foreground);
  outline: none;
  resize: vertical;
  margin-bottom: 4px;
}

.rp-anncmp-input.invalid,
.rp-anncmp-textarea.invalid { border-color: var(--destructive); }

.rp-anncmp-err {
  display: block;
  font-size: 12px;
  color: var(--destructive);
  margin: 0 0 10px;
}

/* Segmented audience toggle sits full-width inside the dialog, same override recipe as .rp-chat-head. */
.rp-anncmp .rp-seg { width: 100%; margin-bottom: 14px; }

.rp-anncmp-preview {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--muted-foreground);
  margin-bottom: 14px;
}
.rp-anncmp-preview b { color: var(--foreground); }

.rp-anncmp-limit {
  font-size: 12.5px;
  background: var(--warning-muted);
  color: var(--warning-muted-foreground);
  border-radius: var(--radius-md);
  padding: 9px 12px;
  margin-bottom: 14px;
}

.rp-anncmp-err-banner {
  font-size: 13px;
  background: var(--destructive-muted);
  color: var(--destructive-muted-foreground);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  margin-bottom: 14px;
}

.rp-anncmp-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.rp-anncmp-cancel {
  height: 40px;
  padding: 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
}

.rp-anncmp-submit {
  height: 40px;
  padding: 0 20px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
}
.rp-anncmp-submit:disabled { opacity: 0.6; cursor: default; }

/* Mobile bottom-sheet variant: same BbDialogContent, repositioned + full-width. Overrides the
   library's own centering (fixed + translate(-50%,-50%)) — !important mirrors the codebase's one
   other defensive override of a Blueprint component's own inline sizing (.rp-tbw-profile__avatar).
   BbDialogContent centers via the Tailwind v4 utilities `left-[50%] translate-x-[-50%]
   translate-y-[-50%]`; in Tailwind v4 translate-* compiles to the standalone CSS `translate`
   property, NOT into `transform` (`transform: none` here is a defensive no-op left for whatever
   else sets it, not the fix) — so a sheet anchored by left/right/bottom must reset `translate` too,
   or it keeps shifting -50% of its own width away from that anchor.

   🔒 This reset is NOT what fixed the "half a panel off-centre" defect the cabinet used to have, and
   it never was — that was global, hit EVERY centred Blueprint element at every width, and is fixed
   at its source in Repetitum.Tenants.Web/Styles/landing.tailwind.css (a Tailwind v3 bundle was
   emitting an unlayered `*{--tw-translate-x:0}` reset that beat v4's layered utilities; see that
   file and LandingBundleCssTests). Measured here after that fix: with this declaration removed the
   sheet does not move at 390px — the left/right/bottom anchors already place it. It stays as a
   belt-and-braces guard for the anchored layout, so do not read it as load-bearing evidence about
   centring, and do not copy it to "fix" a dialog that looks displaced — that would be masking the
   general defect again, which is exactly how it survived two review rounds. */
@media (max-width: 600px) {
  .rp-anncmp {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    transform: none !important;
    translate: none !important;
    width: 100% !important;
    max-width: 100% !important;
    margin: 0 !important;
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0 !important;
    /* Dialog panels stack above the bottom nav via the global «Portal stacking ladder» (end of
       file), so the sheet only needs safe-area clearance, not a 74px band for the nav. */
    padding: 10px 18px calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .rp-anncmp-grabber { display: block; }
  .rp-anncmp-close { display: none; }
  .rp-anncmp-cancel { display: none; }
  .rp-anncmp-submit { width: 100%; height: 48px; font-size: 14.5px; }
}

/* ── Task 6, wave 2 — announcement history tab + tutor channel meta (Чат тьютора.dc.html) ─────
   Two surfaces: (1) AnnouncementHistoryList, the ChatInbox «Объявления» tab body — a plain list of
   sent-broadcast cards inside .rp-chat-list, so it needs no scroll/pane rules of its own; (2) the
   tutor's per-message meta line inside the announcement conversation, replacing .rp-msg-receipt on
   their own bubble. The conversation header's audience chip and the daily-budget footer are
   deliberately NOT new classes — .rp-convo-chip (already info-muted, matching the mockup's "Вся
   площадка" pill) and .rp-composer-disabled (already the right flex/border/typography for a
   centered footer notice) are reused as-is; only .rp-convo-newann (the header's outline «Новое
   объявление» button) is new. */
.rp-annh-head { padding: 8px 8px 4px; font-size: 12px; font-weight: 600; color: var(--muted-foreground); }

.rp-annh-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  padding: 11px 12px;
  margin: 0 0 8px;
}

.rp-annh-row-top { display: flex; align-items: baseline; gap: 8px; }
.rp-annh-title {
  font-size: 13.5px;
  font-weight: 600;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-annh-date { font-size: 11.5px; color: var(--muted-foreground); flex-shrink: 0; }

.rp-annh-preview {
  font-size: 12.5px;
  color: var(--muted-foreground);
  margin: 3px 0 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rp-annh-row-bottom { display: flex; align-items: center; gap: 10px; }
.rp-annh-chip { font-size: 12px; font-weight: 600; padding: 2px 9px; border-radius: 999px; }
.rp-annh-chip--partial { background: var(--info-muted); color: var(--info-muted-foreground); }
.rp-annh-chip--all { background: var(--success-muted); color: var(--success-muted-foreground); }
.rp-annh-audience { font-size: 12px; color: var(--muted-foreground); }
.rp-annh-revoke {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--destructive);
  cursor: pointer;
}
.rp-annh-revoke:hover { opacity: 0.8; }
.rp-annh-revoke:disabled { opacity: 0.5; cursor: default; }

.rp-annh-empty { padding: 24px 12px; text-align: center; color: var(--muted-foreground); }
.rp-annh-empty-text { font-size: 13px; margin-bottom: 4px; }
.rp-annh-empty-hint { font-size: 12px; }

/* Conversation header: tutor-only «Новое объявление» outline button, replacing the kebab menu
   (announcement rooms have no mute/archive actions of their own — see ChatConversation.razor). */
.rp-convo-newann {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--primary);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--primary);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
.rp-convo-newann:hover { background: var(--accent); }
/* Full label by default; the ≤768px media query below swaps in the mockup's compact «Объявление». */
.rp-convo-newann-short { display: none; }

/* Tutor's per-message meta line, under their own announcement bubble — reuses .rp-msg-receipt for
   the "прочитано N из M" segment (same check-check icon/tabular figures as every other receipt);
   only the surrounding flex row + the destructive revoke text-button are new. */
.rp-msg-annmeta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11.5px;
  color: var(--muted-foreground);
  margin-top: 4px;
}
.rp-msg-annmeta .rp-msg-receipt { margin-top: 0; }
.rp-msg-annmeta-revoke {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--destructive);
  cursor: pointer;
}
.rp-msg-annmeta-revoke:hover { opacity: 0.8; }

/* ── rp-notif* (Task 7, wave 2 — /settings/notifications: Уведомления.dc.html) ───────────────
   Push status card + a 4-column event matrix (Событие/Чат/Email/Push) that collapses to one
   card per event ≤600px. Chat is a static check, never a switch — the channel is mandatory by
   product rule. Email/Push cells use NotifChannelSwitch.razor, a bespoke 40×23 pill switch (see
   that component's doc comment for why it isn't BbSwitch). Both the desktop matrix and the
   mobile cards always render; this media query is the only thing that picks between them, same
   idiom as rp-payrow-desktop/rp-payrow-cards. */
.rp-notif-push {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 14px 18px;
  margin-bottom: 18px;
}

.rp-notif-push-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  flex-shrink: 0;
}

.rp-notif-push-icon--on  { background: var(--success-muted); color: var(--success-muted-foreground); }
.rp-notif-push-icon--off { background: var(--secondary);     color: var(--muted-foreground); }

.rp-notif-push-body { flex: 1; min-width: 0; }
.rp-notif-push-title { font-size: 14px; font-weight: 600; }
.rp-notif-push-sub   { font-size: 12.5px; color: var(--muted-foreground); }

.rp-notif-push-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 36px;
  padding: 0 16px;
  border-radius: var(--radius-md);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
.rp-notif-push-btn:disabled { opacity: 0.7; cursor: not-allowed; }

.rp-notif-push-btn--primary {
  border: 0;
  background: var(--primary);
  color: var(--primary-foreground);
}

.rp-notif-push-btn--outline {
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--foreground);
}

/* Spinner inside a push button follows the button's own text colour (currentColor), so it reads
   correctly on both the filled and the outline variant. */
.rp-notif-push-btn .rp-spin {
  border-color: color-mix(in srgb, currentColor 40%, transparent);
  border-top-color: currentColor;
}

.rp-notif-loading {
  display: flex;
  justify-content: center;
  padding: 24px 0;
}
.rp-notif-loading .rp-spin {
  border-color: color-mix(in srgb, var(--muted-foreground) 30%, transparent);
  border-top-color: var(--muted-foreground);
}

/* Desktop matrix. */
.rp-notif-matrix {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  overflow: hidden;
}

.rp-notif-matrix-head,
.rp-notif-matrix-row {
  display: grid;
  grid-template-columns: 1fr 84px 84px 84px;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
}

.rp-notif-matrix-head {
  padding: 11px 18px;
  background: var(--muted);
  border-bottom: 1px solid var(--border);
}

.rp-notif-matrix-head span {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted-foreground);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.rp-notif-matrix-row + .rp-notif-matrix-row {
  border-top: 1px solid var(--border);
}

.rp-notif-col-center { display: flex; justify-content: center; }

.rp-notif-row-label {
  font-size: 13.5px;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rp-notif-chat { display: inline-flex; color: var(--success); }

.rp-notif-push-note {
  font-size: 12.5px;
  color: var(--muted-foreground);
  margin: 10px 2px 0;
}

/* Bespoke pill switch — 40×23 track, 18px knob (NotifChannelSwitch.razor). The knob stays
   literal white in both themes, matching the mockup exactly (a switch thumb, unlike every other
   surface here, isn't meant to tint with dark mode). */
/* .rp-notif-switch geometry is in the canonical toggle block (global controls section); only the
   a11y focus ring + disabled state are component-specific. A bespoke <button role="switch"> has no
   native focus ring — keyboard users must see which switch is focused before toggling. */
.rp-notif-switch:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.rp-notif-switch:disabled,
.rp-notif-switch--disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Mobile card-per-event — hidden by default, shown ≤600px (the mockup's own breakpoint; this
   screen has no wide table to begin with, so it doesn't share the 640px rp-payrow-cards value). */
.rp-notif-cards {
  display: none;
  flex-direction: column;
  gap: 10px;
}

.rp-notif-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 13px 14px;
}

.rp-notif-card-label { font-size: 13.5px; font-weight: 600; }

.rp-notif-card-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 10px;
}

.rp-notif-card-chat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--muted-foreground);
}
.rp-notif-card-chat .rp-notif-chat { width: 15px; height: 15px; }

.rp-notif-card-spacer { flex: 1; }

.rp-notif-card-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  color: var(--muted-foreground);
}

@media (max-width: 600px) {
  .rp-notif-matrix    { display: none; }
  .rp-notif-push-note { display: none; }
  .rp-notif-cards     { display: flex; }
}

