/* ═══════════════════════════════════════════════════════════════════════════
   Sonus — appearance, in MOTION's language
   ═══════════════════════════════════════════════════════════════════════════

   Taken from MOTION and applied here, so the two read as one family:

     · one near-black ground, and white held back at four opacities above it —
       no greys, anywhere
     · hairlines instead of borders, and gradient rules that fade at their ends
       rather than stopping dead
     · buttons that are words, not boxes: small capitals, widely tracked, dim,
       brightening when pointed at
     · corners kept tight; roundness reserved for menus and for pills
     · one colour, used for what is selected and what is playing, and for
       nothing else

   Nothing here moves anything. Every panel, column, menu and control keeps its
   place and its behaviour; what changes is the material.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ground and type ───────────────────────────────────────────────────── */

html,
body {
  background: var(--ink);
  color: var(--text);
}

html,
body,
input,
button,
select,
textarea {
  font-family: var(--font);
  font-weight: var(--ui-weight-regular, 400);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis: none;
}

/* MOTION's signposts: small capitals set so far apart that the word stops
   being read and becomes a marker. The spacing is the whole idea — at a
   quarter of that, the same word in the same weight just looks heavy, which is
   what these headings looked like. These are its values exactly. */
.sidebar-section-label {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* The column headings say the same kind of thing, but each has a column to fit
   inside, so they are given as much of that spacing as the narrowest one can
   take. */
.sample-list-header,
.sample-list-header * {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* ── the frame ─────────────────────────────────────────────────────────────
   One ground from edge to edge. The regions are told apart by a single
   hairline, not by a change of grey. */

.app-titlebar,
.app-tabbar,
.app-header,
.app-subheader,
.app-sidebar {
  background: var(--ink);
}

.app-tabbar,
.app-header {
  border-bottom: 1px solid var(--divider);
}

.app-subheader {
  border-bottom: 1px solid var(--hairline);
}

/* The rule down the side of the sidebar, drawn the way MOTION draws the ones
   between its groups: a gradient that comes out of the dark at the top and
   goes back into it at the bottom, rather than a line that starts and stops.
   A pseudo-element rather than a border, because a border cannot fade. */
.app-sidebar {
  border-right: 0;
}

.app-sidebar::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 1px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg,
    transparent 0,
    var(--divider) 15%,
    var(--divider) 85%,
    transparent 100%);
}

.bpm-footer {
  border-top: 1px solid var(--hairline);
}

/* The transport bar had nothing above it, so it ran straight into the library.
   The same fading rule the headings use, drawn across the whole width. */
.app-footer {
  background: var(--ink);
  position: relative;
}

.app-footer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(90deg,
    transparent 0,
    var(--divider) 4%,
    var(--divider) 96%,
    transparent 100%);
}

/* ── buttons ───────────────────────────────────────────────────────────────
   MOTION has no button boxes at all. A button is a word: uppercase, tracked,
   dim, and it brightens when you point at it. That is the whole treatment, and
   it is why nothing in its panel looks like a square. */

.header-btn,
.subheader-type-btn,
.sidebar-add-btn,
.header-search-btn,
.app-tab-close {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transition: color 0.15s var(--ease);
}

.header-btn:hover,
.subheader-type-btn:hover,
.sidebar-add-btn:hover,
.header-search-btn:hover,
.app-tab-close:hover {
  color: var(--text);
  background: none;
}

.subheader-type-btn.is-active,
.header-btn--clear {
  color: var(--glow);
  background: none;
}

/* ── filters ───────────────────────────────────────────────────────────── */

.subheader-trigger,
.header-dropdown-trigger {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0;
  transition: color 0.15s var(--ease);
}

.subheader-trigger:hover,
.header-dropdown-trigger:hover {
  color: var(--text);
  background: none;
}

.subheader-dropdown.has-selection .subheader-trigger,
.header-dropdown.has-selection .header-dropdown-trigger {
  color: var(--glow);
  background: none;
  border: 0;
}

/* The name of a filter is a signpost; its current value is the thing being
   read. So the name takes the small-capitals treatment and the value is left
   plain — the same relationship MOTION has between a knob's label and its
   reading. */
.header-label {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

.header-select {
  font-size: calc(12px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0;
  transition: color 0.15s var(--ease);
}

.header-select:hover {
  color: var(--text);
  background: none;
}

.header-dropdown.has-selection .header-select,
.header-dropdown.is-open .header-select {
  color: var(--glow);
  background: none;
}

/* The key chooser was the loudest thing left: two tabs in a different voice,
   twelve boxed keys and a heavy outlined button underneath. It now speaks the
   same way as everything else — words, hairlines, and the one colour on what is
   chosen. */

.key-tab {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  border-bottom: 1px solid transparent;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.key-tab:hover {
  color: var(--text);
}

.key-tab.is-active {
  color: var(--glow);
  border-bottom-color: var(--glow);
}

.key-tabs {
  border-bottom: 1px solid var(--divider);
}

.key-btn {
  font-size: calc(12px * var(--ui-font-scale, 1));
  letter-spacing: 0.06em;
  color: var(--dim);
  background: none;
  border: 1px solid var(--divider);
  border-radius: 3px;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.key-btn:hover {
  color: var(--text);
  background: none;
  border-color: var(--hairline);
}

.key-btn.is-selected {
  color: var(--glow);
  border-color: rgba(255, 255, 255, 0.45);
  background: var(--glow-soft);
}

/* Not a control of its own — the way out of the filter. */
.key-btn--any {
  border-color: transparent;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-size: calc(11px * var(--ui-font-scale, 1));
}

.key-btn--any:hover {
  border-color: transparent;
  color: var(--text);
}

.key-btn--any.is-selected {
  border-color: transparent;
  background: none;
  color: var(--glow);
}

/* ── waveform ──────────────────────────────────────────────────────────────
   Fifty of them are on screen at once. Individually faint, they read as a
   shape; any brighter and the column becomes a wall of grey blocks. What has
   been listened to is drawn in the one colour. */

.waveform-bars rect,
.sample-row.is-played .waveform-bars rect {
  fill: rgba(255, 255, 255, 0.13);
}

.sample-row:hover .waveform-bars rect {
  fill: rgba(255, 255, 255, 0.2);
}

.waveform-bars-played rect {
  fill: var(--glow);
}

/* ── tabs ──────────────────────────────────────────────────────────────── */

.app-tab {
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: color 0.15s var(--ease);
}

.app-tab:hover {
  background: none;
  color: var(--read);
}

/* Every tab gets the rule, not only the one you are on.
 *
 * One tab wearing a line and the rest wearing nothing reads as a line that
 * appeared rather than as a set of tabs, and it left the strip looking unequal.
 * They all carry it now and the chosen one carries it at full strength, which
 * is the same way the rest of this window says "this one": by weight, not by
 * presence or absence.
 *
 * The colour is the place that tab is showing, when that place has one of its
 * own — see the note at the foot of this file — and the accent otherwise. */
.app-tab {
  box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--item-color, var(--glow)) 26%, transparent);
}

.app-tab:hover {
  box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--item-color, var(--glow)) 48%, transparent);
}

.app-tab.is-active,
.app-tab.is-active:hover {
  background: none;
  color: var(--text);
  box-shadow: inset 0 -1px 0 var(--item-color, var(--glow));
}

/* ── search ────────────────────────────────────────────────────────────────
   This field already had its own mark: a gradient rule that runs under it and
   lights up as you type. Putting a border round it as well gave the field two
   edges at once — the pill and the rule, at different widths. The rule is the
   older idea and the better one, so the border goes. */

.header-search {
  background: none;
  border: 0;
}

.header-search-text {
  color: var(--text);
  font-size: calc(13px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
}

.header-search-text::placeholder {
  color: var(--faint);
  text-transform: uppercase;
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.12em;
}

/* ── menus and modals ──────────────────────────────────────────────────────
   A raised surface is a slightly different black, lifted by its edge and its
   shadow rather than by being lighter. */

/* Anything that floats has to be opaque. Repainting the interface turned a
   number of solid greys into white at three and a half per cent, which is the
   right answer for a panel sitting on the page and the wrong one for a popup
   sitting over it — the volume slider, the genre and save lists and the colour
   picker all went see-through, with the library legible straight through them.
   Everything that leaves the page gets the same raised ground, edge and
   shadow. */
/* Every surface that floats gets MOTION's preset menu, to the value: its
   ground, its hairline, its corner and its shadow. One radius everywhere —
   twelve — so no two menus in the window disagree about their own shape.

   This rule was lost to a bad edit of mine: a script removing the Stack
   selectors matched the last name in this list and swallowed the four above it
   with the rule, which is why the menus came back transparent and square. */
.header-dropdown-menu,
.subheader-menu,
.account-popover,
.accent-popover,
.volume-popup,
.params-popup,
.sidebar-ctx-menu,
.sample-ctx-menu,
.sample-ctx-sub-popup,
.sidebar-new-badge-tooltip {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.55);
}

.header-dropdown-menu,
.subheader-menu,
.account-popover,
.volume-popup {
  padding: 6px;
}

/* Le panneau des parametres respire plus que les menus : il porte des reglages
   qu'on lit, pas une liste de choix qu'on survole. */
.params-popup {
  padding: 12px;
}

.modal-shell > * {
  background: rgba(10, 10, 13, 0.94);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
}

.modal-shell {
  background: rgba(0, 0, 0, 0.52);
}

/* Type and colour only — no padding. These items reserve room on their left
   for a tick that is positioned rather than laid out, and setting the padding
   here took that room away: every label ended up printed over its own tick. */
.header-dropdown-item,
.subheader-item,
.account-popover-item {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 7px;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.header-dropdown-item:hover,
.subheader-item:hover,
.account-popover-item:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.055);
}

/* A tick that stays white beside a word that has turned blue reads as two
   different states at once. The mark is a grey image, so it is tinted to match
   whatever its label is doing — and likewise in the sidebar, where the icon was
   taking the colour while its word stayed white. */
.header-dropdown-item.is-selected,
.subheader-item.is-selected {
  color: var(--glow);
  background: none;
}

.subheader-item.is-selected::before,
.header-dropdown-item.is-selected::before {
  filter: var(--glow-filter);
}

.subheader-item:hover::before,
.header-dropdown-item:hover::before {
  filter: brightness(0) invert(1);
}

.subheader-item.is-selected:hover::before,
.header-dropdown-item.is-selected:hover::before {
  filter: var(--glow-filter);
}

.account-popover-item--danger {
  color: var(--danger);
}

/* ── sidebar ───────────────────────────────────────────────────────────────
   MOTION marks what is chosen with a bright edge along one side and the
   faintest wash behind it, rounded away from that edge. Sonus already draws a
   collection's colour down exactly that edge, so the two land on top of each
   other. */

.sidebar-item-btn,
.sidebar-page-btn {
  font-size: calc(13px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.04em;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0 8px 8px 0;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

/* No shape appears under the pointer: the word simply comes forward, the way
   MOTION's do. A block of ground sliding in and out is a second thing moving
   for no reason. */
.sidebar-item-btn:hover,
.sidebar-page-btn:hover {
  color: var(--text);
  background: none;
}

/* The icons ship as grey images, so they are tinted rather than swapped. They
   follow their label exactly: white when pointed at, the accent when chosen. */
.sidebar-item-btn img {
  opacity: 1;
  filter: brightness(0) saturate(100%) invert(64%) sepia(6%) saturate(200%)
          hue-rotate(180deg) brightness(95%);
  transition: filter 0.15s var(--ease);
}

.sidebar-item-btn:hover img {
  opacity: 1;
  filter: brightness(0) invert(1);
}

/* Le filtre a été mesuré plutôt qu'estimé : il rend exactement rgb(255,178,122). */
.sidebar-item-btn.is-selected img,
.sidebar-item-btn.is-selected:hover img {
  opacity: 1;
  filter: var(--glow-filter);
}

/* Add Folder and Add Collection, brought under the same rule.
 *
 * Their word does what every other word in the panel does - dim at rest, white
 * when pointed at - and the plus beside it did not follow: it was pinned to one
 * fixed grey and only changed how see-through it was, by a fifth, which reads
 * as the icon staying behind while the label steps forward.
 *
 * The three text colours in this window are all pure white with an alpha, so a
 * white plus at the same alpha is not an approximation of the label's colour -
 * it is that colour. The numbers are --dim and --text, and they have to move if
 * those do; a filter cannot be given an alpha, and a mask would be a technique
 * this stylesheet uses nowhere else for one icon. */
.sidebar-add-btn img {
  filter: brightness(0) invert(1);
  opacity: 0.44;
  transition: opacity 0.15s var(--ease);
}

.sidebar-add-btn:hover img {
  opacity: 0.94;
}

/* What is chosen is said by the word itself taking the colour — MOTION marks a
   selected preset exactly this way, with no shape behind it. A filled block
   with a corner radius was two decisions where none was needed, and it fought
   with the colour bar a collection already draws down its leading edge. */
.sidebar-item-btn.is-selected {
  color: var(--glow);
  background: none;
}

.sidebar-item-btn.is-selected:hover {
  color: var(--glow);
  background: none;
}

/* Browser is the name of where you are, not somewhere to go: it answered to
   the pointer while doing nothing about it. */
.sidebar-page-btn.is-page-switcher-disabled,
.sidebar-page-btn.is-page-switcher-disabled:hover {
  background: none;
  color: var(--read);
  cursor: default;
}

/* ── rules ─────────────────────────────────────────────────────────────────
   MOTION's dividers fade out at both ends rather than stopping dead, and they
   sit at five and a half per cent white — seven times fainter than the forty
   per cent line that was under these headings. Same colour, same thickness,
   same fade, everywhere a rule is drawn. */

.sidebar-section-label::after {
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0,
    var(--divider) 15%,
    var(--divider) 85%,
    transparent 100%);
}

.sample-list-header {
  border-bottom: 0;
  position: relative;
}

.sample-list-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent 0,
    var(--divider) 4%,
    var(--divider) 96%,
    transparent 100%);
}

.sidebar-tree-toggle {
  color: var(--faint);
  background: none;
  border: 0;
}

.sidebar-tree-toggle:hover {
  color: var(--dim);
  background: none;
}

/* ── the list ──────────────────────────────────────────────────────────────
   The densest surface in the application, so the quietest: rows sit directly
   on the ground, divided by the faintest rule that still reads. */

.sample-list-header {
  border-bottom: 1px solid var(--hairline);
}

.sample-row {
  /* The fade at the end of the tag run has to end in the colour of the row it
     sits on, so the row publishes its own ground for the fade to use. */
  --row-bg: var(--ink);
  background: none;
  border-bottom: 1px solid var(--divider);
  transition: background 0.15s var(--ease);
}

.sample-row:hover {
  --row-bg: #0b0b0f;
  background: rgba(255, 255, 255, 0.035);
}

.sample-row.is-focused {
  --row-bg: #0c0d11;
  background: rgba(255, 255, 255, 0.05);
}

/* What is playing keeps its wash of the accent — that is what makes it findable
   while scrolling. Only the bar down its leading edge is gone. */
.sample-row.is-playing {
  --row-bg: #17120e;
  background: var(--glow-soft);
  box-shadow: none;
}

.sample-name {
  font-size: calc(13px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
  color: var(--read);
  transition: color 0.15s var(--ease);
}

.sample-row:hover .sample-name,
.sample-row.is-focused .sample-name,
.sample-row.is-playing .sample-name {
  color: var(--text);
}

.sample-col--length,
.sample-col--tempo,
.sample-col--key,
.sample-col--scale {
  font-size: calc(12px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

.sample-row:hover .sample-col--length,
.sample-row:hover .sample-col--tempo,
.sample-row:hover .sample-col--key,
.sample-row:hover .sample-col--scale {
  color: var(--read);
}

/* ── tags ──────────────────────────────────────────────────────────────────
   A dozen can sit on one row, so they are written the way MOTION writes a
   label: very small capitals, widely tracked, barely there until looked at. */

.sample-tag {
  font-size: calc(9.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.28);
  background: none;
  border: 0;
  border-radius: 0;
  transition: color 0.15s var(--ease);
}

.sample-row:hover .sample-tag {
  color: rgba(255, 255, 255, 0.42);
}

.sample-tag:hover {
  color: var(--glow);
  background: none;
}

.sample-tags-fade {
  background: linear-gradient(90deg, transparent, var(--row-bg) 70%);
}

/* ── artwork ───────────────────────────────────────────────────────────── */

.pack-avatar,
.pack-avatar__img {
  border-radius: 3px;
}

.pack-avatar {
  box-shadow: inset 0 0 0 1px var(--hairline);
}

.sample-type-icon {
  opacity: 0.32;
  transition: opacity 0.15s var(--ease);
}

.sample-row:hover .sample-type-icon {
  opacity: 0.6;
}

/* The play and stop glyphs are built with .sample-type-icon on them, so they
   inherited its 0.32 dimming — right for a decorative loop or one-shot glyph,
   wrong for the control that says what is playing. The footer's play button
   shows var(--glow) at full strength; this is the same control and reads the
   same. */
.sample-type-icon.sample-transport-icon,
.sample-row:hover .sample-type-icon.sample-transport-icon {
  opacity: 1;
}

/* ── the list header ───────────────────────────────────────────────────────
   Two things were wrong here at once. Every column carried its own underline
   at forty per cent white — barely there on the old grey, a row of bright
   dashes on black — and only the sortable columns were styled as labels, so
   PACK and LENGTH came out in a different size and weight from SAMPLE NAME and
   TEMPO. One line under the whole row, one treatment for all six words. */

.sample-list-header .sample-col,
.sample-list-header .sample-col--pack,
.sample-list-header .sample-col--type,
.sample-list-header .sample-col--name,
.sample-list-header .sample-col--length,
.sample-list-header .sample-col--tempo,
.sample-list-header .sample-col--key,
.sample-list-header .sample-col--scale,
.sample-list-header .sample-col--rating,
.sample-list-header .sample-sort-btn {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
  border-bottom: 0;
}

.sample-list-header .sample-sort-btn:hover {
  color: var(--text);
}

/* The name column drew its own separate underline, so the row ended up with a
   run of bright dashes of different lengths instead of one rule. */
.sample-list-header .sample-col--name .sample-sort-btn::after {
  display: none;
}

/* ── the keyboard ──────────────────────────────────────────────────────────
   A piano is read by the difference between its two kinds of key, and the
   repaint had left both of them nearly black. The naturals come back as a
   surface you can see; the sharps stay on the ground, told apart by their edge
   and their shadow. */

.midi-piano-key--white {
  background: rgba(255, 255, 255, 0.16);
  border-left: 1px solid rgba(0, 0, 0, 0.55);
  border-bottom: 0;
}

.midi-piano-key--black {
  background: var(--ink);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-top: 0;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
}

.midi-piano-keys {
  background: var(--ink);
  border-radius: 0;
}

/* ── transport ─────────────────────────────────────────────────────────────
   A toggle that is on was a solid block of the accent — the brightest thing on
   screen, for a headphone icon. It now says the same thing the way everything
   else says it: the icon takes the colour, and the ground stays where it is. */

/* The icons ship white, so a toggle that is on tints its icon rather than
   filling the space behind it. */
/* ── transport ─────────────────────────────────────────────────────────── */

/* ── scrollbars ────────────────────────────────────────────────────────── */

* {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.16) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.16);
  background-clip: content-box;
  border: 2px solid transparent;
  border-radius: 5px;
}

*::-webkit-scrollbar-thumb:hover {
  background-color: rgba(255, 255, 255, 0.26);
  background-clip: content-box;
}

*::-webkit-scrollbar-corner {
  background: transparent;
}

/* ── inside the menus ──────────────────────────────────────────────────────
   The drop-downs were the last things still speaking the old language: mixed
   case, thirteen pixels, and controls drawn by the operating system in a cyan
   loud enough to be the brightest thing on screen. They now read the way the
   rest of the window reads. */

.subheader-source-name,
.bpm-radio-label {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
}

.subheader-source-row:hover .subheader-source-name {
  color: var(--text);
}

.subheader-source-count {
  font-size: calc(10px * var(--ui-font-scale, 1));
  letter-spacing: 0.06em;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.bpm-range-sep {
  color: var(--faint);
  font-size: calc(11px * var(--ui-font-scale, 1));
}

/* A tick and a dot, drawn here rather than by the system: a hairline outline
   that takes the colour only once it is chosen. */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 13px;
  height: 13px;
  margin: 0;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  cursor: pointer;
  transition: border-color 0.15s var(--ease);
}

input[type="radio"] {
  border-radius: 50%;
}

input[type="checkbox"]:hover,
input[type="radio"]:hover {
  border-color: rgba(255, 255, 255, 0.45);
}

input[type="checkbox"]:checked,
input[type="radio"]:checked {
  border-color: var(--glow);
}

input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: 1px;
  background: var(--glow);
}

input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--glow);
}

/* The two buttons at the foot of a menu: words, like every other button. */
.bpm-clear,
.bpm-save {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0;
  text-decoration: none;
  padding: 4px 0;
  transition: color 0.15s var(--ease);
}

.bpm-clear:hover,
.bpm-save:hover {
  color: var(--text);
  background: none;
}

.bpm-save {
  color: var(--glow);
}

.bpm-footer {
  border-top: 1px solid var(--divider);
}

/* ── fields ────────────────────────────────────────────────────────────────
   MOTION's house rule, in its own words: nothing is drawn like a form. It
   singles out the one control that carried a border AND a background as the
   reason that control "read as something from another application pasted into
   this one" — which is exactly what a bordered pill sitting in this window
   looks like.

   So a field is not a box. It is the text you type, on a rule that lights up
   when the caret is in it. The main search already worked this way; everything
   else now does too. */

input[type="text"],
input[type="search"],
input[type="number"],
input[type="email"],
input[type="password"],
textarea,
.subheader-source-search,
.bpm-exact-input,
.bpm-range-input {
  background: none;
  border: 0;
  border-bottom: 1px solid var(--divider);
  border-radius: 0;
  color: var(--text);
  font-size: calc(12px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  transition: border-color 0.15s var(--ease);
}

/* The folder search sits in a sticky strip that paints itself so the list can
   scroll under it. That strip was painted the application's own black, which is
   darker than the menu holding it — so a box appeared around the field even
   after the field stopped drawing one. It takes the menu's ground instead. */
.subheader-source-search-wrap {
  background: var(--raised);
}

/* Made to read as the same object as the search at the top of the window: the
   words you type, on a rule that lights up under them. */
/* Left in ordinary case: small capitals are for signposts, and this is a place
   you type a folder's name — which is not written in capitals either. */
.subheader-source-search {
  height: 25px;
  padding: 0 2px;
  font-size: calc(12px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  text-transform: none;
  border-bottom: 1.5px solid var(--divider);
}

.subheader-source-search::placeholder {
  text-transform: none;
  letter-spacing: 0.02em;
}

.subheader-source-search:hover {
  border-bottom-color: var(--hairline);
}

/* The same gradient the main search lights up with, so the two are plainly the
   same control in two places. */
.subheader-source-search:focus {
  border-bottom-color: transparent;
  background: linear-gradient(90deg, var(--glow) 0%, rgba(var(--glow-rgb), 0.35) 100%) no-repeat bottom / 100% 1.5px;
}

input::placeholder,
textarea::placeholder {
  color: var(--faint);
}

input:hover,
textarea:hover {
  border-bottom-color: var(--hairline);
}

input:focus,
textarea:focus {
  border-bottom-color: var(--glow);
  background: none;
}

/* ── focus ─────────────────────────────────────────────────────────────────
   Never a rectangle drawn around the outside. The system's focus ring belongs
   to the system, not to this window, and it lands on top of whatever it
   surrounds. A field says it has the caret by lighting its own rule; anything
   else says it by taking the colour, from the inside. */

*:focus,
*:focus-visible,
input:focus,
input:focus-visible,
textarea:focus,
button:focus,
button:focus-visible,
[contenteditable]:focus {
  outline: none;
  box-shadow: none;
}

/* An ordinary button says nothing about having the focus, and that is the whole
 * rule now. It used to draw a one-pixel white rectangle just inside its edge -
 * a rectangle after all, which is what the paragraph above says not to do, and
 * it looked like the browser's own ring rather than anything of this window's.
 *
 * What made it a bug rather than a matter of taste is when it turned up. Click
 * a footer button with the mouse: the button keeps the focus, and :focus-visible
 * is false, so nothing shows - correct. Then press an arrow key to move down the
 * list, and Chromium decides the session is now being driven by the keyboard and
 * flips :focus-visible to true on whatever is focused. The button that was
 * clicked a minute ago lights a box and holds it, through every arrow key after
 * that. It reads as a stuck hover on a control nobody is pointing at.
 *
 * There is no CSS that can tell "tabbed here" from "clicked here and then typed
 * somewhere else", so the mark goes. It costs little: this window has five
 * deliberate tab stops - the four envelope knobs and the sidebar's edge - and
 * they are exactly the things the arrow keys really do drive. Those keep their
 * own accent outline from styles.css, which is more specific than the reset
 * above and therefore survives it untouched. Nothing else was ever reachable by
 * Tab on purpose.
 *
 * Enter and Space on a focused button were checked before deciding this was
 * only about appearances: the window's own key handling takes both first, so
 * the button underneath does not fire. */

/* Named only because each draws a pale rectangle of its own in styles.css, and
   a class beats the reset above. */
.footer-btn:focus-visible,
.footer-transport-btn:focus-visible {
  outline: none;
  box-shadow: none;
}

/* A drop target is feedback rather than focus, but a rectangle drawn outside an
   element is the same unwelcome shape either way. */
#collections-likes-btn.drag-over {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--glow);
}

::selection {
  background: rgba(var(--glow-rgb), 0.28);
  color: var(--text);
}

/* ── the accent picker ─────────────────────────────────────────────────────
   One colour runs the whole window, so it gets one control, next to the
   account. Drawn the way everything else here is drawn: a word for a heading,
   hairlines, and no chrome around anything. The swatches are the only place in
   the interface where colour is the subject rather than the signal, so they are
   allowed to be circles of pure colour. */

/* The title bar is centred and marked as a drag handle for the window, so a
   button dropped into its flow lands on the word "Sonus" and cannot be
   clicked. It is placed against the account button instead, and opted out of
   the drag region. */
.accent-menu {
  position: absolute;
  top: 6px;
  right: 46px;
  z-index: 80;
  display: inline-flex;
  align-items: center;
  -webkit-app-region: no-drag;
}

html[data-platform="win32"] .accent-menu {
  right: auto;
  left: 46px;
}

.accent-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  cursor: pointer;
  transition: color 0.15s var(--ease);
}

.accent-menu-btn svg {
  width: 16px;
  height: 16px;
}

/* The control that picks the colour is drawn in it — the only thing in the
   window that says what it does by being it. */
.accent-menu-btn {
  color: var(--glow);
}

.accent-menu-btn:hover,
.accent-menu-btn[aria-expanded="true"] {
  color: var(--glow);
  opacity: 0.75;
}

.accent-popover {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 60;
  width: 184px;
  padding: 12px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.55);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease);
}

.accent-popover[aria-hidden="false"] {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.accent-popover-title {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
  margin-bottom: 10px;
}

.accent-swatches {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 7px;
}

.accent-swatch {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  padding: 0;
  background: var(--swatch);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  /* Only the hairline moves now, at the speed the context-menu swatches use. */
  transition: box-shadow 80ms ease;
}

/* The same two signs the colour swatches in the sidebar's context menu use: a
   white hairline on hover, a small dark dot on the one in use. The picker had
   neither — it grew the swatch under the pointer and drew nothing at all for
   the current colour, so there was no way to tell which one was selected.
   paintSwatches() had been setting .is-selected all along with no rule to
   match it.

   The dot is 6px rather than the 5px used there because these swatches are
   larger — 21px against 15px — and the same dot would read lighter. */
.accent-swatch:hover {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.72);
}

.accent-swatch.is-selected::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

/* ── context menus ─────────────────────────────────────────────────────────
   A menu opened with the right button is still a menu. These were drawn in a
   different black with a different corner and their entries in a different
   voice, which is exactly the kind of small disagreement that makes a window
   feel assembled rather than designed. They read as the drop-downs now. */

.sidebar-ctx-item,
.sample-ctx-item,
.sample-ctx-col-btn {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 7px;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.sidebar-ctx-item:hover,
.sample-ctx-item:hover,
.sample-ctx-col-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.055);
}

.sidebar-ctx-item--danger,
.sidebar-ctx-item--delete,
.sample-ctx-item--danger {
  color: var(--danger);
}

.sidebar-ctx-item--danger:hover,
.sidebar-ctx-item--delete:hover,
.sample-ctx-item--danger:hover {
  color: var(--danger);
  background: rgba(255, 107, 94, 0.09);
}

.sidebar-ctx-separator,
.sample-ctx-separator {
  background: var(--divider);
}

/* A tooltip carries a few words, so it keeps the ground and the edge of a menu
   at a size that suits a few words. */
.sidebar-new-badge-tooltip {
  padding: 6px 9px;
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.06em;
}

/* ── transport toggles ─────────────────────────────────────────────────────
   A toggle that is on was a solid block of the accent — the brightest thing on
   screen, for a headphone icon. The icon takes the colour instead and the
   ground stays where it is.

   This rule was written once and lost: a script removing the Stack selectors
   matched the last name in its group and took the others with it, which is the
   same way the menus came back transparent. */

.footer-btn,
.footer-btn[data-footer-btn="midi"] {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.footer-btn:hover {
  background: none;
}

.footer-btn[aria-pressed="true"],
.footer-btn[data-footer-btn="midi"][aria-pressed="true"] {
  background: none;
  box-shadow: none;
}

.footer-btn[aria-pressed="true"] img {
  opacity: 1;
  filter: var(--glow-filter);
}

/* Locked on, which is on with one thing you cannot do.
 *
 * It already carries aria-pressed, so the rule above has tinted the icon to
 * the accent like every other lit control down there. The only thing left to
 * say is that this one is not yours to change here, and that is said by
 * holding it back a little rather than by a filled block: the cursor does not
 * become a hand, and the tooltip names the setting that owns it. */
/* Armed, on the installer's own Cancel.
 *
 * Same two-step as the button under the analysis bar, and the same colour for
 * it, but a different element: that one is .analysis-progress-cancel and this
 * one is the modal's .import-modal-btn, so the rule has to be written twice
 * even though the idea is written once. Scoped by id rather than to every
 * import-modal button — the others have nothing to confirm. */
#ai-setup-cancel.is-armed,
#ai-setup-cancel.is-armed:hover {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
}

.footer-btn--loop-locked[aria-pressed="true"] img {
  /* The attribute is in the selector to win, not for show: the pressed rule
     above sets opacity to 1 at one class and one attribute, and a single class
     here loses to it. Locked is always pressed, so naming both costs nothing
     and puts the two rules on equal terms — after which this one, being later,
     decides. */
  opacity: 0.62;
}

/* ── where capitals belong ─────────────────────────────────────────────────
   One rule decides this, because otherwise every new element is a fresh
   argument: small capitals name the parts of the interface, and everything the
   library itself says keeps the case it was written in.

   So PLACES, COLLECTIONS, PACK, TEMPO, SHUFFLE — the window's own vocabulary —
   stay in capitals. Tags, genres, instruments, key names, tab titles and every
   entry in a menu do not: those are the user's words, not ours. Setting them in
   capitals also destroyed information, since a tag stored as "FX" and one
   stored as "Hats" came out looking like the same kind of thing.

   Wide tracking goes with the capitals and is dropped along with them: letters
   spread that far apart are a device for reading four words at a glance, not a
   paragraph. */

.sample-tag,
.subheader-item,
.subheader-item-label,
.header-dropdown-item,
.account-popover-item,
.sidebar-ctx-item,
.sample-ctx-item,
.sample-ctx-col-btn,
.header-select,
.app-tab,
.key-btn,
.key-tab,
.subheader-source-name,
.bpm-radio-label {
  text-transform: none;
  letter-spacing: 0.01em;
}

/* Tags carry the most text on screen and the least weight in the reading, so
   they stay a step smaller and a step quieter than the name above them. */
.sample-tag {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  letter-spacing: 0.015em;
}

/* A tab is named after what the user put in it. */
.app-tab {
  font-size: calc(12px * var(--ui-font-scale, 1));
}

/* The value of a filter is read, not scanned. */
.header-select {
  font-size: calc(12px * var(--ui-font-scale, 1));
}

/* ── one label, one treatment ──────────────────────────────────────────────
   PLACES, PACK, TEMPO, INSTRUMENTS, SHUFFLE and ALL are the same kind of thing
   — a word in small capitals naming a part of the window — and they had grown
   six different treatments between them: four sizes, five trackings and two
   weights. Nothing was wrong with any one of them; what was wrong was that
   there were six.

   There is one now. Size, weight and tracking are fixed; only the colour
   moves, and it says what the label is for rather than how important it is:
   dim for a name you read, brighter for a word you can click, the accent for
   one that is doing something. */

.sidebar-section-label,
.sample-list-header,
.sample-list-header *,
.sample-list-header .sample-col,
.sample-list-header .sample-sort-btn,
.header-label,
.accent-popover-title {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.3);
}

.header-btn,
.subheader-trigger,
.subheader-type-btn,
.sidebar-add-btn {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  line-height: 1.4;
  color: var(--dim);
}

.subheader-type-btn.is-active,
.header-btn--clear,
.subheader-dropdown.has-selection .subheader-trigger {
  color: var(--glow);
}

/* ── the header row sits on one line ───────────────────────────────────────
   A filter's name and its value were two different sizes in boxes of two
   different heights, centred rather than aligned, so no two words in the row
   shared a baseline. Giving them the same line box is what puts them back on
   one line. */

.header-label,
.header-select,
.header-search-text,
.header-btn {
  line-height: 1.4;
}

.header-field,
.header-dropdown {
  align-items: baseline;
}

/* The waveform column was left out of the rule that drops every heading to the
   foot of its cell, so that one word floated twenty pixels above the rest. */
.sample-list-header .sample-col--waveform {
  height: 100%;
  display: flex;
  align-items: flex-end;
  padding-bottom: 4px;
}

/* The last two pixels. These buttons sit in the same rows as the labels they
   answer to, and each carried a different vertical padding — one, two and three
   pixels — so no two words in a row landed on the same line. Only the vertical
   padding is equalised: the horizontal padding is where each of these keeps
   room for its own chevron, and taking that away moved the arrows. */
.header-btn,
.subheader-trigger,
.subheader-type-btn {
  padding-top: 3px;
  padding-bottom: 3px;
}

/* ── fewer capitals ────────────────────────────────────────────────────────
   Three rows of small capitals stacked on top of each other is a wall, whatever
   the tracking. MOTION carries it because it has a dozen such words on screen;
   this window had over twenty before the first sample.

   So the line moves in: capitals are kept for the two places that are genuinely
   furniture — the headings over a section of the sidebar, and the headings over
   the columns of the table. Everything you can click reads as an ordinary word.
   Those are still told apart from the content, by weight and by colour, which
   is enough. */

.header-label,
.header-btn,
.subheader-trigger,
.subheader-type-btn,
.sidebar-add-btn,
.app-tab-close,
.header-search-btn,
.bpm-clear,
.bpm-save,
.key-tab,
.accent-popover-title {
  text-transform: none;
  letter-spacing: 0.03em;
  font-size: calc(12px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
}

/* The value of a filter is what you read; its name is what tells you which
   filter it is, so the name steps back rather than shouting. */
.header-label {
  font-weight: var(--ui-weight-regular, 400);
  color: rgba(255, 255, 255, 0.42);
}

/* ── fewer rules ───────────────────────────────────────────────────────────
   Four horizontal lines inside a hundred and twenty pixels, and one of them
   drawn twice: the column headings carried a solid border and the gradient
   meant to replace it, stacked. Two remain, and each is doing something — one
   closes the controls, one closes the table's head. */

.app-header {
  border-top: 0;
  border-bottom: 0;
}

.sample-list-header {
  border-bottom: 0;
}

/* ── things painted in the colour of the ground ────────────────────────────
   The scroll thumb was filled with the sidebar's own background. That worked
   while the sidebar was a mid grey on a lighter grey; once everything became
   one near-black it was black on black, and the bar disappeared. Two more
   controls were caught the same way. They are white held back, like every
   other mark in this window. */

.app-main-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: var(--r-pill, 999px);
}

.app-main-scrollbar:hover .app-main-scrollbar-thumb,
.app-main-scrollbar.is-dragging .app-main-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.3);
}

.volume-track-inner {
  background: rgba(255, 255, 255, 0.12);
}

/* ── the keyboard, as MOTION draws it ──────────────────────────────────────
   Its values, to the number. The naturals are white held at fifty-six per cent
   rather than near-white, so the keyboard sits in the window instead of on it;
   the sharps are a black slightly lifted off the ground, rounded only at the
   bottom, where a key would be.

   The mask is the part worth copying carefully, and MOTION says why in its own
   source: the top of every key dissolves into the picture, and it is done to
   the whole strip rather than to each kind of key — a white key that faded
   while the black one beside it stopped on a hard edge was the tell. Masked as
   one, the keyboard has no top edge at all. */

.midi-piano-keys {
  background: transparent;
  border-radius: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom,
                      rgba(0, 0, 0, 0) 0%,
                      rgba(0, 0, 0, 0.28) 26%,
                      rgba(0, 0, 0, 0.78) 56%,
                      rgba(0, 0, 0, 1) 82%);
  mask-image: linear-gradient(to bottom,
              rgba(0, 0, 0, 0) 0%,
              rgba(0, 0, 0, 0.28) 26%,
              rgba(0, 0, 0, 0.78) 56%,
              rgba(0, 0, 0, 1) 82%);
}

.midi-piano-key {
  transition: background 0.09s var(--ease), box-shadow 0.09s var(--ease);
}

.midi-piano-key--white {
  height: 100%;
  background: rgba(255, 255, 255, 0.56);
  border-left: 0;
  border-right: 1px solid rgba(0, 0, 0, 0.5);
  border-bottom: 0;
}

.midi-piano-key--white:hover {
  background: rgba(255, 255, 255, 0.74);
}

.midi-piano-key--black {
  height: 62%;
  z-index: 2;
  background: #06060a;
  border: 0;
  border-radius: 0 0 2px 2px;
  box-shadow: none;
}

.midi-piano-key--black:hover {
  background: #17171f;
}

/* A key that is down takes the accent and carries a little of it into the air
   around itself. */
.midi-piano-key--white.is-active,
.midi-piano-key--black.is-active,
.midi-piano-key.is-active {
  background: var(--glow);
  box-shadow: 0 0 12px -3px var(--glow);
}

.midi-piano-key--white.is-active {
  transform: none;
}

.midi-piano-key--black.is-active {
  transform: translateX(-50%);
}

/* ── the column headings ───────────────────────────────────────────────────
   The last row of capitals in the window, and the one with the most words in
   it. Set in ordinary case they stop being a band of texture across the top of
   the list and go back to being seven short words naming seven columns.

   Nothing else changes about how they read: they are still quieter than the
   samples underneath and still the heavier weight, which is what separates a
   heading from a row. The tracking goes with the capitals — letters spread
   apart are a device for reading a word as a shape, and a word in ordinary
   case is read as a word. */

.sample-list-header,
.sample-list-header *,
.sample-list-header .sample-col,
.sample-list-header .sample-col--pack,
.sample-list-header .sample-col--type,
.sample-list-header .sample-col--name,
.sample-list-header .sample-col--waveform,
.sample-list-header .sample-col--length,
.sample-list-header .sample-col--tempo,
.sample-list-header .sample-col--key,
.sample-list-header .sample-col--scale,
.sample-list-header .sample-col--rating,
.sample-list-header .sample-sort-btn {
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.015em;
  text-transform: none;
  color: rgba(255, 255, 255, 0.34);
}

.sample-list-header .sample-sort-btn:hover {
  color: var(--text);
}

/* Three stragglers the sweep left behind: the placeholder in the search field,
   the trigger of a header drop-down, and the "any" that clears a key filter.
   They are words you read, so they are set like words you read. */
.header-search-text::placeholder,
.header-dropdown-trigger,
.key-btn--any {
  text-transform: none;
  letter-spacing: 0.02em;
}

.header-search-text::placeholder {
  font-size: calc(13px * var(--ui-font-scale, 1));
}

/* ── the sample loaded into the keyboard ───────────────────────────────────
   Staying marked after it has finished playing is right — it is still the
   sample the keyboard will play. What was wrong was how it was marked: a wash
   of the old cyan and a bar down its leading edge, written in the space-and-
   slash colour syntax that the passes over this stylesheet never matched. It
   is the accent now, and the bar is gone. */

.sample-row.is-midi-source {
  box-shadow: none;
}

/* The waveform column keeps its width so the row still lines up, but the word
   over it said nothing the pictures underneath do not. */
.sample-list-header .sample-col--waveform {
  border-bottom: 0;
}

/* ── which line fades and which does not ───────────────────────────────────
   The rule under the filters closes the controls and runs the full width of
   the window, so it fades out at both ends the way MOTION's dividers do — it
   separates two regions rather than underlining anything.

   The rule under the column headings is doing the opposite job: it belongs to
   the table, it is the same width as the rows below it, and it should start
   and stop with them. A fade there reads as a mistake in the drawing. */

.app-subheader {
  border-bottom: 0;
  position: relative;
}

.app-subheader::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent 0,
    var(--hairline) 15%,
    var(--hairline) 85%,
    transparent 100%);
}

.sample-list-header::after {
  background: var(--divider);
}

/* ── every rule fades ──────────────────────────────────────────────────────
   One treatment for every horizontal line in the window, including the fifty
   between the samples. */

.sample-list-header::after {
  background: linear-gradient(90deg,
    transparent 0,
    var(--divider) 4%,
    var(--divider) 96%,
    transparent 100%);
}

/* ── UN TRAIT EST UN TRAIT, PLUS UN FOND ───────────────────────────────────
 *
 * Les cinquante traits d'une page etaient dessines comme background-image de
 * la rangee, pour ne pas payer une boite de plus par ligne. Cinquante boites
 * de un pixel ne se mesurent pas. Ce que ca coutait vraiment, c'est qu'une
 * seule propriete portait trois choses sans rapport : le surlignage, le trait,
 * et la place de la rangee dans la liste. Chaque regle qui parlait de l'une
 * redeclarait les deux autres, et la plus specifique gagnait sans qu'aucune ne
 * sache qu'elle avait perdu.
 *
 * Trois bugs, et les trois de la :
 *
 *   La premiere rangee reprenait son trait des qu'elle etait survolee, cliquee
 *   ou simplement deja ecoutee, juste sous celui de la tete. Double trait.
 *   Comme .is-played reste, ca apparaissait dans une place et pas dans une
 *   autre, sans raison visible.
 *
 *   La derniere perdait celui du bas des que la rangee au-dessus etait
 *   surlignee : la regle de voisinage pese trois classes, :last-child en pese
 *   deux, donc elle gagnait et redeclarait l'image entiere, les deux traits
 *   remplaces par un seul. Une collection de deux samples suffisait a le voir.
 *
 *   Et rien de tout ca dans la vue All, ou l'on n'a encore rien ecoute.
 *
 * Chaque chose a donc sa propriete : le surlignage est un background-color, le
 * trait est un pseudo-element. Aucun des deux ne peut plus effacer l'autre. */
.sample-row {
  border-bottom: 0;
  background-image: none;
}

/* LE TRAIT EST CELUI DU HAUT DE LA RANGEE, PAS DE SON BAS.
   Dessine en bas, le trait au-dessus d'une rangee appartient a la rangee
   PRECEDENTE : il vaut alors du blanc a 5,5 % pose sur du noir, juste au-dessus
   d'un surlignage de blanc a 5 %. Deux gris qu'on ne distingue pas, donc plus
   de trait du tout au-dessus d'une rangee cliquee. Dans sa propre boite il est
   compose sur le surlignage, et les deux bords se voient. */
.sample-row::before,
.sample-row:last-child::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent 0,
    var(--divider) 4%,
    var(--divider) 96%,
    transparent 100%);
}

.sample-row::before { top: 0; }

/* La derniere ferme la liste, et personne d'autre ne le fera. */
.sample-row:last-child::after { bottom: 0; }

/* La premiere n'en a pas : la tete en pose deja un, et deux traits colles se
   lisent comme un trait epais. « La premiere », c'est celle qui SUIT LA TETE et
   pas :first-child : le premier enfant de la liste est la tete elle-meme. */
.sample-list-header + .sample-row::before {
  content: none;
}

/* Et quand c'est cette premiere rangee qui est surlignee, c'est le trait de la
   TETE qui se renforce. Le probleme est reel : du 5,5 % pose juste au-dessus
   d'un surlignage a 5 %, ce sont deux gris qu'on ne distingue pas. La reponse
   d'avant etait de rendre son trait a la rangee, ce qui en faisait deux. Un
   seul, plus franc. */
.sample-list-header:has(+ .sample-row:is(:hover, .is-focused, .is-playing, .is-midi-source, .is-played))::after {
  background: linear-gradient(90deg,
    transparent 0,
    var(--hairline) 4%,
    var(--hairline) 96%,
    transparent 100%);
}

/* Meme raison sous une rangee surlignee : son bord du bas est le trait du haut
   de la rangee suivante, pose sur du noir. Il prend --hairline, les 9 % que la
   palette tient deja pour un trait franc, aucune valeur inventee ici. */
.sample-row:is(:hover, .is-focused, .is-playing, .is-midi-source, .is-played) + .sample-row::before {
  background: linear-gradient(90deg,
    transparent 0,
    var(--hairline) 4%,
    var(--hairline) 96%,
    transparent 100%);
}

/* ── one head, one rule ────────────────────────────────────────────────────
   The filters and the column headings were each closed by their own line, the
   same weight and the same width, ninety pixels apart with nothing between
   them. Two identical rules that close in a row read as a pattern rather than
   as a structure, and the band between them reads as an empty container.

   They are one thing — everything above the samples — so one line closes them,
   under the column headings, where the list actually begins. */

.app-subheader::after {
  display: none;
}

/* The line under the column headings appears when the list moves under it and
   goes away when it stops. At the top there is nothing to separate the
   headings from — the gap does that — and a line drawn there is just a line. */
.sample-list-header::after {
  opacity: 0;
  transition: opacity 0.22s var(--ease);
}

.sample-list-header.is-scrolled::after {
  opacity: 1;
}

/* Corrected: it is the rule under the filters that comes and goes, not the one
   under the column headings. The headings belong to the table and are always
   closed off from it; the filters only need separating from the list once the
   list has slid up under them. */
.sample-list-header::after {
  opacity: 1;
}

.app-subheader::after {
  display: block;
  opacity: 0;
  transition: opacity 0.22s var(--ease);
}

.app-subheader.is-scrolled::after {
  opacity: 1;
}

/* Les titres etaient a quatre pixels de leur trait et a huit de la premiere
   rangee : ils se lisaient comme colles au trait et detaches du tableau. La
   hauteur les en eloigne.

   La marge, elle, est nulle, et c'est la POCHETTE qui donne la mesure. Une
   rangee porte dix pixels au-dessus de sa pochette et dix au-dessous, jusqu'a
   son propre trait du bas. Dix de marge ici en ajoutaient dix aux dix de la
   rangee : vingt au-dessus de la premiere pochette contre dix au-dessous, et
   la premiere rangee flottait sous la tete. Sans marge, le trait de la tete est
   a la meme distance de la pochette que celui du bas de la rangee. */
.sample-list-header {
  height: 34px;
  margin-bottom: 0;
}

.sample-list-header .sample-col,
.sample-list-header .sample-col--pack,
.sample-list-header .sample-col--type,
.sample-list-header .sample-col--name,
.sample-list-header .sample-col--waveform,
.sample-list-header .sample-col--length,
.sample-list-header .sample-col--tempo,
.sample-list-header .sample-col--key,
.sample-list-header .sample-col--scale,
.sample-list-header .sample-col--rating {
  padding-bottom: 10px;
}

/* ── the wordmark ──────────────────────────────────────────────────────────
   MOTION's size and weight — fifteen pixels, medium — and its idea: a name set
   wide enough to read as a mark rather than as a word.

   Not its number, though. MOTION tracks its wordmark at 0.42em, which capitals
   carry easily because they share one height and have no descenders. The same
   spacing on lowercase pulls the word apart into loose letters. The effect is
   kept and the amount is a third of it. */

.app-titlebar-text {
  font-size: calc(15px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: none;
  color: #fff;
}

/* Tracking is added after every letter, including the last, so the word sits
   half a space left of true centre. */
.app-titlebar-title {
  padding-left: 0.14em;
}

/* ── the tag under the pointer ─────────────────────────────────────────────
   Pointing at a tag did nothing, and the reason was a rule of mine: hovering a
   tag also hovers its row, and `.sample-row:hover .sample-tag` counts three
   classes against the two of `.sample-tag:hover`. The row won, so the tag kept
   the row's colour. Named against a hovered row, the tag wins its own state
   back. */

.sample-row:hover .sample-tag:hover,
.sample-row.is-focused .sample-tag:hover,
.sample-row.is-playing .sample-tag:hover,
.sample-tag:hover {
  color: var(--glow);
  background: none;
  cursor: pointer;
}

/* ── the shuffle mark ──────────────────────────────────────────────────────
   Drawn in the button's own colour, so it dims and brightens with the word
   rather than living a life of its own beside it. */

.header-btn-icon {
  width: 13px;
  height: 13px;
  margin-right: 6px;
  vertical-align: -2px;
  flex: 0 0 auto;
}

.header-btn[data-header-btn="shuffle"] {
  display: inline-flex;
  align-items: center;
}

/* The three filter names carry a mark of their own, drawn to the same weight
   and the same stroke as the one on Shuffle so the row reads as one set. Like
   that one, they take the label's colour rather than having one of their own. */

.header-label {
  display: inline-flex;
  align-items: center;
}

.header-label-icon {
  width: 13px;
  height: 13px;
  margin-right: 6px;
  flex: 0 0 auto;
  opacity: 0.85;
}

/* ── pagination ────────────────────────────────────────────────────────────
   The heavy line above these numbers was two lines a pixel apart: the last
   sample row draws its own, and the bar underneath drew a second one that
   stopped dead at both ends. The row's is enough.

   And a page number is a word, like every other thing you can click here. The
   one you are on takes the colour; it does not sit in a box. */

.pagination-bar {
  border-top: 0;
  background: transparent;
}

.pagination-btn,
.pagination-page-btn {
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  transition: color 0.15s var(--ease);
}

.pagination-btn:hover:not(:disabled),
.pagination-page-btn:hover {
  background: transparent;
  color: var(--text);
}

.pagination-page-btn.is-active,
.pagination-page-btn.is-active:hover {
  background: transparent;
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

/* The arrows are flat images, so they follow the buttons by being tinted. */
.pagination-btn:hover:not(:disabled) .pagination-icon {
  opacity: 1;
  filter: brightness(0) invert(1);
}

/* ── the filter marks, sharpened ───────────────────────────────────────────
   They were washed out and soft for two reasons that compounded: an opacity of
   0.85 laid over a colour already held at forty-two per cent white, and a
   1.25-wide stroke drawn into twelve and a bit pixels, which lands the line
   between two of them and asks the renderer to smear it across both.

   The opacity is gone — they take the label's colour exactly, which is what
   made the shuffle mark read cleanly. The stroke is a whole pixel and the box
   is a whole even number, so every line falls on a pixel boundary.

   Adding a mark to a label makes that label wider; there is no way around it.
   What can be given back is the space around them, so the row keeps its shape
   instead of pushing the values along. */

.header-label-icon,
.header-btn-icon {
  width: 14px;
  height: 14px;
  margin-right: 5px;
  opacity: 1;
  shape-rendering: geometricPrecision;
}

.header-label-icon path,
.header-label-icon rect,
.header-btn-icon path {
  stroke-width: 1;
}

.header-field {
  gap: 6px;
}

.header-controls-center {
  gap: 20px;
}

/* ── the icon must not set the baseline ────────────────────────────────────
   The values sat low because the labels were laid out as inline-flex boxes,
   and a flex box takes its baseline from its first item. That item is now the
   icon, which has no text in it, so each label was aligned by its picture
   rather than by its word — and the row's baseline alignment dutifully lined
   the values up with a graphic.

   The labels go back to being ordinary text with the mark set inline inside
   them, so their baseline is their word's, which is what the value is aligned
   against. */

.header-label {
  display: inline;
}

.header-label-icon {
  display: inline-block;
  vertical-align: -0.18em;
}

.header-btn[data-header-btn="shuffle"] {
  display: inline;
}

.header-btn-icon {
  display: inline-block;
  vertical-align: -0.18em;
}

/* ══ the three filter menus ═══════════════════════════════════════════════
   Tempo, Key and Scale had each been designed on their own terms and none of
   them on the window's: text a third larger than anywhere else, keys drawn as
   boxes, a tick beside every choice including the ones not chosen, and a tab
   underlined across half the menu. Brought onto the same footing as the rest.
   ══════════════════════════════════════════════════════════════════════════ */

/* A tick that appears on every line says nothing. It marks the chosen one and
   nothing else — the space it occupies is kept, so the labels stay in column. */
.subheader-item::before,
.header-dropdown-item::before {
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}

.subheader-item.is-selected::before,
.header-dropdown-item.is-selected::before {
  opacity: 1;
}

/* ── the trigger ─────────────────────────────────────────────────────────── */

.header-select:hover,
.header-dropdown.is-open .header-select {
  background: none;
  border: 0;
  outline: none;
  box-shadow: none;
}

/* ── one size inside a menu ──────────────────────────────────────────────── */

.bpm-menu,
.bpm-menu *,
.key-menu,
.key-menu * {
  font-size: calc(11px * var(--ui-font-scale, 1));
}

.bpm-radio-label,
.key-tab,
.bpm-clear,
.bpm-save,
.key-btn,
.key-btn--any {
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  text-transform: none;
}

/* ── the keys ─────────────────────────────────────────────────────────────
   Twelve outlined boxes were the densest thing in the window. The notes stand
   on their own; the one you pick takes the colour. */

.key-btn {
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--dim);
  padding: 5px 0;
}

.key-btn:hover {
  background: rgba(255, 255, 255, 0.05);
  border-color: transparent;
  color: var(--text);
}

.key-btn.is-selected {
  background: none;
  border-color: transparent;
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

.key-btn--any,
.key-btn--any:hover,
.key-btn--any.is-selected {
  border-color: transparent;
  background: none;
}

/* ── the two tabs ─────────────────────────────────────────────────────────
   The rule ran the full half-width of the menu under whichever tab was live,
   which is a lot of line for two words. It is the width of the word now, and
   the tabs sit on a hairline rather than a visible border. */

.key-tabs {
  border-bottom: 1px solid var(--divider);
  gap: 4px;
}

.key-tab {
  flex: 0 0 auto;
  padding: 4px 10px 7px;
  border-bottom: 1px solid transparent;
  margin-bottom: -1px;
}

/* ── exact / range ───────────────────────────────────────────────────────── */

.bpm-radio,
.bpm-mode-row input[type="radio"] {
  width: 11px;
  height: 11px;
}

.bpm-radio-label {
  gap: 7px;
  color: var(--dim);
}

.bpm-mode-row {
  gap: 14px;
}

.bpm-clear,
.bpm-save {
  letter-spacing: 0.06em;
}

/* ══ the tempo menu, laid out ═════════════════════════════════════════════
   A generic `padding: 6px` I put on every drop-down overrode the padding this
   one sets for itself, so its contents ended up against the edges, and the
   footer — thirty-three pixels tall with its text pinned to the top — left the
   empty band underneath that had nothing in it.

   Written out properly here: a menu with room around it, two rows that each
   read as one line, a field that belongs to the word beside it, and a footer
   whose two words sit on the same line as each other.
   ══════════════════════════════════════════════════════════════════════════ */

.bpm-menu {
  width: 236px;
  padding: 14px;
  gap: 2px;
}

/* The choice on the left, the number it takes on the right. */
.bpm-mode-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 30px;
  padding: 0;
}

.bpm-radio-label {
  gap: 9px;
  min-width: 0;
  color: var(--dim);
  cursor: pointer;
}

.bpm-range-inputs {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* The field keeps the rule-not-a-box treatment every other field in the window
   uses — it was never the underline that was wrong, it was an underline sitting
   in the middle of nowhere. Short, set against the same edge as everything else
   in the menu, on the line of the word it belongs to. */
.bpm-exact-input,
.bpm-range-input {
  width: 56px;
  height: 24px;
  padding: 0;
  text-align: center;
}

.bpm-exact-input:hover,
.bpm-range-input:hover {
  border-bottom-color: var(--hairline);
}

.bpm-exact-input:focus,
.bpm-range-input:focus {
  border-bottom-color: var(--glow);
}

.bpm-exact-input::placeholder,
.bpm-range-input::placeholder {
  color: var(--faint);
}

.bpm-range-sep {
  color: var(--faint);
}

/* Clear and Save on one line, with the rule above them at the same distance as
   the padding around the menu.

   No ground of its own. This footer had been swept into a rule that paints the
   application's own bars in `--ink`, which is darker than the menu holding it —
   so a black band appeared across the bottom of the popup. Inside a menu, the
   menu's ground is the only ground. */
.bpm-footer {
  align-items: center;
  min-height: 0;
  margin-top: 10px;
  padding: 10px 0 0;
  background: none;
  border-top: 1px solid var(--divider);
}

.bpm-clear,
.bpm-save {
  padding: 0;
  line-height: 1.4;
}

/* ══ the accent marks what is set, and nothing else ═══════════════════════
   I had this wrong. Asked to stop the values reading as dead grey, I painted
   them all accent — including "Any". But "Any" is the absence of a filter, so
   that put the one reserved colour on the one thing that means nothing is
   chosen. Counted at rest, the header carried eight accent items: three
   "Any", their three arrows, Clear, and the type filter. A colour worn by
   everything stops telling you anything.

   The rule, stated once and applied everywhere below: the accent marks what is
   actually set. A value that has not been chosen is simply legible — brighter
   than its label, quieter than a choice — and the moment you pick a tempo or a
   key, the accent appears exactly where the filter is.

   The arrow is a PNG tinted by a filter rather than by `color`, so each state
   has to be told twice or the mark and its word disagree. */

.header-select {
  color: var(--read);
}

.header-select .header-arrow {
  filter: brightness(0) saturate(0%) invert(66%);
}

/* Set: the value and its arrow take the colour together. */
.header-dropdown.has-selection .header-select {
  color: var(--glow);
}

.header-dropdown.has-selection .header-select .header-arrow {
  filter: var(--glow-filter);
}

.header-arrow {
  transition: filter 0.15s var(--ease);
}

/* Clear is an action, not a state, and with nothing filtered it has nothing to
   do — so it waits quietly with the other words instead of shouting in the
   corner. It was the eighth accent item in that header. */
.header-btn--clear {
  color: var(--dim);
}

.header-btn--clear:hover {
  color: var(--text);
}

/* Hover brightens to white, the way every other selected thing in the window
   does; the accent returns when the pointer leaves.

   Both halves are spelled out for the states that already had an opinion about
   the colour — a rule saying `has-selection` beats a plain `:hover`, so without
   this the word would have stayed accent while the arrow beside it turned
   white, which is the mismatch that keeps getting noticed. */
.header-select:hover,
.header-dropdown.has-selection .header-select:hover,
.header-dropdown.is-open .header-select:hover {
  color: var(--text);
}

.header-select:hover .header-arrow {
  filter: brightness(0) invert(1);
}

/* The name triggers get the same courtesy the other way round: their arrow
   follows their word, grey while nothing is chosen, accent once something is. */
.subheader-dropdown.has-selection .header-arrow {
  filter: var(--glow-filter);
}

.subheader-trigger:hover .header-arrow {
  filter: brightness(0) invert(1);
}

/* ══ one menu, one size ═══════════════════════════════════════════════════
   The Scale list was built as `min-width: 100%` of its own trigger, so it came
   out roughly ninety pixels wide — a cramped little box next to the Instruments
   list, which has a settled hundred and eighty and scrolls once it is long. Two
   menus doing the same job should not be two different objects.

   The tempo and key menus are left alone: they are not lists, they lay out
   their own contents and set their own widths.
   ══════════════════════════════════════════════════════════════════════════ */

.header-dropdown-menu:not(.bpm-menu):not(.key-menu) {
  min-width: 180px;
  max-height: 320px;
  overflow-y: auto;
  overflow-x: visible;
  scrollbar-width: thin;
  scrollbar-color: var(--faint) transparent;
}

/* And the rows inside them take the same height as the rows in the other list,
   rather than sitting two pixels tighter for no reason. */
.header-dropdown-item,
.subheader-item {
  padding: 5px 10px 5px 28px;
}

/* ══ keys, the way the pagination works ═══════════════════════════════════
   Each key was a filled, outlined box, and the chosen one carried a tinted
   ground on top of that. The pagination settled this question already: no
   ground and no outline, the word brightening under the pointer and taking the
   accent when it is the one selected. The grid holds itself together on
   spacing alone, as it does there.
   ══════════════════════════════════════════════════════════════════════════ */

.key-btn,
.key-btn--any {
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  transition: color 0.15s var(--ease);
}

.key-btn:hover,
.key-btn--any:hover {
  background: none;
  border: 0;
  color: var(--text);
}

.key-btn.is-selected,
.key-btn.is-selected:hover,
.key-btn--any.is-selected,
.key-btn--any.is-selected:hover {
  background: none;
  border: 0;
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

/* ══ the import dialogue, in the same language as everything else ═════════
   This modal was never touched by the repaint — it kept its own shapes while
   the rest of the window moved on. Its corners were two pixels where menus are
   twelve and rows are seven; it marked the chosen option with a tinted panel
   where the whole application marks a choice by colouring the word; and it
   ended in two filled boxes where there is not a single filled box left
   anywhere else.

   The colour was already right — it reads `--glow`, so it follows the palette.
   What follows is only shape and treatment.

   `.import-modal-btn` is used by several other dialogues too, so they come
   along with it.
   ══════════════════════════════════════════════════════════════════════════ */

/* Its title speaks like the other panel titles. */
.import-modal-title {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.03em;
  text-transform: none;
  color: rgba(255, 255, 255, 0.3);
  padding: 16px 16px 4px;
}

.import-modal-options {
  padding: 6px 8px 10px;
  gap: 2px;
}

/* A row in a list, with the radius rows have. */
.import-modal-option {
  padding: 9px 10px;
  border-radius: 7px;
  transition: background 0.15s var(--ease);
}

.import-modal-option:hover {
  background: rgba(255, 255, 255, 0.055);
}

/* No tinted panel. A choice is shown the way every other choice in the window
   is shown: the word takes the colour. */
.import-modal-option.is-selected {
  background: none;
}

.import-modal-option.is-selected:hover {
  background: rgba(255, 255, 255, 0.055);
}

.import-modal-option-label {
  font-size: calc(12px * var(--ui-font-scale, 1));
  color: var(--read);
  transition: color 0.15s var(--ease);
}

.import-modal-option.is-selected .import-modal-option-label {
  color: var(--glow);
}

.import-modal-option-desc {
  font-size: calc(11px * var(--ui-font-scale, 1));
  color: var(--faint);
  line-height: 1.45;
}

/* The radio matches the one in the tempo menu, to the pixel. */
.import-modal-option-radio {
  width: 11px;
  height: 11px;
  margin-top: 3px;
  border: 1px solid var(--faint);
  transition: border-color 0.15s var(--ease);
}

.import-modal-option:hover .import-modal-option-radio {
  border-color: var(--dim);
}

.import-modal-option.is-selected .import-modal-option-radio::after {
  inset: 2px;
}

/* Words at the foot, set against the right edge, above a hairline — the same
   footer the tempo menu has. */
.import-modal-actions {
  justify-content: flex-end;
  gap: 20px;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--divider);
}

.import-modal-btn {
  flex: 0 0 auto;
  padding: 0;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.03em;
  text-transform: none;
  background: none;
  border: 0;
  border-radius: 0;
  line-height: 1.4;
  transition: color 0.15s var(--ease);
}

.import-modal-btn--cancel {
  color: var(--dim);
  background: none;
}

.import-modal-btn--cancel:hover {
  color: var(--text);
  background: none;
}

.import-modal-btn--add {
  color: var(--glow);
  background: none;
  font-weight: var(--ui-weight-medium, 500);
}

.import-modal-btn--add:hover {
  color: var(--glow);
  background: none;
}

.import-modal-btn--add:disabled {
  color: var(--faint);
  background: none;
  cursor: default;
}

/* The same treatment for every other dialogue that still ended in filled
   boxes: settings and the folder relink prompt. A secondary
   action is a dim word, a primary action is an accent word.

   The settings primary had a second problem worth naming: accent ground with
   `--glow-soft` text, which is a translucent wash of that same accent — the
   label was very nearly invisible on its own button. As a word on the panel's
   ground it simply reads. */

.settings-action-btn,
.folder-relink-btn,
#settings-close-btn-footer {
  padding: 0;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.03em;
  text-transform: none;
  color: var(--dim);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transition: color 0.15s var(--ease);
}

.settings-action-btn:hover,
.folder-relink-btn:hover:not(:disabled) {
  color: var(--text);
  background: none;
}

.settings-action-btn--primary,
.settings-action-btn--primary:hover,
.folder-relink-btn--choose,
.folder-relink-btn--choose:hover:not(:disabled),
#settings-close-btn-footer,
#settings-close-btn-footer:hover {
  color: var(--glow);
  background: none;
  border-color: transparent;
  font-weight: var(--ui-weight-medium, 500);
}

/* Destructive stays destructive — the one place a different colour is right. */
.settings-action-btn--danger,
.settings-action-btn--danger[data-confirm-pending] {
  color: var(--danger);
  background: none;
}

.settings-action-btn--danger:hover {
  color: var(--danger);
  background: none;
}

/* ══ settings, brought onto the same footing ══════════════════════════════
   Three separate things were wrong in here.

   First, the corners. This panel alone used 2, 3, 4, 5, 7, 12 and 999 pixel
   radii, chosen individually. The window has a scale — twelve for a surface
   that floats, seven for a row inside one, a full round for a pill or a knob,
   and nothing at all for a word — and everything below is put on it.

   Second, controls whose label could not be read. Two of them painted an accent
   ground and then set the text to `--glow-soft`, which is a translucent wash of
   that same accent: the word disappeared into its own button. That is the empty
   coloured block in the keyboard-layout row and in the folders section.

   Third, the tick. It was drawn with rotated borders in `--hairline` — nine per
   cent white — on top of a solid accent fill, so the mark was very nearly
   invisible against its own box.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── corners on the scale ──────────────────────────────────────────────── */

.settings-modal,
.ai-confirm-modal,
.feedback-modal,
.daw-bridge-modal,
.folder-relink-modal,
.auth-confirm-modal,
.app-update-modal {
  border-radius: 12px;
}

.settings-library-folder-card,
.settings-library-file-row,
.settings-ai-installed-row,
.midi-device-item,
.midi-envelope-editor,
.midi-envelope-graph,
.midi-piano-keys,
.ai-option-warning,
.settings-library-empty {
  border-radius: 7px;
}

.settings-value-badge,
.settings-status-pill,
.settings-ai-installed-badge,
.settings-library-file-status,
.settings-library-folder-mode {
  border-radius: var(--r-pill, 999px);
}

/* ── the switch: a pill with a round knob, not a square with a square ──── */

.settings-toggle {
  border-radius: var(--r-pill, 999px);
  background: rgba(255, 255, 255, 0.1);
}

.settings-toggle[aria-pressed="true"] {
  background: var(--glow);
}

.settings-toggle-knob {
  border-radius: 50%;
}

/* ── the keyboard layout: two words, not two boxes ─────────────────────── */

.midi-layout-switch {
  background: none;
  border-radius: 0;
  padding: 0;
  gap: 14px;
}

.midi-layout-btn {
  padding: 0;
  background: none;
  border-radius: 0;
  color: var(--dim);
  transition: color 0.15s var(--ease);
}

.midi-layout-btn:hover {
  color: var(--text);
  background: none;
}

/* The one that is chosen takes the colour, the way the pagination and the keys
   do — rather than a filled block with an unreadable label inside it. */
.midi-layout-btn.is-active,
.midi-layout-btn.is-active:hover {
  background: none;
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

/* ── the tick, copied from MOTION ──────────────────────────────────────────
   Its own dialogue draws the mark as a stroked path — `M2.5 6.5 5 9l4.5-6`,
   round caps and joins — in `currentColor`, so the box only has to change its
   colour for the mark to appear. Reproduced here through a mask, which is how
   a path gets into an element that has no SVG of its own; the geometry is
   MOTION's to the decimal, and so are the box, the corner and the two tints. */

.ai-option-checkmark,
.new-coll-follow-check {
  width: 13px;
  height: 13px;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: none;
  color: transparent;
  transition: color 0.16s var(--ease), border-color 0.16s var(--ease),
              background 0.16s var(--ease);
}

.ai-option-checkmark::after,
.new-coll-follow-check::after {
  content: "";
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  border: 0;
  transform: none;
  background-color: currentColor;
  -webkit-mask-image: var(--tick);
  mask-image: var(--tick);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 11px 11px;
  mask-size: 11px 11px;
}

.ai-confirm-option-item:hover .ai-option-checkmark {
  border-color: rgba(255, 255, 255, 0.22);
}

/* Ticked: the mark in the accent, on a ground just light enough to say the
   square is filled rather than merely marked. MOTION's own words for it. */
.ai-confirm-option-item input[type="checkbox"]:checked + .ai-option-checkmark,
.new-coll-follow-option input:checked + .new-coll-follow-check {
  color: var(--glow);
  border-color: rgb(var(--glow-channels) / 0.45);
  background: rgb(var(--glow-channels) / 0.1);
}

/* MOTION's tick, as a path. `currentColor` cannot reach inside a data URI, so
   the shape arrives as a mask and the colour is painted behind it — which keeps
   the box's own `color` as the single thing that shows or hides the mark. */
:root {
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5 5 9l4.5-6' fill='none' stroke='%23fff' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* The corner scale, named. `--r-pill` was being read in three places without
     ever having been defined, so every one of them was quietly living on its
     own fallback. It is only for things that really are round: a scrollbar
     thumb and the switch. */
  --r-surface: 12px;
  --r-row: 7px;
  --r-small: 5px;
  --r-pill: 999px;

  /* The transport bar's height, asked for directly: fifty-eight.

     For the record of what fits: the tallest control in the bar is thirty-six
     pixels and the cover is thirty-four, so this leaves eleven pixels above and
     below the button and twelve around the cover. It started at fifty, which
     gave seven, and read as a strip.

     One token, and the six places that read it — the bar itself, its taller form
     with the keyboard out, and the three overlays that sit on top of it — all
     follow from here. */
  --footer-height: 58px;
}

/* The old mark was two rotated borders, and its rule carries four class-level
   parts, so it outranked the plain one above and came back the moment a box was
   ticked. Named again at the same weight, with the borders taken away. */
.ai-confirm-option-item input[type="checkbox"]:checked + .ai-option-checkmark::after,
.new-coll-follow-option input:checked + .new-coll-follow-check::after {
  content: "";
  position: absolute;
  inset: 0;
  left: 0;
  top: 0;
  width: auto;
  height: auto;
  border: 0;
  transform: none;
  background-color: currentColor;
  -webkit-mask-image: var(--tick);
  mask-image: var(--tick);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 11px 11px;
  mask-size: 11px 11px;
}

/* The analysis dialogue is the import dialogue's twin — same shell, same
   buttons — so it gets the same title and the same footer. Its two actions were
   sitting against the left edge while the other's sat against the right. */
.ai-confirm-title {
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.03em;
  text-transform: none;
  color: rgba(255, 255, 255, 0.3);
  padding: 16px 16px 4px;
}

.ai-confirm-actions {
  justify-content: flex-end;
  align-items: center;
  gap: 20px;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--divider);
}

/* Its section heading speaks like the sidebar's headings, since that is what it
   is — a signpost over a group, not a title. */
.ai-confirm-options-label {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* The list down the side of the settings panel is a list of rows, so it takes
   the row treatment the rest of the window uses: seven-pixel corners, a faint
   ground under the pointer, and the accent on the one you are looking at —
   rather than a filled grey block, which is the one shape the window has
   otherwise stopped using. */
.settings-nav-item {
  font-size: calc(12px * var(--ui-font-scale, 1));
  border-radius: 7px;
  padding: 6px 10px;
  color: var(--dim);
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

/* ET LA SELECTION EST UNE COULEUR, PAS UN BLOC.
 *
 * Il restait un fond gris sous le mot choisi et un autre sous le mot survole.
 * C'est la derniere forme que cette fenetre empruntait encore a ailleurs : rien
 * d'autre dans Sonus ne remplit un rectangle pour dire ou l'on est. Une place
 * choisie dans la barre laterale passe a l'accent, une tonalite survolee
 * blanchit, un bouton est un mot qui s'eclaire. Le rail du choix d'icone est
 * dans la meme phrase, parce que c'est le meme objet : une liste a gauche qui
 * choisit ce qu'on regarde a droite. */
.settings-nav-item:hover,
.icon-modal-tab:hover {
  background: none;
  color: var(--text);
}

.settings-nav-item.is-active,
.settings-nav-item.is-active:hover,
.icon-modal-tab.is-selected,
.icon-modal-tab.is-selected:hover {
  background: none;
  color: var(--glow);
}

/* ══ the corner scale, corrected ══════════════════════════════════════════
   I put the small readouts on a full round in the last pass, and that was the
   wrong reading of MOTION: it uses a full pill three times in the whole panel,
   against thirteen uses of two pixels and a handful of five and seven. A badge
   holding "8 voices" is a small rounded rectangle, not a lozenge.

   So the scale, stated once and used everywhere: twelve for a surface that
   floats, seven for a row or a card, five for something small sitting inside a
   row, a full round only for a genuine circle, and nothing at all for a word.
   ══════════════════════════════════════════════════════════════════════════ */

.settings-value-badge,
.settings-status-pill,
.settings-ai-installed-badge,
.settings-library-file-status,
.settings-library-folder-mode,
.settings-shortcut-key {
  border-radius: 5px;
}

/* The machine list had rules here — cards with a remove button on each. Both
   lists it dressed are gone: the one in the login panel went with auth.js, and
   the one in Settings went when that panel stopped pretending to know what is
   on somebody's account. Four selectors, no markup. */

/* Reset and Detect were the last two outlined boxes in the panel. They say
   what they do in a word, like every other action here. */
.midi-reset-btn,
.midi-detect-btn {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.03em;
  text-transform: none;
  color: var(--dim);
  transition: color 0.15s var(--ease);
}

.midi-reset-btn:hover,
.midi-detect-btn:hover {
  background: none;
  border-color: transparent;
  color: var(--text);
}

/* ══ every dialogue ends the same way ═════════════════════════════════════
   Ten dialogues, ten different feet: some spread their two words to opposite
   edges, some pushed them right, some left them stacked against the left with
   six pixels between them so the second read as stuck to the first.

   One rule for all of them now: both words on the left, twenty pixels apart,
   above a hairline, at the same distance from the edge as the text they
   follow. The one that cancels comes first, since that is the order they are
   written in the markup and the order they are read.

   Worth saying plainly: MOTION puts its two on the right, "where the hand goes
   and where Return already points". This is the one place the two disagree, and
   it disagrees on purpose.
   ══════════════════════════════════════════════════════════════════════════ */

.bpm-footer,
.import-modal-actions,
.ai-confirm-actions,
.auth-confirm-actions,
.auth-entitlement-actions,
.daw-bridge-modal-actions,
.feedback-actions,
.folder-relink-actions,
.ai-setup-actions {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 20px;
}

/* The dialogues share one measure; the tempo menu keeps its own, because it
   sits inside a menu that already holds it away from its edges. */
.import-modal-actions,
.ai-confirm-actions,
.auth-confirm-actions,
.auth-entitlement-actions,
.daw-bridge-modal-actions,
.feedback-actions,
.folder-relink-actions,
.ai-setup-actions {
  padding: 12px 16px 14px;
  border-top: 1px solid var(--divider);
}

.bpm-footer {
  padding: 10px 0 0;
}

/* None of them stretch a word to fill the space it is given. */
.import-modal-btn,
.settings-action-btn,
.folder-relink-btn,
.feedback-btn,
.auth-confirm-btn,
.daw-bridge-btn {
  flex: 0 0 auto;
}

/* The licence dialogue borrowed the sign-in screen's buttons, which are
   full-width filled blocks — right for a sign-in form, wrong at the foot of a
   dialogue, where they stretched edge to edge instead of sitting as two words.
   Only the copies inside that footer are changed; the sign-in screen keeps its
   own. */
.auth-entitlement-actions .auth-primary-btn,
.auth-entitlement-actions .auth-secondary-btn {
  width: auto;
  flex: 0 0 auto;
  padding: 0;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.03em;
  text-transform: none;
  background: none;
  border: 0;
  border-radius: 0;
  transition: color 0.15s var(--ease);
}

.auth-entitlement-actions .auth-secondary-btn {
  color: var(--dim);
}

.auth-entitlement-actions .auth-secondary-btn:hover,
.auth-entitlement-actions .auth-secondary-btn:focus-visible {
  color: var(--text);
  background: none;
}

.auth-entitlement-actions .auth-primary-btn {
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

.auth-entitlement-actions .auth-primary-btn:hover,
.auth-entitlement-actions .auth-primary-btn:focus-visible {
  color: var(--glow);
  background: none;
}

/* ══ menus that open and close ════════════════════════════════════════════
   Every one of these was switched between `display: none` and `display: flex`,
   and a transition cannot run on that — the box is simply not there one frame
   and fully there the next. Two of them, the two context menus, even carried an
   opacity and a transform written for the purpose; toggling `display` in the
   same breath threw both away, which is why they snapped as hard as the rest.

   MOTION never touches `display`. Its menu stays in the layout and is held shut
   by opacity, a small lift and `pointer-events`, with the fade a little quicker
   than the movement — opacity in 0.2s, transform in 0.28s, on the curve this
   file already shares with it. That is what is used here.

   `visibility` is folded into the same transition, with no delay opening and
   the full delay closing, so a shut menu is really gone — out of the reach of
   the pointer and of anything reading the page — without cutting the fade
   short.
   ══════════════════════════════════════════════════════════════════════════ */

.header-dropdown-menu,
.subheader-menu,
.sidebar-ctx-menu,
.sample-ctx-menu,
.sample-ctx-sub-popup {
  display: flex;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transform: translateY(-8px) scale(0.985);
  transform-origin: top center;
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s linear 0.28s;
}

.header-dropdown.is-open .header-dropdown-menu,
.subheader-dropdown.is-open .subheader-menu,
.sidebar-ctx-menu.is-open,
.sample-ctx-menu.is-open,
.sample-ctx-collections-row.is-sub-open .sample-ctx-sub-popup {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: translateY(0) scale(1);
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s;
}

/* Two of them are centred under their trigger by a transform, so the lift has
   to be added to that centring rather than replace it — otherwise the menu
   jumps half its own width sideways as it opens. */
.header-dropdown-menu,
.volume-popup {
  transform: translateX(-50%) translateY(-8px) scale(0.985);
}

.header-dropdown.is-open .header-dropdown-menu,
.volume-wrapper:hover .volume-popup,
.volume-popup.is-dragging {
  transform: translateX(-50%) translateY(0) scale(1);
}

.volume-popup {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s linear 0.28s;
}

.volume-wrapper:hover .volume-popup,
.volume-popup.is-dragging {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s;
}

/* The two popovers already moved rather than blinked; they are brought onto the
   same timing so no two surfaces in the window open at different speeds. */
.account-popover,
.accent-popover {
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s linear 0.28s;
}

.account-popover.is-open,
.accent-popover[aria-hidden="false"] {
  transition: opacity 0.2s var(--ease),
              transform 0.28s var(--ease),
              visibility 0s;
}

/* ══ the transport sits on top ════════════════════════════════════════════
   The library stops dead at the transport bar's edge: two flat regions meeting
   on a line, with nothing to say which one is in front. A short shadow cast
   upward out of the bar settles it — the last rows darken as they reach it, so
   they read as running underneath rather than simply ending.

   It is drawn above the bar's own top edge, so the hairline stays where it is
   and the shadow falls on the library rather than on the bar. Both regions are
   positioned and neither claims a stacking order, so the bar paints over the
   library on document order alone, which is what makes this possible without
   touching either one's layout.
   ══════════════════════════════════════════════════════════════════════════ */

.app-footer::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 12px;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.5) 0,
    rgba(0, 0, 0, 0.22) 40%,
    rgba(0, 0, 0, 0.07) 70%,
    transparent 100%);
}

/* ══ the account button ═══════════════════════════════════════════════════
   It was a filled disc — an accent-tinted ground, an accent ring and a letter
   inside — sitting immediately beside the colour picker, which is a bare
   stroked glyph on no ground at all. Two buttons, side by side, in two
   different languages, and the only filled disc left in the window.

   The letter stays, because it is the true thing to show: it is your account,
   not a drawing of a person. What goes is the fill. A hairline ring holds it,
   so it still reads as something to press, and ring and letter change together
   — grey at rest, white under the pointer, the accent while the menu is open.
   One hairline and one colour, which is what everything else here does.
   ══════════════════════════════════════════════════════════════════════════ */

.account-avatar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  color: var(--dim);
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.account-avatar-btn:hover,
.account-avatar-btn:focus-visible {
  background: none;
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.22);
}

.account-avatar-btn.is-open {
  background: none;
  color: var(--glow);
  border-color: rgb(var(--glow-channels) / 0.45);
}

/* ══ favourites is not a deletion ═════════════════════════════════════════
   "Add to Favorites" was printed in the destructive red, with a yellow heart
   beside it, inside a menu whose every other row is grey. Both halves have a
   traceable cause.

   The red is mine. One class — `sidebar-ctx-item--delete` — is worn by four
   different rows: Close, Delete, and Add to Favorites twice. I read its name,
   took it for the destructive one and grouped it with the others, which turned
   the favourite red. Before that it was `--glow` for all four, which had the
   opposite fault: Delete came out in the accent.

   No reading of that class can be right for both. What each row actually does
   is already written on it, in `data-action`, so that is what decides here:
   deleting and closing are destructive, favouriting is not.

   The heart was yellow for a different reason: its tint was a fixed filter,
   written for the old accent and left behind when the colour became yours to
   choose. It takes the same filter as every other tinted icon in the window.
   ══════════════════════════════════════════════════════════════════════════ */

.sidebar-ctx-item--delete[data-action="favorite"],
.sidebar-ctx-item--delete[data-action="favorite"]:hover {
  color: var(--glow);
}

.sidebar-ctx-item--delete[data-action="favorite"]:hover {
  background: rgba(255, 255, 255, 0.055);
}

.sidebar-ctx-item--delete[data-action="favorite"] .sidebar-ctx-item-icon,
.sidebar-ctx-item--delete[data-action="favorite"]:hover .sidebar-ctx-item-icon,
.sample-ctx-fav-icon {
  filter: var(--glow-filter);
  opacity: 1;
}

/* Delete and Close keep the red they should have had all along. */
.sidebar-ctx-item--delete[data-action="delete"],
.sidebar-ctx-item--delete[data-action="close"] {
  color: var(--danger);
}

.sidebar-ctx-item--delete[data-action="delete"]:hover,
.sidebar-ctx-item--delete[data-action="close"]:hover {
  color: var(--danger);
  background: rgba(255, 107, 94, 0.09);
}

/* Their icons carried the old accent's warm tint, written as a fixed filter, so
   the mark was orange beside a red word. They take the red itself now.
   `--danger-filter` is solved at startup by the same code that solves the
   accent's — a filter chain is six operations deep and is not something to
   write by hand. The fallback is the neutral tint, so a mark is never left
   the wrong colour if that solve has not run. */
.sidebar-ctx-item--delete[data-action="delete"] .sidebar-ctx-item-icon,
.sidebar-ctx-item--delete[data-action="close"] .sidebar-ctx-item-icon,
.sidebar-ctx-item--danger .sidebar-ctx-item-icon,
.sample-ctx-item--danger .sample-ctx-item-icon {
  filter: var(--danger-filter, brightness(0) saturate(0%) invert(70%));
  opacity: 1;
}

/* ══ companion mode ═══════════════════════════════════════════════════════
   A tall narrow window to keep beside a DAW. The button in the title bar only
   reshapes the window and sets one attribute; everything below decides what
   fits from the width the window actually has.

   That is deliberate, and it is what makes stretching work. Nothing is hidden
   because "companion mode is on" — things are hidden because there is no room
   for them. Drag the companion window wider and the columns, the waveform and
   the sidebar come back one at a time, in the order they were given up, with
   no state anywhere that could disagree with what is on screen.

   The columns are collapsed by setting their width tokens to zero rather than
   by taking the cells out of the grid: the grid is a fixed run of numbered
   slots, so a removed cell would leave a hole, while a zero-width slot simply
   closes up.
   ══════════════════════════════════════════════════════════════════════════ */

/* The button. Placed by the title bar's own grid, out of the drag region, and
   solid — no transparency, at full strength like every other icon here. */
.companion-btn {
  position: absolute;
  left: 82px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  opacity: 1;
  cursor: pointer;
  -webkit-app-region: no-drag;
  z-index: 5;
  transition: color 0.15s var(--ease);
}

/* Sixteen for a sixteen-unit box, so one unit is one pixel. At fifteen the
   drawing was scaled by 0.9375 and the 1.2 stroke came out 1.125 pixels wide,
   spread across two pixel columns at part strength — which is what read as the
   icon being transparent. The colour picker beside it was already 1:1, which is
   why only this one looked washed out. */
.companion-btn svg {
  width: 16px;
  height: 16px;
  display: block;
  opacity: 1;
}

.companion-btn:hover {
  color: var(--text);
  background: none;
}

html[data-companion] .companion-btn {
  color: var(--glow);
}

/* Where it sits depends on where the system keeps its own window buttons.

   macOS puts them at the top left, so this goes just to their right, at 82 —
   clear of the third light — while the account and colour buttons stay at the
   top right, where they have always been.

   Windows puts minimise, maximise and close at the top RIGHT, which is why the
   account button already lives at the left there with the colour button beside
   it at 46. This joins the end of that same row rather than the far corner: at
   twelve it would have been printed underneath the account button, and on the
   right it would have run into the window controls. */
html[data-platform="win32"] .companion-btn,
html:not([data-platform="darwin"]) .companion-btn {
  left: 80px;
}

/* ── what goes, and at what width ─────────────────────────────────────────

   The sidebar is not on this scale at all. It is what tells the two modes
   apart at a glance, so in companion mode it is simply gone — at any width.
   The window is capped at nine hundred pixels for the same reason: wide enough
   to earn the columns back, never wide enough to be mistaken for the ordinary
   window. Everything else below reappears as the room arrives, across the
   three hundred and forty to nine hundred the window can actually take. */

/* It closes rather than vanishes. The sidebar already carries a 150ms
   transition on its width and flex-basis — it was built to animate — and
   `display: none` threw that away, so the column disappeared between two
   frames. Taking its width to zero instead lets the transition it already has
   do the work, and the library slides across to fill the space. */
html[data-companion] .app-sidebar {
  --sidebar-width: 0px;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
  border-right: 0;
  transition:
    flex-basis 150ms ease,
    width 150ms ease,
    opacity 120ms ease;
}

html[data-companion] .app-sidebar::after {
  opacity: 0;
}

/* The order they are given up, widest threshold first.

   Key and tempo are what you actually want beside a DAW, so they survive the
   whole normal range of this window — at the four-hundred-and-sixty it opens
   at, both are still there. What goes first is what you can read from the
   waveform or the name anyway: scale, then the waveform, then the length.

   Only when the window is squeezed well past its default does tempo go, and
   key last of all, a moment before the window hits its floor of three hundred
   and forty. */

/* Scale: the least useful of the three readings on its own. */
@media (max-width: 860px) {
  html[data-companion] {
    --sl-col-scale: 0px;
    --sl-col-gap-key-scale: 0px;
  }

  html[data-companion] .sample-col--scale,
  html[data-companion] .header-field {
    display: none;
  }
}

/* ── LES DEUX BOUTONS D'UNE LIGNE, RESSERRES, ET LA PLACE QU'ILS PRENNENT ──
   Dans la fenetre ordinaire le coeur s'arrete a vingt-quatre du bord droit,
   comme la pochette commence a vingt-quatre du gauche, et la marque « sons
   proches » garde dix pixels d'ecart avec lui. Ici le bord est a quatorze et la
   fenetre peut descendre a trois cent quarante : les memes nombres poussaient
   la paire au milieu des colonnes, et elle se posait sur la tonalite — vu en
   capture par le proprietaire, les deux cercles a cheval sur un « F# ».

   Ce mode a redresse ca avec trois regles a lui, le temps que la fenetre
   ordinaire garde ses nombres en dur. Elle ne les a plus : elle ecrit les trois
   en fonction du retrait, et le seul retrait que ce mode change est justement
   celui-la. Les six pixels d'ecart entre les deux boutons, choisis ici, sont
   passes de ce cote-la aussi.

   Les trois regles ont donc ete effacees plutot que gardees identiques. Deux
   endroits qui disent la meme chose finissent toujours par ne plus la dire
   pareil. */

/* ── ET LE VIDE QUE LES COLONNES RETIREES LAISSENT DERRIERE ELLES ──────────
   Une colonne qu'on cache passe a zero, mais l'ecart qui la separait de sa
   voisine reste : la forme d'onde et la duree en laissent trente-deux a elles
   deux, au milieu de la rangee, pendant que le nom est tronque a cote. Et les
   quatre lectures gardent leurs cinquante-six pixels de large pour y poser
   « F » ou « — », ce qui en laisse quarante de plus a droite.

   Les colonnes, elles, font deja la largeur de leur titre partout — voir la
   note sur --sl-col-length dans styles.css — donc il ne reste ici que l'ecart
   commun, qui descend de seize a douze.

   Ce qui est repris va au nom, qui est la seule chose de cette rangee qu'on ne
   peut pas deviner. */
@media (max-width: 620px) {
  html[data-companion] {
    --sl-col-waveform: 0px;
    --sl-col-gap: 12px;
    --sl-pad-left: 14px;
    --sl-pad-right: 12px;
    --main-content-pad-left: 14px;
  }

  html[data-companion] .sample-col--waveform {
    display: none;
  }
}

@media (max-width: 560px) {
  html[data-companion] {
    --sl-col-length: 0px;
  }

  html[data-companion] .sample-col--length {
    display: none;
  }
}

/* Squeezed past the default: tempo goes, key stays. */
@media (max-width: 430px) {
  html[data-companion] {
    --sl-col-tempo: 0px;
  }

  html[data-companion] .sample-col--tempo {
    display: none;
  }
}

/* And key only at the very end, with the name and its tags left holding the
   row on their own. */
@media (max-width: 380px) {
  html[data-companion] {
    --sl-col-key: 0px;
  }

  html[data-companion] .sample-col--key {
    display: none;
  }
}

/* At companion widths the header has more names than room, so it wraps rather
   than pushing its contents off the edge. */
@media (max-width: 520px) {
  html[data-companion] .subheader-inner,
  html[data-companion] .header-controls {
    flex-wrap: wrap;
    row-gap: 6px;
  }
}

/* The search field is declared `flex: 0 0 240px` — a fixed width that refuses
   to shrink. At four hundred pixels it claims two hundred and forty of them on
   its own, and Shuffle and Clear are pushed thirty-five pixels off the edge.
   Inside companion mode it is allowed to give way instead. */
@media (max-width: 700px) {
  html[data-companion] .header-search {
    flex: 1 1 auto;
    min-width: 0;
  }

  html[data-companion] .header-inner {
    gap: 14px;
    padding: 0 14px;
  }

  /* And the filter row on the same inset, which it was not.

     This is the ten pixels of the misalignment. The header narrows its padding
     from twenty-four to fourteen at companion widths and the row of filters
     below it kept twenty-four — so, measured at 460 wide, the search field's
     text sat at 234 and the word Instruments at 244.

     Its gap stays at six. The header's fourteen is for two controls and a
     search field; this row is four short words that have to fit a narrow
     window. */
  html[data-companion] .subheader-inner {
    padding: 0 14px;
  }
}

/* A menu is free to be wider than its trigger, but not wider than the window
   holding it. */
@media (max-width: 700px) {
  html[data-companion] .header-dropdown-menu,
  html[data-companion] .subheader-menu {
    max-width: calc(100vw - 24px);
  }

  /* And anchored to the right of their trigger, not the left.
  
     The cap above was not enough, because the overflow was never about width.
     A menu is positioned `left: 0` against its own dropdown, so it grows to the
     right of the word that opened it — and measured at 460 wide, Source sits at
     297 and its menu is 256 across, which puts its right edge at 555. Ninety-five
     pixels outside the window, and the interface slid sideways to make room.
  
     Right-anchored, the same menu spans 113 to 369 and there is nothing to
     scroll. The beak stays where it is: it belongs to the trigger and points
     down from it, so a menu that extends leftward instead of rightward does not
     move it or need to.
  
     Only in companion mode. At a normal width there is room to the right and
     opening leftward from the first filter would be the same fault mirrored. */
  html[data-companion] .subheader-menu {
    left: auto;
    right: 0;
  }
}

/* ══ the three controls at the foot, brought together ═════════════════════
   Each button carried ten pixels of padding on each side and the row added
   eight more between them, so two icons sat twenty-eight pixels apart — which
   is why they read as scattered rather than as a group. The divider before the
   keyboard widened its own gap to eighteen on top of that.

   Tightened, not crammed: seven of padding, six between, and sixteen across
   the divider — which stays wider on purpose, because that is what a divider
   is for. Icon to icon goes from twenty-eight down to twenty.
   ══════════════════════════════════════════════════════════════════════════ */

.footer-controls {
  gap: 6px;
}

.footer-controls .footer-btn {
  padding: 4px 7px;
}

.footer-btn[data-footer-btn="midi"] {
  width: 30px;
  margin-left: 10px;
}

.footer-btn[data-footer-btn="midi"]::before {
  left: -8px;
}

/* ══ loading and reloading, without the jolt ══════════════════════════════
   Filtering used to read as a bug. Two things caused it, and neither was slow
   code.

   The rows were cleared before the query ran, so the list sat empty for the
   whole wait and then filled in one frame. That is fixed in renderPage: the
   outgoing rows now stay until the incoming ones are built.

   And the loading veil was painted in the application's own background —
   opaque, with `transition: none`. Even with the old rows still there it hid
   them completely, and it appeared and vanished on a hard cut. It becomes a
   thin scrim that fades: the list stays legible underneath, dimmed enough to
   say it is busy.
   ══════════════════════════════════════════════════════════════════════════ */

.search-spinner-overlay {
  background: rgba(5, 5, 7, 0.58);
  transition: opacity 0.18s var(--ease), visibility 0s linear 0.18s;
}

.search-spinner-overlay.is-visible {
  transition: opacity 0.18s var(--ease), visibility 0s;
}

/* Every row of a page arrives in the same frame, so they rise together as one
   sheet rather than one at a time — a stagger at fifty rows reads as a machine
   dealing cards, which is slower to watch than it is to render. */
@keyframes sonus-row-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.sample-row.dynamic {
  animation: sonus-row-in 0.2s var(--ease) both;
}

.sample-list-empty {
  animation: sonus-row-in 0.24s var(--ease) both;
}

/* Anyone who has asked their system for less movement gets the fade only. */
@media (prefers-reduced-motion: reduce) {
  .sample-row.dynamic,
  .sample-list-empty {
    animation-duration: 0.12s;
    animation-name: sonus-fade-in;
  }

  @keyframes sonus-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

/* Shuffle sits a step below the accent rather than on it.

   By the rule the rest of this file follows, the accent marks what is *set*,
   and Shuffle is an action, not a state — so full strength would put it back in
   competition with a chosen filter. At about seven tenths it reads as warm and
   deliberate without claiming to be a selection, and it comes to full accent
   under the pointer, which is what a thing you can press should do.

   Its mark is stroked in currentColor, so it follows the word without being
   told twice. */
.header-btn[data-header-btn="shuffle"] {
  color: rgb(var(--glow-channels) / 0.7);
}

.header-btn[data-header-btn="shuffle"]:hover {
  color: var(--glow);
  background: none;
}

/* ══ the similar-samples panel, brought into the window ═══════════════════
   This panel kept its own shapes while the rest moved on — the same story as
   the import dialogue. Its close mark was a filled grey square, its six match
   modes were filled and outlined boxes with the chosen one a solid block of
   accent carrying dark text, and the source sample's tags were grey pills.
   None of those shapes exist anywhere else here any more.

   The colour was never the problem: it already reads `--glow-rgb`, so the
   cyan in question was simply whichever accent was chosen at the time. What
   follows is shape, weight and the one rule about what the accent means.
   ══════════════════════════════════════════════════════════════════════════ */

/* Its two headings speak like every other signpost in the window. */
.similarity-panel-kicker {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

.similarity-mode-label,
.similarity-target-label {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

.similarity-panel-title {
  font-size: calc(13px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--text);
}

/* The close mark: a mark, not a box. */
.similarity-panel-close {
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 5px;
  color: var(--dim);
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.similarity-panel-close:hover {
  background: rgba(255, 255, 255, 0.055);
  color: var(--text);
}

/* Six words on a grid, not six boxes. The one in use takes the colour, the way
   the keys and the pagination do — rather than a filled block whose label has
   to be inverted to stay readable. */
.similarity-mode-options {
  gap: 2px 10px;
}

.similarity-mode-btn {
  padding: 6px 8px;
  background: none;
  border: 0;
  border-radius: 7px;
  color: var(--dim);
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.03em;
  text-transform: none;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.similarity-mode-btn:hover {
  background: rgba(255, 255, 255, 0.055);
  color: var(--text);
  border-color: transparent;
}

.similarity-mode-btn.is-active,
.similarity-mode-btn.is-active:hover {
  background: none;
  border-color: transparent;
  color: var(--glow);
  font-weight: var(--ui-weight-medium, 500);
}

/* The source sample's tags are the same tags as in the library, so they are
   written the same way: quiet words, a ground only under the pointer. */
.similarity-target-tag {
  padding: 2px 6px;
  background: none;
  border: 0;
  border-radius: 5px;
  color: var(--faint);
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
  text-transform: none;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.similarity-target-tags {
  gap: 2px;
  margin-left: -6px;
}

.similarity-target-name {
  font-size: calc(13px * var(--ui-font-scale, 1));
  color: var(--text);
}

/* The little beak above a menu should be the colour of the menu's own edge —
   that is why the filter menus read as clean: theirs is drawn in `--hairline`.
   The two right-click menus had theirs in a fixed near-black instead, so on the
   raised ground of the menu it read as a dark notch rather than a point. Same
   colour as the edge, in both directions, like the others. */
.sidebar-ctx-menu::before,
.sample-ctx-menu::before {
  border-bottom-color: var(--hairline);
}

.sidebar-ctx-menu[data-flip="up"]::before,
.sample-ctx-menu[data-flip="up"]::before {
  border-top-color: var(--hairline);
}

/* The beak has to be OPAQUE.
   `--hairline` is white at nine per cent, so a triangle painted with it lets
   the page through and reads as a see-through notch rather than as the corner
   of a menu. `--beak` is that same edge colour composited onto the menu's own
   ground — rgb(31,31,34) — so it looks identical to the outline while hiding
   what is behind it. One value, used by all three menus, so they stay the
   same shape as each other. */
:root {
  --beak: rgb(31, 31, 34);
}

.subheader-menu:not(.subheader-menu--tags):not(.subheader-menu--source)::before,
.subheader-dropdown.is-open::after,
.sidebar-ctx-menu::before,
.sample-ctx-menu::before {
  border-bottom-color: var(--beak);
}

.sidebar-ctx-menu[data-flip="up"]::before,
.sample-ctx-menu[data-flip="up"]::before {
  border-top-color: var(--beak);
}

/* And the same size as the others: seven, not six. The filter menus point with
   a seven-pixel beak and the right-click menus with a six, which is exactly the
   sort of difference that reads as sloppiness without being nameable. The
   offset moves with it, or the point detaches from the menu it belongs to. */
.sidebar-ctx-menu::before,
.sample-ctx-menu::before {
  top: -7px;
  border-left-width: 7px;
  border-right-width: 7px;
  border-bottom-width: 7px;
}

.sidebar-ctx-menu[data-flip="up"]::before,
.sample-ctx-menu[data-flip="up"]::before {
  top: auto;
  bottom: -7px;
  border-bottom-width: 0;
  border-top-width: 7px;
}

/* Tempo, Key and Scale point with the same beak as the others.

   Theirs was still drawn in `--hairline`, so it stayed see-through while the
   rest turned opaque — my previous rule named every menu but this one.

   Its anchor is corrected too. The subheader beak starts three pixels below its
   trigger and is seven tall, and its menu opens ten below: the point lands
   exactly on the menu's edge. These menus open six below, so their beak has to
   start one pixel ABOVE the trigger's foot for the same join. It was sitting at
   zero, a pixel short, which is the gap that made it look like a different
   size. */
.header-dropdown.is-open::after {
  top: calc(100% - 1px);
  border-bottom-color: var(--beak);
}

/* One pixel of it was hidden, which is why it still looked smaller.

   These two beaks are positioned inside their own menu, and an absolutely
   positioned child is placed against the PADDING box — which starts one pixel
   in from the outer edge, behind the menu's border. So `top: -7px` put only
   six pixels of the triangle outside the menu and tucked the seventh under its
   own edge. The filter menus never had this: their beak hangs off the trigger,
   not off the menu.

   Eight, so that seven of it shows. */
.sidebar-ctx-menu::before,
.sample-ctx-menu::before {
  top: -8px;
}

.sidebar-ctx-menu[data-flip="up"]::before,
.sample-ctx-menu[data-flip="up"]::before {
  top: auto;
  bottom: -8px;
}

/* ══ one tracking for every signpost ══════════════════════════════════════
   PLACES and COLLECTIONS were set at 0.34em, which is MOTION's figure — right
   for its 8.5-pixel labels inside a plugin panel, too wide for ten pixels down
   the side of a desktop window, where the word stops being a word.

   And they were not alone: the same kind of label was running at 0.34, 0.24 and
   0.2 in different places. They all take 0.2 now — enough to read as small
   capitals, not so much that the letters stop belonging to each other.
   ══════════════════════════════════════════════════════════════════════════ */

/* The column headings are NOT on this list, and that was my mistake a moment
   ago: I swept them in with the small-capital signposts and they came out
   half-changed — the sort buttons at 0.2em, the plain cells still at 0.015em,
   two treatments in the same line of text. Read side by side that does not look
   like one word spaced differently from another, it looks like two typefaces,
   which is exactly how it was described.

   They were deliberately taken out of small capitals earlier: they are set in
   ordinary case, and a word in ordinary case is read as a word, not as a shape.
   Wide tracking is a device for the second kind. They keep their own rule. */
.sidebar-section-label,
.ai-confirm-options-label,
.similarity-panel-kicker,
.similarity-mode-label,
.similarity-target-label {
  letter-spacing: 0.2em;
}

/* Not the colour picker's title: that one is set in ordinary sentence case, so
   small-capital tracking would only pull its letters apart. */

/* ══ the feedback sheet ═══════════════════════════════════════════════════
   Another panel that kept its own vocabulary. Its three fields were filled,
   outlined boxes with two-pixel corners — the one shape this window no longer
   uses anywhere; its two headings ran at a tracking of their own; and its title
   sat at sixteen pixels, larger than anything else in the application.

   It is rebuilt on what is already here: a signpost over a title, fields that
   are a rule rather than a box, and the accent only on the caret of the field
   you are typing in.
   ══════════════════════════════════════════════════════════════════════════ */

.feedback-modal {
  width: 460px;
}

.feedback-modal-header {
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--divider);
}

.feedback-modal-kicker {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

.feedback-modal-title {
  font-size: calc(13px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--text);
  margin-top: 2px;
}

.feedback-form {
  padding: 16px 18px 4px;
  gap: 16px;
}

.feedback-field {
  gap: 6px;
}

/* The name of a field is a signpost, like every other one. */
.feedback-label {
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* A field is the text you type on a rule that lights up when the caret is in
   it — the same field the search uses, and the tempo menu, and the folder
   search. Boxes were the only thing making this sheet look foreign. */
.feedback-input,
.feedback-select,
.feedback-textarea {
  width: 100%;
  padding: 5px 0;
  font-family: inherit;
  font-size: calc(12px * var(--ui-font-scale, 1));
  letter-spacing: 0.01em;
  color: var(--text);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--divider);
  border-radius: 0;
  transition: border-color 0.15s var(--ease);
}

.feedback-input:hover,
.feedback-select:hover,
.feedback-textarea:hover {
  border-bottom-color: var(--hairline);
  background: none;
}

.feedback-input:focus,
.feedback-select:focus,
.feedback-textarea:focus {
  border-bottom-color: var(--glow);
  background: none;
  outline: none;
}

.feedback-input::placeholder,
.feedback-textarea::placeholder {
  color: var(--faint);
}

.feedback-textarea {
  min-height: 96px;
  line-height: 1.55;
  resize: vertical;
}

.feedback-status {
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.01em;
  color: var(--dim);
}

/* The close mark loses its box, like the one on the similar-samples panel. */
.feedback-close-btn {
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 5px;
  color: var(--dim);
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.feedback-close-btn:hover {
  background: rgba(255, 255, 255, 0.055);
  color: var(--text);
}

/* ══ LA FICHE D'UN SAMPLE ══════════════════════════════════════════════════
   Elle n'a pas de feuille a elle. C'est la meme boite que la fiche de retour
   juste au-dessus, avec les memes classes, donc le meme en-tete, les memes
   champs poses sur un trait, le meme accent sur le champ ou est le curseur, et
   les memes deux boutons. Une deuxieme serie de valeurs pour la meme sorte
   d'objet, ce sont deux series qui divergent au premier reglage.

   Ce qui suit est donc uniquement ce que celle-ci a en plus : une image, et
   quatre champs courts sur une ligne.
   ══════════════════════════════════════════════════════════════════════════ */

.info-modal {
  width: 480px;
}

/* L'image et le nom ensemble, parce que c'est ce qu'on voit d'une rangee avant
   de lire quoi que ce soit d'autre. Alignes par le BAS : le champ porte son
   titre au-dessus de lui, donc c'est son trait qui doit tomber sur le bord de
   l'image, pas son titre. */
.info-head {
  display: flex;
  align-items: flex-end;
  gap: 14px;
}

.info-head-field {
  flex: 1 1 auto;
  min-width: 0;
}

/* A la taille de la pochette d'une rangee, au meme arrondi que la boite qui la
   contient. */
.info-cover {
  position: relative;
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  overflow: hidden;
  background: var(--pane);
  cursor: pointer;
}

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

/* Le mot n'apparait qu'au survol : une image porte deja son propre appel, et un
   mot pose dessus en permanence cache ce qu'on est venu regarder. */
.info-cover-veil {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  background: rgba(0, 0, 0, 0.55);
  opacity: 0;
  transition: opacity 0.15s var(--ease);
}

.info-cover:hover .info-cover-veil,
.info-cover:focus-visible .info-cover-veil {
  opacity: 1;
}

.info-cover:focus-visible {
  outline: 1px solid var(--glow);
  outline-offset: 2px;
}

/* Tempo, tonalite, gamme, type : quatre mots sur une ligne se lisent comme une
   seule reponse, la ou empiles ils auraient fait une fiche deux fois plus haute
   pour la meme chose.

   Dans cet ordre-la parce que c'est celui du sens : une tonalite appelle sa
   gamme, les deux vont ensemble et ne se separent pas. Le type vient apres,
   parce qu'il ne parle pas de la meme chose. */
.info-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

/* L'extension, sous le nom, en clair : elle ne s'edite pas. Elle ne se choisit
   pas non plus, elle constate un format, et la changer ne convertirait rien. */
.info-note {
  font-size: calc(10px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  color: var(--faint);
}

/* ══ looking for a newer one ══════════════════════════════════════════════
   MOTION's system, reproduced. Its own words for why it is built this way are
   worth keeping: the question and its answer are drawn in one place — the line
   travels while it is asking, and if the answer is yes the words arrive where
   the line was. Nothing turns up beside the name and nothing has to be found.

   Both live under the wordmark and out of the flow, so news arriving never
   moves the name of the application.

   One figure is not copied literally: MOTION sets this at 8.5 pixels, which
   suits a plug-in panel and is too small in a desktop window. It is taken up
   to 9.5 and its tracking eased to match, which is the same relationship at
   this size.
   ══════════════════════════════════════════════════════════════════════════ */

.app-titlebar-title {
  position: relative;
}

/* ── the rule, with a light going along it ─────────────────────────────── */

.looking {
  position: absolute;
  top: calc(100% + 7px);
  left: 0;
  /* The wordmark's own width, less its trailing letterspace — the last letter
     carries that space after it, so a rule spanning the box overhangs the "s"
     and reads as a mistake. Written in the same em the tracking is, so the two
     cannot come apart. */
  right: 0.14em;

  height: 1px;
  overflow: hidden;

  /* Faint enough to be the ghost of a line and not a line: what is being said
     is that something is happening, not that the header has furniture. */
  background: var(--divider);

  opacity: 0;
  transition: opacity 0.35s var(--ease);
  pointer-events: none;
}

/* The light itself. A third of the way across at a time, because a segment long
   enough to read as travelling has to be short enough to have somewhere to go. */
.looking::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 34%;
  background: var(--text);
  /* Eased at both ends. A sweep at constant speed reads as a machine rather
     than as a search. */
  animation: sonus-looking 1.15s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

.looking.in { opacity: 1; }
.looking[hidden] { display: none; }

/* Out of sight at both ends rather than in and out of view at the edges: the
   parent clips, so the light enters under the S and leaves under the s. */
@keyframes sonus-looking {
  from { transform: translateX(-100%); }
  to   { transform: translateX(calc(100% / 0.34)); }
}

/* ── and the word, when there is one ───────────────────────────────────── */

/* Under the wordmark, centred on it, and inside the bar without moving it.
 *
 * Three things at once, and the third is what makes the first two hard. The
 * title bar is forty pixels and the name is centred in it, from 12.5 to 27.5,
 * so there are TWELVE AND A HALF PIXELS under the name and everything written
 * there has to fit in them. It was seven of gap and eleven of text: eighteen,
 * running to 47, seven past the bottom edge. Nothing clipped it, which is why
 * nothing looked broken from the code's side - the bar underneath simply paints
 * over the overhang, and what was left was the top two thirds of a line of
 * text.
 *
 * Lifting the block to centre the pair was tried and taken straight back out:
 * it fits, and it moves the name, and the name moving when a version turns up
 * is worse than the thing that was wrong.
 *
 * So it fits instead. Three of gap and eight of text, which is eleven of the
 * twelve and a half. line-height: 1 is load-bearing - the default leading on an
 * 8-pixel font is about eleven, and eleven plus three does not fit. Every
 * character here is a capital or a digit, so there are no descenders to lose to
 * the last pixel and a half.
 *
 * Sideways it centres on the name and cannot move it: absolute, so a label
 * wider than the word - and it is nearly always wider - has nothing to push.
 */
.newer {
  appearance: none;
  position: absolute;
  /* Flush under the name's box, and the box below is the whole of the room:
     twelve pixels of it, with the eight of text centred inside. It was eight
     pixels tall and nothing else, which is a target sixty-six by eight to hit
     with a pointer - the words are the same size and the thing you have to land
     on is half again as tall. All of it inside the bar: 27.5 to 39.5, with the
     text itself at 29.5 to 37.5. */
  top: 100%;
  left: 50%;

  display: inline-flex;
  align-items: center;
  gap: 5px;

  border: 0;
  background: none;
  padding: 2px 6px;

  font-family: inherit;
  font-size: calc(8px * var(--ui-font-scale, 1));
  line-height: 1;
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;

  cursor: pointer;
  -webkit-app-region: no-drag;

  /* And the pointer reaches it, which it did not.
   *
   * styles.css gives .app-titlebar-title `pointer-events: none` so the whole
   * bar can be taken hold of to move the window, and that inherits: the mark is
   * a child of it, so it was transparent to the mouse from the day it was
   * written. It looked like a link, it had a title attribute, it had an onclick,
   * and elementFromPoint at the middle of it answered "the title bar". Nothing
   * in the code was wrong; the click simply never arrived.
   *
   * no-drag above is the other half and is not enough on its own - that is
   * Electron being told this rectangle is not the window's handle, and this is
   * the page being told it is not scenery. */
  pointer-events: auto;
}

/* Not in the markup, because the label is written with textContent and would
   take any child element out with it on the next answer. */
.newer::before {
  content: "";
  flex: 0 0 4px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--glow);

  /* Barely a halo. On a black field a 4px dot with a hard edge reads as a dead
     pixel; six of soft light around it reads as something switched on. */
  box-shadow: 0 0 6px -1px rgb(var(--glow-channels) / 0.55);

  transition: box-shadow 0.25s var(--ease);
}

.newer:hover {
  color: var(--text);
  background: none;
}

.newer:hover::before {
  box-shadow: 0 0 9px -1px rgb(var(--glow-channels) / 0.8);
}

.newer[hidden] { display: none; }

/* Arrived rather than appeared. News that snaps into a header is read as a
   fault in the header. `hidden` comes off first and the class a frame later,
   because there is nothing to move from while the element is not laid out. */
/* The centring travels with the arrival, in one transform.
 *
 * It cannot be two: `left: 50%` needs its own half-width back and this rule is
 * the last word on transform, so a bare translateY(-2px) here - and `none` on
 * .in - would leave the label starting at the middle of the name and running
 * off to the right of it. Both states carry the -50%, and the arrival is the
 * two pixels on top of it. */
.newer {
  opacity: 0;
  transform: translate(-50%, -2px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease),
              color 0.25s var(--ease);
}

.newer.in {
  opacity: 1;
  transform: translateX(-50%);
}

/* The old shortcut — a bordered pill with an arrow in it — is replaced by the
   word above and no longer drawn. */
.app-update-shortcut {
  display: none;
}

/* ══ the gate ═════════════════════════════════════════════════════════════
   MOTION's unlock panel, reproduced. Its values, its words, its shapes.

   One figure is not copied literally, for the reason it was not copied for the
   update notice either: MOTION sets this type at 8.5 and 9 pixels, which suits
   a plug-in panel and is too small in a desktop window across the room from
   somebody. Every size below is that ladder moved up a step and the tracking
   eased to match — the same relationship, at this distance.

   The panels this replaces — a login, a sign-up, a username step, a
   machine-limit list and an entitlement notice — are gone with the account
   they belonged to.
   ══════════════════════════════════════════════════════════════════════════ */

.unlock-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 0 40px;
}

/* The wordmark. MOTION tracks this at 0.42em, which is right for four
   capitals; "Sonus" is not, and at that spacing its lowercase letters stop
   belonging to each other — the same correction the title bar needed. */
.unlock-mark {
  font-size: calc(17px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.14em;
  text-transform: none;
  line-height: 1;
  margin: 0;
  color: #fff;
}

.unlock-line {
  margin: 18px 0 26px;
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
}

.unlock-row {
  display: flex;
  align-items: stretch;
  gap: 10px;
  width: min(520px, 70vw);
}

/* The field is a box here, and deliberately — this is the one place in the
   window that is a box, because it is MOTION's and because what matters is
   that the paste landed. */
#gate-key {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: calc(11px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  color: var(--text);
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--hairline);
  border-radius: 7px;
  padding: 0 12px;
  height: 36px;
  outline: none;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}

#gate-key::placeholder { color: var(--faint); letter-spacing: 0.16em; }

#gate-key:focus {
  border-color: rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.055);
}

.unlock-go {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: calc(10px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-medium, 500);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--glow);
  border: 0;
  border-radius: 7px;
  padding: 0 20px;
  height: 36px;
  cursor: pointer;
  transition: opacity 0.2s var(--ease), transform 0.12s var(--ease);
}

.unlock-go:hover { opacity: 0.9; background: var(--glow); }
.unlock-go:active { transform: scale(0.97); }
.unlock-go:disabled { opacity: 0.45; cursor: default; transform: none; }

/* It holds its height whether or not it is saying anything, so the panel does
   not move when it speaks. */
.unlock-word {
  margin: 20px 0 0;
  min-height: 13px;
  font-size: calc(10px * var(--ui-font-scale, 1));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  text-align: center;
  transition: color 0.2s var(--ease);
}

.unlock-word.is-error { color: var(--danger); }

.unlock-machine {
  margin: 26px 0 0;
  font-family: var(--font-mono);
  font-size: calc(9px * var(--ui-font-scale, 1));
  letter-spacing: 0.16em;
  color: var(--faint);
}

/* The old logo, one last time.

   `.auth-brand::before` in auth.css draws SonusIcon.png as a 22-pixel square
   beside the word. It is the same mark that was taken off the title bar and off
   the splash, and it survived here because these panels were written before
   that and nothing on screen showed them afterwards — until the gate started
   using this panel, and a licence key being accepted made an old logo and a
   spinner appear for half a second. */
.auth-brand::before { content: none; display: none; }

/* ══ the volume popup does not travel ═════════════════════════════════════
   Every other surface in this window opens with a small lift — eight pixels of
   translateY over 0.28s — and it suits them, because they are menus that arrive
   over the page.

   This one must not, and the reason is mechanical. The slider sits twenty-eight
   pixels above the headphone button, and those pixels belong to neither: the
   pointer leaves the button, nothing is hovered, and the popup shuts on the way
   to itself. styles.css already covers that with an invisible bridge — a
   pseudo-element on the popup spanning exactly those twenty-eight pixels.

   The bridge is positioned against the popup, so a popup that travels takes the
   bridge with it. For the length of the opening animation its lower edge sits
   eight pixels short of the button, leaving a dead band across the gap. Which
   is why it only happened sometimes: it depended on how fast the hand moved
   against a 0.28s animation.

   So: it fades, and it does not move. And the bridge is given room either side
   of the exact figure, because a seam that is precisely zero wide is a seam a
   pointer can still fall through between two frames.
   ══════════════════════════════════════════════════════════════════════════ */

.volume-popup,
.volume-wrapper:hover .volume-popup,
.volume-popup.is-dragging {
  transform: translateX(-50%);
}

.volume-popup::after {
  bottom: -34px;
  height: 34px;
}

/* ══ the field you type a collection's name into ═══════════════════════════
   It was a filled box: a grey panel with a two-pixel radius and the text sitting
   inside it. Nothing else in this window takes text that way.

   The search at the top of the header is words with a rule under them that
   lights up as you type. The source search in the subheader is the same object
   at a smaller size, and the comment beside it says why it is written in
   ordinary case rather than small capitals: it is a place you type a folder's
   name. This is a place you type a collection's name, which is the same thing.

   So it takes that treatment exactly — no box, a rule under the words, and on
   focus the accent gradient both searches light with. One idiom for typing,
   in three places.

   The gradient is a background rather than a border because a border can only be
   one colour and this one runs from the accent to a third of it. And the border
   is left in place with a transparent colour rather than removed, so the field
   does not change height between the two states.
   ══════════════════════════════════════════════════════════════════════════ */

.new-coll-input {
  background: none;
  border: 0;
  border-bottom: 1.5px solid var(--divider);
  border-radius: 0;
  padding: 0 2px;
  height: 27px;
  font-size: calc(13px * var(--ui-font-scale, 1));
  letter-spacing: 0.01em;
  color: var(--text);
  transition: border-bottom-color 0.15s var(--ease);
}

.new-coll-input:hover {
  border-bottom-color: var(--hairline);
}

.new-coll-input:focus {
  background: linear-gradient(90deg, var(--glow) 0%, rgba(var(--glow-rgb), 0.35) 100%)
              no-repeat bottom / 100% 1.5px;
  border-bottom-color: transparent;
}

.new-coll-input::placeholder {
  color: var(--faint);
}

/* And the row under it, which was on the radius scale's old value. Five is what
   every small surface in this window rounds to. */
.new-coll-follow-option {
  border-radius: var(--r-small);
}

/* ══ the one line a confirmation dialogue needs ════════════════════════════
   The body of these dialogues was written inline, every time, with the same
   three declarations repeated: font-size 12.5px, colour at 0.55, line-height
   1.6 — set on an element that already carried .ai-confirm-note, whose own
   values are 11px at 0.28. So the class was there and being argued with, in
   four places, by hand.

   This is that line, named. Readable, because it holds the name of the thing
   about to happen to something — .ai-confirm-note at 0.28 is for a footnote,
   and the name is not a footnote.
   ══════════════════════════════════════════════════════════════════════════ */

.ai-confirm-line {
  margin: 0;
  font-size: calc(12.5px * var(--ui-font-scale, 1));
  line-height: 1.6;
  color: var(--read);
}

.ai-confirm-line strong {
  font-weight: var(--ui-weight-medium, 500);
  color: var(--text);
}

/* The rescan dialogue's body, which had `padding-bottom: 0` written on the
   element because the four scope rows below it bring their own spacing and the
   shared padding put a gap under the last one. A real difference, so it gets a
   name rather than an attribute. */
.ai-confirm-body.rescan-body {
  padding-bottom: 0;
}

/* ══ the screen that says the window is closing ════════════════════════════
   This one my whole pass missed: theme.css did not mention it once, so it was
   still wearing what it shipped with — and it is the last thing anybody sees.

   Three things were wrong, and the ground was the loud one. rgba(53, 53, 53,
   0.96) is a mid-grey, over an application whose every surface is #050507. It
   did not read as this window dimming; it read as a different window arriving
   in front of it.

   So it takes the veil the modal shells take, which is already written above:
   near-black at ninety-four per cent, blurred and slightly saturated, so what
   is behind stays legible as the thing being closed.

   And the ring: not wrong in kind — an accent-topped ring is what
   .ai-confirm-spinner and .rescan-file-loading-spinner both are — but wrong in
   scale, at thirty-four pixels with a three-pixel border where those two are
   sixteen and two. Nothing else in the window announces itself that size.

   The label likewise: eighteen pixels at weight 500 is heading type for one
   word. Twelve at 400, which is what the label beside every other spinner here
   is set at.
   ══════════════════════════════════════════════════════════════════════════ */

.app-closing-overlay {
  background: rgba(10, 10, 13, 0.94);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);

  /* It appeared at full strength the instant the window was told to close. The
     shells fade in over 140ms and this can afford the same. */
  transition: opacity 140ms ease;
}

.app-closing-panel {
  gap: 12px;
  color: var(--read);
  font-size: calc(12px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
}

.app-closing-spinner {
  width: 16px;
  height: 16px;
  border-width: 2px;
}

/* ══ the companion button, inside the plugin ═══════════════════════════════
   The host owns the window there and draws no controls of its own into it, so
   the eighty pixels this button was keeping clear are keeping nothing clear —
   which is why it read as pushed inward with a gap beside it.

   Only on macOS, and that is not an oversight. What the plugin removes is the
   three traffic lights, which are a macOS thing; on Windows the desktop keeps
   its account and colour buttons at the left of this bar — at twelve and
   forty-six — because the system's own minimise, maximise and close sit at the
   right. That row exists in the plugin too, so the button stays at the end of
   it rather than being printed underneath the account button.

   Fourteen, which is --sl-pad-left in companion mode: the inset this window
   already uses when it has no room to spare, rather than a third number.
   ══════════════════════════════════════════════════════════════════════════ */

html[data-host="plugin"][data-platform="darwin"] .companion-btn {
  left: 14px;
}

/* ══ the first filter, aligned with everything above and below it ══════════
   "Instruments" sat eight pixels right of the search field, the Pack column and
   every row — enough to read as a mistake at companion widths, where there is
   less around it to excuse the drift.

   Neither bar was at fault: .header-inner and .subheader-inner both inset their
   contents by twenty-four. The difference is that the search field's text
   begins at its container's edge, while a filter is a button and
   .subheader-trigger pads itself by eight so its hover background is not tight
   around the word. So the search text landed at 24 and the word Instruments at
   32.

   Pulled back by exactly that eight, on the first one only. The word now begins
   where every other first thing on the page begins, and the button keeps its
   padding — so its hover background extends a little to the left of the text,
   which is what a button whose label sits on a grid always does, and is
   invisible until pointed at.

   The other filters are untouched: they are spaced from each other by the row's
   own gap, and it is only the first that has an edge to agree with.
   ══════════════════════════════════════════════════════════════════════════ */

.subheader-inner > .subheader-dropdown:first-child {
  margin-left: -8px;
}

/* ── the dot on a place, and what it becomes ──────────────────────────────────
   A place holding files that have never been analysed shows a dot. Hovering the
   place turns it into two rotating arrows, because at that point it stops being
   a notice and becomes a button: clicking it asks which analysis to run and then
   runs it on those files alone.

   Drawn rather than fetched. It is 14 by 14 pixels of two arrows and an inline
   SVG costs nothing, where an icons/ file would be one more thing to package,
   one more request, and one more thing to forget when the accent changes. The
   stroke is currentColor so it follows the accent like everything else here. */

.sidebar-new-badge {
  /* A button, and it has to look like one the moment the place is hovered. */
  border: 0;
  padding: 0;
  background: var(--glow);
  transition: width 0.14s var(--ease), height 0.14s var(--ease),
              background 0.14s var(--ease), transform 0.14s var(--ease);
}

.sidebar-item-btn:hover .sidebar-new-badge.is-visible,
.sidebar-new-badge.is-visible:focus-visible {
  width: 15px;
  height: 15px;
  border-radius: 4px;
  box-shadow: none;
  cursor: pointer;
  /* Two arrows chasing each other round a circle: the shape everybody reads as
     "do this again". A mask rather than a background image, so the colour comes
     from `background` and follows the accent. Written as an image it would have
     needed the accent baked into the data URI, and would have stayed orange the
     moment somebody chose violet. */
  background-color: var(--glow);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 0 1-9 9 9 9 0 0 1-7.5-4'/%3E%3Cpath d='M3 12a9 9 0 0 1 9-9 9 9 0 0 1 7.5 4'/%3E%3Cpolyline points='21 3 19.5 7 15.5 7'/%3E%3Cpolyline points='3 21 4.5 17 8.5 17'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 0 1-9 9 9 9 0 0 1-7.5-4'/%3E%3Cpath d='M3 12a9 9 0 0 1 9-9 9 9 0 0 1 7.5 4'/%3E%3Cpolyline points='21 3 19.5 7 15.5 7'/%3E%3Cpolyline points='3 21 4.5 17 8.5 17'/%3E%3C/svg%3E");
  -webkit-mask-size: 15px 15px;
  mask-size: 15px 15px;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.sidebar-item-btn:hover .sidebar-new-badge.is-visible:hover {
  transform: rotate(-35deg);
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-new-badge,
  .sidebar-item-btn:hover .sidebar-new-badge.is-visible:hover {
    transition: none;
    transform: none;
  }
}

/* ── waiting for an analysis nobody has started ───────────────────────────────
   Distinct from .sample-type-pending, which spins and means work is under way.
   This one is still: a hollow ring, dim, saying the type is not known yet and
   that nothing is currently going to find out. It matches the dot on the place
   in the sidebar — same idea, same restraint. */
.sample-type-waiting {
  width: 18px;
  height: 18px;
  display: block;
  border-radius: 999px;
  border: 1.5px dashed rgba(180, 176, 174, 0.34);
  pointer-events: none;
}

/* ── Reordering the sidebar ────────────────────────────────────────────────
   A line where the row will land, and nothing else moves until it lands there.

   The rows used to slide apart as the pointer crossed them, which sounds like
   the clearest possible feedback and is not: a sidebar is short evenly spaced
   words, so one list of names becoming a slightly different list of names gives
   the eye nothing to hold on to. The question being asked is where this will
   go, so the answer is drawn as a line at that spot.

   No grab cursor on hover. These are buttons first - they are clicked far more
   often than they are dragged - and a hand that turns into a claw over every
   entry in the sidebar announces a gesture nobody asked about. It changes once
   the drag is actually under way, which is when it is true. */
/* The one being held fades where it is standing.
 *
 * It was given the accent and that was invisible, for a reason worth writing
 * down: the selected entry is ALREADY the accent. So dragging the folder you
 * are currently looking at - which is most of the time, since looking at it is
 * what makes you want it higher up - painted it the colour it was already
 * wearing and changed nothing at all. On any other row it merely made it look
 * selected, which is a different lie.
 *
 * Opacity is orthogonal to all of that. It composes with selected, hovered,
 * analysing, offline, whatever the row happens to be, and it says the right
 * thing: this one has been lifted out of the list and the line below is where
 * it is going to be put down. */
.sidebar-item-btn.is-reordering,
.sidebar-item-btn.is-reordering:hover {
  opacity: 0.38;
  transition: opacity 0.12s var(--ease);
}

/* Drawn on the neighbour rather than as a floating element: nothing to keep in
   step with a scrolling list, and it cannot outlive the row it belongs to. */
.sidebar-scroll-inner [data-folder-id],
.sidebar-scroll-inner [data-collection-id] { position: relative; }

.sidebar-item-btn.is-drop-before::after,
.sidebar-item-btn.is-drop-after::after {
  content: "";
  position: absolute;
  left: 6px;
  right: 8px;
  height: 2px;
  border-radius: 1px;
  /* --drag-color is put on the list while a row is held, and it is the colour
     of the row being dragged rather than of the neighbour this mark is drawn
     on. Without one, the accent. */
  background: var(--drag-color, var(--glow));
  pointer-events: none;
}

.sidebar-item-btn.is-drop-before::after { top: -1px; }
.sidebar-item-btn.is-drop-after::after  { bottom: -1px; }

/* While a row is held the pointer is a claw everywhere - it does not become a
   caret over a label or a hand over a button halfway through the gesture. And
   nothing selects: a drag across text used to leave the sidebar highlighted
   blue behind it. */
body.is-reordering-sidebar,
body.is-reordering-sidebar * {
  cursor: grabbing !important;
  user-select: none;
}

/* ── An item's own colour, wherever the accent would have marked it ─────────
 *
 * Right-clicking a place or a collection gives it a colour, and until now that
 * colour drew one thing: a six-pixel bar down its leading edge. Everything else
 * about the item — the word and icon when it is selected, the outline when a
 * sample is dragged onto it, the rule under its tab — stayed the one accent the
 * window runs on, so two places with two different colours looked identical the
 * moment you clicked either.
 *
 * Every one of those now reads var(--item-color, var(--glow)). An item with no
 * colour of its own has no --item-color, the fallback answers, and it looks
 * exactly as it did. applyItemColor in main.js sets and clears the variable, so
 * choosing and clearing a colour both take effect without a re-render.
 *
 * The icon is the awkward one. It is an <img> of a grey silhouette tinted by a
 * filter, and a filter chain can be written to produce one particular colour —
 * --glow-filter is exactly that, measured against rgb(255,178,122) — but not an
 * arbitrary one. So for a coloured item the icon is drawn instead as a box of
 * that colour masked by the icon's own shape. Same pixels, any colour. */
/* background-color and not background: the colour bar down the leading edge is
   a background-image, and the shorthand would wipe it. Twelve per cent is a
   wash rather than a block - the word and the icon are what say "this one", and
   this only gives them a line to sit on. */
/* SELECTIONNER UNE LIGNE NE DOIT PAS L'ETEINDRE.
 *
 * Le survol met du blanc a 94 %, qui se compose sur le fond de la barre a une
 * clarte perçue de 0,955 en OKLCH. La selection posait la couleur telle quelle,
 * et aucune couleur n'atteint le blanc : le blanc EST le maximum. Mesure des
 * deux etats reels :
 *
 *     survol, blanc a 94 %              L 0.955
 *     --glow #ffb27a                    L 0.825
 *     un bleu choisi par l'utilisateur  L 0.662
 *
 * Donc cliquer sur une ligne qu'on survolait la faisait perdre de la lumiere,
 * et d'autant plus que la couleur choisie etait sombre. On voyait la selection
 * comme un assombrissement alors qu'elle devrait etre l'etat le plus present.
 *
 * oklch(from … 0.92 c h) garde la TEINTE et la SATURATION de la couleur et ne
 * fixe que la clarte. Un bleu nuit et un jaune arrivent donc au meme niveau,
 * ce qu'un eclaircissement en pourcentage ne fait pas : melanger 30 % de blanc
 * delave un jaune clair et ne suffit pas a un bleu fonce. Une regle couvre
 * toutes les couleurs que l'utilisateur peut choisir, y compris celles qui
 * n'existent pas encore.
 *
 * La ligne sans oklch est declaree d'abord expres : un moteur qui ne connait
 * pas la syntaxe relative ignore la seconde et garde le comportement d'avant
 * plutot que de perdre la couleur. */
.sidebar-item-btn.is-selected,
.sidebar-item-btn.is-selected:hover {
  color: var(--item-color, var(--glow));
  color: oklch(from var(--item-color, var(--glow)) 0.92 c h);
  /* Square. The 0 8px 8px 0 these buttons carry was invisible while nothing was
     drawn behind them; with a wash behind, it became a rounded tab hanging off
     the right of the row. The wash is a line to sit on, not a shape. */
  border-radius: 0;
  /* A stacking context, so the wash below - a negative z-index - is resolved
     inside this row rather than against whichever ancestor has one, which
     would paint it behind the panel instead of behind the row. */
  isolation: isolate;
}

/* The wash behind the chosen row, and the one pixel it stops short of.
 *
 * .app-sidebar::after draws the panel's right edge: one pixel of white at 5.5%,
 * which is invisible against the near-black it was made for. Run a coloured
 * wash under it and it stops being invisible - 5.5% white shows THROUGH an 11%
 * wash, because a translucent background cannot hide what is behind it - and it
 * reads as a bright nick at exactly the point the selection ends.
 *
 * Three things were tried before this. Raising the row above the divider
 * (z-index 2) works only if the row is opaque, which was proved by giving it a
 * solid colour: the divider vanished. Pushing the divider behind the sidebar's
 * content changed nothing. Putting the wash in background-color rather than a
 * pseudo-element changed nothing either, for the same reason: 11% of anything
 * is see-through.
 *
 * So the wash simply stops a pixel early and the panel edge keeps the dark
 * background it was drawn for. On a pseudo-element rather than the row's own
 * background because an inset needs a box of its own - a border on the row
 * would shift its label by a pixel the moment it was selected - and ::after
 * rather than ::before, guarded, because a drag's own mark owns them both while
 * a row is being moved and matters more than a wash. */
/* LE LAVIS DE COULEUR EST PARTI, A LA DEMANDE.
 *
 * Une ligne selectionnee portait un fond teinte a 11 % sur toute sa largeur, en
 * plus du mot colore, de l'icone coloree et de la barre de 6 px sur son bord.
 * Quatre facons de dire la meme chose pour une seule ligne. Les trois autres
 * restent : c'est le mot et l'icone qui disent « celle-ci », et la barre du bord
 * qui dit de quelle couleur elle est.
 *
 * Le bloc ::after tout entier s'en va plutot que son fond seul : il n'existait
 * que pour le porter, et un pseudo-element vide en position absolue avec un
 * z-index negatif est un piege pour la prochaine personne qui lira ce fichier. */

/* LA COULEUR SE POSE PAR-DESSUS L'ICONE, ELLE NE LA REMPLACE PLUS.
 *
 * L'ancienne version echangeait les deux : l'image passait a opacity 0 et le
 * span devenait une boite de couleur decoupee par un masque. Un masque est une
 * ressource image a part ; tant qu'elle n'est pas rasterisee, l'element est
 * masque par RIEN, donc efface — l'icone partait puis revenait. Deux tentatives
 * ont echoue avant celle-ci : attendre decode() sur une Image() a nous ne dit
 * rien de ce que le moteur a rasterise pour le masque, et le desarmement a
 * chaque peinture a fabrique un clignotement de plus.
 *
 * Ici l'image grise reste EN PLACE et ne bouge jamais. La couleur est un
 * ::after pose exactement dessus, avec la meme forme. Si le masque n'est pas
 * pret, ce calque ne peint rien et on voit l'icone grise : une couleur en
 * retard d'une frame au lieu d'un trou. Il n'y a plus d'instant ou l'icone peut
 * ne pas exister, quel que soit le moment ou le masque arrive.
 *
 * C'est aussi pourquoi armIconMask et is-icon-masked ont disparu : ils
 * servaient a deviner le bon instant, et il n'y a plus d'instant a deviner. */
.sidebar-item-btn.has-item-color.is-selected .sidebar-folder-icon,
.sidebar-item-btn.has-item-color.is-selected:hover .sidebar-folder-icon {
  width: 17px;
  height: 17px;
  position: relative;
}

.sidebar-item-btn.has-item-color.is-selected .sidebar-folder-icon::after,
.sidebar-item-btn.has-item-color.is-selected:hover .sidebar-folder-icon::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* La meme clarte que le mot a cote, sinon l'icone reste terne pendant que le
     label se rallume et la ligne se lit en deux morceaux. Voir la note sur
     .is-selected plus haut pour la mesure. */
  background-color: var(--item-color);
  background-color: oklch(from var(--item-color) 0.92 c h);
  -webkit-mask-image: var(--icon-url);
  mask-image: var(--icon-url);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

/* The img still holds the box open at the right size; only its pixels go. */
/* L'image ne s'efface plus : c'est elle le filet de securite. Voir la note
   ci-dessus. La regle est gardee, vide, pour que personne ne la reecrive sans
   lire pourquoi elle a ete videe. */

/* Dropping a sample onto a collection: its own colour if it has one. */
.sidebar-item-btn.drag-over,
#collections-likes-btn.drag-over {
  outline-color: var(--item-color, var(--glow));
}

#collections-likes-btn.drag-over {
  box-shadow: inset 0 0 0 1px var(--item-color, var(--glow));
}

/* The wash behind the outline, same colour, same fallback. The !important
   matches the rule it overrides in styles.css rather than adding emphasis. */
.sidebar-item-btn.drag-over,
#collections-likes-btn.drag-over {
  background: color-mix(in srgb, var(--item-color, var(--glow)) 8%, transparent) !important;
}
