/* Patterns.
 *
 * Jukebox-shaped compositions built out of primitives. Same no-literal-colours
 * rule as primitives.css. Every one of these appears in all three source
 * designs; the differences between those designs live in skins/, not here.
 */

/* ------------------------------------------------------------ visualizer */

/* width/flex are explicit because the bars are all `flex: 1 1 0`, giving the
   container a min-content width of nearly nothing. Dropped into a flex row it
   would otherwise collapse to a sliver instead of filling the space. */
.rk-viz {
  display: flex;
  align-items: flex-end;
  gap: var(--viz-gap);
  width: 100%;
  flex: 1 1 auto;
  min-width: 0;
  height: var(--rk-viz-height, 100%);
  min-height: 32px;
  padding: var(--space-1);
}

.rk-viz__bar {
  flex: 1 1 0;
  height: 100%;
  background: var(--viz-gradient);
  border-radius: var(--viz-radius);
  transform-origin: bottom;
  transform: scaleY(var(--viz-floor));
  animation: rk-eq var(--rk-bar-period, var(--viz-period))
             var(--ease) var(--rk-bar-delay, 0ms) infinite;
}

/* Room EQ only exists while the TV speaker has live audio to analyse.
   Decorative loops remain available to the home page and design previews. */
.rk-room-shell .rk-viz { display: none; }

.rk-room-shell .rk-viz__bar {
  animation: none;
  transform: scaleY(var(--rk-bar-level, 0));
}

.rk-room-shell--tv .rk-viz[data-tv-audio-target="visualizer"] { display: flex; }

/* An EQ that is on but has nothing to draw holds its place, empty. */
.rk-room-shell--tv .rk-viz[data-tv-audio-resting] { visibility: hidden; }

/* --------------------------------------------------------------- marquee */

.rk-marquee {
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  min-width: 0;
}

.rk-marquee__track {
  display: flex;
  flex: 0 0 auto;
  min-width: 200%;
  animation: rk-marquee var(--marquee-duration) linear infinite;
}

.rk-marquee--chrome {
  height: var(--chrome-height);
  padding: 0 var(--space-2);
  background: var(--chrome-bg);
}

.rk-marquee--chrome .rk-marquee__item { color: var(--chrome-ink); }

.rk-marquee__item {
  flex: 1 0 auto;
  padding-inline-end: var(--space-6);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  letter-spacing: var(--title-tracking);
  color: var(--accent);
}

/* ------------------------------------------------------------------- art */

.rk-art {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  width: var(--rk-art-size, 62px);
  background-color: var(--art-bg);
  background-image: var(--art-texture);
  border-radius: var(--art-radius);
  overflow: hidden;
}

.rk-art__disc {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--art-radius);
  animation: var(--art-spin);
}

.rk-art__ring {
  position: absolute;
  width: 52%;
  aspect-ratio: 1;
  border-radius: var(--edge-radius-pill);
  background: var(--art-label);
}

.rk-art__label {
  position: relative;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  color: var(--art-ink);
}

/* A record's spindle. Skins whose art is a square sleeve set --art-spindle and
   --art-hole to transparent, so the same element simply stops being visible --
   no display toggle, no skin-specific selector. */
.rk-art__spindle {
  position: absolute;
  width: 19%;
  aspect-ratio: 1;
  border-radius: var(--edge-radius-pill);
  background: var(--art-spindle);
}

.rk-art__hole {
  position: absolute;
  width: 5%;
  aspect-ratio: 1;
  border-radius: var(--edge-radius-pill);
  background: var(--art-hole);
}

/* ----------------------------------------------------------- now playing */

.rk-now {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.rk-now__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  flex: 1 1 auto;
}

.rk-now__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--title-weight);
  letter-spacing: var(--title-tracking);
  color: var(--ink-primary);
}

.rk-now__artist { color: var(--ink-secondary); font-size: var(--text-sm); }

.rk-now__eq {
  flex: 0 0 auto;
  width: 26px;
  height: 22px;
}

.rk-now__progress {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-readout);
  font-size: var(--text-md);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* ----------------------------------------------------------------- queue */

.rk-queue { display: flex; flex-direction: column; gap: var(--row-gap); min-width: 0; }

/* Dense mode is the playlist-window look: zero gap, full-bleed rows, no
   per-row border. Both source pixel skins use it; cantina uses the airy
   default. It is a modifier rather than a skin override so any skin can pick. */
.rk-queue--dense { gap: 0; }

.rk-queue__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--row-height);
  padding: 0 var(--space-3);
  background: var(--surface-row);
  color: var(--ink-on-row);
  border-radius: var(--edge-radius-sm);
  min-width: 0;
}

.rk-queue--dense .rk-queue__row {
  min-height: 34px;
  border-radius: 0;
  font-family: var(--font-readout);
  font-size: var(--text-lg);
}

.rk-queue__row--active {
  background: var(--surface-row-active);
  color: var(--ink-on-active-row);
}

.rk-queue__row--active .rk-queue__artist,
.rk-queue__row--active .rk-queue__duration { color: var(--ink-on-active-row); }

.rk-queue__index {
  flex: 0 0 auto;
  width: 24px;
  font-family: var(--font-readout);
  font-size: var(--text-lg);
  font-variant-numeric: tabular-nums;
  color: var(--ink-on-row-dim);
}

.rk-queue__row--active .rk-queue__index { color: var(--ink-on-active-row); }

.rk-queue__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.rk-queue--dense .rk-queue__text { flex-direction: row; gap: var(--space-2); align-items: baseline; }

.rk-queue__title  { font-size: var(--text-md); font-weight: 600; }
.rk-queue--dense .rk-queue__title { font-weight: 400; font-size: inherit; }
.rk-queue__artist { font-size: var(--text-sm); color: var(--ink-on-row-dim); }
.rk-queue--dense .rk-queue__artist { font-size: var(--label-size);
                                     font-family: var(--font-label); }

.rk-queue__duration {
  flex: 0 0 auto;
  /* Five characters wide whether or not the length is known yet -- the width
     the "--:--" placeholder used to hold open. A song whose duration arrives
     later must not reflow the row under the eyes reading it. */
  min-width: 5ch;
  text-align: right;
  font-family: var(--font-readout);
  font-variant-numeric: tabular-nums;
  color: var(--ink-on-row-dim);
  font-size: var(--text-md);
}

.rk-queue__badges { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-1); flex: 0 0 auto; }

.rk-queue__empty {
  padding: var(--space-4);
  color: var(--ink-on-row-dim);
  font-family: var(--font-label);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-align: center;
  text-transform: var(--label-transform);
}

/* Phone rows, the row's trigger and the song sheet live in system/room_now.css
   with the rest of the Now screen. */

/* --------------------------------------------------- waiting (per member) */

/* The holding area's rules live with the screen that draws it in full:
   system/room_add.css (.rk-staged). */

/* ------------------------------------------------------------- transport */

.rk-transport { display: flex; gap: var(--space-1); }
.rk-transport .rk-button { flex: 1 1 0; }

/* ---------------------------------------------------------------- search */

.rk-search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--control-height);
  padding: 0 var(--space-3);
  background: var(--surface-sunken);
  border-style: solid;
  border-width: var(--edge-width);
  border-top-color: var(--edge-inset-top);
  border-left-color: var(--edge-inset-left);
  border-right-color: var(--edge-inset-right);
  border-bottom-color: var(--edge-inset-bottom);
  border-radius: var(--control-radius);
  color: var(--ink-muted);
}

.rk-search__input {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ink-primary);
  font-size: var(--text-md);
}

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

.rk-search:focus-within {
  box-shadow: var(--focus-shadow);
  transition: box-shadow var(--motion-fast) var(--ease);
}

.rk-search__input:focus-visible { outline: none; }

.rk-chips { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* ------------------------------------------------------------------- nav */

/* Every tab gets an equal share of the bar, however many there are. */
.rk-nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  padding: 6px var(--space-2) 14px;
  background: var(--surface-panel);
  border-top: var(--edge-width) solid var(--edge-hairline);
  /* The other surface that meets the page directly, so it frosts with the
     panels rather than turning into an opaque bar over a glass room. */
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
}

.rk-nav__item {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 52px;
  padding: 6px var(--space-1);
  color: var(--ink-muted);
  font-family: var(--font-label);
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  letter-spacing: 0.12em;
  text-transform: var(--label-transform);
  text-decoration: none;
  white-space: nowrap;
  border-radius: var(--edge-radius-sm);
}

.rk-nav__icon { flex: 0 0 auto; }

.rk-nav__item[aria-current] { color: var(--accent); }

/* The current tab sits on a wash of the accent: the token painted at a tenth,
   by a pseudo-element, so the stylesheet never mixes a colour of its own. */
.rk-nav__item[aria-current]::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: inherit;
  background: var(--accent);
  opacity: 0.1;
}

/* ------------------------------------------------------------ status bar */

.rk-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-2);
  font-family: var(--font-label);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: var(--label-transform);
  color: var(--ink-muted);
  min-width: 0;
}

.rk-status__action { color: var(--accent); flex: 0 0 auto; }

/* -------------------------------------------------------- personal notices

   The room's own banner (.rk-playback__notice) is a danger-coloured line of
   label type that erases itself. These are the opposite object and are drawn
   as the opposite thing: raised cards in body type that sit there until the
   person they are addressed to clears them. Nothing here is a colour of its
   own -- the distinction is card versus line, so it survives every skin. */

.rk-notices {
  display: grid;
  gap: var(--space-2);
}

/* The container is always in the DOM as the broadcast's target, so an empty
   one has to take no room at all -- a grid with nothing in it still answers
   to the stack's gap once a sibling sits below it. */
.rk-notices:empty { display: none; }

.rk-notices__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: var(--edge-width) solid var(--edge-hairline);
  border-radius: var(--edge-radius-sm);
  background: var(--surface-raised);
  color: var(--ink-primary);
  min-width: 0;
}

.rk-notices__text {
  flex: 1 1 auto;
  font-size: var(--text-sm);
  line-height: var(--body-line);
  min-width: 0;
}

.rk-notices__form { flex: 0 0 auto; }

/* Shrunk to its label rather than standing at full control height beside two
   lines of text. The ink is stated rather than inherited from the ghost
   variant: a ghost button is --ink-secondary, which the contrast contract
   guarantees on the page and on a panel but not on --surface-raised, and llama
   measures 4.37:1 there. --ink-primary on --surface-raised is asserted. */
.rk-notices__dismiss,
.rk-notices__dismiss-all {
  height: auto;
  min-height: 0;
  padding: var(--space-1) var(--space-2);
  color: var(--ink-primary);
}

.rk-notices__form--all { justify-self: end; }

/* ------------------------------------------------------------- join code */

.rk-joincode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* The details grow to fill a shared row, so this only bites once the code
     wraps below the QR -- the moment the QR would otherwise hug the left edge
     of a row it has to itself, off-centre from the code sitting under it. */
  justify-content: center;
  gap: var(--space-3);
}

.rk-joincode__details {
  flex: 1 1 130px;
  text-align: center;
}

.rk-joincode__code {
  font-family: var(--font-readout);
  font-size: var(--text-xl);
  letter-spacing: 0.12em;
  color: var(--accent);
}

/* Where to type the code, for the guest whose phone will not scan a QR. */
.rk-joincode__address {
  display: block;
  color: var(--ink-muted);
  font-family: var(--font-label);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  overflow-wrap: anywhere;
}

.rk-joincode__qr {
  flex: 0 0 auto;
  display: block;
  width: clamp(128px, 35vw, 180px);
  max-width: 100%;
  aspect-ratio: 1;
  padding: var(--space-2);
  border-radius: var(--edge-radius-sm);
  background-color: var(--readout-ink);
  color: var(--readout-bg);
}

.rk-joincode__qr-image { display: block; width: 100%; height: auto; }

.rk-joincode--tv .rk-joincode__qr { width: clamp(220px, 24vw, 460px); }

/* Narrow enough that a column beside the QR would squeeze both: the QR takes a
   row of its own and the code, label and share button centre under it. */
.rk-joincode--stacked { flex-direction: column; justify-content: center; text-align: center; }
.rk-joincode--stacked .rk-joincode__details { flex: 0 0 auto; align-items: center; }

/* ----------------------------------------------------------- room header */

.rk-roomhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
}

.rk-roomhead__name {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--title-weight);
  letter-spacing: var(--title-tracking);
  text-transform: var(--title-transform);
  color: var(--accent);
}

.rk-people {
  position: relative;
  flex: 0 0 auto;
}

.rk-people__trigger {
  display: block;
  list-style: none;
  border-radius: var(--edge-radius-pill);
  cursor: pointer;
}

.rk-people__trigger::-webkit-details-marker { display: none; }

.rk-people__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.rk-people[open] .rk-people__trigger {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.rk-people__popup {
  position: absolute;
  z-index: 15;
  top: calc(100% + var(--space-2));
  right: 0;
  width: min(380px, calc(100vw - var(--space-6)));
  display: flex;
  flex-direction: column;
  max-height: max(0px, calc(var(--rk-popup-available-height, 100dvh) - var(--space-3)));
  padding: var(--space-4);
  background: var(--surface-panel);
  -webkit-backdrop-filter: var(--surface-blur);
  backdrop-filter: var(--surface-blur);
  border-style: solid;
  border-width: var(--edge-width);
  border-top-color: var(--edge-top);
  border-left-color: var(--edge-left);
  border-right-color: var(--edge-right);
  border-bottom-color: var(--edge-bottom);
  border-radius: var(--edge-radius);
  box-shadow: var(--edge-shadow-float);
  /* Same reason .rk-panel clips: the list below runs its scrollbar right up to
     this border, and a scrollbar is a square the corner radius knows nothing
     about. Clipping here is what keeps the popup the shape the skin drew. */
  overflow: hidden;
}

.rk-people__heading,
.rk-people__identity,
.rk-people__actions {
  display: flex;
  align-items: center;
}

.rk-people__heading {
  flex: 0 0 auto;
  justify-content: space-between;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: var(--edge-width) solid var(--edge-hairline);
}

/* The list is the scroll box, and a scroll box clips everything its children
   paint outside it -- a raised button's drop shadow included. The row buttons
   sit hard against the right edge and the last row's sit hard against the
   bottom one, so on a skin whose --control-shadow carries a real drop shadow
   the halo was being sliced off along both.
   Switching the clipping off is not an option (the list has to scroll once a
   room fills up), so the cut is moved instead: negative margins push the list's
   edges out to the popup's border, and the popup's own padding comes back as
   the list's, inside the scroll box. The rows land where the popup's padding
   puts them either way, but the cut now sits --space-4 clear of the buttons on
   the sides and --space-4 plus the row's own --space-3 clear below them, which
   covers the largest shadow any skin currently casts, hovered included. The
   one thing that does change is the budget: max-height is a border-box figure,
   so the scrollport is now --space-4 shorter than the number below says. */
.rk-people__list {
  min-height: 0;
  display: flex;
  flex-direction: column;
  max-height: min(60vh, 420px);
  margin: 0 calc(var(--space-4) * -1) calc(var(--space-4) * -1);
  padding: 0 var(--space-4) var(--space-4);
  overflow-y: auto;
  list-style: none;
}

.rk-people__member {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block: var(--space-3);
}

.rk-people__member + .rk-people__member {
  border-top: var(--edge-width) solid var(--edge-hairline);
}

.rk-people__identity { gap: var(--space-2); }

.rk-people__name {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  overflow-wrap: anywhere;
}

.rk-people__account-link {
  color: var(--ink-primary);
  text-decoration: none;
}

.rk-people__account-link:hover { text-decoration: underline; }

.rk-people__edit-toggle {
  width: calc(var(--control-height) - var(--space-2));
  min-height: calc(var(--control-height) - var(--space-2));
  height: calc(var(--control-height) - var(--space-2));
}

.rk-people__edit-form {
  display: block;
}

.rk-people__edit-form[hidden] { display: none; }
.rk-people__edit-form form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-2);
}
.rk-people__edit-form .rk-label { grid-column: 1 / -1; }
.rk-people__edit-form .rk-input { min-width: 0; }

.rk-people__actions {
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

.rk-people__actions form {
  min-width: 0;
  max-width: 100%;
}

.rk-people__actions .rk-button {
  max-width: 100%;
  height: auto;
  white-space: normal;
  overflow-wrap: anywhere;
  padding-block: var(--space-2);
  min-height: calc(var(--control-height) - var(--space-2));
  padding-inline: var(--space-2);
}

/* --------------------------------------------------------------- layouts */

.rk-stack   { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.rk-stack--tight { gap: var(--space-2); }
.rk-row     { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.rk-row--between { justify-content: space-between; }
/* Basis 0, not auto: a nowrap child (a marquee, a long title) would otherwise
   inflate this item's hypothetical main size and wrap the whole layout. */
.rk-grow    { flex: 1 1 0%; min-width: 0; }
.rk-pad     { padding: var(--space-5); }

.rk-row > .rk-label { flex: 0 0 auto; white-space: nowrap; }

/* A phone-sized frame for previewing the controller surface. */
.rk-device {
  width: min(390px, 100%);
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface-panel);
  border: var(--edge-width) solid var(--edge-hairline);
  border-radius: var(--device-radius);
  box-shadow: var(--edge-shadow-float);
  /* Deliberately NOT frosted, though it paints --surface-panel on the page: the
     phone frame *contains* panels, and an element with a backdrop-filter becomes
     a backdrop root, which leaves its descendants nothing to blur. Frosting a
     container silently un-frosts everything inside it. */
}
