/* The chat board: the room's own chat, big enough to read from the camera,
   with the four numbers that matter along the top.

   It exists for one situation. The stream is on the main monitor and this
   panel is beside it, so chat does not have to share the screen with the
   thing being broadcast. Everything here is subordinate to the river in the
   middle: the stats strip is four tiles and stops, the room rail is one band
   at the foot, and nothing else competes.

   Same tokens as wall.css, verbatim, so flipping between boards does not feel
   like flipping between products. ORANGE is money, BLUE is audience. */

:root {
  color-scheme: dark;
  font-family: "Instrument Sans", Inter, ui-sans-serif, system-ui, sans-serif;

  --plane: #0e0f12;
  --surface: #17181c;
  --surface-2: #1c1e23;
  --line: #262931;
  --line-strong: #343841;

  --ink: #eceef2;
  --ink-2: #a3a7b0;
  --ink-3: #71757e;

  --money: #ff8a4c;
  --money-fill: #ef6c34;
  --money-soft: rgba(239, 108, 52, .16);
  --audience: #4a9bdf;
  --audience-soft: rgba(74, 155, 223, .14);

  --good: #3fcf3f;
  --warn: #f0b429;
  --critical: #f06a6a;

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --radius: 12px;
  --gap: 12px;
  --pad: 14px;

  /* Everything inside the river is sized off this one number, because
     "make the chat bigger" should be one edit and not twenty. 1 is the
     size it shipped at; 1.5 is what it reads at from across the desk.
     2 since 11 Oct 2026 (the owner: "make the text on the chat dashboard a bit
     bigger", PIM-122), a third up on 1.5. */
  --chat-scale: 2;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; }
body { background: var(--plane); color: var(--ink); -webkit-font-smoothing: antialiased; }

main {
  width: 100vw; height: 100vh; padding: var(--gap); gap: var(--gap);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* /chat: the ribbon, the stats strip, the river, the foot. The strip takes
     120px (PIM-270, 13 Oct 2026); the river keeps everything else. */
  grid-template-rows: 46px auto minmax(0, 1fr) 24px;   /* strip: one or two rows (13 Oct 2026) */
}
/* /roomboard: the numbers, then one column holding the private band (some
   nights), the couples list at its fixed height and the room rail taking
   whatever is left. */
body[data-view="room"] main { grid-template-rows: 46px 128px minmax(0, 1fr) 24px; }
body[data-view="room"] .couples { flex: none; height: 860px; }
body[data-view="room"] .rail { flex: 1; }

.fig {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-weight: 600; letter-spacing: -.01em; line-height: 1; white-space: nowrap;
}
.hero-fig { font-size: 62px; }
.fig.lg { font-size: 34px; }
.money { color: var(--money); }
.audience { color: var(--audience); }
.lbl {
  font-size: 13px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
}
.lbl.dim { color: var(--ink-3); opacity: .7; }

/* ---- ribbon ----------------------------------------------------------- */
.ribbon {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 2px;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand strong { font-size: 22px; letter-spacing: -.01em; }
.brand #status {
  font-size: 13px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
}
.mic {
  background: var(--good); color: #0b0b0c; font-weight: 700;
  font-size: 18px; letter-spacing: .07em; text-transform: uppercase;
  padding: 2px 12px; border-radius: 6px;
}
.mic[hidden] { display: none; }
.mic.muted { background: var(--critical); animation: mic-shout 1.2s ease-in-out infinite; }
@keyframes mic-shout { 50% { box-shadow: 0 0 0 5px rgba(240, 106, 106, .3); filter: brightness(1.25); } }
.live-dot {
  width: 11px; height: 11px; border-radius: 50%; background: var(--ink-3);
}
.live-dot.on { background: var(--good); animation: breathe 2.4s ease-in-out infinite; }
@keyframes breathe { 50% { opacity: .35 } }
.ribbon-meta {
  display: flex; align-items: baseline; gap: 16px;
  font-size: 15px; color: var(--ink-2);
}
.ribbon-meta b { font-family: var(--mono); font-weight: 500; color: var(--ink-2); }

/* ---- stats ------------------------------------------------------------ */
.stats { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: var(--gap); }
.tile {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--pad); display: flex; flex-direction: column;
  justify-content: center; gap: 8px; min-width: 0; overflow: hidden;
}

/* ---- the strip (PIM-270, 13 Oct 2026) -------------------------------- */
/* the owner: "can you please add some of the stats from /tv onto the chat board
   at the top". One row of figures big enough to read from the bed, /tv's
   colours: money orange, audience blue, everything else ink. The goal bar is
   the only status colour. Cells that have nothing to say are hidden and the
   rest share the width. */
/* Two rows when needed (the owner, 13 Oct 2026: "the stats at the top of the chat board are all mangled. they can go
   over two lines"): a grid of cells at least a third wide, the 1px gap on the line colour draws the dividers. */
.strip {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 0;
  min-width: 0; overflow: hidden;
}
.strip .cell {
  min-width: 0; padding: 10px 18px; background: var(--surface);
  box-shadow: 1px 0 0 var(--line), 0 1px 0 var(--line);   /* dividers right and below; the strip edge hides the outer ones */
  display: flex; flex-direction: column; justify-content: center; gap: 6px;
}
.strip .cell > * { flex: none; }
.strip .cell[hidden] { display: none; }

.sfig {
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600;
  font-size: 50px; line-height: 1; letter-spacing: -.02em; white-space: nowrap; color: var(--ink);
}
.sfig.money, .ssub b.money { color: var(--money); }
.sfig.audience { color: var(--audience); }
/* The overlay bars (PIM-297, 13 Oct 2026): the real numbers behind the two bars viewers see without any. */
.sfig.keep { color: #FF5FA8; }
.sfig.bgoal { color: #B06AFA; font-size: 42px; }
.sfig.keep.low { color: var(--critical); }
.sfig.keep.done { color: var(--ink-2); }
.sfig.word { font-size: 34px; }
.ssub {
  font-size: 20px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; line-height: 1.25;
}
.ssub b { font-family: var(--mono); font-weight: 600; color: var(--ink); line-height: 1; }
.strip .goal-track { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; flex: none; }
.strip .goal-track i { display: block; width: 0; height: 100%; border-radius: inherit; background: var(--money); transition: width .35s ease; }
.strip .goal-track.done i { background: var(--good); }
.strip .goal-track.off i { background: var(--ink-3); }

/* A private show is the one thing that changes what the numbers mean, so it
   gets its own band rather than a chip somewhere in the strip. */
.private {
  background: var(--money-soft); border: 1px solid var(--money-fill);
  border-radius: var(--radius); padding: 10px var(--pad);
  display: flex; align-items: baseline; gap: 14px;
  animation: breathe 2.6s ease-in-out infinite;
}
.private b { font-size: 22px; letter-spacing: .1em; text-transform: uppercase; color: var(--money); }
.private span { font-family: var(--mono); font-size: 20px; color: var(--ink); }

.mid { display: flex; flex-direction: column; gap: var(--gap); min-height: 0; }

/* ---- the river -------------------------------------------------------- */
/* Newest at the bottom, like every chat client anyone has ever used, so the
   eye rests in one place instead of chasing the top of a list. */
.river {
  flex: 1;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 16px; overflow: hidden; min-height: 0;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 2px;
  /* The oldest line is always half off the top. Fade it rather than slice it
     through the middle of the letters. */
  --fade: calc(52px * var(--chat-scale));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--fade));
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--fade));
}
.row {
  display: flex; align-items: baseline;
  gap: calc(10px * var(--chat-scale));
  padding: calc(7px * var(--chat-scale)) 2px;
  border-top: 1px solid transparent; animation: arrive .35s ease-out;
}
@keyframes arrive { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

.row .at { font-family: var(--mono); font-size: calc(15px * var(--chat-scale)); color: var(--ink-3); flex: none; }
.row .who {
  font-size: calc(24px * var(--chat-scale)); font-weight: 600; flex: none;
  max-width: calc(300px * var(--chat-scale));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row .msg { font-size: calc(25px * var(--chat-scale)); line-height: 1.25; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
/* At scale 2 a name and two badges leave a long message a column a few words
   wide. Below 440px it drops under the name and takes the full width
   (PIM-122, 11 Oct 2026). */
.row { flex-wrap: wrap; }
.row .msg { flex: 1 1 440px; }
.row .badge {
  font-size: calc(12px * var(--chat-scale)); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: calc(3px * var(--chat-scale)) calc(7px * var(--chat-scale));
  border-radius: 5px; flex: none;
  /* Baseline, not centre: at this size a long message wraps to two lines, and
     a centred badge drifts down away from the name it belongs to. */
  align-self: baseline;
}
.row.tip .badge, .row.media .badge, .row.fanclub .badge { align-self: center; }
/* One badge shape, one chip colour, and the text carries the meaning. Three
   different washes side by side was the thing that made the river look like a
   toybox: orange belongs to money and nothing else. */
.badge.mod { background: #22242a; color: var(--audience); }
.badge.fc { background: #22242a; color: #43d17d; }
.badge.tonight { background: #22242a; color: var(--money); font-family: var(--mono); }
.badge.spend { background: #22242a; color: var(--ink-3); font-family: var(--mono); }
.row .note { font-size: calc(15px * var(--chat-scale)); color: var(--ink-3); font-style: italic; flex: none; }

/* Names carry Chaturbate's own colour group, the same key the room uses, so
   this panel and the chat tab agree at a glance: green is fan club, purple has
   spent real money in the last fortnight, blue has tokens, grey never will.
   The hexes are lifted off CB's own, which are picked for a white chat
   background and go muddy on this plane. None of them is orange: money keeps
   that to itself, so a coloured name can never be mistaken for a tip. */
.row .who { color: var(--ink-2); }
.who.cg-o  { color: #ffffff; }                     /* the room owner */
.who.cg-m  { color: #ff5c5c; }                     /* moderator */
.who.cg-f  { color: #43d17d; }                     /* fan club */
.who.cg-l  { color: #b57bff; }                     /* dark purple, 1000 tk / 2 weeks */
.who.cg-p  { color: #ff77cc; }                     /* light purple, 250 tk */
.who.cg-tr { color: #5b8cf0; }                     /* dark blue, 50 tk */
.who.cg-t  { color: #74c0f5; }                     /* light blue, has tokens */
.who.cg-g  { color: #6e737d; }                     /* grey, no tokens */
.who.cg-whale { color: var(--ink); }               /* spent big, group not seen yet */

/* Money gets a card. The card is a lift in the surface with an orange rail,
   not an orange wash: a 16% orange over this plane came out muddy brown, and
   with orange text sitting on it the whole line lost contrast. Only a big tip
   gets the wash now, which is what makes it look big. */
.row.tip, .row.media, .row.fanclub {
  background: var(--surface-2); border-radius: 10px;
  padding: calc(12px * var(--chat-scale)) calc(14px * var(--chat-scale));
  margin: 5px 0; border-left: 4px solid var(--money-fill); border-top: 0;
  align-items: center;
  /* Portrait is 1080 wide and the badges never shrink, so a chatty tip used to
     squeeze its own message into a four-word column. Let the row wrap: the
     amount and the name stay on line one, the badges drop under. */
  flex-wrap: wrap;
}
.row.tip .msg, .row.media .msg { flex: 1 1 calc(260px * var(--chat-scale)); }
.row.tip .amount, .row.media .amount, .row.fanclub .amount {
  font-family: var(--mono); font-weight: 600; font-size: calc(38px * var(--chat-scale)); color: var(--money);
  flex: none; line-height: 1;
}
.row.tip .who, .row.media .who, .row.fanclub .who { font-size: calc(26px * var(--chat-scale)); }
.row.tip .msg, .row.media .msg, .row.fanclub .msg { font-size: calc(23px * var(--chat-scale)); color: var(--ink-2); }
.row.tip .bought {
  font-size: calc(21px * var(--chat-scale)); font-weight: 600; color: var(--money); flex: none;
}
.row.tip.big { border-left-width: 8px; background: var(--money-soft); }
.row.tip.big .amount { font-size: calc(50px * var(--chat-scale)); }

.row.follow .msg, .row.enter .msg, .row.subject .msg {
  font-size: calc(21px * var(--chat-scale)); color: var(--ink-2);
}
/* Same card as money, blue rail instead of orange, so audience and money read
   as the same kind of event without sharing a colour. */
.row.enter {
  background: var(--surface-2); border-radius: 10px;
  padding: calc(10px * var(--chat-scale)) calc(14px * var(--chat-scale));
  margin: 4px 0; border-left: 4px solid var(--audience);
}
.row.follow { opacity: .85; }
.row.subject { opacity: .8; }
.row.subject .msg { font-style: italic; font-size: calc(19px * var(--chat-scale)); }

/* A private message: the same card as money and walk-ins with its own rail,
   so it cannot be mistaken for something the room can read. Ours are dimmer. */
.row.pm {
  background: rgba(181, 123, 255, .10); border-radius: 10px;
  padding: calc(10px * var(--chat-scale)) calc(14px * var(--chat-scale));
  margin: 4px 0; border-left: 4px solid #b57bff; border-top: 0;
}
.row.pm .tag {
  font-size: calc(12px * var(--chat-scale)); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: calc(3px * var(--chat-scale)) calc(7px * var(--chat-scale));
  border-radius: 5px; flex: none; background: #b57bff; color: #0b0b0c;
}
.row.pm.out { background: var(--surface-2); }
.row.pm.out .tag { background: #22242a; color: #b57bff; }
.row.pm.out .msg { color: var(--ink-2); }

.river .empty {
  margin: auto; text-align: center; color: var(--ink-3); font-size: calc(20px * var(--chat-scale));
}

/* ---- the rival tracker ------------------------------------------------ */
/* Blue throughout: this is audience, not money. Yellow is the one exception,
   and it only ever means somebody else is selling tickets. */
.couples {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px var(--pad); min-height: 0; overflow: hidden;
  display: grid; grid-template-rows: 54px 40px minmax(0, 1fr) 26px; gap: 8px;
}
.cp-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 18px; min-width: 0; }
.cp-us { display: flex; align-items: baseline; gap: 10px; }
.cp-us .fig { font-size: 44px; }
.cp-of { font-size: 16px; color: var(--ink-2); }
.cp-move { font-family: var(--mono); font-size: 18px; font-weight: 600; }
.cp-near { min-width: 0; display: flex; flex-direction: column; gap: 2px; font-size: 16px; color: var(--ink-2); }
.cp-near > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cp-near b { color: var(--ink); font-weight: 600; }
.cp-near .num { font-family: var(--mono); color: var(--audience); }
.up { color: var(--good); }
.down { color: var(--critical); }
.flat { color: var(--ink-3); }

/* One room at a time, its whole subject and the numbers the grid has no room
   for. Cycles through the rooms either side of us and anyone selling. */
.cp-spot {
  background: var(--surface-2); border-radius: 9px; padding: 0 12px;
  display: flex; align-items: center; gap: 12px; min-width: 0; overflow: hidden;
  font-size: 17px; color: var(--ink-2); border-left: 4px solid var(--audience);
}
.cp-spot.sell { border-left-color: var(--warn); }
.cp-spot > * { flex: none; white-space: nowrap; }
.cp-spot .who { color: var(--ink); font-weight: 600; }
.cp-spot .meta { font-family: var(--mono); font-size: 15px; color: var(--ink-3); }
.cp-spot .subj { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-style: italic; }
.cp-spot.fade { animation: arrive .4s ease-out; }

.cp-grid {
  min-height: 0; display: grid; grid-auto-flow: column;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: auto repeat(15, minmax(0, 1fr)); column-gap: 14px;
}
.cp-row {
  display: grid; align-items: center; column-gap: 8px; min-width: 0;
  grid-template-columns: 34px 34px minmax(0, 1fr) 62px 52px 64px;
  border-bottom: 1px solid var(--line); padding: 0 4px;
}
.cp-row .pos { font-family: var(--mono); font-size: 17px; color: var(--ink-3); text-align: right; }
.cp-row .mv { font-family: var(--mono); font-size: 13px; font-weight: 600; white-space: nowrap; }
.cp-row .nm { min-width: 0; display: flex; align-items: center; gap: 6px; }
.cp-row .nm span:first-child { font-size: 19px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.cp-row .chip {
  flex: none; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; background: #22242a;
}
.cp-row .chip.sell { color: var(--warn); }
.cp-row .chip.new { color: var(--good); }
.cp-row .vw { font-family: var(--mono); font-size: 18px; font-weight: 600; color: var(--audience); text-align: right; }
.cp-row .dv { font-family: var(--mono); font-size: 14px; text-align: right; }
.cp-row svg { width: 64px; height: 22px; }
.cp-row svg polyline { fill: none; stroke: var(--audience); stroke-width: 1.6; opacity: .8; }
.cp-row.us { background: var(--audience-soft); border-radius: 7px; border-bottom-color: transparent; }
.cp-row.us .pos, .cp-row.us .nm span:first-child { color: var(--ink); }
.cp-row.sell .nm span:first-child { color: var(--warn); }
.cp-row.cols { border-bottom-color: var(--line); padding-bottom: 4px; }
.cp-row.cols span {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap; text-align: right;
}
.cp-row.cols span:nth-child(2), .cp-row.cols span:nth-child(3) { text-align: left; }
.cp-grid .empty { grid-row: 1 / -1; grid-column: 1 / -1; place-self: center; color: var(--ink-3); font-size: 18px; }
.cp-foot {
  display: flex; align-items: center; gap: 8px; min-width: 0; overflow: hidden;
  font-size: 15px; color: var(--ink-3); white-space: nowrap;
}
.cp-foot b { color: var(--ink-2); font-weight: 600; }

/* ---- the room rail ---------------------------------------------------- */
.rail {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--pad); display: flex; flex-direction: column; gap: 10px; min-height: 0;
}
.rail-head { display: flex; justify-content: space-between; align-items: baseline; }
.rail-grid {
  flex: 1; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr)); gap: 8px; min-height: 0; overflow: hidden;
  /* chat.js sets the row count from the height it actually got. */
}
.person {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 9px;
  padding: 8px 10px; display: flex; flex-direction: column; justify-content: center;
  gap: 4px; min-width: 0; overflow: hidden;
}
/* Column flex items shrink by default, which crops the descenders off a
   username rather than clipping the card. */
.person > * { flex: none; }
.person .name {
  font-size: 18px; font-weight: 600; white-space: nowrap; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis;
}
/* Same colour key as the river, so the room rail reads like the room's own
   user list rather than a second, private ranking of the same people. */
.person .name.cg-o  { color: #ffffff; }
.person .name.cg-m  { color: #ff5c5c; }
.person .name.cg-f  { color: #43d17d; }
.person .name.cg-l  { color: #b57bff; }
.person .name.cg-p  { color: #ff77cc; }
.person .name.cg-tr { color: #5b8cf0; }
.person .name.cg-t  { color: #74c0f5; }
.person .name.cg-g  { color: #6e737d; }
.person .nums { display: flex; gap: 10px; align-items: baseline; line-height: 1.3; }
.person .tonight { font-family: var(--mono); font-size: 19px; font-weight: 600; color: var(--money); }
.person .life { font-family: var(--mono); font-size: 15px; color: var(--ink-3); }
.person.fc { border-color: var(--money-fill); }
.person.quiet .tonight { color: var(--ink-3); }
.rail .empty { color: var(--ink-3); font-size: 18px; grid-column: 1 / -1; place-self: center; }

/* ---- foot + heartbeat ------------------------------------------------- */
.foot {
  display: flex; justify-content: space-between; align-items: center;
  padding: 0 4px; font-size: 12px; color: var(--ink-3); letter-spacing: .04em;
}
.foot b { font-family: var(--mono); font-weight: 500; }

.sweep { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 5; background: var(--line); }
.sweep i { display: block; height: 100%; width: 0; background: var(--money-fill); opacity: .85; }
.sweep i.run { animation: sweep var(--sweep, 2s) linear forwards; }
@keyframes sweep { from { width: 0 } to { width: 100% } }

@keyframes bump {
  0% { transform: translateY(0); filter: brightness(1) }
  22% { transform: translateY(-3px); filter: brightness(1.7) }
  100% { transform: translateY(0); filter: brightness(1) }
}
.bump { animation: bump .85s ease-out; }
