/* Innovation Dashboard on Glance: api-core theme tokens, layout, components and motion. */

:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dash-radius: 10px;
  --gold: #f5c542;
  --silver: #c3cad5;
  --bronze: #d6935a;
}

/* api-core shell tokens: :root and :root[data-theme='dark'] in resources/assets/scss/_app-shell-theme.scss */
:root[data-app-theme='dark'] {
  color-scheme: dark;
  --border-radius: var(--dash-radius);
  --color-background: #15161b;
  --color-widget-background: #1d1e24;
  --color-widget-background-highlight: #26272e;
  --color-widget-content-border: rgba(244, 245, 247, 0.1);
  --color-separator: rgba(244, 245, 247, 0.1);
  --color-popover-background: #1d1e24;
  --color-popover-border: rgba(244, 245, 247, 0.16);
  --color-progress-border: rgba(244, 245, 247, 0.16);
  --color-progress-value: rgba(244, 245, 247, 0.3);
  --color-vertical-progress-value: rgba(244, 245, 247, 0.3);
  --color-graph-gridlines: rgba(244, 245, 247, 0.08);
  --color-text-highlight: #f4f5f7;
  --color-text-paragraph: rgba(244, 245, 247, 0.74);
  --color-text-base: rgba(244, 245, 247, 0.74);
  --color-text-base-muted: rgba(244, 245, 247, 0.6);
  --color-text-subdue: rgba(244, 245, 247, 0.6);
  --color-primary: #3ddc84;
  --color-positive: #3ddc84;
  --color-negative: #f4687e;
  --dash-warning: #ffb057;
  --dash-track: rgba(244, 245, 247, 0.16);
  --dash-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  --dash-shimmer: rgba(255, 255, 255, 0.05);
  --dash-accent-fill: #3ddc84;
  --dash-accent-ink: #090a0d;
}

:root[data-app-theme='light'] {
  color-scheme: light;
  --border-radius: var(--dash-radius);
  --color-background: #f4f4f5;
  --color-widget-background: #ffffff;
  --color-widget-background-highlight: rgba(9, 10, 13, 0.05);
  --color-widget-content-border: rgba(9, 10, 13, 0.08);
  --color-separator: rgba(9, 10, 13, 0.08);
  --color-popover-background: #ffffff;
  --color-popover-border: rgba(9, 10, 13, 0.12);
  --color-progress-border: rgba(9, 10, 13, 0.12);
  --color-progress-value: rgba(9, 10, 13, 0.3);
  --color-vertical-progress-value: rgba(9, 10, 13, 0.3);
  --color-graph-gridlines: rgba(9, 10, 13, 0.06);
  --color-text-highlight: #090a0d;
  --color-text-paragraph: rgba(9, 10, 13, 0.72);
  --color-text-base: rgba(9, 10, 13, 0.72);
  --color-text-base-muted: rgba(9, 10, 13, 0.62);
  --color-text-subdue: rgba(9, 10, 13, 0.62);
  --color-primary: #00792f;
  --color-positive: #00792f;
  --color-negative: #c4183c;
  --dash-warning: #9a4a00;
  --dash-track: rgba(9, 10, 13, 0.12);
  --dash-card-shadow: 0 1px 2px rgba(9, 10, 13, 0.04), 0 1px 3px rgba(9, 10, 13, 0.06);
  --dash-shimmer: rgba(255, 255, 255, 0.7);
  --dash-accent-fill: #00792f;
  --dash-accent-ink: #ffffff;
  /* Medal colors deep enough for 4.5:1 text on white. */
  --gold: #9a6b00;
  --silver: #5f6b7a;
  --bronze: #9c5a1f;
}

/* Light theme: the header takes the Talk sidebar (_shell-theme.scss in aloware-talk2). */
:root[data-app-theme='light'] .header {
  background: #15163f;
  border-color: rgba(255, 255, 255, 0.12);
  color: #9797ae;
  --color-text-highlight: #ffffff;
  --color-text-base: #9797ae;
  --color-text-subdue: #9797ae;
  --color-text-paragraph: #9797ae;
  --color-primary: #ffffff;
  --color-widget-background-highlight: rgba(255, 255, 255, 0.08);
  --dash-switch-pill: rgba(255, 255, 255, 0.16);
}

body {
  font-family: "Nunito", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Component display rules (display: flex) would otherwise override the hidden attribute. */
[hidden] {
  display: none !important;
}

/* Glance tightens word spacing for its monospace font; Nunito's spaces disappear with it. */
.text-compact {
  word-spacing: normal;
}

/* Glance's frame draws a 3px solid bottom edge from its own palette math, which turns black in light mode. */
.widget-content:not(.widget-content-frameless),
.widget-content-frame {
  box-shadow: var(--dash-card-shadow);
}

.dash-frameless > .widget-content {
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}

/* Titles sit inside the cards: the widget is the card, and Glance's content frame inside it goes flat. */
.widget:not(.dash-tiles-widget) {
  overflow: hidden;
  border: 1px solid var(--color-widget-content-border);
  border-radius: var(--dash-radius);
  background: var(--color-widget-background);
  box-shadow: var(--dash-card-shadow);
}

.widget:not(.dash-tiles-widget) > .widget-content {
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.widget > .widget-header {
  margin: 0;
  padding: 14px calc(var(--widget-content-horizontal-padding) + 1px) 0;
}

.widget > .widget-header {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
}

.widget > .widget-header h2 {
  color: var(--color-text-highlight);
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: none;
}

.widget > .widget-header + .widget-content {
  padding-top: 12px;
}

/* A folded card (pages.py card(folded=True)) is a <details>: its title row opens and closes it. */
.widget-folded > summary.widget-header {
  padding-bottom: 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.widget-folded > summary::-webkit-details-marker {
  display: none;
}

.widget-folded > summary:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: -2px;
  border-radius: var(--dash-radius);
}

.widget-folded[open] > summary.widget-header {
  padding-bottom: 0;
}

.widget-chevron {
  width: 1.8rem;
  height: 1.8rem;
  margin-left: auto;
  color: var(--color-text-subdue);
  transition: transform 200ms var(--ease-out), color 150ms ease;
}

.widget-folded > summary:hover .widget-chevron {
  color: var(--color-text-highlight);
}

.widget-folded[open] > summary .widget-chevron {
  transform: rotate(180deg);
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 220ms;
  animation-timing-function: ease;
}

/* Desktop (Glance stacks columns at 1190px and below): the page never scrolls, each column does.
   A scrolling page rubber-bands on macOS and drags a sticky header along; a column keeps its bounce to itself. */
@media (min-width: 1191px) {
  html,
  body {
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
  }

  .body-content {
    height: 100%;
  }

  .header-container {
    flex-shrink: 0;
  }

  /* The header container is also .content-bounds, so it is excluded. */
  .body-content > .content-bounds:not(.header-container),
  .page,
  .page-content,
  .page-columns {
    height: 100%;
    min-height: 0;
  }

  /* Cards keep their full height and the column scrolls. A card with overflow hidden would otherwise shrink to fit. */
  .page-column > * {
    flex-shrink: 0;
  }

  /* A page toolbar sits above the columns, and the columns take the height left. */
  .page-content {
    display: flex;
    flex-direction: column;
  }

  .page-columns {
    flex: 1;
  }

  /* The chat card fills the Spotify side column, so the list shows as many messages as fit. */
  .page[data-page='spotify'] .page-column-small > .widget:last-child {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-height: 320px;
  }

  .page[data-page='spotify'] .page-column-small > .widget:last-child > .widget-content,
  [data-mount='room-chat'] {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
  }

  /* My Spotify fills the column with one centered player, so its card ends where the chat ends. */
  :root[data-spotify-mode='me'] .page[data-page='spotify'] .widget.mode-me {
    display: flex;
    flex: 1 0 auto;
    flex-direction: column;
  }

  :root[data-spotify-mode='me'] .page[data-page='spotify'] .widget.mode-me > .widget-content {
    display: flex;
    flex: 1;
    flex-direction: column;
    justify-content: center;
  }

  :root[data-spotify-mode='me'] .page[data-page='spotify'] .sp-player--hero {
    grid-template-columns: minmax(240px, min(440px, 45%)) minmax(0, 560px);
    justify-content: center;
  }

  .page[data-page='spotify'] .chat-list {
    flex: 1;
    max-height: none;
  }

  .page-column {
    height: 100%;
    /* Room for focus rings and shadows at the column edge. */
    margin-inline: -4px;
    padding: 0 4px var(--widget-gap);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }

  .page-column::-webkit-scrollbar {
    display: none;
  }

  :root[data-scrolled] .page-column-full {
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 20px);
    mask-image: linear-gradient(to bottom, transparent, #000 20px);
  }
}

/* Theme switch */

.app-theme-switch {
  position: relative;
  display: flex;
  gap: 2px;
  align-self: center;
  padding: 3px;
  border-radius: 9px;
  background: var(--color-widget-background-highlight);
}

.app-theme-switch::before {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 2.8rem;
  height: 2.4rem;
  border-radius: 6px;
  background: var(--dash-switch-pill, var(--color-widget-background));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  transform: translateX(calc(var(--switch-index, 2) * (2.8rem + 2px)));
}

.app-theme-switch.is-ready::before {
  transition: transform 220ms var(--ease-in-out);
}

.app-theme-switch button {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.8rem;
  height: 2.4rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--color-text-subdue);
  cursor: pointer;
  transition: color 150ms ease, transform 160ms var(--ease-out);
}

.app-theme-switch button[aria-pressed='true'] {
  color: var(--color-text-highlight);
}

.app-theme-switch button:active {
  transform: scale(0.94);
}

.app-theme-switch svg {
  width: 1.6rem;
  height: 1.6rem;
}

/* Shared pieces */

.dash-icon {
  flex-shrink: 0;
}

.dash-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 3.4rem;
  padding: 0 12px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 8px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font: inherit;
  font-size: 1.3rem;
  font-weight: 700;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: transform 160ms var(--ease-out), color 150ms ease, border-color 150ms ease, background-color 150ms ease;
}

.dash-button svg {
  width: 1.6rem;
  height: 1.6rem;
}

.dash-button:active:not(:disabled) {
  transform: scale(0.97);
}

.dash-button:disabled {
  opacity: 0.5;
  cursor: default;
}

.dash-button.is-on {
  color: var(--color-positive);
  border-color: color-mix(in srgb, var(--color-positive) 45%, transparent);
}

.dash-button--primary {
  border-color: transparent;
  background: var(--dash-accent-fill);
  color: var(--dash-accent-ink);
}

.dash-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-positive);
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.dash-more {
  margin-top: 8px;
}

.dash-tag {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-subdue);
  font-size: 1.05rem;
  font-weight: normal;
  vertical-align: 1px;
}

.dash-count {
  color: var(--color-text-subdue);
  font-variant-numeric: tabular-nums;
}

.dash-footnote {
  margin: 10px 0 0;
  color: var(--color-text-subdue);
  font-size: 1.15rem;
}

.dash-empty {
  margin: 0;
  padding: 8px 0;
  color: var(--color-text-subdue);
  list-style: none;
}

.dash-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 10px;
}

.dash-tabs button {
  padding: 6px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-subdue);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: color 150ms ease, background-color 150ms ease, transform 160ms var(--ease-out);
}

.dash-tabs button[aria-selected='true'] {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.dash-tabs button:active {
  transform: scale(0.97);
}

.dash-tab-end {
  white-space: nowrap;
}

/* The filters above a card's list (Pull requests, Stale PRs, the Scoreboard, the map). A picker floats over the list
   below it; the card clips what overflows it, so the script grows the card while a picker would reach past it
   (holdRoom in dashboard.js). */
.pr-filter {
  position: relative;
  margin-bottom: 10px;
}

.pr-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.pr-filter-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 3rem;
  padding: 0 10px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-subdue);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: color 150ms ease, background-color 150ms ease, border-color 150ms ease, transform 160ms var(--ease-out);
}

.pr-filter-button svg {
  width: 1.4rem;
  height: 1.4rem;
}

.pr-filter-button.is-active,
.pr-filter-button[aria-expanded='true'] {
  border-color: transparent;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.pr-filter-button:active {
  transform: scale(0.97);
}

.pr-filter-chips {
  display: contents;
}

.pr-filter-everyone {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.pr-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  max-width: 100%;
  height: 2.6rem;
  padding: 0 3px 0 10px;
  border-radius: 999px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font-size: 1.2rem;
  font-weight: 700;
}

.pr-chip.is-unread {
  color: var(--color-text-subdue);
  text-decoration: line-through;
}

.pr-chip button {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--color-text-subdue);
  cursor: pointer;
}

.pr-chip button svg {
  width: 1.2rem;
  height: 1.2rem;
}

/* A picker's width on a wide screen, so each checkmark sits near its name; a phone uses the card's width. */
.pr-filter-panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  width: 100%;
  max-width: 44rem;
  padding: 6px;
  border: 1px solid var(--color-popover-border);
  border-radius: 10px;
  background: var(--color-popover-background);
  box-shadow: var(--dash-shadow-menu);
  transform-origin: top left;
  animation: dash-menu-in 150ms var(--ease-out);
}

.pr-filter-panel .dash-search {
  height: 3.4rem;
}

.pr-filter-options {
  max-height: 30rem;
  margin-top: 4px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.pr-filter-heading {
  margin: 0;
  padding: 10px 8px 4px;
  color: var(--color-text-subdue);
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.pr-filter-note {
  margin: 0;
  padding: 4px 8px 8px;
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.pr-filter-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--color-text-highlight);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.pr-filter-option .sp-avatar {
  width: 26px;
  height: 26px;
}

.pr-filter-option .pr-team-avatar {
  border-radius: 6px;
}

.pr-filter-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.pr-filter-name {
  overflow: hidden;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Wraps instead of cutting off, so a phone still shows the open PR count at the end. */
.pr-filter-sub {
  overflow-wrap: anywhere;
  color: var(--color-text-subdue);
  font-size: 1.15rem;
}

.pr-filter-count {
  white-space: nowrap;
}


.pr-filter-check {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 6px;
  color: transparent;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.pr-filter-check svg {
  width: 1.3rem;
  height: 1.3rem;
}

.pr-filter-option[aria-pressed='true'] .pr-filter-check {
  border-color: var(--color-positive);
  background: var(--color-positive);
  color: var(--color-widget-background);
}

/* A repo has no picture, so its row leads with a folder the size of an avatar. */
.pr-repo-icon {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-subdue);
}

.pr-repo-icon svg {
  width: 1.4rem;
  height: 1.4rem;
}

/* A repo chip carries the folder, so "api-core" does not read as a person or a team. */
.pr-chip .pr-chip-icon {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  margin-right: 4px;
}

/* The Scoreboard's filter sits under its range and measure. */
.dash-score-filter .pr-filter {
  margin: 10px 0 0;
}

.dash-search {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 8px;
  height: 3.6rem;
  padding: 0 12px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 10px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-subdue);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.dash-search:focus-within {
  border-color: var(--color-positive);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-positive) 25%, transparent);
}

.dash-search svg {
  width: 1.6rem;
  height: 1.6rem;
}

.dash-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--color-text-highlight);
  font: inherit;
}

/* Safari on an iPhone zooms the page into any text field under 16px and stays zoomed. The Opened by picker focuses its
   search as soon as it opens. */
@media (max-width: 600px) {
  .dash-search input {
    font-size: 16px;
  }
}

.dash-skeleton,
.dash-skeleton-row {
  position: relative;
  overflow: hidden;
  border-radius: 8px;
  background: var(--color-widget-background-highlight);
  list-style: none;
}

.dash-skeleton-row {
  height: 44px;
  margin-bottom: 6px;
}

.dash-skeleton::after,
.dash-skeleton-row::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--dash-shimmer), transparent);
  transform: translateX(-100%);
  animation: dash-shimmer 1.4s ease-in-out infinite;
}

.sp-player.is-loading .sp-title,
.sp-player.is-loading .sp-artist {
  width: 60%;
  border-radius: 6px;
  background: var(--color-widget-background-highlight);
  color: transparent;
}

.sp-player.is-loading .sp-artist {
  width: 40%;
  margin-top: 6px;
}

.sp-player.is-loading .sp-art-empty {
  display: none;
}

/* Links, as in api-core's Admin: a new-tab text link gets a faint underline and the external-link icon after it, and an
   in-app link inside a line of text gets the underline alone. Buttons, cover art, run squares and links holding an
   image or icon are left alone. */
.page a[target='_blank']:not(.dash-button, .dash-run, .sp-art, [download]):not(:has(svg, img)),
.page :is(p, li) a[href]:not([target='_blank'], .dash-button, [download]):not(:has(svg, img)) {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  text-underline-offset: 2px;
  transition: text-decoration-color 150ms ease;
}

.page a[target='_blank']:not(.dash-button, .dash-run, .sp-art, [download]):not(:has(svg, img)):is(:hover, :focus-visible),
.page :is(p, li) a[href]:not([target='_blank'], .dash-button, [download]):not(:has(svg, img)):is(:hover, :focus-visible) {
  text-decoration-color: currentColor;
  transition-duration: 0s;
}

/* The gap sits inside the icon's box, so the link's underline stops at the text. */
.page a[target='_blank']:not(.dash-button, .dash-run, .sp-art, [download]):not(:has(svg, img))::after {
  display: inline-block;
  width: calc(0.875em + 0.25em);
  height: 0.875em;
  background-color: currentColor;
  content: '';
  vertical-align: -0.1em;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E")
    right center / 0.875em 0.875em no-repeat;
}

/* Metric tiles */

/* Three per row in the middle column, six once the column is wide enough to keep labels on one line. */
.dash-tiles-widget {
  container-type: inline-size;
}

.dash-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

@container (min-width: 1100px) {
  .dash-tiles {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

@container (max-width: 520px) {
  .dash-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.dash-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 128px;
  padding: 14px 16px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: var(--dash-radius);
  background: var(--color-widget-background);
  box-shadow: var(--dash-card-shadow);
}

.dash-tile-label {
  color: var(--color-text-subdue);
  font-size: 1.25rem;
  font-weight: 700;
}

.dash-tile-value {
  color: var(--color-text-highlight);
  font-size: 3.2rem;
  font-weight: 800;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.dash-tile-sub,
.dash-delta {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.dash-delta {
  font-weight: 700;
}

.is-good {
  color: var(--color-positive);
}

.is-warn {
  color: var(--dash-warning);
}

.is-bad {
  color: var(--color-negative);
}

.dash-tile--rank.is-medal-1 { --medal: var(--gold); }
.dash-tile--rank.is-medal-2 { --medal: var(--silver); }
.dash-tile--rank.is-medal-3 { --medal: var(--bronze); }

.dash-tile--rank[class*='is-medal'] {
  background: linear-gradient(160deg, color-mix(in srgb, var(--medal) 16%, var(--color-widget-background)), var(--color-widget-background) 60%);
}

.dash-tile--rank[class*='is-medal'] .dash-tile-value {
  color: var(--medal);
}

/* A custom range's days make the label long, so it stops short of the crown. */
.dash-tile--rank.is-medal-1 .dash-tile-label {
  padding-right: 2.4rem;
}

.dash-tile-crown {
  position: absolute;
  top: 12px;
  right: 14px;
  width: 2.2rem;
  height: 2.2rem;
  color: var(--gold);
  transform: rotate(12deg);
}

.dash-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 32px;
  margin-top: auto;
  padding-top: 8px;
}

.dash-bar {
  position: relative;
  display: flex;
  flex: 1;
  height: 100%;
}

.dash-bars i {
  width: 100%;
  height: 100%;
  border-radius: 3px 3px 1px 1px;
  background: var(--color-text-subdue);
  opacity: 0.35;
  transform: scaleY(max(var(--h), 0.04));
  transform-origin: bottom;
  transition: opacity 150ms ease;
}

.dash-bar.is-current i {
  background: var(--color-positive);
  opacity: 1;
}

/* Bar tooltip: the week and its count. Fast and without delay, since the bars sit side by side. */
.dash-bar::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  z-index: 5;
  padding: 6px 9px;
  border-radius: 7px;
  background: var(--dash-tooltip, #34363e);
  color: #f4f5f7;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.35;
  white-space: pre;
  text-align: center;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 3px) scale(0.97);
  transform-origin: bottom center;
  transition: opacity 125ms ease-out, transform 125ms var(--ease-out);
}

.dash-bar.is-start::after {
  left: 0;
  transform: translate(0, 3px) scale(0.97);
  transform-origin: bottom left;
}

.dash-bar.is-end::after {
  right: 0;
  left: auto;
  transform: translate(0, 3px) scale(0.97);
  transform-origin: bottom right;
}

:root[data-app-theme='light'] {
  --dash-tooltip: #090a0d;
}

/* When the server last counted a card's numbers. */
/* "Updated … ago" with its refresh button, at the end of a card's header. */
.dash-stamp {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

.dash-refresh {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--color-text-subdue);
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.dash-refresh svg {
  width: 14px;
  height: 14px;
}

.dash-refresh:hover:not(:disabled) {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.dash-refresh:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 1px;
}

.dash-refresh:disabled {
  cursor: default;
}

.dash-refresh.is-refreshing svg {
  animation: dash-spin 900ms linear infinite;
}

@keyframes dash-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dash-refresh.is-refreshing svg {
    animation: none;
    opacity: 0.5;
  }
}

.dash-updated {
  color: var(--color-text-subdue);
  font-size: 1.15rem;
  font-weight: 600;
  white-space: nowrap;
}

.dash-tiles-widget .dash-stamp {
  display: flex;
  justify-content: flex-end;
  margin-top: 8px;
}

/* Card info: what the card shows, under the icon on hover or focus. It opens downward, inside the card. */
.widget-info {
  position: relative;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--color-text-subdue);
  cursor: help;
  transition: color 150ms ease;
}

.widget-info svg {
  width: 1.4rem;
  height: 1.4rem;
}

.widget-info:hover,
.widget-info:focus-visible {
  color: var(--color-text-highlight);
}

.widget-info:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 2px;
}

.widget-info::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 8px);
  left: -8px;
  z-index: 5;
  width: max-content;
  max-width: 300px;
  padding: 8px 10px;
  border-radius: 7px;
  background: var(--dash-tooltip, #34363e);
  color: #f4f5f7;
  font-size: 1.2rem;
  font-weight: 600;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-3px) scale(0.97);
  transform-origin: top left;
  transition: opacity 125ms ease-out, transform 125ms var(--ease-out);
}

.widget-info:hover::after,
.widget-info:focus::after {
  opacity: 1;
  transform: none;
}

/* Scoreboard */

.dash-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* Wider than a phone, it scrolls sideways inside itself instead of widening the page. */
.dash-segmented {
  display: inline-flex;
  gap: 2px;
  max-width: 100%;
  padding: 3px;
  border-radius: 9px;
  background: var(--color-widget-background-highlight);
  overflow-x: auto;
  scrollbar-width: none;
}

.dash-segmented::-webkit-scrollbar {
  display: none;
}

.dash-segmented button {
  height: 2.8rem;
  padding: 0 11px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--color-text-subdue);
  font: inherit;
  font-size: 1.25rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: color 150ms ease, background-color 150ms ease, transform 160ms var(--ease-out);
}

.dash-segmented button[aria-pressed='true'] {
  background: var(--color-widget-background);
  color: var(--color-text-highlight);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.dash-segmented button:active {
  transform: scale(0.97);
}

/* The custom range's days, on their own line under the range and measure. */
.dash-dates {
  display: flex;
  flex-basis: 100%;
  flex-wrap: wrap;
  gap: 8px;
}

.dash-dates label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text-subdue);
  font-size: 1.2rem;
  font-weight: 700;
}

.dash-dates input {
  height: 3rem;
  padding: 0 8px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 8px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font: inherit;
  font-size: 1.25rem;
}

.dash-dates input:focus {
  border-color: var(--color-positive);
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-positive) 25%, transparent);
}

/* The browser's own calendar and icon follow the dashboard's theme. */
:root[data-app-theme='dark'] .dash-dates input {
  color-scheme: dark;
}

:root[data-app-theme='light'] .dash-dates input {
  color-scheme: light;
}

/* As with the search fields, Safari on an iPhone zooms into a field under 16px. */
@media (max-width: 600px) {
  .dash-dates input {
    font-size: 16px;
  }
}

.dash-loading-note {
  margin: 18px 0 10px;
}

.dash-podium {
  display: grid;
  grid-template-columns: 1fr 1.15fr 1fr;
  align-items: end;
  gap: 12px;
  max-width: 460px;
  margin: 22px auto 18px;
}

.dash-podium-place {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
  text-align: center;
}

.dash-podium-place.is-rank-1 { --medal: var(--gold); }
.dash-podium-place.is-rank-2 { --medal: var(--silver); }
.dash-podium-place.is-rank-3 { --medal: var(--bronze); }

.dash-podium-avatar {
  width: 52px;
  height: 52px;
  border: 3px solid var(--medal);
  border-radius: 50%;
  object-fit: cover;
}

.is-rank-1 .dash-podium-avatar {
  width: 68px;
  height: 68px;
}

.dash-podium-crown {
  position: absolute;
  top: -20px;
  width: 2.4rem;
  height: 2.4rem;
  color: var(--gold);
}

.dash-podium-name {
  max-width: 100%;
  overflow: hidden;
  color: var(--color-text-highlight);
  font-weight: 800;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.is-me .dash-podium-name {
  color: var(--color-positive);
}

.dash-podium-score {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
  font-variant-numeric: tabular-nums;
}

.dash-podium-step {
  display: grid;
  place-items: center;
  width: 100%;
  margin-top: 6px;
  border-radius: 8px 8px 2px 2px;
  background: color-mix(in srgb, var(--medal) 20%, transparent);
  color: var(--medal);
  font-size: 2rem;
  font-weight: 800;
  transform-origin: bottom;
}

.is-rank-1 .dash-podium-step { height: 64px; }
.is-rank-2 .dash-podium-step { height: 46px; }
.is-rank-3 .dash-podium-step { height: 32px; }

.dash-board {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: 640px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.dash-board ~ .dash-more,
.dash-board ~ .dash-footnote {
  display: block;
  max-width: 640px;
  margin-inline: auto;
  padding-inline: 8px;
}

.dash-board li {
  display: grid;
  grid-template-columns: 2.4rem 3.2rem minmax(0, 1fr) 4.6rem 4.6rem minmax(40px, 120px) 3.2rem;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: 8px;
}

.dash-board li.is-me {
  background: color-mix(in srgb, var(--color-positive) 12%, transparent);
}

.dash-board-rank,
.dash-board-score {
  font-weight: 800;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.dash-board-rank {
  color: var(--color-text-subdue);
}

.dash-board-score {
  color: var(--color-text-highlight);
}

.dash-board-name {
  overflow: hidden;
  color: var(--color-text-highlight);
  font-weight: 700;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.dash-board-stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--color-text-subdue);
  font-variant-numeric: tabular-nums;
}

.dash-board-stat svg {
  width: 1.4rem;
  height: 1.4rem;
}

.dash-board-bar {
  height: 6px;
  overflow: hidden;
  border-radius: 3px;
  background: var(--color-widget-background-highlight);
}

.dash-board-bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--color-positive);
  transform: scaleX(var(--w));
  transform-origin: left;
}

/* A phone has no room for seven columns in a row: they left the name 0px wide. The name takes the first line, and the
   merged PRs, reviews and bar go under it.
   Every row shares the board's columns (subgrid), so the merged PRs and reviews take the width of the widest row, and
   every bar starts and ends in the same place. Sized row by row, "59 15" pushed its bar right of "9 6"'s, and bars of
   different lengths could not be compared. A browser without subgrid keeps the row's own columns. */
@media (max-width: 600px) {
  /* With subgrid, a row's 8px side padding comes off its first and last columns, so the rank's gets the 8px back. */
  .dash-board {
    display: grid;
    grid-template-columns: calc(2.4rem + 8px) 3.2rem auto auto minmax(0, 1fr) auto;
    column-gap: 10px;
  }

  .dash-board li {
    grid-column: 1 / -1;
    grid-template-columns: 2.4rem 3.2rem auto auto minmax(0, 1fr) auto;
    grid-template-columns: subgrid;
    gap: 2px 10px;
  }

  .dash-board-rank,
  .dash-board li > .sp-avatar {
    grid-row: 1 / span 2;
  }

  .dash-board-name {
    grid-row: 1;
    grid-column: 3 / span 3;
  }

  .dash-board-stat {
    grid-row: 2;
    font-size: 1.2rem;
  }

  .dash-board-stat svg {
    width: 1.2rem;
    height: 1.2rem;
  }

  .dash-board-bar {
    grid-row: 2;
    grid-column: 5;
  }

  .dash-board-score {
    grid-row: 1 / span 2;
    grid-column: 6;
  }
}

/* Pull requests under the 2-approval rule */

.dash-pill {
  display: inline-block;
  min-width: 2rem;
  margin-left: 4px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font-size: 1.15rem;
  font-weight: normal;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.dash-pill.is-good {
  background: color-mix(in srgb, var(--color-positive) 18%, transparent);
  color: var(--color-positive);
}

.dash-pill.is-warn {
  background: color-mix(in srgb, var(--dash-warning) 18%, transparent);
  color: var(--dash-warning);
}

.dash-tag.is-accent {
  background: color-mix(in srgb, var(--color-positive) 16%, transparent);
  color: var(--color-positive);
}

.dash-tag.is-warn {
  background: color-mix(in srgb, var(--dash-warning) 16%, transparent);
  color: var(--dash-warning);
}

.dash-tag.is-bad {
  background: color-mix(in srgb, var(--color-negative) 14%, transparent);
  color: var(--color-negative);
}

.pr-age {
  flex-shrink: 0;
  min-width: 5.2rem;
  padding: 3px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--dash-warning) 16%, transparent);
  color: var(--dash-warning);
  font-size: 1.15rem;
  font-weight: normal;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.pr-age.is-old {
  background: color-mix(in srgb, var(--color-negative) 14%, transparent);
  color: var(--color-negative);
}

.pr-need.is-need-0 {
  background: color-mix(in srgb, var(--color-positive) 16%, transparent);
  color: var(--color-positive);
}

.pr-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pr-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px;
  border-radius: 8px;
}

.pr-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.pr-title-text,
.pr-sub {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The title shortens inside its own span, so the external-link icon after it always shows. */
.pr-title {
  display: inline-flex;
  align-items: baseline;
  max-width: 100%;
  color: var(--color-text-highlight);
  font-weight: 700;
}

.pr-title-text {
  min-width: 0;
}

.pr-title::after {
  flex-shrink: 0;
}

.pr-sub {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.pr-approvals {
  display: flex;
  flex-shrink: 0;
}

.pr-approvals .sp-avatar + .sp-avatar {
  margin-left: -6px;
}

.pr-approvals .sp-avatar {
  box-shadow: 0 0 0 2px var(--color-widget-background);
}

.pr-checks {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-positive) 16%, transparent);
  color: var(--color-positive);
}

.pr-checks.is-failure {
  background: color-mix(in srgb, var(--color-negative) 16%, transparent);
  color: var(--color-negative);
}

.pr-checks.is-pending {
  background: color-mix(in srgb, var(--dash-warning) 18%, transparent);
  color: var(--dash-warning);
}

.pr-checks.is-none {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-subdue);
}

.pr-checks svg {
  width: 1.3rem;
  height: 1.3rem;
}

.pr-need {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 8.4rem;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 1.15rem;
  font-weight: normal;
}

.pr-need svg {
  width: 1.3rem;
  height: 1.3rem;
}

.pr-need.is-need-2 {
  background: color-mix(in srgb, var(--dash-warning) 18%, transparent);
  color: var(--dash-warning);
}

.pr-need.is-changes {
  background: color-mix(in srgb, var(--color-negative) 16%, transparent);
  color: var(--color-negative);
}

.pr-need.is-need-1 {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.pr-need.is-ready {
  background: color-mix(in srgb, var(--color-positive) 16%, transparent);
  color: var(--color-positive);
}

.is-entering.pr-list li {
  animation: dash-rise 340ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 30ms);
}

/* A phone gives the title the row's whole width and up to two lines. The approvals, checks and status go under it. */
@media (max-width: 600px) {
  .pr-list li:not(.dash-empty) {
    display: grid;
    grid-template-columns: auto auto auto auto minmax(0, 1fr);
    align-items: center;
    gap: 6px 10px;
  }

  .pr-list li > .sp-avatar:first-child {
    grid-row: 1 / span 2;
    align-self: start;
  }

  .pr-list .pr-text {
    grid-column: 2 / -1;
  }

  .pr-list .pr-approvals,
  .pr-list .pr-checks,
  .pr-list .pr-need,
  .pr-list .pr-age {
    grid-row: 2;
  }

  .pr-list .pr-approvals:empty {
    display: none;
  }

  .pr-list .pr-title-text {
    display: -webkit-box;
    white-space: normal;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
  }

  .pr-list .pr-sub {
    white-space: normal;
  }

  .pr-list .pr-when {
    white-space: nowrap;
  }
}

/* Deploys */

.dash-deploy-axis {
  display: flex;
  justify-content: space-between;
  margin-bottom: 2px;
  color: var(--color-text-subdue);
  font-size: 1.05rem;
}

.dash-deploy-repo + .dash-deploy-repo {
  margin-top: 18px;
}

.dash-deploy-repo-name {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 8px;
  color: var(--color-text-subdue);
  font-size: 1.2rem;
  font-weight: 700;
}

.dash-deploy-repo-name svg {
  width: 14px;
  height: 14px;
  flex: none;
}

.dash-deploy + .dash-deploy {
  margin-top: 12px;
}

.dash-deploy-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.dash-deploy-title {
  min-width: 0;
}

.dash-deploy-name {
  color: var(--color-text-highlight);
  font-weight: 800;
  overflow-wrap: anywhere;
}

.dash-deploy-rate {
  flex: none;
  font-size: 1.6rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* One row of the last 20 runs: oldest on the left, latest on the right. */
.dash-deploy-runs {
  display: grid;
  grid-template-columns: repeat(20, 1fr);
  gap: 3px;
  margin: 5px 0 3px;
}

.dash-run {
  height: 16px;
  border-radius: 3px;
  background: var(--color-widget-background-highlight);
  transition: transform 160ms var(--ease-out);
}

.dash-run.is-success { background: var(--color-positive); }
.dash-run.is-failure,
.dash-run.is-timed_out,
.dash-run.is-startup_failure { background: var(--color-negative); }
.dash-run.is-cancelled,
.dash-run.is-skipped { background: var(--dash-track); }

.dash-run.is-running {
  background: var(--dash-warning);
  animation: dash-pulse 1.4s ease-in-out infinite;
}

.dash-deploy-body .dash-more {
  display: block;
  margin-top: 14px;
}

.dash-deploy-note {
  margin-top: 12px;
}

/* Spotify player */

.sp-player {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.sp-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.sp-art {
  position: relative;
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 8px;
  background: var(--color-widget-background-highlight);
}

.sp-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A solid color faded as one layer: a see-through stroke color shows where the stems cross the circles. */
.sp-art-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--color-text-highlight);
}

.sp-art-empty svg {
  width: 38%;
  height: 38%;
  opacity: 0.27;
}

.sp-player.has-track .sp-art-empty {
  display: none;
}

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

.sp-titles {
  flex: 1;
  min-width: 0;
}

.sp-title,
.sp-artist {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-title {
  color: var(--color-text-highlight);
  font-size: 2rem;
  font-weight: 800;
  line-height: 1.2;
}

.sp-artist {
  color: var(--color-text-subdue);
  font-size: 1.4rem;
}

.sp-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 2px;
}

.sp-button {
  position: relative;
  display: grid;
  place-items: center;
  width: 3.6rem;
  height: 3.6rem;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-subdue);
  cursor: pointer;
  transition: color 150ms ease, transform 160ms var(--ease-out);
}

.sp-button svg {
  width: 2rem;
  height: 2rem;
}

.sp-button:active {
  transform: scale(0.9);
}

.sp-play {
  width: 4.4rem;
  height: 4.4rem;
  background: var(--color-text-highlight);
  color: var(--color-background);
}

.sp-play:active {
  transform: scale(0.94);
}

.sp-play svg {
  position: absolute;
  transition: opacity 160ms ease, transform 200ms var(--ease-out), filter 160ms ease;
}

/* Play and pause swap with a short blur so the two icons read as one morph. */
.sp-icon-pause,
.is-playing .sp-icon-play {
  opacity: 0;
  filter: blur(2px);
  transform: scale(0.6);
}

.is-playing .sp-icon-pause {
  opacity: 1;
  filter: none;
  transform: none;
}

.sp-toggleable.is-on {
  color: var(--color-positive);
}

.sp-toggleable.is-on::after {
  content: '';
  position: absolute;
  bottom: 1px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
}

.sp-icon-repeat-one,
.is-one .sp-icon-repeat {
  display: none;
}

.is-one .sp-icon-repeat-one {
  display: block;
}

.sp-timeline {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--color-text-subdue);
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
}

.sp-progress {
  position: relative;
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--dash-track);
  cursor: pointer;
}

/* A taller hit area than the 4px track. */
.sp-progress::after {
  content: '';
  position: absolute;
  inset: -8px 0;
}

.sp-progress:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 6px;
}

.sp-progress-fill {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background: var(--color-text-highlight);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 250ms linear, background-color 150ms ease;
}

.sp-player:not(.has-track) .sp-controls,
.sp-player:not(.has-track) .sp-timeline,
.sp-player.no-controls .sp-controls {
  opacity: 0.4;
  pointer-events: none;
}

/* An idle room still starts from play, which plays the room queue's top song. */
.sp-player.can-start:not(.has-track):not(.no-controls) .sp-controls {
  opacity: 1;
  pointer-events: auto;
}

.sp-player.can-start:not(.has-track) .sp-button:not(.sp-play) {
  opacity: 0.4;
  pointer-events: none;
}

.sp-note {
  margin: 0;
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

/* A note with an action, such as Leave the party, puts the button after the text. */
.sp-note:has(.dash-button) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.sp-eq {
  display: inline-flex;
  flex-shrink: 0;
  align-items: flex-end;
  gap: 2px;
  width: 14px;
  height: 14px;
  transition: opacity 200ms ease;
}

/* Paused, the three short bars read as a "more" menu, so they show only while music plays. */
.sp-player:not(.is-playing) .sp-eq {
  opacity: 0;
}

.sp-eq i {
  flex: 1;
  height: 100%;
  border-radius: 1px;
  background: var(--color-text-subdue);
  transform: scaleY(0.3);
  transform-origin: bottom;
  transition: background-color 200ms ease;
}

.is-playing .sp-eq i,
.sp-eq.is-on i {
  background: var(--color-positive);
  animation: sp-eq 0.9s ease-in-out infinite alternate;
}

.is-playing .sp-eq i:nth-child(2),
.sp-eq.is-on i:nth-child(2) {
  animation-duration: 0.7s;
  animation-delay: -0.3s;
}

.is-playing .sp-eq i:nth-child(3),
.sp-eq.is-on i:nth-child(3) {
  animation-duration: 1.1s;
  animation-delay: -0.5s;
}

.sp-player--hero {
  display: grid;
  grid-template-columns: minmax(200px, 340px) minmax(0, 1fr);
  align-items: center;
  gap: 32px;
}

/* The controls center over the progress bar, so the bar is capped to keep them near the title. */
.sp-player--hero .sp-body {
  gap: 20px;
  max-width: 560px;
}

.sp-player--hero .sp-title {
  font-size: 3.4rem;
  white-space: normal;
}

.sp-player--hero .sp-artist {
  font-size: 1.8rem;
}

.sp-player--hero .sp-controls {
  justify-content: center;
  gap: 20px;
}

@media (max-width: 800px) {
  .sp-player--hero {
    grid-template-columns: minmax(112px, 200px) minmax(0, 1fr);
    gap: 20px;
  }

  .sp-player--hero .sp-title {
    font-size: 2.4rem;
  }

  .sp-player--hero .sp-artist {
    font-size: 1.5rem;
  }
}

/* A phone stacks the cover over the title, the way Spotify's own app does. */
@media (max-width: 520px) {
  .sp-player--hero {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Spotify lists: up next, recently played, room, search results, team */

.sp-list,
.sp-room,
.sp-results,
.sp-team {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sp-list li,
.sp-room li,
.sp-results li,
.sp-team li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px;
  border-radius: 8px;
}

/* The party plays its queue in order, so it has no shuffle, repeat or previous. */
.sp-player--party [data-action='shuffle'],
.sp-player--party [data-action='previous'],
.sp-player--party [data-action='repeat'] {
  display: none;
}

/* A recently played song joins the party queue again on click. */
/* A played song: when it played, and its Add button. With a mouse, the button takes the time's place on hover. */
.sp-list-end {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

@media (hover: hover) and (pointer: fine) {
  .sp-list li {
    transition: background-color 150ms ease;
  }

  .sp-list li:hover,
  .sp-list li:focus-within {
    background: var(--color-widget-background-highlight);
  }

  .sp-list-end {
    display: grid;
    justify-items: end;
  }

  .sp-list-end > * {
    grid-area: 1 / 1;
    transition: opacity 150ms ease;
  }

  /* The hidden one of the two must not catch the click meant for the other. */
  .sp-list li:not(:hover, :focus-within) .sp-list-action,
  .sp-list li:is(:hover, :focus-within) .sp-list-when {
    opacity: 0;
    pointer-events: none;
  }
}

.sp-list img,
.sp-room li > img,
.sp-results img {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 4px;
  object-fit: cover;
}

.sp-list-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.sp-list-title,
.sp-list-sub {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-list-title {
  color: var(--color-text-highlight);
  font-weight: 700;
}

.sp-list-sub,
.sp-list-meta {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.sp-list-meta {
  font-variant-numeric: tabular-nums;
}

.sp-avatar {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
}

.sp-avatar--small {
  width: 22px;
  height: 22px;
  font-size: 1rem;
}

.sp-avatar--initial {
  display: inline-grid;
  place-items: center;
  container-type: size;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font-weight: 800;
}

/* Two initials fit every avatar size. */
.sp-avatar--initial > span {
  font-size: 38cqw;
  letter-spacing: 0.02em;
  line-height: 1;
}

.sp-team-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.sp-team-name {
  color: var(--color-text-base);
  font-weight: 700;
}

.sp-team li.is-playing .sp-team-name {
  color: var(--color-text-highlight);
}

.sp-team-cover {
  width: 32px;
  height: 32px;
  border-radius: 4px;
}

/* A person's local time, ticking every second, over their time zone's name, on the right of their row. */
.team-when {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  align-items: flex-end;
  max-width: 48%;
  min-width: 0;
  text-align: right;
}

/* The time keeps its width and never wraps before AM or PM. */
.team-time {
  color: var(--color-text-base);
  font-weight: 600;
  white-space: nowrap;
}

.team-when .team-zone {
  max-width: 100%;
}

/* The People card gets about a third of the Team page, and the map the rest. */
@media (min-width: 1191px) {
  .page[data-page='team'] .page-column-small {
    width: clamp(380px, 34%, 520px);
  }
}

.sp-room-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

/* Search results float under the search, over the queue, like the filters' pickers (holdRoom in dashboard.js). */
.sp-results {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  left: 0;
  z-index: 20;
  max-height: 36rem;
  margin: 0;
  padding: 6px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--color-popover-border);
  border-radius: 10px;
  background: var(--color-popover-background);
  box-shadow: var(--dash-shadow-menu);
}

.sp-room li.is-next {
  background: color-mix(in srgb, var(--color-positive) 8%, transparent);
}

/* The queue's order: a number, or a note for the song playing. */
.sp-place {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 2.4rem;
  color: var(--color-text-subdue);
  font-size: 1.25rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.sp-place svg {
  width: 1.5rem;
  height: 1.5rem;
  color: var(--color-positive);
}

/* The DJ's tools on a queued song: play now, move up or down, remove. */
.sp-room-tools {
  display: flex;
  flex-shrink: 0;
  gap: 2px;
}

.sp-room-tools .sp-button:disabled {
  opacity: 0.3;
  cursor: default;
}

.sp-remove {
  color: var(--color-negative);
}

/* On a phone the DJ's four tools took most of the row and squeezed the song's title to a few letters. They go on a line
   of their own under the song. */
@media (max-width: 600px) {
  .sp-room li {
    flex-wrap: wrap;
    row-gap: 0;
  }

  .sp-room-tools {
    order: 1;
    justify-content: flex-end;
    width: 100%;
  }
}

/* Hover only where a pointer can hover, so taps on touch screens leave no stuck state. */
@media (hover: hover) and (pointer: fine) {
  /* The play button keeps its own colors: its icon is the background color on a light disc. */
  .app-theme-switch button:hover,
  .sp-button:not(.sp-play):hover,
  .dash-tabs button:hover {
    color: var(--color-text-highlight);
  }

  .pr-filter-button:hover,
  .pr-chip button:hover {
    color: var(--color-text-highlight);
  }

  .pr-filter-option:hover {
    background: var(--color-widget-background-highlight);
  }

  .sp-play:hover {
    transform: scale(1.04);
  }

  .sp-progress:hover .sp-progress-fill {
    background: var(--color-positive);
  }

  .sp-list li:hover,
  .sp-results li:hover,
  .dash-board li:not(.is-me):hover {
    background: var(--color-widget-background-highlight);
  }

  .dash-run:hover {
    transform: scale(1.18);
  }

  .dash-bars:hover i {
    opacity: 0.25;
  }

  .dash-bars:hover .dash-bar:hover i {
    opacity: 1;
  }

  .dash-bar:hover::after {
    opacity: 1;
    transform: translate(-50%, 0) scale(1);
  }

  .dash-bar.is-start:hover::after,
  .dash-bar.is-end:hover::after {
    transform: translate(0, 0) scale(1);
  }

  .dash-segmented button:not([aria-pressed='true']):hover {
    color: var(--color-text-highlight);
  }
}

/* Entrance motion: first render and tab switches only (the .is-entering class), never a poll's redraw. */

.widget {
  animation: dash-rise 420ms var(--ease-out) backwards;
  animation-delay: calc((var(--widget-index, 0) + var(--column-index, 0)) * 45ms);
}

.page.content-ready > .page-content {
  animation: none;
}

.page-column:nth-child(2) { --column-index: 1; }
.page-column:nth-child(3) { --column-index: 2; }
.page-column > .widget:nth-child(2) { --widget-index: 1; }
.page-column > .widget:nth-child(3) { --widget-index: 2; }
.page-column > .widget:nth-child(4) { --widget-index: 3; }

.is-entering .dash-tile {
  animation: dash-rise 420ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 40ms);
}

.is-entering .dash-bars i {
  animation: dash-grow 520ms var(--ease-out) backwards;
  animation-delay: calc(220ms + var(--i, 0) * 30ms);
}

.is-entering .dash-podium-place {
  animation: dash-rise 480ms var(--ease-out) backwards;
}

.is-entering .dash-podium-place.is-rank-2 { animation-delay: 90ms; }
.is-entering .dash-podium-place.is-rank-3 { animation-delay: 180ms; }

.is-entering .dash-podium-step {
  animation: dash-grow 520ms var(--ease-out) backwards;
  animation-delay: 120ms;
}

.is-entering .dash-board li,
.is-entering.sp-list li,
.is-entering.sp-room li,
.is-entering.sp-results li,
.is-entering .sp-team li,
.sp-room li.is-new {
  animation: dash-rise 340ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 30ms);
}

.is-entering .dash-board li {
  animation-delay: calc(240ms + var(--i, 0) * 30ms);
}

.is-entering .dash-board-bar i {
  animation: dash-grow-x 600ms var(--ease-out) backwards;
  animation-delay: calc(320ms + var(--i, 0) * 30ms);
}

.is-entering .dash-run:not(.is-running) {
  animation: dash-pop 300ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 15ms);
}

/* An automatic reload keeps the screen still. */
:root[data-quiet-load] .widget {
  animation: none;
}

@keyframes dash-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

@keyframes dash-grow {
  from {
    transform: scaleY(0.04);
  }
}

@keyframes dash-grow-x {
  from {
    transform: scaleX(0.02);
  }
}

@keyframes dash-pop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
}

@keyframes dash-pulse {
  50% {
    opacity: 0.45;
  }
}

@keyframes dash-shimmer {
  to {
    transform: translateX(100%);
  }
}

@keyframes sp-eq {
  from {
    transform: scaleY(0.25);
  }

  to {
    transform: scaleY(1);
  }
}

@keyframes dash-fade {
  from {
    opacity: 0;
  }
}

/* Reduced motion keeps fades that help reading and drops movement. */
@media (prefers-reduced-motion: reduce) {
  .widget,
  .is-entering .dash-tile,
  .is-entering .dash-board li,
  .is-entering .sp-team li,
  .sp-room li.is-new {
    animation: dash-fade 200ms ease backwards;
  }

  .is-entering .dash-bars i,
  .is-entering .dash-podium-place,
  .is-entering .dash-podium-step,
  .is-entering .dash-board-bar i,
  .is-entering .dash-run,
  .is-playing .sp-eq i,
  .sp-eq.is-on i,
  .dash-run.is-running,
  .dash-skeleton::after,
  .dash-skeleton-row::after {
    animation: none;
  }

  .app-theme-switch.is-ready::before,
  .sp-progress-fill {
    transition: none;
  }
}

/* Header: settings and account */

.header-icon {
  display: grid;
  place-items: center;
  width: 3.4rem;
  height: 3.4rem;
  border-radius: 8px;
  color: var(--color-text-subdue);
  transition: color 150ms ease, background-color 150ms ease, transform 160ms var(--ease-out);
}

.header-icon svg {
  width: 1.8rem;
  height: 1.8rem;
}

.header-icon[aria-current='page'] {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.header-icon:active {
  transform: scale(0.94);
}

/* The account button's place is held from the first paint, as a grey circle until /api/me answers, so the gear and the
   theme switch beside it never move when it appears. */
/* Notification center (components.notifications): the bell, its unread count, and a panel under it. */
[data-mount='notifications'] {
  position: relative;
}

.notif-button {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.notif-button[aria-expanded='true'] {
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.notif-badge {
  position: absolute;
  top: 2px;
  right: 1px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 999px;
  box-shadow: 0 0 0 2px var(--color-background);
  background: var(--color-negative);
  color: #fff;
  font-size: 1rem;
  font-weight: 800;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.notif-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: -48px;
  z-index: 30;
  display: flex;
  flex-direction: column;
  width: min(460px, calc(100vw - 32px));
  max-height: min(640px, calc(100vh - 96px));
  border: 1px solid var(--color-popover-border);
  border-radius: 12px;
  background: var(--color-popover-background);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
  color: var(--color-text-base);
  transform-origin: top right;
  animation: dash-menu-in 150ms var(--ease-out);
}

/* As with the account menu, the light theme's navy header opens its panel on the page, so it takes the page's ink. */
:root[data-app-theme='light'] .header .notif-panel {
  --color-text-highlight: #090a0d;
  --color-text-subdue: rgba(9, 10, 13, 0.62);
  --color-widget-background-highlight: rgba(9, 10, 13, 0.05);
}

.notif-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px 10px;
}

.notif-title {
  margin-right: auto;
  color: var(--color-text-highlight);
  font-size: 1.5rem;
  font-weight: 800;
}

.notif-actions {
  display: flex;
  gap: 14px;
  font-size: 1.2rem;
}

.notif-actions .dash-link:disabled {
  color: var(--color-text-subdue);
  text-decoration: none;
  cursor: default;
  opacity: 0.6;
}

.notif-filters {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 16px 10px;
  border-bottom: 1px solid var(--color-separator);
}

.notif-filters .dash-segmented {
  flex: 1;
  min-width: 0;
}

.notif-count {
  margin-left: 2px;
  color: var(--color-positive);
  font-variant-numeric: tabular-nums;
}

.notif-unread-toggle {
  flex-shrink: 0;
  padding: 5px 10px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 999px;
  background: none;
  color: var(--color-text-subdue);
  font: inherit;
  font-size: 1.2rem;
  font-weight: 700;
  cursor: pointer;
}

.notif-unread-toggle[aria-pressed='true'] {
  border-color: var(--color-positive);
  color: var(--color-text-highlight);
}

.notif-list {
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: 6px;
  overflow-y: auto;
  list-style: none;
  overscroll-behavior: contain;
}

.notif-item {
  position: relative;
  display: flex;
  align-items: flex-start;
  border-radius: 8px;
}

.notif-link {
  display: flex;
  flex: 1;
  gap: 12px;
  min-width: 0;
  padding: 10px 36px 10px 10px;
  border-radius: 8px;
  color: inherit;
  text-decoration: none;
}

.notif-item.is-unread .notif-link {
  background: color-mix(in srgb, var(--color-positive) 7%, transparent);
}

/* An unread notification has a dot at its left edge. */
.notif-item.is-unread::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 2px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-positive);
  transform: translateY(-50%);
}

/* The avatar keeps its own size, so the kind's badge sits on its corner, not at the foot of a tall row. */
.notif-who {
  position: relative;
  display: grid;
  flex-shrink: 0;
  align-self: flex-start;
}

.notif-kind {
  position: absolute;
  right: -5px;
  bottom: -4px;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--color-popover-background);
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.notif-kind svg {
  width: 1.1rem;
  height: 1.1rem;
}

.notif-kind.is-good {
  background: var(--color-positive);
  color: #000;
}

.notif-kind.is-bad {
  background: var(--color-negative);
  color: #fff;
}

.notif-kind.is-merged {
  background: #a371f7;
  color: #fff;
}

.notif-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: 1.3rem;
}

.notif-line {
  color: var(--color-text-subdue);
}

.notif-line b {
  color: var(--color-text-highlight);
  font-weight: 700;
}

.notif-pr {
  overflow: hidden;
  color: var(--color-text-highlight);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.notif-snippet {
  display: -webkit-box;
  overflow: hidden;
  color: var(--color-text-subdue);
  font-size: 1.2rem;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.notif-when {
  color: var(--color-text-subdue);
  font-size: 1.15rem;
}

.notif-dismiss {
  position: absolute;
  top: 8px;
  right: 6px;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--color-text-subdue);
  cursor: pointer;
}

.notif-dismiss svg {
  width: 1.4rem;
  height: 1.4rem;
}

.notif-empty {
  padding: 24px 16px;
  text-align: center;
}

.notif-failure {
  padding: 8px 10px;
  color: var(--color-negative);
}

@media (hover: hover) and (pointer: fine) {
  .notif-link:hover {
    background: var(--color-widget-background-highlight);
  }

  .notif-item:not(:hover, :focus-within) .notif-dismiss {
    opacity: 0;
  }

  .notif-dismiss:hover {
    background: var(--color-widget-background-highlight);
    color: var(--color-text-highlight);
  }
}

/* A phone opens the panel across the window under the header, not hanging off the bell. */
@media (max-width: 600px) {
  .notif-panel {
    position: fixed;
    top: 64px;
    right: 12px;
    left: 12px;
    width: auto;
    max-height: calc(100vh - 80px);
    max-height: calc(100dvh - 80px);
  }
}

[data-mount='user-menu'] {
  position: relative;
  min-width: 3.2rem;
  min-height: 3.2rem;
}

[data-mount='user-menu']:empty {
  border-radius: 50%;
  background: var(--color-widget-background-highlight);
}

.user-button {
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  transition: transform 160ms var(--ease-out), box-shadow 150ms ease;
}

.user-button .sp-avatar {
  width: 3.2rem;
  height: 3.2rem;
}

.user-button:active {
  transform: scale(0.95);
}

.user-button:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 2px;
}

/* The menu grows from the avatar it belongs to. */
.user-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 240px;
  padding: 6px;
  border: 1px solid var(--color-popover-border);
  border-radius: 12px;
  background: var(--color-popover-background);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
  transform-origin: top right;
  animation: dash-menu-in 150ms var(--ease-out);
}

/* The light theme's header is navy, but its menu opens on the page, so it takes the page's ink. */
:root[data-app-theme='light'] .header .user-menu {
  --color-text-highlight: #090a0d;
  --color-text-subdue: rgba(9, 10, 13, 0.62);
  --color-widget-background-highlight: rgba(9, 10, 13, 0.05);
}

.user-menu-head {
  display: flex;
  flex-direction: column;
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--color-separator);
}

.user-menu-name {
  color: var(--color-text-highlight);
  font-weight: 800;
}

.user-menu-email,
.user-menu-row {
  color: var(--color-text-subdue);
  font-size: 1.2rem;
}

.user-menu-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
}

.user-menu-row svg {
  width: 1.4rem;
  height: 1.4rem;
}

.user-menu-item {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--color-text-highlight);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .header-icon:hover {
    color: var(--color-text-highlight);
  }

  .user-menu-item:hover {
    background: var(--color-widget-background-highlight);
  }
}

@keyframes dash-menu-in {
  from {
    opacity: 0;
    transform: scale(0.96) translateY(-4px);
  }
}

/* Callout: link GitHub */

.dash-callout {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 18px;
  border: 1px solid color-mix(in srgb, var(--color-positive) 35%, transparent);
  border-radius: var(--dash-radius);
  background: color-mix(in srgb, var(--color-positive) 8%, var(--color-widget-background));
  animation: dash-rise 420ms var(--ease-out) backwards;
}

.dash-callout-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dash-callout-title {
  color: var(--color-text-highlight);
  font-size: 1.5rem;
  font-weight: 800;
}

.github-form {
  display: flex;
  gap: 8px;
}

.dash-slot:has(> :empty) {
  display: none;
}

/* Settings */

.page-column.settings-column {
  max-width: 760px;
  margin: 0 auto;
}

.settings-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.settings-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.settings-title {
  color: var(--color-text-highlight);
  font-size: 1.5rem;
  font-weight: 800;
}


.settings-sub {
  color: var(--color-text-subdue);
  font-size: 1.25rem;
}

.settings-row + .settings-sub,
.settings-themes + .settings-sub {
  margin-top: 12px;
}

.settings-avatar {
  width: 4.4rem;
  height: 4.4rem;
}

.settings-icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 4.4rem;
  height: 4.4rem;
  border-radius: 50%;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.settings-icon svg {
  width: 2rem;
  height: 2rem;
}

.settings-themes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.settings-theme {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 2px solid var(--color-widget-content-border);
  border-radius: 12px;
  background: none;
  color: var(--color-text-subdue);
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease, transform 160ms var(--ease-out);
}

.settings-theme[aria-checked='true'] {
  border-color: var(--color-positive);
  color: var(--color-text-highlight);
}

.settings-theme:active {
  transform: scale(0.98);
}

.settings-theme-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
}

.settings-theme-label svg {
  width: 1.5rem;
  height: 1.5rem;
}

.settings-theme-preview {
  display: flex;
  gap: 6px;
  height: 64px;
  padding: 8px;
  border-radius: 8px;
}

.settings-theme-preview i {
  flex: 1;
  border-radius: 5px;
}

.settings-theme-preview.is-dark { background: #15161b; }
.settings-theme-preview.is-dark i { background: #1d1e24; }
.settings-theme-preview.is-light { background: #f4f4f5; }
.settings-theme-preview.is-light i { background: #ffffff; box-shadow: 0 1px 2px rgba(9, 10, 13, 0.08); }
/* System stacks the dark preview on the light one and cuts it on the diagonal. */
.settings-theme-split {
  position: relative;
  display: block;
  height: 64px;
}

.settings-theme-split .settings-theme-preview {
  position: absolute;
  inset: 0;
}

.settings-theme-split .is-dark {
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
}

/* Room people and chat */

.room-people {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.room-people li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.room-person-name {
  color: var(--color-text-highlight);
  font-weight: 700;
}

.is-entering .room-people li {
  animation: dash-rise 340ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 30ms);
}

.chat-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 160px;
  max-height: max(240px, calc(100vh - 470px));
  margin: 0 -4px;
  padding: 0 4px;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

.chat-list li {
  position: relative;
  display: flex;
  gap: 8px;
}

.chat-body {
  min-width: 0;
}

.chat-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.chat-name {
  color: var(--color-text-highlight);
  font-size: 1.25rem;
  font-weight: 800;
}

.chat-time {
  color: var(--color-text-subdue);
  font-size: 1.1rem;
}

.chat-text {
  color: var(--color-text-paragraph);
  overflow-wrap: anywhere;
}

.chat-delete {
  position: absolute;
  top: 0;
  right: 0;
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-subdue);
  opacity: 0;
  cursor: pointer;
  transition: opacity 150ms ease;
}

.chat-delete svg {
  width: 1.3rem;
  height: 1.3rem;
}

.chat-list li:focus-within .chat-delete {
  opacity: 1;
}

@media (hover: hover) and (pointer: fine) {
  .chat-list li:hover .chat-delete {
    opacity: 1;
  }
}

@media (hover: none) {
  .chat-delete {
    opacity: 1;
  }
}

.chat-form {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.chat-form input {
  flex: 1;
  min-width: 0;
  height: 3.4rem;
  padding: 0 12px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 8px;
  outline: none;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.chat-form input:focus {
  border-color: var(--color-positive);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-positive) 25%, transparent);
}

/* As with the search fields, Safari on an iPhone zooms into a field under 16px. */
@media (max-width: 600px) {
  .chat-form input {
    font-size: 16px;
  }
}

/* Team map: the world outline, with a pin per city placed by latitude and longitude. */

.team-map {
  position: relative;
  aspect-ratio: 360 / 142;
}

/* Zoomed in, the world stays inside the map, and a drag moves it. The pins layer is not clipped, so a pin's details
   can open past the edge; zoomable() hides the pins that move out of view. */
.team-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.team-map.is-zoomed {
  cursor: grab;
}

.team-map.is-dragging {
  cursor: grabbing;
  user-select: none;
}

.team-layer {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
}

.team-layer.is-easing,
.team-layer.is-easing .team-pin {
  transition: transform 220ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .team-layer.is-easing,
  .team-layer.is-easing .team-pin {
    transition: none;
  }
}

.team-map-reset {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 7;
}

.team-world,
.team-world svg {
  display: block;
  width: 100%;
  height: 100%;
}

.team-land {
  fill: var(--color-widget-background-highlight);
  stroke: var(--color-separator);
  stroke-width: 0.15;
}

.team-pins {
  position: absolute;
  inset: 0;
}

.team-pin {
  position: absolute;
  display: flex;
  border-radius: 999px;
  transform: translate(-50%, -50%) scale(calc(1 / var(--zoom, 1)));
  cursor: default;
}

.team-pin.is-me {
  z-index: 2;
}

.team-pin:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 2px;
}

/* People in one city: up to three faces, overlapping, then +N. Each face has a ring in the card's color, which keeps
   it apart from the next. The first face, you when you are there, sits on top and shows in full, each later one
   tucks under it, and +N closes the row on top. Only your own face has the green ring. */
/* The shadow sits on the faces, not the pin, so the card inside the pin keeps only its own. */
.team-pin-faces {
  display: flex;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));
}

.team-pin-faces > * {
  position: relative;
  box-shadow: 0 0 0 2px var(--color-widget-background);
}

.team-pin-faces > * + * {
  margin-left: -6px;
}

.team-pin-faces > :nth-child(1) { z-index: 3; }
.team-pin-faces > :nth-child(2) { z-index: 2; }
.team-pin-faces > :nth-child(3) { z-index: 1; }
.team-pin-faces > .team-pin-more { z-index: 4; }

.team-pin-faces > .is-me {
  box-shadow: 0 0 0 2px var(--color-positive);
}

.team-pin-more {
  display: inline-grid;
  place-items: center;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font-size: 1rem;
  font-weight: 800;
}

/* The city, its time and its people, above the pin. zoomable() opens it below a pin near the top, and moves it
   sideways (--card-shift) to keep it inside the card. */
.team-pin-card {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 180px;
  max-width: 260px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--dash-tooltip, #34363e);
  color: #f4f5f7;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transform: translate(calc(-50% + var(--card-shift, 0px)), 3px) scale(0.97);
  transform-origin: bottom center;
  transition: opacity 125ms ease-out, transform 125ms var(--ease-out);
}

.team-pin-head {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.team-pin-place {
  font-size: 1.3rem;
  font-weight: 800;
  white-space: nowrap;
}

.team-pin-when {
  display: flex;
  gap: 6px;
  color: rgba(244, 245, 247, 0.65);
  font-size: 1.15rem;
  white-space: nowrap;
}

.team-pin-time {
  color: #f4f5f7;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.team-pin-people {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 8px;
  border-top: 1px solid rgba(244, 245, 247, 0.12);
}

.team-pin-person {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 1.2rem;
  font-weight: 600;
}

.team-pin-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.team-pin-person .dash-tag {
  flex-shrink: 0;
}

.team-pin-others {
  color: rgba(244, 245, 247, 0.65);
  font-size: 1.15rem;
}

/* A tap focuses a pin, so its details open on a phone too. */
.team-pin:hover,
.team-pin:focus {
  z-index: 6;
}

.team-pin.tip-below .team-pin-card {
  top: calc(100% + 8px);
  bottom: auto;
  transform: translate(calc(-50% + var(--card-shift, 0px)), -3px) scale(0.97);
  transform-origin: top center;
}

.team-pin:hover .team-pin-card,
.team-pin:focus .team-pin-card {
  opacity: 1;
  transform: translate(calc(-50% + var(--card-shift, 0px)), 0) scale(1);
}

/* Below desktop the map is short, too short for a city's people above or below a pin inside the widget. The page
   scrolls as a whole there, with no column to clip, so the map's widget lets a pin's details out above and below it.
   It still clips its sides: closed details are laid out too, and one past the window's edge would let the page
   scroll sideways. placeCard() keeps open ones within the widget's sides. */
@media (max-width: 1190px) {
  .widget:has([data-mount='team-map']) {
    overflow-x: clip;
    overflow-y: visible;
  }
}

.team-pin-note {
  margin: 12px 0 0;
}

.team-people li {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Clock */

.clock-main {
  display: flex;
  flex-direction: column;
}

.clock-time {
  color: var(--color-text-highlight);
  font-size: 3.2rem;
  font-weight: 800;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* The seconds sit smaller and quieter beside the hours and minutes, the way a watch shows them. */
.clock-seconds {
  margin-left: 1px;
  color: var(--color-text-subdue);
  font-size: 2rem;
  font-weight: 700;
}

.clock-date {
  color: var(--color-text-subdue);
}

.clock-zone {
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--color-separator);
  color: var(--color-text-subdue);
  font-variant-numeric: tabular-nums;
}

/* Listeners see the song and its progress; only admins get the controls. */
.sp-player.is-listener .sp-controls {
  display: none;
}

/* Spotify tab modes: My Spotify or the company room */

:root[data-spotify-mode='me'] .mode-room,
:root[data-spotify-mode='room'] .mode-me {
  display: none;
}

.page-toolbar {
  margin-bottom: var(--widget-gap);
}

.spotify-mode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.spotify-join {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

.dash-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
  font-size: 1.25rem;
}

.dash-status.is-good {
  background: color-mix(in srgb, var(--color-positive) 14%, transparent);
  color: var(--color-positive);
}

.dash-status.is-warn {
  background: color-mix(in srgb, var(--dash-warning) 14%, transparent);
  color: var(--dash-warning);
}

.dash-status.is-bad {
  background: color-mix(in srgb, var(--color-negative) 12%, transparent);
  color: var(--color-negative);
}

.settings-notice {
  margin-bottom: 12px;
  text-align: left;
}

/* Tour: ported from api-core's app-tour.vue. Sizes converted from its 16px rem to this page's 10px rem. */

:root[data-app-theme='dark'] {
  --dash-shadow-menu: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
}

:root[data-app-theme='light'] {
  --dash-shadow-menu: 0 8px 24px rgba(9, 10, 13, 0.08), 0 2px 6px rgba(9, 10, 13, 0.06);
}

.app-tour {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
}

.app-tour__ring {
  position: fixed;
  top: var(--ring-top);
  left: var(--ring-left);
  width: var(--ring-width);
  height: var(--ring-height);
  border-radius: var(--ring-radius);
  box-shadow:
    0 0 0 2px var(--color-positive),
    0 0 0 6px color-mix(in srgb, var(--color-positive) 16%, transparent);
  pointer-events: none;
}

.app-tour__card {
  position: fixed;
  top: var(--tour-top);
  left: var(--tour-left);
  width: 32rem;
  max-width: calc(100vw - 32px);
  border: 1px solid var(--color-popover-border);
  border-radius: 12px;
  background: var(--color-popover-background);
  box-shadow: var(--dash-shadow-menu);
  color: var(--color-text-paragraph);
  font-size: 1.3rem;
  font-weight: 500;
}

.app-tour__card.has-arrow-top { transform-origin: var(--tour-arrow) top; }
.app-tour__card.has-arrow-bottom { transform-origin: var(--tour-arrow) bottom; }
.app-tour__card.has-arrow-left { transform-origin: left var(--tour-arrow); }
.app-tour__card.has-arrow-right { transform-origin: right var(--tour-arrow); }

/* A square turned on its corner, half outside the card, with the card's surface and hairline on its outer sides */
.app-tour__arrow {
  position: absolute;
  width: 12px;
  height: 12px;
  border: 1px solid var(--color-popover-border);
  background: var(--color-popover-background);
  transform: rotate(45deg);
}

.has-arrow-top .app-tour__arrow {
  top: -7px;
  left: calc(var(--tour-arrow) - 6px);
  border-right: 0;
  border-bottom: 0;
  border-top-left-radius: 2px;
}

.has-arrow-bottom .app-tour__arrow {
  bottom: -7px;
  left: calc(var(--tour-arrow) - 6px);
  border-top: 0;
  border-left: 0;
  border-bottom-right-radius: 2px;
}

.has-arrow-left .app-tour__arrow {
  top: calc(var(--tour-arrow) - 6px);
  left: -7px;
  border-top: 0;
  border-right: 0;
  border-bottom-left-radius: 2px;
}

.has-arrow-right .app-tour__arrow {
  top: calc(var(--tour-arrow) - 6px);
  right: -7px;
  border-bottom: 0;
  border-left: 0;
  border-top-right-radius: 2px;
}

.app-tour__body,
.app-tour__footer {
  line-height: 1.45;
}

.app-tour__body {
  padding: 16px 16px 14px;
}

.app-tour__title {
  margin: 0;
  color: var(--color-text-highlight);
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.25;
}

.app-tour__text {
  margin: 6px 0 0;
}

.app-tour__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--color-popover-border);
}

.app-tour__count {
  margin-right: auto;
  color: var(--color-text-subdue);
  font-variant-numeric: tabular-nums;
}

.app-tour__button {
  height: 3.2rem;
  padding: 0 12px;
  border: 1px solid var(--color-widget-content-border);
  border-radius: 8px;
  background: var(--color-widget-background);
  color: var(--color-text-highlight);
  font: inherit;
  font-size: 1.3rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 150ms ease, transform 160ms var(--ease-out);
}

.app-tour__button.is-primary {
  border-color: transparent;
  background: var(--dash-accent-fill);
  color: var(--dash-accent-ink);
}

.app-tour__button:active {
  transform: scale(0.97);
}

.app-tour__button:focus-visible {
  outline: 2px solid var(--color-positive);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .app-tour__button:not(.is-primary):hover {
    background: var(--color-widget-background-highlight);
  }
}

/* First sign-in setup: the tour card's surface, footer and buttons, centered over a dimmed page */

.wizard-backdrop {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(9, 10, 13, 0.55);
}

.wizard {
  width: min(100%, 44rem);
  overflow: hidden;
  border: 1px solid var(--color-popover-border);
  border-radius: 12px;
  background: var(--color-popover-background);
  box-shadow: var(--dash-shadow-menu);
  color: var(--color-text-paragraph);
  font-size: 1.3rem;
  font-weight: 500;
}

.wizard-progress {
  display: flex;
  gap: 6px;
  padding: 16px 20px 0;
}

.wizard-progress i {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--color-widget-background-highlight);
  transition: background-color 220ms ease;
}

.wizard-progress i.is-done,
.wizard-progress i.is-current {
  background: var(--color-positive);
}

.wizard-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 22px 20px 20px;
  line-height: 1.5;
}

.wizard-icon {
  display: grid;
  place-items: center;
  width: 4.4rem;
  height: 4.4rem;
  margin-bottom: 6px;
  border-radius: 12px;
  background: var(--color-widget-background-highlight);
  color: var(--color-text-highlight);
}

.wizard-icon svg {
  width: 2.2rem;
  height: 2.2rem;
}

.wizard-icon.is-logo {
  overflow: hidden;
  border-radius: 50%;
  background: none;
}

.wizard-icon.is-logo img {
  width: 100%;
  height: 100%;
  transform: scale(1.16);
}

.wizard-icon.is-done {
  background: color-mix(in srgb, var(--color-positive) 16%, transparent);
  color: var(--color-positive);
}

.wizard-title {
  margin: 0;
  color: var(--color-text-highlight);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.25;
}

.wizard-text {
  margin: 0;
}

.wizard-action {
  margin-top: 8px;
}

.wizard .dash-status svg {
  width: 1.4rem;
  height: 1.4rem;
}

.wizard .app-tour__footer {
  padding: 14px 20px;
}

@media (prefers-reduced-motion: reduce) {
  .app-tour__ring {
    transition: none;
  }
}

/* Narrow screens stack the columns (base.css): the main column leads, and side cards share rows. */
@media (max-width: 1190px) {
  .page-column-full {
    order: -1;
  }

  .page-column-small {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
    align-items: start;
  }

  /* The side player turns sideways, so its cover stays small. */
  .sp-player--mini {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    align-items: center;
    gap: 16px;
  }
}

@media (max-width: 600px) {
  /* Settings has the same theme choice, and a phone header has no room for it. */
  .header .app-theme-switch {
    display: none;
  }

  .header {
    gap: 1.2rem;
    padding: 0 12px;
  }

  .desktop-navigation {
    gap: 1.6rem;
  }

  /* The room search takes a row of its own, with the skip button under it. */
  .sp-room-head {
    flex-wrap: wrap;
  }

  .sp-room-head .dash-search {
    flex-basis: 100%;
  }

  /* The Listen along note wraps beside its button instead of on a line of its own. */
  .spotify-join {
    flex-wrap: nowrap;
    justify-content: space-between;
    width: 100%;
  }

  .spotify-join .settings-sub {
    flex: 1;
    min-width: 0;
  }
}
