/* The app's one component stylesheet. Loaded after tokens.css on every page.
   Started from design/mocks/mock.css: class names match the mocks on purpose.
   Colour, type, spacing and radius all come from tokens.css: nothing here sets
   a raw colour, font size or spacing value of its own. */

*, *::before, *::after { box-sizing: border-box; }

/* A class below this line that sets its own `display` (.field, .stack, ...)
   has the same specificity as the browser's own [hidden] rule, and author
   styles win ties against the user-agent sheet. Without this, a hidden field
   inside a flex wrapper stays visible. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-base-size);
  line-height: var(--text-base-line);
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }

a { color: var(--accent); }

img, svg { display: block; max-width: 100%; }

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.mono { font-family: var(--font-mono); }

/* ---- Icons ----------------------------------------------------------------
   icons.js returns <svg class="icon ...">; sized 1em by default so it tracks
   the surrounding text, then pinned to a fixed size wherever it sits beside
   fixed-size controls. */
.icon { width: 1em; height: 1em; flex: none; display: inline-block; vertical-align: -0.15em; }

.h-xl { font-size: var(--text-xl-size); line-height: var(--text-xl-line); font-weight: 600; }
.h-lg { font-size: var(--text-lg-size); line-height: var(--text-lg-line); font-weight: 600; }
.text-sm { font-size: var(--text-sm-size); line-height: var(--text-sm-line); }
.ink-2 { color: var(--ink-2); }

/* Page frame: 16px gutter at 375px, a comfortable measure above it. */
.page {
  max-width: 72rem;
  margin: 0 auto;
  padding: var(--space-4) 1rem var(--space-5);
}
@media (min-width: 640px) {
  .page { padding-left: var(--space-4); padding-right: var(--space-4); }
}

/* The project route replaces .page's centred, scrolling column with a full
   rack: the canvas fills the viewport under the top bar instead of sitting
   in a 1150px box. */
.project-page {
  max-width: none;
  padding: 0;
  gap: 0;
  height: calc(100dvh - var(--topbar-h));
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ---- Top bar ----------------------------------------------------------
   A faceplate strip: brushed gradient face, a highlight on its top edge, a
   hairline on its bottom edge, and the key shadow cast onto whatever sits
   below it (the rack, or the page column). */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 1rem;
  min-height: var(--topbar-h);
  border-bottom: 1px solid var(--edge-lo);
  background: linear-gradient(to bottom, var(--panel-hi), var(--panel));
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 4px var(--shadow-key);
  flex-wrap: wrap;
  position: relative;
  z-index: 1;
}
@media (min-width: 640px) { .topbar { padding-left: var(--space-4); padding-right: var(--space-4); } }
/* Pin the bar to a fixed height once there is room, so the readout strip and
   the canvas below it get the rest of the viewport instead of the bar
   growing with wrapped content. The phone route below 768px keeps wrapping
   and scrolling. */
@media (min-width: 768px) { .topbar { height: var(--topbar-h); flex-wrap: nowrap; } }

.wordmark {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--text-lg-size);
  letter-spacing: 0.02em;
}
a.wordmark { text-decoration: none; color: inherit; }

.topbar-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* A route's own keys and the Account key wrap as one run on a phone. */
#topbarExtra { display: contents; }

/* A song on a phone: one row of the mark, the title, Share and "…". The
   wordmark becomes the mark (the tab's icon, drawn here with the tokens), and
   the view switch and the Account key stand aside: a phone has no patch bay,
   and "…" carries the account's items. The page is page.js's data-route. */
@media (max-width: 767px) {
  .topbar[data-route="song"] { flex-wrap: nowrap; gap: var(--space-2); }
  .topbar[data-route="song"] .topbar-actions { flex: none; flex-wrap: nowrap; }
  .topbar[data-route="song"] .seg,
  .topbar[data-route="song"] #accountToggle { display: none; }
  .topbar[data-route="song"] .project-title { padding-inline: var(--space-2); }
  .topbar[data-route="song"] .wordmark {
    flex: none;
    width: 2rem;
    height: 2rem;
    overflow: hidden;
    font-size: 0;
    color: transparent;
    border-radius: var(--radius-sm);
    background:
      linear-gradient(var(--bg), var(--bg)) 6px 8.5px / 14px 3px no-repeat,
      linear-gradient(var(--bg), var(--bg)) 10px 14.5px / 14px 3px no-repeat,
      linear-gradient(var(--bg), var(--bg)) 14px 20.5px / 12px 3px no-repeat,
      var(--accent);
  }
}

/* ---- Buttons ------------------------------------------------------------
   A key: a raised cap with a top highlight and a bottom edge it casts onto
   the plate below. Pressing it sinks the cap 1px and thins that edge.

   Every key is as tall as its --key: 44px for a .btn, 32px for a .btn-sm,
   28px for a .btn-sm on a canvas card. A square icon key takes the same
   value for its width and height, so a row of keys with words and keys with
   glyphs lines up. A place that wants one size for all its keys (the Song
   card, the top bar, the Mixer on a phone) sets --key on its .btn, never a
   height on one key. */

.btn {
  --key: var(--touch-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--key);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--edge-lo);
  background: var(--panel-hi);
  color: var(--ink);
  font-size: var(--text-base-size);
  font-weight: 500;
  cursor: pointer;
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo);
}
.btn .icon { width: 14px; height: 14px; }
a.btn { text-decoration: none; }
.btn:hover { border-color: var(--ink-2); }
.btn:active { transform: translateY(1px); box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo); }
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo);
}
.btn-primary:hover { border-color: var(--ink-2); }
.btn-primary:active { transform: translateY(1px); box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo); }
.btn-ghost { background: transparent; border-color: transparent; box-shadow: none; }
.btn-ghost:hover {
  border-color: var(--edge-lo);
  background: var(--panel-hi);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo);
}
.btn-ghost:active { transform: translateY(1px); box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo); }
/* No padding above or below the words: they sit in the middle of --key
   rather than pushing the key past it. */
.btn-sm { --key: 2rem; padding: 0 var(--space-2); font-size: var(--text-sm-size); border-radius: var(--radius-sm); }
/* One height across the top bar: the project's keys and the page links. */
.topbar .btn { --key: var(--touch-min); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn[disabled]:active { transform: none; }

/* ---- Fields -------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-3); }
.field label { font-size: var(--text-sm-size); color: var(--ink-2); }
.field-label-row { display: flex; align-items: center; gap: var(--space-1); }
/* A well: the control sits recessed into the plate, not resting on top of
   it, so the shadow goes inward instead of under an edge. */
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field select,
.field-row input[type="email"],
.field-row input[type="number"] {
  min-height: var(--touch-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--control-edge);
  background: var(--rack);
  color: var(--ink);
  box-shadow: inset 0 1px 2px var(--shadow-key);
}
.field .help { font-size: var(--text-sm-size); color: var(--ink-2); }
.field-row { display: flex; align-items: center; gap: var(--space-2); min-height: var(--touch-min); }
/* A tick box is 24px, the smallest target WCAG 2.2 allows (2.5.8). */
.field-row input[type="checkbox"] { width: 1.5rem; height: 1.5rem; accent-color: var(--accent); }

/* "What is this?": a small LED-ringed dot that reveals a tooltip faceplate
   on hover, focus, or (for touch) a click anywhere in the .help wrapper. */
.help { position: relative; display: inline-flex; }
.help-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel-hi);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.625rem;
  line-height: 1;
  cursor: pointer;
}
.tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + var(--space-1));
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  width: max-content;
  max-width: 18rem;
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm-size);
  color: var(--ink-2);
  box-shadow: 0 1px 2px var(--shadow-key), 0 4px 12px var(--shadow-ambient);
}
.help-dot:hover, .help.open .help-dot { color: var(--ink); border-color: var(--ink-2); }
.help-dot:hover + .tooltip,
.help-dot:focus-visible + .tooltip,
.help.open .tooltip { display: block; }

/* ---- Cards, empty states --------------------------------------------------
   Faceplate: a brushed plate standing off the rack, lit from the top left.
   The gradient is the brushed metal, the inset lines are its top and bottom
   edges catching and losing the light, the two shadows are the tight contact
   shadow and the wider ambient one a plate this thick casts. */

.card {
  border: 1px solid var(--edge-lo);
  background: linear-gradient(to bottom, var(--panel-hi), var(--panel));
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    inset 0 -1px 0 var(--edge-lo),
    0 1px 2px var(--shadow-key),
    0 12px 32px var(--shadow-ambient);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}

/* A card split into a node card's parts: a head strip brushed a shade
   lighter, with its own hairline, then a body, then keys behind a hairline
   at the foot. Each part carries its own padding. The body takes the spare
   height, so the feet of a row of cards line up. */
.card-split { display: flex; flex-direction: column; padding: 0; }
.card-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: linear-gradient(to bottom, var(--panel-hi), var(--panel));
  border-bottom: 1px solid var(--edge-lo);
  border-top-left-radius: calc(var(--radius-lg) - 1px);
  border-top-right-radius: calc(var(--radius-lg) - 1px);
  box-shadow: inset 0 1px 0 var(--edge-hi);
}
.card-body { flex: 1; display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); }
.card-foot { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); border-top: 1px solid var(--line); }

.empty {
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  color: var(--ink-2);
  text-align: center;
}

.grid { display: grid; gap: var(--space-3); }
.grid-2 { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }

/* ---- Songs and templates ----------------------------------------------------
   A song is a split card: its head names it, its body says what it holds,
   and its keys sit at the foot. A template, which a blank patch bay offers, is
   pressed to fill the patch bay, so it is a key like the Add drawer's, a
   raised cap on the small radius, not a faceplate. Its title is its one button, stretched over the whole cap, so a
   press anywhere on the cap counts and the focus ring goes round all of it.
   The delete key on one of your own templates sits above that stretch, so it
   stays a key of its own.

   Both open on a strip with one segment per node type inside, lit in the
   module colour its .node-<type> class sets, so two cards differ at a glance
   before either title is read. */

.type-strip { --mod: var(--line); display: flex; gap: 2px; height: 3px; }
.type-strip > span { flex: 1; border-radius: 1px; background: var(--mod); }

.template-key {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--edge-lo);
  background: linear-gradient(to bottom, var(--panel-hi), var(--panel));
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo);
}
.template-key:has(.template-open:hover) { border-color: var(--ink-2); }
.template-key:has(.template-open:active) { transform: translateY(1px); box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo); }
.template-key:has(.template-open[disabled]) { opacity: 0.5; }
.template-key > .chip-row { margin-top: auto; }
.template-key .btn { position: relative; z-index: 1; }
.template-key-head { display: flex; align-items: flex-start; gap: var(--space-2); }
.template-title { flex: 1; min-width: 0; font-size: var(--text-base-size); line-height: var(--text-base-line); font-weight: 600; }
.template-open { padding: 0; border: 0; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.template-open::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius-sm); }
.template-open[disabled] { cursor: not-allowed; }
.template-open:focus-visible { outline: none; }
.template-open:focus-visible::after { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }

.project-link { color: inherit; text-decoration: none; }
.project-link:hover, .project-link:focus-visible { text-decoration: underline; }

.share-line { justify-content: space-between; }

/* ---- Chips --------------------------------------------------------------------
   A chip is a readout, not a key, so it sits recessed into the plate like a
   well instead of raised like a cap, and the dot carries its colour: a kind
   colour below, or the module colour a .node-<type> class sets. Its label
   stays in the secondary ink, so a row of chips reads as one line of names
   rather than a line of colours. */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 1.25rem;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--rack);
  box-shadow: inset 0 1px 2px var(--shadow-key);
  color: var(--ink-2);
  font-size: var(--text-xs-size);
  line-height: var(--text-xs-line);
  font-weight: 500;
  white-space: nowrap;
}
.chip::before {
  content: "";
  flex: none;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--mod, var(--mod-default));
}
.chip.audio { --mod: var(--kind-audio); }
.chip.midi { --mod: var(--kind-midi); }
.chip.text, .chip.json { --mod: var(--kind-text); }
.chip.session { --mod: var(--kind-session); }
.chip.archive { --mod: var(--kind-archive); }
.chip.stems { --mod: var(--kind-stems); }
.chip-row { display: flex; flex-wrap: wrap; gap: var(--space-1); }

/* ---- State lamp, dot-matrix ------------------------------------------------- */

.lamp { display: inline-flex; align-items: center; gap: var(--space-2); }
.lamp-grid {
  display: grid;
  grid-template-columns: repeat(3, 5px);
  grid-template-rows: repeat(3, 5px);
  gap: 2px;
  flex: none;
}
/* Each lit cell sets color, not just background, so it can halo itself with
   color-mix(currentColor) instead of naming its own colour a second time. */
.lamp-grid span { display: block; background: var(--line); border-radius: 1px; }
/* A lamp's words (Not run, Running, Failed, "Finding the beat") are words, so they
   are sans everywhere. Mono is for numbers and readouts. */
.lamp-label { font-family: var(--font-sans); font-size: var(--text-sm-size); }

.lamp.queued .lamp-grid span:nth-child(1),
.lamp.queued .lamp-grid span:nth-child(3),
.lamp.queued .lamp-grid span:nth-child(7),
.lamp.queued .lamp-grid span:nth-child(9) { background: var(--ink-2); }

.lamp.running .lamp-grid span:nth-child(odd) {
  color: var(--accent);
  background: currentColor;
  box-shadow: 0 0 6px 1px color-mix(in srgb, currentColor var(--led-halo), transparent);
}
.lamp.running .lamp-label { color: var(--accent); }

.lamp.done .lamp-grid span {
  color: var(--ok);
  background: currentColor;
  box-shadow: 0 0 6px 1px color-mix(in srgb, currentColor var(--led-halo), transparent);
}
.lamp.done .lamp-label { color: var(--ok); }

.lamp.failed .lamp-grid span:nth-child(1),
.lamp.failed .lamp-grid span:nth-child(3),
.lamp.failed .lamp-grid span:nth-child(5),
.lamp.failed .lamp-grid span:nth-child(7),
.lamp.failed .lamp-grid span:nth-child(9) {
  color: var(--bad);
  background: currentColor;
  box-shadow: 0 0 6px 1px color-mix(in srgb, currentColor var(--led-halo), transparent);
}
.lamp.failed .lamp-label { color: var(--bad); }

.lamp.stale .lamp-grid span:not(:nth-child(5)) {
  color: var(--warn);
  background: currentColor;
  box-shadow: 0 0 6px 1px color-mix(in srgb, currentColor var(--led-halo), transparent);
}
.lamp.stale .lamp-label { color: var(--warn); }

/* ---- Progress: a well with an LED bar for a fill -------------------------- */

.progress {
  height: 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--edge-lo);
  background: var(--rack);
  box-shadow: inset 0 1px 2px var(--shadow-key);
  overflow: hidden;
}
.progress > span {
  display: block;
  height: 100%;
  color: var(--accent);
  background: currentColor;
  box-shadow: 0 0 6px 1px color-mix(in srgb, currentColor var(--led-halo), transparent);
}
/* A stage is a sentence ("Finding the beats."), its percentage a readout. */
.stage-text { font-size: var(--text-sm-size); margin: var(--space-1) 0; }
.stage-pct { font-family: var(--font-mono); }

/* ---- Node cards -------------------------------------------------------------
   Only what share.html's read-only node list needs. The canvas step appends
   the interactive node (options form, upload zone) and canvas rules below. */

.node {
  --mod: var(--mod-default);
  border: 1px solid var(--edge-lo);
  background: linear-gradient(to bottom, var(--panel-hi), var(--panel));
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    inset 0 -1px 0 var(--edge-lo),
    0 1px 2px var(--shadow-key),
    0 12px 32px var(--shadow-ambient);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* One colour per node type, carried in --mod: the head's top edge, its
   tint, its icon, the Add menu entry and the library chip all read it. */
.node-song { --mod: var(--mod-song); }
.node-songbones { --mod: var(--mod-songbones); }
.node-drumscribe { --mod: var(--mod-drumscribe); }
.node-chartwright { --mod: var(--mod-chartwright); }
.node-beatgrid { --mod: var(--mod-beatgrid); }
.node-lyrics { --mod: var(--mod-lyrics); }
.node-mixdown { --mod: var(--mod-mixdown); }
.node-backingtrack { --mod: var(--mod-backingtrack); }
.node-preview { --mod: var(--mod-preview); }
.node-pianoroll { --mod: var(--mod-pianoroll); }
.node-transpose, .node-tempo, .node-loudness, .node-convert { --mod: var(--mod-transform); }
.node-loopfinder, .node-drumslicer, .node-samplekit { --mod: var(--mod-sampling); }
.node-sections { --mod: var(--mod-sections); }
/* The head strip is brushed a shade lighter than the plate under it, with
   its own hairline where the two meet, and a top edge in the module's
   colour. border-radius already matches the faceplate's own top corners. */
.node-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin: calc(var(--space-3) * -1) calc(var(--space-3) * -1) 0;
  padding: var(--space-2) var(--space-3);
  background: linear-gradient(to bottom, color-mix(in srgb, var(--mod) 14%, var(--panel-hi)), var(--panel));
  border-bottom: 1px solid var(--edge-lo);
  border-top: 3px solid var(--mod);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
}
.node-icon { width: 16px; height: 16px; color: var(--mod); }
/* The title takes the head's spare width, so it sits beside its icon rather
   than being pushed to the far end (the share page and phone list have no
   delete key after it to push against). */
.node-title { font-size: var(--text-lg-size); line-height: var(--text-lg-line); font-weight: 600; flex: 1; min-width: 0; }
.node-summary { font-family: var(--font-mono); font-size: var(--text-sm-size); color: var(--ink-2); }
.node-outputs { display: flex; flex-direction: column; gap: var(--space-2); border-top: 1px solid var(--line); padding-top: var(--space-3); }

.output-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: var(--touch-min);
}
.output-row-main { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.output-row-label { font-size: var(--text-base-size); }
.output-row-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* An output row's icon keys, square on the row's --key, so they stand as
   tall as a key with words beside them. */
.row-play, .row-download.icon-only { width: var(--key); height: var(--key); padding: 0; }
.row-play svg { width: 12px; height: 12px; }
/* On, it lights its whole key the way the preview's toggles do: accent face,
   dark glyph. An accent glyph alone barely differs from an off key now that
   the accent is ivory. */
.row-play.playing {
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    0 2px 0 var(--edge-lo),
    0 0 6px 1px color-mix(in srgb, var(--accent) var(--led-halo), transparent);
}

/* The phone list's socket is a plain jack dot: a ring in the kind colour
   around a dark hole, filled with that colour once the output has a file. */
.socket {
  flex: none;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: var(--rack);
}
.socket.filled { background: currentColor; }
.socket.audio { color: var(--kind-audio); }
.socket.midi { color: var(--kind-midi); }
.socket.text, .socket.json { color: var(--kind-text); }
.socket.session { color: var(--kind-session); }
.socket.archive { color: var(--kind-archive); }
.socket.stems { color: var(--kind-stems); }

.row-icon { flex: none; width: 14px; height: 14px; }
.row-icon.audio { color: var(--kind-audio); }
.row-icon.midi { color: var(--kind-midi); }
.row-icon.text, .row-icon.json { color: var(--kind-text); }
.row-icon.session { color: var(--kind-session); }
.row-icon.archive { color: var(--kind-archive); }
.row-icon.stems { color: var(--kind-stems); }

.node-list { display: flex; flex-direction: column; gap: var(--space-3); }

/* ---- Tables (dashboard) --------------------------------------------------------- */

table { width: 100%; border-collapse: collapse; font-size: var(--text-sm-size); }
th, td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--line); }
th { color: var(--ink-2); font-weight: 500; }
td.num, th.num { font-family: var(--font-mono); text-align: right; }
/* Positioned, so that what is hidden from the eye inside a table (a column's name
   that has no words to show) is clipped with the table and does not widen the page. */
.table-wrap { overflow-x: auto; position: relative; }
/* A checkbox in a cell: the dashboard's keep column. */
td input[type="checkbox"] { width: 1.5rem; height: 1.5rem; accent-color: var(--accent); }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--space-3); }
/* A tile is a readout, so it is a well like a chip, not a box on the plate. */
.stat-tile { border-radius: var(--radius-sm); padding: var(--space-3); background: var(--rack); box-shadow: inset 0 1px 2px var(--shadow-key); }
.stat-tile .value { font-family: var(--font-mono); font-size: var(--text-lg-size); display: block; }
.stat-tile .label { font-size: var(--text-sm-size); color: var(--ink-2); }

/* ---- Panels layout for onboarding, auth ------------------------------------------ */

.panels-3 { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }

.auth-shell { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--space-4) 1rem; }
.auth-card { width: 100%; max-width: 28rem; border: 1px solid var(--line); background: var(--panel); border-radius: var(--radius-lg); padding: var(--space-4); }
.auth-intro { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.auth-section + .auth-section { margin-top: var(--space-5); padding-top: var(--space-4); border-top: 1px solid var(--line); }

/* ---- Account panel --------------------------------------------------------------- */

.account-secret-row { display: flex; gap: var(--space-3); align-items: flex-start; flex-wrap: wrap; }
.qr-box { flex: none; width: 10rem; height: 10rem; display: flex; align-items: center; justify-content: center; text-align: center; }
.qr { width: 160px; height: 160px; color: var(--ink); }
.recovery-codes { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2) var(--space-4); }

/* ---- Phone banner --------------------------------------------------------------- */

.banner {
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  color: var(--ink-2);
  font-size: var(--text-sm-size);
}

/* ---- Errors -------------------------------------------------------------------- */

.error-block { display: flex; flex-direction: column; gap: var(--space-1); }
.error-sentence { color: var(--bad); }
.warn-text { color: var(--warn); }

/* ---- Songs page and add-song sheet ------------------------------------------
   Songs is the drop well's plate, the shelf of song cards, and one line at the
   foot that opens a blank patch bay. The add-song sheet is a .drawer: the one
   question, one sentence for what the answer makes, and a foot that stays put
   with one line and one key. */

.add-song-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 7rem;
  padding: var(--space-3);
  text-align: center;
  background: var(--rack);
  border: 1px dashed var(--control-edge);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 1px 2px var(--shadow-key);
  cursor: pointer;
}
.add-song-drop .icon { width: 1.5rem; height: 1.5rem; color: var(--ink-2); }
.add-song-drop:hover, .add-song-drop.drop-ready { border-color: var(--ink-2); }
.add-song-drop:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }
.add-song-link {
  flex: 1 1 12rem;
  min-width: 0;
  min-height: var(--touch-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--control-edge);
  background: var(--rack);
  color: var(--ink);
  box-shadow: inset 0 1px 2px var(--shadow-key);
}
.add-song-link::placeholder { color: var(--ink-2); }
@media (max-width: 519px) { .add-song-link { flex-basis: 100%; } }

/* Out of songs, said under the well before a file is picked. */
.add-song-out { color: var(--ink); }
.add-song-out a { text-underline-offset: 0.2em; white-space: nowrap; }

/* On a phone there is nothing to drop a song on. The well, and "Choose a file"
   beside it, give way to one full-width key that opens the same picker, with the
   formats line right under it; a link field, where the edition has one, comes
   after. The key is drawn at every width and shown only here, so a window made
   narrower changes nothing the page must redraw. */
.add-song-key { display: none; }
@media (max-width: 767px) {
  .add-song-key { display: inline-flex; width: 100%; }
  .add-song-drop, .add-song-choose, .add-song-row.solo { display: none; }
  .add-song > * { order: 5; }
  .add-song > .add-song-key { order: 1; }
  .add-song > .add-song-out { order: 2; }
  .add-song > .add-song-formats { order: 3; }
  .add-song > .add-song-row { order: 4; }
}

.song-grid { grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: var(--space-4); }
.song-title-row { display: flex; align-items: center; gap: var(--space-3); }
.song-title-block { min-width: 0; }
.song-cover {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-sm);
  background: var(--rack);
  box-shadow: inset 0 1px 2px var(--shadow-key);
  color: var(--ink-2);
  overflow: hidden;
}
.song-cover .icon { width: 1.5rem; height: 1.5rem; }
.song-cover img { width: 100%; height: 100%; object-fit: cover; }
/* A song's title is the way in, so its link is stretched over the whole card: a
   press anywhere on the card opens the song, and the focus ring goes round all of
   it. The card's own keys sit above the stretch and stay keys of their own. The
   card has no "Open" key to repeat the link. */
.song-grid .card-split { position: relative; }
.song-grid .project-link::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg); }
.song-grid .project-link:focus-visible { outline: none; text-decoration: underline; }
.song-grid .project-link:focus-visible::after { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }
.song-grid .card-foot .btn,
.song-grid .card-foot input,
.song-grid .share-line > * { position: relative; z-index: 1; }

/* The door to the workshop: one quiet line at the foot of Songs, the whole
   line a key, its last words underlined. A phone has no patch bay. */
.patch-door-row { display: flex; flex-direction: column; gap: var(--space-1); }
.patch-door {
  align-self: flex-start;
  min-height: var(--touch-min);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font-size: var(--text-base-size);
  text-align: left;
  cursor: pointer;
}
.patch-door span {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 0.25em;
}
.patch-door:hover span { text-decoration-color: var(--ink); }
.patch-door:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); border-radius: var(--radius-sm); }
.patch-door[disabled] { opacity: 0.5; cursor: progress; }
@media (max-width: 767px) { .patch-door-row { display: none; } }

/* The sheet: a little wider than a drawer, so each answer's line reads in two
   or three short lines beside its neighbour, and the answers, the sentence and
   the key all fit a laptop's screen at once. The account drawer matches it, so
   "I play" is one row of keys in both. */
.drawer-host > .add-sheet,
.drawer-host > .account-drawer { width: min(36rem, 100vw); }
.add-sheet .drawer-head { align-items: flex-start; gap: var(--space-3); }
.add-title { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.add-source-line { display: flex; align-items: center; gap: var(--space-2); min-width: 0; color: var(--ink-2); }
.add-source-line .icon { flex: none; color: var(--kind-audio); }
.add-source-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.add-source-size { flex: none; }
.add-question { font-size: var(--text-lg-size); line-height: var(--text-lg-line); font-weight: 600; margin-top: var(--space-2); }

.intent-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
@media (max-width: 519px) { .intent-grid { grid-template-columns: 1fr; } }
.btn.intent {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  gap: var(--space-1);
  padding: var(--space-3);
  height: auto;
  width: 100%;
  position: relative;
  white-space: normal;
}
/* The glyph sits beside the answer, not over it, and the check beside that. */
.intent-name { display: flex; align-items: center; gap: var(--space-2); padding-right: var(--space-4); }
.btn.intent .icon { width: 18px; height: 18px; color: var(--ink-2); }
.btn.intent .text-sm { font-weight: 400; }
.btn.intent .intent-check { display: none; position: absolute; top: var(--space-3); right: var(--space-3); }
.btn.intent .intent-check .icon { width: 16px; height: 16px; color: var(--accent); }
.btn.intent[aria-pressed="true"] {
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo), inset 0 0 0 2px var(--accent);
}
.btn.intent[aria-pressed="true"] .intent-check { display: block; }
.btn.intent[aria-pressed="true"] .intent-name > .icon { color: var(--accent); }

/* "I play": small keys in a row, the one lit outlined like a picked answer. */
.add-plays { display: flex; flex-direction: column; gap: var(--space-2); }
.chip-keys { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip-key {
  min-height: 2rem;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--edge-lo);
  background: var(--panel-hi);
  color: var(--ink-2);
  font-size: var(--text-sm-size);
  font-weight: 500;
  cursor: pointer;
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo);
}
.chip-key:hover { border-color: var(--ink-2); color: var(--ink); }
.chip-key:active { transform: translateY(1px); box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 0 var(--edge-lo); }
.chip-key[aria-pressed="true"] {
  color: var(--ink);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 0 var(--edge-lo), inset 0 0 0 2px var(--accent);
}

/* What the answer makes, in one sentence, with the ticks one key away. */
.add-summary { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); margin-top: var(--space-1); }
.add-change { margin-left: calc(-1 * var(--space-2)); color: var(--ink-2); }
.add-change:hover { color: var(--ink); }
.add-change .chevron { width: 14px; height: 14px; transform: rotate(-90deg); transition: transform 0.15s ease; }
.add-change[aria-expanded="true"] .chevron { transform: rotate(0deg); }
@media (prefers-reduced-motion: reduce) { .add-change .chevron { transition: none; } }

.get-list { display: grid; gap: var(--space-2); }
.get-row { display: grid; grid-template-columns: 1.5rem 1fr; column-gap: var(--space-2); align-items: start; }
.get-row > input[type="checkbox"] { width: 1.5rem; height: 1.5rem; margin: 0; accent-color: var(--accent); }
.get-row label, .get-row strong { font-weight: 500; }
.get-fixed > .icon { width: 1.25rem; height: 1.25rem; margin-top: 2px; color: var(--ok); }

.add-sheet .sheet-foot {
  position: sticky;
  bottom: calc(-1 * var(--space-4));
  margin: auto calc(-1 * var(--space-4)) 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--panel);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.add-foot-line { color: var(--ink-2); }
/* While a song goes in, the bar stands where the key was, beside Cancel. */
.add-sheet .sheet-foot .row .progress { flex: 1 1 8rem; min-width: 6rem; }
/* With no room for the song the sheet is its refusal and nothing else: the foot
   sits under the file's name, not at the bottom of a drawer with nothing above it. */
.add-sheet.refused .sheet-foot {
  position: static;
  margin: 0;
  padding: 0;
  border-top: 0;
  background: none;
}
/* What stands in the way of a song: said in the ink, not the secondary ink. */
.add-foot-line.blocked { color: var(--ink); }
/* What the key's "I have the rights" means, as the small print under it. */
.add-rights { font-size: var(--text-xs-size); line-height: var(--text-xs-line); color: var(--ink-2); }
.add-rights a { color: inherit; text-underline-offset: 0.2em; }

/* ---- Account drawer -------------------------------------------------------------
   The plan first, then one section after another under a hairline: I play,
   two-step sign-in, and what the packages draw in the account slot, whose
   headings sit at the drawer's one section size. */

.account-drawer { gap: var(--space-4); }
.account-drawer .drawer-head { align-items: flex-start; gap: var(--space-3); }
.account-who { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.account-who p { overflow-wrap: anywhere; }
.account-heading { font-size: var(--text-base-size); line-height: var(--text-base-line); font-weight: 600; }
.account-plan > .card-body { padding: 0; }
.account-section { display: flex; flex-direction: column; gap: var(--space-2); }
.account-slot { display: flex; flex-direction: column; gap: var(--space-4); }
.account-section,
.account-slot > .card-foot,
.account-slot > .card-body { padding: var(--space-4) 0 0; border-top: 1px solid var(--line); }
.account-slot .h-lg { font-size: var(--text-base-size); line-height: var(--text-base-line); }
.account-secret { overflow-wrap: anywhere; }

/* ---- Utility --------------------------------------------------------------------- */

/* Read by a screen reader and never seen: the live region a page announces
   into, and a column's name where it has no words to show. Not display: none,
   which a screen reader skips. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The first thing a keyboard reaches on the app's own pages: a key past the top
   bar to the page's main. Off the top of the screen until it has focus. The
   marketing pages have their own (.mk-skip), set the same way. */
.skip-link {
  position: absolute;
  left: 1rem;
  top: var(--space-2);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-min);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--ink-2);
  border-radius: var(--radius-sm);
  background: var(--panel-hi);
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  transform: translateY(-300%);
}
.skip-link:focus { transform: none; }
/* The page's main takes focus from the skip key, and shows no ring for it: it is
   a place, not a control. */
main:focus { outline: none; }

/* Motion is never the only way to see something happen, so a person who asks for
   less of it gets none. The rules for single places (the drawer, the chevron, the
   canvas) say the same; this one is the net under any that were missed. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

.stack { display: flex; flex-direction: column; gap: var(--space-3); }
/* A stack of whole sections, spaced wider than the heading and grid inside
   each, so a page of them reads as groups rather than one long run. */
.stack-sections { display: flex; flex-direction: column; gap: var(--space-5); }
.row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
/* Sends an item, and whatever follows it, to the far end of its row. */
.push { margin-left: auto; }

/* Canvas */
