/**
 * NUKE – Main Stylesheet
 * Dark theme · Monospace typography
 *
 * Grayscale reference:
 *   gray-1  #0e0e0e  Body/content background
 *   gray-2  #1a1a1a  Cards, inputs, raised surfaces
 *   gray-3  #2a2a2a  Borders, hover states, elevated surfaces
 *   gray-4  #bdbdbd  Bright text on dark (nav links)
 *   gray-5  #9e9e9e  Muted text (timestamps, hints)
 *   gray-6  #aaaaaa  Secondary text (descriptions, labels)
 *   gray-7  #333333  Strong borders, button hover
 *   gray-8  #252525  Button surfaces, interactive elements
 *   gray-9  #181818  Header, deep surfaces
 */

/* ===================================================================
   TABLE OF CONTENTS   ·   approximate line numbers (Ctrl-G to jump)
   ===================================================================
   L41    1. VARIABLES & THEMING
   L138   2. FONTS
   L327   3. RESET & UTILITIES
   L378   4. LAYOUT
   L503   6. SIGN IN
   L592   7. ONBOARDING
   L744   Header user-search dropdown
   L2072  13. ANIMATIONS
   L2080  Announcement Popup
   L2162  Announcement Overlay (Radio)
   L2180  Item Preview Modal
   L2311  Cursors — the custom Notwaita bitmap cursors were removed 
   L2348  ANIMATION LIBRARY — @keyframes preserved from the old prod
   L2441  NUKE PAGE — styles scoped under .nuke-container (was style
   L2445  /nuke sandbox stylesheet — MSN Messenger design All visual
   L4190  X.com-style chat feed
   L5089  Profile PAGE (/~handle), X.com-style
   L5814  Contact group collapse / expand animation COLLAPSE (snappy
   L6545  Edit Profile button + modal (own profile page)
   =================================================================== */

/* ===========================================
   1. VARIABLES & THEMING
   =========================================== */
:root {
  /* Theme */
  --color-nuke-primary: #ed1c24;
  --color-nuke-primary-hover: #c41920;
  --color-nuke-accent: #fff200;
  --color-nuke-success: #00a651;
  --color-nuke-success-hover: #008c44;
  --color-nuke-danger: #ed1c24;
  --color-nuke-warning: #ff9900;
  --color-nuke-link: #4a9eed;

  /* Semantic */
  --color-border-strong: rgba(0, 0, 0, 0.8);
  --color-collapsible-bg: rgba(0, 100, 255, 1);
  --color-collapsible-header: transparent;
  --color-text-secondary: #9e9e9e;
  --color-text-muted: #757575;
  --color-text-primary: var(--color-white);
  --color-border: #2a2a2a;
  --color-success: #00a651;
  --color-danger: #ed1c24;

  /* Neutral gray ramp (tight dark end, Notion-style) */
  --color-black: #000000;
  --color-gray-1: #191919;
  --color-gray-2: #1e1e1e;
  --color-gray-3: #252525;
  --color-gray-4: #2c2c2c;
  --color-gray-5: #333333;
  --color-gray-6: #656565;
  --color-gray-7: #a9a9a9;
  --color-gray-8: #bbbbbb;
  --color-gray-9: #cccccc;
  --color-gray-10: #e4e4e6;
  --color-gray-11: #f1f1f1;
  --color-gray-12: #f8f8fa;
  --color-white: #ffffff;
  --color-text: var(--color-gray-11);

  /* Layout */
  --header-height: 56px;
  --border-width: 2px;
  --spacing: 16px;
  --spacing-sm: 8px;
  --spacing-lg: 24px;
  --border-radius: 4px;

  /* Icons */
  --level-icon-sm: 18px;

  /* Typography */
  --font-mono: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  /* `Noto Color Emoji` is appended to every font stack so emoji code
     points fall through to Google's color emoji font sitewide (loaded
     via the Google Fonts <link> in index.html's <head>). */
  --font-digital: 'DS Digital', 'Noto Color Emoji', monospace;
  --font-bank-gothic: 'Inter', 'Noto Color Emoji', sans-serif;
  --font-conduit: 'Sunghyun Sans', 'Inter', 'Noto Color Emoji', sans-serif;
  --font-top-secret: 'Sunghyun Sans', 'Inter', 'Noto Color Emoji', sans-serif;
  --font-elegant-typewriter: 'Sunghyun Sans', 'Inter', 'Noto Color Emoji', sans-serif;
  --font-numeric: 'Sunghyun Sans', 'Nunito', 'Noto Color Emoji', sans-serif;
  --font-size-xs: 10px;
  --font-size-sm: 12px;
  --font-size-md: 14px;
  --font-size-lg: 16px;
  --font-size-xl: 20px;
  --font-size-xxl: 24px;
  --line-height: 1.5;

  /* Soft ambient halo painted behind floating-surface chrome (calling
     card, search bar, notif button, day-mode calendar). Layered ON TOP
     of the existing tight drop shadow (`--cc-shadow-rest`) so each
     surface gets both a 1-2px ledge AND a wider glow around it. */
  --soft-ambient-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}

@property --cc-lift {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

@property --cc-press {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --cc-tilt {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* ===========================================
   2. FONTS
   =========================================== */
@font-face {
  font-family: 'Berkeley Mono';
  src: url('/fonts/BerkeleyMono-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Sunghyun Sans';
  src: url('/fonts/SunghyunSans-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Sunghyun Sans';
  src: url('/fonts/SunghyunSans-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Sunghyun Sans';
  src: url('/fonts/SunghyunSans-Black.woff2') format('woff2');
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Game Boy 1989';
  src: url('/fonts/game_boy_1989.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Berkeley Mono';
  src: url('/fonts/BerkeleyMono-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'DS Digital';
  src: url('/fonts/DS-DIGI.TTF') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'DS Digital';
  src: url('/fonts/DS-DIGIB.TTF') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'DS Digital';
  src: url('/fonts/DS-DIGII.TTF') format('truetype');
  font-weight: 400;
  font-style: italic;
}

@font-face {
  font-family: 'DS Digital';
  src: url('/fonts/DS-DIGIT.TTF') format('truetype');
  font-weight: 700;
  font-style: italic;
}

@font-face {
  font-family: 'Arial Rounded';
  src: url('/fonts/arialroundedmtbold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'Bank Gothic';
  src: url('/fonts/BankGothic Md BT.ttf') format('truetype');
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: 'Bank Gothic';
  src: url('/fonts/BankGothic Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'Conduit ITC';
  src: url('/fonts/Conduit ITC Light Regular.otf') format('opentype');
  font-weight: 300;
  font-style: normal;
}

@font-face {
  font-family: 'Conduit ITC';
  src: url('/fonts/Conduit ITC Regular.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Conduit ITC';
  src: url('/fonts/Conduit ITC Medium Regular.otf') format('opentype');
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: 'Conduit ITC';
  src: url('/fonts/Conduit ITC Black Regular.otf') format('opentype');
  font-weight: 900;
  font-style: normal;
}

@font-face {
  font-family: 'Top Secret';
  src: url('/fonts/Top Secret.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Elegant Typewriter';
  src: url('/fonts/ELEGANT TYPEWRITER Light.ttf') format('truetype');
  font-weight: 300;
  font-style: normal;
}

@font-face {
  font-family: 'Elegant Typewriter';
  src: url('/fonts/ELEGANT TYPEWRITER Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Elegant Typewriter';
  src: url('/fonts/ELEGANT TYPEWRITER Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'Elegant Typewriter';
  src: url('/fonts/ELEGANT TYPEWRITER Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
}

@font-face {
  font-family: 'Impact';
  src: url('/fonts/impact.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Impact';
  src: url('/fonts/Impacted.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'Impact Unicode';
  src: url('/fonts/unicode.impact.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'Rosetta';
  src: url('/fonts/rosetta.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

/* ===========================================
   3. RESET & UTILITIES
   =========================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: var(--font-size-md);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button {
  -webkit-user-select: none;
  user-select: none;
}

body {
  font-family: 'Sunghyun Sans', 'Inter', -apple-system, BlinkMacSystemFont, 'Noto Color Emoji', sans-serif;
  font-weight: 400;
  font-size: var(--font-size-md);
  background: #eaeaea;
  color: var(--color-text);
  line-height: var(--line-height);
  min-height: 100vh;
}
/* Tiled `wallpaper.png` pattern at true 10% opacity. Painted via a
   fixed `::before` so `opacity:` applies directly to the image (CSS
   doesn't support per-layer opacity on `background-image`). z-index
   `-1` puts it behind body content but above the body's own bg color. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background: url('/img/ui/wallpaper.png') 0 0 / 400px 400px repeat;
  opacity: 0.2;
  z-index: -1;
  pointer-events: none;
}

a { color: inherit; text-decoration: none; }
button { font-family: inherit; font-weight: 400; font-size: var(--font-size-md); color: var(--color-text-primary); cursor: pointer; border: none; background: none; }
input, textarea, select { font-family: inherit; font-weight: 400; font-size: var(--font-size-md); color: var(--color-text-primary); }
ul, ol { list-style: none; }
img { max-width: 100%; height: auto; }

.hidden { display: none !important; }

/* ===========================================
   4. LAYOUT
   =========================================== */

/* Shell */
.app-wrapper {
  display: flex;
  flex-direction: row;
  min-height: 100vh;
  max-width: 100vw;
}

.layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  flex: 1;
  min-width: 0;
  overflow-x: hidden;
}

.content {
  flex: 1;
  min-width: 0;
  padding: var(--spacing-lg);
  position: relative;
  background: transparent;
}

.nav-link.hidden,
.nav-link-hidden {
  display: none !important;
}

/* Page containers */
.signin-container,
.onboarding-container {
  max-width: 1200px;
  margin: 0 auto;
}

/* Buttons */
.action-btn {
  padding: 12px 24px;
  background: var(--color-gray-8);
  color: var(--color-text-primary);
  border-radius: var(--border-radius);
  font-size: var(--font-size-sm);
  font-weight: 700;
  transition: all 0.2s;
}

.action-btn:hover:not(:disabled) { background: var(--color-gray-7); }
.action-btn:disabled { cursor: not-allowed; }
.action-btn.primary { background: var(--color-nuke-primary); }
.action-btn.primary:hover:not(:disabled) { background: var(--color-nuke-primary-hover); }
.action-btn.accent { background: var(--color-nuke-success); }
.action-btn.accent:hover:not(:disabled) { background: var(--color-nuke-success-hover); }

/* Modals */
.modal-overlay,
.profile-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-content {
  background: var(--color-gray-2);
  color: var(--color-white);
  border-radius: var(--border-radius);
  padding: var(--spacing-lg);
  max-width: 500px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
}

.modal-title {
  font-size: var(--font-size-xl);
  font-weight: 700;
  margin-bottom: var(--spacing);
}

.modal-actions {
  display: flex;
  gap: var(--spacing);
  margin-top: var(--spacing-lg);
}

.modal-btn {
  flex: 1;
  padding: 12px;
  font-weight: 700;
  border-radius: var(--border-radius);
  transition: all 0.2s;
}

.modal-btn.primary { background: var(--color-nuke-primary); color: var(--color-white); }
.modal-btn.secondary { background: var(--color-gray-7); color: var(--color-white); }

/* Badges & tags */
.notif-badge {
  display: inline-block;
  background: var(--color-nuke-primary);
  color: var(--color-white);
  font-size: 10px;
  font-weight: 700;
  min-width: 16px;
  height: 16px;
  line-height: 16px;
  text-align: center;
  border-radius: 8px;
  padding: 0 4px;
  margin-left: 4px;
  vertical-align: middle;
}

/* ===========================================
   6. SIGN IN
   =========================================== */

/* Hide header, XP bar, and shop border when on signin/onboarding page.
   Uses the `page-*` body classes (set by JS in main.ts) instead of a
   body-level `:has()` walk — much cheaper on every layout pass. */
body.page-signin .header,
body.page-signin .content,
body.page-onboarding .content {
  background-color: var(--color-black);
}

.signin-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
}

.signin-content {
  max-width: 400px;
  width: 100%;
  text-align: center;
}

.signin-title {
  font-size: var(--font-size-xxl);
  margin-bottom: var(--spacing);
  color: var(--color-white);
}

.signin-description {
  color: var(--color-gray-6);
  margin-bottom: var(--spacing-lg);
}

.wallet-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing);
}

.wallet-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing);
  padding: 16px;
  background: var(--color-gray-9);
  color: var(--color-white);
  border: var(--border-width) solid var(--color-gray-7);
  border-radius: var(--border-radius);
  font-weight: 700;
  transition: all 0.2s;
}

.wallet-btn:hover:not(:disabled) {
  background: var(--color-gray-8);
  border-color: var(--color-nuke-primary);
}

.wallet-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.wallet-btn.not-installed { opacity: 0.5; }

.signin-status {
  margin-top: var(--spacing-lg);
  padding: var(--spacing);
  background: transparent;
  border-radius: var(--border-radius);
  color: var(--color-gray-6);
}

.signin-status .status-text::after {
  content: '';
  display: inline-block;
  width: 12px;
  text-align: left;
  animation: signin-dots 1.2s steps(4, end) infinite;
}

.signin-error {
  margin-top: var(--spacing);
  padding: var(--spacing);
  background: rgba(237, 28, 36, 0.1);
  color: var(--color-nuke-danger);
  border-radius: var(--border-radius);
}

/* ===========================================
   7. ONBOARDING
   =========================================== */
.onboarding-container { max-width: 800px; display: flex; align-items: center; justify-content: center; min-height: 100%; }
.onboarding-content { padding: var(--spacing-lg); width: 100%; }

.onboarding-title {
  font-size: var(--font-size-xxl);
  text-align: center;
  margin-bottom: var(--spacing);
  color: var(--color-white);
}

.onboarding-banner {
  display: block;
  max-width: 280px;
  margin: 0 auto var(--spacing-lg);
  border-radius: var(--border-radius);
}

.onboarding-description {
  text-align: center;
  color: var(--color-gray-6);
  margin-bottom: var(--spacing-lg);
}

.onboarding-section { margin-bottom: var(--spacing-lg); }

.onboarding-section-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
  margin-bottom: var(--spacing-sm);
  color: var(--color-text);
  text-align: center;
}

.onboarding-hint {
  font-size: var(--font-size-sm);
  color: var(--color-gray-5);
  margin-bottom: var(--spacing);
}

.onboarding-error {
  padding: var(--spacing);
  background: rgba(237, 28, 36, 0.1);
  color: var(--color-nuke-danger);
  border-radius: var(--border-radius);
  margin-bottom: var(--spacing);
  text-align: center;
}

.onboarding-selected {
  font-weight: 700;
  color: var(--color-nuke-primary);
  text-align: center;
  min-height: 24px;
}

.onboarding-input-group { max-width: 400px; margin: 0 auto; }

.onboarding-input {
  width: 100%;
  padding: 16px;
  font-family: var(--font-mono);
  font-size: var(--font-size-lg);
  font-weight: 400;
  border: var(--border-width) solid var(--color-gray-3);
  border-radius: var(--border-radius);
  background: var(--color-gray-2);
  color: var(--color-white);
}

.onboarding-input:focus {
  outline: none;
  border-color: var(--color-nuke-primary);
}

.onboarding-username-error {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-nuke-danger);
}

.onboarding-actions {
  display: flex;
  justify-content: center;
  margin-top: var(--spacing-lg);
}

.onboarding-actions .action-btn {
  min-width: 200px;
  padding: 16px 32px;
  font-size: var(--font-size-lg);
}

.onboarding-fee-notice {
  text-align: center;
  margin-top: var(--spacing);
  font-size: var(--font-size-sm);
  color: var(--color-gray-5);
}

body.page-chat .content {
  padding: 0;
  overflow: hidden;
}

.chat-pill-header .dock-pill {
  cursor: grab;
}
.chat-pill-header.dragging .dock-pill {
  cursor: grabbing;
}

@media (hover: hover) {
  .chat-pill-nuke-time.nuke-time-day:hover {
    transform: translateY(-1.5px) rotate(0deg) scale(1.01);
    /* Match .cc-entry:hover — profile color blended into gray-6, same
       as the search bar / notif button / dropdown rings. */
    --cc-outer-border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
  }
  .chat-pill-nuke-time.nuke-time-day:hover::before {
    border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
  }
}

.chat-secondary-header > .user-search-wrapper {
  order: 2;
  margin-left: auto;
  margin-right: auto;
}

.chat-secondary-header > .notif-wrapper {
  order: 5;
}

body.is-mobile #chat-secondary-header-card .cc-pfp-wrapper {
  width: 30px;
  height: 30px;
  margin: 3px;
}

body.is-mobile #chat-secondary-header-card .cc-pfp {
  width: 30px;
  height: 30px;
}

.notif-wrapper {
  position: relative;
  display: inline-flex;
}

/* ============ Header user-search dropdown ============ */

/* Wrapper carries the chrome (bg, outer ring, drop shadow). Motion
   stays on the input so it doesn't drag the dropdown around. */
/* Wrapper is just a positioning container — it holds the bar and the
   dropdown as siblings (mirrors `.notif-wrapper` → button + popover).
   No chrome, no transform. The dropdown lives outside the bar's
   transformed coordinate space, so it stays put when the bar lifts. */
.user-search-wrapper {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

/* The visual search bar — owns the chrome (bg, ring, drop shadow) and
   the lift/scale transform. Input + glyph sit inside; both follow the
   transform via DOM nesting. */
.user-search-bar {
  position: relative;
  display: flex;
  width: 423px;
  height: 45px;
  border-radius: 6px;
  background: #FFFFFF;
  /* Rest ring matches .cc-entry's rest ring (gray-7). */
  --cc-outer-border-color: var(--color-gray-7);
  box-shadow:
    0 0 0 1px var(--cc-outer-border-color),
    var(--cc-shadow-rest),
    var(--soft-ambient-shadow);
  transform: translateY(var(--cc-lift)) scale(var(--cc-press));
  transition:
    --cc-lift 0.18s cubic-bezier(0.22, 1, 0.36, 1),
    --cc-press 0.2s cubic-bezier(0.34, 1.4, 0.64, 1),
    box-shadow 0.04s ease;
}

.user-search-input {
  width: 100%;
  height: 100%;
  /* Right padding reserves space for the inset glyph tile
     (37px wide + 4px inset + 4px breathing = 45px). */
  padding: 0 49px 0 14px;
  background: transparent;
  border: none;
  border-radius: inherit;
  color: var(--color-black);
  /* Caret matches the search bar's hovered/dropdown border ring so the
     input cursor reads as part of the same chrome family. */
  caret-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
  font-family: var(--font-conduit);
  font-size: 13px;
  font-weight: 700;
  outline: none;
}

.user-search-bar:hover,
.user-search-bar:focus-within {
  --cc-lift: -1.5px;
  --cc-press: 1.01;
  /* Same desaturated profile-color tint .cc-entry:hover uses, just keyed
     to the logged-in user (--current-user-color). */
  --cc-outer-border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
}
/* Keep the bar's outer ring colored while the cursor is in the dropdown
   — moving from the bar into the dropdown would otherwise drop `:hover`
   on the bar and snap the ring back to gray. Only the ring carries
   over (no lift/scale) so the bar itself stays put. */
.user-search-wrapper:has(.user-search-dropdown:hover) .user-search-bar {
  --cc-outer-border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
}
.user-search-bar:has(.cc-pressed) {
  --cc-press: 0.99;
  --cc-lift: 0px;
}

/* Glyph sits inside the search bar (its parent `.user-search-wrapper`
   carries the bg/ring/lift+scale transform). The button itself is
   transparent; the inner span carries the chrome. The wrapper's
   transform composes onto the glyph via DOM nesting — no per-element
   motion replication. */
.user-search-glyph {
  position: absolute;
  top: 50%;
  right: 4px;
  width: 37px;
  height: 37px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  z-index: 2;
  transform: translateY(-50%);
  color: var(--color-text);
}
.user-search-glyph .chat-secondary-action-inner {
  width: 100%;
  height: 100%;
}

.user-search-input::placeholder {
  font-weight: 700;
  color: var(--color-gray-7);
  opacity: 1;
}

.user-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}



/* Dropdown opens only when there's a typed query AND either the input is
   focused OR the wrapper is hovered. Hovering the wrapper covers both
   hovering the bar and hovering the dropdown (it's a descendant), so
   moving the cursor from bar → dropdown keeps it open after blur. */

.user-search-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
}

.user-search-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}

/* When the dropdown is closed (no typed query), keep all rows hidden so
   the closing fade doesn't flash an unfiltered list mid-transition. */
.user-search-wrapper:not(.has-query) .user-search-row {
  display: none;
}

/* User-search dropdown row width override. Base design lives in
   CALLING CARD DESIGN SYSTEM (~line 2796). */
.user-search-list .cc-entry {
  width: 240px;
  flex-shrink: 0;
}

/* Standalone online dot, sibling to the cc-entry — only the user-search
   dropdown shows this. Anchors to the row's top-left corner so it sits
   just outside the pfp without being a child of `.cc-pfp-wrapper`. */
.user-search-online-dot {
  position: absolute;
  top: -2px;
  left: -2px;
  width: 8px;
  height: 8px;
  z-index: 4;
  pointer-events: none;
}

.user-search-empty {
  padding: 16px;
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

/* Spotlight overlay — desktop only. When hovering the notification button
   or the header calling card, the rest of the page dims slightly while the
   hovered surface (and only that surface) stays bright. */
.hover-spotlight {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  z-index: 90;
}

body.is-mobile .hover-spotlight {
  display: none;
}

body.spotlight-notif .hover-spotlight,
body.spotlight-profile .hover-spotlight,
body.spotlight-search .hover-spotlight {
  opacity: 1;
}

/* Lift only the spotlighted surface above the overlay. The notif popover
   already lives at z-index 200 so it stays above the dim regardless. */
body.spotlight-notif .notif-wrapper {
  position: relative;
  z-index: 95;
}

body.spotlight-search .user-search-wrapper {
  position: relative;
  z-index: 95;
}

.notif-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: auto;
  right: 0;
  width: 300px;
  /* Same cap as the search dropdown — ends at the chat container's
     bottom edge. See `.user-search-dropdown` for the offset breakdown. */
  max-height: calc(100dvh - 116px);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Background + 1px ring mirror `.user-search-bar:hover`; drop shadow
     is stronger than the bar's so the floating panel reads as elevated. */
  background: var(--color-gray-10);
  border-radius: 6px;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6)),
    0 12px 32px rgba(0, 0, 0, 0.5);
  padding: 12px 8px;
  opacity: 0;
  visibility: hidden;
  z-index: 200;
}

.notif-popover::-webkit-scrollbar {
  display: none;
}

.notif-wrapper:hover .notif-popover,
.notif-popover:hover {
  opacity: 1;
  visibility: visible;
}

.notif-popover-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.notif-group-header {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-black);
  padding: 6px 4px 2px;
  /* First header has no top space; subsequent headers gain a small gap
     above to separate from the previous group. */
  margin-top: 4px;
}
.notif-group-header:first-child {
  margin-top: 0;
}

@media (hover: hover) {
  .chat-secondary-action-btn:hover {
    --cc-lift:  -1.5px;
    --cc-press:  1.01;
    /* Match .cc-entry:hover — profile-tinted bg (mixed into gray-9) and
       outer ring (mixed into gray-6). The glyph icon's pfp ring tracks
       the same hover color so the inner chrome lines up with the
       calling card's pfp on hover. */
    --cc-outer-border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
    --pfp-outer-border-color: var(--cc-outer-border-color);
    background:               color-mix(in srgb, var(--current-user-color, #6a6a6a) 50%, var(--color-gray-9));
  }
  .chat-secondary-action-btn:hover .chat-secondary-action-inner {
    transform: rotate(1deg);
  }
  .chat-secondary-action-btn:hover .chat-secondary-action-inner::after {
    transform: rotate(1deg);
  }
  /* Keep the notif button's outer ring colored while the cursor is in
     the popover — moving from the button into the popover would
     otherwise drop `:hover` on the button and snap the ring back to
     gray, which reads as a hand-off bug. Only the ring carries over
     (no lift/scale) so the button itself stays put. */
  .notif-wrapper:has(.notif-popover:hover) .chat-secondary-notif-btn {
    --cc-outer-border-color: color-mix(in srgb, var(--current-user-color, #6a6a6a) 45%, var(--color-gray-6));
    --pfp-outer-border-color: var(--cc-outer-border-color);
    background:               color-mix(in srgb, var(--current-user-color, #6a6a6a) 50%, var(--color-gray-9));
  }
}

/* Press + rebound — same as the calling card click animation. */
.chat-secondary-action-btn:hover.cc-pressed,
.chat-secondary-action-btn.cc-pressed {
  --cc-press: 0.97;
  --cc-lift: 0px;
}

.chat-secondary-action-btn.cc-rebound {
  animation: cc-rebound 0.42s cubic-bezier(0.45, 0.05, 0.55, 0.95);
}

.chat-secondary-action-btn.cc-rebound .chat-secondary-action-inner,
.chat-secondary-action-btn.cc-rebound .chat-secondary-action-inner::after {
  animation: cc-pfp-shake 0.36s ease-in-out;
}

/* Visual chrome (size, border, radius, drop shadow, gloss, rim) is now
   centralized in the PFP DESIGN SYSTEM — `.chat-secondary-action-inner`
   is a variant in the same selector lists. Only the layout / icon
   positioning lives here. The `.chat-secondary-action-btn` parent gives
   higher specificity than the PFP `display: block` rule. */
.chat-secondary-action-btn .chat-secondary-action-inner,
.user-search-glyph .chat-secondary-action-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

/* Right-side panel — chat by default; swaps to the notifications view for
   the currently-viewed profile when a `/~handle` page is open. */
.right-stack,
.notif-stack {
  /* Sized + placed by the .chat-container grid (column 2, row 3 — sits
     under the secondary header in row 2 and the pill header in row 1).
     `min-width: 0` lets the column shrink past its content's intrinsic
     min-width (default `auto`); content that doesn't fit is clipped by
     the `overflow: hidden` below. `position: relative` so the pager
     arrows (absolute-positioned) anchor to this container. */
  grid-column: 2;
  grid-row: 3;
  min-width: 0;
  position: relative;
  align-self: stretch;
  background: blue;
  padding: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 0;
  overflow: hidden;
}
.notif-stack { background: green; }
.notif-stack-header {
  flex-shrink: 0;
  height: 32px;
  background: lime;
  display: flex;
  align-items: stretch;
}
.notif-stack-header .ns-tab {
  flex: 1;
  height: 100%;
  padding: 0 12px;
  border: none;
  background: transparent;
  color: var(--color-white);
  font-family: var(--font-conduit);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.15s ease, background 0.15s ease;
}
.notif-stack-header .ns-tab:hover {
  opacity: 0.8;
}
.notif-stack-header .ns-tab.active {
  opacity: 1;
  background: rgba(0, 0, 0, 0.18);
}
.notif-stack[data-active-tab="profile"] .ns-tab-view[data-tab-view="profile"],
.notif-stack[data-active-tab="notifs"]  .ns-tab-view[data-tab-view="notifs"] {
  display: flex;
}



/* ════════════════════════════════════════════════════════════════════
   PFP DESIGN SYSTEM
   ════════════════════════════════════════════════════════════════════
   Every profile picture site-wide shares this design. Tweak any value
   in `:root` and all variants update together.

   Variants:
     • cc-pfp              calling-card pfp           37×37
     • msg-pfp             chat-message pfp           37×37
     • profile-panel-pfp   large profile-page pfp     72×72

   Layers (bottom → top, stacked inside the wrapper):
     1. img content        the actual pfp photo
     2. ::before           gloss sheen          mix-blend-mode: screen
     3. ::after            reflective rim       mix-blend-mode: overlay
     4. .{x}-pfp-border    optional asset ring  (off by default)

   Modules:
     A. SIZES        per-variant dimensions
     B. BASE         img chrome (radius, shadow, background, transitions)
     C. GLOSS        domed sheen (::before)
     D. RIM          reflective inset highlights (::after)
     E. HOVERED      tilt + shadow boost on hover
     F. PRESSED      snappier press tilt
     G. ACTIVE       active shadow + suppression + rebound shake
     H. BORDER RING  asset-driven decorative ring (off by default)
   ════════════════════════════════════════════════════════════════════ */

:root {
  /* — Geometry — */
  --pfp-radius:        16%;   /* % so corners scale proportionally  */
  --pfp-border-width:  1px;   /* rim thickness (overridden on profile pfp) */

  /* — Outer border (1px ring around the pfp, gray everywhere — does not
       react to state). Painted via box-shadow spread, no layout space. */
  --pfp-outer-border-width: 1px;
  --pfp-outer-border-color: var(--color-gray-6);

  /* — Drop shadows (img box-shadow per state) — */
  --pfp-shadow:                0 1px 3px rgba(0, 0, 0, 0.15);
  --pfp-hover-shadow:          0 3px 6px rgba(0, 0, 0, 0.45);
  --pfp-active-shadow:         0 2px 4px rgba(0, 0, 0, 0.30);
  --pfp-active-resting-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);

  /* — Tilt angles — */
  --pfp-hover-tilt:          -2deg;
  --pfp-pressed-tilt:        -4deg;
  --pfp-active-pressed-tilt:  0deg;

  /* — Transitions — */
  --pfp-tilt-transition:    transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
  --pfp-pressed-transition: transform 0.10s ease-out;
  --pfp-shadow-transition:  box-shadow 0.18s ease;

  /* — Gloss (domed sheen, screen-blended) —
     Radial gradient centered above the pfp so the visible edge curves
     like the bottom of a circle (vs a flat linear gradient). */
  --pfp-gloss: radial-gradient(
    ellipse 120% 90% at 50% -45%,
    rgba(255, 255, 255, 0.40) 0%,
    rgba(255, 255, 255, 0.12) 60%,
    rgba(255, 255, 255, 0.00) 100%);

  /* — Rim (inset highlights, overlay-blended) —
     App-icon "lit edge": bright top (primary light), dim sides
     (ambient reflection), dark bottom (anchors the icon). */
  --pfp-rim-top:    rgba(255, 255, 255, 0.36);
  --pfp-rim-side:   rgba(255, 255, 255, 0.14);
  --pfp-rim-bottom: rgba(0,   0,   0,   0.25);

  /* — Border ring (asset-driven, off by default) — */
  --pfp-border-ring-asset: none;
  --pfp-border-ring-inset: -3px;
}

/* ─── A. SIZES ──────────────────────────────────────────────────────
   Per-variant dimensions, positioning, and interaction props. The
   larger profile pfp gets a thicker rim via `--pfp-border-width`. */

.cc-pfp-wrapper,
.msg-pfp-frame,
.profile-panel-pfp-wrapper,
.chat-secondary-action-inner {
  position: relative;
  isolation: isolate;        /* contain blend modes to this stacking ctx */
  flex-shrink: 0;
}
.cc-pfp-wrapper,
.msg-pfp-frame,
.msg-pfp-spacer,
.cc-pfp,
.chat-secondary-action-inner { width: 37px; height: 37px; }

.cc-pfp-wrapper            { margin: 4px; z-index: 2; }

/* ─── B. BASE ───────────────────────────────────────────────────────
   The pfp img: rounded edges, drop shadow, fallback bg, transitions.
   The wrapper inherits the same radius so its pseudo-elements
   (gloss + rim) clip cleanly. */

.cc-pfp,
.msg-pfp,
.profile-panel-pfp,
.chat-secondary-action-inner {
  display: block;
  box-sizing: border-box;
  border-radius: var(--pfp-radius);
  background: var(--color-gray-2);   /* placeholder while img loads */
  box-shadow:
    0 0 0 var(--pfp-outer-border-width) var(--pfp-outer-border-color),
    var(--pfp-shadow);
  object-fit: cover;
  transform-origin: center center;
  transition: var(--pfp-tilt-transition), var(--pfp-shadow-transition);
}
.cc-pfp-wrapper,
.msg-pfp-frame,
.profile-panel-pfp-wrapper { border-radius: var(--pfp-radius); }

/* ─── C. GLOSS ──────────────────────────────────────────────────────
   `::before` paints the domed sheen. `mix-blend-mode: screen` always
   lightens, so the highlight reads against any pfp color. */

.cc-pfp-wrapper::before,
.msg-pfp-frame::before,
.profile-panel-pfp-wrapper::before,
.chat-secondary-action-inner::before,
.notif-item::before,
.notif-item-glyph::before,
.cc-entry::before,
.pill-pfp::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--pfp-gloss);
  mix-blend-mode: screen;
  pointer-events: none;
  z-index: 3;
  transform-origin: center center;
  transition: var(--pfp-tilt-transition);
}

/* ─── D. RIM ────────────────────────────────────────────────────────
   `::after` paints four inset shadows for an app-icon "lit edge"
   look. `mix-blend-mode: overlay` adapts to the underlying colors
   (lightens lights, darkens darks). Blur radius scales with
   `--pfp-border-width` (1.5×) for soft edges instead of a hard line. */

.cc-pfp-wrapper::after,
.msg-pfp-frame::after,
.profile-panel-pfp-wrapper::after,
.chat-secondary-action-inner::after,
.notif-item::after,
.notif-item-glyph::after,
.cc-entry::after,
.pill-pfp::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow:
    inset  0                                 var(--pfp-border-width)            calc(var(--pfp-border-width) * 1.5) var(--pfp-rim-top),
    inset  0                                 calc(-1 * var(--pfp-border-width)) calc(var(--pfp-border-width) * 1.5) var(--pfp-rim-bottom),
    inset  var(--pfp-border-width)           0                                  calc(var(--pfp-border-width) * 1.5) var(--pfp-rim-side),
    inset  calc(-1 * var(--pfp-border-width)) 0                                 calc(var(--pfp-border-width) * 1.5) var(--pfp-rim-side);
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: 4;
  transform-origin: center center;
  transition: var(--pfp-tilt-transition);
}

/* ─── D2. CLEAN BORDER (pfps only) ──────────────────────────────────
   Keep the gloss + rim OFF the outer ring so every pfp's border reads
   as one clean color. Scoped to the pfp WRAPPERS (not the calling-card
   surface or icon buttons, which keep the full edge treatment):
     • gloss  -> confined to the inner face, fully transparent before
                 the edges (the default is centered above the pfp and is
                 brightest right at the top edge, bleeding onto the ring)
     • rim    -> edge highlights neutralized
   3D still comes from the face sheen + the drop shadow. */
.cc-pfp-wrapper,
.msg-pfp-frame,
.profile-panel-pfp-wrapper {
  --pfp-gloss: radial-gradient(ellipse 82% 46% at 50% 33%,
    rgba(255, 255, 255, 0.36) 0%,
    rgba(255, 255, 255, 0.12) 48%,
    rgba(255, 255, 255, 0.00) 78%);
  --pfp-rim-top: transparent;
  --pfp-rim-side: transparent;
  --pfp-rim-bottom: transparent;
}

/* ─── E. HOVERED ────────────────────────────────────────────────────
   Tilt the img + both pseudos in unison (so gloss & rim follow the
   pfp). Deepen the drop shadow on the img only. Desktop pointers
   only — touch never triggers tilt. */

@media (hover: hover) {
  /* tilt — img + both pseudos */
  .cc-entry:hover                  .cc-pfp,
  .cc-entry:hover                  .cc-pfp-wrapper::before,
  .cc-entry:hover                  .cc-pfp-wrapper::after,
  .msg-pfp-frame:hover             .msg-pfp,
  .msg-pfp-frame:hover::before,
  .msg-pfp-frame:hover::after,
  .profile-panel-pfp-wrapper:hover .profile-panel-pfp,
  .profile-panel-pfp-wrapper:hover::before,
  .profile-panel-pfp-wrapper:hover::after {
    transform: rotate(var(--pfp-hover-tilt));
  }
  /* shadow boost — img only */
  .cc-entry:hover                  .cc-pfp,
  .msg-pfp-frame:hover             .msg-pfp,
  .profile-panel-pfp-wrapper:hover .profile-panel-pfp {
    box-shadow:
      0 0 0 var(--pfp-outer-border-width) var(--pfp-outer-border-color),
      var(--pfp-hover-shadow);
  }
}

/* ─── F. PRESSED ────────────────────────────────────────────────────
   Snappier tilt + shorter transition while held. */

.cc-entry.cc-pressed                  .cc-pfp,
.cc-entry.cc-pressed                  .cc-pfp-wrapper::before,
.cc-entry.cc-pressed                  .cc-pfp-wrapper::after,
.msg-pfp-frame.cc-pressed             .msg-pfp,
.msg-pfp-frame.cc-pressed::before,
.msg-pfp-frame.cc-pressed::after,
.profile-panel-pfp-wrapper.cc-pressed .profile-panel-pfp,
.profile-panel-pfp-wrapper.cc-pressed::before,
.profile-panel-pfp-wrapper.cc-pressed::after {
  transform: rotate(var(--pfp-pressed-tilt));
  transition: var(--pfp-pressed-transition);
}

/* ─── G. ACTIVE ─────────────────────────────────────────────────────
   `.active` = this pfp represents the currently-viewed user.
     • Resting:     elevated drop shadow.
     • Header card: suppress the elevation (it'd look weird on the
                    user's own pinned card, which is always "active").
     • Pressed:     no tilt (already feels lifted).
     • Released:    soft rebound shake on img + both pseudos. */

/* resting elevation */
.cc-entry.active                  .cc-pfp,
.msg-pfp-frame.active             .msg-pfp,
.profile-panel-pfp-wrapper.active .profile-panel-pfp {
  box-shadow:
    0 0 0 var(--pfp-outer-border-width) var(--pfp-outer-border-color),
    var(--pfp-active-shadow);
}
/* pressed-while-active = no tilt */
.cc-entry.active.cc-pressed                  .cc-pfp,
.cc-entry.active.cc-pressed                  .cc-pfp-wrapper::before,
.cc-entry.active.cc-pressed                  .cc-pfp-wrapper::after,
.msg-pfp-frame.active.cc-pressed             .msg-pfp,
.msg-pfp-frame.active.cc-pressed::before,
.msg-pfp-frame.active.cc-pressed::after,
.profile-panel-pfp-wrapper.active.cc-pressed .profile-panel-pfp,
.profile-panel-pfp-wrapper.active.cc-pressed::before,
.profile-panel-pfp-wrapper.active.cc-pressed::after {
  transform: rotate(var(--pfp-active-pressed-tilt));
}
/* rebound shake on release */
.cc-entry.cc-rebound-soft                  .cc-pfp,
.cc-entry.cc-rebound-soft                  .cc-pfp-wrapper::before,
.cc-entry.cc-rebound-soft                  .cc-pfp-wrapper::after,
.msg-pfp-frame.cc-rebound-soft             .msg-pfp,
.msg-pfp-frame.cc-rebound-soft::before,
.msg-pfp-frame.cc-rebound-soft::after,
.profile-panel-pfp-wrapper.cc-rebound-soft .profile-panel-pfp,
.profile-panel-pfp-wrapper.cc-rebound-soft::before,
.profile-panel-pfp-wrapper.cc-rebound-soft::after {
  animation: cc-pfp-shake 0.36s ease-in-out;
}

/* ─── H. BORDER RING ────────────────────────────────────────────────
   Asset-driven decorative ring around the pfp (e.g. for special
   user-tier borders later). Off by default — opt in per element by
   adding `data-has-border` to the wrapper, optionally overriding
   `--pfp-border-ring-asset: url(...)` inline. */

.cc-pfp-border,
.msg-pfp-border,
.profile-panel-pfp-border {
  position: absolute;
  inset: var(--pfp-border-ring-inset);
  background-image: var(--pfp-border-ring-asset);
  background-size: contain;
  background-repeat: no-repeat;
  pointer-events: none;
  z-index: 5;
  display: none;
}
.cc-pfp-wrapper[data-has-border]            > .cc-pfp-border,
.msg-pfp-frame[data-has-border]             > .msg-pfp-border,
.profile-panel-pfp-wrapper[data-has-border] > .profile-panel-pfp-border {
  display: block;
}

/* ════════════════════════════════════════════════════════════════════
   CALLING CARD DESIGN SYSTEM
   ════════════════════════════════════════════════════════════════════
   The `.cc-entry` calling card is rendered on the leaderboard, header
   self-card, user-search dropdown, and mention toast. All visual
   design lives here; context-specific overrides (mobile sizing,
   header-square mirrored layout, leaderboard width) stay in their
   own surface blocks and reference these vars.

   Sub-elements:
     • .cc-banner / .cc-banner-img    background banner image + mask
     • .cc-info                       absolute-positioned overlay
       ├─ .cc-top                     row 1: name + rank (or pfp side)
       │  ├─ .cc-name                 username + level icon
       │  └─ .cc-rank                 rank #
       └─ .cc-bottom                  row 2: balance + dpr-pill
          ├─ .cc-balance              $ amount
          └─ .cc-dpr-pill             pill containing .cc-dpr
             └─ .cc-dpr               daily-percent rate

   Layers (bottom → top, stacked inside the cc-entry):
     1. cc-entry bg               solid card color (--cc-surface-rest)
     2. ::after                   blurred + darkened banner backdrop
     3. .cc-banner-img            sharp banner with right-side fade mask
     4. .cc-pfp-wrapper           pfp (z-index 2)
     5. .cc-info                  text overlay (z-index 3)
     6. ::before                  domed gloss + reflective rim, blends
                                  into the layers below via overlay

   Animation custom properties (registered via @property near the top
   of this file): `--cc-lift`, `--cc-press`, `--cc-tilt`. Set by
   `attachCcPressAnimation` in TS via class toggles (`cc-pressed`,
   `cc-rebound`, `cc-rebound-soft`).

   State summary:
     hover      motion (lift + scale) + outer ring → profile color +
                inner-content reactions (text shadow / banner / dpr)
     pressed    scale 0.97 (motion only, no chrome change)
     active     outer ring → profile color + inner-content reactions
                (header self-card resting still gets the ring, but
                 inner-content reactions are suppressed)
     rebound /  outer ring → profile color (animation runs, ring colored
     rebound-soft  during the brief animation)

   Modules:
     A. BASE        flex layout, sizing, transform composition, transitions
     B. SURFACE     bg + outer ring + drop shadow at rest
     C. STRUCTURE   .cc-info / .cc-top / .cc-bottom / .cc-banner positioning
     D. CONTENT     typography + .cc-banner-img + .cc-dpr-pill chrome
     E. EFFECTS     `::after` blurred backdrop + `::before` rim + gloss
     F. HOVERED     hover state (motion + outer ring color + inner content)
     G. PRESSED     pressed state (scale only)
     H. ACTIVE      active state + header self-card inner-content suppression
     I. ANIMATIONS  @keyframes cc-rebound + cc-rebound-soft
   ════════════════════════════════════════════════════════════════════ */

:root {
  /* — Geometry — */
  --cc-width:        240px;
  --cc-height:       45px;
  --cc-radius:       6px;
  --cc-banner-width: 180px;

  /* — Surface (solid card color, no gradient — translucent bases caused
        edge artifacts at the rounded corners). */
  --cc-surface-rest: #2c2c2c;

  /* — Outer 1px ring around the card. Painted via box-shadow spread (no
        layout space). Gray at rest; HOVERED / ACTIVE / ANIMATIONS swap
        the color to the user's profile color. */
  --cc-outer-border-width: 1px;
  --cc-outer-border-color: var(--color-gray-7);

  /* — Drop shadow at rest (soft only; states don't change this). */
  --cc-shadow-rest: 0 2px 4px rgba(0, 0, 0, 0.36), 0 1px 2px rgba(0, 0, 0, 0.20);

  /* — Lift / scale (drive the existing transform composition) — */
  --cc-lift-hover:           -1.5px;
  --cc-lift-active-hover:    -1px;
  --cc-press-hover:           1.01;
  --cc-press-pressed:         0.97;
  --cc-press-active-pressed:  0.99;

  /* — Transitions — */
  --cc-transition-base:
    --cc-lift  0.18s cubic-bezier(0.22, 1,    0.36, 1),
    --cc-press 0.20s cubic-bezier(0.34, 1.4,  0.64, 1),
    --cc-tilt  0.20s cubic-bezier(0.22, 1,    0.36, 1),
    box-shadow 0.04s ease,
    background 0.18s ease;
  --cc-transition-pressed:
    --cc-press 0.08s ease-out,
    --cc-lift  0.18s cubic-bezier(0.22, 1,    0.36, 1);

  /* — Text shadows on the inner labels — */
  --cc-text-shadow-hover:  0 1px 3px rgba(0, 0, 0, 0.85);
  --cc-text-shadow-active: 0 1px 2px rgba(0, 0, 0, 0.65);

  /* — Banner image opacity per state — */
  --cc-banner-rest:   0.3;
  --cc-banner-hover:  0.5;
  --cc-banner-active: 0.5;

  /* — DPR pill background (hover + active) — */
  --cc-dpr-pill-bg: rgba(0, 0, 0, 0.2);

  /* — Blurred backdrop (`::after`): banner image + heavy blur + dim.
        --cc-backdrop-image is unset by default (no banner ships with
        the site). Set it via inline style or override when a banner
        is assigned. */
  --cc-backdrop-filter: blur(12px) brightness(0.8);

  /* — Rim + gloss (`::before`, `mix-blend-mode: overlay`). Rotated 90°
        from the pfp version: RIGHT side is the "top" (brightest light),
        LEFT side is the "bottom" (anchor shadow). Top/bottom edges are
        the "sides" (ambient reflection). */
  --cc-gloss: radial-gradient(
    ellipse 90% 120% at 145% 50%,
    rgba(255, 255, 255, 0.40) 0%,
    rgba(255, 255, 255, 0.12) 60%,
    rgba(255, 255, 255, 0.00) 100%);
  --cc-rim-right: rgba(255, 255, 255, 0.36);
  --cc-rim-side:  rgba(255, 255, 255, 0.14);
  --cc-rim-left:  rgba(0,   0,   0,   0.25);
}

/* ─── E. EFFECTS ────────────────────────────────────────────────────
   `::after` paints the blurred + dimmed banner backdrop (z-index 0;
   sits below `.cc-banner-img` at z-index 1).
   `::before` paints the domed gloss + reflective rim with
   `mix-blend-mode: overlay`, on top of all card content (z-index 5).

   Note: the hit-area `::before` (snap-to-cursor on leaderboard /
   header / user-search) was previously here — removed because rim +
   gloss now own `::before` and the visual treatment is more important
   than the small hover-snap nicety. */

/* `.cc-entry::before` and `.cc-entry::after` are defined alongside
   `.cc-pfp-wrapper`'s in the PFP DESIGN SYSTEM — gloss + rim with
   `--pfp-gloss` / `--pfp-rim-*` (see ~styles.css:2594). The card's banner
   image sits BELOW these layers (z-index 1) so the gloss/rim glaze
   over the visible banner like the notif-item glyph. */

/* ─── F. HOVERED ────────────────────────────────────────────────────
   Motion (lift + scale) + outer ring/bg → profile-color tint + banner
   opacity bump. Text labels do NOT change on hover. Desktop pointers
   only — touch never triggers tilt/lift. */

@media (hover: hover) {
  .cc-entry:hover {
    --cc-lift:                var(--cc-lift-hover);
    --cc-press:               var(--cc-press-hover);
    /* Bg / border both tint toward the profile color while keeping the
       gray-9 / gray-8 lightness pair from the rest state — bg mixes
       into gray-9, border into gray-8. The pfp border stays neutral. */
    --cc-outer-border-color:  color-mix(in srgb, var(--cc-profile-color, #6a6a6a) 45%, var(--color-gray-6));
    background:               color-mix(in srgb, var(--cc-profile-color, #6a6a6a) 50%, var(--color-gray-9));
  }
  /* Active card hover: lighter lift (it's already raised at rest) */
  .cc-entry.active:hover {
    --cc-lift:  var(--cc-lift-active-hover);
    --cc-press: 1;
  }
  /* Banner brightens on hover */
  .cc-entry:hover .cc-banner-img { opacity: var(--cc-banner-hover); }
}

/* ─── G. PRESSED ────────────────────────────────────────────────────
   Scale only (no chrome color change). `transition` overridden for a
   snappier press feel. */

.cc-entry.cc-pressed {
  --cc-press: var(--cc-press-pressed);
  transition: var(--cc-transition-pressed);
}
.cc-entry.active.cc-pressed { --cc-press: var(--cc-press-active-pressed); }

/* ─── I. ANIMATIONS ─────────────────────────────────────────────────
   Two rebound keyframe sets fire on press release via
   `attachCcPressAnimation`: `cc-rebound` (full spring) for non-active
   cards, `cc-rebound-soft` (muted) for active cards. Outer ring stays
   colored during the animation so the rebound reads as a continuation
   of the pressed-and-released state. */

.cc-entry.cc-rebound,
.cc-entry.cc-rebound-soft { --cc-outer-border-color: color-mix(in srgb, var(--cc-profile-color, #6a6a6a) 45%, var(--color-gray-6)); }
.cc-entry.cc-rebound      { animation: cc-rebound      0.42s cubic-bezier(0.45, 0.05, 0.55, 0.95); }

.dock-pill {
  height: 20px;
  border-radius: 9px;
  border: none;
  padding: 0 10px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  display: flex;
  align-items: center;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: default;
  user-select: none;
  transform-origin: top center;
}

/* Tiny inline pfp injected next to each {userN} placeholder in header
   pills. Plugged into the PFP design system above (gloss/rim selector
   lists) so it gets the same shading as cc-pfp / notif-item etc. */
.pill-pfp {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  margin-right: 4px;
  /* Slightly tighter rim for the small footprint. */
  --pfp-border-width: 0.5px;
  /* `middle` aligns the pfp's vertical center with the line's x-height
     midpoint. The 1px translate compensates for the gap between that
     midpoint and the pill's geometric center (font metrics put the
     x-height midpoint slightly below the pill's true center). */
  vertical-align: middle;
  transform: translateY(-1px);
}
.pill-pfp-img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: var(--color-gray-2);
  object-fit: cover;
  display: block;
}

/* Single wrapper around the rendered pill body. Keeps the templated
   {pfp + name + literal text} fragments inside one flex item of the
   parent .dock-pill so leading whitespace after a `</span>` isn't
   collapsed (block-box rule on anonymous flex items). */
.pill-content {
  display: inline;
  white-space: nowrap;
}

/* Wraps the {pfp + name} pair from a templated pill so they read as one
   clickable unit. Hovering anywhere within the wrapper underlines the
   name; the pfp does NOT get the underline (it's a sibling).
   Display is plain `inline` (not inline-flex) so the trailing text
   following the link sits on the same baseline as the name — the pfp's
   own `vertical-align: -2px` handles its baseline nudge. */
.pill-user-link {
  display: inline;
  cursor: pointer;
}
.pill-user-link:hover .pill-name {
  text-decoration: underline;
}

.pill-red { background: #6e1a1a; color: #ff8080; }
.pill-orange { background: #6e3a1a; color: #ffb380; }
.pill-blue { background: #1a3a6e; color: #80b3ff; }
.pill-purple { background: #4a1a6e; color: #c080ff; }
.pill-green { background: #1a6e2e; color: #80ff99; }
.pill-yellow { background: #6e5a1a; color: #ffe080; }
.pill-transparent { background: transparent; color: var(--color-white); font-size: 10px; }

.recording-time {
  color: var(--color-white);
  font-weight: 700;
  font-size: var(--font-size-lg);
  min-width: 50px;
}

/* --- Chat Panel: XP Bar --- */
/* Drawer */
.chat-xp-bar {
  height: 8px;
  background: transparent;
  overflow: hidden;
  flex-shrink: 0;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 100;
  pointer-events: none;
}

/* Both the XP bar and the inventory drawer are global overlays. They only
   make sense on the chat page — hide them on signin / onboarding for both
   desktop and mobile. */
body:not(.page-chat) .chat-xp-bar {
  display: none !important;
}

/* Mobile: XP bar lives at the top of the screen above the pill header,
   since the bottom is now occupied by the inventory drawer. */
body.is-mobile .chat-xp-bar {
  top: 0;
  bottom: auto;
}

.chat-xp-fill {
  height: 100%;
  background: #c0762d;
  width: 0;
  transition: width 1.2s linear, background 0.5s ease;
}

.chat-xp-fill.xp-loss {
  background: #c02d2d;
}

/* --- Chat Panel: Animations --- */

@keyframes msg-jiggle {
  0%   { transform: scale(1); }
  25%  { transform: scale(1.08); }
  50%  { transform: scale(0.97); }
  75%  { transform: scale(1.02); }
  100% { transform: scale(1); }
}

.chat-message-new {
  animation: msg-slide-in-other 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.player-panel-section .action-btn { padding: 8px 12px; font-size: var(--font-size-sm); }
.player-panel-section .notifications-empty {
  font-size: var(--font-size-sm);
  color: var(--color-gray-8);
  text-align: center;
  padding: 4px;
  display: block;
}

/* Shared shake keyframe used by the PFP system + a few other elements
   (pill scroll buttons, chat secondary action buttons). Not cc-entry-
   specific but lives here for historical reasons — leave in place. */
@keyframes cc-pfp-shake {
  0%   { transform: rotate(0deg); }
  18%  { transform: rotate(-5deg); }
  36%  { transform: rotate(4deg); }
  54%  { transform: rotate(-3deg); }
  72%  { transform: rotate(2deg); }
  88%  { transform: rotate(-1deg); }
  100% { transform: rotate(0deg); }
}

/* Numeric font (Nunito) + tabular digits for all number-displaying elements */
.cc-rank,
.msg-balance, .msg-level-num,
.stat-value,
.mission-timer,
.leaderboard-value, .leaderboard-online-count,
.presale-user-value, .presale-input,
.slot-charges, .item-ammo-label,
.notif-badge-count {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
}

.slot-icon { color: var(--color-text-primary); font-weight: 400; font-size: var(--font-size-xxl); }
.inventory-slot.equipped .slot-icon { filter: invert(1); }

.dock-pill.pill-landed {
  animation: pill-land 0.3s ease-out;
}

.pawn-action .action-btn {
  width: 100%;
}

/* --- Profile Panel: Header & Navigation --- */
.notification-header {
  height: 34px;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  background: transparent;
  border-radius: 0;
  position: relative;
}

.profile-dropdown-bank .action-btn {
  flex: 1;
}
.bank-modal-actions .action-btn {
  flex: 1;
}
.inventory-panel-slots .inventory-slot {
  width: auto;
  height: auto;
  aspect-ratio: auto;
  background: transparent;
}

.inventory-slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  padding: var(--spacing);
  box-sizing: border-box;
  border: none;
  border-radius: 2px;
  background: var(--color-gray-1);
  cursor: pointer;
  transition: all 0.2s;
}

.inventory-slot:hover { border-color: var(--color-nuke-primary); }
.inventory-slot.empty { cursor: default; }

.inventory-slot.selected {
  border-color: var(--color-nuke-primary);
  background: rgba(237, 28, 36, 0.05);
}

/* Notification header bar */
.notification-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  font-size: var(--font-size-md);
  border-bottom: none;
}

.notif-rotate-item { display: none; }
.notif-rotate-item.active { display: inline; }

.notif-season {
  font-family: var(--font-conduit);
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-white);
  letter-spacing: 1px;
  text-transform: uppercase;
}

.notif-online {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
}

.notif-jackpot {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-nuke-accent);
}

.notif-info {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
}

.notifications-header-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

#notifications-count {
  display: inline-block;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: var(--color-nuke-primary);
  color: var(--color-text-primary);
  font-size: var(--font-size-sm);
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  border-radius: 8px;
}

#notifications-count:empty {
  display: none;
}

.notifications-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--spacing);
}

.notifications-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.notifications-count {
  font-size: var(--font-size-sm);
  color: var(--color-gray-5);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.notifications-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

.notifications-list::-webkit-scrollbar {
  display: none;
}

.notifications-empty {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  font-weight: 400;
  padding: var(--spacing);
  text-align: center;
}

.notif-item {
  position: relative;
  padding: var(--spacing);
  background: var(--color-gray-1);
  font-size: var(--font-size-sm);
  transition: background 0.15s;
}

.notif-item.unread {
  background: var(--color-gray-2);
}

.notif-unread-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 8px;
  height: 8px;
  background: var(--color-nuke-success, #4caf50);
  border-radius: 50%;
}

.notif-item, .notif-item * { color: var(--color-black); }

.notif-mention-quote {
  margin-top: 4px;
  padding: 4px 8px;
  background: var(--color-gray-2);
  border-left: 2px solid var(--color-nuke-link);
  color: var(--color-gray-6, var(--color-white));
  font-style: italic;
}

.notifications-header-text-wrapper {
  overflow: hidden;
  min-width: 0;
}

#notifications-header-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-weight: 400;
  font-size: var(--font-size-md);
  color: var(--color-text-primary);
}

.notif-header-quote {
  margin-left: 8px;
  color: var(--color-gray-5);
  font-style: italic;
}
.notif-header-quote::before {
  content: '"';
}
.notif-header-quote::after {
  content: '"';
}

/* --- Profile Panel: Missions --- */

@keyframes mission-bg-in {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

#mission-list .notif-item.mission-locked {
  opacity: 0.45;
  background: rgba(255, 255, 255, 0.015);
  border-color: var(--color-gray-4);
}

#mission-list .notif-item.mission-claimed {
  opacity: 0.7;
  text-decoration: line-through;
  text-decoration-color: var(--color-gray-7);
}

#mission-list .notif-item.mission-exhausted {
  opacity: 0.5;
  font-style: italic;
}

#mission-list .notif-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.shortcuts-panel.hidden { display: none; }

.presale-input-group .action-btn {
  flex-shrink: 0;
}

.notification-header-toast-clip {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: 400px;
  overflow: hidden;
  pointer-events: none;
}

.notification-header .mention-toast {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: relative;
  left: 0;
  right: 0;
  width: 100%;
  transform: translateY(-100%);
  transition: transform 0.15s ease-out;
  pointer-events: auto;
}

.notification-header .mention-toast-visible {
  transform: translateY(4px);
}

/* ===========================================
   13. ANIMATIONS
   =========================================== */
@keyframes crt-scroll {
  0% { background-position: 0 0, 0 0; }
  100% { background-position: 0 0, 0 80px; }
}

/* ============ Announcement Popup ============ */

.announcement-popup {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}

.announcement-popup-content {
  background: var(--bg-primary, #1a1a1a);
  border: 1px solid var(--border-color, #333);
  border-radius: 8px;
  padding: 24px;
  max-width: 360px;
  width: 90%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.announcement-popup-header {
  font-size: 16px;
  font-weight: bold;
  color: var(--text-primary, #fff);
}

.announcement-popup-desc {
  font-size: 13px;
  color: var(--text-secondary, #999);
  margin: 0;
}

.announcement-msg-input {
  background: var(--bg-secondary, #222);
  border: 1px solid var(--border-color, #444);
  border-radius: 4px;
  color: var(--color-white);
  padding: 8px 10px;
  font-size: var(--font-size-md);
  font-weight: 400;
  font-family: inherit;
}

.announcement-confirm-btn {
  background: var(--accent-color, #4a9eff);
  color: var(--color-white);
  border: none;
  border-radius: 4px;
  padding: 12px;
  font-size: var(--font-size-md);
  font-weight: 400;
  cursor: pointer;
  font-family: inherit;
}

.announcement-confirm-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.announcement-cancel-btn {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px solid var(--border-color, #444);
  border-radius: 4px;
  padding: 8px;
  font-size: var(--font-size-sm);
  font-weight: 400;
  cursor: pointer;
  font-family: inherit;
}

.announcement-status {
  font-size: 12px;
  color: var(--text-secondary, #999);
  min-height: 1em;
}

/* ============ Announcement Overlay (Radio) ============ */

.announcement-overlay {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9000;
  background: rgba(0, 0, 0, 0.85);
  border: 1px solid var(--accent-color, #4a9eff);
  border-radius: 8px;
  padding: 12px 24px;
  color: var(--color-white);
  font-size: 14px;
  pointer-events: none;
  animation: announcement-pulse 1.5s ease-in-out infinite;
}

/* ============ Item Preview Modal ============ */
.item-preview-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}

.item-preview-modal {
  position: relative;
  width: 280px;
  background: var(--color-gray-1);
  border-radius: 12px;
  padding: 24px 20px 16px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

.item-preview-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--color-gray-8);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}

.item-preview-close:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--color-white);
}

.item-preview-icon {
  width: 96px;
  height: 96px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 64px;
  margin-bottom: 12px;
}

.item-preview-name {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-white);
  text-align: center;
  margin-bottom: 12px;
}

.item-preview-description {
  font-size: var(--font-size-sm);
  color: var(--color-gray-8);
  text-align: center;
  line-height: 1.4;
  margin-bottom: 20px;
  min-height: 40px;
}

.item-preview-buy-btn,
.item-preview-action-btn {
  padding: 12px 16px;
  border: none;
  border-radius: 12px;
  font-size: var(--font-size-md);
  font-weight: 700;
  cursor: pointer;
  transition: filter 0.15s;
  color: var(--color-white);
}

.item-preview-buy-btn {
  width: 100%;
  background: var(--color-nuke-primary);
}

.item-preview-actions {
  display: flex;
  gap: 8px;
  width: 100%;
}

.item-preview-action-btn {
  flex: 1;
}

.item-preview-action-btn.primary {
  background: var(--color-nuke-primary);
}

.item-preview-action-btn.secondary {
  background: var(--color-gray-4);
}

.item-preview-buy-btn:hover,
.item-preview-action-btn:hover {
  filter: brightness(1.1);
}

.item-preview-buy-btn:active,
.item-preview-action-btn:active {
  filter: brightness(0.92);
}











/* /nuke sandbox styles have been moved to src/css/styles.css. Loaded
   alongside styles.css via a separate <link> in index.html. Keep
   nuke-page rules out of this file. */

/* ============================================
   Cursors — the custom Notwaita bitmap cursors were removed site-wide. These keep
   the STANDARD SYSTEM cursor semantics (pointer on clickables, text in inputs,
   etc.) so nothing regresses to a bare arrow. The ONLY custom cursor left is the
   notepad pen (`.nuke-notebook-canvas` in styles.css). `!important` so per-element
   rules don't shadow these.
   ============================================ */
a,
button,
[role="button"],
[role="tab"],
[role="menuitem"],
[role="option"],
[role="link"],
[onclick],
label[for],
summary,
select,
.cursor-pointer,
.classic-button,
.action-btn,
.action-button,
.nuke-stage-tab,
.nuke-stage-tab-action,
.contact-group-header,
.nuke-row,
.nuke-profile-name-row,
.nuke-profile-mail-item,
.item-card:not(.item-card-empty),
.nuke-post-quote,
.nuke-post[data-has-account="true"],
.nuke-reaction-badge,
.nuke-chat-img-wrap {
  cursor: pointer !important;
}


/* ============================================================
   ANIMATION LIBRARY — @keyframes preserved from the old prod UI.
   The prod rules that referenced these were removed; the keyframes
   themselves are kept, named, and grouped here for reuse later.
   ============================================================ */

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

@keyframes signin-dots {
  0% { content: ''; }
  25% { content: '.'; }
  50% { content: '..'; }
  75% { content: '...'; }
}

@keyframes cc-rebound {
  0%   { --cc-press: 0.97;   }
  28%  { --cc-press: 1.018;  }
  52%  { --cc-press: 0.995;  }
  72%  { --cc-press: 1.003;  }
  88%  { --cc-press: 0.9995; }
  100% { --cc-press: 1;      }
}

@keyframes cc-rebound-soft {
  0%   { --cc-press: 0.99; --cc-tilt: 0deg; }
  100% { --cc-press: 1;    --cc-tilt: 0deg; }
}

@keyframes msg-row-fade {
  0%, 30% { opacity: 1; }
  100%    { opacity: 0; }
}

@keyframes msg-slide-in-other {
  0% { transform: translateY(20px) scale(0.95) rotate(0deg); }
  20% { transform: translateY(-4px) scale(1.02) rotate(var(--tilt, -0.8deg)); }
  40% { transform: translateY(3px) scale(0.99) rotate(0.5deg); }
  60% { transform: translateY(-1px) scale(1.005) rotate(-0.25deg); }
  80% { transform: translateY(0) scale(1) rotate(0.1deg); }
  100% { transform: translateY(0) scale(1) rotate(0deg); }
}

@keyframes shop-jiggle {
  0%   { transform: translateX(0); }
  15%  { transform: translateX(-4px); }
  30%  { transform: translateX(4px); }
  45%  { transform: translateX(-3px); }
  60%  { transform: translateX(3px); }
  75%  { transform: translateX(-1px); }
  90%  { transform: translateX(1px); }
  100% { transform: translateX(0); }
}

@keyframes pill-land {
  0%   { transform: scale(1); }
  25%  { transform: scale(1.08); }
  50%  { transform: scale(0.97); }
  75%  { transform: scale(1.02); }
  100% { transform: scale(1); }
}

@keyframes mission-bg-out {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes mission-in {
  0% { transform: scale(0.9); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes mission-out {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.9); opacity: 0; }
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.7; }
}

@keyframes blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

@keyframes announcement-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}


/* ===================================================================
   NUKE PAGE — styles scoped under .nuke-container (was styles.css)
   =================================================================== */

/* =============================================================
   /nuke sandbox stylesheet — MSN Messenger design
   =============================================================
   All visual rules for the /nuke sandbox live in this file,
   scoped under `.nuke-container ...` (or the `page-nuke` body /
   html class for global mode). Imported alongside styles.css from
   index.html. Keep this file the sole source of truth for any
   visual property used on the nuke page; HTML and TS template
   strings should hold layout primitives only.
   ============================================================= */

/* --- Page-mode global rules ------------------------------------ */

body.page-nuke .content {
  padding: 0;
  overflow: hidden;
}
/* Block all overscroll rubber-band on the /nuke sandbox — both the
   document itself and the inner scroll containers. The `page-nuke`
   class is set on BOTH `html` and `body` (see main.ts) so we don't
   need a `:has()` walk to reach `html`. */
body.page-nuke,
html.page-nuke {
  overscroll-behavior: none;
}
/* Solid white page background on /nuke — overrides the global `body`
   `#eaeaea`, and hides the tiled `wallpaper.png` pattern (`body::before`). */
body.page-nuke {
  background: #FFFFFF;
}
body.page-nuke::before {
  display: none;
}

/* --- Pill overrides (nuke-page color/typography family) -------- */

/* Nuke-page pill overrides — soft MSN-style chrome that fits the
   light-blue/white nuke chat aesthetic. Lighter pastel backgrounds
   with dark text for legibility on the white chat bg, plus a thin
   matching border (same #E0E0E0 used everywhere else in the nuke
   sandbox) so the pills read as the same component family as the
   chat bubbles and white containers. */
.nuke-container .dock-pill {
  height: auto;
  /* Tight vertical padding + a pinned line-height keep the pill short
     so it clears the 32px header strip with margin to spare (avoids the
     top edge being clipped by the ticker's `overflow-y: hidden`). */
  padding: 1px 9px;
  line-height: 1.3;
  /* Match the local chat-bubble's typography exactly — same font
     family + weight as messages sent by you (the inline `<span>`
     in the bubble template uses `var(--font-conduit)` with no
     explicit weight, so a regular 400 here mirrors it). */
  font-family: var(--font-conduit);
  font-size: 13px;
  font-weight: 500;
  border-radius: 10px;
  border: 1px solid #E0E0E0;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.02);
}
.nuke-container .pill-red    { background: #FFE5E5; color: #8B1F1F; --pill-bd: #F5C7C7; border-color: var(--pill-bd); }
.nuke-container .pill-orange { background: #FFEEDD; color: #8B5A1F; --pill-bd: #F5D9B7; border-color: var(--pill-bd); }
.nuke-container .pill-blue   { background: #E6F1FF; color: #1F478B; --pill-bd: #C8DDF2; border-color: var(--pill-bd); }
.nuke-container .pill-purple { background: #F0E5FF; color: #5A1F8B; --pill-bd: #DFC8F2; border-color: var(--pill-bd); }
.nuke-container .pill-green  { background: #E5F5E5; color: #1F6B1F; --pill-bd: #C7E2C7; border-color: var(--pill-bd); }
.nuke-container .pill-yellow { background: #FFF5D9; color: #8B6B1F; --pill-bd: #F2E2B0; border-color: var(--pill-bd); }
.nuke-container .pill-transparent {
  background: transparent;
  color: #666;
  border-color: transparent;
  box-shadow: none;
  font-size: 12px;
  font-weight: 500;
}

/* --- Mini-preflight reset (scoped to nuke subtree) ------------- */

/* Mini-preflight for borders, scoped to the nuke sandbox subtree.
   Without this, Tailwind's `border-t` / `border-l` / `border-r` /
   `border-b` only set `border-*-width: 1px` — the other three sides
   default to `border-width: medium` (3px) and `border-style: none`,
   so adding `border-solid` would render borders on all four sides
   instead of just the intended one. Setting `border-width: 0` and
   `border-style: solid` globally for the subtree means
   side-specific Tailwind border classes work the way Tailwind
   documents them. Specificity stays 0,0,0 so explicit `.foo {
   border: 1px solid X }` rules still override. */
.nuke-container *,
.nuke-container *::before,
.nuke-container *::after {
  border-width: 0;
  border-style: solid;
}

/* --- Nuke-container shell -------------------------------------- */

.nuke-container {
  /* Fills the viewport at native 1:1 — true pixel size on every monitor. */
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
  user-select: none;
  /* Reserve space for the fixed-position chrome at the top: the 62px search
     header + the 40px pill header = 102px. The .nuke-page-footer was
     removed, so no bottom reservation is needed — the stage's own 16px
     padding-bottom is the only bottom gap. */
  padding-top: 102px;
  padding-bottom: 0;
  /* Sunghyun Sans is the main site's calling-card font. Bundled
     locally in three weights only (400 / 700 / 900) — no thin variant,
     so secondary fonts on this page stay as the source design's
     inline values. `Noto Color Emoji` is already in the var's fallback
     chain (see :root) so emoji content renders in color here too. */
  font-family: var(--font-conduit);
  font-size: 13px;
  color: #000000;
  /* Plain white page background. */
  background-color: #ffffff;
  /* Defined here so `.nuke-stage`, `.nuke-side-left`, and
     `.nuke-side-right` (siblings) can all read these. */
  /* LEFT column holds the profile card + notebook (fixed 320px). */
  --left-col-w: 320px;
  /* The vw-flexed column (now LEFT = chat): 30vw clamped to
     [360px, 480px]. Safe to use vw-based widths here because the
     column is `position: fixed; right: 8px` — its right edge is
     anchored directly to the viewport, so width changes only move
     the LEFT edge (no detachment from the viewport-right edge). */
  /* RIGHT column holds chat/online (the wider, vw-flexed one). */
  /* 384px column = 360px content + the 12px --right-inset on each side. */
  --right-col-w: 398px; /* 350px content (x.com's sidebarColumn) + the 24px --right-inset on each side */
  /* Inner horizontal inset for the right column's content. The column keeps its
     full width; this just pads the content inward by 24px on each side, leaving
     350px of content. */
  --right-inset: 24px;
  /* Missions column (far right) — matches the profile column width. */
  --missions-col-w: 320px;
  /* X.com-style 3-column layout, centered as a group with a gap between each.
     The middle chat column is the X.com feed width (600px). The left (Profile)
     column is `--col-w`; the right (Shop) column is a fixed `--right-col-w`
     (350px, x.com's sidebar width) that collapses on narrow viewports — see the
     media query at the end of this file. */
  --chat-w: 600px;
  --col-w: clamp(340px, 25vw, 480px);
  /* No inter-column gap — the only spacing between the chat and the side panels
     is each panel's 8px content inset (--right-inset). */
  --col-gap: 0px;
  /* x.com-style left nav rail (replaces the old profile side panel). A fixed,
     narrow icons+labels column glued to the left of the centered feed — see
     `.nuke-nav`. Narrower than the old profile column (`--col-w`).
     348px column → 300px content (348 − 24×2 inset). */
  --nav-col-w: 348px;
  /* Flat 16px gutter on each side, matching the 16px top/bottom gap. The page
     renders at native 1:1 (no zoom/scaling), so the columns just anchor to the
     viewport at this fixed inset on every monitor. */
  --page-edge-x: 16px;
  /* Mobile-build chrome heights — consumed by the responsive tiers at the
     bottom of this file (≤768px): the slim top app bar + the bottom tab bar
     that frame the full-width feed on phones. */
  --m-topbar-h: 48px;
  --m-botnav-h: 54px;
}
.nuke-container.hidden { display: none; }

/* --- Nuke-page header / footer / stage / side columns ---------- */

/* Empty full-width top bar — solid white, spans the whole page at the very top.
   48px tall. Everything else below is anchored 48px lower (+12px gap) to clear
   it. Placeholder; no content yet. */
.nuke-container .nuke-page-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 48px;
  /* The element stays full-width (its logo + pill ticker are positioned with 50%
     relative to it), but the white fill is clipped to the panel CONTENT span — the
     left nav's content edge (column + 24px padding) to the right column's content
     edge (column − --right-inset) — so it lines up with the visible cards below,
     not the column boxes. */
  background: linear-gradient(
    to right,
    transparent 0,
    transparent calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w) + 24px),
    #FFFFFF     calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w) + 24px),
    #FFFFFF     calc(50% + var(--chat-w) / 2 + var(--col-gap) + var(--right-col-w) - var(--right-inset)),
    transparent calc(50% + var(--chat-w) / 2 + var(--col-gap) + var(--right-col-w) - var(--right-inset)),
    transparent 100%
  );
  z-index: 35;
  /* The top bar sits ABOVE the spotlight scrim (z-28), so the scrim can't darken
     it. Instead dim it with brightness(0.7) — which equals the scrim's
     rgba(0,0,0,.3) — when the spotlight is active (see rule below). Transitioned
     over the scrim's 0.18s so they fade together (no z-index snap). */
  transition: filter 0.18s ease;
}
/* Spotlight active (search hovered/focused, or a profile hovercard open) → dim the
   top bar in lockstep with the rest of the page. */
.nuke-container:has(.nuke-subheader-search .user-search-bar:hover) .nuke-page-topbar,
.nuke-container:has(.nuke-subheader-search:focus-within) .nuke-page-topbar,
.nuke-container:has(.nuke-hovercard.is-visible) .nuke-page-topbar {
  filter: brightness(0.4);
}
/* Brand logo in the header — left-aligned over the nav column (its left edge +
   the nav's 12px inset), the same horizontal alignment it had in the left rail.
   Vertically centered in the bar (transform-free; margins handle the 50px button
   in the 48px bar). */
.nuke-container .nuke-page-topbar .nuke-nav-logo {
  position: absolute;
  left: calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w) + 24px);
  top: 0;
  bottom: 0;
  margin: auto 0;
}
/* Recent-actions pill ticker, hosted in the top bar. Spans from the chat
   column's LEFT edge to the right panel's RIGHT edge (chat is centered at
   width --chat-w; the right panel ends at 50% + chat-w/2 + col-gap + right-col-w),
   and is vertically centered in the bar (overrides the old bottom-aligned band). */
.nuke-container .nuke-page-topbar .nuke-pill-header {
  position: absolute;
  top: 0;
  bottom: 0;
  height: auto;
  left: calc(50% - var(--chat-w) / 2);
  right: calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--right-col-w) + var(--right-inset));
  align-items: center;
  /* First pill sits flush at the left edge; 12px trailing gutter at the right
     (the original gutters — the leading/trailing padding scrolls off-screen once
     the nav buttons appear, so button clearance is handled by the frosted edges,
     not here). */
  padding-left: 0;
  padding-right: 12px;
  /* Alpha mask: pills are fully HIDDEN only at the very outer edge (0→l1), then
     ramp to opaque quickly by l2 — kept SHORT (much less than the ~80px frost) so
     the pill becomes opaque well within the blur zone. The frosted edge (below)
     then blurs that now-visible pill, tapering to crisp ~140px in: hidden sliver →
     opaque blurred → crisp. Per-side vars collapse to 0 when that end's nav button
     is hidden, leaving the first/last pill crisp. */
  --pe-l1: 20px;
  --pe-l2: 60px;
  --pe-r1: 20px;
  --pe-r2: 60px;
  -webkit-mask: linear-gradient(to right, transparent 0, transparent var(--pe-l1), #000 var(--pe-l2), #000 calc(100% - var(--pe-r2)), transparent calc(100% - var(--pe-r1)), transparent 100%);
  mask: linear-gradient(to right, transparent 0, transparent var(--pe-l1), #000 var(--pe-l2), #000 calc(100% - var(--pe-r2)), transparent calc(100% - var(--pe-r1)), transparent 100%);
}
/* Left button hidden (at the start) → no left hide-mask, first pill crisp. */
.nuke-container .nuke-page-topbar:has(.nuke-pill-scroll-cap--left.is-hidden) .nuke-pill-header {
  --pe-l1: 0px;
  --pe-l2: 0px;
}
/* Right button hidden (at the end) → no right hide-mask, last pill crisp. */
.nuke-container .nuke-page-topbar:has(.nuke-pill-scroll-cap--right.is-hidden) .nuke-pill-header {
  --pe-r1: 0px;
  --pe-r2: 0px;
}
/* Frosted edges, layered OVER the strip's hide-mask: a graduated blur (no white
   tint — the tint washed out the blur; the strip's alpha mask already lightens the
   edge) that sits across the strip's reveal ramp, so a pill emerging from the hidden
   outer edge appears blurred first, then sharpens. `backdrop-filter` blurs the pills
   behind; the layer's own `mask` ramps the blur from strongest at the very edge to
   nothing ~140px inward. Sits below the nav buttons (z-2) and above the pill strip. */
.nuke-container .nuke-page-topbar::before,
.nuke-container .nuke-page-topbar::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 140px;
  z-index: 1;
  pointer-events: none;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  /* Swift fade so a side's frost appears/vanishes with its nav button. */
  transition: opacity 0.16s ease;
}
.nuke-container .nuke-page-topbar::before {
  left: calc(50% - var(--chat-w) / 2);
  -webkit-mask: linear-gradient(to right, #000, transparent);
  mask: linear-gradient(to right, #000, transparent);
}
.nuke-container .nuke-page-topbar::after {
  right: calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--right-col-w) + var(--right-inset));
  -webkit-mask: linear-gradient(to left, #000, transparent);
  mask: linear-gradient(to left, #000, transparent);
}
/* Left button hidden (scrolled to the start) → no left frost, first pill crisp. */
.nuke-container .nuke-page-topbar:has(.nuke-pill-scroll-cap--left.is-hidden)::before {
  opacity: 0;
}
/* Right button hidden (scrolled to the end) → no right frost, last pill crisp. */
.nuke-container .nuke-page-topbar:has(.nuke-pill-scroll-cap--right.is-hidden)::after {
  opacity: 0;
}
/* Round nav button inset from each end of the pill strip. The frosted edge
   (topbar ::before / ::after) blurs the pills as they slide past, so the buttons
   read as floating scroll controls (X-style) rather than hard white blocks. */
.nuke-container .nuke-page-topbar .nuke-pill-scroll-cap {
  position: absolute;
  /* Center vertically WITHOUT a transform (top/bottom + auto margins): a
     `translateY(-50%)` under the container's `zoom` lands on a fractional pixel,
     which the browser snaps differently when the opacity fade promotes/demotes
     the layer — causing the arrow to jump a couple px. This is transform-free. */
  top: 0;
  bottom: 0;
  margin-top: auto;
  margin-bottom: auto;
  width: 28px;
  height: 28px;
  background: #EFF3F4;
  border: none;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #536471;
  /* Arrow glyphs come from the Rosetta glyph font: F172 = left, F173 = right. */
  font-family: 'Rosetta';
  font-size: 13px;
  line-height: 1;
  cursor: pointer; /* clicking snaps to the next / previous pill */
  z-index: 2; /* above the pill strip so it sits over the fading pills */
  /* Swift fade in/out, toggled by `.is-hidden` (JS) when that end of the strip
     is reached / there's nothing more to scroll. `will-change` keeps it on a
     stable composited layer so the fade doesn't re-snap its position. */
  transition: opacity 0.16s ease, background 0.12s ease;
  will-change: opacity;
}
.nuke-container .nuke-page-topbar .nuke-pill-scroll-cap:hover {
  background: #E1E8ED;
}
/* Round scroll buttons sit flush on each strip edge (no inset). */
.nuke-container .nuke-page-topbar .nuke-pill-scroll-cap--right {
  right: calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--right-col-w) + var(--right-inset));
}
.nuke-container .nuke-page-topbar .nuke-pill-scroll-cap--left {
  left: calc(50% - var(--chat-w) / 2);
}
/* Reached that end (or no overflow) → fade the whole cap group out (opacity
   covers the white box, the arrow, AND the `::before` fade). */
.nuke-container .nuke-page-topbar .nuke-pill-scroll-cap.is-hidden {
  opacity: 0;
  pointer-events: none;
}
/* Top header bar, fixed at the very top. 54px tall = the 38px search bar +
   8px padding above and below. No background — only the search bar inside it
   is visible. */
.nuke-container .nuke-subheader {
  position: fixed;
  top: 48px; /* flush below the 48px top bar — no gap */
  /* Over the RIGHT (profile) column ONLY — a header for that column, X-style.
     The chat + Shop columns have no page header above them, so they reach the
     very top of the page. */
  left: calc(50% + var(--chat-w) / 2 + var(--col-gap));
  right: auto;
  width: var(--right-col-w);
  height: 62px;
  background: transparent;
  z-index: 30;
  display: flex;
  /* Center the search bar on the chat's top sort header. That header sits at the
     column top (60px — its absolute top:0 ignores the chat-container margin),
     height 53px → center 86.5px. The 44px bar centered there has its top at
     64.5px; with the bar centered in its 38px row (−3px) that needs 7.5px padding.
     The focused dropdown is pinned to the same top so focusing doesn't shift it. */
  align-items: flex-start;
  justify-content: center;
  padding: 7.5px 0 0 0;
}
/* Search row — same width as the right (profile) column (`--col-w`), aligned
   over it. */
.nuke-container .nuke-subheader-search {
  width: var(--right-col-w);
  flex: 0 0 auto;
  /* Inset the collapsed search bar from the column edges (border-box keeps the
     column at --right-col-w). */
  padding-left: var(--right-inset);
  padding-right: var(--right-inset);
}

/* Spotlight scrim (prod /home parity): a full-page dim shown while the search
   is hovered or focused. Sits ABOVE the columns + chat stage (z-index 25) but
   BELOW the search subheader (30) and its focused dropdown (40), so everything
   dims except the search itself — no z-index lifting needed. */
.nuke-container .nuke-hover-spotlight {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
  z-index: 28;
}
.nuke-container:has(.nuke-subheader-search .user-search-bar:hover) .nuke-hover-spotlight,
.nuke-container:has(.nuke-subheader-search:focus-within) .nuke-hover-spotlight,
/* Open profile hovercard dims the page too — the card (z-50) sits above the
   scrim (z-28) so it stays bright, just like the raised search bar. */
.nuke-container:has(.nuke-hovercard.is-visible) .nuke-hover-spotlight {
  opacity: 1;
}
/* While a hovercard is open the search bar isn't the focus, so dim it too — but
   instead of dropping it below the scrim (z-index can't animate, so it would snap
   bright before the scrim finished fading), darken it with `brightness(0.7)`,
   which is mathematically the scrim's rgba(0,0,0,.3). Transitioned over the same
   0.18s as the scrim so they fade in and out together. */
.nuke-container:has(.nuke-hovercard.is-visible) .nuke-subheader .user-search-bar {
  filter: brightness(0.4);
}

/* Kill the PROD header's leftover spotlight overlay on this page. `#hover-spotlight`
   (styles.css, z-90) is a sibling OUTSIDE `.nuke-container`, but it's still switched on
   by initHoverSpotlight() — which toggles `body.spotlight-search` whenever this page's
   search (a shared `.user-search-wrapper`) is hovered/focused. At z-90 it paints OVER
   the search subheader (z-30): the search can't escape its z-30 stacking context to
   reach prod's z-95 wrapper-lift, so the whole bar + dropdown gets dimmed along with
   the page. This page has its OWN scrim (`.nuke-hover-spotlight`, z-28, correctly
   below the subheader), so the prod one is pure interference — suppress it while /nuke
   is visible. The nuke scrim still dims the page; the search stays bright. (Not
   `.nuke-container`-scoped because the target lives outside that subtree.) */
body:has(#nuke-container:not(.hidden)) #hover-spotlight {
  display: none;
}

/* Recent-actions ticker inside the page header. Horizontal strip of
   `.dock-pill` bubbles (pastel colors from the pill overrides above),
   newest-first, scrolls/drags sideways. Fills the header width; the
   scrollbar is hidden so it reads as a clean ticker. Pills + their
   render/diff logic are shared with the prod chat header (chat.ts
   `populateHeaderPills`). */
.nuke-container .nuke-pill-header {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  display: flex;
  /* Pills sit at the BOTTOM of the header band (the ticker fills the header's
     full height), so the extra header height opens a gap above them — away
     from the profile header. */
  align-items: flex-end;
  gap: 6px;
  /* The header is now one column wide (over the left/Shop column), so the
     pills fill it edge-to-edge and scroll within it. */
  margin-left: 0;
  /* Trailing gutter so the last pill isn't jammed against the edge; an 8px
     leading gutter sets the first pill's distance from the left edge. Lives as
     container padding (not a header inset) so it scrolls WITH the content. */
  padding: 0 12px 0 8px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  user-select: none;
  /* Own compositor layer so scrolling (native AND the JS drag) translates the
     layer on the GPU rather than repainting the pills + their pfp gloss every
     frame on the main thread, which the page-wide zoom otherwise forces. This
     is what brings the drag up to native-trackpad smoothness. */
  transform: translateZ(0);
}
/* Custom grab cursor (site's Notwaita hand) over the whole strip at
   rest, including the pills — the base `.dock-pill` sets
   `cursor: default`, so the pills are listed explicitly to override it.
   Same asset + hotspot as `.cursor-grab` in styles.css; `!important`
   matches the cursor system's convention there. */
.nuke-container .nuke-pill-header,
.nuke-container .nuke-pill-header .dock-pill {
  cursor: grab !important;
}
/* Username links inside a pill are the only clickable part (they open
   the user's profile), so they get the site's custom pointer/click
   cursor (hand2) instead of the strip's grab. Targets the link + its
   inner name/pfp so the whole clickable area reads as clickable. Same
   asset + hotspot as the pointer rule in styles.css. */
.nuke-container .nuke-pill-header .pill-user-link,
.nuke-container .nuke-pill-header .pill-user-link * {
  cursor: pointer !important;
}
/* Active-drag state: no closed-fist "grabbing" asset exists, so use the
   site's `move` cursor (4-arrow) as the dragging affordance. Same asset
   + hotspot as `.cursor-move` in styles.css. The `.dragging` variants for
   the username link carry extra specificity so a drag-in-progress reads
   as move everywhere, even over a clickable name. */
.nuke-container .nuke-pill-header.dragging,
.nuke-container .nuke-pill-header.dragging .dock-pill,
.nuke-container .nuke-pill-header.dragging .pill-user-link,
.nuke-container .nuke-pill-header.dragging .pill-user-link * {
  cursor: move !important;
}
.nuke-container .nuke-pill-header::-webkit-scrollbar {
  display: none;
}
/* Pill pfps + usernames styled like the chat's — 20px pfp + bold name. The
   pill grows a touch so the 20px pfp fits (base pill is 20px tall). */
.nuke-container .nuke-pill-header .pill-pfp {
  width: 24px;
  height: 24px;
  transform: none;
  /* Slightly-rounded square — tracks the shared --pfp-radius (the inner img is
     clipped to this). */
  border-radius: var(--pfp-radius);
  /* Flat — no drop shadow on the pill avatars. */
  box-shadow: none;
  /* 1px border (same thickness as the `.dock-pill` border) painted in the
     EXACT same color the parent pill uses for its own border — both pull from
     the shared `--pill-bd` variable so they can never drift apart. */
  border: 1px solid var(--pill-bd, #E0E0E0);
  /* Keep the 24px footprint with the border inside it. */
  box-sizing: border-box;
}
/* Drop the PFP design-system's 3D rim (lit-top / dark-bottom inset edge) on the
   ticker pfp so its only visible edge is the flat `--pill-bd` border above —
   matching the pill it sits in instead of the rim's grey/dark bevel. */
.nuke-container .nuke-pill-header .pill-pfp::after {
  display: none;
}
/* Flex the whole pill row (like the chat chain header) so the name AND the
   words around it center on the same line. `gap` restores the single space
   that flex collapses at each flex-item boundary (the template's spaces around
   every pill-user-link). */
.nuke-container .nuke-pill-header .pill-content {
  display: flex;
  align-items: center;
  gap: 0.25em;
}
.nuke-container .nuke-pill-header .pill-user-link {
  display: inline-flex;
  align-items: center;
}
.nuke-container .nuke-pill-header .dock-pill {
  /* Equal padding on all sides (4px) so the inline pfp sits the same distance
     from the pill's left edge as from its top/bottom. Content-sized. */
  padding: 4px;
  /* Uniform height for every pill: the pfp pill's full box — 24px pfp + 4px×2
     padding + 1px×2 border = 34px. `border-box` makes `min-height` count the
     whole box, so text-only pills grow to 34px instead of sitting shorter;
     `align-items: center` (base) keeps the short text centered. */
  box-sizing: border-box;
  min-height: 34px;
  /* Match the player-list banner's corner radius (6px) instead of the base 9px. */
  border-radius: 6px;
  /* Flat — no drop shadow on the pills. */
  box-shadow: none;
}
.nuke-container .nuke-pill-header .pill-name {
  font-weight: 600;
}

/* --- Profile card banner (profile tab) ------------------------- */
/* Calling-card banner — sits on the right side of the header,
   behind the PFP and text via z-index: -1 (header creates its own
   stacking context via `isolate`). 224×56 matches the prod 4:1
   banner aspect ratio scaled to the card's 56px height. Mask fades
   the left side so the text/icons stay legible; always visible (no
   hover gate, unlike the contact-row banners). */
.nuke-container .nuke-profile-name {
  font-weight: 600;
  font-size: 14px;
  color: #000;
}
.nuke-container .nuke-profile-status {
  color: #808080; /* same as `.msn-text-grey` */
}
.nuke-container .nuke-stage {
  /* MIDDLE column of the centered trio — the chat, now a fixed centered column
     (was a relative element squeezed to the right via padding). Same top/bottom
     band and z-layer as the side columns. */
  position: fixed;
  /* Flush below the empty page top bar (48px) — no gap. */
  top: 48px;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--chat-w);
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 25;
}

.nuke-container .nuke-side-right {
  /* Anchored directly to the viewport's left/right edge (same approach as
     `.nuke-page-header` / `.nuke-page-footer`). No layout-chain dependency, so no
     resize-detachment frame. top sits the first container 12px below the search
     bar (bar bottom = 60 + 7.5px pad + 38px bar = 105.5px → 117.5px); the `gap`
     below spaces the rest of the column by the same 12px. */
  position: fixed;
  top: 105.5px;
  /* Spans the full height down to the page bottom (no empty footer band).
     The right column overrides this to reserve the 8px XP bar. */
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 25;
  /* Isolate layout / style so internal updates in one panel
     (e.g. chat scroll position, message addition) don't invalidate
     rendering work on the others or the nuke-stage. Paint containment
     is intentionally omitted because it clips child box-shadows to
     the panel's bounds — children (window-frames + notebook) need
     their drop-shadows to extend outside the panel. */
  contain: layout style;
}

/* --- JACKPOT marquee banner (top of the right column, under the search) ----- */
.nuke-container .nuke-jackpot {
  /* "JACKPOT" marquee: bulb lights around a rectangular outline — NO filled
     plaque/background, and NO clipping (so the corner bulbs aren't cut off) —
     plus a bright cluster that travels the perimeter, gold text in the middle.
     Top of the right column, under the search bar. */
  --cell: 15px;          /* bulb spacing */
  position: relative;
  flex: 0 0 auto;
  height: 64px;
  /* Hidden for now (markup + styles kept) — change to `flex` to restore it. */
  display: none;
  align-items: center;
  justify-content: center;
}
/* Bulbs evenly spaced via `space` — it pins one bulb to each corner so adjacent
   edges share the corner (no overlap/crowding) and stays even at any width. */
.nuke-container .nuke-jackpot::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  --jp-dot: radial-gradient(circle at center,
    #ffe6a8 0 30%, #dd9540 30% 44%, rgba(220, 140, 60, 0) 60%);
  background:
    var(--jp-dot) left top    / var(--cell) var(--cell) space no-repeat,
    var(--jp-dot) left bottom / var(--cell) var(--cell) space no-repeat,
    var(--jp-dot) left top    / var(--cell) var(--cell) no-repeat space,
    var(--jp-dot) right top   / var(--cell) var(--cell) no-repeat space;
}
/* The "spin": a bright cluster of ~3 bulbs that travels around the PERIMETER at
   constant speed (not an angular sweep). `.nuke-jackpot-lights` is a static layer
   masked to the bulbs + screen-blended; inside it the `.nuke-jackpot-spark` comet
   rides the rounded-rect perimeter via offset-path, lighting whichever bulbs it
   covers. Path inset by cell/2 so it runs along the bulb row. */
.nuke-container .nuke-jackpot-lights {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  mix-blend-mode: screen;
  --jp-mask: radial-gradient(circle at center, #000 0 44%, rgba(0, 0, 0, 0) 60%);
  -webkit-mask:
    var(--jp-mask) left top    / var(--cell) var(--cell) space no-repeat,
    var(--jp-mask) left bottom / var(--cell) var(--cell) space no-repeat,
    var(--jp-mask) left top    / var(--cell) var(--cell) no-repeat space,
    var(--jp-mask) right top   / var(--cell) var(--cell) no-repeat space;
  mask:
    var(--jp-mask) left top    / var(--cell) var(--cell) space no-repeat,
    var(--jp-mask) left bottom / var(--cell) var(--cell) space no-repeat,
    var(--jp-mask) left top    / var(--cell) var(--cell) no-repeat space,
    var(--jp-mask) right top   / var(--cell) var(--cell) no-repeat space;
}
.nuke-container .nuke-jackpot-spark {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--cell) * 2.8);
  height: calc(var(--cell) * 2.8);
  background: radial-gradient(circle, #fff7df 0 24%, rgba(255, 247, 223, 0) 60%);
  /* Square-cornered rectangle path inset by cell/2 so it runs along the bulb row
     and passes through the corner bulbs. */
  offset-path: inset(calc(var(--cell) / 2));
  offset-anchor: center;
  offset-distance: 0%;
  animation: nuke-jackpot-travel 9s linear infinite;
}
@keyframes nuke-jackpot-travel { to { offset-distance: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .nuke-container .nuke-jackpot-spark { animation: none; }
}
/* Gold, beveled "JACKPOT" lettering. */

/* --- Decorative radio (below the notebook; static — no playback wired up) ---- */
/* Floating radio — COLLAPSED: a red rounded-square play FAB pinned to the screen's
   bottom-right (Grok-style). Clicking play expands it (.is-playing) into the full
   radio row. Fixed + a direct child of .nuke-container so it floats at the true
   screen corner (escapes the side panel's layout containment). */
.nuke-container .nuke-radio {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 90;
  width: 56px;
  height: 56px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  background: #FFFFFF;
  border: 1px solid #EFF3F4;
  border-radius: 16px;
  /* Smaller blur + NOT animated — a big animated box-shadow is a per-frame paint hog. */
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  cursor: pointer;
  overflow: hidden;
  /* Only width transitions; `will-change` isolates the FAB on its own compositor
     layer so its per-frame repaint doesn't re-rasterise the chat underneath. */
  will-change: transform;
  transition: width 0.26s cubic-bezier(0.2, 0.8, 0.2, 1), filter 0.18s ease;
}
.nuke-container .nuke-radio:hover { box-shadow: 0 8px 26px rgba(0, 0, 0, 0.32); }
/* Darken in lockstep with the search/hovercard page-dim scrim (the FAB sits above
   the scrim, so brightness(0.4) ≈ the scrim's rgba(0,0,0,.6)). */
.nuke-container:has(.nuke-subheader-search .user-search-bar:hover) .nuke-radio,
.nuke-container:has(.nuke-subheader-search:focus-within) .nuke-radio,
.nuke-container:has(.nuke-hovercard.is-visible) .nuke-radio {
  filter: brightness(0.4);
}
/* Collapsed → hide the station info + equaliser; the FAB is one big play target. */
.nuke-container .nuke-radio .nuke-radio-station,
.nuke-container .nuke-radio .nuke-radio-eq { display: none; }
.nuke-container .nuke-radio-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #8a94a6;
}
.nuke-container .nuke-radio-btn svg { width: 18px; height: 18px; }
/* Play/pause button — a 30px red circle in BOTH states (matches the expanded
   pause button), centred in the collapsed white square. */
.nuke-container .nuke-radio-btn.play {
  flex: 0 0 auto;
  border: none;
  cursor: pointer;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #e0566c;
  color: #fff;
}
.nuke-container .nuke-radio-btn.play svg { width: 15px; height: 15px; }
/* EXPANDED (playing): the full radio row — white box, station info, equaliser.
   Width animates 56px → 230px (content reveals as it widens; height stays 56). */
.nuke-container .nuke-radio.is-playing {
  width: 230px;
  justify-content: flex-start;
  padding: 0 14px;
  gap: 12px;
  /* bg/border/shadow inherited from the base rule — kept identical across states
     so the expand only animates `width` (no per-frame shadow/paint changes). */
}
.nuke-container .nuke-radio.is-playing .nuke-radio-station { display: block; }
.nuke-container .nuke-radio.is-playing .nuke-radio-eq { display: flex; }
.nuke-container .nuke-radio.is-playing .nuke-radio-btn.play {
  position: static;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #e0566c;
}
.nuke-container .nuke-radio.is-playing .nuke-radio-btn.play svg { width: 15px; height: 15px; }
.nuke-container .nuke-radio-station { flex: 1; min-width: 0; }
.nuke-container .nuke-radio-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: #8a94a6;
}
.nuke-container .nuke-radio-freq { font-size: 17px; font-weight: 800; color: #0F1419; }
.nuke-container .nuke-radio-eq { display: flex; align-items: flex-end; gap: 3px; height: 18px; }
.nuke-container .nuke-radio-eq span {
  width: 3px;
  border-radius: 1px;
  background: #e0566c;
  transform-origin: bottom;
  transform: scaleY(0.3);   /* resting (paused) — bars only move while playing */
}
.nuke-container .nuke-radio.is-playing .nuke-radio-eq span {
  animation: nuke-radio-eq 0.9s ease-in-out infinite;
}
/* Play / pause icon swap driven by the .is-playing toggle. */
.nuke-container .nuke-radio-icon-pause { display: none; }
.nuke-container .nuke-radio.is-playing .nuke-radio-icon-play { display: none; }
.nuke-container .nuke-radio.is-playing .nuke-radio-icon-pause { display: block; }
.nuke-container .nuke-radio-eq span:nth-child(1) { height: 50%; animation-delay: -0.2s; }
.nuke-container .nuke-radio-eq span:nth-child(2) { height: 90%; animation-delay: -0.5s; }
.nuke-container .nuke-radio-eq span:nth-child(3) { height: 65%; animation-delay: 0s; }
.nuke-container .nuke-radio-eq span:nth-child(4) { height: 40%; animation-delay: -0.35s; }
@keyframes nuke-radio-eq { 0%, 100% { transform: scaleY(0.4); } 50% { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) {
  .nuke-container .nuke-radio.is-playing .nuke-radio-eq span { animation: none; }
}

/* --- x.com-style left nav rail -------------------------------------------- */
/* Replaces the old `.nuke-side-left` profile column. Fixed, full-height, and
   anchored so its RIGHT edge meets the same seam the profile column did
   (`50% - chat-w/2 - gap`), then grows leftward by `--nav-col-w`. No top chrome
   sits over the left side, so it runs from the very top. */
.nuke-container .nuke-nav {
  position: fixed;
  top: 48px; /* flush below the 48px top bar — no gap */
  bottom: 0;
  left: calc(50% - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w));
  width: var(--nav-col-w);
  /* 24px inset on each side; border-box keeps the column width. The column is
     348px (--nav-col-w) so the content area is 300px (348 − 24×2). */
  box-sizing: border-box;
  padding-left: 24px;
  padding-right: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 25;
  /* Isolate layout/style like the other fixed columns (no paint containment so
     child shadows aren't clipped). */
  contain: layout style;
}
/* Empty fixed-height box at the top of the nav (above the logo), top-aligned with
   the chat panel. Border/radius/background come from `.nuke-plain-box` (same as
   the shop container); this just fixes its height and stops it from shrinking. */
.nuke-container .nuke-nav-top-box {
  flex: 0 0 auto;
  /* Adaptive height — grows to fit the profile card (was a fixed 200px). */
  height: auto;
  position: relative;
  /* Pinned to the BOTTOM of the left nav column (x.com-style account block).
     `order: 1` drops it below the nav list (default order 0); `margin-top: auto`
     pushes it to the very bottom of the flex column. */
  order: 1;
  margin-top: auto;
  /* 16px gap below the card so it doesn't sit flush on the screen bottom. */
  margin-bottom: 16px;
}
/* Profile card filling the box — reuses the hovercard's inner styling, but
   neutralizes the popup-only chrome: always visible + static (not a fixed,
   animated, shadowed popup), and no border/shadow/bg of its own since the
   surrounding `.nuke-plain-box` already provides the white card chrome. */
.nuke-container .nuke-nav-top-box .nuke-hovercard.nuke-profile-card {
  position: static;
  width: 100%;
  opacity: 1;
  pointer-events: auto;
  transform: none;
  transition: none;
  border: none;
  box-shadow: none;
  background: none;
  cursor: default;
}
/* Banner strip at the top of the left profile card — mirrors the /~handle
   profile page. Breaks out of the card's 12px padding (negative margins) to
   span the full card width and sit flush at the top; the plain-box's rounded
   `overflow:hidden` clips its top corners. Solid black shows when no banner image. */
.nuke-container .nuke-profile-card-banner,
.nuke-container .nuke-hovercard-banner {
  margin: -12px -12px 0;
  /* Twitter/X banner aspect ratio (1500×500 = 3:1); height follows the card width. */
  aspect-ratio: 3 / 1;
  position: relative;
  flex-shrink: 0;
  background: #000000;
  overflow: hidden;
}
.nuke-container .nuke-profile-card-banner-img,
.nuke-container .nuke-hovercard-banner-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Head row: pfp on the left, username + @handle to its right. The row is pulled
   up so the pfp's top 20px overlaps the banner and 44px (of the 64px pfp) hangs
   below. The name + handle (which total exactly 40px) bottom-align to the pfp's
   bottom, so the handle bottom meets the pfp bottom and the username top lands
   4px below the banner (44 − 40).
   SHARED by the left profile card AND the message hover popup — they are the same
   banner+pfp+head design, so they read from ONE rule and can never diverge (which
   is what caused the popup's "missing ring" before). */
.nuke-container .nuke-profile-card-head,
.nuke-container .nuke-hovercard-head {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-top: -20px;
  /* Clickable → opens your own profile page (wired in initNukeProfileNav). */
  cursor: pointer;
}
/* Subtle affordance: the name underlines on hover, like a profile link. */
.nuke-container .nuke-profile-card-head:hover .nuke-hovercard-name,
.nuke-container .nuke-hovercard-head:hover .nuke-hovercard-name {
  text-decoration: underline;
}
/* Pfp size (64px) + white ring, keyed off `.nuke-hovercard` itself so it matches
   BOTH the popup (`.nuke-hovercard`) AND the left card (`.nuke-hovercard
   .nuke-profile-card`) as ONE rule, independent of the inner head markup.
   The ring lives on the WRAPPER, not the inner `.cc-pfp` image: the `<img>` has a
   browser-default `overflow: clip` that swallows a box-shadow placed on it (the
   shadow exists but never paints). The wrapper is `overflow: visible`, so the ring
   shows — and because the `.cc-pfp { box-shadow: none !important }` kill rule only
   targets the image, a ring on the wrapper needs NO `!important`. The wrapper's
   `border-radius` makes the ring follow the rounded corners. */
.nuke-container .nuke-hovercard .nuke-hovercard-pfp {
  width: 64px;
  height: 64px;
  box-shadow: 0 0 0 4px #FFFFFF;
}
/* Nudge the @handle 1px left so its italic glyphs' left edge lines up under the
   (upright) username above it. */
.nuke-container .nuke-profile-card .nuke-hovercard-handle,
.nuke-container .nuke-hovercard-head .nuke-hovercard-handle {
  margin-left: -1px;
}
/* Detailed balance breakdown under the headline balance (left card only),
   laid out horizontally like X.com's "Following / Followers" row: each stat is a
   bold value immediately followed by a muted label, with the stats spaced across a
   single row (wrapping only if the card is too narrow). Hidden entirely (inline
   style, via renderNukeProfileBox) when the user holds nothing — except in nuclear
   fallout, where it always shows. A hairline divider separates it from the
   cred/balance row. */
.nuke-container .nuke-profile-card .nuke-profile-breakdown {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #EFF3F4;
  /* 2×2 grid of "value label" stats (still X.com-style inline units) — balanced,
     rather than a ragged single-row wrap, now that there are four stats
     (wallet / pending / staked / DPR). Left column: Wallet, Staked; right: Pending,
     DPR. row-gap / column-gap below. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 12px;
  font-size: 12px;
  line-height: 1.3;
}
/* One stat: bold value + muted label sitting inline, X.com "123 Following"-style. */
.nuke-container .nuke-profile-card .nuke-profile-breakdown-stat {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  white-space: nowrap;
}
.nuke-container .nuke-profile-card .nuke-profile-breakdown-value {
  color: #0F1419;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-profile-card .nuke-profile-breakdown-label {
  color: #536471;
}
/* Stake / Unstake row at the bottom of the card. Overrides the shared
   `.nuke-staking` panel flex (column + grow) into a simple 2-up button row. */
.nuke-container .nuke-profile-card .nuke-staking.nuke-profile-actions {
  flex: 0 0 auto;
  flex-direction: row;
  gap: 8px;
  margin-top: 12px;
}
.nuke-container .nuke-profile-action-btn {
  flex: 1;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  background: #0F1419;
  color: #FFFFFF;
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-profile-unstake-btn {
  background: #F4212E;
}
.nuke-container .nuke-profile-action-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.nuke-container .nuke-nav-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 4px 0 0 0;
}
/* Each item is a pill that hugs its content (icon + label); the rounded hover
   wash wraps only the item, x.com-style — not the full column width. Labels are
   regular weight at 20px with a 26px icon and a 20px gap between them — x.com's
   actual sidebar metrics; the active route goes bold with a filled icon. */
.nuke-container .nuke-nav-item {
  display: flex;
  align-items: center;
  gap: 20px;
  width: fit-content;
  max-width: 100%;
  padding: 12px 24px 12px 12px;
  border: none;
  background: none;
  border-radius: 9999px;
  color: #0F1419;
  font-family: inherit;
  font-size: 20px;
  font-weight: 400;
  line-height: 1;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-nav-item:hover {
  /* x.com's exact nav-hover wash (10% ink on white). */
  background-color: rgba(15, 20, 25, 0.1);
}
.nuke-container .nuke-nav-icon {
  flex-shrink: 0;
  font-size: 26px;
  line-height: 1;
}
.nuke-container .nuke-nav-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Active route — bold label (the icon's filled variant is set in the markup via
   `ph-fill`). */
.nuke-container .nuke-nav-item.is-active {
  font-weight: 700;
}

/* Brand logo — a circular icon button at the top, glyph centered. The 50px box +
   4px left margin lines the glyph's center up with the nav items' icon centers
   (placeholder mark; swap for a real logo asset later). */
.nuke-container .nuke-nav-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 50px;
  height: 50px;
  margin: 8px 0 2px 0;
  border: none;
  background: none;
  border-radius: 9999px;
  color: #0F1419;
  font-size: 30px;
  line-height: 1;
  cursor: pointer;
}

/* Primary "Post" button — x.com's filled, rounded-full accent button. Stretches
   to the rail width (minus margins) since `.nuke-nav` is a stretch flex column. */

/* Account chip — pinned to the BOTTOM of the rail (margin-top:auto eats the free
   space above it). Avatar + name/@handle + a trailing ··· , in a rounded hover
   pill. Populated by renderNukeUserCard via `.nuke-profile-card-header`. */
/* Avatar uses the site's standard pfp chrome — gloss/rim + 28% rounded corners
   via `.cc-pfp-wrapper` + `.nuke-post-pfp` (40px), same as the message and chat
   header avatars — instead of a plain circle. Just guard it from flex-shrinking. */

/* Live game stats in the rail (nuke countdown + global DPR) — styled exactly like
   the nav links (icon + label, same size/weight), just informational: no hover
   wash, no pointer. Populated live by initNukeNav(). */
.nuke-container .nuke-nav-item-static {
  cursor: default;
}
.nuke-container .nuke-nav-item-static:hover {
  background: none;
}
/* Tabular figures so the ticking countdown doesn't wobble its width. */
.nuke-container .nuke-nav-timer-value {
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-side-right {
  /* RIGHT column of the trio (Shop + Season Pass + notebook): a gap to the right
     of the wider centered middle (chat) column. The pills + search headers sit
     above it; no XP bar here, so it runs to the very bottom. */
  left: calc(50% + var(--chat-w) / 2 + var(--col-gap));
  right: auto;
  width: var(--right-col-w);
  bottom: 0;
  /* Inset the shop / notebook content from the column edges (column width
     unchanged — border-box). Matches the search bar's inset. */
  padding-left: var(--right-inset);
  padding-right: var(--right-inset);
}

/* --- Nuke stage tab content panes ------------------------------ */

.nuke-container .nuke-tab-pane {
  position: absolute;
  inset: 0;
  overflow: auto;
  padding: 12px;
}
.nuke-container .nuke-tab-pane.hidden {
  display: none;
}

/* Profile pane: flex column hosting the centered full profile card. */
.nuke-container .nuke-tab-pane[data-tab-pane="1"] {
  display: flex;
  flex-direction: column;
  padding: 0;
}

/* --- Notebook paper widget (right column, below shop) ---------- */

/* Base notebook box — establishes the positioning context for the absolutely
   positioned paper/canvas/content layers, the container-query context (every
   notebook dimension below is in `cqw`), and the ruled-line pitch variables. */
.nuke-container .nuke-notebook {
  flex: 1;
  min-height: 0;
  position: relative;
  container-type: inline-size;
  /* Ruled-line pitch — single source of truth for the paper's blue lines AND
     the mission text line-height, so every line of text rests on a rule. */
  --rule: 7.0625cqw;
  /* Vertical nudge so text baselines sit just on top of the rules (tuned). */
  --rule-rest: 1.3cqw;
}
/* Notebook in the RIGHT column below the shop — fills the column width like the
   other containers there, at a fixed height. */
.nuke-container .nuke-side-right .nuke-notebook {
  width: 100%;
  flex: 0 0 400px; /* fixed height (was 40% of the column) */
  min-height: 0;
}
.nuke-container .nuke-notebook-inner {
  position: absolute;
  inset: 0;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06)) drop-shadow(0 8px 20px rgba(0, 0, 0, 0.06));
}

.nuke-container .nuke-notebook-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: url('/img/cursors/pencil.png') 5 18, crosshair !important;
  touch-action: none;
  filter: url(#fibrous-tear);
  mask-image:
    radial-gradient(circle at 7.8125cqw 20%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 50%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 80%, transparent 3.4375cqw, black 3.4375cqw);
  mask-composite: intersect;
  -webkit-mask-image:
    radial-gradient(circle at 7.8125cqw 20%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 50%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 80%, transparent 3.4375cqw, black 3.4375cqw);
  -webkit-mask-composite: source-in;
}

.nuke-container .nuke-notebook-paper {
  position: absolute;
  inset: 0;
}

/* All the paper visuals — crumple/tear filter, cream fill, noise, ruled lines,
   red margin, binder-hole mask — live on this layer BEHIND the text. Keeping the
   filter off .nuke-notebook-paper itself means it no longer cascades to the
   .nuke-notebook-content child, so the mission text stays crisp/undistorted while
   the paper keeps its crinkle. */
.nuke-container .nuke-notebook-paper::before {
  content: '';
  position: absolute;
  inset: 0;
  /* Match the avatar/reply roundedness — shared --pfp-radius. (Was 3.75cqw.) */
  border-radius: var(--pfp-radius);
  border: 1px solid #EBDFA8;
  filter: url(#fibrous-tear);
  background-color: #FFFFE1;
  background-image:
    url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"),
    linear-gradient(90deg, transparent 18.75cqw, rgba(232, 135, 135, 0.5) 18.75cqw, rgba(232, 135, 135, 0.5) 19.0625cqw, transparent 19.0625cqw),
    repeating-linear-gradient(180deg, transparent, transparent calc(var(--rule) - 1px), rgba(123, 163, 194, 0.3) calc(var(--rule) - 1px), rgba(123, 163, 194, 0.3) var(--rule));
  background-size: 100% 100%, 100% 100%, 100% 100%;
  background-position: 0 0, 0 0, 0 0;
  mask-image:
    radial-gradient(circle at 7.8125cqw 20%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 50%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 80%, transparent 3.4375cqw, black 3.4375cqw);
  mask-composite: intersect;
  -webkit-mask-image:
    radial-gradient(circle at 7.8125cqw 20%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 50%, transparent 3.4375cqw, black 3.4375cqw),
    radial-gradient(circle at 7.8125cqw 80%, transparent 3.4375cqw, black 3.4375cqw);
  -webkit-mask-composite: source-in;
}

.nuke-container .nuke-notebook-content {
  position: relative;
  width: 100%;
  height: 100%;
  /* Top padding = 2 rules so the first line starts on the grid; bottom padding
     clears the snapped footer cell (its top edge = --footer-bottom + one rule). */
  padding: calc(var(--rule) * 2) 6.25cqw calc(var(--footer-bottom, calc(var(--rule) * 1.5)) + var(--rule) + 1cqw) 20cqw;
  display: flex;
  flex-direction: column;
  /* If the list ever outgrows the paper (short viewport, many wrapped rows),
     scroll inside the paper rather than spilling rows/footer onto the window
     below. */
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  font-family: var(--font-conduit);
  color: #2d3748;
  /* 12px = the page's canonical list/UI text size (.nuke-row-name); the .nuke
     subtree runs at an 11px base under zoom, so the old 16px was an anomaly. */
  font-size: 14px;
  box-sizing: border-box;
}

.nuke-container .nuke-notebook-row {
  display: flex;
  align-items: flex-start;
  gap: 2.5cqw;
  /* No vertical padding/min-height: each row is sized purely by its text, whose
     line-height equals one rule — so single- and multi-line rows both stay on
     the ruled grid and a wrapped description just occupies the next rule. */
  position: relative;
}

.nuke-container .nuke-notebook-check {
  position: absolute;
  /* In the red-margin gutter, clear of the binder holes. Absolute + top-anchored
     to the (flex-start) row means it always sits in the task's top-left corner
     and never drifts when the description wraps to a second line. */
  left: -8cqw;
  /* Track the text's baseline nudge so the box stays centered on its line. */
  top: calc(var(--rule-rest) + 1cqw);
  width: 4.6cqw;
  height: 4.6cqw;
  border: 0.7cqw solid rgba(74, 85, 104, 0.7);
  border-radius: 1.5cqw;
  transform: rotate(-2deg);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}

.nuke-container .nuke-notebook-check--checked {
  border-color: rgba(25, 39, 66, 0.7);
  transform: rotate(1deg);
}

.nuke-container .nuke-notebook-check--checked::after {
  content: '';
  position: absolute;
  left: 30%;
  top: 5%;
  width: 35%;
  height: 75%;
  border-right: 2px solid rgba(25, 39, 66, 0.85);
  border-bottom: 2px solid rgba(25, 39, 66, 0.85);
  transform: rotate(40deg);
}

.nuke-container .nuke-notebook-text {
  flex: 1 1 auto;
  min-width: 0;
  /* One rule per line of text + a small downward nudge so the baseline rests on
     the printed rule (writing-on-lined-paper). translateY keeps every wrapped
     line shifted by the same amount, so they all stay registered. */
  line-height: var(--rule);
  transform: translateY(var(--rule-rest));
  overflow-wrap: anywhere;
}

.nuke-container .nuke-notebook-text--done {
  text-decoration: line-through;
  text-decoration-color: rgba(239, 68, 68, 0.7);
  text-decoration-thickness: 2px;
}

/* Active-mission grant progress (e.g. "1/3"). flex-shrink:0 + nowrap keep it
   fully visible; align-self:flex-start pins it beside the first text line so it
   stays top-right even when the description wraps. Spacing comes from the row's
   gap, not padding. */
.nuke-container .nuke-notebook-badge {
  margin-left: auto;
  align-self: flex-start;
  font-size: 12px;
  /* Match the text's rule height + baseline nudge so it lines up with line 1. */
  line-height: var(--rule);
  transform: translateY(var(--rule-rest));
  color: #7a8aa0;
  white-space: nowrap;
  flex-shrink: 0;
}
/* Exhausted-group + cap-locked missions read muted / italic. */
.nuke-container .nuke-notebook-item--exhausted .nuke-notebook-text,
.nuke-container .nuke-notebook-item--locked .nuke-notebook-text {
  color: #9aa5b1;
  font-style: italic;
}
/* Quest-streak + reset-countdown footer. It sits ABOVE the drawing canvas (so
   its clear button is clickable and it never scrolls away with the rows),
   pinned to the bottom of the paper. pointer-events:none lets you keep drawing
   through the streak/timer text — only the clear button re-enables clicks. */
.nuke-container .nuke-notebook-footer {
  position: absolute;
  left: 0;
  right: 0;
  /* --footer-bottom is set by snapNukeNotebookFooter() (JS) so this one-rule
     cell's bottom edge lands exactly on a blue rule — the streak then rests on
     the grid like the missions instead of floating between lines. */
  bottom: var(--footer-bottom, calc(var(--rule) * 1.5));
  height: var(--rule);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2cqw;
  /* padding-left:20cqw keeps the streak aligned under the mission-text column. */
  padding: 0 6.25cqw 0 20cqw;
  font-size: 12px;
  pointer-events: none;
}
/* Streak + timer use the mission text's rule rhythm so their baseline rests on
   the cell's bottom rule, matching the rows above. */
.nuke-container .nuke-notebook-streak,
.nuke-container .nuke-notebook-timer {
  line-height: var(--rule);
  transform: translateY(var(--rule-rest));
  white-space: nowrap;
}
.nuke-container .nuke-notebook-streak {
  font-weight: 700;
  color: #c2410c;
}
.nuke-container .nuke-notebook-timer {
  color: #7a8aa0;
  text-align: right;
}

/* Always-present eraser button in the left margin gutter, to the left of the
   streak. Eraser glyph via mask so the TS/HTML stays a plain <button> primitive;
   background-color is the icon ink. pointer-events:auto re-enables clicks that
   the footer disables. Clears the drawing canvas (handler in notebook.ts). */
.nuke-container .nuke-notebook-clear {
  position: absolute;
  left: 12cqw;
  /* Center on the footer's text line, same calc as the mission checkboxes. */
  top: calc(var(--rule-rest) + 1cqw);
  width: 4.6cqw;
  height: 4.6cqw;
  padding: 0;
  border: 0;
  background-color: #7a8aa0;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21'/%3E%3Cpath d='M22 21H7'/%3E%3Cpath d='m5 11 9 9'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21'/%3E%3Cpath d='M22 21H7'/%3E%3Cpath d='m5 11 9 9'/%3E%3C/svg%3E") center / contain no-repeat;
  cursor: pointer;
  pointer-events: auto;
  transition: background-color 0.12s ease;
}
.nuke-container .nuke-notebook-clear:hover {
  background-color: #c2410c;
}

/* --- Window chrome (MSN-style frames + title-bar + buttons) ---- */

.nuke-container .nuke-window-cluster {
  position: relative;
  isolation: isolate;
}
.nuke-container .nuke-window-cluster > .window-frame {
  position: relative;
  z-index: 1;
}
.nuke-container .window-frame {
  background-color: #E6F1FF;
  /* Matches `.chat-bubble-local`'s soft-blue border instead of the
     old bright `#0054E3` so all the chrome reads as the same color
     family. */
  border: 1px solid #C8DDF2;
  /* Matches the PFP design's effective radius (16% of 58px ≈ 9px) so
     the window's corner softness reads alongside the calling-card PFP
     as the same chrome family. `overflow: hidden` clips child
     backgrounds (gradient header, footer strip) to the rounded
     corners — without it, children paint past the rounded edge. */
  border-radius: 9px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* Empty placeholder container where the missions notebook used to be — fills
   the remaining height; keeps the default fully-rounded window-frame look. */
/* Combined left container (`.nuke-side-stack`, a flex column): the scrollable
   shop list spans the full height, with the inventory row pinned to the bottom
   — both in one white container. */
/* Plain white container + 1px gray border — replaces the old blue window-frame
   chrome for the shop & season-pass panels. No tab header, shading, or shadow. */
.nuke-container .nuke-plain-box {
  background: #FFFFFF;
  border: 1px solid #EFF3F4;
  /* Match the avatar/reply roundedness — shared --pfp-radius. (Was 18px.) */
  border-radius: var(--pfp-radius);
  overflow: hidden;
}
/* Shop box: a flex column so the scroll wrap fills it. Fixed height (the list
   scrolls inside) instead of growing to take the column's slack. */
.nuke-container .nuke-shop-box {
  display: flex;
  flex-direction: column;
  flex: 0 0 300px;
  min-height: 0;
  /* Containing block for the absolutely-positioned frosted header below. */
  position: relative;
}
/* Twitter-style widget header (the "Today's News"-tab look): a bold title row
   pinned at the top of the box. Same frosted technique as the chat nav header —
   absolutely positioned OVER the scroll area with a translucent bg + backdrop
   blur, so the shop items blur as they scroll up behind it. The grid below gets
   matching top padding so the first row clears the header. */
.nuke-container .nuke-shop-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 5;
  height: 44px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-size: 22px;
  font-weight: 800;
  color: #0F1419;
  background: rgba(255, 255, 255, 0.88);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
/* Shop wrap takes all the slack above the inventory; the grid scrolls inside it
   (absolutely positioned, native bar hidden) with a JS-driven overlay thumb. */
.nuke-container .nuke-side-stack .nuke-shop-wrap {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
}
.nuke-container .nuke-side-stack .nuke-shop-grid {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  /* Native bar hidden — the overlay thumb (`.nuke-shop-thumb`) shows instead. */
  scrollbar-width: none;
}
.nuke-container .nuke-side-stack .nuke-shop-grid::-webkit-scrollbar {
  display: none;
}
/* Overlay scrollbar thumb — same chrome + behavior as the chat's
   `.nuke-chat-scroll-thumb` (driven by attachNukeOverlayScrollbar). */
.nuke-container .nuke-shop-thumb {
  position: absolute;
  /* Clears the 44px frosted header so the thumb doesn't run behind it. */
  top: 48px;
  right: 2px;
  width: 8px;
  background: #D1D1D1;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.nuke-container .nuke-shop-wrap.is-scrolling .nuke-shop-thumb {
  opacity: 1;
}
/* Vertical item list. The 4-column track grid is KEPT (each square is still a
   `1fr` column, so it's the exact same size as in the old 4×4 layout — and the
   same square as an inventory slot), but every slot is forced into column 1
   (see `.inventory-slot` below) so the items stack straight down the left edge,
   one per row, instead of flowing across all four columns. `align-content:start`
   keeps rows at their natural square height rather than stretching. */
.nuke-container .nuke-shop-grid {
  display: grid;
  grid-template-columns: 1fr;
  align-content: start;
  gap: 8px;
  /* Top padding clears the 44px frosted header so the first row isn't hidden. */
  padding: 52px 8px 8px;
}
/* Each shop entry is a clickable ROW: the square icon box on the left, and the
   item title (top) + description (below) as plain text BESIDE it (outside the
   box), top-aligned. */
.nuke-container .nuke-shop-row {
  grid-column: 1;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}
/* The square icon box (the "item container") — same size as an inventory slot. */
.nuke-container .nuke-shop-grid .inventory-slot {
  position: relative;
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F2F2F2;
  border: 1px solid #E5E5E5;
  /* Match the avatar/reply roundedness — shared --pfp-radius. (Was 8px.) */
  border-radius: var(--pfp-radius);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.nuke-container .nuke-shop-row:hover .inventory-slot {
  background: #EAF2FB;
  border-color: #C8DDF2;
}
.nuke-container .nuke-shop-grid .inventory-slot.empty {
  cursor: default;
}
.nuke-container .nuke-shop-grid .inventory-slot.empty:hover {
  background: #F2F2F2;
  border-color: #E5E5E5;
}
/* `filter:none` cancels styles.css's `.inventory-slot.equipped .slot-icon`
   invert, which would mangle the emoji on the light background. */
.nuke-container .nuke-shop-grid .slot-icon {
  font-size: 26px;
  line-height: 1;
  filter: none;
  flex-shrink: 0;
}
/* Item text column to the right of the icon — title on top, description below. */
.nuke-container .nuke-shop-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  margin-top: 4px; /* nudge the title/description down slightly */
}
.nuke-container .nuke-shop-item-name {
  font-size: 15px;
  font-weight: 700;
  color: #222222;
  line-height: 1.25;
}
.nuke-container .nuke-shop-item-desc {
  font-size: 12px;
  color: #777777;
  line-height: 1.35;
}
.nuke-container .nuke-shop-grid .slot-charges {
  position: absolute;
  bottom: 2px;
  right: 3px;
  font-size: 11px;
  font-weight: 700;
  color: #555;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 4px;
  padding: 0 2px;
  line-height: 1.3;
}
/* Toolbar strips inside a window-frame (contact list search row, main
   stage tabs) use the window-frame's blue border color so the
   underline reads as part of the same chrome family. They also get
   the subtle inner feather (white top + sides, dark above the bottom
   border) for a slight inset depth — the standalone top page header
   stays flat. */

.nuke-container .inset-box {
  background: #FFFFFF;
  border-left: 1px solid #A0A0A0;
  border-top: 1px solid #A0A0A0;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
}
/* --- Classic XP-style buttons (Block / Video / etc.) ----------- */

.nuke-container .classic-button {
  background: linear-gradient(to bottom, #F4F8FC 0%, #D8E6F5 100%);
  border: 1px solid #83A6D4;
  border-radius: 3px;
  color: #000000;
  padding: 2px 8px;
  text-align: center;
}
.nuke-container .classic-button:hover {
  background: linear-gradient(to bottom, #FFFFFF 0%, #E6F0FA 100%);
  border-color: #316AC5;
}
.nuke-container .classic-button:active {
  background: linear-gradient(to bottom, #D8E6F5 0%, #F4F8FC 100%);
  box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.2);
}
/* Toggle/active state — used for the chat view tab buttons. */
.nuke-container .classic-button.is-active {
  background: linear-gradient(to bottom, #D8E6F5 0%, #F4F8FC 100%);
  box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.2);
  border-color: #316AC5;
}
/* Chat-toolbar buttons (e.g. "Global Chat") borrow the search-bar's
   6px corner radius so they read as part of the same chrome family.
   Zeroed vertical padding so the button's text fills the visible
   gradient more snugly — without it, the 2px top/bottom from
   `.classic-button` leaves a thicker empty band around the text
   than the search bar shows around its input. */
.nuke-container .nuke-search-row .classic-button {
  border-radius: 6px;
  padding-top: 0;
  padding-bottom: 0;
  /* Match `.nuke-row-name` (contact-list username) so the toolbar
     reads as the same typographic scale. Explicit because Tailwind
     preflight is disabled — without it, `<button>` falls back to
     the user-agent default (~13.3px) rather than inheriting from
     `.nuke-container`. */
  font-size: 14px;
  line-height: 16px;
}

/* --- Status dots + theme accents ------------------------------- */

.nuke-container .status-dot {
  /* Fixed-px circle (so it's ALWAYS a perfect circle — % sizing made it an
     ellipse), centered exactly on the pfp's bottom-right corner via
     translate(50%,50%) so it lines up with the moat cutout (also centered on
     that corner) with zero coordinate math. --dot-size is set per context.
     `.nuke-container` beats the markup's Tailwind `bottom-0 right-0`. */
  /* width == height, so it's always square → a true circle. */
  width: var(--dot-size, 8px);
  height: var(--dot-size, 8px);
  bottom: 0;
  right: 0;
  transform: translate(50%, 50%);
  /* Was `rgba(0, 0, 0, 0.3)` — softened so the border reads as a
     subtle tint of the dot's inner gradient instead of a hard
     dark outline. */
  border: none;
  border-radius: 50%;
  box-shadow: inset 1px 1px 2px rgba(255, 255, 255, 0.5);
  /* (Dot rotates via the wrapper's --cc-tilt now — no own transform needed.) */
}
/* Solid, unshaded online dot — no gradient, no inset gloss (box-shadow none
   cancels the highlight inherited from `.status-dot`). Color is the midpoint of
   the old #7DDA3F→#47A111 gradient so the tone is unchanged, just flat. */
.nuke-container .status-online  { background: #62BE28; box-shadow: none; }
.nuke-container .status-away    { background: linear-gradient(to bottom right, #FFD24D, #EAA600); }
.nuke-container .status-busy    { background: linear-gradient(to bottom right, #FF6B6B, #D31212); }
/* Solid, unshaded offline dot — flat gray, gloss cancelled. Color is the
   midpoint of the old #D0D0D0→#9A9A9A gradient. */
.nuke-container .status-offline { background: #B5B5B5; box-shadow: none; }

/* Status-dot "moat": carve a small transparent gap out of the pfp around the
   status dot so the dot reads as separate from the avatar on ANY background
   (white feed, tinted hover row, etc.) — a radial-gradient MASK on the image,
   centered on the dot and slightly larger than it (the dot itself is drawn on
   top by its own rule; this only removes pixels behind/around it). Gated by
   :has(.status-dot) so pfps with no dot keep their full image. */
.nuke-container .nuke-post-pfp:has(.status-online, .status-offline, .status-away, .status-busy) .cc-pfp,
.nuke-container .nuke-row-pfp:has(.status-online, .status-offline, .status-away, .status-busy) .cc-pfp,
.nuke-container .nuke-post-quote-pfp:has(.status-online, .status-offline, .status-away, .status-busy) .cc-pfp,
.nuke-container .nuke-hovercard-pfp:has(.status-online, .status-offline, .status-away, .status-busy) .cc-pfp {
  -webkit-mask-image: radial-gradient(circle at 100% 100%,
    transparent var(--moat), #000 var(--moat));
  mask-image: radial-gradient(circle at 100% 100%,
    transparent var(--moat), #000 var(--moat));
}
/* Per-context sizing — only two values each: --dot-size (dot diameter, ~20% of
   the pfp, kept in px so the dot is always a perfect circle) and --moat (cutout
   radius = dot radius + ~2px gap). Both the dot and the cutout are centered on
   the pfp corner, so they line up with no coordinate math. */
.nuke-container .nuke-post-pfp,
.nuke-container .nuke-row-pfp { --dot-size: 8px; --moat: 6px; }
.nuke-container .nuke-hovercard-pfp { --dot-size: 11px; --moat: 7.5px; }
.nuke-container .nuke-post-quote-pfp { --dot-size: 5px; --moat: 3.5px; }

/* --- Chat bubbles (local / remote) ----------------------------- */

.nuke-container .chat-bubble-local {
  background-color: #F0F6FF;
  border: 1px solid #C8DDF2;
  border-radius: 4px;
}
.nuke-container .chat-bubble-remote {
  background-color: #FFFFFF;
  border: 1px solid #E0E0E0;
  border-radius: 4px;
}
/* Brief blue-border flash when a reply-quote click scrolls to a
   target message. Border-color snaps instantly to blue (transition
   is suppressed inline for that paint), then the class is removed
   and the color eases back via the transition below. */
.nuke-container .chat-bubble-local,
.nuke-container .chat-bubble-remote {
  transition: border-color 0.4s ease-out;
  /* Subtle drop shadow on every bubble — single source of truth.
     Replaces the broken `shadow-sm` Tailwind utility that was being
     appended from the TS render template (preflight off → composed
     `box-shadow` variables resolve to undefined → no visible shadow). */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.nuke-container .chat-bubble-local.chat-msg-flash-border,
.nuke-container .chat-bubble-remote.chat-msg-flash-border {
  border-color: #2196F3;
}
/* Typography of message text inside the bubble. Was inline
   `style="font-family:...; color:..."` per bubble — now a class
   each. The visual contrast between local + remote stays the
   same: local reads as your own clean conduit text, remote reads
   as the playful purple Comic Sans. */
.nuke-container .nuke-chat-text-self {
  font-family: var(--font-conduit);
  color: #000;
}
.nuke-container .nuke-chat-text-remote {
  font-family: var(--font-conduit);
  color: #000;
}

/* --- Chat content extras: links, reply, day separator, image -- */

/* `@mention` + URL links — X.com link blue (#1D9BF0), no underline at rest,
   underline on hover (matches X). URLs are real `<a target="_blank"
   rel="noopener noreferrer">` anchors, so they open in a new tab like X. */
.nuke-container .nuke-chat-link {
  color: #1D9BF0;
}
/* @-mention links show the username only — hide the leading avatar (matches
   the `.dock-pill` mention pills). */
.nuke-container .nuke-chat-link .pill-pfp {
  display: none;
}
.nuke-container .nuke-chat-link:hover {
  text-decoration: underline;
}
/* @-mention: bold like the username shown in message headers (`.nuke-post-name`).
   Underline on hover is inherited from `.nuke-chat-link:hover`; clicking it opens
   the user's profile (data-tgid → handler in nuke/index.ts). */
.nuke-container .nuke-chat-mention {
  font-weight: 700;
}
/* Day separator above messages from a new day. Was inline
   `w-full text-center border-b border-[#E0E0E0] leading-[0.1em] my-2`
   wrapping a `bg-white px-2 text-[#999] text-[10px]` label. The
   `border-b` was preflight-broken (silently rendering as
   `border-width: 1px` with `border-style: none`). */
.nuke-container .nuke-chat-day-sep {
  /* Zero-height divider floating on the seam between days (adds no layout
     space). A flex row — line · label · line — where the line is broken by a
     REAL gap around the label (no opaque mask), so the post-hover darkening
     shows through uniformly instead of leaving a white square. */
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 0;
  margin: 0;
  /* Lift above the posts: they're `position: relative` (so they paint over
     in-flow siblings) and now have an opaque gradient fill, which would
     otherwise hide this seam-floating divider. */
  position: relative;
  z-index: 1;
}
/* The two line halves flank the label and flex to fill the rest of the width,
   matching the post border colour. */
.nuke-container .nuke-chat-day-sep::before,
.nuke-container .nuke-chat-day-sep::after {
  content: '';
  display: block;
  flex: 1 1 auto;
  height: 1px;
  background: #EFF3F4;
}
.nuke-container .nuke-chat-day-sep-label {
  /* In-flow between the two line halves — no background, no mask. The GAP, not
     a fill, breaks the line, so nothing here resists the hover darkening. */
  flex: 0 0 auto;
  color: #999;
  font-size: 12px;
  white-space: nowrap;
}
/* The last-of-day post's own bottom border would fill the day-sep's gap, so
   drop it — the day-sep is the divider for that seam. */
.nuke-container .nuke-post:has(+ .nuke-chat-day-sep) {
  border-bottom-color: transparent;
}

/* --- Mention pill PFP spacing ---------------------------------- */
/* When an `@mention` follows a natural word-space (mid-sentence),
   the inline pfp gets 3px left margin so it doesn't sit flush with
   the preceding word. JS toggles `.nuke-mention-pfp-spaced` on the
   `.pill-pfp` based on the character that preceded the mention. */
.nuke-container .nuke-mention-pfp-spaced {
  margin-left: 3px;
}

/* --- Reaction badge typography (count) ------------------------- */
.nuke-container .nuke-reaction-badge {
  font-size: 14px;
  line-height: 14px;
}
.nuke-container .nuke-reaction-count {
  color: #536471;
  font-family: var(--font-conduit);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- Chat image bubble wrapper (gray placeholder + radius) ----- */
.nuke-container .nuke-chat-img-wrap {
  background: #E0E0E0;
  /* Match the page's rounded-edge style — shared --pfp-radius. (Was 4px.) */
  border-radius: var(--pfp-radius);
  /* Same drop shadow as the reply containers, so images lift off the chat. */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

/* --- Contact row hover + offline name color -------------------- */

/* Row hover background highlight. */
.nuke-container .nuke-row:hover {
  background: #EAF2FB;
}
/* Hover underline on the displayed name (was Tailwind `group-hover:underline`). */
.nuke-container .nuke-row:hover .nuke-row-name {
  text-decoration: underline;
}
/* Offline rows get a muted name color via the `data-status` attribute
   (was Tailwind `text-[#666]` inlined into the class string). */
.nuke-container .nuke-row[data-status="offline"] .nuke-row-name {
  color: #666;
}

/* --- Chat image shimmer placeholder ---------------------------- */

/* Shine/shimmer sweep across the gray placeholder while a chat image
   loads. The ::before pseudo paints first in source order, so the
   actual <img> child sits on top — when the img has opacity 1 it
   fully covers the shimmer, and when at opacity 0 (still loading) the
   shimmer shows through. */
.nuke-container .nuke-chat-img-wrap::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(255, 255, 255, 0.3) 50%,
    transparent 70%
  );
  transform: translateX(-100%);
  animation: nuke-chat-img-shimmer 1.4s infinite ease-in-out;
  pointer-events: none;
}
/* Stop + hide the shimmer once the image has actually loaded — the
   `loaded` class is added by JS in the load handler. Important for
   transparent-background images where the shimmer would otherwise
   show through the loaded pixels. */
.nuke-container .nuke-chat-img-wrap.loaded::before {
  display: none;
}
@keyframes nuke-chat-img-shimmer {
  100% { transform: translateX(100%); }
}

/* ===== X.com-style chat feed ===================================== */
/* Each sender-chain renders as one post: avatar + name/@handle/time
   header, paragraph-spaced segments, quote-tweet replies, X-style
   1/2/3/4 image grids. Divider line per post; white feed. */
.nuke-container .nuke-post {
  /* Column: the avatar + content row on top, then a full-width footer bar that
     rests flush against the post's bottom edge. */
  display: flex;
  flex-direction: column;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 16px;
  border-bottom: 1px solid #EFF3F4;
  /* X.com feed type: 15px base, weight 400; the name's explicit 700 stands. */
  font-size: 15px;
  font-weight: 400;
  position: relative;
  /* Hover highlight fades in swiftly (in + out). The scroll-to highlight opts out
     of this below so it stays instant/animation-driven. */
  transition: background-color 0.12s ease;
}
/* Avatar gutter + message content, side by side (the old `.nuke-post` row). */
.nuke-container .nuke-post-row {
  display: flex;
  gap: 12px;
}
/* The very last message needs no underline — it rests on the chat footer. */
.nuke-container .nuke-post:last-child { border-bottom: none; }
.nuke-container .nuke-post-aside { flex: 0 0 auto; }

/* ── x.com-style comment-chain connector ─────────────────────────────
   A vertical thread line through the avatar gutter linking a user's event-pill
   post to their adjacent text post (set by renderPost via `sameThread`). The
   avatar is 40px at the post's 16px left padding, so its centre is x=36px; the
   footer starts at x=68px, so the line never collides with footer content.
   `-above` runs from the post's top edge to just above the avatar; `-below` from
   just below the avatar to the bottom edge — adjacent segments meet at the shared
   post border for one continuous line. */
.nuke-container .nuke-post-thread-above::before,
.nuke-container .nuke-post-thread-below::after {
  content: "";
  position: absolute;
  left: 36px;
  width: 2px;
  margin-left: -1px;
  background: #CFD9DE;
  z-index: 0;
}
.nuke-container .nuke-post-thread-above::before { top: 0; height: 8px; }
.nuke-container .nuke-post-thread-below::after { top: 56px; bottom: 0; }
/* No divider inside a thread: the post linking DOWN to the next one drops its
   bottom border so the pair reads as one continuous chain. The divider returns
   at the thread's bottom (last post has no `-below`). */
.nuke-container .nuke-post-thread-below { border-bottom: none; }
/* System / event posts (nuke, stake, season, …) render inline in the feed as
   normal message rows authored by the acting user — square pfp + full name /
   handle / time / balance / cred chrome (see renderActorSystemPost). No-user
   broadcasts use the "nuke" app identity. The `.nuke-post-system` hook class is
   kept on the article for any future system-specific styling. */
/* The top ticker pills now scroll to their inline event post on click. */
.nuke-container #nuke-pill-header .dock-pill {
  cursor: pointer;
}
/* Cred-vote posts (from nuke-profile, systemType credUp/credDown): tint the
   one-line sentence so the up/down read at a glance. The ▲/▼ glyph is baked
   into the content by the chat server. */
.nuke-container .nuke-post-cred-up { color: var(--color-nuke-success); }
.nuke-container .nuke-post-cred-down { color: var(--color-nuke-primary); }
/* ── /home pfps: flat rounded image + 1px border ─────────────────────
   Strips the shared app-icon treatment (gloss, rim, drop shadow, hover
   tilt) from every avatar pfp on /home. Scoped to .nuke-container so the
   prod design in styles.css is untouched; the 1px ring + rounded corners
   (--pfp-radius, set per variant below) are kept. */
.nuke-container .cc-pfp-wrapper::before,
.nuke-container .cc-pfp-wrapper::after {
  display: none;
}
.nuke-container .cc-pfp {
  /* Borderless — no outer ring (was a 1px box-shadow ring via
     `--pfp-outer-border-*`); just the flat rounded image. */
  box-shadow: none;
}

.nuke-container .nuke-post-pfp {
  /* Player-list pfp chrome (gloss/rim via .cc-pfp-wrapper). */
  width: 40px;
  height: 40px;
  margin: 0;
  --pfp-outer-border-color: #EFF3F4;
}
.nuke-container .nuke-post-pfp .cc-pfp { width: 100%; height: 100%; }
/* .nuke-post-pfp dot: size (--dot-size) + position come from the base .status-dot rule. */

/* Prod calling-card pfp animation, brought to ALL nuke pfps: a subtle tilt +
   shadow on hover, a snappier tilt while pressed, and a spring-back shake on
   release. Reuses the global `:root` `--pfp-*` vars + styles.css's `cc-pfp-shake`
   keyframe; `.cc-pressed` / `.cc-rebound` are toggled by the delegated press
   handler in index.ts (`initNukePfpPress`). */
@media (hover: hover) {
  /* Hover tilt → the WRAPPER (not the image), so the pfp, its moat mask, AND the
     status dot rotate together as ONE rigid unit (all are inside the wrapper) and
     can't desync mid-animation. Drives the registered @property --cc-tilt (an
     <angle>), which eases via the wrapper transition below. Fires when the pfp OR
     its containing card is hovered. */
  .nuke-container .cc-pfp-wrapper:hover,
  .nuke-container .nuke-post:hover .nuke-post-pfp,
  .nuke-container .nuke-post-quote:hover .nuke-post-quote-pfp,
  .nuke-container .nuke-chat-header-2:hover .nuke-post-pfp,
  .nuke-container .nuke-row:hover .nuke-row-pfp,
  .nuke-container .nuke-hovercard:hover .nuke-hovercard-pfp {
    --cc-tilt: var(--pfp-hover-tilt);
  }
  /* Hover shadow stays on the image. */
  .nuke-container .cc-pfp-wrapper:hover .cc-pfp,
  .nuke-container .nuke-post:hover .nuke-post-pfp .cc-pfp,
  .nuke-container .nuke-post-quote:hover .nuke-post-quote-pfp .cc-pfp,
  .nuke-container .nuke-chat-header-2:hover .nuke-post-pfp .cc-pfp,
  .nuke-container .nuke-row:hover .nuke-row-pfp .cc-pfp,
  .nuke-container .nuke-hovercard:hover .nuke-hovercard-pfp .cc-pfp {
    box-shadow:
      0 0 0 var(--pfp-outer-border-width) var(--pfp-outer-border-color),
      var(--pfp-hover-shadow);
  }
  /* Message pfps DON'T tilt on hover (the press tilt + click animations stay). */
  .nuke-container .nuke-post:hover .nuke-post-pfp:not(.cc-pressed) {
    --cc-tilt: 0deg;
  }
  .nuke-container .nuke-post:hover .nuke-post-pfp:not(.cc-pressed) .cc-pfp {
    box-shadow: 0 0 0 var(--pfp-outer-border-width) var(--pfp-outer-border-color);
  }
}
/* The wrapper carries ALL pfp motion — rotate (--cc-tilt) + scale (--cc-press) —
   so the image, its moat, and the status dot move together as one rigid unit and
   can never desync. Both are registered @property vars, so they ease via this
   transition (and the shake keyframes below animate --cc-tilt the same way). */
.nuke-container .cc-pfp-wrapper {
  /* ONLY a rotate (tilt) — no scale. The whole wrapper (image + moat + dot)
     rotates as one rigid unit, so they can never desync; and with no scale the
     pfp never shrinks on press. --cc-tilt is a registered @property <angle>, so
     it eases via this transition. */
  transform: rotate(var(--cc-tilt, 0deg));
  transition: --cc-tilt 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.nuke-container .cc-pfp-wrapper.cc-pressed {
  --cc-press: 0.95;
  --cc-tilt: var(--pfp-pressed-tilt);
}
.nuke-container .cc-pfp-wrapper.cc-rebound {
  animation: cc-rebound 0.42s cubic-bezier(0.45, 0.05, 0.55, 0.95);
}
/* EXCEPTIONS = the WIGGLE: NPC chat pfps and the header pfp on ANOTHER user's
   profile page only ("other"). Shake the WRAPPER via --cc-tilt so the dot + moat
   wiggle with the image; overrides the scale-pop rebound above. */
.nuke-container .nuke-post[data-has-account="false"] .cc-pfp-wrapper.cc-rebound,
.nuke-container[data-profile-view="other"] .nuke-chat-header-2 .cc-pfp-wrapper.cc-rebound {
  animation: nuke-cc-pfp-shake 0.36s ease-in-out;
}
@keyframes nuke-cc-pfp-shake {
  0%   { --cc-tilt: 0deg; }
  18%  { --cc-tilt: -5deg; }
  36%  { --cc-tilt: 4deg; }
  54%  { --cc-tilt: -3deg; }
  72%  { --cc-tilt: 2deg; }
  88%  { --cc-tilt: -1deg; }
  100% { --cc-tilt: 0deg; }
}
/* (The status dot no longer needs its own rotation rules — it rides the wrapper's
   --cc-tilt rotation along with the image + moat, so they can't desync.) */
.nuke-container .nuke-post-main {
  flex: 1 1 auto;
  min-width: 0;
  /* Center the username + first text line against the avatar for short messages:
     a min-height equal to the pfp (40px) + vertical centering. Taller (multi-line)
     messages exceed 40px, so there's no free space to distribute and the content
     naturally top-aligns — the pfp lines up with the first lines, not the middle. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 40px;
}
.nuke-container .nuke-post-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px;
  line-height: 1.25;
  /* No extra gap — the body sits directly under the name/handle row, reading as
     the next line of text rather than a separate block. */
  margin-bottom: 0;
}
.nuke-container .nuke-post-name-cluster {
  display: inline-flex;
  align-items: center;
  /* Tight gap so the level badge / @handle sit RIGHT after the name like a
     verified checkmark — 4px read as a full word-space after every username
     (the "odd space" after the name). */
  gap: 3px;
  min-width: 0;
}
.nuke-container .nuke-post-name { font-weight: 700; color: #0F1419; }
/* Level icon as a verified-checkmark-style badge right after a username. A
   transparent 1em square — the size of one username character — that the icon
   fills, sitting right after the name like the next character. One rule for
   BOTH badge classes so the size is identical in every context (message head,
   reply quote, profile header, hover card, contact row, left profile panel) and
   the two classes can't fight over size by source order. `1em` is relative, so
   each row self-sizes from its own font-size; `align-self: center` keeps every
   badge vertically centered on its row. */
.nuke-container .nuke-post-name-badge,
.nuke-container .nuke-post-level-icon {
  width: 1em;
  height: 1em;
  object-fit: contain;
  flex-shrink: 0;
  align-self: center;
  display: block;
}
/* Sender's balance, top-right of the name row — same weight/size/color as the
   username (`.nuke-post-name`), pushed right and baseline-aligned with it. */
.nuke-container .nuke-post-balance {
  margin-left: auto;
  flex-shrink: 0;
  padding-left: 8px;
  /* Black, at the same weight as the username (700). */
  font-weight: 700;
  color: #000;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.nuke-container .nuke-post-name-cluster[data-has-account="true"] { cursor: pointer; }
/* The header username underlines only when its OWN name cluster is hovered — NOT
   on a hover anywhere in the post. (Previously any in-post hover, e.g. over an
   @mention, underlined the header name; that's been dropped so a mention hover
   only underlines the mention itself.) */
.nuke-container .nuke-post-name-cluster[data-has-account="true"]:hover .nuke-post-name { text-decoration: underline; }
/* Whole-post hover darkening, X-style — on every post (NPCs included). The
   pointer cursor + name underline stay account-only; this is just the bg. */
.nuke-container .nuke-post[data-has-account="true"]:hover,
/* Keep the source message darkened while its hover card is open (the cursor has
   left the message for the card, so `:hover` no longer applies). Hover tint is a
   soft version of the post author's profile colour (`--nuke-post-hl`, set inline
   by renderPost), falling back to the default grey when they have no colour. */
.nuke-container .nuke-post.is-hovercard-active { background-color: var(--nuke-post-hl, rgba(0, 0, 0, 0.018)); }
.nuke-container .nuke-post-handle,
.nuke-container .nuke-post-time,
.nuke-container .nuke-post-dot { color: #536471; font-weight: 400; }
/* All @handles render italic, site-wide: messages, reply quotes, the profile
   header, the hover card, profile/nav calling cards, and contact-row subtitles. */
.nuke-container .nuke-post-handle,
.nuke-container .nuke-post-quote-handle,
.nuke-container .nuke-chat-header-handle,
.nuke-container .nuke-hovercard-handle,
.nuke-container .nuke-profile-status,
.nuke-container .nuke-row-subtitle {
  font-style: italic;
  /* Italic glyphs lean past their advance width, so the last letter's slant gets
     shaved in any clipping container (the profile header handle and contact-row
     subtitle use overflow:hidden + ellipsis). Pad the right edge to give the lean
     room (overflow clips at the padding box, so it stays visible), and cancel the
     padding with an equal negative margin so nothing after the handle shifts. */
  padding-right: 0.2em;
  margin-right: -0.2em;
}
/* Sender's level icon + DPR%, pushed to the opposite (right) end of the header
   same level data prod shows on its cards. Pinned to the post's bottom-right
   corner (inside the 12px/16px padding). */
/* Full-width footer bar resting flush against the post's bottom edge: the
   negative side margins cancel the post's 16px h-padding (edge to edge) and the
   negative bottom cancels the 12px b-padding (no gap below); the inner padding
   re-insets the content. Holds the timestamp + reactions. */
.nuke-container .nuke-post-footer {
  display: flex;
  /* Reactions vertically centered in the footer band. */
  align-items: center;
  gap: 8px;
  /* Sides (-16) + bottom (-12) cancel the post's 16/12 padding so the band is
     edge-to-edge and flush with the post bottom. Symmetric 9px top/bottom
     padding centers the reaction pill in the band. Left padding is 16 + 52
     (the 40px pfp + 12px row gap) so footer content starts at the MESSAGE
     FIELD (text column) and never bleeds left into the avatar gutter. */
  margin: 0 -16px -12px;
  padding: 9px 16px 9px 68px;
}
/* Reactions live inside the footer now — the footer owns the left pull; the
   symmetric footer padding centers the pill, so no own top margin. The pill is
   ordered LAST so it sits on the RIGHT of the footer, after the actions. */
.nuke-container .nuke-post-footer .nuke-post-reactions {
  margin-left: 0;
  margin-top: 0;
  order: 2;
}
/* Account posts only (NPC posts have no footer). Ordered FIRST (left side); takes
   the footer width left of the reaction pill (flex: 1). The cred vote pill and the
   "Use Item" pill sit together at the LEFT, the Use Item pill directly to the right
   of the cred pill (flex-start + gap), with the empty space trailing to the right. */
.nuke-container .nuke-post-actions {
  order: 1;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
}
/* The share / copy-link button sits directly to the RIGHT of the reaction pill —
   ordered AFTER it (pill is order 2) at the footer's far right. */
.nuke-container .nuke-post-footer > .nuke-post-action-link {
  order: 3;
  flex-shrink: 0;
}
.nuke-container .nuke-post-action-btn {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  background: transparent;
  color: #536471;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-post-action-btn:hover {
  background: rgba(0, 0, 0, 0.06);
  color: #0F1419;
}
/* Per-action hover tint — like green, dislike red, item blue. Both the circle
   (background) and the icon (color) take the color on hover; unhovered they
   stay the default gray (#536471, transparent) from the base rule. The double
   class keeps these more specific than the generic :hover above. */
.nuke-container .nuke-post-action-btn.nuke-post-action-like:hover {
  background: rgba(0, 186, 124, 0.12);
  color: #00BA7C;
}
.nuke-container .nuke-post-action-btn.nuke-post-action-dislike:hover {
  background: rgba(244, 33, 46, 0.12);
  color: #F4212E;
}
.nuke-container .nuke-post-action-btn.nuke-post-action-item:hover {
  background: rgba(29, 155, 240, 0.12);
  color: #1D9BF0;
}
/* Share button — a circle wearing the pill chrome (same #F7F9FA fill + 1px
   #CFD9DE border as the reaction / vote / use-item pills). Sized 24px (border-box)
   so its height matches those pills (vote/use-item are 24px) rather than the base
   28px action circle. */
.nuke-container .nuke-post-action-btn.nuke-post-action-link {
  box-sizing: border-box;
  width: 24px;
  height: 24px;
  font-size: 15px;
  background: #F7F9FA;
  border: 1px solid #CFD9DE;
}
.nuke-container .nuke-post-action-btn.nuke-post-action-link:hover {
  background: rgba(29, 155, 240, 0.12);
  color: #1D9BF0;
}
/* "Use Item" pill — black capsule with white label + box icon (icon to the RIGHT
   of the text). White capsule sharing the cred/reaction pill's chrome — #CFD9DE
   border — and the cred text style (Conduit, #536471). Border-box so the border
   doesn't add to the 24px height (matches the vote/cred pill). */
.nuke-container .nuke-post-use-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  box-sizing: border-box;
  height: 24px;
  padding: 0 12px;
  border: 1px solid #CFD9DE;
  border-radius: 9999px;
  background: #F7F9FA;
  color: #536471;
  font-family: var(--font-conduit);
  font-weight: 600;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-post-use-item i { font-size: 15px; }
/* Just-copied feedback: the glyph swaps to a check and turns green for ~1.2s. */
.nuke-container .nuke-post-action-btn.nuke-post-action-link.is-copied,
.nuke-container .nuke-post-action-btn.nuke-post-action-link.is-copied:hover {
  background: rgba(0, 186, 124, 0.12);
  color: #00BA7C;
}
/* Reddit-style vote pill in the footer action row: upvote arrow, the cred value
   in the centre, downvote arrow — one rounded capsule. Chrome deliberately matches
   the reaction pill (`.nuke-reaction-pill`): #F7F9FA fill, 1px #CFD9DE border, fully
   rounded; count is the same #666 / 12px as `.nuke-reaction-count`. */
.nuke-container .nuke-post-vote {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  padding: 0 2px;
  background: #F7F9FA;
  border: 1px solid #CFD9DE;
  border-radius: 999px;
}
.nuke-container .nuke-post-vote-btn {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: #536471;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
/* Upvote → reddit orange, downvote → periwinkle, on hover (circle + glyph). */
.nuke-container .nuke-post-vote-up:hover { background: rgba(255, 69, 0, 0.14); color: #FF4500; }
.nuke-container .nuke-post-vote-down:hover { background: rgba(113, 147, 255, 0.18); color: #7193FF; }
.nuke-container .nuke-post-vote-count {
  min-width: 1.4em;
  text-align: center;
  color: #536471;
  font-family: var(--font-conduit);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* `.nuke-post-level-icon` is sized together with `.nuke-post-name-badge` in the
   unified badge rule above (hug-the-glyph). No size rule here — a standalone
   block would override that one by source order and reintroduce the square box. */
.nuke-container .nuke-post-dpr {
  /* Conduit font, 15px (inherited), weight 400 — gray like the @handle. */
  font-family: var(--font-conduit);
  color: #536471;
  font-weight: 400;
}
/* Everything in the body flows at natural next-line spacing — no paragraph gaps
   between segments, images, or the DPR row. Sequential messages, captions, and
   images all read like consecutive lines of one message (line-height does the
   spacing). */
.nuke-container .nuke-post-body { display: flex; flex-direction: column; gap: 0; }
/* Reactions SLOT — reserves a fixed 3-reaction width regardless of how many
   reactions a message has, so the footer action icons never shift with the count.
   Widened to 170px (was 126px) to fit three 2-digit badges at the larger Conduit
   count font on ONE line. The pill is right-aligned inside and hugs its badges;
   0–2 reactions leave empty space on the slot's LEFT, never moving the actions. */
.nuke-container .nuke-post-reactions {
  flex: 0 0 170px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
/* The visible pill — chrome + badges, hugging its content, right-aligned within
   the slot. Rendered only when the message has reactions (no empty chip). A rare
   3-digit-count pill wraps inside the slot rather than overflowing the actions. */
.nuke-container .nuke-reaction-pill {
  gap: 8px;
  padding: 3px 10px;
  background: #F7F9FA;
  border: 1px solid #CFD9DE;
  border-radius: 999px;
}
.nuke-container .nuke-post-text {
  overflow-wrap: anywhere;
  line-height: 1.35;
}
/* Level icon + DPR% floated to the top-right of the first text block, right-aligned
   so its right edge lines up under the balance's right edge. The first line of text
   wraps before it; it's a single row tall, so line 2 onward clears it and runs
   full width. Posts with no DPR don't get this span, so their text is full width. */
.nuke-container .nuke-post-rate {
  float: right;
  display: flex;
  align-items: center;
  gap: 3px;
  /* Wider gap so the first line of text cuts to the next line a bit sooner,
     well clear of the rate rather than crowding right up against it. */
  margin-left: 20px;
}
/* Image-only posts: the level icon + DPR ride their own right-aligned row at the
   top of the body (under the balance), since there's no text line to float into. */
.nuke-container .nuke-post-rate-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
}
/* DPR + level icon match the @handle / header text size — no longer shrunk: the
   DPR inherits the base 15px `.nuke-post-dpr` and the level icon is a 1em square
   via the unified badge rule. */
/* Images get X.com-style breathing room above them — a roomier, CONSISTENT gap
   (not next-line) whatever precedes the image: a caption, the DPR row (image-only
   post), or a prior message. Body gap is 0, so this margin is the whole gap. This
   also covers a reply's own image (the gap ABOVE it). */
.nuke-container .nuke-post-media { margin-top: 12px; }
/* A reply's image renders above the quoted-message container; give the gap BELOW
   it (image → quote box) the same 12px as every other image, instead of the
   tighter 8px the quote box's own default margin would give. */
.nuke-container .nuke-post-media + .nuke-post-reply { margin-top: 12px; }
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap {
  /* Fill the full post width; height comes from the inline aspect-ratio. */
  width: 100%;
  max-width: 100%;
  /* Match the page's rounded-edge style — shared --pfp-radius. (Was 16px.) */
  border-radius: var(--pfp-radius);
  border: 1px solid #CFD9DE;
  /* Hover lift transition (snappy). The press → rebound is driven in JS
     (`playCardPress`) so a fast click still plays the full animation and reads the
     same across sizes — see chat-render.ts. */
  transition: transform 0.09s ease, box-shadow 0.18s ease;
}
/* Hover lift — both framed images and stickers scale; only framed ones grow a
   shadow (stickers stay bare). */
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap:hover {
  transform: scale(1.006);
}
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap:hover:not(.is-sticker) {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);
}
/* Pressed — flatter shadow (the scale press itself is the JS animation). */
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap:active:not(.is-sticker) {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
}
/* Sticker (transparent-bg image, tagged by markStickerIfTransparent): no framed
   container — drop the border, radius, gray placeholder fill + shimmer so the
   image sits bare in the post and its transparency shows the post behind it.
   Sizing (width + aspect-ratio) is unchanged. */
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap.is-sticker {
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.nuke-container .nuke-post-img-wrap.is-sticker::before {
  display: none;
}
/* Multi-image grid (2/3/4, +N on overflow). */
.nuke-container .nuke-post-media-grid {
  display: grid;
  gap: 2px;
  width: 100%;
  max-width: 380px;
  aspect-ratio: 16 / 9;
  /* Match the page's rounded-edge style — shared --pfp-radius. (Was 16px.) */
  border-radius: var(--pfp-radius);
  overflow: hidden;
  border: 1px solid #CFD9DE;
}
.nuke-container .nuke-post-media-2 { grid-template-columns: 1fr 1fr; }
.nuke-container .nuke-post-media-3 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.nuke-container .nuke-post-media-3 .nuke-post-grid-cell:first-child { grid-row: 1 / span 2; }
.nuke-container .nuke-post-media-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.nuke-container .nuke-post-grid-cell { background: #E0E0E0; }
.nuke-container .nuke-post-media-grid .nuke-chat-img-wrap { border-radius: 0; }
.nuke-container .nuke-post-grid-more {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.42);
  color: #FFFFFF;
  font-size: 22px;
  font-weight: 700;
  z-index: 3;
}
/* Reply wrapper — holds the arrow OUTSIDE the scaling quote box so the arrow
   doesn't move/scale with the hover/press animation. Carries the flow spacing. */
.nuke-container .nuke-post-reply {
  position: relative;
  margin-top: 8px;
}
/* Quote-tweet box (replies). */
.nuke-container .nuke-post-quote {
  position: relative;
  /* Pure-white fill matching the chat background. */
  background: #FFFFFF;
  border: 1px solid #CFD9DE;
  /* Subtle drop shadow so the reply box lifts slightly off the post (weaker than
     the chat images'). Grows + scales on hover for a lift effect. */
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
  /* Reply hover tint fades in slightly longer than the post's swift 0.12s. */
  transition: transform 0.09s ease, box-shadow 0.18s ease, background 0.22s ease;
  /* Scale from the left edge (the arrow side) so that edge stays anchored to the
     arrow while the box grows/shrinks toward the right. */
  transform-origin: left center;
  /* Match the avatar roundedness — reuse the shared --pfp-radius so the reply
     box's corners track the pfp corner (was a much rounder 14px). The clamp's
     max keeps this box's corner small even though the box is large. */
  border-radius: var(--pfp-radius);
  padding: 8px 10px;
  cursor: pointer;
}
/* Old reply arrow, restored — flipped upside down, in the quote-border color,
   resting on the right edge of the reply avatar and pointing down at the quote. */
.nuke-container .nuke-post-reply-arrow {
  position: absolute;
  top: 10px;
  left: -24px;
  color: #CFD9DE;
  font-size: 18px;
  /* Thicken the glyph strokes a touch (beyond ph-bold). */
  -webkit-text-stroke: 0.5px currentColor;
  transform: scaleY(-1);
  pointer-events: none;
}
/* Rests pure white with a constant shadow; hovering darkens the reply with a
   faint fill (matches the post's own hover). */
.nuke-container .nuke-post:hover .nuke-post-quote,
.nuke-container .nuke-post.is-hovercard-active .nuke-post-quote { background: #FBFBFB; }
/* Hovering the reply itself tints it with a soft version of the QUOTED user's
   profile colour (`--nuke-quote-hl`, set inline by renderQuoteBox), falling back to
   the default grey when they have no colour. More specific than the rule above
   (which it always co-occurs with), so it wins. */
.nuke-container .nuke-post:hover .nuke-post-quote:hover,
.nuke-container .nuke-post-quote:hover { background: var(--nuke-quote-hl, #F8F8F8); }
/* Hovering the reply lifts it off the page — a subtle scale-up + a stronger,
   wider shadow (transitioned via `.nuke-post-quote` above). */
.nuke-container .nuke-post-quote:hover {
  transform: scale(1.006);
  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.10);
}
/* NPC reply (quoted user has no account) — no hover lift; keep its resting look.
   The click press + rebound (JS) still applies. */
.nuke-container .nuke-post-quote[data-has-account="false"]:hover {
  transform: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
/* Pressed — flatter shadow (the scale press itself is the JS animation,
   `playCardPress`, which pivots at the arrow via this box's transform-origin). */
.nuke-container .nuke-post-quote:active {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.nuke-container .nuke-post-quote-head {
  display: flex;
  align-items: center;
  /* 3px — match the feed/header/hovercard name rows so the reply's badge/handle
     hug the name too. */
  gap: 3px;
  margin-bottom: 3px;
  /* Tight line box so the name/@handle/time/balance text hugs its glyphs and
     centers cleanly against the 20px pfp (the inherited 15px text otherwise
     carries a tall line box that pushes the glyphs optically high). */
  line-height: 1;
}
.nuke-container .nuke-post-quote-pfp {
  width: 20px;
  height: 20px;
  margin: 0;
  flex: 0 0 auto;
  --pfp-outer-border-color: #EFF3F4;
}
.nuke-container .nuke-post-quote-pfp .cc-pfp { width: 100%; height: 100%; }
/* Tiny status dot scaled for the 20px quote pfp (vs 10px on the 40px post pfp). */
/* .nuke-post-quote-pfp dot: size + offset are proportional now (base .status-dot). */
.nuke-container .nuke-post-quote-name { font-weight: 700; color: #0F1419; }
/* Quoted sender's balance — pushed to the top-right of the quote header, bold
   like the per-message balance (inherits the quote header's font size). */
.nuke-container .nuke-post-quote-balance {
  margin-left: auto;
  flex-shrink: 0;
  padding-left: 8px;
  font-weight: 700;
  color: #0F1419;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.nuke-container .nuke-post-quote-handle { color: #536471; }
.nuke-container .nuke-post-quote-dot,
.nuke-container .nuke-post-quote-time { color: #536471; font-weight: 400; }
.nuke-container .nuke-post-quote-text { color: #0F1419; line-height: 1.3; overflow-wrap: anywhere; font-family: var(--font-conduit); }
/* Reply-quote body: the image (when the quoted message is an image) fills the
   full allocated width at its natural aspect ratio, with an optional caption
   stacked below it. */
.nuke-container .nuke-post-quote-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.nuke-container .nuke-post-quote-img {
  line-height: 0;
  /* Lift the thumbnail off the quoted username: the quote-head only gives 3px,
     which reads as cramped for an image. ~8px total (3px head + 5px) matches
     the regular post's name→image gap. Text-only quotes are unaffected. */
  margin-top: 5px;
}
.nuke-container .nuke-post-quote-img img {
  display: block;
  /* Fill the allocated width, but scale the WHOLE image down (never crop) when
     it would otherwise be taller than the cap — so a very tall image shrinks to
     `max-height` instead of taking over. Real images (wider than the quote)
     fill the width; a capped tall one sits left at its scaled-down size. */
  width: auto;
  height: auto;
  max-width: 100%;
  /* Matches TEST_IMG_MAX_H in chat-render.ts. High enough that square +
     landscape images fill the quote width (max-width:100% binds); only very
     tall portraits get scaled down to this height (never cropped). */
  max-height: 510px;
  /* Match the page's rounded-edge style — shared --pfp-radius. (Was 8px.) */
  border-radius: var(--pfp-radius);
  border: 1px solid #CFD9DE;
}
/* Navigate-to-message flash (new structure) — a one-shot blue highlight that
   snaps on, holds, then fades out. Implemented as a keyframe ANIMATION (not an
   always-on background-color transition) so it doesn't delay the hover
   darkening — the post bg and reply quote now darken at the same instant. */
@keyframes nuke-msg-flash {
  0%   { background-color: rgba(29, 155, 240, 0.14); }
  60%  { background-color: rgba(29, 155, 240, 0.14); }
  100% { background-color: transparent; }
}
.nuke-container [data-msg-id].chat-msg-flash-border {
  animation: nuke-msg-flash 1s ease-out;
}
/* Scroll-to highlight (reply-quote / ticker-pill click). JS holds `nuke-post-hl`
   for the whole scroll, then swaps to `nuke-post-hl-out` to fade once the scroll
   settles — see flashScrollToNukePost. Colour is `--nuke-hl-color`, a soft tint of
   the scrolled-to user's profile colour set inline by JS; with no profile colour
   (NPCs / no-colour users) it falls back to a neutral grey strong enough to read as
   a deliberate highlight. The fade's first keyframe matches the held colour so the
   swap is seamless. */
.nuke-container [data-msg-id].nuke-post-hl,
.nuke-container .nuke-post.nuke-post-hl {
  background-color: var(--nuke-hl-color, rgba(0, 0, 0, 0.06));
  transition: none; /* scroll-to highlight is instant (not the hover fade) */
}
.nuke-container [data-msg-id].nuke-post-hl-out,
.nuke-container .nuke-post.nuke-post-hl-out {
  animation: nuke-msg-fadeout 0.6s ease-out;
}
@keyframes nuke-msg-fadeout {
  0%   { background-color: var(--nuke-hl-color, rgba(0, 0, 0, 0.06)); }
  100% { background-color: transparent; }
}

/* ─── Reaction pop-in + emoji burst ────────────────────────────────
   `.nuke-reaction-badge` is the inline-flex container holding the
   emoji glyph + count. Positioned (relative) so the particle copies
   below can absolute-position from the badge's center.

   • `.nuke-reaction-pop` spring-scales a fresh badge from nothing
     to slightly oversized → settle (`nuke-reaction-pop` keyframe).
   • `.nuke-reaction-particle` is a JS-injected copy of the same
     emoji glyph that flies outward from the badge's center, using
     the inline `--dx` / `--dy` set by the spawner. Removed on
     `animationend` by the existing scroll-level listener.
   • `.nuke-reaction-bump` is a subtler count-only bounce for the
     "+1 same emoji" case — the count span scales 1 → 1.4 → 1
     without re-popping the whole badge. */
.nuke-container .nuke-reaction-badge {
  position: relative;
}
/* Glyph box — fixed 1em square that holds two layers:
   1. `.nuke-reaction-glyph-fallback` (unicode emoji, base layer,
      visible immediately so the pop is always visible);
   2. the animated-WebP emoji `<img>` (in the markup, autoplays on load,
      sits on top of the fallback and covers it visually).
   Both are absolutely positioned to fill the same 1em box so the
   transition is in-place — no swap, no layout shift, no flicker. */
.nuke-container .nuke-reaction-glyph {
  position: relative;
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.2em;
  font-size: 14px;
  line-height: 1;
}
.nuke-container .nuke-reaction-glyph-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
  font-variant-emoji: emoji;
  /* Slight scale-down so unicode visually matches the WebP emoji's
     drawn glyph (which has internal padding inside its 1em box). */
  font-size: 0.92em;
  line-height: 1;
  /* Longer (240ms) ease-out crossfade so any small color/size
     delta between the unicode fallback and the WebP emoji's first
     painted frame is hidden inside the fade rather than a hard cut. */
  transition: opacity 0.24s ease-out;
}
/* JS sets `data-glyph-ready="1"` on the glyph once the WebP emoji `<img>` has
   painted. Hide the unicode fallback so we don't see both layers stacked. */
.nuke-container .nuke-reaction-glyph[data-glyph-ready="1"] .nuke-reaction-glyph-fallback {
  opacity: 0;
}
.nuke-container .nuke-reaction-glyph img[data-noto-emoji] {
  position: absolute !important;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
}
/* Lightbox-open: hide the chat's animated emoji so an animating WebP under the
   ~95%-white blurred overlay doesn't force the backdrop-filter to re-blur every
   frame. visibility:hidden = no paint = no backdrop cost (toggled by
   pause/resumeNukeChatLotties). */
.nuke-container #nuke-chat-scroll.nuke-chat-emoji-paused [data-noto-emoji] {
  visibility: hidden;
}
/* Particles use the same color-emoji font stack as the fallback. */
.nuke-container .nuke-reaction-particle {
  font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
  font-variant-emoji: emoji;
}
.nuke-container .nuke-reaction-pop {
  animation: nuke-reaction-pop 0.36s cubic-bezier(0.34, 1.56, 0.64, 1);
  transform-origin: center;
}
.nuke-container .nuke-reaction-bump {
  display: inline-block;
  animation: nuke-reaction-count-bump 0.22s ease-out;
  transform-origin: center;
}
.nuke-container .nuke-reaction-particle {
  position: absolute;
  left: 50%;
  top: 50%;
  pointer-events: none;
  font-size: 10px;
  line-height: 1;
  animation: nuke-reaction-particle-burst 0.5s ease-out forwards;
}
@keyframes nuke-reaction-pop {
  0%   { transform: scale(0.2); opacity: 0; }
  55%  { transform: scale(1.35); opacity: 1; }
  78%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
@keyframes nuke-reaction-count-bump {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.4); }
  100% { transform: scale(1); }
}
@keyframes nuke-reaction-particle-burst {
  0%   { transform: translate(-50%, -50%) scale(0.4); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx, 0px)), calc(-50% + var(--dy, 0px))) scale(1); opacity: 0; }
}

/* --- Disclaimer toast + lightbox blur -------------------------- */

/* Disclaimer toast dismiss animation — quick scale-down + fade out.
   `display: none` is applied via JS after the transition completes.
   Box-shadow set explicitly here (not via Tailwind `shadow-*`)
   because Tailwind's `ring-1` already drives the `box-shadow`
   property via its --tw-ring-shadow CSS variable — composing a
   second arbitrary shadow utility on top via --tw-shadow can fail
   silently when preflight isn't initializing those variables. A
   direct rule wins by being late + more specific. */
.nuke-container #nuke-chat-disclaimer {
  /* Temporarily hidden for now — remove this line to bring the Stay Safe
     toast back. */
  display: none;
  transition: opacity 0.1s ease-out, transform 0.1s ease-out;
  transform-origin: center;
  /* Matches the chat-bubble shadow exactly so the toast reads as
     the same chrome family. */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* Image lightbox blur — written directly here (not via Tailwind's
   `backdrop-blur-*` utility) because Tailwind composes
   `backdrop-filter` from a chain of CSS variables that need
   preflight to initialize the unused ones to identity values.
   Preflight is disabled in this build, so a direct rule wins. */
.nuke-container #nuke-chat-lightbox {
  /* No backdrop blur — just a plain dark scrim (see .nuke-chat-lightbox bg). */
}
.nuke-container #nuke-chat-disclaimer.dismissing {
  opacity: 0;
  transform: scale(0.9);
  pointer-events: none;
}

/* --- Search row (header + input + icon + add-contact) ---------- */

/* Nuke page reuses the main app's search bar chrome but adopts the
   thinner height, italic gray placeholder font, and inline magnifying-
   glass icon from the original nuke design. The right-side glyph
   button holds the add-contact icon instead of the magnifying glass. */
.nuke-container .nuke-search-row {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Fixed 38px height = the profile-tab ticker height, so the search bar
     matches it and centers in the 54px subheader (8px each side). */
  height: 38px;
}
.nuke-container .nuke-search-row .user-search-wrapper {
  flex: 1;
}
/* Add-contact button: perfect square 26×26 (matching the bar's full
   visual height — 24px box + 1px ring outside). Rounded 6px corners
   match the bar. The 16px icon centers via flex layout; with the 1px
   border, that leaves ~4px breathing room on each side. */
.nuke-container .nuke-search-row .nuke-search-add-btn {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 6px;
  flex-shrink: 0;
}
/* Add-contact button's icon — was `text-[#2B784A] text-base` inline. */
.nuke-container .nuke-search-add-icon {
  font-size: 18px; /* matches Tailwind text-base */
  color: #2B784A;
}

/* Adjacent contact-groups: zero the top padding on any group after
   the first so the gap between groups equals the offline group's
   bottom-edge padding (instead of double-stacking online's `pb-2`
   + offline's `pt-2`). */
.nuke-container .contact-group + .contact-group {
  padding-top: 0;
}

/* --- Contact group header (Online/Offline) --------------------- */

.nuke-container .contact-group-header .nuke-group-caret {
  font-size: 12px;
  color: #555;
}
.nuke-container .contact-group-header .nuke-group-label {
  font-weight: 700;
  color: #333;
}
.nuke-container .contact-group-header:hover .nuke-group-label {
  text-decoration: underline;
}

/* --- Disclaimer toast body + close + chat footer + lightbox ---- */

/* Disclaimer body color + bg + radius (was inline Tailwind). */
.nuke-container #nuke-chat-disclaimer {
  background: #FFFFE1;
  color: #666;
  border-radius: 6px;
}
/* Keep the safety text clear of the top-right close (x) button. */
.nuke-container #nuke-chat-disclaimer p {
  padding-right: 12px;
  /* Closer to the chat message body (15px) instead of the small container base. */
  font-size: 14px;
  line-height: 1.35;
}
/* Disclaimer close button glyph color + hover. */
.nuke-container .nuke-chat-disclaimer-close {
  color: #888;
}
.nuke-container .nuke-chat-disclaimer-close:hover {
  color: #333;
}
.nuke-container .nuke-chat-disclaimer-close .ph-x {
  font-size: 10px;
}
/* Frosted chat header — mirrors the footer's chrome at the TOP of the chat.
   Empty for now; the chat scrolls behind it. */
.nuke-container .nuke-chat-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 52px;
  z-index: 5;
  background: rgba(255, 255, 255, 0.88);
  border-bottom: 1px solid #EAEAEA;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
}
/* Back button on the top header's LEFT — only shown on a profile page. */
.nuke-container .nuke-chat-back-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-left: 8px;
  border: none;
  border-radius: 9999px;
  background: transparent;
  color: #0F1419;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-chat-back-btn:hover {
  background: rgba(0, 0, 0, 0.06);
}
/* No top header on a profile page — the banner runs to the very top. */
.nuke-container[data-profile-view] .nuke-chat-header {
  display: none;
}
/* x.com-style feed tabs filling the top header — 3 equal-width labels; the active
   tab gets a centered, text-width underline bar. Scaffold (no click wiring yet).
   Hidden in profile view, where the header shows the back button instead. */
.nuke-container .nuke-feed-tabs {
  display: flex;
  /* The header is itself a flex row (for the back button), so grow to fill its
     full width — otherwise this shrinks to content and the tabs bunch up. */
  flex: 1 1 auto;
  height: 100%;
}
.nuke-container[data-profile-view] .nuke-feed-tabs {
  display: none;
}
.nuke-container .nuke-feed-tab {
  flex: 1 1 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  border: none;
  background: none;
  cursor: pointer;
  color: #536471;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
}
.nuke-container .nuke-feed-tab-label {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 100%;
}
/* Active tab — dark bold label + the underline bar pinned to the header's bottom
   edge, spanning just the label (text) width. */
.nuke-container .nuke-feed-tab.is-active {
  color: #0F1419;
  font-weight: 700;
}
.nuke-container .nuke-feed-tab.is-active .nuke-feed-tab-label::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  border-radius: 9999px;
  background-color: #1D9BF0;
}
/* Second frosted header — sits directly below the first, twice its height
   (104px). Holds player actions; the chat scrolls behind it too. */
.nuke-container .nuke-chat-header-2 {
  /* In-flow as the first item of the chat scroll, so it scrolls AWAY with the
     feed (the pinned feed-tabs header above it stays). `relative` keeps it the
     containing block for its absolutely-positioned balance + stake/unstake btns. */
  position: relative;
  flex-shrink: 0;
  height: 104px;
  box-sizing: border-box;
  /* Solid white (no frosted blur) — reads as part of the chat panel. */
  background: #FFFFFF;
  border-bottom: 1px solid #EAEAEA;
  /* Player-actions content is laid out like a post: avatar gutter + content,
     same 12px gap and 12px/16px padding as `.nuke-post`. */
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
}
/* Third header — sits directly below header-2 (52 + 104 = 156px) and holds the
   recent-actions pill ticker (moved here from the right-column page header).
   Transparent (no fill, blur, or border) so the pills float over the chat. */
/* Profile page view (`/~handle`): the body holds the X.com-style profile page.
   The identity header-2 is hidden (the page IS the identity), and the banner runs
   to the very top — scrolling UNDER the sticky back-button header (header-1). */
.nuke-container[data-profile-view] .nuke-chat-header-2 { display: none; }
.nuke-container[data-profile-view] #nuke-chat-disclaimer { display: none; }

/* ── Single-post modal (`/~handle/<postId>`) ─────────────────────────────────
   Conceptually a modal, but styled like a full PANEL TAKEOVER (the old X-style
   "open post" page): an opaque overlay mounted inside the chat panel that covers
   it with a "← Post" bar + the single post. Because it's only an overlay, the
   feed underneath is never re-rendered → its scroll position is kept. The panel's
   own rounded overflow:hidden clips these corners. */
.nuke-container .nuke-post-modal-overlay {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: #FFFFFF;
  /* The panel's top/side hairline + rounded top corners are drawn by
     `.nuke-chat-container::after` (z-index 6); this overlay sits above it, so
     redraw the same #EFF3F4 hairline AND the same `--pfp-radius` top rounding here
     (same `inset: 0`, so it lines up exactly) — otherwise the panel's side borders
     and rounded top vanish under the overlay. */
  border-top: 1px solid #EFF3F4;
  border-left: 1px solid #EFF3F4;
  border-right: 1px solid #EFF3F4;
  border-radius: var(--pfp-radius) var(--pfp-radius) 0 0;
  overflow: hidden;
}
/* Top bar — a back arrow + "Post" (same as the prior detail page). A column so a
   second row (the notifications sorting strip) can stack beneath the top row. */
.nuke-container .nuke-post-modal-bar {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
}
.nuke-container .nuke-post-modal-bar-top {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
  /* Match the feed's nav header (`.nuke-chat-header`): same 52px height, and the
     8px left inset lines the back arrow up with the nav header's back button. */
  height: 52px;
  padding: 0 8px;
}
/* Notifications sorting strip — hidden for post/profile; the notif view turns it
   on (see the `[data-subview="notif"]` rules below). */
.nuke-container .nuke-notif-tabs { display: none; }
.nuke-container .nuke-post-modal-close {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  background: transparent;
  color: #0F1419;
  font-size: 18px;
  cursor: pointer;
}
.nuke-container .nuke-post-modal-close:hover { background: rgba(0, 0, 0, 0.06); }
.nuke-container .nuke-post-modal-title {
  font-weight: 700;
  font-size: 17px;
  color: #0F1419;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
/* Profile bar title name: matches `.nuke-profile-page-name` (the name by the pfp)
   so the header username reads identically. Kept in its own span so the title's
   flex gap sits only between the name and the level icon. */
.nuke-container .nuke-post-modal-title-name {
  font-size: 20px;
  font-weight: 800;
  line-height: 1.2;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Level icon next to the viewed user's name in the profile bar title. */
.nuke-container .nuke-post-modal-title-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  flex-shrink: 0;
}
/* When the opened post is mid-chain (its leading post carries the up-connector —
   the previous chain message is cut off above), the bar gets a bottom border so
   that up-line reads as continuing into the header. Otherwise the bar is borderless. */
.nuke-container .nuke-post-modal-overlay:has(.nuke-post-modal-body > .nuke-post-thread-above:first-child) .nuke-post-modal-bar {
  border-bottom: 1px solid #EFF3F4;
}

/* ── Notifications view header ── No back button; a taller (104px) bordered
   header: the "Notifications" label row on top, then a feed-style sorting strip
   (All / Priority / Mentions) below it. */
.nuke-container .nuke-post-modal-overlay[data-subview="notif"] .nuke-post-modal-close { display: none; }
.nuke-container .nuke-post-modal-overlay[data-subview="notif"] .nuke-post-modal-bar {
  border-bottom: 1px solid #EFF3F4;
}
/* No back arrow → inset the label to 16px so it lines up with the list below. */
.nuke-container .nuke-post-modal-overlay[data-subview="notif"] .nuke-post-modal-bar-top {
  padding: 0 16px;
}
.nuke-container .nuke-post-modal-overlay[data-subview="notif"] .nuke-post-modal-title {
  font-size: 20px;
  font-weight: 800;
}
/* The sorting strip: a second 52px row → 104px total (twice a normal header).
   Reuses `.nuke-feed-tab` chrome so the labels + active underline match the feed. */
.nuke-container .nuke-post-modal-overlay[data-subview="notif"] .nuke-notif-tabs {
  display: flex;
  height: 52px;
}
/* The post fills the body; it scrolls if it's taller than the panel. */
.nuke-container .nuke-post-modal-body { flex: 1; overflow-y: auto; }
/* Keep the post's bottom divider in the modal — the last post here is the last
   child, so the base `.nuke-post:last-child { border-bottom: none }` would drop
   it. The `:not(.nuke-post-thread-below)` keeps it OFF chained posts (whose
   divider is dropped so the thread reads continuous), restoring it only on the
   chain's last post (and a lone post). Same #EFF3F4 hairline as a feed post. */
.nuke-container .nuke-post-modal-body .nuke-post:not(.nuke-post-thread-below) { border-bottom: 1px solid #EFF3F4; }
/* NPC replies + NPC quotes in the post overlay are inert (no account → nothing to
   open, no profile) — `pointer-events: none` kills every affordance at once: no
   hover colour/shadow, no pfp press animation, no click. */
.nuke-container .nuke-post-modal-body .nuke-post[data-has-account="false"],
.nuke-container .nuke-post-modal-body .nuke-post-quote[data-has-account="false"] {
  pointer-events: none;
}
.nuke-container .nuke-post-modal-missing {
  padding: 24px 16px;
  color: #536471;
  text-align: center;
}

/* ============ Notifications tab modal — X.com-style rows ============
   Reuses the prod `createNotificationItem` markup (.notif-item > glyph + content)
   but re-skins it for the X look: a small flat circular glyph on the left, a bold
   title line, then a muted preview + time. (The MSN-era sizing was stripped in the
   styles consolidation, so without this the glyph SVG renders at intrinsic size —
   the "giant icons".) Scoped to `.nuke-notif-list` so it never touches the mission
   list or other `.notif-item` users. */
.nuke-container .nuke-notif-list .notif-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  background: transparent;
  border: none;
  border-bottom: 1px solid #EFF3F4;
  font-size: 15px;
  cursor: default;
  transition: background 0.12s;
}
.nuke-container .nuke-notif-list .notif-item:hover { background: #F7F9F9; }
.nuke-container .nuke-notif-list .notif-item.unread { background: #F7FBFF; }
.nuke-container .nuke-notif-list .notif-item.unread:hover { background: #EFF6FF; }

/* Glyph: a small flat tinted circle (the global gloss/rim app-icon chrome is
   suppressed for the clean X look). SVG inherits `color` via currentColor. */
.nuke-container .nuke-notif-list .notif-item-glyph {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #EAF4FF;
  color: #1D9BF0;
}
.nuke-container .nuke-notif-list .notif-item-glyph::before,
.nuke-container .nuke-notif-list .notif-item-glyph::after { display: none; }
.nuke-container .nuke-notif-list .notif-item-glyph svg {
  width: 18px;
  height: 18px;
  display: block;
}
/* Force the glyph tint through the SVG: the global `.notif-item *` rule sets
   color:black directly on the svg/paths, overriding the wrapper's inherited
   color — so re-assert currentColor across the glyph subtree. */
.nuke-container .nuke-notif-list .notif-item-glyph,
.nuke-container .nuke-notif-list .notif-item-glyph svg,
.nuke-container .nuke-notif-list .notif-item-glyph svg * {
  color: #1D9BF0;
}

/* Content column: title on top, muted preview + time below. */
.nuke-container .nuke-notif-list .notif-item-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nuke-container .nuke-notif-list .notif-item-title {
  font-weight: 700;
  font-size: 15px;
  line-height: 1.3;
  color: #0F1419;
}
.nuke-container .nuke-notif-list .notif-item-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.nuke-container .nuke-notif-list .notif-item-message {
  flex: 1;
  min-width: 0;
  color: #536471;
  font-size: 14px;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nuke-container .nuke-notif-list .notif-item-time {
  flex-shrink: 0;
  color: #536471;
  font-size: 13px;
}

/* Unread marker → small blue dot at the right edge (X-style). */
.nuke-container .nuke-notif-list .notif-unread-dot {
  top: 16px;
  right: 16px;
  width: 8px;
  height: 8px;
  background: #1D9BF0;
}

/* Mention preview keeps a slim quote bar, restyled to the light palette. */
.nuke-container .nuke-notif-list .notif-mention-quote {
  margin-top: 2px;
  padding: 2px 0 2px 8px;
  border-left: 2px solid #1D9BF0;
  background: transparent;
  color: #536471;
  font-style: normal;
  white-space: normal;
}
/* The profile CARD in the left column is redundant on a profile page (the page
   IS the profile), so hide just that box — the nav list below it stays. */
.nuke-container[data-profile-view] .nuke-nav-top-box { display: none; }
.nuke-container[data-profile-view] .nuke-chat-wrap > #nuke-chat-scroll {
  padding-top: 0;
}
/* Close the 12px gap between the top bar (48px) and the chat window so the
   profile banner butts directly against the top header — no spacing. */
.nuke-container[data-profile-view] .nuke-stage {
  top: 48px;
}
/* Profile page keeps the base chat-panel top — rounded top corners + crisp top
   hairline (inherited, no override) — so the profile panel matches the chat
   panel. (Was squared-off + borderless; user wants the rounded/bordered top.) */

/* ============ Profile PAGE (/~handle), X.com-style ============ */
.nuke-container .nuke-profile-page { display: flex; flex-direction: column; }
/* Banner — full column width, fixed height; runs under the sticky header. */
.nuke-container .nuke-profile-page-banner {
  position: relative;
  width: 100%;
  /* Twitter/X banner aspect ratio (1500×500 = 3:1); height follows the column width. */
  aspect-ratio: 3 / 1;
  flex-shrink: 0;
  background: #000000;
  overflow: hidden;
}
.nuke-container .nuke-profile-page-banner-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Hide the banner image while it has no source (placeholder banners removed, so
   the `<img>` is left empty as a wiring point). A sized `<img>` with no loadable
   src otherwise paints Chrome's broken-image frame — the gray border that reads
   around the black banner. Same for the left card's banner image. */
.nuke-container .nuke-profile-page-banner-img:not([src]),
.nuke-container .nuke-profile-page-banner-img[src=""],
.nuke-container .nuke-profile-card-banner-img:not([src]),
.nuke-container .nuke-profile-card-banner-img[src=""],
.nuke-container .nuke-hovercard-banner-img:not([src]),
.nuke-container .nuke-hovercard-banner-img[src=""] {
  display: none;
}
/* Identity block under the banner. */
.nuke-container .nuke-profile-page-id {
  display: flex;
  flex-direction: column;
  padding: 0 16px 8px;
}
/* Pfp row: the big avatar overlaps the banner's bottom edge (X.com). */
.nuke-container .nuke-profile-page-toprow {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-top: -40px;
  margin-bottom: 10px;
}
.nuke-container .nuke-profile-page-pfp {
  width: 80px;
  height: 80px;
  margin: 0;
  /* Rounded square, not a circle — inherit the root's bounded corner
     (clamp(3px, 13%, 5px)) so this 80px pfp reads the SAME gentle ~5px corner
     as the smaller list/hovercard avatars. A plain % made it look rounder. */
  /* White ring separating the avatar from the banner, like X.com. On the WRAPPER
     (not the inner `.cc-pfp` image) — the img's browser-default `overflow: clip`
     swallows a shadow placed on it, and the wrapper isn't hit by the `.cc-pfp`
     box-shadow:none kill rule, so this needs no `!important`. */
  box-shadow: 0 0 0 4px #FFFFFF;
}
.nuke-container .nuke-profile-page-pfp .cc-pfp {
  width: 100%;
  height: 100%;
}
.nuke-container .nuke-profile-page-name {
  font-size: 20px;
  font-weight: 800;
  color: #0F1419;
  line-height: 1.2;
}
.nuke-container .nuke-profile-page-handle {
  margin-top: 1px;
  font-size: 15px;
  color: #536471;
  font-style: italic;
  line-height: 1.3;
}
.nuke-container .nuke-profile-page-bio {
  margin-top: 12px;
  font-size: 15px;
  color: #0F1419;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
/* Tab strip. */
/* Info block — large inline metrics laid out like X.com's Following/Followers
   row (bold value + gray label), wrapping if needed. */
.nuke-container .nuke-profile-page-info {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 24px;
  padding: 4px 16px 14px;
}
.nuke-container .nuke-profile-metric {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
}
.nuke-container .nuke-profile-metric-value {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 22px;
  font-weight: 800;
  color: #0F1419;
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-profile-metric-icon { width: 22px; height: 22px; flex-shrink: 0; }
.nuke-container .nuke-profile-metric-label {
  font-size: 15px;
  color: #536471;
}
/* Section header above the notifications feed. */
/* The notifications list spans the full container width (the items carry their
   own post-style padding), like the message feed. The top border is the divider
   between the profile stats above and the notifications feed below. */
.nuke-container .nuke-profile-notifs {
  width: 100%;
  border-top: 1px solid #EFF3F4;
}
/* Empty state (e.g. Notifications tab placeholder). */
.nuke-container .nuke-profile-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 48px 16px;
  text-align: center;
}
.nuke-container .nuke-profile-empty-icon { font-size: 36px; color: #AAB8C2; }
.nuke-container .nuke-profile-empty-title { font-size: 17px; font-weight: 700; color: #0F1419; }
.nuke-container .nuke-profile-empty-sub { font-size: 14px; color: #536471; max-width: 280px; }
/* Spinning loader glyph (reuses the global `btn-spin` keyframe from styles.css). */
.nuke-container .nuke-profile-spin { animation: btn-spin 0.8s linear infinite; }
/* Retry button shown in the notifications error state. */
.nuke-container .nuke-profile-notif-retry {
  margin-top: 8px;
  padding: 6px 16px;
  border: 1px solid #CFD9DE;
  border-radius: 9999px;
  background: #FFFFFF;
  font-size: 14px;
  font-weight: 700;
  color: #0F1419;
  cursor: pointer;
}
.nuke-container .nuke-profile-notif-retry:hover { background: #F7F9F9; }

/* Notifications tab: re-style prod's `.notif-item` to read like a message post
   (icon in the pfp gutter + content column), but ALTERED so it's clearly a notif:
   a round colored icon-circle (not a square user avatar), and a faint blue tint +
   left accent on unread ones. */
.nuke-container .nuke-profile-notifs .notif-item {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 16px;
  border-bottom: 1px solid #EFF3F4;
  background: none;
  font-size: 15px;
  cursor: default;
}
.nuke-container .nuke-profile-notifs .notif-item.unread {
  background: rgba(29, 155, 240, 0.06);
  box-shadow: inset 3px 0 0 #1D9BF0;
}
/* Drop prod's app-icon gloss/rim pseudos on BOTH the item and its glyph — the
   item's `::after` rim was painting a thick inset bottom edge (the "underline").
   The flat 1px `border-bottom` above is the only separator. */
.nuke-container .nuke-profile-notifs .notif-item::before,
.nuke-container .nuke-profile-notifs .notif-item::after,
.nuke-container .nuke-profile-notifs .notif-item-glyph::before,
.nuke-container .nuke-profile-notifs .notif-item-glyph::after { display: none; }
.nuke-container .nuke-profile-notifs .notif-item-glyph {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #EFF3F4;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #1D9BF0;
}
.nuke-container .nuke-profile-notifs .notif-item-glyph svg,
.nuke-container .nuke-profile-notifs .notif-item-glyph img {
  width: 22px;
  height: 22px;
}
.nuke-container .nuke-profile-notifs .notif-item-content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.nuke-container .nuke-profile-notifs .notif-item-title {
  font-size: 15px;
  font-weight: 700;
  color: #0F1419;
  line-height: 1.3;
}
.nuke-container .nuke-profile-notifs .notif-item-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.nuke-container .nuke-profile-notifs .notif-item-message {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  color: #0F1419;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.nuke-container .nuke-profile-notifs .notif-item-message.notif-mention-quote {
  color: #536471;
  font-style: italic;
}
.nuke-container .nuke-profile-notifs .notif-item-time {
  flex-shrink: 0;
  font-size: 13px;
  color: #536471;
  white-space: nowrap;
}
/* The green unread dot is redundant with the tint+accent; hide it. */
.nuke-container .nuke-profile-notifs .notif-unread-dot { display: none; }

/* The chat header identity (pfp + name block) is clickable → that user's page. */
.nuke-container .nuke-chat-header-2 .nuke-post-pfp,
.nuke-container .nuke-chat-header-2 .nuke-chat-header-main {
  cursor: pointer;
}
/* Secondary header laid out like a post: pfp gutter + a main column holding the
   name/@handle row and the balance (the "message text"). */
.nuke-container .nuke-chat-header-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Center the name + @handle block against the 40px pfp (same as the messages:
     min-height of the pfp + vertical centering), instead of top-aligning it. */
  justify-content: center;
  min-height: 40px;
}
/* Username row. Reserve room on the right so a long name truncates before the
   absolutely-positioned balance (the meta row below is free of this so its DPR
   can sit under the balance). */
.nuke-container .nuke-chat-header-namerow {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  /* Tight, like the feed name cluster — badge hugs the name (not a word-space away). */
  gap: 3px;
  line-height: 1.25;
  font-size: 15px;
  padding-right: 110px;
}
.nuke-container .nuke-chat-header-name {
  /* Centered so it aligns with the level badge beside it (the balance is now
     absolutely positioned, so there's nothing to pin away from). */
  align-self: center;
  font-weight: 700;
  color: #000;
  white-space: nowrap;
}
.nuke-container .nuke-chat-header-handle {
  align-self: flex-start;
  color: #536471;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  /* Match the message @handle / cred size (15px) — they sit outside the 15px
     namerow so they'd otherwise inherit the 13px container base and read smaller.
     line-height matches the namerow (1.25) so name → handle → cred stack evenly. */
  font-size: 15px;
  line-height: 1.25;
}
.nuke-container .nuke-chat-header-rate .nuke-post-dpr {
  font-size: 15px;
  line-height: 1.25;
}
/* Meta row UNDER the name — @handle · DPR% · level icon, laid out exactly like
   the message hover card's meta line. */
/* Big bold balance (Phantom-style), pinned to the header's top-right and pulled
   OUT of flow so its tall 28px line box doesn't inflate the name row. The 13.5px
   top aligns the balance's visual top with the username's (the vertically-centered
   name sits ~1.5px lower than a plain top:12 would put the balance). */
.nuke-container .nuke-chat-balance {
  position: absolute;
  top: 13.5px;
  right: 16px;
  font-size: 24px;
  font-weight: 800;
  color: #0F1419;
  line-height: 1.1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Level icon (left) + DPR, pushed to the right end of the @handle row so the DPR
   lines up vertically with the handle and sits under the balance (it may overlap
   the balance — intentional). Header's default sizing (16px icon, 15px DPR). */
.nuke-container .nuke-chat-header-rate {
  /* Holds the cred vote pill + Use Item pill (same section as the post footer).
     Pinned to the header's bottom-left (left = 16px pad + 40px pfp + 12px gap, the
     message-field start) at the SAME `bottom:12px` as the stake/unstake buttons, so
     the pills' bottoms line up with the buttons'. Name/@handle stay top-aligned in
     the main column. */
  position: absolute;
  left: 68px;
  bottom: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* "Unstake" + "Stake" pills in the chat panel's secondary header — bottom-right,
   side by side, X "Post"-button style (fully-rounded, bold white). Unstake is
   red, Stake is near-black. */
.nuke-container .nuke-chat-action-btns {
  position: absolute;
  right: 16px;
  bottom: 12px;
  display: flex;
  gap: 8px;
}
.nuke-container .nuke-chat-stake-btn,
.nuke-container .nuke-chat-unstake-btn,
.nuke-container .nuke-chat-contribute-btn {
  height: 36px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  background: #0F1419;
  color: #FFFFFF;
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-chat-unstake-btn {
  background: #F4212E;
}
/* Nuclear-fallout swap: the Contribute (presale) button replaces Stake/Unstake.
   Hidden by default; the row gets data-fallout="true" (set in
   renderNukeHeaderBalance) during fallout, which hides Stake/Unstake and shows
   Contribute instead. */
.nuke-container .nuke-chat-contribute-btn {
  display: none;
}
.nuke-container .nuke-chat-action-btns[data-fallout="true"] .nuke-chat-stake-btn,
.nuke-container .nuke-chat-action-btns[data-fallout="true"] .nuke-chat-unstake-btn {
  display: none;
}
.nuke-container .nuke-chat-action-btns[data-fallout="true"] .nuke-chat-contribute-btn {
  display: inline-flex;
}
/* Full-page dark overlay (x.com style): fixed over the ENTIRE viewport, above all
   page chrome — darkens everything and floats the image on top. It's a direct child
   of .nuke-container so `fixed` escapes the stage's transform. (Blur set on the id.) */
.nuke-container .nuke-chat-lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.95);
}
.nuke-container .nuke-chat-lightbox-close {
  width: 36px;
  height: 36px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.1);
  color: #FFFFFF;
}
.nuke-container .nuke-chat-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.2);
  color: #FFFFFF;
}
.nuke-container .nuke-chat-lightbox-close .ph-x {
  font-size: 20px;
}
.nuke-container .nuke-chat-lightbox-img {
  border-radius: 6px;
}
/* Prev/next swipe arrows — hidden unless a multi-image gallery is open. */
.nuke-container .nuke-chat-lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: none;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.14);
  color: #FFFFFF;
  cursor: pointer;
  z-index: 110;
  display: none;
}
.nuke-container .nuke-chat-lightbox-nav:hover { background: rgba(255, 255, 255, 0.26); }
.nuke-container .nuke-chat-lightbox-nav i { font-size: 24px; }
.nuke-container .nuke-chat-lightbox-prev { left: 16px; }
.nuke-container .nuke-chat-lightbox-next { right: 16px; }
.nuke-container .nuke-chat-lightbox.has-gallery .nuke-chat-lightbox-nav { display: flex; }

/* --- Contact list / chat container chrome (white squares) ------ */

/* Contact list container — overrides `.inset-box`'s asymmetric XP-inset
   borders with the same 1px gray-7 ring + 6px corner radius the search
   bar uses, so they read as a matched chrome family. */
.nuke-container .nuke-contact-list,
.nuke-container .nuke-chat-container {
  border: none;
  border-radius: 6px;
  /* Matches the remote chat-bubble border (#E0E0E0) — single
     accent color used across all light UI edges in the nuke
     sandbox. */
  box-shadow: 0 0 0 1px #E0E0E0;
}

/* --- Staking panel (left player card) -------------------------- */
.nuke-container .nuke-staking {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0;
  font-family: var(--font-conduit);
}

/* --- Season Pass panel (left profile card) --------------------- */
/* 2x2 stat grid — mirrors the staking grid. */
.nuke-container .nuke-pass-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}

/* --- Middle stage browser-style tabs --------------------------- */

/* Tab strip — Chrome-style. Height matches the contact-list
   search-bar header exactly: that header is `min-height: 38px` plus
   `py-1.5` (6 + 6 = 12px padding), so its effective height is 44px.
   We size to 44px here and leave a 6px top inset that matches the
   horizontal `px-1.5`. Tabs stretch to fill the remaining 38px. */
.nuke-container .nuke-stage-tabs {
  height: 46px;
  box-sizing: border-box;
  padding-top: 6px;
  padding-bottom: 0;
  /* Override the strip's Tailwind `px-1.5` left padding so the action
     button sits flush against the container edge. */
  padding-left: 0;
}
/* Manual left-panel collapse (instant, no transition). Slides the left panel
   off-screen; the other columns reclaim the space. */
.nuke-container .nuke-stage {
  transition: none;
}
.nuke-container.is-left-collapsed .nuke-side-right {
  left: var(--page-edge-x);
}
.nuke-container.is-left-collapsed .nuke-stage {
  padding-left: calc(var(--page-edge-x) + var(--right-col-w) + 8px);
}
/* Each tab spans the full strip height, fixed wider width. Rounded
   top corners, flat bottom — visually attaches to the content area
   below it like a Chrome browser tab. Holds an icon + title pair. */
.nuke-container .nuke-stage-tab {
  position: relative;
  width: 200px;
  height: 100%;
  padding: 0 12px;
  margin-top: 1px;
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  -webkit-appearance: none;
  appearance: none;
}

.nuke-container .nuke-stage-tab:not(.is-active):hover::before {
  content: '';
  position: absolute;
  inset: 0 6px 7px 6px;
  background: #C8DDF2;
  border-radius: 8px;
  pointer-events: none;
}
/* Tabs that follow another tab overlap their left edge by 1px so
   adjacent borders don't double-stack into a visible 2px seam. */
.nuke-container .nuke-stage-tab + .nuke-stage-tab {
  margin-left: -1px;
}
/* Inactive-tab hover — solid white pill rounded on all four sides.
   The highlight lives on a ::before pseudo, inset 6px from the left
   and right edges of the tab so it matches the strip's 5px
   padding-top + 1px tab margin-top top gap. Pseudo + opacity
   transition for the hover fade is now defined alongside the
   base `.nuke-stage-tab` rule earlier in this file. */
/* Active tab — brighter bg that matches the stage-box surface
   (#FAFAFA), and a 1px negative bottom margin so the tab's bottom
   edge overlaps the toolbar's bottom border, "merging" with the
   content area below. */
.nuke-container .nuke-stage-tab.is-active {
  background-color: #E6F1FF;
  /* Background-image stack — first listed paints LAST (on top).
     1. Bg-colored cover from bottom up to the midpoint — sits above
        the dark shading and masks it out below 50% height.
     2. White top edge highlight.
     3/4/5. Dark shading from top/left/right edges (paints under #1). */
  background-image:
    linear-gradient(to top, #E6F1FF 0%, #E6F1FF 30%, transparent 60%),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.6) 0%, transparent 4px),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.05) 0%, transparent 4px),
    linear-gradient(105deg, rgba(0, 0, 0, 0.04) 0%, transparent 10px),
    linear-gradient(255deg, rgba(0, 0, 0, 0.04) 0%, transparent 10px);
  border-color: #C8DDF2;
  margin-bottom: -1px;
  z-index: 1;
}
/* Outward "feet" — only on the active tab. Each pseudo is an 8×8
   transparent square sitting just outside the tab's bottom corner.
   Only two adjacent sides get a border, and the corner between them
   is fully rounded — so the visible result is just a thin
   quarter-circle arc OUTLINE (no fill), bulging outward from the tab
   like the rounded outer edge of a circle. */
.nuke-container .nuke-stage-tab.is-active::before,
.nuke-container .nuke-stage-tab.is-active::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 8px;
  height: 8px;
  background: transparent;
  pointer-events: none;
  box-sizing: border-box;
}
.nuke-container .nuke-stage-tab.is-active::before {
  left: -8px;
  background:
    radial-gradient(circle 8px at top left, transparent 7.5px, #C8DDF2 7.5px, #C8DDF2 8.5px, transparent 8.5px),
    radial-gradient(circle 8px at top left, transparent 8px, #E6F1FF 8px);
}
.nuke-container .nuke-stage-tab.is-active::after {
  right: -8px;
  background:
    radial-gradient(circle 8px at top right, transparent 7.5px, #C8DDF2 7.5px, #C8DDF2 8.5px, transparent 8.5px),
    radial-gradient(circle 8px at top right, transparent 8px, #E6F1FF 8px);
}
/* Grow the profile column's tab strip so the larger profile tab fits
   (50px tab + 6px top inset). The shop/chat strips stay 46px. */
/* All three panel headers share the taller 56px strip so the Profile, Shop and
   Chat tab headers line up at the same height (was 46px for Shop/Chat). */
.nuke-container .nuke-side-right .nuke-stage-tabs,
.nuke-container .nuke-stage .nuke-stage-tabs {
  height: 56px;
}
/* Tiled motif background on the chat container — `repeat` lays
   `motif.png` over a near-white surface (#FAFAFA) as a subtle
   pattern. The `!important` overrides Tailwind's `bg-white` set on
   the element. */
.nuke-container .nuke-chat-container {
  /* Fully transparent feed — no veil, no blur. (X.com-style plain feed;
     blue gradient + motif overlay removed.) */
  background-color: transparent;
  background-image: none;
  isolation: isolate;
}
/* Chat panel — no tab header, no blue window-frame: the white chat fills the
   middle column with X-style vertical dividers (left + right border only, no
   top or bottom). */
.nuke-container .nuke-stage .window-frame {
  border: none;
  /* Side dividers match the per-message underline color (`.nuke-post`
     border-bottom: #EFF3F4) so the chat's vertical edges read as the same
     hairline as the horizontal post separators. */
  border-left: 1px solid #EFF3F4;
  border-right: 1px solid #EFF3F4;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
/* The chat's frosted header/footer have `backdrop-filter: blur`, and the
   window-frame's side border sits in their backdrop — so the blur smears it at
   the edges. Drop the border from the CHAT window-frame only (`:has` keeps the
   profile panel's border intact) and redraw the dividers crisply on top via
   `.nuke-chat-container::after` below, above the chrome's z-index. */
.nuke-container .nuke-stage .window-frame:has(.nuke-chat-container) {
  border-left: none;
  border-right: none;
  /* Round just the TOP corners to the shared --pfp-radius (was the base
     window-frame's 9px); the bottom sits flush, so it stays square. */
  border-radius: var(--pfp-radius) var(--pfp-radius) 0 0;
}
.nuke-container .nuke-stage .nuke-chat-container {
  margin: 0;
  /* Square corners + no ring — the ::after overlay frames the panel. */
  border-radius: 0;
  box-shadow: none;
}
/* Crisp side + top dividers painted ABOVE the frosted header/footer (z-index 6 >
   the 5 they use) so the backdrop blur can't reach them. Same #EFF3F4 hairline as
   the per-message underline; full height; click-through. */
.nuke-container .nuke-stage .nuke-chat-container::after {
  content: '';
  position: absolute;
  inset: 0;
  border-top: 1px solid #EFF3F4;
  border-left: 1px solid #EFF3F4;
  border-right: 1px solid #EFF3F4;
  /* Follow the frame's rounded top corners so the crisp hairline curves too
     (this overlay being square is what made the top read as square). */
  border-radius: var(--pfp-radius) var(--pfp-radius) 0 0;
  pointer-events: none;
  z-index: 6;
}
/* Motif texture, faded to 0.6 over the blue fill — a background-image can't
   take its own opacity, so it lives on this overlay instead. Behind all chat
   content via the negative z-index within the isolated container. */
.nuke-container .nuke-chat-container::before {
  display: none; /* motif removed for the white feed */
}

/* --- Overlay scrollbars (contact list + chat) ------------------ */

/* True overlay scrollbar: hide the native one entirely and float a
   custom thumb inside the wrapper, JS-positioned. */
.nuke-container .nuke-contact-list-wrap {
  position: relative;
  display: flex;
  min-height: 0;
  /* White bg so macOS rubber-band overscroll reveals the same color
     as the list itself, not the light-blue `#E6F1FF` window frame. */
  background: #FFFFFF;
  border-radius: 6px;
}
.nuke-container .nuke-contact-list-wrap > .nuke-contact-list {
  overscroll-behavior: none;
}
.nuke-container .nuke-contact-list-wrap > .nuke-contact-list {
  flex: 1;
  scrollbar-width: none; /* Firefox */
  /* Breathing room above the first group label so "Online" doesn't
     kiss the container's top edge. */
  padding-top: 4px;
  /* `contain: layout paint` tells the browser nothing inside affects
     layout/paint outside this element. During viewport resize, the
     layout pass doesn't have to walk into the contact-list subtree
     to recompute anything — big win when the list has many rows. */
  contain: layout paint;
}
.nuke-container .nuke-contact-list-wrap > .nuke-contact-list::-webkit-scrollbar {
  display: none; /* Chromium */
}
.nuke-container .nuke-contact-list-thumb {
  position: absolute;
  /* Top inset much larger than the bottom — leaves visible breathing
     room below the group header. JS uses matching INSET_TOP/_BOTTOM
     constants to clamp the thumb travel. */
  top: 26px;
  right: 2px;
  width: 8px;
  /* Matches the "Connected" footer strip's top border (#D1D1D1). */
  background: #D1D1D1;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  /* JS sets `height` and `transform: translateY(...)` based on
     scrollTop. Hidden by default; only shown when overflow exists. */
}
.nuke-container .nuke-contact-list-wrap.is-scrolling .nuke-contact-list-thumb {
  opacity: 1;
}
/* Chat scroll — same overlay scrollbar chrome as the contact list,
   scoped to the chat conversation window's scroller. The scroller is
   absolutely positioned inside `.nuke-chat-wrap` (so it can fill the
   white container regardless of the floating disclaimer toast above
   it). The thumb hides the native bar and tracks scroll via JS. */
.nuke-container .nuke-chat-wrap > #nuke-chat-scroll {
  /* Edge-to-edge posts (override the element's Tailwind px-3 py-2 gap-2). The
     scroll fills the full height behind the pinned feed-tabs header; 52px top
     padding clears just that header. The identity header-2 now lives INSIDE the
     scroll (first child) and scrolls away with the feed. Small bottom breathing
     margin (no footer chrome to clear). */
  padding: 52px 0 12px;
  gap: 0;
  overscroll-behavior: none;
  scrollbar-width: none;
  /* Same containment hint as `.nuke-contact-list` — the chat-message
     subtree (often dozens of rendered messages, images, animated
     emoji) is isolated from outer-layout work. */
  contain: layout paint;
  /* Own compositor layer — pins the animated-emoji repaints inside the
     chat to their own raster tile so they can't dirty the blurred panel
     headers / stage (the page-wide zoom otherwise shares one raster). */
  transform: translateZ(0);
}
.nuke-container .nuke-chat-wrap > #nuke-chat-scroll::-webkit-scrollbar {
  display: none;
}
.nuke-container .nuke-chat-scroll-thumb {
  /* Scrollbar removed — chat still scrolls (wheel/trackpad), just no visible
     bar (native bar is already hidden via `scrollbar-width: none`). */
  display: none;
  position: absolute;
  top: 44px;
  right: 2px;
  width: 8px;
  background: #D1D1D1;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.nuke-container .nuke-chat-wrap.is-scrolling .nuke-chat-scroll-thumb {
  opacity: 1;
}

.nuke-container .nuke-chat-scroll-btn {
  position: absolute;
  /* x.com "back to top / show new posts" pill: solid blue, horizontally centered,
     resting just below the pinned feed-tabs header (52px + 8px gap). */
  top: 60px;
  left: 50%;
  height: 34px;
  padding: 0 16px;
  box-sizing: border-box;
  background: #1D9BF0;
  border: none;
  border-radius: 9999px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
  flex-shrink: 0;
  opacity: 0;
  /* Slides down from under the header; translateX keeps it centered. */
  transform: translateX(-50%) translateY(-8px);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 15;
}

.nuke-container .nuke-chat-scroll-btn-icon {
  font-size: 18px;
  color: #FFFFFF;
}

.nuke-container .nuke-chat-scroll-btn.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

.nuke-container .nuke-chat-scroll-btn:hover {
  background: #1A8CD8;
}

/* --- Contact list groups (online / offline) -------------------- */

/* Contact rows + group headers: rounded corners so the hover bg-fill
   reads as a soft pill rather than a sharp rectangle. */
.nuke-container .nuke-contact-list .contact-group-header,
.nuke-container .nuke-contact-list .contact-group-inner > div {
  border-radius: 6px;
}

/* =============================================================
   Contact group collapse / expand animation
   =============================================================
   COLLAPSE (snappy, fade-only):
     • Each row: opacity 1 → 0 over 100ms, no visible scale change
     • Bottom-to-top stagger: 25ms between rows, capped at 4 rows
     • Container height shrinks 200ms after the fade chain starts

   EXPAND (fancy spring pop-in):
     • Container expands first (180ms)
     • Then each row pops: opacity 0 → 1 over 150ms,
       transform scale(0.7) → scale(1) over 320ms with back-overshoot
     • Top-to-bottom stagger: 60ms between rows, capped at 4 rows

   How the "no scale on collapse" trick works: in the closed state, the
   transform transition has 0s duration AND a delay equal to the fade
   chain — so the scale snap to 0.7 happens INVISIBLY (opacity is
   already 0). On re-expand, the row is primed at scale(0.7) and gets
   the spring-back animation.

   Per-row stagger is driven by `--stagger`, set per-state with calc()
   on `--row-idx` / `--row-from-bottom` (custom props set inline by JS).
   ============================================================= */

/* Container height collapse/expand. */
.nuke-container .nuke-contact-list .contact-group-content {
  display: grid;
  grid-template-rows: 1fr;
}
.nuke-container .nuke-contact-list .contact-group[data-open="false"] .contact-group-content {
  grid-template-rows: 0fr;
}
.nuke-container .nuke-contact-list .contact-group-inner {
  overflow: hidden;
  min-height: 0;
  padding-top: 6px;
}

/* Open / closed states for rows — no transitions, just instant
   visibility toggling. */
.nuke-container .nuke-contact-list .contact-group[data-open="false"] .contact-group-inner > div {
  opacity: 0;
}

/* Caret rotates 90° counter-clockwise (points right) when collapsed. */
.nuke-container .nuke-contact-list .contact-group[data-open="false"] .contact-group-header .ph-caret-down {
  transform: rotate(-90deg);
}

/* --- Search-bar input chrome ----------------------------------- */

/* ===== Rebuilt player search — ONE column element ====================
   Collapsed = a fixed-height input row (icon + input). On hover/focus the results
   list expands BELOW it (the bar grows downward + gains a drop shadow) and the page
   darkens via `.nuke-hover-spotlight`. The input row is identical in both states so
   the placeholder/text never shifts; the bar is position:fixed at the SAME spot in
   both states (see `.nuke-subheader .user-search-bar` below). */
.nuke-container .nuke-search-row .user-search-bar {
  display: flex;
  flex-direction: column; /* input row + results, ALWAYS — no row→column flip */
  width: 100%;
  background: #FFFFFF;
  border: 1px solid #E0E0E0;
  box-sizing: border-box;
  border-radius: 22px;
  overflow: hidden; /* clip the results to the rounded corners */
  box-shadow: none;
  transform: none; /* suppress the base bar's --cc-lift/--cc-press scale-on-hover */
  z-index: 40; /* above the .nuke-hover-spotlight scrim */
  transition: box-shadow 0.18s ease;
}
.nuke-container .nuke-search-row .user-search-bar:hover,
.nuke-container .nuke-search-row .user-search-bar:focus-within {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
/* Fixed-height input row — IDENTICAL collapsed and expanded, so the input text
   never shifts when the panel opens. Holds the icon (absolute) + the input. */
.nuke-container .user-search-inputrow {
  position: relative;
  flex: 0 0 44px;
  height: 44px;
  display: flex;
  align-items: center;
}
/* Magnifying glass anchored to the left of the input row, vertically centered. */
.nuke-container .nuke-search-row .nuke-search-icon {
  position: absolute;
  top: 50%;
  left: 14px;
  transform: translateY(-50%);
  color: #808080;
  font-size: 16px;
  pointer-events: none;
  z-index: 2;
}
.nuke-container .nuke-search-row .user-search-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: none;
  outline: none;
  background: transparent;
  padding: 0 14px 0 38px; /* left clears the icon (14 + 16 + 8) */
  font-family: 'Tahoma', 'Segoe UI', Arial, sans-serif;
  font-size: 14px;
  font-style: normal;
  font-weight: 500;
  color: #808080;
}
/* Placeholder keeps the smaller italic style — only the typed
   text uses the larger username-style font. */
.nuke-container .nuke-search-row .user-search-input::placeholder {
  font-family: 'Tahoma', 'Segoe UI', Arial, sans-serif;
  font-size: 12px;
  font-style: italic;
  font-weight: 500;
  color: #808080;
  opacity: 1;
}

/* --- Search bar focus expansion ------------------------------------
   When focused, the sub-header search pill drops down into the player
   list as a panel and its edges un-round from the full pill to the 6px
   radius the page-content containers use. The bar is absolutely
   positioned over a fixed-height wrapper (top-anchored) so the header
   layout stays put and the input + icon stay pinned to the top while the
   panel grows below them. */
.nuke-container .nuke-subheader-search .user-search-wrapper {
  position: relative;
  height: 44px; /* reserves the collapsed bar height */
}
.nuke-container .nuke-subheader .user-search-bar {
  /* Fixed at the EXACT SAME spot as the focused/expanded state below, so the bar
     and its expanded dropdown are ONE continuous element that simply grows
     (height 44px → auto) on focus — no `absolute → fixed` position swap, which was
     what made the collapsed bar and the dropdown behave as two separate things.
     The wrapper still reserves the 44px collapsed height for layout. */
  position: fixed;
  top: 52.5px;
  left: auto;
  right: calc(50% - var(--right-col-w) - var(--chat-w) / 2 - var(--col-gap) + var(--right-inset));
  width: calc(var(--right-col-w) - 2 * var(--right-inset));
  /* Heavily rounded ends (22px = half the 44px height → capsule). */
  border-radius: 22px;
}
/* The rebuilt bar needs NO focus-state layout overrides — the input row is a
   constant 44px and the bar grows simply by un-hiding the results below (see the
   `.nuke-search-results` toggle). Expand shadow is on `:hover/:focus-within` above. */
/* Player-list results below the input, in normal flow so the panel sizes to
   them; only shown while focused. It's a `.nuke-contact-list-wrap` clone, so
   the inner `.nuke-contact-list` scrolls with the same overlay scrollbar as
   the real player list. */
.nuke-container .nuke-search-results {
  display: none;
  min-height: 0;
}
.nuke-container .nuke-subheader-search:is(:focus-within, :has(.user-search-bar:hover)) .nuke-search-results {
  display: block;
}
.nuke-container .nuke-search-results .nuke-contact-list {
  /* Drop the inner 1px ring (the panel's own border frames it) and cap the
     list at the available height down to the chat bottom — short lists size to
     their rows; long ones scroll. 69px = 9px top + 44px input + 16px bottom. */
  box-shadow: none;
  max-height: calc((100vh - 69px) / 2 + 64px);
}
/* Top fade so rows dissolve as they scroll up under the input instead of
   being hard-cut. Implemented as a white gradient overlay (the panel is white)
   rather than a mask, so its opacity can transition smoothly — it only appears
   once the list is scrolled (JS toggles `is-scrolled` on the wrap). */
.nuke-container .nuke-search-results::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 36px;
  background: linear-gradient(to bottom, #FFFFFF 0, rgba(255, 255, 255, 0) 100%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 1;
}
.nuke-container .nuke-search-results.is-scrolled::after {
  opacity: 1;
}

/* --- Simplified PFP: square + 1px border, NO shading ----------- */
/* The /home pfp design layers a domed gloss (::before), a lit rim (::after),
   and a drop shadow on every avatar. On /nuke we want a flat, simple thumbnail,
   so: (1) hide the gloss + rim pseudo-elements on every pfp wrapper, and
   (2) collapse the img's box-shadow to just the outer border ring (drop the
   `--pfp-shadow`). Rounded corners + the ring stay. */
/* Avatars are slightly-rounded squares: override the design-system's
   --pfp-radius (default 16% in styles.css :root) at the /nuke root. The pfps vary
   widely in size (20px reply quotes → 56px hovercard), so use a BOUNDED
   percentage via clamp(min, %, max): the % scales the corner proportionally in
   the middle while the min keeps it from vanishing on the tiniest avatars and
   the max keeps it from over-rounding the biggest — so every size reads as the
   same gentle corner (effective range ~3–6px). A plain % left the 20px ones
   looking square and a plain px made them look rounder than the big ones.
   Cascades to every pfp that reads the var (.cc-pfp, .msg-pfp,
   .nuke-hovercard-pfp, .pill-pfp, etc.). Single source of truth — tune here.
   Scoped to .nuke-container so prod is untouched. */
.nuke-container {
  --pfp-radius: clamp(3px, 13%, 5px);
}
.nuke-container .cc-pfp-wrapper::before,
.nuke-container .cc-pfp-wrapper::after,
.nuke-container .msg-pfp-frame::before,
.nuke-container .msg-pfp-frame::after,
.nuke-container .profile-panel-pfp-wrapper::before,
.nuke-container .profile-panel-pfp-wrapper::after,
.nuke-container .nuke-post-pfp::before,
.nuke-container .nuke-post-pfp::after,
.nuke-container .nuke-row-pfp::before,
.nuke-container .nuke-row-pfp::after,
.nuke-container .nuke-post-quote-pfp::before,
.nuke-container .nuke-post-quote-pfp::after,
.nuke-container .nuke-hovercard-pfp::before,
.nuke-container .nuke-hovercard-pfp::after,
.nuke-container .pill-pfp::before,
.nuke-container .pill-pfp::after {
  display: none !important;
}
/* Belt-and-suspenders: kill the gloss gradient at the source so even an
   unforeseen ::before that slips through paints nothing. */
.nuke-container .cc-pfp-wrapper,
.nuke-container .msg-pfp-frame,
.nuke-container .profile-panel-pfp-wrapper,
.nuke-container .pill-pfp {
  --pfp-gloss: none;
  --pfp-rim-top: transparent;
  --pfp-rim-side: transparent;
  --pfp-rim-bottom: transparent;
}
.nuke-container .cc-pfp,
.nuke-container .msg-pfp,
.nuke-container .profile-panel-pfp,
.nuke-container .pill-pfp-img {
  /* Borderless — no outer ring (was a 1px box-shadow ring). NO gloss, rim,
     or drop shadow either, just the flat rounded image. */
  box-shadow: none !important;
}
/* Hover popup + reply quote pfps: borderless too. */
.nuke-container .nuke-hovercard-pfp .cc-pfp,
.nuke-container .nuke-post-quote-pfp .cc-pfp {
  box-shadow: none !important;
}

/* --- Profile / row PFPs ---------------------------------------- */

/* Row PFPs in the contact list — same PFP design system chrome
   (gloss/rim via the existing :before/:after selector lists) at
   a much smaller size. The right-edge status dot reuses the same
   placement pattern as the header PFP. */
.nuke-container .nuke-row-pfp {
  width: 40px;
  height: 40px;
  margin: 0;
  /* Matches the remote chat-bubble border (#E0E0E0) — single
     accent color used across all light UI edges. */
  --pfp-outer-border-color: #EFF3F4;
}
.nuke-container .nuke-row-pfp .cc-pfp {
  width: 100%;
  height: 100%;
}
/* Status dot hangs on the bottom-right corner of the PFP — pushed
   down + right so it overlaps the visual ring with a slight outside
   overhang (centered roughly on the corner of the visual PFP). */
/* .nuke-row-pfp dot: size + offset are proportional now (base .status-dot). */

/* --- Row banner image (hover-revealed) ------------------------- */

/* Random banner image on the right side of every row, full row
   height × 160px wide (matches the prod calling-card 4:1 banner ratio
   scaled to a 40px row). Grayscale, low opacity, hidden at rest, fades
   in with the hover bg highlight. Sits BEHIND the row's normal-flow
   content via z-index: -1 inside the row's `isolate` stacking context,
   so it never paints over the PFP or text. */
.nuke-container .nuke-row-banner {
  position: absolute;
  top: 0;
  right: 0;
  width: 200px;
  height: 50px;
  border-radius: 6px;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  opacity: 0;
  transition: opacity 0.2s ease;
  /* Fade the banner out on the left so it never crowds the text column —
     mirrors the prod `.cc-banner-img` mask. */
  mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.4) 35%, black 75%);
  -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,0.4) 35%, black 75%);
}
.nuke-container .nuke-row:hover .nuke-row-banner {
  opacity: 1;
}
.nuke-container .nuke-row-banner img {
  width: 100%;
  height: 100%;
  object-fit: fill;
  filter: grayscale(1);
  opacity: 0.4;
}

/* --- Pill PFPs inside chat content ----------------------------- */

/* `.pill-pfp` inside the nuke chat — sized 1px smaller than the
   design-system default (13px instead of 14px). The inline-text
   alignment tricks (`vertical-align: middle` + `translateY(-1px)`)
   stay so @-mention PFPs inside bubble content sit correctly with
   the surrounding text. */
.nuke-container #nuke-chat-scroll .pill-pfp {
  width: 20px;
  height: 20px;
}
/* @-mention pills (a `.pill-pfp` left of the name inside a `.dock-pill`) show
   the name only — hide the inline avatar. The chain-header avatar is NOT inside
   a `.dock-pill`, so it stays. */
.nuke-container #nuke-chat-scroll .dock-pill .pill-pfp {
  display: none;
}
/* Chat system pills wrap to the next line instead of overflowing the
   conversation width. The base `.dock-pill` / `.pill-content` are
   `white-space: nowrap` + `flex-shrink: 0` for the header ticker; in the chat
   the wrapper is capped at 90% (TS) and the text wraps here. A very long
   unbreakable token (e.g. an address) breaks rather than forcing h-scroll. */
.nuke-container #nuke-chat-scroll .dock-pill {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}
.nuke-container #nuke-chat-scroll .dock-pill .pill-content {
  white-space: normal;
}

/* The global `.nuke-page-footer` was removed; its flat #EBEBEB +
   raised-bevel design now lives (flipped) on `.nuke-page-header`. */

/* --- Contact list row geometry + typography -------------------- */

/* Row geometry — 40px tall with 5px padding all sides → 30px interior.
   Padding is 5px (not 4) because the PFP's box-shadow ring extends 1px
   past its layout box; 5px CSS padding renders as 4px *visible* padding
   from the row edge to the ring, matching equally on all four sides.
   PFP wrapper = 30px (fills the interior exactly, no centering offset);
   visible PFP = 32px including the 1px ring on each side. Text column
   is also 30px to match the interior. */
.nuke-container .nuke-row {
  height: 50px;
  flex-shrink: 0;
  box-sizing: border-box;
  /* Skip rendering rows scrolled out of the contact list viewport.
     With ~50px fixed height the placeholder size is exact, so scroll
     geometry stays stable. */
  content-visibility: auto;
  contain-intrinsic-size: 50px;
}
.nuke-container .nuke-row-text {
  height: 40px;
  justify-content: flex-start;
  /* Breathing room between the username and the @handle sub-line. */
  gap: 4px;
}
/* Matches the profile hover card name: 15px bold #0F1419. */
.nuke-container .nuke-row-name {
  font-weight: 700;
  font-size: 15px;
  line-height: 17px;
  height: 17px;
  color: #0F1419;
}
/* The @handle — matches the profile hover card handle (#536471). Size comes from
   `.nuke-row-meta` below (one 15px sub-line source); it takes the row's free
   space and truncates. */
.nuke-container .nuke-row-subtitle {
  color: #536471;
  line-height: 17px;
  height: 17px;
  min-width: 0;
  flex: 0 1 auto;
}
/* Contact-row second line: @handle · DPR% + level icon, laid out like a message
   head's meta cluster (reuses .nuke-post-dpr / -dot / -level-icon). 15px matches
   the profile hover card's meta + the row name. */
.nuke-container .nuke-row-meta {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 15px;
}
.nuke-container .nuke-row-meta .nuke-post-dot,
.nuke-container .nuke-row-meta .nuke-post-dpr {
  flex-shrink: 0;
  font-style: normal;
}
/* Level icon keeps the message size (1em square, from the unified badge
   rule) — no search-specific override. */

/* Heavier base body weight across the nuke page (web-standard presence). */
.nuke-container { font-weight: 500; }

/* ─── X.com-style right-panel responsiveness ───────────────────────────────
   The right column is a fixed `--right-col-w` (350px, x.com's sidebar width).
   Below the width where all three columns + gaps + 16px edges fit comfortably
   (~1360px — the right column overflows first, since the left/Profile column is
   wider than x.com's icon nav), the right column AND its headers (recent-actions
   pills + player search) drop out entirely — exactly like x.com hiding its right
   sidebar under ~1050px — and the Profile + chat pair recenters into the freed
   space: shift right by half the removed column (= (--col-w + --col-gap) / 2),
   which the translateX(-50%)-centered chat follows via its `left`. */
@media (max-width: 1360px) {
  .nuke-container .nuke-side-right,
.nuke-container .nuke-subheader {
    display: none;
  }
  /* Right column + its headers drop out; the nav + chat pair recenters into the
     freed space — shift the chat RIGHT by half the nav side (= (nav-col-w +
     col-gap) / 2), and the nav follows so it stays glued to the chat's left. */
  .nuke-container .nuke-stage {
    left: calc(50% + (var(--nav-col-w) + var(--col-gap)) / 2);
  }
  .nuke-container .nuke-nav {
    left: calc(50% - var(--chat-w) / 2 - var(--col-gap) / 2 - var(--nav-col-w) / 2);
  }
}

/* ─── Profile hover card (hover a message's pfp / username / handle) ────────
   A small rounded white card that fades in with the focused-search drop shadow;
   the user's pfp sits top-left in the same chrome as the message pfps
   (.nuke-post-pfp), with name + @handle beside it. Positioned + populated by
   src/ts/pages/nuke/hovercard.ts. */
.nuke-container .nuke-hovercard {
  position: fixed;
  z-index: 50;
  /* Match the left profile card's width (the nav content area = --nav-col-w minus
     its 12px side padding x2). The .nuke-profile-card override keeps the left one
     at 100% of its box; this sizes the popup to the same width. */
  width: calc(var(--nav-col-w) - 24px);
  box-sizing: border-box;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Clickable → opens the shown user's profile page. */
  cursor: pointer;
  /* Recessed shading carried over from the (removed) profile-panel tab
     (.nuke-stage-tab.is-active), recolored to a light gray so the white top-edge
     highlight reads, with dark shading on the bottom plus the
     dark shading bleeding in from the top/left/right edges. The tab's
     ::before/::after "feet" are tab-specific and omitted. */
  background-color: #FDFDFE;
  background-image:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.7) 0%, transparent 5px),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.04) 0%, transparent 5px),
    linear-gradient(to top, rgba(0, 0, 0, 0.04) 0%, transparent 16px),
    linear-gradient(105deg, rgba(0, 0, 0, 0.04) 0%, transparent 10px),
    linear-gradient(255deg, rgba(0, 0, 0, 0.04) 0%, transparent 10px);
  /* Match the avatar/reply roundedness — shared --pfp-radius. (Was 16px.) */
  border-radius: var(--pfp-radius);
  /* Same drop shadow as the focused search dropdown. */
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  padding: 12px;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.95);
  transform-origin: top left;
  transition: opacity 0.16s ease, transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  font-size: 15px;
}
/* x.com-style rounded cards: replies, framed images, the side panels, and the
   profile hover popup get a larger 16px corner than the avatars' ~5px
   --pfp-radius. Placed after all four originals so it wins on source order; the
   bare-sticker rule (.is-sticker, higher specificity) still keeps transparent
   images square-free. */
.nuke-container .nuke-post-quote,
.nuke-container .nuke-post-media-1 .nuke-post-img-wrap,
.nuke-container .nuke-post-media-grid,
.nuke-container .nuke-plain-box,
.nuke-container .nuke-hovercard {
  border-radius: 16px;
}
.nuke-container .nuke-hovercard.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: scale(1);
}
/* The popup's banner / head / pfp size + ring all come from the SHARED rules
   defined alongside the left profile card (search `.nuke-profile-card-banner` /
   `-head`). The popup markup (`.nuke-hovercard-banner`, `.nuke-hovercard-head`) is
   listed in those same selector groups, so the popup and card are guaranteed
   identical — no popup-specific pfp copies here that could drift. The status dot
   sizes/offsets proportionally off the base `.status-dot`. */
.nuke-container .nuke-hovercard-meta {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}
/* Identity row below the pfp: name/@handle block on the left, balance on the
   right, vertically centered together so the balance lines up with the username
   and handle (rather than being pinned at the card's top-right). */
.nuke-container .nuke-hovercard-idrow {
  margin-top: 8px;
  display: flex;
  /* Top-align so the balance's TOP lines up with the username's top — both the
     hover popup AND the left profile box. */
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.nuke-container .nuke-hovercard-id {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Name + level badge on one row (badge sits next to the name like a checkmark).
   Font-size matches the 17px name so the badge's `1em` resolves to 17px. */
.nuke-container .nuke-hovercard-namerow {
  display: flex;
  align-items: center;
  /* Tight, like the feed name cluster — badge hugs the name. */
  gap: 3px;
  min-width: 0;
  font-size: 17px;
}
.nuke-container .nuke-hovercard-name {
  font-weight: 700;
  color: #0F1419;
  font-size: 17px;
  line-height: 1.25;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nuke-container .nuke-hovercard-handle {
  color: #536471;
  font-size: 15px;
  line-height: 1.25;
}
/* Bio line below the pfp/name section (hidden when the user has no bio). */
.nuke-container .nuke-hovercard-bio {
  margin-top: 8px;
  color: #0F1419;
  font-size: 15px;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
/* Balance on the right of the identity row, vertically centered with the
   username + @handle (28px / 700, same as the profile-header balance). Hidden via
   inline style when the user has no (non-zero) balance. */
.nuke-container .nuke-hovercard-balance {
  flex-shrink: 0;
  font-size: 24px;
  font-weight: 800;
  color: #0F1419;
  line-height: 1.1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE FRAMEWORK — screen-size breakpoints for /home
   ═══════════════════════════════════════════════════════════════════════════
   /home is this 3-column, x.com-style shell. Every column is `position: fixed`
   and anchored to the viewport CENTRE via `calc(50% ± var(--chat-w)/2 …)`, so
   the layout is centre-out, not flow-based. The tiers below degrade that down
   to a single-column mobile build.

   Desktop-first cascade: each `max-width` block layers UNDER the ones above it
   (source order 1360 → 1024 → 768 → 480). A 700px viewport therefore gets the
   1360 + 1024 + 768 rules, with the narrowest tier winning any conflict.

     ┌────────────┬──────────────┬───────────────────────────────────────────┐
     │ Tier       │ Width        │ Layout                                      │
     ├────────────┼──────────────┼───────────────────────────────────────────┤
     │ Desktop    │ ≥ 1361px     │ 3 cols:  nav │ feed (600) │ right sidebar   │
     │ Laptop     │ 1025–1360px  │ 2 cols:  nav │ feed       (right hidden)    │
     │ Tablet     │  769–1024px  │ 1 col:   centred fluid feed                 │
     │ Mobile     │ ≤  768px     │ full-width feed + top app bar + bottom nav  │
     │ Sm. phone  │ ≤  480px     │ mobile build, tighter gutters               │
     └────────────┴──────────────┴───────────────────────────────────────────┘

   The Desktop + Laptop tiers live ABOVE this block (the `max-width: 1360px`
   query earlier in the file). Everything from here down is new scaffold —
   structure only; refine the visuals later. The side-column content (profile
   card, shop, notebook, radio) is hidden below the tablet break; the intent is
   to resurface it through the bottom-nav tabs as drawers/sheets later.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Mobile bottom tab bar — primary nav on phones ──────────────────────────
   Fully styled here but `display:none` until the ≤768px build switches it on.
   Markup is the `.nuke-mobile-nav` scaffold in index.html; the `data-mnav`
   hooks are unwired placeholders to build on later. */
.nuke-container .nuke-mobile-nav {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  box-sizing: border-box;
  /* Height includes the iOS home-indicator inset so the icon row clears the
     gesture bar; the matching feed `bottom` below reserves the same amount. */
  height: calc(var(--m-botnav-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  z-index: 40;
  align-items: stretch;
  background: #FFFFFF;
  border-top: 1px solid #EAEAEA;
}
.nuke-container .nuke-mobile-nav-item {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  color: #536471;          /* x.com inactive-icon grey */
  font-size: 26px;
  line-height: 1;
  -webkit-appearance: none;
  appearance: none;
}
.nuke-container .nuke-mobile-nav-item.is-active {
  color: #0F1419;          /* x.com active-icon near-black */
}

/* ── Tablet (≤1024px): collapse to a single centred feed ───────────────────── */
@media (max-width: 1024px) {
  /* The left nav rail joins the right sidebar in hiding; the feed stands alone. */
  .nuke-container .nuke-nav {
    display: none;
  }
  /* Feed recentres on the viewport (undo the laptop tier's nav-pair shift) and
     goes fluid so it never overflows the narrower viewport. */
  .nuke-container .nuke-stage {
    left: 50%;
    transform: translateX(-50%);
    width: min(var(--chat-w), 100vw - 2 * var(--page-edge-x));
  }
  /* The top-bar logo is anchored off the (now hidden) nav column — repin it to
     the page edge so it doesn't fly off-screen. */
  .nuke-container .nuke-page-topbar .nuke-nav-logo {
    left: var(--page-edge-x);
  }
  /* The pill ticker + its scroll caps span the (now hidden) side columns — drop
     them so nothing floats in the empty gutters. */
  .nuke-container .nuke-page-topbar .nuke-pill-header,
  .nuke-container .nuke-page-topbar .nuke-pill-scroll-cap {
    display: none;
  }
}

/* ── Mobile (≤768px): the mobile build ─────────────────────────────────────── */
@media (max-width: 768px) {
  /* Feed fills the whole viewport between the slim top app bar and the bottom
     tab bar — edge to edge, no centring. */
  .nuke-container .nuke-stage {
    left: 0;
    right: 0;
    width: auto;
    transform: none;
    top: calc(var(--m-topbar-h) + env(safe-area-inset-top, 0px));
    bottom: calc(var(--m-botnav-h) + env(safe-area-inset-bottom, 0px));
    /* The feed's inner header (pfp + name + balance + stake buttons) was sized
       for the 600px desktop column and has a ~485px min-content width that
       won't shrink. `min-width: 0` lets this full-width column honour the
       viewport instead of being forced wider, and `overflow: hidden` clips any
       residual bleed — together they stop the horizontal page scroll that
       would otherwise drag the fixed bottom nav off-screen. The inner header
       still needs to wrap/shrink at this width — that's the refine-later work. */
    min-width: 0;
    overflow: hidden;
  }
  /* Slim top app bar — logo only (the ticker is already hidden at the tablet
     tier). Grows under the notch via the safe-area inset. */
  .nuke-container .nuke-page-topbar {
    height: calc(var(--m-topbar-h) + env(safe-area-inset-top, 0px));
    padding-top: env(safe-area-inset-top, 0px);
  }
  /* Switch the bottom tab bar on. */
  .nuke-container .nuke-mobile-nav {
    display: flex;
  }
}

/* ── Small phones (≤480px): tighten the page gutters ───────────────────────── */
@media (max-width: 480px) {
  .nuke-container {
    --page-edge-x: 8px;
  }
}

/* ============ Edit Profile button + modal (own profile page) ============ */
/* X.com-style outlined pill, right-aligned in the identity toprow (which is
   align-items:flex-end), so it sits level with the bottom of the big pfp. */
.nuke-container .nuke-profile-edit-btn {
  align-self: flex-end;
  flex-shrink: 0;
  padding: 6px 16px;
  border: 1px solid #CFD9DE;
  border-radius: 9999px;
  background: transparent;
  color: #0F1419;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-profile-edit-btn:hover {
  background: rgba(15, 20, 25, 0.08);
}

/* Overlay scrim — covers the viewport, centers the modal. Above the headers
   (z 5) and the chat lightbox (z 20). */
.nuke-container .nuke-edit-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.4);
}

.nuke-container .nuke-edit-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  max-height: 85vh;
  background: #FFFFFF;
  border-radius: 16px;
  box-shadow: 0 0 16px rgba(0, 0, 0, 0.18), 0 0 4px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}

.nuke-container .nuke-edit-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: 14px 16px;
  border-bottom: 1px solid #EAEAEA;
}
.nuke-container .nuke-edit-title {
  font-size: 17px;
  font-weight: 800;
  color: #0F1419;
}
.nuke-container .nuke-edit-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: #0F1419;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-edit-close:hover { background: rgba(15, 20, 25, 0.08); }

.nuke-container .nuke-edit-body {
  overflow-y: auto;
  padding: 4px 16px 16px;
}

/* One option per section, divided by a hairline. */
.nuke-container .nuke-edit-section {
  padding: 14px 0;
  border-bottom: 1px solid #EAEAEA;
}
.nuke-container .nuke-edit-body .nuke-edit-section:last-child { border-bottom: none; }
/* Locked = not available to this user (no season pass / no item / nothing
   earned): dimmed + non-interactive, but still shown so it's discoverable. */
.nuke-container .nuke-edit-section[data-locked="true"] {
  opacity: 0.5;
}
.nuke-container .nuke-edit-section[data-locked="true"] .nuke-edit-section-body {
  pointer-events: none;
}

.nuke-container .nuke-edit-section-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.nuke-container .nuke-edit-section-title {
  font-size: 13px;
  font-weight: 700;
  color: #0F1419;
}
.nuke-container .nuke-edit-lock-hint {
  font-size: 11px;
  font-style: italic;
  color: #536471;
}
.nuke-container .nuke-edit-section-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.nuke-container .nuke-edit-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.nuke-container .nuke-edit-swatch {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 6px;
  border: 1px solid #CFD9DE;
}
/* Hue slider — full-spectrum track at the picker's fixed S/L (85% / 50%). */
.nuke-container .nuke-edit-hue {
  flex: 1;
  height: 12px;
  border-radius: 9999px;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  background: linear-gradient(to right,
    hsl(0, 85%, 50%), hsl(60, 85%, 50%), hsl(120, 85%, 50%), hsl(180, 85%, 50%),
    hsl(240, 85%, 50%), hsl(300, 85%, 50%), hsl(360, 85%, 50%));
}
.nuke-container .nuke-edit-hue::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid #0F1419;
  cursor: pointer;
}
.nuke-container .nuke-edit-hue::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid #0F1419;
  cursor: pointer;
}

.nuke-container .nuke-edit-select,
.nuke-container .nuke-edit-input,
.nuke-container .nuke-edit-textarea {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid #CFD9DE;
  border-radius: 8px;
  background: #FFFFFF;
  color: #0F1419;
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}
.nuke-container .nuke-edit-textarea {
  resize: none;
  min-height: 52px;
}
.nuke-container .nuke-edit-font-preview {
  font-size: 18px;
  font-weight: 700;
  color: #0F1419;
  padding: 2px 0;
  min-height: 24px;
}
.nuke-container .nuke-edit-count {
  font-size: 11px;
  color: #536471;
  text-align: right;
}

/* Earned-asset grids. Banners are wide (3:1) rows; borders are small squares. */
.nuke-container .nuke-edit-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.nuke-container .nuke-edit-grid[data-kind="banner"] { flex-direction: column; }
.nuke-container .nuke-edit-grid-empty {
  font-size: 12px;
  font-style: italic;
  color: #536471;
}
.nuke-container .nuke-edit-grid-item {
  padding: 0;
  border: 2px solid transparent;
  border-radius: 8px;
  background: #F2F4F5;
  cursor: pointer;
  overflow: hidden;
}
.nuke-container .nuke-edit-grid-item[data-selected="true"] { border-color: #1D9BF0; }
.nuke-container .nuke-edit-grid[data-kind="banner"] .nuke-edit-grid-item {
  width: 100%;
  aspect-ratio: 3 / 1;
}
.nuke-container .nuke-edit-grid[data-kind="border"] .nuke-edit-grid-item {
  width: 52px;
  height: 52px;
}
.nuke-container .nuke-edit-grid-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.nuke-container .nuke-edit-save {
  align-self: flex-start;
  padding: 7px 18px;
  border: none;
  border-radius: 9999px;
  background: #1D9BF0;
  color: #FFFFFF;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.nuke-container .nuke-edit-save:disabled {
  background: #8ECDF6;
  cursor: not-allowed;
}

.nuke-container .nuke-edit-status {
  font-size: 11px;
  min-height: 14px;
  color: #536471;
}
.nuke-container .nuke-edit-status[data-state="ok"] { color: #1A9E5E; }
.nuke-container .nuke-edit-status[data-state="err"] { color: #D32F2F; }
