:root {
  --bg: #0c0e12;
  --panel: #14171d;
  --panel-alt: #191d24;
  --border: #262b33;
  --text: #e7eaee;
  --text-muted: #8a93a1;
  --accent: #f2a93b;
  --public: #3ecf8e;
  --private: #4fa3f7;
  --away: #f2a93b;
  --offline: #55606e;
  --danger: #e8635c;
  --radius: 10px;
  --menu-bar-height: 48px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: 'Space Grotesk', system-ui, sans-serif;
}

body {
  background-image: radial-gradient(circle at 20% 0%, #1a1e26 0%, var(--bg) 55%);
  min-height: 100vh;
  /* iOS Safari's address bar grows/shrinks with scroll, so 100vh overshoots
     the actually-visible area -- dvh tracks the real visible viewport.
     Declared second so it wins in browsers that support it, ignored (falls
     back to the 100vh above) otherwise. */
  min-height: 100dvh;
}

.mono {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
}

/* Shown once per browser (localStorage, see app.ts) before anything else is
   usable -- z-index above everything, including a centerstaged tile
   (z-index: 50), so it can't be clicked through or covered. */
.age-gate {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(6, 7, 9, 0.96);
}

.age-gate[hidden] {
  display: none;
}

body.age-gate-open {
  overflow: hidden;
}

.age-gate__box {
  max-width: 440px;
  width: 100%;
  padding: 32px 28px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}

.age-gate__title {
  margin: 0 0 16px;
  font-size: 26px;
  font-weight: 700;
  color: var(--accent);
}

.age-gate__text {
  margin: 0 0 24px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-muted);
}

.age-gate__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.age-gate__actions .btn {
  width: 100%;
}

.board {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* -- Hamburger menu + drop-down bar ----------------------------------------
   One consistent chrome mechanism for both Board and Gooning: a slim
   horizontal bar pinned to the top of the screen. Collapsed, it's just the
   toggle, the page (Board/Gooning) switch, and -- on the Gooning tab only --
   the session timer and view-mode switch. Expanding it (click the toggle)
   drops down the title and each panel's search/picker controls without
   covering the main content off to the side. Non-modal (no dimming
   backdrop) -- see app.ts.

   While collapsed, the whole bar fades out after a few seconds of no
   interaction (see app.ts) so the grid gets the full screen; any mouse
   movement fades it back in. It never fades while expanded. */

.menu-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 46;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
  transition: opacity 0.6s ease;
  /* Clears the iPhone notch/Dynamic Island in portrait. */
  padding-top: env(safe-area-inset-top);
}

/* Only fades on the Goon tab (body.tab-gooning, set in app.ts) -- the Goon
   grid benefits from reclaiming the header's space, but the Board list
   already scrolls under its own sticky filter bar and doesn't need the
   header to disappear. */
body.menu-bar-idle.tab-gooning:not(.menu-open) .menu-bar {
  opacity: 0;
  pointer-events: none;
}

/* Three-zone grid (toggle | centered mode switch | right-side cluster) so
   the Gooning mode switch sits truly centered in the bar regardless of how
   wide the left/right zones are. The two outer columns are equal `1fr`
   shares of the same leftover space either side of the (auto-sized) middle
   column, so the middle column's midpoint always lands on the row's true
   center -- unlike `auto 1fr auto`, which only centers within whatever
   space happens to be left between two *unequal*-width side groups. */
.menu-bar__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  height: var(--menu-bar-height);
  /* max() keeps the usual 14px on non-notched screens but grows to clear a
     landscape notch when the safe-area inset is larger. */
  padding: 0 max(14px, env(safe-area-inset-right)) 0 max(14px, env(safe-area-inset-left));
  cursor: pointer;
  /* On narrow phones, the toggle + mode switch + Board/Gooning switch + mute
     + fullscreen buttons (all at their 44px touch-target minimum, see the
     `pointer: coarse` rules) can add up to more than the viewport is wide.
     Rather than shrinking touch targets below Apple's recommendation or
     hiding controls, let the row scroll horizontally -- every control stays
     reachable via a swipe. Every direct child already has flex-shrink: 0
     (.menu-toggle, .icon-btn, .tabs), so they scroll intact instead of
     being squished. */
  overflow-x: auto;
  scrollbar-width: none;
}

.menu-bar__row::-webkit-scrollbar {
  display: none;
}

/* Explicit grid-column assignments (rather than relying on auto-placement)
   are load-bearing here: when #goon-mode-switch is `display: none` (Board
   tab), it stops generating a grid item at all, so plain row-major
   auto-placement would slide .menu-bar__right into the now-vacant middle
   column instead of the third one -- which is exactly what was happening,
   visually dragging the Board/Goon switch toward the center on the Board
   tab. Pinning each element to its column number regardless of the
   others' visibility avoids that entirely. */
.menu-toggle {
  grid-column: 1;
  justify-self: start;
}

#goon-mode-switch {
  grid-column: 2;
  justify-self: center;
}

.menu-bar__right {
  grid-column: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  justify-self: end;
  flex-shrink: 0;
}

.menu-toggle {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-alt);
  cursor: pointer;
  transition: border-color 0.15s ease;
}

.menu-toggle:hover {
  border-color: var(--accent);
}

.menu-toggle__bar {
  display: block;
  width: 15px;
  height: 2px;
  background: var(--text);
  border-radius: 1px;
  transition: transform 0.25s ease, opacity 0.15s ease;
}

body.menu-open .menu-toggle {
  border-color: var(--accent);
}

body.menu-open .menu-toggle__bar:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

body.menu-open .menu-toggle__bar:nth-child(2) {
  opacity: 0;
}

body.menu-open .menu-toggle__bar:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

.menu-drawer {
  max-height: 0;
  overflow: hidden;
  padding: 0 20px;
  transition: max-height 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}

body.menu-open .menu-drawer {
  max-height: 80vh;
  max-height: 80dvh;
  overflow-y: auto;
  padding: 4px 20px 20px;
}

/* Title block on the left, the Gooning session timer (relocated here from
   the collapsed bar) pinned to the right of the same row. */
.menu-drawer__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-bottom: 24px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border);
}

.icon-btn {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-alt);
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

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

.icon-btn.is-active {
  border-color: var(--accent);
  color: var(--accent);
}

/* Same [hidden]-vs-author-`display` gotcha as .tabs below: .icon-btn sets
   its own `display: flex`, which would otherwise beat the browser's default
   [hidden]{display:none} and keep a conditionally-hidden icon button (e.g.
   the paste buttons on browsers without Clipboard API support) visible. */
.icon-btn[hidden] {
  display: none;
}

.menu-section[hidden] {
  display: none;
}

/* Shrink the header chrome on narrow/mobile screens rather than enlarging
   it for touch -- on a small phone screen the hamburger, mode switch,
   Board/Gooning switch, and fullscreen button felt oversized at a uniform
   44px touch-target size, crowding out the content underneath. */
@media (max-width: 480px) {
  .menu-bar__row {
    gap: 8px;
  }
  .tab {
    padding: 5px 10px;
    font-size: 12px;
  }
  .icon-btn,
  .menu-toggle {
    width: 30px;
    height: 30px;
  }
}

/* Inverted from the original eyebrow+h1 pattern: "Goon Board" is now the
   big, primary heading, and "Track your streamers" sits underneath it as a
   small slogan-like tagline rather than the other way around. */
.board__heading-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.board__heading {
  margin: 0;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--accent);
}

/* Deliberately understated -- a quiet raw hit counter, not a stat meant to
   draw attention. */
.board__visit-count {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  color: var(--text-muted);
  opacity: 0.6;
}

.board__visit-count:empty {
  display: none;
}

.board__tagline {
  margin: 2px 0 0;
  font-size: 13px;
  font-weight: 500;
  font-style: italic;
  color: var(--text-muted);
}

.board__meta {
  display: block;
  margin-top: 6px;
  color: var(--text-muted);
  font-size: 13px;
}

/* Segmented pill switch, sits top-right of the drawer header. */
.tabs {
  display: inline-flex;
  flex-shrink: 0;
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
}

/* .tabs' own `display` otherwise beats the browser's default [hidden]
   handling (author styles always win over the UA stylesheet), which would
   keep an explicitly hidden .tabs element (e.g. the Gooning-only mode
   switch on the Board tab) visible and clickable anyway. */
.tabs[hidden] {
  display: none;
}

.tab {
  border: none;
  background: transparent;
  color: var(--text-muted);
  border-radius: 999px;
  padding: 6px 14px;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}

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

.tab--active {
  background: var(--accent);
  color: #201404;
}

/* The Cam Only/Interactive switch sits right next to the Board/Goon switch
   in the header and previously used the exact same active color, making
   the two adjacent pill controls easy to mix up at a glance. --private
   (blue) is reused here rather than inventing a new color, since it's
   already part of the palette (private-room status dot). */
#goon-mode-switch .tab--active {
  background: var(--private);
  color: #06131f;
}

/* Sits directly on the Board panel (not behind the hamburger, per explicit
   request -- filtering an already-visible list should not require opening a
   menu first) and stays pinned to the top of the scrollable list via
   position: sticky. Its top offset naturally tracks #panel-board main's own
   padding-top, which already animates down when the bar fades out idle (see
   body.menu-bar-idle rules), so the filter bar rides along with that. */
.board-filter-bar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  margin: 0 auto 16px;
  max-width: 1520px;
  width: 100%;
  padding-bottom: 4px;
  background: linear-gradient(to bottom, var(--bg) 80%, transparent);
}

/* A grouped "split button" rather than two disconnected ghost buttons, so
   the export/import pairing reads as one cohesive backup/restore action. */
.board-io {
  margin-bottom: 18px;
}

.board-io__label {
  display: block;
  margin-bottom: 8px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.board-io__actions {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.board-io__btn {
  border: none;
  background: var(--panel-alt);
  color: var(--text);
  padding: 10px 16px;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.board-io__btn + .board-io__btn {
  border-left: 1px solid var(--border);
}

.board-io__btn:hover {
  background: var(--accent);
  color: #201404;
}

.board-io__icon {
  display: inline-block;
}

.board-io .search__status {
  margin: 8px 0 0;
}

.search {
  margin-bottom: 18px;
}

.search__row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.search__input {
  flex: 1;
  /* Fallback for the filter bar above, which isn't a flex row -- flex: 1
     only has an effect inside a flex/grid parent, and without an explicit
     width the input would otherwise fall back to the browser's default
     (~20 characters), clipping the placeholder text. */
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  color: var(--text);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s ease;
}

.search__input:focus {
  border-color: var(--accent);
}

.search__input::placeholder {
  color: var(--text-muted);
}

.btn {
  border: none;
  border-radius: var(--radius);
  padding: 12px 18px;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.15s ease, transform 0.05s ease;
}

.btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.btn--primary {
  background: var(--accent);
  color: #201404;
}

.btn--primary:not(:disabled):hover {
  opacity: 0.88;
}

.btn--primary:not(:disabled):active {
  transform: scale(0.98);
}

/* An accent-outlined secondary action (e.g. "Track & Goon" next to the
   primary "Track" button) -- deliberately not .btn--ghost, whose hover
   turns red for destructive actions like Remove, which would be a
   misleading affordance here. */
.btn--secondary {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
}

.btn--secondary:not(:disabled):hover {
  background: rgba(242, 169, 59, 0.15);
}

.btn--secondary:not(:disabled):active {
  transform: scale(0.98);
}

.btn--ghost {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  padding: 6px 12px;
  font-size: 12px;
}

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

.search__status {
  min-height: 18px;
  margin: 8px 2px 0;
  font-size: 13px;
  color: var(--text-muted);
}

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

.search__status--ok {
  color: var(--public);
}

.legend {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 2px 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.legend__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', monospace;
}

.legend__note {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', monospace;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}

.dot--public {
  background: var(--public);
  box-shadow: 0 0 8px var(--public);
}

.dot--private {
  background: var(--private);
  box-shadow: 0 0 8px var(--private);
}

.dot--away {
  background: var(--away);
}

.dot--offline {
  background: var(--offline);
}

/* Both panels use the same frame: a flex column filling whatever space
   isn't taken by the (drawer-hidden) chrome, with their own main content
   area stretching to fill it. */
.panel {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.panel[hidden] {
  display: none;
}

.panel main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The header never fades on Board (see body.tab-gooning gating above), so
   unlike .goon-grid below, this padding stays constant -- no idle-reclaim
   rule needed here. */
#panel-board main {
  overflow-y: auto;
  padding: calc(var(--menu-bar-height) + 16px) 20px 24px;
}

.empty-state {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}

.empty-state[hidden],
.goon-grid[hidden] {
  display: none;
}

.empty-state p {
  margin: 4px 0;
}

.empty-state__hint {
  font-size: 13px;
}

.board-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 1520px;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px 20px;
}

@media (max-width: 900px) {
  .board-list {
    grid-template-columns: 1fr;
    max-width: 720px;
  }
}

.row {
  display: grid;
  grid-template-columns: 220px 1fr auto auto auto auto;
  align-items: center;
  gap: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  transition: border-color 0.2s ease;
}

/* Rows are individually toggled via the item-3 live filter (app.ts) -- .row
   sets its own `display: grid` above, which would otherwise beat the
   browser's default [hidden]{display:none} (same gotcha as .tabs/.icon-btn
   elsewhere in this file). */
.row[hidden] {
  display: none;
}

.row--online {
  border-color: var(--public);
  border-width: 2px;
}

.row__thumb {
  width: 220px;
  height: 132px;
  border-radius: 8px;
  object-fit: cover;
  background: var(--panel-alt);
  border: 1px solid var(--border);
  flex-shrink: 0;
}

/* Applied (via app.ts, based on each streamer's live `online` status) to
   any thumbnail -- Board rows, Goon picker chips, the Goon search preview
   -- so an offline room reads as visually "off" at a glance rather than
   showing a normal-looking snapshot that's actually stale/outdated. */
.thumb-offline {
  filter: grayscale(1) brightness(0.55);
}

.row__main {
  min-width: 0;
}

.row__name-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

.row__name {
  font-weight: 600;
  font-size: 15px;
  text-decoration: none;
  color: var(--text);
}

.row__name:hover {
  color: var(--accent);
}

.row__subject {
  margin: 3px 0 0;
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row__tags {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tag {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px;
  color: var(--text-muted);
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
}

.row__status {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  text-align: right;
  white-space: nowrap;
}

.row__status--public {
  color: var(--public);
}
.row__status--private {
  color: var(--private);
}
.row__status--away {
  color: var(--away);
}
.row__status--offline,
.row__status--unknown {
  color: var(--offline);
}

.row__viewers {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  text-align: right;
  color: var(--text-muted);
  min-width: 64px;
}

.row__viewers strong {
  color: var(--text);
  font-size: 15px;
}

/* Doubles as a label for "already on Goon" (its --active state) and the
   add/remove control itself -- one control instead of a separate static
   badge plus a separate button. */
.row__goon-toggle {
  border: 1px solid var(--border);
  background: var(--panel-alt);
  color: var(--text-muted);
  border-radius: 999px;
  padding: 6px 12px;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}

.row__goon-toggle:hover {
  border-color: var(--accent);
  color: var(--text);
}

.row__goon-toggle--active {
  background: var(--accent);
  border-color: var(--accent);
  color: #201404;
}

.row__goon-toggle--active:hover {
  opacity: 0.85;
}

@media (max-width: 640px) {
  .row {
    grid-template-columns: 88px 1fr auto;
    grid-template-areas:
      'thumb main status'
      'thumb main viewers'
      'goon goon remove';
    row-gap: 10px;
    /* Overrides the base .row's align-items: center -- thumb/main now
       properly span 2 rows (see grid-area assignments below), and
       top-aligning them reads as a normal mobile card instead of centering
       the thumbnail oddly within a tall spanned area. */
    align-items: start;
  }
  /* The area names above only take effect on children explicitly assigned
     to them -- without this, auto-placement ignores the named areas
     entirely and free-places every child in row-major order instead,
     which is what caused the mobile thumbnail to render in the wrong
     (non-spanning, seemingly "missing") position. */
  .row__thumb-link {
    grid-area: thumb;
  }
  .row__main {
    grid-area: main;
  }
  .row__status {
    grid-area: status;
  }
  .row__viewers {
    grid-area: viewers;
    text-align: right;
  }
  .row__goon-toggle {
    grid-area: goon;
    justify-self: start;
  }
  .row__remove-btn {
    grid-area: remove;
    justify-self: end;
  }
  .row__thumb {
    width: 88px;
    height: 66px;
  }
}

.goon-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
}

.goon-picker:empty {
  display: none;
}

/* A rounded-corner rectangle rather than a full pill -- a 999px pill radius
   didn't match the thumbnail's own much smaller corner radius, especially
   now that the thumbnail is big enough to make the mismatch obvious. */
.goon-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  color: var(--text-muted);
}

.goon-chip--online {
  border-color: var(--public);
  border-width: 2px;
  color: var(--text);
}

.goon-chip__thumb {
  width: 112px;
  height: 84px;
  object-fit: cover;
  border-radius: 6px;
  flex-shrink: 0;
  background: var(--panel-alt);
}

.goon-chip button {
  border: none;
  background: var(--panel-alt);
  color: var(--text);
  border-radius: 999px;
  width: 20px;
  height: 20px;
  line-height: 1;
  cursor: pointer;
  font-size: 13px;
}

.goon-chip button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

@media (pointer: coarse) {
  .goon-chip button {
    width: 28px;
    height: 28px;
  }
}

.goon-search-thumb {
  width: 96px;
  height: 72px;
  object-fit: cover;
  border-radius: 6px;
  flex-shrink: 0;
  background: var(--panel-alt);
  border: 1px solid var(--border);
}

.goon-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  gap: 4px;
  /* Only the top needs clearance for the bar; the other edges go flush to
     the viewport so tiles actually reach the corners (4 tiles = 4 corners),
     matching four windows dragged into a quadrant layout rather than a
     framed grid with margins around it. */
  /* Small deliberate deviation from "flush to every edge": on notched
     phones the bottom edge reserves the home-indicator safe area so tile
     controls there stay reachable, instead of sitting under the gesture
     bar. env() resolves to 0 on devices without a safe area, so this is a
     no-op everywhere else. */
  padding: var(--menu-bar-height) 0 env(safe-area-inset-bottom);
  transition: padding-top 0.6s ease;
  /* grid-template-columns/rows set inline by app.ts based on tile count, so
     the grid always divides the available space evenly -- 1 tile fills the
     screen, 2 split it, etc. */
}

/* Bar's faded out -- give the streams that space back instead of leaving a
   dead gap at the top. The grid's own ResizeObserver (app.ts) picks up the
   size change as it animates and keeps every tile's embed correctly scaled
   throughout. */
body.menu-bar-idle:not(.menu-open) .goon-grid {
  padding-top: 0;
}

.goon-tile {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  min-height: 0;
  min-width: 0;
}

/* Overlaid on top of the video instead of a layout row above it, so the
   stream gets the tile's full height instead of losing a chunk of it to a
   header bar. `pointer-events: none` on the bar itself lets clicks fall
   through to the video/embed controls underneath except on the name link
   and remove button, which opt back in. */
.goon-tile__bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 8px;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent);
  pointer-events: none;
}

.goon-tile__name {
  pointer-events: auto;
  font-weight: 600;
  font-size: 12px;
  color: var(--text);
  text-decoration: none;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.goon-tile__name:hover {
  color: var(--accent);
}

.goon-tile__bar .btn {
  pointer-events: auto;
}

/* Hover-revealed on tiles with real hover (mouse); always dimly visible on
   touch devices, which have no hover state to reveal it with. Shared by
   both the centerstage button and the "open in new tab" button next to it
   -- same look, same reveal behavior. */
.goon-tile__centerstage,
.goon-tile__open {
  pointer-events: auto;
  flex-shrink: 0;
  border: none;
  background: rgba(12, 14, 18, 0.75);
  color: var(--text);
  border-radius: 6px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  text-decoration: none;
  transition: opacity 0.15s ease, background-color 0.15s ease;
}

.goon-tile__centerstage:hover,
.goon-tile__open:hover {
  background: var(--accent);
  color: #201404;
}

@media (hover: hover) and (pointer: fine) {
  .goon-tile:hover .goon-tile__centerstage,
  .goon-tile:hover .goon-tile__open {
    opacity: 1;
  }
}

@media (hover: none), (pointer: coarse) {
  .goon-tile__centerstage,
  .goon-tile__open {
    opacity: 0.6;
  }
}

@media (pointer: coarse) {
  .goon-tile__centerstage,
  .goon-tile__open {
    width: 32px;
    height: 32px;
  }
}

/* Centerstage (item 4): a tile expands to fill the whole viewport via a
   FLIP animation driven from app.ts (toggleCenterstage). `position: fixed`
   pulls it out of the grid's visual flow without reparenting it in the DOM
   -- reparenting an iframe reloads it, which would interrupt the stream. */
.goon-tile--centerstaged {
  position: fixed;
  inset: 0;
  z-index: 50;
  border-radius: 0;
  padding-bottom: env(safe-area-inset-bottom);
}

/* visibility (not display:none) keeps the other tiles' layout boxes intact
   so their frame-wraps don't fire a 0x0 ResizeObserver entry and thrash
   fitEmbedIframe's scale math while centerstaged. */
.goon-grid--has-centerstage .goon-tile:not(.goon-tile--centerstaged) {
  visibility: hidden;
}

.goon-tile__frame-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
  overflow: hidden;
}

/*
 * Chaturbate's embed page has a hard minimum internal layout size (its
 * video element alone refused to render narrower than ~500px in testing) --
 * below that, it doesn't reflow, it just clips, which both crops the video
 * and pushes interactive elements (like the volume button, bottom-left of
 * the video) outside the visible/clickable area entirely. Sizing the iframe
 * to shrink-fit our tile hits that wall.
 *
 * Instead the iframe is always laid out at Chaturbate's own reference size
 * (850x480, the dimensions used in every linkcode they've given us) so its
 * internal layout never clips anything, then CSS-scaled down as a whole via
 * `transform: scale()` to fit the tile -- app.ts computes and centers this
 * per tile with a ResizeObserver. A transform is a pure visual scale, not a
 * layout change, so hit-testing/click coordinates still map correctly onto
 * the (still fully laid-out) 850x480 document underneath.
 */
.goon-tile__frame-wrap iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 850px;
  height: 480px;
  transform-origin: top left;
  border: 0;
}

.goon-tile__unavailable {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
}

/* Lives in .menu-drawer__header now (moved off the always-visible collapsed
   bar) -- has room to wrap onto a second line instead of needing to
   truncate, and sits right-aligned opposite the title block. */
.goon-timer {
  margin: 0;
  min-width: 160px;
  max-width: 320px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-muted);
  text-align: right;
}

.goon-timer:empty {
  display: none;
}

.goon-timer strong {
  color: var(--accent);
}

@media (max-width: 560px) {
  .goon-timer {
    text-align: left;
  }
}

