/* ==========================================================
   SURFCHAT — appearance
   This file controls how everything LOOKS. Nothing here
   touches the database. Edit freely.
   ========================================================== */

:root {
  /* Every surface is the same pitch black. Separation comes
     from hairlines and type weight, never a lighter panel. */
  --void:  #000000;

  /* No colour at all — five stops of grey doing the work that a
     hue would. This is what a new account sees. */
  --glow:   #ffffff;   /* brightest — your own messages */
  --signal: #e8e8e8;   /* the accent */
  --deep:   #8a8a8a;   /* second tone */
  --muted:  #7a7a7a;   /* timestamps, labels */
  --line:   #333333;   /* hairlines */

  --text:   #dcdcdc;

  /* With no hue to contrast against, a warning has to be loud
     some other way — so it leans on brightness instead, which is
     the one thing left to be loud with. */
  --alert:      #ffffff;
  --alert-deep: #767676;

  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Emoji come from the operating system, not from us — every
     platform ships its own set and there is no way to override
     that without shipping a font of several megabytes. What this
     does is name the modern ones explicitly and in order, so a
     machine with more than one installed picks the current one
     instead of an ancient fallback. */
  --emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji',
           'Segoe UI Symbol', 'Android Emoji', 'EmojiSymbols', sans-serif;
  --sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;

  /* A wash of the accent, used for hover and selection. Written
     as separate channels so a theme only has to change three
     numbers and every tint follows it. */
  --accent-rgb: 232, 232, 232;

  /* Scanline strength. Raise toward 0.30 for harsher,
     drop toward 0.06 for barely-there. */
  --scan-strength: 0.16;
}


/* ============================================================
   THEMES

   Only colours change. Every rule in this stylesheet already
   reads its colours from the variables above, so a theme is a
   short list of overrides and nothing else has to know about it.
   ============================================================ */

/* ---- SKINSURF: the mint one ---- */
html[data-theme="skinsurf"] {
  --void:   #000000;
  --glow:   #9dffdd;
  --signal: #4cffb8;
  --deep:   #1f8f6b;
  --muted:  #3f8c74;
  --line:   #10382a;
  --text:   #cdffec;

  --alert:      #ff5c6e;
  --alert-deep: #7d2230;

  --accent-rgb: 76, 255, 184;
  --scan-strength: 0.14;
}

html[data-theme="inverted"] .veil { background: rgba(0,0,0,0.45); }

/* ---- INVERTED: black on white ---- */
html[data-theme="inverted"] {
  --void:   #ffffff;
  --glow:   #000000;
  --signal: #111111;
  --deep:   #4a4a4a;
  --muted:  #6b6b6b;
  --line:   #d2d2d2;
  --text:   #1a1a1a;

  --alert:      #c81e2d;
  --alert-deep: #8b1620;

  --accent-rgb: 17, 17, 17;

  /* The scanline layer is painted in black. On a dark screen it
     reads as texture; at full strength over white it just looks
     like a dirty page, so it drops to a whisper here. */
  --scan-strength: 0.045;
}

/* ---- COLORBLOCK: turquoise, mustard and magenta ---- */
html[data-theme="colorblock"] {
  --void:   #150f1e;
  --glow:   #ffc94d;
  --signal: #3fd6c4;
  --deep:   #b6479b;
  --muted:  #9184c4;
  --line:   #322a4a;
  --text:   #ece6f8;

  --alert:      #ff4d8d;
  --alert-deep: #7d2350;

  --accent-rgb: 63, 214, 196;
  --scan-strength: 0.12;
}

/* ---- COMMODORE: cream on blue ---- */
html[data-theme="commodore"] {
  --void:   #3a2fa0;
  --glow:   #fffdf0;
  --signal: #f7ecc0;
  --deep:   #b3a6ea;
  --muted:  #a99ce2;
  --line:   #5b4fc4;
  --text:   #f2e6b8;

  /* Red would vanish against this blue, so the warning colour
     leans warm-orange instead — still obviously "wrong" without
     fighting the background. */
  --alert:      #ff9a6b;
  --alert-deep: #b85a38;

  --accent-rgb: 247, 236, 192;
  --scan-strength: 0.10;
}

/* ---- MOONSWORLD: cream on purple ---- */
html[data-theme="moonsworld"] {
  /* The violet is taken a good deal darker than the one in the
     artwork. At its original brightness there's nowhere for cream
     text to sit — everything lands in the same middle register
     and the whole screen goes flat. This keeps the hue and gives
     the creams room to be bright against it. */
  --void:   #2a2140;
  --glow:   #fdf6b8;
  --signal: #ecd97a;
  --deep:   #8a76b8;
  --muted:  #9c8ac4;
  --line:   #453765;
  --text:   #f0e9c4;

  /* Warm pink rather than red: red against violet reads as brown
     and stops looking like a warning. */
  --alert:      #ff8fa3;
  --alert-deep: #8f4457;

  --accent-rgb: 236, 217, 122;
  --scan-strength: 0.13;
}

/* ---- HOTPINK: pink on black ---- */
html[data-theme="hotpink"] {
  --void:   #000000;
  --glow:   #ff9ed2;
  --signal: #ff2d95;
  --deep:   #a2175f;
  --muted:  #b8477f;
  --line:   #3d0a24;
  --text:   #ffd9ee;

  /* Red on pink is invisible as a warning, so alerts go yellow —
     the one thing on this screen that isn't pink. */
  --alert:      #ffd23f;
  --alert-deep: #8a6a12;

  --accent-rgb: 255, 45, 149;
  --scan-strength: 0.16;
}

/* ---- AMBER: the old phosphor monitor look ---- */
html[data-theme="amber"] {
  --void:   #0d0700;
  --glow:   #ffd479;
  --signal: #ffab2e;
  --deep:   #9c5a12;
  --muted:  #8a5416;
  --line:   #3a1f06;
  --text:   #f2b95c;

  --alert:      #ff6b3d;
  --alert-deep: #7a2c10;

  --accent-rgb: 255, 171, 46;
  --scan-strength: 0.20;
}

* , *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--void);

  /* The document itself is nailed down and cannot scroll.
     Everything on screen is a fixed layer sized to the visible
     area, so there was never anything here worth scrolling — but
     "can't usefully scroll" and "can't scroll" are different
     things to a browser, and iOS Safari exploits the gap.

     Tap a field and Safari scrolls the PAGE to lift that field
     above the keyboard. It does this from its own measurements,
     without checking whether the field was actually hidden, and
     a fixed app that already resized itself to fit gets shoved
     up off the top of the screen as a result. The app was then
     scrolling it back on the next frame, and the two fought each
     other the whole time you were typing.

     With the document unscrollable there is nothing for Safari to
     move, so the shove never happens and there's nothing to
     correct. */
  position: fixed;
  overflow: hidden;
  width: 100%;
  top: 0;
  left: 0;
}

body {
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;

  /* Kill the grey flash iOS paints over anything you tap. We give
     our own feedback with :active states below. */
  -webkit-tap-highlight-color: transparent;
}

/* Chrome shouldn't be selectable — long-pressing a button to get
   a "Copy" bubble is never what anyone wanted. Message text and
   bios stay selectable, since copying those is the point. */
button, .tab, .row, .who-row, .mark, .av, .eyebrow, .wordmark, .msg-meta {
  -webkit-user-select: none;
  user-select: none;
}
.bubble, .prof-bio, .report-quote, .lk-url, .log-line, input, textarea {
  -webkit-user-select: text;
  user-select: text;
}

/* Tells the browser these are tap targets, not things to
   double-tap-zoom on. Removes the old click delay. */
button, .tab, .row, .who-row, label, a { touch-action: manipulation; }

button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
input  { font: inherit; color: inherit; background: none; border: none; outline: none; }
input::placeholder { color: var(--muted); }
:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

.mono    { font-family: var(--mono); }
.eyebrow {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- CRT scanlines ---------- */
.scan {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0,0,0,0) 0px,
    rgba(0,0,0,0) 2px,
    rgba(0,0,0,var(--scan-strength)) 2px,
    rgba(0,0,0,var(--scan-strength)) 3px
  );

  /* A full-screen fixed layer sitting above everything is cheap
     on some browsers and very expensive on others — Firefox on
     Android was redrawing this gradient across the whole screen
     on every scroll frame, which is exactly what "runs badly"
     feels like. These three lines hand it to the compositor as
     its own layer, so it's painted once and then just sits there
     while the page moves underneath. */
  transform: translateZ(0);
  will-change: transform;
  contain: strict;
}

/* ---------- app shell ----------
   Height, in order of preference:
     --vvh  measured live in JS from the visible viewport. This is
            the only thing iOS gets right when the keyboard is up.
     100dvh modern browsers, accounts for the sliding address bar
     100vh  last resort
   Without this, an open keyboard on iOS pushes the message box
   off the bottom of the screen and you type blind. */
.app {
  position: fixed;
  inset: 0;
  /* Three deep on purpose: each line only applies if the browser
     understands it, so an old one still gets a usable height.
     The max() is a floor — if the measured value ever comes back
     nonsense, the app doesn't collapse to nothing. */
  height: 100vh;
  height: 100dvh;
  height: max(var(--vvh, 100dvh), 320px);
  display: flex;
  flex-direction: column;
  overflow: hidden;

  /* keeps the header clear of the iPhone camera cutout */
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* ---------- header ---------- */
.header { flex: none; border-bottom: 1px solid var(--line); }
.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 16px;
}
.wordmark {
  display: flex;
  align-items: center;
  min-width: 0;
}
/* ---- HEADER LOGO SIZE ----
   This one number controls it. Lower it to shrink, raise to grow. */
.wordmark-img {
  height: 26px;
  width: auto;
  display: block;
  /* No tint. The artwork is white with a heavy black outline, so
     on this background the outline disappears and the letterforms
     read on their own. */
}


.status {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
}
.dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--deep);
  transition: background 0.3s;
}
.dot.is-live { background: var(--signal); }

/* ---------- auth ---------- */
/* Centred while there's room, but allowed to scroll normally once
   the keyboard takes half the screen. place-items:center on a
   grid that's too short pins the content to the middle and pushes
   its top edge off the container — so the fields you're typing in
   end up above the visible area with no way to reach them. Using
   safe-centre keeps it centred when it fits and falls back to
   top-aligned when it doesn't. */
.auth {
  flex: 1;
  min-height: 0;
  display: grid;
  /* Plain centring first, then the safe variant on top. A browser
     that doesn't know "safe" ignores that line and keeps the
     ordinary centring; one that does gets the version that stops
     pushing content off the top when space runs short. */
  place-items: center;
  place-items: safe center;
  align-content: safe center;
  padding: 24px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.auth[hidden] { display: none; }
.auth-card { width: 100%; max-width: 340px; }

/* The sign-in mark. No filter on it — this one keeps its own
   colours, unlike the wordmark in the top bar which is tinted to
   sit in the palette. */
.auth-brand {
  display: block;
  width: 200px;
  max-width: 100%;
  height: auto;
  margin: 0 auto 16px;
}
.auth-title {
  text-align: center;
  font-family: var(--mono);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: 0.3em;
  color: var(--signal);
  margin: 0 0 8px;
}
.auth-sub {
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.55;
  margin: 0 0 24px;
  text-align: center;
}

.field { margin-bottom: 15px; }
.field label { display: block; margin-bottom: 6px; }
.field input {
  width: 100%;
  border: 1px solid var(--line);
  padding: 12px 13px;
  font-family: var(--mono);
  /* 16px minimum: anything smaller makes iOS zoom in on tap */
  font-size: 16px;
}
.field input:focus { border-color: var(--signal); }

/* ---------- password show/hide ---------- */
.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 46px; }   /* clear room for the eye */

.pw-eye {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 44px;
  display: grid;
  place-items: center;
  color: var(--muted);
}
.pw-eye:hover { color: var(--signal); }
.pw-eye svg { width: 19px; height: 19px; display: block; }

/* Show the open eye by default; swap once the password is visible,
   so the icon reflects what the next click will do. */
.pw-eye .eye-shut { display: none; }
.pw-eye.is-on { color: var(--signal); }
.pw-eye.is-on .eye-open { display: none; }
.pw-eye.is-on .eye-shut { display: block; }

/* ---------- buttons ---------- */
.btn-solid {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  font-weight: 600;
  color: var(--void);
  background: var(--signal);
  padding: 13px 18px;
}
.btn-solid:disabled { background: var(--line); color: var(--muted); cursor: default; }
.btn-wide { width: 100%; margin-top: 6px; }
.btn-flex { flex: 1; }

.btn-ghost {
  flex: 1;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  border: 1px solid var(--line);
  padding: 12px;
  color: var(--muted);
}
.btn-ghost:hover { border-color: var(--muted); }

.btn-ghost-sm {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--signal);
  border: 1px solid var(--line);
  padding: 6px 10px;
}
.btn-ghost-sm:hover { border-color: var(--signal); }

/* HOME is a real link, not a button, so it needs these to sit
   flush with the others. */
a.btn-ghost-sm {
  text-decoration: none;
  display: inline-block;
  cursor: pointer;
}

.linkish {
  display: block;
  width: 100%;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: 14px 0 0;
  text-align: center;
}
.linkish:hover { color: var(--signal); }
.linkish[hidden] { display: none; }

.note { font-family: var(--mono); font-size: 11px; line-height: 1.6; margin: 14px 0 0; color: var(--muted); }
.note.is-bad { color: var(--glow); }

/* ---------- tabs ---------- */
.tabs { display: flex; border: 1px solid var(--line); margin-bottom: 20px; }
.tab {
  flex: 1;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  padding: 10px;
  color: var(--muted);
}
.tab.is-on { color: var(--signal); box-shadow: inset 0 -2px 0 var(--signal); }

/* ---------- layout ----------
   The media query at the bottom is the thermostat: below
   720px wide, show one pane at a time. Above, show both. */
.body { flex: 1; display: flex; min-height: 0; }
.body[hidden] { display: none; }
.pane { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1; }
.list-pane { border-right: 1px solid var(--line); }

@media (max-width: 719px) {
  .list-pane, .chat-pane { display: none; }
  .list-pane.is-active, .chat-pane.is-active { display: flex; }
}
@media (min-width: 720px) {
  .list-pane { flex: 0 0 300px; }
  .back { display: none !important; }
}

/* ---------- chat list ---------- */
.list-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 16px;
}
.head-actions { display: flex; gap: 8px; }

.scroll { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;  /* older iOS only; Firefox ignores it */ }

.row {
  display: flex;
  gap: 12px;
  width: 100%;
  padding: 12px 16px;
  text-align: left;
  border-left: 2px solid transparent;
}
.row:hover { border-left-color: var(--deep); }
.row.is-open { border-left-color: var(--signal); }
.row.is-open .name { color: var(--signal); }
.row.is-open .mark { border-color: var(--signal); }

.mark {
  flex: none;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--signal);
}
.mark.is-group { color: var(--deep); }

.row-text { flex: 1; min-width: 0; }
.row-top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.name { font-family: var(--mono); font-size: 13px; font-weight: 500; }
.time { font-family: var(--mono); font-size: 10px; color: var(--muted); flex: none; }
.preview {
  font-size: 13px;
  color: var(--muted);
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- conversation ---------- */
.chat-pane { position: relative; }
.convo { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.convo[hidden] { display: none; }

.chat-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--line);
}
.chat-head-text { min-width: 0; }
.back { font-family: var(--mono); font-size: 17px; color: var(--muted); padding: 4px 6px 4px 0; }

.msgs {
  flex: 1;
  overflow-y: auto;
  /* Keeps scrolling on its own layer instead of repainting the
     whole conversation each frame. */
  transform: translateZ(0);
  -webkit-overflow-scrolling: touch;  /* older iOS only; Firefox ignores it */
  /* Extra room at the bottom so the newest message isn't pressed
     against the composer. Opening its buttons needs somewhere for
     them to go — without this the row appears half underneath the
     message box and no amount of scrolling reveals it, because
     there's nothing below to scroll to. */
  padding: 20px 16px 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Every message sits on the left, yours included, so the eye has
   one column to follow instead of zigzagging down the page.
   Width comes from the content, up to 80% — left as a plain block
   it filled the whole column, which pushed the action row far off
   to the side of a three-word message. */
.msg { max-width: 80%; width: fit-content; }
.msg-meta {
  display: flex; gap: 8px; align-items: baseline;
  font-family: var(--mono); font-size: 10px; color: var(--muted);
  margin-bottom: 4px;
}
/* The name carries the line, so it gets a little more weight than
   the timestamp beside it. */
.msg-meta .who { font-size: 12px; color: var(--text); }
/* No border. The box was stretching to the full width of the
   message column regardless of how little was in it, which drew a
   huge empty rectangle around three words. Yours and theirs are
   told apart by side and colour instead. */
.bubble {
  padding: 2px 0;
  font-size: 14.5px;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.msg.is-mine .bubble { color: var(--glow); }

/* ---------- video in messages ---------- */
.msg-video {
  display: block;
  width: 100%;
  max-width: 420px;
  max-height: 340px;
  margin-top: 8px;
  border: 1px solid var(--line);
  background: var(--void);
  outline: none;
}
.bubble.is-image-only .msg-video { margin-top: 0; }

.attach-thumb.is-video {
  display: grid;
  place-items: center;
  color: var(--signal);
  font-size: 15px;
}
.attach-strip.is-working { opacity: 0.5; }

/* ---------- images in messages ---------- */
.msg-img-link { display: block; margin-top: 8px; line-height: 0; }
.bubble.is-image-only { padding: 0; }
.bubble.is-image-only .msg-img-link { margin-top: 0; }

.msg-img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 340px;
  border: 1px solid var(--line);
  background: var(--void);
  object-fit: contain;
}
.msg-img-link:hover .msg-img { border-color: var(--signal); }
.msg-img-gone {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  border: 1px dashed var(--line);
  padding: 14px;
  text-align: center;
  margin-top: 8px;
}

/* ---------- the staged attachment ---------- */
.attach-strip {
  flex: none;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 16px;
  border-top: 1px solid var(--line);
}
.attach-strip[hidden] { display: none; }
.attach-thumb {
  flex: none;
  width: 42px;
  height: 42px;
  border: 1px solid var(--line);
  background: var(--void) center/cover no-repeat;
}
.attach-info { flex: 1; min-width: 0; }
.attach-info .eyebrow { display: block; text-transform: none; letter-spacing: 0.06em; }
.attach-info .eyebrow:first-child {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 3px;
}

/* ---------- composer buttons ---------- */
.compose-btn {
  flex: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  color: var(--muted);
  border: 1px solid var(--line);
}
.compose-btn i { font-size: 15px; }
.compose-btn:hover { color: var(--signal); border-color: var(--signal); }
.compose-btn.is-on { color: var(--signal); border-color: var(--signal); }

/* ---------- emoji ---------- */
.emoji-panel {
  flex: none;
  border-top: 1px solid var(--line);
  background: var(--void);
}
.emoji-panel[hidden] { display: none; }

.emoji-tabs {
  display: flex;
  overflow-x: auto;
  border-bottom: 1px solid var(--line);
  -webkit-overflow-scrolling: touch;  /* older iOS only; Firefox ignores it */
}
.emoji-tab {
  flex: none;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  color: var(--muted);
  padding: 10px 12px;
  white-space: nowrap;
}
.emoji-tab.is-on { color: var(--signal); box-shadow: inset 0 -2px 0 var(--signal); }

.emoji-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  max-height: 172px;
  overflow-y: auto;
  padding: 6px;
  -webkit-overflow-scrolling: touch;  /* older iOS only; Firefox ignores it */
}
.emoji {
  font-family: var(--emoji);
  font-size: 21px;
  line-height: 1;
  padding: 7px 0;
  border-radius: 0;
}
.emoji:hover { background: rgba(var(--accent-rgb), 0.10); }

/* ---------- reactions ---------- */
/* Collapses when there are no reactions, rather than holding a
   blank strip under every message. */
.msg-foot { display: flex; flex-wrap: wrap; gap: 3px; }
.msg-foot:not(:empty) { margin-top: 5px; }

.react {
  display: none;
  align-items: center;
  gap: 4px;
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1;
  color: var(--muted);
  padding: 4px 5px;
}
.react i { font-size: 11px; }
.react-n:empty { display: none; }

/* A reaction someone has actually left always shows. Otherwise
   nobody could see what a message had collected without waving a
   cursor over every line. The empty ones are what hides. */
.react.has-count { display: inline-flex; }
.react.is-mine { color: var(--signal); }

/* Desktop: the whole row appears on hover. Wrapped in a hover
   query so a phone — which reports a permanent fake hover on the
   last thing tapped — doesn't get stuck showing them. */
@media (hover: hover) {
  .msg:hover .react { display: inline-flex; }
  .react:hover { color: var(--signal); }
}

/* Phones: tapping the message reveals the row. */
.msg.is-picking .react { display: inline-flex; }
.msg.is-picking .bubble { border-color: var(--deep); }

/* ---------- mentions ---------- */
.mention {
  color: var(--signal);
  font-weight: 500;
  cursor: pointer;
}
.mention:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Being named should be findable when you scroll back. */
.mention.is-me {
  color: var(--void);
  background: var(--signal);
  padding: 1px 4px;
  font-weight: 600;
}

.mention-pop {
  flex: none;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
  max-height: 180px;
  overflow-y: auto;
}
.mention-pop[hidden] { display: none; }
.mention-opt {
  font-family: var(--mono);
  font-size: 13px;
  text-align: left;
  padding: 11px 16px;
  color: var(--text);
}
.mention-opt.is-on { color: var(--signal); background: rgba(var(--accent-rgb), 0.08); }
.mention-opt:hover { color: var(--signal); }

/* ---------- private message alert ---------- */
.row.is-dm-alert { border-left-color: var(--glow); }
.row.is-dm-alert .name { color: var(--glow); }
.row.is-dm-alert .preview { color: var(--text); }
.row.is-dm-alert .unread { background: var(--glow); }

/* ---------- sound toggle ---------- */
#soundBtn { padding: 6px 9px; }
#soundBtn i { font-size: 12px; }
#soundBtn.is-off { color: var(--muted); }



/* ---------- rules ---------- */
.rules { margin: 0; padding-left: 22px; }
.rules li {
  font-size: 13.5px;
  line-height: 1.55;
  margin-bottom: 13px;
  padding-left: 4px;
}
.rules li::marker { color: var(--signal); font-family: var(--mono); }
.rules b { color: var(--signal); font-weight: 500; }
.rules span {
  display: block;
  color: var(--muted);
  font-size: 12.5px;
  margin-top: 3px;
}

.rules-bottom {
  border-top: 1px solid var(--line);
  margin-top: 18px;
  padding-top: 16px;
  font-size: 13.5px;
  line-height: 1.6;
}
.rules-bottom b { color: var(--signal); font-weight: 500; }

/* ---------- audio player ---------- */
.audio {
  display: flex;
  align-items: center;
  gap: 11px;
  border: 1px solid var(--line);
  padding: 10px 12px;
  margin-top: 8px;
  min-width: 260px;
  max-width: 420px;
}
.bubble.is-image-only .audio { margin-top: 0; }
.msg.is-mine .audio { border-color: var(--deep); }
.audio.is-playing { border-color: var(--signal); }

.audio-play {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  color: var(--signal);
}
.audio-play i { font-size: 12px; }
.audio-play:hover { border-color: var(--signal); }
.audio.is-playing .audio-play { border-color: var(--signal); }

.audio-main { flex: 1; min-width: 0; text-align: left; }
.audio-name {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 7px;
}

/* The bar is a thin track with the played part filled in, and a
   real range input laid over it so dragging and keyboard control
   work without having to reinvent either. */
.audio-bar { position: relative; height: 14px; display: flex; align-items: center; }
.audio-bar::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: var(--line);
}
.audio-fill {
  position: absolute;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--signal);
  pointer-events: none;
}
.audio-seek {
  position: relative;
  width: 100%;
  height: 14px;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.audio-seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px;
  height: 11px;
  background: var(--signal);
  border: none;
  border-radius: 0;
  cursor: pointer;
}
.audio-seek::-moz-range-thumb {
  width: 11px;
  height: 11px;
  background: var(--signal);
  border: none;
  border-radius: 0;
  cursor: pointer;
}
.audio-seek::-moz-range-track { background: none; }

.audio-time {
  flex: none;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
  text-align: right;
  line-height: 1.5;
}
.audio-at { display: block; color: var(--text); }
.audio-of { display: block; }

.attach-thumb.is-audio {
  display: grid;
  place-items: center;
  color: var(--signal);
  font-size: 18px;
}

/* ---------- links in messages ---------- */
.msg-link {
  color: var(--signal);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}
.msg-link:hover { color: var(--glow); }
.msg.is-mine .msg-link { color: var(--glow); }

/* ---------- edit / delete ---------- */
.edited { font-style: italic; opacity: 0.75; }


.msg-actions[hidden] { display: none; }

.act {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  border: 1px solid var(--line);
  padding: 4px 7px;
  color: var(--muted);
}
.act:hover { border-color: var(--signal); color: var(--signal); }
.act.is-armed { border-color: var(--glow); color: var(--glow); }
.act:disabled { opacity: 0.5; cursor: default; }

.edit-box {
  width: 100%;
  min-width: 200px;
  border: none;
  background: none;
  color: var(--glow);
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.5;
  resize: vertical;
  outline: none;
  padding: 0;
}
.edit-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }

.placeholder, .empty {
  flex: 1;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 32px;
}
.placeholder[hidden] { display: none; }
.placeholder p, .empty p { color: var(--muted); font-size: 14px; margin: 8px 0 0; }

/* ---------- composer ---------- */
.composer {
  flex: none;
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  /* Nothing in here may spill sideways. */
  max-width: 100%;
  overflow: hidden;
}
.composer .btn-solid { flex: none; }
.input {
  flex: 1;
  /* Without this a flex item refuses to shrink below its own
     content, so a long message pushed SEND clean off the edge of
     a narrow screen. */
  min-width: 0;
  border: 1px solid var(--line);
  padding: 12px 13px;
  font-size: 16px;   /* again: 16px stops iOS from zooming */
}
.input:focus { border-color: var(--signal); }

/* ---------- header actions ----------
   One menu at every size. Nine buttons strung across the top bar
   was a row of shouting; behind three bars it's a list you read
   when you want it. The buttons themselves are untouched — same
   ids, same links — they just live in a panel now. */
.header-right { display: flex; align-items: center; gap: 10px; }

.menu-toggle {
  display: flex;
  width: 44px;
  height: 40px;
  padding: 11px 10px;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
}
.menu-toggle span {
  display: block;
  height: 2px;
  background: var(--signal);
  transition: transform 0.18s, opacity 0.18s;
}
.menu-toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.menu-toggle.is-open span:nth-child(2) { opacity: 0; }
.menu-toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.header-actions {
  position: absolute;
  top: 100%;
  right: 10px;
  z-index: 45;
  display: none;
  flex-direction: column;
  gap: 0;
  background: var(--void);
  border: 1px solid var(--line);
  min-width: 184px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.85);

  /* The menu is long enough now that it can outgrow the screen,
     and an item hanging off the bottom is simply unreachable —
     it's a fixed layer, so the page behind it won't scroll to
     bring it back. This keeps it inside whatever room is left
     below the header and lets the list itself scroll.

     Height comes from the visible viewport rather than the
     window, so a phone's address bar sliding in and out doesn't
     leave the last item stranded underneath it. */
  max-height: calc(100vh - 84px);
  max-height: calc(var(--vvh, 100dvh) - 84px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;

  /* Stops a flick that reaches the end of the menu from carrying
     on and scrolling the chat behind it. */
  overscroll-behavior: contain;
}

/* A slim bar rather than the system default, which on desktop is
   wide enough to crowd a 184px menu. Only shows when there's
   something to scroll. */
.header-actions::-webkit-scrollbar { width: 8px; }
.header-actions::-webkit-scrollbar-track { background: transparent; }
.header-actions::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 2px solid var(--void);
}
.header-actions::-webkit-scrollbar-thumb:hover { background: var(--deep); }
.header-actions { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }

/* The rows must not be squashed to fit — a flex child in a
   scrolling column will shrink itself before it lets the
   container scroll, which would quietly shave every button
   instead of producing a scrollbar. */
.header-actions .btn-ghost-sm { flex: none; }
.header-actions.is-open { display: flex; }
.header-actions .btn-ghost-sm {
  border: none;
  border-bottom: 1px solid var(--line);
  text-align: left;
  padding: 14px 16px;
  font-size: 12px;
}
.header-actions .btn-ghost-sm:last-child { border-bottom: none; }
.header-actions .btn-ghost-sm:hover { background: rgba(var(--accent-rgb), 0.08); }
.header-actions .btn-ghost-sm:active { background: var(--line); }

/* The sound button carries its word everywhere now, since a lone
   icon among rows of words reads as a mistake. */
.btn-word { display: inline; margin-left: 9px; }

/* LIVE lights up while anyone's in the room. */
#liveBtn.has-live { color: var(--signal); }

@media (max-width: 460px) {
  .header-right .status { display: none; }
}

.header { position: relative; }

/* ---------- mobile refinements ---------- */
@media (max-width: 719px) {

  /* Anything you tap needs to be reachable with a thumb. These
     were all sized for a mouse pointer. */
    .act { padding: 10px 13px; font-size: 11px; }
  .react { padding: 7px 8px; }
  .react i { font-size: 14px; }
  .msg-foot { gap: 4px; }
  .compose-btn { width: 42px; height: 42px; }
  .compose-btn i { font-size: 17px; }
  .composer { gap: 7px; padding: 10px 10px; }
  .composer .btn-solid { padding: 13px 12px; letter-spacing: 0.1em; }
  .emoji-grid { grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); max-height: 150px; }
  .emoji { font-size: 23px; padding: 9px 0; }
  .msg-img { max-height: 260px; }
  .audio { min-width: 0; padding: 11px; gap: 9px; }
  .audio-play { width: 40px; height: 40px; }
  .audio-play i { font-size: 14px; }
  .audio-seek::-webkit-slider-thumb { width: 15px; height: 15px; }
  .audio-seek::-moz-range-thumb { width: 15px; height: 15px; }
  .audio-bar { height: 18px; }
  .msg-video { max-height: 260px; }

  /* Picking a name is a tap target like any other. */
  .mention-opt { padding: 14px 16px; font-size: 14px; }
  .mention-pop { max-height: 132px; }

  /* Tick boxes are drawn small by default and are awkward to hit,
     even though the whole row is clickable. */
  .perm-row input { width: 21px; height: 21px; }
  .perm-row { padding: 14px 0; }

  .chan-topic { padding: 11px 12px; }
  .color-row input[type="color"] { width: 52px; height: 46px; }
  .rules li { margin-bottom: 16px; }
  .lk-kill { width: 44px; min-height: 42px; }
  .back { padding: 10px 12px 10px 0; font-size: 20px; }
  .info-btn { width: 38px; height: 38px; }
  .info-btn i { font-size: 16px; }
  .btn-ghost-sm { padding: 9px 12px; }
  .tab { padding: 13px 8px; }
  .row { padding: 14px 16px; }
  .who-row { padding: 13px 16px; }

  /* Bubbles get wider — 78% of a phone screen wastes the margin */
  .msg { max-width: 92%; }
  .msg-meta .who { font-size: 12.5px; }
  /* The profile doesn't take the whole screen the way the reading
     panels do — it's a card, and a card that reaches both edges
     of a phone stops looking like one. A margin around it also
     leaves somewhere to tap to close. */
  /* The banner bleeds to the card's edges, so its pull matches
     whatever padding the card is using. */
  .prof-banner { height: 88px; margin: -16px -14px 13px; }
  .prof-pic { width: 58px; height: 58px; font-size: 18px; }
  .prof-head { gap: 12px; margin-bottom: 14px; }
  .banner-edit { height: 76px; }
  .gradient-label input { width: 20px; height: 20px; }
  .rename-in { padding: 11px 12px; }
  .load-older { padding: 11px 20px; font-size: 11px; }
  .blocked-row { padding: 12px 0; }

  /* Modals go edge to edge. A centred card with 24px of margin on
     a phone is a card with no room in it. */
  /* Panels are cards here, not whole screens. Filling the display
     edge to edge made a short block of help text look like the app
     had navigated somewhere, and left nothing around it to tap to
     get out. A margin does both jobs: it keeps the panel reading
     as a layer on top of the chat, and gives you somewhere to
     dismiss it. The safe-area insets live on the backdrop now, so
     the card itself never has to reason about the notch. */
  .veil {
    /* 10px, not more: the per-panel widths are set in vw and win
       on specificity, so the widest of them (moderation, at 94vw)
       plus a bigger margin would come to more than the screen. */
    padding: calc(10px + env(safe-area-inset-top)) 10px
             calc(10px + env(safe-area-inset-bottom));
    place-items: center;
  }
  .modal, .modal-wide {
    max-width: 100%;
    width: 100%;
    height: auto;
    max-height: 86vh;
    max-height: 86dvh;
    border: 1px solid var(--line);
    border-radius: 0;
    padding: 16px 14px;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
  }
  .mod-body, .help-body { max-height: none; flex: 1; min-height: 0; }
  .modal-actions { margin-top: auto; padding-top: 18px; }

  /* Report action buttons wrap into a tidy grid rather than a
     ragged row */
  .report-acts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 7px; }
  .report-acts .act { text-align: center; }

  .link-row { flex-wrap: wrap; }
  .link-row .lk-label { flex: 1 1 100%; }
  .link-row .lk-addr { flex: 1 1 auto; }

  .prof-pic { width: 66px; height: 66px; font-size: 20px; }
  .term-add { flex-wrap: wrap; }
  .term-add input { flex: 1 1 100%; }
}

/* Visible feedback on press, since we removed the grey flash */
.btn-ghost-sm:active, .act:active, .tab:active,
.btn-solid:active, .btn-ghost:active { opacity: 0.6; }
.row:active, .who-row:active { background: rgba(var(--accent-rgb), 0.06); }

/* Very small phones: stop the wordmark crowding the toggle */
@media (max-width: 380px) {
  .wordmark-img { height: 21px; }
  .header-row { padding: 11px 12px; }
}

/* Landscape phones are short. Give the messages the room. */
@media (max-height: 460px) and (orientation: landscape) {
  .header-row { padding: 7px 16px; }
  .msgs { padding: 12px 16px; }
  .list-head { padding: 9px 16px; }
}

/* ---------- list tabs ---------- */
.list-tabs {
  flex: none;
  margin: 0 16px 8px;
}

/* ---------- channel info ---------- */
.info-btn {
  flex: none;
  margin-left: 8px;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  color: var(--muted);
}
.info-btn i { font-size: 14px; }
.info-btn:hover { color: var(--signal); }
.info-btn[hidden] { display: none; }

.topic-body {
  font-size: 14px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border-left: 2px solid var(--line);
  padding-left: 12px;
  margin: 4px 0 4px;
}
.topic-body.is-empty { color: var(--muted); font-style: italic; }

/* ---------- who's online ---------- */
.who-pane { border-left: 1px solid var(--line); }
.who-btn { flex: none; margin-left: auto; }
.who-close { display: none; }

.who-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  font-family: var(--mono);
  font-size: 12.5px;
}
.who-row .dot { background: var(--signal); animation: none; }
.who-row.is-me { color: var(--signal); }
.who-empty { padding: 14px 16px; color: var(--muted); font-size: 13px; }

/* The roster can run long, so the pane scrolls rather than
   pushing the count out of view. */
.who-pane .scroll { overflow-y: auto; }

/* Three columns on a wide screen. Below that the online list
   becomes a panel you call up with the USERS button, because
   three columns on a phone is two columns too many. */
@media (min-width: 1000px) {
  .who-pane { flex: 0 0 210px; display: flex !important; }
  .who-btn { display: none; }
}
@media (max-width: 999px) {
  .who-pane { display: none; }
  .who-pane.is-active { display: flex; }
  .who-close { display: block; }
}
@media (min-width: 720px) and (max-width: 999px) {
  .who-pane.is-active { flex: 0 0 210px; }
}

/* ---------- help ---------- */
/* The scrolling area reaches the full width of the panel, so its
   scrollbar sits on the edge where you expect it. Previously it
   was inset by a padding and a max-width, which parked a second
   scrollbar in the middle of the panel with a dead strip beyond
   it. The comfortable reading width is applied to the text
   inside instead. */
.help-body {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;  /* older iOS only; Firefox ignores it */
  margin-top: 4px;
  flex: 1;
  min-height: 0;
}
.help-body h3 {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--signal);
  margin: 20px 0 7px;
}
.help-body h3:first-child { margin-top: 0; }
.help-body p { font-size: 13.5px; line-height: 1.6; margin: 0 0 10px; color: var(--text); }
.help-body ul { margin: 0 0 10px; padding-left: 18px; }
.help-body li { font-size: 13.5px; line-height: 1.6; margin-bottom: 5px; }
.help-body b { color: var(--signal); font-weight: 500; }

/* ---------- send refusals ---------- */
.send-error {
  flex: none;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--glow);
  border-top: 1px solid var(--deep);
  padding: 9px 16px;
}
.send-error[hidden] { display: none; }

/* ---------- moderator stats ---------- */
.mod-stats {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  margin-bottom: 14px;
}
.stat { background: var(--void); padding: 9px 6px; text-align: center; }
.stat-n { font-family: var(--mono); font-size: 16px; color: var(--signal); }
@media (max-width: 460px) {
  .mod-stats { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- audit log ---------- */
.log-row { padding: 9px 0; border-bottom: 1px solid var(--line); }
.log-line { font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; }
.log-when { font-family: var(--mono); font-size: 10px; color: var(--muted); margin-top: 3px; }

.term-ban { color: var(--void); background: var(--signal); }

/* ---------- reports run red ----------
   Scoped to the reports pane only. The USERS tab reuses the same
   card markup and stays mint, so red always means "someone
   flagged this", never just "this is a card". */
#modReports .report { border-color: var(--alert-deep); }
#modReports .report-who { color: var(--alert); }
#modReports .report-quote { border-left-color: var(--alert-deep); }
#modReports .report-reason { color: var(--alert-deep); }
#modReports .act:hover { border-color: var(--alert); color: var(--alert); }
#modReports .act.is-armed { border-color: var(--alert); color: var(--alert); }

/* The MOD button only goes red when there's actually something
   in the queue. A permanently red button stops meaning anything. */
.mod-btn.has-reports {
  color: var(--alert);
  border-color: var(--alert-deep);
}
.tab.has-reports { color: var(--alert); }
.tab.is-on.has-reports { box-shadow: inset 0 -2px 0 var(--alert); }

/* ---------- name colour ---------- */
.color-row { display: flex; gap: 8px; align-items: stretch; }
.color-row input[type="color"] {
  flex: none;
  width: 46px;
  height: 42px;
  padding: 3px;
  border: 1px solid var(--line);
  background: var(--void);
  cursor: pointer;
}
.color-hex {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--line);
  padding: 11px 13px;
  font-family: var(--mono);
  font-size: 16px;
  text-transform: lowercase;
}
.color-hex:focus { border-color: var(--signal); }

.color-preview {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--signal);
  border: 1px solid var(--line);
  padding: 10px 12px;
  margin-top: 8px;
  overflow-wrap: anywhere;
}
.color-preview.is-default { color: var(--text); }

/* ---------- permissions ---------- */
.perm-list { display: flex; flex-direction: column; }
.perm-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.perm-row:last-child { border-bottom: none; }
.perm-row input {
  flex: none;
  width: 17px;
  height: 17px;
  margin-top: 1px;
  accent-color: var(--signal);
  cursor: pointer;
}
.perm-text { display: flex; flex-direction: column; gap: 3px; }
.perm-label { font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; }
.perm-hint { font-size: 12px; color: var(--muted); line-height: 1.45; }
.perm-row input:checked + .perm-text .perm-label { color: var(--signal); }

/* ---------- channel form ---------- */
.chan-topic {
  width: 100%;
  border: 1px solid var(--line);
  padding: 9px 10px;
  font-size: 16px;
  margin-bottom: 8px;
}
.chan-topic:focus { border-color: var(--signal); }


/* Anywhere an emoji is shown on its own it uses that stack. In
   running text the sans-serif takes the letters and the emoji
   font takes the pictures, which browsers handle automatically. */
.react-ch, .react-opt, .emoji, .emoji-tab-ch, .attach-thumb {
  font-family: var(--emoji);
}
.bubble, .preview, .msg-link { font-family: var(--sans), var(--emoji); }




/* ---------- crash banner ---------- */
#crashBar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  background: var(--void);
  border-top: 1px solid var(--alert-deep);
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--alert);
}
.crash-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.crash-reload {
  flex: none;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--void);
  background: var(--alert);
  padding: 8px 12px;
}
.crash-hide {
  flex: none;
  font-size: 16px;
  line-height: 1;
  color: var(--alert-deep);
  padding: 4px 8px;
}

/* ---------- message actions ----------
   Shown on hover with a pointer, and on tap without one. They sit
   under the message rather than floating over it, so nothing is
   ever covered by the thing you reached for. */
/* A small + appears at the end of the message on hover, or on a
   tap where there's no pointer. Pressing it opens the buttons
   underneath. Two steps rather than one, but the row only exists
   when it's been asked for — nothing hovers into view unbidden
   and nothing reserves space it isn't using. */
.msg { position: relative; }

/* A small + at the end of every message. On a computer it waits
   for a hover; on a phone it's simply there, because there is no
   hover and because tapping a photo opens the photo. */
.msg-plus {
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1;
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 1px 5px;
  margin-left: 7px;
  vertical-align: 1px;
}
.msg-plus:hover { color: var(--signal); border-color: var(--signal); }

/* Under a picture, video or track it can't trail a word, so it
   drops below and sits at the right edge. */
.msg-plus.is-below { margin: 5px 0 0 auto; vertical-align: baseline; }

@media (hover: hover) {
  .msg-plus { display: none; }
  .msg-plus.is-below { display: none; }
  .msg:hover .msg-plus,
  .msg.is-open .msg-plus { display: inline-block; }
  .msg:hover .msg-plus.is-below,
  .msg.is-open .msg-plus.is-below { display: block; }
}

@media (hover: none) {
  .msg-plus { display: inline-block; opacity: 0.5; }
  .msg-plus.is-below { display: block; opacity: 0.5; }
  .msg.is-open .msg-plus { opacity: 1; }
}

.msg.is-open .msg-plus { color: var(--signal); border-color: var(--signal); }

/* The buttons sit under the message in the normal flow. Nothing
   floats, nothing is positioned by script — that's what broke it
   before, because the message list is on its own layer for
   scrolling and a fixed element inside one is positioned against
   the layer rather than the screen. */
.msg-actions {
  display: none;
  gap: 5px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.msg.is-open .msg-actions { display: flex; }

/* The last message needs the most room, since it's the one
   sitting against the composer. */
.msgs > .msg:last-child { scroll-margin-bottom: 60px; }
.msgs::after { content: ""; display: block; flex: none; height: 4px; }

/* Report reads as a warning without leaving the palette — the
   same alert red used for the moderation queue. */
.act-report { color: var(--alert-deep); border-color: var(--alert-deep); }
.act-report:hover { color: var(--alert); border-color: var(--alert); }
.act-report.is-armed { color: var(--alert); border-color: var(--alert); }

/* ---------- replies ---------- */
.quote {
  display: block;
  width: 100%;
  text-align: left;
  border-left: 2px solid var(--deep);
  padding: 2px 0 2px 9px;
  margin-bottom: 5px;
  opacity: 0.75;
}
.quote:hover { opacity: 1; border-color: var(--signal); }
.quote-who {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--signal);
  margin-bottom: 1px;
}
.quote-text {
  display: block;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.quote.is-gone .quote-text { font-style: italic; }

/* A brief flash when you jump to the quoted message, so the eye
   lands in the right place instead of hunting. */
.msg.is-flash .bubble {
  animation: msg-flash 1.4s ease-out;
}
@keyframes msg-flash {
  0%, 40% { background: rgba(var(--accent-rgb), 0.16); }
  100%    { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .msg.is-flash .bubble { animation: none; outline: 1px solid var(--signal); }
}

.reply-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 16px;
  border-top: 1px solid var(--line);
}
.reply-bar[hidden] { display: none; }
.reply-mark { flex: none; width: 2px; align-self: stretch; background: var(--signal); }
.reply-info { flex: 1; min-width: 0; }
.reply-text {
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}
#replyCancel { font-size: 15px; line-height: 1; padding: 5px 10px; }

@media (max-width: 719px) {
  .msg-actions { gap: 5px; }
  .msg-plus { font-size: 13px; padding: 3px 8px; }
  .msg-actions .act { padding: 7px 9px; font-size: 10px; }
  #replyCancel { padding: 9px 13px; }
}






/* ---------- grouped messages ----------
   A follow-up from the same person loses the repeated name and
   picture and sits tight under the one above, so a burst reads
   as one turn rather than three. */
.msg.is-grouped .msg-meta { display: none; }
.msg.is-grouped { margin-top: -1px; }

/* Hovering anywhere in the run shows the actions, same as any
   other message — the buttons don't disappear just because the
   header did. */
.msg.is-grouped .bubble { padding-top: 1px; }


/* ---------- leaving / deleting a chat ---------- */
#chatMenuBtn { flex: none; font-size: 15px; line-height: 1; padding: 5px 10px; }
.cm-actions { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 2px; }
.cm-actions .act { padding: 13px; text-align: center; }
.cm-leave:hover { border-color: var(--signal); color: var(--signal); }
.cm-delete { color: var(--alert-deep); border-color: var(--alert-deep); }
.cm-delete:hover, .cm-delete.is-armed { color: var(--alert); border-color: var(--alert); }

/* The most severe action in the panel, so it gets the same alert
   red as delete, but solid rather than outlined — the one button
   here that should never read as "just another option in the row". */
.act-terminate {
  color: var(--void);
  background: var(--alert-deep);
  border-color: var(--alert-deep);
}
.act-terminate:hover { background: var(--alert); border-color: var(--alert); }




/* ---------- text faces ----------
   Wide, and made of ordinary characters rather than emoji, so
   they need roomier cells and a font stack that actually has the
   katakana and box-drawing pieces they're built from. Left in the
   emoji font and half of them would come out as empty boxes. */
.emoji-grid.is-text {
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  max-height: 200px;
}
.emoji.is-text {
  font-family: var(--mono), "Segoe UI Symbol", "Noto Sans Symbols 2",
               "Apple Symbols", "Arial Unicode MS", sans-serif;
  font-size: 12px;
  line-height: 1.5;
  padding: 8px 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 719px) {
  .emoji-grid.is-text { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .emoji.is-text { font-size: 13px; padding: 10px 6px; }
}



/* ---------- picture galleries ----------
   A row rather than a grid: several pictures stay one line tall
   however many there are, so a message with six doesn't push the
   conversation off the screen. Slide sideways to see the rest. */
.gallery {
  display: flex;
  gap: 4px;
  margin-top: 7px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  padding-bottom: 2px;
}
.gallery::-webkit-scrollbar { height: 6px; }
.gallery::-webkit-scrollbar-track { background: transparent; }
.gallery::-webkit-scrollbar-thumb { background: var(--line); }

.gallery-cell {
  flex: none;
  display: block;
  width: 110px;
  height: 110px;
  border: 1px solid var(--line);
  overflow: hidden;
  line-height: 0;
}
.gallery-cell:hover { border-color: var(--signal); }
.gallery-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* One on its own doesn't need cropping to a square. */
.gallery.is-one .gallery-cell {
  width: auto;
  height: auto;
  max-width: 260px;
  max-height: 260px;
}
.gallery.is-one .gallery-cell img { width: auto; height: auto; max-width: 260px; }

/* ---------- several pictures waiting to go ---------- */
.shots-row {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: 5px;
  overflow-x: auto;
  scrollbar-width: thin;
}
.shot-cell {
  flex: none;
  position: relative;
  width: 54px;
  height: 54px;
  background: var(--void) center/cover no-repeat;
  border: 1px solid var(--line);
}
.shot-kill {
  position: absolute;
  top: -1px;
  right: -1px;
  width: 20px;
  height: 20px;
  line-height: 1;
  font-size: 13px;
  color: var(--text);
  background: var(--void);
  border: 1px solid var(--line);
}
.shot-kill:hover { color: var(--alert); border-color: var(--alert); }

@media (max-width: 719px) {
  .gallery-cell { width: 92px; height: 92px; }
  .gallery.is-one .gallery-cell,
  .gallery.is-one .gallery-cell img { max-width: 200px; max-height: 200px; }
  .shot-cell { width: 62px; height: 62px; }
  .shot-kill { width: 24px; height: 24px; font-size: 15px; }
}


/* ---------- a private message waiting ----------
   Inverted rather than merely coloured: at a glance across a room
   the eye catches a block that has swapped light for dark long
   before it registers a change of shade. */
@keyframes tab-calling {
  0%, 49%   { background: transparent; color: var(--signal); }
  50%, 100% { background: var(--signal); color: var(--void); }
}

.tab.is-calling {
  animation: tab-calling 1.1s steps(1, end) infinite;
}

/* Anyone who has asked their system to cut down on movement gets
   the inverted state held still instead of flashing. It still
   stands out; it just doesn't blink. */
@media (prefers-reduced-motion: reduce) {
  .tab.is-calling {
    animation: none;
    background: var(--signal);
    color: var(--void);
  }
}

/* ---------- the alerts panel ---------- */
.alert-group { margin-bottom: 18px; }
.alert-group .eyebrow { margin-bottom: 8px; }
.alert-list { display: flex; flex-direction: column; gap: 1px; }

.alert-opt {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  text-align: left;
  border: 1px solid var(--line);
  padding: 11px 13px;
  color: var(--muted);
}
.alert-opt + .alert-opt { border-top: none; }
.alert-opt:hover { border-color: var(--deep); }
.alert-opt.is-on {
  color: var(--signal);
  border-color: var(--signal);
  background: rgba(var(--accent-rgb), 0.07);
}

.alert-tick { flex: none; font-size: 11px; line-height: 1.5; }
.alert-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.alert-label { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.1em; }
.alert-note { font-size: 12px; color: var(--muted); line-height: 1.45; }
.alert-opt.is-on .alert-note { color: var(--text); }

.alert-perm {
  border: 1px solid var(--alert-deep);
  color: var(--alert);
  font-size: 12.5px;
  line-height: 1.5;
  padding: 11px 13px;
  margin: 0 0 18px;
}
.alert-perm[hidden] { display: none; }
.alert-perm .act { display: block; margin-top: 9px; }

@media (max-width: 719px) {
  .alert-opt { padding: 13px; }
  .alert-note { display: block; }
}

/* ---------- link embeds ----------
   Small on purpose. A chat is a column of conversation, and a
   full-width player under every link turns it into a scroll of
   media with the talking squeezed between. These are sized to
   say "this is what was linked" and no more — tap through for
   the real thing. */
.embed {
  margin-top: 7px;
  border: 1px solid var(--line);
  overflow: hidden;
  max-width: 400px;
  line-height: 0;
}

.embed iframe,
.embed video,
.embed img {
  display: block;
  width: 100%;
  border: none;
}

/* Video keeps its shape rather than a fixed height, so nothing
   is letterboxed. */
.embed.is-video iframe { aspect-ratio: 16 / 9; height: auto; }
.embed.is-file-video video { max-height: 220px; width: auto; max-width: 100%; }

/* Spotify renders at 80, 152 or 352 and nothing else — hand it
   any other number and it draws its compact player anyway, then
   leaves the difference as dead space. A single track fits the
   80px bar; albums and playlists get 152. */
.embed.is-audio iframe { height: 80px; }
.embed.is-audio-tall iframe { height: 152px; }

/* Tenor and Giphy embeds are roughly square and size themselves
   inside whatever they're given. */
.embed.is-gif { max-width: 260px; }
.embed.is-gif iframe { height: 220px; }

.embed.is-image { max-width: 260px; }
.embed.is-image img { height: auto; max-height: 240px; object-fit: cover; }
.embed.is-image a { display: block; }

@media (max-width: 719px) {
  .embed { max-width: 100%; }
  .embed.is-image { max-width: 200px; }
  .embed.is-image img { max-height: 190px; }
  .embed.is-gif { max-width: 220px; }
  .embed.is-gif iframe { height: 190px; }
}

/* ---------- the donate panel ---------- */
#donateModal .modal {
  max-width: min(92vw, 460px);
  height: min(90vh, 860px);
  max-height: min(90vh, 860px);
}

.kofi-frame {
  flex: 1;
  min-height: 0;
  border: 1px solid var(--line);
  /* Ko-fi's own recommended backing, not plain white — it's what
     their page is designed against, so the frame's edges don't
     show as a bright seam while it loads. */
  background: #f9f9f9;
  overflow: hidden;
  margin-top: 4px;
}
.kofi-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  /* 712px is the height Ko-fi builds the widget for. Below that
     their own layout starts scrolling internally, which is worse
     than the panel scrolling. */
  min-height: 712px;
  border: none;
  padding: 4px;
  background: #f9f9f9;
}
#donateOpen { text-decoration: none; text-align: center; }

@media (max-width: 719px) {
  /* The panel scrolls, the frame doesn't shrink.

     Before this the frame was told to fill the panel and hide
     anything that didn't fit — so on a short screen Ko-fi's form
     was cut off partway down the amount field, with no way to
     reach the button underneath it. You could see the form but
     not finish using it.

     Letting the frame keep its full height and having the panel
     scroll instead means the whole form is reachable, however
     short the screen. */
  .kofi-frame {
    flex: none;
    overflow: visible;
  }
  .kofi-frame iframe {
    height: 712px;
    min-height: 712px;
  }
}

/* ---------- surfamp ----------
   Framed as a window rather than a panel: a title bar, a minimise
   button, and the application filling everything below. The
   explanatory text and the buttons underneath were eating the
   height the playlist needed. */
#ampModal .modal {
  /* Roomier than the reading panels: this one has a playlist, a
     console readout and a row of transport controls, all of which
     want horizontal space. Still capped well short of the display
     so it reads as a window sitting on the chat. */
  max-width: min(94vw, 1100px);
  height: min(90vh, 1000px);
  max-height: min(90vh, 1000px);
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

.win-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 4px 0 12px;
  border-bottom: 1px solid var(--line);
}
.win-title {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  color: var(--signal);
}
.win-min {
  flex: none;
  font-family: var(--mono);
  font-size: 17px;
  line-height: 1;
  color: var(--muted);
  /* Even padding, unlike the underscore this replaced — that sat
     on the baseline and needed the weight underneath it. */
  padding: 9px 14px;
}
.win-min:hover { color: var(--signal); background: rgba(var(--accent-rgb), 0.08); }

.win-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.win-body iframe {
  flex: 1;
  width: 100%;
  min-height: 600px;
  border: none;
  display: block;
}

/* Closed, but still rendered. Taking it out of the layout with
   display:none would let a browser suspend the audio inside it;
   this way the frame carries on and the panel simply can't be
   seen or touched. */
.amp-veil.is-tucked {
  opacity: 0;
  pointer-events: none;
  /* Belt and braces: pointer-events:none SHOULD let touches pass
     straight through, but some mobile browsers still route a
     scroll gesture to a fixed, full-viewport element underneath
     it regardless of that property. Physically collapsing it to a
     corner pixel removes the hit area entirely, so there's simply
     nothing there to catch a thumb on. The iframe inside keeps
     playing either way — audio doesn't depend on being visible or
     full size, only on staying in the page at all. */
  inset: auto 0 0 auto;
  width: 1px;
  height: 1px;
  min-width: 0;
  overflow: hidden;
}

@media (max-width: 719px) {
  #ampModal .modal {
    max-width: 100%;
    /* An explicit height, NOT auto. The player inside is a flex
       child set to fill whatever room it's given — and "auto"
       means the room is decided by the content, so there was
       nothing to fill and it collapsed to a sliver. Naming the
       height gives it something to work with.

       92% rather than the whole screen: enough of a margin to
       still read as a window sitting on the chat, not so much
       that the playlist loses rows. */
    height: 92vh;
    height: 92dvh;
    max-height: 92dvh;
  }
  /* Was 0, which let the frame collapse entirely when the window
     had no fixed height to hand it. */
  .win-body iframe { min-height: 320px; }
  .win-min { padding: 12px 16px 14px; font-size: 17px; }
}

/* ---------- live panel ---------- */
/* An explicit height, not just a ceiling. Without one the window
   sizes to its contents, and the frame inside is set to fill
   whatever it's given — so it settled at its own minimum and no
   larger, however much room was going spare. */
#liveModal .modal {
  max-width: min(92vw, 1040px);
  height: min(88vh, 920px);
  max-height: min(88vh, 920px);
}

.live-modal { display: flex; flex-direction: column; }

.live-frame {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  background: var(--void);
  overflow: hidden;
  margin-top: 4px;
}
.live-frame iframe {
  flex: 1;
  width: 100%;
  min-height: 560px;
  border: none;
  display: block;
}
.live-hint { flex: none; padding: 9px 12px; border-top: 1px solid var(--line); }

#liveOpen { text-decoration: none; text-align: center; }

@media (max-width: 719px) {
  .live-frame iframe { min-height: 300px; }
}

/* ---------- pins ---------- */
.msg.is-pinned .bubble {
  border-left: 2px solid var(--signal);
  padding-left: 9px;
}
.act-pin { letter-spacing: 0.1em; }

.pin-card {
  border: 1px solid var(--line);
  padding: 12px 13px;
  margin-bottom: 10px;
}
.pin-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 8px;
}
.pin-where {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--signal);
  text-align: left;
}
.pin-where:hover { text-decoration: underline; text-underline-offset: 2px; }
.pin-when { font-family: var(--mono); font-size: 10px; color: var(--muted); flex: none; }

.pin-body {
  font-size: 13.5px;
  line-height: 1.55;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.pin-att { font-family: var(--mono); font-size: 11px; color: var(--muted); display: block; margin-top: 5px; }

.pin-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}
.pin-by { flex: 1; font-family: var(--mono); font-size: 10.5px; color: var(--muted); overflow-wrap: anywhere; }


/* ---------- channel rows in the mod panel ----------
   Their own layout now. They were borrowing the report row's,
   which spaces its children to opposite ends — fine for two
   things, but once a drag handle was added the channel name
   ended up marooned in the middle of the row. */
.chan-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 7px;
}
.chan-name {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--signal);
  overflow-wrap: anywhere;
}
.chan-icon {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--signal);
}
.chan-topic-line {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  margin-bottom: 10px;
  overflow-wrap: anywhere;
}

/* ---------- the icon field ---------- */
.chan-icon-label { display: block; margin: 12px 0 6px; }
.chan-icon-row { display: flex; align-items: stretch; gap: 8px; }
.chan-icon-preview {
  flex: none;
  width: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 13px;
  color: var(--signal);
}
.chan-icon-in {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--line);
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 16px;
}
.chan-icon-in:focus { border-color: var(--signal); }
.chan-icon-hint { margin: 7px 0 0; line-height: 1.5; }

/* An icon in the sidebar badge sits a little larger than the
   slash it replaces, since a glyph reads smaller than a character
   at the same size. */
.mark.has-icon { font-size: 15px; }

@media (max-width: 719px) {
  .chan-icon-preview { width: 46px; }
  .chan-icon-in { padding: 12px; }
}

/* ---------- dragging channels ---------- */
/* No transition on the row itself: it animated every reorder,
   which during a drag meant rows sliding around under a pointer
   that had already moved on. */
.chan-row { position: relative; }
.chan-grip {
  flex: none;
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  padding: 2px 9px 2px 0;
  cursor: grab;
  /* Stops the browser scrolling the panel while you drag a row. */
  touch-action: none;
}
.chan-grip:hover { color: var(--signal); }
.chan-row.is-dragging {
  cursor: grabbing;
  border-color: var(--signal);
  opacity: 0.9;
  z-index: 5;
  transition: none;
}
.chan-row.just-moved { border-color: var(--signal); }

@media (max-width: 719px) {
  .chan-grip { font-size: 19px; padding: 6px 12px 6px 0; }
  .pin-card { padding: 13px; }
}


/* ---------- role badges ---------- */
.badge-chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  border: 1px solid var(--line);
  padding: 3px 7px;
  white-space: nowrap;
}

.prof-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 7px;
}
.prof-badges:empty { display: none; }

.badge-pick { display: flex; flex-direction: column; }
.badge-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.badge-row:last-child { border-bottom: none; }
.badge-row input {
  flex: none;
  width: 17px;
  height: 17px;
  accent-color: var(--signal);
  cursor: pointer;
}

.role-count { flex: none; }
#roleColor {
  flex: none;
  width: 42px;
  align-self: stretch;
  padding: 3px;
  border: 1px solid var(--line);
  background: var(--void);
  cursor: pointer;
}

@media (max-width: 719px) {
  .badge-row { padding: 14px 0; }
  .badge-row input { width: 21px; height: 21px; }
  .badge-chip { font-size: 10.5px; padding: 4px 8px; }
}

/* ---------- status ---------- */
.who-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
}
.who-dot.is-online  { background: #4ade80; }
.who-dot.is-away    { background: #facc15; }
.who-dot.is-offline { background: #ef4444; }

.who-head {
  padding: 14px 16px 6px;
  border-top: 1px solid var(--line);
  margin-top: 6px;
}
.who-row.is-dim .who-name { color: var(--muted); }
.who-row.is-dim .av { opacity: 0.55; }
.who-row.is-dim .staff-badge { opacity: 0.6; }

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  border: 1px solid var(--line);
  padding: 4px 9px;
  margin-top: 6px;
}
.status-chip[hidden] { display: none; }
.status-chip.is-online  { color: #4ade80; border-color: #1d6b3f; }
.status-chip.is-away    { color: #facc15; border-color: #6b5610; }
.status-chip.is-offline { color: #ef4444; border-color: #6b1f1f; }

.status-pick { display: flex; flex-direction: column; gap: 1px; }
.status-opt {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: 10px;
  text-align: left;
  border: 1px solid var(--line);
  padding: 11px 13px;
  color: var(--muted);
}
.status-opt + .status-opt { border-top: none; }
.status-opt .status-label { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.12em; }
.status-opt .status-note { font-size: 12px; color: var(--muted); }
.status-opt.is-on { background: rgba(var(--accent-rgb), 0.07); }
.status-opt.is-on.is-online  { color: #4ade80; border-color: #1d6b3f; }
.status-opt.is-on.is-away    { color: #facc15; border-color: #6b5610; }
.status-opt.is-on.is-offline { color: #ef4444; border-color: #6b1f1f; }
.status-opt.is-on .status-note { color: var(--text); }

@media (max-width: 719px) {
  .status-opt { padding: 14px 13px; }
  .status-opt .status-note { display: none; }
}

/* ---------- theme picker ---------- */
.theme-list { display: flex; flex-direction: column; }
.theme-row {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  text-align: left;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
}
.theme-row:last-child { border-bottom: none; }
.theme-row.is-on .theme-name { color: var(--signal); }

.theme-swatch {
  flex: none;
  display: flex;
  border: 1px solid var(--line);
}
.theme-swatch i { display: block; width: 16px; height: 30px; }

.theme-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.theme-name { font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em; }
.theme-note { font-size: 12px; color: var(--muted); }
.theme-tick { flex: none; color: var(--signal); font-size: 14px; }

@media (max-width: 719px) {
  .theme-row { padding: 15px 0; }
  .theme-swatch i { width: 18px; height: 34px; }
}

/* ---------- staff badge ---------- */
/* Bigger and brighter than the first attempt, which sat at the
   same size as the timestamp beside it and vanished into it. */
.staff-badge {
  font-size: 12px;
  color: var(--signal);
  margin-left: 1px;
  flex: none;
}
.staff-badge.is-admin { color: var(--glow); }
.who-row .staff-badge { margin-left: 4px; }
.prof-name + .staff-badge { font-size: 13px; margin-left: 8px; vertical-align: middle; }

/* ---------- channel info editor ---------- */
.chan-edit { margin-top: 10px; }
.chan-edit-in {
  width: 100%;
  border: 1px solid var(--signal);
  background: var(--void);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  padding: 10px 12px;
  resize: vertical;
  outline: none;
}
.chan-edit-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.chan-count { flex: 1; }

/* ---------- reaction picker ----------
   This floats above everything and is placed by script. Without
   position:fixed the coordinates it sets mean nothing and the
   whole thing lands at the bottom of the page, which is exactly
   what it was doing. */
#reactPop {
  position: fixed;
  z-index: 70;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  max-width: 268px;
  padding: 6px;
  border: 1px solid var(--line);
  background: var(--void);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.9);
}
#reactPop[hidden] { display: none; }

.react-opt {
  font-family: var(--emoji);
  font-size: 21px;
  line-height: 1;
  padding: 6px;
  min-width: 38px;
}
.react-opt:hover { background: rgba(var(--accent-rgb), 0.12); }
.react-more {
  font-family: var(--mono);
  font-size: 15px;
  color: var(--muted);
}
.react-more:hover { color: var(--signal); }


@media (max-width: 719px) {
  .react-opt { font-size: 24px; padding: 8px; min-width: 44px; }
  #reactPop { max-width: 300px; }
  }

/* ---------- account details ---------- */
.user-facts { margin-bottom: 11px; }
.user-fact {
  display: flex;
  gap: 10px;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.7;
}
.uf-k { flex: 0 0 76px; color: var(--muted); letter-spacing: 0.1em; }
.uf-v { flex: 1; min-width: 0; overflow-wrap: anywhere; color: var(--text); }
.user-fact.is-email .uf-v { color: var(--glow); }

@media (max-width: 719px) {
  .user-fact { flex-direction: column; gap: 0; line-height: 1.5; margin-bottom: 7px; }
  .uf-k { flex: none; }
}

/* ---------- older messages ---------- */
.load-older-wrap { display: flex; justify-content: center; padding: 4px 0 14px; }
.load-older {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 8px 16px;
}
.load-older:hover { border-color: var(--signal); color: var(--signal); }
.load-older:disabled { opacity: 0.5; cursor: default; }

/* ---------- blocked list ---------- */
.blocked-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.blocked-row:last-child { border-bottom: none; }
.blocked-name { flex: 1; font-family: var(--mono); font-size: 13px; overflow-wrap: anywhere; }

#pvBlock.is-armed { border-color: var(--glow); color: var(--glow); }

/* ---------- gradient names ----------
   The colour is painted as a background and then clipped to the
   shape of the letters, which is the only way to fade text across
   two colours. */
.has-gradient {
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* ---------- profile banner ---------- */
.prof-banner {
  height: 110px;
  margin: -20px -20px 16px;
  background: var(--void) center/cover no-repeat;
  border-bottom: 1px solid var(--line);
}
.prof-banner[hidden] { display: none; }

.banner-edit {
  height: 84px;
  background: var(--void) center/cover no-repeat;
  border: 1px solid var(--line);
  margin-bottom: 8px;
}
.banner-edit.is-empty::after {
  content: "no banner";
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--muted);
  display: grid;
  place-items: center;
  height: 100%;
}
.banner-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- second colour ---------- */
.gradient-label {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 9px;
  cursor: pointer;
}
.gradient-label input {
  width: 16px;
  height: 16px;
  accent-color: var(--signal);
  cursor: pointer;
}
.gradient-label input:disabled { opacity: 0.4; cursor: default; }
#peColor2Row[hidden] { display: none; }

/* ---------- renaming a channel ---------- */
.rename-row { display: flex; gap: 7px; margin-top: 9px; }
.rename-in {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--signal);
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 16px;
}

/* ---------- moderation ---------- */
.mod-btn { color: var(--glow); border-color: var(--deep); }

.mod-body { overflow-y: auto; max-height: min(62vh, 620px); margin-bottom: 4px; }

.report {
  border: 1px solid var(--line);
  padding: 12px;
  margin-bottom: 10px;
}
.report-top {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: baseline;
  margin-bottom: 8px;
}
.report-who { font-family: var(--mono); font-size: 13px; color: var(--signal); }
.report-from { font-family: var(--mono); font-size: 10px; color: var(--muted); }

.report-quote {
  font-size: 13.5px;
  line-height: 1.55;
  border-left: 2px solid var(--line);
  padding-left: 10px;
  margin-bottom: 8px;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.report-reason { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-bottom: 10px; }
.report-reason:empty { display: none; }
.report-acts { display: flex; flex-wrap: wrap; gap: 7px; }

.term-add { display: flex; gap: 7px; margin-bottom: 8px; }
.term-add input {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--line);
  padding: 9px 10px;
  font-family: var(--mono);
  font-size: 16px;
}
.term-add input:focus { border-color: var(--signal); }
.term-sev {
  border: 1px solid var(--line);
  background: var(--void);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
  padding: 0 8px;
  outline: none;
}
.term-hint { margin: 0 0 14px; }

.term-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.term-word { flex: 1; font-family: var(--mono); font-size: 13px; overflow-wrap: anywhere; }
.term-tag { font-family: var(--mono); font-size: 9px; letter-spacing: 0.12em; padding: 3px 6px; }
.term-block { color: var(--void); background: var(--glow); }
.term-flag { color: var(--muted); border: 1px solid var(--line); }

/* ---------- unread ---------- */
.row { align-items: center; }
.unread {
  flex: none;
  align-self: center;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--void);
  background: var(--signal);
  padding: 2px 6px;
  margin-left: 8px;
}
.row.has-unread .name { color: var(--signal); }
.row.has-unread .preview { color: var(--text); }

/* ---------- avatars ---------- */
.av {
  flex: none;
  width: 18px;
  height: 18px;
  background: var(--void) center/cover no-repeat;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 8px;
  color: var(--deep);
  overflow: hidden;
}
.av.av-lg { width: 38px; height: 38px; font-size: 12px; color: var(--signal); }

.who-row { cursor: pointer; }
.who-row:hover .who-name { color: var(--signal); }
.msg-meta .who { cursor: pointer; }
.msg-meta .who:hover { color: var(--signal); }

/* ---------- profile ---------- */
.prof-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}
.prof-pic {
  flex: none;
  width: 78px;
  height: 78px;
  border: 1px solid var(--line);
  background: var(--void) center/cover no-repeat;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 24px;
  color: var(--signal);
}
.prof-id { min-width: 0; }
.prof-name {
  font-family: var(--mono);
  font-size: 17px;
  font-weight: 600;
  color: var(--signal);
  overflow-wrap: anywhere;
}

.prof-pic-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.pic-btn { cursor: pointer; display: inline-block; }
.pic-hint { margin-top: 2px; }

/* The panel is a column: banner and name at the top, buttons
   pinned at the bottom, and the part in between scrolls. It used
   to rely on a fixed height cap, which mobile removed — and the
   flex:1 that was supposed to replace it did nothing, because the
   thing it sat inside was never a flex container. Somebody with
   eight links simply ran off the bottom of the screen. */
#profileView,
#profileEdit {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
#profileView[hidden],
#profileEdit[hidden] { display: none; }

/* The scrolling middle of the view. */
.prof-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* The editor is a stack of fields with no single middle section,
   so the whole thing scrolls and the buttons ride along at the
   end of it. */
#profileEdit { overflow-y: auto; }

/* Head and banner keep their size rather than being squashed to
   make room. */
.prof-banner, .prof-head { flex: none; }
.modal-actions { flex: none; }
.prof-body h3 {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--signal);
  margin: 18px 0 7px;
}
.prof-body h3:first-child { margin-top: 0; }
.prof-bio {
  font-size: 13.5px;
  line-height: 1.6;
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.prof-bio.is-empty, .prof-none { color: var(--muted); font-style: italic; }

.prof-link {
  display: block;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  border: 1px solid var(--line);
  padding: 9px 11px;
  margin-bottom: 7px;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.prof-link:hover { border-color: var(--signal); color: var(--signal); }
.prof-link .lk-url { display: block; color: var(--muted); font-size: 10.5px; margin-top: 3px; }
.prof-link:hover .lk-url { color: var(--deep); }

/* ---------- profile editor ---------- */
.prof-input {
  width: 100%;
  border: 1px solid var(--line);
  background: var(--void);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  padding: 11px 13px;
  resize: vertical;
  outline: none;
}
.prof-input:focus { border-color: var(--signal); }
.char-count { text-align: right; margin-top: 5px; }

.link-row { display: flex; gap: 7px; margin-bottom: 7px; }
.link-row input {
  border: 1px solid var(--line);
  padding: 9px 10px;
  font-family: var(--mono);
  font-size: 16px;
  min-width: 0;
}
.link-row input:focus { border-color: var(--signal); }
.link-row .lk-label { flex: 0 0 34%; }
.link-row .lk-addr { flex: 1; }
.link-row .lk-kill {
  flex: none;
  border: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 13px;
  width: 34px;
}
.link-row .lk-kill:hover { border-color: var(--glow); color: var(--glow); }
.add-link { margin-top: 4px; }

/* ---------- modal ---------- */
.veil {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.9);
  /* Nine-tenths black is right over a dark app. Over a white one
     it turns the whole screen into a black rectangle, so the
     light theme dims rather than blacks out. */
  display: grid;
  place-items: center;
  padding: 24px;
  z-index: 40;
  overflow-y: auto;
}
.veil[hidden] { display: none; }
/* Panels grow with the window instead of staying phone-width on a
   monitor, but stop before they sprawl — a line of text past about
   90 characters is tiring to read, however much room there is.
   Each panel gets a width suited to what's in it. */
.modal {
  width: 100%;
  max-width: min(92vw, 420px);
  max-height: min(88vh, 760px);
  overflow-y: auto;
  border: 1px solid var(--line);
  background: var(--void);
  padding: 22px;
}

/* Reading panels — rules, help, a profile. */
.modal-wide {
  max-width: min(92vw, 680px);
  max-height: min(90vh, 900px);
  display: flex;
  flex-direction: column;
}

/* Each panel gets the width its contents actually need, rather
   than one size for all of them. A form is easier to read narrow;
   a table of users is not. */
#modModal   .modal { max-width: min(94vw, 1000px); max-height: min(92vh, 1000px); }
#infoModal  .modal { max-width: min(92vw, 760px); }
#helpModal  .modal { max-width: min(92vw, 760px); }
#profileModal .modal { max-width: min(92vw, 620px); }
#newModal   .modal { max-width: min(92vw, 480px); }
#reportModal .modal { max-width: min(92vw, 540px); }
#topicModal .modal { max-width: min(92vw, 560px); }
#permModal  .modal { max-width: min(92vw, 520px); }

/* ---------- mobile panel heights ----------
   This sits deliberately AFTER the per-panel rules above, not up
   in the main mobile block. Everything here has the same weight
   as those rules, so the only thing deciding the winner is which
   comes last in the file — and the mobile block is a thousand
   lines earlier, which meant its height was being quietly
   discarded for any panel using .modal-wide. That's INFO and
   HELP: both were still growing to nearly the full screen no
   matter what the earlier rule asked for.

   Reading panels get a shorter ceiling than the rest. A wall of
   text that reaches both ends of the display reads as a page you
   navigated to; keeping it clearly short of that keeps it a card
   sitting on top of the chat. */
@media (max-width: 719px) {
  .modal, .modal-wide {
    max-height: 78vh;
    max-height: 78dvh;
  }

  #infoModal .modal,
  #helpModal .modal,
  #pinsModal .modal,
  #liveModal .modal {
    max-height: 72vh;
    max-height: 72dvh;
  }

  /* The moderation panel is a working tool rather than something
     you read once, so it keeps the extra room. */
  #modModal .modal {
    max-height: 84vh;
    max-height: 84dvh;
  }

  /* The amp gets more room than a reading panel, since it's a
     working window with controls and a playlist — but it no
     longer fills the screen. Restated here because this block
     sits later in the file than the amp's own rules and would
     otherwise overwrite them on its way past. */
  #ampModal .modal {
    height: 92vh;
    height: 92dvh;
    max-height: 92dvh;
  }
}

/* Reading a long block of text at 1000px wide is unpleasant, so
   the prose inside a wide panel stays at a comfortable measure. */
.help-body > * { max-width: 68ch; }
.modal h2 {
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.1em;
  font-weight: 600;
  margin: 0 0 5px;
  color: var(--signal);
}
.modal-sub { color: var(--muted); font-size: 13px; line-height: 1.5; margin: 0 0 18px; }
.modal-actions { display: flex; gap: 10px; margin-top: 18px; }
