:root {
  /* Warm charcoal rather than black, warm off-white rather than white, and a
     pale yellow that carries both fills and text. Every pair was measured:
     body and secondary text clear 4.5 on every surface they sit on, and each
     status colour clears it on its own tint as well as on a card. */

  /* Surfaces, page outward to nested. Warm charcoal, not black: the greys carry
     a trace of brown so the yellow sits in the same family rather than against
     it. */
  --bg: #1f1e1d;
  --panel: #262624;
  --panel-2: #30302e;
  /* Where something is typed or chosen. Recessed below the card it sits on, so
     a control reads as a place to put something. */
  --field: #1a1a19;
  /* A chip or a quiet block: lifted a shade off the card behind it. */
  --raised: #353532;

  --text: #faf9f5;
  --muted: #b0aea6;
  --border: #3d3d3a;
  --border-strong: #4d4d49;

  /* Chosen for chroma rather than brightness. A pale yellow is lighter but
     carries less actual colour, which is what makes a wash look faded against
     charcoal; this one is well past that while staying yellow rather than
     turning to mustard.

     On this background it is light enough to be read as well as filled with, so
     --accent-ink is the same yellow. The two names are kept apart because they
     answer different questions, and only one of them would change if the
     surfaces ever went light. */
  --accent: #f5d547;
  --accent-hover: #f9e071;
  --accent-ink: #f5d547;
  /* Edges and focus rings: the accent held back so a border does not glare. */
  --accent-line: rgba(245, 213, 71, 0.42);
  --accent-soft: rgba(245, 213, 71, 0.11);
  /* Barely there: the tail of a fade, where a tint should die out rather than
     stop. */
  --accent-veil: rgba(245, 213, 71, 0.03);
  /* What goes on top of the accent fill. */
  --on-accent: #1f1e1d;

  --danger: #f0897c;
  --danger-line: rgba(240, 137, 124, 0.5);
  --danger-soft: rgba(240, 137, 124, 0.14);

  --positive: #7fd6a0;
  --positive-line: rgba(127, 214, 160, 0.5);
  --positive-soft: rgba(127, 214, 160, 0.12);

  /* One shadow, for the one thing that genuinely floats above the page. A page
     section is a region and does not need lifting off its own background; on a
     dark surface the lift would barely register anyway. */
  --shadow-raised: 0 22px 60px rgba(0, 0, 0, 0.5);

  /* What the page is put behind while something is open over it. Dark rather
     than a tint, so the panel in front reads as the only live thing. */
  --scrim: rgba(0, 0, 0, 0.55);

  /* The interface face is whatever the platform already draws its own chrome
     in, which is what makes it feel native rather than styled. Arial was a
     1990s lowest common denominator; this reaches for the modern default first
     and only falls back that far at the end. */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  /* For a token or an address: anything read character by character. */
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  /* Four radii, and only four. A pill is a label: a chip, a badge, a count,
     something you read. Anything you click or type into is squared off, so the
     shape says which is which without being told. */
  --radius-control: 6px;
  --radius-card: 10px;
  /* A region, softened at the corner rather than rounded into a tile. */
  --radius-panel: 12px;
  --radius-pill: 999px;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

body {
  margin: 0;
  font-family: var(--font-sans);
  /* Slightly open, because a dense screen of small text needs the air more than
     it needs the extra line. */
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  background:
    radial-gradient(circle at 12% -8%, var(--accent-soft), transparent 34rem),
    linear-gradient(180deg, var(--panel-2) 0, var(--bg) 34rem);
  color: var(--text);
  min-height: 100vh;
}

.site-footer {
  padding: 0 24px 28px;
  text-align: center;
}

.site-footer a {
  color: var(--muted);
  font-size: 0.9rem;
}

.site-footer a:hover,
.site-footer a:focus-visible {
  color: var(--accent-ink);
}

/* The network icons, one row wherever they stand: centred above the terms line
   on the catalog, seated with the welcome copy on the dashboard. The
   colours are stated on the row itself rather than inherited, so it reads the
   same inside a card as it does in the footer. */
.social-links {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
  margin-bottom: 12px;
}

.social-links a {
  display: inline-flex;
  color: var(--accent-ink);
}

.social-links a:hover,
.social-links a:focus-visible {
  color: var(--accent-hover);
}

.social-links a:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

.social-links svg {
  width: 22px;
  height: 22px;
}

/* The dashboard's copy of the row lives inside the welcome card, so it leaves
   the centre line to the footer and lines up with the text instead. */
.social-links-start {
  justify-content: flex-start;
  margin: 1.1rem 0 0;
}

/* A page of prose rather than a page of controls, so it is held to a measure a
   person can read a paragraph across instead of the full width of the screen.
   The lit wash is restated with a fixed reach because the shared rule sizes it
   to the panel, and a document this tall stretches it to nothing: held to the
   span a small card gives it, and laid on twice, the corner glows here the way
   it glows on the sign-up card instead of thinning out over the whole page. */
.terms-page-card {
  max-width: 780px;
  margin: 32px auto;
  background:
    radial-gradient(560px at top right, var(--accent-soft), transparent 70%),
    radial-gradient(920px at top right, var(--accent-soft), transparent),
    var(--panel-2);
}

/* The document itself, shaped the way the popup shapes it: pasted line breaks
   are the only form a plain-text document has, and a legal address with no
   spaces in it wraps rather than pushing the card wider than the screen. The
   page scrolls rather than the card, because here there is nothing standing
   over the words that has to stay in view. */
.terms-page-text {
  color: var(--muted);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The band across the top of every page reads the same way: what the page is
   called on the left, the logo in the middle, the buttons on the right. The
   two outer columns are given equal share of whatever the logo leaves, so the
   logo sits on the centre line of the page rather than of the gap. */
.site-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 1rem;
  padding: 2rem;
}

/* The title block. It may shrink, so a long title wraps within its own column
   instead of pushing the logo off centre. */
.site-header > div:first-child {
  grid-column: 1;
  justify-self: start;
  min-width: 0;
  overflow-wrap: break-word;
}

.header-logo {
  grid-column: 2;
  display: block;
  width: auto;
  max-width: min(420px, 60vw);
  max-height: 84px;
  margin: 0 auto;
  object-fit: contain;
}

/* The public table's front-door size, the one the logo had when it stood in
   for the title there. */
.header-logo-large {
  max-width: min(520px, calc(100vw - 4rem));
  max-height: 150px;
}

/* The anchor the branding script wraps the logo in. It adds the click and
   nothing else: the header is a grid and the image is the placed grid item, so
   the anchor stays out of layout entirely. */
.header-logo-link { display: contents; }

/* The bar an administrator wears while reading the site as one of its accounts.
   branding.js puts it in front of everything else on the page, so it takes the
   top of the page above the header band and stays there while the page scrolls:
   the way back has to be one click away wherever the reading got to. Sticky
   rather than fixed, so it holds its own room and nothing below it needs an
   offset. It stops short of the panels that open over the page, which have to
   stay in front of it. */
.viewing-banner {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  background: var(--accent-soft);
  border-bottom: 1px solid var(--accent-line);
  color: var(--text);
  font-size: 0.9rem;
}

.site-header > .nav-actions,
.site-header > .small-actions {
  grid-column: 3;
  justify-self: end;
  justify-content: flex-end;
}

.public-account-actions {
  grid-column: 3;
  justify-self: end;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* The way around the site once an account is signed in. The header band holds
   the one page the account came from and the way out; everything else is here,
   so the band stays two controls wide on every page and at every width.
   menu.js builds the button and the panel, so nothing below is written into a
   page. */

/* Sized to sit level with the compact buttons beside it, and drawn rather than
   loaded: three bars in the accent ink the rest of the header asks with. */
.menu-toggle {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: transparent;
  padding: 0.55rem 0.7rem;
  color: var(--accent-ink);
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease;
}

.menu-toggle span {
  display: block;
  width: 1.1rem;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}

.menu-toggle:hover {
  background: var(--raised);
  border-color: var(--muted);
}

.menu-toggle:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

/* Something is waiting in the inbox: the unread count in a yellow circle
   riding the button's top corner, so the number is read without opening the
   panel. Named through the button because the bar rule above styles every
   span in it, and the circle has to win back its own width, height, and
   fill. */
.menu-toggle .menu-toggle-count {
  position: absolute;
  top: -0.8rem;
  right: -0.8rem;
  width: auto;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 0.3rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.75rem;
  text-align: center;
  box-shadow: 0 0 0 3px var(--bg);
}

/* The panel comes in from the right edge and covers whatever is under it, so
   it sits above every other layer on these pages. */
.site-menu {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 200;
  width: min(320px, 85vw);
  height: 100vh;
  height: 100dvh;
  border-left: 1px solid var(--accent-line);
  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 42%),
    var(--panel-2);
  padding: 1rem;
  overflow-y: auto;
  transform: translateX(0);
  transition: transform 200ms ease;
}

/* The closed state has to keep a box for the panel to slide out of, so the
   display the hidden attribute would set is put back and the panel is taken
   out of reach with visibility instead. Visibility is held until the slide has
   finished, which is what keeps the panel out of the tab order while it is
   away without cutting the movement short. */
.site-menu[hidden] {
  display: block;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 200ms ease, visibility 0s linear 200ms;
}

.site-menu-close {
  display: block;
  margin: 0 0 0.5rem auto;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: transparent;
  padding: 0.2rem 0.6rem;
  color: var(--text);
  font-size: 1.2rem;
  line-height: 1.2;
  cursor: pointer;
}

.site-menu-close:hover {
  background: var(--raised);
  border-color: var(--muted);
}

.site-menu-close:focus-visible,
.site-menu a:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

/* Rows rather than buttons: this is a list of places, and a column of gold
   fills would be a column of things shouting at once. */
.site-menu a {
  display: block;
  border-radius: var(--radius-control);
  padding: 0.8rem 1rem;
  color: inherit;
  font-size: 1.05rem;
  text-decoration: none;
}

.site-menu a:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* The page you are already on, marked the way the header row marks it. Named
   through the panel so it outweighs the plain row rule above rather than
   losing to it. */
.site-menu a.site-menu-current {
  background: var(--accent-soft);
  color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent-ink);
}

.site-menu-badge {
  float: right;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.55rem;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 0.78rem;
  font-weight: 700;
}

/* Dims the page behind the panel, and catches the click that closes it. */
.site-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 199;
  background: var(--scrim);
}

@media (prefers-reduced-motion: reduce) {
  .site-menu,
  .site-menu[hidden] { transition: none; }
}

.public-user-label {
  max-width: 16rem;
  overflow: hidden;
  color: var(--muted);
  font-size: 0.9rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sits beside the address, where two runs of plain grey text would read as one
   phrase. The pill says the second is a different kind of fact. */
.account-level-badge {
  display: inline-block;
  margin-left: 0.4rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  padding: 0.16rem 0.45rem;
  color: var(--accent-ink);
  font-size: 0.72rem;
  background: var(--accent-soft);
}

/* Headings sit closer than body text and carry a little less weight than the
   old bold, which at these sizes read as shouting. */
h1, h2, h3 {
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.011em;
}

h1, h2 { margin: 0 0 0.5rem; }
p { color: var(--muted); }
a { color: var(--accent-ink); }
a:hover { color: var(--accent-hover); }

/* A section is a region of the page, not an object resting on top of one. The
   page and the panel sit a shade apart, which is enough to group what is inside
   without an edge highlight or a shadow. Elevation is kept for the few things
   that genuinely float, so that a shadow still says something when it appears. */
.card {
  width: min(900px, calc(100% - 2rem));
  max-width: calc(100vw - 2rem);
  min-width: 0;
  margin: 0 auto 1.5rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  padding: 1.5rem;
}

.wide-card { width: min(1550px, calc(100% - 2rem)); }

.center-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1rem;
}

.login-card { max-width: 430px; }

label {
  display: block;
  margin-top: 1rem;
  margin-bottom: 0.35rem;
  color: var(--muted);
  font-size: 0.92rem;
}

input, select {
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  /* Recessed rather than level with the card, so a field reads as somewhere to
     put something before it is read at all. */
  background: var(--field);
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
}

input::placeholder { color: var(--muted); }

input:hover:not(:disabled),
select:hover:not(:disabled) { border-color: var(--border-strong); }

/* A ring rather than a thick outline: enough to find the focus, not enough to
   redraw the layout around it. */
input:focus,
select:focus {
  border-color: var(--accent-ink);
  outline: 2px solid var(--accent-line);
  outline-offset: 1px;
}

select[multiple] { min-height: 7.5rem; }
input[type="file"] { margin: 1rem 0; }
input[type="file"]::file-selector-button {
  margin-right: 0.75rem;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.55rem 0.8rem;
  font-weight: 700;
  cursor: pointer;
}

.button {
  display: inline-block;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.55rem 0.95rem;
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.3;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}

/* Colour shifts, nothing moves. A button that lifts under the cursor reads as a
   consumer app, and on a page this dense it makes the eye chase. */
.button:hover { background: var(--accent-hover); color: var(--on-accent); }

/* One accent per group of controls. A secondary action is neutral rather than
   a second gold button, so the eye lands on the one thing worth pressing. */
.button.ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--border-strong);
}

.button.ghost:hover {
  background: var(--raised);
  border-color: var(--muted);
  color: var(--text);
}

/* Named for what it does, so it is coloured for what it does. */
.button.danger {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger-line);
}

.button.danger:hover {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

.button:disabled {
  cursor: wait;
  opacity: 0.58;
}

.button:focus-visible,
.link-button:focus-visible,
.search-clear:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

.nav-actions,
.filter-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.message, .status {
  margin-top: 1rem;
  color: var(--muted);
}

.message.success { color: var(--accent-ink); }
.message.error { color: var(--danger); }
.status.success { color: var(--accent-ink); }
.status.error { color: var(--danger); }

.hidden { display: none !important; }

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip: rect(0 0 0 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.global-filter-grid {
  display: grid;
  grid-template-columns: minmax(240px, 2fr) minmax(150px, 0.75fr) minmax(240px, 1fr);
  gap: 1rem;
  align-items: start;
}

.filter-chip,
.muted-chip {
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.55rem;
  font-size: 0.78rem;
  color: var(--muted);
  background: var(--raised);
}

.active-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

.filter-chip strong { color: var(--text); }

.filter-chip-clear {
  display: inline-grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.15rem -0.25rem -0.15rem 0.2rem;
  border: 0;
  border-radius: 50%;
  padding: 0;
  background: transparent;
  color: var(--accent-ink);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.filter-chip-clear:hover {
  background: var(--accent);
  color: var(--on-accent);
}

.filter-chip-clear:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.table-wrap {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
}

#csvTable,
#adminDataTable {
  background: var(--panel);
  color: var(--text);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.dt-container { color: var(--text); }

.dt-container input,
.dt-container select {
  background: var(--panel-2);
  color: var(--text);
}

/* Too narrow for three columns, so the band becomes one and the three parts
   stack in reading order, each starting at the same left edge. */
@media (max-width: 900px) {
  .site-header {
    grid-template-columns: 1fr;
    justify-items: start;
  }

  .site-header > div:first-child,
  .header-logo,
  .site-header > .nav-actions,
  .site-header > .small-actions,
  .public-account-actions {
    grid-column: 1;
  }

  .header-logo { margin: 0; }

  .site-header > .nav-actions,
  .site-header > .small-actions,
  .public-account-actions {
    justify-self: start;
    justify-content: flex-start;
  }

  .global-filter-grid { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  .site-header { padding: 1rem; }
  .nav-actions { flex-wrap: wrap; }

  .header-logo {
    max-width: calc(100vw - 2rem);
    max-height: 64px;
  }

  .header-logo-large { max-height: 120px; }
}

.simple-grid {
  grid-template-columns: minmax(240px, 2fr) minmax(150px, 0.75fr);
}

.column-picker {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--raised);
}

.column-picker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-bottom: 0.75rem;
}

.column-picker h3 {
  margin: 0;
  font-size: 1rem;
}

.small-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* A button beside a taller control, such as a switch with its words, sits on
   the middle line rather than stretching to the control's height with its
   words left at the top. */
.small-actions > .button {
  align-self: center;
}

.link-button {
  border: 0;
  background: transparent;
  color: var(--accent-ink);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  padding: 0;
}

.link-button:hover {
  color: var(--accent-hover);
  text-decoration: underline;
  text-underline-offset: 0.2rem;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0.6rem;
  max-height: 16rem;
  overflow: auto;
  padding-right: 0.25rem;
}

.checkbox-field {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
}

.checkbox-field input {
  width: auto;
  margin: 0;
  accent-color: var(--accent-ink);
}

.checkbox-field:has(input:checked) {
  border-color: var(--accent-ink);
  background: var(--accent-soft);
}

.empty-filter-note {
  margin: 0.75rem 0 0;
  color: var(--muted);
}

.admin-grid-card {
  display: grid;
  grid-template-columns: minmax(260px, 0.8fr) minmax(260px, 1.2fr);
  gap: 1.5rem;
}

.import-history {
  display: grid;
  gap: 0.75rem;
  max-height: 34rem;
  overflow: auto;
  padding-right: 0.25rem;
}

.import-card {
  display: grid;
  gap: 0.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.85rem;
  background: var(--raised);
}

.import-card span,
.import-card small {
  color: var(--muted);
}

@media (max-width: 900px) {
  .admin-grid-card {
    grid-template-columns: 1fr;
  }
}

.admin-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  min-width: 0;
  gap: 1.5rem;
  margin-bottom: 2rem;
}

.admin-page > * {
  min-width: 0;
}

.branding-grid {
  display: grid;
  grid-template-columns: minmax(280px, 1fr) minmax(260px, 0.75fr);
  gap: 1.5rem;
  align-items: center;
}

/* Two logo spots stand in the one card, so the second is given air and a line
   above it and no reader has to guess which words belong to which picture. */
.branding-grid + .branding-grid {
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--accent-line);
}

.logo-upload-form {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) auto;
  gap: 0.6rem 1rem;
  align-items: center;
  margin-top: 1rem;
}

.logo-upload-form label {
  grid-column: 1 / -1;
  margin: 0;
}

.logo-upload-form input[type="file"] {
  margin: 0;
}

.logo-preview-shell {
  display: grid;
  min-height: 170px;
  place-items: center;
  padding: 1rem;
  border: 1px dashed var(--accent-line);
  border-radius: var(--radius-card);
  background:
    radial-gradient(circle at center, var(--accent-soft), transparent 70%),
    var(--panel-2);
}

.logo-preview-shell p {
  max-width: 32ch;
  margin: 0;
  text-align: center;
}

.admin-logo-preview {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 150px;
  object-fit: contain;
}

.admin-data-panel {
  min-width: 0;
  overflow: hidden;
  margin-top: 0;
}

.admin-data-panel .dt-container,
.admin-data-panel .dt-scroll {
  width: 100%;
  max-width: 100%;
  min-width: 0;
}

.admin-data-panel .dt-scroll-body {
  max-width: 100%;
  max-height: none;
  overflow-x: auto !important;
  overflow-y: hidden !important;
}

.docs-choice-toggle {
  display: inline-grid;
  place-items: center;
  margin: 0;
  cursor: pointer;
}

.docs-choice-checkbox {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--accent-ink);
  cursor: pointer;
}

.docs-choice-checkbox:disabled {
  cursor: wait;
  opacity: 0.58;
}

.admin-data-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}

.admin-search-row {
  margin: 1rem 0;
}

/* The table's own title, worn as a header to the search field under it: close
   to what it names, with the room kept above it instead. */
.admin-table-heading {
  margin: 1.5rem 0 0.35rem;
}

.admin-table-heading + .admin-search-row {
  margin-top: 0;
}

.private-note {
  margin-top: -0.25rem;
  margin-bottom: 0.85rem;
}

.private-column-field { border-color: var(--accent-line); }

.private-badge {
  margin-left: auto;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  padding: 0.16rem 0.45rem;
  color: var(--accent-ink);
  font-size: 0.72rem;
  background: var(--accent-soft);
}

.public-visibility-panel {
  margin-top: 0;
}

.visibility-actions {
  align-items: center;
  justify-content: flex-end;
}

.public-column-grid {
  margin-top: 1rem;
}

.hidden-public-field {
  opacity: 0.82;
}

.hidden-public-field:has(input:not(:checked)) {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

/* The lit panel: a yellow edge, and a wash of the accent falling away from the
   top right corner into the grey. The catalog's intro strip and search panel
   and the News card on the dashboard wear it by name, and any card can put it
   on as .lit-panel, the way the Pricing page's three sections do. Said once
   here rather than scattered through this sheet. None of them is a plain
   card: each is a thing the eye is meant to land on. */
.discovery-panel,
.announcement-card,
.catalog-intro,
.lit-panel {
  border: 1px solid var(--accent-line);
  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 42%),
    var(--panel-2);
}

/* The strip above the search panel that says what the catalog is. Kept short
   on purpose, so the table is still near the top of the page. */
.catalog-intro {
  margin: 0 0 1.5rem;
  padding: clamp(0.9rem, 2vw, 1.25rem) clamp(1rem, 2.5vw, 1.5rem);
  border-radius: var(--radius-panel);
}

.catalog-intro h1 {
  margin: 0;
  font-size: clamp(1.35rem, 2.8vw, 2rem);
}

.catalog-intro details {
  margin: 0.5rem 0 0;
}

/* A quiet control rather than a button: it opens a paragraph, so it is asked
   for in the accent ink the rest of the site uses for a link. Its width is the
   width of the words, so the whole strip is not one wide click target, and the
   focus ring it shares with every other control has a corner to follow. */
.catalog-intro summary {
  width: fit-content;
  border-radius: var(--radius-control);
  color: var(--accent-ink);
  font-weight: 600;
  cursor: pointer;
}

.catalog-intro summary:hover {
  color: var(--accent-hover);
}

.catalog-intro p {
  margin: 0.5rem 0 0;
  max-width: 78ch;
}

/* Public catalog search and quick filters */
.discovery-panel {
  margin: 0 0 1.5rem;
  padding: clamp(1rem, 2.5vw, 1.5rem);
  border-radius: var(--radius-panel);
}

.discovery-heading {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}

.discovery-heading h2 {
  font-size: clamp(1.35rem, 2.8vw, 2rem);
}

.discovery-heading p {
  margin: 0.35rem 0 0;
}

.eyebrow {
  margin: 0 0 0.35rem !important;
  color: var(--accent-ink) !important;
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The base button is already compact, so this is now only about not stretching
   inside a flex row, plus a shade less padding. */
.compact-button {
  flex: 0 0 auto;
  padding: 0.45rem 0.8rem;
  font-size: 0.88rem;
}

.main-search-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.search-shell {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.65rem;
  margin-top: 1.2rem;
  padding: 0.35rem 0.45rem 0.35rem 0.85rem;
  border: 2px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

.search-shell:focus-within {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-line);
}

.search-icon {
  color: var(--accent-ink);
  font-size: 1.65rem;
  line-height: 1;
  transform: rotate(-16deg);
}

.search-shell input {
  min-width: 0;
  padding: 0.8rem 0.15rem;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font-size: clamp(1rem, 2vw, 1.13rem);
}

.search-shell input::placeholder {
  color: var(--muted);
}

.search-clear {
  border: 0;
  border-radius: var(--radius-control);
  padding: 0.55rem 0.75rem;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 800;
  cursor: pointer;
}

.search-clear:hover {
  background: var(--accent-hover);
}

.search-help {
  margin: 0.55rem 0 0;
  font-size: 0.85rem;
}

kbd {
  display: inline-block;
  min-width: 1.5rem;
  padding: 0.1rem 0.35rem;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--radius-control);
  background: var(--panel-2);
  color: var(--text);
  font-family: inherit;
  font-size: 0.78rem;
  text-align: center;
}

/* Type, song type, one of the two genre controls, and the result count. The two
   genre controls swap places rather than sitting side by side, so this is four
   columns however it looks in the markup. */
.quick-filter-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(150px, 1fr)) minmax(200px, 0.9fr);
  gap: 0.9rem;
  align-items: end;
  margin-top: 1.15rem;
}

.quick-filter-field {
  margin: 0;
  font-weight: 700;
  color: var(--text);
}

.quick-filter-field span {
  display: block;
  margin-bottom: 0.4rem;
}

.quick-filter-field select,
.quick-filter-field input {
  min-height: 46px;
  border-color: var(--border-strong);
  background: var(--panel-2);
  font-size: 0.98rem;
  cursor: pointer;
}

.result-summary-wrap {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 46px;
  padding: 0.55rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--raised);
}

.result-summary-label {
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.result-summary {
  color: var(--text);
  font-size: 1rem;
  font-weight: 800;
}

.certified-filter-panel {
  margin: 0 0 1.5rem;
}

.certified-filter-control {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  margin: 0;
  padding: 1rem 1.25rem;
  /* The lit panel's edge and wash, said here rather than through the shared
     class because this control repaints its border as it is hovered and
     ticked, and the shared rule would sit under those states and lose. */
  border: 2px solid var(--accent-line);
  border-radius: var(--radius-control);
  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 42%),
    var(--panel-2);
  cursor: pointer;
  user-select: none;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.certified-filter-control:hover {
  border-color: var(--accent-ink);
}

.certified-filter-control:has(.certified-only-checkbox:checked) {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-line);
}

.certified-filter-control:focus-within {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

.certified-only-checkbox {
  flex: 0 0 auto;
  width: 1.65rem;
  height: 1.65rem;
  margin: 0;
  accent-color: var(--accent-ink);
  cursor: pointer;
}

.docs-choice-badge.certified-filter-badge {
  flex: 0 0 auto;
  width: auto;
  height: 3.6rem;
  margin: 0;
}

.auth-logo {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 5.5rem;
  margin: 0 auto 1.1rem;
  object-fit: contain;
}

.field-hint {
  display: block;
  margin: -0.4rem 0 0.6rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* A hint above a group of fields introduces them rather than following one, so
   it needs air above it, not the tuck under an input that .field-hint gives
   every other hint. */
.field-group-hint {
  margin: 1rem 0 0.35rem;
}

/* The A&R page's one warning stands where the intro used to and is the only
   thing said before the form, so it speaks a size up from a hint. */
.ar-review-note {
  font-size: 0.95rem;
}

/* The submit button on the auth pages follows whatever field comes last, and
   no field brings its own gap below, so the button carries one of its own. */
.auth-submit {
  margin-top: 1.25rem;
}

/* Why one field was refused, said under that field rather than in a list at the
   bottom, so the reason sits beside the thing to correct. */
.field-error { color: var(--danger); }

/* Submit a Song. Nine fields in one column is a page of scrolling for a form
   that fits on a screen, so they are set two across. */
/* The picture over the submission form, drawn the width of the card and shown
   whole at its own proportions, never cropped, so the art reads as it was made.
   It wears the card's own corner, because it is the top of the card rather than
   something resting on it. */
.submit-banner {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  margin: 0 0 1rem;
}

.submit-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 1rem;
  align-items: start;
}

.submit-field { min-width: 0; }

/* A title and a web address are the long ones, so they take the whole row
   rather than half of it. */
.submit-field.full { grid-column: 1 / -1; }

/* The label above carries the space between rows, so the hint below only needs
   to clear its own control. */
.submit-field .field-hint { margin: 0.35rem 0 0; }

/* The rating tick while the review is ticked: still there, plainly not on
   offer, with the reason said beside it in the accent. */
.submit-rating-field.submit-rating-included label,
.submit-rating-field.submit-rating-included input {
  opacity: 0.45;
  cursor: not-allowed;
}

.submit-rating-field .submit-rating-included-note { color: var(--accent-ink); }

/* A file control carries a margin of its own for the pages that stand one on
   its own. Here it is a field in a grid like any other, and its label already
   gives it the room. */
.submit-field input[type="file"] { margin: 0; }

/* The two fields on the form that are a choice rather than an answer. The box
   sits beside its own label instead of under it, the way a tick box reads, and
   the sentence under the pair explains what ticking it spends. The boost and the
   review and the rating are laid out by one rule because they are one shape:
   three lines of the same form, one under the other. */
.submit-boost-field,
.submit-review-field,
.submit-rating-field {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 0.1rem 0.55rem;
}

.submit-boost-field label,
.submit-review-field label,
.submit-rating-field label {
  order: 2;
  margin: 0;
  color: var(--accent-ink);
}

.submit-boost-field input[type="checkbox"],
.submit-review-field input[type="checkbox"],
.submit-rating-field input[type="checkbox"] {
  order: 1;
  width: 1rem;
  height: 1rem;
  margin: 0;
  accent-color: var(--accent);
}

.submit-boost-field .field-hint,
.submit-review-field .field-hint,
.submit-rating-field .field-hint {
  order: 3;
  grid-column: 1 / -1;
}

/* What stands in those slots for an artist who holds no boost and no review.
   The rows are the pack rows the plan pages sell in, so the only thing said here
   is the space around them: a block of rows needs the room a single line of form
   does not, and the list sits under one sentence rather than under a card
   heading, so the gap the plan pages leave over it would read as a break in the
   form. */
.submit-boost-offer,
.submit-review-offer { margin-top: 1rem; }

.submit-boost-offer .plan-upgrades,
.submit-review-offer .plan-upgrades { margin-top: 0.5rem; }

/* Which half of the form a field belongs to, said quietly: the fields are what
   is read, not the headings over them. */
.submit-section {
  grid-column: 1 / -1;
  margin: 1.5rem 0 0.2rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--border);
  color: var(--accent-ink);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.submit-form .submit-section:first-child { margin-top: 0.6rem; }

.submit-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

/* The page after a song lands. It holds one sentence and two ways onward, so it
   is set in the middle rather than ranged left across a wide card. */
.submit-success-card { text-align: center; }

.submit-success-card h2 { color: var(--positive); }

/* The song just sent, named back to the artist so they can see it went in as
   they typed it. */
.submit-success-song {
  color: var(--text);
  font-size: 1.05rem;
}

/* How long a review takes, said under the song that asked for one. Quieter
   than the song line, because it is a note on the news rather than the news. */
.submit-success-review {
  color: var(--muted);
  max-width: 36rem;
  margin: 0 auto;
}

.submit-success-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

@media (max-width: 640px) {
  .submit-form { grid-template-columns: 1fr; }
}

.auth-alt-action {
  margin: 0.9rem 0 0.4rem;
  color: var(--muted);
  font-size: 0.92rem;
}

.account-requests-list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.8rem;
}

.account-request {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.75rem 0.9rem;
  background: var(--panel-2);
}

.account-request-pending { border-left-color: var(--accent-ink); }
.account-request-approved { border-left-color: var(--positive); }
.account-request-rejected { border-left-color: var(--danger); }
/* Suspended wears the same warning edge a rejection does, because from the
   outside the two look alike: neither account can sign in. What tells them apart
   is the word on the row and the way back that only this one has. */
.account-request-suspended { border-left-color: var(--danger); }

/* The one control on a row that ends an account rather than changing it. It
   reads as a link like the review control beside it, and takes the danger ink so
   the row says which of the two is the irreversible one. */
.account-delete { color: var(--danger); }
.account-delete:hover { color: var(--danger); }

/* The line counting a week down on the account page. It is the one thing on that
   card somebody needs to read at a glance, so it takes the page's own ink rather
   than the quiet grey a paragraph gets. */
.delete-countdown {
  color: var(--text);
  font-weight: 600;
}

.account-request-details {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}

.account-request-details strong {
  color: var(--text);
  overflow-wrap: anywhere;
}

.account-request-details small { color: var(--muted); }

.account-request-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* One account's activity, opened in the list under the row it belongs to. It
   takes the row's own surface and border so the two read as one block, and the
   room underneath keeps it off the next account. */
.account-request-detail {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.9rem;
  margin-bottom: 0.4rem;
  background: var(--panel-2);
}

/* What an account is allowed to do. An admin-level account can do everything on
   this page, so it says so in the accent colour rather than reading like one
   more dropdown. A select cannot be styled from its selected option, so the
   value is mirrored onto the element as data-level. */
.account-level-select {
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

.account-level-select[data-level='admin'] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* When the plan next comes round. It sits beside the plan picker and reads
   quietly, because on most rows it is a dash. A plan the artist has asked to end
   is the one thing here worth catching an eye. */
.account-renewal {
  color: var(--muted);
  font-size: 0.82rem;
  white-space: nowrap;
}

.account-renewal-ending { color: var(--danger); }

.account-level-select:hover:not(:disabled) { border-color: var(--accent-ink); }
.account-level-select:focus { outline: none; border-color: var(--accent-ink); }
.account-level-select:disabled { opacity: 0.5; cursor: progress; }

.danger-link {
  color: var(--danger);
}

.danger-link:hover {
  color: var(--text);
  text-decoration-color: var(--danger);
}

.import-card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.4rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--border);
}

.entry-delete-cell {
  text-align: center !important;
  white-space: nowrap;
}

/* The page you are already on keeps its slot in the row rather than vanishing,
   so the buttons never shuffle position between pages. */
.nav-actions .nav-current {
  border-color: var(--accent-ink);
  background: var(--accent-soft);
  color: var(--accent-ink);
  cursor: default;
}

.daily-content { margin-top: 0.9rem; }

/* Putting entries on the to-do list. Sized to be seen and hit easily: it is
   the control this page is worked with. */
.recording-pick {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.85rem 0.4rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--raised);
  color: var(--muted);
  font-size: 0.92rem;
  white-space: nowrap;
  cursor: pointer;
}

.recording-pick:hover { border-color: var(--accent-line); }

.recording-pick-checkbox {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--accent-ink);
  cursor: pointer;
}

.recording-pick:has(.recording-pick-checkbox:checked) {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 700;
}

/* The standing link */
.links-list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.9rem;
}

.link-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

.link-card-main { min-width: 0; }

.link-card-meta {
  margin: 0 0 0.3rem;
  font-size: 0.78rem;
}

/* The address is the thing being copied, so it is readable at a glance and
   wraps rather than being clipped. */
.link-card-url {
  display: block;
  overflow-wrap: anywhere;
  color: var(--accent-ink);
  font-size: 0.9rem;
}

/* Links from before the standing one, still open. A warning, not a detail. */
.link-card-older {
  margin: 0.4rem 0 0;
  color: var(--danger);
  font-size: 0.78rem;
}

.link-card-side {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

/* The shared recording page */
.recording-list {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.recording-item {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

/* Done fades back so what is left to record stands out. */
.recording-item-done {
  border-style: dashed;
  background: var(--raised);
  opacity: 0.66;
}

.recording-position {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  border: 1px solid var(--accent-line);
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 0.82rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.recording-details {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.recording-details strong {
  font-size: 0.98rem;
  overflow-wrap: anywhere;
}

.recording-details small {
  color: var(--accent-ink);
  font-size: 0.8rem;
  font-weight: 700;
}

.recording-detail-line {
  color: var(--muted) !important;
  font-weight: 400 !important;
  font-size: 0.76rem !important;
}

.recording-side {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex: 0 0 auto;
}

/* Styled with the Daily Manager's copy of this control, further down. */

@media (max-width: 700px) {
  .recording-item {
    flex-wrap: wrap;
    row-gap: 0.6rem;
  }

  .recording-side { width: 100%; justify-content: space-between; }

  .link-card {
    flex-direction: column;
    align-items: stretch;
  }
}

/* The recording status is what these pages exist to track, so it is the one
   control that gets to be loud. Shared by the Daily Manager and the recording
   list so both read the same. */
.daily-status-select,
.edit-status-select,
.recording-status-select {
  min-width: 13.5rem;
  max-width: 16rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
}

/* Colour follows the value. A select cannot be styled from its selected option,
   so both pages mirror the current value onto the element as data-status. */
.daily-status-select[data-status='not_recorded'],
.recording-status-select[data-status='not_recorded'] {
  border-color: var(--danger-line);
  color: var(--danger);
}

.daily-status-select[data-status='recorded'],
.recording-status-select[data-status='recorded'] {
  border-color: var(--positive-line);
  color: var(--positive);
}

/* The open list, on the platforms that honour it. */
.daily-status-select option[value='not_recorded'],
.recording-status-select option[value='not_recorded'] { color: var(--danger); }

.daily-status-select option[value='recorded'],
.recording-status-select option[value='recorded'] { color: var(--positive); }

/* The edit status sits beside the recording status and reads the same way, so
   it follows its value with the same data-status mirror. Three stages rather
   than two, with the house yellow for the one in the middle. */
.edit-status-select[data-status='not_edited'] {
  border-color: var(--danger-line);
  color: var(--danger);
}

.edit-status-select[data-status='edited'] {
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

.edit-status-select[data-status='edited_posted'] {
  border-color: var(--positive-line);
  color: var(--positive);
}

.edit-status-select option[value='not_edited'] { color: var(--danger); }
.edit-status-select option[value='edited'] { color: var(--accent-ink); }
.edit-status-select option[value='edited_posted'] { color: var(--positive); }

.recording-status-select:hover:not(:disabled) { border-color: var(--accent-ink); }
.recording-status-select:focus { outline: none; border-color: var(--accent-ink); }
.recording-status-select:disabled { opacity: 0.5; cursor: progress; }

/* Sits directly under the note block and lines up with it. */
.daily-show-block {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.55rem;
}

/* Bigger than the note label beside it: which show an entry is on is the thing
   scanned for down this list, so it is sized to be found without reading. */
.daily-show-block .show-note-label { font-size: 0.74rem; }

.daily-show-booking {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.28rem 0.7rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  font-size: 1rem;
}

.daily-show-booking strong {
  color: var(--accent-ink);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.daily-show-booking small {
  color: var(--muted);
  font-size: 0.84rem;
}

/* Waiting for a show stays quiet so it does not compete with the entries that
   already have a home. */
.daily-show-none {
  border-color: var(--border);
  background: var(--raised);
  color: var(--muted);
  font-size: 0.9rem;
  font-style: italic;
}

/* Not picked for a show at all. A settled state, so it reads as a label rather
   than as something still outstanding. */
.daily-show-only {
  border-color: var(--border);
  background: var(--raised);
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.03em;
}

.daily-status-select:hover:not(:disabled),
.edit-status-select:hover:not(:disabled) { border-color: var(--accent-ink); }
.daily-status-select:focus,
.edit-status-select:focus { outline: none; border-color: var(--accent-ink); }
.daily-status-select:disabled,
.edit-status-select:disabled { opacity: 0.5; cursor: progress; }

/* Whether an entry carries an image. The picture is never shown on the page;
   attached, and downloadable again, is the whole feature. Sits under the show
   booking and lines up with the blocks above it. */
.entry-image {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
  margin-top: 0.55rem;
}

.entry-image-chip {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--positive-line);
  border-radius: var(--radius-pill);
  background: var(--positive-soft);
  color: var(--positive);
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
}

/* A label wired to a hidden file input, dressed as the buttons beside it. */
.entry-image-upload { cursor: pointer; }

/* Recorded entries are done with, so the section reads as settled rather than
   as more work waiting. It used to carry an accent edge, which said the
   opposite now that no other panel has one. */
.finished-panel { border-top-color: var(--border); }

.finished-panel .show-entry {
  border-style: dashed;
  background: var(--raised);
}

/* Show Manager: guests */
/* Five fields on a six-column grid: Name, Role and Company share the first row,
   then Show sits beside a Notes field wide enough to be worth typing into.
   auto-fit left them all the same width and broke the rows at odd places. */
.guest-fields { grid-template-columns: repeat(6, minmax(0, 1fr)); }

.guest-fields label { grid-column: span 2; }

/* Notes is always last, and is the one field worth extra room. */
.guest-fields label:last-child { grid-column: span 4; }

/* The edit form has no Show field, so Notes takes the whole second row. */
.guest-edit-form .guest-fields label:last-child { grid-column: span 6; }

@media (max-width: 900px) {
  .guest-fields label,
  .guest-fields label:last-child,
  .guest-edit-form .guest-fields label:last-child { grid-column: span 3; }
}

@media (max-width: 620px) {
  .guest-fields label,
  .guest-fields label:last-child,
  .guest-edit-form .guest-fields label:last-child { grid-column: span 6; }
}

.guest-list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.9rem;
}

.guest-card {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

.guest-card-editing { display: block; border-color: var(--accent-line); }

.guest-card-main { min-width: 0; }

.guest-card-main h3 {
  margin: 0;
  font-size: 1rem;
  overflow-wrap: anywhere;
}

.guest-card-main .show-entry-chips { margin-top: 0.45rem; }

.guest-card-side {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

/* The guest picker is styled with the entry picker further down: a guest is
   placed on a show the same way a song is, so the two controls match. */

/* Guests are listed on the show card itself, directly above the running order
   when it is open, so the running order does not repeat them. */

@media (max-width: 640px) {
  .guest-card {
    flex-direction: column;
    align-items: stretch;
  }
}

/* Show Manager: plans */
.create-plan-form {
  margin: 0.9rem 0 0.3rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-card);
  background: var(--accent-soft);
}

.create-plan-fields {
  display: grid;
  grid-template-columns: minmax(12rem, 1.2fr) minmax(9rem, auto) minmax(14rem, 2fr);
  gap: 0.8rem 0.9rem;
  /* Bottom-aligned so the fields line up even when one caption wraps to two
     lines, which it does at narrow widths. */
  align-items: end;
}

.create-plan-fields label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-transform: uppercase;
}

/* Selects were left to the browser here, which put a light default control in
   the middle of a row of dark fields. */
.create-plan-fields input,
.create-plan-fields select,
.create-plan-fields textarea {
  width: 100%;
  height: 2.4rem;
  padding: 0 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-transform: none;
  letter-spacing: normal;
}

.create-plan-fields select { cursor: pointer; }

/* A textarea is the one field here with more than a line in it, so it grows
   past the shared height and keeps the same dark dress. */
.create-plan-fields textarea {
  height: auto;
  min-height: 4.5rem;
  padding: 0.5rem 0.6rem;
  resize: vertical;
}

.create-plan-fields input::placeholder,
.create-plan-fields textarea::placeholder { color: var(--muted); }

.create-plan-fields input:hover,
.create-plan-fields select:hover,
.create-plan-fields textarea:hover { border-color: var(--accent-line); }

.create-plan-fields input:focus,
.create-plan-fields select:focus,
.create-plan-fields textarea:focus {
  outline: none;
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-line);
}

/* The picker button on a date input is invisible against the dark field. */
.create-plan-fields input[type='date']::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.7;
  cursor: pointer;
}

.field-optional {
  color: var(--muted);
  font-size: 0.7rem;
  font-style: italic;
  text-transform: none;
}

.create-plan-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.8rem;
}

/* The profile asks four short questions, one of which the three-column set the
   admin forms use would leave alone on a row of its own. */
#profileForm .create-plan-fields {
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}

/* The picture over those questions, with the two ways to change it beside it. */
.profile-avatar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: 0.8rem;
}

/* One size whatever was uploaded: the frame is round and fixed, and a tall or
   wide picture is cropped to fill it rather than setting the height of the
   card. */
.profile-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--field);
  border: 1px solid var(--border);
}

.profile-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* What stands in until there is a picture: the first letter of the account on a
   plain disc, drawn here rather than shipped as a file. */
.profile-avatar-initial {
  font-size: 2rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
}

.profile-avatar-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The label is the button, so the browser's own file control is kept out of the
   card rather than shown beside a second control that does the same thing. */
.profile-avatar-pick input[type="file"] { display: none; }

/* The upload and its Save are one form and Remove is another, here where the
   old page had buttons alone. The forms step aside so the three buttons share
   one row, one gap and one middle line, the way they stood on the old card. */
.profile-avatar-controls { align-items: center; }

.profile-avatar-controls form { display: contents; }

/* The membership plans and the song packs in the admin panel, a line each. The
   fields sit in the page behind every line and only the Edit button lets them
   through, so a card of ten plans reads as a list rather than as ten forms. */
.plan-list {
  display: grid;
  gap: 0.4rem;
  margin-top: 0.8rem;
}

.plan-row {
  display: grid;
  gap: 0.35rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-line);
  border-radius: var(--radius-card);
  padding: 0.5rem 0.75rem;
  background: var(--panel-2);
}

/* The closed line: what the thing is on the left, the way into it on the
   right. */
.plan-row-compact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 0.75rem;
}

.plan-row-summary {
  display: grid;
  gap: 0.1rem;
  min-width: 0;
}

.plan-row-identity {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.plan-row-identity strong { color: var(--text); }

/* The key the accounts point at, shown because it is what a name change leaves
   behind and what an integration will ask for. */
.plan-row-identity code {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted);
}

/* Everything the row would otherwise have to be opened to read: the allowance,
   the price line, whether Stripe knows it, and why a Remove would refuse. */
.plan-row-meta {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* The fields, once a row is open. Ruled off from the line above so the two read
   as the name and its workings rather than as one long block. */
.plan-row-edit {
  display: grid;
  gap: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

/* Only one row is open at a time, and its edge says which one. */
.plan-row-open { border-left-color: var(--accent-ink); }

/* The plan region: one headline over a row of cards. It carries the width a
   card would have carried on its own, because the cards inside it are now the
   columns of a grid rather than centred blocks. */
.plan-section {
  width: min(900px, calc(100% - 2rem));
  max-width: calc(100vw - 2rem);
  min-width: 0;
  margin: 0 auto 1.5rem;
}

/* The count an artist opens this page for, said once and large. It is the only
   heading on the page in the accent, which is what makes it read as the answer
   rather than as another section title. */
.plan-headline {
  margin: 0 0 0.9rem;
  color: var(--accent-ink);
  font-size: clamp(1.35rem, 2.8vw, 2rem);
}

/* The same headline where it stands on its own in the page rather than inside
   a section that already holds the page's width. It takes the card's own
   measure, so the line starts exactly where the card under it starts. */
.plan-headline-standalone {
  width: min(900px, calc(100% - 2rem));
  max-width: calc(100vw - 2rem);
  margin: 0 auto 0.9rem;
}

/* The line an administrator writes under the pricing title, standing on its own
   in the page the way that title does. It takes the same measure, so the two
   lines and the card under them all start at the same place. */
.pricing-intro-text {
  width: min(900px, calc(100% - 2rem));
  max-width: calc(100vw - 2rem);
  margin: -0.4rem auto 1.2rem;
  color: var(--muted);
  font-size: 1.1rem;
}

/* A count over one card and another card beside it. The cells belong to one
   grid rather than to two stacks side by side, which is what keeps the tops of
   the cards level: a card is put in the second row outright, so the card with
   nothing over it does not climb into the space the count beside it takes.

   The rows are set no space apart on purpose. The count already carries the gap
   under it, and a row gap on top of that margin would set it further from its
   card than anything else on the page. */
.plan-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto;
  gap: 0 1.5rem;
  align-items: start;
}

/* Which of the two columns a cell belongs to. Said on the cell rather than left
   to the order they are written in, so the pairs cannot come apart when one of
   the four is away. */
.plan-cards > .plan-column-current { grid-column: 1; }
.plan-cards > .plan-column-offer { grid-column: 2; }
.plan-cards > .plan-headline { grid-row: 1; }

/* The way into the submission form, standing over the offer card the way the
   count stands over the plan card: same row, same gap under it, sat on the
   bottom of the row so it lines up with the count's baseline rather than the
   top of the cell. */
.plan-cards > .plan-submit-cta {
  grid-row: 1;
  justify-self: end;
  align-self: end;
  margin: 0 0 0.9rem;
}

/* Inside the row a card is a column, so it gives up the width and the centring
   it uses when it stands alone. The pair also shares one height: the row is
   read as one thing, so the shorter card stretches to the taller one, and both
   sit tighter than a lone card so the pair stays a summary rather than a
   wall. */
.plan-cards > .card {
  grid-row: 2;
  width: 100%;
  max-width: none;
  margin: 0;
  align-self: stretch;
  padding: 1.1rem 1.5rem;
}

/* What is left when there is nothing to sell and no boosts to count: one
   column, so the plan card takes the whole row rather than leaving a gap
   beside itself. */
.plan-cards-single {
  grid-template-columns: 1fr;
}

@media (max-width: 900px) {
  .plan-cards {
    grid-template-columns: 1fr;
  }

  /* Stacked rather than paired, so the cells go back to the order they are
     written in: the count directly above its own card, rather than holding a
     row of its own above cards that are no longer beside each other. */
  .plan-cards > .plan-column-current,
  .plan-cards > .plan-column-offer {
    grid-column: auto;
    grid-row: auto;
  }

  /* Stacked, the button follows the plan card rather than standing beside
     the count, so it needs the room over it that the row gave it across.
     Connor's call, 2026-08-25. */
  .plan-cards > .plan-submit-cta { margin-top: 0.9rem; }
}

/* An artist's own membership plan, said in three lines that go from quiet to
   loud and back: what the line is, which plan, then what is left of the month
   on it. Read as a pill in a sentence the name was the smallest thing in the
   card; it is the fact the card is opened for, so it is now the largest. */
.plan-identity {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0.2rem 0 0;
}

/* The same small capitals the note and summary labels wear, which is how the
   site says "this names the thing under it" rather than "read this". */
.plan-identity-label {
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Heading weight without being a heading: the card already has one, and two
   would argue over which is the title. The yellow count above the cards stays
   the louder of the two, because it is the answer this one explains. */
.plan-identity-name {
  margin: 0;
  color: var(--accent-ink);
  font-size: 1.4rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.011em;
}

.plan-identity-usage {
  margin: 0.1rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* The other balance the plan carries, under the songs and in the same grey, so
   the two read as one answer rather than as two cards' worth. The accent is
   kept for the number of boosts itself is not: the line is a count, not an
   offer. */
.plan-boost-balance {
  margin: 0.35rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* The dates under the balances, in the same grey, because they are notes about
   the plan rather than the answer the card was opened for. A plan the artist has
   asked to end is the one line here worth catching an eye. */
.plan-renewal-line {
  margin: 0.35rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.plan-renewal-ending { color: var(--danger); }

/* The way out of the card, kept off the end of whatever is above it so it is
   not read as part of that. */
.plan-billing-actions,
.plan-packs-actions { margin-top: 1.25rem; }

/* What else the artist could be on. One row per plan, each one a name, what it
   costs and what it sends, with the button that opens checkout on the end. */
.plan-upgrades {
  display: grid;
  gap: 0.6rem;
  margin-top: 1.25rem;
}

.plan-upgrade-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-line);
  border-radius: var(--radius-card);
  padding: 0.75rem 1rem;
  background: var(--panel-2);
}

/* What is on offer takes the width of the row, which puts the button that takes
   it on the right edge of every row rather than wherever the words end. */
.plan-upgrade-identity {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: baseline;
  min-width: 0;
  column-gap: 0.6rem;
  row-gap: 0.2rem;
}

/* The name on a line of its own. Beside the price the two ran together as one
   phrase, and a plan is not called Pro $19.99. */
.plan-upgrade-identity strong {
  flex: 0 0 100%;
  color: var(--text);
}

/* Written by hand in the panel, so it is read as a claim about money and given
   the weight of one. */
.plan-upgrade-price {
  color: var(--accent-ink);
  font-size: 0.9rem;
}

.plan-upgrade-allowance {
  color: var(--muted);
  font-size: 0.85rem;
}

/* The picture on the card that sells a video review, on the dashboard and on the
   pricing page alike. Whatever shape it was uploaded at, it is held to the width
   of the card it sits in. */
.offer-card-image {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0.75rem 0;
  border-radius: var(--radius-card);
}

/* The chips on that same card saying what the account has already paid for.
   They stand between the pitch and the prices, where they are read before
   anything is bought a second time. */
.offer-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.9rem;
}

/* The tick box on the plan form, in the panel. A question rather than a field,
   so it is set the way the other tick boxes on the site are rather than being
   stretched across a row the way every input in that grid is. */
.plan-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.55rem;
  color: var(--muted);
  font-size: 0.95rem;
}

.plan-toggle input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
}

/* The Pricing page proper: one column per plan, read across against each other
   rather than down a list. auto-fit rather than a fixed three, because how many
   plans a site runs is an administrator's business and two or five have to be
   laid out by the same rule. */
.pricing-grid {
  display: grid;
  /* 190px is the widest minimum that still seats four plans inside the
     standard 900px card once its padding and the gaps are paid for. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1rem;
  margin-top: 1.25rem;
}

/* The width at which three columns stop being columns, which is the same width
   the plan cards give up their row at. */
@media (max-width: 900px) {
  .pricing-grid { grid-template-columns: 1fr; }
}

/* A column is read top to bottom in one order: what it is called, what it is
   for, what it costs, the way on to it, then the particulars. A flex column
   rather than a block so a card set beside a taller one fills its own height
   instead of leaving the grid ragged. */
.pricing-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-card);
  padding: 1.25rem;
  /* The lit-panel treatment the News card wears: a yellow edge, and the accent
     washing away from the top right into the grey. Every plan column is a
     thing the eye is meant to land on, so each wears it whole. */
  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 42%),
    var(--panel-2);
}

/* The one plan the site puts forward. The edge is doubled with a ring rather
   than a thicker border, so the recommended column is not a pixel narrower
   inside than the ones beside it. No second colour: it is the accent the page
   already uses, carried louder. */
.pricing-card-best {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line);
}

/* The tag across the top of that column. It runs the full width by pulling back
   over the card's own padding, so it reads as a band on the card rather than as
   a first line inside it. */
.pricing-ribbon {
  margin: -1.25rem -1.25rem 1rem;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  padding: 0.4rem 1.25rem;
  color: var(--accent-ink);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: center;
  text-transform: uppercase;
  background: var(--accent-soft);
}

/* The plan's name. A heading in weight, but under the section's own: a column
   names itself rather than starting a new part of the page. */
.pricing-name {
  margin: 0;
  color: var(--text);
  font-size: 1.5rem;
}

/* One sentence written in the panel, in the grey the site explains things in,
   so it sits under the name without arguing with it. */
.pricing-blurb {
  margin: 0.35rem 0 0;
  font-size: 0.88rem;
  line-height: 1.45;
}

/* What the column is opened for, so it is the largest thing in it. */
.pricing-price {
  margin: 0.75rem 0 0;
  color: var(--accent-ink);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.011em;
}

/* One thing to press per column, in the same place in every column and across
   the whole of it, which is what makes the row of cards read as a choice. */
.pricing-cta {
  width: 100%;
  margin-top: 1rem;
  text-align: center;
}

/* Where that button would be on the plan the account is already on. It keeps
   the button's size and the accent, and is a line rather than a control:
   there is nothing here to press. */
.pricing-current {
  margin: 1rem 0 0;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-control);
  padding: 0.55rem 0.95rem;
  color: var(--accent-ink);
  font-size: 0.92rem;
  font-weight: 600;
  text-align: center;
  background: var(--accent-soft);
}

/* What the plan actually gives, under the button. One bordered line per fact,
   the same facts in the same order in every column, so a reader can run an eye
   across the row as well as down a card. */
.pricing-specs { margin: 1.25rem 0 0; }

/* The one sentence under the counted rows, quiet like the row labels: it is
   an aside about topping up, not a third count. */
.pricing-spec-note {
  margin: 0.6rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
}

.pricing-spec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  border-top: 1px solid var(--border);
  padding: 0.55rem 0;
}

.pricing-spec:last-child { border-bottom: 1px solid var(--border); }

.pricing-spec dt {
  color: var(--muted);
  font-size: 0.82rem;
}

/* The one sentence in the ruled list, in the answer's weight rather than the
   label's grey: it is a thing the plan includes, not a caption about one. */
.pricing-spec-included {
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
}

/* The answer, weighted against the grey label so the eye finds the right-hand
   edge of the column and reads down it. */
.pricing-spec dd {
  margin: 0;
  color: var(--text);
  font-size: 0.95rem;
  font-weight: 600;
  text-align: right;
}

.plan-list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.9rem;
}

.plan-card {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

.plan-card-active {
  border-color: var(--accent-ink);
  background: var(--accent-soft);
}

/* The card whose running order is open. It holds the panel, so it reads as one
   thing rather than a heading matched against a list further down. */
.plan-card-open {
  border-color: var(--accent-ink);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* The link for whoever is running the show. Its own row, because handing out an
   address that needs no password should not read as one more button. */
.plan-card-share {
  flex: 1 0 100%;
  min-width: 0;
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

.plan-card-share-live {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.plan-share-main { min-width: 0; }

.plan-share-label {
  display: block;
  color: var(--accent-ink);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.plan-share-url {
  display: block;
  margin-top: 0.2rem;
  overflow-wrap: anywhere;
  color: var(--accent-ink);
  font-size: 0.86rem;
}

.plan-share-note {
  margin: 0.25rem 0 0;
  color: var(--muted);
  font-size: 0.74rem;
}

.plan-share-side {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

/* A full row of its own under the card's own buttons. */
.plan-card-lineup {
  flex: 1 0 100%;
  min-width: 0;
}

/* One panel is moved between cards, so inside a card it gives up the chrome
   that made it a section in its own right. */
.plan-card-lineup .lineup-panel {
  width: auto;
  max-width: none;
  margin: 0.7rem 0 0;
  padding: 0.75rem 0 0;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.plan-card-lineup .lineup-panel h2 { font-size: 0.95rem; }

/* The whole card becomes the form while it is being edited. */
.plan-card-editing {
  display: block;
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.plan-edit-form .create-plan-fields { margin-top: 0; }

.plan-card h3 {
  margin: 0;
  font-size: 1rem;
  overflow-wrap: anywhere;
}

.plan-card-date {
  margin: 0.15rem 0 0;
  color: var(--accent-ink);
  font-weight: 700;
  font-size: 0.84rem;
}

.plan-card-notes {
  margin: 0.4rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

/* Booked guests, listed on the card so a show can be read without opening it. */
.plan-card-guests {
  margin-top: 0.55rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.plan-card-guests-label {
  display: block;
  color: var(--accent-ink);
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.plan-guest-list {
  margin: 0.45rem 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 0.55rem;
}

/* One chip each, so a long list wraps down the card instead of running off it.
   Sized to be read at a glance: who is on a show is the headline of the card,
   not a footnote to it. */
.plan-guest {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  padding: 0.32rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--raised);
  font-size: 1rem;
  overflow-wrap: anywhere;
}

.plan-guest strong { font-weight: 700; }

.plan-guest small {
  color: var(--muted);
  font-size: 0.82rem;
}

.plan-card-no-guests {
  margin: 0.55rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.84rem;
  font-style: italic;
}

.plan-card-side {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

/* Completing a show ends its planning, so the action sits apart from the
   everyday buttons rather than beside them. */
.plan-card-footer {
  flex: 1 0 100%;
  margin-top: 0.7rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
}

.plan-card { flex-wrap: wrap; }

.completed-panel { opacity: 0.94; }

.plan-card-completed {
  border-style: dashed;
  background: var(--raised);
}

.plan-card-completed .plan-card-date { color: var(--muted); }

.plan-card-completed-note {
  margin: 0.4rem 0 0;
  color: var(--accent-ink);
  font-size: 0.76rem;
  font-weight: 700;
}

.show-chip-scheduled {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.show-picker { display: block; }

/* Entries and guests are placed on a show by the same control, so they are
   sized and coloured together. */
.show-picker-select,
.guest-show-select {
  min-width: 15rem;
  max-width: 20rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
}

/* The entry picker's options carry the artist count as well, so it alone gets
   the extra room to show them uncut. */
.show-picker-select {
  min-width: 18rem;
  max-width: 27rem;
}

/* Whether an entry has a show yet is the thing this page is for, so it is
   readable without reading. A select cannot be styled from its selected
   option, so the answer is mirrored onto the element as data-assigned. */
.show-picker-select[data-assigned='no'],
.guest-show-select[data-assigned='no'] {
  border-color: var(--danger-line);
  color: var(--danger);
}

.show-picker-select[data-assigned='yes'],
.guest-show-select[data-assigned='yes'] {
  border-color: var(--positive-line);
  color: var(--positive);
}

/* The open list, on the platforms that honour it. */
.show-picker-select option[value=''],
.guest-show-select option[value=''] { color: var(--danger); }

.show-picker-select option:not([value='']),
.guest-show-select option:not([value='']) { color: var(--positive); }

.show-picker-select:hover:not(:disabled),
.guest-show-select:hover:not(:disabled) { border-color: var(--accent-ink); }

.show-picker-select:focus,
.guest-show-select:focus {
  outline: none;
  border-color: var(--accent-ink);
}

.show-picker-select:disabled,
.guest-show-select:disabled { opacity: 0.5; cursor: progress; }

.show-no-plans {
  color: var(--muted);
  font-size: 0.76rem;
  font-style: italic;
}

@media (max-width: 640px) {
  .create-plan-fields { grid-template-columns: 1fr; }

  .plan-card {
    flex-direction: column;
    align-items: stretch;
  }

  .plan-card-share-live {
    flex-direction: column;
    align-items: stretch;
  }
}

/* Show Manager: running order */
.lineup-panel { border-color: var(--accent-line); }

.lineup-list {
  display: grid;
  gap: 0.45rem;
  margin: 0.9rem 0 0;
  padding: 0;
  list-style: none;
  counter-reset: lineup;
}

.lineup-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
  cursor: grab;
}

.lineup-item.lineup-dragging {
  opacity: 0.45;
  cursor: grabbing;
}

/* Marks the slot the dragged entry will take. */
.lineup-item.lineup-drop-target {
  border-color: var(--accent-ink);
  box-shadow: inset 0 3px 0 -1px var(--accent);
}

.lineup-handle {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 1.05rem;
  letter-spacing: -0.1em;
  user-select: none;
}

.lineup-position {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent-ink);
  font-size: 0.82rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.lineup-details {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.lineup-details strong {
  font-size: 0.92rem;
  overflow-wrap: anywhere;
}

.lineup-details small {
  color: var(--accent-ink);
  font-size: 0.78rem;
  font-weight: 700;
}

.lineup-side {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

.lineup-moves {
  display: inline-flex;
  gap: 0.25rem;
}

.lineup-move {
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--raised);
  color: var(--text);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}

.lineup-move:hover:not(:disabled) {
  border-color: var(--accent-ink);
  color: var(--accent-ink);
}

.lineup-move:disabled { opacity: 0.3; cursor: default; }

.lineup-destination { flex: 0 0 auto; }

.lineup-destination-select {
  min-width: 11rem;
  max-width: 14rem;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.78rem;
  /* Sits inside a draggable row, so it needs its own pointer. */
  cursor: pointer;
}

.lineup-destination-select:hover:not(:disabled) { border-color: var(--accent-ink); }

.lineup-destination-select:focus {
  outline: none;
  border-color: var(--accent-ink);
}

.lineup-destination-select:disabled { opacity: 0.5; cursor: progress; }

.lineup-empty {
  padding: 0.7rem 0.9rem;
  list-style: none;
}

@media (max-width: 640px) {
  .lineup-item {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  .lineup-details { flex: 1 1 60%; }
  .lineup-side { width: 100%; justify-content: space-between; }
}

/* Show Manager: entries */
.show-list {
  display: grid;
  gap: 0.75rem;
  margin-top: 1rem;
}

.show-entry {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

.show-entry-certified {
  border-color: var(--accent-line);
  box-shadow: inset 3px 0 0 var(--accent);
}

.show-entry-main { min-width: 0; }

.show-entry-heading h3 {
  margin: 0;
  font-size: 1.02rem;
  overflow-wrap: anywhere;
}

.show-entry-artist {
  margin: 0.15rem 0 0;
  color: var(--accent-ink);
  font-weight: 700;
  font-size: 0.88rem;
}

/* The handle under the artist's name, quieter than the name it belongs to. */
.show-entry-instagram {
  margin: 0.1rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

.show-entry-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
}

.show-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  padding: 0.22rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--raised);
  font-size: 0.78rem;
  color: var(--text);
}

.show-chip small {
  color: var(--muted);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.show-chip-certified {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 700;
}

.show-chip-id {
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}

.show-note-block {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.show-note-label {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* The preview is sized for a narrow table cell by default; in a card there is
   room to show most of a note before it has to be opened. */
.show-note-block .entry-note-preview {
  max-width: none;
  width: min(100%, 32rem);
  font-size: 0.84rem;
}

.show-entry-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.55rem;
  flex: 0 0 auto;
}

.show-no-audio {
  color: var(--muted);
  font-size: 0.78rem;
  font-style: italic;
}

@media (max-width: 640px) {
  .show-entry {
    flex-direction: column;
    align-items: stretch;
  }

  .show-entry-side {
    align-items: flex-start;
    flex-direction: row;
    justify-content: space-between;
  }
}

/* These hold nothing but a checkbox, so their width was set entirely by
   the header text. The heading is allowed to stack instead. */
.assign-show-cell,
.assign-daily-cell,
.already-listened-cell,
.stem-opp-cell,
.pick-cell,
.docs-choice-admin-cell {
  width: 78px;
  min-width: 78px;
  padding-left: 0.3rem !important;
  padding-right: 0.3rem !important;
  text-align: center !important;
  vertical-align: middle;
}

/* Smaller and tighter than the other headings, because the narrowest a column
   can go is the width of its longest unbreakable word: CERTIFIED. */
.head-narrow {
  display: inline-block;
  max-width: 4.6rem;
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  line-height: 1.25;
  white-space: normal;
  overflow-wrap: break-word;
}

.assign-daily-toggle,
.already-listened-toggle,
.stem-opp-toggle,
.pick-toggle,
.assign-show-toggle {
  display: inline-grid;
  place-items: center;
  margin: 0;
  cursor: pointer;
}

.assign-show-checkbox,
.assign-daily-checkbox,
.already-listened-checkbox,
.stem-opp-checkbox {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--accent-ink);
  cursor: pointer;
}

.assign-show-checkbox:disabled,
.assign-daily-checkbox:disabled,
.already-listened-checkbox:disabled,
.stem-opp-checkbox:disabled { cursor: progress; opacity: 0.5; }

/* The save control draws as a star: an outline when the song is not on the
   list, filled when it is. The input still holds the state and the keyboard
   focus; the label only does the drawing, so nothing about the control's
   behaviour changes. */
.pick-checkbox {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}

.pick-toggle::before {
  content: '☆';
  font-size: 1.7rem;
  line-height: 1;
  color: var(--accent-ink);
}

.pick-toggle:has(.pick-checkbox:checked)::before {
  content: '★';
}

.pick-toggle:has(.pick-checkbox:disabled) {
  cursor: progress;
  opacity: 0.5;
}

.pick-toggle:has(.pick-checkbox:focus-visible) {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-radius: var(--radius-control);
}

/* Admin review notes. The cell stays small; double-clicking opens the editor. */
.entry-notes-cell {
  max-width: 132px;
  vertical-align: middle;
}

.entry-note-preview {
  display: block;
  width: 100%;
  max-width: 132px;
  padding: 0.32rem 0.45rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius-control);
  background: var(--raised);
  color: var(--text);
  font: inherit;
  font-size: 0.8rem;
  line-height: 1.3;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  /* Double-clicking would otherwise flash a text selection across the label. */
  user-select: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.entry-note-preview[data-empty='true'] {
  color: var(--muted);
  font-style: italic;
}

/* A written note is worth spotting at a glance down the column. */
.entry-note-preview[data-empty='false'] {
  border-style: solid;
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.entry-note-preview:hover {
  border-color: var(--accent-ink);
  background: var(--raised);
}

.entry-note-preview:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

.entry-note-preview[data-editing='true'] {
  border-color: var(--accent-ink);
  background: var(--accent-soft);
}

.entry-note-preview[data-state='saving'] { opacity: 0.6; }

.entry-note-preview[data-state='error'] {
  border-color: var(--danger);
  background: var(--danger-soft);
}

/* Fixed to the viewport so the table's own scrolling cannot clip it. */
.entry-note-editor {
  position: fixed;
  z-index: 60;
  width: min(360px, calc(100vw - 16px));
  padding: 0.7rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-card);
  background: var(--panel-2);
  box-shadow: var(--shadow-raised);
}

.entry-note-input {
  display: block;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
  line-height: 1.45;
  resize: vertical;
}

.entry-note-input:focus {
  outline: none;
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-line);
}

.entry-note-editor-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-top: 0.45rem;
  font-size: 0.72rem;
  color: var(--muted);
}

.entry-note-count { font-variant-numeric: tabular-nums; }

.user-controls {
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

.user-controls select {
  width: auto;
  min-width: 11rem;
}

.pending-panel {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line);
}

.pending-panel h2::before {
  content: "●";
  margin-right: 0.5rem;
  color: var(--accent-ink);
  font-size: 0.7em;
  vertical-align: middle;
}

.user-search-row {
  margin: 0.9rem 0 0.2rem;
}

.user-search-shell {
  max-width: 26rem;
}

.user-list-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.85rem;
}

/* The way through a list that is read five rows at a time: back, where you are,
   and on. It sits under the list it pages, and inside the accounts footer it is
   the second thing on that row, beside the count. */
.list-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.user-list-footer .list-pager { margin-top: 0; }

.user-detail-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 1rem;
  margin-bottom: 1rem;
}

.user-detail-header h3 {
  margin: 0 0 0.2rem;
  color: var(--text);
  overflow-wrap: anywhere;
}

.user-detail-header p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* Sent to the far end of the header, away from the control that shuts the
   account again at the other end, so the two are not read as a pair. */
.user-detail-login { margin-left: auto; }

/* The two halves of an account's story, one showing at a time. They are the
   panel's own ghost buttons rather than a tab control of their own, and the
   only thing added is a mark saying which half is open. */
.user-detail-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.user-detail-tab.is-active,
.user-detail-tab.is-active:hover {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

.user-detail-empty span { color: var(--muted); font-style: italic; }

.user-detail-row {
  grid-template-columns: 1fr auto;
}

.activity-summary { margin-top: 0.8rem; }

.activity-stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.activity-stat {
  display: grid;
  gap: 0.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.7rem 0.8rem;
  background: var(--panel-2);
  text-align: center;
}

.activity-stat strong {
  color: var(--accent-ink);
  font-size: 1.5rem;
  line-height: 1.1;
}

.activity-stat small { color: var(--muted); }

/* The Reports page borrows the tiles above and adds only its two lists. A plan
   name is written in the catalog's own ink wherever it is shown, and the head
   count beside it is the quieter half of the line. */
.report-plan-line {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}

.report-plan-line:last-child { border-bottom: none; }
.report-plan-line span:first-child { color: var(--accent-ink); }
.report-plan-line span:last-child { color: var(--muted); }

/* The window picker sits above the cards it governs, dressed the same as the
   range picker on the admin dashboard so the two read as one control. */
.report-window {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: min(1550px, calc(100% - 2rem));
  margin: 0 auto -0.5rem;
}

.report-window label {
  color: var(--muted);
  font-size: 0.9rem;
}

.report-window select {
  width: auto;
  padding: 0.4rem 2rem 0.4rem 0.7rem;
  font-size: 0.9rem;
}

/* The whole take, written big. The card's other numbers are slices of this one,
   so this is the one that reads first. */
.report-money-total {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin-bottom: 1rem;
}

.report-money-total strong {
  font-size: 3rem;
  line-height: 1;
  color: var(--accent-ink);
}

.report-money-total small { color: var(--muted); }

/* The first line of a scored message: what it is and which song, in the
   accent, so a thread holding several reads song by song. */
.message-review-heading {
  margin: 0 0 0.45rem;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
}

/* The line an administrator writes over the scores, on reviews and ratings
   both, edited beside the categories on the offers page. */
.message-review-description {
  margin: 0 0 0.6rem;
  font-size: 0.9rem;
  color: var(--muted);
}

/* The song's name is the one part of the heading in the accent, so the eye
   lands on which song before it reads what was done to it. */
.message-review-song { color: var(--accent-ink); }

.report-top-songs {
  display: grid;
  gap: 0.3rem;
  margin: 0;
  padding-left: 1.2rem;
  color: var(--muted);
}

.activity-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.75rem;
}

.activity-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.75rem 0.9rem;
  background: var(--panel-2);
}

.activity-card h4 {
  margin: 0 0 0.5rem;
  color: var(--text);
  font-size: 0.95rem;
}

.activity-card ul {
  display: grid;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.activity-card li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* A long name wraps rather than being cut off: what was searched, played or
   favorited is the whole point of the row, and the count keeps its place on
   the right because the label may break anywhere it needs to. */
.activity-card li span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.activity-card li strong { color: var(--accent-ink); }

.activity-drill {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  /* The button is itself a flex item, and without this floor it refuses to
     shrink below its longest label and runs out of the card. */
  min-width: 0;
  border: 0;
  border-radius: var(--radius-control);
  padding: 0.2rem 0.3rem;
  margin: 0 -0.3rem;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.activity-drill:hover {
  background: var(--accent-soft);
  color: var(--text);
}

.activity-drill:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 1px;
}

.activity-drill span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.activity-drill strong { color: var(--accent-ink); }

.activity-who-row { grid-template-columns: minmax(10rem, 1fr) auto auto; }
.activity-anon { color: var(--muted); font-style: italic; }

/* A colleague listening from this panel, not an unknown visitor. */
.activity-admin-actor {
  color: var(--accent-ink);
  font-style: normal;
  font-weight: 700;
}

.activity-subhead {
  margin: 1.2rem 0 0.5rem;
  color: var(--text);
  font-size: 1rem;
}

.activity-feed {
  display: grid;
  gap: 0.35rem;
  max-height: 26rem;
  overflow-y: auto;
}

.activity-row {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr auto;
  align-items: baseline;
  gap: 0.75rem;
  border-bottom: 1px solid var(--border);
  padding: 0.5rem 0.2rem;
  font-size: 0.9rem;
}

.activity-who {
  overflow: hidden;
  color: var(--text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.activity-what { color: var(--muted); overflow-wrap: anywhere; }
.activity-what strong { color: var(--accent-ink); font-weight: 600; }
.activity-when { color: var(--muted); font-size: 0.82rem; white-space: nowrap; }

@media (max-width: 720px) {
  .activity-row {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }
}

.account-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.artist-back-panel {
  display: flex;
  margin-bottom: 0.85rem;
}

.artist-back-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1.1rem;
}

.artist-back-arrow {
  font-size: 1.15rem;
  line-height: 1;
}

.certified-filter-copy {
  display: grid;
  min-width: 0;
  gap: 0.2rem;
}

.certified-filter-copy strong {
  color: var(--text);
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  line-height: 1.2;
}

.certified-filter-copy small {
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 400;
}

/* Make DataTables controls and messages feel integrated with the page. */
.table-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel);
}

table.dataTable {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}

/* Column names read as labels above the data rather than as a filled bar across
   the top of a spreadsheet: small, spaced capitals on the panel itself, with a
   single gold rule separating them from the rows. */
table.dataTable thead th,
table.dataTable thead td {
  border-bottom: 2px solid var(--accent-line) !important;
  background: linear-gradient(180deg, var(--accent-soft), var(--accent-veil)) !important;
  color: var(--muted) !important;
  padding-top: 0.85rem !important;
  padding-bottom: 0.85rem !important;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  line-height: 1.3;
  text-transform: uppercase;
  white-space: normal;
  vertical-align: bottom;
}

/* Long names wrap onto two or three lines rather than stretching the column.
   Capping the title is what lets that happen: DataTables sizes each column from
   its widest content, so an uncapped heading on one line sets the width. */
table.dataTable thead > tr > th div.dt-column-header .dt-column-title,
table.dataTable thead > tr > td div.dt-column-header .dt-column-title {
  max-width: 9rem;
  /* Permits a break inside a run like Song/EP/Album/Beat, and counts toward the
     minimum width so the column can actually narrow. */
  overflow-wrap: anywhere;
}

/* Headers of differing heights line up along the bottom, next to the rows they
   label, instead of floating at their own centres. */
table.dataTable thead > tr > th div.dt-column-header,
table.dataTable thead > tr > td div.dt-column-header {
  align-items: flex-end;
}

table.dataTable thead > tr > th.dt-orderable-asc:hover,
table.dataTable thead > tr > th.dt-orderable-desc:hover {
  color: var(--accent-ink) !important;
  background: var(--accent-soft) !important;
}

/* The column being sorted on is the one worth picking out. */
table.dataTable thead > tr > th.dt-ordering-asc,
table.dataTable thead > tr > th.dt-ordering-desc {
  color: var(--accent-ink) !important;
  background: var(--accent-soft) !important;
}

/* DataTables draws both arrows on every sortable column at opacity .125, and
   .6 on the active one. These selectors mirror the vendor's own shape so they
   match on specificity and win by loading afterwards. */
table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order::before,
table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order::after,
table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order::before,
table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order::after {
  color: var(--accent-ink);
  opacity: 0.32;
}

table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order::before,
table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order::after {
  color: var(--accent-ink);
  opacity: 1;
}

/* With scrollX the header is cloned into the scrolling body at zero height to
   keep the columns aligned. Padding it would push the first row down. */
div.dt-scroll-body thead th,
div.dt-scroll-body thead td {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  border-bottom: 0 !important;
  background: transparent !important;
}

table.dataTable > tbody > tr {
  background-color: var(--panel) !important;
  color: var(--text);
}

/* A hairline between rows instead of a full grid, with room to breathe. */
table.dataTable > tbody > tr > * {
  border-top: 1px solid var(--border) !important;
  padding-top: 0.7rem !important;
  padding-bottom: 0.7rem !important;
  background: var(--panel) !important;
  color: var(--text);
  box-shadow: none !important;
}

table.dataTable > tbody > tr:first-child > * { border-top: 0 !important; }

/* Only where a pointer can really hover. A thumb scrolling a phone lands on
   rows as it goes, and a browser reads each touch as a hover, so without the
   guard the page lights up yellow under the reader's own hand. */
@media (hover: hover) {
  table.dataTable.display > tbody > tr:hover > *,
  table.dataTable.hover > tbody > tr:hover > * {
    background: var(--accent-soft) !important;
  }
}

.docs-choice-cell {
  min-width: 112px;
  width: 112px;
  padding-right: 0.65rem !important;
  padding-left: 0.65rem !important;
  text-align: center !important;
  vertical-align: middle;
}

.docs-choice-badge {
  display: block;
  height: 2.7rem;
  width: auto;
  max-width: 100%;
  margin: 0 auto;
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
}

table.dataTable a {
  color: var(--accent-ink);
  text-decoration-color: var(--accent-line);
  text-underline-offset: 0.16rem;
}

.table-artist-link {
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--accent-ink);
  padding: 0;
  font: inherit;
  font-weight: 800;
  text-align: left;
  text-decoration: underline;
  text-decoration-color: var(--accent-line);
  text-underline-offset: 0.18rem;
  cursor: pointer;
}

.table-artist-link:hover {
  color: var(--text);
  text-decoration-color: var(--accent-ink);
}

.table-artist-link:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

/* In-table song player. The ring around the button doubles as the progress
   indicator: --song-progress is set from the audio element's position. */
.song-player {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
}

.song-play-button {
  position: relative;
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  background: conic-gradient(var(--accent) var(--song-progress, 0%), var(--border) 0);
  transition: transform 0.12s ease;
}

.song-play-button::before {
  content: '';
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  background: var(--panel-2);
}

.song-play-button:hover { transform: scale(1.08); }

.song-play-button:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

/* Idle and paused: a play triangle, nudged right so it looks centred. */
.song-play-glyph {
  position: relative;
  z-index: 1;
  display: block;
  margin-left: 0.16rem;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0.36rem 0 0.36rem 0.6rem;
  border-color: transparent transparent transparent var(--accent-line);
}

.song-play-button[data-state='playing'] .song-play-glyph {
  margin-left: 0;
  width: 0.62rem;
  height: 0.68rem;
  border: 0;
  background:
    linear-gradient(var(--accent), var(--accent)) left / 0.2rem 100% no-repeat,
    linear-gradient(var(--accent), var(--accent)) right / 0.2rem 100% no-repeat;
}

.song-play-button[data-state='loading'] .song-play-glyph {
  margin-left: 0;
  width: 0.82rem;
  height: 0.82rem;
  border: 2px solid var(--accent-line);
  border-top-color: var(--accent-ink);
  border-radius: 50%;
  animation: song-play-spin 0.7s linear infinite;
}

/* A file that will not load stops pretending to be a player and opens instead. */
.song-play-button[data-state='error'] {
  background: var(--danger-soft);
}

.song-play-button[data-state='error'] .song-play-glyph {
  margin-left: 0;
  width: auto;
  height: auto;
  border: 0;
  color: var(--danger);
  font-size: 0.9rem;
  line-height: 1;
}

.song-play-button[data-state='error'] .song-play-glyph::after { content: '\2197'; }

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

.song-play-count {
  display: inline-flex;
  align-items: center;
  gap: 0.34rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0.2rem 0.5rem;
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  background: var(--raised);
}

.song-play-waves {
  display: inline-flex;
  align-items: flex-end;
  gap: 1px;
  height: 0.62rem;
}

.song-play-waves i {
  width: 2px;
  border-radius: 1px;
  background: currentColor;
  opacity: 0.8;
}

.song-play-waves i:nth-child(1) { height: 40%; }
.song-play-waves i:nth-child(2) { height: 100%; }
.song-play-waves i:nth-child(3) { height: 65%; }

/* The row that is sounding is the one worth picking out of a long table. */
.song-player:has(.song-play-button[data-state='playing']) .song-play-count {
  color: var(--accent-ink);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.song-player:has(.song-play-button[data-state='playing']) .song-play-waves i {
  animation: song-wave 0.9s ease-in-out infinite;
}

.song-player:has(.song-play-button[data-state='playing']) .song-play-waves i:nth-child(2) { animation-delay: 0.15s; }
.song-player:has(.song-play-button[data-state='playing']) .song-play-waves i:nth-child(3) { animation-delay: 0.3s; }

@keyframes song-wave {
  0%, 100% { height: 35%; }
  50% { height: 100%; }
}

/* The redacted catalog. A withheld value still occupies its cell, so the table
   keeps its shape and its filters keep working on everything that is real. The
   blur is paint over text the server never sent: there is nothing underneath it
   to sharpen, select or copy. */
.redacted-cell {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
}

.redacted-text {
  display: inline-block;
  filter: blur(4px);
  color: var(--muted);
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  white-space: nowrap;
}

/* Where a play button sits in the full catalog. A padlock rather than a
   control, because there is no address behind it to load. */
/* The play button's quiet twin: the same circle in the same seat, drawn in the
   muted ink so the column reads as players with this one refused. */
.song-locked {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--field);
  color: var(--muted);
}

.song-locked-glyph {
  display: block;
  width: 1.05rem;
  height: 1.05rem;
}

.table-open-link { white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .song-play-button { transition: none; }
  .song-play-button:hover { transform: none; }
  .song-play-button[data-state='loading'] .song-play-glyph,
  .song-player:has(.song-play-button[data-state='playing']) .song-play-waves i {
    animation: none;
  }
}

.dt-container .dt-info,
.dt-container .dt-paging {
  margin-top: 0.9rem;
}

/* Written with the html prefix because the library's own sheet says the same
   things at the same weight and later in the page, so a tie goes to it: the
   prefix is what makes these the last word. Every control is the page's own
   white, the disabled ones included, and only the page being stood on differs:
   black on the yellow, so the one number that is not a way anywhere reads as a
   marker and not a button. */
html .dt-container .dt-paging .dt-paging-button {
  border: 1px solid transparent !important;
  border-radius: var(--radius-control) !important;
  color: var(--text) !important;
}

html .dt-container .dt-paging .dt-paging-button:hover {
  border-color: var(--accent-ink) !important;
  background: var(--accent-soft) !important;
  color: var(--accent-ink) !important;
}

html .dt-container .dt-paging .dt-paging-button.current,
html .dt-container .dt-paging .dt-paging-button.current:hover {
  border-color: var(--accent-ink) !important;
  background: var(--accent) !important;
  color: var(--on-accent) !important;
}

html .dt-container .dt-paging .dt-paging-button.disabled,
html .dt-container .dt-paging .dt-paging-button.disabled:hover {
  border-color: transparent !important;
  background: transparent !important;
  color: var(--text) !important;
}

.dt-container .dt-length,
.dt-container .dt-search,
.dt-container .dt-info {
  color: var(--muted);
}

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

  .quick-filter-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .result-summary-wrap {
    grid-column: auto;
  }
}

@media (max-width: 640px) {
  .logo-upload-form {
    grid-template-columns: 1fr;
  }

  .logo-upload-form label {
    grid-column: auto;
  }

  .logo-upload-form .button {
    width: 100%;
  }

  .discovery-heading {
    flex-direction: column;
  }

  .quick-filter-grid {
    grid-template-columns: 1fr;
  }

  .result-summary-wrap {
    grid-column: auto;
  }

  .search-shell {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .search-clear {
    grid-column: 1 / -1;
    width: 100%;
  }

  .certified-filter-control {
    gap: 0.65rem;
    padding: 0.8rem;
  }

  .docs-choice-badge.certified-filter-badge {
    width: auto;
    height: 3.1rem;
  }
}

/* The instruction sheet sent to an outside developer. Reading is the whole job
   of the page, so the measure is held short and code is set apart plainly. */
.api-docs h3 {
  margin: 1.4rem 0 0.5rem;
  font-size: 1rem;
}

.api-docs p,
.api-doc-list {
  max-width: 62ch;
}

.api-doc-list {
  margin: 0.5rem 0 0.9rem;
  padding-left: 1.2rem;
  display: grid;
  gap: 0.35rem;
  color: var(--muted);
}

.api-docs code {
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.86em;
}

.api-code {
  margin: 0.5rem 0 1rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--field);
  /* Long lines are the normal case in a code sample, so the block scrolls
     rather than making the whole page scroll sideways. */
  overflow-x: auto;
}

.api-code code {
  padding: 0;
  background: none;
  color: var(--text);
  font-size: 0.84rem;
  line-height: 1.55;
  white-space: pre;
}

/* A plain table sitting in a card. The A&R's picks list is set out the same
   way, so the two share these rules rather than repeat them. */
.api-table,
.picks-table {
  width: 100%;
  margin: 0.6rem 0 0.4rem;
  border-collapse: collapse;
  font-size: 0.88rem;
}

.api-table th,
.api-table td,
.picks-table th,
.picks-table td {
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.api-table th,
.picks-table th {
  font-size: 0.76rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.api-table td,
.picks-table td { color: var(--text); }

/* The artist's own list. It is the picks table above with one thing added:
   every cell sits on the middle line, so a row with a badge and a row without
   one are the same height and read as one calm column of rows. */
.submissions-table th,
.submissions-table td { vertical-align: middle; }

/* The column that only holds the Remove button has nothing to line up on the
   left, so the button sits under the middle of its own heading. */
.submissions-table th:last-child,
.submissions-table .submission-action-cell { text-align: center; }

/* The play column is one line and stays one line. The cell keeps its own
   layout; the line inside it is what puts the badge, the button and the
   counter side by side. */
/* Wide enough for the badge slot, the button and the counter side by side. The
   badge slot is an empty box on most rows, so the table's own sizing counts the
   column too narrow and the counter would spill under the title without this. */
.submission-play-cell {
  white-space: nowrap;
  min-width: 11.5rem;
}

.submission-play-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Held open on every row, filled on the certified ones. An empty slot is what
   keeps every play button on the same vertical line down the table. The width
   is the catalog badge's own, drawn at the height the catalog draws it. */
.submission-badge-slot {
  flex: 0 0 3.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* What a song has been picked for. The cell is given room for the longer of
   the two pills so neither has to squash the columns beside it, and the pills
   themselves stack inside a box of their own rather than inside the cell. */
.submission-status-cell { min-width: 11.5rem; }

.submission-status-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.submission-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

/* The loudest thing on the page, and it should be: a show is the furthest a
   submission can go. It wears the site's own gold, the same tones the certified
   chip wears, so the brightest row is still in the palette. */
.submission-pill-show {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* Good news said more quietly, in the green the site keeps for it, so a song on
   both never argues with the pill beside it. */
.submission-pill-daily {
  border-color: var(--positive-line);
  background: var(--positive-soft);
  color: var(--positive);
}

/* The rocket itself, wherever the feature draws one: the mark on the Song
   Submissions list, the pill on the artist's own list, the button that spends a
   boost, the tick box on the form and the headline over the offer. It is larger
   than the words beside it, because it is the mark that says what this is. The
   line height is pinned at one so a bigger glyph cannot push the line it sits
   in taller than its neighbours. The clapperboard on the review's tick box is
   the same mark doing the same job, so it is sized by the same rule. */
.boost-rocket,
.review-clapper {
  display: inline-block;
  font-size: 1.2em;
  line-height: 1;
  vertical-align: -0.1em;
}

/* The microphone inside it is drawn, not typed, so it takes the span's size and
   the site's yellow rather than a platform's picture. */
.review-clapper {
  color: var(--accent-ink);
}

.review-clapper .review-mic,
.review-clapper .review-star {
  width: 1.1em;
  height: 1.1em;
  display: inline-block;
  vertical-align: -0.15em;
}

/* The song cell on the Song Submissions list wraps whichever control it holds
   and does nothing else to it. It is not a flex row and it sets no size: a link
   and a play button are laid out inside it exactly as they were before boosts
   existed, because the only job here is to be the box the marks below are
   positioned against. */
.song-cell {
  position: relative;
  display: inline-block;
}

/* Every mark the row carries, in one box that hangs off the left of the cell
   and is out of the flow entirely, so the marks take no width at all and cannot
   move the control they stand beside. That is what leaves every play button and
   every link exactly where it sat before boosts existed, on a marked row and on
   every other. `right: 100%` puts the box's right edge on the cell's left edge,
   and the margin past that is the whole of the space between the last mark and
   the control. What it hangs over to its left is empty room in the column
   beside it. A song can be both boosted and waiting on a review, so the marks
   are laid out in a line rather than each pinned to the same edge. */
.song-cell-marks {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-right: 0.3rem;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

/* A song holding the top of the Song Submissions list: the rocket, and nothing
   else.
   No pill, because the row is already a row of boxes and one more outline
   around one character would read as another control. No clock either, because
   how long is left is the artist's business and their own list counts it down.

   The artist's own list says the same thing in a pill with a clock in it, and
   the two are separate classes so neither can be restyled into the other by
   accident. */
.boost-mark {
  display: inline-flex;
  align-items: center;
  /* The rocket takes its own multiple of this, which puts the glyph at 1.5rem:
     well above the row's own text, so it is read from across the table rather
     than found, and still inside the 2.1rem the play control gives the row, so
     nothing here makes a boosted row taller than the rest. */
  font-size: 1.25rem;
  line-height: 1;
}

/* A song somebody bought a video review for. It is a button, because clicking
   it is how the review is written, but it is dressed as a mark: no fill and no
   outline, so the two marks read as one pair rather than as a glyph beside a
   control. The size matches the rocket beside it. */
.admin-review-mark,
/* And the star on a song somebody bought a rating for, the same mark doing the
   same job in the same box. */
.admin-rating-mark {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  /* The site's yellow, and larger than the rocket beside it. A review is the
     dearest thing on a song and the one the team owes somebody, so it is the
     mark found first. */
  color: var(--accent-ink);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.admin-review-mark .review-mic,
.admin-rating-mark .review-star {
  width: 1em;
  height: 1em;
}

.admin-review-mark:focus-visible,
.admin-rating-mark:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}

/* Answered. The mark stays, so a reviewer scanning the table can see which
   songs have been sent back, and it is faded so the ones still waiting are what
   the eye lands on. Nothing left to click either, which is the other half of
   why it is dimmer. */
.admin-review-mark-sent,
.admin-rating-mark-sent {
  opacity: 0.45;
  cursor: default;
}

/* The same answer on the artist's own list, where it is one pill among the
   pills that say what else became of the song, so it is dressed as one. */
.boost-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  padding: 0.16rem 0.5rem;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 0.74rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

/* Already a pill in a stack of pills, so it takes the padding the stack uses
   rather than drawing a second, tighter one of its own. */
.submission-pill.boost-chip { padding: 0.22rem 0.6rem; }

/* The digits, kept apart from the rocket so the clock can be rewritten every
   second without touching the glyph beside it. */
.boost-time { font-variant-numeric: tabular-nums; }

/* How long the song has left in front of the public. Every row carries one, so
   at rest it is the quietest pill on the row, in the same greys the opportunity
   pill uses. It is the one pill whose words can run to a sentence, so it is also
   the one allowed to wrap: on a phone the row is a stacked block and a line that
   would not break would push the whole page sideways. */
.submission-pill-catalog {
  max-width: 100%;
  border-color: var(--border-strong);
  background: var(--raised);
  color: var(--muted);
  white-space: normal;
  overflow-wrap: anywhere;
}

/* The last few days. A deadline rather than a note, so it takes the accent the
   boost chip takes; it is not filled, which leaves the filled gold to mean what
   it already means on this row. */
.submission-pill-catalog-soon {
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

/* And the day after, which is the one state on this row that is not good news.
   It takes the red the site already keeps for a thing that has not been done,
   the same pair the Not Edited status wears, rather than a colour of its own. */
.submission-pill-catalog-off {
  border-color: var(--danger-line);
  background: var(--danger-soft);
  color: var(--danger);
}

/* Where else the song went. Nobody chose it for anything, so it is the quietest
   of the three and stays in the greys. */
.submission-pill-opportunity {
  border-color: var(--border-strong);
  background: var(--raised);
  color: var(--muted);
}

/* A phone is not wide enough for six columns, and reading this list should not
   mean pushing the whole page sideways to reach the Remove button. Under this
   width the two dashboard lists stop being grids of columns: the headings go,
   and each row is laid out as one stacked block. The song leads, its title
   beside the play control with room to wrap; the genre and the date follow in
   the quiet ink; then the pills; then the buttons on a line of their own. Only
   these two lists are turned over here. The admin table is DataTables' own and
   is left as it is; the public catalog is DataTables as well and is turned over
   in the block below, which has its scroller to undo first. */
@media (max-width: 700px) {
  .picks-table,
  .picks-table tbody { display: block; }

  .picks-table thead { display: none; }

  /* The row is the block now, and the line under it is the one its own cells
     used to draw, so the list still reads as rows rather than as loose cards. */
  .picks-table tr {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    align-items: center;
    gap: 0.3rem 0.6rem;
    padding: 0.8rem 0;
    border-bottom: 1px solid var(--border);
  }

  .picks-table tbody tr:last-child { border-bottom: 0; }

  /* Any cell not placed below takes the whole width of the block. A title with
     no space in it long enough to pass the screen breaks rather than push the
     page, because there is no room here to give it. */
  .picks-table td {
    grid-column: 1 / -1;
    padding: 0;
    border-bottom: 0;
    overflow-wrap: anywhere;
  }

  /* Nothing lines up down a column any more, so the slot holds width only on
     the rows that have a badge to put in it. */
  .submission-badge-slot { flex: 0 0 auto; }

  /* Where each cell goes in the block. The columns lost their headings, so they
     are named here instead. */
  .picks-table td:first-child {
    /* the play control */
    grid-row: 1;
    grid-column: 1;
  }

  .submissions-table td:nth-child(2) {
    /* the title */
    grid-row: 1;
    grid-column: 2;
  }

  .submissions-table td:nth-child(3) {
    /* the genre */
    grid-row: 2;
    grid-column: 1;
  }

  .submissions-table td:nth-child(4) {
    /* the date it arrived */
    grid-row: 2;
    grid-column: 2;
  }

  .submissions-table .submission-status-cell { grid-row: 3; }
  .submissions-table .submission-action-cell { grid-row: 4; }

  /* The favorites list carries the artist as well, and names it before the
     title. Placing the cells rather than reordering the markup puts the title
     back beside the play control and the artist down with the rest. */
  .picks-table:not(.submissions-table) td:nth-child(2) {
    /* the artist */
    grid-row: 2;
  }

  .picks-table:not(.submissions-table) td:nth-child(3) {
    /* the title */
    grid-row: 1;
    grid-column: 2;
  }

  .picks-table:not(.submissions-table) td:nth-child(4) {
    /* the genre */
    grid-row: 3;
    grid-column: 1;
  }

  .picks-table:not(.submissions-table) td:nth-child(5) {
    /* the date it was saved */
    grid-row: 3;
    grid-column: 2;
  }

  .picks-table:not(.submissions-table) td:last-child { grid-row: 4; }

  /* Everything under the title is detail, said in the caption ink so the song
     keeps the block. */
  .submissions-table td:nth-child(3),
  .submissions-table td:nth-child(4),
  .picks-table:not(.submissions-table) td:nth-child(2),
  .picks-table:not(.submissions-table) td:nth-child(4),
  .picks-table:not(.submissions-table) td:nth-child(5) {
    color: var(--muted);
    font-size: 0.8rem;
  }

  /* A date says nothing on its own with no heading over it, so it carries the
     word its heading used. A row without a date is left with an empty cell. */
  .submissions-table td:nth-child(4):not(:empty)::before { content: 'Received '; }
  .picks-table:not(.submissions-table) td:nth-child(5):not(:empty)::before { content: 'Picked '; }

  /* The pills run along the block and wrap where they must, rather than
     standing in a column as wide as the longest of them. */
  .submissions-table .submission-status-cell { min-width: 0; }

  .submission-status-stack {
    flex-direction: row;
    flex-wrap: wrap;
  }

  /* The one pill carrying words somebody typed, and so the only one that can be
     longer than the screen. Here it wraps rather than push the page sideways. */
  .submission-pill-opportunity { white-space: normal; }

  /* The buttons take a line of their own, at a size a thumb can hit. The card's
     own padding is what keeps them off the edge of the screen. */
  .picks-table td:last-child {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.2rem;
  }

  .picks-table td:last-child .button {
    flex: 1 1 auto;
    padding: 0.55rem 0.9rem;
  }
}

/* The public catalog on a phone. It is a table of whichever columns an
   administrator made public, held in a sideways scroller, so at this width two
   or three of them show and the play control is off the edge of the screen.
   Under 700px the scroller is undone and each row is laid out as one stacked
   block: the DOC Select badge beside the name the row is opened for, the rest
   of the fields under it in the quiet ink, and the song with the favorites
   toggle on the last line. Because the columns are chosen rather than fixed,
   nothing here is placed by counting cells: every cell says what it holds in
   data-card-role, and the block is laid out from that. The sorting headings go
   with the scroller, which is accepted: the filters above the table are what a
   reader narrows the catalog with here. */
@media (max-width: 700px) {
  /* DataTables splits a scrolling table in two, sizes the pieces itself, and
     writes those widths and that overflow inline again on every draw. An inline
     style loses only to !important, so this is where the stylesheet has to
     raise its voice to take the scrolling back. */
  #csvTable_wrapper .dt-scroll,
  #csvTable_wrapper .dt-scroll-body {
    width: auto !important;
    max-width: 100% !important;
    overflow: visible !important;
  }

  /* In that split the whole header is a table of its own, and there are no
     columns left for it to head. */
  #csvTable_wrapper .dt-scroll-head { display: none !important; }

  /* The rows are blocks now, so the table stops being one. Its width was
     measured for the columns and is written inline, hence the loud word again.
     DataTables keeps a copy of the headings inside the body to size those
     columns by, and a colgroup carrying a width for each of them; neither has
     anything left to do here. */
  #csvTable {
    display: block;
    width: auto !important;
  }

  #csvTable thead,
  #csvTable colgroup { display: none; }

  #csvTable tbody { display: block; }

  /* The row is the block, and the line under it is the one its own cells used
     to draw. The block is held off the panel's rounded edge by its own padding,
     because the cells have none left. */
  #csvTable tbody tr {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
    align-items: center;
    gap: 0.35rem 0;
    padding: 0.8rem 0.75rem;
    border-bottom: 1px solid var(--border);
  }

  #csvTable tbody tr:last-child { border-bottom: 0; }

  /* Any cell not placed below, the empty-table message among them, takes the
     whole width of the block. The table is marked nowrap for the wide view,
     where a column can be given more room; here a value wraps instead, and one
     with nowhere in it to break breaks anyway rather than push the page. */
  #csvTable tbody td {
    grid-column: 1 / -1;
    padding: 0 !important;
    border-top: 0 !important;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  /* The first line: the badge, only as wide as itself, and the name the row is
     opened for, which is a button in the full catalog and a blurred stand-in in
     the redacted one. The gap between the two is the badge's own margin rather
     than the grid's, so a row with no badge starts its name at the same edge as
     everything under it. */
  #csvTable td[data-card-role='badge'] {
    order: 1;
    grid-column: 1;
    width: auto;
    min-width: 0;
    margin-right: 0.6rem;
  }

  #csvTable td[data-card-role='badge']:empty { display: none; }

  #csvTable td[data-card-role='artist'] {
    order: 2;
    grid-column: 2 / -1;
    font-weight: 700;
  }

  /* Then everything else the row carries, a line each in the caption ink, with
     the song type among them rather than in a seat of its own. A value with no
     heading over it reads as a loose word, so each line is given the name of
     the column it came from, in the small spaced capitals those headings were
     set in. A column with nothing in it for this row says nothing at all. */
  #csvTable td[data-card-role='field'],
  #csvTable td[data-card-role='songtype'] {
    order: 3;
    grid-column: 1 / -1;
    font-size: 0.85rem;
    color: var(--muted);
  }

  #csvTable td[data-card-role='field']:empty,
  #csvTable td[data-card-role='songtype']:empty { display: none; }

  #csvTable td[data-card-label]::before {
    content: attr(data-card-label);
    margin-right: 0.4rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--muted);
  }

  /* The last line: the song across the block, whether it is a player, an
     outbound link or the lock, with the favorites toggle held at the end of it.
     A reader with no favorites to save has no toggle, and the song keeps the
     line to itself. */
  #csvTable td[data-card-role='song'] {
    order: 4;
    grid-column: 1 / 3;
  }

  #csvTable td[data-card-role='pick'] {
    order: 5;
    grid-column: 3;
    justify-self: end;
    width: auto;
    min-width: 0;
    margin-left: 0.6rem;
  }
}

.api-table td small {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.76rem;
}

/* What the form control has to be. It is the one instruction in the rules
   column that changes the markup, so it is the one that carries the accent. */
.api-control { color: var(--accent-ink); }

/* The keys handed to an outside developer. Two fields, not the three the
   account form has, so the row is set out for this card. */
#createApiKeyForm .create-plan-fields {
  grid-template-columns: minmax(12rem, 1fr) minmax(16rem, 2fr);
}

.api-key-list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.8rem;
}

.api-key-row {
  display: grid;
  gap: 0.35rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--positive);
  border-radius: var(--radius-card);
  padding: 0.75rem 0.9rem;
  background: var(--panel-2);
}

.api-key-row.revoked {
  border-left-color: var(--danger);
  opacity: 0.65;
}

.api-key-identity {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.api-key-identity strong { color: var(--text); }

.api-key-identity code {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted);
}

.api-key-tag {
  border: 1px solid var(--danger-line);
  border-radius: var(--radius-pill);
  padding: 0.14rem 0.45rem;
  font-size: 0.72rem;
  color: var(--danger);
  background: var(--danger-soft);
}

.api-key-meta {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.api-key-origins {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.35rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}

.api-key-origins input {
  width: 100%;
  max-width: 34rem;
  height: 2.2rem;
  padding: 0 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
  text-transform: none;
  letter-spacing: normal;
}

.api-key-origins input:focus {
  outline: none;
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-line);
}

/* Shown once, at the moment the key is made. Nothing stores it in the clear, so
   this is the only chance to copy it. */
.new-api-key {
  margin: 0.9rem 0 0.3rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-card);
  background: var(--accent-soft);
}

.new-api-key-title {
  margin: 0 0 0.5rem;
  font-weight: 600;
  color: var(--accent-ink);
}

.new-api-key-value {
  display: block;
  margin-bottom: 0.7rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
  user-select: all;
}

/* An entry already heard, sitting at the bottom of the table. */
table.dataTable > tbody > tr.already-listened-row > * {
  opacity: 0.5;
}

/* Guarded the way the row highlight is, and for the same reason: a touch is
   not a hover, and a heard row going loud under a scrolling thumb reads as the
   page changing its mind about it. */
@media (hover: hover) {
  table.dataTable > tbody > tr.already-listened-row:hover > * {
    opacity: 1;
  }
}

/* A song the public catalog has dropped. The admin table keeps every row for
   good, so the red edge is the only thing saying this one is off the catalog.
   A table row cannot carry a border of its own, so the cells draw it: top and
   bottom across the row, and the ends closed by the first and last cell. An
   outline rather than a fill, because a whole table can be in this state and
   still has to be read. */
table.dataTable > tbody > tr.catalog-expired-row > * {
  border-top: 1px solid var(--danger-line) !important;
  border-bottom: 1px solid var(--danger-line) !important;
}

table.dataTable > tbody > tr.catalog-expired-row > *:first-child {
  border-left: 1px solid var(--danger-line) !important;
}

table.dataTable > tbody > tr.catalog-expired-row > *:last-child {
  border-right: 1px solid var(--danger-line) !important;
}

/* The message center, shared by the dashboard's card and the admin one. A line
   sits on the side of whoever wrote it, read from the reader's own chair: your
   own words on the right, the other end of the conversation on the left. */
.message-thread {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-height: 320px;
  /* Set below for the pages the conversation is the whole point of. */
  overflow-y: auto;
  padding: 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  /* Recessed like a field, so the conversation reads as a place words go into
     rather than another block of the card. */
  background: var(--field);
}

/* A page given over to the conversation has room for more of it than a card
   did, so the thread grows to most of the window rather than to a fixed strip. */
.message-thread-tall { max-height: 60vh; }

.message-line {
  max-width: 75%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}

.message-own {
  align-self: flex-end;
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.message-house {
  align-self: flex-start;
  background: var(--panel-2);
}

.message-body {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.5;
  /* A message is typed, not written as markup, so its own line breaks are kept
     and a long unbroken address wraps rather than widening the thread. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.message-time {
  display: block;
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.75rem;
}

/* The certified mark, standing beside a name rather than over a song. It is cut
   to the height of the line it signs and dropped onto the text, so the signature
   reads as one line rather than as a picture with words after it. */
.message-verified-badge {
  height: 1em;
  width: auto;
  margin-left: 0.3rem;
  vertical-align: -0.15em;
}

.message-empty {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.message-subheading {
  margin: 0 0 0.6rem;
  font-size: 0.95rem;
}

/* The open conversation's name with its way out beside it. */
.thread-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.thread-heading-row .message-subheading { margin-bottom: 0; }

.thread-heading-row { margin-bottom: 0.6rem; }

.message-columns {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
}

.message-column { min-width: 0; }

.message-composer {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.8rem;
}

.message-input {
  display: block;
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  line-height: 1.5;
  resize: vertical;
}

.message-input::placeholder { color: var(--muted); }

.message-input:focus {
  outline: 2px solid var(--accent-line);
  outline-offset: 1px;
  border-color: var(--accent-ink);
}

.announcement-audience { width: auto; min-width: 10rem; }

/* A count, not an action, so it is a pill. */
.unread-pill {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 0.78rem;
  font-weight: 700;
}

/* The count riding on a link in the header, beside the word rather than under
   it. */
.button .unread-pill { margin-left: 0.4rem; }

.conversation-list, .announcement-list {
  display: flex;
  flex-direction: column;
}

.conversation-row, .announcement-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
}

.conversation-row:last-child, .announcement-row:last-child { border-bottom: 0; }

.conversation-detail {
  display: grid;
  gap: 0.2rem;
  min-width: 0;
}

.conversation-meta, .conversation-preview {
  color: var(--muted);
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

.conversation-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.announcement-body {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A notice is read top down in the order it was written: what it is called,
   when it was posted, the picture, and then the words. So the heading and the
   stamp both carry a margin below rather than above. */
.announcement-title {
  margin: 0 0 0.25rem;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.3;
}

.announcement-meta {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--muted);
  font-size: 0.75rem;
}

/* The way from a notice to the form it was written about. It wears the site's
   own button, the same as every other way to that form, and asks only for the
   room to sit clear of the words above it. */
.announcement-link { margin-top: 0.6rem; }

/* A picture over the words, on a notice and on an opportunity card. The two are
   the same shape on purpose: an opportunity's picture is often the very one its
   news post went out with. Shown whole at its own proportions, never cropped:
   the picture is made as one piece of art, and a band cut from it loses the
   words drawn into it. */
.announcement-banner,
.opportunity-page-image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  margin-bottom: 0.6rem;
}

/* A video where the banner would have been, on a notice that carries one. It
   takes the banner's width and corners so the two read as the same block, and
   is given the shape a player is made in: a frame holds no picture of its own
   to be measured from, so the height has to be said here. */
.announcement-video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: none;
  border-radius: var(--radius-card);
  margin-bottom: 0.6rem;
}

/* The file control in the composer, whose grid already gives the row its room. */
.announcement-banner-field { align-items: center; }
.announcement-banner-field input[type="file"] { margin: 0; }

/* The video controls stand in one row the way the picture controls do, with the
   address first and the two questions about how it starts beside it. The field
   takes the room left over rather than the whole row, the same way the audience
   select does, so the two tick boxes keep their place next to it. */
.announcement-video-field { align-items: center; }
.announcement-video-field input[type="url"] {
  width: auto;
  min-width: 16rem;
  flex: 1;
}

/* While a video is linked the picture controls are dim rather than gone: a post
   says what it will carry, and a control that vanished would leave the writer
   wondering where their file went. */
.announcement-banner-field:has(input[type="file"]:disabled),
.opportunity-image-toggle:has(input[type="checkbox"]:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The News card on the member's dashboard. It wears the lit panel the catalog's
   search card wears, defined once beside that card, so word from the team is
   the first thing the eye lands on. Only the heading is added here: the edge
   and the wash come from there.

   What is inside it does not repeat the wash. A card lit yellow with lit yellow
   blocks in it has nothing left to draw the eye with, so a notice is a plain
   grey block: the fill is the quiet panel every block on the site is built
   from. The edge is the exception, and is drawn in the accent so the block
   reads as part of the card it sits in rather than as something dropped into
   it, with the rail down its left in the full yellow. */
.announcement-line {
  margin: 0 0 0.6rem;
  padding: 0.8rem 1rem;
  background: var(--panel);
  border: 1px solid var(--accent-line);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-card);
}

.announcement-line:last-child { margin-bottom: 0; }

.announcement-line .announcement-title {
  color: var(--accent-ink);
  font-size: 1.15rem;
}

.announcement-line .announcement-body {
  color: var(--text);
  font-size: 1.05rem;
  line-height: 1.5;
  margin: 0;
}

/* A card title with one quiet way out beside it, level with the words rather
   than under them. The heading keeps the space below it that every card title
   has, so the row costs the card nothing it did not already spend. */
.card-heading-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

/* What the team is asking for a song to, on the artist's own dashboard. One
   entry per row, read as a name, what it is, and the way in, so the card is the
   list rather than a heading over one. */
.opportunity-list {
  display: grid;
  gap: 0.6rem;
}

.opportunity-row {
  display: grid;
  justify-items: start;
  gap: 0.4rem;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-card);
  background: var(--panel-2);
}

.opportunity-name {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.3;
}

/* The description on the page that reads one opportunity in full, where the
   blank lines the team typed become paragraphs of their own and need the space
   between them that a card gives its own paragraphs. */
.opportunity-body {
  display: grid;
  gap: 0.6rem;
  margin: 0 0 0.9rem;
}

/* Written as paragraphs and read as paragraphs. The text is set with
   textContent everywhere it appears, so the breaks the team typed are kept by
   the stylesheet rather than by turning the words into markup. */
.opportunity-description {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* The editor an opportunity card opens, which lives inside the bordered block
   rather than beside it. That block is a flex row, so the editor is given a
   line of its own under the head it belongs to; the rule along its top is then
   the divider between the two, the same as on a plan row. */
.account-request .plan-row-edit {
  flex-basis: 100%;
  min-width: 0;
}

/* The opportunity form reads as one group of questions rather than as five
   sections stacked up, so every block in it takes the same short step from the
   one above it. The step is set once here rather than by each row, because the
   rows are shared with taller forms that want their own room. */
#createOpportunityForm > * + * { margin-top: 0.5rem; }

/* The editor on a card is the same form asked again, and it is a grid, so the
   same step is its gap. */
#opportunityList .plan-row-edit { gap: 0.5rem; }

/* Nothing inside either one carries a margin of its own, so the step above is
   the whole of the space between one question and the next. */
#createOpportunityForm .create-plan-fields label,
#createOpportunityForm .opportunity-description-field,
#createOpportunityForm .opportunity-image-toggle,
#createOpportunityForm .create-plan-actions,
#opportunityList .plan-row-edit .create-plan-fields label,
#opportunityList .plan-row-edit .opportunity-description-field,
#opportunityList .plan-row-edit .opportunity-image-toggle,
#opportunityList .plan-row-edit .entry-image,
#opportunityList .plan-row-edit .small-actions {
  margin-top: 0;
  margin-bottom: 0;
}

/* The buttons stand a step further off the tick box above them, so the box
   reads with the fields it pairs rather than with the button that submits
   them. The editor's step is smaller because its grid gap already gives it
   part of the room. */
#createOpportunityForm .create-plan-actions { margin-top: 0.9rem; }
#opportunityList .plan-row-edit .small-actions { margin-top: 0.4rem; }

/* The same question in the news composer, where it stands in a row of its own
   beside the note about a missing picture. The row already carries the space
   the composer gives its fields, so the step this class brings elsewhere would
   be a second one. */
#announcementOpportunityImageField .opportunity-image-toggle {
  margin-top: 0;
  margin-bottom: 0;
}

/* The tick box that pairs the two pictures, on the create form, inside the
   editor on a card, and in the news composer. It is a question rather than a
   field, so it is set in the quieter text the notes around it use. */
.opportunity-image-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.55rem;
  color: var(--muted);
  font-size: 0.95rem;
}

/* A box, not a field. Every input on the site is stretched to its row and given
   field padding, which turns a tick box into a wide empty strip with the glyph
   adrift in it, so this is sized back the way the form's own tick box is. */
.opportunity-image-toggle input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
}

/* When an opportunity runs, in the accent and a step larger than the words
   around it: the window is the fact an artist checks first, on the dashboard
   card and the opportunities page alike. */
.opportunity-dates {
  margin: 0;
  color: var(--accent-ink);
  font-size: 1.05rem;
}

/* What has already run, under everything still to come. The heading takes the
   card measure so its line starts where the cards below it start. */
.past-opportunities-heading {
  width: min(900px, calc(100% - 2rem));
  margin: 1.5rem auto 0.6rem;
}

/* The same rows on the admin side, each leading to the page that lists what
   came in for it. */
.opportunity-admin-list {
  display: grid;
  gap: 0.6rem;
}

.opportunity-admin-row {
  display: grid;
  gap: 0.5rem;
}

.opportunity-admin-description {
  margin: 0.2rem 0 0;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* The description is written where a news post is written rather than in the
   row of short fields above it, so it takes the full width of the form. */
.opportunity-description-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.8rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-transform: uppercase;
}

/* The five marks stand under the play button rather than beside it: in a row
   they would push the song itself off a narrow screen, and the point of the page
   is that a song can be worked through without leaving it. */
.opportunity-side {
  flex-direction: column;
  align-items: flex-end;
  gap: 0.6rem;
}

.opportunity-flags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem 0.9rem;
}

/* The admin table names each of these in a column heading, which this list has
   nowhere to put, so every tick carries its own name in the same words. */
.opportunity-flag { text-align: center; }

.opportunity-flag-label {
  display: block;
  margin-bottom: 0.2rem;
  color: var(--muted);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

@media (max-width: 700px) {
  .opportunity-side {
    align-items: flex-start;
    width: 100%;
  }

  .opportunity-flags { justify-content: flex-start; }
}

/* The control a team member opens a new conversation from. It sits above the
   list rather than in it, because it is how a conversation that is not there yet
   is reached. */
.start-conversation { margin-bottom: 1rem; }

/* What an answer from this chair is signed as. A note about the reader rather
   than part of the list, so it reads at the size of a caption and in the quiet
   ink every caption on this site uses. */
.admin-signature {
  align-items: baseline;
  margin-bottom: 0.6rem;
}

.admin-signature-name {
  color: var(--muted);
  font-size: 0.88rem;
}

/* One button per conversation, shown to a member only when there is more than
   one to choose between. */
.thread-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.8rem;
}

/* The conversation on screen, marked on its own button so the row says which one
   is open without a line of text saying it. */
.thread-picker .thread-picker-current {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* The account a team member is about to write to. A native select would be the
   obvious control, but an A&R is read by two answers at once, their own name and
   the company they are from, and an option can hold only one run of text, so the
   list is drawn instead. Closed, it is the field it replaces. */
.account-picker {
  position: relative;
  min-width: 16rem;
}

.account-picker-field {
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
}

.account-picker-field:hover:not(:disabled) { border-color: var(--border-strong); }

.account-picker-field:focus {
  border-color: var(--accent-ink);
  outline: 2px solid var(--accent-line);
  outline-offset: 1px;
}

/* Open, it hangs under the field rather than pushing the page apart, which is
   the one thing here that genuinely floats above what it covers. */
.account-picker-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.3rem);
  left: 0;
  right: 0;
  max-height: 18rem;
  overflow-y: auto;
  padding: 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--panel);
  box-shadow: var(--shadow-raised);
}

.account-picker-group {
  margin: 0.4rem 0 0.2rem;
  padding: 0 0.4rem;
  color: var(--muted);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.account-picker-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}

.account-picker-row:hover { background: var(--raised); }

/* Who they are from, in the house yellow, so the person and the company are told
   apart at a glance rather than read as one long name. */
.account-picker-company {
  color: var(--accent-ink);
  font-size: 0.82rem;
}

/* What an artist gets, under the greeting on their dashboard. It stands in for
   the plain welcome line, so it keeps that line's place and its spacing. */
.included { margin: 0 0 0.35rem; }

/* The line over the five rows, in the accent: the one yellow line over rows
   that keep the page's own text colour, so the label carries the pitch and the
   sentences stay readable. */
.included-heading {
  margin: 0 0 0.7rem;
  font-weight: 600;
  font-size: 1.35rem;
  color: var(--accent-ink);
}

.included-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.6rem;
  font-size: 1.15rem;
}

/* Icon on the left, sentence on the right. The text is a flexible cell of its
   own, so a line that runs past the card's width wraps under itself and keeps
   its left edge on the other rows' rather than sliding under the icon. */
.included-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.included-row > span:last-child {
  flex: 1 1 auto;
  min-width: 0;
}

/* The column that lines the sentences up. It is a fixed width, so a wide mark
   and a narrow one start their text at the same place. The marks are set a
   little larger than the words they stand beside, and the box is centred on the
   first line of the sentence rather than on the whole of it. */
.included-icon {
  flex: 0 0 2.8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2.3rem;
  color: var(--accent-ink);
}

/* The drawn marks take the colour above, which is what keeps them and the
   badges beside them in one family. */
.included-icon svg {
  width: 2em;
  height: 2em;
}

/* The picture mark is sized by height alone, so it keeps its own shape inside
   the column. */
.included-badge {
  max-width: 100%;
  height: 2.2em;
  width: auto;
  object-fit: contain;
}

/* Narrow enough that a sentence takes most of the card. The column closes up a
   little so the words get the room back. */
@media (max-width: 700px) {
  .included-row { gap: 0.5rem; }
  .included-icon { flex-basis: 2.4rem; }
}

/* The picture an administrator can put in place of those five lines. It takes
   the card's content width and keeps the spacing the list had, so the greeting
   over it sits exactly where it always did. Shown whole at its own proportions,
   never cropped, so the art reads as it was made. */
.welcome-banner {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  margin: 1.1rem 0 0.35rem;
}

/* With no heading over it, the picture leads the card, and the gap the
   heading would have stood in goes with it. */
h2[hidden] + .welcome-banner { margin-top: 0; }

/* The picture an administrator can put under the recent opportunities heading.
   It takes the card's content width and is rounded like the panels around it,
   with air on both sides: the heading and its button stand clear above it, and
   there is room under it before the first opportunity. Shown whole at its own
   proportions, never cropped, so the art reads as it was made. */
.dashboard-opportunities-image {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  margin: 0.6rem 0 0.9rem;
}

/* The heading between that picture and the line under it, on the dashboard and
   on the opportunities page both. It is written as an h3 because it stands under
   a heading already, but it reads in the voice the opportunity names read in
   further down the page: those are plain card headings, so the size and the ink
   are named here to match them. The weight and the line come from the rule every
   heading on the site shares. The margins keep it against the picture above and
   the line below. */
.welcome-banner-subtitle {
  margin: 0 0 0.35rem;
  font-size: 1.5rem;
  color: var(--text);
  /* An administrator's words, which may hold a run the page cannot break at a
     space, so the line is allowed to break anywhere rather than leave the
     card. The description below is given the same permission for the same
     reason. */
  overflow-wrap: anywhere;
}

/* The line under that picture, in the list heading's rhythm. */
.welcome-banner-text {
  margin: 0 0 0.35rem;
  overflow-wrap: anywhere;
}

/* The title an A&R reads under the greeting. It is a second heading of the same
   size, so only the ink says which of the two is the site speaking: the accent
   marks it as this level's own rather than the page's. The bottom margin keeps
   it against the line it heads instead of floating between the two. The words
   are an administrator's, so a run the page cannot break at a space is allowed
   to break anywhere rather than leave the card. */
.ar-welcome-title {
  margin: 0 0 0.35rem;
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* The seam between the two greetings on the admin welcome card. The A&R pair
   above it and the artist area below it are edited on the one card, so the line
   and the air around it are what say where one ends and the other begins. */
.welcome-artist-heading {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

/* The same five lines as an administrator edits them: the mark on the left, the
   words and the buttons on the right. */
.welcome-line-row {
  display: flex;
  align-items: flex-start;
  gap: 0.8rem;
}

.welcome-line-mark {
  flex: 0 0 3.4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.4rem;
}

/* Sized by height alone, the way the dashboard sizes the mark it stands for, so
   an upload of any size takes one row's worth of the panel and no more. */
.welcome-line-preview {
  max-width: 100%;
  height: 2.4rem;
  width: auto;
  object-fit: contain;
}

.welcome-line-fields {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 0.5rem;
}

.welcome-line-fields input[type="text"] { width: 100%; }

/* The tick box that decides which of the two an artist reads, standing beside
   the button that opens the lines. It is a question rather than a field, so it
   is set in the quieter text the notes around it use, and the box is sized back
   the way every other tick box on the page is. */
.welcome-image-ticker {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.95rem;
}

.welcome-image-ticker input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
}

/* The switch beside it, for a control that turns a whole feature off rather than
   choosing between two ways of drawing one card. A slider reads as a state the
   site is in; a tick box reads as an option on a form, and the difference is
   worth the few rules. The checkbox underneath is still the control, so nothing
   about the keyboard or a screen reader changes: its appearance is taken away
   and the track and knob are drawn in its place. */
.admin-switch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.95rem;
}

.switch-control {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 2.4rem;
  height: 1.3rem;
  margin: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 0;
  background: var(--panel-2);
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease;
}

/* The knob, and the only thing that moves. Off it sits left in the quiet grey
   the track is drawn against; on it slides right and takes the ink that stands
   on the accent, so the state reads from the colour as well as the position. */
.switch-control::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0.15rem;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  background: var(--muted);
  transform: translateY(-50%);
  transition: transform 140ms ease, background-color 140ms ease;
}

.switch-control:checked {
  border-color: var(--accent-line);
  background: var(--accent);
}

.switch-control:checked::before {
  background: var(--on-accent);
  transform: translateY(-50%) translateX(1.1rem);
}

/* The ring every other control on the site wears, so a switch is no harder to
   find with a keyboard than a link is. */
.switch-control:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 3px;
}

.switch-control:disabled { cursor: progress; }

@media (prefers-reduced-motion: reduce) {
  .switch-control,
  .switch-control::before { transition: none; }
}

/* Terms of service. The tick box and its sentence read as one line, and the
   trigger inside that sentence is underlined so it reads as the link it behaves
   like rather than as the button it has to be to open something over this page. */
.terms-agree {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.terms-agree input[type="checkbox"] {
  flex: none;
  width: 1rem;
  height: 1rem;
  margin: 0.15rem 0 0;
  padding: 0;
  accent-color: var(--accent);
}

.terms-agree .link-button {
  font-size: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2rem;
}

/* The document itself, on the card's own surface. It is the only live thing
   while it is open, so it is held to a readable width and to a height that
   leaves the page showing around it, and the words scroll inside rather than
   the page scrolling behind. Line breaks are the only shape a pasted document
   has, so they are kept, and a legal address with no spaces in it wraps rather
   than pushing the panel wider than the screen. */
.terms-dialog {
  width: min(640px, calc(100% - 2rem));
  max-width: calc(100vw - 2rem);
  max-height: 80vh;
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  padding: 1.5rem;
  background: var(--panel);
  color: var(--text);
}

.terms-dialog::backdrop { background: var(--scrim); }

.terms-dialog-text {
  max-height: 60vh;
  margin: 0 0 1rem;
  overflow-y: auto;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The agreement itself, at the foot of the document rather than on the form, so
   it is answered where it was read. It takes the row the form box takes, in the
   page's own ink because it is the question rather than a note about one, and
   the line under it says why it is shut. */
.terms-dialog-agree {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0 0 0.4rem;
  color: var(--text);
  font-size: 0.9rem;
}

.terms-dialog-agree input[type="checkbox"] {
  flex: none;
  width: 1rem;
  height: 1rem;
  margin: 0.15rem 0 0;
  padding: 0;
  accent-color: var(--accent);
}

.terms-scroll-hint {
  margin: 0 0 1rem;
  font-size: 0.85rem;
}

/* The one sentence on a finished sign-up that has to be read rather than
   skimmed: what is waiting in their inbox, and what they cannot do until they
   answer it. Said in the ink the site keeps for what it wants noticed, because
   quiet paragraph grey is what a person skips. */
.signup-verify-note {
  color: var(--accent-ink);
}

/* The chip on the artist's own list for a song a review was bought for, in the
   two states the artist can do anything about. It is dressed as the boost chip
   is, because it says the same kind of thing: something was paid for and it is
   happening. Once the review is back the chip is a link, so the way to it is the
   thing that says it arrived. */
.review-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
  text-decoration: none;
}

a.review-chip:hover,
a.review-chip:focus-visible {
  border-color: var(--accent);
  color: var(--accent-hover);
}

/* The video on a review, under the words that go with it. It takes the width of
   the message and no more, and it sits on the page's own darkest surface so the
   letterboxing around a tall video reads as part of the page rather than as a
   band of some other grey. */
.message-video {
  display: block;
  width: 100%;
  max-width: 100%;
  margin-top: 0.5rem;
  border-radius: var(--radius-card);
  background: var(--bg);
}

/* Where a review is written, floating over the table the way the note editor
   does and fixed to the viewport for the same reason: the table scrolls inside
   a box that clips whatever hangs out of it. */
.review-editor {
  position: fixed;
  /* Centered in the viewport, because the panel opens on a page load rather
     than under a pointer, and a row low on the list would otherwise push it
     off the bottom of the screen. Taller than the window, it scrolls itself. */
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  z-index: 60;
  width: min(420px, calc(100vw - 16px));
  padding: 0.8rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-card);
  background: var(--panel-2);
  box-shadow: var(--shadow-raised);
}

/* Which song is being answered, at the top, because the panel is opened from a
   mark rather than from a row that says so. */
.review-editor-song {
  margin: 0 0 0.6rem;
  color: var(--accent-ink);
  font-size: 0.85rem;
  font-weight: 700;
}

/* What the panel is for, said above the song on a rating. The review's panel
   needs no line: a video field says what it is. */
.review-editor-title {
  margin: 0 0 0.3rem;
  font-size: 0.95rem;
  font-weight: 700;
}

.review-editor-field {
  display: block;
  margin-bottom: 0.6rem;
  color: var(--muted);
  font-size: 0.75rem;
}

/* One score and the five marks to pick between. A box apiece, so a panel of
   five reads as five questions rather than as a wall of buttons. */
.review-editor-score {
  margin: 0 0 0.45rem;
  padding: 0.25rem 0.6rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--field);
}

/* The name of the mark is what DOC is answering, so it is the loudest thing in
   the row: the site's yellow, at the size of a field label rather than a hint. */
.review-editor-score legend {
  padding: 0 0.3rem;
  color: var(--accent-ink);
  font-size: 1rem;
  font-weight: 700;
}

.review-editor-marks {
  display: flex;
  gap: 0.75rem;
}

.review-editor-mark {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--text);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.review-editor-file {
  display: block;
  width: 100%;
  margin-top: 0.25rem;
  color: var(--text);
  font-size: 0.82rem;
}

.review-editor-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  color: var(--muted);
  font-size: 0.72rem;
}

.review-editor-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.review-editor-status {
  margin: 0.5rem 0 0;
  color: var(--muted);
  font-size: 0.78rem;
}

.review-editor-status.error { color: var(--danger); }

/* The video reviews page. Its list is the opportunity page's list, so the row,
   the player and the way an answered row fades back all come from there. What is
   new is the pill saying where a row stands, and the count on the link that
   opens the page. */
.review-pill {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}

/* Which of the two a row on the list is, beside the pill that says where it
   stands. Sized like the mark on the admin table, for the same reason. */
.review-kind {
  display: inline-flex;
  align-items: center;
  color: var(--accent-ink);
  font-size: 1.6rem;
  line-height: 1;
}

.review-kind .review-mic,
.review-kind .review-star {
  width: 1em;
  height: 1em;
}

/* Answered, said in the green the site keeps for good news. */
.review-pill-sent {
  border-color: var(--positive-line);
  background: var(--positive-soft);
  color: var(--positive);
}

/* What is still owed, on the link that opens the list of it. Away entirely at
   zero, which is the dashboard saying there is nothing to go and do. */
.admin-link-count {
  margin-left: 0.4rem;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
}

/* The marks on a review, read in the message that carried them: one line per
   mark, the way the words would have said it. */
/* Two columns, the names and the marks, so every first microphone starts on
   the same line down the message however long the name beside it. */
.review-scores {
  margin-top: 0.35rem;
  display: grid;
  grid-template-columns: max-content auto;
  align-items: center;
  gap: 0.2rem 0.6rem;
}

/* Each line hands its two halves straight to the grid, so the columns line up
   across every row rather than inside each one. */
.review-score {
  margin: 0;
  display: contents;
  font-size: 0.95rem;
}

.review-score-title {
  color: var(--accent-ink);
  font-size: 1.05rem;
  font-weight: 600;
}

/* Spaced so five can be counted, and a touch larger than the name beside them. */
.review-score-marks {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  color: var(--accent-ink);
  white-space: nowrap;
}

/* The sentence written for a mark, across both columns so it sits under the
   name and the microphones together rather than beside either. */
.review-score-line {
  grid-column: 1 / -1;
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0 0 0.25rem;
}

.review-mic,
/* The rating's star, at the microphone's size everywhere it stands in for one. */
.review-star {
  width: 1.5rem;
  height: 1.5rem;
  display: block;
}




/* The five categories, one under the other. Five short names across a three
   column grid left a ragged second row, and a list reads as the order the
   artist will see them in. */
.video-review-criteria {
  grid-template-columns: minmax(0, 28rem);
}

/* One category is its name and the five sentences under it, kept together so
   the block reads as the one question. */
.video-review-criterion {
  display: grid;
  gap: 0.4rem;
}

/* Folded by default, so five categories with five sentences each do not stand
   the height of the card. The bar names the category and the word it goes by,
   in the same quiet accent the other folding controls use. */
.video-review-criterion summary {
  width: fit-content;
  border-radius: var(--radius-control);
  color: var(--accent-ink);
  font-weight: 600;
  cursor: pointer;
}

.video-review-criterion summary:hover {
  color: var(--accent-hover);
}

.video-review-criterion-name {
  color: var(--muted);
  font-weight: 400;
}

/* The sentences are indented under the name they belong to, which is what says
   they are not five more names. */
.video-review-lines {
  display: grid;
  gap: 0.3rem;
  padding-left: 0.9rem;
}

/* The team's way to the review list, standing between two cards with the same
   breathing room the cards keep between themselves. */
.team-video-reviews-row {
  width: min(1550px, calc(100% - 2rem));
  margin: 0 auto 1rem;
}

.team-video-reviews-row .button {
  padding: 0.8rem 1.4rem;
  font-size: 1.05rem;
}

/* A song entered into an opportunity, on the Song Submissions list: its name
   in the accent, so an entry is told from a song that only came to the catalog
   at a glance. A link inside keeps the same colour. */
.song-title-opportunity,
.song-title-opportunity a {
  color: var(--accent-ink);
  font-weight: 600;
}

/* The one line under the table heading that says what the yellow names are. */
.admin-table-subtitle {
  margin: -0.4rem 0 0.9rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* --- Rendering the table without DataTables -------------------------------
   The catalog table is written by the server now. Three things the script used
   to do have to be said here instead; nothing else in this sheet changed. */

/* DataTables split a wide table in two, an outer scroller holding a header
   table and a body table, and wrote the scrolling on the body as an inline
   style on every draw. One table scrolls here, so the scroller is the box that
   scrolls, which keeps the bar under the table rather than under the count
   line. */
.dt-scroll {
  max-width: 100%;
  overflow-x: auto;
}

/* A heading is a link now, because sorting is asked of the server. It has to go
   on reading as a heading: the row's own colour, no underline, and the hover
   colour the cell around it already changes to. */
table.dataTable thead a.dt-column-title,
table.dataTable thead a.dt-column-title:hover {
  color: inherit;
  text-decoration: none;
}

/* Logging out is a posted form rather than a script call, so the form stands
   where the button used to stand and takes no room of its own. */
.public-account-actions > form,
.nav-actions > form {
  display: contents;
}

/* --- Two admin column widths --------------------------------------------
   A deliberate change from the live site, asked for and approved before it was
   made. The old table let DataTables measure every column in the browser, so
   the song name and the artist name ended up whatever width their longest row
   wanted. The song column carries the player and the review marks beside the
   name, so it gets a floor to sit on, and the artist column gives some of that
   room back by wrapping instead of running on. */
.admin-data-panel th[data-column="Song/EP/Album/Beat"],
.admin-data-panel td[data-column="Song/EP/Album/Beat"] {
  min-width: 22rem;
}

/* The vendor sheet writes nowrap on every cell of a nowrap table, so the artist
   cell is named through the table it sits in to win that rule. Without the wrap
   a long name runs out of its cell and over the column beside it. */
.admin-data-panel table.dataTable th[data-column="Artist Name (AKA)"],
.admin-data-panel table.dataTable td[data-column="Artist Name (AKA)"] {
  max-width: 9rem;
  white-space: normal;
}

/* --- The account card's controls ----------------------------------------
   A deliberate change from the live site, asked for and approved before it was
   made. The old page gave an account one flex row, where five controls fitted
   because each was a bare select or a link. This page gives an account a card,
   so the same controls are grouped by what they do: the everyday presses
   first, the two pickers under them, then the grant form and, last, the two
   that take something away, each of those behind a rule of its own. */
.account-day-actions {
  flex-wrap: wrap;
  align-items: center;
}

/* The plan and the level, side by side while there is room for both and
   stacked when there is not. Each is a caption over one control, so the pair
   reads as two questions rather than as a row of buttons. */
.account-pickers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 0.8rem;
}

/* One question, so the three column set the admin forms share would leave two
   empty tracks holding the card open. */
.account-pickers .create-plan-fields {
  grid-template-columns: minmax(11rem, 1fr);
}

/* Nothing that ends an account shares a line with anything else, so the pair
   stands at the foot of the card behind the rule the grant form stands behind. */
.account-danger-actions {
  margin-top: 0.8rem;
}

/* --- The block the three ticks stand in ----------------------------------
   The old form appended each tick row straight into the form's grid, where the
   row's own `full` gave it the whole width. Here the three rows share a block,
   because the review tick asks the server for them again and htmx swaps one
   element. The block takes the width the rows used to take, so each row reads
   as it did: the box at the left of its label line, the sentence beneath. */
#submitTicks {
  grid-column: 1 / -1;
  min-width: 0;
}

/* Appended for the rebuild: the header row's action buttons keep their size.
   The row is flex with space-between, and a long heading squeezed the actions
   slot narrower than its own button, which then hung out of the card. The
   heading is the part that wraps; the buttons are not. */
.admin-data-header > .small-actions {
  flex-shrink: 0;
}

/* Appended for the rebuild: the pending panel's fold, holding the whole
   sign-up behind Open. Connor asked for it on 2026-08-25. */
.account-request-company {
  display: block;
  color: var(--accent-ink);
}

.account-request-more {
  margin-top: 0.35rem;
  font-size: 0.85rem;
}

.account-request-more summary {
  cursor: pointer;
  color: var(--accent-ink);
  width: fit-content;
}

.account-request-fields {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.9rem;
  margin: 0.4rem 0 0;
}

.account-request-fields dt {
  color: var(--muted);
}

.account-request-fields dd {
  margin: 0;
}

/* Appended for the rebuild: the line an administrator writes between an
   offer's name and its price. Connor asked for it on 2026-08-25. The identity
   box is a wrapping flex row, so the description takes the whole row and
   pushes the price onto its own line under it. */
.plan-upgrade-description {
  flex-basis: 100%;
  color: var(--muted);
  font-size: 0.85rem;
}

.plan-upgrade-description + .plan-upgrade-price {
  flex-basis: 100%;
}

/* Daily Manager and Show Manager on a phone. Every side strip was one row
   that never wrapped, and the show pickers asked for 18rem in a 390px
   viewport, so the cards ran off the right edge. Connor's call, 2026-08-25:
   the strips wrap, the pickers take the width they are given, and the panel
   headers stack their filter under the title. */
@media (max-width: 640px) {
  .show-entry-side,
  .plan-card-side,
  .guest-card-side,
  .plan-share-side {
    flex-wrap: wrap;
    row-gap: 0.55rem;
    max-width: 100%;
  }

  .show-entry-side > form,
  .guest-card-side > form,
  .show-picker {
    max-width: 100%;
  }

  .show-picker-select,
  .guest-show-select {
    min-width: 0;
    max-width: 100%;
    width: 100%;
  }

  .recording-pick { white-space: normal; }

  /* The two status pickers on a daily card stand one under the other, the
     same width and on the same left edge, rather than one beside the player
     and the other under it. */
  .show-entry-side > form:has(.daily-status-select),
  .show-entry-side > form:has(.edit-status-select) {
    flex: 1 1 100%;
  }

  .show-entry-side .daily-status-select,
  .show-entry-side .edit-status-select {
    min-width: 0;
    max-width: 100%;
    width: 100%;
  }

  .entry-image > form { max-width: 100%; }

  .entry-image-input {
    max-width: 100%;
    min-width: 0;
  }

  /* The card is a column here, and a wrapping column lays anything tall enough
     out sideways into a second column off the edge. */
  .plan-card,
  .plan-card-share-live { flex-wrap: nowrap; }

  /* Their 100% basis is a height in a column, which stretched each one to the
     card's full height. */
  .plan-card-footer,
  .plan-card-share,
  .plan-card-lineup { flex: 0 0 auto; max-width: 100%; }

  .lineup-side { flex-wrap: wrap; }

  .lineup-destination { max-width: 100%; }

  .lineup-destination-select {
    min-width: 0;
    max-width: 100%;
  }

  .plan-card-main,
  .guest-card-main { min-width: 0; }

  .admin-page .admin-data-header {
    flex-direction: column;
    align-items: stretch;
    gap: 0.6rem;
  }

  .admin-page .admin-data-header .account-filter {
    flex-wrap: wrap;
  }

  .admin-page .admin-data-header .account-filter select {
    flex: 1 1 12rem;
    min-width: 0;
    max-width: 100%;
  }
}
