/* The DOM here is only type and hit areas. Every shape on screen is drawn by the
   shader, which reads where these elements landed. With HTML-in-canvas the type goes
   through the shader too, which takes its alpha and inks it per theme. Everywhere
   else it just sits on top of the canvas in the theme's colours. */

:root {
  --pad: clamp(16px, 2.6vw, 40px);
  --dot: clamp(34px, 6vh, 56px);
  --ink: #0e1119;
  --on-shape: #f1eee6;
  --paper: #f1eee6;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--paper);
  color: var(--ink);
  font-family: "DM Sans", sans-serif;
  -webkit-font-smoothing: antialiased;
}

.stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.surface {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.page {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 12px;
  width: 100%;
  height: 100%;
  padding:
    max(var(--pad), env(safe-area-inset-top))
    max(var(--pad), env(safe-area-inset-right))
    max(var(--pad), env(safe-area-inset-bottom))
    max(var(--pad), env(safe-area-inset-left));
  color: var(--ink);
  transition: color 0.3s;
}

.hic .page {
  color: #000;
}

/* Type that sits on a shape, not on the background. */
.core,
.note,
.modes {
  color: var(--on-shape);
}

.hic .core,
.hic .note,
.hic .modes {
  color: #000;
}

button,
input {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 6px;
}

/* --- the corners --- */

.top {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.key {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  min-height: 44px;
  text-align: left;
}

.key-name {
  font-size: clamp(44px, 9vh, 96px);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.045em;
}

.key-mode,
.theme-name,
.bpm-unit,
.word,
.hint,
.row-name,
.dot .t,
.core-label {
  font: 500 12px/1.2 "DM Mono", ui-monospace, monospace;
}

.corner {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.themes {
  display: flex;
  align-items: center;
  gap: 12px;
}

.credit {
  padding-block: 6px;
  color: inherit;
  font: 500 11px/1.2 "DM Mono", ui-monospace, monospace;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.swatches {
  display: flex;
}

.swatch {
  width: 36px;
  height: 44px;
}

/* --- the banks --- */

.bank {
  grid-row: 2;
  align-self: center;
  display: grid;
  gap: clamp(8px, 2vh, 20px);
  min-height: 0;
}

.bank[data-side="right"] {
  grid-column: 3;
}

.row-name {
  display: block;
  height: 16px;
  margin-bottom: 8px; /* an active dot swells past its button */
}

.bank[data-side="right"] .row-name {
  text-align: right;
}

.dots {
  display: flex;
  gap: 8px;
}

.dot {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  width: calc(var(--dot) + 12px);
  height: calc(var(--dot) + 22px);
}

.dot .t {
  font-size: 11px;
  opacity: 0.72;
}

.dot[aria-pressed="true"] .t {
  opacity: 1;
}

.middle {
  grid-row: 2;
  grid-column: 2;
  min-width: 0;
  min-height: 0;
}

/* --- the bottom --- */

.bottom {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
}

.tempo {
  display: flex;
  align-items: baseline;
  gap: 0.5em;
  cursor: default;
}

.bpm-value {
  min-width: 1.62em;
  font-size: clamp(44px, 9vh, 96px);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.045em;
}

.slider {
  flex: 1;
  min-width: 0;
}

/* Invisible but real: keyboards, screen readers and thumbs all use the native
   control, the shader draws the bead sitting on it. */
#bpm {
  display: block;
  width: 100%;
  height: 44px;
  opacity: 0;
  touch-action: none;
}

.word {
  min-width: 44px;
  min-height: 44px;
}

.song {
  max-width: 28vw;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: right;
}

/* --- over the blob --- */

.core {
  position: absolute;
  border-radius: 50%;
  touch-action: none;
}

.core-label {
  opacity: 0.9;
}

.playing .core-label {
  opacity: 0;
}

.ring {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.ring[hidden] {
  display: none;
}

.note,
.modes {
  position: absolute;
  pointer-events: auto;
  transform: translate(-50%, -50%);
}

.note {
  width: 44px;
  height: 44px;
  font-size: 16px;
  font-weight: 500;
}

.modes {
  display: flex;
  flex-direction: column;
}

.modes .word {
  min-height: 34px;
}

.modes .word[aria-checked="false"] .t {
  opacity: 0.5;
}

.hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(max(var(--pad), env(safe-area-inset-bottom)) + 78px);
  margin: 0;
  text-align: center;
  pointer-events: none;
}

.hint[hidden] {
  display: none;
}

/* --- what's playing, and what's next --- */

/* Top centre is the one open space: the song gets real type, clear of the blob. */
.upnext {
  position: absolute;
  top: max(var(--pad), env(safe-area-inset-top));
  left: 24vw;
  right: 24vw;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  column-gap: 14px;
  text-align: center;
  pointer-events: none;
}

.upnext[hidden] {
  display: none;
}

.now,
.next {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  min-width: 0;
}

.now {
  flex: 0 0 100%;
  font-size: clamp(18px, 2vw, 28px);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.next {
  max-width: calc(100% - 60px);
  font: 500 12px/1.2 "DM Mono", ui-monospace, monospace;
  opacity: 0.6;
}

.next:empty {
  display: none;
}

.upnext .word {
  position: relative;
  flex: none;
  min-height: 32px;
  font: 500 12px/1.2 "DM Mono", ui-monospace, monospace;
  pointer-events: auto;
}

/* The word stays small, the thumb gets a full-size target. */
.upnext .word::after {
  content: "";
  position: absolute;
  inset: -9px -4px;
}

/* --- the order dialog: outside the canvas, so it's plain HTML in the theme's colours --- */

.sort {
  width: min(520px, calc(100vw - 24px));
  max-height: calc(100dvh - 24px);
  padding: 24px;
  border: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
  border-radius: 28px;
  background: color-mix(in srgb, var(--paper) 95%, transparent); /* the blur can't be relied on to quieten what's behind */
  color: var(--ink);
  -webkit-backdrop-filter: blur(24px) saturate(1.4);
  backdrop-filter: blur(24px) saturate(1.4);
  font-family: "DM Sans", sans-serif;
}

.sort[open] {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.sort::backdrop {
  background: color-mix(in srgb, var(--paper) 35%, transparent);
}

.sort h2 {
  margin: 0;
  font-size: 36px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.04em;
}

.notice {
  margin: 0;
  font: 500 12px/1.4 "DM Mono", ui-monospace, monospace;
  opacity: 0.72;
}

.sort-message:empty {
  display: none;
}

.sort-list {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
  counter-reset: song;
}

.sort-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  counter-increment: song;
}

.sort-row.lifted {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: 12px;
}

.grip {
  display: grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 44px;
  font-size: 14px;
  letter-spacing: -0.2em;
  opacity: 0.55;
  cursor: grab;
  touch-action: none;
}

.row-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 16px;
}

.row-title::before {
  content: counter(song);
  display: inline-block;
  width: 3ch;
  font: 500 12px "DM Mono", ui-monospace, monospace;
  opacity: 0.55;
}

.row-length {
  flex: none;
  font: 500 12px "DM Mono", ui-monospace, monospace;
  opacity: 0.72;
}

.row-length.over {
  opacity: 1;
  font-weight: 500;
  text-decoration: underline;
}

.row-tool {
  flex: none;
  width: 36px;
  height: 44px;
  font-size: 16px;
}

.sort-actions {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
}

.pill {
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid currentColor;
  border-radius: 22px;
  font: 500 13px "DM Mono", ui-monospace, monospace;
}

.pill.go {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

.pill:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Phones held upright: a single column of dots down each edge, so the circle keeps
   as much of the width as it can. */
@media (max-aspect-ratio: 1/1) and (max-width: 720px) {
  :root {
    --pad: 12px;
    --dot: clamp(26px, 4.3vh, 36px);
  }

  .page {
    gap: 8px;
  }

  .bank {
    gap: clamp(6px, 1.2vh, 12px);
  }

  .dots {
    flex-direction: column;
    gap: 0;
  }

  .bank[data-side="right"] .dots {
    align-items: flex-end;
  }

  .dot {
    width: var(--dot);
    height: calc(var(--dot) + 3px);
    justify-content: center;
  }

  .dot .t {
    display: none;
  }

  .row-name {
    height: 13px;
    margin-bottom: 5px;
    font-size: 10px;
  }

  .swatch {
    width: 30px;
  }

  .theme-name {
    display: none;
  }

  .bpm-value,
  .key-name {
    font-size: 52px;
  }

  .bpm-unit {
    display: none;
  }

  /* The hint and the song share one row under the header, always the same height,
     so nothing jumps when a song starts. */
  .page {
    --song-row: 54px;
    grid-template-rows: auto var(--song-row) minmax(0, 1fr) auto;
  }

  .hint,
  .upnext {
    position: static;
    grid-column: 1 / -1;
    grid-row: 2;
    align-self: center;
  }

  .now {
    font-size: 18px;
  }

  .bank,
  .middle {
    grid-row: 3;
  }

  .hint {
    font-size: 11px;
  }

  /* The number and slider share a row with a fixed gap, the words sit below. The
     slider's basis is what pushes the first word onto the next line. */
  .bottom {
    grid-row: 4;
    flex-wrap: wrap;
    gap: 0 14px;
  }

  .bpm-value {
    min-width: 0;
  }

  .slider {
    flex: 1 1 calc(100% - 110px);
  }

  .bottom .word {
    flex: 1 1 15%;
    min-height: 36px;
    text-align: center;
  }

  #mute {
    text-align: left;
  }

  #song {
    max-width: none;
    text-align: right;
  }

  /* Drag does the ordering on a phone, and the song's name needs the room. */
  .sort {
    padding: 18px;
  }

  .row-tool[aria-label^="move"] {
    display: none;
  }
}

/* Short phones held upright: twelve dots have to fit the height that's left. */
@media (max-aspect-ratio: 1/1) and (max-width: 720px) and (max-height: 760px) {
  :root {
    --dot: clamp(24px, calc((100svh - 318px) / 12 - 3px), 36px);
  }

  .swatch {
    height: 36px;
  }

  .bank {
    gap: 6px;
  }

  .row-name {
    height: 12px;
    margin-bottom: 3px;
  }

  .bpm-value,
  .key-name {
    font-size: 44px;
  }

  /* One line here, because every pixel of height is a dot's. */
  .page {
    --song-row: 30px;
  }

  .upnext {
    flex-wrap: nowrap;
  }

  .now {
    flex: 0 1 auto;
    font-size: 14px;
  }

  .next {
    flex: 0 1 auto;
    max-width: none;
  }

  .upnext .word {
    min-height: 30px;
  }
}

@media (max-aspect-ratio: 1/1) and (max-width: 720px) and (max-height: 600px) {
  .row-name {
    display: none;
  }

  .bank {
    gap: 4px;
  }
}

/* Phones on their side: no room for the words under the dots. */
@media (max-height: 560px) {
  :root {
    --dot: 30px;
  }

  .page {
    gap: 4px;
  }

  .bank {
    gap: 2px;
  }

  .row-name {
    height: 12px;
    margin-bottom: 5px;
    font-size: 10px;
  }

  .dot {
    height: calc(var(--dot) + 6px);
    justify-content: center;
  }

  .dot .t {
    display: none;
  }

  .key-name,
  .bpm-value {
    font-size: 36px;
  }

  /* Top centre is the one free spot, between the key and the themes. */
  .hint,
  .upnext {
    top: max(8px, env(safe-area-inset-top));
    bottom: auto;
    left: 150px; /* clear of the key and the themes */
    right: 170px;
  }

  .now {
    font-size: 15px;
  }

  .upnext .word {
    min-height: 26px;
  }
}

/* A phone on its side has the up-next line beside the swatches, which say it already. */
@media (hover: none) and (max-height: 560px) {
  .theme-name {
    display: none;
  }
}
