/* These seven (plus --on-accent) are the whole themeable surface -- a
   server admin can override every one of them (see /theme.css, filled in
   from Manage > Settings > Theme). Everything else below is either a fixed
   semantic colour (--ok, --danger: status, not brand -- see the Theme panel's
   own note on why those two stay put) or computed FROM these seven with
   color-mix(), so the whole app re-derives its raised surfaces, recessed
   fields and shading from just the handful of colors an admin actually
   picks, rather than each needing its own setting. Mixing toward --accent
   or --border (not flatly toward black/white) is deliberate: it's the one
   direction that reads right whether the base colors end up light or dark,
   so a light theme doesn't need a whole second formula. */
:root {
  --bg: #1a1410;
  --bg-section: #241c16;
  --border: #3b2e24;
  --text: #f1e6d8;
  --text-dim: #a8998a;
  --accent: #c8873a;
  --on-accent: #1a1206; /* text/icons drawn on an --accent background */
  --ok: #6fae6b;
  --danger: #b8503f;
  --danger-text: color-mix(in srgb, var(--danger) 55%, white);
  --accent-hover: color-mix(in srgb, var(--accent) 85%, white);
  --bg-input: color-mix(in srgb, var(--bg) 85%, var(--border) 15%);
  /* Card background: the small items inside a section (member tiles, facts,
     thumbnails). Follows the input shade until a theme sets it. */
  --bg-card: var(--bg-input);
  --surface: color-mix(in srgb, var(--bg-section) 90%, var(--accent) 10%);
  --surface-hover: color-mix(in srgb, var(--bg-section) 78%, var(--accent) 22%);
  --shade: color-mix(in srgb, var(--bg) 85%, var(--border) 15%);
  /* Layout: the one height every module header strip has, so headers in
     neighbouring columns line up. Nothing else sets a module header height
     (tools/check-room-layout.mjs enforces it). */
  --module-header-h: 42px;
  /* The height of every control in the shared bottom row (the call toolbar's buttons, the chat's input and
     Send, each module's quick-add), so they line up whatever cell they are in. */
  --bar-control-h: 38px;
  /* The one height a module's optional toolbar row (under the titlebar) has, wherever it appears. */
  --toolbar-h: 34px;
  /* Themeable on their own (Manage > Theme); each follows the tokens above
     until a theme sets it. */
  --header-bg: var(--bg);
  --header-text: var(--text);
  /* The two rows of the header, each its own colour (see architecture-navigation.md): the primary nav's middle zone
     is --header-bg itself, its left and right zones a shade darker, and the secondary nav (the space) its own,
     between the header and the page. Derived from --header-bg until a theme sets them. */
  --nav-primary-bg: var(--header-bg);
  --nav-primary-edge-bg: rgba(0, 0, 0, 0.03); /* a whisper over the header colour, light or dark */
  --nav-secondary-bg: rgba(0, 0, 0, 0.02);
  /* --icon has no default here: where it is used, var(--icon, <fallback>) picks
     the fallback that suits the surface (dim text in the page, the header
     text color in the header) until a theme sets it. */
  --icon-hover: var(--accent);
  --primary-hover: var(--accent-hover);
  --secondary: var(--surface);
  --secondary-text: var(--text);
  --secondary-hover: var(--surface-hover);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: var(--text);
  background: var(--bg);
}

* {
  box-sizing: border-box;
}

/* Scrollbars in the app's own colours instead of the browser/OS default. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) var(--bg-section);
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track {
  background: var(--bg-section);
}

*::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 6px;
  border: 2px solid var(--bg-section);
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
}

[hidden] {
  display: none !important;
}

a {
  color: var(--accent);
  text-decoration: none;
}

/* Focus: the accent, not the browser's default blue. Text fields get a soft
   ring; buttons and links an outline that only shows for keyboard focus. */
input:not([type='checkbox']):not([type='radio']):not([type='color']):not([type='file']):not([type='range']):focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
}

button:focus-visible,
a:focus-visible,
input[type='checkbox']:focus-visible,
input[type='radio']:focus-visible,
input[type='range']:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input[type='checkbox'],
input[type='radio'],
input[type='range'] {
  accent-color: var(--accent);
}

a:hover {
  color: var(--accent-hover);
}

/* Three zones: left (the logo, where you are), middle (the core navigation, centred on the row whatever the other two
   hold), right (the system's actions and information). See architecture-navigation.md. */
.topbar {
  position: sticky; /* stays at the top while the page scrolls */
  top: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  /* Installed to the home screen, a page runs under the status bar (black-translucent), so the
     header starts below it; the inset is 0 in a browser tab. */
  padding: env(safe-area-inset-top) 16px 0;
  min-height: calc(44px + env(safe-area-inset-top)); /* border-box: 43px of content plus the 1px rule below */
  border-bottom: 1px solid var(--border);
  color: var(--header-text);
  background: var(--nav-primary-bg);
}

/* The left and right zones are a shade darker than the middle, the full height of the row. */
.topbar .nav-left,
.topbar .nav-right {
  align-self: stretch;
  display: flex;
  align-items: center;
  background: var(--nav-primary-edge-bg);
}

.topbar .nav-left {
  padding-right: 14px;
  margin-left: -16px;
  padding-left: 16px;
}

.topbar .nav-right {
  padding-left: 14px;
  margin-right: -16px;
  padding-right: 16px;
}

/* The server icon runs the full height of the header, flush to its top,
   bottom and left edges (hence the negative margins cancelling .topbar's
   own padding on those three sides) rather than sitting inline with the
   name as a small rounded square. Fixed to match .topbar's own
   min-height exactly -- auto/stretch sizing an <img> against aspect-ratio
   with no other definite dimension in the flex line let it render at its
   full natural (1024x1024) size instead. */
.topbar .brand .icon {
  flex-shrink: 0;
  width: 43px;
  height: 43px; /* the topbar's whole content height, so there is no gap above or below */
  border-radius: 0;
  object-fit: cover;
  margin: 0 0 0 -16px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
}

.topbar .nav-left {
  min-width: 0;
  justify-self: start;
}

.topbar .nav-middle {
  justify-self: center;
}

.topbar .nav-right {
  justify-self: end;
}

/* The core navigation: icon and name, the same for the rooms link and each module's own page. */
.core-nav {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.core-link,
.core-nav .module-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: auto;
  height: 30px;
  padding: 0 10px;
  border-radius: 6px;
  color: var(--icon, color-mix(in srgb, var(--header-text) 75%, transparent));
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

.core-link:hover,
.core-nav .module-nav-link:hover {
  color: var(--icon-hover);
  background: color-mix(in srgb, var(--icon-hover) 14%, transparent);
  text-decoration: none;
}

.core-nav .module-nav-label {
  display: inline;
}

/* The host console (admin.<base domain>, public/host.html): the primary nav's left zone only -- the console is the
   deployment's own door, with no spaces to navigate to and no environment's profile or Manage to reach. */
body.host-console .topbar .nav-middle,
body.host-console .topbar .nav-right,
body.host-console .topbar .nav-toggle {
  display: none;
}

body.host-console .host-logo {
  display: block;
  height: 60px; /* the file has wide transparent margins; the bar's own height is unchanged */
  width: auto;
  margin: -12px 0;
}

body.host-console .tenant .section-head h2 code {
  margin-left: 8px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 400;
}

body.host-console #host-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 16px;
  margin: 8px 0 0;
}

body.host-console #host-facts dt {
  color: var(--text-dim);
  font-size: 12px;
  padding-top: 2px;
}

body.host-console #host-facts dd {
  margin: 0;
  line-height: 1.5;
}

/* The environments: spaced cards, each with an edge in its status's colour, the facts as tiles with a bar
   where the plan has a cap, and the plan form opening under a rule. */
body.host-console .tenants {
  display: grid;
  gap: 16px;
}

body.host-console .tenant {
  border-left: 4px solid var(--ok);
}

body.host-console .tenant[data-status="pastDue"] {
  border-left-color: var(--danger);
}

body.host-console .tenant[data-status="suspended"] {
  border-left-color: var(--text-dim);
}

body.host-console .tenant .section-head {
  flex-wrap: wrap;
  gap: 8px;
}

body.host-console .tenant .section-head h2 {
  margin: 0;
  font-size: 18px;
}

body.host-console .tenant .section-head h2 a {
  color: var(--text);
  text-decoration: none;
}

body.host-console .tenant .section-head h2 a:hover {
  color: var(--accent);
}

body.host-console .tenant .facts {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
  margin-top: 14px;
}

body.host-console .fact-value {
  font-size: 14px;
  font-weight: 600;
}

body.host-console .fact .env-cap-bar {
  margin-top: 6px;
  max-width: none;
}

body.host-console .fact-warn {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, var(--bg-card));
}

body.host-console .plan-form {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

body.host-console .plan-form > .hint {
  margin: 0 0 12px;
}

body.host-console .plan-form .fields + .fields,
body.host-console .plan-form .fields + .row {
  margin-top: 12px;
}

body.host-console .subtabs {
  margin-bottom: 6px;
}

body.host-console #tab-host .list {
  display: grid;
  gap: 6px;
  margin: 8px 0;
}

body.host-console .tag {
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

body.host-console .tag[data-status="pastDue"] {
  border-color: var(--danger);
  color: var(--danger-text);
}

body.host-console .tag[data-status="suspended"] {
  opacity: 0.6;
}

body.host-console .btn.danger {
  color: var(--danger-text);
}

body.host-console .grow {
  flex: 1;
}

/* The time, on the server's clock: information, not an action, so it is text among the icons. */
.topbar-clock {
  color: var(--text-dim);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.topbar-clock:empty {
  display: none;
}

/* The server icon, its own small configurable icon, and the server name
   together are the "go home" link -- what "Rooms" used to be as its own
   separate crumb segment, since going home already lands on the room
   list (or the sign-in page, signed out). */
.brand-home {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--header-text);
}

.brand-home:hover {
  color: var(--icon-hover);
}

.brand span {
  font-size: 17px;
}

.status {
  color: var(--text-dim);
}

.status.error {
  color: var(--danger-text);
}

.controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.btn {
  background: var(--secondary);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--secondary-text);
  padding: 7px 12px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.08s;
  white-space: nowrap; /* a whole button wraps to the next line rather than
    its own label breaking across three -- see .links below, on a phone
    narrow enough that the topbar's buttons don't all fit on one row */
}

.btn:hover {
  background: var(--secondary-hover);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.btn:active {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}

.btn.on {
  border-color: var(--accent);
  color: var(--accent);
}

.btn.off {
  border-color: var(--danger);
  color: var(--danger-text);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* The accent look without .btn-primary's stretch -- for a small button that
   sits beside a primary one (see the pop-out join on a room card). */
.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.btn-accent:hover,
.btn-primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  box-shadow: 0 2px 12px color-mix(in srgb, var(--primary-hover) 45%, transparent);
}

.btn-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger-text);
}

select {
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 6px 8px;
  font: inherit;
}

/* Every checkbox and slider in the app draws with the theme's own accent,
   not the browser's native blue -- set once here rather than per page. */
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
  accent-color: var(--accent);
}

/* The join screen: the rooms I may join, one card each, on a grid that
   wraps. Each card is built like the sign-in box. */
/* The rooms page: the header stays put and the page scrolls beneath it, so the scrollbar starts under the
   header. At the table the stage takes over and this is gone. */
body:has(> .page-scroll):not(.at-table) {
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

body.at-table .page-scroll {
  display: none;
}

.page-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: env(safe-area-inset-bottom);
}

.page-scroll > #join {
  margin: 0 auto;
  padding: 24px 20px;
}

.join {
  max-width: 1180px;
  margin: 6vh auto;
  padding: 0 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.join h1 {
  margin: 0;
  font-size: 20px;
}

.join-note {
  text-align: center;
}

.room-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 18px;
  align-items: stretch; /* every card as tall as the tallest, Join at the bottom */
}

/* The rooms page: a sidebar (the dashboard: the widgets that modules provide, stacked) beside the main column
   (who is around, then the room cards in two columns). One column on a phone, the rooms first. The widgets style
   themselves (.dashboard-widget). */
.home {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

.home-main {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}

.home-main .room-list {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

.dashboard {
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* Stays put while the rooms scroll: it starts where the page's top padding ends, so it does not move first. */
  position: sticky;
  top: 24px;
  max-height: calc(100dvh - 44px - env(safe-area-inset-top) - 48px);
  overflow-y: auto;
}

@media (max-width: 760px) {
  .home {
    grid-template-columns: minmax(0, 1fr);
  }

  .home-main {
    order: 1;
  }

  .dashboard {
    order: 2;
    position: static;
    max-height: none;
    overflow: visible;
  }
}

.dashboard[hidden] {
  display: none;
}

.room-choice {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

.room-choice-image {
  grid-area: img;
  width: 96px;
  height: 96px;
  border-radius: 8px;
  object-fit: cover;
  border: 1px solid var(--border);
}

/* A square room image sits beside the name and description. */
.room-choice:has(.room-choice-image:not([hidden])) {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "img head" "img desc" "members members" "join join";
  grid-template-rows: auto auto 1fr auto;
  column-gap: 14px;
}

.room-choice-head {
  grid-area: head;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 17px;
}

.room-choice-desc {
  grid-area: desc;
  margin: 0;
  align-self: start;
}

.room-choice .members {
  grid-area: members;
  align-self: start;
}

.room-choice-actions {
  grid-area: join;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
}

/* All three buttons in this row share one height regardless of their own
   padding/font-size -- box-sizing keeps that padding inside the 40px
   rather than adding to it, and centering the content covers the rest. */
.room-choice-actions .btn {
  height: 40px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.room-choice-actions .btn-primary {
  flex: 1;
  font-size: 16px;
  padding: 0 16px;
}

/* Who belongs to a room, with a dot for those in it right now. */
.members {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.members:empty {
  display: none;
}

.member {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 72px;
  padding: 6px 6px 4px; /* 6px around the picture on top, left and right */
  border-radius: 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
}

.member.online {
  border-color: color-mix(in srgb, var(--ok) 50%, var(--border));
}

.member img {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 8px;
  object-fit: cover;
  background: var(--bg-section);
}

.member .dot {
  position: absolute;
  top: 9px;
  right: 9px;
  width: 7px;
  height: 7px;
  border: 2px solid var(--bg-card);
  box-sizing: content-box;
}

.member-name {
  font-size: 11px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Off stream: online, but not in the room the admin currently is. "aside"
   is the more specific case of being in a private pulled-aside room. */
.stream-badge {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  background: var(--bg-section);
  border-radius: 4px;
  padding: 1px 5px;
}

.stream-badge.aside {
  color: var(--accent);
}

/* A "pull aside" room's join card: no image or member counts worth showing. */
.room-choice.aside .room-choice-image,
.room-choice.aside .room-choice-count {
  display: none;
}

/* Reactions float up from a tile and fade, sized by the tile (a third of its height). */
.tile {
  container-type: size;
}

.tile .reaction {
  position: absolute;
  bottom: 25%;
  font-size: min(34cqh, 40cqw);
  line-height: 1;
  pointer-events: none;
  animation: reaction-float 2.2s ease-out forwards;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.6));
}

@keyframes reaction-float {
  0% { transform: translate(-50%, 0) scale(0.6); opacity: 0; }
  15% { transform: translate(-50%, -10px) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -110px) scale(1); opacity: 0; }
}

/* The reaction tray: big buttons above the bar, wrapping to more rows
   rather than running off the edges of the screen (or, worse, the much
   narrower popped-out window) once there are enough of them. */
.popover.tray {
  width: min(360px, calc(100% - 24px));
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  padding: 8px;
}

.react {
  width: 48px;
  height: 48px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 24px;
  cursor: pointer;
  line-height: 1;
}

.react:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.join label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.join input {
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 8px 10px;
  font: inherit;
  font-size: 14px;
}

.join .hint {
  color: var(--text-dim);
  font-size: 12px;
}

.grid {
  display: grid;
  gap: 10px;
  padding: 12px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.tile {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.tile {
  --talk: var(--ok);
}

.tile.speaking {
  border-color: var(--talk);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--talk) 50%, transparent);
}

.tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A shared screen shouldn't crop like a camera does -- the whole thing
   fits inside the tile, letterboxed rather than losing the edges. */
.tile-screen {
  background: #000;
}

.tile-screen video {
  object-fit: contain;
}

.tile.mirror video {
  transform: scaleX(-1);
}

.tile .name {
  position: absolute;
  left: 8px;
  bottom: 8px;
  padding: 3px 8px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
}

.tile .muted {
  position: absolute;
  right: 8px;
  bottom: 8px;
  padding: 3px 8px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--danger) 85%, transparent);
  color: #fff;
  font-size: 12px;
}

.tile .placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 40px;
}

/* --- shared panels ------------------------------------------------------- */

.panel {
  max-width: 420px;
  margin: 12vh auto;
  padding: 24px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.panel h1 {
  margin: 0;
  font-size: 20px;
}

.panel h2 {
  margin: 16px 0 0;
  font-size: 15px;
}

.panel h3 {
  margin: 14px 0 6px;
  font-size: 13px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.panel label,
.fields label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.panel input[type="text"],
.panel input[type="password"],
.panel input[type="number"],
.panel input[type="email"],
.panel input[type="url"],
.panel textarea,
.fields input[type="text"],
.fields input[type="password"],
.fields input[type="number"],
.fields input[type="email"],
.fields input[type="url"],
.fields select,
.fields textarea {
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 8px 10px;
  font: inherit;
  font-size: 14px;
  text-transform: none;
  letter-spacing: normal;
  max-width: none;
}

.panel .hint,
.hint {
  color: var(--text-dim);
  font-size: 12px;
  margin: 0;
  white-space: pre-line;
}

.panel.narrow {
  max-width: 520px;
  margin-top: 6vh;
}

.brand.big {
  font-size: 18px;
  justify-content: flex-start;
}

.brand .icon {
  width: 24px;
  height: 24px;
  border-radius: 6px;
  object-fit: cover;
}

.brand.big .icon {
  width: 40px;
  height: 40px;
}

.links {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--text-dim);
}

/* Separates "who you are" (whoami) from "what you can do from anywhere"
   (the global nav icons) -- both always there, but visibly two groups. */
.nav-divider {
  width: 1px;
  height: 22px;
  background: var(--border);
}

/* The room's bar, the header's second row, only at the table: the panes to open on the left, the
   controls for the whole app (full screen, pop out) on the right. */
.subnav {
  display: none;
  flex: 0 0 auto;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  box-sizing: border-box;
  width: calc(100% + 32px); /* full bleed: cancels .topbar's side padding */
  margin: 0 -16px;
  padding: 2px 16px;
  border-top: 1px solid var(--border);
  background: var(--nav-secondary-bg);
}

/* Three zones, like the primary nav: left (the room's name, the pane switches), middle (space information and navigation),
   right (space actions); the middle is centred on the row, whatever the other two hold. See architecture-navigation.md. */
body.at-table .subnav {
  display: grid;
  grid-column: 1 / -1; /* the header is a grid too: the second row spans all three of its columns */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
}

.subnav .nav-left {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  justify-self: start;
}

.subnav .nav-middle {
  justify-self: center;
}

.subnav .nav-right {
  justify-self: end;
}

.space-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-right: 10px;
  border-right: 1px solid var(--border);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}

.space-name i {
  color: var(--icon, color-mix(in srgb, var(--header-text) 75%, transparent));
}

.subnav-tools {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.subnav .nav-divider {
  height: 14px;
}

/* the stage-level snap's grid-size slider, beside its switch while the switch is on (the nav-bar registry places it) */
.subnav-tools input[type="range"] {
  width: 84px;
  margin: 0;
  accent-color: var(--accent);
}

.subnav .icon-link {
  height: 20px;
  padding: 0 6px;
  line-height: 1;
}

.subnav-panes {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow-x: auto;
}

/* A pane toggle, not a button: a small switch, on when the pane is open, then its icon and name. */
.subnav-panes .modules-menu-item {
  width: auto;
  gap: 6px;
  padding: 2px 8px 2px 4px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: color-mix(in srgb, var(--header-text) 65%, transparent);
  font-size: 13px;
  white-space: nowrap;
}

.subnav-panes .modules-menu-item::before {
  content: "";
  flex: none;
  width: 22px;
  height: 12px;
  border-radius: 6px;
  background:
    radial-gradient(circle at 6px 50%, color-mix(in srgb, var(--header-text) 70%, transparent) 4px, transparent 4.5px),
    color-mix(in srgb, var(--header-text) 22%, transparent);
}

.subnav-panes .modules-menu-item:hover {
  background: color-mix(in srgb, var(--header-text) 8%, transparent);
  color: var(--header-text);
}

.subnav-panes .modules-menu-item.on {
  color: var(--header-text);
}

.subnav-panes .modules-menu-item.on::before {
  background:
    radial-gradient(circle at 16px 50%, var(--on-accent) 4px, transparent 4.5px),
    var(--accent);
}

/* The bar is a second row of the header; the gap between rows would add height to the first. */
body.at-table .topbar {
  flex-wrap: wrap;
  row-gap: 0;
}

.icon-link {
  position: relative;
}

/* A phone has no use for a pop-out window, and the header has no room for the extra buttons. */
@media (max-width: 560px) {
  .subnav #fullscreen-toggle,
  .subnav #popout,
  .subnav #dock-all,
  .subnav #snap-all,
  .subnav #snap-size,
  .subnav .space-name,
  .subnav .nav-middle,
  .subnav .nav-divider {
    display: none;
  }

  .subnav-panes .modules-menu-item span:not(.badge) {
    display: none;
  }

  .topbar .links .nav-divider {
    display: none;
  }
}

/* The same count bubble inline on a tab (Manage's Modules tab when an update is waiting). */
.subtab .badge {
  display: inline-block;
  min-width: 16px;
  height: 16px;
  margin-left: 6px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  vertical-align: 1px;
}

.icon-link .badge {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 9px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
}

.icon-link.on {
  color: var(--icon-hover);
}

/* A toggle icon-link shows its "on" icon until switched, then its "off" one. */
.icon-link .icon-off,
.icon-link.on .icon-on {
  display: none;
}

.icon-link.on .icon-off {
  display: inline-block;
}

/* The signed-in player: their image next to their name, leading to their profile */
.links a.whoami,
.whoami {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--header-text);
  font-weight: 600;
}

.whoami img {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border);
}

/* Icon links in the header: manage, sign out, install as an app, and the
   crumb's own leave/rejoin -- .icon-link on a <button> (install, leave,
   rejoin) as well as an <a>, so it needs its own reset. Not scoped to
   .links: the crumb uses this class too (see LEAVE_BTN/REJOIN_BTN in
   room.js), and a plain unstyled <button> is what you get if this only
   matches inside .links. */
.icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  border: none;
  background: none;
  color: var(--icon, var(--text-dim));
  font: inherit;
  cursor: pointer;
}

/* On the header, an icon follows the header text unless the theme sets one. */
.topbar .icon-link,
.theme-preview-header .icon-link {
  color: var(--icon, color-mix(in srgb, var(--header-text) 75%, transparent));
}

/* The home icon and the crumb icons (room, page) are header icons too. */
.topbar .brand-home i,
.topbar .crumb-here i {
  color: var(--icon, color-mix(in srgb, var(--header-text) 75%, transparent));
}

.topbar .brand-home:hover i,
.topbar a.crumb-here:hover i {
  color: var(--icon-hover);
}

.icon-link:hover {
  color: var(--icon-hover);
  background: color-mix(in srgb, var(--icon-hover) 14%, transparent);
  text-decoration: none;
}

.icon-link i {
  font-size: 16px;
}

/* Profile page */
.profile-head {
  display: flex;
  align-items: flex-start;
  gap: 18px;
}

.profile-head .slot {
  flex: none;
}

.profile-title {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.profile-title h1 {
  margin: 0;
}

.profile-title .tag {
  align-self: flex-start;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent);
  background: var(--bg-section);
  border-radius: 4px;
  padding: 2px 6px;
}

.slot-pick.big {
  width: 128px;
  height: 128px;
}

/* A profile photo is a real photo, not a transparent OBS overlay -- fill
   the square properly (cropping instead of letterboxing) at the size the
   slot actually is, rather than the smaller generic .slot img default. */
.slot-pick.big img {
  width: 128px;
  height: 128px;
  object-fit: cover;
}

.slot-pick.still {
  cursor: default;
  pointer-events: none;
}

.slot-pick.still:hover {
  outline: none;
}

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 8px 16px;
}

.fact {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-card);
}

.fact-key {
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.fact-value {
  font-size: 13px;
}

/* The editable version of the same grid (#account-fields) reuses .facts/
   .fact for the box itself -- .panel's own input styling (and .fields'
   now-unmatched select styling) would otherwise draw a second box inside
   the one .fact already draws, or leave the select with none at all. */
#account-fields input[type="text"],
#account-fields input[type="password"],
#account-fields select {
  background: none;
  border: none;
  padding: 0;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-transform: none;
  letter-spacing: normal;
}


.join .btn-primary {
  font-size: 16px;
  padding: 10px 16px;
}

.tile .placeholder {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--bg-section);
  font-size: 0;
}

/* Behind the profile photo, only present while the camera is off and only
   filled in when that person has a background image set at all (see
   updateBackgroundPlaceholder in room.js) -- an empty img with no src
   stays invisible on its own, .has-bg-image is just what shrinks the
   photo on top of it into a centered circle once there's really a
   picture behind it. */
.tile .placeholder-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile.has-bg-image .placeholder {
  inset: auto;
  top: 50%;
  left: 50%;
  /* sized off the tile's height on both axes (via container query units, since
     .tile is a size container) so it comes out square no matter the tile's
     own aspect ratio, instead of stretching into an ellipse. --pic-scale
     (set from the room, from the same Portrait Size setting the OBS view
     uses) is a 0-100 percentage of that height. */
  width: calc(var(--pic-scale, 38) * 1cqh);
  height: calc(var(--pic-scale, 38) * 1cqh);
  min-width: 72px;
  min-height: 72px;
  transform: translate(-50%, -50%);
  border-radius: 16%;
  object-fit: cover;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.5);
}

.image-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

.preview {
  width: 160px;
  height: 160px;
  object-fit: contain;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.preview.small {
  width: 64px;
  height: 64px;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.panel label.file-btn,
.fields label.file-btn,
.file-btn {
  display: inline-block;
  text-align: center;
  text-transform: none;
  letter-spacing: normal;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
}

/* --- admin ----------------------------------------------------------------- */

.admin {
  max-width: 960px;
  margin: 0 auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.admin .panel {
  max-width: none;
  margin: 0;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px;
}

.fields .wide {
  grid-column: 1 / -1;
}

.field-label {
  display: block;
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}

/* A picker of buttons, not a <select> -- every choice is visible and one
   click away instead of hidden behind a dropdown. */
.icon-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.icon-grid button {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: 15px;
  cursor: pointer;
}

.icon-grid button:hover {
  border-color: var(--accent);
  color: var(--text);
}

.icon-grid button.selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* Same "every choice is one click away" idea as .icon-grid, but a row of
   equal-width labeled buttons instead of icon squares. */
.layout-pick {
  display: flex;
  gap: 6px;
}

.layout-pick button {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  padding: 7px 6px;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.layout-pick button:hover {
  border-color: var(--accent);
  color: var(--text);
}

.layout-pick button.selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* A hotkey field looks like the other .fields inputs at rest, then turns
   into an obvious "waiting for you to press something" state once clicked. */
.hotkey-input {
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 8px 10px;
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}

.hotkey-input:hover {
  border-color: var(--accent);
}

.hotkey-input.recording {
  border-color: var(--accent);
  color: var(--text-dim);
  font-style: italic;
}

.fields .check,
.check {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  text-transform: none;
  letter-spacing: normal;
  font-size: 13px;
  color: var(--text);
}

/* A row of checkboxes belongs together, not scattered one per grid cell
   like every other .fields item -- group them into one compact,
   wrapping row instead. */
.check-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
}

.row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.danger-row {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.btn-small {
  padding: 5px 10px;
  font-size: 13px;
}

.key {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  word-break: break-all;
  max-width: 100%;
}

.key.dim {
  color: var(--text-dim);
}

.users {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.user-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-section);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
}

.user-title {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.user-title .meta {
  color: var(--text-dim);
  font-size: 12px;
}

.user-title code {
  font-size: 11px;
}

.thumb {
  width: 36px;
  height: 36px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--bg-card);
}

.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--border);
  flex: none;
}

.dot.online {
  background: var(--ok);
  box-shadow: 0 0 6px rgba(111, 174, 107, 0.7);
}

.live {
  color: var(--text-dim);
  font-size: 12px;
}

.slots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}

.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border: 1px dashed var(--border);
  border-radius: 8px;
  font-size: 12px;
  color: var(--text-dim);
}

.slot.set {
  border-style: solid;
}

.slot img {
  width: 96px;
  height: 96px;
  object-fit: contain;
  background: var(--bg-card);
  border-radius: 6px;
}

.add-user {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

a.btn {
  text-decoration: none;
  display: inline-block;
}

@media (max-width: 600px) {
  .topbar {
    flex-wrap: wrap;
    padding-top: calc(4px + env(safe-area-inset-top));
    padding-bottom: 4px;
  }
  .links {
    flex-wrap: wrap;
    row-gap: 6px;
  }
  .panel {
    margin: 4vh 12px;
  }
}

.version {
  color: var(--text-dim);
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  margin-left: 8px;
  white-space: nowrap;
}

.panel .version {
  align-self: flex-end;
  margin: 4px 0 -8px;
}

/* --- the stage: full-window table with floating controls ------------------- */

/* On the page the table is a column: the site header, the tiles, and a
   toolbar locked to the bottom edge (like Zoom or Meet). Popped out, the
   stage is the whole window with the floating pill and status instead. */
body.at-table {
  overflow: hidden;
  /* The base body rule's min-height: 100vh is the large viewport on a phone (777px in Chrome on an
     iPhone where 740px is visible), and a min-height beats height: the table stayed taller than the
     screen and its bottom bar sat under the browser's own. */
  min-height: 0;
  height: 100vh; /* fallback for browsers without dvh */
  height: 100dvh; /* the actual visible viewport, so the toolbar at the
                      bottom isn't hidden under a phone browser's own
                      address/tab bar when it's showing */
  /* dvh is supposed to already do this, but some mobile browsers are slow
     to recompute it as their own address/tab bars show and hide -- room.js
     tracks window.visualViewport itself and keeps --app-vh in sync live,
     which is what actually keeps the floating controls above the browser's
     own chrome rather than sliding out from under it. Only takes effect
     once JS has set it at least once. */
  height: var(--app-vh, 100dvh);
  display: flex;
  flex-direction: column;
}

body.at-table .topbar {
  flex: none;
}

.topbar .brand > span {
  white-space: nowrap;
}

/* The crumb: "where you are", always after the server name, sometimes
   with its own action buttons (Leave, Rejoin Call) right alongside
   whatever it's naming -- see setTopbarLocation()/updateCrumb(). */
.crumb {
  white-space: nowrap; /* the left zone shares the row with two others; a long crumb must not wrap into a second line */
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 17px;
  font-weight: 400;
}

.crumb-sep {
  color: var(--text-dim);
}

.crumb-here {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

a.crumb-here {
  color: inherit;
}

a.crumb-here:hover {
  color: var(--icon-hover);
}

/* Hides at narrow widths (see the media query below), leaving just the
   icon each crumb-here already carries -- the reason every segment has
   one, not just decoration. */
.crumb-label {
  white-space: nowrap;
}

.crumb-action {
  flex-shrink: 0;
}

.topbar .topbar-status {
  font-size: 13px;
  font-weight: 400;
  margin-left: 6px;
}

@media (max-width: 600px) {
  .topbar .brand {
    flex-wrap: wrap;
    row-gap: 2px;
  }
  .crumb {
    font-size: 16px;
  }
  /* Every crumb-here carries its own icon for exactly this: run out of
     space and the label drops, the icon (and the room/page it names)
     doesn't. */
  .crumb-label,
  .brand-home span[data-brand="serverName"] {
    display: none;
  }
  .topbar .topbar-status {
    display: none;
  }
}

.topbar .topbar-status:empty {
  display: none;
}

.away {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  color: var(--text-dim);
}

/* Your profile or Manage, over the call: just the real page in an iframe.
   Its own header gets the way back (see wireOverlayBack in brand.js). */
.page-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg);
}

/* The room is a grid: each module owns a column with a content area over an
   action bar, and the bars share one bottom row so they line up. The row is
   as tall as the tallest bar; nothing is measured. See
   documentation/architecture/architecture-room-layout.md. */
.stage {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  display: grid;
  /* a column for each docked pane (conference, chat, then modules); the room-modules
     manager sets the whole template, one column flexible and the rest fixed */
  grid-template-columns: var(--stage-cols, minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr) auto;
  background: var(--bg);
  overflow: hidden;
}

/* A module's own box vanishes; its content and bar are the grid items. */
.module {
  display: contents;
}

.mod-content {
  grid-row: 1;
  position: relative;
  min-width: 0;
  min-height: 0;
}

.mod-bar {
  grid-row: 2;
  position: relative;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.module-video > .mod-content,
.module-video > .mod-bar {
  grid-column: 1;
}

.module-chat > * {
  grid-column: 2;
}

.module-video .mod-content {
  display: flex;
  flex-direction: column;
}

/* Popped out (the whole table, body.popout, or the conference pane alone in its own window,
   body.conference-window), the conference keeps its titlebar and toolbar, but they slide away
   when nothing has moved for a moment (the stage gets .idle) so the window is only the tiles,
   and slide back on the next movement. Gone, they leave the flow, so the tiles take the
   whole window. */
.popout .conference-head,
.popout .video-bar,
.conference-window .conference-head,
.conference-window .video-bar {
  transition: transform 0.25s ease;
}

.conference-window .stage {
  position: relative; /* the hidden titlebar and toolbar park against the window's edges */
}

/* The header goes with the titlebar and toolbar, so an idle popout is only tiles. */
.popout:has(.stage.idle) .topbar {
  position: absolute;
  left: 0;
  right: 0;
  transform: translateY(-100%);
}

.popout .topbar {
  transition: transform 0.25s ease;
}

.popout .stage.idle .conference-head,
.conference-window .stage.idle .conference-head {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 6;
  transform: translateY(-100%);
}

.popout .stage.idle .video-bar,
.conference-window .stage.idle .video-bar {
  position: absolute;
  grid-row: 1 / -1;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  transform: translateY(100%);
}

.popout .conference-head [data-popout] {
  display: none; /* the whole table is already in a window */
}

/* The conference alone in a window of its own: no close on its titlebar, which would read as "close this window"
   (which brings the call back to the page) when it means "leave the call"; Hang up on the toolbar leaves. */
.conference-window .conference-head [data-pane-close],
.conference-window .conference-head [data-popout] {
  display: none;
}

.float {
  position: absolute;
  z-index: 5;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.popout .stage.idle .float:not(.floatbar),
.conference-window .stage.idle .float:not(.floatbar) {
  opacity: 0;
  pointer-events: none;
}

.popout .stage.idle,
.conference-window .stage.idle {
  cursor: none;
}

.status-float {
  display: none;
  top: 10px;
  left: 12px;
  padding: 5px 10px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--bg-section) 88%, transparent);
  font-size: 12px;
  color: var(--text-dim);
  gap: 8px;
}

/* docked: a full-width bar at the bottom of the page */
.floatbar {
  position: static;
  flex: none;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  flex-wrap: nowrap;
  overflow: hidden;
  gap: 8px;
  padding: 10px 12px;
  padding-bottom: max(10px, env(safe-area-inset-bottom));
}

.video-bar {
  background: var(--shade);
  border-top: 1px solid var(--border);
}

/* Buttons keep their natural size rather than squeezing to fit -- fitFloatbar()
   reads scrollWidth vs clientWidth to decide what collapses, which only works
   if what doesn't fit actually overflows instead of shrinking in place. */
.floatbar > * {
  flex-shrink: 0;
}

.fbtn {
  position: relative;
  width: var(--bar-control-h);
  height: var(--bar-control-h);
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--secondary-text);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}

/* mic, audio, camera and layout each carry a small caret over their corner
   that opens just that control's own settings, instead of the whole thing. */
.fbtn-wrap {
  position: relative;
  display: inline-flex;
}

.fbtn-caret {
  position: absolute;
  top: 3px;
  right: 3px;
  z-index: 2;
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 16px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.fbtn-caret:hover,
.fbtn-caret.on {
  color: var(--accent);
}

.fbtn:hover {
  background: var(--secondary-hover);
  border-color: var(--accent);
}

.fbtn.on {
  border-color: var(--accent);
}

/* Microphone, deafen and camera are Primary buttons while they are on; off
   keeps the red look below. */
#mic.on,
#deafen.on,
#cam.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

#mic.on:hover,
#deafen.on:hover,
#cam.on:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}

#mic.on .meter {
  background: color-mix(in srgb, var(--on-accent) 35%, transparent);
}

#mic.on + .fbtn-caret,
#deafen.on + .fbtn-caret,
#cam.on + .fbtn-caret {
  color: var(--on-accent);
}

.fbtn.off {
  border-color: var(--danger);
  background: rgba(184, 80, 63, 0.35);
}

.fbtn .glyph i {
  font-size: 20px;
}

/* the off state swaps to the slashed icon */
.fbtn .icon-off {
  display: none;
}

.fbtn.off .icon-on {
  display: none;
}

.fbtn.off .icon-off {
  display: inline-block;
}

/* while a device is being acquired, a spinner replaces on/off entirely --
   so a slow getUserMedia (or the camera retry after a reconnect) never
   looks like a dead click inviting a second one. */
.fbtn .icon-loading {
  display: none;
}

.fbtn.loading .icon-on,
.fbtn.loading .icon-off {
  display: none;
}

.fbtn.loading .icon-loading {
  display: inline-block;
}

.fbtn.loading {
  opacity: 0.7;
  cursor: default;
}

.fbtn.danger {
  background: var(--danger);
  border-color: var(--danger);
}

/* After Hang up: the phone turns green to dial back in, the pane says so, and the rest of the toolbar waits. */
.fbtn.dial {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
}

.no-call {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--text-dim);
  font-size: 15px;
  pointer-events: none;
}

.no-call i {
  font-size: 28px;
  opacity: 0.7;
}

.module-video.call-off .fbtn:not(#hangup):not(#floatbar-more) {
  opacity: 0.4;
  pointer-events: none;
}

.fbtn .glyph {
  position: relative;
  z-index: 1;
}

/* microphone level fills the button from the bottom */
.fbtn .meter {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(var(--level, 0) * 100%);
  background: rgba(111, 174, 107, 0.55);
  transition: height 0.08s linear;
}

/* The meter still tracks your real input level while muted (so you can see
   you're talking before you unmute), but a green fill there read as "still
   live" -- red matches the muted icon/border instead. */
.fbtn.off .meter {
  background: rgba(184, 80, 63, 0.55);
}

.fbtn .badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  z-index: 2;
}

/* The chat module: its content (header and messages) over its bar (the
   formatting buttons and the message box), in the second column. */
.module-chat .mod-content {
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--bg) 96%, transparent);
  border-left: 1px solid var(--border);
}

.chat-bar {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-end;
  background: var(--shade); /* the same as the video toolbar and the module bars in the shared bottom row */
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
}

/* Covers just the chat's content, not the whole stage */, not the whole stage -- see the markup comment
   in room.html for why. */
.chat-delete-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.7);
}

.chat-delete-overlay p {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 500;
  text-align: center;
}

.chat-delete-overlay-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
}

/* a thin strip over the chat column's left edge, to drag it wider or narrower */
.chat-resize {
  grid-column: 2;
  grid-row: 1 / -1;
  justify-self: start;
  width: 8px;
  margin-left: -4px;
  cursor: ew-resize;
  z-index: 7;
  touch-action: none;
}

.chat-resize:hover,
.chat-resize.dragging {
  background: color-mix(in srgb, var(--accent) 50%, transparent);
}

.chat header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
  flex: none;
  height: var(--module-header-h);
  padding: 0 8px 0 12px;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

.chat-tools {
  display: flex;
  gap: 2px;
}

.msg-btn {
  background: none;
  border: 0;
  color: var(--icon, var(--text-dim));
  cursor: pointer;
  padding: 4px 5px;
  border-radius: 4px;
  font-size: 13px;
  line-height: 1;
}

.msg-btn:hover {
  color: var(--icon-hover);
  background: color-mix(in srgb, var(--icon-hover) 12%, transparent);
}

.msg-btn.on {
  color: var(--accent);
}

.chat-hint {
  margin: 0;
  padding: 0 12px 6px;
  font-size: 11px;
}

.chat .messages {
  flex: 1;
  overflow-y: auto;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
}

.message {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px 6px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

.message.own {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.message .actions {
  position: absolute;
  right: 2px;
  top: 2px;
  display: none;
  gap: 1px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  border-radius: 4px;
}

.message:hover .actions,
.message:focus-within .actions {
  display: flex;
}

.message .text {
  overflow-wrap: anywhere;
}

.message .text img {
  display: block;
  max-width: 100%;
  max-height: 240px;
  border-radius: 6px;
  cursor: zoom-in;
  margin-top: 2px;
}

.message .text code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  background: color-mix(in srgb, var(--text) 10%, transparent);
  padding: 0 4px;
  border-radius: 3px;
}

.message .text a {
  color: var(--accent);
}

.message .who {
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
}

.message.own .who {
  color: var(--text-dim);
}

.message .who .when {
  margin-left: 6px;
  color: var(--text-dim);
  font-weight: 400;
}

.message .text {
  white-space: pre-wrap;
  word-break: break-word;
}

.message .text p {
  margin: 0 0 4px;
}

.message .text p:last-child {
  margin-bottom: 0;
}

.message .text ul,
.message .text ol {
  margin: 4px 0;
  padding-left: 18px;
}

.message .text h1,
.message .text h2,
.message .text h3 {
  margin: 6px 0 4px;
  font-size: 1em;
}

.message .text pre {
  margin: 4px 0;
  padding: 6px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  overflow-x: auto;
  white-space: pre;
}

.message .text blockquote {
  margin: 4px 0;
  padding: 2px 0 2px 8px;
  border-left: 2px solid var(--border);
  color: var(--text-dim);
}

.chat-session-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0;
  color: var(--text-dim);
  font-size: 11px;
  text-align: center;
}

.chat-session-divider::before,
.chat-session-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* The formatting tools: a layer above the chat's input row, like the conference's popovers. */
.chat-bar {
  position: relative;
}

.chat-format-bar {
  position: absolute;
  z-index: 9;
  left: 8px;
  bottom: calc(100% + 6px);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 8px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-section) 97%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}

.chat-format-bar[hidden] {
  display: none;
}

.chat-format-spacer {
  width: 8px;
  flex: none;
}

.chat-format-bar .msg-btn {
  font-size: 13px;
}

/* Positions each popup relative to the button that opens it, not the bar
   as a whole -- see .chat-help-popup/.chat-emoji-popup below. */
.chat-format-item {
  position: relative;
  display: flex;
}

/* Toggled by #chat-help, hangs just above the button that opened it. */
.chat-help-popup {
  position: absolute;
  z-index: 8;
  right: 0;
  bottom: 100%;
  margin-bottom: 6px;
  width: min(280px, calc(100vw - 32px));
  padding: 10px 12px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--bg-section) 97%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}

.chat-help-popup p {
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
}

.chat-help-popup code {
  font-size: 11px;
}

/* Toggled by #chat-emoji, same above-the-button treatment as the help
   popup but a grid of picks instead of a paragraph. */
.chat-emoji-popup {
  position: absolute;
  z-index: 8;
  right: 0;
  bottom: 100%;
  margin-bottom: 6px;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
  width: min(220px, calc(100vw - 32px));
  max-height: min(260px, 50vh);
  overflow-y: auto;
  padding: 8px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--bg-section) 97%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}

.chat-emoji-btn {
  border: none;
  background: none;
  border-radius: 6px;
  font-size: 18px;
  line-height: 1.6;
  cursor: pointer;
}

.chat-emoji-btn:hover {
  background: var(--surface-hover);
}

.chat form {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 8px 10px 8px 8px; /* as the module bars beside it */
}

.chat form .msg-btn {
  width: var(--bar-control-h);
  height: var(--bar-control-h);
  font-size: 16px;
}

.chat form .btn {
  height: var(--bar-control-h);
  min-width: var(--bar-control-h);
}

.chat textarea {
  box-sizing: border-box;
  min-height: var(--bar-control-h);
  flex: 1;
  min-width: 0;
  resize: none;
  max-height: 120px;
  overflow-y: auto;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 9px;
  font: inherit;
  line-height: 18px; /* one line plus padding is exactly the control height */
}

.popover {
  position: absolute;
  z-index: 7;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translateX(-50%);
  width: min(360px, calc(100% - 24px));
  padding: 14px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-section) 97%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.popover label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Toggled by #floatbar-more: whatever fitFloatbar() couldn't fit, as a
   plain vertical menu instead of the centred settings-style popover. */
.floatbar-overflow {
  left: auto;
  right: 12px;
  bottom: calc(100% + 10px);
  transform: none;
  width: max-content;
  min-width: 180px;
  max-width: calc(100% - 24px); /* never wider than the bar it hangs from */
  padding: 6px;
  gap: 2px;
}

/* #floatbar-overflow, not .floatbar-overflow -- these need to outrank the
   .stage.compact/.stage.tiny sizing rules further down the sheet, which
   share the same 2-class specificity and would otherwise win on source
   order alone. */
#floatbar-overflow .fbtn-wrap {
  width: 100%;
}

#floatbar-overflow .fbtn {
  width: 100%;
  height: auto;
  min-height: 34px;
  justify-content: flex-start;
  gap: 10px;
  padding: 6px 10px;
}

#floatbar-overflow .fbtn::after {
  content: attr(title);
  font-size: 13px;
  font-weight: 500;
  white-space: normal; /* a long title wraps instead of running off a narrow screen */
  text-align: left;
}

#floatbar-overflow .fbtn .glyph i {
  font-size: 16px;
}

#floatbar-overflow .fbtn-caret {
  display: none;
}

.popover .links {
  justify-content: space-between;
  flex-wrap: wrap;
}

.install-hint {
  margin-top: 4px;
}

@media (max-width: 600px) {
  .floatbar {
    gap: 6px;
    padding: 6px;
    padding-bottom: max(6px, env(safe-area-inset-bottom));
  }
  .fbtn {
    width: 40px;
    height: 40px;
  }
  .fbtn-caret {
    width: 14px;
    height: 14px;
    font-size: 12px;
    top: 2px;
    right: 2px;
  }
}

/* --- layouts: tile sizes come from fitTiles() as CSS variables --------- */

.tile {
  cursor: pointer;
  user-select: none;
}

.tile.dragging {
  opacity: 0.4;
}

.stage .grid {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 12px;
  overflow: hidden;
}

.stage .grid .tile {
  flex: 0 0 auto;
  width: var(--tw, 280px);
  height: var(--th, 158px);
  aspect-ratio: auto;
}

.stage .grid[data-layout="strip"] {
  flex-wrap: nowrap;
  flex-direction: row;
}

.stage .grid[data-layout="strip"].portrait {
  flex-direction: column;
}

.stage .grid[data-layout="spotlight"] {
  flex-direction: column;
  flex-wrap: nowrap;
}

.stage .grid .rest {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 10px;
  max-width: 100%;
  overflow: hidden;
}

.stage .grid .rest .tile {
  width: var(--sw, 160px);
  height: var(--sh, 90px);
}

.stage .grid[data-layout="spotlight"] .tile.spot .name {
  font-size: 15px;
}

/* --- audio and video controls ------------------------------------------- */


.popover {
  max-height: calc(100vh - 100px);
  overflow-y: auto;
}

.popover h3 {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.popover input[type="range"] {
  width: 100%;
}

.checks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
}

/* .popover label defaults to a column (field label above its input), but a
   checkbox's own label reads left to right: the box, then its text. */
.popover label.check {
  flex-direction: row;
  align-items: center;
  gap: 6px;
}

.fbtn.gated .meter {
  background: rgba(168, 153, 138, 0.35);
}

.fbtn.ptt .glyph::after {
  content: "";
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.fbtn.ptt.on .glyph::after {
  background: var(--ok);
  box-shadow: 0 0 6px var(--ok);
}

.tile .vol {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 90px;
  height: 18px;
  margin: 0;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 2;
}

.tile:hover .vol {
  opacity: 1;
}

/* Admin only: pull this person into a private room. */
.tile-aside {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--secondary-text);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3);
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 2;
}

.tile:hover .tile-aside {
  opacity: 1;
}

.tile-aside:hover {
  background: var(--secondary-hover);
  color: var(--icon-hover);
  border-color: var(--accent);
}

/* Admin only, centered over the tile on hover: mute and kick. The
   container itself doesn't capture clicks (pointer-events: none) so the
   darkening scrim never blocks the volume slider or the aside button
   underneath -- only the two buttons themselves are actually clickable. */
.tile-admin-tools {
  position: absolute;
  inset: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: rgba(0, 0, 0, 0.35);
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
  z-index: 3;
}

.tile:hover .tile-admin-tools {
  opacity: 1;
}

.tile-admin-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--secondary-text);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3);
  font-size: 14px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  flex-shrink: 0;
}

.tile-admin-btn:hover {
  background: var(--secondary-hover);
  color: var(--icon-hover);
  border-color: var(--accent);
}

.tile-admin-btn.danger:hover {
  color: var(--danger);
  border-color: var(--danger);
}

/* Float a shared screen into its own always-on-top window (native video
   Picture-in-Picture), separate from the whole-stage pop out. */
.tile-pip {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--secondary-text);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3);
  font-size: 12px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 2;
}

.tile:hover .tile-pip {
  opacity: 1;
}

.tile-pip:hover {
  background: var(--secondary-hover);
  color: var(--icon-hover);
  border-color: var(--accent);
}

/* A room member who is online but currently in a private aside elsewhere:
   their picture stands in for video, dimmed, with who they stepped away
   with -- so they read as "still at the table, just aside" rather than
   looking like they hung up. */
.tile-ghost {
  cursor: default;
}

.tile-ghost .placeholder {
  filter: brightness(0.45) saturate(0.6);
}

/* The Aside/Private picture, laid over the (dimmed) profile photo same
   as OBS lays it over the Online/Offline picture -- a badge, not a
   replacement, sized the same way so it sits in the same spot either way. */
.tile-ghost-badge {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.tile-ghost-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  padding: 8px;
}

.tile-ghost-status {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.tile-ghost-with {
  font-size: 12px;
  color: var(--text-dim);
}

/* Someone still fully in the call but looking at their profile or Manage
   right now: their real video keeps playing underneath, dimmed, with a
   small "Away" mark -- visible to everyone else, not to themselves (they
   already know). */
.tile-away video,
.tile-away .placeholder {
  filter: brightness(0.4);
}

.tile-away-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
  pointer-events: none;
}

/* The away mark sits on a dark bubble so it reads over any picture: a bold
   icon and AWAY, then the person's message under them if they left one. */
.tile-away-overlay.custom {
  padding: 12px;
}

.away-bubble {
  max-width: 90%;
  padding: 10px 16px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.away-head {
  font-size: 22px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.1;
}

.away-msg {
  font-size: 15px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

#away-form {
  width: min(440px, calc(100% - 32px));
  max-width: none;
}

#away-message {
  width: 100%;
  resize: vertical;
  box-sizing: border-box;
  padding: 8px 10px;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* Picked to step aside together, pending confirmation: stays visible even
   without hovering, so a multi-person selection is easy to keep track of. */
.tile-aside.selected {
  opacity: 1;
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

.aside-overlay {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}

.aside-overlay-card {
  background: color-mix(in srgb, var(--bg-section) 97%, transparent);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: center;
  max-width: min(360px, calc(100% - 32px));
  text-align: center;
}

.aside-overlay-card p {
  margin: 0;
  font-weight: 500;
}

.recall-countdown {
  font-size: 40px;
  font-weight: 700;
  color: var(--accent);
}

.aside-overlay-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

/* Visually distinct from the ordinary (still-recorded) Step aside button on
   purpose -- an accidental mix-up here means something meant to be off the
   record ends up in the recording, or vice versa. */
#aside-confirm-private {
  border-color: #b8503f;
  color: #b8503f;
}

#aside-confirm-private:hover {
  background: #b8503f;
  color: #fff;
}

/* --- small windows (pop out, phones) --------------------------------------- */

.stage.compact .grid {
  padding: 6px;
  gap: 6px;
}

.stage.compact .floatbar {
  gap: 4px;
  padding: 5px;
  padding-bottom: max(5px, env(safe-area-inset-bottom));
}

.stage.compact .fbtn {
  width: 34px;
  height: 34px;
  font-size: 15px;
}

.stage.compact .fbtn .glyph i {
  font-size: 14px;
}

.stage.compact .fbtn-caret {
  width: 13px;
  height: 13px;
  font-size: 11px;
  top: 2px;
  right: 2px;
}

.stage.compact .status-float {
  top: 6px;
  left: 6px;
  padding: 3px 7px;
  font-size: 11px;
}

.stage.compact .tile .name {
  font-size: 11px;
  padding: 2px 6px;
  left: 5px;
  bottom: 5px;
}

.stage.compact .tile .muted {
  font-size: 10px;
  padding: 2px 6px;
  right: 5px;
  bottom: 5px;
}

/* Layout and Settings used to just vanish here -- now fitFloatbar() moves
   them (and whatever else doesn't fit) into "More" instead, so they stay
   reachable at any width. */
.stage.tiny .status-float {
  display: none;
}

.stage.tiny .fbtn {
  width: 30px;
  height: 30px;
  font-size: 13px;
}

.stage.tiny .fbtn .glyph i {
  font-size: 12px;
}

/* room pictures fill their square */
.slot[data-room-image] .slot-pick img {
  object-fit: cover;
}

/* --- manage page tabs and settings layout ---------------------------------- */

/* A live sample of the seven theme colors, updated straight from the color
   inputs below it (see admin.js) -- its own background/border/text come
   from the SAME custom properties the inputs are editing, scoped to just
   this box (see admin.js's updateThemePreview()) so picking a color shows
   its real effect without recoloring the whole settings page underneath. */
.roles-scroll {
  overflow-x: auto;
}

.roles-table {
  width: 100%;
  border-collapse: collapse;
  margin: 8px 0;
}

.roles-table th,
.roles-table td {
  padding: 8px 12px;
  text-align: center;
  border-bottom: 1px solid var(--border);
}

.roles-table thead th {
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.roles-table tbody th[scope="row"] {
  text-align: left;
  font-weight: 400;
}

.roles-table .roles-group th {
  text-align: left;
  padding-top: 16px;
  color: var(--accent);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: none;
}

.about p {
  margin: 0 0 10px;
}

.theme-preview {
  padding: 16px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  margin-bottom: 14px;
}

.theme-preview {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.theme-preview-main {
  flex: 1 1 260px;
  min-width: 0;
}

.theme-preview-header {
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 12px;
  min-height: 44px;
  margin: -16px -16px 0;
  border-bottom: 1px solid var(--border);
  color: var(--header-text);
  background: linear-gradient(180deg, color-mix(in srgb, var(--header-bg) 85%, var(--border) 15%), var(--header-bg));
  border-radius: 10px 10px 0 0;
}

.theme-preview-brand {
  font-weight: 700;
}

/* The Light/Dark switch beside the theme chooser: sun, a track, moon; on (aria-checked) is dark. */
.theme-mode {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border: 0;
  background: none;
  color: var(--icon, var(--text-dim));
  cursor: pointer;
  font: inherit;
}

.theme-mode-track {
  position: relative;
  width: 36px;
  height: 20px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--border);
}

.theme-mode-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  transition: transform 0.15s;
}

.theme-mode[aria-checked="true"] .theme-mode-knob {
  transform: translateX(16px);
}

.theme-mode[aria-checked="false"] .fa-sun,
.theme-mode[aria-checked="true"] .fa-moon {
  color: var(--accent);
}

.theme-optional {
  align-items: end;
}

.theme-opt {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.theme-preview p {
  margin: 0 0 10px;
}

.theme-preview .row {
  margin-bottom: 10px;
}

.theme-preview .members {
  margin: 0;
}

.subtabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
}

.subtab {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  color: var(--text-dim);
  padding: 8px 16px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.subtab:hover {
  color: var(--text);
}

.subtab.active {
  background: var(--bg-section);
  border-color: var(--border);
  color: var(--accent);
  margin-bottom: -1px;
}

.admin .panel {
  margin-top: 0;
}

.room-section-heading {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.room-section-heading h2 {
  margin: 0;
}

/* Token-sized: a nod to which room this is, not a picture of it. */
.room-token {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  object-fit: cover;
  border: 1px solid var(--border);
  flex: none;
}

/* room-sections wraps a variable number of per-room panels; it needs the
   same stacking gap .tab-stack gives its own direct children, since this
   whole div is just one of those children itself. */
#room-sections {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.section-head .btn {
  margin-left: auto;
}

.brand-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

.site-images {
  display: flex;
  gap: 12px;
  flex: none;
  align-items: flex-start;
}

/* --- rooms ------------------------------------------------------------------ */

/* Every manage tab is a stack of separate boxes: a heading box, then one
   box per user, room or settings section. */
.tab-rooms,
.tab-stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.tab-stack .panel h2:first-child,
.tab-stack .panel .section-head h1 {
  margin-top: 0;
}

.user-card.lobby {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}

.room-thumb {
  width: 36px;
  height: 36px;
  border-radius: 6px;
  background: var(--bg-card);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 14px;
}

.room-thumb .thumb {
  width: 100%;
  height: 100%;
}

.member-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.reactions-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}

.reaction-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.reaction-row .reaction-glyph {
  width: 44px;
  text-align: center;
  flex: none;
}

.reaction-row .reaction-label {
  flex: 1;
  min-width: 0;
}

.icon-row .icon-preview {
  width: 44px;
  text-align: center;
  flex: none;
  font-size: 20px;
}

.icon-row .icon-html {
  flex: 2;
  min-width: 0;
  font-family: ui-monospace, monospace;
}

.icon-row .icon-html.invalid {
  border-color: #b8503f;
}

/* Member tiles on a room: click to toggle, lit while in the room */
.member-toggle {
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  transition: opacity 0.15s ease, border-color 0.15s ease;
}

.member-toggle input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.member-toggle.online {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent);
}

.member-toggle:not(.online) {
  opacity: 0.45;
}

.member-toggle:hover:not(.locked) {
  opacity: 1;
}

.member-toggle.locked {
  cursor: default;
}

.slot-pick.wide {
  width: 170px;
}

/* The sign-in page behind a picture: it fills the whole viewport and the box lifts off it. */
html.has-background {
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
}

html.has-background body {
  background: transparent;
}

html.has-background .panel.login {
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.55);
}

.fields.grow {
  flex: 1;
}

/* Sign-in page: the text box stands as tall as the picture box beside it. */
.brand-row.stretch {
  align-items: stretch;
}

.brand-row.stretch .fields.grow {
  display: flex;
  flex-direction: column;
}

.brand-row.stretch .fields.grow label {
  flex: 1;
}

.brand-row.stretch .fields.grow textarea {
  flex: 1;
  min-height: 0;
  resize: vertical;
}

.borders-row .field-label {
  min-width: 130px;
}

.check input[type="number"] {
  width: 64px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 6px 8px;
  font: inherit;
  font-size: 14px;
}

@media (max-width: 600px) {
  .brand-row {
    flex-direction: column;
  }
}

/* --- image slots: click the picture to change it ---------------------------- */

.slot-pick {
  position: relative;
  display: block;
  width: 96px;
  height: 96px;
  border-radius: 6px;
  background: var(--bg-card);
  cursor: pointer;
  overflow: hidden;
  text-transform: none;
  letter-spacing: normal;
}

.slot-pick:hover {
  outline: 2px solid var(--accent);
}

.slot-pick img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.slot-pick .unset {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 12px;
}

.slot > span:not(.unset) {
  font-size: 12px;
}

input[type="color"] {
  width: 32px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-input);
  cursor: pointer;
}

/* checkbox and picture labels inside panels keep normal text */
.panel label.check,
.fields label.check {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  text-transform: none;
  letter-spacing: normal;
  font-size: 13px;
  color: var(--text);
}

.panel label.slot-pick {
  text-transform: none;
  letter-spacing: normal;
}

/* Icon/background pickers on the settings page: a small round remove button
   over the corner instead of a separate Remove button and a label that just
   repeats the heading above it. */
.slot-pick-wrap {
  position: relative;
  display: inline-block;
}

.slot-remove {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--danger);
  color: #fff;
  font-size: 14px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.slot-remove:hover {
  background: color-mix(in srgb, var(--danger) 85%, white);
}

/* The icon shows at 24-40px in real use (the header, the sign-in page); a
   preview close to that size shows how it'll actually read instead of a
   detailed picture that turns to mush once it's shrunk down. */
.slot-pick.icon-size {
  width: 40px;
  height: 40px;
}

/* Manage > Modules */
.module-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.module-head h2 {
  margin: 0;
}

.module-icon {
  font-size: 22px;
  color: var(--accent);
}

.module-asks {
  margin: 0 0 12px;
  padding-left: 20px;
}

.pill {
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}

.pill.on {
  color: var(--accent);
  border-color: var(--accent);
}

.pill.warn {
  color: #e0b25a;
  border-color: #e0b25a;
}

/* Narrow windows (a phone): the chat replaces the video while it is open, with
   the toolbar still under it, so one click on Chat brings the video back. */
.stage.narrow {
  grid-template-columns: minmax(0, 1fr);
}

.stage.narrow.chat-open {
  grid-template-rows: minmax(0, 1fr) auto auto;
}

/* One view at a time: the pane being shown (the conference, the chat or a module) takes the whole stage above
   the tab bar. The others stay open but hidden, so a call keeps running while the chat is read. */
.narrow-hidden {
  display: none !important;
}

.stage.narrow.chat-open .module-chat > * {
  grid-column: 1;
}

.stage.narrow.chat-open .chat-resize {
  display: none;
}

/* A docked module on a narrow window is laid out as the chat is: it fills the stage, its action bar under it. */
.stage.narrow.module-open {
  grid-template-rows: minmax(0, 1fr) auto auto;
}

.stage.narrow.module-open .module-docked > * {
  grid-column: 1;
}

.stage.narrow.module-open .module-docked .mod-content {
  grid-row: 1;
}

.stage.narrow.module-open .module-docked .dock-bar {
  grid-row: 2;
}

.stage.narrow.module-open .dock-resize {
  display: none;
}

/* A comfortable touch width for a module's titlebar buttons, the close button above all; the height is the shared header's. */
.stage.narrow .module-docked .mod-header .msg-btn {
  min-width: 44px;
  align-self: stretch; /* as tall as the shared header, which sets the touch height */
}

/* Something dragged over the chat, to send as a picture */
.chat.drop .mod-content,
.chat.drop .chat-bar {
  outline: 2px dashed var(--accent);
  outline-offset: -6px;
}

/* --- modules ------------------------------------------------------------ */

/* Header items for modules that have a page of their own. */
.module-nav {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.module-nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 6px;
  color: var(--icon, color-mix(in srgb, var(--header-text) 75%, transparent));
  font-weight: 500;
}

.module-nav-link:hover {
  color: var(--icon-hover);
  background: color-mix(in srgb, var(--icon-hover) 14%, transparent);
  text-decoration: none;
}

/* Module links in the header are icons, the same size as the others. */
.module-nav-label {
  display: none;
}

/* A module's own page: the frame fills everything under the header. */
body.module-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

.module-stage {
  flex: 1;
  min-height: 0;
  display: flex;
}

.module-frame {
  flex: 1;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg);
}

.module-missing {
  margin: 40px auto;
}

/* The conference in a floating panel or a window of its own: its tiles are styled under
   a .stage, so it brings one. Popovers hang above the toolbar, past the panel's edge. */
.conference-panel {
  overflow: visible;
}

.conference-panel > .stage,
.conference-window .stage {
  height: 100%;
  overflow: visible;
  border-radius: 10px;
}

.conference-panel .conference-head {
  cursor: grab;
  user-select: none;
  touch-action: none;
  border-radius: 10px 10px 0 0;
}

.conference-panel .video-bar {
  border-radius: 0 0 10px 10px;
}

body.conference-window {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
}

.conference-window .stage {
  height: 100vh;
  border-radius: 0;
}

/* Nothing docked, floating or in a window of its own. */
.stage-empty {
  grid-column: 1 / -1;
  grid-row: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  color: var(--text-dim);
  text-align: center;
}

/* The flexible column takes what is left, so it has no edge to drag. */
.is-flex .chat-resize,
.is-flex .dock-resize {
  display: none;
}

/* Room modules: the toolbar menu and the floating panels. */
.modules-menu {
  z-index: 30;
  left: 0;
  right: auto;
  bottom: auto;
  transform: none;
  width: max-content;
  min-width: 180px;
  max-width: calc(100% - 24px);
  padding: 6px;
  gap: 2px;
}

.modules-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: var(--secondary);
  color: var(--secondary-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.modules-menu-item:hover {
  background: var(--secondary-hover);
}

.modules-menu-item.on {
  border-color: var(--accent);
}

.modules-menu-item .badge {
  margin-left: auto;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* Above the call (and the overlay pages), below nothing that matters. */
.module-layer {
  position: fixed;
  inset: 0;
  z-index: 25;
  pointer-events: none;
}

.module-panel {
  position: absolute;
  display: flex;
  flex-direction: column;
  min-width: 240px;
  min-height: 160px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  pointer-events: auto;
}

.module-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: none;
  background: var(--shade);
  font-weight: 600;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.module-panel-title {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.module-panel-frame {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  background: var(--bg);
}

.module-panel-grip {
  position: absolute;
  z-index: 8; /* above the conference's toolbar, which reaches the panel's corner */
  right: 0;
  bottom: 0;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  touch-action: none;
  background: linear-gradient(135deg, transparent 50%, var(--border) 50%, var(--border) 60%, transparent 60%, transparent 70%, var(--border) 70%, var(--border) 80%, transparent 80%);
}

.module-layer.dragging .module-panel-frame {
  pointer-events: none;
}

/* The grid a snapped floating pane sits in, drawn over the stage only while one is being dragged or
   resized (room-modules.js sets the cell size as --snap-cw/--snap-ch and the box to the stage's). */
.snap-grid {
  position: absolute;
  display: none;
  pointer-events: none;
  box-sizing: border-box;
  border: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  background-image: linear-gradient(to right, color-mix(in srgb, var(--accent) 45%, transparent) 1px, transparent 1px), linear-gradient(to bottom, color-mix(in srgb, var(--accent) 45%, transparent) 1px, transparent 1px);
  background-size: var(--snap-cw) var(--snap-ch);
}

.module-layer.snapping .snap-grid {
  display: block;
}

/* Unread count on a module's header item */
.nav-badge {
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* Notification toasts */
.toast-layer {
  position: fixed;
  top: 56px;
  right: 12px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(340px, calc(100vw - 24px));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: var(--bg-section);
  color: var(--text);
  font: inherit;
  text-align: left;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  pointer-events: auto;
}

.toast:hover {
  background: var(--secondary-hover);
}

.toast-icon {
  margin-top: 2px;
  color: var(--accent);
}

.toast-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.toast-body {
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.toast-from {
  color: var(--text-dim);
  font-size: 11px;
}

/* Docked modules: a column of the room's grid, beside the video and the chat. */
.module-docked > .mod-content {
  grid-row: 1 / -1; /* fills the whole column unless the module has an action bar */
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--bg) 96%, transparent); /* the same as the chat's content */
  border-left: 1px solid var(--border);
}

/* The one header every module has: same height everywhere so columns line up. */
.mod-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  flex: none;
  height: var(--module-header-h);
  padding: 0 6px 0 10px;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

.mod-header-tools {
  display: inline-flex;
  gap: 2px;
  flex: none;
}

/* A module's optional toolbar row, under the titlebar and above its content: filters, tabs, a progress
   bar -- see host.toolbar.set. Sits inside the same flex column as the titlebar and the content in both
   a docked and a floating module, so no layout rule needs to know which one it is. */
.mod-toolbar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  height: var(--toolbar-h);
  padding: 0 10px;
  border-bottom: 1px solid var(--border);
  background: var(--shade);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none; /* a narrow pane scrolls the row by touch/drag rather than clipping it */
}

.mod-toolbar::-webkit-scrollbar {
  display: none;
}

.mod-toolbar[hidden] {
  display: none;
}

.tb-text {
  color: var(--text-dim);
  font-size: 12px;
  white-space: nowrap;
}

.tb-sep {
  align-self: stretch;
  width: 1px;
  margin: 6px 0;
  background: var(--border);
}

.tb-tabs {
  display: inline-flex;
  flex: none;
}

.tb-tab {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: none;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.tb-tab-icon {
  padding-inline: 8px;
}

.tb-tab:first-child {
  border-radius: 6px 0 0 6px;
}

.tb-tab:last-child {
  border-radius: 0 6px 6px 0;
}

.tb-tab:not(:first-child) {
  margin-left: -1px;
}

.tb-tab.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.tb-progress {
  position: relative;
  flex: 1;
  min-width: 40px;
  max-width: 160px;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

.tb-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
}

.tb-slider {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

.tb-slider-label {
  color: var(--text-dim);
  font-size: 12px;
  white-space: nowrap;
}

.tb-slider input[type="range"] {
  width: 90px;
  accent-color: var(--accent);
}

.tb-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.tb-btn:hover,
.tb-btn:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  outline: none;
}

.tb-btn:disabled {
  color: var(--text-dim);
  cursor: default;
}

.tb-btn.on,
.tb-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.tb-btn.tb-more {
  margin-left: auto;
}

/* The host-drawn "..." for a header, action bar or toolbar row that ran out of room (module-host.js's
   toggleOverflow) -- the same idea as a module's own host.menu.show, but for the host's own chrome, so
   it can't reach into a module's sandboxed frame to draw there. */
.host-menu {
  position: absolute;
  z-index: 9;
  min-width: 160px;
  max-width: 280px;
  padding: 4px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  font-size: 13px;
}

.host-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.host-menu-item i {
  flex: none;
  width: 1em;
}

.host-menu-item:hover,
.host-menu-item:focus-visible {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  outline: none;
}

.host-menu-item:disabled {
  color: var(--text-dim);
  cursor: default;
}

.dock-frame {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  background: var(--bg);
}

.dock-resize {
  grid-row: 1 / -1;
  justify-self: start;
  width: 8px;
  margin-left: -4px;
  cursor: ew-resize;
  z-index: 7;
  touch-action: none;
}

.dock-resize:hover,
.dock-resize.dragging {
  background: color-mix(in srgb, var(--accent) 50%, transparent);
}

.stage.resizing-dock .dock-frame,
.stage.resizing-dock .module-video iframe {
  pointer-events: none; /* a frame under the pointer would swallow the drag */
}


/* A module popped out into its own window: just the module under its own titlebar. */
body.module-popout .module-titlebar {
  position: relative;
}

body.module-popout .topbar {
  display: none;
}

/* A module's action bar. Docked, it is a cell in the room's shared bottom row,
   lined up with the video toolbar and the chat box; elsewhere it is a strip
   under the module. */
.module-docked.has-bar > .mod-content {
  grid-row: 1;
}

.dock-bar {
  grid-row: 2;
  z-index: 1;
  justify-content: flex-end;
  gap: 8px;
  padding: 8px 10px;
  background: var(--shade); /* the same as the video toolbar's cell beside it */
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
}

.module-panel-bar,
.module-bar {
  display: flex;
  flex: none;
  justify-content: flex-end;
  gap: 8px;
  padding: 8px 10px;
  background: var(--shade);
  border-top: 1px solid var(--border);
}

.module-panel-bar[hidden],
.module-bar[hidden],
.dock-bar[hidden] {
  display: none;
}

/* A module's quick-add: a text field with a small + button, filling the module's bar (docked bar, panel bar, page bar). It sits at the bottom, lined up with the chat box. */
.dock-bar:has(.quick-add),
.module-panel-bar:has(.quick-add),
.module-bar:has(.quick-add) {
  align-items: flex-end;
}

.quick-add {
  display: flex;
  flex: 1;
  align-items: flex-end;
  gap: 6px;
  min-width: 0;
  margin: 0;
}

.quick-add input {
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-input);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}

.quick-add input::placeholder {
  color: var(--text-dim);
}

.quick-add input,
.quick-add-go {
  box-sizing: border-box;
  height: var(--bar-control-h);
}

.quick-add-go {
  flex: none;
  padding-left: 10px;
  padding-right: 10px;
}

/* a quick-add with buttons beside it (a module sets more than one bar item): the field takes the room, and an icon-only button is as tall as the field is */
.dock-bar:has(.quick-add),
.module-panel-bar:has(.quick-add),
.module-bar:has(.quick-add) {
  gap: 6px;
}

.mod-bar .bar-icon {
  flex: none;
  width: var(--bar-control-h);
  height: var(--bar-control-h);
  padding: 0;
}

body.module-page .module-stage {
  flex-direction: column;
}

/* The chat as a floating panel or in a window of its own: its pieces stack instead of taking grid columns. */
.native-panel .chat-resize,
.chat-window-grid .chat-resize {
  display: none;
}

.native-panel .module-chat .mod-content {
  flex: 1;
  min-height: 0;
  border-left: 0;
}

.native-panel .chat-bar {
  flex: none;
  border-left: 0;
}

.native-panel .chat header {
  cursor: grab;
  user-select: none;
  touch-action: none;
}

body.chat-window {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
}

.chat-window-grid {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  height: 100vh;
}

.chat-window-grid .module-chat > * {
  grid-column: 1;
}

.chat-window-grid .module-chat .mod-content,
.chat-window-grid .chat-bar {
  border-left: 0;
}

.module-nav-link i {
  font-size: 16px;
}

.module-nav-link .nav-badge {
  position: absolute;
  top: -2px;
  right: -2px;
}

/* "Join with": the panes a room opens with, from its card on the room list. */
.room-choice {
  position: relative;
}

.join-with {
  position: absolute;
  right: 12px;
  bottom: 64px;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 200px;
  padding: 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg-section);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}

.join-with-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.join-with .check {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
}

.join-with .check input {
  width: auto;
  margin: 0;
}

/* In a room the Modules menu is the way to a module; the header's links to the global pages would be
   a second, confusing way to the same thing. The links are tools the nav-bar registry draws straight into the
   zone (and, on a phone, into the menu), so this names the link, not a wrapper, at both places. */
body.at-table .topbar .module-nav-link,
body.at-table .topbar.menu-open .links .module-nav-link {
  display: none;
}

/* A newer version of a module ships with this server. */
.module-update {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 6px 0;
  padding: 8px 10px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* One bundled module per row: the icon, then the name and its description (which may wrap), then Install on the right,
   whatever the description's length. Rows are separated by a rule, not by whitespace. */
.module-available {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 14px;
  margin: 0;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.module-available:first-of-type {
  border-top: 0;
}

.module-available .module-icon {
  font-size: 22px;
  width: 30px;
  text-align: center;
}

.module-available .grow {
  min-width: 0;
}

.module-available .btn {
  white-space: nowrap;
}

.module-available .module-needs {
  margin-top: 4px;
  color: var(--text);
}

/* A module's own icon buttons in its titlebar (host.header.set), then a pipe before the pane's buttons. */
.titlebar-custom {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.header-pipe {
  width: 1px;
  height: 16px;
  margin: 0 6px;
  background: var(--border);
}

/* A module that runs in the page: an element of its own, holding a shadow root the module fills. */
.module-root {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  color: var(--text);
}

.module-runmode { margin: 8px 0; }
/* the filters at the top of the Modules tab */
.module-filters { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.filter-chip { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-card); color: var(--text-dim); font: inherit; font-size: 13px; cursor: pointer; }
.filter-chip:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--text); }
.filter-chip.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.filter-chip .count { min-width: 18px; padding: 0 5px; border-radius: 9px; background: color-mix(in srgb, var(--text) 16%, transparent); font-size: 11px; line-height: 18px; text-align: center; }
.filter-chip.on .count { background: color-mix(in srgb, var(--on-accent) 25%, transparent); }
/* recent module activity: a box of rows that scrolls, newest first; the text can be selected and copied */
.activity-head { display: flex; align-items: baseline; gap: 10px; margin: 18px 0 6px; }
.activity-head h3 { margin: 0; font-size: 15px; }
.activity-log-box { max-height: 240px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-input, var(--bg-card)); user-select: text; }
.activity-log-box:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.activity-log { list-style: none; margin: 0; padding: 0; }
.activity-row { display: flex; align-items: baseline; gap: 10px; padding: 7px 12px; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); font-size: 13px; line-height: 1.4; }
.activity-row:first-child { border-top: 0; }
.activity-time { flex: none; width: 92px; color: var(--text-dim); font-variant-numeric: tabular-nums; font-size: 12px; }
.activity-source { flex: none; width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--accent); font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
.activity-event { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.activity-row[data-level="warn"] { background: color-mix(in srgb, var(--danger) 10%, transparent); }
.activity-row[data-level="warn"] .activity-source, .activity-row[data-level="warn"] .fa-triangle-exclamation { color: var(--danger); }
.activity-empty { margin: 0; padding: 14px 12px; }
@media (max-width: 640px) { .activity-row { flex-wrap: wrap; gap: 2px 10px; } .activity-time, .activity-source { width: auto; } .activity-event { flex-basis: 100%; } }

/* --- phone: the room as an app ---------------------------------------------
   Below 640px (the same width the stage calls "narrow") the table is a phone layout: a slim
   header, the stage, and the room bar as a tab bar fixed to the bottom, one thumb away. The
   panes in it are the same items as on a desktop, shown as tabs (icon over name, accent when
   open). The call toolbar sits directly above it.
   The page is pinned to the visible screen with position: fixed rather than a measured
   height, so a browser's own bottom bar (Chrome on an iPhone) can never cover the toolbar. */
@media (max-width: 640px) {
  body.at-table {
    --tabbar-h: 54px;
    /* Sized in dynamic viewport units, not pinned with inset: 0 (which is the large viewport in
       Chrome on an iPhone, 37px taller than what shows) and not 100% (47px short in the
       home-screen app). Measured on an iPhone 13 Pro Max, see documentation/architecture. */
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100vh;
    height: 100dvh;
  }

  body.at-table .topbar {
    min-height: calc(40px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 10px 0;
  }

  body.at-table .topbar .brand .icon {
    width: 39px;
    height: 39px;
    margin-left: -10px;
  }

  /* The tab bar sits under the toolbar, so the toolbar has no home indicator to clear. */
  body.at-table .floatbar,
  body.at-table .stage.compact .floatbar {
    padding-bottom: 6px;
  }

  /* room.js moves the bar out of the header, after the stage, so it is the last thing in the
     page's column: the toolbar sits right above it with no padding to calculate. */
  body.at-table .subnav {
    display: flex; /* the tab bar: the pane switches are the bar; the room's name and the middle zone are not drawn */
    flex: none;
    width: auto;
    margin: 0;
    /* The tabs get their full height; the safe-area padding (a browser's bottom bar, the home
       indicator) comes on top of it, not out of it. */
    min-height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    box-sizing: border-box;
    padding: 0 4px env(safe-area-inset-bottom);
    align-items: stretch;
    gap: 0;
    background: var(--header-bg);
    border-top: 1px solid var(--border);
  }

  body.at-table .subnav-panes {
    flex: 1;
    gap: 0;
    justify-content: space-around;
  }

  body.at-table .subnav-panes .modules-menu-item {
    flex: 1 0 60px;
    flex-direction: column;
    justify-content: center;
    gap: 3px;
    min-width: 0;
    padding: 4px 2px;
    border-radius: 8px;
    font-size: 11px;
    line-height: 1.1;
  }

  body.at-table .subnav-panes .modules-menu-item::before {
    display: none;
  }

  body.at-table .subnav-panes .modules-menu-item i {
    font-size: 18px;
  }

  body.at-table .subnav-panes .modules-menu-item span:not(.badge) {
    display: block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  body.at-table .subnav-panes .modules-menu-item.on {
    color: var(--accent);
  }

  body.at-table .subnav-panes .modules-menu-item {
    position: relative;
  }

  body.at-table .subnav-panes .modules-menu-item .badge {
    position: absolute;
    top: 2px;
    left: calc(50% + 6px);
    margin: 0;
  }

  body.at-table .subnav-tools {
    align-items: stretch;
    margin-left: 0;
  }

  body.at-table .subnav #fullscreen-toggle,
  body.at-table .subnav #popout {
    display: none; /* a phone has no use for either */
  }

  /* A pane fills the screen: no floating it over the page or opening it in a window. */
  body.at-table .stage [data-mode],
  body.at-table .stage [data-popout] {
    display: none;
  }

  body.at-table .subnav #leave-room {
    height: auto;
    min-width: 48px;
    padding: 0 8px;
    font-size: 20px;
    color: var(--danger-text);
  }

  /* In the call but looking at something else: a dot on the Conference tab, green with the
     microphone off and red while it is live, so nobody talks unseen. */
  body.at-table .subnav-panes .modules-menu-item.in-call::after {
    content: "";
    position: absolute;
    top: 6px;
    left: calc(50% + 6px);
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--ok);
    border: 2px solid var(--header-bg);
  }

  body.at-table .subnav-panes[data-mic="live"] .modules-menu-item.in-call::after {
    background: var(--danger);
  }

}

/* --- phone: the header's links are a menu -----------------------------------
   Below 640px, on every page, the header keeps where you are (the server, the crumb) and a menu
   button. The links (your profile, the rooms, Manage, install, the module pages, sign out) open
   as a panel under it, with their names, big enough to tap. brand.js builds the button. */
.topbar .nav-toggle,
.topbar .call-settings-link {
  display: none;
}

@media (max-width: 640px) {
  /* a row again: the logo and the crumb, then the menu button; the middle zone's links are in the menu (brand.js moves them) */
  .topbar {
    display: flex;
    justify-content: space-between;
  }

  .topbar .nav-middle {
    display: none;
  }

  .topbar .nav-toggle {
    display: inline-flex;
    flex: none;
    width: 40px;
    height: 36px;
    margin-left: auto;
    font-size: 18px;
  }

  .topbar.menu-open .links .core-link {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    width: auto;
    height: auto;
    min-height: 44px;
    padding: 6px 12px;
    border-radius: 8px;
    color: var(--text);
    font-size: 15px;
    font-weight: 500;
  }

  .topbar.menu-open .links .core-link i {
    width: 22px;
    font-size: 18px;
    color: var(--icon, var(--text-dim));
  }

  .topbar.menu-open .links .topbar-clock {
    padding: 6px 12px;
  }

  .topbar nav.links {
    display: none;
  }

  .topbar.menu-open nav.links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 2px;
    position: absolute;
    top: 100%;
    right: 8px;
    z-index: 40;
    min-width: 240px;
    max-width: calc(100vw - 16px);
    padding: 8px;
    box-sizing: border-box;
    background: var(--bg-section);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
  }

  .topbar.menu-open .links > a:not([hidden]),
  .topbar.menu-open .links > button:not([hidden]),
  .topbar.menu-open .links .module-nav-link {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    width: auto;
    height: auto;
    min-height: 44px;
    padding: 6px 12px;
    border-radius: 8px;
    color: var(--text);
    font-size: 15px;
    font-weight: 500;
  }

  .topbar.menu-open .links .icon-link i,
  .topbar.menu-open .links .module-nav-link i {
    width: 22px;
    font-size: 18px;
    color: var(--icon, var(--text-dim));
  }

  /* The links are icons with a name in aria-label; the menu shows the name. */
  .topbar.menu-open .links > .icon-link::after {
    content: attr(aria-label);
  }

  .topbar.menu-open .links .module-nav-label {
    display: inline;
  }

  .topbar.menu-open .links .whoami {
    color: var(--text);
  }

  .topbar.menu-open .links .whoami img {
    width: 28px;
    height: 28px;
  }

  .topbar.menu-open .links .nav-divider {
    display: block;
    width: auto;
    height: 1px;
    margin: 4px 0;
  }
}

/* Dashboard widgets: the cards on the rooms page (public/dashboard.js). Only theme tokens, so it follows any theme. */
#dashboard .dashboard-widget {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding: 14px 16px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 10px;
}

/* Who is around: a titled section above the room cards, its people in a grid. */
.whos-around {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px 16px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.whos-around[hidden] {
  display: none;
}

.whos-around-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.whos-around-title i {
  color: var(--accent);
}

.whos-around-count {
  min-width: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
  font-size: 11px;
  line-height: 20px;
  text-align: center;
}

.whos-around-count:empty {
  display: none;
}

.whos-around-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 8px;
}

.whos-around .dashboard-person {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-card);
  font-size: 14px;
}

.whos-around .dashboard-person.in-room {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

.whos-around .dashboard-person img {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg);
}

.whos-around .dashboard-person-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.whos-around .dashboard-person-name {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.whos-around .dashboard-person-where,
.whos-around .dashboard-empty {
  overflow: hidden;
  color: var(--text-dim);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* a green dot before "online", the accent for someone in a room */
.whos-around .dashboard-person-where::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text-dim) 60%, transparent);
}

.whos-around .dashboard-person.in-room .dashboard-person-where::before {
  background: var(--accent);
}

.whos-around .dashboard-empty {
  margin: 0;
}

.whos-around .dashboard-person-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.whos-around .dashboard-person-call {
  color: var(--accent);
}

#dashboard .dashboard-widget-title {
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-decoration: none;
  text-transform: uppercase;
}

#dashboard a.dashboard-widget-title:hover {
  color: var(--accent);
}

#dashboard .dashboard-widget-title i {
  color: var(--accent);
}

#dashboard .dashboard-widget-frame {
  display: block;
  width: 100%;
  min-height: 60px;
  border: 0;
  background: transparent;
}

#dashboard .dashboard-person {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-size: 14px;
}

#dashboard .dashboard-person img {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg);
}

#dashboard .dashboard-person-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#dashboard .dashboard-person-where,
#dashboard .dashboard-empty {
  color: var(--text-dim);
  font-size: 12px;
}

#dashboard .dashboard-empty {
  margin: 0;
}

#dashboard .dashboard-person-call {
  color: var(--accent);
}

#dashboard .dashboard-badge {
  min-width: 16px;
  margin-left: 8px;
  padding: 0 5px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

#dashboard .dashboard-widget > header {
  display: flex;
  align-items: center;
}

/* The invitation toast (public/brand.js): Join and Decline under the text. */
.toast-invite {
  cursor: default;
}

.toast-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.whos-around .dashboard-invite {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text-dim);
  cursor: pointer;
}

.whos-around .dashboard-invite:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.whos-around .whos-around-note {
  margin: 0;
  color: var(--danger);
  font-size: 12px;
}

/* Module settings forms (public/module-settings.js) */
.module-settings-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.module-settings-card:first-child {
  border-top: 0;
}

.module-settings-card h3 {
  margin: 0;
  font-size: 15px;
}

.module-settings-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.module-setting .hint {
  margin: 2px 0 0;
}

/* ---- a module's own configuration page (Manage > Modules > Module Configuration) ---- */
.module-config-back { display: inline-flex; align-items: center; gap: 6px; margin: 0 0 10px; color: var(--text-dim); text-decoration: none; font-size: 13px; }
.module-config-back:hover { color: var(--accent); }
.module-config h1 { margin: 0; font-size: 20px; }
.module-config .module-head { margin-bottom: 8px; }
.module-config .module-settings-card { border: 0; padding: 0; margin-top: 12px; }

/* a table of files to tick (a module setting that takes several) */
/* Manage > Server > Environment: each cap as a bar, and the past-due banner. */
.env-caps { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 16px; margin: 8px 0 12px; align-items: center; }
.env-caps dt { color: var(--text-dim); font-size: 13px; }
.env-caps dd { margin: 0; display: flex; align-items: center; gap: 10px; }
.env-cap-text { font-size: 13px; min-width: 9em; }
.env-cap-bar { flex: 1; max-width: 260px; height: 8px; border-radius: 4px; background: var(--bg-section); overflow: hidden; }
.env-cap-bar span { display: block; height: 100%; background: var(--accent); }
.env-cap-bar.warn span { background: var(--danger); }
.env-banner { padding: 8px 12px; border: 1px solid var(--danger); border-radius: 8px; background: color-mix(in srgb, var(--danger) 12%, var(--bg-card)); color: var(--text); }
/* The same banner across the top of every page, for an owner, while the environment is past due or suspended. */
.env-page-banner { padding: 6px 16px; background: color-mix(in srgb, var(--danger) 18%, var(--bg)); border-bottom: 1px solid var(--danger); color: var(--text); font-size: 13px; text-align: center; }
.env-page-banner a { color: var(--text); font-weight: 600; margin-left: 6px; }
/* The host console's plans catalog: one card per plan, the design system's fields grid inside it. */
.plans-editor { display: grid; gap: 12px; margin: 8px 0; }
.plan-row { display: grid; gap: 10px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-card, var(--bg)); }
.plan-row .plan-id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* The host console's managed AI: one company per row, its own small form. */
.ai-services { display: grid; gap: 12px; margin-top: 8px; }
.ai-service { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-card, var(--bg)); }
.ai-service-head { display: flex; align-items: center; gap: 10px; }
.ai-service-address { display: flex; flex-direction: column; gap: 4px; }
.shared-files { margin: 6px 0 0; padding-left: 18px; font-size: 13px; }
.shared-files li { margin: 2px 0; }
.files-table { width: 100%; border-collapse: collapse; margin: 6px 0; }
.files-table th, .files-table td { padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border); font-size: 13px; }
.files-table th { color: var(--text-dim); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
.files-table td:first-child, .files-table th:first-child { width: 48px; }

/* a choice setting: what the chosen option means, under its list (e.g. a service's terms) */
.option-help:empty { display: none; }
.option-help { padding: 8px 10px; border-left: 3px solid var(--accent); border-radius: 0 6px 6px 0; background: color-mix(in srgb, var(--accent) 7%, var(--bg-card)); }
.option-help p { margin: 4px 0; }
.option-help a { color: var(--accent); }

/* Reordering the columns of the stage: a docked pane is dragged by its titlebar (buttons in it still click). */
.mod-header.reorderable, .chat-head.reorderable, .conference-head.reorderable, header.reorderable { cursor: grab; }
.reorderable button, .reorderable a, .reorderable input { cursor: pointer; }
.stage.reordering, .stage.reordering * { cursor: grabbing !important; user-select: none; }
.stage.reordering iframe { pointer-events: none; }
.pane-lifted { position: relative; z-index: 3; box-shadow: 0 0 0 2px var(--accent), 0 12px 32px color-mix(in srgb, black 40%, transparent); }

/* a list setting (Planner's marker types): a row each with a colour, an icon, a label and tools */
.list-rows { list-style: none; margin: 6px 0 8px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.list-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card); }
.list-row input[type="color"] { flex: none; width: 34px; height: 30px; padding: 0; border: 1px solid var(--border); border-radius: 6px; background: none; cursor: pointer; }
.list-row [data-f="label"] { flex: 1; min-width: 0; }
.list-tools { display: flex; align-items: center; gap: 4px; flex: none; }
.icon-pick { position: relative; flex: none; }
.icon-pick summary { display: flex; align-items: center; justify-content: center; width: 34px; height: 30px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-input, var(--bg-card)); cursor: pointer; list-style: none; }
.icon-pick summary::-webkit-details-marker { display: none; }
/* the popover form of the grid, only inside a list row's icon picker; the plain .icon-grid above stays a static grid */
.icon-pick .icon-grid { position: absolute; z-index: 20; top: 36px; left: 0; display: grid; grid-template-columns: repeat(8, 32px); gap: 2px; padding: 6px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-section); box-shadow: 0 10px 28px color-mix(in srgb, black 35%, transparent); }
.icon-pick .icon-grid button { width: 32px; height: 32px; padding: 0; border: 0; border-radius: 6px; background: none; color: var(--text); cursor: pointer; }
.icon-pick .icon-grid button:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); }
@media (max-width: 640px) { .list-row { flex-wrap: wrap; } .icon-pick .icon-grid { grid-template-columns: repeat(6, 36px); } }

/* the saved search results of a module (Module Configuration): counts and purge buttons */
.cache-section { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; }
.cache-section h3 { margin: 0; font-size: 15px; }
.cache-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin: 0; }
.cache-stats > div { padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card); }
.cache-stats dt { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
.cache-stats dd { margin: 2px 0 0; font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cache-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.cache-older { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); }
.cache-older input { width: 72px; text-align: center; }

.region-section { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; }
.region-section h3 { margin: 0; font-size: 15px; }
.region-section #region-q { flex: 1; min-width: 180px; }
.region-confirm, .region-progress { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card); }
.region-confirm p, .region-progress p { margin: 0; }
.region-confirm label { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); }
#region-zoom { width: 60px; text-align: center; }
#region-filename { width: 200px; }
.region-progress progress { width: 100%; height: 8px; appearance: none; border: 0; border-radius: 999px; background: var(--bg-section); overflow: hidden; }
.region-progress progress::-webkit-progress-bar { background: var(--bg-section); }
.region-progress progress::-webkit-progress-value { background: var(--accent); }
.region-progress progress::-moz-progress-bar { background: var(--accent); }

/* the AI service panel (Modules tab) */
.ai-fields { display: flex; flex-direction: column; gap: 12px; margin: 12px 0; }
.ai-fields label { display: flex; flex-direction: column; gap: 4px; }
.ai-fields [hidden] { display: none; }
.ai-key-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ai-key-row input { flex: 1; min-width: 200px; }
.ai-usage { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border); }
.ai-usage h3 { margin: 0 0 8px; font-size: 14px; }
.ai-meter { height: 8px; border-radius: 999px; background: var(--bg-card); border: 1px solid var(--border); overflow: hidden; }
.ai-meter span { display: block; height: 100%; background: var(--accent); border-radius: inherit; transition: width 0.3s ease; }
.ai-meter.warn span { background: var(--danger); }

/* the pre-made background picker (public/background-picker.js) */
.bgpick { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 16px; background: color-mix(in srgb, black 60%, transparent); }
.bgpick-card { display: flex; flex-direction: column; width: min(880px, 100%); max-height: min(720px, 100%); background: var(--bg-section); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 20px 60px color-mix(in srgb, black 50%, transparent); overflow: hidden; }
.bgpick-head { display: flex; align-items: center; gap: 10px; padding: 14px 16px 8px; }
.bgpick-head h2 { margin: 0; font-size: 18px; flex: 1; }
.bgpick-close { width: 32px; height: 32px; border: 0; border-radius: 8px; background: none; color: var(--text-dim); cursor: pointer; }
.bgpick-close:hover { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
.bgpick-filters { display: flex; flex-direction: column; gap: 6px; padding: 0 16px 8px; }
.bgpick-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.bgpick-chip { height: 28px; padding: 0 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-card); color: var(--text-dim); font: inherit; font-size: 12px; cursor: pointer; }
.bgpick-chip.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.bgpick-body { flex: 1; min-height: 160px; overflow: auto; padding: 4px 16px 12px; }
.bgpick-msg { padding: 24px 0; text-align: center; }
.bgpick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
.bgpick-tile { display: flex; flex-direction: column; gap: 6px; padding: 6px; border: 2px solid transparent; border-radius: 12px; background: var(--bg-card); color: var(--text); font: inherit; font-size: 12px; text-align: left; cursor: pointer; transition: border-color 0.12s ease, transform 0.12s ease; }
.bgpick-tile img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; background: color-mix(in srgb, var(--accent) 12%, var(--bg-card)); }
.bgpick-tile span { padding: 0 2px; color: var(--text-dim); }
.bgpick-tile:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); transform: translateY(-1px); }
.bgpick-tile[aria-pressed="true"] { border-color: var(--accent); }
.bgpick-tile[aria-pressed="true"] span { color: var(--text); font-weight: 600; }
.bgpick-foot { display: flex; align-items: center; gap: 8px; padding: 10px 16px 14px; border-top: 1px solid var(--border); }
.bgpick-foot .grow { flex: 1; }
.bgpick-picked { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { .bgpick-tile { transition: none; } .bgpick-tile:hover { transform: none; } }
@media (max-width: 640px) { .bgpick { padding: 0; align-items: flex-end; } .bgpick-card { width: 100%; max-height: 92%; border-radius: 14px 14px 0 0; } .bgpick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.slot-library { margin-top: 6px; }

/* the AI service panel: every field styled alike (the url and number boxes were plain), and the model list */
.ai-fields input[type="url"], .ai-fields input[type="number"], .ai-fields input[type="text"], .ai-fields input[type="password"], .ai-fields select { background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: 8px 10px; font: inherit; font-size: 14px; text-transform: none; letter-spacing: normal; width: 100%; }
.ai-fields > label, .ai-label { color: var(--text-dim); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.ai-fields > label .hint, .ai-label .hint { text-transform: none; letter-spacing: normal; font-weight: 400; }
.ai-label { margin-bottom: 4px; }
.ai-model-row { display: flex; gap: 8px; align-items: stretch; }
.ai-model-row select, .ai-model-row input { flex: 1; min-width: 0; }
.ai-model-row .btn { flex: none; }
.linklike { align-self: flex-start; margin-top: 2px; padding: 0; border: 0; background: none; color: var(--accent); font: inherit; font-size: 12px; text-decoration: underline; cursor: pointer; }

/* the AI enable step, at the top of its configuration page */
.ai-enable { display: flex; align-items: center; gap: 12px; margin: 12px 0; padding: 12px 14px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 7%, var(--bg-card)); }
.ai-enable .grow { flex: 1; min-width: 0; }
.ai-enable p { margin: 2px 0 0; }

/* Two-step sign-in (plans/plan-mfa.md): the enrolment block (the QR, the key, the code, then the recovery codes), the
   code page, and the small rows on the profile and the console. */
.login-step.wide { max-width: 560px; }
.login label.check { flex-direction: row; align-items: center; gap: 8px; text-transform: none; letter-spacing: normal; font-size: 13px; }
.mfa-block { margin: 6px 0 10px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-card, var(--bg)); }
.mfa-scan { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.mfa-qr svg { display: block; width: 168px; height: 168px; padding: 8px; border-radius: 8px; background: #fff; }
.mfa-key { display: flex; flex-direction: column; gap: 6px; min-width: 220px; flex: 1; }
.mfa-key .key { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg, #1a1410); word-break: break-all; }
.mfa-key input { max-width: 9em; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.12em; }
.mfa-codes { list-style: none; margin: 8px 0; padding: 0; columns: 2; gap: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; line-height: 1.8; }
.owner-reset .hint { margin: 0; }
.owner-reset input { max-width: 14em; }

/* The host console's shared map folder: the files table names files in its first column (the generic .files-table
   keeps a narrow first column for a thumbnail), and the world map address is a label, a field with its button, and
   the hint under them in sentence case. */
#shared-files .files-table td:first-child, #shared-files .files-table th:first-child { width: auto; }
#shared-files .files-table td:last-child, #shared-files .files-table th:last-child { width: 1%; text-align: right; white-space: nowrap; }
.shared-address { display: grid; gap: 4px; margin: 16px 0 4px; }
.shared-address .row { flex-wrap: nowrap; }
.shared-address input { flex: 1; min-width: 0; }
.shared-address .btn { flex: none; }
.shared-address .hint { margin: 2px 0 0; }
