/**
 * 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 */
.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; opacity: 0.5; }

/* Cred-compose modal — an X.com-style "compose" card, themed with the site tokens.
   Mounts on document.body, so it inherits the --nk-* tokens from body.page-nuke. */
.cred-compose-modal {
  box-sizing: border-box;
  width: min(440px, 92vw);
  display: flex;
  flex-direction: column;
  padding: 20px;
  background: var(--nk-surface, #FFFFFF);
  border: 1px solid var(--nk-border-soft, #EFF3F4);
  border-radius: 18px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.02), 0 16px 48px rgba(0, 0, 0, 0.22);
}
.cred-compose-modal .profile-modal-title {
  margin: 0 0 14px;
  font-size: 20px;
  font-weight: 800;
  color: var(--nk-text, #0F1419);
}
.cred-compose-modal .bio-modal-textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: 96px;
  resize: none;
  padding: 12px 14px;
  border: 1px solid var(--nk-border, #CFD9DE);
  border-radius: 14px;
  background: var(--nk-surface, #FFFFFF);
  color: var(--nk-text, #0F1419);
  font-family: inherit;
  font-size: 15px;
  line-height: 1.4;
  outline: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.cred-compose-modal .bio-modal-textarea::placeholder { color: var(--nk-text-2, #536471); }
.cred-compose-modal .bio-modal-textarea:focus {
  border-color: var(--nk-accent, #1D9BF0);
  box-shadow: 0 0 0 3px rgba(var(--nk-accent-rgb, 29, 155, 240), 0.16);
}
.cred-compose-modal .bio-char-count {
  margin-top: 6px;
  text-align: right;
  font-size: 12px;
  color: var(--nk-text-2, #536471);
}
.cred-compose-modal .action-btn.primary {
  border: none;
  border-radius: 9999px;
  background: var(--nk-accent, #1D9BF0);
  color: #FFFFFF;
  font-weight: 800;
}
.cred-compose-modal .action-btn.primary:hover:not(:disabled) { background: #1A8CD8; }

/* Cred-compose modal: Like / Dislike segmented toggle (mounts on document.body, so it
   inherits the --nk-* tokens from body.page-nuke). */
.cred-compose-toggle {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.cred-compose-choice {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 10px;
  border: 1px solid var(--nk-border, #CFD9DE);
  border-radius: 9999px;
  background: transparent;
  color: var(--nk-text-2, #536471);
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.cred-compose-choice i { font-size: 16px; }
.cred-compose-like.is-active { background: rgba(255, 69, 0, 0.12); border-color: #FF4500; color: #FF4500; }
.cred-compose-dislike.is-active { background: rgba(113, 147, 255, 0.18); border-color: #7193FF; color: #7193FF; }
.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 the 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-onboarding .content {
  background-color: var(--color-black);
}

/* Reused by the onboarding page headings (index.html). Kept after the
   sign-in page removal because onboarding still applies this class. */
.signin-title {
  font-size: var(--font-size-xxl);
  margin-bottom: var(--spacing);
  color: var(--color-white);
}

/* Reused by the NPC wallet-chooser modal (showWalletChooser in main.ts) for
   its :hover state — the buttons set base styles inline. */
.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; }

/* ===========================================
   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: var(--nk-surface);
  /* 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(var(--nk-surface-rgb), 0.40) 0%,
    rgba(var(--nk-surface-rgb), 0.12) 60%,
    rgba(var(--nk-surface-rgb), 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(var(--nk-surface-rgb), 0.36);
  --pfp-rim-side:   rgba(var(--nk-surface-rgb), 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(var(--nk-surface-rgb), 0.36) 0%,
    rgba(var(--nk-surface-rgb), 0.12) 48%,
    rgba(var(--nk-surface-rgb), 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(var(--nk-surface-rgb), 0.40) 0%,
    rgba(var(--nk-surface-rgb), 0.12) 60%,
    rgba(var(--nk-surface-rgb), 0.00) 100%);
  --cc-rim-right: rgba(var(--nk-surface-rgb), 0.36);
  --cc-rim-side:  rgba(var(--nk-surface-rgb), 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(var(--nk-surface-rgb), 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 — redesigned to the nuke light aesthetic (white card, hairline
   var(--nk-border) border, #0F1419/#536471 text, green buy pill, x.com-style dim overlay). */
.item-preview-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 20, 25, 0.4);
}

.item-preview-modal {
  position: relative;
  width: 300px;
  box-sizing: border-box;
  background: var(--nk-surface);
  border: 1px solid var(--nk-border);
  border-radius: 16px;
  padding: 28px 22px 20px 22px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow: 0 12px 40px rgba(15, 20, 25, 0.18);
}

.item-preview-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 30px;
  height: 30px;
  background: transparent;
  border: none;
  border-radius: 9999px;
  color: var(--nk-text-2);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}

.item-preview-close:hover {
  background: var(--nk-border-soft);
  color: var(--nk-text);
}

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

.item-preview-name {
  font-size: 19px;
  font-weight: 800;
  color: var(--nk-text);
  text-align: center;
  margin-bottom: 8px;
}

.item-preview-description {
  font-size: 14px;
  color: var(--nk-text-2);
  text-align: center;
  line-height: 1.45;
  margin-bottom: 20px;
  min-height: 38px;
}

.item-preview-buy-btn,
.item-preview-action-btn {
  height: 44px;
  padding: 0 16px;
  border: none;
  border-radius: 9999px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
  transition: filter 0.15s, background-color 0.15s;
  color: #FFFFFF;
}

.item-preview-buy-btn {
  width: 100%;
  background: #0E8A53;
}

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

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

.item-preview-action-btn.primary {
  background: #0E8A53;
}

.item-preview-action-btn.secondary {
  background: #536471;
}

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

.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 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); }
}
/* Applies the shake above — added by JS (`.shop-jiggle`) and removed on
   animationend. Used for the signed-out shop/season-pass tap feedback and the
   notification/store jiggles in main.ts / services/shop.ts. */
.shop-jiggle { animation: shop-jiggle 0.4s ease both; }

@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`). */
/* ╔══════════════════════════════════════════════════════════════════════════╗
   ║  NUKE THEME TOKENS — single source of truth for Light + Lights Out (dark)   ║
   ╠══════════════════════════════════════════════════════════════════════════╣
   ║  Every colour / elevation in the .nuke-container UI flows through these     ║
   ║  --nk-* tokens. LIGHT values live in this block; the dark values live in    ║
   ║  the mirrored `.theme-lightsout body.page-nuke` block (search              ║
   ║  "LIGHTS OUT TOKENS"). Both blocks list the SAME tokens in the SAME order   ║
   ║  so it is obvious what each theme flips.                                    ║
   ║                                                                            ║
   ║  ADDING A NEW COMPONENT  →  style it with these tokens, never a raw hex /   ║
   ║  rgba. It then themes in BOTH modes automatically from the two blocks.      ║
   ║  The only legitimate theme-specific CSS is a *structural* flip that can't   ║
   ║  be a single token (e.g. a shadow that must change DIRECTION, not just      ║
   ║  colour). Write those as `.theme-lightsout <selector>` next to the          ║
   ║  component, prefixed and commented with WHY — see the reply/image shadow    ║
   ║  + notification overrides for the pattern. Avoid them when a token works.   ║
   ╚══════════════════════════════════════════════════════════════════════════╝ */
body.page-nuke {
  /* ── Surfaces ─────────────────────────────────────────────────────────── */
  --nk-surface: #FFFFFF;            /* page / card background */
  --nk-surface-2: #F7F9FA;          /* recessed / secondary surface */
  --nk-surface-rgb: 255, 255, 255;  /* triplet for rgba() gradients that fade to the surface */

  /* ── Text ─────────────────────────────────────────────────────────────── */
  --nk-text: #0F1419;               /* primary text */
  --nk-text-2: #536471;             /* muted / secondary text */

  /* ── Borders ──────────────────────────────────────────────────────────── */
  --nk-border: #CFD9DE;             /* default card / divider border */
  --nk-border-soft: #EFF3F4;        /* hairline / soft divider */
  --nk-border-2: #E0E0E0;           /* misc control border */

  /* ── Accent (X-style link blue — same hue in both themes, readable on each) ─ */
  --nk-accent: #1D9BF0;             /* links, unread dots, toggles, selected states */
  --nk-accent-rgb: 29, 155, 240;    /* triplet for rgba() accent tints */

  /* ── Interaction / hover ──────────────────────────────────────────────────
     --nk-hover-tint sets the hover DIRECTION: black darkens (light) so a hover
     always reads as a shade; the dark block flips it to white so a hover always
     reads as a highlight. The lift tokens resolve tint + surface at use, so they
     auto-adapt per theme. They double as the var-fallback for profile-colour
     tints, so a missing colour can never make a hover invalid/transparent.
       hover-1 = message row · hover-2 = reply (indirect) · hover-3 = reply (direct) */
  --nk-hover: rgba(0, 0, 0, 0.04);  /* generic translucent hover (notif rows, etc.) */
  --nk-hover-quote: #F8F8F8;
  --nk-hover-tint: #000000;
  --nk-hover-1: color-mix(in srgb, var(--nk-hover-tint) 2.5%, var(--nk-surface));
  --nk-hover-2: color-mix(in srgb, var(--nk-hover-tint) 2.5%, var(--nk-surface));
  --nk-hover-3: color-mix(in srgb, var(--nk-hover-tint) 5%, var(--nk-surface));
  --nk-hl-soft: color-mix(in srgb, var(--nk-hover-tint) 5%, var(--nk-surface)); /* no-colour scroll-to pulse */

  /* ── Profile-colour tint strength (hover + scroll highlight) ───────────────
     A user's profile colour is mixed over the surface at these %. Reply > post
     mirrors hover-3 > hover-1; dark uses higher % (a tint needs more over black). */
  --nk-hl-color-pct: 8%;
  --nk-hl-color-pct-quote: 10%;

  /* ── Elevation ────────────────────────────────────────────────────────────
     Light uses dark-cast drop shadows declared inline on each component (reply
     boxes, images). Dark can't (a dark shadow is invisible on black) so it remaps
     them via --nk-shadow-lift in the dark block. */

  background: var(--nk-surface);
}
body.page-nuke::before {
  display: none;
}
/* Full-page boot splash (mounted in index.html). Solid white from first paint;
   initBootSplash adds .is-hiding to fade it out once the first view is fully ready. */
#nuke-boot-splash {
  transition: opacity 0.4s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Brand logo (the header's radioactive mark) — large + centered on the load screen.
   Inherits --nk-text from body.page-nuke, so it flips black/white with the theme. */
#nuke-boot-splash .nuke-boot-splash-logo {
  font-size: 128px;
  line-height: 1;
  color: var(--nk-text);
  /* Continuous spin while the app loads (reuses the btn-spin rotate keyframe). */
  animation: btn-spin 2.5s linear infinite;
}
#nuke-boot-splash.is-hiding {
  opacity: 0;
  pointer-events: 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 var(--nk-border-2) 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 var(--nk-border-2);
  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: var(--nk-text);
  /* Plain white page background. */
  background-color: var(--nk-surface);
  /* 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`).
     284px column → 236px content (284 − 24×2 inset). */
  --nav-col-w: 284px;
  /* The trio (nav | chat | right) is anchored around the chat's center, so when
     the side columns differ in width the GROUP sits off-center. Shift every
     chat-center anchor left by half that imbalance to center all three as a unit.
     Zeroed in the ≤1360px media query (right column hides; nav+chat recenter on
     their own there). */
  --trio-shift: calc((var(--right-col-w) - var(--nav-col-w)) / 2);
  /* 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(--trio-shift) - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w) + 24px),
    rgb(var(--nk-surface-rgb))     calc(50% - var(--trio-shift) - var(--chat-w) / 2 - var(--col-gap) - var(--nav-col-w) + 24px),
    rgb(var(--nk-surface-rgb))     calc(50% - var(--trio-shift) + var(--chat-w) / 2 + var(--col-gap) + var(--right-col-w) - var(--right-inset)),
    transparent calc(50% - var(--trio-shift) + 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 {
  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(--trio-shift) - 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(--trio-shift) - var(--chat-w) / 2);
  right: calc(50% + var(--trio-shift) - 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(--trio-shift) - 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(--trio-shift) - 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;
  box-sizing: border-box;
  /* Match the cred pill (.nuke-post-vote): #F7F9FA fill + 1px var(--nk-border) border. */
  background: var(--nk-surface-2);
  border: 1px solid var(--nk-border);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--nk-text-2);
  /* 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;
}
/* 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(--trio-shift) - 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(--trio-shift) - 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(--trio-shift) + 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;
  /* z-index revert is DELAYED 0.18s (past the opacity fade) so on close the scrim keeps
     covering the search/top bar until they've fully un-dimmed — see the hovercard rule. */
  transition: opacity 0.18s ease, z-index 0s linear 0.18s;
  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;
}
/* Hovercard open → lift the scrim ABOVE the search bar + top bar (still below the
   card, z-50) so they dim as part of the page in ONE synced overlay — no per-element
   brightness/overlay faking. The scrim is transparent at rest, so raising its z-index
   doesn't snap anything; only its opacity (animatable) fades in. */
.nuke-container:has(.nuke-hovercard.is-visible) .nuke-hover-spotlight {
  z-index: 45;
  /* On OPEN, flip z-index immediately (no delay) so the scrim covers the search/top bar
     from the start of the fade-in. On CLOSE the base rule's delayed revert keeps it
     covering through the fade-out, so everything un-dims together. */
  transition: opacity 0.18s ease, z-index 0s linear 0s;
}

/* 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, var(--nk-border-2));
  /* 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: var(--nk-text);
}
.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: calc(50% - var(--trio-shift));
  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. The search bar is fixed at top:52.5px, height 45px →
     its bottom edge is 97.5px; top = 97.5 + 16 = 113.5px puts the first container
     (shop) 16px below it, and the `gap` below spaces shop → missions by the same
     16px — so search, shop and missions are all evenly 16px apart. */
  position: fixed;
  top: 113.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: 16px;
  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: var(--nk-surface);
  border: 1px solid var(--nk-border-soft);
  border-radius: 16px;
  /* Soft + NOT animated — a big animated box-shadow is a per-frame paint hog. */
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
  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;
}
/* 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;
  cursor: pointer;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #e0566c;
  border: 1px solid rgba(0, 0, 0, 0.12);
  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: 248px;
  justify-content: flex-start;
  padding: 0 14px;
  gap: 11px;
  /* 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;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.nuke-container .nuke-radio-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 9px;
  font-weight: 800;
  line-height: 1.3;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: #8a94a6;
}
.nuke-container .nuke-radio-freq { font-size: 15px; font-weight: 800; line-height: 1.25; color: var(--nk-text); }
/* Now-playing song title under the frequency — single line, ellipsised to the FAB width.
   Hidden when empty (nothing playing). */
.nuke-container .nuke-radio-song {
  margin-top: 1px;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--nk-text-2);
}
.nuke-container .nuke-radio-song:empty { display: none; }
.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(--trio-shift) - 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
     284px (--nav-col-w) so the content area is 236px (284 − 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;
}
/* Canvas overlay used by banner-loader.ts to force-loop finite-loop animated banners
   (a dedicated class, NOT the banner-img class, so the :not([src]) hide rule skips it). */
.nuke-container .nuke-banner-loop-canvas {
  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 var(--nk-surface);
}
/* 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 var(--nk-border-soft);
  /* 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: var(--nk-text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-profile-card .nuke-profile-breakdown-label {
  color: var(--nk-text-2);
}
/* 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;
}
/* Relocated Sign In CTA — the x.com "Post" button slot. Sits directly below the nav
   items (order 0, above the order:1 profile card pinned at the bottom); shown only
   while signed out via the container's data-signedout flag (set in main.ts). */
.nuke-container .nuke-nav-signin {
  display: none;
  align-items: center;
  justify-content: center;
  order: 0;
  width: 100%;
  box-sizing: border-box;
  height: 48px;
  /* Sits directly below the nav items (the "sort list") — NOT pinned to the panel
     bottom. The nav's 12px row gap plus this 4px gives the spacing above it. */
  margin-top: 4px;
  border: none;
  border-radius: 9999px;
  background: var(--nk-accent);
  color: #FFFFFF;
  font-family: inherit;
  font-weight: 800;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-nav-signin:hover { background: #1A8CD8; }
.nuke-container[data-signedout="true"] .nuke-nav-signin { display: flex; }
/* Signed-out users have no profile — hide the identity header card in the chat panel
   and the profile container in the left nav entirely (the feed + Sign In CTA remain). */
.nuke-container[data-signedout="true"] .nuke-chat-header-2 { display: none; }
.nuke-container[data-signedout="true"] .nuke-nav-top-box { display: none; }
/* Signed-out page is intentionally a stripped-down teaser to push sign-ups: cut the
   nav menu (the Sign In CTA in the same rail stays), the shop, and the mission
   notebook. The feed + search + Sign In remain as the limited interface. */
.nuke-container[data-signedout="true"] .nuke-nav-list,
.nuke-container[data-signedout="true"] .nuke-shop-box,
.nuke-container[data-signedout="true"] .nuke-notebook {
  display: none;
}
/* 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: var(--nk-text);
  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: var(--nk-text);
  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(--trio-shift) + 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;
  /* No fibrous-tear displacement here — it distorted the corners into a torn edge;
     dropping it lets the rounded corners render cleanly, matching the shop container. */
  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: var(--nk-surface);
  border: 1px solid var(--nk-border-soft);
  /* 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 400px;
  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: var(--nk-text);
  background: rgba(var(--nk-surface-rgb), 0.88);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
/* The header's bottom edge: a solid-white band fading UP into transparency, so the
   frosted/blurred header doesn't butt straight into the solid-white scroll mask
   below it — instead it eases into the mask's matching solid-white top. */
.nuke-container .nuke-shop-header::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30px;
  /* Below the header's "Shop" text but above its background — the header sets a
     stacking context (z-index:5), so a negative z here sits above the bg fill yet
     under the inline title text, keeping the gradient off the letters. */
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(to top, rgb(var(--nk-surface-rgb)) 0%, rgb(var(--nk-surface-rgb)) 15%, rgba(var(--nk-surface-rgb), 0) 100%);
}
/* 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;
}
/* Top fade: a white→transparent gradient pinned just under the 44px frosted
   header so rows dissolve into it as they scroll up — no hard cut at the header
   edge. Sits above the grid content (z 4) but below the header (z 5). Hidden at
   the very top (nothing scrolled under yet); quickly fades in once scrolled
   (`.is-scrolled`, toggled in shop-panel.ts). */
.nuke-container .nuke-side-stack .nuke-shop-wrap::before {
  content: '';
  position: absolute;
  top: 44px;
  left: 0;
  right: 0;
  height: 32px;
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom, rgb(var(--nk-surface-rgb)) 0%, rgba(var(--nk-surface-rgb), 0) 100%);
  opacity: 0;
  transition: opacity 0.12s ease;
}
.nuke-container .nuke-side-stack .nuke-shop-wrap.is-scrolled::before {
  opacity: 1;
}
.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;
  /* Above the top fade mask (z 4) so the scrollbar is never veiled by it. The
     thumb starts at 48px — below the header — so sitting above it is harmless. */
  z-index: 5;
  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.
     Extra RIGHT padding (16px) keeps the right-aligned price tag clear of the
     overlay scrollbar thumb (right:2px + 8px wide → occupies the rightmost 10px). */
  padding: 52px 16px 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;
  /* The grid track is minmax(auto,1fr); without this the title's `nowrap`
     min-content blows the track past the panel and the price overflows. */
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  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 — a head row (title + price) on top,
   description below. Flex-fills the row so the price tag rides the right edge. */
.nuke-container .nuke-shop-item-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
/* Head row: title hugs the left and truncates; price tag pinned to the right. */
.nuke-container .nuke-shop-item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.nuke-container .nuke-shop-item-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 700;
  color: var(--nk-text);
  line-height: 1.25;
}
.nuke-container .nuke-shop-item-desc {
  font-size: 12px;
  color: var(--nk-text-2);
  line-height: 1.35;
}
/* Price tag — a money-green capsule hugging its "$X.XX", right-aligned in the head
   row and vertically centered with the title. */
.nuke-container .nuke-shop-price {
  flex-shrink: 0;
  padding: 1px 8px;
  border: 1px solid #B7E4CE;
  border-radius: 9999px;
  background: #E9F8F1;
  color: #0E8A53;
  font-family: var(--font-conduit);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.4;
}
/* Quick-buy pill — same shape/size as the price tag but FILLED green so it reads as the
   buy action. Click purchases immediately, skipping the preview modal. */
.nuke-container .nuke-shop-buy {
  flex-shrink: 0;
  padding: 1px 10px;
  border: 1px solid #0E8A53;
  border-radius: 9999px;
  background: #0E8A53;
  color: #FFFFFF;
  font-family: var(--font-conduit);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.4;
  cursor: pointer;
  transition: filter 0.15s ease;
}
.nuke-container .nuke-shop-buy:hover { filter: brightness(1.08); }
.nuke-container .nuke-shop-buy:active { filter: brightness(0.94); }
/* Transient action toast (e.g. shop buy feedback) — dark pill, bottom-center, fades in. */
.nuke-container .nuke-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%) translateY(8px);
  z-index: 1300;
  max-width: 360px;
  padding: 10px 18px;
  border-radius: 9999px;
  background: #0F1419;
  color: #FFFFFF;
  font-size: 14px;
  font-weight: 700;
  text-align: center;
  box-shadow: 0 8px 28px rgba(15, 20, 25, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.nuke-container .nuke-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.nuke-container .nuke-toast.is-error { background: #F4212E; }
.nuke-container .nuke-shop-grid .slot-charges {
  position: absolute;
  bottom: 2px;
  right: 3px;
  font-size: 11px;
  font-weight: 700;
  color: #555;
  background: rgba(var(--nk-surface-rgb), 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: var(--nk-surface);
  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: var(--nk-text);
  padding: 2px 8px;
  text-align: center;
}
.nuke-container .classic-button:hover {
  background: linear-gradient(to bottom, rgb(var(--nk-surface-rgb)) 0%, var(--nk-surface-2) 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(var(--nk-surface-rgb), 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: var(--nk-surface);
  border: 1px solid var(--nk-border-2);
  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: var(--nk-text);
}
.nuke-container .nuke-chat-text-remote {
  font-family: var(--font-conduit);
  color: var(--nk-text);
}

/* --- 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: var(--nk-accent);
}
/* @-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-[var(--nk-border-2)] 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: var(--nk-border-soft);
}
.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: var(--nk-text-2);
  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: var(--nk-border-2);
  /* Match the page's rounded-edge style — shared --pfp-radius. (Was 4px.) */
  border-radius: var(--pfp-radius);
  /* No resting shadow — images sit flat on just their border and only grow a drop
     shadow on hover (matches the reply boxes). */
}

/* --- 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(var(--nk-surface-rgb), 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 var(--nk-border-soft);
  /* 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 + out subtly — matched to the reply box's 0.22s background
     fade. The scroll-to highlight opts out below so it stays instant/animation-driven. */
  transition: background-color 0.22s 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: var(--nk-border);
  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); }
/* Received-posts list on a profile page (the cred votes targeting the user). Layout is
   intentionally minimal — the cred tint above carries the up/down read; the verb +
   voter name + message are the data. */
.nuke-container .nuke-profile-page-posts { padding: 0; }
.nuke-container .nuke-profile-page-posts-title {
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 8px;
}
.nuke-container .nuke-profile-post {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 8px 0;
  border-top: 1px solid var(--color-nuke-border, rgba(var(--nk-surface-rgb), 0.08));
}
.nuke-container .nuke-profile-post-pfp { width: 36px; height: 36px; flex: 0 0 36px; }
.nuke-container .nuke-profile-post-pfp .cc-pfp { width: 36px; height: 36px; }
.nuke-container .nuke-profile-post-body { min-width: 0; flex: 1; }
.nuke-container .nuke-profile-post-head { font-size: 0.85rem; }
.nuke-container .nuke-profile-post-voter { font-weight: 600; }
.nuke-container .nuke-profile-post-msg {
  font-size: 0.9rem;
  word-break: break-word;
  margin-top: 2px;
}
/* ── /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: var(--nk-border-soft);
}
.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: var(--nk-text); }
/* 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;
  /* Center on the head row (not baseline) so it lines up vertically with the "..."
     button beside it, which is also center-aligned. */
  align-self: center;
  padding-left: 8px;
  /* Black, bold to match the profile's balance metric (800). */
  font-weight: 800;
  color: var(--nk-text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* "..." more-options button at the right end of the post head (inert for now). The
   `--lead` variant carries the right-push when there's no balance before it. */
.nuke-container .nuke-post-more {
  margin-left: 6px;
  align-self: center;
  flex-shrink: 0;
  /* Negative block margins so the 22px button keeps its hit-area/hover circle WITHOUT
     inflating the head row past its ~19px text line (which pushed the message body down). */
  margin-top: -3px;
  margin-bottom: -3px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--nk-text-2);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-post-more--lead { margin-left: auto; }
.nuke-container .nuke-post-more:hover { background: rgba(0, 0, 0, 0.06); color: var(--nk-text); }
.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: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 {
  /* Opaque hover that MATCHES the scroll-to highlight: the poster's colour at 12% over the
     surface (set inline by renderPost as `--nuke-post-hl`, a ready color-mix expression),
     falling back to the plain grey lift when there's no profile colour (or it's white). */
  background-color: var(--nuke-post-hl, var(--nk-hover-1));
}
.nuke-container .nuke-post-handle,
.nuke-container .nuke-post-time,
.nuke-container .nuke-post-dot { color: var(--nk-text-2); 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 bar now — hug the pill content; ordered LAST so the pill
   sits at the bar's RIGHT edge, after the cred stat + action icons. */
.nuke-container .nuke-post-footer .nuke-post-reactions {
  margin-left: 0;
  margin-top: 0;
  order: 2;
  flex: 0 0 auto;
  gap: 6px;
}
/* The bar holds the cred pill, the 4 action icons (Use Item / Compose / Like / Dislike)
   and the reaction pill as direct children, spread by space-between so EVERY gap is
   equal — cred→icon, icon→icon, and icon→reaction-pill all match. Fills the footer width
   left of the share button (flex: 1). */
.nuke-container .nuke-post-bar {
  order: 1;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Mid-chain posts (no cred/icons) leave just the reaction pill in the bar — pin it right
   so it doesn't jump to the left edge. */
.nuke-container .nuke-post-bar > .nuke-post-reactions:only-child { margin-left: auto; }
/* 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: var(--nk-text-2);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-post-action-btn:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--nk-text);
}
/* 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(var(--nk-accent-rgb), 0.12);
  color: var(--nk-accent);
}
/* Share button — a circle wearing the pill chrome (same #F7F9FA fill + 1px
   var(--nk-border) 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: var(--nk-surface-2);
  border: 1px solid var(--nk-border);
}
.nuke-container .nuke-post-action-btn.nuke-post-action-link:hover {
  background: rgba(var(--nk-accent-rgb), 0.12);
  color: var(--nk-accent);
}
/* "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 — var(--nk-border)
   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). */
/* Use Item — icon-only button matching the like/dislike/compose buttons (no text,
   background, or border; the label is hidden). */
.nuke-container .nuke-post-use-item {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--nk-text-2);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.nuke-container .nuke-post-use-item:hover { background: rgba(0, 186, 124, 0.14); color: #00BA7C; }
.nuke-container .nuke-post-use-item i { font-size: 15px; }
.nuke-container .nuke-post-use-item-label { display: none; }
/* 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 var(--nk-border) border, fully
   rounded; count is the same #666 / 12px as `.nuke-reaction-count`. */
/* Cred STAT pill — just the score now (the up/down vote buttons moved out into the
   action row). Matches the Use Item pill height/chrome. */
.nuke-container .nuke-post-vote {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-sizing: border-box;
  height: 24px;
  padding: 0 10px;
  background: var(--nk-surface-2);
  border: 1px solid var(--nk-border);
  border-radius: 999px;
}
/* Like / dislike group — sits at the left of the footer, just before the reaction pill. */
.nuke-container .nuke-post-likedislike {
  display: flex;
  align-items: center;
  /* Room between 👍/👎, plus a gap after the group so it isn't cramped against the
     reaction pill (or the share button, when there are no reactions). */
  gap: 16px;
  margin-right: 16px;
  flex-shrink: 0;
}
/* Like / dislike (cred up/down) + Compose — icon buttons. */
.nuke-container .nuke-post-vote-btn,
.nuke-container .nuke-post-compose {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--nk-text-2);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
/* Upvote → reddit orange, downvote → periwinkle, compose → brand blue, on hover. */
.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-compose:hover { background: rgba(29, 155, 240, 0.14); color: #1D9BF0; }
.nuke-container .nuke-post-vote-count {
  min-width: 1.4em;
  text-align: center;
  color: var(--nk-text-2);
  font-family: var(--font-conduit);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Cred-vote cooldown: while the per-target 24h window is active, a countdown shows in
   place of the like/dislike buttons (no buttons → re-voting is gated). Standalone in the
   action row now (the up/down moved out of the cred pill). */
.nuke-container .nuke-post-vote-cooldown {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  padding: 0 4px;
  color: var(--nk-text-2);
  font-family: var(--font-conduit);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-post-vote-cooldown i {
  /* Match the footer's other action icons (15px) — the clock looked undersized. */
  font-size: 15px;
  line-height: 1;
}
/* `.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: var(--nk-text-2);
  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: var(--nk-surface-2);
  border: 1px solid var(--nk-border);
  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 var(--nk-border);
  /* 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 var(--nk-border);
}
.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: var(--nk-border-2); }
.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: var(--nk-surface);
  border: 1px solid var(--nk-border);
  /* No resting shadow — the reply box rests flat on just its border; it grows a drop
     shadow only on hover for the lift effect (matches the chat images). */
  /* 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: var(--nk-border);
  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: var(--nk-hover-2); }
/* 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 {
  /* Matches the scroll-to highlight too: the quoted user's colour at 12% over the surface
     (set inline by renderQuoteBox as `--nuke-quote-hl`), opaque so it never bleeds the
     poster's colour, falling back to the plain grey lift when they have no colour. */
  background-color: var(--nuke-quote-hl, var(--nk-hover-3));
}
/* 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: none;
}
/* 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);
}

/* Dark mode: a dark drop shadow is invisible on a black page, so the reply boxes and
   images would lose their hover "lift". Remap the HOVER/active lift shadows to a grey
   darker than the reply border (--nk-shadow-lift) — cohesive with the borders, not a
   white glow. Resting reply boxes + images carry NO shadow (light or dark). */
.theme-lightsout .nuke-container .nuke-post-quote:hover {
  box-shadow: 0 3px 10px var(--nk-shadow-lift);
}
.theme-lightsout .nuke-container .nuke-post-quote[data-has-account="false"]:hover {
  box-shadow: none;
}
.theme-lightsout .nuke-container .nuke-post-quote:active {
  box-shadow: 0 1px 2px var(--nk-shadow-lift);
}
.theme-lightsout .nuke-container .nuke-post-media-1 .nuke-post-img-wrap:hover:not(.is-sticker) {
  box-shadow: 0 4px 14px var(--nk-shadow-lift);
}
.theme-lightsout .nuke-container .nuke-post-media-1 .nuke-post-img-wrap:active:not(.is-sticker) {
  box-shadow: 0 1px 4px var(--nk-shadow-lift);
}
.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: var(--nk-border-soft);
}
.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: var(--nk-text); }
/* 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: var(--nk-text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.nuke-container .nuke-post-quote-handle { color: var(--nk-text-2); }
.nuke-container .nuke-post-quote-dot,
.nuke-container .nuke-post-quote-time { color: var(--nk-text-2); font-weight: 400; }
.nuke-container .nuke-post-quote-text { color: var(--nk-text); 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 ONLY when the image leads (image-only
   quote): the quote-head gives 3px, cramped for an image, so ~8px total (3px head +
   5px) matches the regular post's name→image gap. When a caption sits above the image
   the body `gap` already spaces them, so no extra top margin is added. */
.nuke-container .nuke-post-quote-body > .nuke-post-quote-img:first-child {
  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 var(--nk-border);
}
/* 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(var(--nk-accent-rgb), 0.14); }
  60%  { background-color: rgba(var(--nk-accent-rgb), 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, var(--nk-hl-soft));
  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, var(--nk-hl-soft)); }
  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(var(--nk-surface-rgb), 0.88);
  border-bottom: 1px solid var(--nk-border-soft);
  -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: var(--nk-text);
  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: var(--nk-text-2);
  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: var(--nk-text);
  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: var(--nk-accent);
}
/* 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: var(--nk-surface);
  border-bottom: 1px solid var(--nk-border-soft);
  /* 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: var(--nk-surface);
  /* 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 var(--nk-border-soft) 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 var(--nk-border-soft);
  border-left: 1px solid var(--nk-border-soft);
  border-right: 1px solid var(--nk-border-soft);
  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;
}
/* One robust clip for the whole profile overlay — kills the last ~1px backdrop-filter leak at
   the rounded top corners. clip-path clips the entire subtree (incl. the frosted bar's
   backdrop) to the rounded shape, where overflow:hidden + border-radius leave a sub-pixel sliver. */
.nuke-container .nuke-post-modal-overlay[data-subview="profile"] {
  clip-path: inset(0 round var(--pfp-radius) var(--pfp-radius) 0 0);
}
/* Profile subview: the back-bar FLOATS over the scrolling profile (banner + content scroll
   UNDER it) with the same frosted blur as the feed's nav header (.nuke-chat-header) — 88%
   surface + blur(8px). Scoped to profile so post/notif keep their in-flow solid bar. */
.nuke-container .nuke-post-modal-overlay[data-subview="profile"] .nuke-post-modal-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 5;
  background: rgba(var(--nk-surface-rgb), 0.88);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  /* A backdrop-filter element isn't clipped by the overlay's overflow:hidden + radius (browser
     quirk), so round the bar's OWN top corners to match the container — otherwise its square
     corners + blurred content poke past the rounded top border. -1px nests inside the hairline. */
  border-radius: calc(var(--pfp-radius) - 1px) calc(var(--pfp-radius) - 1px) 0 0;
}
/* The bar floats (above), so push the profile body down by the bar height — the banner sits
   BELOW the bar at rest (fully visible, square top, not clipped by the overlay's rounded
   corners) and only scrolls UNDER the frosted bar as you scroll. */
.nuke-container .nuke-post-modal-overlay[data-subview="profile"] .nuke-post-modal-body {
  padding-top: 52px;
  /* Clip the SCROLLING content to over-rounded top corners. clip-path (not just
     border-radius + overflow) reliably clips a scroll container's content past the
     backdrop-filter bar; the generous radius over-crops so nothing reaches the top border. */
  border-radius: calc(var(--pfp-radius) + 10px) calc(var(--pfp-radius) + 10px) 0 0;
  clip-path: inset(0 round calc(var(--pfp-radius) + 10px) calc(var(--pfp-radius) + 10px) 0 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: var(--nk-text);
  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: var(--nk-text);
  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 var(--nk-border-soft);
}

/* ── 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 var(--nk-border-soft);
}
/* 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 var(--nk-border-soft) hairline as a feed post. */
.nuke-container .nuke-post-modal-body .nuke-post:not(.nuke-post-thread-below) { border-bottom: 1px solid var(--nk-border-soft); }
/* 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: var(--nk-text-2);
  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 var(--nk-border-soft);
  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: var(--nk-accent);
}
.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: var(--nk-accent);
}

/* 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: var(--nk-text);
}
.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: var(--nk-text-2);
  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: var(--nk-text-2);
  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: var(--nk-accent);
}

/* 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 var(--nk-accent);
  background: transparent;
  color: var(--nk-text-2);
  font-style: normal;
  white-space: normal;
}
/* Notification list — Lights Out. The grey hover + blue unread/glyph tints above are
   light-palette literals; flip them to dark equivalents (icon/dot blue #1D9BF0 already
   reads fine on black, so only the fills change). */
.theme-lightsout .nuke-container .nuke-notif-list .notif-item:hover { background: var(--nk-hover); }
.theme-lightsout .nuke-container .nuke-notif-list .notif-item.unread { background: rgba(var(--nk-accent-rgb), 0.08); }
.theme-lightsout .nuke-container .nuke-notif-list .notif-item.unread:hover { background: rgba(var(--nk-accent-rgb), 0.13); }
.theme-lightsout .nuke-container .nuke-notif-list .notif-item-glyph { background: rgba(var(--nk-accent-rgb), 0.16); }
/* 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 14px;
}
/* Profile sort/tab bar — reuses the chat feed-tabs styling (.nuke-feed-tab /
   .nuke-feed-tab-label) for font, hover, and the active underline. The label uses
   explicit vertical padding for the text→underline gap (instead of the chat's
   height:100% chain, which doesn't resolve the same outside the fixed-height chat
   header — leaving the profile underline too close to the text). */
.nuke-container .nuke-profile-tabs {
  display: flex;
  border-bottom: 1px solid var(--nk-border-soft);
  /* In-content tab bar (solid). It scrolls UP under the frosted header and is replaced there
     by the DOCKED nav, which is a child of that one frosted bar — so there's only ever one
     backdrop-filter (no two-blur seam). The dock swap is driven by data-tabs-docked. */
  background: var(--nk-surface);
}
/* Docked tab nav — same tabs, rendered INSIDE the frosted header bar. Hidden until the scroll
   handler flags the overlay data-tabs-docked. Transparent (the bar's single frost is the bg),
   and no backdrop-filter of its own (that's the whole point: one blur, not two). */
.nuke-container .nuke-profile-docked-nav { display: none; }
.nuke-container .nuke-post-modal-overlay[data-subview="profile"][data-tabs-docked="true"] .nuke-profile-docked-nav {
  display: flex;
}
.nuke-container .nuke-profile-docked-nav.nuke-profile-tabs {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* When docked, hide the in-content nav (keep its layout space so nothing jumps) — the docked
   copy in the header takes over. */
.nuke-container .nuke-post-modal-overlay[data-subview="profile"][data-tabs-docked="true"] .nuke-post-modal-body .nuke-profile-tabs {
  visibility: hidden;
}
.nuke-container .nuke-profile-tabs .nuke-feed-tab-label {
  height: auto;
  padding: 16px 0;
}
.nuke-container .nuke-profile-tab-pane[hidden] { display: none; }
/* 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: -74px;
  margin-bottom: 12px;
}
/* Top-right column under the banner: Edit button stacked over the headline balance. The
   86px top pull replaces the Edit button's old offset (toprow is lifted 74px into the
   banner; 86 drops this row just below the banner edge, like X's edit row). */
.nuke-container .nuke-profile-page-toprow-actions {
  align-self: flex-start;
  margin-top: 86px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}
/* Headline balance — same size/weight as the chat profile card balance (.nuke-chat-balance). */
.nuke-container .nuke-profile-page-balance {
  font-size: 32px;
  font-weight: 800;
  color: var(--nk-text);
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex-shrink: 0;
}
/* Hide the headline balance entirely when it's empty (zero) — keeps the username row clean. */
.nuke-container .nuke-profile-page-balance:empty { display: none; }
.nuke-container .nuke-profile-page-pfp {
  width: 133px;
  height: 133px;
  margin: 0;
  /* Corner + ring scaled from the hovercard avatar (5px corner, 4px ring at 64px) to
     this 133px size (×~2.08) so the profile pfp reads with the SAME proportions: a ~10px
     corner (not the boxy 5px cap, not an over-round 13%) and an ~8px ring below. */
  border-radius: 10px;
  /* 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 8px var(--nk-surface);
}
.nuke-container .nuke-profile-page-pfp .cc-pfp {
  width: 100%;
  height: 100%;
  border-radius: inherit;
}
.nuke-container .nuke-profile-page-name {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 20px;
  font-weight: 800;
  color: var(--nk-text);
  line-height: 1.2;
}
/* Headline row: name + handle column on the left, balance on the right, TOP-aligned so the
   username's top lines up with the balance's top. Bio/cred are full-width siblings below it
   (the bio spans the whole panel, not just the name column). */
.nuke-container .nuke-profile-page-headline {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.nuke-container .nuke-profile-page-namecol {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Header cred pill is injected via credPillHtml into this host; display:contents keeps the
   pill itself as the direct flex child of .nuke-chat-header-rate (layout unchanged). */
.nuke-container .nuke-chat-header-cred { display: contents; }
.nuke-container .nuke-profile-page-name-badge {
  width: 22px;
  height: 22px;
  object-fit: contain;
  flex-shrink: 0;
}
.nuke-container .nuke-profile-page-handle {
  margin-top: 1px;
  font-size: 15px;
  color: var(--nk-text-2);
  font-style: italic;
  line-height: 1.3;
}
.nuke-container .nuke-profile-page-bio {
  margin-top: 12px;
  font-size: 15px;
  color: var(--nk-text);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
/* Cred stat in the top identity section (X-style "value label"). */
.nuke-container .nuke-profile-page-stats {
  margin-top: 12px;
  font-size: 15px;
}
.nuke-container .nuke-profile-page-stat-value {
  font-weight: 700;
  color: var(--nk-text);
}
.nuke-container .nuke-profile-page-stat-label {
  color: var(--nk-text-2);
}
/* Tab strip. */
/* Info block — large inline metrics laid out like X.com's Following/Followers
   row (bold value + gray label), wrapping if needed. */
/* Phantom-wallet-style total-balance headline, centered between the bio and the
   holdings list. */
.nuke-container .nuke-profile-balance {
  text-align: center;
  padding: 18px 16px 10px;
}
.nuke-container .nuke-profile-balance-amount {
  font-size: 52px;
  font-weight: 800;
  color: var(--nk-text);
  line-height: 1.04;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.nuke-container .nuke-profile-balance-label {
  font-size: 14px;
  color: var(--nk-text-2);
  margin-top: 4px;
}
/* Stake / Unstake action row (self profile) — between the balance and the stat grid. */
.nuke-container .nuke-profile-page-actions {
  display: flex;
  gap: 10px;
  margin: 4px 16px 16px;
}
.nuke-container .nuke-profile-page-info {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 8px 16px 16px;
}
/* Staking stats as a 2x2 grid — each stat in its own bordered card: icon + label head
   on top, the value large beneath. */
.nuke-container .nuke-profile-stat-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--nk-border);
  border-radius: 14px;
}
.nuke-container .nuke-profile-stat-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.nuke-container .nuke-profile-stat-icon {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--nk-surface-2);
  color: var(--nk-text-2);
  font-size: 16px;
}
.nuke-container .nuke-profile-stat-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--nk-text-2);
}
.nuke-container .nuke-profile-stat-value {
  font-size: 22px;
  font-weight: 800;
  color: var(--nk-text);
  font-variant-numeric: tabular-nums;
}
.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: var(--nk-text);
  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: var(--nk-text-2);
}
/* 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 var(--nk-border-soft);
}
/* 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: var(--nk-text); }
.nuke-container .nuke-profile-empty-sub { font-size: 14px; color: var(--nk-text-2); 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 var(--nk-border);
  border-radius: 9999px;
  background: var(--nk-surface);
  font-size: 14px;
  font-weight: 700;
  color: var(--nk-text);
  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 var(--nk-border-soft);
  background: none;
  font-size: 15px;
  cursor: default;
}
.nuke-container .nuke-profile-notifs .notif-item.unread {
  background: rgba(var(--nk-accent-rgb), 0.06);
  box-shadow: inset 3px 0 0 var(--nk-accent);
}
/* 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: var(--nk-border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--nk-accent);
}
.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: var(--nk-text);
  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: var(--nk-text);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.nuke-container .nuke-profile-notifs .notif-item-message.notif-mention-quote {
  color: var(--nk-text-2);
  font-style: italic;
}
.nuke-container .nuke-profile-notifs .notif-item-time {
  flex-shrink: 0;
  font-size: 13px;
  color: var(--nk-text-2);
  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). */
/* The chat-feed identity card (pfp + balance) drops the username row (name + level
   badge) and the @handle — they still appear in the feed posts and on the profile page. */
.nuke-container .nuke-chat-header-2 .nuke-chat-header-namerow,
.nuke-container .nuke-chat-header-2 .nuke-chat-header-handle {
  display: none;
}
.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: var(--nk-text);
  white-space: nowrap;
}
.nuke-container .nuke-chat-header-handle {
  align-self: flex-start;
  color: var(--nk-text-2);
  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). */
/* Balance ROW — anchored top-right, occupying the same 40px band as the name block
   (so the balance lines up with the username/handle middle). Holds the fallout
   contribute INPUT (left) + the giant balance number (right), vertically centered. */
.nuke-container .nuke-chat-balance-row {
  position: absolute;
  right: 16px;
  /* Left edge at the message-field start (16px pad + 40px pfp + 12px gap) so the
     contribute input can stretch all the way to the pfp; the balance hugs the right. */
  left: 68px;
  /* Overlay the pfp's exact vertical band (12px header padding + 40px pfp) so the input
     and balance are vertically centered with the pfp. */
  top: 12px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}
.nuke-container .nuke-chat-balance {
  display: flex;
  align-items: center;
  font-size: 32px;
  font-weight: 800;
  color: var(--nk-text);
  line-height: 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;
}
/* In the header card's action row, size the Use Item / Compose to the Buy button's
   height (34px) so they line up with the Buy / Stake / Unstake buttons. The cred pill
   is intentionally left at its default size so it matches the cred pills in the message
   footers. (Scoped to the header — the post footers keep their compact 24px icons.) */
.nuke-container .nuke-chat-header-rate .nuke-post-use-item,
.nuke-container .nuke-chat-header-rate .nuke-post-compose {
  width: 34px;
  height: 34px;
}
.nuke-container .nuke-chat-header-rate .nuke-post-use-item i,
.nuke-container .nuke-chat-header-rate .nuke-post-compose i {
  font-size: 19px;
}
/* "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-signin-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;
}
/* Signed-out swap: the Sign In button replaces Stake/Unstake. Hidden by default;
   the row gets data-signedout="true" (set in renderNukeHeaderBalance) while the
   user has no connected wallet, which hides Stake/Unstake and shows Sign In
   instead. data-signedout takes precedence over data-fallout. */
.nuke-container .nuke-chat-signin-btn {
  display: none;
  background: var(--nk-accent);
}
/* Nuclear fallout: hide the Stake/Unstake actions; the contribute field
   (shown below) takes their place. data-fallout is set in renderNukeHeaderBalance. */
.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;
}
/* Contribute amount field — shown by default in nuclear fallout (in place of
   Stake/Unstake). Pinned to the action row's bottom + right edge (16px); its LEFT
   is set inline by renderNukeHeaderBalance to start just past the Use Item pill. */
/* Contribute row — holds the amount field (fills) + a Buy button (right). Pinned
   to the action row's bottom; nudged in from 16px to sit slightly further right.
   Left is sized in renderNukeHeaderBalance to start past the Use Item pill. */
.nuke-container .nuke-chat-contribute-row {
  display: none;
  position: absolute;
  bottom: 12px;
  right: 12px;
  /* Match the left profile container's Unstake button height (34px). */
  height: 34px;
  align-items: center;
  gap: 8px;
  z-index: 2;
}
.nuke-container .nuke-chat-contribute-input {
  /* Hidden by default; shown to the LEFT of the balance number during nuclear fallout
     (see the :has rules below). Fills the row — stretching from the pfp to the balance. */
  display: none;
  flex: 1 1 auto;
  min-width: 0;
  height: 34px;
  box-sizing: border-box;
  /* No left padding — text + placeholder sit flush against the squared-off left edge. */
  padding: 0 16px 0 0;
  /* Transparent border + background — the input blends into the header band. */
  border: 1px solid transparent;
  border-radius: 0 22px 22px 0;
  background: transparent;
  color: var(--nk-text);
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  outline: none;
}
/* Fallout → reveal the contribute input beside the balance. The data-fallout flag lives
   on the action row (a LATER sibling of the balance row), so target it via :has on the
   header. The signed-out rule comes after so it wins (an NPC sees no input). */
.nuke-container .nuke-chat-header-2:has(.nuke-chat-action-btns[data-fallout="true"]) .nuke-chat-contribute-input {
  display: block;
}
.nuke-container .nuke-chat-header-2:has(.nuke-chat-action-btns[data-signedout="true"]) .nuke-chat-contribute-input {
  display: none;
}
/* Buy button at the right of the row — dark pill, same height as the field. */
.nuke-container .nuke-chat-contribute-buy {
  flex: 0 0 auto;
  height: 34px;
  padding: 0 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  /* Gray until a valid amount is entered; turns green via .is-valid (set in
     initNukeContribute). */
  background: #8899A6;
  color: #FFFFFF;
  font-weight: 800;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-chat-contribute-buy.is-valid {
  background: #00BA7C;
}
.nuke-container .nuke-chat-contribute-input::placeholder {
  color: #8899A6;
  font-weight: 600;
}
/* Shown in fallout — the action row carries data-fallout and the contribute row
   is its following sibling. */
.nuke-container .nuke-chat-action-btns[data-fallout="true"] ~ .nuke-chat-contribute-row {
  display: flex;
}
/* Signed-out swap (placed AFTER the fallout swap so it wins at equal specificity):
   the Sign In button replaces Stake/Unstake/Contribute whenever the row has
   data-signedout="true" (set in renderNukeHeaderBalance) — i.e. no connected
   wallet. Takes precedence over data-fallout, so an NPC during nuclear fallout
   still sees Sign In rather than Contribute. */
.nuke-container .nuke-chat-action-btns[data-signedout="true"] .nuke-chat-stake-btn,
.nuke-container .nuke-chat-action-btns[data-signedout="true"] .nuke-chat-unstake-btn {
  display: none;
}
/* Signed-out also hides the fallout contribute row (its preceding sibling carries
   data-signedout), so a logged-out user during fallout sees only Sign In. */
.nuke-container .nuke-chat-action-btns[data-signedout="true"] ~ .nuke-chat-contribute-row {
  display: none;
}
.nuke-container .nuke-chat-action-btns[data-signedout="true"] .nuke-chat-signin-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(var(--nk-surface-rgb), 0.1);
  color: #FFFFFF;
}
.nuke-container .nuke-chat-lightbox-close:hover {
  background: rgba(var(--nk-surface-rgb), 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(var(--nk-surface-rgb), 0.14);
  color: #FFFFFF;
  cursor: pointer;
  z-index: 110;
  display: none;
}
.nuke-container .nuke-chat-lightbox-nav:hover { background: rgba(var(--nk-surface-rgb), 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 (var(--nk-border-2)) — single
     accent color used across all light UI edges in the nuke
     sandbox. */
  box-shadow: 0 0 0 1px var(--nk-border-2);
}

/* --- 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(var(--nk-surface-rgb), 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: var(--nk-border-soft)) so the chat's vertical edges read as the same
     hairline as the horizontal post separators. */
  border-left: 1px solid var(--nk-border-soft);
  border-right: 1px solid var(--nk-border-soft);
  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 var(--nk-border-soft) 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 var(--nk-border-soft);
  border-left: 1px solid var(--nk-border-soft);
  border-right: 1px solid var(--nk-border-soft);
  /* 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: var(--nk-surface);
  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;
}

/* No visible scrollbars anywhere in the app — scrolling still works (wheel / trackpad),
   matching the chat's existing bar-less look. */
.nuke-container,
.nuke-container * { scrollbar-width: none; }
.nuke-container::-webkit-scrollbar,
.nuke-container *::-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: var(--nk-accent);
  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%;
  /* `auto` (not the base bar's fixed 45px) so the bar GROWS to fit the results
     when they un-hide — otherwise overflow:hidden below clips them and the
     dropdown never appears. Collapsed height comes from the 44px input row. */
  height: auto;
  background: var(--nk-surface);
  border: 1px solid var(--nk-border-2);
  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(--trio-shift) - 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. */
/* No animation — the results just show/hide instantly on hover/focus. */
.nuke-container .nuke-search-results {
  display: none;
  min-height: 0;
}
.nuke-container .nuke-search-clip {
  min-height: 0;
}
.nuke-container .nuke-subheader-search:is(:focus-within, :has(.user-search-bar:hover)) .nuke-search-results {
  display: block;
}
/* No profile-card banners in the search dropdown (removed per request; also drops
   their per-row mask/grayscale paint). The main player list keeps its banners. */
.nuke-container .nuke-search-results .nuke-row-banner {
  display: none;
}
.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, rgb(var(--nk-surface-rgb)) 0, rgba(var(--nk-surface-rgb), 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 (var(--nk-border-2)) — single
     accent color used across all light UI edges. */
  --pfp-outer-border-color: var(--nk-border-soft);
}
.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: var(--nk-text);
}
/* 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: var(--nk-text-2);
  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) {
  /* Right column hides here and the nav+chat pair recenters on its own (below),
     so the trio-centering shift must NOT apply — collapse it to zero. */
  .nuke-container { --trio-shift: 0px; }
  .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;
  /* Fixed popup width, intentionally INDEPENDENT of the nav column (was
     `calc(var(--nav-col-w) - 24px)`, which made the popup shrink whenever the nav
     was narrowed). 324px = the original nav content width (348 − 24), kept as the
     popup's own size. The static in-rail `.nuke-profile-card` override still tracks
     the nav at 100%. */
  width: 324px;
  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: var(--nk-surface);
  background-image:
    linear-gradient(to bottom, rgba(var(--nk-surface-rgb), 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);
  /* 1px card border (same --nk-border token as the reply boxes + side cards) so the
     popup reads as a framed card. The left static profile card overrides this to none. */
  border: 1px solid var(--nk-border);
  /* 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: var(--nk-text);
  font-size: 17px;
  line-height: 1.25;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nuke-container .nuke-hovercard-handle {
  color: var(--nk-text-2);
  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: var(--nk-text);
  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: var(--nk-text);
  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: var(--nk-surface);
  border-top: 1px solid var(--nk-border-soft);
}
.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: var(--nk-text-2);          /* 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: var(--nk-text);          /* 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;
  }

  /* ── Mobile drawers: Search + Shop surfaced as full-screen panes ───────────
     The desktop shop column (.nuke-side-right) and the search subheader
     (.nuke-subheader) are display:none below 1360px. The bottom-nav Search/Shop
     tabs set a body `m-view-*` class (JS setMobileView) that reveals the matching
     panel as a fixed pane filling the gap between the slim top app bar and the
     bottom nav, layered over the feed. NPCs reach the shop + search here too. */
  body.m-view-shop .nuke-container .nuke-side-right,
  body.m-view-search .nuke-container .nuke-subheader {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    top: calc(var(--m-topbar-h) + env(safe-area-inset-top, 0px));
    bottom: calc(var(--m-botnav-h) + env(safe-area-inset-bottom, 0px));
    width: auto;
    margin: 0;
    padding: 8px;
    box-sizing: border-box;
    z-index: 35;
    background: var(--nk-surface);
    overflow-y: auto;
  }
  /* Shop pane shows just the shop list — drop the column's mission notebook. */
  body.m-view-shop .nuke-container .nuke-side-right .nuke-notebook {
    display: none;
  }
  /* Search pane: keep the results list open regardless of input focus. */
  body.m-view-search .nuke-container .nuke-search-results {
    display: block;
  }
}

/* ── 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 {
  flex-shrink: 0;
  padding: 8px 16px;
  border: 1px solid var(--nk-border);
  border-radius: 9999px;
  background: transparent;
  color: var(--nk-text);
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nuke-container .nuke-profile-edit-btn:hover {
  background: color-mix(in srgb, var(--nk-text) 8%, transparent);
}

/* 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: var(--nk-surface);
  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 var(--nk-border-soft);
}
.nuke-container .nuke-edit-title {
  font-size: 17px;
  font-weight: 800;
  color: var(--nk-text);
}
.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: var(--nk-text);
  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 var(--nk-border-soft);
}
.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: var(--nk-text);
}
.nuke-container .nuke-edit-lock-hint {
  font-size: 11px;
  font-style: italic;
  color: var(--nk-text-2);
}
.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 var(--nk-border);
}
/* 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: var(--nk-surface);
  border: 2px solid #0F1419;
  cursor: pointer;
}
.nuke-container .nuke-edit-hue::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--nk-surface);
  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 var(--nk-border);
  border-radius: 8px;
  background: var(--nk-surface);
  color: var(--nk-text);
  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: var(--nk-text);
  padding: 2px 0;
  min-height: 24px;
}
.nuke-container .nuke-edit-count {
  font-size: 11px;
  color: var(--nk-text-2);
  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: var(--nk-text-2);
}
.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: var(--nk-accent); }
.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: var(--nk-accent);
  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: var(--nk-text-2);
}
.nuke-container .nuke-edit-status[data-state="ok"] { color: #1A9E5E; }
.nuke-container .nuke-edit-status[data-state="err"] { color: #D32F2F; }

/* Footer — pinned below the scrolling body; holds the single Save button (right)
   and an overall status line (left). flex-shrink:0 keeps it visible while the
   sections scroll. */
.nuke-container .nuke-edit-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  padding: 12px 16px;
  border-top: 1px solid var(--nk-border-soft);
}
.nuke-container .nuke-edit-footer-status { min-height: 0; }
/* The footer Save button overrides the per-section default (which left-aligned
   itself in a column) — here it sits at the row's right edge. */
.nuke-container .nuke-edit-save-all {
  align-self: auto;
  padding: 9px 22px;
  font-size: 14px;
}


/* ═════════════════════ Lights Out theme ═════════════════════
   Toggled by `.theme-lightsout` on <html> (no-flash script in index.html + the
   Settings toggle). Flips the theme vars to pure-black; the vars cascade to every
   rule that was migrated to them. A handful of dark elements are flipped explicitly. */
/* ╔══════════════════════════════════════════════════════════════════════════╗
   ║  LIGHTS OUT TOKENS — dark overrides (mirror of the light block above)       ║
   ║  Same token order as `body.page-nuke`; only the VALUES differ.             ║
   ╚══════════════════════════════════════════════════════════════════════════╝ */
.theme-lightsout body.page-nuke {
  /* ── Surfaces ─────────────────────────────────────────────────────────── */
  --nk-surface: #000000;
  --nk-surface-2: #16181C;
  --nk-surface-rgb: 0, 0, 0;

  /* ── Text ─────────────────────────────────────────────────────────────── */
  --nk-text: #E7E9EA;
  --nk-text-2: #71767B;

  /* ── Borders ──────────────────────────────────────────────────────────── */
  --nk-border: #2F3336;
  --nk-border-soft: #2F3336;
  --nk-border-2: #2F3336;

  /* ── Accent (same blue; reads fine on black) ──────────────────────────── */
  --nk-accent: #1D9BF0;
  --nk-accent-rgb: 29, 155, 240;

  /* ── Interaction / hover (white tint; dialed lower than light — white-on-black
     reads brighter than black-on-white at the same %) ─────────────────────── */
  --nk-hover: rgba(255, 255, 255, 0.035);
  --nk-hover-quote: rgba(255, 255, 255, 0.035);
  --nk-hover-tint: #FFFFFF;
  --nk-hover-1: color-mix(in srgb, var(--nk-hover-tint) 1.2%, var(--nk-surface));
  --nk-hover-2: color-mix(in srgb, var(--nk-hover-tint) 1.2%, var(--nk-surface));
  --nk-hover-3: color-mix(in srgb, var(--nk-hover-tint) 2%, var(--nk-surface));
  --nk-hl-soft: color-mix(in srgb, var(--nk-hover-tint) 3%, var(--nk-surface));

  /* ── Profile-colour tint strength ─────────────────────────────────────── */
  --nk-hl-color-pct: 10%;
  --nk-hl-color-pct-quote: 12%;

  /* ── Elevation — dark-only: a dark shadow is invisible on black, so reply/image
     HOVER shadows are remapped to a grey just below the border colour. Consumed by
     the `.theme-lightsout … box-shadow` rules beside the reply + image components. */
  --nk-shadow-lift: color-mix(in srgb, var(--nk-border) 22%, #000);
}
/* Pfp outer ring becomes a bg-coloured mask (no bright ring on black); search ring dark. */
html.theme-lightsout { --pfp-outer-border-color: #000000; }
.theme-lightsout .user-search-bar { --cc-outer-border-color: #2F3336; }
/* Boot splash is black on a Lights Out load (overrides its inline white). */
.theme-lightsout #nuke-boot-splash { background: #000000 !important; }
/* Dark primary buttons (#0F1419) would vanish on black — flip them to light pills. */
.theme-lightsout .nuke-container .nuke-profile-action-btn,
.theme-lightsout .nuke-container .nuke-chat-stake-btn {
  background: #EFF3F4;
  color: #0F1419;
}
/* Action toast lifts off pure black for contrast. */
.theme-lightsout .nuke-container .nuke-toast { background: #2F3336; }


/* ───────────────────────── Settings modal ─────────────────────────
   Opened from the left-nav "More" button. Themed via the --nk-* vars so it goes
   dark in Lights Out too. */
.nuke-settings-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 20, 25, 0.4);
}
.nuke-settings-modal {
  width: 360px;
  box-sizing: border-box;
  background: var(--nk-surface);
  border: 1px solid var(--nk-border);
  border-radius: 16px;
  box-shadow: 0 12px 40px rgba(15, 20, 25, 0.18);
  overflow: hidden;
}
.nuke-settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 18px;
  border-bottom: 1px solid var(--nk-border-soft);
  font-size: 17px;
  font-weight: 800;
  color: var(--nk-text);
}
.nuke-settings-close {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 9999px;
  background: transparent;
  color: var(--nk-text-2);
  font-size: 16px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.nuke-settings-close:hover { background: var(--nk-border-soft); color: var(--nk-text); }
.nuke-settings-body { padding: 6px 0 10px 0; }
.nuke-settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 18px;
}
/* Appearance row stacks its label over the System/Light/Dark segmented control. */
.nuke-settings-row-stack {
  flex-direction: column;
  align-items: stretch;
}
.nuke-theme-seg {
  display: flex;
  gap: 4px;
  margin-top: 12px;
  padding: 4px;
  background: var(--nk-surface-2);
  border: 1px solid var(--nk-border-soft);
  border-radius: 10px;
}
.nuke-theme-opt {
  flex: 1;
  padding: 8px 10px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--nk-text-2);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.nuke-theme-opt:hover { color: var(--nk-text); }
.nuke-theme-opt[aria-checked="true"] {
  background: var(--nk-accent);
  color: #FFFFFF;
}
.nuke-settings-row-label { font-size: 15px; font-weight: 700; color: var(--nk-text); }
.nuke-settings-row-sub { font-size: 12px; color: var(--nk-text-2); margin-top: 2px; }
/* iOS-style toggle switch. */
.nuke-toggle {
  flex-shrink: 0;
  width: 46px;
  height: 26px;
  border: none;
  border-radius: 9999px;
  background: var(--nk-border);
  position: relative;
  cursor: pointer;
  transition: background 0.18s ease;
}
.nuke-toggle[aria-checked="true"] { background: var(--nk-accent); }
.nuke-toggle::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #FFFFFF;
  transition: transform 0.18s ease;
}
.nuke-toggle[aria-checked="true"]::after { transform: translateX(20px); }

/* ── Music / video upload composer (header Music + Video buttons) ─────────── */
/* The modal reuses the cred-compose card (.cred-compose-modal) + .bio-modal-textarea +
   .bio-char-count + .action-btn; only the link input + status/quota lines are new, and
   they're styled with the theme tokens so they match in light + dark. */
.cred-compose-modal .music-upload-link {
  width: 100%;
  height: 40px;
  padding: 0 12px;
  margin-bottom: 10px;
  border: 1px solid var(--nk-border);
  border-radius: 10px;
  background: var(--nk-surface-2);
  color: var(--nk-text);
  font-family: var(--font-conduit);
  font-size: 15px;
}
.cred-compose-modal .music-upload-link::placeholder { color: var(--nk-text-2); }
.cred-compose-modal .music-upload-link:focus {
  outline: none;
  border-color: var(--nk-accent);
  box-shadow: 0 0 0 3px rgba(var(--nk-accent-rgb), 0.18);
}
.cred-compose-modal .music-upload-status {
  min-height: 16px;
  margin-top: 8px;
  font-size: 13px;
  color: var(--nk-text-2);
}
.cred-compose-modal .music-upload-status.music-status-err { color: #e0245e; }
.cred-compose-modal .music-quota { margin-top: 4px; font-size: 12px; color: var(--nk-text-2); }
