/* Hidden on screen, still there for screen readers and keyboard focus. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Widths include padding and border, so `width: 100%` never overflows its container. */
*,
*::before,
*::after {
  box-sizing: border-box;
  font-family: "Space Mono", monospace;
  font-size: 13px;
}

body {
  max-width: 1200px;
  margin: 0 auto;
}

/* Panel: playlists and tracks as the main content, player and upload aside,
   invites (or BUTT details for guests) across the bottom. */
/* The panel's top part is a fixed dashboard that fills the window below the
   header: the two left rows split the height, the aside spans both, and long
   content scrolls inside its own section instead of resizing the layout.
   Invites follow below, reached by scrolling the page. */
:root {
  --header-height: 4rem;
  /* Both dashboard rows together: the window below the header, minus the gap
     between them and a little room at the bottom. */
  --dashboard-rows: max(20rem, calc(100dvh - var(--header-height) - 2rem));
}

main.panel {
  display: grid;
  /* minmax(0, …): wide content can't push a column past its share. */
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  /* Playing playlist : playlists = 3 : 2. */
  grid-template-rows:
    calc(var(--dashboard-rows) * 3 / 5)
    calc(var(--dashboard-rows) * 2 / 5)
    auto;
  grid-template-areas:
    "tracks    aside"
    "playlists aside"
    "bottom    bottom";
  gap: 1rem;

  > aside {
    grid-area: aside;
    min-height: 0;

    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  /* Dashboard sections keep their size; what doesn't fit scrolls inside them,
     under a header that stays put. */
  #tracks,
  #playlists {
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: none;

    &::-webkit-scrollbar {
      display: none;
    }

    .topbar {
      position: sticky;
      top: 0;
      z-index: 1;
      background-color: #fff;
    }
  }

  #library {
    flex: 1;
    min-height: 0;
  }

  .library-list {
    flex: 1;
    min-height: 0;
  }
}

header,
nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

header {
  height: var(--header-height);
}

form,
p,
h1,
h2,
h3,
h4,
h5 {
  padding: 0;
  margin: 0;
}

/* Heading sizes on top of the 13px base, so sections stand out from their content. */
h1 {
  font-size: 1.75rem;
}

h2 {
  font-size: 1.25rem;
}

h3 {
  font-size: 1rem;
}

header h1 a {
  /* The * rule gives every element 13px; the title link should follow its h1. */
  font-size: inherit;
  color: inherit;
  text-decoration: none;
}

/* Buttons inside list rows are smaller, so lists stay compact and the big
   buttons are left for the main actions. */
.track-list button,
.library-list button,
.playlist-row button,
.invite-list button {
  padding: 0.25rem 0.75rem;
}

/* Form groups look like the upload and library boxes: a thin rounded border,
   with the legend as a small label inside rather than cut into the border. */
fieldset {
  margin: 0;
  padding: 0.75rem;
  min-width: 0;
  border: 1px solid #ccc;
  border-radius: 0.5rem;
}

legend {
  float: left;
  width: 100%;
  padding: 0;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
  color: #666;
}

/* Whatever follows the floated legend starts below it, never beside it. */
legend + * {
  clear: both;
}

/* Text-like inputs share the same rounded box. */
input[type="text"],
input[type="search"],
input[type="number"],
input[type="password"] {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 0.5rem;
}

button,
.button {
  width: 100%;
  padding: 0.5rem;
  border-radius: 0.5rem;
  background-color: #f0f0f0;
  border: none;
  cursor: pointer;

  &:hover {
    background-color: #e0e0e0;
  }
}

.player-section {
  display: flex;
  flex-direction: column;
  gap: 1rem;

  .topbar {
    display: flex;
    flex-direction: column;

    div {
      display: flex;
      gap: 1rem;
      align-items: center;
    }
  }

  fieldset {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    justify-content: space-between;
    align-items: center;

    div {
      width: 100%;
      display: flex;
      gap: 1rem;
      align-items: center;
      justify-content: space-between;
    }
  }

  fieldset label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  /* Rows: play/pause, then shuffle and skip side by side, then volume.
     A grid splits the row exactly; flex would give the button its padding extra. */
  .player-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .player-volume {
    width: 100%;

    input[type="range"] {
      flex: 1;
    }
  }

  output:empty {
    display: none;
  }
}

#playlists {
  grid-area: playlists;

  display: flex;
  flex-direction: column;
  gap: 0.5rem;

  .topbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid #ccc;
  }

  .playlist-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Rows with and without a "Put on air" button are the same height. */
    min-height: 2.25rem;

    div {
      display: flex;
      align-items: center;
      gap: 1rem;
    }
  }
}

#upload {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;

  /* One row: the folder name, then a single Upload button that opens the file picker. */
  form {
    display: flex;
    gap: 0.5rem;
  }

  input[type="text"] {
    flex: 1;
    min-width: 0;
  }

  .button {
    flex: none;
    width: auto;
    text-align: center;
  }

  /* The file input is hidden, so show its keyboard focus on the button. */
  input[type="file"]:focus-visible + .button {
    outline: 2px solid #4a7dff;
    outline-offset: 2px;
  }
}

#tracks {
  grid-area: tracks;

  span {
    display: inline-block;
    padding: 0.25rem 0.5rem;
  }

  .current {
    font-weight: bold;
    background-color: #fff3c4;
  }

  .next {
    background-color: #eef3ff;
  }

  display: flex;
  flex-direction: column;
  gap: 0.5rem;

  .topbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid #ccc;
  }

  .track-list {
    padding-inline-start: 0;
    margin-inline-start: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }

  .track-list li {
    display: grid;
    /* ch scales with the font: "Now playing" fits on one line. */
    grid-template-columns: 14ch minmax(0, 1fr) auto;
    align-items: center;
    gap: 1rem;

    .marker {
      grid-column: 1;
      white-space: nowrap;
    }
    .filename {
      grid-column: 2;
    }
    .queue-button {
      grid-column: 3;
    }
  }

  button {
    width: auto;
  }
}

#invites,
#go-live {
  grid-area: bottom;
}

#invites .container {
  display: flex;
  align-items: center;
  justify-content: space-between;

  div {
    display: flex;
    gap: 1rem;
    align-items: center;
  }

  button {
    width: auto;
  }
}

/* Invite list: one row per invite. The list sets the columns (status and name,
   join link, BUTT login, actions); each invite and its details use subgrid, so
   the columns line up across rows like a table would. */
#invites .invite-list {
  list-style: none;
  padding: 0;
  margin: 1rem 0 0;

  display: grid;
  grid-template-columns:
    minmax(6rem, max-content) max-content minmax(0, 1fr)
    max-content;
  column-gap: 1.5rem;

  > li {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: center;
    padding: 0.75rem 0;
    border-bottom: 1px solid #ccc;
  }

  > li[data-status="revoked"],
  > li[data-status="expired"] {
    color: #777;
  }

  dl {
    grid-column: 2 / 4;
    display: grid;
    grid-template-columns: subgrid;
    margin: 0;
  }

  dt {
    font-size: 0.85rem;
    color: #666;
  }

  dd {
    margin: 0;

    button {
      width: auto;
    }
  }

  code {
    overflow-wrap: anywhere;
  }

  .actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-end;

    form {
      display: flex;
      gap: 0.5rem;
      align-items: center;
    }

    input[type="number"] {
      width: 4rem;
    }

    button {
      width: auto;
    }
  }
}

/* Narrow screens: each invite stacks into one column. */
@media (max-width: 800px) {
  #invites .invite-list {
    grid-template-columns: minmax(0, 1fr);

    > li,
    dl {
      grid-column: 1;
      grid-template-columns: minmax(0, 1fr);
      gap: 0.5rem;
    }

    .actions {
      justify-content: flex-start;
    }
  }
}

/* Library in the aside: a search box over a scrolling list of every track. */
#library {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;

  output:empty {
    display: none;
  }

  .library-list {
    list-style: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    border: 1px solid #ccc;
    border-radius: 0.5rem;

    /* Scrolls with wheel, touch and keyboard, without showing a scrollbar. */
    scrollbar-width: none;

    &::-webkit-scrollbar {
      display: none;
    }
  }

  /* Nothing matches the search: the count says so, no need for an empty box. */
  .library-list:not(:has(li:not([hidden]))) {
    display: none;
  }

  .library-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.25rem 0.5rem;

    &[hidden] {
      display: none;
    }

    & + li {
      border-top: 1px solid #eee;
    }
  }

  .library-list span {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .library-list small {
    display: block;
    color: #666;
  }

  button {
    flex: none;
    width: auto;
  }
}

/* Toasts: action confirmations and errors, bottom right (static/feedback.js). */
#toasts {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
  pointer-events: none;
}

.toast {
  max-width: 24rem;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  background-color: #222;
  color: #fff;
  animation: toast-in 0.15s ease-out;
}

.toast-error {
  background-color: #b3261e;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

/* A button whose action is still being handled. */
button[aria-busy="true"] {
  cursor: progress;
  opacity: 0.6;
}

/* The open front page: just the player, in a narrow centred column. */
main.listen {
  max-width: 28rem;
  margin: 2rem auto;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* What's on, at the top of the player: left-aligned like a heading, unlike the
   centred controls below it (which the fieldset rules above are for). */
.player-section .topbar {
  align-items: flex-start;

  > div {
    justify-content: flex-start;
  }
}
