/*
Room pages: the full-screen video UI.

What belongs here:
Rules that carry a decision about how a room looks — the sizes, crops, and
overlays that make the stage feel like a video call rather than a web page.
Page-specific by design, so a name only has to make sense within a room.

What does not:
Rules that change what a Bootstrap class does (bootstrap_overrides.css),
generic reusable utilities (bootstrap_additions.css), and components that
belong to some other page (application.css).

Layout vocabulary — source, framing, crop, layout — is defined in SYSTEM.md.
room_layout_controller.js writes data-layout, data-framing, and --video-aspect
onto .room; everything below reacts to those and nothing computes for itself.
*/

/*
Rail beside the room, never above it. Its own element rather than <main>: the
layout hardcodes Bootstrap's .flex-column there, which is declared after
.flex-row and equally !important, so a flex-row passed in via main_class
loses the tiebreak and the rail stacks on top.
*/
.room-page {
  display: flex;
  flex-direction: row;
  flex-grow: 1;
  min-height: 0;
  overflow: hidden;
}

/*
The room's layer stack, declared in one place so the order is readable without
hunting. Tens, so a layer can be inserted later without renumbering.

ONLY IMMERSIVE STACKS. In beside and below every piece of UI is on the same
plane, in normal flow with its own background — nothing floats over anything,
and these values go unused.

Two rules that are easy to get wrong, both explained in SYSTEM.md:
- Layers must be SIBLINGS. filter and transform each create a stacking
  context, and we use both (.overlay-shadow, the mirrored preview), so a
  nested layer is sealed in and cannot rise above its parent's siblings.
- Transient layers pass clicks through (pointer-events: none), with individual
  interactive children turning it back on. Confetti that eats a click while
  someone is typing is worse than no confetti.

There is no layer for face effects: those are composited on the streamer's
device and muxed into the outgoing track, so they are part of the picture
rather than something positioned over it.

Nothing uses these yet. They are here so the first UI element that lands has a
slot to go in instead of inventing a number.
*/
.room {
  --z-placeholder: 0;
  --z-video: 10;
  --z-chat: 30;
  --z-composer: 40;
  --z-room-ui: 50;
  --z-banner: 60;
  --z-creator-ui: 70;
  --z-celebration: 80;

  display: flex;
  flex-grow: 1;
  isolation: isolate;
  min-width: 0;
  overflow: hidden;
  position: relative;
}

/*
The stage never scrolls and never forces the room taller.
min-* 0 because a flex item will not shrink below its content otherwise, and
the content here is a video with intrinsic dimensions.
*/
/*
The stage stacks rather than lays out: placeholder underneath, video over it,
both filling. So a <video> with no stream yet — the broadcaster before they
start a camera, a viewer before the first track arrives — is simply
transparent and reveals what is under it, with nothing to toggle.
*/
.room-stage {
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  position: relative;
}

.room-video {
  display: block;
  height: 100%;
  inset: 0;
  position: absolute;
  width: 100%;
  z-index: var(--z-video);
}

/*
LAYER 0 — the stage with no picture on it. Never takes a click: there is
nothing here to press, and in immersive this spans the whole room.
*/
.room-placeholder {
  align-items: center;
  display: flex;
  inset: 0;
  justify-content: center;
  padding: 2rem;
  pointer-events: none;
  position: absolute;
  z-index: var(--z-placeholder);
}

.room-placeholder-message {
  align-items: center;
  color: var(--bs-secondary-color);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0;
  max-width: 22rem;
  text-align: center;
  text-wrap: balance;
}

/*
IMMERSIVE — aspects agree closely enough that the leftover is not worth
splitting. Video fills every edge and crops, the way FaceTime and TikTok do;
by definition we are cropping less than the minimum, so little is lost. Chat
floats on top rather than taking space away.
*/
.room[data-layout="immersive"] .room-stage {
  flex: 1 1 auto;
}

.room[data-layout="immersive"] .room-video {
  object-fit: cover;
}

/*
Chat floats over the picture, anchored to the bottom so messages rise off the
floor rather than hanging from a fixed box.

pointer-events is the fiddly part: the layer spans the whole width, so a solid
one would swallow every click meant for the video. The layer passes clicks
through and the log itself takes them back, which keeps the log scrollable
while the empty space above it stays transparent to the touch.

The scrim is what makes white text survive an arbitrary bright frame — a
gradient rather than a flat panel so it fades out where there is nothing to
read, and taller than the log so text never crosses its edge.
*/
.room[data-layout="immersive"] .room-chat {
  background: linear-gradient(to top, rgb(0 0 0 / 70%), rgb(0 0 0 / 0%));
  bottom: 0;
  color: #fff;
  inset-inline: 0;
  justify-content: flex-end;
  max-height: 30%;
  padding-top: 3rem;
  pointer-events: none;
  position: absolute;
  z-index: var(--z-chat);
}

.room[data-layout="immersive"] .room-chat-log {
  flex: 0 1 auto;
  pointer-events: auto;
}

/*
The message partial is written for a normal page, so its Bootstrap text and
link colours have to be pulled back to white over the video.
*/
.room[data-layout="immersive"] .room-chat-log a,
.room[data-layout="immersive"] .room-chat-log .text-secondary {
  color: #fff;
}

/*
The guest note and the unavailable-chat lines sit on the scrim rather than on
a panel, so they need the same treatment as the messages above them.
*/
.room[data-layout="immersive"] .room-composer-note,
.room[data-layout="immersive"] .room-composer-note a {
  color: #fff;
}

/*
BESIDE — portrait video in a wide window. The stage takes exactly the video's
shape at full height (hence --video-aspect), chat takes the leftover up to a
cap, and the pair centers with background either side. Uncapped, a 1920 window
would give a 608px video against a 1312px chat.
*/
.room[data-layout="beside"] {
  flex-direction: row;
  justify-content: center;
}

.room[data-layout="beside"] .room-stage {
  aspect-ratio: var(--video-aspect);
  flex: 0 1 auto;
  height: 100%;
}

.room[data-layout="beside"] .room-chat {
  flex: 1 1 20rem;
  max-width: 45rem;
}

/*
Split layouts: an ordinary panel, so the log fills it and scrolls inside
rather than hugging a bottom edge. No scrim — nothing is over the video here.
*/
.room[data-layout="beside"] .room-chat-log,
.room[data-layout="below"] .room-chat-log {
  flex: 1 1 auto;
}

/*
BELOW — landscape video in a tall window. Video across the top at full width,
chat gets everything under it. No cap here: a chat that is too tall is just
more history, where one that is too wide is unreadable line lengths.
*/
.room[data-layout="below"] {
  flex-direction: column;
}

.room[data-layout="below"] .room-stage {
  aspect-ratio: var(--video-aspect);
  flex: 0 1 auto;
  width: 100%;
}

.room[data-layout="below"] .room-chat {
  flex: 1 1 12rem;
  min-height: 0;
}

/*
Both split layouts show the whole frame: chat already has its own space, and
the streamer may be demonstrating something that must not be cropped away.
*/
.room[data-layout="beside"] .room-video,
.room[data-layout="below"] .room-video {
  object-fit: contain;
}

/*
LAYER 50 — the room's own chrome: the way out, whose room this is, and how
many people are in it. One strip across the top of the stage in every layout.

Passes clicks through, since it spans the full width and most of it is empty
space over the picture; the two things you can actually press take them back.

The scrim is not decoration. With no stream the stage shows the placeholder on
the page background, so white text would vanish in light mode — the gradient
is what makes this readable over anything, video or not.
*/
.room-header {
  align-items: flex-start;
  background: linear-gradient(to bottom, rgb(0 0 0 / 55%), rgb(0 0 0 / 0%));
  color: #fff;
  display: flex;
  gap: 0.75rem;
  inset-inline: 0;
  padding: 0.5rem 0.75rem 2.5rem;
  pointer-events: none;
  position: absolute;
  top: 0;
  z-index: var(--z-room-ui);
}

.room-escape {
  color: #fff;
  flex: 0 0 auto;
  line-height: 1;
  padding: 0.25rem;
  pointer-events: auto;
}

.room-escape:hover,
.room-escape:focus-visible {
  color: #fff;
  opacity: 0.7;
}

/*
min-width 0 so the title can truncate: a flex item will not shrink below its
content without it, and a long room title would otherwise push the occupancy
count off the edge.
*/
.room-identity {
  flex: 1 1 auto;
  min-width: 0;
}

.room-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.room-byline {
  align-items: center;
  display: flex;
  font-size: 0.875rem;
  gap: 0.375rem;
  margin: 0;
  opacity: 0.85;
}

.room-byline a {
  color: #fff;
  pointer-events: auto;
  text-decoration: none;
}

/*
Takes clicks back from the strip, which passes them through — these are inert
buttons, but a disabled control still wants its tooltip on hover.
*/
.room-media-state {
  flex: 0 0 auto;
  pointer-events: auto;
}

.room-occupancy {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}

.room-invite {
  background: none;
  border: 0;
  color: #fff;
  flex: 0 0 auto;
  font-size: 0.875rem;
  line-height: 1;
  padding: 0.25rem;
  pointer-events: auto;
  white-space: nowrap;
}

.room-invite:hover,
.room-invite:focus-visible {
  opacity: 0.7;
}

/*
RAIL — the bonus nav, in the margin the wide layouts leave over. Keyed to the
window rather than to that margin on purpose: deriving it from the leftover
space would make the two mutually dependent (margin sizes rail, rail changes
width, width changes layout, layout changes margin). Keyed to the window, the
rail simply takes its column first and .room measures what remains.

Everything in it is duplicated elsewhere. Nothing may live only here, since it
is absent at most window sizes.
*/
.room-rail {
  display: none;
}

@media (min-width: 90rem) {
  .room-rail {
    align-items: center;
    display: flex;
    flex: 0 0 4rem;
    flex-direction: column;
    gap: 0.25rem;
    overflow-y: auto;
    padding-block: 0.75rem;
  }
}

.room-rail-link {
  border-radius: 0.375rem;
  color: var(--bs-secondary-color);
  display: block;
  line-height: 1;
  padding: 0.625rem;
}

.room-rail-link:hover,
.room-rail-link:focus-visible {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

/*
LAYER 30 — chat. The only element that changes kind between layouts: an
overlay floating on the video in immersive, an ordinary panel in beside and
below. Everything specific to one of those lives in that layout's block above;
this is what they share.
*/
.room-chat {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}

.room-chat-log {
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
}

/*
LAYER 70 — creator controls, the owner's only. Two positions for two states:
the start control centred while off air, because going live is the whole job
then; a strip along the top once the picture matters more than the buttons.

Both sit inside .room-stage, so they stay over the video rather than reaching
across the chat panel in the split layouts.
*/
.room-creator-start {
  inset: 0;
  place-content: center;
  pointer-events: none;
  position: absolute;
  z-index: var(--z-creator-ui);
}

/*
display is set here rather than in the markup because broadcaster_controller
toggles the `hidden` attribute, and [hidden] in bootstrap_overrides.css is
display: none !important — so this can safely claim the element's display
without fighting the controller.
*/
.room-creator-start:not([hidden]) {
  display: grid;
}

.room-creator-start .btn-group {
  pointer-events: auto;
  transform: translateY(4rem);
}

/*
Below the header strip, aligned right so it stays clear of the title. The
buttons are light-on-video rather than themed: this floats over a picture,
not over a panel.
*/
.room-creator-bar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  inset-inline: 0.75rem;
  justify-content: flex-end;
  pointer-events: none;
  position: absolute;
  top: 3.5rem;
  z-index: var(--z-creator-ui);
}

.room-creator-bar > * {
  pointer-events: auto;
}

.room-creator-status,
.room-creator-visibility {
  align-items: center;
  color: #fff;
  display: flex;
  font-size: 0.875rem;
  gap: 0.25rem;
}

.room-creator-visibility {
  opacity: 0.75;
}

.room-creator-close {
  margin: 0;
}

/*
LAYER 60 — banners, at the top of the chat panel. In flow rather than
positioned, so they sit above the log wherever the panel is: over the video in
immersive, in the column in beside, in the row in below.

Spacing lives on the children rather than as padding here, so an empty
container collapses to nothing instead of leaving a gap on every room.

Takes clicks back from the chat layer, which passes them through — one of
these carries a Resend button.

Bootstrap's .alert brings its own background, so nothing here needs a scrim.
*/
.room-banners {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  pointer-events: auto;
}

.room-banners > * {
  margin: 0.75rem 0.75rem 0;
}

/*
LAYER 80 — celebrations. Tips arrive here by broadcast and remove themselves
after a few seconds; empty the rest of the time.

Never takes a click. It spans the stage, and a burst that swallows a tap while
someone is trying to press something underneath is worse than no burst at all.
*/
.room-celebrations {
  align-items: center;
  bottom: 25%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  inset-inline: 0;
  pointer-events: none;
  position: absolute;
  z-index: var(--z-celebration);
}

.room-celebration {
  align-items: center;
  animation: room-celebration-enter 0.4s ease-out;
  background: rgb(0 0 0 / 70%);
  border-radius: 2rem;
  color: #fff;
  display: flex;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
}

.room-celebration.leaving {
  opacity: 0;
  transition: opacity 0.5s ease-in;
}

@keyframes room-celebration-enter {
  from {
    opacity: 0;
    transform: translateY(1rem);
  }
}

/*
Nobody asked for motion. A tip is legible standing still, and this is exactly
the kind of thing that makes some readers ill.
*/
@media (prefers-reduced-motion: reduce) {
  .room-celebration {
    animation: none;
  }
}

/*
Tipping the streamer. Takes clicks back from the chat layer, which passes them
through, and its balance line gets the same white-on-scrim treatment the rest
of the chat panel gets in immersive.
*/
.room-tip {
  flex: 0 0 auto;
  padding: 0.75rem 0.75rem 0;
  pointer-events: auto;
}

.room[data-layout="immersive"] .room-tip .form-text,
.room[data-layout="immersive"] .room-tip .form-text a {
  color: #fff;
}

/*
LAYER 40 — the composer, always on the floor of the chat panel however much
or little is above it. Takes clicks back from the chat layer, which passes
them through so the video stays reachable in immersive.
*/
.room-composer {
  flex: 0 0 auto;
  pointer-events: auto;
}

.room-composer-note {
  font-size: 0.875rem;
  margin: 0;
  padding: 0.5rem 0.75rem;
}

/*
One row by default, growing with the text where the browser supports it
(Chrome, Safari 26.2+, Firefox mid-2026) and simply scrolling where it does
not. Capped so a pasted essay cannot eat the panel. No Bootstrap equivalent —
there are no field-sizing or resize utilities.
*/
.chat-composer {
  field-sizing: content;
  max-height: 8rem;
  resize: none;
}

/*
Shift+Enter puts real newlines in a message, and HTML would collapse them.
Bootstrap has text-wrap and text-break but no white-space utility.
*/
.chat-content {
  white-space: pre-wrap;
}
