/* The quick search panel.
 *
 * Everything that moves here moves in CSS, and that is the whole design. The
 * window is transparent, fixed in size, and never resizes: a window that grows
 * on every keystroke stutters, because each resize is a round trip through the
 * window server and the page relayouts behind it. So the window is big enough
 * for the largest state from the moment it opens, the panel floats inside it,
 * and growing is a grid row going from 0fr to 1fr with the panel translating up
 * at the same time. Both are compositor work on this machine and neither can be
 * out of step with the other, because they are one transition on one element.
 */

@font-face {
  font-family: 'Space Grotesk';
  src: url("../fonts/SpaceGrotesk.woff2") format("woff2");
  font-weight: 400 600;
  font-display: block;
}

:root {
  /* Straight out of the application's styles.css, name for name.
   
     The panel was its own shade of dark grey and read as a different product
     sitting on top of this one. Nothing above the ground has a colour of its
     own here either: it is white, held back, in the same four steps. */
  /* La taille du texte, dite par le processus principal — ce panneau ne lit pas
     les reglages. Un, c'est la taille dessinee, et c'est ce qui vaut tant que
     personne n'a rien demande. Les neuf tailles de cette feuille sont ecrites
     comme un nombre fois cette variable. */
  --ui-font-scale: 1;

  /* Et la graisse, qui est l'autre moitie de la plainte : « small AND THIN ».
     Deux cent onze declarations passent par ces deux-la — les seules graisses
     que cette fenetre emploie — donc « plus gras » les monte d'un cran ensemble
     et la hierarchie entre les deux tient.
     Space Grotesk est variable de 400 a 600 et pas au-dela : un cran de plus
     est tout ce que le fichier sait faire, et rien n'est donc synthetise. */
  --ui-weight-regular: 400;
  --ui-weight-medium: 500;

  --ink:      #050507;
  /* Gardee pour ce qui doit se detacher du fond ; le panneau lui-meme ne s'en
     sert plus, il prend --ink comme l'application. */
  --raised:   rgb(9, 9, 12);
  --text:     rgba(255, 255, 255, 0.94);
  --read:     rgba(255, 255, 255, 0.62);
  --dim:      rgba(255, 255, 255, 0.44);
  --faint:    rgba(255, 255, 255, 0.20);
  --hairline: rgba(255, 255, 255, 0.09);
  --divider:  rgba(255, 255, 255, 0.055);
  --pane:     rgba(255, 255, 255, 0.035);

  /* What the foot of the panel is painted with: the panel's own ground, opaque
     enough that a row cannot show through the numbers or the shortcuts. The
     panel itself is 0.97 of this over a transparent window, and the difference
     between the two is not visible - what is visible is a sample name behind a
     page number, which is what having no ground at all looked like. */
  /* Le pied est la meme surface que le panneau, donc la meme valeur : deux tons
     voisins sur une meme fenetre se lisent comme une erreur d'alignement. */
  --foot-ground: #050507;

  --glow: #ffb27a;
  --glow-rgb: 255, 178, 122;
  /* The wash under the row that is playing.
   
     Built FROM the accent, not written out. Copied from styles.css as a literal
     it stayed orange while everything else followed the colour the user picked,
     so a purple accent got a warm brown row. The two above are only defaults for
     the instant before spotlight.js reads the real accent and overwrites them;
     anything derived has to be derived, or it is left behind. */
  --glow-soft: rgba(var(--glow-rgb), 0.10);

  /* Every horizontal line in the window, drawn the one way.
   
     The library fades each rule out at both ends rather than running it wall to
     wall, so a list of fifty reads as a texture instead of fifty hard edges.
     Same numbers, same variable: 4 % and 96 %. */
  --rule: linear-gradient(90deg,
    transparent 0,
    var(--divider) 4%,
    var(--divider) 96%,
    transparent 100%);

  /* Le meme trait, mais pose contre un surlignage. Du blanc a 5,5 % le long
     d'une bande a 5 %, ce sont deux gris qu'on ne distingue pas ; les 9 % que
     cette feuille tient deja pour un trait franc se voient. Meme dessin, meme
     fondu, une seule valeur de plus. */
  --rule-firm: linear-gradient(90deg,
    transparent 0,
    var(--hairline) 4%,
    var(--hairline) 96%,
    transparent 100%);

  --panel-width: 700px;
  --bar-height: 62px;
  --foot-height: 34px;
  --bottom-air: 26px;
  /* Derived from the window rather than chosen.
   
     It was a flat 396 pixels, and with a full list the panel came to more than
     the window could hold: bar plus results plus the line of shortcuts overran
     the bottom edge and the shortcuts were simply cut off, which is what "the
     shortcuts are gone when there are too many samples" was. Taken from the
     height that actually exists, the panel cannot outgrow its window whatever
     the window ends up being. */
  /* --page-height is gone. It reserved room under the list for a pagination bar
     pinned across the foot, and the numbers are inside the scrolling list now,
     the way the library has them - so there is nothing to reserve. */
  /* Taller than the line of shortcuts: the numbers and the arrows are the
     library's own size, 26 and 14, and squeezing them into 34 would be a
     smaller pagination rather than the same one. */
  --pages-height: 40px;

  --results-height: calc(100vh - var(--top) - var(--bar-height)
                         - var(--foot-height) - var(--bottom-air));
  /* Where the bar sits inside the window when nothing has been typed. The
     window itself is placed on screen so that this lands a little above the
     middle, which is where a search panel is looked for. */
  --top: 130px;

  --ease: cubic-bezier(0.22, 0.61, 0.24, 1);
  --dur: 300ms;

  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: transparent;      /* the window is transparent; this must not fill it */
  overflow: hidden;
  /* The panel is dragged from, and a text selection started by a stray drag
     would fight the file drag. Selection is re-enabled on the input alone. */
  user-select: none;
  cursor: default;
}

/* ── How the letters are drawn, which is not a size and is not a colour ─────
 *
 * Every measurement matched and the panel still read heavier than the list:
 * same words, same pixels, thicker strokes. It is none of the three things
 * being compared - it is how the glyphs are rasterised, and theme.css says so
 * for the window and this file said nothing for the panel.
 *
 * Three declarations, and the first is the one that shows:
 *
 *   -webkit-font-smoothing: antialiased asks macOS for greyscale rasterising.
 *   Without it the platform uses subpixel antialiasing, which puts colour into
 *   the edge of every stroke and reads as extra weight - noticeably so on a
 *   dark ground, which is the whole of this panel.
 *
 *   font-weight: 400 on the elements themselves, not just inherited, because a
 *   button and an input do not start at 400 on their own.
 *
 *   font-synthesis: none refuses a weight the file does not contain rather than
 *   smearing one, which is the other way a letter comes out fat.
 *
 * Same element list as theme.css, because a control has to be told as well as
 * a paragraph.
 */
html,
body,
input,
button,
select,
textarea {
  font-family: 'Space Grotesk', -apple-system, system-ui, sans-serif;
  font-weight: var(--ui-weight-regular, 400);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis: none;
}

/* ── The window's empty area, which is also the way out ──────────────────── */
.scrim {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Anchored by the top, not centred.
   
     Centred, the panel growing would move the bar up on its own, but by way of
     layout: the flex container would re-centre a blurred, shadowed box on every
     frame of the growth. Anchored, the growth goes downward and the rise is a
     transform, which the compositor handles without touching layout at all. */
  justify-content: flex-start;
  padding: var(--top) 24px 0;
  opacity: 0;
  transition: opacity 140ms linear;
}

/* Added a frame after the window is shown, so the first paint is the closed
   state and the opening is a transition rather than a jump. */
body.is-open .scrim { opacity: 1; }

/* ── The panel ──────────────────────────────────────────────────────────── */
.panel {
  width: min(var(--panel-width), 100%);
  display: block;
  border-radius: 16px;
  /* Opaque, a la demande du proprietaire.
     
     C'etait 0.97 : presque solide, avec un flou d'arriere-plan pour le reste.
     Presque n'est pas solide — sur une fenetre de DAW claire, les trois pour
     cent qui restaient laissaient passer assez de contraste pour qu'un nom de
     sample de onze pixels se lise sur un fond qui bouge. Glass is pretty over a
     desktop and unreadable over an arrangement, disait deja la note ; elle avait
     raison jusqu'au bout. */
  background: var(--ink);
  /* The frame is neutral, and that is deliberate.
   
     Everything here was tinted for a moment, edge and keys and separators and
     all, and it was worse: the accent is the only colour in the interface, and
     spending it on the furniture is what stops it meaning anything when it
     lands on the row that is playing. It is kept for what is selected, what is
     sounding, and nothing else. */
  border: 1px solid var(--hairline);
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.55),
    0 2px 10px rgba(0, 0, 0, 0.4);
  /* Le flou d'arriere-plan est parti avec la transparence : il ne rend visible
     que ce qu'on voit AU TRAVERS, et on ne voit plus rien au travers. Le garder
     ferait composer une couche floutee a chaque image pour un resultat que rien
     n'affiche — cher sur une fenetre qui s'ouvre et se ferme a la volee. */
  overflow: hidden;
  transform: translateY(12px) scale(0.985);
  opacity: 0;
  transition:
    transform var(--dur) var(--ease),
    opacity 160ms linear;
}

body.is-open .panel {
  transform: translateY(0) scale(1);
  opacity: 1;
}

/* The results open underneath and the bar does not move.
   
   It used to rise as they arrived, which sounds better than it reads: the thing
   you are typing into moving away from you while you type is exactly the wrong
   moment for it to move. The bar is fixed and only the room below it grows. */
body.is-open .panel.has-results .results {
  max-height: calc(var(--results-height) + var(--foot-height) + 2px);
}

/* ── Being pushed about ─────────────────────────────────────────────────────
 *
 * The panel has no title bar, so it is taken hold of by itself: anywhere except
 * the field, where a press places the caret, and a sample, where it begins a
 * drag into the DAW. The controls are out of it too. Which press counts is
 * decided in spotlight.js — this is only the cursor while one is running.
 *
 * No user-select to add: html and body already say none, for the drag that was
 * here before this one. The claw is on everything, not just the panel, because
 * a gesture that starts on the panel and travels over the samples should not
 * change hands halfway.
 */
body.is-moving-panel,
body.is-moving-panel * { cursor: grabbing !important; }

/* ── The bar ────────────────────────────────────────────────────────────── */
.bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px 0 18px;
  height: var(--bar-height);
}

.bar-icon {
  width: 19px;
  height: 19px;
  flex: 0 0 auto;
  color: var(--dim);
  transition: color 200ms linear;
}

.panel.has-query .bar-icon { color: var(--glow); }

.bar-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  caret-color: var(--glow);
  font-family: inherit;
  font-size: calc(19px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: -0.01em;
  user-select: text;
  cursor: text;
}

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

/* How many there are, and nothing else.
 *
 * It said "6355 · page 1/159", which is two facts in one place and the second
 * of them is already written along the bottom of the panel in the pagination.
 * And it was written in the panel's own voice - twelve pixels at forty-four per
 * cent - rather than the library's.
 *
 * .subheader-sample-count in styles.css: eleven pixels, weight 400, 0.02em,
 * white at twenty-two per cent. Quieter than anything else in the bar, which is
 * right for a number nobody is reading on purpose. No tabular figures: the
 * library does not ask for them here and they change the width of every digit,
 * which is only worth it in a column. */
.bar-count {
  flex: 0 0 auto;
  font-size: calc(11px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.22);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 180ms linear;
}

.panel.has-results .bar-count { opacity: 1; }

/* CE QU'ON REGARDE PENDANT QUE LA QUESTION EST POSEE.
 *
 * Une barre qui ne repond pas ne dit pas si elle cherche ou si elle est cassee,
 * et sur soixante-dix-huit mille samples la reponse n'est pas instantanee.
 *
 * L'anneau est celui de l'application, aux valeurs de l'application : deux
 * pixels d'accent a 20 %, le haut a l'accent plein, un tour en 600 ms. Voir
 * .search-spinner dans src/styles.css. Personne ne doit apprendre une animation
 * de plus, elle est deja dans la fenetre d'a cote.
 *
 * Plus petit qu'elle, parce qu'il tient dans une barre de recherche et pas au
 * milieu d'une liste : il remplace le compte, il ne recouvre rien. */
.bar-spinner {
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid rgba(var(--glow-rgb), 0.20);
  border-top-color: var(--glow);
  animation: bar-spin 600ms linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .bar-spinner { animation-duration: 1.8s; }
}

.bar-esc,
.hint kbd {
  font-family: inherit;
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  line-height: 1;
  padding: 4px 6px;
  border-radius: 5px;
  color: var(--dim);
  background: var(--pane);
  border: 1px solid var(--divider);
}

.bar-esc { flex: 0 0 auto; }

/* ── Shuffle and the pages ──────────────────────────────────────────────────
   Both are the library's, down to the rule the library follows for everything
   you can click: nothing sits in a box. A control is a word or a mark, it is
   held back until you reach for it, and reaching for it changes its colour and
   nothing else. See src/theme.css - the header buttons and the pagination there
   both strip the radius, the border and the background that styles.css gives
   them and keep only the colour.

   That rule is why the page you are on is the accent and not a filled block: the
   one that is chosen takes the colour. */

.bar-shuffle {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  /* The accent, at rest and not only when reached for.
     It is the one control in this bar that does something to the list rather
     than describing it, and the library marks that kind in colour - the same
     way the clear button in its header wears the accent while the buttons
     either side of it stay grey. */
  color: var(--glow);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms linear, color 150ms var(--ease);
}

/* Shown only once there is a list to shuffle, like the count beside it. */
.panel.has-results .bar-shuffle { opacity: 1; pointer-events: auto; }

.bar-shuffle svg { width: 15px; height: 15px; display: block; }

/* Reaching for it lifts the same colour rather than replacing it: turning it
   grey or white on hover would read as it being switched off. */
.bar-shuffle:hover,
.bar-shuffle.is-spun {
  background: none;
  color: color-mix(in oklab, var(--glow) 62%, #ffffff);
}

.pages {
  height: var(--pages-height);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 10px;
  user-select: none;

  /* Its own ground, and this is the correction.
   *
   * It had none, and the note that used to be here argued for none - no rule
   * above it, because the last row of the list draws its own line. Which is
   * true right up until the list is longer than the panel: then the row under
   * the numbers is not the last one, it is a row cut in half, and the numbers
   * sit in the middle of it. The list has to pass BEHIND this, not through it.
   *
   * .pagination-bar in styles.css is the same thing done properly - a ground of
   * the window's own background and a hairline along the top - and this is
   * that, against the panel's ground rather than the window's. */
  background: var(--foot-ground);
  border-top: 1px solid var(--divider);
}

/* [hidden] alone would lose to the display above it, the way .results-empty
   once did and pushed the shortcuts out of the window. */
.pages[hidden] { display: none; }

.pages-nums {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 4px;
}

.pages-step,
.pages-num {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: calc(13px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  min-width: 26px;
  height: 26px;
  padding: 0 4px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  transition: color 150ms var(--ease);
}

.pages-step:hover:not(:disabled),
.pages-num:hover { color: var(--text); background: transparent; }

.pages-step:disabled { opacity: 0.2; cursor: default; }

/* The one you are on takes the colour, and does not sit in a box. */
.pages-num.is-here {
  color: var(--glow);
  background: transparent;
  font-weight: var(--ui-weight-medium, 500);
  cursor: default;
}

/* The arrows are the library's own image, mirrored for the far side. Flat, so
   they follow the buttons by being tinted rather than recoloured. */
.pages-icon {
  width: 14px;
  height: 14px;
  object-fit: contain;
  display: block;
  opacity: 0.5;
  transition: opacity 150ms var(--ease);
}

.pages-icon--next { transform: scaleX(-1); }

.pages-step:hover:not(:disabled) .pages-icon {
  opacity: 1;
  filter: brightness(0) invert(1);
}

/* ── The results ────────────────────────────────────────────────────────── */
/* Closed is a maximum of nothing; open is a maximum, not a height.
   
   This was a grid row going from 0fr to 1fr, which reads well and does not work:
   in a panel with no height of its own the fr track resolved to nothing, so the
   row measured one pixel while holding four hundred, the list spilled past the
   panel and the line of shortcuts landed below the window entirely. A maximum
   animates the same way and still lets the panel be exactly as tall as the few
   results it has. */
.results {
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--dur) var(--ease), opacity 180ms linear;
  background-image: var(--rule);
  background-size: 100% 1px;
  background-position: left top;
  background-repeat: no-repeat;
  /* The separator would otherwise be visible as a line across the closed bar. */
  opacity: 0;
}

.panel.has-results .results { opacity: 1; }

/* What scrolls: the rows AND the numbers at the end of them.
 *
 * It was the list alone, with the numbers pinned across the foot beside the
 * shortcuts - so they never moved, and the last row was always under them. The
 * library does not do that: it inserts its pagination bar after the list and
 * inside .app-main, which is the element with the overflow, so the numbers are
 * the end of the samples and you get to them by scrolling.
 *
 * As tall as its contents up to a limit, rather than always the limit: a fixed
 * height left a hand's width of empty panel under a single result and under
 * "nothing matches". No --page-height subtracted any more - the numbers are
 * inside this box now, not reserved space below it. */
.results-scroll {
  max-height: var(--results-height);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--faint) transparent;
}

.results-scroll::-webkit-scrollbar { width: 8px; }
.results-scroll::-webkit-scrollbar-thumb {
  background: var(--faint);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: content-box;
}

.results-inner {
  /* Full bleed: the rows are bands across the panel, so their rules can reach
     the edges and fade there rather than starting six pixels in. */
  padding: 0;
}

/* ── One result ─────────────────────────────────────────────────────────── */
.row {
  display: grid;
  /* The library's row, measured: sixty tall, a forty-pixel cover, and the meta
     as FOUR cells in the order length, tempo, key, scale. It was fifty tall
     with a thirty-two cover and three cells in another order, which is why the
     same sample did not look like the same sample in the two lists. */
  grid-template-columns: 40px 20px minmax(0, 1fr) 116px 172px;
  align-items: center;
  gap: 12px;
  height: 60px;
  padding: 0 16px 0 13px;
  cursor: grab;
  /* Pour que le trait ci-dessous se pose dans la rangee. */
  position: relative;

  /* Only the property that is cheap to animate. Backgrounds on a list that is
     rebuilt on every keystroke are the difference between smooth and not. */
  transition: background-color 110ms linear;
}

/* LE TRAIT EST UN TRAIT, PAS UN FOND, et c'est la meme loi que dans la liste de
   la bibliotheque, pour la meme raison qu'elle.

   Il etait le background-image de la rangee, ce qui mettait le surlignage et le
   trait dans la meme propriete : chaque regle qui parlait de l'un redeclarait
   l'autre, et la plus specifique gagnait sans le savoir. Ici ca donnait le
   double trait sous la barre de recherche des que la premiere rangee etait
   survolee ou selectionnee.

   EN HAUT et pas en bas : dessine en bas, le trait au-dessus d'une rangee
   appartient a la PRECEDENTE, donc du blanc a 5,5 % sur du noir colle sous un
   surlignage a 5 %, et il disparait. Dans sa propre boite il est compose sur le
   surlignage, et les deux bords se voient. */
.row::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  pointer-events: none;
  background: var(--rule);
}

/* Rien au-dessus de la premiere : le bloc des resultats pose deja ce trait-la,
   et deux traits colles en font un epais.
   Et plus rien a faire pour la derniere : c'est le bord du panneau qui ferme la
   liste, comme avant. */
.row:first-child::before { content: none; }

/* Quand c'est la premiere rangee qui est surlignee, c'est le trait DU BLOC qui
   se renforce. L'ancienne reponse etait de rendre son trait a la rangee, et on
   en voyait deux. */
.results:has(.row:first-child:is(:hover, .is-active, .is-playing)) {
  background-image: var(--rule-firm);
}

/* Le trait SOUS une rangee surlignee appartient a la suivante, sur du noir :
   c'est le meme piege de l'autre cote, et la meme reponse. */
.row:is(:hover, .is-active, .is-playing) + .row::before {
  background: var(--rule-firm);
}

.row:active { cursor: grabbing; }

/* The three states the library uses, and the reason it uses three.
 
   Hover is the faintest, because the pointer is only passing. Selected is a
   little more, because that is where the keyboard is. What is PLAYING is a wash
   of the accent, and that is the one that matters: it is the only colour in the
   interface, and it is what makes the playing row findable while the list moves
   under it. Hover and selected sharing one grey was the complaint, and it was
   right: nothing said which one was making the sound. */
.row:hover { background-color: var(--pane); }

/* Where the keyboard is. Neutral, so it cannot be mistaken for the accent that
   means something else.
 *
 * Five per cent, which is what .sample-row.is-focused is in theme.css. It was
 * 0.055 - close enough to be an accident and far enough that the two lists did
 * not match when they were open side by side. Every number in this file that
 * has a counterpart over there is now that counterpart. */
.row.is-active { background-color: rgba(255, 255, 255, 0.05); }

/* What is playing. The one colour in the interface, and the reason it is worth
   spending here: this is the row you are looking for when the list moves. */
.row.is-playing { background-color: var(--glow-soft); }

/* Text is BRIGHTENED by a state, never recoloured by it. That is the library's
   rule and it is the right one: a name in the accent competes with the accent
   itself, which is already saying "this one" through the wash under the row and
   the shape at the end of it. */
.row:hover .row-name,
.row.is-active .row-name,
.row.is-playing .row-name { color: var(--text); }

/* Under the pointer only, to forty-two per cent. Measured: a row the keyboard
   is on does not brighten its tags in the library, and neither does one that is
   playing - only the one the mouse is over. */
.row:hover .row-tag { color: rgba(255, 255, 255, 0.42); }

/* The pack cover, the same one the library draws, at list size.
   PackCoverSystem paints into this and brings its own rules for the shimmer it
   shows while one is being rendered; all that is needed here is the box. */
/* Three pixels of radius and a hairline ring inside it, which is what
   .pack-avatar wears in the library. Seven was a different shape of corner from
   the one on the same artwork one window over. */
.row-cover {
  width: 40px;
  height: 40px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--pane);
  box-shadow: inset 0 0 0 1px var(--hairline);
}

.row-cover .pack-avatar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.row-type {
  position: relative;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
}

/* The ring around the glyph, which the list draws and this did not.
 *
 * Straight off .sample-col--type::before in styles.css: two pixels of the
 * accent, four out from the box on every side, an eight-pixel radius, and it
 * fades in over a quarter of a second. Shown for the row that is PLAYING and
 * for the row the keyboard is on - the same pair over there, where they are
 * called is-playing and is-focused.
 *
 * It is a pseudo-element and not a border on the box itself, because the box
 * holds three glyphs that are swapped by display and a border would move them:
 * four pixels out is outside the grid that centres them. */
.row-type::before {
  content: "";
  position: absolute;
  inset: -4px;
  border: 2px solid var(--glow);
  border-radius: 8px;
  pointer-events: none;
  transition: opacity 250ms ease;
  opacity: 0;
}

/* The row that is PLAYING and the row the listening is on - not the row the
   pointer happens to be over.
 *
 * It was is-active, and is-active follows the mouse here: the ring chased the
 * pointer down the list, which is not what the library does. Over there the
 * pointer only changes the wash behind a row, and the ring stays on is-focused
 * - the last row played or clicked. is-focused here means the same thing and is
 * set in spotlight.js, in its own right rather than as a side effect of where
 * the mouse is. */
.row.is-playing .row-type::before,
.row.is-focused .row-type::before {
  opacity: 1;
}

/* The same filter the library puts on these two images: black, desaturated,
   then lifted back to a grey. */
/* The same filter AND the same dimming the library puts on these two images.
   The filter was already right; the opacity was missing, so a loop or one-shot
   glyph here was three times the ink it is over there - the brightest thing in
   a row whose job is to be read as a name. */
.type-icon {
  width: 20px;
  height: 20px;
  display: block;
  filter: brightness(0) saturate(0%) invert(66%);
  opacity: 0.32;
  pointer-events: none;
}

/* The type is also the transport, exactly as it is in the library: what it is at
   rest, play when the pointer is on the row, stop while it sounds. A ring drawn
   around a loop icon said "this one" without saying what to do about it. */
.type-play,
.type-stop {
  width: 20px;
  height: 20px;
  display: none;
  fill: var(--glow);
}

.row-type { cursor: pointer; }

.row:hover .type-icon { display: none; }
.row:hover .type-play { display: block; }

.row.is-playing .type-icon,
.row.is-playing .type-play { display: none; }
.row.is-playing .type-stop { display: block; }

.row-text { display: block; min-width: 0; }

/* The name, in the library's own type: thirteen, and tracked a hair. It was
   13.5 and untracked, which reads as a different font at a glance. */
.row-name {
  display: block;
  width: 100%;
  font-size: calc(13px * var(--ui-font-scale, 1));
  letter-spacing: 0.01em;
  color: var(--read);
  transition: color 150ms var(--ease);
  white-space: nowrap;
  overflow: hidden;
  /* And no ellipsis: the library does not put one here, and the fade is the
     statement it makes instead. */
  -webkit-mask-image: linear-gradient(to right, black 75%, transparent 100%);
  mask-image: linear-gradient(to right, black 75%, transparent 100%);
}

/* ── Fading out rather than cutting off ────────────────────────────────────
 *
 * The library ends both the name and the tag run in a fade, and it does it with
 * a mask rather than a gradient laid over the top: masking goes to transparent,
 * so it is right on any ground and in every row state, where an overlay has to
 * be told the colour underneath it and told again for hover, for selected and
 * for playing. There IS such an overlay in the markup - .sample-tags-fade - and
 * it is display: none, which is how long that approach lasted.
 *
 * The panel cut both off instead: hard clip on the tags, and an ellipsis on the
 * name. An ellipsis is a different statement - it says "there is more of this
 * name" where the fade says "the row goes on" - and next to a list that fades
 * it was the most visible thing about the two not matching.
 *
 * Seventy-five per cent for the name and eighty for the tags, which is what
 * .sample-name and .sample-tags-row use. Not the same number, and copied rather
 * than averaged.
 */

/* The tags, and this took two goes to get right.
 *
 * The first was read out of theme.css and was wrong: .sample-tag there is 9.5
 * pixels of widely tracked CAPITALS, and two hundred lines further down the
 * same file drops both - "wide tracking goes with the capitals and is dropped
 * along with them" - to 10.5 pixels of ordinary sentence text at 0.015em. I
 * matched the block that had already been superseded, so the panel went from
 * one wrong answer to another.
 *
 * These are measured off the library's own row in a browser, which is the only
 * reading that accounts for a cascade: 10.5px, weight 400, 0.015em, no
 * transform, white at twenty-eight per cent, brightening to forty-two only
 * under the pointer - a selected row does not touch them.
 */
/* Meme alignement que la bibliotheque, et pour la meme raison : .row-name n'a
   pas de retrait, .row-tag en a quatre pixels. Voir la note dans styles.css. */
.row-sub {
  display: flex;
  flex-wrap: nowrap;
  gap: 2px;
  margin-top: 3px;
  margin-left: -4px;
  width: calc(100% + 4px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, black 80%, transparent 100%);
  mask-image: linear-gradient(to right, black 80%, transparent 100%);
}

/* One label each, with the library's own pixel of padding and two of gap. */
.row-tag {
  flex: 0 0 auto;
  padding: 1px 4px;
  font-size: calc(10.5px * var(--ui-font-scale, 1));
  font-weight: var(--ui-weight-regular, 400);
  letter-spacing: 0.015em;
  /* Said out loud rather than left to the default, because the library says it
     out loud: the capitals these used to be were dropped on purpose. */
  text-transform: none;
  color: rgba(255, 255, 255, 0.28);
  white-space: nowrap;
  transition: color 150ms var(--ease);
}

/* The shape, at the size it is stored: enough to tell a kick from a riser and a
   loop from a one-shot without listening, which is the whole job of it here. */
.row-wave {
  position: relative;
  width: 116px;
  height: 22px;
  cursor: pointer;
}

.row-wave svg {
  width: 116px;
  height: 22px;
  display: block;
}

/* Two copies of the same shape, the played one revealed by a box that widens.
   Clipping a copy is what lets the playhead move without redrawing the path on
   every frame: only one width changes, and it changes on the compositor. */
.wave-base { display: block; }

/* Thirteen per cent at rest, twenty when the pointer is on the row - the two
   numbers in theme.css, and the note there is the reason for them: forty of
   these are on screen at once, individually faint so that they read as a shape
   rather than as a wall of grey blocks. It was --faint, which is twenty, so the
   panel's list was at the library's HOVER weight all the time and never moved. */
.wave-base svg { fill: rgba(255, 255, 255, 0.13); }

.row:hover .wave-base svg,
.row.is-active .wave-base svg,
.row.is-playing .wave-base svg { fill: rgba(255, 255, 255, 0.2); }

.wave-played {
  position: absolute;
  inset: 0;
  width: 0;
  overflow: hidden;
  pointer-events: none;
}

.wave-played svg { fill: var(--glow); }

.row-meta {
  display: grid;
  /* Four, in the library's order: length, tempo, key, scale. */
  grid-template-columns: 46px 38px 34px 46px;
  align-items: center;
  gap: 4px;
  /* Thirteen at sixty-two per cent, measured off .sample-col--length in the
     running window - not the twelve at forty-four that theme.css appears to
     say. And it does not change with any state over there, so it does not here
     either: the rules that lifted it on hover, on selected and on playing are
     gone. */
  font-size: calc(13px * var(--ui-font-scale, 1));
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--read);
}

/* Right aligned, so the digits stack under each other down the list instead of
   starting wherever the word before them happened to end. */
/* The dash is a placeholder, not a value, and should not read as loudly as a
   tempo does. */
.row-meta .is-none { opacity: 0.42; }

.row-meta span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
}

/* ── Nothing found ──────────────────────────────────────────────────────── */
/* hidden means hidden.
   
   Any display value in a stylesheet beats the one the browser attaches to the
   [hidden] attribute, so this block kept its box while claiming to be hidden:
   fifty-odd empty pixels under every result list, which pushed the line of
   shortcuts past the bottom of the window and made it look as though it
   disappeared whenever there were enough samples to fill the panel. */
.results-empty[hidden] { display: none; }

.results-empty {
  display: grid;
  place-items: center;
  padding: 26px 24px 30px;
  text-align: center;
  font-size: calc(13px * var(--ui-font-scale, 1));
  color: var(--dim);
}

.results-empty b { color: var(--text); font-weight: var(--ui-weight-medium, 500); }

/* ── The line under the results ─────────────────────────────────────────── */
/* The shortcuts, on the same ground as the numbers above them.
 *
 * It was drawing a faded rule along its top as a background image and nothing
 * else, so the list showed through it exactly as it did through the numbers. */
.hint {
  margin: 0;
  height: var(--foot-height);
  box-sizing: border-box;
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 0 14px;
  background: var(--foot-ground);
  border-top: 1px solid var(--divider);
  font-size: calc(11px * var(--ui-font-scale, 1));
  color: var(--dim);
  pointer-events: none;
}

/* And a shadow under whichever of the two is on top, so what scrolls away goes
 * UNDER the foot rather than stopping at it.
 *
 * Cast upward, short and soft: eighteen pixels of it at a fifth of black, with
 * the spread pulled in so it is a darkening against the foot and not a band
 * across the list. The panel clips, so there is nothing to see below.
 *
 * Two selectors because which one is on top depends on whether there is more
 * than one page. The numbers carry it when they are there; the shortcuts carry
 * it when they are not, and `+` still finds them past a hidden sibling. */
/* The shortcuts are the only thing pinned across the foot now, so they are the
   only thing that casts a shadow over what scrolls under them. The numbers used
   to share this, and used to need it; they travel with the list instead. */
.hint {
  box-shadow: 0 -10px 18px -10px rgba(0, 0, 0, 0.55);
}

.hint span { display: inline-flex; align-items: center; gap: 6px; }

/* Somebody who asked for no animation gets none, and still gets the panel. */
@media (prefers-reduced-motion: reduce) {
  .scrim, .panel, .results, .hint, .bar-count, .row, .row-wave {
    transition-duration: 1ms !important;
  }
}
